Spaces:
Running
Running
| """ | |
| checker_slabs — Slab compliance checks (IFCore contract). | |
| Checks: | |
| check_slab_thickness — EHE / Código Estructural — slab thickness: | |
| floor slabs 100–200 mm | |
| roof slabs 200–350 mm | |
| Notes: | |
| - Non-structural slabs (finish, wood joist, live roof) are skipped. | |
| - IFC model lengths are in metres; converted to mm using _get_length_scale. | |
| - Roof slabs detected by "roof" in the slab name (case-insensitive). | |
| """ | |
| import sys | |
| import ifcopenshell | |
| import ifcopenshell.util.element | |
| # Floor slab limits | |
| MIN_THICKNESS_MM = 100 | |
| MAX_THICKNESS_MM = 200 | |
| # Roof slab limits | |
| MIN_ROOF_THICKNESS_MM = 200 | |
| MAX_ROOF_THICKNESS_MM = 350 | |
| def _get_length_scale(model: ifcopenshell.file) -> float: | |
| """Returns factor to convert model length units → metres.""" | |
| try: | |
| for assignment in model.by_type("IfcUnitAssignment"): | |
| for unit in assignment.Units: | |
| if hasattr(unit, "UnitType") and unit.UnitType == "LENGTHUNIT": | |
| prefix_map = {"MILLI": 0.001, "CENTI": 0.01, "DECI": 0.1, "KILO": 1000.0} | |
| if hasattr(unit, "Prefix") and unit.Prefix: | |
| return prefix_map.get(unit.Prefix, 1.0) | |
| return 1.0 # SI base METRE, no prefix | |
| except Exception: | |
| pass | |
| return 1.0 | |
| def _get_slab_thickness(slab, scale_to_mm: float): | |
| """Extract thickness and return value in mm. | |
| scale_to_mm = _get_length_scale(model) * 1000 | |
| """ | |
| material = ifcopenshell.util.element.get_material(slab) | |
| if material is not None: | |
| layer_set = None | |
| if material.is_a("IfcMaterialLayerSetUsage"): | |
| layer_set = material.ForLayerSet | |
| elif material.is_a("IfcMaterialLayerSet"): | |
| layer_set = material | |
| if layer_set is not None: | |
| total = sum(layer.LayerThickness for layer in layer_set.MaterialLayers) | |
| return round(total * scale_to_mm, 2) | |
| # Fallback: Qto | |
| for rel in slab.IsDefinedBy: | |
| if rel.is_a("IfcRelDefinesByProperties"): | |
| pset = rel.RelatingPropertyDefinition | |
| if pset.is_a("IfcElementQuantity") and "Slab" in (pset.Name or ""): | |
| for q in pset.Quantities: | |
| if q.is_a("IfcQuantityLength") and q.Name in ("Width", "Depth", "Height"): | |
| return round(q.LengthValue * scale_to_mm, 2) | |
| return None | |
| def _get_storey_name(slab): | |
| """Get the building storey name for a slab.""" | |
| storey = ifcopenshell.util.element.get_container(slab) | |
| if storey is not None and storey.is_a("IfcBuildingStorey"): | |
| return storey.Name or f"Storey (#{storey.id()})" | |
| return "Unknown Storey" | |
| def check_slab_thickness(model): | |
| """EHE / Código Estructural — slab thickness by type: | |
| floor slabs: 100–200 mm | |
| roof slabs: 200–350 mm | |
| Skips non-structural slabs (finish, wood joist, live roof). | |
| Converts IFC model units to mm via IfcUnitAssignment. | |
| """ | |
| scale_to_mm = _get_length_scale(model) * 1000 | |
| results = [] | |
| for slab in model.by_type("IfcSlab"): | |
| name = slab.Name or f"Slab #{slab.id()}" | |
| # Skip non-structural slab types | |
| name_lower = name.lower() | |
| if any(kw in name_lower for kw in ("finish", "wood joist", "live roof")): | |
| continue | |
| # Select limits based on slab type | |
| is_roof = "roof" in name_lower | |
| t_min = MIN_ROOF_THICKNESS_MM if is_roof else MIN_THICKNESS_MM | |
| t_max = MAX_ROOF_THICKNESS_MM if is_roof else MAX_THICKNESS_MM | |
| slab_type_label = "roof slab" if is_roof else "floor slab" | |
| storey = _get_storey_name(slab) | |
| thickness = _get_slab_thickness(slab, scale_to_mm) | |
| if thickness is None: | |
| check_status = "blocked" | |
| actual = None | |
| comment = "Thickness property not found in material layers or Qto" | |
| elif t_min <= thickness <= t_max: | |
| check_status = "pass" | |
| actual = f"{thickness:.0f} mm" | |
| comment = None | |
| else: | |
| check_status = "fail" | |
| actual = f"{thickness:.0f} mm" | |
| if thickness < t_min: | |
| comment = f"{slab_type_label.capitalize()} is {t_min - thickness:.0f} mm too thin" | |
| else: | |
| comment = f"{slab_type_label.capitalize()} is {thickness - t_max:.0f} mm too thick" | |
| results.append({ | |
| "element_id": getattr(slab, "GlobalId", None), | |
| "element_type": "IfcSlab", | |
| "element_name": f"{storey} / {name}", | |
| "element_name_long": f"{name} ({storey})", | |
| "check_status": check_status, | |
| "actual_value": actual, | |
| "required_value": f"{t_min}–{t_max} mm ({slab_type_label})", | |
| "comment": comment, | |
| "log": None, | |
| }) | |
| return results | |
| if __name__ == "__main__": | |
| ifc_path = sys.argv[1] if len(sys.argv) > 1 else "data/01_Duplex_Apartment.ifc" | |
| print("Loading:", ifc_path) | |
| _model = ifcopenshell.open(ifc_path) | |
| print("Slabs:", len(list(_model.by_type("IfcSlab")))) | |
| _ICON = {"pass": "PASS", "fail": "FAIL", "warning": "WARN", "blocked": "BLKD", "log": "LOG "} | |
| for _fn in [check_slab_thickness]: | |
| print("\n" + "=" * 60) | |
| print(" ", _fn.__name__) | |
| print("=" * 60) | |
| for _row in _fn(_model): | |
| print(" ", "[" + _ICON.get(_row["check_status"], "?") + "]", _row["element_name"]) | |
| if _row["actual_value"]: | |
| print(" actual :", _row["actual_value"]) | |
| if _row["required_value"]: | |
| print(" required :", _row["required_value"]) | |
| if _row["comment"]: | |
| print(" comment :", _row["comment"]) | |