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/>.
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:
@@ -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)
+1 -6
View File
@@ -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)
+7
View File
@@ -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
+23 -7
View File
@@ -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,
)
+60
View File
@@ -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,
)
+60 -21
View File
@@ -17,45 +17,84 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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")
+73
View File
@@ -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
@@ -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:
@@ -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:
@@ -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