///|
/// 32-spool-holder-bracket
///
/// A bracket that adapts to multiple spool widths and axle diameters.
///
/// Usage (stdout):
/// moon run --target native examples/32-spool-holder-bracket -- > bracket.step
///
/// Usage (write file):
/// moon run --target native examples/32-spool-holder-bracket -- -o bracket.step
async fn main {
let base_args_spec = {
"baseLength": @cli.opt_double(
80.0,
help="Base plate length (mm)",
min=0.0,
min_exclusive=true,
),
"baseThickness": @cli.opt_double(
10.0,
help="Base plate thickness (mm)",
min=0.0,
min_exclusive=true,
),
"armHeight": @cli.opt_double(
120.0,
help="Arm height from base (mm)",
min=0.0,
min_exclusive=true,
),
"armWidth": @cli.opt_double(
30.0,
help="Arm width / extrusion depth (mm)",
min=0.0,
min_exclusive=true,
),
"armBaseThickness": @cli.opt_double(
40.0,
help="Arm thickness at base (mm)",
min=0.0,
min_exclusive=true,
),
"armTopThickness": @cli.opt_double(
25.0,
help="Arm thickness at top (mm)",
min=0.0,
min_exclusive=true,
),
"axleDia": @cli.opt_double(
20.0,
help="Axle diameter (mm)",
min=0.0,
min_exclusive=true,
),
"holeDia": @cli.opt_double(
6.0,
help="Mounting hole diameter (mm)",
min=0.0,
min_exclusive=true,
),
"holeSpacing": @cli.opt_double(
60.0,
help="Mounting hole spacing (mm)",
min=0.0,
min_exclusive=true,
),
}
let args_spec = @cli.with_translate(@common.with_output_args(base_args_spec))
guard @cli.parse(
args_spec,
prog="32-spool-holder-bracket",
description="Generate a spool holder bracket STEP model.",
)
is Some(args) else {
return
}
let base_length = args.double("baseLength")
let base_thickness = args.double("baseThickness")
let arm_height = args.double("armHeight")
let arm_width = args.double("armWidth")
let arm_base_thickness = args.double("armBaseThickness")
let arm_top_thickness = args.double("armTopThickness")
let axle_dia = args.double("axleDia")
let hole_dia = args.double("holeDia")
let hole_spacing = args.double("holeSpacing")
let output = @common.output_paths(args)
let (tx, ty, tz) = match args.translate_xyz() {
Some(v) => v
None => {
@cli.eprintln("error: invalid translation")
abort("")
}
}
// Validate parameters
if arm_top_thickness < axle_dia {
@cli.eprintln("error: armTopThickness must be >= axleDia")
abort("")
}
if hole_spacing >= base_length {
@cli.eprintln("error: holeSpacing must be < baseLength")
abort("")
}
if arm_base_thickness >= hole_spacing - hole_dia {
@cli.eprintln(
"error: armBaseThickness must be < holeSpacing - holeDia to avoid blocking holes",
)
abort("")
}
// 1. Base (Top view extrusion)
let base_outer = @cad.Profile2D::centered_rect(base_length, arm_width)
let hole_profile = @cad.Profile2D::circle(hole_dia).rev()
let base_holes = [
@cad.translate_pts(hole_profile, -hole_spacing / 2.0, 0.0),
@cad.translate_pts(hole_profile, hole_spacing / 2.0, 0.0),
]
let base = @cad.ExtrudedProfile::new(
base_outer,
base_thickness,
holes=base_holes,
)
.with_name("base")
.with_color(@cad.Rgb::new(0.2, 0.4, 0.8)) // Beautiful blue
.translate(x=tx, y=ty, z=tz)
// 2. Arm (Side view extrusion)
let arm_z0 = base_thickness
let arm_z1 = base_thickness + arm_height
let arm_pts = []
arm_pts.push((-arm_base_thickness / 2.0, arm_z0))
arm_pts.push((arm_base_thickness / 2.0, arm_z0))
arm_pts.push((arm_top_thickness / 2.0, arm_z1))
// Add cradle arc (concave)
// arc(dia, PI, 2*PI).rev() goes from (R, 0) to (-R, 0) through (0, -R)
let arc_pts = @cad.Profile2D::arc(axle_dia, @math.PI, 2.0 * @math.PI).rev()
for pt in arc_pts {
arm_pts.push((pt.0, pt.1 + arm_z1))
}
arm_pts.push((-arm_top_thickness / 2.0, arm_z1))
let arm = @cad.ExtrudedProfile::new(arm_pts, arm_width)
.with_name("arm")
.with_color(@cad.Rgb::new(0.2, 0.8, 0.8)) // Beautiful cyan
.rotate_x(90.0)
.translate(x=tx, y=ty + arm_width / 2.0, z=tz) // center it on base
let design = @cad.Design::new(
name="spool-holder-bracket",
documentation=args.documentation(),
)
.add(base)
.add(arm)
@common.write_output(design, output)
}