diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py
index a81a15174e..1890714b04 100644
--- a/src/blenderbim/blenderbim/bim/module/model/profile.py
+++ b/src/blenderbim/blenderbim/bim/module/model/profile.py
@@ -17,13 +17,13 @@
# along with BlenderBIM Add-on. If not, see .
import bpy
-import bmesh
import math
+import bmesh
+import mathutils.geometry
import ifcopenshell
import ifcopenshell.util.type
import ifcopenshell.util.unit
import ifcopenshell.util.element
-import mathutils.geometry
import blenderbim.bim.handler
import blenderbim.tool as tool
import blenderbim.core.type
@@ -100,9 +100,7 @@ def ensure_solid(usecase_path, ifc_file, settings):
material = ifcopenshell.util.element.get_material(product)
if material and material.is_a("IfcMaterialProfileSetUsage"):
settings["profile_set_usage"] = material
- else:
- return
- settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
+ settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
class DumbProfileGenerator:
diff --git a/src/blenderbim/blenderbim/bim/module/type/operator.py b/src/blenderbim/blenderbim/bim/module/type/operator.py
index a2986d248a..68acc39b05 100644
--- a/src/blenderbim/blenderbim/bim/module/type/operator.py
+++ b/src/blenderbim/blenderbim/bim/module/type/operator.py
@@ -51,7 +51,7 @@ class AssignType(bpy.types.Operator, Operator):
else context.selected_objects or [context.active_object]
)
for obj in related_objects:
- core.assign_type(tool.Ifc, tool.Geometry, tool.Type, element=tool.Ifc.get_entity(obj), type=type)
+ core.assign_type(tool.Ifc, tool.Type, element=tool.Ifc.get_entity(obj), type=type)
oprops = obj.BIMObjectProperties
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
GeometryData.load(IfcStore.get_file(), oprops.ifc_definition_id)
diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py
index 87a61d8544..5414041ffe 100644
--- a/src/blenderbim/blenderbim/core/geometry.py
+++ b/src/blenderbim/blenderbim/core/geometry.py
@@ -74,12 +74,7 @@ def add_representation(
def switch_representation(
- geometry,
- obj=None,
- representation=None,
- should_reload=True,
- enable_dynamic_voids=True,
- is_global=True,
+ geometry, obj=None, representation=None, should_reload=True, enable_dynamic_voids=True, is_global=True
):
representation = geometry.resolve_mapped_representation(representation)
data = geometry.get_representation_data(representation)
diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py
index 6a70b536ad..9c3b94f216 100644
--- a/src/blenderbim/blenderbim/core/tool.py
+++ b/src/blenderbim/blenderbim/core/tool.py
@@ -197,8 +197,15 @@ class Surveyor:
class Type:
def disable_editing(cls, obj): pass
def get_any_representation(cls, element): pass
+ def get_body_context(cls): pass
def get_body_representation(cls, element): pass
+ def get_ifc_representation_class(cls, element): pass
+ def get_profile_set_usage(cls, element): pass
+ def get_representation_context(cls, representation): pass
def has_dynamic_voids(cls, obj): pass
+ def has_material_usage(cls, element): pass
+ def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass
+ def run_geometry_switch_representation(cls, obj=None, representation=None, should_reload=None, enable_dynamic_voids=None, is_global=None): pass
@interface
diff --git a/src/blenderbim/blenderbim/core/type.py b/src/blenderbim/blenderbim/core/type.py
index 7d88898d87..5076ee5e93 100644
--- a/src/blenderbim/blenderbim/core/type.py
+++ b/src/blenderbim/blenderbim/core/type.py
@@ -19,18 +19,34 @@
import blenderbim.core.geometry
-def assign_type(ifc, geometry, type_tool, element=None, type=None):
+def assign_type(ifc, type_tool, element=None, type=None):
ifc.run("type.assign_type", related_object=element, relating_type=type)
- representation = type_tool.get_body_representation(element)
- if not representation:
- representation = type_tool.get_any_representation(element)
obj = ifc.get_object(element)
+ if type_tool.has_material_usage(element):
+ representation = type_tool.get_body_representation(element)
+ if representation:
+ body_context = type_tool.get_representation_context(representation)
+ ifc.run("geometry.unassign_representation", product=element, representation=representation)
+ ifc.run("geometry.remove_representation", representation=representation)
+ else:
+ body_context = type_tool.get_body_context()
+ representation = type_tool.run_geometry_add_representation(
+ obj=obj,
+ context=body_context,
+ ifc_representation_class=type_tool.get_ifc_representation_class(element),
+ profile_set_usage=type_tool.get_profile_set_usage(element),
+ )
+ should_reload = True
+ else:
+ representation = type_tool.get_body_representation(element)
+ if not representation:
+ representation = type_tool.get_any_representation(element)
+ should_reload = False
if representation:
- blenderbim.core.geometry.switch_representation(
- geometry,
+ type_tool.run_geometry_switch_representation(
obj=obj,
representation=representation,
- should_reload=False,
+ should_reload=should_reload,
enable_dynamic_voids=type_tool.has_dynamic_voids(obj),
is_global=False,
)
diff --git a/src/blenderbim/blenderbim/tool/type.py b/src/blenderbim/blenderbim/tool/type.py
index d2bddd3486..a4885d359c 100644
--- a/src/blenderbim/blenderbim/tool/type.py
+++ b/src/blenderbim/blenderbim/tool/type.py
@@ -18,6 +18,7 @@
import ifcopenshell
import blenderbim.core.tool
+import blenderbim.core.geometry
import blenderbim.tool as tool
import blenderbim.bim.helper
@@ -34,6 +35,10 @@ class Type(blenderbim.core.tool.Type):
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
return element.RepresentationMaps[0].MappedRepresentation
+ @classmethod
+ def get_body_context(cls):
+ return ifcopenshell.util.representation.get_context(tool.Ifc.get(), "Model", "Body", "MODEL_VIEW")
+
@classmethod
def get_body_representation(cls, element):
if element.is_a("IfcProduct") and element.Representation and element.Representation.Representations:
@@ -45,9 +50,64 @@ class Type(blenderbim.core.tool.Type):
if representation_map.MappedRepresentation.ContextOfItems.ContextIdentifier == "Body":
return representation_map.MappedRepresentation
+ @classmethod
+ def get_ifc_representation_class(cls, element):
+ material = ifcopenshell.util.element.get_material(element)
+ if material:
+ if material.is_a("IfcMaterialProfileSetUsage"):
+ return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
+ elif material.is_a("IfcMaterialLayerSetUsage"):
+ return "IfcExtrudedAreaSolid/IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
+
+ @classmethod
+ def get_profile_set_usage(cls, element):
+ material = ifcopenshell.util.element.get_material(element)
+ if material:
+ if material.is_a("IfcMaterialProfileSetUsage"):
+ return material
+
+ @classmethod
+ def get_representation_context(cls, representation):
+ return representation.ContextOfItems
+
@classmethod
def has_dynamic_voids(cls, obj):
for modifier in obj.modifiers:
if modifier.name == "IfcOpeningElement" and modifier.type == "BOOLEAN":
return True
return False
+
+ @classmethod
+ def has_material_usage(cls, element):
+ material = ifcopenshell.util.element.get_material(element)
+ if material:
+ return "Usage" in material.is_a()
+ return False
+
+ @classmethod
+ def run_geometry_add_representation(
+ cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None
+ ):
+ return blenderbim.core.geometry.add_representation(
+ tool.Ifc,
+ tool.Geometry,
+ tool.Style,
+ tool.Surveyor,
+ obj=obj,
+ context=context,
+ ifc_representation_class=ifc_representation_class,
+ profile_set_usage=profile_set_usage,
+ )
+
+ @classmethod
+ def run_geometry_switch_representation(
+ cls, obj=None, representation=None, should_reload=None, enable_dynamic_voids=None, is_global=None
+ ):
+ return blenderbim.core.geometry.switch_representation(
+ tool.Geometry,
+ obj=obj,
+ representation=representation,
+ should_reload=should_reload,
+ enable_dynamic_voids=enable_dynamic_voids,
+ is_global=is_global,
+ )
diff --git a/src/blenderbim/test/core/test_type.py b/src/blenderbim/test/core/test_type.py
index 79bce1b781..d117fb67fd 100644
--- a/src/blenderbim/test/core/test_type.py
+++ b/src/blenderbim/test/core/test_type.py
@@ -17,45 +17,84 @@
# along with BlenderBIM Add-on. If not, see .
import blenderbim.core.type as subject
-from test.core.bootstrap import ifc, geometry, type
+from test.core.bootstrap import ifc, type
class TestAssignType:
- def test_assigning_and_switching_preferably_to_a_body_representation(self, ifc, geometry, type):
+ def test_assigning_and_switching_preferably_to_a_body_representation(self, ifc, type):
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
+ type.has_material_usage("element").should_be_called().will_return(False)
type.get_body_representation("element").should_be_called().will_return("mapped_rep")
ifc.get_object("element").should_be_called().will_return("obj")
type.has_dynamic_voids("obj").should_be_called().will_return(False)
-
- geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
- geometry.get_representation_data("representation").should_be_called().will_return("data")
- geometry.change_object_data("obj", "data", is_global=False).should_be_called()
- geometry.clear_dynamic_voids("obj").should_be_called()
-
+ type.run_geometry_switch_representation(
+ obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
+ ).should_be_called()
type.disable_editing("obj").should_be_called()
+ subject.assign_type(ifc, type, element="element", type="type")
- subject.assign_type(ifc, geometry, type, element="element", type="type")
-
- def test_assigning_and_switching_to_any_representation_as_a_fallback(self, ifc, geometry, type):
+ def test_assigning_and_switching_to_any_representation_as_a_fallback(self, ifc, type):
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
+ type.has_material_usage("element").should_be_called().will_return(False)
type.get_body_representation("element").should_be_called().will_return(None)
type.get_any_representation("element").should_be_called().will_return("mapped_rep")
ifc.get_object("element").should_be_called().will_return("obj")
type.has_dynamic_voids("obj").should_be_called().will_return(False)
-
- geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation")
- geometry.get_representation_data("representation").should_be_called().will_return("data")
- geometry.change_object_data("obj", "data", is_global=False).should_be_called()
- geometry.clear_dynamic_voids("obj").should_be_called()
-
+ type.run_geometry_switch_representation(
+ obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
+ ).should_be_called()
type.disable_editing("obj").should_be_called()
+ subject.assign_type(ifc, type, element="element", type="type")
- subject.assign_type(ifc, geometry, type, element="element", type="type")
-
- def test_assigning_and_not_changing_representation_if_not_available(self, ifc, type):
+ def test_assigning_and_not_changing_representation_if_there_is_no_representation_to_change_to(self, ifc, type):
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
+ type.has_material_usage("element").should_be_called().will_return(False)
type.get_body_representation("element").should_be_called().will_return(None)
type.get_any_representation("element").should_be_called().will_return(None)
ifc.get_object("element").should_be_called().will_return("obj")
type.disable_editing("obj").should_be_called()
- subject.assign_type(ifc, geometry, type, element="element", type="type")
+ subject.assign_type(ifc, type, element="element", type="type")
+
+ def test_updating_an_existing_body_if_there_is_a_parametric_material_usage(self, ifc, type):
+ ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
+
+ type.has_material_usage("element").should_be_called().will_return(True)
+ type.get_body_representation("element").should_be_called().will_return("representation")
+ type.get_representation_context("representation").should_be_called().will_return("context")
+ ifc.run(
+ "geometry.unassign_representation", product="element", representation="representation"
+ ).should_be_called()
+ ifc.run("geometry.remove_representation", representation="representation").should_be_called()
+ type.get_ifc_representation_class("element").should_be_called().will_return("class")
+ type.get_profile_set_usage("element").should_be_called().will_return("usage")
+ type.run_geometry_add_representation(
+ obj="obj", context="context", ifc_representation_class="class", profile_set_usage="usage"
+ ).should_be_called().will_return("mapped_rep")
+
+ ifc.get_object("element").should_be_called().will_return("obj")
+ type.has_dynamic_voids("obj").should_be_called().will_return(False)
+ type.run_geometry_switch_representation(
+ obj="obj", representation="mapped_rep", should_reload=True, enable_dynamic_voids=False, is_global=False
+ ).should_be_called()
+ type.disable_editing("obj").should_be_called()
+ subject.assign_type(ifc, type, element="element", type="type")
+
+ def test_creating_a_new_body_if_there_is_a_parametric_material_usage(self, ifc, type):
+ ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called()
+
+ type.has_material_usage("element").should_be_called().will_return(True)
+ type.get_body_representation("element").should_be_called().will_return(None)
+ type.get_body_context().should_be_called().will_return("context")
+ type.get_ifc_representation_class("element").should_be_called().will_return("class")
+ type.get_profile_set_usage("element").should_be_called().will_return("usage")
+ type.run_geometry_add_representation(
+ obj="obj", context="context", ifc_representation_class="class", profile_set_usage="usage"
+ ).should_be_called().will_return("mapped_rep")
+
+ ifc.get_object("element").should_be_called().will_return("obj")
+ type.has_dynamic_voids("obj").should_be_called().will_return(False)
+ type.run_geometry_switch_representation(
+ obj="obj", representation="mapped_rep", should_reload=True, enable_dynamic_voids=False, is_global=False
+ ).should_be_called()
+ type.disable_editing("obj").should_be_called()
+ subject.assign_type(ifc, type, element="element", type="type")
diff --git a/src/blenderbim/test/tool/test_type.py b/src/blenderbim/test/tool/test_type.py
index 7aeffd4f8c..87c7f27f71 100644
--- a/src/blenderbim/test/tool/test_type.py
+++ b/src/blenderbim/test/tool/test_type.py
@@ -53,6 +53,16 @@ class TestGetAnyRepresentation(NewFile):
assert subject.get_any_representation(element) == representation
+class TestGetBodyContext(NewFile):
+ def test_run(self):
+ ifc = ifcopenshell.file()
+ context = ifc.createIfcGeometricRepresentationSubContext(
+ ContextType="Model", ContextIdentifier="Body", TargetView="MODEL_VIEW"
+ )
+ tool.Ifc.set(ifc)
+ assert subject.get_body_context() == context
+
+
class TestGetBodyRepresentation(NewFile):
def test_get_product_representation(self):
ifc = ifcopenshell.file()
@@ -78,6 +88,49 @@ class TestGetBodyRepresentation(NewFile):
assert subject.get_body_representation(element) == body_rep
+class TestGetIfcRepresentationClass(NewFile):
+ def test_detecting_profile_set_representations(self):
+ ifc = ifcopenshell.file()
+ element = ifc.createIfcColumn()
+ ifc.createIfcRelAssociatesMaterial(
+ RelatingMaterial=ifc.createIfcMaterialProfileSetUsage(), RelatedObjects=[element]
+ )
+ assert subject.get_ifc_representation_class(element) == "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
+
+ def test_detecting_layer_set_representations(self):
+ ifc = ifcopenshell.file()
+ element = ifc.createIfcColumn()
+ ifc.createIfcRelAssociatesMaterial(
+ RelatingMaterial=ifc.createIfcMaterialLayerSetUsage(), RelatedObjects=[element]
+ )
+ assert (
+ subject.get_ifc_representation_class(element)
+ == "IfcExtrudedAreaSolid/IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids"
+ )
+
+ def test_returning_null_for_non_parametric_represntations(self):
+ ifc = ifcopenshell.file()
+ assert subject.get_ifc_representation_class(ifc.createIfcColumn()) is None
+
+
+class TestGetProfileSetUsage(NewFile):
+ def test_getting_a_profile_set_usage(self):
+ ifc = ifcopenshell.file()
+ element = ifc.createIfcColumn()
+ assert subject.get_profile_set_usage(element) is None
+ usage = ifc.createIfcMaterialProfileSetUsage()
+ ifc.createIfcRelAssociatesMaterial(RelatingMaterial=usage, RelatedObjects=[element])
+ assert subject.get_profile_set_usage(element) == usage
+
+
+class TestGetRepresentationContext(NewFile):
+ def test_getting_a_profile_set_usage(self):
+ ifc = ifcopenshell.file()
+ context = ifc.createIfcGeometricRepresentationSubContext()
+ representation = ifc.createIfcShapeRepresentation(ContextOfItems=context)
+ assert subject.get_representation_context(representation) == context
+
+
class TestHasDynamicVoids(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
@@ -87,3 +140,23 @@ class TestHasDynamicVoids(NewFile):
obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
assert subject.has_dynamic_voids(obj) is True
+
+
+class TestHasMaterialUsage(NewFile):
+ def test_getting_a_profile_set_usage(self):
+ ifc = ifcopenshell.file()
+ element = ifc.createIfcColumn()
+ assert subject.has_material_usage(element) is False
+ usage = ifc.createIfcMaterialProfileSetUsage()
+ ifc.createIfcRelAssociatesMaterial(RelatingMaterial=usage, RelatedObjects=[element])
+ assert subject.has_material_usage(element) is True
+
+
+class TestRunGeometryAddRepresentation(NewFile):
+ def test_nothing(self):
+ pass
+
+
+class TestRunGeometrySwitchRepresentation(NewFile):
+ def test_nothing(self):
+ pass
diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py
index 1288096963..60517f3312 100644
--- a/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py
+++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/remove_representation.py
@@ -22,7 +22,8 @@ class Usecase:
ifcopenshell.util.element.remove_deep2(
self.file,
self.settings["representation"],
- extra_subgraph_elements=list(styled_items | presentation_layer_assignments),
+ also_consider=list(styled_items | presentation_layer_assignments),
+ do_not_delete=self.file.by_type("IfcGeometricRepresentationContext"),
)
for element in styled_items:
diff --git a/src/ifcopenshell-python/ifcopenshell/util/element.py b/src/ifcopenshell-python/ifcopenshell/util/element.py
index 6c3eac9b15..9d496d8464 100644
--- a/src/ifcopenshell-python/ifcopenshell/util/element.py
+++ b/src/ifcopenshell-python/ifcopenshell/util/element.py
@@ -136,17 +136,21 @@ def remove_deep(ifc_file, element):
ifc_file.unbatch()
-def remove_deep2(ifc_file, element, extra_subgraph_elements=[]):
+def remove_deep2(ifc_file, element, also_consider=[], do_not_delete=[]):
# Experimental remove deep proposal. No batch for now until this is more certain. See #1812.
# ifc_file.batch()
to_delete = set()
subgraph = list(ifc_file.traverse(element, breadth_first=True))
- subgraph.extend(extra_subgraph_elements)
+ subgraph.extend(also_consider)
subgraph_set = set(subgraph)
subelement_queue = ifc_file.traverse(element, max_levels=1)
while subelement_queue:
subelement = subelement_queue.pop(0)
- if subelement.id() and len(set(ifc_file.get_inverse(subelement)) - subgraph_set) == 0:
+ if (
+ subelement.id()
+ and len(set(ifc_file.get_inverse(subelement)) - subgraph_set) == 0
+ and subelement not in do_not_delete
+ ):
to_delete.add(subelement)
subelement_queue.extend(ifc_file.traverse(subelement, max_levels=1)[1:])
for subelement in to_delete:
diff --git a/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py b/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py
index da889be3db..8f5ddecdf7 100644
--- a/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py
+++ b/src/ifcopenshell-python/test/api/geometry/test_remove_representation.py
@@ -77,3 +77,9 @@ class TestRemoveRepresentation(test.bootstrap.IFC4):
ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation)
assert len(self.file.by_type("IfcPresentationLayerAssignment")) == 1
assert len(self.file.by_type("IfcShapeRepresentation")) == 1
+
+ def test_not_purging_geometric_representation_contexts(self):
+ context = self.file.createIfcGeometricRepresentationSubContext()
+ representation = self.file.createIfcShapeRepresentation(ContextOfItems=context)
+ ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation)
+ assert len(self.file.by_type("IfcGeometricRepresentationContext")) == 1