mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-05 23:41:44 +00:00
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:
committed by
Dion Moult
parent
be56984e12
commit
0d8813348c
@@ -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
|
||||
|
||||
|
||||
@@ -318,12 +318,12 @@
|
||||
"GrossSurfaceArea": null,
|
||||
"GrossVolume": null,
|
||||
"GrossWeight": "gross_get_weight",
|
||||
"Height": "net_get_z",
|
||||
"Length": "net_get_max_xy",
|
||||
"Height": "net_get_footing_height",
|
||||
"Length": "net_get_footing_length",
|
||||
"NetVolume": "net_get_volume",
|
||||
"NetWeight": "net_get_weight",
|
||||
"OuterSurfaceArea": null,
|
||||
"Width": null
|
||||
"Width": "net_get_footing_width"
|
||||
}
|
||||
},
|
||||
"IfcHeatExchanger": {
|
||||
|
||||
@@ -323,7 +323,7 @@
|
||||
"NetVolume": "get_net_volume",
|
||||
"NetWeight": "get_net_weight",
|
||||
"OuterSurfaceArea": "get_outer_surface_area",
|
||||
"Width": "get_width"
|
||||
"Width": "get_footing_width"
|
||||
}
|
||||
},
|
||||
"IfcHeatExchanger": {
|
||||
|
||||
@@ -363,12 +363,12 @@
|
||||
"GrossSurfaceArea": null,
|
||||
"GrossVolume": null,
|
||||
"GrossWeight": "gross_get_weight",
|
||||
"Height": "net_get_z",
|
||||
"Length": "net_get_max_xy",
|
||||
"Height": "net_get_footing_height",
|
||||
"Length": "net_get_footing_length",
|
||||
"NetVolume": "net_get_volume",
|
||||
"NetWeight": "net_get_weight",
|
||||
"OuterSurfaceArea": null,
|
||||
"Width": null
|
||||
"Width": "net_get_footing_width"
|
||||
}
|
||||
},
|
||||
"IfcGeotechnicalStratum": {
|
||||
|
||||
@@ -363,12 +363,12 @@
|
||||
"GrossSurfaceArea": "get_gross_surface_area",
|
||||
"GrossVolume": "get_gross_volume",
|
||||
"GrossWeight": "get_gross_weight",
|
||||
"Height": "get_height",
|
||||
"Length": "get_length",
|
||||
"Height": "get_footing_height",
|
||||
"Length": "get_footing_length",
|
||||
"NetVolume": "get_net_volume",
|
||||
"NetWeight": "get_net_weight",
|
||||
"OuterSurfaceArea": "get_outer_surface_area",
|
||||
"Width": "get_width"
|
||||
"Width": "get_footing_width"
|
||||
}
|
||||
},
|
||||
"IfcGeotechnicalStratum": {
|
||||
|
||||
+67
-1
@@ -265,6 +265,27 @@ class IfcOpenShell(QtoCalculator):
|
||||
"Opening Depth",
|
||||
"The depth of an opening (through the voided element), guessing the opening orientation",
|
||||
),
|
||||
"get_footing_length": Function(
|
||||
"IfcLengthMeasure",
|
||||
"Footing Length",
|
||||
"The footing length. For beam-like footings (STRIP_FOOTING, FOOTING_BEAM) this is the "
|
||||
"extruded run along the local Z axis. For slab-like footings (PAD_FOOTING, PILE_CAP) and "
|
||||
"other predefined types it is the longer footprint side (the larger of local X or Y).",
|
||||
),
|
||||
"get_footing_width": Function(
|
||||
"IfcLengthMeasure",
|
||||
"Footing Width",
|
||||
"The footing width. For beam-like footings (STRIP_FOOTING, FOOTING_BEAM) this is the "
|
||||
"cross section width along the local X axis. For slab-like footings (PAD_FOOTING, PILE_CAP) "
|
||||
"and other predefined types it is the shorter footprint side (the smaller of local X or Y).",
|
||||
),
|
||||
"get_footing_height": Function(
|
||||
"IfcLengthMeasure",
|
||||
"Footing Height",
|
||||
"The footing height. For beam-like footings (STRIP_FOOTING, FOOTING_BEAM) this is the "
|
||||
"cross section height along the local Y axis. For slab-like footings (PAD_FOOTING, PILE_CAP) "
|
||||
"and other predefined types it is the thickness along the local Z axis.",
|
||||
),
|
||||
# IfcAreaMeasure
|
||||
"get_area": Function("IfcAreaMeasure", "Area", "The total surface area of the element"),
|
||||
"get_footprint_area": Function(
|
||||
@@ -312,6 +333,15 @@ class IfcOpenShell(QtoCalculator):
|
||||
functions[f"gross_{k}"] = Function(v.measure, f"Gross {v.name}", v.description)
|
||||
functions[f"net_{k}"] = Function(v.measure, f"Net {v.name}", v.description)
|
||||
|
||||
# Predefined-type-aware footing functions. They read the element's predefined type to
|
||||
# pick the correct local axis, so they receive the element (not just the geometry) and
|
||||
# cannot live in ifcopenshell.util.shape.
|
||||
footing_functions = (
|
||||
"get_footing_length",
|
||||
"get_footing_width",
|
||||
"get_footing_height",
|
||||
)
|
||||
|
||||
internal_functions = (
|
||||
"get_segment_length",
|
||||
"get_weight",
|
||||
@@ -319,7 +349,7 @@ class IfcOpenShell(QtoCalculator):
|
||||
"get_opening_height",
|
||||
"get_opening_depth",
|
||||
"get_opening_area",
|
||||
)
|
||||
) + footing_functions
|
||||
|
||||
@classmethod
|
||||
def calculate(cls, ifc_file, elements, qtos, results):
|
||||
@@ -386,6 +416,11 @@ class IfcOpenShell(QtoCalculator):
|
||||
elif formula.startswith("get_opening_"):
|
||||
value = cls.get_opening_quantity(geometry, formula)
|
||||
value = cls.unit_converter.convert(value, IfcOpenShell.raw_functions[formula].measure)
|
||||
elif formula in cls.footing_functions:
|
||||
value = getattr(cls, formula)(element, geometry)
|
||||
if value is None:
|
||||
continue
|
||||
value = cls.unit_converter.convert(value, cls.raw_functions[formula].measure)
|
||||
else:
|
||||
value = formula_functions[formula](geometry)
|
||||
assert isinstance(value, (float, int))
|
||||
@@ -469,6 +504,36 @@ class IfcOpenShell(QtoCalculator):
|
||||
z = item.Depth
|
||||
return max([x, y, z])
|
||||
|
||||
# Footings are authored two ways, so a single static axis rule cannot be correct for both.
|
||||
# Beam-like footings (STRIP_FOOTING, FOOTING_BEAM) are a profile extruded along the local Z
|
||||
# axis, so the run (Length) is local Z and the cross section sits on local X (Width) and
|
||||
# local Y (Height). Slab-like footings (PAD_FOOTING, PILE_CAP) have their footprint on the
|
||||
# local X/Y plane and their thickness (Height) on local Z. These functions branch on the
|
||||
# predefined type to pick the right axis. This mirrors the Blender calculator's
|
||||
# get_footing_length / get_footing_width / get_footing_height.
|
||||
_beam_like_footings = ("STRIP_FOOTING", "FOOTING_BEAM")
|
||||
|
||||
@classmethod
|
||||
def get_footing_length(cls, element: ifcopenshell.entity_instance, geometry: ifcopenshell.geom.ShapeType) -> float:
|
||||
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
if predefined_type in cls._beam_like_footings:
|
||||
return ifcopenshell.util.shape.get_z(geometry)
|
||||
return max(ifcopenshell.util.shape.get_x(geometry), ifcopenshell.util.shape.get_y(geometry))
|
||||
|
||||
@classmethod
|
||||
def get_footing_width(cls, element: ifcopenshell.entity_instance, geometry: ifcopenshell.geom.ShapeType) -> float:
|
||||
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
if predefined_type in cls._beam_like_footings:
|
||||
return ifcopenshell.util.shape.get_x(geometry)
|
||||
return min(ifcopenshell.util.shape.get_x(geometry), ifcopenshell.util.shape.get_y(geometry))
|
||||
|
||||
@classmethod
|
||||
def get_footing_height(cls, element: ifcopenshell.entity_instance, geometry: ifcopenshell.geom.ShapeType) -> float:
|
||||
predefined_type = ifcopenshell.util.element.get_predefined_type(element)
|
||||
if predefined_type in cls._beam_like_footings:
|
||||
return ifcopenshell.util.shape.get_y(geometry)
|
||||
return ifcopenshell.util.shape.get_z(geometry)
|
||||
|
||||
@classmethod
|
||||
def get_weight(
|
||||
cls,
|
||||
@@ -544,6 +609,7 @@ class Blender(QtoCalculator):
|
||||
"get_width": Function("IfcLengthMeasure", "Width", ""),
|
||||
"get_footing_height": Function("IfcLengthMeasure", "Height", ""),
|
||||
"get_footing_length": Function("IfcLengthMeasure", "Length", ""),
|
||||
"get_footing_width": Function("IfcLengthMeasure", "Width", ""),
|
||||
# IfcAreaMeasure
|
||||
"get_covering_gross_area": Function("IfcAreaMeasure", "Covering Gross Area", ""),
|
||||
"get_covering_net_area": Function("IfcAreaMeasure", "Covering Net Area", ""),
|
||||
|
||||
Reference in New Issue
Block a user