Skip to content

Commit c437ba8

Browse files
committed
Assigning a parametric profile type now auto switches from non parametric geometry to parametric geometry
1 parent aee7fbc commit c437ba8

11 files changed

Lines changed: 242 additions & 43 deletions

File tree

src/blenderbim/blenderbim/bim/module/model/profile.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
1818

1919
import bpy
20-
import bmesh
2120
import math
21+
import bmesh
22+
import mathutils.geometry
2223
import ifcopenshell
2324
import ifcopenshell.util.type
2425
import ifcopenshell.util.unit
2526
import ifcopenshell.util.element
26-
import mathutils.geometry
2727
import blenderbim.bim.handler
2828
import blenderbim.tool as tool
2929
import blenderbim.core.type
@@ -100,9 +100,7 @@ def ensure_solid(usecase_path, ifc_file, settings):
100100
material = ifcopenshell.util.element.get_material(product)
101101
if material and material.is_a("IfcMaterialProfileSetUsage"):
102102
settings["profile_set_usage"] = material
103-
else:
104-
return
105-
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
103+
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
106104

107105

108106
class DumbProfileGenerator:

src/blenderbim/blenderbim/bim/module/type/operator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def _execute(self, context):
5151
else context.selected_objects or [context.active_object]
5252
)
5353
for obj in related_objects:
54-
core.assign_type(tool.Ifc, tool.Geometry, tool.Type, element=tool.Ifc.get_entity(obj), type=type)
54+
core.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=type)
5555
oprops = obj.BIMObjectProperties
5656
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
5757
GeometryData.load(IfcStore.get_file(), oprops.ifc_definition_id)

