diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index 5a38a99e01..e3237bdecc 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -115,6 +115,8 @@ classes = ( ui.BIM_PT_roof, ui.LaunchTypeManager, ui.BIM_MT_model, + ui.BIM_PT_GridsSpatialManager, + ui.BIM_PT_Grids, grid.BIM_OT_add_object, stair.BIM_OT_add_object, stair.BIM_OT_add_clever_stair, diff --git a/src/blenderbim/blenderbim/bim/module/model/grid.py b/src/blenderbim/blenderbim/bim/module/model/grid.py index 68203f3cc7..adb4a240c4 100644 --- a/src/blenderbim/blenderbim/bim/module/model/grid.py +++ b/src/blenderbim/blenderbim/bim/module/model/grid.py @@ -96,7 +96,7 @@ def add_object(self, context): obj.BIMObjectProperties.ifc_definition_id = result.id() -class BIM_OT_add_object(Operator): +class BIM_OT_add_object(Operator, tool.Ifc.Operator): bl_idname = "mesh.add_grid" bl_label = "IFC Grid" bl_options = {"REGISTER", "UNDO"} @@ -108,10 +108,7 @@ class BIM_OT_add_object(Operator): @classmethod def poll(cls, context): - return IfcStore.get_file() - - def execute(self, context): - return IfcStore.execute_ifc_operator(self, context) + return tool.Ifc.get() def _execute(self, context): add_object(self, context) diff --git a/src/blenderbim/blenderbim/bim/module/model/ui.py b/src/blenderbim/blenderbim/bim/module/model/ui.py index 4b28fe7752..9fb60cda61 100644 --- a/src/blenderbim/blenderbim/bim/module/model/ui.py +++ b/src/blenderbim/blenderbim/bim/module/model/ui.py @@ -137,6 +137,30 @@ class BIM_PT_authoring(Panel): row.operator("bim.generate_spaces_from_walls") +class BIM_PT_GridsSpatialManager(Panel): + bl_label = "Grids and Containers" + bl_idname = "BIM_PT_GridsSpatialManager" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_options = {"DEFAULT_CLOSED"} + bl_category = "BlenderBIM" + + def draw(self, context): + pass + +class BIM_PT_Grids(Panel): + bl_label = "Grid Creator" + bl_idname = "BIM_PT_Grids" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_GridsSpatialManager" + + def draw(self, context): + self.animation_props = context.scene.BIMAnimationProperties + row = self.layout.row() + row.operator("mesh.add_grid", icon="ADD", text="Add Grids") + class BIM_PT_array(bpy.types.Panel): bl_label = "IFC Array" bl_idname = "BIM_PT_array" diff --git a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py index 01bac6c2cc..b29fff2371 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/__init__.py @@ -31,11 +31,21 @@ classes = ( operator.SelectContainer, operator.SelectSimilarContainer, operator.SelectProduct, + operator.LoadContainerManager, + operator.EditContainerAttributes, + operator.AddBuildingStorey, + operator.ContractContainer, + operator.ExpandContainer, + operator.DeleteContainer, prop.SpatialElement, prop.BIMSpatialProperties, prop.BIMObjectSpatialProperties, + prop.BIMContainer, + prop.BIMSpatialManagerProperties, ui.BIM_PT_spatial, ui.BIM_UL_containers, + ui.BIM_UL_containers_manager, + ui.BIM_PT_Storeys, workspace.Hotkey, ) @@ -45,8 +55,10 @@ def register(): bpy.utils.register_tool(workspace.SpatialTool, after={"bim.annotation_tool"}, separator=False, group=True) bpy.types.Scene.BIMSpatialProperties = bpy.props.PointerProperty(type=prop.BIMSpatialProperties) bpy.types.Object.BIMObjectSpatialProperties = bpy.props.PointerProperty(type=prop.BIMObjectSpatialProperties) + bpy.types.Scene.BIMSpatialManagerProperties = bpy.props.PointerProperty(type=prop.BIMSpatialManagerProperties) def unregister(): del bpy.types.Scene.BIMSpatialProperties del bpy.types.Object.BIMObjectSpatialProperties + del bpy.types.Scene.BIMSpatialManagerProperties diff --git a/src/blenderbim/blenderbim/bim/module/spatial/data.py b/src/blenderbim/blenderbim/bim/module/spatial/data.py index 32a98f5421..9623045536 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/data.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/data.py @@ -36,9 +36,20 @@ class SpatialData: "is_directly_contained": cls.is_directly_contained(), "label": cls.label(), "references": cls.references(), + "containers": cls.containers(), } cls.is_loaded = True + @classmethod + def containers(cls): + results = {} + for container in tool.Ifc.get().by_type("IfcSpatialElement"): + results[container.id()] = { + "type": container.is_a(), + "id": container.id(), + } + return results + @classmethod def get_parent_container_id(cls): container_id = bpy.context.scene.BIMSpatialProperties.active_container_id diff --git a/src/blenderbim/blenderbim/bim/module/spatial/operator.py b/src/blenderbim/blenderbim/bim/module/spatial/operator.py index 17358e3c2a..4cc2298ec8 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/operator.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/operator.py @@ -21,6 +21,8 @@ import ifcopenshell.api import ifcopenshell.util.element import blenderbim.tool as tool import blenderbim.core.spatial as core +import blenderbim.core.geometry +import blenderbim.core.aggregate import blenderbim.core.root import blenderbim.bim.handler from blenderbim.bim.ifc import IfcStore @@ -171,3 +173,76 @@ class SelectProduct(bpy.types.Operator): def execute(self, context): core.select_product(tool.Spatial, product=tool.Ifc.get().by_id(self.product)) return {"FINISHED"} + + +class LoadContainerManager(bpy.types.Operator): + bl_idname = "bim.load_container_manager" + bl_label = "Load Container Manager" + bl_options = {"REGISTER", "UNDO"} + + def execute(self, context): + core.load_container_manager(tool.Spatial) + return {"FINISHED"} + + +class EditContainerAttributes(bpy.types.Operator): + bl_idname = "bim.edit_container_attributes" + bl_label = "Edit container attributes" + bl_options = {"REGISTER", "UNDO"} + container: bpy.props.IntProperty() + + def execute(self, context): + core.edit_container_attributes(tool.Spatial, entity=tool.Ifc.get().by_id(self.container)) + return {"FINISHED"} + + +class ContractContainer(bpy.types.Operator): + bl_idname = "bim.contract_container" + bl_label = "Contract Container" + bl_options = {"REGISTER", "UNDO"} + container: bpy.props.IntProperty() + + def execute(self, context): + core.contract_container(tool.Spatial, container=tool.Ifc.get().by_id(self.container)) + return {"FINISHED"} + + +class ExpandContainer(bpy.types.Operator): + bl_idname = "bim.expand_container" + bl_label = "Expand Container" + bl_options = {"REGISTER", "UNDO"} + container: bpy.props.IntProperty() + + def execute(self, context): + core.expand_container(tool.Spatial, container=tool.Ifc.get().by_id(self.container)) + return {"FINISHED"} + + +class DeleteContainer(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.delete_container" + bl_label = "Delete Container" + bl_options = {"REGISTER", "UNDO"} + container: bpy.props.IntProperty() + + def _execute(self, context): + core.delete_container(tool.Ifc, tool.Spatial, tool.Geometry, container=tool.Ifc.get().by_id(self.container)) + +class AddBuildingStorey(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.add_building_storey" + bl_label = "Add Storey" + bl_options = {"REGISTER", "UNDO"} + part_class: bpy.props.StringProperty() + + def _execute(self, context): + active_container = tool.Spatial.get_active_container() + obj = tool.Ifc.get_object(active_container) + blenderbim.core.aggregate.add_part_to_object( + tool.Ifc, + tool.Aggregate, + tool.Collector, + tool.Blender, + obj=obj, + part_class=self.part_class, + part_name="Unnamed", + ) + core.load_container_manager(tool.Spatial) diff --git a/src/blenderbim/blenderbim/bim/module/spatial/prop.py b/src/blenderbim/blenderbim/bim/module/spatial/prop.py index 6dbecb6120..8f23beddb7 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/prop.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/prop.py @@ -30,6 +30,30 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +import blenderbim.tool as tool +import blenderbim.core.geometry + + +def update_elevation(self, context): + entity = tool.Ifc.get().by_id(self.active_container_id) + obj = tool.Ifc.get_object(entity) + if not obj: + return + obj.location.z = self.elevation + + +def update_active_container_index(self, context): + self.active_container_id = self.containers[self.active_container_index].ifc_definition_id + self.container_name = self.containers[self.active_container_index].name + self.elevation = self.containers[self.active_container_index].elevation + + +def updateContainerName(self, context): + props = context.scene.BIMSpatialManagerProperties + if not props.is_container_update_enabled or self.name == "Unnamed": + return + tool.Spatial.edit_container_name(tool.Ifc.get().by_id(props.active_container_id), self.name) + props.container_name = self.name class SpatialElement(PropertyGroup): @@ -48,3 +72,23 @@ class BIMSpatialProperties(PropertyGroup): class BIMObjectSpatialProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing") + + +class BIMContainer(PropertyGroup): + name: StringProperty(name="Name", update=updateContainerName) + elevation: FloatProperty(name="Elevation") + level_index: IntProperty(name="Level Index") + has_children: BoolProperty(name="Has Children") + is_expanded: BoolProperty(name="Is Expanded") + ifc_definition_id: IntProperty(name="IFC Definition ID") + + +class BIMSpatialManagerProperties(PropertyGroup): + containers: CollectionProperty(name="Containers", type=BIMContainer) + contracted_containers: StringProperty(name="Contracted containers", default="[]") + expanded_containers: StringProperty(name="Expanded containers", default="[]") + active_container_index: IntProperty(name="Active Container Index", update=update_active_container_index) + active_container_id: IntProperty(name="Active Container Id") + container_name: StringProperty(name="Container Name") + elevation: FloatProperty(name="Elevation", update=update_elevation) + is_container_update_enabled: BoolProperty(name="Is Container Update Enabled", default=True) # TODO:review diff --git a/src/blenderbim/blenderbim/bim/module/spatial/ui.py b/src/blenderbim/blenderbim/bim/module/spatial/ui.py index 3acac0906e..46451f8f56 100644 --- a/src/blenderbim/blenderbim/bim/module/spatial/ui.py +++ b/src/blenderbim/blenderbim/bim/module/spatial/ui.py @@ -19,6 +19,7 @@ from bpy.types import Panel, UIList from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.spatial.data import SpatialData +import blenderbim.tool as tool class BIM_PT_spatial(Panel): @@ -93,3 +94,70 @@ class BIM_UL_containers(UIList): text="", emboss=False, ) + + +class BIM_PT_Storeys(Panel): + bl_label = "Spatial Manager" + bl_idname = "BIM_PT_Storeys" + bl_space_type = "VIEW_3D" + bl_region_type = "UI" + bl_options = {"DEFAULT_CLOSED"} + bl_parent_id = "BIM_PT_GridsSpatialManager" + + @classmethod + def poll(cls, context): + return tool.Ifc.get() + + def draw(self, context): + if not SpatialData.is_loaded: + SpatialData.load() + self.props = context.scene.BIMSpatialManagerProperties + row = self.layout.row() + row.operator("bim.load_container_manager", icon="FILE_REFRESH", text="Load Spatial Structure") + if self.props.active_container_index < len(self.props.containers): + ifc_definition_id = self.props.containers[self.props.active_container_index].ifc_definition_id + row = self.layout.row() + row.alignment = "RIGHT" + if SpatialData.data["containers"][ifc_definition_id]["type"] in ["IfcBuildingStorey", "IfcBuilding"]: + row.operator("bim.add_building_storey", icon="ADD", text="Add storey").part_class = "IfcBuildingStorey" + row.operator("bim.delete_container", icon="X", text="Delete").container = ifc_definition_id + self.layout.template_list( + "BIM_UL_containers_manager", + "", + self.props, + "containers", + self.props, + "active_container_index", + ) + row = self.layout.row() + if self.props.active_container_index < len(self.props.containers): + row.prop(self.props, "container_name", text="") + row.prop(self.props, "elevation", text="") + op = row.operator("bim.edit_container_attributes", icon="CHECKMARK", text="Apply") + op.container = self.props.containers[self.props.active_container_index].ifc_definition_id + + +class BIM_UL_containers_manager(UIList): + def draw_item(self, context, layout, data, item, icon, active_data, active_propname): + if item: + row = layout.row(align=True) + self.draw_hierarchy(row, item) + split1 = row.split(factor=0.7) + split1.prop(item, "name", emboss=False, text="") + split2 = row.split(factor=1) + split2.label(text=str(item.elevation), icon="BLANK1") + + def draw_hierarchy(self, row, item): + for i in range(0, item.level_index): + row.label(text="", icon="BLANK1") + if item.has_children: + if item.is_expanded: + row.operator( + "bim.contract_container", text="", emboss=False, icon="DISCLOSURE_TRI_DOWN" + ).container = item.ifc_definition_id + else: + row.operator( + "bim.expand_container", text="", emboss=False, icon="DISCLOSURE_TRI_RIGHT" + ).container = item.ifc_definition_id + else: + row.label(text="", icon="DOT") diff --git a/src/blenderbim/blenderbim/core/spatial.py b/src/blenderbim/blenderbim/core/spatial.py index d832d8cfff..0bbbf614f4 100644 --- a/src/blenderbim/blenderbim/core/spatial.py +++ b/src/blenderbim/blenderbim/core/spatial.py @@ -96,3 +96,22 @@ def select_similar_container(ifc, spatial, obj=None): def select_product(spatial, product): spatial.select_products([product]) + +def load_container_manager(spatial): + spatial.load_container_manager() + +def edit_container_attributes(spatial, entity=None): + spatial.edit_container_attributes(entity) + spatial.load_container_manager() + +def contract_container(spatial, container=None): + spatial.contract_container(container) + spatial.load_container_manager() + +def expand_container(spatial, container=None): + spatial.expand_container(container) + spatial.load_container_manager() + +def delete_container(ifc, spatial, geometry, container=None): + geometry.delete_ifc_object(ifc.get_object(container)) + spatial.load_container_manager() diff --git a/src/blenderbim/blenderbim/core/tool.py b/src/blenderbim/blenderbim/core/tool.py index 7a0588c431..968646da52 100644 --- a/src/blenderbim/blenderbim/core/tool.py +++ b/src/blenderbim/blenderbim/core/tool.py @@ -747,12 +747,18 @@ class Sequence: class Spatial: def can_contain(cls, structure_obj, element_obj): pass def can_reference(cls, structure, element): pass + def contract_container(cls, container): pass def copy_xy(cls, src_obj, destination_obj): pass + def create_new_storey_li(cls, element, level_index): pass def deselect_objects(cls): pass def disable_editing(cls, obj): pass def duplicate_object_and_data(cls, obj): pass + def edit_container_attributes(cls, entity): pass + def edit_container_name(cls, container, name): pass def enable_editing(cls, obj): pass + def expand_container(cls, container): pass def filter_products(cls, products, action): pass + def get_active_container(cls): pass def get_container(cls, element): pass def get_decomposed_elements(cls, container): pass def get_object_matrix(cls, obj): pass @@ -760,6 +766,7 @@ class Spatial: def get_selected_product_types(cls): pass def get_selected_products(cls): pass def import_containers(cls, parent=None): pass + def load_container_manager(cls): pass def run_root_copy_class(cls, obj=None): pass def run_spatial_assign_container(cls, structure_obj=None, element_obj=None): pass def select_object(cls, obj): pass @@ -768,6 +775,7 @@ class Spatial: def set_relative_object_matrix(cls, target_obj, relative_to_obj, matrix): pass def show_scene_objects(cls): pass + @interface class Structural: def disable_editing_structural_analysis_model(cls): pass diff --git a/src/blenderbim/blenderbim/tool/geometry.py b/src/blenderbim/blenderbim/tool/geometry.py index 5b89951f18..96676c1117 100644 --- a/src/blenderbim/blenderbim/tool/geometry.py +++ b/src/blenderbim/blenderbim/tool/geometry.py @@ -24,6 +24,7 @@ import numpy as np import ifcopenshell import blenderbim.core.tool import blenderbim.core.style +import blenderbim.core.spatial import blenderbim.tool as tool import blenderbim.bim.import_ifc from mathutils import Vector @@ -97,12 +98,15 @@ class Geometry(blenderbim.core.tool.Geometry): if element.VoidsElements: bpy.ops.bim.remove_opening(opening_id=element.id()) else: + is_spatial = element.is_a("IfcSpatialElement") if getattr(element, "HasOpenings", None): for rel in element.HasOpenings: bpy.ops.bim.remove_opening(opening_id=rel.RelatedOpeningElement.id()) for port in ifcopenshell.util.system.get_ports(element): blenderbim.core.system.remove_port(tool.Ifc, tool.System, port=port) ifcopenshell.api.run("root.remove_product", tool.Ifc.get(), product=element) + if is_spatial: + blenderbim.core.spatial.load_container_manager(tool.Spatial) try: obj.name bpy.data.objects.remove(obj) diff --git a/src/blenderbim/blenderbim/tool/spatial.py b/src/blenderbim/blenderbim/tool/spatial.py index bce06d8b77..b88d87aa4b 100644 --- a/src/blenderbim/blenderbim/tool/spatial.py +++ b/src/blenderbim/blenderbim/tool/spatial.py @@ -22,6 +22,7 @@ import blenderbim.core.tool import blenderbim.core.root import blenderbim.core.spatial import blenderbim.tool as tool +import json class Spatial(blenderbim.core.tool.Spatial): @@ -110,6 +111,7 @@ class Spatial(blenderbim.core.tool.Spatial): new.long_name = element.LongName or "" new.has_decomposition = bool(element.IsDecomposedBy) new.ifc_definition_id = element.id() + new.elevation = element[1] @classmethod def run_root_copy_class(cls, obj=None): @@ -151,7 +153,11 @@ class Spatial(blenderbim.core.tool.Spatial): [obj.select_set(True) for obj in objects] elif action == "isolate": [obj.hide_set(False) for obj in objects if bpy.context.view_layer.objects.get(obj.name)] - [obj.hide_set(True) for obj in bpy.context.visible_objects if not obj in objects and bpy.context.view_layer.objects.get(obj.name)] # this is slow + [ + obj.hide_set(True) + for obj in bpy.context.visible_objects + if not obj in objects and bpy.context.view_layer.objects.get(obj.name) + ] # this is slow elif action == "unhide": [obj.hide_set(False) for obj in objects if bpy.context.view_layer.objects.get(obj.name)] elif action == "hide": @@ -164,7 +170,11 @@ class Spatial(blenderbim.core.tool.Spatial): @classmethod def show_scene_objects(cls): - [obj.hide_set(False) for obj in bpy.data.scenes["Scene"].objects if bpy.context.view_layer.objects.get(obj.name)] + [ + obj.hide_set(False) + for obj in bpy.data.scenes["Scene"].objects + if bpy.context.view_layer.objects.get(obj.name) + ] @classmethod def get_selected_products(cls): @@ -185,3 +195,66 @@ class Spatial(blenderbim.core.tool.Spatial): z = src_obj.location[2] src_obj.location = (destination_obj.location[0], destination_obj.location[1], z) + @classmethod + def load_container_manager(cls): + cls.props = bpy.context.scene.BIMSpatialManagerProperties + cls.props.containers.clear() + cls.contracted_containers = json.loads(cls.props.contracted_containers) + cls.props.is_container_update_enabled = False + parent = tool.Ifc.get().by_type("IfcProject")[0] + + for object in ifcopenshell.util.element.get_parts(parent): + if object.is_a("IfcSpatialElement"): + cls.create_new_storey_li(object, 0) + cls.props.is_container_update_enabled = True + + @classmethod + def create_new_storey_li(cls, element, level_index): + new = cls.props.containers.add() + new.name = element.Name or "Unnamed" + new.long_name = element.LongName or "" + new.has_decomposition = bool(element.IsDecomposedBy) + new.ifc_definition_id = element.id() + new.elevation = ifcopenshell.util.placement.get_storey_elevation(element) + + new.is_expanded = element.id() not in cls.contracted_containers + new.level_index = level_index + if new.has_decomposition: + new.has_children = True + if new.is_expanded: + for related_object in ifcopenshell.util.element.get_parts(element): + if related_object.is_a("IfcSpatialElement"): + cls.create_new_storey_li(related_object, level_index + 1) + + @classmethod + def edit_container_attributes(cls, entity): + obj = tool.Ifc.get_object(entity) + blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) + name = bpy.context.scene.BIMSpatialManagerProperties.container_name + if name != entity.Name: + cls.edit_container_name(entity, name) + + @classmethod + def edit_container_name(cls, container, name): + tool.Ifc.run("attribute.edit_attributes", product=container, attributes={"Name": name}) + + @classmethod + def get_active_container(cls): + props = bpy.context.scene.BIMSpatialManagerProperties + if props.active_container_index < len(props.containers): + container = tool.Ifc.get().by_id(props.containers[props.active_container_index].ifc_definition_id) + return container + + @classmethod + def contract_container(cls, container): + props = bpy.context.scene.BIMSpatialManagerProperties + contracted_containers = json.loads(props.contracted_containers) + contracted_containers.append(container.id()) + props.contracted_containers = json.dumps(contracted_containers) + + @classmethod + def expand_container(cls, container): + props = bpy.context.scene.BIMSpatialManagerProperties + contracted_containers = json.loads(props.contracted_containers) + contracted_containers.remove(container.id()) + props.contracted_containers = json.dumps(contracted_containers)