<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://wiki.opensourceecology.org/index.php?action=history&amp;feed=atom&amp;title=Validate_wall_geometry.py</id>
	<title>Validate wall geometry.py - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://wiki.opensourceecology.org/index.php?action=history&amp;feed=atom&amp;title=Validate_wall_geometry.py"/>
	<link rel="alternate" type="text/html" href="https://wiki.opensourceecology.org/index.php?title=Validate_wall_geometry.py&amp;action=history"/>
	<updated>2026-05-04T00:30:41Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.39.13</generator>
	<entry>
		<id>https://wiki.opensourceecology.org/index.php?title=Validate_wall_geometry.py&amp;diff=321155&amp;oldid=prev</id>
		<title>Marcin: Created page with &quot;#!/usr/bin/env python3  import sys from pathlib import Path import yaml import FreeCAD  CAD_DIR = Path(&quot;cad_library&quot;)   def ft_to_in(ft):     return ft * 12.0   def nominal_to_actual_lumber(nominal):     table = {         &quot;2x2&quot;: (1.5, 1.5),         &quot;2x3&quot;: (1.5, 2.5),         &quot;2x4&quot;: (1.5, 3.5),         &quot;2x6&quot;: (1.5, 5.5),         &quot;2x8&quot;: (1.5, 7.25),         &quot;2x10&quot;: (1.5, 9.25),         &quot;2x12&quot;: (1.5, 11.25),     }     return table[nominal]   def load_yaml(path):     with op...&quot;</title>
		<link rel="alternate" type="text/html" href="https://wiki.opensourceecology.org/index.php?title=Validate_wall_geometry.py&amp;diff=321155&amp;oldid=prev"/>
		<updated>2026-03-11T02:28:10Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;#!/usr/bin/env python3  import sys from pathlib import Path import yaml import FreeCAD  CAD_DIR = Path(&amp;quot;cad_library&amp;quot;)   def ft_to_in(ft):     return ft * 12.0   def nominal_to_actual_lumber(nominal):     table = {         &amp;quot;2x2&amp;quot;: (1.5, 1.5),         &amp;quot;2x3&amp;quot;: (1.5, 2.5),         &amp;quot;2x4&amp;quot;: (1.5, 3.5),         &amp;quot;2x6&amp;quot;: (1.5, 5.5),         &amp;quot;2x8&amp;quot;: (1.5, 7.25),         &amp;quot;2x10&amp;quot;: (1.5, 9.25),         &amp;quot;2x12&amp;quot;: (1.5, 11.25),     }     return table[nominal]   def load_yaml(path):     with op...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;#!/usr/bin/env python3&lt;br /&gt;
