From 53c050c6ed9cdd64a72b59ec4ee41a4c99fee9b2 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Thu, 5 May 2022 18:00:42 +1000 Subject: [PATCH] Port locations now sync relative to their parent even when not actively displayed in the viewport --- .../blenderbim/bim/module/model/mep.py | 6 +-- .../blenderbim/bim/module/model/product.py | 9 ++-- .../blenderbim/bim/module/system/operator.py | 2 +- src/blenderbim/blenderbim/core/geometry.py | 11 +++-- src/blenderbim/blenderbim/core/system.py | 6 ++- .../docs/users/exploring_an_ifc_model.rst | 4 +- src/blenderbim/test/core/test_system.py | 8 ++- .../api/geometry/edit_object_placement.py | 4 ++ .../ifcopenshell/api/root/copy_class.py | 15 ++++-- .../geometry/test_edit_object_placement.py | 49 ++++++++++++++++++- .../test/api/library/test_assign_reference.py | 2 +- .../test/api/root/test_copy_class.py | 2 + .../test/util/test_element.py | 2 +- 13 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/mep.py b/src/blenderbim/blenderbim/bim/module/model/mep.py index b653eadbb6..3fe07c3fe8 100644 --- a/src/blenderbim/blenderbim/bim/module/model/mep.py +++ b/src/blenderbim/blenderbim/bim/module/model/mep.py @@ -110,11 +110,9 @@ class MepGenerator: end_port_matrix = Matrix.Translation((0, 0, self.length)) for mat in [start_port_matrix, end_port_matrix]: - port_obj = bpy.data.objects.new("Port", None) - port_obj.matrix_world = mat - port_obj.parent = obj - port = tool.System.run_root_assign_class(obj=port_obj, ifc_class="IfcDistributionPort") + port = tool.Ifc.run("root.create_entity", ifc_class="IfcDistributionPort") tool.Ifc.run("system.assign_port", element=element, port=port) + tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True) obj.select_set(True) return obj diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index d9fe8797db..b56712216d 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -142,11 +142,10 @@ class AddTypeInstance(bpy.types.Operator): mat[0][3] *= unit_scale mat[1][3] *= unit_scale mat[2][3] *= unit_scale - port_obj = bpy.data.objects.new("Port", None) - port_obj.matrix_world = mathutils.Matrix(mat) - port_obj.parent = obj - port = tool.System.run_root_assign_class(obj=port_obj, ifc_class="IfcDistributionPort") - tool.Ifc.run("system.assign_port", element=element, port=port) + mat = obj.matrix_world @ mathutils.Matrix(mat) + new_port = tool.Ifc.run("root.create_entity", ifc_class="IfcDistributionPort") + tool.Ifc.run("system.assign_port", element=element, port=new_port) + tool.Ifc.run("geometry.edit_object_placement", product=new_port, matrix=mat, is_si=True) bpy.ops.object.select_all(action="DESELECT") obj.select_set(True) diff --git a/src/blenderbim/blenderbim/bim/module/system/operator.py b/src/blenderbim/blenderbim/bim/module/system/operator.py index 12e78cda4d..94349cfd74 100644 --- a/src/blenderbim/blenderbim/bim/module/system/operator.py +++ b/src/blenderbim/blenderbim/bim/module/system/operator.py @@ -140,7 +140,7 @@ class ShowPorts(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.show_ports(tool.System, element=tool.Ifc.get_entity(context.active_object)) + core.show_ports(tool.Ifc, tool.System, element=tool.Ifc.get_entity(context.active_object)) class HidePorts(bpy.types.Operator, Operator): diff --git a/src/blenderbim/blenderbim/core/geometry.py b/src/blenderbim/blenderbim/core/geometry.py index 6ed2e53f12..65742a684e 100644 --- a/src/blenderbim/blenderbim/core/geometry.py +++ b/src/blenderbim/blenderbim/core/geometry.py @@ -21,11 +21,12 @@ import blenderbim.core.style def edit_object_placement(ifc, geometry, surveyor, obj=None): element = ifc.get_entity(obj) - if element: - geometry.clear_cache(element) - geometry.clear_scale(obj) - ifc.run("geometry.edit_object_placement", product=element, matrix=surveyor.get_absolute_matrix(obj)) - geometry.record_object_position(obj) + if not element: + return + geometry.clear_cache(element) + geometry.clear_scale(obj) + ifc.run("geometry.edit_object_placement", product=element, matrix=surveyor.get_absolute_matrix(obj)) + geometry.record_object_position(obj) def add_representation( diff --git a/src/blenderbim/blenderbim/core/system.py b/src/blenderbim/blenderbim/core/system.py index 7153ab8c53..490739bd0e 100644 --- a/src/blenderbim/blenderbim/core/system.py +++ b/src/blenderbim/blenderbim/core/system.py @@ -66,7 +66,11 @@ def select_system_products(system_tool, system=None): system_tool.select_system_products(system) -def show_ports(system, element=None): +def show_ports(ifc, system, element=None): + obj = ifc.get_object(element) + if obj and ifc.is_moved(obj): + system.run_geometry_edit_object_placement(obj=obj) + ports = system.get_ports(element) system.load_ports(element, ports) system.select_elements(ports) diff --git a/src/blenderbim/docs/users/exploring_an_ifc_model.rst b/src/blenderbim/docs/users/exploring_an_ifc_model.rst index 72e099caab..a4754afab6 100644 --- a/src/blenderbim/docs/users/exploring_an_ifc_model.rst +++ b/src/blenderbim/docs/users/exploring_an_ifc_model.rst @@ -65,8 +65,8 @@ the **Navigate Gizmo**. .. image:: navigate-gizmo.png You can also use your mouse to navigate. Hover your mouse over the **Viewport** -panel and click and drag the Middle Mouse Button (MMB) to **Orbit**. Scroll the -mousewheel to **Zoom**, and use Shift-MMB to **Pan**. +panel and click and drag the Middle Mouse Button (``MMB``) to **Orbit**. Scroll +the mousewheel to **Zoom**, and use ``Shift-MMB`` to **Pan**. If you have a numpad, you can use the numpad keys to quickly switch to top, front, or side view. Use ``7`` for top view, ``1`` for front view, and ``3`` for diff --git a/src/blenderbim/test/core/test_system.py b/src/blenderbim/test/core/test_system.py index 3d818fe301..df8ff35228 100644 --- a/src/blenderbim/test/core/test_system.py +++ b/src/blenderbim/test/core/test_system.py @@ -91,11 +91,15 @@ class TestSelectSystemProducts: class TestShowPorts: - def test_run(self, system): + def test_run(self, ifc, system): + ifc.get_object("element").should_be_called().will_return("obj") + ifc.is_moved("obj").should_be_called().will_return(True) + system.run_geometry_edit_object_placement(obj="obj").should_be_called() + system.get_ports("element").should_be_called().will_return(["port"]) system.load_ports("element", ["port"]).should_be_called() system.select_elements(["port"]).should_be_called() - subject.show_ports(system, element="element") + subject.show_ports(ifc, system, element="element") class TestHidePorts: diff --git a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py index caad38bb05..f604d5a3da 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py @@ -103,6 +103,10 @@ class Usecase: for referenced_placement in placement.ReferencedByPlacements: matrix = ifcopenshell.util.placement.get_local_placement(referenced_placement) for obj in referenced_placement.PlacesObject: + # Although a port is technically a nested child, it is generally + # more intuitive that the ports always move with the parent. + if obj.is_a("IfcDistributionPort"): + continue results.append({"product": obj, "matrix": matrix, "is_si": False, "should_transform_children": False}) results.extend(self.get_children_settings(referenced_placement)) return results diff --git a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py index acb57a9979..15ca564142 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py +++ b/src/ifcopenshell-python/ifcopenshell/api/root/copy_class.py @@ -49,13 +49,20 @@ class Usecase: ports = [e for e in inverse.RelatedObjects if e.is_a("IfcDistributionPort")] if ports: new_ports = [ifcopenshell.api.run("root.copy_class", self.file, product=p) for p in ports] - [ - ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=p) - for p in new_ports - ] inverse = ifcopenshell.util.element.copy(self.file, inverse) inverse.RelatingObject = to_element inverse.RelatedObjects = new_ports + for port in new_ports: + ifcopenshell.api.run("system.unassign_port", self.file, element=from_element, port=port) + matrix = ifcopenshell.util.placement.get_local_placement(port.ObjectPlacement) + ifcopenshell.api.run( + "geometry.edit_object_placement", + self.file, + product=port, + matrix=matrix, + is_si=False, + should_transform_children=False, + ) elif inverse.is_a("IfcRelAggregates") and inverse.RelatingObject == from_element: continue elif inverse.is_a("IfcRelContainedInSpatialStructure") and inverse.RelatingStructure == from_element: diff --git a/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py b/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py index 384c5fc5d3..e213ed21a2 100644 --- a/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py +++ b/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py @@ -460,8 +460,55 @@ class TestEditObjectPlacement(test.bootstrap.IFC4): assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + def test_changing_placements_always_affecting_child_ports_as_a_special_case(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) + element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcChiller") + subelement = ifcopenshell.api.run("system.add_port", self.file, element=element) + matrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 1.0), + (0.0, 0.0, 1.0, 1.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 2.0), + (0.0, 0.0, 1.0, 3.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + shifted_submatrix = numpy.array( + ( + (1.0, 0.0, 0.0, 1.0), + (0.0, 1.0, 0.0, 3.0), + (0.0, 0.0, 1.0, 5.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=subelement, matrix=submatrix.copy(), is_si=False + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", + self.file, + product=element, + matrix=submatrix.copy(), + is_si=False, + should_transform_children=False, + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), submatrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), shifted_submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement -class TestEditObjectPlacement(test.bootstrap.IFC2X3): + +class TestEditObjectPlacementIFC2X3(test.bootstrap.IFC2X3): def test_changing_placements_relative_to_a_distribution_element(self): ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") ifcopenshell.api.run("unit.assign_unit", self.file) diff --git a/src/ifcopenshell-python/test/api/library/test_assign_reference.py b/src/ifcopenshell-python/test/api/library/test_assign_reference.py index 8565514502..1133b592d6 100644 --- a/src/ifcopenshell-python/test/api/library/test_assign_reference.py +++ b/src/ifcopenshell-python/test/api/library/test_assign_reference.py @@ -38,7 +38,7 @@ class TestAssignReference(test.bootstrap.IFC4): assert reference.LibraryRefForObjects[0].RelatedObjects == (product,) -class TestAssignReference(test.bootstrap.IFC2X3): +class TestAssignReferenceIFC2X3(test.bootstrap.IFC2X3): def test_assigning_a_reference(self): reference = self.file.createIfcLibraryReference() product = self.file.createIfcWall() diff --git a/src/ifcopenshell-python/test/api/root/test_copy_class.py b/src/ifcopenshell-python/test/api/root/test_copy_class.py index fb67ac3214..ce9875c475 100644 --- a/src/ifcopenshell-python/test/api/root/test_copy_class.py +++ b/src/ifcopenshell-python/test/api/root/test_copy_class.py @@ -151,6 +151,8 @@ class TestCopyClass(test.bootstrap.IFC4): assert not new.Types def test_copying_distribution_ports(self): + ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + ifcopenshell.api.run("unit.assign_unit", self.file) element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcChiller") port = ifcopenshell.api.run("system.add_port", self.file) ifcopenshell.api.run("system.assign_port", self.file, element=element, port=port) diff --git a/src/ifcopenshell-python/test/util/test_element.py b/src/ifcopenshell-python/test/util/test_element.py index e2b9c72a80..c00931252d 100644 --- a/src/ifcopenshell-python/test/util/test_element.py +++ b/src/ifcopenshell-python/test/util/test_element.py @@ -363,7 +363,7 @@ class TestGetlayers(test.bootstrap.IFC4): assert subject.get_layers(self.file, element) == [layer] -class TestGetlayers(test.bootstrap.IFC2X3): +class TestGetlayersIFC2X3(test.bootstrap.IFC2X3): def test_getting_the_layer_of_a_product_item(self): element = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") layer = ifcopenshell.api.run("layer.add_layer", self.file)