Validate wall geometry.py

From Open Source Ecology
Revision as of 02:28, 11 March 2026 by Marcin (talk | contribs) (Created page with "#!/usr/bin/env python3 import sys from pathlib import Path import yaml import FreeCAD CAD_DIR = Path("cad_library") def ft_to_in(ft): return ft * 12.0 def nominal_to_actual_lumber(nominal): table = { "2x2": (1.5, 1.5), "2x3": (1.5, 2.5), "2x4": (1.5, 3.5), "2x6": (1.5, 5.5), "2x8": (1.5, 7.25), "2x10": (1.5, 9.25), "2x12": (1.5, 11.25), } return table[nominal] def load_yaml(path): with op...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
  1. !/usr/bin/env python3

import sys from pathlib import Path import yaml import FreeCAD

CAD_DIR = Path("cad_library")


def ft_to_in(ft):

   return ft * 12.0


def nominal_to_actual_lumber(nominal):

   table = {
       "2x2": (1.5, 1.5),
       "2x3": (1.5, 2.5),
       "2x4": (1.5, 3.5),
       "2x6": (1.5, 5.5),
       "2x8": (1.5, 7.25),
       "2x10": (1.5, 9.25),
       "2x12": (1.5, 11.25),
   }
   return table[nominal]


def load_yaml(path):

   with open(path, "r") as f:
       return yaml.safe_load(f)


def count_objects(doc, prefix):

   count = 0
   for obj in doc.Objects:
       if obj.Name.startswith(prefix):
           count += 1
   return count


def validate_instance(instance):

   instance_id = instance["id"]
   params = instance["parameters"]
   width_ft = params["nominal_width_ft"]
   height_ft = params["nominal_height_ft"]
   stud_nominal = params["stud_lumber_nominal"]
   osb_thickness = params["osb_thickness_in"]
   width_in_expected = ft_to_in(width_ft)
   height_in_expected = ft_to_in(height_ft)
   stud_thickness, stud_depth = nominal_to_actual_lumber(stud_nominal)
   file_path = CAD_DIR / f"{instance_id}.FCStd"
   if not file_path.exists():
       return [f"{instance_id}: CAD file not found"]
   doc = FreeCAD.openDocument(str(file_path))
   errors = []
   bbox = None
   for obj in doc.Objects:
       if hasattr(obj, "Shape"):
           bb = obj.Shape.BoundBox
           if bbox is None:
               bbox = bb
           else:
               bbox.add(bb)
   if bbox is None:
       errors.append(f"{instance_id}: no geometry found")
       return errors
   width_actual = bbox.XLength
   height_actual = bbox.ZLength
   thickness_actual = bbox.YLength
   if abs(width_actual - width_in_expected) > 0.25:
       errors.append(
           f"{instance_id}: width mismatch expected {width_in_expected} got {width_actual}"
       )
   if abs(height_actual - height_in_expected) > 0.25:
       errors.append(
           f"{instance_id}: height mismatch expected {height_in_expected} got {height_actual}"
       )
   expected_thickness = stud_depth + osb_thickness
   if abs(thickness_actual - expected_thickness) > 0.25:
       errors.append(
           f"{instance_id}: thickness mismatch expected {expected_thickness} got {thickness_actual}"
       )
   stud_count = count_objects(doc, "stud_")
   if stud_count < 2:
       errors.append(f"{instance_id}: invalid stud count {stud_count}")
   osb_count = count_objects(doc, "osb")
   if osb_count != 1:
       errors.append(f"{instance_id}: expected 1 OSB panel, found {osb_count}")
   FreeCAD.closeDocument(doc.Name)
   return errors


def main():

   if len(sys.argv) != 2:
       print("Usage: validate_wall_geometry.py instances.yaml")
       sys.exit(1)
   instances_doc = load_yaml(sys.argv[1])
   instances = instances_doc["instances"]
   all_errors = []
   for inst in instances:
       errs = validate_instance(inst)
       all_errors.extend(errs)
   if all_errors:
       print("Geometry validation FAILED:")
       for e in all_errors:
           print(" ", e)
       sys.exit(1)
   print(f"Geometry validation passed for {len(instances)} modules")


if __name__ == "__main__":

   main()