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