use alice_sdf::SdfNode;
use glam::{Quat, Vec3};
use std::sync::Arc;
pub const SKADIS_PEG_W: f32 = 5.0;
pub const SKADIS_PEG_H: f32 = 15.0;
pub const SKADIS_PEG_R: f32 = 2.5;
pub const FDM_CLEARANCE: f32 = 0.2;
pub const PROFILE_2020_SIZE: f32 = 20.0;
pub const PROFILE_3030_SIZE: f32 = 30.0;
pub const PROFILE_2020_CENTER_BORE: f32 = 5.2;
pub const PROFILE_3030_CENTER_BORE: f32 = 6.2;
#[must_use]
pub fn bracket_l(
horizontal_length: f32,
vertical_height: f32,
thickness: f32,
depth: f32,
fillet_radius: f32,
) -> SdfNode {
let horizontal = SdfNode::Box3d {
half_extents: Vec3::new(horizontal_length * 0.5, thickness * 0.5, depth * 0.5),
};
let vertical_raw = SdfNode::Box3d {
half_extents: Vec3::new(thickness * 0.5, vertical_height * 0.5, depth * 0.5),
};
let vertical = SdfNode::Translate {
child: Arc::new(vertical_raw),
offset: Vec3::new(
-(horizontal_length - thickness) * 0.5,
(vertical_height + thickness) * 0.5,
0.0,
),
};
if fillet_radius > 0.0 {
SdfNode::SmoothUnion {
a: Arc::new(horizontal),
b: Arc::new(vertical),
k: fillet_radius,
}
} else {
SdfNode::Union {
a: Arc::new(horizontal),
b: Arc::new(vertical),
}
}
}
#[must_use]
pub fn flange_circular(
od: f32,
center_bore_dia: f32,
thickness: f32,
bolt_pcd: f32,
bolt_count: u32,
bolt_dia: f32,
) -> SdfNode {
let disc = SdfNode::Cylinder {
radius: od * 0.5,
half_height: thickness * 0.5,
};
let single_bolt = SdfNode::Cylinder {
radius: bolt_dia * 0.5,
half_height: thickness * 0.5 + 5.0,
};
let bolt_offset = SdfNode::Translate {
child: Arc::new(single_bolt),
offset: Vec3::new(bolt_pcd * 0.5, 0.0, 0.0),
};
let bolt_ring = SdfNode::PolarRepeat {
child: Arc::new(bolt_offset),
count: bolt_count,
};
let with_bolts = SdfNode::Subtraction {
a: Arc::new(disc),
b: Arc::new(bolt_ring),
};
if center_bore_dia > 0.0 {
let center_bore = SdfNode::Cylinder {
radius: center_bore_dia * 0.5,
half_height: thickness * 0.5 + 5.0,
};
SdfNode::Subtraction {
a: Arc::new(with_bolts),
b: Arc::new(center_bore),
}
} else {
with_bolts
}
}
#[must_use]
pub fn rack_shelf(
length: f32,
thickness: f32,
width: f32,
notch_pitch: f32,
notch_dia: f32,
notch_count: u32,
) -> SdfNode {
let plate = SdfNode::Box3d {
half_extents: Vec3::new(length * 0.5, thickness * 0.5, width * 0.5),
};
let notch = SdfNode::Cylinder {
radius: notch_dia * 0.5,
half_height: thickness * 0.5 + 5.0,
};
let notch_row = SdfNode::RepeatFinite {
child: Arc::new(notch),
count: [notch_count, 0, 0],
spacing: Vec3::new(notch_pitch, 1.0, 1.0),
};
SdfNode::Subtraction {
a: Arc::new(plate),
b: Arc::new(notch_row),
}
}
#[must_use]
pub fn skadis_peg_compat(board_thickness: f32) -> SdfNode {
let peg_w = SKADIS_PEG_W - FDM_CLEARANCE;
SdfNode::RoundedBox {
half_extents: Vec3::new(peg_w * 0.5, SKADIS_PEG_H * 0.5, board_thickness * 0.5),
round_radius: SKADIS_PEG_R,
}
}
fn extrusion_profile(size: f32, length: f32, center_bore_dia: f32) -> SdfNode {
let outer = SdfNode::Box3d {
half_extents: Vec3::new(size * 0.5, length * 0.5, size * 0.5),
};
let t_slot = crate::stdlib::hardsurface::joint::t_slot_2020(length);
let face_offset = size * 0.5;
let mut with_slots = outer;
for i in 0..4 {
let angle = std::f32::consts::FRAC_PI_2 * i as f32;
let rotated = SdfNode::Rotate {
child: Arc::new(t_slot.clone()),
rotation: Quat::from_rotation_y(angle),
};
let offset = Vec3::new(face_offset * angle.cos(), 0.0, -face_offset * angle.sin());
let placed = SdfNode::Translate {
child: Arc::new(rotated),
offset,
};
with_slots = SdfNode::Subtraction {
a: Arc::new(with_slots),
b: Arc::new(placed),
};
}
if center_bore_dia > 0.0 {
let bore = SdfNode::Cylinder {
radius: center_bore_dia * 0.5,
half_height: length * 0.5 + 5.0,
};
SdfNode::Subtraction {
a: Arc::new(with_slots),
b: Arc::new(bore),
}
} else {
with_slots
}
}
#[must_use]
pub fn profile_2020(length: f32) -> SdfNode {
extrusion_profile(PROFILE_2020_SIZE, length, PROFILE_2020_CENTER_BORE)
}
#[must_use]
pub fn profile_3030(length: f32) -> SdfNode {
extrusion_profile(PROFILE_3030_SIZE, length, PROFILE_3030_CENTER_BORE)
}
#[cfg(test)]
mod tests {
use super::*;
use alice_sdf::eval;
fn approx_eq(a: f32, b: f32) -> bool {
(a - b).abs() < 1e-4
}
#[test]
fn bracket_l_with_fillet_returns_smooth_union() {
let b = bracket_l(60.0, 40.0, 4.0, 40.0, 3.0);
assert!(matches!(b, SdfNode::SmoothUnion { k, .. } if approx_eq(k, 3.0)));
}
#[test]
fn bracket_l_without_fillet_returns_plain_union() {
let b = bracket_l(60.0, 40.0, 4.0, 40.0, 0.0);
assert!(matches!(b, SdfNode::Union { .. }));
}
#[test]
fn bracket_l_horizontal_center_inside() {
let b = bracket_l(60.0, 40.0, 4.0, 40.0, 3.0);
assert!(eval(&b, Vec3::ZERO) < 0.0);
}
#[test]
fn flange_circular_has_bolt_holes() {
let f = flange_circular(60.0, 20.0, 6.0, 45.0, 4, 4.2);
assert!(eval(&f, Vec3::ZERO) > 0.0);
assert!(eval(&f, Vec3::new(22.5, 0.0, 0.0)) > 0.0);
assert!(eval(&f, Vec3::new(28.0, 0.0, 0.0)) < 0.0);
}
#[test]
fn flange_circular_no_center_bore() {
let f = flange_circular(60.0, 0.0, 6.0, 45.0, 4, 4.2);
assert!(eval(&f, Vec3::ZERO) < 0.0);
}
#[test]
fn rack_shelf_has_periodic_notches() {
let r = rack_shelf(200.0, 5.0, 30.0, 25.0, 6.0, 3);
assert!(eval(&r, Vec3::ZERO) > 0.0);
assert!(eval(&r, Vec3::new(12.5, 0.0, 0.0)) < 0.0);
}
#[test]
fn skadis_peg_dimensions_match_bamboo_spec() {
let p = skadis_peg_compat(5.0);
match p {
SdfNode::RoundedBox {
half_extents,
round_radius,
} => {
assert!(approx_eq(half_extents.x, 2.4)); assert!(approx_eq(half_extents.y, 7.5)); assert!(approx_eq(half_extents.z, 2.5)); assert!(approx_eq(round_radius, SKADIS_PEG_R));
}
_ => panic!("expected RoundedBox"),
}
}
#[test]
fn profile_2020_outer_dimensions() {
let p = profile_2020(100.0);
assert!(eval(&p, Vec3::new(9.0, 0.0, 9.0)) < 0.0);
assert!(eval(&p, Vec3::new(15.0, 0.0, 0.0)) > 0.0);
}
#[test]
fn profile_3030_center_bore_is_hollow() {
let p = profile_3030(100.0);
assert!(eval(&p, Vec3::ZERO) > 0.0);
assert!(eval(&p, Vec3::new(13.0, 0.0, 13.0)) < 0.0);
}
#[test]
fn all_mount_primitives_produce_finite_sdf() {
let nodes = [
bracket_l(60.0, 40.0, 4.0, 40.0, 3.0),
flange_circular(60.0, 20.0, 6.0, 45.0, 4, 4.2),
rack_shelf(200.0, 5.0, 30.0, 25.0, 6.0, 3),
skadis_peg_compat(5.0),
profile_2020(300.0),
profile_3030(500.0),
];
for (i, node) in nodes.iter().enumerate() {
let d = eval(node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "primitive {i} produced non-finite SDF: {d}");
}
}
}