From af7bdf6b9f248e3b8a36ec618384c52c444c79f9 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 30 Sep 2024 14:03:56 +1000 Subject: [PATCH] Deprecate type_class in favour of new simpler type manager workflow --- src/bonsai/bonsai/bim/module/model/data.py | 172 +++--------- src/bonsai/bonsai/bim/module/model/mep.py | 2 +- src/bonsai/bonsai/bim/module/model/product.py | 64 +++-- src/bonsai/bonsai/bim/module/model/prop.py | 29 -- src/bonsai/bonsai/bim/module/model/ui.py | 3 +- .../bonsai/bim/module/model/workspace.py | 99 +++---- src/bonsai/bonsai/bim/module/type/__init__.py | 1 - src/bonsai/bonsai/bim/module/type/operator.py | 251 ------------------ src/bonsai/bonsai/tool/blender.py | 10 +- 9 files changed, 134 insertions(+), 497 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/model/data.py b/src/bonsai/bonsai/bim/module/model/data.py index 812ed34c1c..d9952dde04 100644 --- a/src/bonsai/bonsai/bim/module/model/data.py +++ b/src/bonsai/bonsai/bim/module/model/data.py @@ -59,10 +59,8 @@ class AuthoringData: cls.data["relating_type_id"] = cls.relating_type_id() # only after .ifc_classes() cls.data["relating_type_name"] = cls.relating_type_name() # only after .relating_type_id() cls.data["predefined_type"] = cls.predefined_type() # only after .relating_type_id() - cls.data["type_class"] = cls.type_class() - # only after .type_class() - cls.data["type_predefined_type"] = cls.type_predefined_type() + # only after .ifc_classes() cls.data["total_types"] = cls.total_types() cls.data["total_pages"] = cls.total_pages() cls.data["next_page"] = cls.next_page() @@ -78,7 +76,6 @@ class AuthoringData: cls.data["active_representation_type"] = cls.active_representation_type() cls.data["boundary_class"] = cls.boundary_class() cls.data["selected_material_usages"] = cls.selected_material_usages() - cls.data["type_template"] = cls.type_template() @classmethod def default_container(cls) -> str | None: @@ -97,41 +94,6 @@ class AuthoringData: version = tool.Ifc.get_schema() return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)] - @classmethod - def type_class(cls): - declaration = tool.Ifc.schema().declaration_by_name("IfcElementType") - declarations = ifcopenshell.util.schema.get_subtypes(declaration) - names = [d.name() for d in declarations] - - if tool.Ifc.get_schema() == "IFC2X3": - declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialStructureElementType") - else: - declaration = tool.Ifc.schema().declaration_by_name("IfcSpatialElementType") - declarations = ifcopenshell.util.schema.get_subtypes(declaration) - names.extend([d.name() for d in declarations]) - - if tool.Ifc.get_schema() in ("IFC2X3", "IFC4"): - names.extend(("IfcDoorStyle", "IfcWindowStyle")) - - version = tool.Ifc.get_schema() - return [(c, c, get_entity_doc(version, c).get("description", "")) for c in sorted(names)] - - @classmethod - def type_predefined_type(cls): - results = [] - declaration = tool.Ifc().schema().declaration_by_name(cls.props.type_class) - version = tool.Ifc.get_schema() - for attribute in declaration.attributes(): - if attribute.name() == "PredefinedType": - results.extend( - [ - (e, e, get_predefined_type_doc(version, cls.props.type_class, e)) - for e in attribute.type_of_attribute().declared_type().enumeration_items() - ] - ) - break - return results - @classmethod def type_thumbnail(cls): if not cls.data["relating_type_id"]: @@ -142,76 +104,15 @@ class AuthoringData: element = tool.Ifc.get().by_id(int(relating_type_id)) return cls.type_thumbnails.get(element.id(), None) or 0 - @classmethod - def type_template(cls): - type_class = cls.props.type_class - templates = [ - ( - "MESH", - "Custom Mesh", - "Use as a representation currently active object mesh or default cube if no object selected", - ), - ( - "LAYERSET_AXIS2", - "Vertical Layers", - "For objects similar to walls, will automatically add IfcMaterialLayerSet", - ), - ( - "LAYERSET_AXIS3", - "Horizontal Layers", - "For objects similar to slabs, will automatically add IfcMaterialLayerSet", - ), - ( - "PROFILESET", - "Extruded Profile", - "Create profile type object, automatically defines IfcMaterialProfileSet with the first profile from library", - ), - ("EMPTY", "Non-Geometric Type", "Start with an empty object"), - ] - if not type_class: - pass - elif type_class in ("IfcWindowType", "IfcWindowStyle"): - templates.append(("WINDOW", "Window", "Parametric window")) - elif type_class in ("IfcDoorType", "IfcDoorStyle"): - templates.append(("DOOR", "Door", "Parametric door")) - elif type_class in ("IfcStairType", "IfcStairFlightType"): - templates.append(("STAIR", "Stair", "Parametric stair")) - elif type_class == "IfcRailingType": - templates.append(("RAILING", "Railing", "Parametric railing")) - elif type_class == "IfcRoofType": - templates.append(("Roof", "Roof", "Parametric roof")) - - templates.extend( - ( - ( - "FLOW_SEGMENT_RECTANGULAR", - "Rectangular Distribution Segment", - "Works similarly to Profile, has distribution ports", - ), - ( - "FLOW_SEGMENT_CIRCULAR", - "Circular Distribution Segment", - "Works similarly to Profile, has distribution ports", - ), - ( - "FLOW_SEGMENT_CIRCULAR_HOLLOW", - "Circular Hollow Distribution Segment", - "Works similarly to Profile, has distribution ports", - ), - ) - ) - - return templates - @classmethod def total_types(cls): - type_class = cls.props.type_class - return len(tool.Ifc.get().by_type(type_class)) if type_class else 0 + ifc_class = cls.props.ifc_class + return len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0 @classmethod def total_pages(cls): - type_class = cls.props.type_class - total_types = len(tool.Ifc.get().by_type(type_class)) if type_class else 0 + ifc_class = cls.props.ifc_class + total_types = len(tool.Ifc.get().by_type(ifc_class)) if ifc_class else 0 return math.ceil(total_types / cls.types_per_page) @classmethod @@ -226,11 +127,11 @@ class AuthoringData: @classmethod def paginated_relating_types(cls): - type_class = cls.props.type_class - if not type_class: + ifc_class = cls.props.ifc_class + if not ifc_class: return [] results = [] - elements = natsorted(tool.Ifc.get().by_type(type_class), key=lambda e: e.Name or "Unnamed") + elements = natsorted(tool.Ifc.get().by_type(ifc_class), key=lambda e: e.Name or "Unnamed") elements = elements[(cls.props.type_page - 1) * cls.types_per_page : cls.props.type_page * cls.types_per_page] for element in elements: predefined_type = ifcopenshell.util.element.get_predefined_type(element) @@ -250,45 +151,50 @@ class AuthoringData: @classmethod def is_voidable_element(cls): - element = tool.Ifc.get_entity(bpy.context.active_object) - return element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement") + if active_object := tool.Blender.get_active_object(): + element = tool.Ifc.get_entity(active_object) + return element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement") @classmethod def has_visible_openings(cls): - element = tool.Ifc.get_entity(bpy.context.active_object) - if element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement"): - for opening in [r.RelatedOpeningElement for r in element.HasOpenings]: - if tool.Ifc.get_object(opening): - return True + if active_object := tool.Blender.get_active_object(): + element = tool.Ifc.get_entity(active_object) + if element and element.is_a("IfcElement") and not element.is_a("IfcOpeningElement"): + for opening in [r.RelatedOpeningElement for r in element.HasOpenings]: + if tool.Ifc.get_object(opening): + return True return False @classmethod def has_visible_boundaries(cls): - element = tool.Ifc.get_entity(bpy.context.active_object) - if element: - if element.is_a("IfcRelSpaceBoundary"): - return True - for boundary in getattr(element, "BoundedBy", []): - if tool.Ifc.get_object(boundary): + if active_object := tool.Blender.get_active_object(): + element = tool.Ifc.get_entity(active_object) + if element: + if element.is_a("IfcRelSpaceBoundary"): return True + for boundary in getattr(element, "BoundedBy", []): + if tool.Ifc.get_object(boundary): + return True return False @classmethod def active_class(cls): - element = tool.Ifc.get_entity(bpy.context.active_object) - if element: - return element.is_a() + if active_object := tool.Blender.get_active_object(): + element = tool.Ifc.get_entity(active_object) + if element: + return element.is_a() @classmethod def active_material_usage(cls): - element = tool.Ifc.get_entity(bpy.context.active_object) - if element: - return tool.Model.get_usage_type(element) + if active_object := tool.Blender.get_active_object(): + element = tool.Ifc.get_entity(active_object) + if element: + return tool.Model.get_usage_type(element) @classmethod def active_representation_type(cls): - if bpy.context.active_object: - representation = tool.Geometry.get_active_representation(bpy.context.active_object) + if active_object := tool.Blender.get_active_object(): + representation = tool.Geometry.get_active_representation(active_object) if representation and representation.is_a("IfcShapeRepresentation"): representation = tool.Geometry.resolve_mapped_representation(representation) return representation.RepresentationType @@ -317,7 +223,7 @@ class AuthoringData: if not ifc_classes: return [] results = [] - ifc_class = cls.props.ifc_class + ifc_class = tool.Blender.get_enum_safe(cls.props, "ifc_class") if not ifc_class and ifc_classes: ifc_class = ifc_classes[0][0] if ifc_class: @@ -328,7 +234,11 @@ class AuthoringData: @classmethod def relating_type_name(cls): - if relating_type_id := cls.props.relating_type_id: + relating_type_id = tool.Blender.get_enum_safe(cls.props, "relating_type_id") + relating_type_id_data = cls.data["relating_type_id"] + if not relating_type_id and relating_type_id_data: + relating_type_id = relating_type_id_data[0][0] + if relating_type_id: return tool.Ifc.get().by_id(int(relating_type_id)).Name or "Unnamed" @classmethod @@ -345,7 +255,7 @@ class AuthoringData: @classmethod def selected_material_usages(cls): selected_usages = {} - for obj in bpy.context.selected_objects: + for obj in tool.Blender.get_selected_objects(): element = tool.Ifc.get_entity(obj) if not element: continue diff --git a/src/bonsai/bonsai/bim/module/model/mep.py b/src/bonsai/bonsai/bim/module/model/mep.py index 542924ddda..8886036cdd 100644 --- a/src/bonsai/bonsai/bim/module/model/mep.py +++ b/src/bonsai/bonsai/bim/module/model/mep.py @@ -516,7 +516,7 @@ class MEPGenerator: break def create_obstruction_type(self, segment): - # code is very similar to "bim.add_type" + # code is very similar to "bim.add_element" profile_set = ifcopenshell.util.element.get_material(segment, should_skip_usage=True) material_profile = profile_set.MaterialProfiles[0] profile = material_profile.Profile diff --git a/src/bonsai/bonsai/bim/module/model/product.py b/src/bonsai/bonsai/bim/module/model/product.py index 4c8c7bb96e..2c3253320d 100644 --- a/src/bonsai/bonsai/bim/module/model/product.py +++ b/src/bonsai/bonsai/bim/module/model/product.py @@ -67,37 +67,37 @@ class AddDefaultType(bpy.types.Operator, tool.Ifc.Operator): ifc_element_type: bpy.props.StringProperty() def _execute(self, context): - props = context.scene.BIMModelProperties - ifc_file = tool.Ifc.get() - props.type_class = self.ifc_element_type + props = context.scene.BIMRootProperties + props.ifc_product = "IfcElementType" + props.ifc_class = self.ifc_element_type if self.ifc_element_type == "IfcWallType": - if ifc_file.schema == "IFC2X3": - props.type_predefined_type = "STANDARD" + if tool.Ifc.get().schema == "IFC2X3": + props.ifc_predefined_type = "STANDARD" else: - props.type_predefined_type = "SOLIDWALL" - props.type_template = "LAYERSET_AXIS2" + props.ifc_predefined_type = "SOLIDWALL" + props.representation_template = "LAYERSET_AXIS2" elif self.ifc_element_type == "IfcSlabType": - props.type_predefined_type = "FLOOR" - props.type_template = "LAYERSET_AXIS3" + props.ifc_predefined_type = "FLOOR" + props.representation_template = "LAYERSET_AXIS3" elif self.ifc_element_type == "IfcDoorType": - props.type_predefined_type = "DOOR" - props.type_template = "DOOR" + props.ifc_predefined_type = "DOOR" + props.representation_template = "DOOR" elif self.ifc_element_type == "IfcWindowType": - props.type_predefined_type = "WINDOW" - props.type_template = "WINDOW" + props.ifc_predefined_type = "WINDOW" + props.representation_template = "WINDOW" elif self.ifc_element_type == "IfcColumnType": - props.type_predefined_type = "COLUMN" - props.type_template = "PROFILESET" + props.ifc_predefined_type = "COLUMN" + props.representation_template = "PROFILESET" elif self.ifc_element_type == "IfcBeamType": - props.type_predefined_type = "BEAM" - props.type_template = "PROFILESET" + props.ifc_predefined_type = "BEAM" + props.representation_template = "PROFILESET" elif self.ifc_element_type == "IfcDuctSegmentType": - props.type_predefined_type = "RIGIDSEGMENT" - props.type_template = "FLOW_SEGMENT_RECTANGULAR" + props.ifc_predefined_type = "RIGIDSEGMENT" + props.representation_template = "FLOW_SEGMENT_RECTANGULAR" elif self.ifc_element_type == "IfcPipeSegmentType": - props.type_predefined_type = "RIGIDSEGMENT" - props.type_template = "FLOW_SEGMENT_CIRCULAR" - bpy.ops.bim.add_type() + props.ifc_predefined_type = "RIGIDSEGMENT" + props.representation_template = "FLOW_SEGMENT_CIRCULAR" + bpy.ops.bim.add_element() class AddOccurrence(bpy.types.Operator, PolylineOperator): @@ -111,10 +111,6 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator): def __init__(self): super().__init__() - self.relating_type = None - relating_type_id = bpy.context.scene.BIMModelProperties.relating_type_id - if relating_type_id: - self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) def create_occurrence(self, context, event): if not self.relating_type: @@ -151,8 +147,20 @@ class AddOccurrence(bpy.types.Operator, PolylineOperator): snap_obj.select_set(False) def modal(self, context, event): + # Ensure state of BIM tool props is valid + props = bpy.context.scene.BIMModelProperties + relating_type_id = tool.Blender.get_enum_safe(props, "relating_type_id") + relating_type_id_data = AuthoringData.data["relating_type_id"] + if not relating_type_id and relating_type_id_data: + props.relating_type_id = relating_type_id_data[0][0] + + self.relating_type = None + relating_type_id = props.relating_type_id + if relating_type_id: + self.relating_type = tool.Ifc.get().by_id(int(relating_type_id)) + if not self.relating_type: - self.report({"WARNING"}, "You need to select a wall type.") + self.report({"WARNING"}, "You need to select a type.") PolylineDecorator.uninstall() tool.Blender.update_viewport() return {"FINISHED"} @@ -463,7 +471,7 @@ class ChangeTypePage(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): props = context.scene.BIMModelProperties - bpy.ops.bim.load_type_thumbnails(ifc_class=props.type_class, offset=9 * (self.page - 1), limit=9) + bpy.ops.bim.load_type_thumbnails(ifc_class=props.ifc_class, offset=9 * (self.page - 1), limit=9) props.type_page = self.page return {"FINISHED"} diff --git a/src/bonsai/bonsai/bim/module/model/prop.py b/src/bonsai/bonsai/bim/module/model/prop.py index 4eb6ae4f6b..44600191ef 100644 --- a/src/bonsai/bonsai/bim/module/model/prop.py +++ b/src/bonsai/bonsai/bim/module/model/prop.py @@ -32,12 +32,6 @@ def get_ifc_class(self, context): return AuthoringData.data["ifc_classes"] -def get_type_class(self, context): - if not AuthoringData.is_loaded: - AuthoringData.load() - return AuthoringData.data["type_class"] - - def get_boundary_class(self, context): if not AuthoringData.is_loaded: AuthoringData.load() @@ -50,18 +44,6 @@ def get_relating_type_id(self, context): return AuthoringData.data["relating_type_id"] -def get_type_predefined_type(self, context): - if not AuthoringData.is_loaded: - AuthoringData.load() - return AuthoringData.data["type_predefined_type"] - - -def get_type_template(self, context): - if not AuthoringData.is_loaded: - AuthoringData.load() - return AuthoringData.data["type_template"] - - def update_ifc_class(self, context): bpy.ops.bim.load_type_thumbnails(ifc_class=self.ifc_class) AuthoringData.data["relating_type_id"] = AuthoringData.relating_type_id() @@ -69,19 +51,11 @@ def update_ifc_class(self, context): if tool.Blender.get_enum_safe(self, "relating_type_id") is None: self["relating_type_id"] = 0 - -def update_type_class(self, context): AuthoringData.data["total_types"] = AuthoringData.total_types() AuthoringData.data["total_pages"] = AuthoringData.total_pages() AuthoringData.data["prev_page"] = AuthoringData.prev_page() AuthoringData.data["next_page"] = AuthoringData.next_page() AuthoringData.data["paginated_relating_types"] = AuthoringData.paginated_relating_types() - AuthoringData.data["type_predefined_type"] = AuthoringData.type_predefined_type() - AuthoringData.data["type_template"] = AuthoringData.type_template() - - type_class = self.type_class - if (type_class, type_class, "") in get_ifc_class(self, context): - self.ifc_class = type_class def update_relating_type_id(self, context): @@ -173,9 +147,6 @@ class BIMModelProperties(PropertyGroup): rl3: bpy.props.FloatProperty(name="RL", default=1, subtype="DISTANCE", description="Z offset for space calculation") x_angle: bpy.props.FloatProperty(name="X Angle", default=0, subtype="ANGLE", min=-pi / 180 * 89, max=pi / 180 * 89) type_page: bpy.props.IntProperty(name="Type Page", default=1, update=update_type_page) - type_template: bpy.props.EnumProperty(items=get_type_template, name="Type Template", default=0, options=set()) - type_class: bpy.props.EnumProperty(items=get_type_class, name="IFC Class", update=update_type_class) - type_predefined_type: bpy.props.EnumProperty(items=get_type_predefined_type, name="Predefined Type", default=None) type_name: bpy.props.StringProperty(name="Name", default="TYPEX") boundary_class: bpy.props.EnumProperty(items=get_boundary_class, name="Boundary Class") diff --git a/src/bonsai/bonsai/bim/module/model/ui.py b/src/bonsai/bonsai/bim/module/model/ui.py index a4acb4b571..096a59a321 100644 --- a/src/bonsai/bonsai/bim/module/model/ui.py +++ b/src/bonsai/bonsai/bim/module/model/ui.py @@ -100,7 +100,6 @@ class LaunchTypeManager(bpy.types.Operator): # will be None if project has no types if ifc_class is not None: - props.type_class = ifc_class bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class, offset=0, limit=9) return context.window_manager.invoke_props_dialog( self, width=550, title="Type Manager", confirm_text="Add Type" @@ -165,7 +164,7 @@ class LaunchTypeManager(bpy.types.Operator): else: row = box.row() op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH") - op.ifc_class = props.type_class + op.ifc_class = props.ifc_class class BIM_PT_authoring(Panel): diff --git a/src/bonsai/bonsai/bim/module/model/workspace.py b/src/bonsai/bonsai/bim/module/model/workspace.py index 7178585172..5f84015300 100644 --- a/src/bonsai/bonsai/bim/module/model/workspace.py +++ b/src/bonsai/bonsai/bim/module/model/workspace.py @@ -20,6 +20,7 @@ import os import sys import bpy import bpy.utils.previews +import bonsai.bim import bonsai.tool as tool import bonsai.core.model as core from bonsai.bim.module.model.wall import DumbWallJoiner @@ -339,10 +340,12 @@ class CreateObjectUI: ) cls.layout.label(text=tool_name, icon="TOOL_SETTINGS") - cls.draw_type_manager_launcher(context) - cls.draw_add_object(context) - cls.draw_thumbnail() if context.region.type != "TOOL_HEADER" else cls - cls.draw_add_object_parameters(context) + if AuthoringData.data["ifc_classes"] and AuthoringData.data["relating_type_id"]: + cls.draw_add_object(context) + cls.draw_thumbnail() if context.region.type != "TOOL_HEADER" else cls + cls.draw_add_object_parameters(context) + else: + cls.draw_type_manager_launcher(context) @classmethod def draw_container_info(cls, context): @@ -355,50 +358,46 @@ class CreateObjectUI: @classmethod def draw_type_manager_launcher(cls, context): row = cls.layout.row(align=True) - if not AuthoringData.data["ifc_classes"]: - if AuthoringData.data["ifc_element_type"]: - row.label(text=f"No {AuthoringData.data['ifc_element_type']} Found", icon="ERROR") - row = cls.layout.row(align=True) - op = row.operator( - "bim.add_default_type", - icon_value=custom_icon_previews["ADD_TYPE"].icon_id, - text=f"Create default {AuthoringData.data['ifc_element_type']}", - ) - op.ifc_element_type = AuthoringData.data["ifc_element_type"] - else: - row.label(text="No Element Types Found", icon="ERROR") + row.label(text="No Element Types Found", icon="ERROR") + row = cls.layout.row(align=True) + row.operator("bim.add_element", icon_value=bonsai.bim.icons["IFC"].icon_id, text="Add New Type") + if AuthoringData.data["ifc_element_type"]: row = cls.layout.row(align=True) - row.operator("bim.launch_type_manager", icon=tool.Blender.TYPE_MANAGER_ICON, text="Launch Type Manager") + op = row.operator( + "bim.add_default_type", + icon_value=custom_icon_previews["ADD_TYPE"].icon_id, + text=f"Quick Create Default {AuthoringData.data['ifc_element_type']}", + ) + op.ifc_element_type = AuthoringData.data["ifc_element_type"] @classmethod def draw_add_object(cls, context): ui_context = str(context.region.type) - if AuthoringData.data["ifc_classes"]: - if not AuthoringData.data["ifc_element_type"]: - row = cls.layout.row(align=True) - prop_with_search(row, cls.props, "ifc_class", text="Type Class" if ui_context != "TOOL_HEADER" else "") - if AuthoringData.data["relating_type_id"]: - row = cls.layout.row(align=True) - box = row.box() - # This trick creates a fake dropdown - row2 = box.row(align=True) - row2.operator( - "bim.launch_type_manager", - icon="FILE_3D", - text=AuthoringData.data["relating_type_name"], - emboss=False, - ) - row2.operator( - "bim.launch_type_manager", - icon="DOWNARROW_HLT", - text="", - emboss=False, - ) - row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row - op = row.operator("bim.hotkey", text="Add", icon_value=custom_icon_previews["ADD"].icon_id) - op.hotkey = "S_A" - else: - row.label(text="No Construction Type", icon="FILE_3D") + if not AuthoringData.data["ifc_element_type"]: + row = cls.layout.row(align=True) + prop_with_search(row, cls.props, "ifc_class", text="Type Class" if ui_context != "TOOL_HEADER" else "") + if AuthoringData.data["relating_type_id"]: + row = cls.layout.row(align=True) + box = row.box() + # This trick creates a fake dropdown + row2 = box.row(align=True) + row2.operator( + "bim.launch_type_manager", + icon="FILE_3D", + text=AuthoringData.data["relating_type_name"], + emboss=False, + ) + row2.operator( + "bim.launch_type_manager", + icon="DOWNARROW_HLT", + text="", + emboss=False, + ) + row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row + op = row.operator("bim.hotkey", text="Add", icon_value=custom_icon_previews["ADD"].icon_id) + op.hotkey = "S_A" + else: + row.label(text="No Construction Type", icon="FILE_3D") @classmethod def draw_add_object_parameters(cls, context): @@ -858,13 +857,15 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): props = bpy.context.scene.BIMModelProperties if bpy.context.scene.BIMGeometryProperties.mode == "ITEM": bpy.ops.wm.call_menu(name="BIM_MT_add_representation_item") - elif ( - props.relating_type_id - and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(props.relating_type_id))) == "LAYER2" - ): - bpy.ops.bim.draw_polyline_wall("INVOKE_DEFAULT") else: - bpy.ops.bim.add_occurrence("INVOKE_DEFAULT") + for obj in tool.Blender.get_selected_objects(): + obj.select_set(False) + if ( + relating_type_id := tool.Blender.get_enum_safe(props, "relating_type_id") + ) and tool.Model.get_usage_type(tool.Ifc.get().by_id(int(relating_type_id))) == "LAYER2": + bpy.ops.bim.draw_polyline_wall("INVOKE_DEFAULT") + else: + bpy.ops.bim.add_occurrence("INVOKE_DEFAULT") def hotkey_S_Q(self): if not bpy.context.selected_objects: diff --git a/src/bonsai/bonsai/bim/module/type/__init__.py b/src/bonsai/bonsai/bim/module/type/__init__.py index 6cdcb7c276..a083333456 100644 --- a/src/bonsai/bonsai/bim/module/type/__init__.py +++ b/src/bonsai/bonsai/bim/module/type/__init__.py @@ -20,7 +20,6 @@ import bpy from . import ui, prop, operator classes = ( - operator.AddType, operator.AssignType, operator.AutoRenameOccurrences, operator.DisableEditingType, diff --git a/src/bonsai/bonsai/bim/module/type/operator.py b/src/bonsai/bonsai/bim/module/type/operator.py index 071d3cd837..0a6742bdc3 100644 --- a/src/bonsai/bonsai/bim/module/type/operator.py +++ b/src/bonsai/bonsai/bim/module/type/operator.py @@ -229,257 +229,6 @@ class SelectTypeObjects(bpy.types.Operator): return {"FINISHED"} -class AddType(bpy.types.Operator, tool.Ifc.Operator): - bl_idname = "bim.add_type" - bl_label = "Add Type" - bl_options = {"REGISTER", "UNDO"} - - def _execute(self, context): - props = context.scene.BIMModelProperties - ifc_class = props.type_class - predefined_type = props.type_predefined_type - name = props.type_name - template = props.type_template - ifc_file = tool.Ifc.get() - body = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Body", "MODEL_VIEW") - if not body: - props.type_class = props.type_class - self.report({"ERROR"}, "No Model/Body/MODEL_VIEW context found.") - return {"FINISHED"} - - if template == "MESH": - location = context.scene.cursor.location - if context.active_object and context.selected_objects and context.active_object.data: - obj = context.active_object - element = tool.Ifc.get_entity(obj) - if element: - mesh = obj.data.copy() - mesh.BIMMeshProperties.ifc_definition_id = 0 - obj = bpy.data.objects.new(element.Name or name, mesh) - else: - mesh = bpy.data.meshes.new(name) - bm = bmesh.new() - bmesh.ops.create_cube(bm, size=1) - bm.to_mesh(mesh) - bm.free() - obj = bpy.data.objects.new(name, mesh) - obj.matrix_world.translation = location - bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - ifc_class=ifc_class, - predefined_type=predefined_type, - should_add_representation=True, - context=body, - ifc_representation_class=None, - ) - - elif template in ("LAYERSET_AXIS2", "LAYERSET_AXIS3"): - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - obj = bpy.data.objects.new(name, None) - element = bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - ifc_class=ifc_class, - predefined_type=predefined_type, - should_add_representation=True, - context=body, - ifc_representation_class=None, - ) - materials = ifc_file.by_type("IfcMaterial") - if materials: - material = materials[0] # Arbitrarily pick a material - else: - material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown") - rel = ifcopenshell.api.run( - "material.assign_material", ifc_file, products=[element], type="IfcMaterialLayerSet" - ) - layer_set = rel.RelatingMaterial - layer = ifcopenshell.api.run("material.add_layer", ifc_file, layer_set=layer_set, material=material) - thickness = 0.1 # Arbitrary metric thickness for now - layer.LayerThickness = thickness / unit_scale - - pset = ifcopenshell.api.run("pset.add_pset", ifc_file, product=element, name="EPset_Parametric") - if template == "LAYERSET_AXIS2": - axis = "AXIS2" - elif template == "LAYERSET_AXIS3": - axis = "AXIS3" - ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={"LayerSetDirection": axis}) - - elif template == "PROFILESET" or template.startswith("FLOW_SEGMENT_"): - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file) - obj = bpy.data.objects.new(name, None) - element = bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - ifc_class=ifc_class, - predefined_type=predefined_type, - should_add_representation=True, - context=body, - ifc_representation_class=None, - ) - materials = ifc_file.by_type("IfcMaterial") - if materials: - material = materials[0] # Arbitrarily pick a material - else: - material = ifcopenshell.api.run("material.add_material", tool.Ifc.get(), name="Unknown") - if template == "PROFILESET": - named_profiles = [p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName] - if named_profiles: - profile = named_profiles[0] - else: - size = 0.5 / unit_scale - profile = ifc_file.create_entity( - "IfcRectangleProfileDef", ProfileName="New Profile", ProfileType="AREA", XDim=size, YDim=size - ) - else: - # NOTE: defaults dims are in meters / mm - # for now default names are hardcoded to mm - if template == "FLOW_SEGMENT_RECTANGULAR": - default_x_dim = 0.4 - default_y_dim = 0.2 - profile_name = f"{ifc_class}-{default_x_dim*1000}x{default_y_dim*1000}" - profile = ifc_file.create_entity( - "IfcRectangleProfileDef", - ProfileName=profile_name, - ProfileType="AREA", - XDim=default_x_dim / unit_scale, - YDim=default_y_dim / unit_scale, - ) - elif template == "FLOW_SEGMENT_CIRCULAR": - default_diameter = 0.1 - profile_name = f"{ifc_class}-{default_diameter*1000}" - profile = ifc_file.create_entity( - "IfcCircleProfileDef", - ProfileName=profile_name, - ProfileType="AREA", - Radius=(default_diameter / 2) / unit_scale, - ) - elif template == "FLOW_SEGMENT_CIRCULAR_HOLLOW": - default_diameter = 0.15 - default_thickness = 0.005 - profile_name = f"{ifc_class}-{default_diameter*1000}x{default_thickness*1000}" - profile = ifc_file.create_entity( - "IfcCircleHollowProfileDef", - ProfileName=profile_name, - ProfileType="AREA", - Radius=(default_diameter / 2) / unit_scale, - WallThickness=default_thickness, - ) - - rel = ifcopenshell.api.run( - "material.assign_material", ifc_file, products=[element], type="IfcMaterialProfileSet" - ) - profile_set = rel.RelatingMaterial - material_profile = ifcopenshell.api.run( - "material.add_profile", ifc_file, profile_set=profile_set, material=material - ) - ifcopenshell.api.run( - "material.assign_profile", ifc_file, material_profile=material_profile, profile=profile - ) - - elif template == "EMPTY": - obj = bpy.data.objects.new(name, None) - bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - ifc_class=ifc_class, - predefined_type=predefined_type, - should_add_representation=True, - context=body, - ifc_representation_class=None, - ) - - elif template == "WINDOW": - mesh = bpy.data.meshes.new(name) - obj = bpy.data.objects.new(name, mesh) - element = bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None, - ifc_class="IfcWindowType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcWindowStyle", - should_add_representation=False, - ) - with context.temp_override(active_object=obj): - bpy.ops.bim.add_window() - - elif template == "DOOR": - mesh = bpy.data.meshes.new(name) - obj = bpy.data.objects.new(name, mesh) - element = bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - predefined_type=predefined_type if tool.Ifc.get_schema() != "IFC2X3" else None, - ifc_class="IfcDoorType" if tool.Ifc.get_schema() != "IFC2X3" else "IfcDoorStyle", - should_add_representation=False, - ) - with context.temp_override(active_object=obj): - bpy.ops.bim.add_door() - - elif template == "STAIR": - mesh = bpy.data.meshes.new(name) - obj = bpy.data.objects.new(name, mesh) - element = bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - predefined_type=predefined_type, - ifc_class=ifc_class, - should_add_representation=False, - ) - with context.temp_override(active_object=obj): - bpy.ops.bim.add_stair() - - elif template == "RAILING": - mesh = bpy.data.meshes.new(name) - obj = bpy.data.objects.new(name, mesh) - element = bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - predefined_type=predefined_type, - ifc_class="IfcRailingType", - should_add_representation=True, - context=body, - ) - with context.temp_override(active_object=obj): - bpy.ops.bim.add_railing() - - elif template == "ROOF": - mesh = bpy.data.meshes.new(name) - obj = bpy.data.objects.new(name, mesh) - element = bonsai.core.root.assign_class( - tool.Ifc, - tool.Collector, - tool.Root, - obj=obj, - predefined_type=predefined_type, - ifc_class="IfcRoofType", - should_add_representation=True, - context=body, - ) - with context.temp_override(active_object=obj): - bpy.ops.bim.add_roof() - - bpy.ops.bim.load_type_thumbnails(ifc_class=ifc_class) - props.type_class = props.type_class - return {"FINISHED"} - - class RemoveType(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.remove_type" bl_label = "Remove Type" diff --git a/src/bonsai/bonsai/tool/blender.py b/src/bonsai/bonsai/tool/blender.py index 231df2cbfd..6ae5fabdfa 100644 --- a/src/bonsai/bonsai/tool/blender.py +++ b/src/bonsai/bonsai/tool/blender.py @@ -144,11 +144,11 @@ class Blender(bonsai.core.tool.Blender): @classmethod def get_selected_objects(cls) -> set[bpy.types.Object]: - if bpy.context.selected_objects: - if active_obj := bpy.context.active_object: - return set(bpy.context.selected_objects + [active_obj]) - return set(bpy.context.selected_objects) - if active_obj := bpy.context.active_object: + if selected_objects := getattr(bpy.context, "selected_objects", None): + if active_obj := cls.get_active_object(): + return set(selected_objects + [active_obj]) + return set(selected_objects) + if active_obj := cls.get_active_object(): return {active_obj} return set()