Fix IfcFooting Qto_FootingBaseQuantities axis mapping per predefined type #4783

Footings are authored two ways with different local axis conventions. Beam-like
footings (STRIP_FOOTING, FOOTING_BEAM) are a profile extruded along local Z, so
Length is local Z and the cross section sits on local X (Width, horizontal) and
local Y (Height, vertical). Slab-like footings (PAD_FOOTING, PILE_CAP) have their
footprint on local X/Y and their thickness (Height) on local Z.

The engine rule set is keyed per IfcFooting and cannot branch on predefined type,
so the previous static rule (Height=net_get_z, Length=net_get_max_xy, Width=null)
swapped Length and Height for beam-like footings and never emitted Width.

Add predefined-type-aware get_footing_length/width/height to the IfcOpenShell and
Blender calculators, and point the IfcFooting rule at them in all four IFC4/IFC4X3
ios/Blender rule files.

Confirmed by authoring footings through the real Bonsai generators and measuring
world-axis orientation: a beam-like footing with a 0.3 wide by 0.6 tall cross
section and 6.0 run reports Length 6.0, Width 0.3, Height 0.6, with the 0.3
physically horizontal and 0.6 physically vertical; a 2.0x1.5x0.3 pad reports
Length 2.0, Width 1.5, Height 0.3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 4ceadd8f10)
This commit is contained in:
Petru Conduraru
2026-07-08 13:34:03 +03:00
committed by Dion Moult
parent be56984e12
commit 0d8813348c
6 changed files with 93 additions and 13 deletions
+16 -2
View File
@@ -104,7 +104,7 @@ def get_footing_length(o: bpy.types.Object) -> float:
return get_length(o)
if predefined_type == "FOOTING_BEAM" or predefined_type == "STRIP_FOOTING":
return get_z(o)
elif predefined_type == "PAD_FOOTING":
elif predefined_type == "PAD_FOOTING" or predefined_type == "PILE_CAP":
return max(get_x(o), get_y(o))
else:
return get_length(o)
@@ -197,12 +197,26 @@ def get_footing_height(o: bpy.types.Object) -> float:
return get_height(o)
if predefined_type == "FOOTING_BEAM" or predefined_type == "STRIP_FOOTING":
return get_y(o)
elif predefined_type == "PAD_FOOTING":
elif predefined_type == "PAD_FOOTING" or predefined_type == "PILE_CAP":
return get_z(o)
else:
return get_height(o)
def get_footing_width(o: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(o)
assert element
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
if not predefined_type:
return get_width(o)
if predefined_type == "FOOTING_BEAM" or predefined_type == "STRIP_FOOTING":
return get_x(o)
elif predefined_type == "PAD_FOOTING" or predefined_type == "PILE_CAP":
return get_width(o)
else:
return get_width(o)
def get_height(o: bpy.types.Object) -> float:
"""_summary_: Returns the height of the object bounding box