Generate wall library.py: Difference between revisions

From Open Source Ecology
Jump to navigation Jump to search
No edit summary
No edit summary
 
(One intermediate revision by the same user not shown)
Line 3: Line 3:
from pathlib import Path
from pathlib import Path
import sys
import sys
import math
import yaml
import yaml
import FreeCAD
import FreeCAD
import Part
import Part
Line 12: Line 10:


IN = 25.4
IN = 25.4
FT = 304.8




Line 21: Line 18:


def nominal_to_actual_lumber(nominal):
def nominal_to_actual_lumber(nominal):
     table = {
     table = {
         "2x2": (1.5, 1.5),
         "2x2": (1.5, 1.5),
Line 30: Line 28:
         "2x12": (1.5, 11.25),
         "2x12": (1.5, 11.25),
     }
     }
     return table[nominal]
     return table[nominal]




def in_to_mm(x):
def in_to_mm(v):
     return x * IN
     return v * IN
 
 
def ft_to_mm(x):
    return x * FT




def make_box(x_len, y_len, z_len, x, y, z):
def ft_to_in(v):
    shape = Part.makeBox(x_len, y_len, z_len)
     return v * 12
    shape.translate(FreeCAD.Vector(x, y, z))
     return shape




Line 77: Line 70:
     stud_thickness_in, stud_depth_in = nominal_to_actual_lumber(stud_nominal)
     stud_thickness_in, stud_depth_in = nominal_to_actual_lumber(stud_nominal)


     width_in = width_ft * 12
     width_in = ft_to_in(width_ft)
     framed_height_in = height_ft * 12
     framed_height_in = ft_to_in(height_ft)


     plate_thickness_in = stud_thickness_in
     plate_thickness_in = stud_thickness_in
     stud_length_in = framed_height_in - 2 * plate_thickness_in
     stud_length_in = framed_height_in - (2 * plate_thickness_in)
 
    doc = FreeCAD.newDocument(instance_id)
 
    shapes = []


     width_mm = in_to_mm(width_in)
     width_mm = in_to_mm(width_in)
Line 94: Line 83:
     framed_height_mm = in_to_mm(framed_height_in)
     framed_height_mm = in_to_mm(framed_height_in)


     bottom_plate = make_box(
     shapes = []
 
    # bottom plate
    shapes.append(
        Part.makeBox(
            width_mm,
            stud_depth_mm,
            plate_thickness_mm
        )
    )
 
    # top plate
    top_plate = Part.makeBox(
         width_mm,
         width_mm,
         stud_depth_mm,
         stud_depth_mm,
         plate_thickness_mm,
         plate_thickness_mm
        0,
        0,
        0
     )
     )
    shapes.append(("bottom_plate", bottom_plate))


     top_plate = make_box(
     top_plate.translate(
         width_mm,
         FreeCAD.Vector(
        stud_depth_mm,
            0,
        plate_thickness_mm,
            0,
        0,
            framed_height_mm - plate_thickness_mm
        0,
        )
        framed_height_mm - plate_thickness_mm
     )
     )
    shapes.append(("top_plate", top_plate))


    shapes.append(top_plate)
    # studs
     stud_positions = derive_stud_positions(width_in, stud_thickness_in, spacing_oc_in)
     stud_positions = derive_stud_positions(width_in, stud_thickness_in, spacing_oc_in)


     for i, x in enumerate(stud_positions):
     for x in stud_positions:


         stud = make_box(
         stud = Part.makeBox(
             in_to_mm(stud_thickness_in),
             in_to_mm(stud_thickness_in),
             stud_depth_mm,
             stud_depth_mm,
             stud_length_mm,
             stud_length_mm
             in_to_mm(x),
        )
            0,
 
             plate_thickness_mm
        stud.translate(
             FreeCAD.Vector(
                in_to_mm(x),
                0,
                plate_thickness_mm
             )
         )
         )


         shapes.append((f"stud_{i+1:02d}", stud))
         shapes.append(stud)


     osb = make_box(
    # OSB sheathing (south side = negative Y)
     osb = Part.makeBox(
         width_mm,
         width_mm,
         osb_thickness_mm,
         osb_thickness_mm,
         framed_height_mm,
         framed_height_mm
         0,
    )
        stud_depth_mm,
 
         0
    osb.translate(
         FreeCAD.Vector(
            0,
            -osb_thickness_mm,
            0
         )
     )
     )


     shapes.append(("osb_exterior", osb))
     shapes.append(osb)


     for name, shape in shapes:
     # create compound module
        obj = doc.addObject("Part::Feature", name)
    wall = Part.makeCompound(shapes)
        obj.Shape = shape
 
    doc = FreeCAD.newDocument(instance_id)
 
    obj = doc.addObject("Part::Feature", "wall_module")
 
    obj.Shape = wall


     doc.recompute()
     doc.recompute()
