Assigning aggregations now protects against various invalid aggregation combinations

This commit is contained in:
Dion Moult
2021-10-06 12:50:46 +11:00
parent cbc0b8d5d4
commit f21005c29e
21 changed files with 448 additions and 128 deletions
@@ -17,20 +17,21 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui, operator
from . import ui, prop, operator
classes = (
operator.AssignObject,
operator.EnableEditingAggregate,
operator.DisableEditingAggregate,
operator.AddAggregate,
prop.BIMObjectAggregateProperties,
ui.BIM_PT_aggregate,
)
def register():
pass
bpy.types.Object.BIMObjectAggregateProperties = bpy.props.PointerProperty(type=prop.BIMObjectAggregateProperties)
def unregister():
pass
del bpy.types.Object.BIMObjectAggregateProperties
@@ -0,0 +1,27 @@
import bpy
import blenderbim.tool as tool
import ifcopenshell.util.element
def refresh():
AggregateData.is_loaded = False
class AggregateData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {"has_aggregate": cls.has_aggregate(), "label": cls.get_label()}
cls.is_loaded = True
@classmethod
def has_aggregate(cls):
return ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
@classmethod
def get_label(cls):
aggregate = ifcopenshell.util.element.get_aggregate(tool.Ifc.get_entity(bpy.context.active_object))
if aggregate:
return f"{aggregate.is_a()}/{aggregate.Name or ''}"
@@ -17,89 +17,52 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
import ifcopenshell.api
import blenderbim.tool as tool
import blenderbim.core.aggregate as core
import blenderbim.bim.handler
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.aggregate.data import Data
class AssignObject(bpy.types.Operator):
class Operator:
def execute(self, context):
IfcStore.execute_ifc_operator(self, context)
blenderbim.bim.handler.refresh_ui_data()
return {"FINISHED"}
class AssignObject(bpy.types.Operator, Operator):
bl_idname = "bim.assign_object"
bl_label = "Assign Object"
bl_options = {"REGISTER", "UNDO"}
relating_object: bpy.props.StringProperty()
related_object: bpy.props.StringProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
relating_object: bpy.props.IntProperty()
related_object: bpy.props.IntProperty()
def _execute(self, context):
self.file = IfcStore.get_file()
related_objects = (
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects
core.assign_object(
tool.Ifc,
tool.Aggregator,
tool.Collector,
relating_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.relating_object)),
related_obj=tool.Ifc.get_object(tool.Ifc.get().by_id(self.related_object)),
)
relating_object = bpy.data.objects.get(self.relating_object)
if not relating_object or not relating_object.BIMObjectProperties.ifc_definition_id:
return {"FINISHED"}
for related_object in related_objects:
oprops = related_object.BIMObjectProperties
product = self.file.by_id(oprops.ifc_definition_id)
ifcopenshell.api.run(
"aggregate.assign_object",
self.file,
**{
"product": product,
"relating_object": self.file.by_id(relating_object.BIMObjectProperties.ifc_definition_id),
},
)
bpy.ops.bim.edit_object_placement(obj=related_object.name)
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
bpy.ops.bim.disable_editing_aggregate(obj=related_object.name)
spatial_collection = bpy.data.collections.get(related_object.name)
relating_collection = bpy.data.collections.get(relating_object.name)
if spatial_collection:
self.remove_collection(context.scene.collection, spatial_collection)
for collection in bpy.data.collections:
if collection == relating_collection:
if not collection.children.get(spatial_collection.name):
collection.children.link(spatial_collection)
continue
self.remove_collection(collection, spatial_collection)
else:
for collection in related_object.users_collection:
if collection.name.startswith("Ifc"):
collection.objects.unlink(related_object)
relating_collection.objects.link(related_object)
return {"FINISHED"}
def remove_collection(self, parent, child):
try:
parent.children.unlink(child)
except:
pass
class EnableEditingAggregate(bpy.types.Operator):
class EnableEditingAggregate(bpy.types.Operator, Operator):
bl_idname = "bim.enable_editing_aggregate"
bl_label = "Enable Editing Aggregate"
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.active_object.BIMObjectProperties.relating_object = None
context.active_object.BIMObjectProperties.is_editing_aggregate = True
return {"FINISHED"}
def _execute(self, context):
core.enable_editing_aggregate(tool.Aggregator, obj=context.active_object)
class DisableEditingAggregate(bpy.types.Operator):
class DisableEditingAggregate(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_aggregate"
bl_label = "Disable Editing Aggregate"
obj: bpy.props.StringProperty()
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object
obj.BIMObjectProperties.is_editing_aggregate = False
return {"FINISHED"}
def _execute(self, context):
core.disable_editing_aggregate(tool.Aggregator, obj=context.active_object)
class AddAggregate(bpy.types.Operator):
@@ -0,0 +1,19 @@
import bpy
from blenderbim.bim.prop import StrProperty, Attribute
from blenderbim.bim.module.spatial.data import SpatialData
from bpy.types import PropertyGroup
from bpy.props import (
PointerProperty,
StringProperty,
EnumProperty,
BoolProperty,
IntProperty,
FloatProperty,
FloatVectorProperty,
CollectionProperty,
)
class BIMObjectAggregateProperties(PropertyGroup):
is_editing: BoolProperty(name="Is Editing")
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object)
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from bpy.types import Panel
from ifcopenshell.api.aggregate.data import Data
from blenderbim.bim.module.aggregate.data import AggregateData
from blenderbim.bim.ifc import IfcStore
@@ -40,34 +40,27 @@ class BIM_PT_aggregate(Panel):
return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
return False
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
if not Data.products[props.ifc_definition_id]:
return False
return True
def draw(self, context):
props = context.active_object.BIMObjectProperties
if not props.ifc_definition_id:
return
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
if not AggregateData.is_loaded:
AggregateData.load()
if props.is_editing_aggregate:
props = context.active_object.BIMObjectAggregateProperties
if props.is_editing:
row = self.layout.row(align=True)
row.prop(props, "relating_object", text="")
if props.relating_object:
row.operator(
"bim.assign_object", icon="CHECKMARK", text=""
).relating_object = props.relating_object.name
op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
else:
row = self.layout.row(align=True)
name = "{}/{}".format(
Data.products[props.ifc_definition_id]["type"], Data.products[props.ifc_definition_id]["Name"]
)
if name == "None/None":
name = "No Aggregate Found"
row.label(text=name)
if AggregateData.data["has_aggregate"]:
row.label(text=AggregateData.data["label"])
else:
row.label(text="No Aggregate Found")
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", text="")
@@ -94,9 +94,16 @@ class CreateProject(bpy.types.Operator):
bpy.ops.bim.assign_class(obj=site.name, ifc_class="IfcSite")
bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding")
bpy.ops.bim.assign_class(obj=building_storey.name, ifc_class="IfcBuildingStorey")
bpy.ops.bim.assign_object(related_object=site.name, relating_object=project.name)
bpy.ops.bim.assign_object(related_object=building.name, relating_object=site.name)
bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name)
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregator, tool.Collector, relating_obj=project, related_obj=site
)
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregator, tool.Collector, relating_obj=site, related_obj=building
)
blenderbim.core.aggregate.assign_object(
tool.Ifc, tool.Aggregator, tool.Collector, relating_obj=building, related_obj=building_storey
)
context.view_layer.objects.active = active_object
return {"FINISHED"}
@@ -32,10 +32,7 @@ class SpatialData:
@classmethod
def load(cls):
cls.data = {
"is_editing": bpy.context.active_object.BIMObjectSpatialProperties.is_editing,
"parent_container_id": cls.get_parent_container_id(),
"container_id": cls.get_container_id(),
"container_list_settings": cls.get_container_list_settings(),
"is_contained": cls.is_contained(),
"is_directly_contained": cls.is_directly_contained(),
"label": cls.get_label(),
@@ -52,19 +49,6 @@ class SpatialData:
return
return container.Decomposes[0].RelatingObject.id()
@classmethod
def get_container_id(cls):
props = bpy.context.scene.BIMSpatialProperties
try:
return props.containers[props.active_container_index].ifc_definition_id
except:
return 0
@classmethod
def get_container_list_settings(cls):
props = bpy.context.scene.BIMSpatialProperties
return ["", props, "containers", props, "active_container_id"]
@classmethod
def is_contained(cls):
return ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object))
@@ -32,10 +32,6 @@ from bpy.props import (
)
def update_active_container_index(self, context):
SpatialData.is_loaded = False
class SpatialElement(PropertyGroup):
name: StringProperty(name="Name")
long_name: StringProperty(name="Long Name")
@@ -46,7 +42,7 @@ class SpatialElement(PropertyGroup):
class BIMSpatialProperties(PropertyGroup):
containers: CollectionProperty(name="Containers", type=SpatialElement)
active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index)
active_container_index: IntProperty(name="Active Container Index")
active_container_id: IntProperty(name="Active Container Id")
@@ -18,7 +18,6 @@
from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.spatial.data import Data
from blenderbim.bim.module.spatial.data import SpatialData
@@ -38,26 +37,26 @@ class BIM_PT_spatial(Panel):
return False
if not IfcStore.get_element(oprops.ifc_definition_id):
return False
if oprops.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), oprops.ifc_definition_id)
if not Data.products[oprops.ifc_definition_id]:
return False
return True
def draw(self, context):
if not SpatialData.is_loaded:
SpatialData.load()
if SpatialData.data["is_editing"]:
props = context.scene.BIMSpatialProperties
osprops = context.active_object.BIMObjectSpatialProperties
if osprops.is_editing:
row = self.layout.row(align=True)
if SpatialData.data["parent_container_id"]:
op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV")
op.parent = SpatialData.data["parent_container_id"]
row.operator("bim.assign_container", icon="CHECKMARK").structure = SpatialData.data["container_id"]
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="")
row.operator("bim.disable_editing_container", icon="CANCEL", text="")
self.layout.template_list("BIM_UL_containers", *SpatialData.data["container_list_settings"])
self.layout.template_list("BIM_UL_containers", "", props, "containers", props, "active_container_id")
else:
row = self.layout.row(align=True)
if SpatialData.data["is_contained"]:
-2
View File
@@ -267,8 +267,6 @@ class BIMObjectProperties(PropertyGroup):
)
is_reassigning_class: BoolProperty(name="Is Reassigning Class")
global_ids: CollectionProperty(name="GlobalIds", type=GlobalId)
relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object)
is_editing_aggregate: BoolProperty(name="Is Editing Aggregate")
psets: CollectionProperty(name="Psets", type=PsetQto)
qtos: CollectionProperty(name="Qtos", type=PsetQto)
@@ -0,0 +1,18 @@
def enable_editing_aggregate(aggregator, obj=None):
aggregator.enable_editing(obj)
def disable_editing_aggregate(aggregator, obj=None):
aggregator.disable_editing(obj)
def assign_object(ifc, aggregator, collector, relating_obj=None, related_obj=None):
if not aggregator.can_aggregate(relating_obj, related_obj):
return
rel = ifc.run(
"aggregate.assign_object", product=ifc.get_entity(related_obj), relating_object=ifc.get_entity(relating_obj)
)
collector.assign(relating_obj)
collector.assign(related_obj)
aggregator.disable_editing(related_obj)
return rel
+17
View File
@@ -56,6 +56,23 @@ class AddressEditor(abc.ABC):
pass
class Aggregator(abc.ABC):
@classmethod
@abc.abstractmethod
def enable_editing(cls, obj):
pass
@classmethod
@abc.abstractmethod
def disable_editing(cls, obj):
pass
@classmethod
@abc.abstractmethod
def can_aggregate(cls, relating_object, related_object):
pass
class Blender(abc.ABC):
pass
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from blenderbim.tool.address_editor import AddressEditor
from blenderbim.tool.aggregator import Aggregator
from blenderbim.tool.blender import Blender
from blenderbim.tool.collector import Collector
from blenderbim.tool.container import Container
@@ -0,0 +1,34 @@
import blenderbim.core.tool
import blenderbim.tool as tool
class Aggregator(blenderbim.core.tool.Aggregator):
@classmethod
def enable_editing(cls, obj):
obj.BIMObjectAggregateProperties.is_editing = True
@classmethod
def disable_editing(cls, obj):
obj.BIMObjectAggregateProperties.is_editing = False
@classmethod
def can_aggregate(cls, relating_obj, related_obj):
relating_object = tool.Ifc.get_entity(relating_obj)
related_object = tool.Ifc.get_entity(related_obj)
if not relating_object or not related_object:
return False
if relating_object.is_a("IfcElement") and related_object.is_a("IfcElement"):
if relating_obj.data:
return False
return True
if tool.Ifc.get_schema() == "IFC2X3":
if relating_object.is_a("IfcSpatialStructureElement") and related_object.is_a("IfcSpatialStructureElement"):
return True
if relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialStructureElement"):
return True
else:
if relating_object.is_a("IfcSpatialElement") and related_object.is_a("IfcSpatialElement"):
return True
if relating_object.is_a("IfcProject") and related_object.is_a("IfcSpatialElement"):
return True
return False
+56 -9
View File
@@ -26,29 +26,76 @@ import blenderbim.tool as tool
class Collector(blenderbim.core.tool.Collector):
@classmethod
def assign(cls, obj):
for collection in obj.users_collection:
collection.objects.unlink(obj)
element = tool.Ifc.get_entity(obj)
object_collection = None
collection_collection = None
object_collection = cls._get_own_collection(element, obj)
if object_collection:
collection_collection = cls._get_collection(element, obj)
else:
object_collection = cls._get_collection(element, obj)
if obj.users_collection != (object_collection,):
for collection in obj.users_collection:
collection.objects.unlink(obj)
object_collection.objects.link(obj)
if collection_collection and collection_collection.children.find(object_collection.name) == -1:
for collection in bpy.data.collections:
if collection.children.find(object_collection.name) != -1:
collection.children.unlink(object_collection)
collection_collection.children.link(object_collection)
@classmethod
def _get_own_collection(cls, element, obj):
if element.is_a("IfcProject"):
collection = bpy.data.collections.get(obj.name)
if not collection:
collection = bpy.data.collections.new(obj.name)
return collection
if tool.Ifc.get_schema() == "IFC2X3":
if element.is_a("IfcSpatialStructureElement"):
collection = bpy.data.collections.get(obj.name)
if not collection:
collection = bpy.data.collections.new(obj.name)
return collection
else:
if element.is_a("IfcSpatialElement"):
collection = bpy.data.collections.get(obj.name)
if not collection:
collection = bpy.data.collections.new(obj.name)
return collection
if element.IsDecomposedBy:
collection = bpy.data.collections.get(obj.name)
if not collection:
collection = bpy.data.collections.new(obj.name)
return collection
@classmethod
def _get_collection(cls, element, obj):
aggregate = ifcopenshell.util.element.get_aggregate(element)
if aggregate:
aggregate_obj = tool.Ifc.get_object(aggregate)
collection = bpy.data.collections.get(aggregate_obj.name)
if collection:
collection.objects.link(obj)
return
return collection
container = ifcopenshell.util.element.get_container(element)
if container:
container_obj = tool.Ifc.get_object(container)
collection = bpy.data.collections.get(container_obj.name)
if collection:
collection.objects.link(obj)
return
return collection
if element.is_a("IfcProject"):
return bpy.context.scene.collection
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
if project_obj:
collection = bpy.data.collections.get(project_obj.name)
if collection:
collection.objects.link(obj)
return
return collection
@@ -0,0 +1,28 @@
@aggregate
Feature: Aggregate
Covers element and spatial aggregation
Scenario: Enable editing aggregate
Given an empty IFC project
And the object "IfcSite/My Site" is selected
When I press "bim.enable_editing_aggregate"
Then nothing happens
Scenario: Disable editing aggregate
Given an empty IFC project
And the object "IfcSite/My Site" is selected
And I press "bim.enable_editing_aggregate"
When I press "bim.disable_editing_aggregate"
Then nothing happens
Scenario: Assign object
Given an empty IFC project
And the object "IfcSite/My Site" is selected
And I press "bim.enable_editing_aggregate"
And the variable "relating_object" is "tool.Ifc.get().by_type('IfcSite')[0].id()"
And the variable "related_object" is "tool.Ifc.get().by_type('IfcBuildingStorey')[0].id()"
When I press "bim.assign_object(relating_object={relating_object}, related_object={related_object})"
Then the object "IfcSite/My Site" is in the collection "IfcSite/My Site"
Then the object "IfcBuildingStorey/My Storey" is in the collection "IfcBuildingStorey/My Storey"
Then the collection "IfcBuildingStorey/My Storey" is in the collection "IfcSite/My Site"
+5
View File
@@ -262,3 +262,8 @@ def prop_is_value(prop, value):
@then(parsers.parse('the object "{name}" is in the collection "{collection}"'))
def the_object_name_is_in_the_collection_collection(name, collection):
assert collection in [c.name for c in the_object_name_exists(name).users_collection]
@then(parsers.parse('the collection "{name1}" is in the collection "{name2}"'))
def the_collection_name1_is_in_the_collection_name2(name1, name2):
assert bpy.data.collections.get(name2).children.get(name1)
+7
View File
@@ -35,6 +35,13 @@ def blender():
prophet.verify()
@pytest.fixture
def aggregator():
prophet = Prophecy(blenderbim.core.tool.Aggregator)
yield prophet
prophet.verify()
@pytest.fixture
def person_editor():
prophet = Prophecy(blenderbim.core.tool.PersonEditor)
@@ -0,0 +1,31 @@
import blenderbim.core.aggregate as subject
from test.core.bootstrap import ifc, aggregator, collector
class TestEnableEditingAggregate:
def test_run(self, aggregator):
aggregator.enable_editing("obj").should_be_called()
subject.enable_editing_aggregate(aggregator, obj="obj")
class TestDisableEditingAggregate:
def test_run(self, aggregator):
aggregator.disable_editing("obj").should_be_called()
subject.disable_editing_aggregate(aggregator, obj="obj")
class TestAssignObject:
def test_run(self, ifc, aggregator, collector):
aggregator.can_aggregate("relating_obj", "related_obj").should_be_called().will_return(True)
ifc.get_entity("relating_obj").should_be_called().will_return("relating_object")
ifc.get_entity("related_obj").should_be_called().will_return("related_object")
ifc.run(
"aggregate.assign_object", product="related_object", relating_object="relating_object"
).should_be_called().will_return("rel")
aggregator.disable_editing("related_obj").should_be_called()
collector.assign("relating_obj").should_be_called()
collector.assign("related_obj").should_be_called()
assert (
subject.assign_object(ifc, aggregator, collector, relating_obj="relating_obj", related_obj="related_obj")
== "rel"
)
+101
View File
@@ -0,0 +1,101 @@
import bpy
import ifcopenshell
import blenderbim.core.tool
import blenderbim.tool as tool
from test.bim.bootstrap import NewFile
from blenderbim.tool.aggregator import Aggregator as subject
class TestImplementsTool(NewFile):
def test_run(self):
assert isinstance(subject(), blenderbim.core.tool.Aggregator)
class TestEnableEditing(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
subject.enable_editing(obj)
assert obj.BIMObjectAggregateProperties.is_editing is True
class TestDisableEditing(NewFile):
def test_run(self):
obj = bpy.data.objects.new("Object", None)
subject.enable_editing(obj)
subject.disable_editing(obj)
assert obj.BIMObjectAggregateProperties.is_editing is False
class TestCanAggregate(NewFile):
def test_elements_can_aggregate(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcElementAssembly()
element_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(element, element_obj)
subelement = ifc.createIfcBeam()
subelement_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(subelement, subelement_obj)
assert subject.can_aggregate(element_obj, subelement_obj) is True
def test_spatial_elements_can_aggregate(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcSite()
element_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(element, element_obj)
subelement = ifc.createIfcBuilding()
subelement_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(subelement, subelement_obj)
assert subject.can_aggregate(element_obj, subelement_obj) is True
def test_spatial_elements_can_aggregate_2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
element = ifc.createIfcSite()
element_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(element, element_obj)
subelement = ifc.createIfcBuilding()
subelement_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(subelement, subelement_obj)
assert subject.can_aggregate(element_obj, subelement_obj) is True
def test_spatial_elements_and_projects_can_aggregate(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcProject()
element_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(element, element_obj)
subelement = ifc.createIfcBuilding()
subelement_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(subelement, subelement_obj)
assert subject.can_aggregate(element_obj, subelement_obj) is True
def test_spatial_elements_and_projects_can_aggregate_2x3(self):
ifc = ifcopenshell.file(schema="IFC2X3")
tool.Ifc.set(ifc)
element = ifc.createIfcProject()
element_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(element, element_obj)
subelement = ifc.createIfcBuilding()
subelement_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(subelement, subelement_obj)
assert subject.can_aggregate(element_obj, subelement_obj) is True
def test_unlinked_elements_cannot_aggregate(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element_obj = bpy.data.objects.new("Object", None)
subelement_obj = bpy.data.objects.new("Object", None)
assert subject.can_aggregate(element_obj, subelement_obj) is False
def test_aggregates_with_meshes_are_invalid(self):
ifc = ifcopenshell.file()
tool.Ifc.set(ifc)
element = ifc.createIfcElementAssembly()
element_obj = bpy.data.objects.new("Object", bpy.data.meshes.new("Mesh"))
tool.Ifc.link(element, element_obj)
subelement = ifc.createIfcBeam()
subelement_obj = bpy.data.objects.new("Object", None)
tool.Ifc.link(subelement, subelement_obj)
assert subject.can_aggregate(element_obj, subelement_obj) is False
@@ -55,3 +55,47 @@ class TestAssign(NewFile):
subject.assign(wall_obj)
assert len(wall_obj.users_collection) == 1
assert "IfcProject" in wall_obj.users_collection[0].name
def test_in_decomposition_mode_spatial_elements_are_placed_in_a_collection_of_the_same_name(self):
bpy.ops.bim.create_project()
space_obj = bpy.data.objects.new("IfcSpace/Name", None)
space_element = tool.Ifc.get().createIfcSpace()
tool.Ifc.link(space_element, space_obj)
bpy.context.scene.collection.objects.link(space_obj)
ifcopenshell.api.run(
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=tool.Ifc.get().by_type("IfcSite")[0],
product=space_element,
)
subject.assign(space_obj)
assert len(space_obj.users_collection) == 1
assert space_obj.users_collection[0].name == space_obj.name
def test_in_decomposition_mode_aggregates_are_placed_in_a_collection_of_the_same_name(self):
bpy.ops.bim.create_project()
element_obj = bpy.data.objects.new("IfcElementAssembly/Name", None)
element = tool.Ifc.get().createIfcElementAssembly()
subelement_obj = bpy.data.objects.new("IfcBeam/Name", None)
subelement = tool.Ifc.get().createIfcBeam()
tool.Ifc.link(element, element_obj)
bpy.context.scene.collection.objects.link(element_obj)
ifcopenshell.api.run(
"aggregate.assign_object",
tool.Ifc.get(),
relating_object=element,
product=subelement,
)
subject.assign(element_obj)
assert len(element_obj.users_collection) == 1
assert element_obj.users_collection[0].name == element_obj.name
def test_in_decomposition_mode_projects_are_placed_in_a_collection_of_the_same_name(self):
tool.Ifc.set(ifcopenshell.file())
element_obj = bpy.data.objects.new("IfcProject/Name", None)
element = tool.Ifc.get().createIfcProject()
tool.Ifc.link(element, element_obj)
bpy.context.scene.collection.objects.link(element_obj)
subject.assign(element_obj)
assert len(element_obj.users_collection) == 1
assert element_obj.users_collection[0].name == element_obj.name