diff --git a/src/blenderbim/blenderbim/bim/module/model/__init__.py b/src/blenderbim/blenderbim/bim/module/model/__init__.py index c226210dae..5a43a63b56 100644 --- a/src/blenderbim/blenderbim/bim/module/model/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/model/__init__.py @@ -17,11 +17,13 @@ # along with BlenderBIM Add-on. If not, see . import bpy -from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace +from . import handler, prop, ui, grid, product, wall, slab, stair, opening, pie, workspace, profile classes = ( product.AddEmptyType, product.AddTypeInstance, + product.DisplayIFCTypes, + product.AddIFCTypeInstance, product.AlignProduct, product.DynamicallyVoidProduct, workspace.Hotkey, @@ -32,6 +34,7 @@ classes = ( opening.AddElementOpening, profile.ExtendProfile, prop.BIMModelProperties, + prop.IfcTypeInfo, ui.BIM_PT_authoring, ui.BIM_PT_authoring_architectural, grid.BIM_OT_add_object, @@ -51,6 +54,7 @@ def register(): if not bpy.app.background: bpy.utils.register_tool(workspace.BimTool, after={"builtin.scale_cage"}, separator=True, group=True) bpy.types.Scene.BIMModelProperties = bpy.props.PointerProperty(type=prop.BIMModelProperties) + bpy.types.Scene.IfcTypeInfo = bpy.props.CollectionProperty(type=prop.IfcTypeInfo) bpy.types.VIEW3D_MT_mesh_add.append(grid.add_object_button) bpy.types.VIEW3D_MT_mesh_add.append(stair.add_object_button) bpy.types.VIEW3D_MT_mesh_add.append(opening.add_object_button) @@ -68,6 +72,7 @@ def unregister(): if not bpy.app.background: bpy.utils.unregister_tool(workspace.BimTool) del bpy.types.Scene.BIMModelProperties + del bpy.types.Scene.IfcTypeInfo bpy.app.handlers.load_post.remove(handler.load_post) bpy.types.VIEW3D_MT_mesh_add.remove(grid.add_object_button) bpy.types.VIEW3D_MT_mesh_add.remove(stair.add_object_button) diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py index 393fe73273..3048360ab6 100644 --- a/src/blenderbim/blenderbim/bim/module/model/data.py +++ b/src/blenderbim/blenderbim/bim/module/model/data.py @@ -18,6 +18,10 @@ import bpy import blenderbim.tool as tool +from blenderbim.bim.ifc import IfcStore + + +preview_icon_ids = {} def refresh(): @@ -31,10 +35,23 @@ class AuthoringData: @classmethod def load(cls): cls.is_loaded = True - cls.data = { - "ifc_classes": cls.ifc_classes(), - "relating_types": cls.relating_types(), - } + if not hasattr(cls, "data"): + cls.data = {} + cls.load_ifc_classes() + cls.load_relating_types() + cls.load_preview_ifc_types() + + @classmethod + def load_ifc_classes(cls): + cls.data["ifc_classes"] = cls.ifc_classes() + + @classmethod + def load_relating_types(cls): + cls.data["relating_types"] = cls.relating_types() + + @classmethod + def load_preview_ifc_types(cls): + cls.data["preview_ifc_types"] = preview_icon_ids @classmethod def ifc_classes(cls): @@ -49,16 +66,71 @@ class AuthoringData: return results @classmethod - def relating_types(cls): - ifc_classes = cls.ifc_classes() + def ifc_class_entities(cls, ifc_class=None): + ifc_classes = cls.data["ifc_classes"] if not ifc_classes: return [] results = [] - ifc_class = bpy.context.scene.BIMModelProperties.ifc_class + if ifc_class is None: + ifc_class = bpy.context.scene.BIMModelProperties.ifc_class if not ifc_class and ifc_classes: ifc_class = ifc_classes[0][0] if ifc_class: - elements = [(str(e.id()), e.Name, e.Description or "") for e in tool.Ifc.get().by_type(ifc_class)] - results.extend(sorted(elements, key=lambda s: s[1])) + elements = sorted(tool.Ifc.get().by_type(ifc_class), key=lambda s: s.Name) + results.extend(elements) return results return [] + + @classmethod + def relating_types(cls, ifc_class=None): + return [(str(e.id()), e.Name, e.Description or "") for e in cls.ifc_class_entities(ifc_class=ifc_class)] + + @classmethod + def assetize_relating_type_from_selection(cls): + props = bpy.context.scene.BIMModelProperties + ifc_class = props.ifc_class + relating_type_id = props.relating_type + ifc_class_occurrences = cls.ifc_class_entities(ifc_class=ifc_class) + ifc_class_occurrences = [entity for entity in ifc_class_occurrences if entity.id() == int(relating_type_id)] + if len(ifc_class_occurrences) == 0: + return + ifc_class_entity = ifc_class_occurrences[0] + obj = tool.Ifc.get_object(ifc_class_entity) + relating_type = ifc_class_entity.Name + to_be_deleted = False + if obj.type == 'EMPTY': + bpy.ops.bim.add_type_instance() + new_obj = bpy.context.selected_objects[-1] + if new_obj is not None: + to_be_deleted = True + obj = new_obj + obj.asset_mark() + obj.asset_generate_preview() + icon_id = obj.preview.icon_id + if ifc_class not in cls.data["preview_ifc_types"]: + cls.data["preview_ifc_types"][ifc_class] = {} + cls.data["preview_ifc_types"][ifc_class][relating_type] = {"icon_id": icon_id, "object": obj} + if to_be_deleted: + for col in obj.users_collection: + col.objects.unlink(obj) + + @staticmethod + def ifc_type_info(ifc_class): + ifc_type_infos = [element for element in bpy.context.scene.IfcTypeInfo if element.ifc_class == ifc_class] + return None if len(ifc_type_infos) == 0 else ifc_type_infos[0] + + @classmethod + def new_ifc_type_instance(cls, ifc_class, relating_type_id): + props = bpy.context.scene.BIMModelProperties + props.ifc_class = ifc_class + props.relating_type = str(relating_type_id) + bpy.ops.bim.add_type_instance() + + @staticmethod + def relating_type_name_by_id(ifc_class, relating_type_id): + file = IfcStore.get_file() + try: + ifc_class_entity = file.by_id(int(relating_type_id)) + except (RuntimeError, ValueError): + return None + return ifc_class_entity.Name if ifc_class_entity.is_a() == ifc_class else None diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index b56712216d..f18b748cf8 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -29,6 +29,7 @@ import blenderbim.core.type import blenderbim.core.geometry from . import wall, slab, profile, mep from blenderbim.bim.ifc import IfcStore +from blenderbim.bim.module.model.data import AuthoringData from ifcopenshell.api.pset.data import Data as PsetData from mathutils import Vector, Matrix from bpy_extras.object_utils import AddObjectHelper @@ -152,7 +153,8 @@ class AddTypeInstance(bpy.types.Operator): context.view_layer.objects.active = obj return {"FINISHED"} - def generate_layered_element(self, ifc_class, relating_type): + @staticmethod + def generate_layered_element(ifc_class, relating_type): layer_set_direction = None parametric = ifcopenshell.util.element.get_psets(relating_type).get("EPset_Parametric") @@ -174,6 +176,83 @@ class AddTypeInstance(bpy.types.Operator): pass # Dumb block generator? Eh? :) +class DisplayIFCTypes(bpy.types.Operator): + bl_idname = "bim.display_ifc_types" + bl_label = "Browse IFC Types" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Display all possible IFC types for new instances" + + def execute(self, context): + bpy.ops.object.mode_set(mode="OBJECT") + return {"FINISHED"} + + def invoke(self, context, event): + if not AuthoringData.is_loaded: + AuthoringData.load() + props = context.scene.BIMModelProperties + props.relating_type = props.relating_type + min_width = 250 + width = max([min_width, int(context.region.width / 7)]) + return context.window_manager.invoke_popup(self, width=width) + + def draw(self, context): + layout = self.layout + props = context.scene.BIMModelProperties + split = layout.split(align=True, factor=0.6) + col = split.column(align=True) + row = col.row() + row.label(text="Select IFC Type to add:") + col.row().separator(factor=2) + enabled = True + if AuthoringData.data["ifc_classes"]: + row = col.row() + row.prop(data=props, property="ifc_class", text="", icon="FILE_VOLUME") + col.row().separator() + else: + enabled = False + if AuthoringData.data["relating_types"]: + row = col.row() + row.prop(data=props, property="relating_type", text="", icon="FILE_3D") + col.row().separator() + else: + enabled = False + col.row().separator(factor=4) + row = col.row() + op = row.operator("bim.add_ifc_type_instance", icon="ADD") + row.enabled = enabled + op.ifc_class = props.ifc_class + op.relating_type_id = props.relating_type + col = split.column() + box = col.box() + if enabled: + box.template_icon(icon_value=props.icon_id, scale=6.) + + +class AddIFCTypeInstance(bpy.types.Operator): + bl_idname = "bim.add_ifc_type_instance" + bl_label = "Add" + bl_options = {"REGISTER", "UNDO"} + bl_description = "Add an instance of this IFC type" + ifc_class: bpy.props.StringProperty() + relating_type_id: bpy.props.StringProperty() + + def close_panel(self, event): + x, y = event.mouse_x, event.mouse_y + bpy.context.window.cursor_warp(10, 10) + move_back = lambda: bpy.context.window.cursor_warp(x, y) + bpy.app.timers.register(move_back, first_interval=0.001) + + def invoke(self, context, event): + self.close_panel(event) + return self.execute(context) + + def execute(self, context): + props = context.scene.BIMModelProperties + props.ifc_class, props.relating_type = self.ifc_class, self.relating_type_id + bpy.ops.bim.add_type_instance() + return {'FINISHED'} + + class AlignProduct(bpy.types.Operator): bl_idname = "bim.align_product" bl_label = "Align Product" diff --git a/src/blenderbim/blenderbim/bim/module/model/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index 1cbe8eb3df..5da1e67535 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -21,17 +21,8 @@ import ifcopenshell.util.type from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.ifc import IfcStore +import blenderbim.tool as tool from bpy.types import PropertyGroup -from bpy.props import ( - PointerProperty, - StringProperty, - EnumProperty, - BoolProperty, - IntProperty, - FloatProperty, - FloatVectorProperty, - CollectionProperty, -) def get_ifc_class(self, context): @@ -46,15 +37,50 @@ def get_relating_type(self, context): return AuthoringData.data["relating_types"] +def update_icon_id(self, context): + ifc_class = self.ifc_class + relating_type_id = self.relating_type + relating_type = AuthoringData.relating_type_name_by_id(ifc_class, relating_type_id) + if ((ifc_class not in AuthoringData.data["preview_ifc_types"] + or relating_type not in AuthoringData.data["preview_ifc_types"][ifc_class]) + and relating_type is not None): + AuthoringData.assetize_relating_type_from_selection() + props = bpy.context.scene.BIMModelProperties + props.icon_id = AuthoringData.data["preview_ifc_types"][ifc_class][relating_type]["icon_id"] + + def update_ifc_class(self, context): - AuthoringData.is_loaded = False + AuthoringData.load_ifc_classes() + AuthoringData.load_relating_types() + self.relating_type = AuthoringData.data["relating_types"][0][0] + update_icon_id(self, context) + + +def update_relating_type(self, context): + AuthoringData.load_relating_types() + update_icon_id(self, context) class BIMModelProperties(PropertyGroup): ifc_class: bpy.props.EnumProperty(items=get_ifc_class, name="IFC Class", update=update_ifc_class) - relating_type: bpy.props.EnumProperty(items=get_relating_type, name="Relating Type") + relating_type: bpy.props.EnumProperty(items=get_relating_type, name="Relating Type", update=update_relating_type) + icon_id: bpy.props.IntProperty() occurrence_name_style: bpy.props.EnumProperty( items=[("CLASS", "By Class", ""), ("TYPE", "By Type", ""), ("CUSTOM", "Custom", "")], name="Occurrence Name Style", ) occurrence_name_function: bpy.props.StringProperty(name="Occurrence Name Function") + + +def get_ifc_type_info_relating_types(self, context): + ifc_class = self.ifc_class + return AuthoringData.relating_types(ifc_class=ifc_class) + + +class IfcTypeInfo(PropertyGroup): + ifc_class: bpy.props.StringProperty(name="IFC class", description="IFC class") + relating_type: bpy.props.EnumProperty( + name="Relating type", description="Relating type", items=get_ifc_type_info_relating_types + ) + + diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index 20239c2855..1f65486c12 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -37,7 +37,6 @@ class BimTool(WorkSpaceTool): bl_keymap = ( # ("bim.wall_tool_op", {"type": 'MOUSEMOVE', "value": 'ANY'}, {"properties": []}), # ("mesh.add_wall", {"type": 'LEFTMOUSE', "value": 'PRESS'}, {"properties": []}), - ("bim.hotkey", {"type": "A", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_A")]}), ("bim.hotkey", {"type": "E", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_E")]}), ("bim.join_wall", {"type": "T", "value": "PRESS", "shift": True}, {"properties": [("join_type", "L")]}), ("bim.join_wall", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("join_type", "V")]}), @@ -61,20 +60,14 @@ class BimTool(WorkSpaceTool): return props = context.scene.BIMModelProperties if AuthoringData.data["ifc_classes"]: - row.prop(props, "ifc_class", text="") + row.operator("bim.display_ifc_types", icon="COLLAPSEMENU") else: row.label(text="No IFC Class") - if AuthoringData.data["relating_types"]: - row.prop(props, "relating_type", text="") - else: + if not AuthoringData.data["relating_types"]: row.label(text="No Relating Type") row.label(text="", icon="BLANK1") - row = layout.row(align=True) - row.label(text="", icon="EVENT_SHIFT") - row.label(text="Add Type Instance", icon="EVENT_A") - if AuthoringData.data["ifc_classes"]: if props.ifc_class == "IfcWallType": row = layout.row() @@ -155,9 +148,6 @@ class Hotkey(bpy.types.Operator): getattr(self, f"hotkey_{self.hotkey}")() return {"FINISHED"} - def hotkey_S_A(self): - bpy.ops.bim.add_type_instance() - def hotkey_S_C(self): if self.has_ifc_class and self.props.ifc_class == "IfcWallType": bpy.ops.bim.align_wall(align_type="CENTERLINE")