Generate wall library.py: Difference between revisions
Jump to navigation
Jump to search
(Created page with "#!/usr/bin/env python3 from pathlib import Path from typing import Any import math import sys import yaml import FreeCAD import Part OUTPUT_DIR = Path("cad_library") def load_yaml(path: Path) -> Any: with path.open("r", encoding="utf-8") as f: return yaml.safe_load(f) def nominal_to_actual_lumber(nominal: str) -> tuple[float, float]: mapping = { "2x2": (1.5, 1.5), "2x3": (1.5, 2.5), "2x4": (1.5, 3.5), "2x6": (1.5,...") |
No edit summary |
||
| Line 2: | Line 2: | ||
from pathlib import Path | from pathlib import Path | ||
import sys | |||
import math | import math | ||
import yaml | import yaml | ||
| Line 11: | Line 9: | ||
import Part | import Part | ||
OUTPUT_DIR = Path("cad_library") | |||
IN = 25.4 | |||
FT = 304.8 | |||
def load_yaml(path | def load_yaml(path): | ||
with | with open(path, "r") as f: | ||
return yaml.safe_load(f) | return yaml.safe_load(f) | ||
def nominal_to_actual_lumber(nominal | def nominal_to_actual_lumber(nominal): | ||
table = { | |||
"2x2": (1.5, 1.5), | "2x2": (1.5, 1.5), | ||
"2x3": (1.5, 2.5), | "2x3": (1.5, 2.5), | ||
| Line 30: | Line 30: | ||
"2x12": (1.5, 11.25), | "2x12": (1.5, 11.25), | ||
} | } | ||
return table[nominal] | |||
return | |||
def | def in_to_mm(x): | ||
return | return x * IN | ||
def make_box(x_len | def ft_to_mm(x): | ||
return x * FT | |||
def make_box(x_len, y_len, z_len, x, y, z): | |||
shape = Part.makeBox(x_len, y_len, z_len) | shape = Part.makeBox(x_len, y_len, z_len) | ||
shape.translate(FreeCAD.Vector(x, y, z)) | shape.translate(FreeCAD.Vector(x, y, z)) | ||
| Line 45: | Line 47: | ||
def derive_stud_positions(width_in | def derive_stud_positions(width_in, stud_thickness_in, spacing_oc_in): | ||
positions = [0.0] | positions = [0.0] | ||
right_edge = width_in - stud_thickness_in | |||
current = spacing_oc_in | |||
while | while current + stud_thickness_in <= right_edge: | ||
positions.append( | positions.append(current) | ||
current += spacing_oc_in | |||
if | if positions[-1] != right_edge: | ||
positions.append( | positions.append(right_edge) | ||
return positions | |||
def build_wall(instance): | |||
instance_id = instance["id"] | instance_id = instance["id"] | ||
params = instance["parameters"] | params = instance["parameters"] | ||
width_ft = | width_ft = params["nominal_width_ft"] | ||
height_ft = | height_ft = params["nominal_height_ft"] | ||
stud_nominal = | stud_nominal = params["stud_lumber_nominal"] | ||
spacing_oc_in = | spacing_oc_in = params["stud_spacing_oc_in"] | ||
osb_thickness_in = | osb_thickness_in = params["osb_thickness_in"] | ||
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_in = width_ft * 12 | ||
framed_height_in = | framed_height_in = height_ft * 12 | ||
plate_thickness_in = stud_thickness_in | plate_thickness_in = stud_thickness_in | ||
stud_length_in = framed_height_in - 2 | stud_length_in = framed_height_in - 2 * plate_thickness_in | ||
doc = FreeCAD.newDocument(instance_id) | doc = FreeCAD.newDocument(instance_id) | ||
| Line 98: | Line 87: | ||
shapes = [] | shapes = [] | ||
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) | |||
bottom_plate = make_box( | bottom_plate = make_box( | ||
width_mm, | |||
stud_depth_mm, | |||
plate_thickness_mm, | |||
0, | |||
0, | |||
0 | 0 | ||
) | ) | ||
shapes.append(("bottom_plate", bottom_plate)) | shapes.append(("bottom_plate", bottom_plate)) | ||
top_plate = make_box( | top_plate = make_box( | ||
width_mm, | |||
stud_depth_mm, | |||
plate_thickness_mm, | |||
0, | |||
0, | |||
framed_height_mm - plate_thickness_mm | |||
) | ) | ||
shapes.append(("top_plate", top_plate)) | shapes.append(("top_plate", top_plate)) | ||
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 i, x in enumerate(stud_positions): | ||
stud = make_box( | stud = make_box( | ||
stud_thickness_in, | in_to_mm(stud_thickness_in), | ||
stud_depth_mm, | |||
stud_length_mm, | |||
x, | in_to_mm(x), | ||
0, | |||
plate_thickness_mm | |||
) | ) | ||
shapes.append((f"stud_{i+1:02d}", stud)) | |||
osb = make_box( | osb = make_box( | ||
width_mm, | |||
osb_thickness_mm, | |||
framed_height_mm, | |||
0, | |||
stud_depth_mm, | |||
0 | 0 | ||
) | ) | ||
shapes.append(("osb_exterior", osb)) | shapes.append(("osb_exterior", osb)) | ||
| Line 158: | Line 144: | ||
obj.Shape = shape | obj.Shape = shape | ||
doc.recompute() | |||
return doc | return doc | ||
def save_document(doc | def save_document(doc, path): | ||
path.parent.mkdir(exist_ok=True) | |||
doc.saveAs(str( | doc.saveAs(str(path)) | ||
def main(): | |||
if len(sys.argv) != 2: | if len(sys.argv) != 2: | ||
print("Usage: | 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}...") | |||
print(" | |||
doc = build_wall(inst) | |||
out_path = OUTPUT_DIR / f"{instance_id}.FCStd" | out_path = OUTPUT_DIR / f"{instance_id}.FCStd" | ||
save_document(doc, out_path) | save_document(doc, out_path) | ||
print(f"Generated {len(instances)} | print(f"Saved {out_path}") | ||
print(f"Generated {len(instances)} modules") | |||
if __name__ == "__main__": | if __name__ == "__main__": | ||
main() | |||
Revision as of 02:41, 11 March 2026
- !/usr/bin/env python3
from pathlib import Path import sys import math import yaml
import FreeCAD import Part
OUTPUT_DIR = Path("cad_library")
IN = 25.4 FT = 304.8
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(x):
return x * IN
def ft_to_mm(x):
return x * FT
def make_box(x_len, y_len, z_len, x, y, z):
shape = Part.makeBox(x_len, y_len, z_len) shape.translate(FreeCAD.Vector(x, y, z)) return shape
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 = width_ft * 12 framed_height_in = height_ft * 12
plate_thickness_in = stud_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) 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)
bottom_plate = make_box(
width_mm,
stud_depth_mm,
plate_thickness_mm,
0,
0,
0
)
shapes.append(("bottom_plate", bottom_plate))
top_plate = make_box(
width_mm,
stud_depth_mm,
plate_thickness_mm,
0,
0,
framed_height_mm - plate_thickness_mm
)
shapes.append(("top_plate", top_plate))
stud_positions = derive_stud_positions(width_in, stud_thickness_in, spacing_oc_in)
for i, x in enumerate(stud_positions):
stud = make_box(
in_to_mm(stud_thickness_in),
stud_depth_mm,
stud_length_mm,
in_to_mm(x),
0,
plate_thickness_mm
)
shapes.append((f"stud_{i+1:02d}", stud))
osb = make_box(
width_mm,
osb_thickness_mm,
framed_height_mm,
0,
stud_depth_mm,
0
)
shapes.append(("osb_exterior", osb))
for name, shape in shapes:
obj = doc.addObject("Part::Feature", name)
obj.Shape = shape
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()