Generate wall library.py: Difference between revisions
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 yaml | import yaml | ||
import FreeCAD | import FreeCAD | ||
import Part | import Part | ||
| Line 12: | Line 10: | ||
IN = 25.4 | IN = 25.4 | ||
| 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( | def in_to_mm(v): | ||
return | return v * IN | ||
def | def ft_to_in(v): | ||
return v * 12 | |||
return | |||
| 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 | width_in = ft_to_in(width_ft) | ||
framed_height_in = height_ft | 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) | ||
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) | ||
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 | ||
) | ) | ||
top_plate | 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) | stud_positions = derive_stud_positions(width_in, stud_thickness_in, spacing_oc_in) | ||
for | for x in stud_positions: | ||
stud = | 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), | ) | ||
stud.translate( | |||
FreeCAD.Vector( | |||
in_to_mm(x), | |||
0, | |||
plate_thickness_mm | |||
) | |||
) | ) | ||
shapes.append( | shapes.append(stud) | ||
osb = | # 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, | ) | ||
osb.translate( | |||
FreeCAD.Vector( | |||
0, | |||
-osb_thickness_mm, | |||
0 | |||
) | |||
) | ) | ||
shapes.append( | 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() | 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
- !/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()