From 6d817703aa1443686eb04fc345e4b23563b4da64 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 22 Oct 2021 18:07:34 +1100 Subject: [PATCH] Copying parametric geometry from any source now attempts to preserve parametric geometry when possible. --- .../bim/module/geometry/operator.py | 33 ++--- .../blenderbim/bim/module/model/wall.py | 2 +- .../blenderbim/bim/module/root/operator.py | 2 +- src/blenderbim/blenderbim/core/root.py | 11 +- src/blenderbim/blenderbim/core/tool.py | 7 +- src/blenderbim/blenderbim/tool/geometry.py | 62 ++++++--- src/blenderbim/blenderbim/tool/root.py | 21 ++- src/blenderbim/blenderbim/tool/spatial.py | 2 +- .../test/bim/feature/geometry.feature | 120 +++++++++++++++++- src/blenderbim/test/bim/feature/type.feature | 8 +- src/blenderbim/test/core/test_root.py | 50 +++++--- src/blenderbim/test/tool/test_geometry.py | 95 +++++++++++--- src/blenderbim/test/tool/test_root.py | 16 ++- 13 files changed, 326 insertions(+), 103 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index b1a2b4e430..7a03fd084e 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -212,7 +212,10 @@ class UpdateRepresentation(bpy.types.Operator): } if not self.ifc_representation_class: - self.auto_detect_ifc_representation_class(product, old_representation, representation_data) + representation_data["ifc_representation_class"] = tool.Geometry.get_ifc_representation_class( + product, old_representation + ) + representation_data["profile_set_usage"] = tool.Geometry.get_profile_set_usage(product) new_representation = ifcopenshell.api.run("geometry.add_representation", self.file, **representation_data) @@ -248,28 +251,6 @@ class UpdateRepresentation(bpy.types.Operator): if obj.data.BIMMeshProperties.ifc_parameters: bpy.ops.bim.get_representation_ifc_parameters() - def auto_detect_ifc_representation_class(self, element, representation, data): - material = ifcopenshell.util.element.get_material(element) - - if material and material.is_a("IfcMaterialProfileSetUsage"): - data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" - data["profile_set_usage"] = material - return - - extruded_areas = [e for e in self.file.traverse(representation) if e.is_a() == "IfcExtrudedAreaSolid"] - - if len(extruded_areas) != 1: - return # It's too complex for us to derive topologically right now - - profile_def = extruded_areas[0].SweptArea - - if profile_def.is_a() == "IfcRectangleProfileDef": - data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcRectangleProfileDef" - elif profile_def.is_a() == "IfcCircleProfileDef": - data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcCircleProfileDef" - else: - data["ifc_representation_class"] = "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" - class UpdateParametricRepresentation(bpy.types.Operator): bl_idname = "bim.update_parametric_representation" @@ -337,7 +318,9 @@ class CopyRepresentation(bpy.types.Operator, Operator): return bm = bmesh.new() bm.from_mesh(context.active_object.data) - geometric_context = tool.Root.get_object_context(context.active_object) + geometric_context = tool.Root.get_representation_context( + tool.Root.get_object_representation(context.active_object) + ) for obj in context.selected_objects: if obj == context.active_object: continue @@ -467,7 +450,7 @@ class OverrideDuplicateMove(bpy.types.Operator): obj.select_set(False) new_obj.select_set(True) # This is the only difference - blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Root, obj=new_obj) + blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new_obj) bpy.ops.transform.translate("INVOKE_DEFAULT") blenderbim.bim.handler.purge_module_data() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/wall.py b/src/blenderbim/blenderbim/bim/module/model/wall.py index d3a90cd9b6..6b3aff5844 100644 --- a/src/blenderbim/blenderbim/bim/module/model/wall.py +++ b/src/blenderbim/blenderbim/bim/module/model/wall.py @@ -225,7 +225,7 @@ class DumbWallSplitter: def duplicate_wall(self): new = self.wall.copy() self.wall.users_collection[0].objects.link(new) - blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Root, obj=new) + blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=new) return new def snap_end_face_to_point(self, wall, which_end): diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index c8ee134110..4dc67e706a 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -356,5 +356,5 @@ class CopyClass(bpy.types.Operator, Operator): def _execute(self, context): objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects for obj in objects: - core.copy_class(tool.Ifc, tool.Collector, tool.Root, obj=obj) + core.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) blenderbim.bim.handler.purge_module_data() diff --git a/src/blenderbim/blenderbim/core/root.py b/src/blenderbim/blenderbim/core/root.py index 4ed58d1cb2..5ca8e3f276 100644 --- a/src/blenderbim/blenderbim/core/root.py +++ b/src/blenderbim/blenderbim/core/root.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . -def copy_class(ifc, collector, root, obj=None): +def copy_class(ifc, collector, geometry, root, obj=None): element = ifc.get_entity(obj) if not element: return @@ -27,7 +27,14 @@ def copy_class(ifc, collector, root, obj=None): if relating_type and root.does_type_have_representations(relating_type): ifc.run("type.map_type_representations", related_object=element, relating_type=relating_type) else: - root.run_geometry_add_representation(obj=obj, context=root.get_object_context(obj)) + representation = root.get_object_representation(obj) + if representation: + root.run_geometry_add_representation( + obj=obj, + context=root.get_representation_context(representation), + ifc_representation_class=geometry.get_ifc_representation_class(element, representation), + profile_set_usage=geometry.get_profile_set_usage(element), + ) collector.assign(obj) if root.is_opening_element(element): root.add_dynamic_opening_voids(element, obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 7e5a2742f3..e90297d42d 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -63,8 +63,10 @@ class Geometry: def does_object_have_mesh_with_faces(cls, obj): pass def duplicate_object_data(cls, obj): pass def get_cartesian_point_coordinate_offset(cls, obj): pass + def get_ifc_representation_class(cls, element, representation): pass def get_object_data(cls, obj): pass def get_object_materials_without_styles(cls, obj): pass + def get_profile_set_usage(cls, element): pass def get_representation_data(cls, representation): pass def get_representation_name(cls, representation): pass def get_total_representation_items(cls, obj): pass @@ -148,9 +150,10 @@ class Root: def add_dynamic_opening_voids(cls, element, obj): pass def does_type_have_representations(cls, element): pass def get_element_type(cls, element): pass - def get_object_context(cls, obj): pass + def get_object_representation(cls, obj): pass + def get_representation_context(cls, representation): pass def is_opening_element(cls, element): pass - def run_geometry_add_representation(cls, obj=None, context=None): pass + def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass @interface diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 80e352fe79..33aaa77c36 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -61,24 +61,6 @@ class Geometry(blenderbim.core.tool.Geometry): obj.data = obj.data.copy() return obj.data - @classmethod - def get_object_data(cls, obj): - return obj.data - - @classmethod - def get_object_materials_without_styles(cls, obj): - return [ - s.material for s in obj.material_slots if s.material and not s.material.BIMMaterialProperties.ifc_style_id - ] - - @classmethod - def get_representation_data(cls, representation): - return bpy.data.meshes.get(cls.get_representation_name(representation)) - - @classmethod - def get_representation_name(cls, representation): - return f"{representation.ContextOfItems.id()}/{representation.id()}" - @classmethod def get_cartesian_point_coordinate_offset(cls, obj): props = bpy.context.scene.BIMGeoreferenceProperties @@ -91,6 +73,50 @@ class Geometry(blenderbim.core.tool.Geometry): ) ) + @classmethod + def get_ifc_representation_class(cls, element, representation): + material = ifcopenshell.util.element.get_material(element) + if material and material.is_a("IfcMaterialProfileSetUsage"): + return "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" + + extruded_areas = [e for e in tool.Ifc.get().traverse(representation) if e.is_a() == "IfcExtrudedAreaSolid"] + + if len(extruded_areas) != 1: + return # It's too complex for us to derive topologically right now + + profile_def = extruded_areas[0].SweptArea + + if profile_def.is_a() == "IfcRectangleProfileDef": + return "IfcExtrudedAreaSolid/IfcRectangleProfileDef" + elif profile_def.is_a() == "IfcCircleProfileDef": + return "IfcExtrudedAreaSolid/IfcCircleProfileDef" + return "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids" + + @classmethod + def get_object_data(cls, obj): + return obj.data + + @classmethod + def get_object_materials_without_styles(cls, obj): + return [ + s.material for s in obj.material_slots if s.material and not s.material.BIMMaterialProperties.ifc_style_id + ] + + @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_data(cls, representation): + return bpy.data.meshes.get(cls.get_representation_name(representation)) + + @classmethod + def get_representation_name(cls, representation): + return f"{representation.ContextOfItems.id()}/{representation.id()}" + @classmethod def get_total_representation_items(cls, obj): return max(1, len(obj.material_slots)) diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index afae8f18d6..d36c5aa34a 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -49,16 +49,29 @@ class Root(blenderbim.core.tool.Root): return ifcopenshell.util.element.get_type(element) @classmethod - def get_object_context(cls, obj): + def get_object_representation(cls, obj): if obj.data and obj.data.BIMMeshProperties.ifc_definition_id: - return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id).ContextOfItems + return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) + + @classmethod + def get_representation_context(cls, representation): + return representation.ContextOfItems @classmethod def is_opening_element(cls, element): return element.is_a("IfcOpeningElement") @classmethod - def run_geometry_add_representation(cls, obj=None, context=None): + 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 + tool.Ifc, + tool.Geometry, + tool.Style, + tool.Surveyor, + obj=obj, + context=context, + ifc_representation_class=ifc_representation_class, + profile_set_usage=profile_set_usage, ) diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index a141868661..6c21bd0050 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -93,7 +93,7 @@ class Spatial(blenderbim.core.tool.Spatial): @classmethod def run_root_copy_class(cls, obj=None): - return blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Root, obj=obj) + return blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=obj) @classmethod def run_spatial_assign_container(cls, structure_obj=None, element_obj=None): diff --git a/src/blenderbim/test/bim/feature/geometry.feature b/src/blenderbim/test/bim/feature/geometry.feature index b8c7cccedc..0385379a49 100644 --- a/src/blenderbim/test/bim/feature/geometry.feature +++ b/src/blenderbim/test/bim/feature/geometry.feature @@ -44,7 +44,7 @@ Scenario: Switch representation - existing Blender modifiers must be purged And I press "bim.switch_representation(obj='IfcWall/Cube', ifc_definition_id={representation})" Then the object "IfcWall/Cube" has no modifiers -Scenario: Update representation +Scenario: Update representation - updating a tessellation Given an empty IFC project And I add a cube And the object "Cube" is selected @@ -53,6 +53,63 @@ Scenario: Update representation And I press "bim.update_representation(obj='IfcWall/Cube')" Then the object "IfcWall/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" +Scenario: Update representation - updating a layered extrusion + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I press "bim.add_default_material" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.assign_material" + And I press "bim.enable_editing_assigned_material" + And the variable "layer_set" is "{ifc}.by_type("IfcMaterialLayerSet")[0].id()" + And I press "bim.add_layer(layer_set={layer_set})" + And the variable "layer" is "{ifc}.by_type("IfcMaterialLayer")[0].id()" + And I press "bim.enable_editing_material_set_item(material_set_item={layer})" + And I set "active_object.BIMObjectMaterialProperties.material_set_item_attributes[0].float_value" to "0.1" + And I press "bim.edit_material_set_item(material_set_item={layer})" + And I press "bim.edit_assigned_material(material_set={layer_set})" + And the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" + When I press "bim.update_representation(obj='IfcWall/Cube')" + Then the object "IfcWall/Cube" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" + +Scenario: Update representation - updating a profiled extrusion + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I press "bim.add_default_material" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" + And I press "bim.assign_material" + And I press "bim.enable_editing_assigned_material" + And the variable "profile_set" is "{ifc}.by_type("IfcMaterialProfileSet")[0].id()" + And I press "bim.add_profile(profile_set={profile_set})" + And the variable "profile" is "{ifc}.by_type("IfcMaterialProfile")[0].id()" + And I press "bim.enable_editing_material_set_item(material_set_item={profile})" + And I set "active_object.BIMObjectMaterialProperties.profile_classes" to "IfcParameterizedProfileDef" + And I set "active_object.BIMObjectMaterialProperties.parameterized_profile_classes" to "IfcCircleProfileDef" + And I press "bim.assign_parameterized_profile(ifc_class='IfcCircleProfileDef', material_profile={profile})" + And I set "active_object.BIMObjectMaterialProperties.material_set_item_profile_attributes[2].float_value" to "0.2" + And I press "bim.edit_material_set_item(material_set_item={profile})" + And I press "bim.edit_assigned_material(material_set={profile_set})" + And the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" + When I press "bim.update_representation(obj='IfcWall/Cube')" + Then the object "IfcWall/Cube" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" + Scenario: Copy representation Given an empty IFC project And I add a cube @@ -126,3 +183,64 @@ Scenario: Override duplicate move - copying a type instance with a representatio When I press "object.duplicate_move" Then the object "IfcWall/Instance.001" exists And the object "IfcWall/Instance.001" has a "MappedRepresentation" representation of "Model/Body/MODEL_VIEW" + +Scenario: Override duplicate move - copying a layered extrusion + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I press "bim.add_default_material" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialLayerSet" + And I press "bim.assign_material" + And I press "bim.enable_editing_assigned_material" + And the variable "layer_set" is "{ifc}.by_type("IfcMaterialLayerSet")[0].id()" + And I press "bim.add_layer(layer_set={layer_set})" + And the variable "layer" is "{ifc}.by_type("IfcMaterialLayer")[0].id()" + And I press "bim.enable_editing_material_set_item(material_set_item={layer})" + And I set "active_object.BIMObjectMaterialProperties.material_set_item_attributes[0].float_value" to "0.1" + And I press "bim.edit_material_set_item(material_set_item={layer})" + And I press "bim.edit_assigned_material(material_set={layer_set})" + And the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" + And the object "IfcWall/Cube" is selected + When I press "object.duplicate_move" + Then the object "IfcWall/Cube.001" exists + Then the object "IfcWall/Cube.001" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" + +Scenario: Override duplicate move - copying a profiled extrusion + Given an empty IFC project + And I add a cube + And the object "Cube" is selected + And I set "scene.BIMRootProperties.ifc_class" to "IfcWall" + And I press "bim.assign_class" + And I add an empty + And the object "Empty" is selected + And I set "scene.BIMRootProperties.ifc_product" to "IfcElementType" + And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" + And I press "bim.assign_class" + And I press "bim.add_default_material" + And I set "active_object.BIMObjectMaterialProperties.material_type" to "IfcMaterialProfileSet" + And I press "bim.assign_material" + And I press "bim.enable_editing_assigned_material" + And the variable "profile_set" is "{ifc}.by_type("IfcMaterialProfileSet")[0].id()" + And I press "bim.add_profile(profile_set={profile_set})" + And the variable "profile" is "{ifc}.by_type("IfcMaterialProfile")[0].id()" + And I press "bim.enable_editing_material_set_item(material_set_item={profile})" + And I set "active_object.BIMObjectMaterialProperties.profile_classes" to "IfcParameterizedProfileDef" + And I set "active_object.BIMObjectMaterialProperties.parameterized_profile_classes" to "IfcCircleProfileDef" + And I press "bim.assign_parameterized_profile(ifc_class='IfcCircleProfileDef', material_profile={profile})" + And I set "active_object.BIMObjectMaterialProperties.material_set_item_profile_attributes[2].float_value" to "0.2" + And I press "bim.edit_material_set_item(material_set_item={profile})" + And I press "bim.edit_assigned_material(material_set={profile_set})" + And the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" + And the object "IfcWall/Cube" is selected + When I press "object.duplicate_move" + Then the object "IfcWall/Cube.001" exists + Then the object "IfcWall/Cube.001" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" diff --git a/src/blenderbim/test/bim/feature/type.feature b/src/blenderbim/test/bim/feature/type.feature index 184c8dd035..d258a077a1 100644 --- a/src/blenderbim/test/bim/feature/type.feature +++ b/src/blenderbim/test/bim/feature/type.feature @@ -13,7 +13,7 @@ Scenario: Assign type - assign to an empty type And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I press "bim.assign_class" When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" - And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" Then the object "IfcWall/Cube" has a "Tessellation" representation of "Model/Body/MODEL_VIEW" Scenario: Assign type - assign to a type with representation maps @@ -28,7 +28,7 @@ Scenario: Assign type - assign to a type with representation maps And I set "scene.BIMRootProperties.ifc_class" to "IfcWallType" And I press "bim.assign_class" When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" - And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" Then the object "IfcWall/Cube" has a "MappedRepresentation" representation of "Model/Body/MODEL_VIEW" Scenario: Assign type - assign to a type with a material layer set @@ -54,7 +54,7 @@ Scenario: Assign type - assign to a type with a material layer set And I press "bim.edit_material_set_item(material_set_item={layer})" And I press "bim.edit_assigned_material(material_set={layer_set})" When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" - And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" Then the object "IfcWall/Cube" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" Scenario: Assign type - assign to a type with a material profile set @@ -83,5 +83,5 @@ Scenario: Assign type - assign to a type with a material profile set And I press "bim.edit_material_set_item(material_set_item={profile})" And I press "bim.edit_assigned_material(material_set={profile_set})" When the variable "type" is "{ifc}.by_type('IfcWallType')[0].id()" - And I press "bim.assign_type(relating_type={type}, related_object="IfcWall/Cube")" + And I press "bim.assign_type(relating_type={type}, related_object='IfcWall/Cube')" Then the object "IfcWall/Cube" has a "SweptSolid" representation of "Model/Body/MODEL_VIEW" diff --git a/src/blenderbim/test/core/test_root.py b/src/blenderbim/test/core/test_root.py index c3ce50cb2f..442ee4c942 100644 --- a/src/blenderbim/test/core/test_root.py +++ b/src/blenderbim/test/core/test_root.py @@ -18,13 +18,13 @@ import blenderbim.core.root as subject import test.core.test_geometry -from test.core.bootstrap import ifc, collector, root +from test.core.bootstrap import ifc, collector, geometry, root class TestCopyClass: def test_doing_nothing_if_not_an_ifc_element(self, ifc, collector, root): ifc.get_entity("obj").should_be_called().will_return(None) - subject.copy_class(ifc, collector, root, obj="obj") + subject.copy_class(ifc, collector, geometry, root, obj="obj") def test_copy_with_new_geometry_derived_from_the_type(self, ifc, collector, root): ifc.get_entity("obj").should_be_called().will_return("original_element") @@ -35,31 +35,43 @@ class TestCopyClass: ifc.run("type.map_type_representations", related_object="element", relating_type="type").should_be_called() collector.assign("obj").should_be_called() root.is_opening_element("element").should_be_called().will_return(False) - subject.copy_class(ifc, collector, root, obj="obj") + subject.copy_class(ifc, collector, geometry, root, obj="obj") - def test_copy_with_new_geometry_added_afresh_for_speed( - self, - ifc, - collector, - root, - ): + def test_copy_with_new_geometry_added_afresh_for_speed(self, ifc, collector, geometry, root): ifc.get_entity("obj").should_be_called().will_return("original_element") ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element") ifc.link("element", "obj").should_be_called() root.get_element_type("element").should_be_called().will_return("type") root.does_type_have_representations("type").should_be_called().will_return(False) - root.get_object_context("obj").should_be_called().will_return("context") - root.run_geometry_add_representation(obj="obj", context="context").should_be_called() + + root.get_object_representation("obj").should_be_called().will_return("representation") + root.get_representation_context("representation").should_be_called().will_return("context") + geometry.get_ifc_representation_class("element", "representation").should_be_called().will_return( + "ifc_representation_class" + ) + geometry.get_profile_set_usage("element").should_be_called().will_return("profile_set_usage") + root.run_geometry_add_representation( + obj="obj", + context="context", + ifc_representation_class="ifc_representation_class", + profile_set_usage="profile_set_usage", + ).should_be_called() collector.assign("obj").should_be_called() root.is_opening_element("element").should_be_called().will_return(False) - subject.copy_class(ifc, collector, root, obj="obj") + subject.copy_class(ifc, collector, geometry, root, obj="obj") - def test_copied_openings_have_dynamic_voids_added( - self, - ifc, - collector, - root, - ): + def test_copy_with_no_new_geometry(self, ifc, collector, geometry, root): + ifc.get_entity("obj").should_be_called().will_return("original_element") + ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element") + ifc.link("element", "obj").should_be_called() + root.get_element_type("element").should_be_called().will_return("type") + root.does_type_have_representations("type").should_be_called().will_return(False) + root.get_object_representation("obj").should_be_called().will_return(None) + collector.assign("obj").should_be_called() + root.is_opening_element("element").should_be_called().will_return(False) + subject.copy_class(ifc, collector, geometry, root, obj="obj") + + def test_copied_openings_have_dynamic_voids_added(self, ifc, collector, root): ifc.get_entity("obj").should_be_called().will_return("original_element") ifc.run("root.copy_class", product="original_element").should_be_called().will_return("element") ifc.link("element", "obj").should_be_called() @@ -69,4 +81,4 @@ class TestCopyClass: collector.assign("obj").should_be_called() root.is_opening_element("element").should_be_called().will_return(True) root.add_dynamic_opening_voids("element", "obj").should_be_called() - subject.copy_class(ifc, collector, root, obj="obj") + subject.copy_class(ifc, collector, geometry, root, obj="obj") diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index c323196f66..f71657a0c0 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -20,20 +20,20 @@ import bpy import math import numpy import ifcopenshell -import test.bim.bootstrap import blenderbim.core.tool import blenderbim.tool as tool from mathutils import Vector +from test.bim.bootstrap import NewFile from blenderbim.tool.geometry import Geometry as subject from blenderbim.bim.ifc import IfcStore -class TestImplementsTool(test.bim.bootstrap.NewFile): +class TestImplementsTool(NewFile): def test_run(self): assert isinstance(subject(), blenderbim.core.tool.Geometry) -class TestChangeObjectData(test.bim.bootstrap.NewFile): +class TestChangeObjectData(NewFile): def test_change_single_object_data(self): data1 = bpy.data.meshes.new("Mesh") data2 = bpy.data.meshes.new("Mesh") @@ -53,7 +53,7 @@ class TestChangeObjectData(test.bim.bootstrap.NewFile): assert obj2.data == data2 -class TestClearModifiers(test.bim.bootstrap.NewFile): +class TestClearModifiers(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) obj.modifiers.new("IfcOpeningElement", "BOOLEAN") @@ -61,7 +61,7 @@ class TestClearModifiers(test.bim.bootstrap.NewFile): assert len(obj.modifiers) == 0 -class TestCreateDynamicVoids(test.bim.bootstrap.NewFile): +class TestCreateDynamicVoids(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) @@ -82,7 +82,7 @@ class TestCreateDynamicVoids(test.bim.bootstrap.NewFile): assert modifier.use_self is True -class TestDoesObjectHaveMeshWithFaces(test.bim.bootstrap.NewFile): +class TestDoesObjectHaveMeshWithFaces(NewFile): def test_empties_return_false(self): obj = bpy.data.objects.new("Object", None) assert subject.does_object_have_mesh_with_faces(obj) is False @@ -101,7 +101,7 @@ class TestDoesObjectHaveMeshWithFaces(test.bim.bootstrap.NewFile): assert subject.does_object_have_mesh_with_faces(obj) is True -class TestDuplicateObjectData(test.bim.bootstrap.NewFile): +class TestDuplicateObjectData(NewFile): def test_run(self): data = bpy.data.meshes.new("Mesh") obj = bpy.data.objects.new("Object", data) @@ -110,14 +110,14 @@ class TestDuplicateObjectData(test.bim.bootstrap.NewFile): assert isinstance(obj.data, bpy.types.Mesh) -class TestGetObjectData(test.bim.bootstrap.NewFile): +class TestGetObjectData(NewFile): def test_run(self): data = bpy.data.meshes.new("Mesh") obj = bpy.data.objects.new("Object", data) assert subject.get_object_data(obj) == obj.data -class TestGetObjectMaterialsWithoutStyles(test.bim.bootstrap.NewFile): +class TestGetObjectMaterialsWithoutStyles(NewFile): def test_run(self): material1 = bpy.data.materials.new("Material") material2 = bpy.data.materials.new("Material") @@ -130,7 +130,7 @@ class TestGetObjectMaterialsWithoutStyles(test.bim.bootstrap.NewFile): assert subject.get_object_materials_without_styles(obj) == [material1, material2] -class TestGetRepresentationData(test.bim.bootstrap.NewFile): +class TestGetRepresentationData(NewFile): def test_run(self): ifc = ifcopenshell.file() context = ifc.createIfcGeometricRepresentationContext() @@ -140,7 +140,7 @@ class TestGetRepresentationData(test.bim.bootstrap.NewFile): assert subject.get_representation_data(representation) == data -class TestGetRepresentationName(test.bim.bootstrap.NewFile): +class TestGetRepresentationName(NewFile): def test_run(self): ifc = ifcopenshell.file() context = ifc.createIfcGeometricRepresentationContext() @@ -149,7 +149,7 @@ class TestGetRepresentationName(test.bim.bootstrap.NewFile): assert subject.get_representation_name(representation) == f"{context.id()}/{representation.id()}" -class TestGetCartesianPointCoordinateOffset(test.bim.bootstrap.NewFile): +class TestGetCartesianPointCoordinateOffset(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) obj.BIMObjectProperties.blender_offset_type = "CARTESIAN_POINT" @@ -177,7 +177,7 @@ class TestGetCartesianPointCoordinateOffset(test.bim.bootstrap.NewFile): assert subject.get_cartesian_point_coordinate_offset(obj) is None -class TestGetTotalRepresentationItems(test.bim.bootstrap.NewFile): +class TestGetTotalRepresentationItems(NewFile): def test_run(self): material1 = bpy.data.materials.new("Material") material2 = bpy.data.materials.new("Material") @@ -187,7 +187,7 @@ class TestGetTotalRepresentationItems(test.bim.bootstrap.NewFile): assert subject.get_total_representation_items(obj) == 2 -class TestImportRepresentation(test.bim.bootstrap.NewFile): +class TestImportRepresentation(NewFile): def test_importing_a_normal_shape(self): ifc = ifcopenshell.open("test/files/basic.ifc") tool.Ifc.set(ifc) @@ -213,7 +213,7 @@ class TestImportRepresentation(test.bim.bootstrap.NewFile): assert len(mesh.edges) == 4 -class TestIsBodyRepresentation(test.bim.bootstrap.NewFile): +class TestIsBodyRepresentation(NewFile): def test_run(self): ifc = ifcopenshell.file() context = ifc.createIfcGeometricRepresentationContext() @@ -225,7 +225,7 @@ class TestIsBodyRepresentation(test.bim.bootstrap.NewFile): assert subject.is_body_representation(representation) is False -class TestLink(test.bim.bootstrap.NewFile): +class TestLink(NewFile): def test_run(self): ifc = ifcopenshell.file() element = ifc.createIfcShapeRepresentation() @@ -234,14 +234,14 @@ class TestLink(test.bim.bootstrap.NewFile): assert obj.BIMMeshProperties.ifc_definition_id == element.id() -class TestRenameObjectData(test.bim.bootstrap.NewFile): +class TestRenameObjectData(NewFile): def test_run(self): obj = bpy.data.meshes.new("Mesh") subject.rename_object(obj, "name") assert obj.name == "name" -class TestResolveMappedRepresentation(test.bim.bootstrap.NewFile): +class TestResolveMappedRepresentation(NewFile): def test_run(self): ifc = ifcopenshell.file() mapped_representation = ifc.createIfcShapeRepresentation() @@ -258,19 +258,72 @@ class TestResolveMappedRepresentation(test.bim.bootstrap.NewFile): assert subject.resolve_mapped_representation(representation) == representation -class TestShouldForceFacetedBrep(test.bim.bootstrap.NewFile): +class TestShouldForceFacetedBrep(NewFile): def test_run(self): result = bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep assert subject.should_force_faceted_brep() is result -class TestShouldForceTriangulation(test.bim.bootstrap.NewFile): +class TestShouldForceTriangulation(NewFile): def test_run(self): result = bpy.context.scene.BIMGeometryProperties.should_force_triangulation assert subject.should_force_triangulation() is result -class TestShouldUsePresentationStyleAssignment(test.bim.bootstrap.NewFile): +class TestShouldUsePresentationStyleAssignment(NewFile): def test_run(self): result = bpy.context.scene.BIMGeometryProperties.should_use_presentation_style_assignment assert subject.should_use_presentation_style_assignment() is result + + +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 TestGetIfcRepresentationClass(NewFile): + def test_detecting_profile_set_representations(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcColumn() + ifc.createIfcRelAssociatesMaterial( + RelatingMaterial=ifc.createIfcMaterialProfileSetUsage(), RelatedObjects=[element] + ) + representation = ifc.createIfcShapeRepresentation() + assert ( + subject.get_ifc_representation_class(element, representation) + == "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage" + ) + + def test_detecting_rectangular_extrusions(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcColumn() + representation = ifc.createIfcShapeRepresentation( + Items=[ifc.createIfcExtrudedAreaSolid(SweptArea=ifc.createIfcRectangleProfileDef())] + ) + assert ( + subject.get_ifc_representation_class(element, representation) + == "IfcExtrudedAreaSolid/IfcRectangleProfileDef" + ) + + def test_detecting_circle_extrusions(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + element = ifc.createIfcColumn() + representation = ifc.createIfcShapeRepresentation( + Items=[ifc.createIfcExtrudedAreaSolid(SweptArea=ifc.createIfcCircleProfileDef())] + ) + assert ( + subject.get_ifc_representation_class(element, representation) == "IfcExtrudedAreaSolid/IfcCircleProfileDef" + ) + + def test_returning_null_for_non_parametric_representations(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + assert subject.get_ifc_representation_class(ifc.createIfcColumn(), ifc.createIfcShapeRepresentation()) is None diff --git a/src/blenderbim/test/tool/test_root.py b/src/blenderbim/test/tool/test_root.py index 61e7fcded0..56fbd8c9d3 100644 --- a/src/blenderbim/test/tool/test_root.py +++ b/src/blenderbim/test/tool/test_root.py @@ -74,15 +74,23 @@ class TestGetElementType(NewFile): assert subject.get_element_type(element) == type -class TestGetElementType(NewFile): +class TestGetObjectRepresentation(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc.set(ifc) + representation = ifc.createIfcShapeRepresentation() + obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) + obj.data.BIMMeshProperties.ifc_definition_id = representation.id() + assert subject.get_object_representation(obj) == representation + + +class TestGetRepresentationContext(NewFile): def test_run(self): ifc = ifcopenshell.file() tool.Ifc.set(ifc) context = ifc.createIfcGeometricRepresentationContext() representation = ifc.createIfcShapeRepresentation(ContextOfItems=context) - obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) - obj.data.BIMMeshProperties.ifc_definition_id = representation.id() - assert subject.get_object_context(obj) == context + assert subject.get_representation_context(representation) == context class TestIsOpeningElement(NewFile):