///|
/// 06-stackable-spacer-tower
///
/// A set of stackable spacers with full-height tapers that let them stack
/// reliably despite print tolerances.
///
/// Usage (stdout):
/// moon run --target native examples/06-stackable-spacer-tower -- --count 3 > tower.step
async fn main {
let base_args_spec = {
"count": @cli.opt_double(
1.0,
help="Number of spacers in the tower",
min=1.0,
),
"id": @cli.opt_double(10.0, help="Inner diameter of the hole (mm)", min=1.0),
"mid": @cli.opt_double(
15.0,
help="Diameter of the interlocking feature (mm)",
min=2.0,
),
"od": @cli.opt_double(
25.0,
help="Outer diameter of the spacer (mm)",
min=3.0,
),
"height": @cli.opt_double(
5.0,
help="Height of the spacer body (mm)",
min=1.0,
),
"clickHeight": @cli.opt_double(
2.0,
help="Height of the interlocking protrusion (mm)",
min=0.5,
),
"resolution": @cli.opt_double(64.0, help="Circle segments", min=3.0),
}
let args_spec = @cli.with_translate(@common.with_output_args(base_args_spec))
guard @cli.parse(
args_spec,
prog="06-stackable-spacer-tower",
description="Generate a stackable spacer tower.",
)
is Some(args) else {
return
}
let count = args.double("count").to_int()
let id = args.double("id")
let mid = args.double("mid")
let od = args.double("od")
let height = args.double("height")
let click_height = args.double("clickHeight")
let resolution = args.double("resolution").to_int()
let output = @common.output_paths(args)
let (tx, ty, tz) = match args.translate_xyz() {
Some(v) => v
None => {
@cli.eprintln("error: invalid translation")
return
}
}
// Basic validation
if id >= mid {
@cli.eprintln(
"error: inner diameter (\{id}) must be less than middle diameter (\{mid})",
)
abort("")
}
if mid >= od {
@cli.eprintln(
"error: middle diameter (\{mid}) must be less than outer diameter (\{od})",
)
abort("")
}
if click_height >= height {
@cli.eprintln(
"error: clickHeight (\{click_height}) must be less than height (\{height})",
)
abort("")
}
let mut design = @cad.Design::new(
name="spacer-tower",
documentation=args.documentation(),
)
// Full-height draft: the female bore narrows over the whole body height and
// the male outer surface narrows at the same cone angle over its full
// height. This self-centres the parts and gives a constant clearance, so
// print tolerances do not stop them stacking.
let mut taper_d = 0.2
if taper_d > (mid - id) * 0.3 {
taper_d = (mid - id) * 0.3
}
if taper_d > (od - mid) * 0.3 {
taper_d = (od - mid) * 0.3
}
let fit_clear = 0.05
let colors = [@cad.Rgb::orange(), @cad.Rgb::green(), @cad.Rgb::blue()]
for i in 0.. Array[@cad.SceneNode] {
let body_steps : Int32 = 8
let click_steps : Int32 = 4
let outer_circle = @cad.Profile2D::circle(od, segments=resolution)
// Holes are wound opposite to the outer boundary.
let inner_circle = @cad.Profile2D::circle(id, segments=resolution).rev()
let nodes : Array[@cad.SceneNode] = []
// Female bore: largest at the bottom, narrowing to mid + fit_clear at the
// top. Each layer uses its mid-height diameter to keep the stair-step small.
let body_step_h = height / Double::from_int(body_steps)
for k in 0..