From 75c650512175b8e800b28910af115223e10e747c Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 25 Jan 2025 20:43:08 +1100 Subject: [PATCH] See #5919. You can now add openings through "Add Element" just like everything else. This means that openings can (and default to) start with extrusions. It also starts to open up the possibility to add other types of feature elements. Still really incomplete. --- .../bonsai/bim/module/geometry/operator.py | 14 +++------- src/bonsai/bonsai/bim/module/root/data.py | 12 +++++---- src/bonsai/bonsai/bim/module/root/operator.py | 25 +++++++++++++++-- src/bonsai/bonsai/bim/module/root/prop.py | 27 ++++++++++++++++--- src/bonsai/bonsai/core/tool.py | 4 +++ src/bonsai/bonsai/tool/__init__.py | 9 ++++--- 6 files changed, 66 insertions(+), 25 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/geometry/operator.py b/src/bonsai/bonsai/bim/module/geometry/operator.py index e56a9d18b5..824711d780 100644 --- a/src/bonsai/bonsai/bim/module/geometry/operator.py +++ b/src/bonsai/bonsai/bim/module/geometry/operator.py @@ -735,16 +735,10 @@ class OverrideDelete(bpy.types.Operator): else: bpy.data.objects.remove(obj) - while True: - has_removed_opening = False - openings = context.scene.BIMModelProperties.openings - for i, opening in enumerate(openings): - if not (element := tool.Ifc.get_entity(opening.obj)): - has_removed_opening = True - bpy.data.objects.remove(opening.obj) - context.scene.BIMModelProperties.openings.remove(i) - if not has_removed_opening: - break + for opening in context.scene.BIMModelProperties.openings: + if not tool.Ifc.get_entity(opening.obj): + bpy.data.objects.remove(opening.obj) + tool.Model.purge_scene_openings() if self.is_batch: old_file = tool.Ifc.get() diff --git a/src/bonsai/bonsai/bim/module/root/data.py b/src/bonsai/bonsai/bim/module/root/data.py index 12f4795fde..10c50839ec 100644 --- a/src/bonsai/bonsai/bim/module/root/data.py +++ b/src/bonsai/bonsai/bim/module/root/data.py @@ -22,7 +22,6 @@ import ifcopenshell.util.element import ifcopenshell.util.schema from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc, get_class_suggestions import bonsai.tool as tool -from bonsai.bim.ifc import IfcStore from typing import Union @@ -58,6 +57,7 @@ class IfcClassData: products = [ "IfcElementType", "IfcElement", + "IfcFeatureElement", "IfcSpatialElement", "IfcSpatialElementType", "IfcStructuralItem", @@ -69,6 +69,7 @@ class IfcClassData: products = [ "IfcElementType", "IfcElement", + "IfcFeatureElement", "IfcSpatialStructureElement", "IfcStructuralItem", "IfcAnnotation", @@ -87,10 +88,11 @@ class IfcClassData: if tool.Ifc.get_schema() in ("IFC2X3", "IFC4"): names.extend(("IfcDoorStyle", "IfcWindowStyle")) if ifc_product == "IfcElement": - names.remove("IfcOpeningElement") - if tool.Ifc.get_schema() == "IFC4": - # Yeah, weird isn't it. - names.remove("IfcOpeningStandardCase") + feature_elements = ifcopenshell.util.schema.get_subtypes( + tool.Ifc.schema().declaration_by_name("IfcFeatureElement") + ) + for feature_element in feature_elements: + names.remove(feature_element.name()) version = tool.Ifc.get_schema() return [(c, c, (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)] diff --git a/src/bonsai/bonsai/bim/module/root/operator.py b/src/bonsai/bonsai/bim/module/root/operator.py index c8178f4d27..cdeeaf204d 100644 --- a/src/bonsai/bonsai/bim/module/root/operator.py +++ b/src/bonsai/bonsai/bim/module/root/operator.py @@ -350,8 +350,13 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator): def _invoke(self, context, event): props = context.scene.BIMRootProperties - # For convenience, preselect OBJ representation template if applicable - if (obj := context.active_object) and obj.type == "MESH": + # For convenience, preselect OBJs if applicable + if props.ifc_product == "IfcFeatureElement": + if (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH": + props.featured_obj = obj + props.representation_template = "EXTRUSION" + props.representation_obj = None + elif (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH": props.representation_template = "OBJ" props.representation_obj = obj # For convenience, preselect IFC class @@ -369,6 +374,9 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator): representation_template = props.representation_template ifc_file = tool.Ifc.get() + if props.ifc_product == "IfcFeatureElement" and not props.featured_obj: + return self.report({"WARNING"}, "A featured element must be nominated.") + ifc_context = None if get_enum_items(props, "contexts", context): ifc_context = int(props.contexts or "0") or None @@ -556,6 +564,16 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator): elif representation_template == "ROOF": with context.temp_override(active_object=obj, selected_objects=[]): bpy.ops.bim.add_roof() + + bpy.context.view_layer.update() # Ensures obj.matrix_world is correct + + if props.ifc_product == "IfcFeatureElement": + tool.Feature.add_feature(props.featured_obj, [obj]) + new = context.scene.BIMModelProperties.openings.add() + new.obj = obj + bpy.ops.bim.show_openings() + tool.Model.purge_scene_openings() + bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) tool.Blender.set_active_object(obj) @@ -577,6 +595,9 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator): if props.ifc_predefined_type == "USERDEFINED": row = self.layout.row() row.prop(props, "ifc_userdefined_type") + if props.ifc_product == "IfcFeatureElement": + row = self.layout.row() + row.prop(props, "featured_obj", text="Featured Object") prop_with_search(self.layout, props, "representation_template", text="Representation", should_click_ok=True) if props.representation_template == "OBJ": row = self.layout.row() diff --git a/src/bonsai/bonsai/bim/module/root/prop.py b/src/bonsai/bonsai/bim/module/root/prop.py index ec9d3e8ee4..bcc0b69456 100644 --- a/src/bonsai/bonsai/bim/module/root/prop.py +++ b/src/bonsai/bonsai/bim/module/root/prop.py @@ -57,17 +57,26 @@ def refresh_classes(self, context): context.scene.BIMRootProperties.ifc_class = enum[0][0] IfcClassData.load() + if self.ifc_product == "IfcFeatureElement": + if (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH": + self.featured_obj = obj + self.representation_template = "EXTRUSION" + self.representation_obj = None + elif (obj := tool.Blender.get_active_object(is_selected=True)) and obj.type == "MESH": + self.representation_template = "OBJ" + self.representation_obj = obj + # When switching between ElementType and Element, keep the same class and predefined type if possible - if context.scene.BIMRootProperties.ifc_product == "IfcElement": + if self.ifc_product == "IfcElement": ifc_class = next(iter(ifcopenshell.util.type.get_applicable_entities(old_class)), None) - elif context.scene.BIMRootProperties.ifc_product == "IfcElementType": + elif self.ifc_product == "IfcElementType": ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(old_class)), None) else: return if ifc_class: - context.scene.BIMRootProperties.ifc_class = ifc_class + self.ifc_class = ifc_class if old_predefined_type: - context.scene.BIMRootProperties.ifc_predefined_type = old_predefined_type + self.ifc_predefined_type = old_predefined_type def refresh_predefined_types(self, context): @@ -142,6 +151,10 @@ def poll_representation_obj(self, obj): return obj.type == "MESH" and obj.data.polygons +def poll_featured_obj(self, obj): + return tool.Ifc.get_entity(obj) + + class BIMRootProperties(PropertyGroup): contexts: EnumProperty(items=get_contexts, name="Contexts", options=set()) name: StringProperty(name="Name") @@ -150,6 +163,12 @@ class BIMRootProperties(PropertyGroup): ifc_class: EnumProperty(items=get_ifc_classes, name="Class", update=refresh_predefined_types) ifc_predefined_type: EnumProperty(items=get_ifc_predefined_types, name="Predefined Type", default=None) ifc_userdefined_type: StringProperty(name="Userdefined Type") + featured_obj: bpy.props.PointerProperty( + type=bpy.types.Object, + name="Featured Object", + poll=poll_featured_obj, + description="The feature will be applied to this object", + ) representation_template: bpy.props.EnumProperty( items=get_representation_template, name="Representation Template", default=0 ) diff --git a/src/bonsai/bonsai/core/tool.py b/src/bonsai/bonsai/core/tool.py index 4e10708786..cbb867eebc 100644 --- a/src/bonsai/bonsai/core/tool.py +++ b/src/bonsai/bonsai/core/tool.py @@ -381,6 +381,10 @@ class Drawing: def update_text_value(cls, obj): pass +@interface +class Feature: + def add_feature(cls, featured_obj, featured_objs): pass + @interface class Geometry: def change_object_data(cls, obj, data, is_global=False): pass diff --git a/src/bonsai/bonsai/tool/__init__.py b/src/bonsai/bonsai/tool/__init__.py index 1503ddc741..2b52464841 100644 --- a/src/bonsai/bonsai/tool/__init__.py +++ b/src/bonsai/bonsai/tool/__init__.py @@ -27,10 +27,13 @@ from bonsai.tool.clash import Clash from bonsai.tool.classification import Classification from bonsai.tool.collector import Collector from bonsai.tool.context import Context +from bonsai.tool.cost import Cost +from bonsai.tool.covering import Covering from bonsai.tool.debug import Debug from bonsai.tool.demo import Demo from bonsai.tool.document import Document from bonsai.tool.drawing import Drawing +from bonsai.tool.feature import Feature from bonsai.tool.geometry import Geometry from bonsai.tool.georeference import Georeference from bonsai.tool.ifc import Ifc @@ -45,18 +48,18 @@ from bonsai.tool.nest import Nest from bonsai.tool.owner import Owner from bonsai.tool.patch import Patch from bonsai.tool.polyline import Polyline -from bonsai.tool.project import Project from bonsai.tool.profile import Profile +from bonsai.tool.project import Project from bonsai.tool.pset import Pset from bonsai.tool.pset_template import PsetTemplate from bonsai.tool.qto import Qto from bonsai.tool.raycast import Raycast from bonsai.tool.resource import Resource from bonsai.tool.root import Root +from bonsai.tool.search import Search from bonsai.tool.sequence import Sequence from bonsai.tool.snap import Snap from bonsai.tool.spatial import Spatial -from bonsai.tool.covering import Covering from bonsai.tool.structural import Structural from bonsai.tool.style import Style from bonsai.tool.surveyor import Surveyor @@ -64,6 +67,4 @@ from bonsai.tool.system import System from bonsai.tool.tester import Tester from bonsai.tool.type import Type from bonsai.tool.unit import Unit -from bonsai.tool.search import Search -from bonsai.tool.cost import Cost from bonsai.tool.web import Web