From 96179db7ada0ecc3a68f554acbdd6d97a16c28d6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 30 Jan 2022 15:08:32 +1100 Subject: [PATCH] IFC element names no longer treat the Blender name suffix as significant --- .../blenderbim/bim/module/root/operator.py | 138 ++---------------- src/blenderbim/blenderbim/core/tool.py | 8 +- src/blenderbim/blenderbim/tool/root.py | 11 ++ src/blenderbim/test/core/test_root.py | 65 +++++++++ src/blenderbim/test/tool/test_root.py | 37 +++++ 5 files changed, 136 insertions(+), 123 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index b4e0eda477..e64b2e5920 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -113,7 +113,7 @@ class ReassignClass(bpy.types.Operator): return {"FINISHED"} -class AssignClass(bpy.types.Operator): +class AssignClass(bpy.types.Operator, Operator): bl_idname = "bim.assign_class" bl_label = "Assign IFC Class" bl_options = {"REGISTER", "UNDO"} @@ -126,135 +126,29 @@ class AssignClass(bpy.types.Operator): should_add_representation: bpy.props.BoolProperty(default=True) ifc_representation_class: bpy.props.StringProperty() - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) - def _execute(self, context): objects = [bpy.data.objects.get(self.obj)] if self.obj else context.selected_objects - self.file = IfcStore.get_file() - if not self.ifc_class: - self.ifc_class = context.scene.BIMRootProperties.ifc_class - self.declaration = IfcStore.get_schema().declaration_by_name(self.ifc_class) - if self.predefined_type == "USERDEFINED": - self.predefined_type = self.userdefined_type + ifc_class = self.ifc_class or context.scene.BIMRootProperties.ifc_class + predefined_type = self.userdefined_type if self.predefined_type == "USERDEFINED" else self.predefined_type + ifc_context = self.context_id + if not ifc_context and get_contexts(self, context): + ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None + if ifc_context: + ifc_context = tool.Ifc.get().by_id(ifc_context) + active_object = context.active_object for obj in objects: - self.assign_class(context, obj) - return {"FINISHED"} - - def assign_class(self, context, obj): - if obj.BIMObjectProperties.ifc_definition_id: - return - product = ifcopenshell.api.run( - "root.create_entity", - self.file, - **{ - "ifc_class": self.ifc_class, - "predefined_type": self.predefined_type or None, - "name": obj.name, - }, - ) - obj.name = "{}/{}".format(product.is_a(), obj.name) - IfcStore.link_element(product, obj) - - if self.should_add_representation: - ifc_context = self.context_id - if not ifc_context and get_contexts(self, context): - ifc_context = int(context.scene.BIMRootProperties.contexts or "0") or None - if ifc_context: - ifc_context = tool.Ifc.get().by_id(ifc_context) - blenderbim.core.geometry.add_representation( + core.assign_class( tool.Ifc, - tool.Geometry, - tool.Style, - tool.Surveyor, + tool.Collector, + tool.Root, obj=obj, + ifc_class=ifc_class, + predefined_type=predefined_type, + should_add_representation=self.should_add_representation, context=ifc_context, ifc_representation_class=self.ifc_representation_class, - profile_set_usage=None, ) - - if product.is_a("IfcElementType"): - tool.Collector.assign(obj) - elif product.is_a("IfcOpeningElement"): - obj.display_type = "WIRE" - tool.Collector.assign(obj) - elif ( - product.is_a("IfcSpatialElement") - or product.is_a("IfcSpatialStructureElement") - or product.is_a("IfcProject") - or product.is_a("IfcContext") - ): - self.place_in_spatial_collection(obj, context) - elif product.is_a("IfcStructuralItem"): - if product.is_a("IfcStructuralMember"): - self.place_in_structural_items_collection(obj, context, structural_collection="Members") - elif product.is_a("IfcStructuralConnection"): - self.place_in_structural_items_collection(obj, context, structural_collection="Connections") - else: - self.assign_potential_spatial_container(obj) - context.view_layer.objects.active = obj - - def place_in_spatial_collection(self, obj, context): - for collection in obj.users_collection: - if collection.name == obj.name: - return - parent_collection = None - for collection in obj.users_collection: - collection.objects.unlink(obj) - if "Ifc" in collection.name: - parent_collection = collection - collection = bpy.data.collections.new(obj.name) - collection.objects.link(obj) - if parent_collection: - parent_collection.children.link(collection) - blenderbim.core.aggregate.assign_object( - tool.Ifc, - tool.Aggregate, - tool.Collector, - relating_obj=bpy.data.objects.get(parent_collection.name), - related_obj=obj, - ) - else: - context.scene.collection.children.link(collection) - - def place_in_structural_items_collection(self, obj, context, structural_collection): - for project in [c for c in context.view_layer.layer_collection.children if "IfcProject" in c.name]: - if not [c for c in project.children if "StructuralItems" in c.name]: - members = bpy.data.collections.new("Members") - connections = bpy.data.collections.new("Connections") - items = bpy.data.collections.new("StructuralItems") - items.children.link(members) - items.children.link(connections) - project.collection.children.link(items) - - for coll in [c for c in project.children if "StructuralItems" in c.name]: - for collection in [c for c in coll.children if structural_collection in c.name]: - for user_collection in obj.users_collection: - user_collection.objects.unlink(obj) - collection.collection.objects.link(obj) - break - break - break - - def assign_potential_spatial_container(self, obj): - for collection in obj.users_collection: - if "Ifc" not in collection.name or collection.name == obj.name: - continue - spatial_obj = bpy.data.objects.get(collection.name) - if spatial_obj and spatial_obj.BIMObjectProperties.ifc_definition_id: - element = self.file.by_id(spatial_obj.BIMObjectProperties.ifc_definition_id) - if self.file.schema != "IFC2X3" and not element.is_a("IfcSpatialElement"): - continue - elif self.file.schema == "IFC2X3" and not element.is_a("IfcSpatialStructureElement"): - continue - blenderbim.core.spatial.assign_container( - tool.Ifc, - tool.Collector, - tool.Spatial, - structure_obj=spatial_obj, - element_obj=obj, - ) - break + context.view_layer.objects.active = active_object class UnlinkObject(bpy.types.Operator): diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 96fea3678d..0e9b33457b 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -150,6 +150,7 @@ class Drawing: def export_text_literal_attributes(cls, obj): pass def generate_drawing_matrix(cls, target_view, location_hint): pass def generate_sheet_identification(cls): pass + def get_body_context(cls): pass def get_sheet_filename(cls, document): pass def get_text_literal(cls, obj): pass def get_text_product(cls, element): pass @@ -158,7 +159,7 @@ class Drawing: def import_text_attributes(cls, obj): pass def import_text_product(cls, obj): pass def open_svg(cls, filepath): pass - def run_assign_class_operator(cls, obj=None, ifc_class=None, predefined_type=None): pass + def run_root_assign_class(cls, obj=None, ifc_class=None, predefined_type=None, should_add_representation=True, context=None, ifc_representation_class=None): pass def update_text_value(cls, obj): pass @@ -171,6 +172,7 @@ class Geometry: def create_dynamic_voids(cls, obj): pass def delete_data(cls, data): pass def does_object_have_mesh_with_faces(cls, obj): pass + def does_representation_id_exist(cls, representation_id): pass def duplicate_object_data(cls, obj): pass def get_cartesian_point_coordinate_offset(cls, obj): pass def get_element_type(cls, element): pass @@ -180,6 +182,7 @@ class Geometry: 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_id(cls, representation): pass def get_representation_name(cls, representation): pass def get_styles(cls, obj): pass def get_total_representation_items(cls, obj): pass @@ -305,11 +308,14 @@ 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_name(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 link_object_data(cls, source_obj, destination_obj): pass def run_geometry_add_representation(cls, obj=None, context=None, ifc_representation_class=None, profile_set_usage=None): pass + def set_element_specific_display_settings(cls, obj, element): pass + def set_object_name(cls, obj, element): pass @interface diff --git a/src/blenderbim/blenderbim/tool/root.py b/src/blenderbim/blenderbim/tool/root.py index d7dac1aa83..3225907780 100644 --- a/src/blenderbim/blenderbim/tool/root.py +++ b/src/blenderbim/blenderbim/tool/root.py @@ -48,6 +48,12 @@ class Root(blenderbim.core.tool.Root): def get_element_type(cls, element): return ifcopenshell.util.element.get_type(element) + @classmethod + def get_object_name(cls, obj): + if "." in obj.name and obj.name.split(".")[-1].isnumeric(): + return ".".join(obj.name.split(".")[:-1]) + return obj.name + @classmethod def get_object_representation(cls, obj): if obj.data and obj.data.BIMMeshProperties.ifc_definition_id: @@ -80,6 +86,11 @@ class Root(blenderbim.core.tool.Root): profile_set_usage=profile_set_usage, ) + @classmethod + def set_element_specific_display_settings(cls, obj, element): + if element.is_a("IfcOpeningElement"): + obj.display_type = "WIRE" + @classmethod def set_object_name(cls, obj, element): name = obj.name diff --git a/src/blenderbim/test/core/test_root.py b/src/blenderbim/test/core/test_root.py index 72e5718f7c..79abb8bcf6 100644 --- a/src/blenderbim/test/core/test_root.py +++ b/src/blenderbim/test/core/test_root.py @@ -86,3 +86,68 @@ class TestCopyClass: 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, geometry, root, obj="obj") + + +class TestAssignClass: + def test_do_nothing_if_already_assigned(self, ifc, collector, root): + ifc.get_entity("obj").should_be_called().will_return("entity") + subject.assign_class( + ifc, + collector, + root, + obj="obj", + ifc_class="ifc_class", + predefined_type="predefined_type", + should_add_representation=True, + context="context", + ifc_representation_class="ifc_representation_class", + ) + + def test_assign_a_class_with_geometry_and_autodetected_spatial_container(self, ifc, collector, root): + ifc.get_entity("obj").should_be_called().will_return(None) + root.get_object_name("obj").should_be_called().will_return("name") + ifc.run( + "root.create_entity", ifc_class="ifc_class", predefined_type="predefined_type", name="name" + ).should_be_called().will_return("element") + root.set_object_name("obj", "element").should_be_called() + ifc.link("element", "obj").should_be_called() + root.run_geometry_add_representation( + obj="obj", context="context", ifc_representation_class="ifc_representation_class", profile_set_usage=None + ).should_be_called() + root.set_element_specific_display_settings("obj", "element").should_be_called() + collector.sync("obj").should_be_called() + collector.assign("obj").should_be_called() + subject.assign_class( + ifc, + collector, + root, + obj="obj", + ifc_class="ifc_class", + predefined_type="predefined_type", + should_add_representation=True, + context="context", + ifc_representation_class="ifc_representation_class", + ) + + def test_not_adding_a_representation_if_requested(self, ifc, collector, root): + ifc.get_entity("obj").should_be_called().will_return(None) + root.get_object_name("obj").should_be_called().will_return("name") + ifc.run( + "root.create_entity", ifc_class="ifc_class", predefined_type="predefined_type", name="name" + ).should_be_called().will_return("element") + root.set_object_name("obj", "element").should_be_called() + ifc.link("element", "obj").should_be_called() + root.set_element_specific_display_settings("obj", "element").should_be_called() + collector.sync("obj").should_be_called() + collector.assign("obj").should_be_called() + subject.assign_class( + ifc, + collector, + root, + obj="obj", + ifc_class="ifc_class", + predefined_type="predefined_type", + should_add_representation=False, + context="context", + ifc_representation_class="ifc_representation_class", + ) diff --git a/src/blenderbim/test/tool/test_root.py b/src/blenderbim/test/tool/test_root.py index ef6ee73861..ef71a0effa 100644 --- a/src/blenderbim/test/tool/test_root.py +++ b/src/blenderbim/test/tool/test_root.py @@ -74,6 +74,18 @@ class TestGetElementType(NewFile): assert subject.get_element_type(element) == type +class TestGetObjectName(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + assert subject.get_object_name(obj) == "Object" + + def test_blender_number_suffixes_are_ignored(self): + obj = bpy.data.objects.new("Object.001", None) + assert subject.get_object_name(obj) == "Object" + obj = bpy.data.objects.new("Object.foo.123", None) + assert subject.get_object_name(obj) == "Object.foo" + + class TestGetObjectRepresentation(NewFile): def test_run(self): ifc = ifcopenshell.file() @@ -113,3 +125,28 @@ class TestLinkObjectData(NewFile): class TestRunGeometryAddRepresntation(NewFile): def test_nothing(self): pass + + +class TestSetElementSpecificDisplaySettings(NewFile): + def test_opening_elements_display_as_wire(self): + ifc = ifcopenshell.file() + obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) + element = ifc.createIfcOpeningElement() + subject.set_element_specific_display_settings(obj, element) + assert obj.display_type == "WIRE" + + +class TestSetObjectName(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) + element = ifc.createIfcWall() + subject.set_object_name(obj, element) + assert obj.name == "IfcWall/Object" + + def test_existing_ifc_prefixes_are_not_repeated(self): + ifc = ifcopenshell.file() + obj = bpy.data.objects.new("IfcSlab/Object", bpy.data.meshes.new("Mesh")) + element = ifc.createIfcWall() + subject.set_object_name(obj, element) + assert obj.name == "IfcWall/Object"