mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 14:23:53 +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/>.
|
# 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"]:
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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"
|
||||||
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user