Assigning a parametric profile type now auto switches from non parametric geometry to parametric geometry

This commit is contained in:
Dion Moult
2021-10-21 16:28:55 +11:00
parent aee7fbcc79
commit c437ba8f9d
11 changed files with 243 additions and 44 deletions
@@ -17,13 +17,13 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
import bmesh
import math import math
import bmesh
import mathutils.geometry
import ifcopenshell import ifcopenshell
import ifcopenshell.util.type import ifcopenshell.util.type
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.util.element import ifcopenshell.util.element
import mathutils.geometry
import blenderbim.bim.handler import blenderbim.bim.handler
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.core.type import blenderbim.core.type
@@ -100,9 +100,7 @@ def ensure_solid(usecase_path, ifc_file, settings):
material = ifcopenshell.util.element.get_material(product) material = ifcopenshell.util.element.get_material(product)
if material and material.is_a("IfcMaterialProfileSetUsage"): if material and material.is_a("IfcMaterialProfileSetUsage"):
settings["profile_set_usage"] = material settings["profile_set_usage"] = material
else: settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
return
settings["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage"
class DumbProfileGenerator: class DumbProfileGenerator:
@@ -51,7 +51,7 @@ class AssignType(bpy.types.Operator, Operator):
else context.selected_objects or [context.active_object] else context.selected_objects or [context.active_object]
) )
for obj in related_objects: 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 oprops = obj.BIMObjectProperties
Data.load(IfcStore.get_file(), oprops.ifc_definition_id) Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
GeometryData.load(IfcStore.get_file(), oprops.ifc_definition_id) GeometryData.load(IfcStore.get_file(), oprops.ifc_definition_id)
+1 -6
View File
@@ -74,12 +74,7 @@ def add_representation(
def switch_representation( def switch_representation(
geometry, geometry, obj=None, representation=None, should_reload=True, enable_dynamic_voids=True, is_global=True
obj=None,
representation=None,
should_reload=True,
enable_dynamic_voids=True,
is_global=True,
): ):
representation = geometry.resolve_mapped_representation(representation) representation = geometry.resolve_mapped_representation(representation)
data = geometry.get_representation_data(representation) data = geometry.get_representation_data(representation)
+7
View File
@@ -197,8 +197,15 @@ class Surveyor:
class Type: class Type:
def disable_editing(cls, obj): pass def disable_editing(cls, obj): pass
def get_any_representation(cls, element): pass def get_any_representation(cls, element): pass
def get_body_context(cls): pass
def get_body_representation(cls, element): 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_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 @interface
+23 -7
View File
@@ -19,18 +19,34 @@
import blenderbim.core.geometry 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) 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) 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: if representation:
blenderbim.core.geometry.switch_representation( type_tool.run_geometry_switch_representation(
geometry,
obj=obj, obj=obj,
representation=representation, representation=representation,
should_reload=False, should_reload=should_reload,
enable_dynamic_voids=type_tool.has_dynamic_voids(obj), enable_dynamic_voids=type_tool.has_dynamic_voids(obj),
is_global=False, is_global=False,
) )
+60
View File
@@ -18,6 +18,7 @@
import ifcopenshell import ifcopenshell
import blenderbim.core.tool import blenderbim.core.tool
import blenderbim.core.geometry
import blenderbim.tool as tool import blenderbim.tool as tool
import blenderbim.bim.helper import blenderbim.bim.helper
@@ -34,6 +35,10 @@ class Type(blenderbim.core.tool.Type):
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps: elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
return element.RepresentationMaps[0].MappedRepresentation 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 @classmethod
def get_body_representation(cls, element): def get_body_representation(cls, element):
if element.is_a("IfcProduct") and element.Representation and element.Representation.Representations: 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": if representation_map.MappedRepresentation.ContextOfItems.ContextIdentifier == "Body":
return representation_map.MappedRepresentation 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 @classmethod
def has_dynamic_voids(cls, obj): def has_dynamic_voids(cls, obj):
for modifier in obj.modifiers: for modifier in obj.modifiers:
if modifier.name == "IfcOpeningElement" and modifier.type == "BOOLEAN": if modifier.name == "IfcOpeningElement" and modifier.type == "BOOLEAN":
return True return True
return False 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,
)
+60 -21
View File
@@ -17,45 +17,84 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import blenderbim.core.type as subject import blenderbim.core.type as subject
from test.core.bootstrap import ifc, geometry, type from test.core.bootstrap import ifc, type
class TestAssignType: 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() 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") type.get_body_representation("element").should_be_called().will_return("mapped_rep")
ifc.get_object("element").should_be_called().will_return("obj") ifc.get_object("element").should_be_called().will_return("obj")
type.has_dynamic_voids("obj").should_be_called().will_return(False) type.has_dynamic_voids("obj").should_be_called().will_return(False)
type.run_geometry_switch_representation(
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation") obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
geometry.get_representation_data("representation").should_be_called().will_return("data") ).should_be_called()
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
geometry.clear_dynamic_voids("obj").should_be_called()
type.disable_editing("obj").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, type):
def test_assigning_and_switching_to_any_representation_as_a_fallback(self, ifc, geometry, type):
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called() 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_body_representation("element").should_be_called().will_return(None)
type.get_any_representation("element").should_be_called().will_return("mapped_rep") type.get_any_representation("element").should_be_called().will_return("mapped_rep")
ifc.get_object("element").should_be_called().will_return("obj") ifc.get_object("element").should_be_called().will_return("obj")
type.has_dynamic_voids("obj").should_be_called().will_return(False) type.has_dynamic_voids("obj").should_be_called().will_return(False)
type.run_geometry_switch_representation(
geometry.resolve_mapped_representation("mapped_rep").should_be_called().will_return("representation") obj="obj", representation="mapped_rep", should_reload=False, enable_dynamic_voids=False, is_global=False
geometry.get_representation_data("representation").should_be_called().will_return("data") ).should_be_called()
geometry.change_object_data("obj", "data", is_global=False).should_be_called()
geometry.clear_dynamic_voids("obj").should_be_called()
type.disable_editing("obj").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_there_is_no_representation_to_change_to(self, ifc, type):
def test_assigning_and_not_changing_representation_if_not_available(self, ifc, type):
ifc.run("type.assign_type", related_object="element", relating_type="type").should_be_called() 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_body_representation("element").should_be_called().will_return(None)
type.get_any_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") ifc.get_object("element").should_be_called().will_return("obj")
type.disable_editing("obj").should_be_called() 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")
+73
View File
@@ -53,6 +53,16 @@ class TestGetAnyRepresentation(NewFile):
assert subject.get_any_representation(element) == representation 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): class TestGetBodyRepresentation(NewFile):
def test_get_product_representation(self): def test_get_product_representation(self):
ifc = ifcopenshell.file() ifc = ifcopenshell.file()
@@ -78,6 +88,49 @@ class TestGetBodyRepresentation(NewFile):
assert subject.get_body_representation(element) == body_rep 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): class TestHasDynamicVoids(NewFile):
def test_run(self): def test_run(self):
obj = bpy.data.objects.new("Object", None) 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 = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
obj.modifiers.new("IfcOpeningElement", "BOOLEAN") obj.modifiers.new("IfcOpeningElement", "BOOLEAN")
assert subject.has_dynamic_voids(obj) is True 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
@@ -22,7 +22,8 @@ class Usecase:
ifcopenshell.util.element.remove_deep2( ifcopenshell.util.element.remove_deep2(
self.file, self.file,
self.settings["representation"], 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: for element in styled_items:
@@ -136,17 +136,21 @@ def remove_deep(ifc_file, element):
ifc_file.unbatch() 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. # Experimental remove deep proposal. No batch for now until this is more certain. See #1812.
# ifc_file.batch() # ifc_file.batch()
to_delete = set() to_delete = set()
subgraph = list(ifc_file.traverse(element, breadth_first=True)) subgraph = list(ifc_file.traverse(element, breadth_first=True))
subgraph.extend(extra_subgraph_elements) subgraph.extend(also_consider)
subgraph_set = set(subgraph) subgraph_set = set(subgraph)
subelement_queue = ifc_file.traverse(element, max_levels=1) subelement_queue = ifc_file.traverse(element, max_levels=1)
while subelement_queue: while subelement_queue:
subelement = subelement_queue.pop(0) 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) to_delete.add(subelement)
subelement_queue.extend(ifc_file.traverse(subelement, max_levels=1)[1:]) subelement_queue.extend(ifc_file.traverse(subelement, max_levels=1)[1:])
for subelement in to_delete: for subelement in to_delete:
@@ -77,3 +77,9 @@ class TestRemoveRepresentation(test.bootstrap.IFC4):
ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation) ifcopenshell.api.run("geometry.remove_representation", self.file, representation=representation)
assert len(self.file.by_type("IfcPresentationLayerAssignment")) == 1 assert len(self.file.by_type("IfcPresentationLayerAssignment")) == 1
assert len(self.file.by_type("IfcShapeRepresentation")) == 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