#![allow(
clippy::disallowed_macros,
reason = "raw-helper: this module calls the platform f32/f64 max/min via crate::raw_minmax::raw_minmax_here!() at sites proven safe by nonzero-const:/both-abs:/int-cast: reasoning, see scripts/raw_minmax_manifest.toml"
)]
#![allow(clippy::similar_names)]
crate::raw_minmax::raw_minmax_here!(max_f32);
use crate::limits::{
checked_positive_finite, checked_product, SpecError, MAX_NODE_EXPANSION, MIN_PITCH_MM,
};
use alice_sdf::SdfNode;
use glam::Vec3;
use std::sync::Arc;
#[inline]
#[allow(clippy::cast_precision_loss)]
const fn n_f32(n: u32) -> f32 {
n as f32
}
const fn rounded_box(hx: f32, hy: f32, hz: f32, r: f32) -> SdfNode {
SdfNode::RoundedBox {
half_extents: Vec3::new(hx, hy, hz),
round_radius: r,
}
}
const fn box3d(hx: f32, hy: f32, hz: f32) -> SdfNode {
SdfNode::Box3d {
half_extents: Vec3::new(hx, hy, hz),
}
}
const fn cylinder(radius: f32, half_height: f32) -> SdfNode {
SdfNode::Cylinder {
radius,
half_height,
}
}
fn cylinder_z(radius: f32, half_height: f32) -> SdfNode {
SdfNode::Rotate {
child: Arc::new(cylinder(radius, half_height)),
rotation: crate::angle::quat_from_rotation_x(std::f32::consts::FRAC_PI_2),
}
}
fn to_z_up(y_up_node: SdfNode) -> SdfNode {
SdfNode::Rotate {
child: Arc::new(y_up_node),
rotation: crate::angle::quat_from_rotation_x(std::f32::consts::FRAC_PI_2),
}
}
fn to_z_up_flipped(y_up_node: SdfNode) -> SdfNode {
SdfNode::Rotate {
child: Arc::new(y_up_node),
rotation: crate::angle::quat_from_rotation_x(-std::f32::consts::FRAC_PI_2),
}
}
fn translate(child: SdfNode, offset: Vec3) -> SdfNode {
SdfNode::Translate {
child: Arc::new(child),
offset,
}
}
fn union(a: SdfNode, b: SdfNode) -> SdfNode {
SdfNode::Union {
a: Arc::new(a),
b: Arc::new(b),
}
}
fn smooth_union(a: SdfNode, b: SdfNode, k: f32) -> SdfNode {
SdfNode::SmoothUnion {
a: Arc::new(a),
b: Arc::new(b),
k,
}
}
fn subtract(a: SdfNode, b: SdfNode) -> SdfNode {
SdfNode::Subtraction {
a: Arc::new(a),
b: Arc::new(b),
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WallHookSpec {
pub hook_width: f32,
pub hook_thickness: f32,
pub hook_depth: f32,
pub hook_opening: f32,
pub hook_throat: f32,
pub backplate_extra_w: f32,
pub backplate_extra_h: f32,
pub backplate_thickness: f32,
pub fillet_radius: f32,
pub mount_hole_dia: Option<f32>,
}
impl WallHookSpec {
#[must_use]
pub const fn pla_1kgf() -> Self {
Self {
hook_width: 10.0,
hook_thickness: 6.0,
hook_depth: 35.0,
hook_opening: 20.0,
hook_throat: 30.0,
backplate_extra_w: 10.0,
backplate_extra_h: 10.0,
backplate_thickness: 6.0,
fillet_radius: 3.0,
mount_hole_dia: Some(4.5),
}
}
}
#[must_use]
pub fn wall_hook(spec: &WallHookSpec) -> SdfNode {
let bp_hw = spec.hook_width + spec.backplate_extra_w;
let bp_hh = spec.hook_throat + spec.hook_depth + spec.backplate_extra_h;
let bp_hx = bp_hw * 0.5;
let bp_hy = bp_hh * 0.5;
let bp_hz = spec.backplate_thickness * 0.5;
let arm_hx = spec.hook_width * 0.5;
let arm_hy = spec.hook_thickness * 0.5;
let arm_hz = spec.hook_depth * 0.5;
let tip_hy = spec.hook_opening * 0.5;
let backplate = rounded_box(bp_hx, bp_hy, bp_hz, spec.fillet_radius);
let arm_placed = translate(
rounded_box(arm_hx, arm_hy, arm_hz, spec.fillet_radius),
Vec3::new(0.0, bp_hy - arm_hy, bp_hz + arm_hz),
);
let tip_placed = translate(
rounded_box(arm_hx, tip_hy, arm_hy, spec.fillet_radius),
Vec3::new(
0.0,
spec.hook_opening.mul_add(-0.5, bp_hy),
bp_hz + spec.hook_depth,
),
);
let bp_arm = smooth_union(backplate, arm_placed, 2.0);
let hook_body = smooth_union(bp_arm, tip_placed, 2.0);
if let Some(hole_dia) = spec.mount_hole_dia {
let hole_r = hole_dia * 0.5;
let hole_half_h = bp_hz + 1.0;
let hole_spacing = bp_hh * 0.3;
let hole = cylinder(hole_r, hole_half_h);
let hole_top = translate(hole.clone(), Vec3::new(0.0, hole_spacing, 0.0));
let hole_bottom = translate(hole, Vec3::new(0.0, -hole_spacing, 0.0));
let holes = union(hole_top, hole_bottom);
subtract(hook_body, holes)
} else {
hook_body
}
}
pub mod gridfinity_spec {
pub const GRID_UNIT: f32 = 42.0;
pub const BIN_CLEARANCE: f32 = 0.25;
pub const HEIGHT_UNIT: f32 = 7.0;
pub const LIP_HEIGHT: f32 = 4.75;
pub const CORNER_FILLET: f32 = 4.0;
pub const INNER_FILLET: f32 = 1.0;
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GridfinitySpec {
pub units_x: u32,
pub units_y: u32,
pub height_u: u32,
pub dividers: Option<(u32, u32)>,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl GridfinitySpec {
#[must_use]
pub const fn default_2x2() -> Self {
Self {
units_x: 2,
units_y: 2,
height_u: 4,
dividers: None,
wall_thickness: 1.2,
floor_thickness: 1.5,
}
}
pub fn validate(&self) -> Result<(), SpecError> {
if let Some((cols, rows)) = self.dividers {
checked_product(cols, rows, MAX_NODE_EXPANSION, "grid_expansion")?;
}
Ok(())
}
}
#[must_use]
pub fn gridfinity_bin(spec: &GridfinitySpec) -> SdfNode {
spec.validate().expect(
"GridfinitySpec が検査を通らない (dividers の積が大きすぎる): \
untrusted な入力は try_gridfinity_bin を使うこと",
);
#[allow(clippy::cast_precision_loss)]
let ext_x = n_f32(spec.units_x) * gridfinity_spec::GRID_UNIT;
#[allow(clippy::cast_precision_loss)]
let ext_y = n_f32(spec.units_y) * gridfinity_spec::GRID_UNIT;
let bin_hx = 2.0f32.mul_add(-gridfinity_spec::BIN_CLEARANCE, ext_x) * 0.5;
let bin_hy = 2.0f32.mul_add(-gridfinity_spec::BIN_CLEARANCE, ext_y) * 0.5;
#[allow(clippy::cast_precision_loss)]
let ext_h =
(n_f32(spec.height_u)).mul_add(gridfinity_spec::HEIGHT_UNIT, gridfinity_spec::LIP_HEIGHT);
let bin_hz = ext_h * 0.5;
let cavity_hz = f32::midpoint(ext_h - spec.floor_thickness, 10.0);
let inner_hx = bin_hx - spec.wall_thickness;
let inner_hy = bin_hy - spec.wall_thickness;
let cavity_offset_z = f32::midpoint(spec.floor_thickness, 10.0);
let outer = rounded_box(bin_hx, bin_hy, bin_hz, gridfinity_spec::CORNER_FILLET);
if let Some((dx, dy)) = spec.dividers {
if dx > 1 && dy > 1 {
#[allow(clippy::cast_precision_loss)]
let cell_w = inner_hx * 2.0 / n_f32(dx);
#[allow(clippy::cast_precision_loss)]
let cell_d = inner_hy * 2.0 / n_f32(dy);
let cell_hx = (cell_w - spec.wall_thickness) * 0.5;
let cell_hy = (cell_d - spec.wall_thickness) * 0.5;
let cx_start = -inner_hx + cell_w * 0.5;
let cy_start = -inner_hy + cell_d * 0.5;
let cavity = rounded_box(cell_hx, cell_hy, cavity_hz, gridfinity_spec::INNER_FILLET);
let mut cavity_list: Vec<SdfNode> = Vec::with_capacity(dx as usize * dy as usize);
for i in 0..dx {
for j in 0..dy {
#[allow(clippy::cast_precision_loss)]
let cx = (n_f32(i)).mul_add(cell_w, cx_start);
#[allow(clippy::cast_precision_loss)]
let cy = (n_f32(j)).mul_add(cell_d, cy_start);
cavity_list.push(translate(
cavity.clone(),
Vec3::new(cx, cy, cavity_offset_z),
));
}
}
let cavities = super::balanced_union_fold(cavity_list);
if let Some(all_cavities) = cavities {
return subtract(outer, all_cavities);
}
}
}
let single_cavity = translate(
rounded_box(inner_hx, inner_hy, cavity_hz, gridfinity_spec::INNER_FILLET),
Vec3::new(0.0, 0.0, cavity_offset_z),
);
subtract(outer, single_cavity)
}
pub fn try_gridfinity_bin(spec: &GridfinitySpec) -> Result<SdfNode, SpecError> {
spec.validate()?;
Ok(gridfinity_bin(spec))
}
#[derive(Debug, Clone, Copy)]
pub struct DrawerSlotSpec {
pub width: f32,
pub min_depth: f32,
pub count: u32,
}
#[derive(Debug, Clone)]
pub struct DrawerSpec {
pub width: f32,
pub depth: f32,
pub height: f32,
pub slots: Vec<DrawerSlotSpec>,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub divider_thickness: f32,
pub fillet_radius: f32,
}
impl DrawerSpec {
#[must_use]
pub fn default_chopsticks_set() -> Self {
Self {
width: 250.0,
depth: 200.0,
height: 40.0,
slots: vec![
DrawerSlotSpec {
width: 14.0,
min_depth: 260.0,
count: 2,
},
DrawerSlotSpec {
width: 32.0,
min_depth: 220.0,
count: 4,
},
DrawerSlotSpec {
width: 28.0,
min_depth: 250.0,
count: 4,
},
],
wall_thickness: 1.5,
floor_thickness: 1.5,
divider_thickness: 1.2,
fillet_radius: 1.0,
}
}
}
#[must_use]
pub fn drawer_organizer(spec: &DrawerSpec) -> SdfNode {
let tray_hx = spec.width * 0.5;
let tray_hy = spec.depth * 0.5;
let tray_hz = spec.height * 0.5;
let inner_width = 2.0f32.mul_add(-spec.wall_thickness, spec.width);
let neg_inner_width = 2.0f32.mul_add(spec.wall_thickness, -spec.width);
let inner_hz = (spec.height - spec.floor_thickness) * 0.5;
let outer = rounded_box(tray_hx, tray_hy, tray_hz, spec.fillet_radius);
let total_slots: u32 = spec.slots.iter().map(|s| s.count).sum();
#[allow(clippy::cast_precision_loss)]
let total_slot_width: f32 = spec
.slots
.iter()
.map(|s| s.width * n_f32(s.count))
.sum::<f32>();
#[allow(clippy::cast_precision_loss)]
let total_divider_width = spec.divider_thickness * total_slots.saturating_sub(1) as f32;
let scale = if total_slot_width + total_divider_width > inner_width {
(inner_width - total_divider_width) / total_slot_width
} else {
1.0
};
let mut cavities: Option<SdfNode> = None;
let mut x_cursor = neg_inner_width * 0.5;
let cavity_z_offset = spec.floor_thickness * 0.5;
for slot in &spec.slots {
let sw = slot.width * scale;
let sd = crate::det_math::min(
slot.min_depth,
2.0f32.mul_add(-spec.wall_thickness, spec.depth),
);
let sd_half = sd * 0.5;
let sw_half = sw * 0.5 - 0.5; for i in 0..slot.count {
let slot_cx = x_cursor + sw * 0.5;
let cav = translate(
rounded_box(sw_half, sd_half, inner_hz, spec.fillet_radius),
Vec3::new(slot_cx, 0.0, cavity_z_offset),
);
cavities = Some(match cavities {
Some(prev) => union(prev, cav),
None => cav,
});
x_cursor += sw;
if i < slot.count - 1 || slot.count > 1 {
x_cursor += spec.divider_thickness;
}
}
if x_cursor < inner_width * 0.5 {
x_cursor += spec.divider_thickness;
}
}
match cavities {
Some(c) => subtract(outer, c),
None => outer,
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ShelfDividerSpec {
pub total_width: f32,
pub depth: f32,
pub height: f32,
pub thickness: f32,
pub hex_hole_radius: f32,
pub hex_hole_pitch: f32,
pub hex_border: f32,
}
impl ShelfDividerSpec {
#[must_use]
pub const fn field_tested_560x250x120() -> Self {
Self {
total_width: 560.0,
depth: 250.0,
height: 120.0,
thickness: 5.0,
hex_hole_radius: 7.5,
hex_hole_pitch: 20.0,
hex_border: 15.0,
}
}
pub fn validate(&self) -> Result<(), SpecError> {
checked_positive_finite(self.hex_hole_pitch, MIN_PITCH_MM, "pitch")?;
Ok(())
}
}
#[must_use]
pub fn shelf_divider(spec: &ShelfDividerSpec) -> SdfNode {
spec.validate().expect(
"ShelfDividerSpec が検査を通らない (hex_hole_pitch が退化している): untrusted な入力は try_shelf_divider を使うこと",
);
let half_width = spec.total_width * 0.5;
let hx = half_width * 0.5;
let hy = spec.depth * 0.5;
let t = spec.thickness * 0.5;
let plate_z = t;
let wall_height = spec.height - spec.thickness;
let wall_hz = wall_height * 0.5;
let wall_cz = spec.thickness + wall_hz;
let side_left_x = -(hx - t);
let side_right_x = hx - t;
let fillet_k = spec.thickness * 0.5;
let plate = translate(box3d(hx, hy, t), Vec3::new(0.0, 0.0, plate_z));
let wall_l = translate(box3d(t, hy, wall_hz), Vec3::new(side_left_x, 0.0, wall_cz));
let wall_r = translate(box3d(t, hy, wall_hz), Vec3::new(side_right_x, 0.0, wall_cz));
let structure = smooth_union(smooth_union(plate, wall_l, fillet_k), wall_r, fillet_k);
let grid_hx = hx - spec.hex_border;
let grid_hy = hy - spec.hex_border;
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let count_x = (grid_hx / spec.hex_hole_pitch).floor() as u32;
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let count_y = (grid_hy / spec.hex_hole_pitch).floor() as u32;
let stagger_x = spec.hex_hole_pitch * 0.5;
let stagger_y = spec.hex_hole_pitch * 0.866;
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let count_x2 = ((grid_hx - stagger_x) / spec.hex_hole_pitch).floor() as u32;
#[allow(clippy::cast_possible_truncation)]
#[allow(clippy::cast_sign_loss)]
let count_y2 = ((grid_hy - stagger_y * 0.5) / stagger_y).floor() as u32;
let hole_half_h = t + 1.0;
let hole_shape = cylinder(spec.hex_hole_radius, hole_half_h);
let grid1 = SdfNode::RepeatFinite {
child: Arc::new(hole_shape.clone()),
count: [count_x, count_y, 0],
spacing: Vec3::new(spec.hex_hole_pitch, spec.hex_hole_pitch, 1.0),
};
let grid1_placed = translate(grid1, Vec3::new(0.0, 0.0, plate_z));
let grid2 = SdfNode::RepeatFinite {
child: Arc::new(hole_shape),
count: [count_x2, count_y2, 0],
spacing: Vec3::new(spec.hex_hole_pitch, stagger_y, 1.0),
};
let grid2_placed = translate(grid2, Vec3::new(stagger_x, stagger_y * 0.5, plate_z));
let all_holes = union(grid1_placed, grid2_placed);
subtract(structure, all_holes)
}
pub fn try_shelf_divider(spec: &ShelfDividerSpec) -> Result<SdfNode, SpecError> {
spec.validate()?;
Ok(shelf_divider(spec))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StickyNoteHolderSpec {
pub pad_width: f32,
pub pad_depth: f32,
pub height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl StickyNoteHolderSpec {
#[must_use]
pub const fn small_square() -> Self {
Self {
pad_width: 76.0,
pad_depth: 76.0,
height: 30.0,
wall_thickness: 1.5,
floor_thickness: 1.5,
}
}
}
#[must_use]
pub fn sticky_note_holder(spec: &StickyNoteHolderSpec) -> SdfNode {
let outer_hx = 2.0f32.mul_add(spec.wall_thickness, spec.pad_width) * 0.5;
let outer_hy = 2.0f32.mul_add(spec.wall_thickness, spec.pad_depth) * 0.5;
let outer_hz = spec.height * 0.5;
let inner_hx = spec.pad_width * 0.5;
let inner_hy = spec.pad_depth * 0.5;
let cavity_h = spec.height - spec.floor_thickness;
let inner_hz = cavity_h * 0.5;
let cavity_offset_z = spec.floor_thickness * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let cavity = translate(
box3d(inner_hx, inner_hy, inner_hz),
Vec3::new(0.0, 0.0, cavity_offset_z),
);
subtract(outer, cavity)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BusinessCardHolderSpec {
pub card_width: f32,
pub card_height: f32,
pub slot_thickness: f32,
pub slot_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl BusinessCardHolderSpec {
#[must_use]
pub const fn jp_meishi() -> Self {
Self {
card_width: 91.0,
card_height: 55.0,
slot_thickness: 22.0,
slot_depth: 32.0,
wall_thickness: 1.5,
floor_thickness: 2.0,
}
}
}
#[must_use]
pub fn business_card_holder(spec: &BusinessCardHolderSpec) -> SdfNode {
let outer_hx = 2.0f32.mul_add(spec.wall_thickness, spec.card_width) * 0.5;
let outer_hy = 2.0f32.mul_add(spec.wall_thickness, spec.slot_thickness) * 0.5;
let outer_hz = spec.slot_depth * 0.5;
let inner_hx = spec.card_width * 0.5;
let inner_hy = spec.slot_thickness * 0.5;
let cavity_h = spec.slot_depth - spec.floor_thickness;
let inner_hz = cavity_h * 0.5;
let cavity_offset_z = spec.floor_thickness * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let cavity = translate(
box3d(inner_hx, inner_hy, inner_hz),
Vec3::new(0.0, 0.0, cavity_offset_z),
);
subtract(outer, cavity)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PenCupSpec {
pub inner_diameter: f32,
pub height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl PenCupSpec {
#[must_use]
pub const fn standard_75x100() -> Self {
Self {
inner_diameter: 75.0,
height: 100.0,
wall_thickness: 2.0,
floor_thickness: 2.0,
}
}
}
#[must_use]
pub fn pen_cup(spec: &PenCupSpec) -> SdfNode {
let outer_r = 2.0f32.mul_add(spec.wall_thickness, spec.inner_diameter) * 0.5;
let outer_hz = spec.height * 0.5;
let inner_r = spec.inner_diameter * 0.5;
let cavity_h = spec.height - spec.floor_thickness;
let inner_hz = cavity_h * 0.5;
let cavity_offset_z = spec.floor_thickness * 0.5;
let outer = cylinder_z(outer_r, outer_hz);
let cavity = translate(
cylinder_z(inner_r, inner_hz),
Vec3::new(0.0, 0.0, cavity_offset_z),
);
subtract(outer, cavity)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PhoneStandSpec {
pub slot_width: f32,
pub slot_depth: f32,
pub base_width: f32,
pub base_depth: f32,
pub base_thickness: f32,
pub back_height: f32,
pub back_thickness: f32,
pub cable_hole_dia: Option<f32>,
}
impl PhoneStandSpec {
#[must_use]
pub const fn phone_default() -> Self {
Self {
slot_width: 14.0,
slot_depth: 6.0,
base_width: 90.0,
base_depth: 90.0,
base_thickness: 6.0,
back_height: 100.0,
back_thickness: 5.0,
cable_hole_dia: Some(18.0),
}
}
}
#[must_use]
pub fn phone_stand(spec: &PhoneStandSpec) -> SdfNode {
let base_hx = spec.base_width * 0.5;
let base_hy = spec.base_depth * 0.5;
let base_hz = spec.base_thickness * 0.5;
let back_hx = spec.base_width * 0.5;
let back_hy = spec.back_thickness * 0.5;
let back_hz = spec.back_height * 0.5;
let base = translate(
box3d(base_hx, base_hy, base_hz),
Vec3::new(0.0, 0.0, base_hz),
);
let back_plate_y = -(base_hy - back_hy);
let back_plate_z = spec.base_thickness + back_hz;
let back = translate(
box3d(back_hx, back_hy, back_hz),
Vec3::new(0.0, back_plate_y, back_plate_z),
);
let slot_hx = spec.slot_width * 0.5;
let slot_hy = back_hy + 0.5; let slot_hz = spec.back_height * 0.4; let slot_center_z = back_plate_z + back_hz - slot_hz;
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(0.0, back_plate_y, slot_center_z),
);
let structure = union(base, back);
let with_slot = subtract(structure, slot);
if let Some(dia) = spec.cable_hole_dia {
let hole = translate(
cylinder_z(dia * 0.5, base_hz + 1.0),
Vec3::new(0.0, 0.0, base_hz),
);
subtract(with_slot, hole)
} else {
with_slot
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HeadphoneHolderSpec {
pub arm_length: f32,
pub arm_thickness: f32,
pub arm_width: f32,
pub mount_width: f32,
pub mount_height: f32,
pub mount_thickness: f32,
pub hook_tip_up: f32,
pub mount_hole_dia: Option<f32>,
}
impl HeadphoneHolderSpec {
#[must_use]
pub const fn wall_mount_default() -> Self {
Self {
arm_length: 80.0,
arm_thickness: 6.0,
arm_width: 50.0,
mount_width: 100.0,
mount_height: 68.0,
mount_thickness: 6.0,
hook_tip_up: 18.0,
mount_hole_dia: Some(4.5),
}
}
}
#[must_use]
pub fn headphone_holder(spec: &HeadphoneHolderSpec) -> SdfNode {
let mp_hx = spec.mount_width * 0.5;
let mp_hy = spec.mount_height * 0.5;
let mp_hz = spec.mount_thickness * 0.5;
let arm_hx = spec.arm_width * 0.5;
let arm_hy = spec.arm_thickness * 0.5;
let arm_hz = spec.arm_length * 0.5;
let tip_hy = spec.hook_tip_up * 0.5;
let mount = rounded_box(mp_hx, mp_hy, mp_hz, 3.0);
let arm = translate(
rounded_box(arm_hx, arm_hy, arm_hz, 2.0),
Vec3::new(0.0, 0.0, mp_hz + arm_hz),
);
let tip = translate(
rounded_box(arm_hx, tip_hy, arm_hy, 2.0),
Vec3::new(0.0, arm_hy + tip_hy, mp_hz + spec.arm_length - arm_hy),
);
let body = smooth_union(smooth_union(mount, arm, 3.0), tip, 3.0);
if let Some(dia) = spec.mount_hole_dia {
let hole_hx = dia * 0.5;
let hole_hy = dia * 0.5;
let hole_hz = mp_hz + 0.5;
let hole_spacing = mp_hy * 0.6;
let hole_top = translate(
box3d(hole_hx, hole_hy, hole_hz),
Vec3::new(0.0, hole_spacing, 0.0),
);
let hole_bot = translate(
box3d(hole_hx, hole_hy, hole_hz),
Vec3::new(0.0, -hole_spacing, 0.0),
);
subtract(body, union(hole_top, hole_bot))
} else {
body
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UnderDeskMountSpec {
pub desk_thickness: f32,
pub clamp_width: f32,
pub clamp_depth: f32,
pub clamp_wall_thickness: f32,
pub screw_hole_dia: Option<f32>,
}
impl UnderDeskMountSpec {
#[must_use]
pub const fn standard_desk() -> Self {
Self {
desk_thickness: 25.0,
clamp_width: 40.0,
clamp_depth: 50.0,
clamp_wall_thickness: 4.0,
screw_hole_dia: Some(4.0),
}
}
}
#[must_use]
pub fn under_desk_mount(spec: &UnderDeskMountSpec) -> SdfNode {
let jaw_hx = spec.clamp_width * 0.5;
let jaw_hy = spec.clamp_wall_thickness * 0.5;
let jaw_hz = spec.clamp_depth * 0.5;
let top_y = spec.desk_thickness.mul_add(0.5, jaw_hy);
let bottom_y = (-spec.desk_thickness).mul_add(0.5, -jaw_hy);
let stem_hx = spec.clamp_width * 0.5;
let stem_hy = 2.0f32.mul_add(spec.clamp_wall_thickness, spec.desk_thickness) * 0.5;
let stem_hz = spec.clamp_wall_thickness * 0.5;
let back_z = -(jaw_hz - stem_hz);
let top_jaw = translate(box3d(jaw_hx, jaw_hy, jaw_hz), Vec3::new(0.0, top_y, 0.0));
let bottom_jaw = translate(box3d(jaw_hx, jaw_hy, jaw_hz), Vec3::new(0.0, bottom_y, 0.0));
let back_stem = translate(
box3d(stem_hx, stem_hy, stem_hz),
Vec3::new(0.0, 0.0, back_z),
);
let body = smooth_union(smooth_union(top_jaw, bottom_jaw, 2.0), back_stem, 2.0);
if let Some(dia) = spec.screw_hole_dia {
let hole = translate(
cylinder(dia * 0.5, jaw_hy + 0.5),
Vec3::new(0.0, bottom_y, 0.0),
);
subtract(body, hole)
} else {
body
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DeskShelfSpec {
pub shelf_width: f32,
pub shelf_depth: f32,
pub shelf_thickness: f32,
pub leg_height: f32,
pub leg_thickness: f32,
}
impl DeskShelfSpec {
#[must_use]
pub const fn desktop_400x200() -> Self {
Self {
shelf_width: 400.0,
shelf_depth: 200.0,
shelf_thickness: 5.0,
leg_height: 100.0,
leg_thickness: 20.0,
}
}
}
#[must_use]
pub fn desk_shelf(spec: &DeskShelfSpec) -> SdfNode {
let shelf_hx = spec.shelf_width * 0.5;
let shelf_hy = spec.shelf_thickness * 0.5;
let shelf_hz = spec.shelf_depth * 0.5;
let leg_hx = spec.leg_thickness * 0.5;
let leg_hy = spec.leg_height * 0.5;
let leg_hz = spec.shelf_depth * 0.5;
let shelf_y = spec.leg_height + shelf_hy;
let leg_x = shelf_hx - leg_hx;
let shelf = translate(
box3d(shelf_hx, shelf_hy, shelf_hz),
Vec3::new(0.0, shelf_y, 0.0),
);
let leg_l = translate(
box3d(leg_hx, leg_hy, leg_hz),
Vec3::new(-leg_x, leg_hy, 0.0),
);
let leg_r = translate(box3d(leg_hx, leg_hy, leg_hz), Vec3::new(leg_x, leg_hy, 0.0));
let fillet_k = spec.shelf_thickness * 0.5;
to_z_up_flipped(smooth_union(
smooth_union(shelf, leg_l, fillet_k),
leg_r,
fillet_k,
))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MonitorRiserSpec {
pub width: f32,
pub depth: f32,
pub height: f32,
pub platform_thickness: f32,
pub leg_thickness: f32,
pub cable_hole_dia: Option<f32>,
}
impl MonitorRiserSpec {
#[must_use]
pub const fn compact_desk() -> Self {
Self {
width: 250.0,
depth: 180.0,
height: 90.0,
platform_thickness: 8.0,
leg_thickness: 25.0,
cable_hole_dia: Some(40.0),
}
}
}
#[must_use]
pub fn monitor_riser(spec: &MonitorRiserSpec) -> SdfNode {
let plat_hx = spec.width * 0.5;
let plat_hy = spec.depth * 0.5;
let plat_hz = spec.platform_thickness * 0.5;
let leg_hx = spec.leg_thickness * 0.5;
let leg_hy = spec.depth * 0.45; let leg_h = spec.height - spec.platform_thickness;
let leg_hz = leg_h * 0.5;
let plat_z = leg_h + plat_hz;
let leg_x = plat_hx - leg_hx;
let platform = translate(
rounded_box(plat_hx, plat_hy, plat_hz, 4.0),
Vec3::new(0.0, 0.0, plat_z),
);
let leg_l = translate(
box3d(leg_hx, leg_hy, leg_hz),
Vec3::new(-leg_x, 0.0, leg_hz),
);
let leg_r = translate(box3d(leg_hx, leg_hy, leg_hz), Vec3::new(leg_x, 0.0, leg_hz));
let structure = smooth_union(smooth_union(platform, leg_l, 2.0), leg_r, 2.0);
if let Some(dia) = spec.cable_hole_dia {
let hole = translate(
cylinder_z(dia * 0.5, plat_hz + 0.5),
Vec3::new(0.0, 0.0, plat_z),
);
subtract(structure, hole)
} else {
structure
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CoasterSpec {
pub diameter: f32,
pub thickness: f32,
pub lip_width: f32,
pub lip_height: f32,
}
impl CoasterSpec {
#[must_use]
pub const fn round_95x5() -> Self {
Self {
diameter: 95.0,
thickness: 5.0,
lip_width: 2.5,
lip_height: 1.5,
}
}
}
#[must_use]
pub fn coaster(spec: &CoasterSpec) -> SdfNode {
let outer_r = spec.diameter * 0.5;
let outer_hz = spec.thickness * 0.5;
let inner_r = outer_r - spec.lip_width;
let recess_hz = spec.lip_height * 0.5;
let recess_offset_z = outer_hz - recess_hz;
let base = cylinder_z(outer_r, outer_hz);
let recess = translate(
cylinder_z(inner_r, recess_hz + 0.5),
Vec3::new(0.0, 0.0, recess_offset_z + 0.25),
);
subtract(base, recess)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TissueBoxCoverSpec {
pub internal_length: f32,
pub internal_width: f32,
pub internal_height: f32,
pub wall_thickness: f32,
pub slot_length: f32,
pub slot_width: f32,
}
impl TissueBoxCoverSpec {
#[must_use]
pub const fn rectangular_us() -> Self {
Self {
internal_length: 231.0,
internal_width: 116.0,
internal_height: 53.0,
wall_thickness: 1.6,
slot_length: 80.0,
slot_width: 30.0,
}
}
}
#[must_use]
pub fn tissue_box_cover(spec: &TissueBoxCoverSpec) -> SdfNode {
let ext_l = 2.0f32.mul_add(spec.wall_thickness, spec.internal_length);
let ext_w = 2.0f32.mul_add(spec.wall_thickness, spec.internal_width);
let ext_h = spec.internal_height + spec.wall_thickness;
let outer_hx = ext_l * 0.5;
let outer_hy = ext_h * 0.5;
let outer_hz = ext_w * 0.5;
let cavity_hx = spec.internal_length * 0.5;
let cavity_hy = (spec.internal_height + spec.wall_thickness + 10.0) * 0.5;
let cavity_hz = spec.internal_width * 0.5;
let cavity_offset_y = -(spec.wall_thickness + 0.5) * 0.5;
let slot_hx = spec.slot_length * 0.5;
let slot_hy = f32::midpoint(spec.wall_thickness, 10.0);
let slot_hz = spec.slot_width * 0.5;
let slot_offset_y = outer_hy - slot_hy + 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(0.0, slot_offset_y, 0.0),
);
to_z_up_flipped(subtract(subtract(outer, cavity), slot))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct StorageBoxSpec {
pub internal_length: f32,
pub internal_width: f32,
pub internal_height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl StorageBoxSpec {
#[must_use]
pub const fn medium() -> Self {
Self {
internal_length: 150.0,
internal_width: 100.0,
internal_height: 60.0,
wall_thickness: 2.0,
floor_thickness: 2.0,
}
}
}
#[must_use]
pub fn storage_box(spec: &StorageBoxSpec) -> SdfNode {
let ext_l = 2.0f32.mul_add(spec.wall_thickness, spec.internal_length);
let ext_w = 2.0f32.mul_add(spec.wall_thickness, spec.internal_width);
let ext_h = spec.internal_height + spec.floor_thickness;
let outer_hx = ext_l * 0.5;
let outer_hy = ext_h * 0.5;
let outer_hz = ext_w * 0.5;
let cavity_hx = spec.internal_length * 0.5;
let cavity_hy = f32::midpoint(spec.internal_height, 10.0);
let cavity_hz = spec.internal_width * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
to_z_up(subtract(outer, cavity))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CableClipSpec {
pub cable_diameter: f32,
pub clip_length: f32,
pub wall_thickness: f32,
pub opening_ratio: f32,
}
impl CableClipSpec {
#[must_use]
pub const fn hdmi() -> Self {
Self {
cable_diameter: 7.0,
clip_length: 28.0,
wall_thickness: 2.0,
opening_ratio: 0.7,
}
}
}
#[must_use]
pub fn cable_clip(spec: &CableClipSpec) -> SdfNode {
let outer_side = 2.0f32.mul_add(spec.wall_thickness, spec.cable_diameter);
let outer_hx = outer_side * 0.5;
let outer_hy = spec.clip_length * 0.5;
let outer_hz = outer_side * 0.5;
let cavity_r = spec.cable_diameter.mul_add(0.5, 0.1);
let cavity_hy = outer_hy + 1.0;
let slot_hx = spec.cable_diameter * spec.opening_ratio * 0.5;
let slot_hy = outer_hy + 1.0;
let slot_top_z = outer_hz + 1.0;
let slot_bottom_z = -spec.cable_diameter * 0.15;
let slot_hz = (slot_top_z - slot_bottom_z) * 0.5;
let slot_offset_z = f32::midpoint(slot_top_z, slot_bottom_z);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness * 0.5);
let cavity = cylinder(cavity_r, cavity_hy);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(0.0, 0.0, slot_offset_z),
);
subtract(subtract(outer, cavity), slot)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LedChannelSpec {
pub strip_width: f32,
pub channel_length: f32,
pub channel_depth: f32,
pub wall_thickness: f32,
}
impl LedChannelSpec {
#[must_use]
pub const fn ws2812b_10mm() -> Self {
Self {
strip_width: 10.0,
channel_length: 300.0,
channel_depth: 2.5,
wall_thickness: 2.0,
}
}
}
#[must_use]
pub fn led_channel(spec: &LedChannelSpec) -> SdfNode {
let inner_w = spec.strip_width + 1.0; let outer_w = 2.0f32.mul_add(spec.wall_thickness, spec.strip_width);
let floor = 1.5;
let outer_h = spec.channel_depth + floor;
let outer_hx = outer_w * 0.5;
let outer_hy = spec.channel_length * 0.5;
let outer_hz = outer_h * 0.5;
let cavity_hx = inner_w * 0.5;
let cavity_hy = outer_hy + 1.0;
let cavity_hz = f32::midpoint(spec.channel_depth, 1.0);
let cavity_offset_z = outer_hz - cavity_hz + 0.5;
let outer = box3d(outer_hx, outer_hy, outer_hz);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, 0.0, cavity_offset_z),
);
subtract(outer, cavity)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CardTraySpec {
pub card_width: f32,
pub card_height: f32,
pub tray_depth: f32,
pub card_clearance: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub finger_notch_radius: f32,
}
impl CardTraySpec {
#[must_use]
pub const fn poker() -> Self {
Self {
card_width: 63.0,
card_height: 88.0,
tray_depth: 30.0,
card_clearance: 1.0,
wall_thickness: 2.0,
floor_thickness: 1.5,
finger_notch_radius: 9.0,
}
}
}
#[must_use]
pub fn card_tray(spec: &CardTraySpec) -> SdfNode {
let ext_x = 2.0f32.mul_add(spec.card_clearance + spec.wall_thickness, spec.card_width);
let ext_z = 2.0f32.mul_add(spec.card_clearance + spec.wall_thickness, spec.card_height);
let ext_y = spec.tray_depth + spec.floor_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = 2.0f32.mul_add(spec.card_clearance, spec.card_width) * 0.5;
let cavity_hy = f32::midpoint(spec.tray_depth, 10.0);
let cavity_hz = 2.0f32.mul_add(spec.card_clearance, spec.card_height) * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let notch_hy = outer_hy + 1.0;
let notch_offset_z = -outer_hz;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let notch = translate(
cylinder(spec.finger_notch_radius, notch_hy),
Vec3::new(0.0, 0.0, notch_offset_z),
);
to_z_up(subtract(subtract(outer, cavity), notch))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TokenWellSpec {
pub well_diameter: f32,
pub well_depth: f32,
pub well_count: u32,
pub well_clearance: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl TokenWellSpec {
#[must_use]
pub const fn dice_4() -> Self {
Self {
well_diameter: 20.0,
well_depth: 20.0,
well_count: 4,
well_clearance: 1.0,
wall_thickness: 2.0,
floor_thickness: 1.5,
}
}
}
#[must_use]
pub fn token_well(spec: &TokenWellSpec) -> SdfNode {
let count = spec.well_count.max(1);
let count_f = n_f32(count);
let pitch = 2.0f32.mul_add(spec.well_clearance, spec.well_diameter);
let ext_x = 2.0f32.mul_add(spec.wall_thickness, count_f * pitch);
let ext_z = 2.0f32.mul_add(
spec.well_clearance + spec.wall_thickness,
spec.well_diameter,
);
let ext_y = spec.well_depth + spec.floor_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let well_r = spec.well_diameter * 0.5;
let well_hy = f32::midpoint(spec.well_depth, 10.0);
let well_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let well = translate(cylinder(well_r, well_hy), Vec3::new(x, well_offset_y, 0.0));
cutters.push(well);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WrenchHolderSpec {
pub min_size_mm: f32,
pub max_size_mm: f32,
pub count: u32,
pub slot_depth: f32,
pub slot_clearance: f32,
pub thickness_ratio: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl WrenchHolderSpec {
#[must_use]
pub const fn metric_6_8to19() -> Self {
Self {
min_size_mm: 8.0,
max_size_mm: 19.0,
count: 6,
slot_depth: 22.0,
slot_clearance: 0.6,
thickness_ratio: 0.5,
wall_thickness: 3.0,
floor_thickness: 4.0,
}
}
}
#[must_use]
pub fn wrench_holder(spec: &WrenchHolderSpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let max_slot_w = 2.0f32.mul_add(spec.slot_clearance, spec.max_size_mm);
let pitch = max_slot_w + 3.0; let max_thickness =
2.0f32.mul_add(spec.slot_clearance, spec.max_size_mm * spec.thickness_ratio);
let ext_x = 2.0f32.mul_add(spec.wall_thickness, count_f * pitch);
let ext_y = spec.slot_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, max_thickness);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let x_start = -(count_f - 1.0) * pitch * 0.5;
let slot_hy = f32::midpoint(spec.slot_depth, 10.0);
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let t = if count == 1 {
0.0
} else {
n_f32(i) / (count_f - 1.0)
};
let size = spec.min_size_mm + t * (spec.max_size_mm - spec.min_size_mm);
let slot_w = 2.0f32.mul_add(spec.slot_clearance, size);
let slot_thick = 2.0f32.mul_add(spec.slot_clearance, size * spec.thickness_ratio);
let x = (n_f32(i)).mul_add(pitch, x_start);
let slot = translate(
box3d(slot_w * 0.5, slot_hy, slot_thick * 0.5),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SocketRailSpec {
pub post_diameter: f32,
pub post_height: f32,
pub post_count: u32,
pub post_spacing: f32,
pub base_thickness: f32,
pub base_margin: f32,
}
impl SocketRailSpec {
#[must_use]
pub const fn half_inch_6() -> Self {
Self {
post_diameter: 12.4,
post_height: 22.0,
post_count: 6,
post_spacing: 6.0,
base_thickness: 4.0,
base_margin: 3.0,
}
}
}
#[must_use]
pub fn socket_rail(spec: &SocketRailSpec) -> SdfNode {
let count = spec.post_count.max(1);
let count_f = n_f32(count);
let pitch = spec.post_diameter + spec.post_spacing;
let ext_x = 2.0f32.mul_add(spec.base_margin, count_f * pitch);
let ext_y = spec.base_thickness;
let ext_z = 2.0f32.mul_add(spec.base_margin, spec.post_diameter);
let base_hx = ext_x * 0.5;
let base_hy = ext_y * 0.5;
let base_hz = ext_z * 0.5;
let post_r = spec.post_diameter * 0.5;
let post_hy = spec.post_height * 0.5;
let post_offset_y = base_hy + post_hy - 0.1; let x_start = -(count_f - 1.0) * pitch * 0.5;
let base = rounded_box(base_hx, base_hy, base_hz, spec.base_margin);
let mut result = base;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let post = translate(cylinder(post_r, post_hy), Vec3::new(x, post_offset_y, 0.0));
cutters.push(post);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = union(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HexBitHolderSpec {
pub rows: u32,
pub cols: u32,
pub spacing: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl HexBitHolderSpec {
#[must_use]
pub const fn grid_4x5() -> Self {
Self {
rows: 5,
cols: 4,
spacing: 12.0,
wall_thickness: 2.0,
floor_thickness: 2.0,
}
}
}
const HEX_BIT_ACROSS_FLATS: f32 = 6.85;
const HEX_BIT_HOLE_DEPTH: f32 = 14.0;
#[must_use]
pub fn hex_bit_holder(spec: &HexBitHolderSpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let ext_x = 2.0f32.mul_add(spec.wall_thickness, cols_f * spec.spacing);
let ext_y = 2.0f32.mul_add(spec.wall_thickness, rows_f * spec.spacing);
let ext_z = HEX_BIT_HOLE_DEPTH + spec.floor_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let hex_r = HEX_BIT_ACROSS_FLATS * 0.5;
let hex_half_h = f32::midpoint(HEX_BIT_HOLE_DEPTH, 10.0);
let hex_offset_z = outer_hz - hex_half_h + 0.5;
let x_start = -(cols_f - 1.0) * spec.spacing * 0.5;
let y_start = -(rows_f - 1.0) * spec.spacing * 0.5;
let outer = box3d(outer_hx, outer_hy, outer_hz);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(spec.spacing, x_start);
let y = (n_f32(r)).mul_add(spec.spacing, y_start);
let hex = translate(
SdfNode::HexPrism {
hex_radius: hex_r,
half_height: hex_half_h,
},
Vec3::new(x, y, hex_offset_z),
);
cutters.push(hex);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
result
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RaspiCaseSpec {
pub pcb_width: f32,
pub pcb_depth: f32,
pub internal_height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub pcb_clearance: f32,
pub standoff_diameter: f32,
pub standoff_height: f32,
pub standoff_pilot_diameter: f32,
pub standoff_inset: f32,
pub port_opening_width: f32,
}
impl RaspiCaseSpec {
#[must_use]
pub const fn rpi5_active_cooler() -> Self {
Self {
pcb_width: 85.0,
pcb_depth: 56.0,
internal_height: 25.0,
wall_thickness: 2.0,
floor_thickness: 3.0,
pcb_clearance: 1.0,
standoff_diameter: 6.0,
standoff_height: 5.0,
standoff_pilot_diameter: 2.2,
standoff_inset: 3.5,
port_opening_width: 60.0,
}
}
}
#[must_use]
pub fn raspi_case(spec: &RaspiCaseSpec) -> SdfNode {
let ext_x = 2.0f32.mul_add(spec.pcb_clearance + spec.wall_thickness, spec.pcb_width);
let ext_z = 2.0f32.mul_add(spec.pcb_clearance + spec.wall_thickness, spec.pcb_depth);
let ext_y = spec.internal_height + spec.floor_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = 2.0f32.mul_add(spec.pcb_clearance, spec.pcb_width) * 0.5;
let cavity_hy = f32::midpoint(spec.internal_height, 10.0);
let cavity_hz = 2.0f32.mul_add(spec.pcb_clearance, spec.pcb_depth) * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let standoff_r = spec.standoff_diameter * 0.5;
let standoff_hy = spec.standoff_height * 0.5;
let standoff_offset_y = -outer_hy + spec.floor_thickness + standoff_hy;
let sx = spec.pcb_width.mul_add(0.5, -spec.standoff_inset);
let sz = spec.pcb_depth.mul_add(0.5, -spec.standoff_inset);
let neg_sx = spec.pcb_width.mul_add(-0.5, spec.standoff_inset);
let neg_sz = spec.pcb_depth.mul_add(-0.5, spec.standoff_inset);
let pilot_r = spec.standoff_pilot_diameter * 0.5;
let pilot_hy = spec.standoff_height * 0.75;
let pilot_offset_y = standoff_offset_y + standoff_hy - pilot_hy + 0.5;
let port_hx = spec.port_opening_width * 0.5;
let port_hy = spec.internal_height * 0.5;
let port_hz = f32::midpoint(spec.wall_thickness, 2.0);
let port_offset_y = spec.floor_thickness.mul_add(0.5, spec.pcb_clearance) + port_hy * 0.5;
let port_offset_z = -outer_hz + port_hz - 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let mut result = subtract(outer, cavity);
for (dx, dz) in [(neg_sx, neg_sz), (sx, neg_sz), (neg_sx, sz), (sx, sz)] {
let peg = translate(
cylinder(standoff_r, standoff_hy),
Vec3::new(dx, standoff_offset_y, dz),
);
result = union(result, peg);
let pilot = translate(
cylinder(pilot_r, pilot_hy),
Vec3::new(dx, pilot_offset_y, dz),
);
result = subtract(result, pilot);
}
let port = translate(
box3d(port_hx, port_hy, port_hz),
Vec3::new(0.0, port_offset_y, port_offset_z),
);
result = subtract(result, port);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Esp32EnclosureSpec {
pub pcb_width: f32,
pub pcb_depth: f32,
pub internal_height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub pcb_clearance: f32,
pub usb_opening_width: f32,
pub usb_opening_height: f32,
}
impl Esp32EnclosureSpec {
#[must_use]
pub const fn esp32_devkit_v1() -> Self {
Self {
pcb_width: 51.6,
pcb_depth: 28.4,
internal_height: 15.0,
wall_thickness: 1.6,
floor_thickness: 2.0,
pcb_clearance: 0.5,
usb_opening_width: 9.0,
usb_opening_height: 5.0,
}
}
}
#[must_use]
pub fn esp32_enclosure(spec: &Esp32EnclosureSpec) -> SdfNode {
let ext_x = 2.0f32.mul_add(spec.pcb_clearance + spec.wall_thickness, spec.pcb_width);
let ext_z = 2.0f32.mul_add(spec.pcb_clearance + spec.wall_thickness, spec.pcb_depth);
let ext_y = spec.internal_height + spec.floor_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = 2.0f32.mul_add(spec.pcb_clearance, spec.pcb_width) * 0.5;
let cavity_hy = f32::midpoint(spec.internal_height, 10.0);
let cavity_hz = 2.0f32.mul_add(spec.pcb_clearance, spec.pcb_depth) * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let usb_hx = f32::midpoint(spec.wall_thickness, 2.0);
let usb_hy = spec.usb_opening_height * 0.5;
let usb_hz = spec.usb_opening_width * 0.5;
let usb_offset_x = outer_hx - usb_hx + 0.5;
let usb_offset_y = spec.floor_thickness.mul_add(0.5, spec.pcb_clearance) + usb_hy;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let usb = translate(
box3d(usb_hx, usb_hy, usb_hz),
Vec3::new(usb_offset_x, usb_offset_y, 0.0),
);
to_z_up(subtract(subtract(outer, cavity), usb))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Battery18650HolderSpec {
pub cell_count: u32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl Battery18650HolderSpec {
#[must_use]
pub const fn row_4_through() -> Self {
Self {
cell_count: 4,
wall_thickness: 2.5,
floor_thickness: 0.0,
}
}
}
const CELL_18650_DIAMETER: f32 = 18.6;
const CELL_18650_LENGTH: f32 = 68.0;
#[must_use]
pub fn battery_18650_holder(spec: &Battery18650HolderSpec) -> SdfNode {
let count = spec.cell_count.max(1);
let count_f = n_f32(count);
let pitch = CELL_18650_DIAMETER + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = 2.0f32.mul_add(spec.floor_thickness, CELL_18650_LENGTH);
let ext_z = 2.0f32.mul_add(spec.wall_thickness, CELL_18650_DIAMETER);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cell_r = CELL_18650_DIAMETER * 0.5;
let cell_hy = f32::midpoint(CELL_18650_LENGTH, 10.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let cavity = translate(cylinder(cell_r, cell_hy), Vec3::new(x, 0.0, 0.0));
cutters.push(cavity);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ToothbrushHolderSpec {
pub count: u32,
pub hole_diameter: f32,
pub hole_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl ToothbrushHolderSpec {
#[must_use]
pub const fn manual_4() -> Self {
Self {
count: 4,
hole_diameter: 15.0,
hole_depth: 70.0,
wall_thickness: 6.0,
floor_thickness: 4.0,
}
}
}
#[must_use]
pub fn toothbrush_holder(spec: &ToothbrushHolderSpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let pitch = spec.hole_diameter + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.hole_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.hole_diameter);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let hole_r = spec.hole_diameter * 0.5;
let hole_hy = f32::midpoint(spec.hole_depth, 10.0);
let hole_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let hole = translate(cylinder(hole_r, hole_hy), Vec3::new(x, hole_offset_y, 0.0));
cutters.push(hole);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DrillBitHolderSpec {
pub min_size_mm: f32,
pub max_size_mm: f32,
pub count: u32,
pub hole_depth: f32,
pub hole_clearance: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl DrillBitHolderSpec {
#[must_use]
pub const fn metric_11_3to13() -> Self {
Self {
min_size_mm: 3.0,
max_size_mm: 13.0,
count: 11,
hole_depth: 22.0,
hole_clearance: 0.25,
wall_thickness: 3.0,
floor_thickness: 3.0,
}
}
}
#[must_use]
pub fn drill_bit_holder(spec: &DrillBitHolderSpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let max_hole_dia = 2.0f32.mul_add(spec.hole_clearance, spec.max_size_mm);
let pitch = max_hole_dia + 4.0;
let ext_x = 2.0f32.mul_add(spec.wall_thickness, count_f * pitch);
let ext_y = spec.hole_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, max_hole_dia);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let x_start = -(count_f - 1.0) * pitch * 0.5;
let hole_hy = f32::midpoint(spec.hole_depth, 10.0);
let hole_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let t = if count == 1 {
0.0
} else {
n_f32(i) / (count_f - 1.0)
};
let size = spec.min_size_mm + t * (spec.max_size_mm - spec.min_size_mm);
let hole_r = 2.0f32.mul_add(spec.hole_clearance, size) * 0.5;
let x = (n_f32(i)).mul_add(pitch, x_start);
let hole = translate(cylinder(hole_r, hole_hy), Vec3::new(x, hole_offset_y, 0.0));
cutters.push(hole);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PliersRackSpec {
pub slot_count: u32,
pub slot_width: f32,
pub slot_depth: f32,
pub slot_height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl PliersRackSpec {
#[must_use]
pub const fn standard_6() -> Self {
Self {
slot_count: 6,
slot_width: 15.0,
slot_depth: 60.0,
slot_height: 35.0,
wall_thickness: 5.0,
floor_thickness: 5.0,
}
}
}
#[must_use]
pub fn pliers_rack(spec: &PliersRackSpec) -> SdfNode {
let count = spec.slot_count.max(1);
let count_f = n_f32(count);
let pitch = spec.slot_width + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.slot_depth + spec.floor_thickness;
let ext_z = spec.slot_height;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hy = f32::midpoint(spec.slot_depth, 10.0);
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let slot = translate(
box3d(spec.slot_width * 0.5, slot_hy, outer_hz + 1.0),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SpiceRackSpec {
pub count: u32,
pub jar_diameter: f32,
pub jar_height: f32,
pub recess_depth: f32,
pub shelf_depth_margin: f32,
pub base_thickness: f32,
pub wall_thickness: f32,
pub lip_height_ratio: f32,
pub lip_thickness: f32,
}
impl SpiceRackSpec {
#[must_use]
pub const fn standard_6() -> Self {
Self {
count: 6,
jar_diameter: 48.0,
jar_height: 100.0,
recess_depth: 5.0,
shelf_depth_margin: 15.0,
base_thickness: 5.0,
wall_thickness: 3.0,
lip_height_ratio: 0.15,
lip_thickness: 3.0,
}
}
}
#[must_use]
pub fn spice_rack(spec: &SpiceRackSpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let jar_clearance = 2.0; let pitch = spec.jar_diameter + jar_clearance * 2.0 + spec.wall_thickness;
let shelf_depth = spec.jar_diameter + spec.shelf_depth_margin;
let lip_height = spec.jar_height * spec.lip_height_ratio;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.base_thickness + lip_height;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, shelf_depth);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let base_hy = spec.base_thickness * 0.5;
let base_offset_y = -outer_hy + base_hy;
let base = translate(
rounded_box(outer_hx, base_hy, outer_hz, 2.0),
Vec3::new(0.0, base_offset_y, 0.0),
);
let lip_hz = spec.lip_thickness * 0.5;
let lip_hy = lip_height * 0.5;
let lip_offset_y = -outer_hy + spec.base_thickness + lip_hy;
let lip_offset_z = -outer_hz + lip_hz;
let lip = translate(
box3d(outer_hx, lip_hy, lip_hz),
Vec3::new(0.0, lip_offset_y, lip_offset_z),
);
let mut result = union(base, lip);
let recess_r = spec.jar_diameter.mul_add(0.5, jar_clearance);
let recess_hy = f32::midpoint(spec.recess_depth, 0.5);
let recess_offset_y = -outer_hy + spec.base_thickness - recess_hy + 0.25;
let x_start = -(count_f - 1.0) * pitch * 0.5;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let recess = translate(
cylinder(recess_r, recess_hy),
Vec3::new(x, recess_offset_y, 0.0),
);
cutters.push(recess);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EggTraySpec {
pub rows: u32,
pub cols: u32,
pub cup_depth: f32,
pub pitch: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl EggTraySpec {
#[must_use]
pub const fn tray_4x3() -> Self {
Self {
rows: 3,
cols: 4,
cup_depth: 18.0,
pitch: 50.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
}
}
}
const EGG_CUP_DIAMETER: f32 = 40.0;
#[must_use]
pub fn egg_tray(spec: &EggTraySpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let ext_x = 2.0f32.mul_add(spec.wall_thickness, cols_f * spec.pitch);
let ext_y = spec.cup_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, rows_f * spec.pitch);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cup_r = EGG_CUP_DIAMETER * 0.5;
let cup_hy = f32::midpoint(spec.cup_depth, 10.0);
let cup_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(cols_f - 1.0) * spec.pitch * 0.5;
let z_start = -(rows_f - 1.0) * spec.pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(spec.pitch, x_start);
let z = (n_f32(r)).mul_add(spec.pitch, z_start);
let cup = translate(cylinder(cup_r, cup_hy), Vec3::new(x, cup_offset_y, z));
cutters.push(cup);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct UtensilCaddySpec {
pub count: u32,
pub compartment_diameter: f32,
pub height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl UtensilCaddySpec {
#[must_use]
pub const fn standard_4() -> Self {
Self {
count: 4,
compartment_diameter: 65.0,
height: 130.0,
wall_thickness: 5.0,
floor_thickness: 4.0,
}
}
}
#[must_use]
pub fn utensil_caddy(spec: &UtensilCaddySpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let pitch = spec.compartment_diameter + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.height + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.compartment_diameter);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let comp_r = spec.compartment_diameter * 0.5;
let comp_hy = f32::midpoint(spec.height, 10.0);
let comp_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let compartment = translate(cylinder(comp_r, comp_hy), Vec3::new(x, comp_offset_y, 0.0));
cutters.push(compartment);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FilamentSpoolHolderSpec {
pub spool_outer_diameter: f32,
pub spool_width: f32,
pub bore_diameter: f32,
pub base_thickness: f32,
pub base_margin: f32,
pub peg_clearance: f32,
pub peg_extra_height: f32,
}
impl FilamentSpoolHolderSpec {
#[must_use]
pub const fn standard_1kg() -> Self {
Self {
spool_outer_diameter: 200.0,
spool_width: 68.0,
bore_diameter: 52.0,
base_thickness: 5.0,
base_margin: 30.0,
peg_clearance: 1.0,
peg_extra_height: 20.0,
}
}
}
#[must_use]
pub fn filament_spool_holder(spec: &FilamentSpoolHolderSpec) -> SdfNode {
let base_side = spec.spool_outer_diameter + spec.base_margin;
let base_hx = base_side * 0.5;
let base_hy = base_side * 0.5;
let base_hz = spec.base_thickness * 0.5;
let peg_r = spec.bore_diameter.mul_add(0.5, -spec.peg_clearance);
let peg_h = spec.spool_width + spec.peg_extra_height;
let peg_half_h = peg_h * 0.5;
let peg_offset_z = spec.base_thickness + peg_half_h - 0.5;
let base = rounded_box(base_hx, base_hy, base_hz, 3.0);
let peg = translate(
cylinder_z(peg_r, peg_half_h),
Vec3::new(0.0, 0.0, peg_offset_z),
);
union(base, peg)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct NozzleHolderSpec {
pub count: u32,
pub hole_diameter: f32,
pub hole_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl NozzleHolderSpec {
#[must_use]
pub const fn m6_row_8() -> Self {
Self {
count: 8,
hole_diameter: 8.0,
hole_depth: 6.0,
wall_thickness: 4.0,
floor_thickness: 3.0,
}
}
}
#[must_use]
pub fn nozzle_holder(spec: &NozzleHolderSpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let pitch = spec.hole_diameter + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.hole_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.hole_diameter);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let hole_r = spec.hole_diameter * 0.5;
let hole_hy = f32::midpoint(spec.hole_depth, 10.0);
let hole_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let hole = translate(cylinder(hole_r, hole_hy), Vec3::new(x, hole_offset_y, 0.0));
cutters.push(hole);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BuildPlateRackSpec {
pub slot_count: u32,
pub slot_spacing: f32,
pub height: f32,
pub slot_width: f32,
pub depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl BuildPlateRackSpec {
#[must_use]
pub const fn standard_5() -> Self {
Self {
slot_count: 5,
slot_spacing: 15.0,
height: 200.0,
slot_width: 5.5,
depth: 60.0,
wall_thickness: 5.0,
floor_thickness: 5.0,
}
}
}
#[must_use]
pub fn build_plate_rack(spec: &BuildPlateRackSpec) -> SdfNode {
let count = spec.slot_count.max(1);
let count_f = n_f32(count);
let ext_x = 2.0f32.mul_add(spec.wall_thickness, count_f * spec.slot_spacing);
let ext_y = spec.height + spec.floor_thickness;
let ext_z = spec.depth;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hy = f32::midpoint(spec.height, 10.0);
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * spec.slot_spacing * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(spec.slot_spacing, x_start);
let slot = translate(
box3d(spec.slot_width * 0.5, slot_hy, outer_hz + 1.0),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CutleryTraySpec {
pub slot_count: u32,
pub slot_width: f32,
pub slot_length: f32,
pub slot_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl CutleryTraySpec {
#[must_use]
pub const fn standard_3() -> Self {
Self {
slot_count: 3,
slot_width: 35.0,
slot_length: 220.0,
slot_depth: 40.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
}
}
}
#[must_use]
pub fn cutlery_tray(spec: &CutleryTraySpec) -> SdfNode {
let count = spec.slot_count.max(1);
let count_f = n_f32(count);
let pitch = spec.slot_width + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.slot_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.slot_length);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hy = f32::midpoint(spec.slot_depth, 10.0);
let slot_hz = spec.slot_length * 0.5;
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let slot = translate(
box3d(spec.slot_width * 0.5, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PillOrganizerSpec {
pub rows: u32,
pub cols: u32,
pub cell_size: f32,
pub cell_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl PillOrganizerSpec {
#[must_use]
pub const fn weekly_7x2() -> Self {
Self {
rows: 7,
cols: 2,
cell_size: 20.0,
cell_depth: 15.0,
wall_thickness: 1.5,
floor_thickness: 1.5,
}
}
}
#[must_use]
pub fn pill_organizer(spec: &PillOrganizerSpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let pitch = spec.cell_size + spec.wall_thickness;
let ext_x = cols_f * pitch + spec.wall_thickness;
let ext_y = spec.cell_depth + spec.floor_thickness;
let ext_z = rows_f * pitch + spec.wall_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cell_h = spec.cell_size * 0.5;
let cell_hy = f32::midpoint(spec.cell_depth, 10.0);
let cell_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(cols_f - 1.0) * pitch * 0.5;
let z_start = -(rows_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(pitch, x_start);
let z = (n_f32(r)).mul_add(pitch, z_start);
let cell = translate(
box3d(cell_h, cell_hy, cell_h),
Vec3::new(x, cell_offset_y, z),
);
cutters.push(cell);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MagneticStripSpec {
pub magnet_count: u32,
pub magnet_diameter: f32,
pub magnet_spacing: f32,
pub magnet_depth: f32,
pub bar_thickness: f32,
pub bar_height: f32,
pub end_margin: f32,
}
impl MagneticStripSpec {
#[must_use]
pub const fn knife_rail_8() -> Self {
Self {
magnet_count: 8,
magnet_diameter: 6.0,
magnet_spacing: 30.0,
magnet_depth: 2.0,
bar_thickness: 5.0,
bar_height: 15.0,
end_margin: 5.0,
}
}
}
#[must_use]
pub fn magnetic_strip(spec: &MagneticStripSpec) -> SdfNode {
let count = spec.magnet_count.max(1);
let count_f = n_f32(count);
let ext_x = 2.0f32.mul_add(spec.end_margin, count_f * spec.magnet_spacing);
let ext_y = spec.bar_thickness;
let ext_z = spec.bar_height;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let magnet_r = spec.magnet_diameter * 0.5;
let magnet_hy = f32::midpoint(spec.magnet_depth, 0.5);
let magnet_offset_y = outer_hy - magnet_hy + 0.25;
let x_start = -(count_f - 1.0) * spec.magnet_spacing * 0.5;
let bar = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = bar;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(spec.magnet_spacing, x_start);
let hole = translate(
cylinder(magnet_r, magnet_hy),
Vec3::new(x, magnet_offset_y, 0.0),
);
cutters.push(hole);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HairdryerHolderSpec {
pub barrel_diameter: f32,
pub holster_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub inner_clearance: f32,
}
impl HairdryerHolderSpec {
#[must_use]
pub const fn dyson_85() -> Self {
Self {
barrel_diameter: 85.0,
holster_depth: 110.0,
wall_thickness: 3.0,
floor_thickness: 5.0,
inner_clearance: 2.0,
}
}
}
#[must_use]
pub fn hairdryer_holder(spec: &HairdryerHolderSpec) -> SdfNode {
let outer_side = 2.0f32.mul_add(
spec.inner_clearance + spec.wall_thickness,
spec.barrel_diameter,
);
let outer_hx = outer_side * 0.5;
let outer_hz = outer_side * 0.5;
let outer_hy = f32::midpoint(spec.holster_depth, spec.floor_thickness);
let cavity_r = spec.barrel_diameter.mul_add(0.5, spec.inner_clearance);
let cavity_hy = f32::midpoint(spec.holster_depth, 10.0);
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 5.0);
let cavity = translate(
cylinder(cavity_r, cavity_hy),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
to_z_up(subtract(outer, cavity))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct KcupHolderSpec {
pub rows: u32,
pub cols: u32,
pub capsule_diameter: f32,
pub capsule_depth: f32,
pub capsule_clearance: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl KcupHolderSpec {
#[must_use]
pub const fn kcup_4x3() -> Self {
Self {
rows: 3,
cols: 4,
capsule_diameter: 53.0,
capsule_depth: 40.0,
capsule_clearance: 3.5,
wall_thickness: 3.0,
floor_thickness: 3.0,
}
}
}
#[must_use]
pub fn kcup_holder(spec: &KcupHolderSpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let pitch = spec.capsule_diameter + spec.capsule_clearance;
let ext_x = 2.0f32.mul_add(spec.wall_thickness, cols_f * pitch);
let ext_y = spec.capsule_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, rows_f * pitch);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let well_r = spec.capsule_diameter * 0.5;
let well_hy = f32::midpoint(spec.capsule_depth, 10.0);
let well_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(cols_f - 1.0) * pitch * 0.5;
let z_start = -(rows_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(pitch, x_start);
let z = (n_f32(r)).mul_add(pitch, z_start);
let well = translate(cylinder(well_r, well_hy), Vec3::new(x, well_offset_y, z));
cutters.push(well);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HexKeyHolderSpec {
pub count: u32,
pub min_key_mm: f32,
pub max_key_mm: f32,
pub hole_depth: f32,
pub hole_clearance: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl HexKeyHolderSpec {
#[must_use]
pub const fn metric_9() -> Self {
Self {
count: 9,
min_key_mm: 1.5,
max_key_mm: 10.0,
hole_depth: 18.0,
hole_clearance: 0.3,
wall_thickness: 3.0,
floor_thickness: 4.0,
}
}
}
#[must_use]
pub fn hex_key_holder(spec: &HexKeyHolderSpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let max_hole_dia = 2.0f32.mul_add(spec.hole_clearance, spec.max_key_mm);
let pitch = max_hole_dia + 3.0;
let ext_x = 2.0f32.mul_add(spec.wall_thickness, count_f * pitch);
let ext_y = spec.hole_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, max_hole_dia);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let x_start = -(count_f - 1.0) * pitch * 0.5;
let hole_hy = f32::midpoint(spec.hole_depth, 10.0);
let hole_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let t = if count == 1 {
0.0
} else {
n_f32(i) / (count_f - 1.0)
};
let size = spec.min_key_mm + t * (spec.max_key_mm - spec.min_key_mm);
let hole_r = 2.0f32.mul_add(spec.hole_clearance, size) * 0.5;
let x = (n_f32(i)).mul_add(pitch, x_start);
let hole = translate(cylinder(hole_r, hole_hy), Vec3::new(x, hole_offset_y, 0.0));
cutters.push(hole);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct WrapHolderSpec {
pub roll_diameter: f32,
pub roll_width: f32,
pub wall_thickness: f32,
pub roll_clearance: f32,
pub cradle_depth_ratio: f32,
}
impl WrapHolderSpec {
#[must_use]
pub const fn foil_12inch() -> Self {
Self {
roll_diameter: 55.0,
roll_width: 305.0,
wall_thickness: 3.0,
roll_clearance: 1.5,
cradle_depth_ratio: 0.6,
}
}
}
#[must_use]
pub fn wrap_holder(spec: &WrapHolderSpec) -> SdfNode {
let cradle_r = spec.roll_diameter.mul_add(0.5, spec.roll_clearance);
let cavity_depth = spec.roll_diameter * spec.cradle_depth_ratio;
let body_height = cavity_depth + spec.wall_thickness + 5.0;
let ext_x = 2.0f32.mul_add(spec.wall_thickness, spec.roll_width);
let ext_y = body_height;
let ext_z = 2.0f32.mul_add(
spec.roll_clearance + spec.wall_thickness,
spec.roll_diameter,
);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cradle_half_h = outer_hx + 1.0; let cradle_offset_y = outer_hy - cavity_depth + cradle_r;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, spec.wall_thickness);
let cradle_yaxis = cylinder(cradle_r, cradle_half_h);
let cradle_xaxis = SdfNode::Rotate {
child: Arc::new(cradle_yaxis),
rotation: crate::angle::quat_from_rotation_z(std::f32::consts::FRAC_PI_2),
};
let cradle = translate(cradle_xaxis, Vec3::new(0.0, cradle_offset_y, 0.0));
to_z_up(subtract(outer, cradle))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SockDividerSpec {
pub cell_count: u32,
pub cell_width: f32,
pub height: f32,
pub cell_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl SockDividerSpec {
#[must_use]
pub const fn standard_4() -> Self {
Self {
cell_count: 4,
cell_width: 80.0,
height: 89.0,
cell_depth: 100.0,
wall_thickness: 2.5,
floor_thickness: 2.0,
}
}
}
#[must_use]
pub fn sock_divider(spec: &SockDividerSpec) -> SdfNode {
let count = spec.cell_count.max(1);
let count_f = n_f32(count);
let inner_x = (count_f - 1.0).mul_add(spec.wall_thickness, count_f * spec.cell_width);
let neg_inner_x = (count_f - 1.0).mul_add(-spec.wall_thickness, -(count_f * spec.cell_width));
let ext_x = 2.0f32.mul_add(spec.wall_thickness, inner_x);
let ext_y = spec.height + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.cell_depth);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = inner_x * 0.5;
let cavity_hy = f32::midpoint(spec.height, 10.0);
let cavity_hz = spec.cell_depth * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let mut result = subtract(outer, cavity);
let wall_hy = spec.height * 0.5;
let wall_hz = spec.cell_depth * 0.5;
let wall_offset_y = spec.floor_thickness + wall_hy;
let x_left = neg_inner_x * 0.5;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 1..count {
let wall_center_x = spec.wall_thickness.mul_add(
-0.5,
(n_f32(i)).mul_add(spec.cell_width + spec.wall_thickness, x_left),
);
let wall = translate(
box3d(spec.wall_thickness * 0.5, wall_hy, wall_hz),
Vec3::new(wall_center_x, wall_offset_y - outer_hy, 0.0),
);
cutters.push(wall);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = union(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SoapTraySpec {
pub tray_length: f32,
pub tray_width: f32,
pub drain_slot_count: u32,
pub tray_depth: f32,
pub drain_slot_width: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl SoapTraySpec {
#[must_use]
pub const fn dual_bottle_l200() -> Self {
Self {
tray_length: 200.0,
tray_width: 90.0,
drain_slot_count: 6,
tray_depth: 12.0,
drain_slot_width: 3.0,
wall_thickness: 2.5,
floor_thickness: 2.0,
}
}
}
#[must_use]
pub fn soap_tray(spec: &SoapTraySpec) -> SdfNode {
let count = spec.drain_slot_count.max(1);
let count_f = n_f32(count);
let ext_x = 2.0f32.mul_add(spec.wall_thickness, spec.tray_length);
let ext_y = spec.tray_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.tray_width);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = spec.tray_length * 0.5;
let cavity_hy = f32::midpoint(spec.tray_depth, 10.0);
let cavity_hz = spec.tray_width * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let mut result = subtract(outer, cavity);
let slot_pitch = spec.tray_length / (count_f + 1.0);
let slot_hx = spec.drain_slot_width * 0.5;
let slot_hy = f32::midpoint(spec.floor_thickness, 10.0);
let slot_hz = spec.tray_width * 0.5;
let slot_offset_y = -outer_hy + slot_hy - 0.5;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (-spec.tray_length).mul_add(0.5, slot_pitch * (n_f32(i) + 1.0));
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RazorHolderSpec {
pub slot_width: f32,
pub slot_depth: f32,
pub mount_hole_diameter: f32,
pub blade_clearance_width: f32,
pub backplate_width: f32,
pub total_height: f32,
pub wall_thickness: f32,
}
impl RazorHolderSpec {
#[must_use]
pub const fn cartridge_razor() -> Self {
Self {
slot_width: 12.0,
slot_depth: 22.0,
mount_hole_diameter: 4.5,
blade_clearance_width: 55.0,
backplate_width: 80.0,
total_height: 60.0,
wall_thickness: 3.0,
}
}
}
#[must_use]
pub fn razor_holder(spec: &RazorHolderSpec) -> SdfNode {
let ext_x = spec.backplate_width;
let ext_y = spec.total_height;
let ext_z = spec.wall_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let backplate_radius: f32 = 3.0;
let punch_z = outer_hz + backplate_radius + 5.0;
let slot_hx = spec.slot_width * 0.5;
let slot_hy = f32::midpoint(spec.slot_depth, 1.0);
let slot_hz = punch_z;
let slot_offset_y = -outer_hy + slot_hy - 0.5;
let mount_r = spec.mount_hole_diameter * 0.5;
let mount_hy = punch_z;
let mount_offset_y = spec.wall_thickness.mul_add(-2.0, outer_hy);
let backplate = rounded_box(outer_hx, outer_hy, outer_hz, backplate_radius);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(0.0, slot_offset_y, 0.0),
);
let mount = translate(
cylinder(mount_r, mount_hy),
Vec3::new(0.0, mount_offset_y, 0.0),
);
to_z_up(subtract(subtract(backplate, slot), mount))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ChopstickHolderSpec {
pub pair_count: u32,
pub slot_width: f32,
pub slot_length: f32,
pub slot_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl ChopstickHolderSpec {
#[must_use]
pub const fn adult_4() -> Self {
Self {
pair_count: 4,
slot_width: 13.0,
slot_length: 260.0,
slot_depth: 15.0,
wall_thickness: 2.5,
floor_thickness: 2.0,
}
}
}
#[must_use]
pub fn chopstick_holder(spec: &ChopstickHolderSpec) -> SdfNode {
let count = spec.pair_count.max(1);
let count_f = n_f32(count);
let pitch = spec.slot_width + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.slot_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.slot_length);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hy = f32::midpoint(spec.slot_depth, 10.0);
let slot_hz = spec.slot_length * 0.5;
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let slot = translate(
box3d(spec.slot_width * 0.5, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SwatchHolderSpec {
pub rows: u32,
pub cols: u32,
pub swatch_width: f32,
pub swatch_height: f32,
pub swatch_thickness: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl SwatchHolderSpec {
#[must_use]
pub const fn standard_8x4() -> Self {
Self {
rows: 8,
cols: 4,
swatch_width: 32.0,
swatch_height: 70.0,
swatch_thickness: 4.5,
wall_thickness: 2.0,
floor_thickness: 3.0,
}
}
}
#[must_use]
pub fn swatch_holder(spec: &SwatchHolderSpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let pitch_x = spec.swatch_thickness + spec.wall_thickness;
let pitch_z = spec.swatch_width + spec.wall_thickness;
let ext_x = cols_f * pitch_x + spec.wall_thickness;
let ext_y = spec.swatch_height + spec.floor_thickness;
let ext_z = rows_f * pitch_z + spec.wall_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hx = spec.swatch_thickness * 0.5;
let slot_hy = f32::midpoint(spec.swatch_height, 10.0);
let slot_hz = spec.swatch_width * 0.5;
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(cols_f - 1.0) * pitch_x * 0.5;
let z_start = -(rows_f - 1.0) * pitch_z * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(pitch_x, x_start);
let z = (n_f32(r)).mul_add(pitch_z, z_start);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, z),
);
cutters.push(slot);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TpHolderSpec {
pub inner_diameter: f32,
pub roll_width: f32,
pub wall_thickness: f32,
}
impl TpHolderSpec {
#[must_use]
pub const fn standard() -> Self {
Self {
inner_diameter: 40.0,
roll_width: 110.0,
wall_thickness: 5.0,
}
}
}
#[must_use]
pub fn tp_holder(spec: &TpHolderSpec) -> SdfNode {
let backplate_size = spec.inner_diameter * 2.0;
let outer_hx = backplate_size * 0.5;
let outer_hy = backplate_size * 0.5;
let outer_hz = spec.wall_thickness * 0.5;
let axle_r = spec.inner_diameter.mul_add(0.5, -0.5);
let axle_half_h = spec.roll_width * 0.5;
let axle_z_center = outer_hz + axle_half_h - 5.0;
let mount_r = 2.25;
let mount_hy = spec.wall_thickness + 1.0;
let mount_y_offset = outer_hy * 0.6;
let mount_x_offset = outer_hx * 0.7;
let backplate = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let axle = translate(
cylinder_z(axle_r, axle_half_h),
Vec3::new(0.0, 0.0, axle_z_center),
);
let mount_left = translate(
cylinder(mount_r, mount_hy),
Vec3::new(-mount_x_offset, mount_y_offset, 0.0),
);
let mount_right = translate(
cylinder(mount_r, mount_hy),
Vec3::new(mount_x_offset, mount_y_offset, 0.0),
);
to_z_up(subtract(
subtract(union(backplate, axle), mount_left),
mount_right,
))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SdCardHolderSpec {
pub rows: u32,
pub cols: u32,
pub card_width: f32,
pub card_height: f32,
pub card_thickness: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl SdCardHolderSpec {
#[must_use]
pub const fn full_sd_4x4() -> Self {
Self {
rows: 4,
cols: 4,
card_width: 24.0,
card_height: 32.0,
card_thickness: 2.5,
wall_thickness: 1.5,
floor_thickness: 2.0,
}
}
}
#[must_use]
pub fn sd_card_holder(spec: &SdCardHolderSpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let pitch_x = spec.card_thickness + spec.wall_thickness;
let pitch_z = spec.card_width + spec.wall_thickness;
let ext_x = cols_f * pitch_x + spec.wall_thickness;
let ext_y = spec.card_height + spec.floor_thickness;
let ext_z = rows_f * pitch_z + spec.wall_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hx = spec.card_thickness * 0.5;
let slot_hy = f32::midpoint(spec.card_height, 10.0);
let slot_hz = spec.card_width * 0.5;
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(cols_f - 1.0) * pitch_x * 0.5;
let z_start = -(rows_f - 1.0) * pitch_z * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(pitch_x, x_start);
let z = (n_f32(r)).mul_add(pitch_z, z_start);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, z),
);
cutters.push(slot);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DriverRackSpec {
pub slot_count: u32,
pub slot_diameter: f32,
pub height: f32,
}
impl DriverRackSpec {
#[must_use]
pub const fn standard_8() -> Self {
Self {
slot_count: 8,
slot_diameter: 25.0,
height: 100.0,
}
}
}
#[must_use]
pub fn driver_rack(spec: &DriverRackSpec) -> SdfNode {
let count = spec.slot_count.max(1);
let count_f = n_f32(count);
let wall_thickness = 3.0;
let floor_thickness = 5.0;
let pitch = spec.slot_diameter + wall_thickness;
let ext_x = count_f * pitch + wall_thickness;
let ext_y = spec.height;
let ext_z = 2.0f32.mul_add(wall_thickness, spec.slot_diameter);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let hole_r = spec.slot_diameter * 0.5;
let hole_depth = spec.height - floor_thickness;
let hole_hy = f32::midpoint(hole_depth, 10.0);
let hole_offset_y = floor_thickness * 0.5 + 5.0;
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let hole = translate(cylinder(hole_r, hole_hy), Vec3::new(x, hole_offset_y, 0.0));
cutters.push(hole);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CottonDispenserSpec {
pub count: u32,
pub inner_diameter: f32,
pub height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl CottonDispenserSpec {
#[must_use]
pub const fn standard_80() -> Self {
Self {
count: 80,
inner_diameter: 90.0,
height: 100.0,
wall_thickness: 2.5,
floor_thickness: 2.5,
}
}
}
#[must_use]
pub fn cotton_dispenser(spec: &CottonDispenserSpec) -> SdfNode {
let outer_r = spec.inner_diameter.mul_add(0.5, spec.wall_thickness);
let outer_hz = spec.height * 0.5;
let inner_r = spec.inner_diameter * 0.5;
let inner_hz = f32::midpoint(spec.height - spec.floor_thickness, 10.0);
let inner_offset_z = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = cylinder_z(outer_r, outer_hz);
let cavity = translate(
cylinder_z(inner_r, inner_hz),
Vec3::new(0.0, 0.0, inner_offset_z),
);
subtract(outer, cavity)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SinkCaddySpec {
pub tray_length: f32,
pub tray_width: f32,
pub drain_hole_count: u32,
pub tray_depth: f32,
pub drain_hole_diameter: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl SinkCaddySpec {
#[must_use]
pub const fn standard_l200() -> Self {
Self {
tray_length: 200.0,
tray_width: 100.0,
drain_hole_count: 8,
tray_depth: 30.0,
drain_hole_diameter: 6.0,
wall_thickness: 2.5,
floor_thickness: 2.5,
}
}
}
#[must_use]
pub fn sink_caddy(spec: &SinkCaddySpec) -> SdfNode {
let count = spec.drain_hole_count.max(1);
let count_f = n_f32(count);
let ext_x = 2.0f32.mul_add(spec.wall_thickness, spec.tray_length);
let ext_y = spec.tray_depth + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.tray_width);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = spec.tray_length * 0.5;
let cavity_hy = f32::midpoint(spec.tray_depth, 10.0);
let cavity_hz = spec.tray_width * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let mut result = subtract(outer, cavity);
let hole_r = spec.drain_hole_diameter * 0.5;
let hole_hy = f32::midpoint(spec.floor_thickness, 10.0);
let hole_offset_y = -outer_hy + hole_hy - 0.5;
let hole_pitch = spec.tray_length / (count_f + 1.0);
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (-spec.tray_length).mul_add(0.5, hole_pitch * (n_f32(i) + 1.0));
let hole = translate(cylinder(hole_r, hole_hy), Vec3::new(x, hole_offset_y, 0.0));
cutters.push(hole);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ClampRackSpec {
pub hook_count: u32,
pub hook_width: f32,
pub height: f32,
pub hook_depth: f32,
pub wall_thickness: f32,
pub hook_opening: f32,
}
impl ClampRackSpec {
#[must_use]
pub const fn standard_5() -> Self {
Self {
hook_count: 5,
hook_width: 30.0,
height: 150.0,
hook_depth: 25.0,
wall_thickness: 5.0,
hook_opening: 15.0,
}
}
}
#[must_use]
pub fn clamp_rack(spec: &ClampRackSpec) -> SdfNode {
let count = spec.hook_count.max(1);
let count_f = n_f32(count);
let pitch = spec.hook_width + 10.0;
let bp_ext_x = count_f * pitch + 10.0;
let outer_hx = bp_ext_x * 0.5;
let outer_hy = spec.height * 0.5;
let outer_hz = spec.wall_thickness * 0.5;
let backplate = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let arm_hx = spec.hook_width * 0.5;
let arm_hy = 4.0;
let arm_hz = spec.hook_depth * 0.5;
let tip_hy = spec.hook_opening * 0.5;
let hook_y_offset = spec.height.mul_add(0.3, -outer_hy);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let mut result = backplate;
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let arm = translate(
rounded_box(arm_hx, arm_hy, arm_hz, 2.0),
Vec3::new(x, hook_y_offset, outer_hz + arm_hz),
);
let tip = translate(
rounded_box(arm_hx, tip_hy, arm_hy, 2.0),
Vec3::new(
x,
hook_y_offset - tip_hy - arm_hy,
outer_hz + spec.hook_depth,
),
);
result = smooth_union(result, arm, 2.0);
result = smooth_union(result, tip, 2.0);
}
let mount_r = 2.25;
let mount_hy = spec.wall_thickness + 1.0;
let mount_y_offset = outer_hy * 0.7;
let mount_x_offset = outer_hx * 0.85;
let mount_left = translate(
cylinder(mount_r, mount_hy),
Vec3::new(-mount_x_offset, mount_y_offset, 0.0),
);
let mount_right = translate(
cylinder(mount_r, mount_hy),
Vec3::new(mount_x_offset, mount_y_offset, 0.0),
);
result = subtract(result, mount_left);
result = subtract(result, mount_right);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DryBoxSpec {
pub rows: u32,
pub cols: u32,
pub filament_diameter: f32,
pub spool_width: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl DryBoxSpec {
#[must_use]
pub const fn standard_2x2() -> Self {
Self {
rows: 2,
cols: 2,
filament_diameter: 68.0,
spool_width: 70.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
}
}
}
#[must_use]
pub fn dry_box(spec: &DryBoxSpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let pitch = spec.filament_diameter + spec.wall_thickness;
let ext_x = cols_f * pitch + spec.wall_thickness;
let ext_y = spec.spool_width + spec.floor_thickness;
let ext_z = rows_f * pitch + spec.wall_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_r = spec.filament_diameter * 0.5;
let cavity_hy = f32::midpoint(spec.spool_width - spec.floor_thickness, 10.0);
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(cols_f - 1.0) * pitch * 0.5;
let z_start = -(rows_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(pitch, x_start);
let z = (n_f32(r)).mul_add(pitch, z_start);
let cavity = translate(
cylinder(cavity_r, cavity_hy),
Vec3::new(x, cavity_offset_y, z),
);
cutters.push(cavity);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct OutdoorEnclosureSpec {
pub internal_width: f32,
pub internal_depth: f32,
pub internal_height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub gasket_width: f32,
pub gasket_depth: f32,
}
impl OutdoorEnclosureSpec {
#[must_use]
pub const fn ip54_120x80() -> Self {
Self {
internal_width: 120.0,
internal_depth: 80.0,
internal_height: 45.0,
wall_thickness: 3.5,
floor_thickness: 3.5,
gasket_width: 2.0,
gasket_depth: 1.5,
}
}
}
#[must_use]
pub fn outdoor_enclosure(spec: &OutdoorEnclosureSpec) -> SdfNode {
let ext_x = 2.0f32.mul_add(spec.wall_thickness, spec.internal_width);
let ext_y = spec.internal_height + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.internal_depth);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = spec.internal_width * 0.5;
let cavity_hy = f32::midpoint(spec.internal_height, 10.0);
let cavity_hz = spec.internal_depth * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let mut result = subtract(outer, cavity);
let groove_offset_y = spec.gasket_depth.mul_add(-0.5, outer_hy) + 5.0;
let groove_hy = f32::midpoint(spec.gasket_depth, 10.0);
let wall_mid_x = spec.wall_thickness.mul_add(0.5, cavity_hx);
let wall_mid_z = spec.wall_thickness.mul_add(0.5, cavity_hz);
let neg_wall_mid_x = spec.wall_thickness.mul_add(-0.5, -cavity_hx);
let neg_wall_mid_z = spec.wall_thickness.mul_add(-0.5, -cavity_hz);
let slot_x_long = translate(
box3d(cavity_hx, groove_hy, spec.gasket_width * 0.5),
Vec3::new(0.0, groove_offset_y, wall_mid_z),
);
let slot_x_long_n = translate(
box3d(cavity_hx, groove_hy, spec.gasket_width * 0.5),
Vec3::new(0.0, groove_offset_y, neg_wall_mid_z),
);
let slot_z_long = translate(
box3d(spec.gasket_width * 0.5, groove_hy, cavity_hz),
Vec3::new(wall_mid_x, groove_offset_y, 0.0),
);
let slot_z_long_n = translate(
box3d(spec.gasket_width * 0.5, groove_hy, cavity_hz),
Vec3::new(neg_wall_mid_x, groove_offset_y, 0.0),
);
result = subtract(result, slot_x_long);
result = subtract(result, slot_x_long_n);
result = subtract(result, slot_z_long);
result = subtract(result, slot_z_long_n);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct JewelryStandSpec {
pub tier_count: u32,
pub bottom_tier_diameter: f32,
pub height: f32,
pub tier_thickness: f32,
pub pillar_diameter: f32,
pub tier_ratio: f32,
}
impl JewelryStandSpec {
#[must_use]
pub const fn standard_3_tier() -> Self {
Self {
tier_count: 3,
bottom_tier_diameter: 100.0,
height: 100.0,
tier_thickness: 5.0,
pillar_diameter: 10.0,
tier_ratio: 0.7,
}
}
}
#[must_use]
pub fn jewelry_stand(spec: &JewelryStandSpec) -> SdfNode {
let count = spec.tier_count.max(1);
let count_f = n_f32(count);
let pillar_r = spec.pillar_diameter * 0.5;
let pillar_hz = spec.height * 0.5;
let pillar_offset_z = pillar_hz;
let pillar = translate(
cylinder_z(pillar_r, pillar_hz),
Vec3::new(0.0, 0.0, pillar_offset_z),
);
let mut result = pillar;
let tier_spacing = spec.height / count_f;
let tier_hz = spec.tier_thickness * 0.5;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let tier_r = spec.bottom_tier_diameter
* 0.5
* spec.tier_ratio.powi(i32::try_from(i).unwrap_or(i32::MAX));
let tier_z = tier_spacing * (n_f32(i) + 1.0) - tier_hz;
let tier = translate(cylinder_z(tier_r, tier_hz), Vec3::new(0.0, 0.0, tier_z));
cutters.push(tier);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = union(result, all);
}
result
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PhoneDockSpec {
pub width: f32,
pub upright_height: f32,
pub cable_diameter: f32,
pub base_depth: f32,
pub base_thickness: f32,
pub upright_thickness: f32,
pub tilt_angle_deg: f32,
}
impl PhoneDockSpec {
#[must_use]
pub const fn standard_80x100() -> Self {
Self {
width: 80.0,
upright_height: 100.0,
cable_diameter: 8.0,
base_depth: 60.0,
base_thickness: 6.0,
upright_thickness: 4.0,
tilt_angle_deg: 15.0,
}
}
}
#[must_use]
pub fn phone_dock(spec: &PhoneDockSpec) -> SdfNode {
let base_hx = spec.width * 0.5;
let base_hy = spec.base_thickness * 0.5;
let base_hz = spec.base_depth * 0.5;
let upright_hx = spec.width * 0.5;
let upright_hy = spec.upright_height * 0.5;
let upright_hz = spec.upright_thickness * 0.5;
let tilt_rad = crate::angle::canon_angle_deg(spec.tilt_angle_deg).to_radians();
let upright_rotation = crate::angle::quat_from_rotation_x(-tilt_rad);
let (sin_tilt, cos_tilt) = alice_det_math::sin_cos(tilt_rad);
let upright_offset_y = base_hy + upright_hy * cos_tilt;
let upright_offset_z = -base_hz + upright_hz + upright_hy * sin_tilt;
let base = rounded_box(base_hx, base_hy, base_hz, 2.0);
let upright_raw = rounded_box(upright_hx, upright_hy, upright_hz, 2.0);
let upright = translate(
SdfNode::Rotate {
child: Arc::new(upright_raw),
rotation: upright_rotation,
},
Vec3::new(0.0, upright_offset_y, upright_offset_z),
);
let hole_r = spec.cable_diameter * 0.5;
let hole_hy = spec.base_thickness + 1.0;
let hole_offset_y = 0.0;
let hole_offset_z = -base_hz + 20.0;
let hole = translate(
cylinder(hole_r, hole_hy),
Vec3::new(0.0, hole_offset_y, hole_offset_z),
);
let combined = union(base, upright);
to_z_up(subtract(combined, hole))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CuttingBoardRackSpec {
pub slot_count: u32,
pub slot_width: f32,
pub height: f32,
pub slot_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl CuttingBoardRackSpec {
#[must_use]
pub const fn standard_3() -> Self {
Self {
slot_count: 3,
slot_width: 12.0,
height: 220.0,
slot_depth: 200.0,
wall_thickness: 4.0,
floor_thickness: 8.0,
}
}
}
#[must_use]
pub fn cutting_board_rack(spec: &CuttingBoardRackSpec) -> SdfNode {
let count = spec.slot_count.max(1);
let count_f = n_f32(count);
let pitch = spec.slot_width + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.height;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.slot_depth);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hx = spec.slot_width * 0.5;
let slot_hy = f32::midpoint(spec.height - spec.floor_thickness, 10.0);
let slot_hz = spec.slot_depth * 0.5;
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TapeDispenserSpec {
pub inner_diameter: f32,
pub roll_width: f32,
pub wall_thickness: f32,
pub outer_diameter: f32,
pub tear_angle_deg: f32,
}
impl TapeDispenserSpec {
#[must_use]
pub const fn packing_tape_standard() -> Self {
Self {
inner_diameter: 76.0,
roll_width: 50.0,
wall_thickness: 3.0,
outer_diameter: 150.0,
tear_angle_deg: 30.0,
}
}
}
#[must_use]
pub fn tape_dispenser(spec: &TapeDispenserSpec) -> SdfNode {
let outer_r = spec.outer_diameter * 0.5;
let full_x = 2.0f32.mul_add(spec.wall_thickness, spec.outer_diameter);
let full_z = 2.0f32.mul_add(spec.wall_thickness, outer_r + spec.roll_width);
let base_hx = full_x * 0.5;
let base_hy = spec.wall_thickness * 0.5;
let base_hz = full_z * 0.5;
let base = rounded_box(base_hx, base_hy, base_hz, 2.0);
let back_hy = outer_r * 0.5;
let back_hz = spec.wall_thickness * 0.5;
let back_offset_y = spec.wall_thickness + back_hy;
let back_offset_z = -base_hz + back_hz;
let back = translate(
rounded_box(base_hx, back_hy, back_hz, 2.0),
Vec3::new(0.0, back_offset_y, back_offset_z),
);
let hood_hy = spec.wall_thickness * 0.5;
let hood_offset_y = spec.wall_thickness + outer_r;
let hood = translate(
rounded_box(base_hx, hood_hy, base_hz, 2.0),
Vec3::new(0.0, hood_offset_y, 0.0),
);
let axle_r = spec.inner_diameter.mul_add(0.5, -0.5);
let axle_half_h = spec.roll_width * 0.5;
let axle_offset_y = spec.wall_thickness + outer_r * 0.5;
let axle_offset_z = spec.wall_thickness.mul_add(2.0, -base_hz) + outer_r + axle_half_h;
let axle = translate(
cylinder_z(axle_r, axle_half_h),
Vec3::new(0.0, axle_offset_y, axle_offset_z),
);
let combined = union(union(union(base, back), hood), axle);
let tear_hx = base_hx + 1.0;
let tear_hy = 3.0;
let tear_hz = 2.0;
let tear_offset_y = spec.wall_thickness + outer_r * 2.0 - 1.0;
let tear_offset_z = base_hz - 2.0;
let tear_rad = crate::angle::canon_angle_deg(spec.tear_angle_deg).to_radians();
let tear_rotation = crate::angle::quat_from_rotation_x(tear_rad);
let tear_raw = box3d(tear_hx, tear_hy, tear_hz);
let tear = translate(
SdfNode::Rotate {
child: Arc::new(tear_raw),
rotation: tear_rotation,
},
Vec3::new(0.0, tear_offset_y, tear_offset_z),
);
to_z_up(subtract(combined, tear))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ShowerCaddySpec {
pub tier_count: u32,
pub tier_length: f32,
pub tier_depth: f32,
pub tier_height: f32,
pub tier_spacing: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub drain_hole_diameter: f32,
pub drains_per_tier: u32,
pub mount_hole_diameter: f32,
}
impl ShowerCaddySpec {
#[must_use]
pub const fn standard_2_tier() -> Self {
Self {
tier_count: 2,
tier_length: 250.0,
tier_depth: 120.0,
tier_height: 40.0,
tier_spacing: 100.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
drain_hole_diameter: 5.0,
drains_per_tier: 6,
mount_hole_diameter: 4.5,
}
}
}
#[must_use]
pub fn shower_caddy(spec: &ShowerCaddySpec) -> SdfNode {
let count = spec.tier_count.max(1);
let count_f = n_f32(count);
let bp_ext_x = 2.0f32.mul_add(spec.wall_thickness, spec.tier_length);
let bp_ext_y = spec
.wall_thickness
.mul_add(2.0, count_f * spec.tier_spacing + spec.tier_height);
let bp_ext_z = spec.wall_thickness;
let bp_hx = bp_ext_x * 0.5;
let bp_hy = bp_ext_y * 0.5;
let bp_hz = bp_ext_z * 0.5;
let backplate = rounded_box(bp_hx, bp_hy, bp_hz, 3.0);
let mut result = backplate;
let tier_outer_hx = 2.0f32.mul_add(spec.wall_thickness, spec.tier_length) * 0.5;
let tier_outer_hy = f32::midpoint(spec.tier_height, spec.floor_thickness);
let tier_outer_hz = f32::midpoint(spec.tier_depth, spec.wall_thickness);
let cavity_hx = spec.tier_length * 0.5;
let cavity_hy = f32::midpoint(spec.tier_height, 10.0);
let cavity_hz = spec.tier_depth * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let drain_r = spec.drain_hole_diameter * 0.5;
let drain_hy = f32::midpoint(spec.floor_thickness, 10.0);
let drain_offset_y = -tier_outer_hy + drain_hy - 0.5;
let drains = spec.drains_per_tier.max(1);
let drain_pitch = spec.tier_length / (n_f32(drains) + 1.0);
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let tier_y = (n_f32(i)).mul_add(
spec.tier_spacing,
-bp_hy + spec.wall_thickness + tier_outer_hy,
);
let tier_z = bp_hz + tier_outer_hz;
let outer = rounded_box(tier_outer_hx, tier_outer_hy, tier_outer_hz, 3.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, spec.wall_thickness * 0.5),
);
let mut tier = subtract(outer, cavity);
for k in 0..drains {
let x = (-spec.tier_length).mul_add(0.5, drain_pitch * (n_f32(k) + 1.0));
let drain = translate(
cylinder(drain_r, drain_hy),
Vec3::new(x, drain_offset_y, spec.wall_thickness * 0.5),
);
tier = subtract(tier, drain);
}
let tier_placed = translate(tier, Vec3::new(0.0, tier_y, tier_z));
cutters.push(tier_placed);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = union(result, all);
}
let mount_r = spec.mount_hole_diameter * 0.5;
let mount_hy = spec.wall_thickness + 1.0;
let mount_y_offset = spec.wall_thickness.mul_add(-2.0, bp_hy);
let mount_x_offset = bp_hx * 0.85;
let mount_left = translate(
cylinder(mount_r, mount_hy),
Vec3::new(-mount_x_offset, mount_y_offset, 0.0),
);
let mount_right = translate(
cylinder(mount_r, mount_hy),
Vec3::new(mount_x_offset, mount_y_offset, 0.0),
);
result = subtract(result, mount_left);
result = subtract(result, mount_right);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CaliperHolderSpec {
pub jaw_length: f32,
pub throat_depth: f32,
pub count: u32,
pub slot_width: f32,
pub wall_thickness: f32,
pub mount_hole_diameter: f32,
}
impl CaliperHolderSpec {
#[must_use]
pub const fn standard_3() -> Self {
Self {
jaw_length: 150.0,
throat_depth: 40.0,
count: 3,
slot_width: 15.0,
wall_thickness: 5.0,
mount_hole_diameter: 4.5,
}
}
}
#[must_use]
pub fn caliper_holder(spec: &CaliperHolderSpec) -> SdfNode {
let count = spec.count.max(1);
let count_f = n_f32(count);
let pitch = spec.throat_depth + 10.0;
let bp_ext_x = count_f * pitch + 10.0;
let bp_ext_y = spec.jaw_length + 20.0;
let outer_hx = bp_ext_x * 0.5;
let outer_hy = bp_ext_y * 0.5;
let outer_hz = spec.wall_thickness * 0.5;
let backplate = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let slot_hx = spec.slot_width * 0.5;
let slot_hy = spec.jaw_length * 0.5;
let slot_hz = spec.wall_thickness + 1.0;
let slot_offset_y = -outer_hy + slot_hy + 5.0;
let x_start = -(count_f - 1.0) * pitch * 0.5;
let mut result = backplate;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
let mount_r = spec.mount_hole_diameter * 0.5;
let mount_hy = spec.wall_thickness + 1.0;
let mount_y_off = outer_hy - 8.0;
let mount_x_off = outer_hx - 8.0;
for (mx, my) in [
(-mount_x_off, mount_y_off),
(mount_x_off, mount_y_off),
(-mount_x_off, -mount_y_off),
(mount_x_off, -mount_y_off),
] {
let mount = translate(cylinder(mount_r, mount_hy), Vec3::new(mx, my, 0.0));
result = subtract(result, mount);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BagClipOrgSpec {
pub slot_count: u32,
pub slot_width: f32,
pub height: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
pub slot_depth: f32,
}
impl BagClipOrgSpec {
#[must_use]
pub const fn standard_8() -> Self {
Self {
slot_count: 8,
slot_width: 8.0,
height: 100.0,
wall_thickness: 2.5,
floor_thickness: 3.0,
slot_depth: 30.0,
}
}
}
#[must_use]
pub fn bag_clip_org(spec: &BagClipOrgSpec) -> SdfNode {
let count = spec.slot_count.max(1);
let count_f = n_f32(count);
let pitch = spec.slot_width + spec.wall_thickness;
let ext_x = count_f * pitch + spec.wall_thickness;
let ext_y = spec.height + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.slot_depth);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let slot_hx = spec.slot_width * 0.5;
let slot_hy = f32::midpoint(spec.height, 10.0);
let slot_hz = spec.slot_depth * 0.5;
let slot_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(count_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..count {
let x = (n_f32(i)).mul_add(pitch, x_start);
let slot = translate(
box3d(slot_hx, slot_hy, slot_hz),
Vec3::new(x, slot_offset_y, 0.0),
);
cutters.push(slot);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CanRackSpec {
pub rows: u32,
pub can_diameter: f32,
pub tilt_angle_deg: f32,
pub cans_per_row: u32,
pub wall_thickness: f32,
pub shelf_thickness: f32,
pub front_lip_height: f32,
}
impl CanRackSpec {
#[must_use]
pub const fn standard_2_tier() -> Self {
Self {
rows: 2,
can_diameter: 66.0,
tilt_angle_deg: 10.0,
cans_per_row: 6,
wall_thickness: 3.0,
shelf_thickness: 3.0,
front_lip_height: 15.0,
}
}
}
#[must_use]
pub fn can_rack(spec: &CanRackSpec) -> SdfNode {
let rows = spec.rows.max(1);
let rows_f = n_f32(rows);
let cans_f = n_f32(spec.cans_per_row.max(1));
let shelf_depth = cans_f * (spec.can_diameter + 2.0);
let shelf_width = spec.can_diameter + 8.0;
let tier_spacing = spec.can_diameter + 15.0;
let total_height = rows_f * tier_spacing + spec.wall_thickness;
let ext_x = 2.0f32.mul_add(spec.wall_thickness, shelf_width);
let ext_y = total_height;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, shelf_depth);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let side_hy = outer_hy;
let side_hz = outer_hz;
let side_hx = spec.wall_thickness * 0.5;
let side_offset_x = outer_hx - side_hx;
let side_l = translate(
box3d(side_hx, side_hy, side_hz),
Vec3::new(-side_offset_x, 0.0, 0.0),
);
let side_r = translate(
box3d(side_hx, side_hy, side_hz),
Vec3::new(side_offset_x, 0.0, 0.0),
);
let back_hx = shelf_width * 0.5;
let back_hy = outer_hy;
let back_hz = spec.wall_thickness * 0.5;
let back_offset_z = -outer_hz + back_hz;
let back = translate(
box3d(back_hx, back_hy, back_hz),
Vec3::new(0.0, 0.0, back_offset_z),
);
let mut result = union(union(side_l, side_r), back);
let tilt_rad = crate::angle::canon_angle_deg(spec.tilt_angle_deg).to_radians();
let shelf_hx = shelf_width * 0.5;
let shelf_hy = spec.shelf_thickness * 0.5;
let shelf_hz = shelf_depth * 0.5;
let lip_hy = spec.front_lip_height * 0.5;
let lip_hz = spec.wall_thickness * 0.5;
let mut cutters: Vec<SdfNode> = Vec::new();
for i in 0..rows {
let tier_y = -outer_hy + spec.wall_thickness + tier_spacing * (n_f32(i)) + shelf_hy;
let shelf_raw = box3d(shelf_hx, shelf_hy, shelf_hz);
let shelf_rotated = SdfNode::Rotate {
child: Arc::new(shelf_raw),
rotation: crate::angle::quat_from_rotation_x(-tilt_rad),
};
let shelf = translate(shelf_rotated, Vec3::new(0.0, tier_y, 0.0));
let lip_offset_z = outer_hz - lip_hz;
let lip_offset_y = tier_y + lip_hy;
let lip = translate(
box3d(shelf_hx, lip_hy, lip_hz),
Vec3::new(0.0, lip_offset_y, lip_offset_z),
);
cutters.push(shelf);
cutters.push(lip);
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = union(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LedHubBoxSpec {
pub internal_width: f32,
pub internal_depth: f32,
pub internal_height: f32,
pub led_window_width: f32,
pub led_window_height: f32,
pub antenna_hole_diameter: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl LedHubBoxSpec {
#[must_use]
pub const fn standard_80x60() -> Self {
Self {
internal_width: 80.0,
internal_depth: 60.0,
internal_height: 30.0,
led_window_width: 40.0,
led_window_height: 15.0,
antenna_hole_diameter: 12.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
}
}
}
#[must_use]
pub fn led_hub_box(spec: &LedHubBoxSpec) -> SdfNode {
let ext_x = 2.0f32.mul_add(spec.wall_thickness, spec.internal_width);
let ext_y = spec.internal_height + spec.floor_thickness;
let ext_z = 2.0f32.mul_add(spec.wall_thickness, spec.internal_depth);
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cavity_hx = spec.internal_width * 0.5;
let cavity_hy = f32::midpoint(spec.internal_height, 10.0);
let cavity_hz = spec.internal_depth * 0.5;
let cavity_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let cavity = translate(
box3d(cavity_hx, cavity_hy, cavity_hz),
Vec3::new(0.0, cavity_offset_y, 0.0),
);
let mut result = subtract(outer, cavity);
let led_hx = spec.led_window_width * 0.5;
let led_hy = spec.led_window_height * 0.5;
let led_hz = spec.wall_thickness + 1.0;
let led_offset_y = cavity_offset_y;
let led_offset_z = spec.wall_thickness.mul_add(-0.5, outer_hz);
let led = translate(
box3d(led_hx, led_hy, led_hz),
Vec3::new(0.0, led_offset_y, led_offset_z),
);
result = subtract(result, led);
let antenna_r = spec.antenna_hole_diameter * 0.5;
let antenna_hy = spec.floor_thickness + spec.wall_thickness + 1.0;
let antenna_offset_x = outer_hx - antenna_r - spec.wall_thickness;
let antenna_offset_y = outer_hy - antenna_hy * 0.5 + 5.0;
let antenna_offset_z = -outer_hz + antenna_r + spec.wall_thickness;
let antenna = translate(
cylinder(antenna_r, antenna_hy),
Vec3::new(antenna_offset_x, antenna_offset_y, antenna_offset_z),
);
result = subtract(result, antenna);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MakeupOrganizerSpec {
pub rows: u32,
pub cols: u32,
pub cell_size: f32,
pub cell_depth: f32,
pub wall_thickness: f32,
pub floor_thickness: f32,
}
impl MakeupOrganizerSpec {
#[must_use]
pub const fn standard_3x4() -> Self {
Self {
rows: 3,
cols: 4,
cell_size: 45.0,
cell_depth: 40.0,
wall_thickness: 2.0,
floor_thickness: 2.5,
}
}
}
#[must_use]
pub fn makeup_organizer(spec: &MakeupOrganizerSpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let pitch = spec.cell_size + spec.wall_thickness;
let ext_x = cols_f * pitch + spec.wall_thickness;
let ext_y = spec.cell_depth + spec.floor_thickness;
let ext_z = rows_f * pitch + spec.wall_thickness;
let outer_hx = ext_x * 0.5;
let outer_hy = ext_y * 0.5;
let outer_hz = ext_z * 0.5;
let cell_h_side = spec.cell_size * 0.5;
let cell_hy = f32::midpoint(spec.cell_depth, 10.0);
let cell_offset_y = spec.floor_thickness.mul_add(0.5, 5.0);
let x_start = -(cols_f - 1.0) * pitch * 0.5;
let z_start = -(rows_f - 1.0) * pitch * 0.5;
let outer = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let mut result = outer;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let x = (n_f32(c)).mul_add(pitch, x_start);
let z = (n_f32(r)).mul_add(pitch, z_start);
let cell = translate(
box3d(cell_h_side, cell_hy, cell_h_side),
Vec3::new(x, cell_offset_y, z),
);
cutters.push(cell);
}
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct VesaMountSpec {
pub vesa_size: f32,
pub plate_thickness: f32,
pub plate_margin: f32,
pub corner_radius: f32,
pub hole_size: crate::stdlib::hardsurface::fastener::MetricSize,
pub bore_kind: u8,
}
impl VesaMountSpec {
#[must_use]
pub const fn vesa_75_m4_cb() -> Self {
Self {
vesa_size: 75.0,
plate_thickness: 5.0,
plate_margin: 15.0,
corner_radius: 3.0,
hole_size: crate::stdlib::hardsurface::fastener::MetricSize::M4,
bore_kind: 1,
}
}
#[must_use]
pub const fn vesa_100_m5_cb() -> Self {
Self {
vesa_size: 100.0,
plate_thickness: 6.0,
plate_margin: 15.0,
corner_radius: 3.0,
hole_size: crate::stdlib::hardsurface::fastener::MetricSize::M5,
bore_kind: 1,
}
}
}
#[must_use]
pub fn vesa_mount(spec: &VesaMountSpec) -> SdfNode {
use crate::stdlib::hardsurface::cavity::{
subtract_through_counterbore, subtract_through_countersink, subtract_through_screw_hole,
};
let plate_extent = 2.0f32.mul_add(spec.plate_margin, spec.vesa_size);
let outer_hx = plate_extent * 0.5;
let outer_hy = spec.plate_thickness * 0.5;
let outer_hz = plate_extent * 0.5;
let plate = rounded_box(outer_hx, outer_hy, outer_hz, spec.corner_radius);
let half_pcd = spec.vesa_size * 0.5;
let corners = [
(half_pcd, half_pcd),
(-half_pcd, half_pcd),
(half_pcd, -half_pcd),
(-half_pcd, -half_pcd),
];
let mut result = plate;
for (x, z) in corners {
result = match spec.bore_kind {
1 => subtract_through_counterbore(result, spec.hole_size, spec.plate_thickness, x, z),
2 => subtract_through_countersink(result, spec.hole_size, spec.plate_thickness, x, z),
_ => subtract_through_screw_hole(result, spec.hole_size, spec.plate_thickness, x, z),
};
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LBracketSpec {
pub arm_width: f32,
pub arm_height: f32,
pub plate_thickness: f32,
pub depth: f32,
pub fillet_radius: f32,
pub hole_size: crate::stdlib::hardsurface::fastener::MetricSize,
pub holes_per_arm: u32,
pub bore_kind: u8,
}
impl LBracketSpec {
#[must_use]
pub const fn m4_2holes() -> Self {
Self {
arm_width: 60.0,
arm_height: 60.0,
plate_thickness: 4.0,
depth: 40.0,
fillet_radius: 3.0,
hole_size: crate::stdlib::hardsurface::fastener::MetricSize::M4,
holes_per_arm: 2,
bore_kind: 0,
}
}
#[must_use]
pub const fn m5_3holes() -> Self {
Self {
arm_width: 80.0,
arm_height: 80.0,
plate_thickness: 5.0,
depth: 50.0,
fillet_radius: 4.0,
hole_size: crate::stdlib::hardsurface::fastener::MetricSize::M5,
holes_per_arm: 3,
bore_kind: 0,
}
}
}
#[must_use]
pub fn l_bracket(spec: &LBracketSpec) -> SdfNode {
use crate::stdlib::hardsurface::fastener::{counterbore, screw_hole};
use crate::stdlib::hardsurface::mount::bracket_l;
let arm_width = crate::det_math::max(spec.arm_width, spec.plate_thickness + 1.0);
let arm_height = crate::det_math::max(spec.arm_height, spec.plate_thickness + 1.0);
let holes = spec.holes_per_arm.clamp(1, 4);
let bracket = bracket_l(
arm_width,
arm_height,
spec.plate_thickness,
spec.depth,
spec.fillet_radius,
);
let make_hole = |plate_thickness: f32| -> SdfNode {
if spec.bore_kind == 1 {
counterbore(spec.hole_size, plate_thickness)
} else {
screw_hole(spec.hole_size, plate_thickness + 10.0)
}
};
let neg_z_margin = spec
.hole_size
.head_diameter_socket()
.mul_add(-0.5, -spec.plate_thickness);
let usable_z = crate::det_math::max(2.0f32.mul_add(neg_z_margin, spec.depth), 0.0);
let step_z = if holes > 1 {
usable_z / n_f32(holes - 1)
} else {
0.0
};
let start_z = -usable_z * 0.5;
let horizontal_hole_x =
(arm_width - spec.plate_thickness - spec.hole_size.head_diameter_socket()) * 0.5;
let vertical_hole_y = f32::midpoint(arm_height, spec.plate_thickness);
let vertical_hole_x = -(arm_width - spec.plate_thickness) * 0.5;
let mut result = bracket;
for i in 0..holes {
let z = start_z + step_z * n_f32(i);
let h_hole = make_hole(spec.plate_thickness);
result = subtract(
result,
translate(h_hole, Vec3::new(horizontal_hole_x, 0.0, z)),
);
let v_hole_raw = make_hole(spec.plate_thickness);
let v_hole_rotated = SdfNode::Rotate {
child: Arc::new(v_hole_raw),
rotation: crate::angle::quat_from_rotation_z(std::f32::consts::FRAC_PI_2),
};
result = subtract(
result,
translate(
v_hole_rotated,
Vec3::new(vertical_hole_x, vertical_hole_y, z),
),
);
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TSlotBracket2020Spec {
pub arm_size: f32,
pub depth: f32,
pub plate_thickness: f32,
pub fillet_radius: f32,
}
impl TSlotBracket2020Spec {
#[must_use]
pub const fn standard_20() -> Self {
Self {
arm_size: 20.0,
depth: 20.0,
plate_thickness: 3.0,
fillet_radius: 3.0,
}
}
#[must_use]
pub const fn heavy_40() -> Self {
Self {
arm_size: 40.0,
depth: 40.0,
plate_thickness: 4.0,
fillet_radius: 4.0,
}
}
}
#[must_use]
pub fn t_slot_bracket_2020(spec: &TSlotBracket2020Spec) -> SdfNode {
use crate::stdlib::hardsurface::fastener::{counterbore, MetricSize};
use crate::stdlib::hardsurface::mount::bracket_l;
let arm = crate::det_math::max(spec.arm_size, spec.plate_thickness + 5.0);
let bracket = bracket_l(
arm,
arm,
spec.plate_thickness,
spec.depth,
spec.fillet_radius,
);
let hole = counterbore(MetricSize::M5, spec.plate_thickness);
let h_hole = translate(hole.clone(), Vec3::new(0.0, 0.0, 0.0));
let v_hole_rotated = SdfNode::Rotate {
child: Arc::new(hole),
rotation: crate::angle::quat_from_rotation_z(std::f32::consts::FRAC_PI_2),
};
let v_hole = translate(
v_hole_rotated,
Vec3::new(
-(arm - spec.plate_thickness) * 0.5,
f32::midpoint(arm, spec.plate_thickness),
0.0,
),
);
let result = subtract(subtract(bracket, h_hole), v_hole);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RaspiMountPlateSpec {
pub model: u32,
pub extra_m4_holes: u32,
pub plate_thickness: f32,
pub plate_margin: f32,
pub m25_hole_dia: f32,
}
impl RaspiMountPlateSpec {
#[must_use]
pub const fn pi_4b_vesa() -> Self {
Self {
model: 4,
extra_m4_holes: 4,
plate_thickness: 4.0,
plate_margin: 15.0,
m25_hole_dia: 2.8,
}
}
#[must_use]
pub const fn pi_zero_bare() -> Self {
Self {
model: 0,
extra_m4_holes: 0,
plate_thickness: 3.0,
plate_margin: 10.0,
m25_hole_dia: 2.8,
}
}
}
#[must_use]
pub fn raspi_mount_plate(spec: &RaspiMountPlateSpec) -> SdfNode {
use crate::stdlib::hardsurface::cavity::{
subtract_through_cylinder, subtract_through_screw_hole,
};
use crate::stdlib::hardsurface::fastener::MetricSize;
let (pattern_x, pattern_z) = match spec.model {
0 => (58.0_f32, 23.0_f32),
_ => (58.0, 49.0),
};
let footprint_x = pattern_x + 20.0;
let footprint_z = pattern_z + 20.0;
let plate_extent_x = 2.0f32.mul_add(spec.plate_margin, footprint_x);
let plate_extent_z = 2.0f32.mul_add(spec.plate_margin, footprint_z);
let outer_hx = plate_extent_x * 0.5;
let outer_hy = spec.plate_thickness * 0.5;
let outer_hz = plate_extent_z * 0.5;
let plate = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let half_x = pattern_x * 0.5;
let half_z = pattern_z * 0.5;
let mut result = plate;
for (x, z) in [
(half_x, half_z),
(-half_x, half_z),
(half_x, -half_z),
(-half_x, -half_z),
] {
result = subtract_through_cylinder(result, spec.m25_hole_dia, spec.plate_thickness, x, z);
}
if spec.extra_m4_holes >= 4 {
let vesa_x = spec.plate_margin.mul_add(-0.5, plate_extent_x * 0.5);
let vesa_z = spec.plate_margin.mul_add(-0.5, plate_extent_z * 0.5);
let neg_vesa_x = spec.plate_margin.mul_add(0.5, -(plate_extent_x * 0.5));
let neg_vesa_z = spec.plate_margin.mul_add(0.5, -(plate_extent_z * 0.5));
for (x, z) in [
(vesa_x, vesa_z),
(neg_vesa_x, vesa_z),
(vesa_x, neg_vesa_z),
(neg_vesa_x, neg_vesa_z),
] {
result =
subtract_through_screw_hole(result, MetricSize::M4, spec.plate_thickness, x, z);
}
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct HeatSetArraySpec {
pub rows: u32,
pub cols: u32,
pub insert_size: crate::stdlib::hardsurface::fastener::MetricSize,
pub pitch: f32,
pub base_thickness: f32,
pub margin: f32,
}
impl HeatSetArraySpec {
#[must_use]
pub const fn m3_2x2() -> Self {
Self {
rows: 2,
cols: 2,
insert_size: crate::stdlib::hardsurface::fastener::MetricSize::M3,
pitch: 20.0,
base_thickness: 6.0,
margin: 10.0,
}
}
#[must_use]
pub const fn m4_3x3() -> Self {
Self {
rows: 3,
cols: 3,
insert_size: crate::stdlib::hardsurface::fastener::MetricSize::M4,
pitch: 25.0,
base_thickness: 8.0,
margin: 10.0,
}
}
}
#[must_use]
pub fn heat_set_array(spec: &HeatSetArraySpec) -> SdfNode {
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let plate_extent_x = 2.0f32.mul_add(spec.margin, (cols_f - 1.0) * spec.pitch);
let plate_extent_z = 2.0f32.mul_add(spec.margin, (rows_f - 1.0) * spec.pitch);
let outer_hx = plate_extent_x * 0.5;
let outer_hy = spec.base_thickness * 0.5;
let outer_hz = plate_extent_z * 0.5;
let plate = rounded_box(outer_hx, outer_hy, outer_hz, 2.0);
let start_x = -(cols_f - 1.0) * spec.pitch * 0.5;
let start_z = -(rows_f - 1.0) * spec.pitch * 0.5;
let mut cutters: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let cx = (n_f32(c)).mul_add(spec.pitch, start_x);
let cz = (n_f32(r)).mul_add(spec.pitch, start_z);
cutters.push(super::cavity::blind_heat_set_cutter(
spec.insert_size,
spec.base_thickness,
cx,
cz,
));
}
}
let result = match super::balanced_union_fold(cutters) {
Some(all) => subtract(plate, all),
None => plate,
};
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FlangeMountSpec {
pub outer_dia: f32,
pub bolt_size: crate::stdlib::hardsurface::fastener::MetricSize,
pub hole_count: u32,
pub thickness: f32,
pub bcd_ratio: f32,
pub center_bore_dia: f32,
}
impl FlangeMountSpec {
#[must_use]
pub const fn od80_m5_4() -> Self {
Self {
outer_dia: 80.0,
bolt_size: crate::stdlib::hardsurface::fastener::MetricSize::M5,
hole_count: 4,
thickness: 6.0,
bcd_ratio: 0.7,
center_bore_dia: 0.0,
}
}
#[must_use]
pub const fn od100_m6_6() -> Self {
Self {
outer_dia: 100.0,
bolt_size: crate::stdlib::hardsurface::fastener::MetricSize::M6,
hole_count: 6,
thickness: 8.0,
bcd_ratio: 0.7,
center_bore_dia: 30.0,
}
}
}
#[must_use]
pub fn flange_mount(spec: &FlangeMountSpec) -> SdfNode {
use crate::stdlib::hardsurface::fastener::CLEARANCE_H2D_FDM;
use crate::stdlib::hardsurface::mount::flange_circular;
let bolt_dia = spec.bolt_size.nominal_diameter() + CLEARANCE_H2D_FDM;
let pcd = spec.outer_dia * spec.bcd_ratio;
let count = spec.hole_count.max(1);
let flange = flange_circular(
spec.outer_dia,
spec.center_bore_dia,
spec.thickness,
pcd,
count,
bolt_dia,
);
to_z_up(flange)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DovetailPairSpec {
pub base_width: f32,
pub height: f32,
pub depth: f32,
pub gender: u8,
pub female_margin: f32,
pub female_plate_thickness: f32,
}
impl DovetailPairSpec {
#[must_use]
pub const fn male_20() -> Self {
Self {
base_width: 20.0,
height: 15.0,
depth: 10.0,
gender: 0,
female_margin: 10.0,
female_plate_thickness: 8.0,
}
}
#[must_use]
pub const fn female_20() -> Self {
Self {
base_width: 20.0,
height: 15.0,
depth: 10.0,
gender: 1,
female_margin: 10.0,
female_plate_thickness: 8.0,
}
}
}
#[must_use]
pub fn dovetail_pair(spec: &DovetailPairSpec) -> SdfNode {
use crate::stdlib::hardsurface::joint::dovetail;
let tenon = dovetail(spec.base_width, spec.height, spec.depth);
if spec.gender == 0 {
to_z_up(tenon)
} else {
let outer_w = 2.0f32.mul_add(spec.female_margin, spec.base_width);
let outer_h = spec.height + spec.female_plate_thickness;
let outer = box3d(outer_w * 0.5, outer_h * 0.5, spec.depth * 0.5);
let pocket = dovetail(spec.base_width + 0.3, spec.height + 0.15, spec.depth + 0.2);
let pocket_placed = translate(
pocket,
Vec3::new(0.0, -(spec.female_plate_thickness) * 0.5, 0.0),
);
to_z_up(subtract(outer, pocket_placed))
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ProfileExtrusionSpec {
pub kind: u32,
pub length: f32,
}
impl ProfileExtrusionSpec {
#[must_use]
pub const fn p2020_100() -> Self {
Self {
kind: 20,
length: 100.0,
}
}
#[must_use]
pub const fn p3030_100() -> Self {
Self {
kind: 30,
length: 100.0,
}
}
}
#[must_use]
pub fn profile_extrusion(spec: &ProfileExtrusionSpec) -> SdfNode {
use crate::stdlib::hardsurface::mount::{profile_2020, profile_3030};
let profile = if spec.kind >= 30 {
profile_3030(spec.length)
} else {
profile_2020(spec.length)
};
to_z_up(profile)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct SnapFitPairSpec {
pub length: f32,
pub width: f32,
pub thickness: f32,
pub hook_height: f32,
}
impl SnapFitPairSpec {
#[must_use]
pub const fn standard() -> Self {
Self {
length: 20.0,
width: 5.0,
thickness: 2.0,
hook_height: 1.0,
}
}
}
#[must_use]
pub fn snap_fit_pair(spec: &SnapFitPairSpec) -> SdfNode {
use crate::stdlib::hardsurface::joint::{snap_fit_cantilever, SnapFitCantileverSpec};
let joint_spec = SnapFitCantileverSpec {
length: spec.length,
width: spec.width,
thickness: spec.thickness,
hook_height: spec.hook_height,
hook_offset: spec.hook_height * 3.0,
};
to_z_up(snap_fit_cantilever(joint_spec))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BossArraySpec {
pub rows: u32,
pub cols: u32,
pub screw_size: crate::stdlib::hardsurface::fastener::MetricSize,
pub boss_height: f32,
pub pitch: f32,
pub base_thickness: f32,
}
impl BossArraySpec {
#[must_use]
pub const fn m3_2x2() -> Self {
Self {
rows: 2,
cols: 2,
screw_size: crate::stdlib::hardsurface::fastener::MetricSize::M3,
boss_height: 10.0,
pitch: 20.0,
base_thickness: 2.0,
}
}
#[must_use]
pub const fn m4_3x3() -> Self {
Self {
rows: 3,
cols: 3,
screw_size: crate::stdlib::hardsurface::fastener::MetricSize::M4,
boss_height: 15.0,
pitch: 25.0,
base_thickness: 3.0,
}
}
}
#[must_use]
pub fn boss_array(spec: &BossArraySpec) -> SdfNode {
use crate::stdlib::hardsurface::reinforcement::boss;
let rows = spec.rows.max(1);
let cols = spec.cols.max(1);
let rows_f = n_f32(rows);
let cols_f = n_f32(cols);
let plate_extent_x = (cols_f - 1.0).mul_add(spec.pitch, spec.pitch);
let plate_extent_z = (rows_f - 1.0).mul_add(spec.pitch, spec.pitch);
let outer_hx = plate_extent_x * 0.5;
let outer_hy = spec.base_thickness * 0.5;
let outer_hz = plate_extent_z * 0.5;
let plate = box3d(outer_hx, outer_hy, outer_hz);
let screw_nominal = spec.screw_size.nominal_diameter();
let boss_body_height = raw_max_f32(
spec.boss_height - spec.base_thickness,
1.0,
crate::raw_minmax::Reason::NonzeroConst,
);
let boss_template = boss(screw_nominal, boss_body_height);
let boss_y = outer_hy + boss_body_height * 0.5;
let start_x = -(cols_f - 1.0) * spec.pitch * 0.5;
let start_z = -(rows_f - 1.0) * spec.pitch * 0.5;
let mut bosses: Vec<SdfNode> = Vec::new();
for r in 0..rows {
for c in 0..cols {
let cx = (n_f32(c)).mul_add(spec.pitch, start_x);
let cz = (n_f32(r)).mul_add(spec.pitch, start_z);
bosses.push(translate(boss_template.clone(), Vec3::new(cx, boss_y, cz)));
}
}
let result = match super::balanced_union_fold(bosses) {
Some(all) => union(plate, all),
None => plate,
};
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum BearingKind {
B608,
B688,
B6001,
B6202,
}
impl BearingKind {
#[must_use]
pub const fn outer_dia(self) -> f32 {
match self {
Self::B608 => 22.0,
Self::B688 => 16.0,
Self::B6001 => 28.0,
Self::B6202 => 35.0,
}
}
#[must_use]
pub const fn inner_dia(self) -> f32 {
match self {
Self::B608 | Self::B688 => 8.0,
Self::B6001 => 12.0,
Self::B6202 => 15.0,
}
}
#[must_use]
pub const fn width(self) -> f32 {
match self {
Self::B608 => 7.0,
Self::B688 => 5.0,
Self::B6001 => 8.0,
Self::B6202 => 11.0,
}
}
#[must_use]
pub fn from_f32_snap(size: f32) -> Self {
let candidates = [
(16.0_f32, Self::B688),
(22.0, Self::B608),
(28.0, Self::B6001),
(35.0, Self::B6202),
];
let mut best = Self::B608;
let mut best_dist = f32::INFINITY;
for (od, kind) in candidates {
let d = (od - size).abs();
if d < best_dist {
best_dist = d;
best = kind;
}
}
best
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BearingSeatSpec {
pub bearing: BearingKind,
pub plate_thickness: f32,
pub plate_margin: f32,
pub corner_radius: f32,
pub style: u8,
}
impl BearingSeatSpec {
#[must_use]
pub const fn b608_press_fit() -> Self {
Self {
bearing: BearingKind::B608,
plate_thickness: 5.0,
plate_margin: 10.0,
corner_radius: 3.0,
style: 0,
}
}
#[must_use]
pub const fn b608_slip_fit() -> Self {
Self {
bearing: BearingKind::B608,
plate_thickness: 5.0,
plate_margin: 10.0,
corner_radius: 3.0,
style: 1,
}
}
#[must_use]
pub const fn b688_press_fit() -> Self {
Self {
bearing: BearingKind::B688,
plate_thickness: 4.0,
plate_margin: 8.0,
corner_radius: 2.5,
style: 0,
}
}
}
#[must_use]
pub fn bearing_seat(spec: &BearingSeatSpec) -> SdfNode {
use crate::stdlib::hardsurface::cavity::{subtract_blind_pocket, subtract_through_cylinder};
let od = spec.bearing.outer_dia();
let id = spec.bearing.inner_dia();
let width = spec.bearing.width();
let plate_extent = 2.0f32.mul_add(spec.plate_margin, od);
let outer_hx = plate_extent * 0.5;
let outer_hy = spec.plate_thickness * 0.5;
let outer_hz = plate_extent * 0.5;
let plate = rounded_box(outer_hx, outer_hy, outer_hz, spec.corner_radius);
let pocket_dia = match spec.style {
0 => od - 0.05, _ => od + 0.1, };
let with_pocket = if spec.style == 2 {
let physical_depth =
crate::det_math::max(crate::det_math::min(width, spec.plate_thickness - 1.0), 1.0);
subtract_blind_pocket(
plate,
pocket_dia,
spec.plate_thickness,
physical_depth,
0.0,
0.0,
)
} else {
subtract_through_cylinder(plate, pocket_dia, spec.plate_thickness, 0.0, 0.0)
};
let shaft_dia = id + 0.5; let result = subtract_through_cylinder(with_pocket, shaft_dia, spec.plate_thickness, 0.0, 0.0);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CableGrommetSpec {
pub outer_dia: f32,
pub height: f32,
pub flange_thickness: f32,
pub flange_overhang: f32,
pub inner_dia: f32,
}
impl CableGrommetSpec {
#[must_use]
pub const fn standard_60() -> Self {
Self {
outer_dia: 60.0,
height: 15.0,
flange_thickness: 3.0,
flange_overhang: 4.0,
inner_dia: 50.0,
}
}
#[must_use]
pub const fn large_80() -> Self {
Self {
outer_dia: 80.0,
height: 20.0,
flange_thickness: 4.0,
flange_overhang: 5.0,
inner_dia: 68.0,
}
}
}
#[must_use]
pub fn cable_grommet(spec: &CableGrommetSpec) -> SdfNode {
let body_r = spec.outer_dia * 0.5;
let body_hh = spec.height * 0.5;
let flange_r = body_r + spec.flange_overhang;
let flange_hh = spec.flange_thickness * 0.5;
let inner_r = spec.inner_dia * 0.5;
let body = cylinder(body_r, body_hh);
let flange = translate(
cylinder(flange_r, flange_hh),
Vec3::new(0.0, body_hh + flange_hh, 0.0),
);
let outer = union(body, flange);
let punch_hh = f32::midpoint(spec.height, spec.flange_thickness) + 5.0;
let punch = cylinder(inner_r, punch_hh);
to_z_up(subtract(outer, punch))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CurtainRodBracketSpec {
pub rod_dia: f32,
pub projection: f32,
pub wall_plate_w: f32,
pub wall_plate_h: f32,
pub plate_thickness: f32,
pub arm_thickness: f32,
pub arm_width: f32,
pub wall_screw: crate::stdlib::hardsurface::fastener::MetricSize,
}
impl CurtainRodBracketSpec {
#[must_use]
pub const fn standard_25() -> Self {
Self {
rod_dia: 25.0,
projection: 120.0,
wall_plate_w: 40.0,
wall_plate_h: 80.0,
plate_thickness: 4.0,
arm_thickness: 8.0,
arm_width: 20.0,
wall_screw: crate::stdlib::hardsurface::fastener::MetricSize::M4,
}
}
#[must_use]
pub const fn large_30() -> Self {
Self {
rod_dia: 30.0,
projection: 150.0,
wall_plate_w: 50.0,
wall_plate_h: 100.0,
plate_thickness: 5.0,
arm_thickness: 10.0,
arm_width: 25.0,
wall_screw: crate::stdlib::hardsurface::fastener::MetricSize::M5,
}
}
}
#[must_use]
pub fn curtain_rod_bracket(spec: &CurtainRodBracketSpec) -> SdfNode {
use crate::stdlib::hardsurface::fastener::counterbore;
let plate_hx = spec.plate_thickness * 0.5;
let plate_hy = spec.wall_plate_h * 0.5;
let plate_hz = spec.wall_plate_w * 0.5;
let wall_plate = box3d(plate_hx, plate_hy, plate_hz);
let arm_hx = spec.projection * 0.5;
let arm_hy = spec.arm_thickness * 0.5;
let arm_hz = spec.arm_width * 0.5;
let arm_raw = box3d(arm_hx, arm_hy, arm_hz);
let arm = translate(arm_raw, Vec3::new(plate_hx + arm_hx, 0.0, 0.0));
let cradle_r = f32::midpoint(spec.rod_dia, 0.5);
let cradle_hh = spec.arm_width.mul_add(0.5, 2.0);
let cradle_x = plate_hx + spec.projection;
let cradle_y = arm_hy + cradle_r;
let cradle_hole = SdfNode::Rotate {
child: Arc::new(cylinder(cradle_r, cradle_hh)),
rotation: crate::angle::quat_from_rotation_x(std::f32::consts::FRAC_PI_2),
};
let cradle_body_r = cradle_r + 3.0; let cradle_body = SdfNode::Rotate {
child: Arc::new(cylinder(cradle_body_r, cradle_hh)),
rotation: crate::angle::quat_from_rotation_x(std::f32::consts::FRAC_PI_2),
};
let cradle_placed = translate(cradle_body, Vec3::new(cradle_x, cradle_y, 0.0));
let hole_placed = translate(cradle_hole, Vec3::new(cradle_x, cradle_y, 0.0));
let bore = counterbore(spec.wall_screw, spec.plate_thickness);
let bore_rotated = SdfNode::Rotate {
child: Arc::new(bore),
rotation: crate::angle::quat_from_rotation_z(std::f32::consts::FRAC_PI_2),
};
let bore_offset_y = plate_hy - 12.0;
let bore_offset_z = plate_hz - 8.0;
let bore_positions = [
Vec3::new(0.0, bore_offset_y, bore_offset_z),
Vec3::new(0.0, bore_offset_y, -bore_offset_z),
Vec3::new(0.0, -bore_offset_y, bore_offset_z),
Vec3::new(0.0, -bore_offset_y, -bore_offset_z),
];
let assembly = union(union(wall_plate, arm), cradle_placed);
let mut result = assembly;
let mut cutters: Vec<SdfNode> = Vec::new();
for pos in bore_positions {
cutters.push(translate(bore_rotated.clone(), pos));
}
if let Some(all) = super::balanced_union_fold(cutters) {
result = subtract(result, all);
}
result = subtract(result, hole_placed);
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ArduinoBoard {
Uno,
Mega,
Nano,
}
impl ArduinoBoard {
#[must_use]
pub const fn board_width(self) -> f32 {
match self {
Self::Uno => 68.6,
Self::Mega => 101.5,
Self::Nano => 43.2,
}
}
#[must_use]
pub const fn board_height(self) -> f32 {
match self {
Self::Uno | Self::Mega => 53.4,
Self::Nano => 17.8,
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)] pub const fn from_f32_snap(v: f32) -> Self {
match v.round() as i32 {
2 => Self::Mega,
3 => Self::Nano,
_ => Self::Uno,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ArduinoMountPlateSpec {
pub board: ArduinoBoard,
pub plate_thickness: f32,
pub plate_margin: f32,
pub extra_m4_holes: u32,
}
impl ArduinoMountPlateSpec {
#[must_use]
pub const fn uno_bare() -> Self {
Self {
board: ArduinoBoard::Uno,
plate_thickness: 4.0,
plate_margin: 10.0,
extra_m4_holes: 0,
}
}
#[must_use]
pub const fn uno_vesa() -> Self {
Self {
board: ArduinoBoard::Uno,
plate_thickness: 4.0,
plate_margin: 10.0,
extra_m4_holes: 4,
}
}
#[must_use]
pub const fn nano_bare() -> Self {
Self {
board: ArduinoBoard::Nano,
plate_thickness: 3.0,
plate_margin: 8.0,
extra_m4_holes: 0,
}
}
}
#[must_use]
pub fn arduino_mount_plate(spec: &ArduinoMountPlateSpec) -> SdfNode {
use crate::stdlib::hardsurface::cavity::subtract_through_screw_hole;
use crate::stdlib::hardsurface::fastener::MetricSize;
let bw = spec.board.board_width();
let bh = spec.board.board_height();
let plate_extent_x = 2.0f32.mul_add(spec.plate_margin, bw);
let plate_extent_z = 2.0f32.mul_add(spec.plate_margin, bh);
let outer_hx = plate_extent_x * 0.5;
let outer_hy = spec.plate_thickness * 0.5;
let outer_hz = plate_extent_z * 0.5;
let plate = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let hole_x = bw.mul_add(0.5, -3.0);
let hole_z = bh.mul_add(0.5, -3.0);
let neg_hole_x = bw.mul_add(-0.5, 3.0);
let neg_hole_z = bh.mul_add(-0.5, 3.0);
let mut result = plate;
for (x, z) in [
(hole_x, hole_z),
(neg_hole_x, hole_z),
(hole_x, neg_hole_z),
(neg_hole_x, neg_hole_z),
] {
result = subtract_through_screw_hole(result, MetricSize::M3, spec.plate_thickness, x, z);
}
if spec.extra_m4_holes >= 4 {
let vesa_x = spec.plate_margin.mul_add(-0.5, plate_extent_x * 0.5);
let vesa_z = spec.plate_margin.mul_add(-0.5, plate_extent_z * 0.5);
let neg_vesa_x = spec.plate_margin.mul_add(0.5, -(plate_extent_x * 0.5));
let neg_vesa_z = spec.plate_margin.mul_add(0.5, -(plate_extent_z * 0.5));
for (x, z) in [
(vesa_x, vesa_z),
(neg_vesa_x, vesa_z),
(vesa_x, neg_vesa_z),
(neg_vesa_x, neg_vesa_z),
] {
result =
subtract_through_screw_hole(result, MetricSize::M4, spec.plate_thickness, x, z);
}
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PixhawkMountSpec {
pub hole_pattern_size: f32,
pub plate_thickness: f32,
pub plate_margin: f32,
pub damper_style: u8,
}
impl PixhawkMountSpec {
#[must_use]
pub const fn standard_45_solid() -> Self {
Self {
hole_pattern_size: 45.0,
plate_thickness: 4.0,
plate_margin: 12.0,
damper_style: 0,
}
}
#[must_use]
pub const fn standard_45_damper() -> Self {
Self {
hole_pattern_size: 45.0,
plate_thickness: 4.0,
plate_margin: 15.0,
damper_style: 1,
}
}
#[must_use]
pub const fn mini_30_solid() -> Self {
Self {
hole_pattern_size: 30.0,
plate_thickness: 3.0,
plate_margin: 10.0,
damper_style: 0,
}
}
}
#[must_use]
pub fn pixhawk_mount(spec: &PixhawkMountSpec) -> SdfNode {
use crate::stdlib::hardsurface::cavity::{subtract_blind_pocket, subtract_through_screw_hole};
use crate::stdlib::hardsurface::fastener::MetricSize;
let plate_extent = 2.0f32.mul_add(spec.plate_margin, spec.hole_pattern_size);
let outer_hx = plate_extent * 0.5;
let outer_hy = spec.plate_thickness * 0.5;
let outer_hz = plate_extent * 0.5;
let plate = rounded_box(outer_hx, outer_hy, outer_hz, 3.0);
let half_pat = spec.hole_pattern_size * 0.5;
let mut result = plate;
for (x, z) in [
(half_pat, half_pat),
(-half_pat, half_pat),
(half_pat, -half_pat),
(-half_pat, -half_pat),
] {
result = subtract_through_screw_hole(result, MetricSize::M3, spec.plate_thickness, x, z);
}
if spec.damper_style >= 1 {
let damper_dia = 10.0;
let damper_depth = spec.plate_thickness * 0.5;
let damper_x = spec.plate_margin.mul_add(-0.5, plate_extent * 0.5);
let damper_z = spec.plate_margin.mul_add(-0.5, plate_extent * 0.5);
let neg_damper_x = spec.plate_margin.mul_add(0.5, -(plate_extent * 0.5));
let neg_damper_z = spec.plate_margin.mul_add(0.5, -(plate_extent * 0.5));
for (x, z) in [
(damper_x, damper_z),
(neg_damper_x, damper_z),
(damper_x, neg_damper_z),
(neg_damper_x, neg_damper_z),
] {
result =
subtract_blind_pocket(result, damper_dia, spec.plate_thickness, damper_depth, x, z);
}
}
to_z_up(result)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ServoKind {
Sg90,
Mg996r,
}
impl ServoKind {
#[must_use]
pub const fn body_width(self) -> f32 {
match self {
Self::Sg90 => 23.0,
Self::Mg996r => 40.7,
}
}
#[must_use]
pub const fn body_depth(self) -> f32 {
match self {
Self::Sg90 => 12.5,
Self::Mg996r => 19.7,
}
}
#[must_use]
pub const fn mount_span(self) -> f32 {
match self {
Self::Sg90 => 32.5,
Self::Mg996r => 49.0,
}
}
#[must_use]
#[allow(clippy::cast_possible_truncation)] pub const fn from_f32_snap(v: f32) -> Self {
match v.round() as i32 {
2 => Self::Mg996r,
_ => Self::Sg90,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ServoMountSpec {
pub servo: ServoKind,
pub plate_thickness: f32,
pub flange_margin: f32,
}
impl ServoMountSpec {
#[must_use]
pub const fn sg90_standard() -> Self {
Self {
servo: ServoKind::Sg90,
plate_thickness: 3.0,
flange_margin: 5.0,
}
}
#[must_use]
pub const fn mg996r_standard() -> Self {
Self {
servo: ServoKind::Mg996r,
plate_thickness: 4.0,
flange_margin: 6.0,
}
}
}
#[must_use]
pub fn servo_mount(spec: &ServoMountSpec) -> SdfNode {
use crate::stdlib::hardsurface::cavity::subtract_through_screw_hole;
use crate::stdlib::hardsurface::fastener::MetricSize;
let plate_x = 2.0f32.mul_add(spec.flange_margin, spec.servo.mount_span());
let plate_z = spec.servo.body_depth() + 4.0;
let outer_hx = plate_x * 0.5;
let outer_hy = spec.plate_thickness * 0.5;
let outer_hz = plate_z * 0.5;
let plate = box3d(outer_hx, outer_hy, outer_hz);
let cutout_hx = f32::midpoint(spec.servo.body_width(), 0.5);
let cutout_hz = f32::midpoint(spec.servo.body_depth(), 0.5);
let cutout = box3d(cutout_hx, outer_hy + 5.0, cutout_hz);
let hole_size = match spec.servo {
ServoKind::Sg90 => MetricSize::M2,
ServoKind::Mg996r => MetricSize::M3,
};
let hole_x = spec.servo.mount_span() * 0.5;
let with_cutout = subtract(plate, cutout);
let with_left =
subtract_through_screw_hole(with_cutout, hole_size, spec.plate_thickness, hole_x, 0.0);
let result =
subtract_through_screw_hole(with_left, hole_size, spec.plate_thickness, -hole_x, 0.0);
to_z_up(result)
}
#[cfg(test)]
mod tests {
use super::*;
use alice_sdf::eval;
#[test]
fn wall_hook_default_produces_smooth_union_tree() {
let hook = wall_hook(&WallHookSpec::pla_1kgf());
assert!(matches!(hook, SdfNode::Subtraction { .. }));
}
#[test]
fn wall_hook_no_mount_holes() {
let mut spec = WallHookSpec::pla_1kgf();
spec.mount_hole_dia = None;
let hook = wall_hook(&spec);
assert!(matches!(hook, SdfNode::SmoothUnion { .. }));
}
#[test]
fn wall_hook_backplate_center_is_inside() {
let hook = wall_hook(&WallHookSpec::pla_1kgf());
assert!(eval(&hook, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn gridfinity_2x2_returns_subtraction() {
let bin = gridfinity_bin(&GridfinitySpec::default_2x2());
assert!(matches!(bin, SdfNode::Subtraction { .. }));
}
#[test]
fn gridfinity_dimensions_match_grid_unit_spec() {
let bin = gridfinity_bin(&GridfinitySpec::default_2x2());
assert!(eval(&bin, Vec3::new(0.0, 0.0, -16.0)) < 0.0);
assert!(eval(&bin, Vec3::new(50.0, 0.0, 0.0)) > 0.0);
}
#[test]
fn gridfinity_spec_validate_accepts_every_dividers_value_in_use_today() {
for (cols, rows) in [(0, 0), (2, 2)] {
let spec = GridfinitySpec {
dividers: Some((cols, rows)),
..GridfinitySpec::default_2x2()
};
assert!(spec.validate().is_ok(), "{cols}x{rows}");
}
}
#[test]
fn gridfinity_spec_validate_refuses_a_dividers_product_over_the_limit() {
let spec = GridfinitySpec {
dividers: Some((1024, 1024)),
..GridfinitySpec::default_2x2()
};
let e = spec.validate().unwrap_err();
assert_eq!(
e.to_string(),
"resource limit exceeded: kind=grid_expansion limit=10000 requested=1048576"
);
}
#[test]
fn try_gridfinity_bin_never_panics_on_a_degenerate_dividers_product() {
let spec = GridfinitySpec {
dividers: Some((1024, 1024)),
..GridfinitySpec::default_2x2()
};
assert!(try_gridfinity_bin(&spec).is_err());
}
#[test]
#[should_panic(expected = "GridfinitySpec が検査を通らない")]
fn gridfinity_bin_panics_with_its_documented_message_on_the_same_degenerate_spec() {
let spec = GridfinitySpec {
dividers: Some((1024, 1024)),
..GridfinitySpec::default_2x2()
};
let _ = gridfinity_bin(&spec);
}
#[test]
fn drawer_default_chopsticks_set_is_subtraction() {
let tray = drawer_organizer(&DrawerSpec::default_chopsticks_set());
assert!(matches!(tray, SdfNode::Subtraction { .. }));
}
#[test]
fn drawer_empty_slots_returns_solid_tray() {
let spec = DrawerSpec {
width: 100.0,
depth: 80.0,
height: 20.0,
slots: Vec::new(),
wall_thickness: 1.5,
floor_thickness: 1.5,
divider_thickness: 1.2,
fillet_radius: 1.0,
};
let tray = drawer_organizer(&spec);
assert!(matches!(tray, SdfNode::RoundedBox { .. }));
}
#[test]
fn shelf_divider_field_tested_spec_is_subtraction() {
let s = shelf_divider(&ShelfDividerSpec::field_tested_560x250x120());
assert!(matches!(s, SdfNode::Subtraction { .. }));
}
#[test]
fn shelf_divider_spec_validate_accepts_the_field_tested_spec() {
assert!(ShelfDividerSpec::field_tested_560x250x120()
.validate()
.is_ok());
}
#[test]
fn shelf_divider_spec_validate_refuses_a_zero_pitch() {
let spec = ShelfDividerSpec {
hex_hole_pitch: 0.0,
..ShelfDividerSpec::field_tested_560x250x120()
};
let e = spec.validate().unwrap_err();
assert_eq!(
e.to_string(),
"resource limit exceeded: kind=pitch limit=0.01 mm value=0 mm"
);
}
#[test]
fn try_shelf_divider_never_hangs_or_panics_on_a_nan_pitch() {
let spec = ShelfDividerSpec {
hex_hole_pitch: f32::NAN,
..ShelfDividerSpec::field_tested_560x250x120()
};
let e = try_shelf_divider(&spec).unwrap_err();
let SpecError::FloatLimit(e) = e else {
panic!("expected FloatLimit, got {e:?}")
};
assert_eq!(e.kind, "pitch");
assert!(e.value.is_nan());
}
#[test]
#[should_panic(expected = "ShelfDividerSpec が検査を通らない")]
fn shelf_divider_panics_with_its_documented_message_on_a_negative_pitch() {
let spec = ShelfDividerSpec {
hex_hole_pitch: -5.0,
..ShelfDividerSpec::field_tested_560x250x120()
};
let _ = shelf_divider(&spec);
}
#[test]
fn shelf_divider_plate_center_is_inside() {
let s = shelf_divider(&ShelfDividerSpec::field_tested_560x250x120());
assert!(
eval(&s, Vec3::new(5.0, 5.0, 2.5)) < 0.0 || eval(&s, Vec3::new(10.0, 5.0, 2.5)) < 0.0
);
}
#[test]
fn all_pattern_sdf_evaluations_finite() {
let nodes = [
wall_hook(&WallHookSpec::pla_1kgf()),
gridfinity_bin(&GridfinitySpec::default_2x2()),
drawer_organizer(&DrawerSpec::default_chopsticks_set()),
shelf_divider(&ShelfDividerSpec::field_tested_560x250x120()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "pattern {i} produced non-finite SDF: {d}");
}
}
#[test]
fn sticky_note_holder_small_square_is_subtraction() {
let h = sticky_note_holder(&StickyNoteHolderSpec::small_square());
assert!(matches!(h, SdfNode::Subtraction { .. }));
}
#[test]
fn sticky_note_holder_floor_center_is_inside() {
let h = sticky_note_holder(&StickyNoteHolderSpec::small_square());
assert!(eval(&h, Vec3::new(0.0, 0.0, -14.0)) < 0.0);
}
#[test]
fn business_card_holder_jp_meishi_is_subtraction() {
let h = business_card_holder(&BusinessCardHolderSpec::jp_meishi());
assert!(matches!(h, SdfNode::Subtraction { .. }));
}
#[test]
fn business_card_holder_wall_is_solid() {
let h = business_card_holder(&BusinessCardHolderSpec::jp_meishi());
assert!(eval(&h, Vec3::new(46.0, 0.0, 5.0)) < 0.0);
}
#[test]
fn pen_cup_standard_is_subtraction() {
let cup = pen_cup(&PenCupSpec::standard_75x100());
assert!(matches!(cup, SdfNode::Subtraction { .. }));
}
#[test]
fn pen_cup_wall_is_solid() {
let cup = pen_cup(&PenCupSpec::standard_75x100());
assert!(eval(&cup, Vec3::new(38.5, 0.0, 0.0)) < 0.0);
assert!(eval(&cup, Vec3::new(0.0, 0.0, -49.0)) < 0.0);
}
#[test]
fn phone_stand_default_has_cable_hole() {
let s = phone_stand(&PhoneStandSpec::phone_default());
assert!(matches!(s, SdfNode::Subtraction { .. }));
}
#[test]
fn phone_stand_no_cable_hole_is_slot_subtract() {
let mut spec = PhoneStandSpec::phone_default();
spec.cable_hole_dia = None;
let s = phone_stand(&spec);
assert!(matches!(s, SdfNode::Subtraction { .. }));
}
#[test]
fn all_part2_archetypes_evaluations_finite() {
let nodes = [
sticky_note_holder(&StickyNoteHolderSpec::small_square()),
business_card_holder(&BusinessCardHolderSpec::jp_meishi()),
pen_cup(&PenCupSpec::standard_75x100()),
phone_stand(&PhoneStandSpec::phone_default()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"PART 2 archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn headphone_holder_default_is_subtraction() {
let h = headphone_holder(&HeadphoneHolderSpec::wall_mount_default());
assert!(matches!(h, SdfNode::Subtraction { .. }));
}
#[test]
fn headphone_holder_no_holes_is_smooth_union() {
let mut spec = HeadphoneHolderSpec::wall_mount_default();
spec.mount_hole_dia = None;
let h = headphone_holder(&spec);
assert!(matches!(h, SdfNode::SmoothUnion { .. }));
}
#[test]
fn under_desk_mount_default_is_subtraction() {
let m = under_desk_mount(&UnderDeskMountSpec::standard_desk());
assert!(matches!(m, SdfNode::Subtraction { .. }));
}
#[test]
fn under_desk_mount_no_screw_is_smooth_union() {
let mut spec = UnderDeskMountSpec::standard_desk();
spec.screw_hole_dia = None;
let m = under_desk_mount(&spec);
assert!(matches!(m, SdfNode::SmoothUnion { .. }));
}
#[test]
fn desk_shelf_default_is_rotate_flipped() {
let s = desk_shelf(&DeskShelfSpec::desktop_400x200());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn desk_shelf_leg_position_is_inside() {
let s = desk_shelf(&DeskShelfSpec::desktop_400x200());
assert!(eval(&s, Vec3::new(-190.0, 0.0, -50.0)) < 0.0);
}
#[test]
fn monitor_riser_default_has_cable_hole() {
let r = monitor_riser(&MonitorRiserSpec::compact_desk());
assert!(matches!(r, SdfNode::Subtraction { .. }));
}
#[test]
fn monitor_riser_no_cable_is_smooth_union() {
let mut spec = MonitorRiserSpec::compact_desk();
spec.cable_hole_dia = None;
let r = monitor_riser(&spec);
assert!(matches!(r, SdfNode::SmoothUnion { .. }));
}
#[test]
fn all_part2_b2_archetypes_evaluations_finite() {
let nodes = [
headphone_holder(&HeadphoneHolderSpec::wall_mount_default()),
under_desk_mount(&UnderDeskMountSpec::standard_desk()),
desk_shelf(&DeskShelfSpec::desktop_400x200()),
monitor_riser(&MonitorRiserSpec::compact_desk()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"PART 2 B2 archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn coaster_default_is_subtraction() {
let c = coaster(&CoasterSpec::round_95x5());
assert!(matches!(c, SdfNode::Subtraction { .. }));
}
#[test]
fn coaster_rim_is_solid() {
let c = coaster(&CoasterSpec::round_95x5());
assert!(eval(&c, Vec3::new(46.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn coaster_bottom_is_solid() {
let c = coaster(&CoasterSpec::round_95x5());
assert!(eval(&c, Vec3::new(0.0, -2.0, 0.0)) < 0.0);
}
#[test]
fn tissue_box_cover_default_is_rotate_flipped() {
let t = tissue_box_cover(&TissueBoxCoverSpec::rectangular_us());
assert!(matches!(t, SdfNode::Rotate { .. }));
}
#[test]
fn tissue_box_cover_top_wall_at_slot_center_is_hollow() {
let t = tissue_box_cover(&TissueBoxCoverSpec::rectangular_us());
assert!(eval(&t, Vec3::new(0.0, 0.0, -27.5)) > 0.0);
}
#[test]
fn storage_box_default_is_rotate_wrapped() {
let b = storage_box(&StorageBoxSpec::medium());
assert!(matches!(b, SdfNode::Rotate { .. }));
}
#[test]
fn storage_box_floor_is_solid() {
let b = storage_box(&StorageBoxSpec::medium());
assert!(eval(&b, Vec3::new(0.0, 0.0, -30.0)) < 0.0);
}
#[test]
fn all_household_archetypes_evaluations_finite() {
let nodes = [
coaster(&CoasterSpec::round_95x5()),
tissue_box_cover(&TissueBoxCoverSpec::rectangular_us()),
storage_box(&StorageBoxSpec::medium()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"household archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn cable_clip_hdmi_is_subtraction() {
let c = cable_clip(&CableClipSpec::hdmi());
assert!(matches!(c, SdfNode::Subtraction { .. }));
}
#[test]
fn cable_clip_wall_is_solid() {
let c = cable_clip(&CableClipSpec::hdmi());
assert!(eval(&c, Vec3::new(0.0, 0.0, -5.0)) < 0.0);
}
#[test]
fn cable_clip_top_is_open() {
let c = cable_clip(&CableClipSpec::hdmi());
assert!(eval(&c, Vec3::new(0.0, 0.0, 5.0)) > 0.0);
}
#[test]
fn led_channel_ws2812b_is_subtraction() {
let c = led_channel(&LedChannelSpec::ws2812b_10mm());
assert!(matches!(c, SdfNode::Subtraction { .. }));
}
#[test]
fn led_channel_floor_is_solid() {
let c = led_channel(&LedChannelSpec::ws2812b_10mm());
assert!(eval(&c, Vec3::new(0.0, 0.0, -1.5)) < 0.0);
}
#[test]
fn led_channel_top_is_open() {
let c = led_channel(&LedChannelSpec::ws2812b_10mm());
assert!(eval(&c, Vec3::new(0.0, 0.0, 1.5)) > 0.0);
}
#[test]
fn card_tray_poker_is_rotate_wrapped() {
let t = card_tray(&CardTraySpec::poker());
assert!(matches!(t, SdfNode::Rotate { .. }));
}
#[test]
fn card_tray_side_wall_is_solid() {
let t = card_tray(&CardTraySpec::poker());
assert!(eval(&t, Vec3::new(33.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn token_well_dice_is_rotate_wrapped() {
let t = token_well(&TokenWellSpec::dice_4());
assert!(matches!(t, SdfNode::Rotate { .. }));
}
#[test]
fn token_well_floor_is_solid() {
let t = token_well(&TokenWellSpec::dice_4());
assert!(eval(&t, Vec3::new(0.0, 0.0, -10.0)) < 0.0);
}
#[test]
fn token_well_center_of_well_is_open() {
let t = token_well(&TokenWellSpec::dice_4());
assert!(eval(&t, Vec3::new(-33.0, 0.0, 0.0)) > 0.0);
}
#[test]
fn all_hobby_diy_archetypes_evaluations_finite() {
let nodes = [
cable_clip(&CableClipSpec::hdmi()),
led_channel(&LedChannelSpec::ws2812b_10mm()),
card_tray(&CardTraySpec::poker()),
token_well(&TokenWellSpec::dice_4()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"hobby-diy archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn wrench_holder_metric_6_is_rotate_wrapped() {
let w = wrench_holder(&WrenchHolderSpec::metric_6_8to19());
assert!(matches!(w, SdfNode::Rotate { .. }));
}
#[test]
fn wrench_holder_wall_between_slots_is_solid() {
let w = wrench_holder(&WrenchHolderSpec::metric_6_8to19());
assert!(eval(&w, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn socket_rail_half_inch_is_rotate_wrapped() {
let s = socket_rail(&SocketRailSpec::half_inch_6());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn socket_rail_post_center_is_solid() {
let s = socket_rail(&SocketRailSpec::half_inch_6());
assert!(eval(&s, Vec3::new(-46.0, 0.0, 12.9)) < 0.0);
}
#[test]
fn hex_bit_holder_grid_4x5_is_subtraction() {
let h = hex_bit_holder(&HexBitHolderSpec::grid_4x5());
assert!(matches!(h, SdfNode::Subtraction { .. }));
}
#[test]
fn hex_bit_holder_floor_is_solid() {
let h = hex_bit_holder(&HexBitHolderSpec::grid_4x5());
assert!(eval(&h, Vec3::new(0.0, 0.0, -7.0)) < 0.0);
}
#[test]
fn hex_bit_holder_wall_between_holes_is_solid() {
let h = hex_bit_holder(&HexBitHolderSpec::grid_4x5());
assert!(eval(&h, Vec3::new(6.0, -18.0, 5.0)) < 0.0);
}
#[test]
fn all_tools_archetypes_evaluations_finite() {
let nodes = [
wrench_holder(&WrenchHolderSpec::metric_6_8to19()),
socket_rail(&SocketRailSpec::half_inch_6()),
hex_bit_holder(&HexBitHolderSpec::grid_4x5()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"tools archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn raspi_case_rpi5_is_rotate_wrapped() {
let c = raspi_case(&RaspiCaseSpec::rpi5_active_cooler());
assert!(matches!(c, SdfNode::Rotate { .. }));
}
#[test]
fn raspi_case_floor_is_solid() {
let c = raspi_case(&RaspiCaseSpec::rpi5_active_cooler());
assert!(eval(&c, Vec3::new(0.0, 0.0, -13.0)) < 0.0);
}
#[test]
fn esp32_enclosure_devkit_is_rotate_wrapped() {
let e = esp32_enclosure(&Esp32EnclosureSpec::esp32_devkit_v1());
assert!(matches!(e, SdfNode::Rotate { .. }));
}
#[test]
fn esp32_enclosure_wall_is_solid() {
let e = esp32_enclosure(&Esp32EnclosureSpec::esp32_devkit_v1());
assert!(eval(&e, Vec3::new(-27.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn battery_18650_holder_row_4_is_rotate_wrapped() {
let b = battery_18650_holder(&Battery18650HolderSpec::row_4_through());
assert!(matches!(b, SdfNode::Rotate { .. }));
}
#[test]
fn battery_18650_holder_wall_between_cells_is_solid() {
let b = battery_18650_holder(&Battery18650HolderSpec::row_4_through());
assert!(eval(&b, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn battery_18650_holder_cell_cavity_is_hollow() {
let b = battery_18650_holder(&Battery18650HolderSpec::row_4_through());
assert!(eval(&b, Vec3::new(-31.65, 0.0, 0.0)) > 0.0);
}
#[test]
fn all_electronics_archetypes_evaluations_finite() {
let nodes = [
raspi_case(&RaspiCaseSpec::rpi5_active_cooler()),
esp32_enclosure(&Esp32EnclosureSpec::esp32_devkit_v1()),
battery_18650_holder(&Battery18650HolderSpec::row_4_through()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"electronics archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn toothbrush_holder_manual_4_is_rotate_wrapped() {
let t = toothbrush_holder(&ToothbrushHolderSpec::manual_4());
assert!(matches!(t, SdfNode::Rotate { .. }));
}
#[test]
fn toothbrush_holder_wall_is_solid() {
let t = toothbrush_holder(&ToothbrushHolderSpec::manual_4());
assert!(eval(&t, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn drill_bit_holder_metric_is_rotate_wrapped() {
let d = drill_bit_holder(&DrillBitHolderSpec::metric_11_3to13());
assert!(matches!(d, SdfNode::Rotate { .. }));
}
#[test]
fn drill_bit_holder_floor_is_solid() {
let d = drill_bit_holder(&DrillBitHolderSpec::metric_11_3to13());
assert!(eval(&d, Vec3::new(0.0, 0.0, -11.0)) < 0.0);
}
#[test]
fn pliers_rack_standard_6_is_rotate_wrapped() {
let p = pliers_rack(&PliersRackSpec::standard_6());
assert!(matches!(p, SdfNode::Rotate { .. }));
}
#[test]
fn pliers_rack_wall_between_slots_is_solid() {
let p = pliers_rack(&PliersRackSpec::standard_6());
assert!(eval(&p, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn all_bathroom_garage_archetypes_evaluations_finite() {
let nodes = [
toothbrush_holder(&ToothbrushHolderSpec::manual_4()),
drill_bit_holder(&DrillBitHolderSpec::metric_11_3to13()),
pliers_rack(&PliersRackSpec::standard_6()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"bathroom-garage archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn spice_rack_standard_6_is_rotate_wrapped() {
let s = spice_rack(&SpiceRackSpec::standard_6());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn spice_rack_base_is_solid() {
let s = spice_rack(&SpiceRackSpec::standard_6());
assert!(eval(&s, Vec3::new(0.0, 0.0, -8.0)) < 0.0);
}
#[test]
fn egg_tray_4x3_is_rotate_wrapped() {
let e = egg_tray(&EggTraySpec::tray_4x3());
assert!(matches!(e, SdfNode::Rotate { .. }));
}
#[test]
fn egg_tray_floor_is_solid() {
let e = egg_tray(&EggTraySpec::tray_4x3());
assert!(eval(&e, Vec3::new(0.0, 0.0, -9.0)) < 0.0);
}
#[test]
fn egg_tray_wall_between_cups_is_solid() {
let e = egg_tray(&EggTraySpec::tray_4x3());
assert!(eval(&e, Vec3::new(0.0, 25.0, 0.0)) < 0.0);
}
#[test]
fn utensil_caddy_standard_4_is_rotate_wrapped() {
let u = utensil_caddy(&UtensilCaddySpec::standard_4());
assert!(matches!(u, SdfNode::Rotate { .. }));
}
#[test]
fn utensil_caddy_wall_is_solid() {
let u = utensil_caddy(&UtensilCaddySpec::standard_4());
assert!(eval(&u, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn all_cable_kitchen_archetypes_evaluations_finite() {
let nodes = [
spice_rack(&SpiceRackSpec::standard_6()),
egg_tray(&EggTraySpec::tray_4x3()),
utensil_caddy(&UtensilCaddySpec::standard_4()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"cable-kitchen archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn filament_spool_holder_1kg_is_union() {
let f = filament_spool_holder(&FilamentSpoolHolderSpec::standard_1kg());
assert!(matches!(f, SdfNode::Union { .. }));
}
#[test]
fn filament_spool_holder_base_is_solid() {
let f = filament_spool_holder(&FilamentSpoolHolderSpec::standard_1kg());
assert!(eval(&f, Vec3::new(0.0, 0.0, 1.0)) < 0.0);
}
#[test]
fn filament_spool_holder_peg_is_solid() {
let f = filament_spool_holder(&FilamentSpoolHolderSpec::standard_1kg());
assert!(eval(&f, Vec3::new(0.0, 0.0, 48.0)) < 0.0);
}
#[test]
fn nozzle_holder_m6_row_8_is_rotate_wrapped() {
let n = nozzle_holder(&NozzleHolderSpec::m6_row_8());
assert!(matches!(n, SdfNode::Rotate { .. }));
}
#[test]
fn nozzle_holder_wall_between_holes_is_solid() {
let n = nozzle_holder(&NozzleHolderSpec::m6_row_8());
assert!(eval(&n, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn build_plate_rack_standard_5_is_rotate_wrapped() {
let r = build_plate_rack(&BuildPlateRackSpec::standard_5());
assert!(matches!(r, SdfNode::Rotate { .. }));
}
#[test]
fn build_plate_rack_floor_is_solid() {
let r = build_plate_rack(&BuildPlateRackSpec::standard_5());
assert!(eval(&r, Vec3::new(0.0, 0.0, -100.0)) < 0.0);
}
#[test]
fn all_printer_modular_archetypes_evaluations_finite() {
let nodes = [
filament_spool_holder(&FilamentSpoolHolderSpec::standard_1kg()),
nozzle_holder(&NozzleHolderSpec::m6_row_8()),
build_plate_rack(&BuildPlateRackSpec::standard_5()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"printer-modular archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn cutlery_tray_standard_3_is_rotate_wrapped() {
let c = cutlery_tray(&CutleryTraySpec::standard_3());
assert!(matches!(c, SdfNode::Rotate { .. }));
}
#[test]
fn cutlery_tray_wall_between_slots_is_solid() {
let c = cutlery_tray(&CutleryTraySpec::standard_3());
assert!(eval(&c, Vec3::new(19.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn pill_organizer_weekly_7x2_is_rotate_wrapped() {
let p = pill_organizer(&PillOrganizerSpec::weekly_7x2());
assert!(matches!(p, SdfNode::Rotate { .. }));
}
#[test]
fn pill_organizer_floor_is_solid() {
let p = pill_organizer(&PillOrganizerSpec::weekly_7x2());
assert!(eval(&p, Vec3::new(0.0, 0.0, -7.0)) < 0.0);
}
#[test]
fn magnetic_strip_knife_rail_is_rotate_wrapped() {
let m = magnetic_strip(&MagneticStripSpec::knife_rail_8());
assert!(matches!(m, SdfNode::Rotate { .. }));
}
#[test]
fn magnetic_strip_bar_back_is_solid() {
let m = magnetic_strip(&MagneticStripSpec::knife_rail_8());
assert!(eval(&m, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn all_drawer_wall_archetypes_evaluations_finite() {
let nodes = [
cutlery_tray(&CutleryTraySpec::standard_3()),
pill_organizer(&PillOrganizerSpec::weekly_7x2()),
magnetic_strip(&MagneticStripSpec::knife_rail_8()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"drawer-wall archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn hairdryer_holder_dyson_is_rotate_wrapped() {
let h = hairdryer_holder(&HairdryerHolderSpec::dyson_85());
assert!(matches!(h, SdfNode::Rotate { .. }));
}
#[test]
fn hairdryer_holder_wall_is_solid() {
let h = hairdryer_holder(&HairdryerHolderSpec::dyson_85());
assert!(eval(&h, Vec3::new(46.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn kcup_holder_4x3_is_rotate_wrapped() {
let k = kcup_holder(&KcupHolderSpec::kcup_4x3());
assert!(matches!(k, SdfNode::Rotate { .. }));
}
#[test]
fn kcup_holder_floor_is_solid() {
let k = kcup_holder(&KcupHolderSpec::kcup_4x3());
assert!(eval(&k, Vec3::new(0.0, 0.0, -20.0)) < 0.0);
}
#[test]
fn hex_key_holder_metric_9_is_rotate_wrapped() {
let h = hex_key_holder(&HexKeyHolderSpec::metric_9());
assert!(matches!(h, SdfNode::Rotate { .. }));
}
#[test]
fn hex_key_holder_floor_is_solid() {
let h = hex_key_holder(&HexKeyHolderSpec::metric_9());
assert!(eval(&h, Vec3::new(0.0, 0.0, -9.0)) < 0.0);
}
#[test]
fn all_sprint12_mix_archetypes_evaluations_finite() {
let nodes = [
hairdryer_holder(&HairdryerHolderSpec::dyson_85()),
kcup_holder(&KcupHolderSpec::kcup_4x3()),
hex_key_holder(&HexKeyHolderSpec::metric_9()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint12 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn wrap_holder_foil_12inch_is_rotate_wrapped() {
let w = wrap_holder(&WrapHolderSpec::foil_12inch());
assert!(matches!(w, SdfNode::Rotate { .. }));
}
#[test]
fn wrap_holder_floor_is_solid() {
let w = wrap_holder(&WrapHolderSpec::foil_12inch());
assert!(eval(&w, Vec3::new(0.0, 0.0, -15.0)) < 0.0);
}
#[test]
fn sock_divider_standard_4_is_rotate_wrapped() {
let d = sock_divider(&SockDividerSpec::standard_4());
assert!(matches!(d, SdfNode::Rotate { .. }));
}
#[test]
fn sock_divider_frame_wall_is_solid() {
let d = sock_divider(&SockDividerSpec::standard_4());
assert!(eval(&d, Vec3::new(165.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn soap_tray_dual_bottle_is_rotate_wrapped() {
let s = soap_tray(&SoapTraySpec::dual_bottle_l200());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn soap_tray_side_wall_is_solid() {
let s = soap_tray(&SoapTraySpec::dual_bottle_l200());
assert!(eval(&s, Vec3::new(101.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn all_sprint13_mix_archetypes_evaluations_finite() {
let nodes = [
wrap_holder(&WrapHolderSpec::foil_12inch()),
sock_divider(&SockDividerSpec::standard_4()),
soap_tray(&SoapTraySpec::dual_bottle_l200()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint13 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn razor_holder_cartridge_is_rotate_wrapped() {
let r = razor_holder(&RazorHolderSpec::cartridge_razor());
assert!(matches!(r, SdfNode::Rotate { .. }));
}
#[test]
fn chopstick_holder_adult_4_is_rotate_wrapped() {
let c = chopstick_holder(&ChopstickHolderSpec::adult_4());
assert!(matches!(c, SdfNode::Rotate { .. }));
}
#[test]
fn chopstick_holder_wall_is_solid() {
let c = chopstick_holder(&ChopstickHolderSpec::adult_4());
assert!(eval(&c, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn swatch_holder_standard_8x4_is_rotate_wrapped() {
let s = swatch_holder(&SwatchHolderSpec::standard_8x4());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn swatch_holder_floor_is_solid() {
let s = swatch_holder(&SwatchHolderSpec::standard_8x4());
assert!(eval(&s, Vec3::new(0.0, 0.0, -35.0)) < 0.0);
}
#[test]
fn all_sprint14_mix_archetypes_evaluations_finite() {
let nodes = [
razor_holder(&RazorHolderSpec::cartridge_razor()),
chopstick_holder(&ChopstickHolderSpec::adult_4()),
swatch_holder(&SwatchHolderSpec::standard_8x4()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint14 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn tp_holder_standard_is_rotate_wrapped() {
let t = tp_holder(&TpHolderSpec::standard());
assert!(matches!(t, SdfNode::Rotate { .. }));
}
#[test]
fn tp_holder_backplate_is_solid() {
let t = tp_holder(&TpHolderSpec::standard());
assert!(eval(&t, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn sd_card_holder_full_sd_is_rotate_wrapped() {
let s = sd_card_holder(&SdCardHolderSpec::full_sd_4x4());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn sd_card_holder_floor_is_solid() {
let s = sd_card_holder(&SdCardHolderSpec::full_sd_4x4());
assert!(eval(&s, Vec3::new(0.0, 0.0, -16.0)) < 0.0);
}
#[test]
fn driver_rack_standard_8_is_rotate_wrapped() {
let d = driver_rack(&DriverRackSpec::standard_8());
assert!(matches!(d, SdfNode::Rotate { .. }));
}
#[test]
fn driver_rack_floor_is_solid() {
let d = driver_rack(&DriverRackSpec::standard_8());
assert!(eval(&d, Vec3::new(0.0, 0.0, -48.0)) < 0.0);
}
#[test]
fn all_sprint15_mix_archetypes_evaluations_finite() {
let nodes = [
tp_holder(&TpHolderSpec::standard()),
sd_card_holder(&SdCardHolderSpec::full_sd_4x4()),
driver_rack(&DriverRackSpec::standard_8()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint15 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn cotton_dispenser_standard_is_subtraction() {
let c = cotton_dispenser(&CottonDispenserSpec::standard_80());
assert!(matches!(c, SdfNode::Subtraction { .. }));
}
#[test]
fn cotton_dispenser_wall_is_solid() {
let c = cotton_dispenser(&CottonDispenserSpec::standard_80());
assert!(eval(&c, Vec3::new(46.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn sink_caddy_standard_l200_is_rotate_wrapped() {
let s = sink_caddy(&SinkCaddySpec::standard_l200());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn sink_caddy_wall_is_solid() {
let s = sink_caddy(&SinkCaddySpec::standard_l200());
assert!(eval(&s, Vec3::new(-101.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn clamp_rack_standard_5_is_rotate_wrapped() {
let c = clamp_rack(&ClampRackSpec::standard_5());
assert!(matches!(c, SdfNode::Rotate { .. }));
}
#[test]
fn clamp_rack_backplate_center_is_solid() {
let c = clamp_rack(&ClampRackSpec::standard_5());
assert!(eval(&c, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn all_sprint16_mix_archetypes_evaluations_finite() {
let nodes = [
cotton_dispenser(&CottonDispenserSpec::standard_80()),
sink_caddy(&SinkCaddySpec::standard_l200()),
clamp_rack(&ClampRackSpec::standard_5()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint16 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn dry_box_standard_2x2_is_rotate_wrapped() {
let d = dry_box(&DryBoxSpec::standard_2x2());
assert!(matches!(d, SdfNode::Rotate { .. }));
}
#[test]
fn dry_box_wall_is_solid() {
let d = dry_box(&DryBoxSpec::standard_2x2());
assert!(eval(&d, Vec3::new(-72.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn outdoor_enclosure_ip54_is_rotate_wrapped() {
let e = outdoor_enclosure(&OutdoorEnclosureSpec::ip54_120x80());
assert!(matches!(e, SdfNode::Rotate { .. }));
}
#[test]
fn outdoor_enclosure_wall_is_solid() {
let e = outdoor_enclosure(&OutdoorEnclosureSpec::ip54_120x80());
assert!(eval(&e, Vec3::new(-62.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn jewelry_stand_standard_3_tier_is_union() {
let j = jewelry_stand(&JewelryStandSpec::standard_3_tier());
assert!(matches!(j, SdfNode::Union { .. }));
}
#[test]
fn jewelry_stand_pillar_center_is_solid() {
let j = jewelry_stand(&JewelryStandSpec::standard_3_tier());
assert!(eval(&j, Vec3::new(0.0, 0.0, 50.0)) < 0.0);
}
#[test]
fn all_sprint17_mix_archetypes_evaluations_finite() {
let nodes = [
dry_box(&DryBoxSpec::standard_2x2()),
outdoor_enclosure(&OutdoorEnclosureSpec::ip54_120x80()),
jewelry_stand(&JewelryStandSpec::standard_3_tier()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint17 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn phone_dock_standard_is_rotate_wrapped() {
let d = phone_dock(&PhoneDockSpec::standard_80x100());
assert!(matches!(d, SdfNode::Rotate { .. }));
}
#[test]
fn phone_dock_base_is_solid() {
let d = phone_dock(&PhoneDockSpec::standard_80x100());
assert!(eval(&d, Vec3::new(0.0, 3.0, 0.0)) < 0.0);
}
#[test]
fn cutting_board_rack_standard_3_is_rotate_wrapped() {
let c = cutting_board_rack(&CuttingBoardRackSpec::standard_3());
assert!(matches!(c, SdfNode::Rotate { .. }));
}
#[test]
fn cutting_board_rack_wall_is_solid() {
let c = cutting_board_rack(&CuttingBoardRackSpec::standard_3());
assert!(eval(&c, Vec3::new(-25.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn tape_dispenser_standard_is_rotate_wrapped() {
let t = tape_dispenser(&TapeDispenserSpec::packing_tape_standard());
assert!(matches!(t, SdfNode::Rotate { .. }));
}
#[test]
fn tape_dispenser_base_center_is_solid() {
let t = tape_dispenser(&TapeDispenserSpec::packing_tape_standard());
assert!(eval(&t, Vec3::new(0.0, 0.0, 1.5)) < 0.0);
}
#[test]
fn all_sprint18_mix_archetypes_evaluations_finite() {
let nodes = [
phone_dock(&PhoneDockSpec::standard_80x100()),
cutting_board_rack(&CuttingBoardRackSpec::standard_3()),
tape_dispenser(&TapeDispenserSpec::packing_tape_standard()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint18 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn shower_caddy_standard_2_tier_is_rotate_wrapped() {
let s = shower_caddy(&ShowerCaddySpec::standard_2_tier());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn shower_caddy_backplate_center_is_solid() {
let s = shower_caddy(&ShowerCaddySpec::standard_2_tier());
assert!(eval(&s, Vec3::new(0.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn caliper_holder_standard_3_is_rotate_wrapped() {
let c = caliper_holder(&CaliperHolderSpec::standard_3());
assert!(matches!(c, SdfNode::Rotate { .. }));
}
#[test]
fn caliper_holder_wall_between_slots_is_solid() {
let c = caliper_holder(&CaliperHolderSpec::standard_3());
assert!(eval(&c, Vec3::new(25.0, 0.0, 60.0)) < 0.0);
}
#[test]
fn bag_clip_org_standard_8_is_rotate_wrapped() {
let b = bag_clip_org(&BagClipOrgSpec::standard_8());
assert!(matches!(b, SdfNode::Rotate { .. }));
}
#[test]
fn bag_clip_org_floor_is_solid() {
let b = bag_clip_org(&BagClipOrgSpec::standard_8());
assert!(eval(&b, Vec3::new(0.0, 0.0, -50.0)) < 0.0);
}
#[test]
fn all_sprint19_mix_archetypes_evaluations_finite() {
let nodes = [
shower_caddy(&ShowerCaddySpec::standard_2_tier()),
caliper_holder(&CaliperHolderSpec::standard_3()),
bag_clip_org(&BagClipOrgSpec::standard_8()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint19 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn can_rack_standard_2_tier_is_rotate_wrapped() {
let c = can_rack(&CanRackSpec::standard_2_tier());
assert!(matches!(c, SdfNode::Rotate { .. }));
}
#[test]
fn can_rack_side_wall_is_solid() {
let c = can_rack(&CanRackSpec::standard_2_tier());
assert!(eval(&c, Vec3::new(38.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn led_hub_box_standard_is_rotate_wrapped() {
let l = led_hub_box(&LedHubBoxSpec::standard_80x60());
assert!(matches!(l, SdfNode::Rotate { .. }));
}
#[test]
fn led_hub_box_wall_is_solid() {
let l = led_hub_box(&LedHubBoxSpec::standard_80x60());
assert!(eval(&l, Vec3::new(-42.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn makeup_organizer_standard_3x4_is_rotate_wrapped() {
let m = makeup_organizer(&MakeupOrganizerSpec::standard_3x4());
assert!(matches!(m, SdfNode::Rotate { .. }));
}
#[test]
fn makeup_organizer_floor_is_solid() {
let m = makeup_organizer(&MakeupOrganizerSpec::standard_3x4());
assert!(eval(&m, Vec3::new(0.0, 0.0, -20.0)) < 0.0);
}
#[test]
fn all_sprint20_mix_archetypes_evaluations_finite() {
let nodes = [
can_rack(&CanRackSpec::standard_2_tier()),
led_hub_box(&LedHubBoxSpec::standard_80x60()),
makeup_organizer(&MakeupOrganizerSpec::standard_3x4()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint20 mix archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn vesa_75_m4_cb_is_rotate_wrapped() {
let m = vesa_mount(&VesaMountSpec::vesa_75_m4_cb());
assert!(matches!(m, SdfNode::Rotate { .. }));
}
#[test]
fn vesa_75_m4_cb_has_solid_center() {
let m = vesa_mount(&VesaMountSpec::vesa_75_m4_cb());
assert!(eval(&m, Vec3::ZERO) < 0.0);
}
#[test]
fn vesa_75_m4_cb_has_hole_at_corner() {
let m = vesa_mount(&VesaMountSpec::vesa_75_m4_cb());
let d = eval(&m, Vec3::new(37.5, -37.5, 0.0));
assert!(d > 0.0, "corner should be a hole (empty), got d={d}");
}
#[test]
fn l_bracket_m4_2holes_is_rotate_wrapped() {
let b = l_bracket(&LBracketSpec::m4_2holes());
assert!(matches!(b, SdfNode::Rotate { .. }));
}
#[test]
fn l_bracket_horizontal_arm_center_solid() {
let b = l_bracket(&LBracketSpec::m4_2holes());
assert!(eval(&b, Vec3::ZERO) < 0.0);
}
#[test]
fn all_sprint21_mechanical_archetypes_evaluations_finite() {
let nodes = [
vesa_mount(&VesaMountSpec::vesa_75_m4_cb()),
vesa_mount(&VesaMountSpec::vesa_100_m5_cb()),
l_bracket(&LBracketSpec::m4_2holes()),
l_bracket(&LBracketSpec::m5_3holes()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint21 mechanical archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn t_slot_bracket_2020_standard_is_rotate_wrapped() {
let b = t_slot_bracket_2020(&TSlotBracket2020Spec::standard_20());
assert!(matches!(b, SdfNode::Rotate { .. }));
}
#[test]
fn raspi_mount_plate_pi_4b_is_rotate_wrapped() {
let p = raspi_mount_plate(&RaspiMountPlateSpec::pi_4b_vesa());
assert!(matches!(p, SdfNode::Rotate { .. }));
}
#[test]
fn raspi_mount_plate_center_is_solid() {
let p = raspi_mount_plate(&RaspiMountPlateSpec::pi_4b_vesa());
assert!(eval(&p, Vec3::ZERO) < 0.0);
}
#[test]
fn heat_set_array_m3_2x2_is_rotate_wrapped() {
let h = heat_set_array(&HeatSetArraySpec::m3_2x2());
assert!(matches!(h, SdfNode::Rotate { .. }));
}
#[test]
fn flange_mount_od80_is_rotate_wrapped() {
let f = flange_mount(&FlangeMountSpec::od80_m5_4());
assert!(matches!(f, SdfNode::Rotate { .. }));
}
#[test]
fn dovetail_pair_male_is_rotate_wrapped() {
let m = dovetail_pair(&DovetailPairSpec::male_20());
assert!(matches!(m, SdfNode::Rotate { .. }));
}
#[test]
fn dovetail_pair_female_is_rotate_wrapped() {
let f = dovetail_pair(&DovetailPairSpec::female_20());
assert!(matches!(f, SdfNode::Rotate { .. }));
}
#[test]
fn profile_extrusion_2020_is_rotate_wrapped() {
let p = profile_extrusion(&ProfileExtrusionSpec::p2020_100());
assert!(matches!(p, SdfNode::Rotate { .. }));
}
#[test]
fn profile_extrusion_3030_is_rotate_wrapped() {
let p = profile_extrusion(&ProfileExtrusionSpec::p3030_100());
assert!(matches!(p, SdfNode::Rotate { .. }));
}
#[test]
fn snap_fit_pair_standard_is_rotate_wrapped() {
let s = snap_fit_pair(&SnapFitPairSpec::standard());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn boss_array_m3_2x2_is_rotate_wrapped() {
let b = boss_array(&BossArraySpec::m3_2x2());
assert!(matches!(b, SdfNode::Rotate { .. }));
}
#[test]
fn all_sprint21_extra8_archetypes_evaluations_finite() {
let nodes = [
t_slot_bracket_2020(&TSlotBracket2020Spec::standard_20()),
t_slot_bracket_2020(&TSlotBracket2020Spec::heavy_40()),
raspi_mount_plate(&RaspiMountPlateSpec::pi_4b_vesa()),
raspi_mount_plate(&RaspiMountPlateSpec::pi_zero_bare()),
heat_set_array(&HeatSetArraySpec::m3_2x2()),
heat_set_array(&HeatSetArraySpec::m4_3x3()),
flange_mount(&FlangeMountSpec::od80_m5_4()),
flange_mount(&FlangeMountSpec::od100_m6_6()),
dovetail_pair(&DovetailPairSpec::male_20()),
dovetail_pair(&DovetailPairSpec::female_20()),
profile_extrusion(&ProfileExtrusionSpec::p2020_100()),
profile_extrusion(&ProfileExtrusionSpec::p3030_100()),
snap_fit_pair(&SnapFitPairSpec::standard()),
boss_array(&BossArraySpec::m3_2x2()),
boss_array(&BossArraySpec::m4_3x3()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint21 extra8 archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn bearing_kind_from_f32_snap_exact() {
assert_eq!(BearingKind::from_f32_snap(22.0), BearingKind::B608);
assert_eq!(BearingKind::from_f32_snap(16.0), BearingKind::B688);
assert_eq!(BearingKind::from_f32_snap(28.0), BearingKind::B6001);
assert_eq!(BearingKind::from_f32_snap(35.0), BearingKind::B6202);
}
#[test]
fn bearing_kind_from_f32_snap_near() {
assert_eq!(BearingKind::from_f32_snap(24.0), BearingKind::B608);
assert_eq!(BearingKind::from_f32_snap(30.0), BearingKind::B6001);
assert_eq!(BearingKind::from_f32_snap(100.0), BearingKind::B6202);
}
#[test]
fn bearing_608_dimensions_are_standard() {
assert!((BearingKind::B608.outer_dia() - 22.0).abs() < 1e-6);
assert!((BearingKind::B608.inner_dia() - 8.0).abs() < 1e-6);
assert!((BearingKind::B608.width() - 7.0).abs() < 1e-6);
}
#[test]
fn bearing_seat_608_press_fit_is_rotate_wrapped() {
let s = bearing_seat(&BearingSeatSpec::b608_press_fit());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn bearing_seat_has_shaft_hole_at_center() {
let s = bearing_seat(&BearingSeatSpec::b608_press_fit());
let d = eval(&s, Vec3::ZERO);
assert!(d > 0.0, "shaft through hole 中心は穴、got d={d}");
}
#[test]
fn all_sprint22_bearing_archetypes_evaluations_finite() {
let nodes = [
bearing_seat(&BearingSeatSpec::b608_press_fit()),
bearing_seat(&BearingSeatSpec::b608_slip_fit()),
bearing_seat(&BearingSeatSpec::b688_press_fit()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"sprint22 bearing archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn cable_grommet_standard_60_is_rotate_wrapped() {
let g = cable_grommet(&CableGrommetSpec::standard_60());
assert!(matches!(g, SdfNode::Rotate { .. }));
}
#[test]
fn cable_grommet_center_is_hole() {
let g = cable_grommet(&CableGrommetSpec::standard_60());
assert!(eval(&g, Vec3::ZERO) > 0.0, "grommet center is a hole");
}
#[test]
fn curtain_rod_bracket_standard_25_is_rotate_wrapped() {
let b = curtain_rod_bracket(&CurtainRodBracketSpec::standard_25());
assert!(matches!(b, SdfNode::Rotate { .. }));
}
#[test]
fn all_multidomain_archetypes_evaluations_finite() {
let nodes = [
cable_grommet(&CableGrommetSpec::standard_60()),
cable_grommet(&CableGrommetSpec::large_80()),
curtain_rod_bracket(&CurtainRodBracketSpec::standard_25()),
curtain_rod_bracket(&CurtainRodBracketSpec::large_30()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"multi-domain archetype {i} produced non-finite SDF: {d}"
);
}
}
#[test]
fn arduino_board_dimensions_are_standard() {
assert!((ArduinoBoard::Uno.board_width() - 68.6).abs() < 1e-4);
assert!((ArduinoBoard::Mega.board_width() - 101.5).abs() < 1e-4);
assert!((ArduinoBoard::Nano.board_width() - 43.2).abs() < 1e-4);
}
#[test]
fn arduino_board_from_f32_snap() {
assert_eq!(ArduinoBoard::from_f32_snap(1.0), ArduinoBoard::Uno);
assert_eq!(ArduinoBoard::from_f32_snap(2.0), ArduinoBoard::Mega);
assert_eq!(ArduinoBoard::from_f32_snap(3.0), ArduinoBoard::Nano);
assert_eq!(ArduinoBoard::from_f32_snap(10.0), ArduinoBoard::Uno);
}
#[test]
fn arduino_uno_bare_is_rotate_wrapped() {
let p = arduino_mount_plate(&ArduinoMountPlateSpec::uno_bare());
assert!(matches!(p, SdfNode::Rotate { .. }));
}
#[test]
fn pixhawk_45_solid_is_rotate_wrapped() {
let p = pixhawk_mount(&PixhawkMountSpec::standard_45_solid());
assert!(matches!(p, SdfNode::Rotate { .. }));
}
#[test]
fn servo_kind_from_f32_snap() {
assert_eq!(ServoKind::from_f32_snap(1.0), ServoKind::Sg90);
assert_eq!(ServoKind::from_f32_snap(2.0), ServoKind::Mg996r);
}
#[test]
fn servo_sg90_is_rotate_wrapped() {
let s = servo_mount(&ServoMountSpec::sg90_standard());
assert!(matches!(s, SdfNode::Rotate { .. }));
}
#[test]
fn all_arduino_pixhawk_servo_evaluations_finite() {
let nodes = [
arduino_mount_plate(&ArduinoMountPlateSpec::uno_bare()),
arduino_mount_plate(&ArduinoMountPlateSpec::uno_vesa()),
arduino_mount_plate(&ArduinoMountPlateSpec::nano_bare()),
pixhawk_mount(&PixhawkMountSpec::standard_45_solid()),
pixhawk_mount(&PixhawkMountSpec::standard_45_damper()),
pixhawk_mount(&PixhawkMountSpec::mini_30_solid()),
servo_mount(&ServoMountSpec::sg90_standard()),
servo_mount(&ServoMountSpec::mg996r_standard()),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(
d.is_finite(),
"arduino/pixhawk/servo archetype {i} produced non-finite SDF: {d}"
);
}
}
}