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.
This commit is contained in:
Dion Moult
2025-01-25 20:43:08 +11:00
parent 2c531501c3
commit 75c6505121
6 changed files with 66 additions and 25 deletions
@@ -735,16 +735,10 @@ class OverrideDelete(bpy.types.Operator):
else: else:
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
while True: for opening in context.scene.BIMModelProperties.openings:
has_removed_opening = False if not tool.Ifc.get_entity(opening.obj):
openings = context.scene.BIMModelProperties.openings bpy.data.objects.remove(opening.obj)
for i, opening in enumerate(openings): tool.Model.purge_scene_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
if self.is_batch: if self.is_batch:
old_file = tool.Ifc.get() old_file = tool.Ifc.get()
+7 -5
View File
@@ -22,7 +22,6 @@ import ifcopenshell.util.element
import ifcopenshell.util.schema import ifcopenshell.util.schema
from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc, get_class_suggestions from ifcopenshell.util.doc import get_entity_doc, get_predefined_type_doc, get_class_suggestions
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.ifc import IfcStore
from typing import Union from typing import Union
@@ -58,6 +57,7 @@ class IfcClassData:
products = [ products = [
"IfcElementType", "IfcElementType",
"IfcElement", "IfcElement",
"IfcFeatureElement",
"IfcSpatialElement", "IfcSpatialElement",
"IfcSpatialElementType", "IfcSpatialElementType",
"IfcStructuralItem", "IfcStructuralItem",
@@ -69,6 +69,7 @@ class IfcClassData:
products = [ products = [
"IfcElementType", "IfcElementType",
"IfcElement", "IfcElement",
"IfcFeatureElement",
"IfcSpatialStructureElement", "IfcSpatialStructureElement",
"IfcStructuralItem", "IfcStructuralItem",
"IfcAnnotation", "IfcAnnotation",
@@ -87,10 +88,11 @@ class IfcClassData:
if tool.Ifc.get_schema() in ("IFC2X3", "IFC4"): if tool.Ifc.get_schema() in ("IFC2X3", "IFC4"):
names.extend(("IfcDoorStyle", "IfcWindowStyle")) names.extend(("IfcDoorStyle", "IfcWindowStyle"))
if ifc_product == "IfcElement": if ifc_product == "IfcElement":
names.remove("IfcOpeningElement") feature_elements = ifcopenshell.util.schema.get_subtypes(
if tool.Ifc.get_schema() == "IFC4": tool.Ifc.schema().declaration_by_name("IfcFeatureElement")
# Yeah, weird isn't it. )
names.remove("IfcOpeningStandardCase") for feature_element in feature_elements:
names.remove(feature_element.name())
version = tool.Ifc.get_schema() version = tool.Ifc.get_schema()
return [(c, c, (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)] return [(c, c, (get_entity_doc(version, c) or {}).get("description", "")) for c in sorted(names)]
+23 -2
View File
@@ -350,8 +350,13 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
def _invoke(self, context, event): def _invoke(self, context, event):
props = context.scene.BIMRootProperties props = context.scene.BIMRootProperties
# For convenience, preselect OBJ representation template if applicable # For convenience, preselect OBJs if applicable
if (obj := context.active_object) and obj.type == "MESH": 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_template = "OBJ"
props.representation_obj = obj props.representation_obj = obj
# For convenience, preselect IFC class # For convenience, preselect IFC class
@@ -369,6 +374,9 @@ class AddElement(bpy.types.Operator, tool.Ifc.Operator):
representation_template = props.representation_template representation_template = props.representation_template
ifc_file = tool.Ifc.get() 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 ifc_context = None
if get_enum_items(props, "contexts", context): if get_enum_items(props, "contexts", context):
ifc_context = int(props.contexts or "0") or None 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": elif representation_template == "ROOF":
with context.temp_override(active_object=obj, selected_objects=[]): with context.temp_override(active_object=obj, selected_objects=[]):
bpy.ops.bim.add_roof() 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) bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj)
tool.Blender.set_active_object(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": if props.ifc_predefined_type == "USERDEFINED":
row = self.layout.row() row = self.layout.row()
row.prop(props, "ifc_userdefined_type") 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) prop_with_search(self.layout, props, "representation_template", text="Representation", should_click_ok=True)
if props.representation_template == "OBJ": if props.representation_template == "OBJ":
row = self.layout.row() row = self.layout.row()
+23 -4
View File
@@ -57,17 +57,26 @@ def refresh_classes(self, context):
context.scene.BIMRootProperties.ifc_class = enum[0][0] context.scene.BIMRootProperties.ifc_class = enum[0][0]
IfcClassData.load() 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 # 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) 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) ifc_class = next(iter(ifcopenshell.util.type.get_applicable_types(old_class)), None)
else: else:
return return
if ifc_class: if ifc_class:
context.scene.BIMRootProperties.ifc_class = ifc_class self.ifc_class = ifc_class
if old_predefined_type: 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): def refresh_predefined_types(self, context):
@@ -142,6 +151,10 @@ def poll_representation_obj(self, obj):
return obj.type == "MESH" and obj.data.polygons return obj.type == "MESH" and obj.data.polygons
def poll_featured_obj(self, obj):
return tool.Ifc.get_entity(obj)
class BIMRootProperties(PropertyGroup): class BIMRootProperties(PropertyGroup):
contexts: EnumProperty(items=get_contexts, name="Contexts", options=set()) contexts: EnumProperty(items=get_contexts, name="Contexts", options=set())
name: StringProperty(name="Name") 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_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_predefined_type: EnumProperty(items=get_ifc_predefined_types, name="Predefined Type", default=None)
ifc_userdefined_type: StringProperty(name="Userdefined Type") 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( representation_template: bpy.props.EnumProperty(
items=get_representation_template, name="Representation Template", default=0 items=get_representation_template, name="Representation Template", default=0
) )
+4
View File
@@ -381,6 +381,10 @@ class Drawing:
def update_text_value(cls, obj): pass def update_text_value(cls, obj): pass
@interface
class Feature:
def add_feature(cls, featured_obj, featured_objs): pass
@interface @interface
class Geometry: class Geometry:
def change_object_data(cls, obj, data, is_global=False): pass def change_object_data(cls, obj, data, is_global=False): pass
+5 -4
View File
@@ -27,10 +27,13 @@ from bonsai.tool.clash import Clash
from bonsai.tool.classification import Classification from bonsai.tool.classification import Classification
from bonsai.tool.collector import Collector from bonsai.tool.collector import Collector
from bonsai.tool.context import Context 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.debug import Debug
from bonsai.tool.demo import Demo from bonsai.tool.demo import Demo
from bonsai.tool.document import Document from bonsai.tool.document import Document
from bonsai.tool.drawing import Drawing from bonsai.tool.drawing import Drawing
from bonsai.tool.feature import Feature
from bonsai.tool.geometry import Geometry from bonsai.tool.geometry import Geometry
from bonsai.tool.georeference import Georeference from bonsai.tool.georeference import Georeference
from bonsai.tool.ifc import Ifc 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.owner import Owner
from bonsai.tool.patch import Patch from bonsai.tool.patch import Patch
from bonsai.tool.polyline import Polyline from bonsai.tool.polyline import Polyline
from bonsai.tool.project import Project
from bonsai.tool.profile import Profile from bonsai.tool.profile import Profile
from bonsai.tool.project import Project
from bonsai.tool.pset import Pset from bonsai.tool.pset import Pset
from bonsai.tool.pset_template import PsetTemplate from bonsai.tool.pset_template import PsetTemplate
from bonsai.tool.qto import Qto from bonsai.tool.qto import Qto
from bonsai.tool.raycast import Raycast from bonsai.tool.raycast import Raycast
from bonsai.tool.resource import Resource from bonsai.tool.resource import Resource
from bonsai.tool.root import Root from bonsai.tool.root import Root
from bonsai.tool.search import Search
from bonsai.tool.sequence import Sequence from bonsai.tool.sequence import Sequence
from bonsai.tool.snap import Snap from bonsai.tool.snap import Snap
from bonsai.tool.spatial import Spatial from bonsai.tool.spatial import Spatial
from bonsai.tool.covering import Covering
from bonsai.tool.structural import Structural from bonsai.tool.structural import Structural
from bonsai.tool.style import Style from bonsai.tool.style import Style
from bonsai.tool.surveyor import Surveyor 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.tester import Tester
from bonsai.tool.type import Type from bonsai.tool.type import Type
from bonsai.tool.unit import Unit from bonsai.tool.unit import Unit
from bonsai.tool.search import Search
from bonsai.tool.cost import Cost
from bonsai.tool.web import Web from bonsai.tool.web import Web