Line 150: Line 164:


def save_document(doc, path):
def save_document(doc, path):
     path.parent.mkdir(exist_ok=True)
     path.parent.mkdir(exist_ok=True)
     doc.saveAs(str(path))
     doc.saveAs(str(path))


Line 161: Line 177:


     instances_doc = load_yaml(sys.argv[1])
     instances_doc = load_yaml(sys.argv[1])
     instances = instances_doc["instances"]
     instances = instances_doc["instances"]



Latest revision as of 03:32, 11 March 2026

  1. !/usr/bin/env python3

from pathlib import Path import sys import yaml import FreeCAD import Part

OUTPUT_DIR = Path("cad_library")

IN = 25.4


def load_yaml(path):

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


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 in_to_mm(v):

   return v * IN


def ft_to_in(v):

   return v * 12


def derive_stud_positions(width_in, stud_thickness_in, spacing_oc_in):

   positions = [0.0]
   right_edge = width_in - stud_thickness_in
   current = spacing_oc_in
   while current + stud_thickness_in <= right_edge:
       positions.append(current)
       current += spacing_oc_in
   if positions[-1] != right_edge:
       positions.append(right_edge)
   return positions


def build_wall(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"]
   spacing_oc_in = params["stud_spacing_oc_in"]
   osb_thickness_in = params["osb_thickness_in"]
   stud_thickness_in, stud_depth_in = nominal_to_actual_lumber(stud_nominal)
   width_in = ft_to_in(width_ft)
   framed_height_in = ft_to_in(height_ft)
   plate_thickness_in = stud_thickness_in
   stud_length_in = framed_height_in - (2 * plate_thickness_in)
   width_mm = in_to_mm(width_in)
   stud_depth_mm = in_to_mm(stud_depth_in)
   plate_thickness_mm = in_to_mm(plate_thickness_in)
   stud_length_mm = in_to_mm(stud_length_in)
   osb_thickness_mm = in_to_mm(osb_thickness_in)
   framed_height_mm = in_to_mm(framed_height_in)
   shapes = []
   # bottom plate
   shapes.append(
       Part.makeBox(
           width_mm,
           stud_depth_mm,
           plate_thickness_mm
       )
   )
   # top plate
   top_plate = Part.makeBox(
       width_mm,
       stud_depth_mm,
       plate_thickness_mm
   )
   top_plate.translate(
       FreeCAD.Vector(
           0,
           0,
           framed_height_mm - plate_thickness_mm
       )
   )
   shapes.append(top_plate)
   # studs
   stud_positions = derive_stud_positions(width_in, stud_thickness_in, spacing_oc_in)
   for x in stud_positions:
       stud = Part.makeBox(
           in_to_mm(stud_thickness_in),
           stud_depth_mm,
           stud_length_mm
       )
       stud.translate(
           FreeCAD.Vector(
               in_to_mm(x),
               0,
               plate_thickness_mm
           )
       )
       shapes.append(stud)
   # OSB sheathing (south side = negative Y)
   osb = Part.makeBox(
       width_mm,
       osb_thickness_mm,
       framed_height_mm
   )
   osb.translate(
       FreeCAD.Vector(
           0,
           -osb_thickness_mm,
           0
       )
   )
   shapes.append(osb)
   # create compound module
   wall = Part.makeCompound(shapes)
   doc = FreeCAD.newDocument(instance_id)
   obj = doc.addObject("Part::Feature", "wall_module")
   obj.Shape = wall
   doc.recompute()
   return doc


def save_document(doc, path):

   path.parent.mkdir(exist_ok=True)
   doc.saveAs(str(path))


def main():

   if len(sys.argv) != 2:
       print("Usage: generate_wall_library.py instances.yaml")
       sys.exit(1)
   instances_doc = load_yaml(sys.argv[1])
   instances = instances_doc["instances"]
   OUTPUT_DIR.mkdir(exist_ok=True)
   for inst in instances:
       instance_id = inst["id"]
       print(f"Generating {instance_id}...")
       doc = build_wall(inst)
       out_path = OUTPUT_DIR / f"{instance_id}.FCStd"
       save_document(doc, out_path)
       print(f"Saved {out_path}")
   print(f"Generated {len(instances)} modules")


if __name__ == "__main__":

   main()