src/blenderbim/blenderbim/core/geometry.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,7 @@ def add_representation(
7474

7575

7676
def switch_representation(
77-
geometry,
78-
obj=None,
79-
representation=None,
80-
should_reload=True,
81-
enable_dynamic_voids=True,
82-
is_global=True,
77+
geometry, obj=None, representation=None, should_reload=True, enable_dynamic_voids=True, is_global=True
8378
):
8479
representation = geometry.resolve_mapped_representation(representation)
8580
data = geometry.get_representation_data(representation)

src/blenderbim/blenderbim/core/tool.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,15 @@ def get_absolute_matrix(cls, obj): pass
197197
class Type:
198198
def disable_editing(cls, obj): pass
199199
def get_any_representation(cls, element): pass
200+
def get_body_context(cls): pass
200201
def get_body_representation(cls, element): pass
202+
def get_ifc_representation_class(cls, element): pass
203+
def get_profile_set_usage(cls, element): pass
204+
def get_representation_context(cls, representation): pass
201205
def has_dynamic_voids(cls, obj): pass
206+
def has_material_usage(cls, element): pass
207+
def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass
208+
def run_geometry_switch_representation(cls, obj=None, representation=None, should_reload=None, enable_dynamic_voids=None, is_global=None): pass
202209

203210

204211
@interface

src/blenderbim/blenderbim/core/type.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,34 @@
1919
import blenderbim.core.geometry
2020

2121

22-
def assign_type(ifc, geometry, type_tool, element=None, type=None):
22+
def assign_type(ifc, type_tool, element=None, type=None):
2323
ifc.run("type.assign_type", related_object=element, relating_type=type)
24-
representation = type_tool.get_body_representation(element)
25-
if not representation:
26-
representation = type_tool.get_any_representation(element)
2724
obj = ifc.get_object(element)
25+
if type_tool.has_material_usage(element):
26+
representation = type_tool.get_body_representation(element)
27+
if representation:
28+
body_context = type_tool.get_representation_context(representation)
29+
ifc.run("geometry.unassign_representation", product=element, representation=representation)
30+
ifc.run("geometry.remove_representation", representation=representation)
31+
else:
32+
body_context = type_tool.get_body_context()
33+
representation = type_tool.run_geometry_add_representation(
34+
obj=obj,
35+
context=body_context,
36+
ifc_representation_class=type_tool.get_ifc_representation_class(element),
37+
profile_set_usage=type_tool.get_profile_set_usage(element),
38+
)
39+
should_reload = True
40+
else:
41+
representation = type_tool.get_body_representation(element)
42+
if not representation:
43+
representation = type_tool.get_any_representation(element)
44+
should_reload = False
2845
if representation:
29-
blenderbim.core.geometry.switch_representation(
30-
geometry,
46+
type_tool.run_geometry_switch_representation(
3147
obj=obj,
3248
representation=representation,
33-
should_reload=False,
49+
should_reload=should_reload,
3450
enable_dynamic_voids=type_tool.has_dynamic_voids(obj),
3551
is_global=False,
3652
)

src/blenderbim/blenderbim/tool/type.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import ifcopenshell
2020
import blenderbim.core.tool
21+
import blenderbim.core.geometry
2122
import blenderbim.tool as tool
2223
import blenderbim.bim.helper
2324

@@ -34,6 +35,10 @@ def get_any_representation(cls, element):
3435
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
3536
return element.RepresentationMaps[0].MappedRepresentation
3637

38+
@classmethod
39+
def get_body_context(cls):
40+
return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
41+
3742
@classmethod
3843
def get_body_representation(cls, element):
3944
if element.is_a("IfcProduct") and element.Representation and element.Representation.Representations:
@@ -45,9 +50,64 @@ def get_body_representation(cls, element):
4550
if representation_map.MappedRepresentation.ContextOfItems.ContextIdentifier == "Body":
4651
return representation_map.MappedRepresentation
4752

53+
@classmethod
54+
def get_ifc_representation_class(cls, element):
55+
material = ifcopenshell.util.element.get_material(element)
56+
if material:
57+
if material.is_a("IfcMaterialProfileSetUsage"):
58+
return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
59+
elif material.is_a("IfcMaterialLayerSetUsage"):
60+
return "IfcExtrudedAreaSolid/IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
61+
62+
@classmethod
63+
def get_profile_set_usage(cls, element):
64+
material = ifcopenshell.util.element.get_material(element)
65+
if material:
66+
if material.is_a("IfcMaterialProfileSetUsage"):
67+
return material
68+
69+
@classmethod
70+
def get_representation_context(cls, representation):
71+
return representation.ContextOfItems
72+
4873
@classmethod
4974
def has_dynamic_voids(cls, obj):
5075
for modifier in obj.modifiers:
5176
if modifier.name == "IfcOpeningElement" and modifier.type == "BOOLEAN":
5277
return True
5378
return False
79+
80+
@classmethod
81+
def has_material_usage(cls, element):
82+
material = ifcopenshell.util.element.get_material(element)
83+
if material:
84+
return "Usage" in material.is_a()
85+
return False
86+
87+
@classmethod
88+
def run_geometry_add_representation(
89+
cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
90+
):
91+
return blenderbim.core.geometry.add_representation(
92+
tool.Ifc,
93+
tool.Geometry,
94+
tool.Style,
95+
tool.Surveyor,
96+
obj=obj,
97+
context=context,
98+
ifc_representation_class=ifc_representation_class,
99+
profile_set_usage=profile_set_usage,
100+
)
101+
102+
@classmethod
103+
def run_geometry_switch_representation(
104+
cls, obj=None, representation=None, should_reload=None, enable_dynamic_voids=None, is_global=None
105+
):
106+
return blenderbim.core.geometry.switch_representation(
107+
tool.Geometry,
108+
obj=obj,
109+
representation=representation,
110+
should_reload=should_reload,
111+
enable_dynamic_voids=enable_dynamic_voids,
112+
is_global=is_global,
113+
)

src/blenderbim/test/core/test_type.py

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,45 +17,84 @@
1717
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
1818

1919
import blenderbim.core.type as subject
20-
from test.core.bootstrap import ifc, geometry, type
20+
from test.core.bootstrap import ifc, type
2121

2222

2323
class TestAssignType:
24-
def test_assigning_and_switching_preferably_to_a_body_representation(self, ifc, geometry, type):
24+
def test_assigning_and_switching_preferably_to_a_body_representation(self, ifc, type):
2525
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
26+
type.has_material_usage("element").should_be_called().will_return(False)
2627
type.get_body_representation("element").should_be_called().will_return("mapped_rep")
2728
ifc.get_object("element").should_be_called().will_return("obj")
2829
type.has_dynamic_voids("obj").should_be_called().will_return(False)
29-
30-
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
31-
geometry.get_representation_data("representation").should_be_called().will_return("data")
32-
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
33-
geometry.clear_dynamic_voids("obj").should_be_called()
34-
30+
type.run_geometry_switch_representation(
31+
obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
32+
).should_be_called()
3533
type.disable_editing("obj").should_be_called()
34+
subject.assign_type(ifc, type, element="element", type="type")
3635

37-
subject.assign_type(ifc, geometry, type, element="element", type="type")
38-
39-
def test_assigning_and_switching_to_any_representation_as_a_fallback(self, ifc, geometry, type):
36+
def test_assigning_and_switching_to_any_representation_as_a_fallback(self, ifc, type):
4037
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
38+
type.has_material_usage("element").should_be_called().will_return(False)
4139
type.get_body_representation("element").should_be_called().will_return(None)
4240
type.get_any_representation("element").should_be_called().will_return("mapped_rep")
4341
ifc.get_object("element").should_be_called().will_return("obj")
4442
type.has_dynamic_voids("obj").should_be_called().will_return(False)
43+
type.run_geometry_switch_representation(
44+
obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
45+
).should_be_called()
46+
type.disable_editing("obj").should_be_called()
47+
subject.assign_type(ifc, type, element="element", type="type")
4548

46-
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
47-
geometry.get_representation_data("representation").should_be_called().will_return("data")
48-
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
49-
geometry.clear_dynamic_voids("obj").should_be_called()
50-
49+
def test_assigning_and_not_changing_representation_if_there_is_no_representation_to_change_to(self, ifc, type):
50+
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
51+
type.has_material_usage("element").should_be_called().will_return(False)
52+
type.get_body_representation("element").should_be_called().will_return(None)
53+
type.get_any_representation("element").should_be_called().will_return(None)
54+
ifc.get_object("element").should_be_called().will_return("obj")
5155
type.disable_editing("obj").should_be_called()
56+
subject.assign_type(ifc, type, element="element", type="type")
5257

53-
subject.assign_type(ifc, geometry, type, element="element", type="type")
58+
def test_updating_an_existing_body_if_there_is_a_parametric_material_usage(self, ifc, type):
59+
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
5460

55-
def test_assigning_and_not_changing_representation_if_not_available(self, ifc, type):
61+
type.has_material_usage("element").should_be_called().will_return(True)
62+
type.get_body_representation("element").should_be_called().will_return("representation")
63+
type.get_representation_context("representation").should_be_called().will_return("context")
64+
ifc.run(
65+
"geometry.unassign_representation", product="element", representation="representation"
66+
).should_be_called()
67+
ifc.run("geometry.remove_representation", representation="representation").should_be_called()
68+
type.get_ifc_representation_class("element").should_be_called().will_return("class")
69+
type.get_profile_set_usage("element").should_be_called().will_return("usage")
70+
type.run_geometry_add_representation(
71+
obj="obj", context="context", ifc_representation_class="class", profile_set_usage="usage"
72+
).should_be_called().will_return("mapped_rep")
73+
74+
ifc.get_object("element").should_be_called().will_return("obj")
75+
type.has_dynamic_voids("obj").should_be_called().will_return(False)
76+
type.run_geometry_switch_representation(
77+
obj="obj", representation="mapped_rep", should_reload=True, enable_dynamic_voids=False, is_global=False
78+
).should_be_called()
79+
type.disable_editing("obj").should_be_called()
80+
subject.assign_type(ifc, type, element="element", type="type")
81+
82+
def test_creating_a_new_body_if_there_is_a_parametric_material_usage(self, ifc, type):
5683
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
84+
85+
type.has_material_usage("element").should_be_called().will_return(True)
5786
type.get_body_representation("element").should_be_called().will_return(None)
58-
type.get_any_representation("element").should_be_called().will_return(None)
87+
type.get_body_context().should_be_called().will_return("context")
88+
type.get_ifc_representation_class("element").should_be_called().will_return("class")
89+
type.get_profile_set_usage("element").should_be_called().will_return("usage")
90+
type.run_geometry_add_representation(
91+
obj="obj", context="context", ifc_representation_class="class", profile_set_usage="usage"
92+
).should_be_called().will_return("mapped_rep")
93+
5994
ifc.get_object("element").should_be_called().will_return("obj")
95+
type.has_dynamic_voids("obj").should_be_called().will_return(False)
96+
type.run_geometry_switch_representation(
97+
obj="obj", representation="mapped_rep", should_reload=True, enable_dynamic_voids=False, is_global=False
98+
).should_be_called()
6099
type.disable_editing("obj").should_be_called()
61-
subject.assign_type(ifc, geometry, type, element="element", type="type")
100+
subject.assign_type(ifc, type, element="element", type="type")

src/blenderbim/test/tool/test_type.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ def test_get_type_product_representation(self):
5353
assert subject.get_any_representation(element) == representation
5454

5555

56+
class TestGetBodyContext(NewFile):
57+
def test_run(self):
58+
ifc = ifcopenshell.file()
59+
context = ifc.createIfcGeometricRepresentationSubContext(
60+
ContextType="Model", ContextIdentifier="Body", TargetView="MODEL_VIEW"
61+
)
62+
tool.Ifc.set(ifc)
63+
assert subject.get_body_context() == context
64+
65+
5666
class TestGetBodyRepresentation(NewFile):
5767
def test_get_product_representation(self):
5868
ifc = ifcopenshell.file()
@@ -78,6 +88,49 @@ def test_get_type_product_representation(self):
7888
assert subject.get_body_representation(element) == body_rep
7989

8090

91+
class TestGetIfcRepresentationClass(NewFile):
92+
def test_detecting_profile_set_representations(self):
93+
ifc = ifcopenshell.file()
94+
element = ifc.createIfcColumn()
95+
ifc.createIfcRelAssociatesMaterial(
96+
RelatingMaterial=ifc.createIfcMaterialProfileSetUsage(), RelatedObjects=[element]
97+
)
98+
assert subject.get_ifc_representation_class(element) == "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
99+
100+
def test_detecting_layer_set_representations(self):
101+
ifc = ifcopenshell.file()
102+
element = ifc.createIfcColumn()
103+
ifc.createIfcRelAssociatesMaterial(
104+
RelatingMaterial=ifc.createIfcMaterialLayerSetUsage(), RelatedObjects=[element]
105+
)
106+
assert (
107+
subject.get_ifc_representation_class(element)
108+
== "IfcExtrudedAreaSolid/IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
109+
)
110+
111+
def test_returning_null_for_non_parametric_represntations(self):
112+
ifc = ifcopenshell.file()
113+
assert subject.get_ifc_representation_class(ifc.createIfcColumn()) is None
114+
115+
116+
class TestGetProfileSetUsage(NewFile):
117+
def test_getting_a_profile_set_usage(self):
118+
ifc = ifcopenshell.file()
119+
element = ifc.createIfcColumn()
120+
assert subject.get_profile_set_usage(element) is None
121+
usage = ifc.createIfcMaterialProfileSetUsage()
122+
ifc.createIfcRelAssociatesMaterial(RelatingMaterial=usage, RelatedObjects=[element])
123+
assert subject.get_profile_set_usage(element) == usage
124+
125+
126+
class TestGetRepresentationContext(NewFile):
127+
def test_getting_a_profile_set_usage(self):
128+
ifc = ifcopenshell.file()
129+
context = ifc.createIfcGeometricRepresentationSubContext()
130+
representation = ifc.createIfcShapeRepresentation(ContextOfItems=context)
131+
assert subject.get_representation_context(representation) == context
132+
133+
81134
class TestHasDynamicVoids(NewFile):
82135
def test_run(self):
83136
obj = bpy.data.objects.new("Object", None)
@@ -87,3 +140,23 @@ def test_checking_modifiers(self):
87140
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
88141
obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
89142
assert subject.has_dynamic_voids(obj) is True
143+
144+
145+
class TestHasMaterialUsage(NewFile):
146+
def test_getting_a_profile_set_usage(self):
147+
ifc = ifcopenshell.file()
148+
element = ifc.createIfcColumn()
149+
assert subject.has_material_usage(element) is False
150+
usage = ifc.createIfcMaterialProfileSetUsage()
151+
ifc.createIfcRelAssociatesMaterial(RelatingMaterial=usage, RelatedObjects=[element])
152+
assert subject.has_material_usage(element) is True
153+
154+
155+
class TestRunGeometryAddRepresentation(NewFile):
156+
def test_nothing(self):
157+
pass
158+
159+
160+
class TestRunGeometrySwitchRepresentation(NewFile):
161+
def test_nothing(self):
162+
pass

src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def execute(self):
2222
ifcopenshell.util.element.remove_deep2(
2323
self.file,
2424
self.settings["representation"],
25-
extra_subgraph_elements=list(styled_items | presentation_layer_assignments),
25+
also_consider=list(styled_items | presentation_layer_assignments),
26+
do_not_delete=self.file.by_type("IfcGeometricRepresentationContext"),
2627
)
2728

2829
for element in styled_items:

0 commit comments

Comments
 (0)