diff --git a/src/ifcblenderexport/blenderbim/bim/module/document/ui.py b/src/ifcblenderexport/blenderbim/bim/module/document/ui.py index d310ed49d2..d1e713902d 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/document/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/document/ui.py @@ -41,12 +41,7 @@ class BIM_PT_documents(Panel): if self.props.is_editing: self.layout.template_list( - "BIM_UL_documents", - "", - self.props, - "documents", - self.props, - "active_document_index", + "BIM_UL_documents", "", self.props, "documents", self.props, "active_document_index" ) if self.props.active_document_id: diff --git a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py index a1c186e5fd..c5bdba0626 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/root/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/root/operator.py @@ -182,6 +182,7 @@ class UnassignClass(bpy.types.Operator): self.remove_representations(obj, product) IfcStore.unlink_element(product, obj) remove_product.Usecase(self.file, {"product": product}).execute() + # TODO: remove object placement if "/" in obj.name and obj.name[0:3] == "Ifc": obj.name = "/".join(obj.name.split("/")[1:]) if obj.data and obj.data.name == "Void": diff --git a/src/ifcblenderexport/blenderbim/bim/module/spatial/__init__.py b/src/ifcblenderexport/blenderbim/bim/module/spatial/__init__.py index cc5e0ef745..7004764078 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/spatial/__init__.py +++ b/src/ifcblenderexport/blenderbim/bim/module/spatial/__init__.py @@ -1,18 +1,25 @@ import bpy -from . import ui, operator +from . import ui, prop, operator classes = ( operator.AssignContainer, operator.RemoveContainer, operator.EnableEditingContainer, operator.DisableEditingContainer, + operator.ChangeSpatialLevel, + prop.SpatialElement, + prop.BIMSpatialProperties, + prop.BIMObjectSpatialProperties, ui.BIM_PT_spatial, + ui.BIM_UL_spatial_elements, ) def register(): - pass + bpy.types.Scene.BIMSpatialProperties = bpy.props.PointerProperty(type=prop.BIMSpatialProperties) + bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties) def unregister(): - pass + del bpy.types.Scene.BIMSpatialProperties + del bpy.types.Object.BIMObjectSpatialProperties diff --git a/src/ifcblenderexport/blenderbim/bim/module/spatial/data.py b/src/ifcblenderexport/blenderbim/bim/module/spatial/data.py index e442acd786..027edea27f 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/spatial/data.py +++ b/src/ifcblenderexport/blenderbim/bim/module/spatial/data.py @@ -3,13 +3,21 @@ from blenderbim.bim.ifc import IfcStore class Data: products = {} + spatial_elements = {} @classmethod def purge(cls): cls.products = {} + cls.spatial_elements = {} @classmethod - def load(cls, product_id): + def load(cls, product_id=None): + if product_id: + return cls.load_product(product_id) + cls.load_spatial_elements() + + @classmethod + def load_product(cls, product_id): file = IfcStore.get_file() if not file: return @@ -21,3 +29,27 @@ class Data: cls.products[product_id] = {"type": structure.is_a(), "Name": structure.Name, "id": int(structure.id())} else: cls.products[product_id] = {"type": None, "Name": None, "id": None} + + @classmethod + def load_spatial_elements(cls): + file = IfcStore.get_file() + if not file: + return + cls.spatial_elements = {} + ifc_class = "IfcSpatialElement" + if file.schema == "IFC2X3": + ifc_class = "IfcSpatialStructureElement" + for element in file.by_type(ifc_class): + decomposes = None + if element.Decomposes: + decomposes = element.Decomposes[0].RelatingObject.id() + is_decomposed_by = [] + if element.IsDecomposedBy: + for rel in element.IsDecomposedBy: + is_decomposed_by.extend([e.id() for e in rel.RelatedObjects]) + cls.spatial_elements[element.id()] = { + "Name": element.Name, + "LongName": element.LongName, + "Decomposes": decomposes, + "IsDecomposedBy": is_decomposed_by + } diff --git a/src/ifcblenderexport/blenderbim/bim/module/spatial/operator.py b/src/ifcblenderexport/blenderbim/bim/module/spatial/operator.py index 7fc90d84f5..e966c457de 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/spatial/operator.py +++ b/src/ifcblenderexport/blenderbim/bim/module/spatial/operator.py @@ -3,12 +3,13 @@ import blenderbim.bim.module.spatial.assign_container as assign_container import blenderbim.bim.module.spatial.remove_container as remove_container from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.spatial.data import Data +from blenderbim.bim.module.spatial.prop import getSpatialContainers class AssignContainer(bpy.types.Operator): bl_idname = "bim.assign_container" bl_label = "Assign Container" - relating_structure: bpy.props.StringProperty() + relating_structure: bpy.props.IntProperty() related_element: bpy.props.StringProperty() def execute(self, context): @@ -16,31 +17,37 @@ class AssignContainer(bpy.types.Operator): related_element = ( bpy.data.objects.get(self.related_element) if self.related_element else bpy.context.active_object ) - props = related_element.BIMObjectProperties + oprops = related_element.BIMObjectProperties + sprops = context.scene.BIMSpatialProperties + props = related_element.BIMObjectSpatialProperties relating_structure = ( - bpy.data.objects.get(self.relating_structure) if self.relating_structure else props.relating_structure + self.relating_structure or sprops.spatial_elements[sprops.active_spatial_element_index].ifc_definition_id ) - if not relating_structure or not relating_structure.BIMObjectProperties.ifc_definition_id: - return {"FINISHED"} + assign_container.Usecase( self.file, { - "product": self.file.by_id(props.ifc_definition_id), - "relating_structure": self.file.by_id(relating_structure.BIMObjectProperties.ifc_definition_id), + "product": self.file.by_id(oprops.ifc_definition_id), + "relating_structure": self.file.by_id(relating_structure), }, ).execute() bpy.ops.bim.edit_object_placement(obj=related_element.name) - Data.load(props.ifc_definition_id) + Data.load(oprops.ifc_definition_id) bpy.ops.bim.disable_editing_container(obj=related_element.name) aggregate_collection = bpy.data.collections.get(related_element.name) - relating_collection = bpy.data.collections.get(relating_structure.name) + + relating_structure_obj = IfcStore.id_map.get(relating_structure) + relating_collection = None + if relating_structure_obj: + relating_collection = bpy.data.collections.get(relating_structure_obj.name) + if aggregate_collection: self.remove_collection(bpy.context.scene.collection, aggregate_collection) for collection in bpy.data.collections: self.remove_collection(collection, aggregate_collection) relating_collection.children.link(aggregate_collection) - else: + elif relating_collection: for collection in related_element.users_collection: collection.objects.unlink(related_element) relating_collection.objects.link(related_element) @@ -58,8 +65,18 @@ class EnableEditingContainer(bpy.types.Operator): bl_label = "Enable Editing Container" def execute(self, context): - bpy.context.active_object.BIMObjectProperties.relating_structure = None - bpy.context.active_object.BIMObjectProperties.is_editing_container = True + bpy.context.active_object.BIMObjectSpatialProperties.is_editing = True + getSpatialContainers(self, context) + return {"FINISHED"} + + +class ChangeSpatialLevel(bpy.types.Operator): + bl_idname = "bim.change_spatial_level" + bl_label = "Change Spatial Level" + parent_id: bpy.props.IntProperty() + + def execute(self, context): + getSpatialContainers(self, context, parent_id=self.parent_id) return {"FINISHED"} @@ -70,7 +87,7 @@ class DisableEditingContainer(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object - obj.BIMObjectProperties.is_editing_container = False + obj.BIMObjectSpatialProperties.is_editing = False return {"FINISHED"} @@ -81,10 +98,10 @@ class RemoveContainer(bpy.types.Operator): def execute(self, context): obj = bpy.data.objects.get(self.obj) if self.obj else bpy.context.active_object - props = obj.BIMObjectProperties + oprops = obj.BIMObjectProperties self.file = IfcStore.get_file() - remove_container.Usecase(self.file, {"product": self.file.by_id(props.ifc_definition_id)}).execute() - Data.load(props.ifc_definition_id) + remove_container.Usecase(self.file, {"product": self.file.by_id(oprops.ifc_definition_id)}).execute() + Data.load(oprops.ifc_definition_id) aggregate_collection = bpy.data.collections.get(obj.name) if aggregate_collection: diff --git a/src/ifcblenderexport/blenderbim/bim/module/spatial/prop.py b/src/ifcblenderexport/blenderbim/bim/module/spatial/prop.py new file mode 100644 index 0000000000..b64228df37 --- /dev/null +++ b/src/ifcblenderexport/blenderbim/bim/module/spatial/prop.py @@ -0,0 +1,67 @@ +import bpy +from blenderbim.bim.prop import StrProperty, Attribute +from blenderbim.bim.module.spatial.data import Data +from blenderbim.bim.ifc import IfcStore +from bpy.types import PropertyGroup +from bpy.props import ( + PointerProperty, + StringProperty, + EnumProperty, + BoolProperty, + IntProperty, + FloatProperty, + FloatVectorProperty, + CollectionProperty, +) + + +def getSpatialContainers(self, context, parent_id=None): + Data.load() + self.file = IfcStore.get_file() + props = context.scene.BIMSpatialProperties + while len(props.spatial_elements) > 0: + props.spatial_elements.remove(0) + + if parent_id: + props.active_decomposes_id = parent_id + + try: + self.file.by_id(props.active_decomposes_id) + except: + props.active_decomposes_id = 0 + + project_id = self.file.by_type("IfcProject")[0].id() + + if not props.active_decomposes_id: + props.active_decomposes_id = project_id + + for ifc_definition_id, spatial_element in Data.spatial_elements.items(): + if spatial_element["Decomposes"] == props.active_decomposes_id: + new = props.spatial_elements.add() + new.name = spatial_element["Name"] or "Unnamed" + new.long_name = spatial_element["LongName"] or "" + new.has_decomposition = bool(spatial_element["IsDecomposedBy"]) + new.ifc_definition_id = ifc_definition_id + + if props.active_decomposes_id == project_id: + props.active_decomposes_parent_id = 0 + else: + props.active_decomposes_parent_id = Data.spatial_elements[props.active_decomposes_id]["Decomposes"] + + +class SpatialElement(PropertyGroup): + name: StringProperty(name="Name") + long_name: StringProperty(name="Long Name") + has_decomposition: BoolProperty(name="Has Decomposition") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + +class BIMSpatialProperties(PropertyGroup): + spatial_elements: CollectionProperty(name="Spatial Elements", type=SpatialElement) + active_spatial_element_index: IntProperty(name="Active Spatial Element Index") + active_decomposes_id: IntProperty(name="Active Decomposes Id") + active_decomposes_parent_id: IntProperty(name="Active Decomposes Parent Id") + + +class BIMObjectSpatialProperties(PropertyGroup): + is_editing: BoolProperty(name="Is Editing") diff --git a/src/ifcblenderexport/blenderbim/bim/module/spatial/ui.py b/src/ifcblenderexport/blenderbim/bim/module/spatial/ui.py index 7e953f3e9c..be8dab598a 100644 --- a/src/ifcblenderexport/blenderbim/bim/module/spatial/ui.py +++ b/src/ifcblenderexport/blenderbim/bim/module/spatial/ui.py @@ -1,4 +1,4 @@ -from bpy.types import Panel +from bpy.types import Panel, UIList from blenderbim.bim.module.spatial.data import Data @@ -13,34 +13,52 @@ class BIM_PT_spatial(Panel): def poll(cls, context): if not context.active_object: return False - props = context.active_object.BIMObjectProperties - if not props.ifc_definition_id: + oprops = context.active_object.BIMObjectProperties + if not oprops.ifc_definition_id: return False - if props.ifc_definition_id not in Data.products: - Data.load(props.ifc_definition_id) - if not Data.products[props.ifc_definition_id]: + if oprops.ifc_definition_id not in Data.products: + Data.load(oprops.ifc_definition_id) + if not Data.products[oprops.ifc_definition_id]: return False return True def draw(self, context): - props = context.active_object.BIMObjectProperties - if not props.ifc_definition_id: + oprops = context.active_object.BIMObjectProperties + sprops = context.scene.BIMSpatialProperties + props = context.active_object.BIMObjectSpatialProperties + if not oprops.ifc_definition_id: return - if props.ifc_definition_id not in Data.products: - Data.load(props.ifc_definition_id) + if oprops.ifc_definition_id not in Data.products: + Data.load(oprops.ifc_definition_id) - if props.is_editing_container: + if props.is_editing: row = self.layout.row(align=True) - row.prop(props, "relating_structure", text="") - row.operator("bim.assign_container", icon="CHECKMARK", text="") + if sprops.active_decomposes_parent_id: + op = row.operator("bim.change_spatial_level", text="", icon="FRAME_PREV") + op.parent_id = sprops.active_decomposes_parent_id + row.operator("bim.assign_container", icon="CHECKMARK") row.operator("bim.disable_editing_container", icon="X", text="") + + self.layout.template_list( + "BIM_UL_spatial_elements", "", sprops, "spatial_elements", sprops, "active_spatial_element_index" + ) else: row = self.layout.row(align=True) name = "{}/{}".format( - Data.products[props.ifc_definition_id]["type"], Data.products[props.ifc_definition_id]["Name"] + Data.products[oprops.ifc_definition_id]["type"], Data.products[oprops.ifc_definition_id]["Name"] ) if name == "None/None": name = "This object is not spatially contained" row.label(text=name) row.operator("bim.enable_editing_container", icon="GREASEPENCIL", text="") row.operator("bim.remove_container", icon="X", text="") + + +class BIM_UL_spatial_elements(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + if item.has_decomposition: + op = layout.operator("bim.change_spatial_level", text="", icon="DISCLOSURE_TRI_RIGHT", emboss=False) + op.parent_id = item.ifc_definition_id + layout.label(text=item.name) + layout.label(text=item.long_name) diff --git a/src/ifcblenderexport/blenderbim/bim/prop.py b/src/ifcblenderexport/blenderbim/bim/prop.py index 6cd51b90a0..eaad220e10 100644 --- a/src/ifcblenderexport/blenderbim/bim/prop.py +++ b/src/ifcblenderexport/blenderbim/bim/prop.py @@ -498,8 +498,6 @@ class BIMObjectProperties(PropertyGroup): global_ids: CollectionProperty(name="GlobalIds", type=GlobalId) relating_object: PointerProperty(name="Aggregate", type=bpy.types.Object) is_editing_aggregate: BoolProperty(name="Is Editing Aggregate") - is_editing_container: BoolProperty(name="Is Editing Container") - relating_structure: PointerProperty(name="Spatial Container", type=bpy.types.Object) psets: CollectionProperty(name="Psets", type=PsetQto) qtos: CollectionProperty(name="Qtos", type=PsetQto) has_boundary_condition: BoolProperty(name="Has Boundary Condition")