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 <noreply@anthropic.com>
(cherry picked from commit b5a0f1fc74)
This commit is contained in:
Ryan Schultz
2026-07-16 18:57:11 -05:00
committed by Dion Moult
parent dbbbc54f19
commit 43fc405b58
+17 -4
View File
@@ -27,6 +27,7 @@ import ifcopenshell.api.material
import ifcopenshell.api.pset import ifcopenshell.api.pset
import ifcopenshell.api.root import ifcopenshell.api.root
import ifcopenshell.util.element import ifcopenshell.util.element
import ifcopenshell.util.representation
import ifcopenshell.util.schema import ifcopenshell.util.schema
import ifcopenshell.util.shape_builder import ifcopenshell.util.shape_builder
import ifcopenshell.util.type import ifcopenshell.util.type
@@ -129,11 +130,23 @@ class ReassignClass(bpy.types.Operator, tool.Ifc.Operator):
same_ifc_product = element.is_a(ifc_product) same_ifc_product = element.is_a(ifc_product)
if not same_ifc_product: if not same_ifc_product:
if not (element.is_a("IfcElement") and ifc_product == "IfcElementType") and not ( # A spatial element (e.g. IfcSite) anchors the containment
element.is_a("IfcElementType") and ifc_product == "IfcElement" # 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
)
if not has_geometry:
self.report( self.report(
{"ERROR"}, f"Not supported class reassignment for object '{obj.name}' -> {ifc_product}." {"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"} return {"CANCELLED"}