&lt;br /&gt;
import sys&lt;br /&gt;
from pathlib import Path&lt;br /&gt;
import yaml&lt;br /&gt;
import FreeCAD&lt;br /&gt;
&lt;br /&gt;
CAD_DIR = Path(&amp;quot;cad_library&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def ft_to_in(ft):&lt;br /&gt;
    return ft * 12.0&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def nominal_to_actual_lumber(nominal):&lt;br /&gt;
    table = {&lt;br /&gt;
        &amp;quot;2x2&amp;quot;: (1.5, 1.5),&lt;br /&gt;
        &amp;quot;2x3&amp;quot;: (1.5, 2.5),&lt;br /&gt;
        &amp;quot;2x4&amp;quot;: (1.5, 3.5),&lt;br /&gt;
        &amp;quot;2x6&amp;quot;: (1.5, 5.5),&lt;br /&gt;
        &amp;quot;2x8&amp;quot;: (1.5, 7.25),&lt;br /&gt;
        &amp;quot;2x10&amp;quot;: (1.5, 9.25),&lt;br /&gt;
        &amp;quot;2x12&amp;quot;: (1.5, 11.25),&lt;br /&gt;
    }&lt;br /&gt;
    return table[nominal]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def load_yaml(path):&lt;br /&gt;
    with open(path, &amp;quot;r&amp;quot;) as f:&lt;br /&gt;
        return yaml.safe_load(f)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def count_objects(doc, prefix):&lt;br /&gt;
    count = 0&lt;br /&gt;
    for obj in doc.Objects:&lt;br /&gt;
        if obj.Name.startswith(prefix):&lt;br /&gt;
            count += 1&lt;br /&gt;
    return count&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def validate_instance(instance):&lt;br /&gt;
&lt;br /&gt;
    instance_id = instance[&amp;quot;id&amp;quot;]&lt;br /&gt;
    params = instance[&amp;quot;parameters&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
    width_ft = params[&amp;quot;nominal_width_ft&amp;quot;]&lt;br /&gt;
    height_ft = params[&amp;quot;nominal_height_ft&amp;quot;]&lt;br /&gt;
    stud_nominal = params[&amp;quot;stud_lumber_nominal&amp;quot;]&lt;br /&gt;
    osb_thickness = params[&amp;quot;osb_thickness_in&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
    width_in_expected = ft_to_in(width_ft)&lt;br /&gt;
    height_in_expected = ft_to_in(height_ft)&lt;br /&gt;
&lt;br /&gt;
    stud_thickness, stud_depth = nominal_to_actual_lumber(stud_nominal)&lt;br /&gt;
&lt;br /&gt;
    file_path = CAD_DIR / f&amp;quot;{instance_id}.FCStd&amp;quot;&lt;br /&gt;
&lt;br /&gt;
    if not file_path.exists():&lt;br /&gt;
        return [f&amp;quot;{instance_id}: CAD file not found&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
    doc = FreeCAD.openDocument(str(file_path))&lt;br /&gt;
&lt;br /&gt;
    errors = []&lt;br /&gt;
&lt;br /&gt;
    bbox = None&lt;br /&gt;
&lt;br /&gt;
    for obj in doc.Objects:&lt;br /&gt;
        if hasattr(obj, &amp;quot;Shape&amp;quot;):&lt;br /&gt;
            bb = obj.Shape.BoundBox&lt;br /&gt;
            if bbox is None:&lt;br /&gt;
                bbox = bb&lt;br /&gt;
            else:&lt;br /&gt;
                bbox.add(bb)&lt;br /&gt;
&lt;br /&gt;
    if bbox is None:&lt;br /&gt;
        errors.append(f&amp;quot;{instance_id}: no geometry found&amp;quot;)&lt;br /&gt;
        return errors&lt;br /&gt;
&lt;br /&gt;
    width_actual = bbox.XLength&lt;br /&gt;
    height_actual = bbox.ZLength&lt;br /&gt;
    thickness_actual = bbox.YLength&lt;br /&gt;
&lt;br /&gt;
    if abs(width_actual - width_in_expected) &amp;gt; 0.25:&lt;br /&gt;
        errors.append(&lt;br /&gt;
            f&amp;quot;{instance_id}: width mismatch expected {width_in_expected} got {width_actual}&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
    if abs(height_actual - height_in_expected) &amp;gt; 0.25:&lt;br /&gt;
        errors.append(&lt;br /&gt;
            f&amp;quot;{instance_id}: height mismatch expected {height_in_expected} got {height_actual}&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
    expected_thickness = stud_depth + osb_thickness&lt;br /&gt;
&lt;br /&gt;
    if abs(thickness_actual - expected_thickness) &amp;gt; 0.25:&lt;br /&gt;
        errors.append(&lt;br /&gt;
            f&amp;quot;{instance_id}: thickness mismatch expected {expected_thickness} got {thickness_actual}&amp;quot;&lt;br /&gt;
        )&lt;br /&gt;
&lt;br /&gt;
    stud_count = count_objects(doc, &amp;quot;stud_&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if stud_count &amp;lt; 2:&lt;br /&gt;
        errors.append(f&amp;quot;{instance_id}: invalid stud count {stud_count}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    osb_count = count_objects(doc, &amp;quot;osb&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    if osb_count != 1:&lt;br /&gt;
        errors.append(f&amp;quot;{instance_id}: expected 1 OSB panel, found {osb_count}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
    FreeCAD.closeDocument(doc.Name)&lt;br /&gt;
&lt;br /&gt;
    return errors&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
def main():&lt;br /&gt;
&lt;br /&gt;
    if len(sys.argv) != 2:&lt;br /&gt;
        print(&amp;quot;Usage: validate_wall_geometry.py instances.yaml&amp;quot;)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&lt;br /&gt;
    instances_doc = load_yaml(sys.argv[1])&lt;br /&gt;
&lt;br /&gt;
    instances = instances_doc[&amp;quot;instances&amp;quot;]&lt;br /&gt;
&lt;br /&gt;
    all_errors = []&lt;br /&gt;
&lt;br /&gt;
    for inst in instances:&lt;br /&gt;
        errs = validate_instance(inst)&lt;br /&gt;
        all_errors.extend(errs)&lt;br /&gt;
&lt;br /&gt;
    if all_errors:&lt;br /&gt;
        print(&amp;quot;Geometry validation FAILED:&amp;quot;)&lt;br /&gt;
        for e in all_errors:&lt;br /&gt;
            print(&amp;quot; &amp;quot;, e)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
&lt;br /&gt;
    print(f&amp;quot;Geometry validation passed for {len(instances)} modules&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;quot;__main__&amp;quot;:&lt;br /&gt;
    main()&lt;/div&gt;</summary>
		<author><name>Marcin</name></author>
	</entry>
</feed>