Fix bug where you couldn't bulk copy non-geometric elements to other spatial containers

This commit is contained in:
Dion Moult
2021-10-20 18:47:55 +11:00
parent 871ad1ab7f
commit 503f0399ff
13 changed files with 234 additions and 80 deletions
@@ -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"
@@ -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)
@@ -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,
)
@@ -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"}
@@ -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="")
+18
View File
@@ -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)
+15 -8
View File
@@ -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
+1 -1
View File
@@ -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
@@ -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
@@ -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"
+7 -7
View File
@@ -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)
+48 -17
View File
@@ -17,44 +17,42 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
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"])
@@ -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