From 2b82eaf01940df87d5e847c46aa076cdf08f9474 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Fri, 17 Sep 2021 16:54:53 +1000 Subject: [PATCH] Fix bug when shifting object placements and their children. Add support for specifying placement matrixes in project units or SI units. --- .../api/geometry/edit_object_placement.py | 51 +-- .../geometry/test_edit_object_placement.py | 338 ++++++++++++++++++ 2 files changed, 369 insertions(+), 20 deletions(-) create mode 100644 src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py 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 1400a768b3..93536bddfb 100644 --- a/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py +++ b/src/ifcopenshell-python/ifcopenshell/api/geometry/edit_object_placement.py @@ -8,40 +8,35 @@ import ifcopenshell.util.placement class Usecase: def __init__(self, file, **settings): self.file = file - self.settings = {"product": None, "matrix": np.eye(4), "should_transform_children": False} + self.settings = {"product": None, "matrix": np.eye(4), "is_si": True, "should_transform_children": False} for key, value in settings.items(): self.settings[key] = value def execute(self): + print('EXECUTING', self.settings["product"]) if not hasattr(self.settings["product"], "ObjectPlacement"): return self.unit_scale = ifcopenshell.util.unit.calculate_unit_scale(self.file) + if not self.settings["is_si"]: + self.settings["matrix"][0][3] *= self.unit_scale + self.settings["matrix"][1][3] *= self.unit_scale + self.settings["matrix"][2][3] *= self.unit_scale + children_settings = [] if not self.settings["should_transform_children"]: + print("THIS IS RUNNING") children_settings = self.get_children_settings(self.settings["product"].ObjectPlacement) - placement_rel_to = None - if hasattr(self.settings["product"], "ContainedInStructure") and self.settings["product"].ContainedInStructure: - placement_rel_to = self.settings["product"].ContainedInStructure[0].RelatingStructure.ObjectPlacement - elif hasattr(self.settings["product"], "Decomposes") and self.settings["product"].Decomposes: - relating_object = self.settings["product"].Decomposes[0].RelatingObject - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - elif hasattr(self.settings["product"], "VoidsElements") and self.settings["product"].VoidsElements: - relating_object = self.settings["product"].VoidsElements[0].RelatingBuildingElement - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - elif hasattr(self.settings["product"], "FillsVoids") and self.settings["product"].FillsVoids: - relating_object = self.settings["product"].FillsVoids[0].RelatingOpeningElement - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - elif hasattr(self.settings["product"], "ProjectsElements") and self.settings["product"].ProjectsElements: - relating_object = self.settings["product"].ProjectsElements[0].RelatingElement - placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None - + placement_rel_to = self.get_placement_rel_to() placement = self.file.createIfcLocalPlacement(placement_rel_to, self.get_relative_placement(placement_rel_to)) old_placement = self.settings["product"].ObjectPlacement - if old_placement and len(self.file.get_inverse(old_placement)) == 1: - old_placement.PlacementRelTo = None + if old_placement: self.settings["product"].ObjectPlacement = None + inverses = self.file.get_inverse(old_placement) + for inverse in inverses: + ifcopenshell.util.element.replace_attribute(inverse, old_placement, placement) + old_placement.PlacementRelTo = None ifcopenshell.util.element.remove_deep(self.file, old_placement) self.settings["product"].ObjectPlacement = placement @@ -53,6 +48,22 @@ class Usecase: return placement + def get_placement_rel_to(self): + if getattr(self.settings["product"], "ContainedInStructure", None): + return self.settings["product"].ContainedInStructure[0].RelatingStructure.ObjectPlacement + elif getattr(self.settings["product"], "Decomposes", None): + relating_object = self.settings["product"].Decomposes[0].RelatingObject + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + elif getattr(self.settings["product"], "VoidsElements", None): + relating_object = self.settings["product"].VoidsElements[0].RelatingBuildingElement + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + elif getattr(self.settings["product"], "FillsVoids", None): + relating_object = self.settings["product"].FillsVoids[0].RelatingOpeningElement + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + elif getattr(self.settings["product"], "ProjectsElements", None): + relating_object = self.settings["product"].ProjectsElements[0].RelatingElement + return relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None + def get_children_settings(self, placement): if not placement: return [] @@ -60,7 +71,7 @@ class Usecase: for referenced_placement in placement.ReferencedByPlacements: matrix = ifcopenshell.util.placement.get_local_placement(referenced_placement) for obj in referenced_placement.PlacesObject: - results.append({"product": obj, "matrix": matrix, "should_transform_children": False}) + results.append({"product": obj, "matrix": matrix, "is_si": self.settings["is_si"], "should_transform_children": False}) results.extend(self.get_children_settings(referenced_placement)) return results 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 new file mode 100644 index 0000000000..339e06e5aa --- /dev/null +++ b/src/ifcopenshell-python/test/api/geometry/test_edit_object_placement.py @@ -0,0 +1,338 @@ +import numpy +import pytest +import test.bootstrap +import ifcopenshell.api +import ifcopenshell.util.placement + + +class TestEditObjectPlacement(test.bootstrap.IFC4): + def test_attemping_to_edit_the_placement_of_an_invalid_element(self): + project = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProject") + result = ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=project) + assert result is None + + def test_setting_an_object_placement(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="IfcWall") + matrix = 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), + ) + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + + def test_setting_an_object_placement_using_si_units(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="IfcWall") + matrix = 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), + ) + ) + matrix_millimeters = numpy.array( + ( + (1.0, 0.0, 0.0, 1000.0), + (0.0, 1.0, 0.0, 2000.0), + (0.0, 0.0, 1.0, 3000.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run("geometry.edit_object_placement", self.file, product=element, matrix=matrix, is_si=True) + assert numpy.array_equal( + ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix_millimeters + ) + + def test_changing_an_object_placement(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="IfcWall") + matrix1 = 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), + ) + ) + matrix2 = numpy.array( + ( + (1.0, 0.0, 0.0, 4.0), + (0.0, 1.0, 0.0, 5.0), + (0.0, 0.0, 1.0, 6.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix1.copy(), is_si=False + ) + created_element_ids = [e.id() for e in self.file.traverse(element.ObjectPlacement)] + ifcopenshell.api.run( + "geometry.edit_object_placement", self.file, product=element, matrix=matrix2.copy(), is_si=False + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix2) + for element_id in created_element_ids: + with pytest.raises(RuntimeError): + self.file.by_id(element_id) + + def test_changing_placements_relative_to_a_spatial_container(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="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + 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), + ) + ) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + 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 + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_an_aggregate(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="IfcElementAssembly") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcBeam") + 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), + ) + ) + ifcopenshell.api.run("aggregate.assign_object", self.file, product=subelement, relating_object=element) + 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 + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_a_voided_element(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="IfcWall") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcOpeningElement") + 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), + ) + ) + ifcopenshell.api.run("void.add_opening", self.file, opening=subelement, element=element) + 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 + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_an_opening(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="IfcOpeningElement") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcDoor") + 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), + ) + ) + ifcopenshell.api.run("void.add_filling", self.file, element=subelement, opening=element) + 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 + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_relative_to_a_projected_element(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="IfcWall") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcProjectionElement") + 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), + ) + ) + self.file.create_entity( + "IfcRelProjectsElement", + **{ + "GlobalId": ifcopenshell.guid.new(), + "RelatingElement": element, + "RelatedFeatureElement": subelement, + } + ) + 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 + ) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(element.ObjectPlacement), matrix) + assert numpy.array_equal(ifcopenshell.util.placement.get_local_placement(subelement.ObjectPlacement), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_without_affecting_children(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="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + 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), + ) + ) + ifcopenshell.api.run("spatial.assign_container", self.file, product=subelement, relating_structure=element) + 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 + ) + 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), submatrix) + assert subelement.ObjectPlacement.PlacementRelTo == element.ObjectPlacement + + def test_changing_placements_with_affecting_children(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="IfcBuilding") + subelement = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcWall") + 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("spatial.assign_container", self.file, product=subelement, relating_structure=element) + 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=True, + ) + 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