From 503f0399ff327cecd5dbc3bfae36ec1d9f070a5c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Wed, 20 Oct 2021 18:47:55 +1100 Subject: [PATCH] Fix bug where you couldn't bulk copy non-geometric elements to other spatial containers --- .../bim/module/geometry/operator.py | 22 +++++- .../blenderbim/bim/module/pset/operator.py | 1 + .../blenderbim/bim/module/root/operator.py | 2 +- .../blenderbim/bim/module/spatial/operator.py | 43 +++--------- .../blenderbim/bim/module/spatial/ui.py | 2 +- src/blenderbim/blenderbim/core/spatial.py | 18 +++++ src/blenderbim/blenderbim/core/tool.py | 23 ++++--- src/blenderbim/blenderbim/tool/__init__.py | 2 +- .../tool/{container.py => spatial.py} | 41 ++++++++++- .../test/bim/feature/spatial.feature | 12 ++++ src/blenderbim/test/core/bootstrap.py | 14 ++-- src/blenderbim/test/core/test_spatial.py | 65 ++++++++++++----- .../{test_container.py => test_spatial.py} | 69 +++++++++++++++++-- 13 files changed, 234 insertions(+), 80 deletions(-) rename src/blenderbim/blenderbim/tool/{container.py => spatial.py} (67%) rename src/blenderbim/test/tool/{test_container.py => test_spatial.py} (74%) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index 14db0ad3cf..7ef14aa8e0 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -312,22 +312,42 @@ 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) for obj in context.selected_objects: if obj == context.active_object: continue if obj.data: + element = tool.Ifc.get_entity(obj) + if not element: + continue bm.to_mesh(obj.data) + old_rep = self.get_representation_by_context(element, geometric_context) + if old_rep: + ifcopenshell.api.run( + "geometry.unassign_representation", tool.Ifc.get(), product=element, representation=old_rep + ) + ifcopenshell.api.run("geometry.remove_representation", tool.Ifc.get(), representation=old_rep) core.add_representation( tool.Ifc, tool.Geometry, tool.Style, tool.Surveyor, obj=obj, - context=tool.Ifc.get().by_id(int(context.scene.BIMProperties.contexts)), + context=geometric_context, ifc_representation_class=None, profile_set_usage=None, ) + def get_representation_by_context(self, element, context): + if element.is_a("IfcProduct") and element.Representation: + for r in element.Representation.Representations: + if r.ContextOfItems == context: + return r + elif element.is_a("IfcTypeProduct") and element.RepresentationMaps: + for r in element.RepresentationMaps: + if r.MappedRepresentation.ContextOfItems == context: + return r.MappedRepresentation + class OverrideDelete(bpy.types.Operator): bl_idname = "object.delete" diff --git a/src/blenderbim/blenderbim/bim/module/pset/operator.py b/src/blenderbim/blenderbim/bim/module/pset/operator.py index 976c76a893..fc9fd6386a 100644 --- a/src/blenderbim/blenderbim/bim/module/pset/operator.py +++ b/src/blenderbim/blenderbim/bim/module/pset/operator.py @@ -29,6 +29,7 @@ from ifcopenshell.api.pset.data import Data from ifcopenshell.api.cost.data import Data as CostData from blenderbim.bim.module.pset.qto_calculator import QtoCalculator + def get_pset_props(context, obj, obj_type): if obj_type == "Object": obj = bpy.data.objects.get(obj) diff --git a/src/blenderbim/blenderbim/bim/module/root/operator.py b/src/blenderbim/blenderbim/bim/module/root/operator.py index 7d0a320fd8..fbc8a46676 100644 --- a/src/blenderbim/blenderbim/bim/module/root/operator.py +++ b/src/blenderbim/blenderbim/bim/module/root/operator.py @@ -269,7 +269,7 @@ class AssignClass(bpy.types.Operator): blenderbim.core.spatial.assign_container( tool.Ifc, tool.Collector, - tool.Container, + tool.Spatial, structure_obj=spatial_obj, element_obj=obj, ) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index c63efc9d1e..4f60148f68 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -44,7 +44,7 @@ class AssignContainer(bpy.types.Operator, Operator): structure_obj = tool.Ifc.get_object(tool.Ifc.get().by_id(self.structure)) for element_obj in context.selected_objects: core.assign_container( - tool.Ifc, tool.Collector, tool.Container, structure_obj=structure_obj, element_obj=element_obj + tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj ) @@ -54,7 +54,7 @@ class EnableEditingContainer(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.enable_editing_container(tool.Container, obj=context.active_object) + core.enable_editing_container(tool.Spatial, obj=context.active_object) class ChangeSpatialLevel(bpy.types.Operator, Operator): @@ -64,7 +64,7 @@ class ChangeSpatialLevel(bpy.types.Operator, Operator): parent: bpy.props.IntProperty() def _execute(self, context): - core.change_spatial_level(tool.Container, parent=tool.Ifc.get().by_id(self.parent)) + core.change_spatial_level(tool.Spatial, parent=tool.Ifc.get().by_id(self.parent)) class DisableEditingContainer(bpy.types.Operator, Operator): @@ -73,7 +73,7 @@ class DisableEditingContainer(bpy.types.Operator, Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - core.disable_editing_container(tool.Container, obj=context.active_object) + core.disable_editing_container(tool.Spatial, obj=context.active_object) class RemoveContainer(bpy.types.Operator, Operator): @@ -86,7 +86,7 @@ class RemoveContainer(bpy.types.Operator, Operator): core.remove_container(tool.Ifc, tool.Collector, obj=obj) -class CopyToContainer(bpy.types.Operator): +class CopyToContainer(bpy.types.Operator, Operator): """ Copies selected objects to selected containers Check the mark next to a container in the container list to select it @@ -96,37 +96,10 @@ class CopyToContainer(bpy.types.Operator): bl_idname = "bim.copy_to_container" bl_label = "Copy To Container" bl_options = {"REGISTER", "UNDO"} - obj: bpy.props.StringProperty() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - self.file = IfcStore.get_file() - objects = list(bpy.data.objects.get(self.obj, context.selected_objects)) sprops = context.scene.BIMSpatialProperties - container_ids = [c.ifc_definition_id for c in sprops.containers if c.is_selected] - for obj in objects: - container = ifcopenshell.util.element.get_container( - self.file.by_id(obj.BIMObjectProperties.ifc_definition_id) - ) - if container: - container_obj = IfcStore.get_element(container.id()) - local_position = container_obj.matrix_world.inverted() @ obj.matrix_world - else: - local_position = obj.matrix_world - - for container_id in container_ids: - container_obj = IfcStore.get_element(container_id) - if not container_obj: - continue - new_obj = obj.copy() - new_obj.data = obj.data.copy() - new_obj.matrix_world = container_obj.matrix_world @ local_position - blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Root, obj=new_obj) - core.assign_container( - tool.Ifc, tool.Collector, tool.Container, structure_obj=container_obj, element_obj=new_obj - ) + containers = [tool.Ifc.get().by_id(c.ifc_definition_id) for c in sprops.containers if c.is_selected] + for obj in context.selected_objects: + core.copy_to_container(tool.Ifc, tool.Spatial, obj=obj, containers=containers) blenderbim.bim.handler.purge_module_data() - obj.BIMObjectSpatialProperties.is_editing = False - return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index ca4878f777..fbe92372c6 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -51,7 +51,7 @@ class BIM_PT_spatial(Panel): if SpatialData.data["parent_container_id"]: op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV") op.parent = SpatialData.data["parent_container_id"] - if props.active_container_index <= len(props.containers): + if props.containers and props.active_container_index < len(props.containers): op = row.operator("bim.assign_container", icon="CHECKMARK") op.structure = props.containers[props.active_container_index].ifc_definition_id row.operator("bim.copy_to_container", icon="COPYDOWN", text="") diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index d54b35b26d..a5e64a602c 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -48,3 +48,21 @@ def change_spatial_level(container, parent=None): def remove_container(ifc, collector, obj=None): ifc.run("spatial.remove_container", product=ifc.get_entity(obj)) collector.assign(obj) + + +def copy_to_container(ifc, spatial, obj=None, containers=None): + element = ifc.get_entity(obj) + if not element: + return + from_container = spatial.get_container(element) + if from_container: + matrix = spatial.get_relative_object_matrix(obj, ifc.get_object(from_container)) + else: + matrix = spatial.get_object_matrix(obj) + for to_container in containers: + to_container_obj = ifc.get_object(to_container) + copied_obj = spatial.duplicate_object_and_data(obj) + spatial.set_relative_object_matrix(copied_obj, to_container_obj, matrix) + spatial.run_root_copy_class(obj=copied_obj) + spatial.run_spatial_assign_container(structure_obj=to_container_obj, element_obj=copied_obj) + spatial.disable_editing(obj) diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 3dcd76a49f..e653194800 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -46,14 +46,6 @@ class Collector: def assign(cls, obj): pass -@interface -class Container: - def can_contain(cls, structure_obj, element_obj): pass - def disable_editing(cls, obj): pass - def enable_editing(cls, obj): pass - def import_containers(cls, parent=None): pass - - @interface class Context: def clear_context(cls): pass @@ -158,6 +150,21 @@ class Selector: def set_active(cls, obj): pass +@interface +class Spatial: + def can_contain(cls, structure_obj, element_obj): pass + def disable_editing(cls, obj): pass + def duplicate_object_and_data(cls, obj): pass + def enable_editing(cls, obj): pass + def get_container(cls, element): pass + def get_object_matrix(cls, obj): pass + def get_relative_object_matrix(cls, target_obj, relative_to_obj): pass + def import_containers(cls, parent=None): pass + def run_root_copy_class(cls, obj=None): pass + def run_spatial_assign_container(cls, structure_obj=None, element_obj=None): pass + def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass + + @interface class Style: def disable_editing(cls, obj): pass diff --git a/src/blenderbim/blenderbim/tool/__init__.py b/src/blenderbim/blenderbim/tool/__init__.py index 05c17e05e1..3c8c505572 100644 --- a/src/blenderbim/blenderbim/tool/__init__.py +++ b/src/blenderbim/blenderbim/tool/__init__.py @@ -19,7 +19,6 @@ from blenderbim.tool.aggregate import Aggregate from blenderbim.tool.blender import Blender from blenderbim.tool.collector import Collector -from blenderbim.tool.container import Container from blenderbim.tool.context import Context from blenderbim.tool.geometry import Geometry from blenderbim.tool.ifc import Ifc @@ -27,6 +26,7 @@ from blenderbim.tool.material import Material from blenderbim.tool.misc import Misc from blenderbim.tool.owner import Owner from blenderbim.tool.root import Root +from blenderbim.tool.spatial import Spatial from blenderbim.tool.style import Style from blenderbim.tool.surveyor import Surveyor from blenderbim.tool.type import Type diff --git a/src/blenderbim/blenderbim/tool/container.py b/src/blenderbim/blenderbim/tool/spatial.py similarity index 67% rename from src/blenderbim/blenderbim/tool/container.py rename to src/blenderbim/blenderbim/tool/spatial.py index d651b17226..a141868661 100644 --- a/src/blenderbim/blenderbim/tool/container.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -19,10 +19,12 @@ import bpy import ifcopenshell import blenderbim.core.tool +import blenderbim.core.root +import blenderbim.core.spatial import blenderbim.tool as tool -class Container(blenderbim.core.tool.Container): +class Spatial(blenderbim.core.tool.Spatial): @classmethod def can_contain(cls, structure_obj, element_obj): structure = tool.Ifc.get_entity(structure_obj) @@ -39,13 +41,32 @@ class Container(blenderbim.core.tool.Container): return False return True + @classmethod + def disable_editing(cls, obj): + obj.BIMObjectSpatialProperties.is_editing = False + + @classmethod + def duplicate_object_and_data(cls, obj): + new_obj = obj.copy() + if obj.data: + new_obj.data = obj.data.copy() + return new_obj + @classmethod def enable_editing(cls, obj): obj.BIMObjectSpatialProperties.is_editing = True @classmethod - def disable_editing(cls, obj): - obj.BIMObjectSpatialProperties.is_editing = False + def get_container(cls, element): + return ifcopenshell.util.element.get_container(element) + + @classmethod + def get_object_matrix(cls, obj): + return obj.matrix_world + + @classmethod + def get_relative_object_matrix(cls, target_obj, relative_to_obj): + return relative_to_obj.matrix_world.inverted() @ target_obj.matrix_world @classmethod def import_containers(cls, parent=None): @@ -69,3 +90,17 @@ class Container(blenderbim.core.tool.Container): new.long_name = element.LongName or "" new.has_decomposition = bool(element.IsDecomposedBy) new.ifc_definition_id = element.id() + + @classmethod + def run_root_copy_class(cls, obj=None): + return blenderbim.core.root.copy_class(tool.Ifc, tool.Collector, tool.Root, obj=obj) + + @classmethod + def run_spatial_assign_container(cls, structure_obj=None, element_obj=None): + return blenderbim.core.spatial.assign_container( + tool.Ifc, tool.Collector, tool.Spatial, structure_obj=structure_obj, element_obj=element_obj + ) + + @classmethod + def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): + target_obj.matrix_world = relative_to_obj.matrix_world @ matrix diff --git a/src/blenderbim/test/bim/feature/spatial.feature b/src/blenderbim/test/bim/feature/spatial.feature index 18e2b354e1..31e200c128 100644 --- a/src/blenderbim/test/bim/feature/spatial.feature +++ b/src/blenderbim/test/bim/feature/spatial.feature @@ -28,3 +28,15 @@ Scenario: Assign container And the variable "site" is "tool.Ifc.get().by_type('IfcSite')[0].id()" When I press "bim.assign_container(structure={site})" Then the object "IfcWall/Cube" is in the collection "IfcSite/My Site" + +Scenario: Copy to container + 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 the object "IfcWall/Cube" is selected + And I press "bim.enable_editing_container" + When I set "scene.BIMSpatialProperties.containers[0].is_selected" to "True" + And I press "bim.copy_to_container" + Then the object "IfcWall/Cube.001" is in the collection "IfcSite/My Site" diff --git a/src/blenderbim/test/core/bootstrap.py b/src/blenderbim/test/core/bootstrap.py index 805900acf1..8982f8d9b9 100644 --- a/src/blenderbim/test/core/bootstrap.py +++ b/src/blenderbim/test/core/bootstrap.py @@ -49,13 +49,6 @@ def collector(): prophet.verify() -@pytest.fixture -def container(): - prophet = Prophecy(blenderbim.core.tool.Container) - yield prophet - prophet.verify() - - @pytest.fixture def context(): prophet = Prophecy(blenderbim.core.tool.Context) @@ -105,6 +98,13 @@ def selector(): prophet.verify() +@pytest.fixture +def spatial(): + prophet = Prophecy(blenderbim.core.tool.Spatial) + yield prophet + prophet.verify() + + @pytest.fixture def style(): prophet = Prophecy(blenderbim.core.tool.Style) diff --git a/src/blenderbim/test/core/test_spatial.py b/src/blenderbim/test/core/test_spatial.py index d0c768d683..1b835428e1 100644 --- a/src/blenderbim/test/core/test_spatial.py +++ b/src/blenderbim/test/core/test_spatial.py @@ -17,44 +17,42 @@ # along with BlenderBIM Add-on. If not, see . import blenderbim.core.spatial as subject -from test.core.bootstrap import ifc, collector, container +from test.core.bootstrap import ifc, collector, spatial class TestAssignContainer: - def test_run(self, ifc, collector, container): - container.can_contain("structure_obj", "element_obj").should_be_called().will_return(True) + def test_run(self, ifc, collector, spatial): + spatial.can_contain("structure_obj", "element_obj").should_be_called().will_return(True) ifc.get_entity("structure_obj").should_be_called().will_return("structure") ifc.get_entity("element_obj").should_be_called().will_return("element") ifc.run( "spatial.assign_container", product="element", relating_structure="structure" ).should_be_called().will_return("rel") - container.disable_editing("element_obj").should_be_called() + spatial.disable_editing("element_obj").should_be_called() collector.assign("element_obj").should_be_called() assert ( - subject.assign_container( - ifc, collector, container, structure_obj="structure_obj", element_obj="element_obj" - ) + subject.assign_container(ifc, collector, spatial, structure_obj="structure_obj", element_obj="element_obj") == "rel" ) class TestEnableEditingContainer: - def test_run(self, container): - container.enable_editing("obj").should_be_called() - container.import_containers().should_be_called() - subject.enable_editing_container(container, obj="obj") + def test_run(self, spatial): + spatial.enable_editing("obj").should_be_called() + spatial.import_containers().should_be_called() + subject.enable_editing_container(spatial, obj="obj") class TestDisableEditingContainer: - def test_run(self, container): - container.disable_editing("obj").should_be_called() - subject.disable_editing_container(container, obj="obj") + def test_run(self, spatial): + spatial.disable_editing("obj").should_be_called() + subject.disable_editing_container(spatial, obj="obj") class TestChangeSpatialLevel: - def test_run(self, container): - container.import_containers(parent="parent").should_be_called() - subject.change_spatial_level(container, parent="parent") + def test_run(self, spatial): + spatial.import_containers(parent="parent").should_be_called() + subject.change_spatial_level(spatial, parent="parent") class TestRemoveContainer: @@ -63,3 +61,36 @@ class TestRemoveContainer: ifc.run("spatial.remove_container", product="element").should_be_called() collector.assign("obj").should_be_called() subject.remove_container(ifc, collector, obj="obj") + + +class TestCopyToContainer: + def test_run(self, ifc, spatial): + ifc.get_entity("obj").should_be_called().will_return("element") + spatial.get_container("element").should_be_called().will_return("container") + ifc.get_object("container").should_be_called().will_return("container_obj") + spatial.get_relative_object_matrix("obj", "container_obj").should_be_called().will_return("matrix") + + ifc.get_object("to_container").should_be_called().will_return("to_container_obj") + spatial.duplicate_object_and_data("obj").should_be_called().will_return("new_obj") + spatial.set_relative_object_matrix("new_obj", "to_container_obj", "matrix").should_be_called() + spatial.run_root_copy_class(obj="new_obj").should_be_called() + spatial.run_spatial_assign_container(structure_obj="to_container_obj", element_obj="new_obj").should_be_called() + + spatial.disable_editing("obj").should_be_called() + + subject.copy_to_container(ifc, spatial, obj="obj", containers=["to_container"]) + + def test_using_an_absolute_matrix_if_there_is_no_from_container(self, ifc, spatial): + ifc.get_entity("obj").should_be_called().will_return("element") + spatial.get_container("element").should_be_called().will_return(None) + spatial.get_object_matrix("obj").should_be_called().will_return("matrix") + + ifc.get_object("to_container").should_be_called().will_return("to_container_obj") + spatial.duplicate_object_and_data("obj").should_be_called().will_return("new_obj") + spatial.set_relative_object_matrix("new_obj", "to_container_obj", "matrix").should_be_called() + spatial.run_root_copy_class(obj="new_obj").should_be_called() + spatial.run_spatial_assign_container(structure_obj="to_container_obj", element_obj="new_obj").should_be_called() + + spatial.disable_editing("obj").should_be_called() + + subject.copy_to_container(ifc, spatial, obj="obj", containers=["to_container"]) diff --git a/src/blenderbim/test/tool/test_container.py b/src/blenderbim/test/tool/test_spatial.py similarity index 74% rename from src/blenderbim/test/tool/test_container.py rename to src/blenderbim/test/tool/test_spatial.py index 5ed8b91ab5..c70fa7e750 100644 --- a/src/blenderbim/test/tool/test_container.py +++ b/src/blenderbim/test/tool/test_spatial.py @@ -20,13 +20,14 @@ import bpy import ifcopenshell import blenderbim.core.tool import blenderbim.tool as tool -from blenderbim.tool.container import Container as subject +from blenderbim.tool.spatial import Spatial as subject from test.bim.bootstrap import NewFile +from mathutils import Matrix class TestImplementsTool(NewFile): def test_run(self): - assert isinstance(subject(), blenderbim.core.tool.Container) + assert isinstance(subject(), blenderbim.core.tool.Spatial) class TestCanContain(NewFile): @@ -80,6 +81,26 @@ class TestCanContain(NewFile): assert subject.can_contain(structure_obj, element_obj) is False +class TestDisableEditing(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + subject.enable_editing(obj) + subject.disable_editing(obj) + assert obj.BIMObjectSpatialProperties.is_editing is False + + +class TestDuplicateObjectAndData(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh")) + new_obj = subject.duplicate_object_and_data(obj) + assert new_obj != obj + assert new_obj.data != obj.data + obj = bpy.data.objects.new("Object", None) + new_obj = subject.duplicate_object_and_data(obj) + assert new_obj != obj + assert new_obj.data is None + + class TestEnableEditing(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) @@ -87,12 +108,27 @@ class TestEnableEditing(NewFile): assert obj.BIMObjectSpatialProperties.is_editing is True -class TestDisableEditing(NewFile): +class TestGetContainer(NewFile): + def test_run(self): + ifc = ifcopenshell.file() + site = ifc.createIfcSite() + wall = ifc.createIfcWall() + ifcopenshell.api.run("spatial.assign_container", ifc, product=wall, relating_structure=site) + assert subject.get_container(wall) == site + + +class TestGetObjectMatrix(NewFile): def test_run(self): obj = bpy.data.objects.new("Object", None) - subject.enable_editing(obj) - subject.disable_editing(obj) - assert obj.BIMObjectSpatialProperties.is_editing is False + assert subject.get_object_matrix(obj) == obj.matrix_world + + +class TestGetRelativeObjectMatrix(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + relative_obj = bpy.data.objects.new("Object", None) + relative_obj.matrix_world[0][3] = 1 + assert subject.get_relative_object_matrix(obj, relative_obj)[0][3] == -1 class TestImportContainers(NewFile): @@ -133,3 +169,24 @@ class TestImportContainers(NewFile): assert len(props.containers) == 2 assert props.containers[0].name == "Lower" assert props.containers[1].name == "Higher" + + +class TestRunRootCopyClass(NewFile): + def test_nothing(self): + pass + + +class TestRunSpatialAssignContainer(NewFile): + def test_nothing(self): + pass + + +class TestSetRelativeObjectMatrix(NewFile): + def test_run(self): + obj = bpy.data.objects.new("Object", None) + relative_obj = bpy.data.objects.new("Object", None) + relative_obj.matrix_world[0][3] = 1 + matrix = Matrix() + matrix[0][3] = 1 + subject.set_relative_object_matrix(obj, relative_obj, matrix) + assert obj.matrix_world[0][3] == 2