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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from . import ui, operator from . import ui, prop, operator
classes = ( classes = (
operator.AssignObject, operator.AssignObject,
operator.EnableEditingAggregate, operator.EnableEditingAggregate,
operator.DisableEditingAggregate, operator.DisableEditingAggregate,
operator.AddAggregate, operator.AddAggregate,
prop.BIMObjectAggregateProperties,
ui.BIM_PT_aggregate, ui.BIM_PT_aggregate,
) )
def register(): def register():
pass bpy.types.Object.BIMObjectAggregateProperties = bpy.props.PointerProperty(type=prop.BIMObjectAggregateProperties)
def unregister(): 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy 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 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_idname = "bim.assign_object"
bl_label = "Assign Object" bl_label = "Assign Object"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
relating_object: bpy.props.StringProperty() relating_object: bpy.props.IntProperty()
related_object: bpy.props.StringProperty() related_object: bpy.props.IntProperty()
def execute(self, context):
return IfcStore.execute_ifc_operator(self, context)
def _execute(self, context): def _execute(self, context):
self.file = IfcStore.get_file() core.assign_object(
related_objects = ( tool.Ifc,
[bpy.data.objects.get(self.related_object)] if self.related_object else context.selected_objects 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_idname = "bim.enable_editing_aggregate"
bl_label = "Enable Editing Aggregate" bl_label = "Enable Editing Aggregate"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def _execute(self, context):
context.active_object.BIMObjectProperties.relating_object = None core.enable_editing_aggregate(tool.Aggregator, obj=context.active_object)
context.active_object.BIMObjectProperties.is_editing_aggregate = True
return {"FINISHED"}
class DisableEditingAggregate(bpy.types.Operator): class DisableEditingAggregate(bpy.types.Operator, Operator):
bl_idname = "bim.disable_editing_aggregate" bl_idname = "bim.disable_editing_aggregate"
bl_label = "Disable Editing Aggregate" bl_label = "Disable Editing Aggregate"
obj: bpy.props.StringProperty()
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
def execute(self, context): def _execute(self, context):
obj = bpy.data.objects.get(self.obj) if self.obj else context.active_object core.disable_editing_aggregate(tool.Aggregator, obj=context.active_object)
obj.BIMObjectProperties.is_editing_aggregate = False
return {"FINISHED"}
class AddAggregate(bpy.types.Operator): 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/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from bpy.types import Panel 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 from blenderbim.bim.ifc import IfcStore
@@ -40,34 +40,27 @@ class BIM_PT_aggregate(Panel):
return False return False
if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"): if not IfcStore.get_file().by_id(props.ifc_definition_id).is_a("IfcObjectDefinition"):
return False 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 return True
def draw(self, context): def draw(self, context):
props = context.active_object.BIMObjectProperties if not AggregateData.is_loaded:
if not props.ifc_definition_id: AggregateData.load()
return
if props.ifc_definition_id not in Data.products:
Data.load(IfcStore.get_file(), props.ifc_definition_id)
if props.is_editing_aggregate: props = context.active_object.BIMObjectAggregateProperties
if props.is_editing:
row = self.layout.row(align=True) row = self.layout.row(align=True)
row.prop(props, "relating_object", text="") row.prop(props, "relating_object", text="")
if props.relating_object: if props.relating_object:
row.operator( op = row.operator("bim.assign_object", icon="CHECKMARK", text="")
"bim.assign_object", icon="CHECKMARK", text="" op.relating_object = props.relating_object.BIMObjectProperties.ifc_definition_id
).relating_object = props.relating_object.name op.related_object = context.active_object.BIMObjectProperties.ifc_definition_id
row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="") row.operator("bim.disable_editing_aggregate", icon="CANCEL", text="")
else: else:
row = self.layout.row(align=True) row = self.layout.row(align=True)
name = "{}/{}".format( if AggregateData.data["has_aggregate"]:
Data.products[props.ifc_definition_id]["type"], Data.products[props.ifc_definition_id]["Name"] row.label(text=AggregateData.data["label"])
) else:
if name == "None/None": row.label(text="No Aggregate Found")
name = "No Aggregate Found"
row.label(text=name)
row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="") row.operator("bim.enable_editing_aggregate", icon="GREASEPENCIL", text="")
row.operator("bim.add_aggregate", icon="ADD", 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=site.name, ifc_class="IfcSite")
bpy.ops.bim.assign_class(obj=building.name, ifc_class="IfcBuilding") 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_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) blenderbim.core.aggregate.assign_object(
bpy.ops.bim.assign_object(related_object=building_storey.name, relating_object=building.name) 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 context.view_layer.objects.active = active_object
return {"FINISHED"} return {"FINISHED"}
@@ -32,10 +32,7 @@ class SpatialData:
@classmethod @classmethod
def load(cls): def load(cls):
cls.data = { cls.data = {
"is_editing": bpy.context.active_object.BIMObjectSpatialProperties.is_editing,
"parent_container_id": cls.get_parent_container_id(), "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_contained": cls.is_contained(),
"is_directly_contained": cls.is_directly_contained(), "is_directly_contained": cls.is_directly_contained(),
"label": cls.get_label(), "label": cls.get_label(),
@@ -52,19 +49,6 @@ class SpatialData:
return return
return container.Decomposes[0].RelatingObject.id() 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 @classmethod
def is_contained(cls): def is_contained(cls):
return ifcopenshell.util.element.get_container(tool.Ifc.get_entity(bpy.context.active_object)) 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): class SpatialElement(PropertyGroup):
name: StringProperty(name="Name") name: StringProperty(name="Name")
long_name: StringProperty(name="Long Name") long_name: StringProperty(name="Long Name")
@@ -46,7 +42,7 @@ class SpatialElement(PropertyGroup):
class BIMSpatialProperties(PropertyGroup): class BIMSpatialProperties(PropertyGroup):
containers: CollectionProperty(name="Containers", type=SpatialElement) 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") active_container_id: IntProperty(name="Active Container Id")
@@ -18,7 +18,6 @@
from bpy.types import Panel, UIList from bpy.types import Panel, UIList
from blenderbim.bim.ifc import IfcStore from blenderbim.bim.ifc import IfcStore
from ifcopenshell.api.spatial.data import Data
from blenderbim.bim.module.spatial.data import SpatialData from blenderbim.bim.module.spatial.data import SpatialData
@@ -38,26 +37,26 @@ class BIM_PT_spatial(Panel):
return False return False
if not IfcStore.get_element(oprops.ifc_definition_id): if not IfcStore.get_element(oprops.ifc_definition_id):
return False 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 return True
def draw(self, context): def draw(self, context):
if not SpatialData.is_loaded: if not SpatialData.is_loaded:
SpatialData.load() 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) row = self.layout.row(align=True)
if SpatialData.data["parent_container_id"]: if SpatialData.data["parent_container_id"]:
op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV") op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV")
op.parent = SpatialData.data["parent_container_id"] 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.copy_to_container", icon="COPYDOWN", text="")
row.operator("bim.disable_editing_container", icon="CANCEL", 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: else:
row = self.layout.row(align=True) row = self.layout.row(align=True)
if SpatialData.data["is_contained"]: if SpatialData.data["is_contained"]:
-2
View File
@@ -267,8 +267,6 @@ class BIMObjectProperties(PropertyGroup):
) )
is_reassigning_class: BoolProperty(name="Is Reassigning Class") is_reassigning_class: BoolProperty(name="Is Reassigning Class")
global_ids: CollectionProperty(name="GlobalIds", type=GlobalId) 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) psets: CollectionProperty(name="Psets", type=PsetQto)
qtos: CollectionProperty(name="Qtos", 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 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): class Blender(abc.ABC):
pass pass
@@ -17,6 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
from blenderbim.tool.address_editor import AddressEditor from blenderbim.tool.address_editor import AddressEditor
from blenderbim.tool.aggregator import Aggregator
from blenderbim.tool.blender import Blender from blenderbim.tool.blender import Blender
from blenderbim.tool.collector import Collector from blenderbim.tool.collector import Collector
from blenderbim.tool.container import Container 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): class Collector(blenderbim.core.tool.Collector):
@classmethod @classmethod
def assign(cls, obj): def assign(cls, obj):
for collection in obj.users_collection:
collection.objects.unlink(obj)
element = tool.Ifc.get_entity(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) aggregate = ifcopenshell.util.element.get_aggregate(element)
if aggregate: if aggregate:
aggregate_obj = tool.Ifc.get_object(aggregate) aggregate_obj = tool.Ifc.get_object(aggregate)
collection = bpy.data.collections.get(aggregate_obj.name) collection = bpy.data.collections.get(aggregate_obj.name)
if collection: if collection:
collection.objects.link(obj) return collection
return
container = ifcopenshell.util.element.get_container(element) container = ifcopenshell.util.element.get_container(element)
if container: if container:
container_obj = tool.Ifc.get_object(container) container_obj = tool.Ifc.get_object(container)
collection = bpy.data.collections.get(container_obj.name) collection = bpy.data.collections.get(container_obj.name)
if collection: if collection:
collection.objects.link(obj) return collection
return
if element.is_a("IfcProject"):
return bpy.context.scene.collection
project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0]) project_obj = tool.Ifc.get_object(tool.Ifc.get().by_type("IfcProject")[0])
if project_obj: if project_obj:
collection = bpy.data.collections.get(project_obj.name) collection = bpy.data.collections.get(project_obj.name)
if collection: if collection:
collection.objects.link(obj) return collection
return
@@ -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}"')) @then(parsers.parse('the object "{name}" is in the collection "{collection}"'))
def the_object_name_is_in_the_collection_collection(name, 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] 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() prophet.verify()
@pytest.fixture
def aggregator():
prophet = Prophecy(blenderbim.core.tool.Aggregator)
yield prophet
prophet.verify()
@pytest.fixture @pytest.fixture
def person_editor(): def person_editor():
prophet = Prophecy(blenderbim.core.tool.PersonEditor) 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) subject.assign(wall_obj)
assert len(wall_obj.users_collection) == 1 assert len(wall_obj.users_collection) == 1
assert "IfcProject" in wall_obj.users_collection[0].name 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