From e179070978b27309751f2441f53ba912e0cae9d6 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Tue, 20 Sep 2022 16:25:52 +1000 Subject: [PATCH] New interface to show, select, and remove path connections --- .../bim/module/geometry/__init__.py | 5 ++- .../blenderbim/bim/module/geometry/data.py | 31 +++++++++++++++ .../bim/module/geometry/operator.py | 20 ++++++++++ .../blenderbim/bim/module/geometry/ui.py | 39 ++++++++++++++++++- src/blenderbim/blenderbim/core/geometry.py | 8 ++++ src/blenderbim/blenderbim/core/tool.py | 7 +++- src/blenderbim/blenderbim/tool/geometry.py | 13 +++++++ src/blenderbim/test/core/test_geometry.py | 12 ++++++ src/blenderbim/test/tool/test_geometry.py | 33 ++++++++++++++++ 9 files changed, 164 insertions(+), 4 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py index 8b4f03a097..92abfc43ab 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/__init__.py @@ -25,17 +25,20 @@ classes = ( operator.EditObjectPlacement, operator.GetRepresentationIfcParameters, operator.OverrideDelete, - operator.OverrideOutlinerDelete, operator.OverrideDuplicateMove, operator.OverrideDuplicateMoveLinked, + operator.OverrideOutlinerDelete, operator.OverridePasteBuffer, + operator.RemoveConnection, operator.RemoveRepresentation, + operator.SelectConnection, operator.SwitchRepresentation, operator.UpdateParametricRepresentation, operator.UpdateRepresentation, prop.BIMGeometryProperties, ui.BIM_PT_derived_placements, ui.BIM_PT_representations, + ui.BIM_PT_connections, ui.BIM_PT_mesh, ui.BIM_PT_workarounds, ) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/data.py b/src/blenderbim/blenderbim/bim/module/geometry/data.py index 5cfcc1113e..c9b94c5411 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/data.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/data.py @@ -25,6 +25,7 @@ from mathutils import Vector def refresh(): DerivedPlacementsData.is_loaded = False RepresentationsData.is_loaded = False + ConnectionsData.is_loaded = False class RepresentationsData: @@ -64,6 +65,36 @@ class RepresentationsData: return results +class ConnectionsData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.data = {"connections": cls.connections()} + cls.is_loaded = True + + @classmethod + def connections(cls): + results = [] + element = tool.Ifc.get_entity(bpy.context.active_object) + for rel in getattr(element, "ConnectedTo", []): + results.append({ + "id": rel.id(), + "is_relating": True, + "Name": rel.RelatedElement.Name or "Unnamed", + "ConnectionType": rel.RelatingConnectionType, + }) + for rel in getattr(element, "ConnectedFrom", []): + results.append({ + "id": rel.id(), + "is_relating": False, + "Name": rel.RelatingElement.Name or "Unnamed", + "ConnectionType": rel.RelatedConnectionType, + }) + return results + + class DerivedPlacementsData: data = {} is_loaded = False diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 2a73b23819..0ad8885de3 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -86,6 +86,26 @@ class AddRepresentation(bpy.types.Operator, Operator): ) +class SelectConnection(bpy.types.Operator, Operator): + bl_idname = "bim.select_connection" + bl_label = "Select Connection" + bl_options = {"REGISTER", "UNDO"} + connection: bpy.props.IntProperty() + + def _execute(self, context): + core.select_connection(tool.Geometry, connection=tool.Ifc.get().by_id(self.connection)) + + +class RemoveConnection(bpy.types.Operator, Operator): + bl_idname = "bim.remove_connection" + bl_label = "Remove Connection" + bl_options = {"REGISTER", "UNDO"} + connection: bpy.props.IntProperty() + + def _execute(self, context): + core.remove_connection(tool.Geometry, connection=tool.Ifc.get().by_id(self.connection)) + + class SwitchRepresentation(bpy.types.Operator, Operator): bl_idname = "bim.switch_representation" bl_label = "Switch Representation" diff --git a/src/blenderbim/blenderbim/bim/module/geometry/ui.py b/src/blenderbim/blenderbim/bim/module/geometry/ui.py index 5f7c918de0..be05825288 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/ui.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/ui.py @@ -20,7 +20,7 @@ import bpy from bpy.types import Panel from blenderbim.bim.ifc import IfcStore from blenderbim.bim.helper import prop_with_search -from blenderbim.bim.module.geometry.data import RepresentationsData, DerivedPlacementsData +from blenderbim.bim.module.geometry.data import RepresentationsData, ConnectionsData, DerivedPlacementsData class BIM_PT_representations(Panel): @@ -68,6 +68,43 @@ class BIM_PT_representations(Panel): row.operator("bim.remove_representation", icon="X", text="").representation_id = representation["id"] +class BIM_PT_connections(Panel): + bl_label = "IFC Connections" + bl_idname = "BIM_PT_connections" + bl_options = {"DEFAULT_CLOSED"} + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "object" + bl_parent_id = "BIM_PT_geometry_object" + + @classmethod + def poll(cls, context): + if not context.active_object: + return False + if not IfcStore.get_element(context.active_object.BIMObjectProperties.ifc_definition_id): + return False + return IfcStore.get_file() + + def draw(self, context): + if not ConnectionsData.is_loaded: + ConnectionsData.load() + + layout = self.layout + props = context.active_object.BIMObjectProperties + + if not ConnectionsData.data["connections"]: + layout.label(text="No connections found") + + for connection in ConnectionsData.data["connections"]: + row = self.layout.row(align=True) + row.label(text=connection["Name"], icon="SNAP_ON" if connection["is_relating"] else "SNAP_OFF") + row.label(text=connection["ConnectionType"]) + op = row.operator("bim.select_connection", icon="RESTRICT_SELECT_OFF", text="") + op.connection = connection["id"] + op = row.operator("bim.remove_connection", icon="X", text="") + op.connection = connection["id"] + + class BIM_PT_mesh(Panel): bl_label = "IFC Representation" bl_idname = "BIM_PT_mesh" diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index e4ac978016..2dc982af36 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -140,3 +140,11 @@ def remove_representation(ifc, geometry, obj=None, representation=None): geometry.replace_object_with_empty(obj) ifc.run("geometry.unassign_representation", product=element, representation=representation) ifc.run("geometry.remove_representation", representation=representation) + + +def select_connection(geometry, connection=None): + geometry.select_connection(connection) + + +def remove_connection(geometry, connection=None): + geometry.remove_connection(connection) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 8cfaab8d94..eb844155d9 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -237,11 +237,13 @@ class Geometry: def link(cls, element, obj): pass def record_object_materials(cls, obj): pass def record_object_position(cls, obj): pass + def remove_connection(cls, connection): pass def rename_object(cls, obj, name): pass def replace_object_with_empty(cls, obj): pass def resolve_mapped_representation(cls, representation): pass def run_geometry_update_representation(cls, obj=None): pass def run_style_add_style(cls, obj=None): pass + def select_connection(cls, connection): pass def should_force_faceted_brep(cls): pass def should_force_triangulation(cls): pass def should_generate_uvs(cls, obj): pass @@ -428,7 +430,8 @@ class Resource: def expand_resource(cls, resource): pass def contract_resource(cls, resource): pass def import_resources(cls, file_path): pass - + + @interface class Root: def add_dynamic_opening_voids(cls, element, obj): pass @@ -483,7 +486,7 @@ class Sequence: def disable_editing_task(cls): pass def get_task_time_attributes(cls): pass def load_task_resources(cls,resources): pass - def load_resources(cls): pass + def load_resources(cls): pass def get_task_inputs(cls, task): pass def load_task_inputs(cls, inputs): pass def load_task_outputs(cls, outputs): pass diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 15be6ddef3..360e237a31 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -261,6 +261,10 @@ class Geometry(blenderbim.core.tool.Geometry): obj.BIMObjectProperties.location_checksum = repr(np.array(obj.matrix_world.translation).tobytes()) obj.BIMObjectProperties.rotation_checksum = repr(np.array(obj.matrix_world.to_3x3()).tobytes()) + @classmethod + def remove_connection(cls, connection): + tool.Ifc.get().remove(connection) + @classmethod def rename_object(cls, obj, name): obj.name = name @@ -293,6 +297,15 @@ class Geometry(blenderbim.core.tool.Geometry): def run_style_add_style(cls, obj=None): return blenderbim.core.style.add_style(tool.Ifc, tool.Style, obj=obj) + @classmethod + def select_connection(cls, connection): + obj = tool.Ifc.get_object(connection.RelatingElement) + if obj: + obj.select_set(True) + obj = tool.Ifc.get_object(connection.RelatedElement) + if obj: + obj.select_set(True) + @classmethod def should_force_faceted_brep(cls): return bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep diff --git a/src/blenderbim/test/core/test_geometry.py b/src/blenderbim/test/core/test_geometry.py index 4eee95cc11..3ec625fb24 100644 --- a/src/blenderbim/test/core/test_geometry.py +++ b/src/blenderbim/test/core/test_geometry.py @@ -382,3 +382,15 @@ class TestRemoveRepresentation: ).should_be_called() ifc.run("geometry.remove_representation", representation="representation").should_be_called() subject.remove_representation(ifc, geometry, obj="obj", representation="representation") + + +class TestSelectConnection: + def test_run(self, geometry): + geometry.select_connection("connection").should_be_called() + subject.select_connection(geometry, connection="connection") + + +class TestRemoveConnection: + def test_run(self, geometry): + geometry.remove_connection("connection").should_be_called() + subject.remove_connection(geometry, connection="connection") diff --git a/src/blenderbim/test/tool/test_geometry.py b/src/blenderbim/test/tool/test_geometry.py index b6bd2ce162..9de2f899a3 100644 --- a/src/blenderbim/test/tool/test_geometry.py +++ b/src/blenderbim/test/tool/test_geometry.py @@ -379,6 +379,17 @@ class TestRecordObjectPosition(NewFile): assert obj.BIMObjectProperties.rotation_checksum == repr(np.array(obj.matrix_world.to_3x3()).tobytes()) +class TestRemoveConnection(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + element1 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + element2 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + rel = ifcopenshell.api.run("geometry.connect_path", ifc, relating_element=element1, related_element=element2) + subject.remove_connection(rel) + assert not tool.Ifc.get().by_type("IfcRelConnectsPathElements") + + class TestRenameObject(NewFile): def test_run(self): obj = bpy.data.meshes.new("Mesh") @@ -431,6 +442,28 @@ class TestRunStyleAddStyle(NewFile): pass +class TestSelectConnection(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + tool.Ifc().set(ifc) + + element1 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + obj1 = bpy.data.objects.new("Object", None) + bpy.context.scene.collection.objects.link(obj1) + tool.Ifc.link(element1, obj1) + + element2 = ifcopenshell.api.run("root.create_entity", ifc, ifc_class="IfcWall") + obj2 = bpy.data.objects.new("Object", None) + bpy.context.scene.collection.objects.link(obj2) + tool.Ifc.link(element2, obj2) + + rel = ifcopenshell.api.run("geometry.connect_path", ifc, relating_element=element1, related_element=element2) + + subject.select_connection(rel) + assert obj1 in bpy.context.selected_objects + assert obj2 in bpy.context.selected_objects + + class TestShouldForceFacetedBrep(NewFile): def test_run(self): result = bpy.context.scene.BIMGeometryProperties.should_force_faceted_brep