From 43fc405b5885eab8f10a652825c1bdf2e43cffb3 Mon Sep 17 00:00:00 2001 From: Ryan Schultz Date: Thu, 16 Jul 2026 18:57:11 -0500 Subject: [PATCH] Bonsai: allow cross-family class reassignment for spatial elements with geometry (#8665) The Reassign Class operator refused to reassign an element to a different IFC product family unless it was an IfcElement <-> IfcElementType swap, so a piece of geometry mistakenly hosted on IfcSite could not be turned into IfcFurniture even though root.reassign_class handles it fine. Loosen the guard: only block the case that actually matters - a spatial element (IfcSpatialElement / IfcSpatialStructureElement for IFC2X3) with no geometry, which would be a real containment-hierarchy container rather than a stray modelled object. Everything else reassigns freely. Closes #8664 Co-authored-by: Claude Opus 4.8 (cherry picked from commit b5a0f1fc74d6315b6985aed16f1ff8c24e90b197) --- src/bonsai/bonsai/bim/module/root/operator.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/bonsai/bonsai/bim/module/root/operator.py b/src/bonsai/bonsai/bim/module/root/operator.py index 159f157d44..993d3bfb4b 100644 --- a/src/bonsai/bonsai/bim/module/root/operator.py +++ b/src/bonsai/bonsai/bim/module/root/operator.py @@ -27,6 +27,7 @@ import ifcopenshell.api.material import ifcopenshell.api.pset import ifcopenshell.api.root import ifcopenshell.util.element +import ifcopenshell.util.representation import ifcopenshell.util.schema import ifcopenshell.util.shape_builder import ifcopenshell.util.type @@ -129,13 +130,25 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator): same_ifc_product = element.is_a(ifc_product) if not same_ifc_product: - if not (element.is_a("IfcElement") and ifc_product == "IfcElementType") and not ( - element.is_a("IfcElementType") and ifc_product == "IfcElement" - ): - self.report( - {"ERROR"}, f"Not supported class reassignment for object '{obj.name}' -> {ifc_product}." + # A spatial element (e.g. IfcSite) anchors the containment + # hierarchy, so only allow reassigning it to another family when + # it actually carries geometry - i.e. it's a real modelled thing + # (a bench dropped onto IfcSite -> IfcFurniture) rather than an + # empty spatial container we'd be turning into a loose element. + # IfcSpatialStructureElement covers IFC2X3, which has no + # IfcSpatialElement supertype. + is_spatial = element.is_a("IfcSpatialElement") or element.is_a("IfcSpatialStructureElement") + if is_spatial: + has_geometry = ( + next(ifcopenshell.util.representation.get_representations_iter(element), None) is not None ) - return {"CANCELLED"} + if not has_geometry: + self.report( + {"ERROR"}, + f"Cannot reassign '{obj.name}' ({element.is_a()}) to {ifc_product}: " + "a spatial element can only be reassigned to another class when it has geometry.", + ) + return {"CANCELLED"} props = tool.Blender.get_object_bim_props(obj) props.is_reassigning_class = False