mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 17:31:45 +00:00
Assigning aggregations now protects against various invalid aggregation combinations
This commit is contained in:
@@ -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"]:
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
)
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user