///|
/// 28-phone-stand-parametric
///
/// A parametric phone stand generator.
/// This example demonstrates complex assembly and coordinate transforms.
///
/// Usage (stdout):
/// moon run --target native examples/28-phone-stand-parametric -- > stand.step
///
/// Usage (write file):
/// moon run --target native examples/28-phone-stand-parametric -- -o stand.step
async fn main {
let base_args_spec = {
"phoneThickness": @cli.opt_double(
12.0,
help="Thickness of the phone/tablet (mm)",
min=0.0,
min_exclusive=true,
),
"viewAngle": @cli.opt_double(
20.0,
help="Viewing angle from vertical (degrees)",
min=0.0,
max=45.0,
),
"width": @cli.opt_double(
70.0,
help="Total width of the stand (mm)",
min=20.0,
),
"height": @cli.opt_double(
120.0,
help="Total height of the back support (mm)",
min=50.0,
),
"thickness": @cli.opt_double(5.0, help="Material thickness (mm)", min=2.0),
}
let args_spec = @cli.with_translate(@common.with_output_args(base_args_spec))
guard @cli.parse(
args_spec,
prog="28-phone-stand-parametric",
description="Generate a parametric phone stand.",
)
is Some(args) else {
return
}
let phone_t = args.double("phoneThickness")
let angle = args.double("viewAngle")
let width = args.double("width")
let height = args.double("height")
let t = args.double("thickness")
let output = @common.output_paths(args)
let (tx, ty, tz) = match args.translate_xyz() {
Some(v) => v
None => {
@cli.eprintln("error: invalid translation")
abort("")
}
}
// Design constants
let lip_h = 20.0
// The back support leans back by `angle` from vertical. Its front face
// meets the base at y = t (1 - sin a) / cos a, so reserve that plus the
// phone thickness and the front lip.
let alpha = angle * @math.PI / 180.0
let sin_a = @math.sin(alpha)
let cos_a = @math.cos(alpha)
let plate_y = t * cos_a // shift the tilted plate into the base
let plate_z = t * sin_a
let base_l = phone_t + t + t * (1.0 - sin_a) / cos_a
// Create side supports. The cuboids start at x = 0 and are t wide, so the
// left one starts at -width/2 and the right one ends at +width/2.
let left_x = -width / 2.0
let right_x = width / 2.0 - t
let left_side = side_support(base_l, lip_h, height, angle, t).map(fn(node) {
node.translate(x=left_x)
})
let right_side = side_support(base_l, lip_h, height, angle, t).map(fn(node) {
node.translate(x=right_x)
})
// Back plate connecting them, leaning backwards from vertical.
let inner_w = width - 2.0 * t
let rot = 90.0 + angle
let back_plate = back_plate(inner_w, height, t, 25.0)
.rotate_x(rot)
.translate(y=plate_y, z=plate_z)
// Front lip plate
let front_lip = @cad.Cuboid::new(inner_w, t, lip_h)
.with_name("front-lip")
.with_color(@cad.Rgb::new(0.1, 0.2, 0.5))
.translate(x=-inner_w / 2.0, y=base_l - t, z=0.0)
// Add branding text on the back plate
let font = @cad.load_font("baloo")
let design = @cad.Design::new(
name="phone-stand",
documentation=args.documentation(),
)
.add_iter(left_side.iter())
.add_iter(right_side.iter())
.add(back_plate)
.add(front_lip)
.add_text_on_face(
back_plate,
"PHONE",
Top, // In ExtrudedProfile, Top is the Z-end face
font,
font_size=10.0,
emboss_depth=2.0,
offset_y=height * 0.2, // Move it up
color=@cad.Rgb::orange(),
)
.translate(x=tx, y=ty, z=tz)
@common.write_output(design, output)
}
///|
fn side_support(
base_l : Double,
lip_h : Double,
back_h : Double,
angle_deg : Double,
t : Double,
) -> Array[@cad.SceneNode] {
let color = @cad.Rgb::new(0.2, 0.4, 0.8)
let alpha = angle_deg * @math.PI / 180.0
let rot = 90.0 + angle_deg
let base = @cad.Cuboid::new(t, base_l, t)
.with_name("side-base")
.with_color(color)
let lip = @cad.Cuboid::new(t, t, lip_h)
.with_name("side-lip")
.with_color(color)
.translate(y=base_l - t)
// Match the back plate: height along local Y, thickness along local Z.
let back = @cad.Cuboid::new(t, back_h, t)
.with_name("side-back")
.with_color(color)
.rotate_x(rot)
.translate(y=t * @math.cos(alpha), z=t * @math.sin(alpha))
[base, lip, back]
}
///|
fn back_plate(
width : Double,
height : Double,
t : Double,
notch_w : Double,
) -> @cad.SceneNode {
let hw = width / 2.0
let nh = height * 0.3
let nw = notch_w
// Back plate with notch merged into outer profile
let outer = [
(-hw, 0.0),
(-nw / 2.0, 0.0),
(-nw / 2.0, nh),
(nw / 2.0, nh),
(nw / 2.0, 0.0),
(hw, 0.0),
(hw, height),
(-hw, height),
]
@cad.ExtrudedProfile::new(outer, t)
.with_name("back-plate")
.with_color(@cad.Rgb::new(0.3, 0.5, 0.9))
}