#![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"
)]
crate::raw_minmax::raw_minmax_here!(max_f32);
use crate::audit_law::{sorted_values, AuditLaw, Clause};
use crate::intent::{HandSide, IntentNode, NodeId, Program, ProgramBuilder};
use crate::SdfNode;
use glam::{Vec2, Vec3};
use std::sync::Arc;
#[inline]
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
const fn lol_u32(v: f32) -> u32 {
v as u32
}
pub const MAX_STDLIB_COUNT: f32 = 1024.0;
pub const MAX_SKADIS_PANEL_MM: f32 = 2000.0;
pub const MAX_STDLIB_SMOOTH_FOLD_COUNT: f32 = 24.0;
pub const MAX_STDLIB_GRID_CELLS: f32 = 4096.0;
#[derive(Debug, Clone)]
pub struct ParseError {
pub message: String,
pub position: usize,
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"LOL parse error at pos {}: {}",
self.position, self.message
)
}
}
type SixFloatsChild = (f32, f32, f32, f32, f32, f32, SdfNode);
impl std::error::Error for ParseError {}
#[derive(Debug, Clone, PartialEq)]
enum Token {
Ident(String),
Number(f32),
LParen,
RParen,
Comma,
}
struct Lexer<'a> {
input: &'a [u8],
pos: usize,
}
impl<'a> Lexer<'a> {
const fn new(input: &'a str) -> Self {
Self {
input: input.as_bytes(),
pos: 0,
}
}
const fn skip_whitespace(&mut self) {
while self.pos < self.input.len() {
if self.input[self.pos].is_ascii_whitespace() {
self.pos += 1;
} else if self.pos + 1 < self.input.len()
&& self.input[self.pos] == b'/'
&& self.input[self.pos + 1] == b'/'
{
while self.pos < self.input.len() && self.input[self.pos] != b'\n' {
self.pos += 1;
}
} else {
break;
}
}
}
fn next_token(&mut self) -> Result<Option<Token>, ParseError> {
self.skip_whitespace();
if self.pos >= self.input.len() {
return Ok(None);
}
let ch = self.input[self.pos];
match ch {
b'(' => {
self.pos += 1;
Ok(Some(Token::LParen))
}
b')' => {
self.pos += 1;
Ok(Some(Token::RParen))
}
b',' => {
self.pos += 1;
Ok(Some(Token::Comma))
}
b'-' | b'0'..=b'9' => self.read_number(),
b'a'..=b'z' | b'A'..=b'Z' | b'_' => self.read_ident(),
_ => Err(ParseError {
message: format!("unexpected character: '{}'", ch as char),
position: self.pos,
}),
}
}
fn read_number(&mut self) -> Result<Option<Token>, ParseError> {
let start = self.pos;
if self.pos < self.input.len() && self.input[self.pos] == b'-' {
self.pos += 1;
}
while self.pos < self.input.len() && self.input[self.pos].is_ascii_digit() {
self.pos += 1;
}
if self.pos < self.input.len() && self.input[self.pos] == b'.' {
self.pos += 1;
while self.pos < self.input.len() && self.input[self.pos].is_ascii_digit() {
self.pos += 1;
}
}
if self.pos < self.input.len()
&& (self.input[self.pos] == b'e' || self.input[self.pos] == b'E')
{
self.pos += 1;
if self.pos < self.input.len()
&& (self.input[self.pos] == b'+' || self.input[self.pos] == b'-')
{
self.pos += 1;
}
while self.pos < self.input.len() && self.input[self.pos].is_ascii_digit() {
self.pos += 1;
}
}
let s = std::str::from_utf8(&self.input[start..self.pos]).map_err(|_| ParseError {
message: "invalid UTF-8 in number".into(),
position: start,
})?;
let v: f32 = s.parse().map_err(|_| ParseError {
message: format!("invalid number: '{s}'"),
position: start,
})?;
if !v.is_finite() {
return Err(ParseError {
message: format!("number out of range: '{s}'"),
position: start,
});
}
Ok(Some(Token::Number(v)))
}
fn read_ident(&mut self) -> Result<Option<Token>, ParseError> {
let start = self.pos;
while self.pos < self.input.len()
&& (self.input[self.pos].is_ascii_alphanumeric() || self.input[self.pos] == b'_')
{
self.pos += 1;
}
let s = std::str::from_utf8(&self.input[start..self.pos]).map_err(|_| ParseError {
message: "invalid UTF-8 in identifier".into(),
position: start,
})?;
Ok(Some(Token::Ident(s.to_owned())))
}
const fn position(&self) -> usize {
self.pos
}
}
struct Parser<'a> {
lexer: Lexer<'a>,
peeked: Option<Token>,
}
impl<'a> Parser<'a> {
const fn new(input: &'a str) -> Self {
Self {
lexer: Lexer::new(input),
peeked: None,
}
}
fn peek(&mut self) -> Result<Option<&Token>, ParseError> {
if self.peeked.is_none() {
self.peeked = self.lexer.next_token()?;
}
Ok(self.peeked.as_ref())
}
fn next(&mut self) -> Result<Option<Token>, ParseError> {
if let Some(t) = self.peeked.take() {
Ok(Some(t))
} else {
self.lexer.next_token()
}
}
fn expect_number(&mut self) -> Result<f32, ParseError> {
match self.next()? {
Some(Token::Number(v)) => Ok(v),
other => Err(ParseError {
message: format!("expected number, got {other:?}"),
position: self.lexer.position(),
}),
}
}
fn expect_comma(&mut self) -> Result<(), ParseError> {
match self.next()? {
Some(Token::Comma) => Ok(()),
other => Err(ParseError {
message: format!("expected comma, got {other:?}"),
position: self.lexer.position(),
}),
}
}
fn expect_rparen(&mut self) -> Result<(), ParseError> {
match self.next()? {
Some(Token::RParen) => Ok(()),
other => Err(ParseError {
message: format!("expected ')', got {other:?}"),
position: self.lexer.position(),
}),
}
}
fn at_rparen(&mut self) -> Result<bool, ParseError> {
Ok(matches!(self.peek()?, Some(Token::RParen)))
}
fn parse_1f(&mut self) -> Result<f32, ParseError> {
let v = self.expect_number()?;
self.expect_rparen()?;
Ok(v)
}
fn parse_2f(&mut self) -> Result<(f32, f32), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_rparen()?;
Ok((a, b))
}
fn parse_tpms_fields(&mut self) -> Result<(f32, f32), ParseError> {
let (scale, thickness) = self.parse_2f()?;
self.validate_tpms_fields(scale, thickness)?;
Ok((scale, thickness))
}
fn validate_tpms_fields(&self, scale: f32, thickness: f32) -> Result<(), ParseError> {
crate::limits::checked_tpms_fields(scale, thickness)
.map(|_| ())
.map_err(|e| self.spec_error(e))
}
fn parse_skadis_panel_args(&mut self) -> Result<(f32, f32, f32), ParseError> {
const DEFAULT_SIZE: f32 = 300.0;
const DEFAULT_THICKNESS: f32 = 5.0;
const DEFAULT_CORNER_R: f32 = 5.0;
if self.at_rparen()? {
self.expect_rparen()?;
return Ok((DEFAULT_SIZE, DEFAULT_THICKNESS, DEFAULT_CORNER_R));
}
let size = self.expect_number()?;
if self.at_rparen()? {
self.expect_rparen()?;
return Ok((size, DEFAULT_THICKNESS, DEFAULT_CORNER_R));
}
self.expect_comma()?;
let thickness = self.expect_number()?;
if self.at_rparen()? {
self.expect_rparen()?;
return Ok((size, thickness, DEFAULT_CORNER_R));
}
self.expect_comma()?;
let corner_r = self.expect_number()?;
self.expect_rparen()?;
Ok((size, thickness, corner_r))
}
fn metric_size(
&self,
m_size: f32,
) -> Result<crate::stdlib::hardsurface::fastener::MetricSize, ParseError> {
crate::stdlib::hardsurface::fastener::MetricSize::try_from_f32(m_size).ok_or_else(|| {
ParseError {
message: format!("ねじ呼び径が有限の数値でない: {m_size}"),
position: self.lexer.pos,
}
})
}
fn parse_3f(&mut self) -> Result<(f32, f32, f32), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_rparen()?;
Ok((a, b, c))
}
fn parse_4f(&mut self) -> Result<(f32, f32, f32, f32), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let d = self.expect_number()?;
self.expect_rparen()?;
Ok((a, b, c, d))
}
#[allow(clippy::type_complexity)]
fn parse_7f(&mut self) -> Result<(f32, f32, f32, f32, f32, f32, f32), ParseError> {
let v0 = self.expect_number()?;
self.expect_comma()?;
let v1 = self.expect_number()?;
self.expect_comma()?;
let v2 = self.expect_number()?;
self.expect_comma()?;
let v3 = self.expect_number()?;
self.expect_comma()?;
let v4 = self.expect_number()?;
self.expect_comma()?;
let v5 = self.expect_number()?;
self.expect_comma()?;
let v6 = self.expect_number()?;
self.expect_rparen()?;
Ok((v0, v1, v2, v3, v4, v5, v6))
}
fn parse_1f_child(&mut self) -> Result<(f32, SdfNode), ParseError> {
let v = self.expect_number()?;
self.expect_comma()?;
let child = self.parse_expr()?;
self.expect_rparen()?;
Ok((v, child))
}
fn parse_3f_child(&mut self) -> Result<(f32, f32, f32, SdfNode), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let child = self.parse_expr()?;
self.expect_rparen()?;
Ok((a, b, c, child))
}
#[allow(clippy::type_complexity)]
fn parse_4f_child(&mut self) -> Result<(f32, f32, f32, f32, SdfNode), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let d = self.expect_number()?;
self.expect_comma()?;
let child = self.parse_expr()?;
self.expect_rparen()?;
Ok((a, b, c, d, child))
}
fn parse_child_only(&mut self) -> Result<SdfNode, ParseError> {
let child = self.parse_expr()?;
self.expect_rparen()?;
Ok(child)
}
fn parse_k_children(&mut self) -> Result<(f32, Vec<SdfNode>), ParseError> {
let k = self.expect_number()?;
self.expect_comma()?;
let children = self.parse_children()?;
Ok((k, children))
}
fn parse_2f_children(&mut self) -> Result<(f32, f32, Vec<SdfNode>), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let children = self.parse_children()?;
Ok((a, b, children))
}
fn parse_1f_ab(&mut self) -> Result<(f32, SdfNode, SdfNode), ParseError> {
let k = self.expect_number()?;
self.expect_comma()?;
let a = self.parse_expr()?;
self.expect_comma()?;
let b = self.parse_expr()?;
self.expect_rparen()?;
Ok((k, a, b))
}
fn parse_2f_ab(&mut self) -> Result<(f32, f32, SdfNode, SdfNode), ParseError> {
let v1 = self.expect_number()?;
self.expect_comma()?;
let v2 = self.expect_number()?;
self.expect_comma()?;
let a = self.parse_expr()?;
self.expect_comma()?;
let b = self.parse_expr()?;
self.expect_rparen()?;
Ok((v1, v2, a, b))
}
#[allow(clippy::many_single_char_names)]
fn parse_6f_child(&mut self) -> Result<SixFloatsChild, ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let d = self.expect_number()?;
self.expect_comma()?;
let e = self.expect_number()?;
self.expect_comma()?;
let f = self.expect_number()?;
self.expect_comma()?;
let child = self.parse_expr()?;
self.expect_rparen()?;
Ok((a, b, c, d, e, f, child))
}
#[allow(clippy::many_single_char_names)]
fn parse_5f(&mut self) -> Result<(f32, f32, f32, f32, f32), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let d = self.expect_number()?;
self.expect_comma()?;
let e = self.expect_number()?;
self.expect_rparen()?;
Ok((a, b, c, d, e))
}
#[allow(clippy::many_single_char_names)]
fn parse_6f(&mut self) -> Result<(f32, f32, f32, f32, f32, f32), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let d = self.expect_number()?;
self.expect_comma()?;
let e = self.expect_number()?;
self.expect_comma()?;
let f = self.expect_number()?;
self.expect_rparen()?;
Ok((a, b, c, d, e, f))
}
#[allow(clippy::many_single_char_names, clippy::type_complexity)]
fn parse_9f(&mut self) -> Result<(f32, f32, f32, f32, f32, f32, f32, f32, f32), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let d = self.expect_number()?;
self.expect_comma()?;
let e = self.expect_number()?;
self.expect_comma()?;
let f = self.expect_number()?;
self.expect_comma()?;
let g = self.expect_number()?;
self.expect_comma()?;
let h = self.expect_number()?;
self.expect_comma()?;
let i = self.expect_number()?;
self.expect_rparen()?;
Ok((a, b, c, d, e, f, g, h, i))
}
#[allow(clippy::many_single_char_names, clippy::type_complexity)]
fn parse_10f(
&mut self,
) -> Result<(f32, f32, f32, f32, f32, f32, f32, f32, f32, f32), ParseError> {
let a = self.expect_number()?;
self.expect_comma()?;
let b = self.expect_number()?;
self.expect_comma()?;
let c = self.expect_number()?;
self.expect_comma()?;
let d = self.expect_number()?;
self.expect_comma()?;
let e = self.expect_number()?;
self.expect_comma()?;
let f = self.expect_number()?;
self.expect_comma()?;
let g = self.expect_number()?;
self.expect_comma()?;
let h = self.expect_number()?;
self.expect_comma()?;
let i = self.expect_number()?;
self.expect_comma()?;
let j = self.expect_number()?;
self.expect_rparen()?;
Ok((a, b, c, d, e, f, g, h, i, j))
}
fn parse_children(&mut self) -> Result<Vec<SdfNode>, ParseError> {
let mut children = vec![self.parse_expr()?];
while !self.at_rparen()? {
self.expect_comma()?;
if self.at_rparen()? {
break;
}
children.push(self.parse_expr()?);
}
self.expect_rparen()?;
if children.len() < 2 {
return Err(ParseError {
message: "operations require at least 2 children".into(),
position: self.lexer.position(),
});
}
Ok(children)
}
fn fold_left<F>(children: Vec<SdfNode>, f: F) -> SdfNode
where
F: Fn(SdfNode, SdfNode) -> SdfNode,
{
let mut iter = children.into_iter();
let first = iter.next().expect("at least 2 children");
iter.fold(first, &f)
}
fn parse_expr(&mut self) -> Result<SdfNode, ParseError> {
stacker::maybe_grow(4 * 1024 * 1024, 32 * 1024 * 1024, || {
self.parse_expr_inner()
})
}
#[allow(clippy::too_many_lines)]
fn parse_expr_inner(&mut self) -> Result<SdfNode, ParseError> {
let name = match self.next()? {
Some(Token::Ident(s)) => s,
other => {
return Err(ParseError {
message: format!("expected function name, got {other:?}"),
position: self.lexer.position(),
})
}
};
if name == "field" {
let _field_name = match self.next()? {
Some(Token::Ident(s)) => s,
other => {
return Err(ParseError {
message: format!("expected field name, got {other:?}"),
position: self.lexer.position(),
})
}
};
return self.parse_expr();
}
match self.next()? {
Some(Token::LParen) => {}
other => {
return Err(ParseError {
message: format!("expected '(' after '{name}', got {other:?}"),
position: self.lexer.position(),
})
}
}
match name.as_str() {
"sphere" => {
let r = self.parse_1f()?;
Ok(SdfNode::Sphere { radius: r })
}
"box3d" => {
let (hx, hy, hz) = self.parse_3f()?;
Ok(SdfNode::Box3d {
half_extents: Vec3::new(hx, hy, hz),
})
}
"rounded_box" => {
let (hx, hy, hz, r) = self.parse_4f()?;
Ok(SdfNode::RoundedBox {
half_extents: Vec3::new(hx, hy, hz),
round_radius: r,
})
}
"cylinder" => {
let (r, h) = self.parse_2f()?;
Ok(SdfNode::Cylinder {
radius: r,
half_height: h,
})
}
"torus" => {
let (major, minor) = self.parse_2f()?;
Ok(SdfNode::Torus {
major_radius: major,
minor_radius: minor,
})
}
"cone" => {
let (r, h) = self.parse_2f()?;
Ok(SdfNode::Cone {
radius: r,
half_height: h,
})
}
"capsule" => {
let (r, h) = self.parse_2f()?;
Ok(SdfNode::Capsule {
point_a: Vec3::new(0.0, -h, 0.0),
point_b: Vec3::new(0.0, h, 0.0),
radius: r,
})
}
"capsule_ab" => {
let (ax, ay, az, bx, by, bz, r) = self.parse_7f()?;
Ok(SdfNode::Capsule {
point_a: Vec3::new(ax, ay, az),
point_b: Vec3::new(bx, by, bz),
radius: r,
})
}
"ellipsoid" => {
let (rx, ry, rz) = self.parse_3f()?;
Ok(SdfNode::Ellipsoid {
radii: Vec3::new(rx, ry, rz),
})
}
"plane" => {
let (nx, ny, nz, d) = self.parse_4f()?;
Ok(SdfNode::Plane {
normal: Vec3::new(nx, ny, nz),
distance: d,
})
}
"octahedron" => {
let s = self.parse_1f()?;
Ok(SdfNode::Octahedron { size: s })
}
"rounded_cone" => {
let (r1, r2, h) = self.parse_3f()?;
Ok(SdfNode::RoundedCone {
r1,
r2,
half_height: h,
})
}
"pyramid" => {
let h = self.parse_1f()?;
Ok(SdfNode::Pyramid { half_height: h })
}
"hex_prism" => {
let (r, h) = self.parse_2f()?;
Ok(SdfNode::HexPrism {
hex_radius: r,
half_height: h,
})
}
"link" => {
let (l, r1, r2) = self.parse_3f()?;
Ok(SdfNode::Link {
half_length: l,
r1,
r2,
})
}
"capped_cone" => {
let (h, r1, r2) = self.parse_3f()?;
Ok(SdfNode::CappedCone {
half_height: h,
r1,
r2,
})
}
"capped_torus" => {
let (maj, min, ang) = self.parse_3f()?;
Ok(SdfNode::CappedTorus {
major_radius: maj,
minor_radius: min,
cap_angle: ang,
})
}
"rounded_cylinder" => {
let (r, rr, h) = self.parse_3f()?;
Ok(SdfNode::RoundedCylinder {
radius: r,
round_radius: rr,
half_height: h,
})
}
"tube" => {
let (or, t, h) = self.parse_3f()?;
Ok(SdfNode::Tube {
outer_radius: or,
thickness: t,
half_height: h,
})
}
"barrel" => {
let (r, h, b) = self.parse_3f()?;
Ok(SdfNode::Barrel {
radius: r,
half_height: h,
bulge: b,
})
}
"heart" => {
let s = self.parse_1f()?;
Ok(SdfNode::Heart { size: s })
}
"egg" => {
let (ra, rb) = self.parse_2f()?;
Ok(SdfNode::Egg { ra, rb })
}
"helix" => {
let (mr, mi, p, h) = self.parse_4f()?;
Ok(SdfNode::Helix {
major_r: mr,
minor_r: mi,
pitch: p,
half_height: h,
})
}
"tetrahedron" => {
let s = self.parse_1f()?;
Ok(SdfNode::Tetrahedron { size: s })
}
"box_frame" => {
let (hx, hy, hz, e) = self.parse_4f()?;
Ok(SdfNode::BoxFrame {
half_extents: Vec3::new(hx, hy, hz),
edge: e,
})
}
"diamond" => {
let (r, h) = self.parse_2f()?;
Ok(SdfNode::Diamond {
radius: r,
half_height: h,
})
}
"star_polygon" => {
let (r, n, m, h) = self.parse_4f()?;
Ok(SdfNode::StarPolygon {
radius: r,
n_points: n,
m,
half_height: h,
})
}
"cross_shape" => {
let (l, t, r, h) = self.parse_4f()?;
Ok(SdfNode::CrossShape {
length: l,
thickness: t,
round_radius: r,
half_height: h,
})
}
"union" => {
let children = self.parse_children()?;
Ok(Self::fold_left(children, |a, b| SdfNode::Union {
a: Arc::new(a),
b: Arc::new(b),
}))
}
"smooth_union" => {
let (k, children) = self.parse_k_children()?;
Ok(Self::fold_left(children, |a, b| SdfNode::SmoothUnion {
a: Arc::new(a),
b: Arc::new(b),
k,
}))
}
"intersection" => {
let children = self.parse_children()?;
Ok(Self::fold_left(children, |a, b| SdfNode::Intersection {
a: Arc::new(a),
b: Arc::new(b),
}))
}
"smooth_intersection" => {
let (k, children) = self.parse_k_children()?;
Ok(Self::fold_left(children, |a, b| {
SdfNode::SmoothIntersection {
a: Arc::new(a),
b: Arc::new(b),
k,
}
}))
}
"subtract" => {
let a = self.parse_expr()?;
self.expect_comma()?;
let b = self.parse_expr()?;
self.expect_rparen()?;
Ok(SdfNode::Subtraction {
a: Arc::new(a),
b: Arc::new(b),
})
}
"smooth_subtract" => {
let (k, a, b) = self.parse_1f_ab()?;
Ok(SdfNode::SmoothSubtraction {
a: Arc::new(a),
b: Arc::new(b),
k,
})
}
"chamfer_union" => {
let (r, children) = self.parse_k_children()?;
Ok(Self::fold_left(children, |a, b| SdfNode::ChamferUnion {
a: Arc::new(a),
b: Arc::new(b),
r,
}))
}
"chamfer_intersection" => {
let (r, children) = self.parse_k_children()?;
Ok(Self::fold_left(children, |a, b| {
SdfNode::ChamferIntersection {
a: Arc::new(a),
b: Arc::new(b),
r,
}
}))
}
"chamfer_subtraction" => {
let (r, a, b) = self.parse_1f_ab()?;
Ok(SdfNode::ChamferSubtraction {
a: Arc::new(a),
b: Arc::new(b),
r,
})
}
"stairs_union" => {
let (r, n, children) = self.parse_2f_children()?;
Ok(Self::fold_left(children, |a, b| SdfNode::StairsUnion {
a: Arc::new(a),
b: Arc::new(b),
r,
n,
}))
}
"stairs_intersection" => {
let (r, n, children) = self.parse_2f_children()?;
Ok(Self::fold_left(children, |a, b| {
SdfNode::StairsIntersection {
a: Arc::new(a),
b: Arc::new(b),
r,
n,
}
}))
}
"stairs_subtraction" => {
let (r, n, a, b) = self.parse_2f_ab()?;
Ok(SdfNode::StairsSubtraction {
a: Arc::new(a),
b: Arc::new(b),
r,
n,
})
}
"xor" => {
let a = self.parse_expr()?;
self.expect_comma()?;
let b = self.parse_expr()?;
self.expect_rparen()?;
Ok(SdfNode::XOR {
a: Arc::new(a),
b: Arc::new(b),
})
}
"pipe" => {
let (r, a, b) = self.parse_1f_ab()?;
Ok(SdfNode::Pipe {
a: Arc::new(a),
b: Arc::new(b),
r,
})
}
"engrave" => {
let (r, a, b) = self.parse_1f_ab()?;
Ok(SdfNode::Engrave {
a: Arc::new(a),
b: Arc::new(b),
r,
})
}
"groove" => {
let (ra, rb, a, b) = self.parse_2f_ab()?;
Ok(SdfNode::Groove {
a: Arc::new(a),
b: Arc::new(b),
ra,
rb,
})
}
"tongue" => {
let (ra, rb, a, b) = self.parse_2f_ab()?;
Ok(SdfNode::Tongue {
a: Arc::new(a),
b: Arc::new(b),
ra,
rb,
})
}
"columns_union" => {
let (r, n, children) = self.parse_2f_children()?;
Ok(Self::fold_left(children, |a, b| SdfNode::ColumnsUnion {
a: Arc::new(a),
b: Arc::new(b),
r,
n,
}))
}
"columns_intersection" => {
let (r, n, children) = self.parse_2f_children()?;
Ok(Self::fold_left(children, |a, b| {
SdfNode::ColumnsIntersection {
a: Arc::new(a),
b: Arc::new(b),
r,
n,
}
}))
}
"columns_subtraction" => {
let (r, n, a, b) = self.parse_2f_ab()?;
Ok(SdfNode::ColumnsSubtraction {
a: Arc::new(a),
b: Arc::new(b),
r,
n,
})
}
"exp_smooth_union" => {
let (k, children) = self.parse_k_children()?;
Ok(Self::fold_left(children, |a, b| SdfNode::ExpSmoothUnion {
a: Arc::new(a),
b: Arc::new(b),
k,
}))
}
"exp_smooth_intersection" => {
let (k, children) = self.parse_k_children()?;
Ok(Self::fold_left(children, |a, b| {
SdfNode::ExpSmoothIntersection {
a: Arc::new(a),
b: Arc::new(b),
k,
}
}))
}
"exp_smooth_subtraction" => {
let (k, a, b) = self.parse_1f_ab()?;
Ok(SdfNode::ExpSmoothSubtraction {
a: Arc::new(a),
b: Arc::new(b),
k,
})
}
"translate" => {
let (x, y, z, child) = self.parse_3f_child()?;
Ok(SdfNode::Translate {
child: Arc::new(child),
offset: Vec3::new(x, y, z),
})
}
"rotate" => {
let (rx, ry, rz, child) = self.parse_3f_child()?;
Ok(SdfNode::Rotate {
child: Arc::new(child),
rotation: crate::angle::quat_from_euler_xyz(
crate::angle::canon_angle_deg(rx).to_radians(),
crate::angle::canon_angle_deg(ry).to_radians(),
crate::angle::canon_angle_deg(rz).to_radians(),
),
})
}
"rotate_q" => {
let (x, y, z, w, child) = self.parse_4f_child()?;
let rotation = crate::limits::checked_rotation_quat(x, y, z, w)
.map_err(|e| self.spec_error(e))?;
Ok(SdfNode::Rotate {
child: Arc::new(child),
rotation,
})
}
"scale" => {
let (f, child) = self.parse_1f_child()?;
Ok(SdfNode::Scale {
child: Arc::new(child),
factor: f,
})
}
"scale_non_uniform" => {
let (sx, sy, sz, child) = self.parse_3f_child()?;
Ok(SdfNode::ScaleNonUniform {
child: Arc::new(child),
factors: Vec3::new(sx, sy, sz),
})
}
"round" => {
let (r, child) = self.parse_1f_child()?;
Ok(SdfNode::Round {
child: Arc::new(child),
radius: r,
})
}
"onion" => {
let (t, child) = self.parse_1f_child()?;
Ok(SdfNode::Onion {
child: Arc::new(child),
thickness: t,
})
}
"twist" => {
let (s, child) = self.parse_1f_child()?;
Ok(SdfNode::Twist {
child: Arc::new(child),
strength: s,
})
}
"bend" => {
let (c, child) = self.parse_1f_child()?;
Ok(SdfNode::Bend {
child: Arc::new(child),
curvature: c,
})
}
"mirror" => {
let (ax, ay, az, child) = self.parse_3f_child()?;
Ok(SdfNode::Mirror {
child: Arc::new(child),
axes: Vec3::new(ax, ay, az),
})
}
"repeat" => {
let (sx, sy, sz, child) = self.parse_3f_child()?;
Ok(SdfNode::RepeatInfinite {
child: Arc::new(child),
spacing: Vec3::new(sx, sy, sz),
})
}
"elongate" => {
let (ax, ay, az, child) = self.parse_3f_child()?;
Ok(SdfNode::Elongate {
child: Arc::new(child),
amount: Vec3::new(ax, ay, az),
})
}
"revolution" => {
let (off, child) = self.parse_1f_child()?;
Ok(SdfNode::Revolution {
child: Arc::new(child),
offset: off,
})
}
"extrude" => {
let (h, child) = self.parse_1f_child()?;
Ok(SdfNode::Extrude {
child: Arc::new(child),
half_height: h,
})
}
"taper" => {
let (f, child) = self.parse_1f_child()?;
Ok(child.taper(f))
}
"displacement" => {
let (s, child) = self.parse_1f_child()?;
Ok(SdfNode::Displacement {
child: Arc::new(child),
strength: s,
})
}
"polar_repeat" => {
let (c, child) = self.parse_1f_child()?;
Ok(SdfNode::PolarRepeat {
child: Arc::new(child),
count: lol_u32(c),
})
}
"shear" => {
let (xy, xz, yz, child) = self.parse_3f_child()?;
Ok(SdfNode::Shear {
child: Arc::new(child),
shear: Vec3::new(xy, xz, yz),
})
}
"noise" => {
let (amp, freq, seed, child) = self.parse_3f_child()?;
Ok(SdfNode::Noise {
child: Arc::new(child),
amplitude: amp,
frequency: freq,
seed: lol_u32(seed),
})
}
"repeat_finite" => {
let (cx, cy, cz, sx, sy, sz, child) = self.parse_6f_child()?;
Ok(SdfNode::RepeatFinite {
child: Arc::new(child),
count: [lol_u32(cx), lol_u32(cy), lol_u32(cz)],
spacing: Vec3::new(sx, sy, sz),
})
}
"octant_mirror" => {
let child = self.parse_child_only()?;
Ok(SdfNode::OctantMirror {
child: Arc::new(child),
})
}
"icosahedral_symmetry" => {
let child = self.parse_child_only()?;
Ok(SdfNode::IcosahedralSymmetry {
child: Arc::new(child),
})
}
"with_material" => {
let (id, child) = self.parse_1f_child()?;
Ok(SdfNode::WithMaterial {
child: Arc::new(child),
material_id: lol_u32(id),
})
}
"surface_roughness" => {
let (freq, amp, oct, child) = self.parse_3f_child()?;
Ok(SdfNode::SurfaceRoughness {
child: Arc::new(child),
frequency: freq,
amplitude: amp,
octaves: lol_u32(oct),
})
}
"lattice_infill" => {
let (shell_t, scale, lattice_t, child) = self.parse_3f_child()?;
self.validate_tpms_fields(scale, lattice_t)?;
Ok(SdfNode::Union {
a: Arc::new(SdfNode::Onion {
child: Arc::new(child.clone()),
thickness: shell_t,
}),
b: Arc::new(SdfNode::Intersection {
a: Arc::new(child),
b: Arc::new(SdfNode::Gyroid {
scale,
thickness: lattice_t,
}),
}),
})
}
"diamond_infill" => {
let (shell_t, scale, lattice_t, child) = self.parse_3f_child()?;
self.validate_tpms_fields(scale, lattice_t)?;
Ok(SdfNode::Union {
a: Arc::new(SdfNode::Onion {
child: Arc::new(child.clone()),
thickness: shell_t,
}),
b: Arc::new(SdfNode::Intersection {
a: Arc::new(child),
b: Arc::new(SdfNode::DiamondSurface {
scale,
thickness: lattice_t,
}),
}),
})
}
"schwarz_infill" => {
let (shell_t, scale, lattice_t, child) = self.parse_3f_child()?;
self.validate_tpms_fields(scale, lattice_t)?;
Ok(SdfNode::Union {
a: Arc::new(SdfNode::Onion {
child: Arc::new(child.clone()),
thickness: shell_t,
}),
b: Arc::new(SdfNode::Intersection {
a: Arc::new(child),
b: Arc::new(SdfNode::SchwarzP {
scale,
thickness: lattice_t,
}),
}),
})
}
"animate" => {
let speed = self.expect_number()?;
self.expect_comma()?;
let amplitude = self.expect_number()?;
self.expect_comma()?;
let child = self.parse_expr()?;
self.expect_rparen()?;
Ok(SdfNode::Animated {
child: Arc::new(child),
speed,
amplitude,
})
}
"morph" => {
let t = self.expect_number()?;
self.expect_comma()?;
let a = self.parse_expr()?;
self.expect_comma()?;
let b = self.parse_expr()?;
self.expect_rparen()?;
Ok(SdfNode::Morph {
a: Arc::new(a),
b: Arc::new(b),
t,
})
}
"triangle" => {
let (ax, ay, az, bx, by, bz, cx, cy, cz) = self.parse_9f()?;
Ok(SdfNode::Triangle {
point_a: Vec3::new(ax, ay, az),
point_b: Vec3::new(bx, by, bz),
point_c: Vec3::new(cx, cy, cz),
})
}
"bezier" => {
let (ax, ay, az, bx, by, bz, cx, cy, cz, r) = self.parse_10f()?;
Ok(SdfNode::Bezier {
point_a: Vec3::new(ax, ay, az),
point_b: Vec3::new(bx, by, bz),
point_c: Vec3::new(cx, cy, cz),
radius: r,
})
}
"triangular_prism" => {
let (w, d) = self.parse_2f()?;
Ok(SdfNode::TriangularPrism {
width: w,
half_depth: d,
})
}
"cut_sphere" => {
let (r, h) = self.parse_2f()?;
Ok(SdfNode::CutSphere {
radius: r,
cut_height: h,
})
}
"cut_hollow_sphere" => {
let (r, h, t) = self.parse_3f()?;
Ok(SdfNode::CutHollowSphere {
radius: r,
cut_height: h,
thickness: t,
})
}
"death_star" => {
let (ra, rb, d) = self.parse_3f()?;
Ok(SdfNode::DeathStar { ra, rb, d })
}
"solid_angle" => {
let (a, r) = self.parse_2f()?;
Ok(SdfNode::SolidAngle {
angle: a,
radius: r,
})
}
"rhombus" => {
let (la, lb, h, r) = self.parse_4f()?;
Ok(SdfNode::Rhombus {
la,
lb,
half_height: h,
round_radius: r,
})
}
"horseshoe" => {
let (angle, radius, half_length, width, thickness) = self.parse_5f()?;
Ok(SdfNode::Horseshoe {
angle,
radius,
half_length,
width,
thickness,
})
}
"vesica" => {
let (r, d) = self.parse_2f()?;
Ok(SdfNode::Vesica {
radius: r,
half_dist: d,
})
}
"infinite_cylinder" => {
let r = self.parse_1f()?;
Ok(SdfNode::InfiniteCylinder { radius: r })
}
"infinite_cone" => {
let a = self.parse_1f()?;
Ok(SdfNode::InfiniteCone { angle: a })
}
"gyroid" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::Gyroid {
scale: s,
thickness: t,
})
}
"chamfered_cube" => {
let (hx, hy, hz, c) = self.parse_4f()?;
Ok(SdfNode::ChamferedCube {
half_extents: Vec3::new(hx, hy, hz),
chamfer: c,
})
}
"schwarz_p" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::SchwarzP {
scale: s,
thickness: t,
})
}
"superellipsoid" => {
let (hx, hy, hz, e1, e2) = self.parse_5f()?;
Ok(SdfNode::Superellipsoid {
half_extents: Vec3::new(hx, hy, hz),
e1,
e2,
})
}
"rounded_x" => {
let (w, r, h) = self.parse_3f()?;
Ok(SdfNode::RoundedX {
width: w,
round_radius: r,
half_height: h,
})
}
"pie" => {
let (a, r, h) = self.parse_3f()?;
Ok(SdfNode::Pie {
angle: a,
radius: r,
half_height: h,
})
}
"trapezoid" => {
let (r1, r2, th, d) = self.parse_4f()?;
Ok(SdfNode::Trapezoid {
r1,
r2,
trap_height: th,
half_depth: d,
})
}
"parallelogram" => {
let (w, h, s, d) = self.parse_4f()?;
Ok(SdfNode::Parallelogram {
width: w,
para_height: h,
skew: s,
half_depth: d,
})
}
"tunnel" => {
let (w, h, d) = self.parse_3f()?;
Ok(SdfNode::Tunnel {
width: w,
height_2d: h,
half_depth: d,
})
}
"uneven_capsule" => {
let (r1, r2, h, d) = self.parse_4f()?;
Ok(SdfNode::UnevenCapsule {
r1,
r2,
cap_height: h,
half_depth: d,
})
}
"arc_shape" => {
let (a, r, t, h) = self.parse_4f()?;
Ok(SdfNode::ArcShape {
aperture: a,
radius: r,
thickness: t,
half_height: h,
})
}
"moon" => {
let (d, ra, rb, h) = self.parse_4f()?;
Ok(SdfNode::Moon {
d,
ra,
rb,
half_height: h,
})
}
"blobby_cross" => {
let (s, h) = self.parse_2f()?;
Ok(SdfNode::BlobbyCross {
size: s,
half_height: h,
})
}
"parabola_segment" => {
let (w, h, d) = self.parse_3f()?;
Ok(SdfNode::ParabolaSegment {
width: w,
para_height: h,
half_depth: d,
})
}
"regular_polygon" => {
let (r, n, h) = self.parse_3f()?;
Ok(SdfNode::RegularPolygon {
radius: r,
n_sides: n,
half_height: h,
})
}
"stairs_prim" => {
let (sw, sh, n, d) = self.parse_4f()?;
let n = self.bounded_count(n)?;
Ok(SdfNode::Stairs {
step_width: sw,
step_height: sh,
n_steps: n,
half_depth: d,
})
}
"dodecahedron" => {
let r = self.parse_1f()?;
Ok(SdfNode::Dodecahedron { radius: r })
}
"icosahedron" => {
let r = self.parse_1f()?;
Ok(SdfNode::Icosahedron { radius: r })
}
"truncated_octahedron" => {
let r = self.parse_1f()?;
Ok(SdfNode::TruncatedOctahedron { radius: r })
}
"truncated_icosahedron" => {
let r = self.parse_1f()?;
Ok(SdfNode::TruncatedIcosahedron { radius: r })
}
"diamond_surface" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::DiamondSurface {
scale: s,
thickness: t,
})
}
"neovius" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::Neovius {
scale: s,
thickness: t,
})
}
"lidinoid" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::Lidinoid {
scale: s,
thickness: t,
})
}
"iwp" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::IWP {
scale: s,
thickness: t,
})
}
"frd" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::FRD {
scale: s,
thickness: t,
})
}
"fischer_koch_s" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::FischerKochS {
scale: s,
thickness: t,
})
}
"pmy" => {
let (s, t) = self.parse_tpms_fields()?;
Ok(SdfNode::PMY {
scale: s,
thickness: t,
})
}
"circle_2d" => {
let (r, h) = self.parse_2f()?;
Ok(SdfNode::Circle2D {
radius: r,
half_height: h,
})
}
"rect_2d" => {
let (hx, hy, h) = self.parse_3f()?;
Ok(SdfNode::Rect2D {
half_extents: Vec2::new(hx, hy),
half_height: h,
})
}
"segment_2d" => {
let (ax, ay, bx, by, t, h) = self.parse_6f()?;
Ok(SdfNode::Segment2D {
a: Vec2::new(ax, ay),
b: Vec2::new(bx, by),
thickness: t,
half_height: h,
})
}
"rounded_rect_2d" => {
let (hx, hy, r, h) = self.parse_4f()?;
Ok(SdfNode::RoundedRect2D {
half_extents: Vec2::new(hx, hy),
round_radius: r,
half_height: h,
})
}
"annular_2d" => {
let (r, t, h) = self.parse_3f()?;
Ok(SdfNode::Annular2D {
outer_radius: r,
thickness: t,
half_height: h,
})
}
"terrain" => {
let (s, a) = self.parse_2f()?;
Ok(SdfNode::Terrain {
scale: s,
amplitude: a,
})
}
"sweep_bezier" => {
let (p0x, p0y, p1x, p1y, p2x, p2y, child) = self.parse_6f_child()?;
Ok(SdfNode::SweepBezier {
child: Arc::new(child),
p0: Vec2::new(p0x, p0y),
p1: Vec2::new(p1x, p1y),
p2: Vec2::new(p2x, p2y),
})
}
"shopping_cart_coin" => {
let (dia, thickness) = self.parse_2f()?;
Ok(crate::stdlib::hardsurface::thin_sdf::shopping_cart_coin_sdf(dia, thickness))
}
"skadis_panel" => {
let (size, thickness, corner_r) = self.parse_skadis_panel_args()?;
crate::stdlib::hardsurface::skadis_sdf::try_skadis_panel_sdf(
size, thickness, corner_r,
)
.map_err(|e| self.spec_error(e))
}
"skadis_hook_l" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::skadis_sdf::skadis_hook_l_sdf())
}
"skadis_hook_j" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::skadis_sdf::skadis_hook_j_sdf())
}
"skadis_hook_s" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::skadis_sdf::skadis_hook_s_sdf())
}
"skadis_container" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::skadis_sdf::skadis_container_sdf())
}
"skadis_clip" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::skadis_sdf::skadis_clip_sdf())
}
"skadis_shelf" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::skadis_sdf::skadis_shelf_sdf())
}
"skadis_elastic_cord" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::skadis_sdf::skadis_elastic_cord_sdf())
}
"mug" => {
let (dia, height) = self.parse_2f()?;
Ok(crate::stdlib::products::mug_sdf(dia, height))
}
"gridfinity_bin" => {
let (ux, uy, hu) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::GridfinitySpec {
units_x: self.count_trunc(ux)?,
units_y: self.count_trunc(uy)?,
height_u: self.count_trunc(hu)?,
dividers: None,
wall_thickness: 1.2,
floor_thickness: 1.5,
};
crate::stdlib::hardsurface::pattern_sdf::try_gridfinity_bin(&spec)
.map_err(|e| self.spec_error(e))
}
"gridfinity_bin_ex" => {
let (ux, uy, hu, div_x, div_y, wall, floor) = self.parse_7f()?;
let dividers = if div_x >= 1.0 && div_y >= 1.0 {
Some((self.count_trunc(div_x)?, self.count_trunc(div_y)?))
} else {
None
};
let wall_thickness = if wall > 0.0 { wall } else { 1.2 };
let floor_thickness = if floor > 0.0 { floor } else { 1.5 };
let spec = crate::stdlib::hardsurface::pattern_sdf::GridfinitySpec {
units_x: self.count_trunc(ux)?,
units_y: self.count_trunc(uy)?,
height_u: self.count_trunc(hu)?,
dividers,
wall_thickness,
floor_thickness,
};
crate::stdlib::hardsurface::pattern_sdf::try_gridfinity_bin(&spec)
.map_err(|e| self.spec_error(e))
}
"wall_hook" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::pattern_sdf::wall_hook(
&crate::stdlib::hardsurface::pattern_sdf::WallHookSpec::pla_1kgf(),
))
}
"drawer_organizer" => {
self.expect_rparen()?;
Ok(crate::stdlib::hardsurface::pattern_sdf::drawer_organizer(
&crate::stdlib::hardsurface::pattern_sdf::DrawerSpec::default_chopsticks_set(),
))
}
"shelf_divider" => {
self.expect_rparen()?;
crate::stdlib::hardsurface::pattern_sdf::try_shelf_divider(
&crate::stdlib::hardsurface::pattern_sdf::ShelfDividerSpec::field_tested_560x250x120(),
)
.map_err(|e| self.spec_error(e))
}
"sticky_note_holder" => {
let (pw, pd, h) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::StickyNoteHolderSpec {
pad_width: pw,
pad_depth: pd,
height: h,
wall_thickness: 1.5,
floor_thickness: 1.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::sticky_note_holder(
&spec,
))
}
"business_card_holder" => {
let (cw, ch, st) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::BusinessCardHolderSpec {
card_width: cw,
card_height: ch,
slot_thickness: st,
slot_depth: ch * 0.6,
wall_thickness: 1.5,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::business_card_holder(&spec))
}
"pen_cup" => {
let (dia, h) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::PenCupSpec {
inner_diameter: dia,
height: h,
wall_thickness: 2.0,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::pen_cup(&spec))
}
"phone_stand" => {
let (sw, bh, chd) = self.parse_3f()?;
let cable_hole_dia = if chd > 0.0 { Some(chd) } else { None };
let spec = crate::stdlib::hardsurface::pattern_sdf::PhoneStandSpec {
slot_width: sw,
slot_depth: 6.0,
base_width: 90.0,
base_depth: 90.0,
base_thickness: 6.0,
back_height: bh,
back_thickness: 5.0,
cable_hole_dia,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::phone_stand(&spec))
}
"headphone_holder" => {
let (al, hw, mw) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::HeadphoneHolderSpec {
arm_length: al,
arm_thickness: 6.0,
arm_width: hw,
mount_width: mw,
mount_height: 68.0,
mount_thickness: 6.0,
hook_tip_up: 18.0,
mount_hole_dia: Some(4.5),
};
Ok(crate::stdlib::hardsurface::pattern_sdf::headphone_holder(
&spec,
))
}
"under_desk_mount" => {
let (dt, cw, sd) = self.parse_3f()?;
let screw_hole_dia = if sd > 0.0 { Some(sd) } else { None };
let spec = crate::stdlib::hardsurface::pattern_sdf::UnderDeskMountSpec {
desk_thickness: dt,
clamp_width: cw,
clamp_depth: 50.0,
clamp_wall_thickness: 4.0,
screw_hole_dia,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::under_desk_mount(
&spec,
))
}
"desk_shelf" => {
let (sw, sd, lh) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::DeskShelfSpec {
shelf_width: sw,
shelf_depth: sd,
shelf_thickness: 5.0,
leg_height: lh,
leg_thickness: 20.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::desk_shelf(&spec))
}
"monitor_riser" => {
let (w, d, h) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::MonitorRiserSpec {
width: w,
depth: d,
height: h,
platform_thickness: 8.0,
leg_thickness: 25.0,
cable_hole_dia: Some(40.0),
};
Ok(crate::stdlib::hardsurface::pattern_sdf::monitor_riser(
&spec,
))
}
"coaster" => {
let (dia, t) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CoasterSpec {
diameter: dia,
thickness: t,
lip_width: 2.5,
lip_height: 1.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::coaster(&spec))
}
"tissue_box_cover" => {
let (il, iw, ih) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::TissueBoxCoverSpec {
internal_length: il,
internal_width: iw,
internal_height: ih,
wall_thickness: 1.6,
slot_length: 80.0,
slot_width: 30.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::tissue_box_cover(
&spec,
))
}
"storage_box" => {
let (il, iw, ih) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::StorageBoxSpec {
internal_length: il,
internal_width: iw,
internal_height: ih,
wall_thickness: 2.0,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::storage_box(&spec))
}
"cable_clip" => {
let (dia, len) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CableClipSpec {
cable_diameter: dia,
clip_length: len,
wall_thickness: 2.0,
opening_ratio: 0.7,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::cable_clip(&spec))
}
"led_channel" => {
let (sw, len) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::LedChannelSpec {
strip_width: sw,
channel_length: len,
channel_depth: 2.5,
wall_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::led_channel(&spec))
}
"card_tray" => {
let (cw, ch, d) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CardTraySpec {
card_width: cw,
card_height: ch,
tray_depth: d,
card_clearance: 1.0,
wall_thickness: 2.0,
floor_thickness: 1.5,
finger_notch_radius: 9.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::card_tray(&spec))
}
"token_well" => {
let (dia, depth, count) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::TokenWellSpec {
well_diameter: dia,
well_depth: depth,
well_count: self.count(count, 1.0)?,
well_clearance: 1.0,
wall_thickness: 2.0,
floor_thickness: 1.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::token_well(&spec))
}
"wrench_holder" => {
let (min_mm, max_mm, count) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::WrenchHolderSpec {
min_size_mm: min_mm,
max_size_mm: max_mm,
count: self.count(count, 1.0)?,
slot_depth: 22.0,
slot_clearance: 0.6,
thickness_ratio: 0.5,
wall_thickness: 3.0,
floor_thickness: 4.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::wrench_holder(
&spec,
))
}
"socket_rail" => {
let (dia, height, count) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SocketRailSpec {
post_diameter: dia,
post_height: height,
post_count: self.count(count, 1.0)?,
post_spacing: 6.0,
base_thickness: 4.0,
base_margin: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::socket_rail(&spec))
}
"hex_bit_holder" => {
let (rows, cols, spacing) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::HexBitHolderSpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
spacing,
wall_thickness: 2.0,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::hex_bit_holder(
&spec,
))
}
"raspi_case" => {
let (pcb_w, pcb_d, int_h) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::RaspiCaseSpec {
pcb_width: pcb_w,
pcb_depth: pcb_d,
internal_height: int_h,
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,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::raspi_case(&spec))
}
"esp32_enclosure" => {
let (pcb_w, pcb_d, int_h) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::Esp32EnclosureSpec {
pcb_width: pcb_w,
pcb_depth: pcb_d,
internal_height: int_h,
wall_thickness: 1.6,
floor_thickness: 2.0,
pcb_clearance: 0.5,
usb_opening_width: 9.0,
usb_opening_height: 5.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::esp32_enclosure(
&spec,
))
}
"battery_18650_holder" => {
let (count, wall, floor) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::Battery18650HolderSpec {
cell_count: self.count(count, 1.0)?,
wall_thickness: wall,
floor_thickness: floor,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::battery_18650_holder(&spec))
}
"toothbrush_holder" => {
let (count, dia, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::ToothbrushHolderSpec {
count: self.count(count, 1.0)?,
hole_diameter: dia,
hole_depth: height,
wall_thickness: 6.0,
floor_thickness: 4.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::toothbrush_holder(
&spec,
))
}
"drill_bit_holder" => {
let (min_mm, max_mm, count) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::DrillBitHolderSpec {
min_size_mm: min_mm,
max_size_mm: max_mm,
count: self.count(count, 1.0)?,
hole_depth: 22.0,
hole_clearance: 0.25,
wall_thickness: 3.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::drill_bit_holder(
&spec,
))
}
"pliers_rack" => {
let (count, width, depth) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::PliersRackSpec {
slot_count: self.count(count, 1.0)?,
slot_width: width,
slot_depth: depth,
slot_height: 35.0,
wall_thickness: 5.0,
floor_thickness: 5.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::pliers_rack(&spec))
}
"spice_rack" => {
let (count, dia, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SpiceRackSpec {
count: self.count(count, 1.0)?,
jar_diameter: dia,
jar_height: height,
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,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::spice_rack(&spec))
}
"egg_tray" => {
let (rows, cols, cup_depth) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::EggTraySpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
cup_depth,
pitch: 50.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::egg_tray(&spec))
}
"utensil_caddy" => {
let (count, dia, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::UtensilCaddySpec {
count: self.count(count, 1.0)?,
compartment_diameter: dia,
height,
wall_thickness: 5.0,
floor_thickness: 4.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::utensil_caddy(
&spec,
))
}
"filament_spool_holder" => {
let (od, width, bore) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::FilamentSpoolHolderSpec {
spool_outer_diameter: od,
spool_width: width,
bore_diameter: bore,
base_thickness: 5.0,
base_margin: 30.0,
peg_clearance: 1.0,
peg_extra_height: 20.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::filament_spool_holder(&spec))
}
"nozzle_holder" => {
let (count, dia, depth) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::NozzleHolderSpec {
count: self.count(count, 1.0)?,
hole_diameter: dia,
hole_depth: depth,
wall_thickness: 4.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::nozzle_holder(
&spec,
))
}
"build_plate_rack" => {
let (count, spacing, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::BuildPlateRackSpec {
slot_count: self.count(count, 1.0)?,
slot_spacing: spacing,
height,
slot_width: 5.5,
depth: 60.0,
wall_thickness: 5.0,
floor_thickness: 5.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::build_plate_rack(
&spec,
))
}
"cutlery_tray" => {
let (count, width, length) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CutleryTraySpec {
slot_count: self.count(count, 1.0)?,
slot_width: width,
slot_length: length,
slot_depth: 40.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::cutlery_tray(&spec))
}
"pill_organizer" => {
let (rows, cols, cell) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::PillOrganizerSpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
cell_size: cell,
cell_depth: 15.0,
wall_thickness: 1.5,
floor_thickness: 1.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::pill_organizer(
&spec,
))
}
"magnetic_strip" => {
let (count, dia, spacing) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::MagneticStripSpec {
magnet_count: self.count(count, 1.0)?,
magnet_diameter: dia,
magnet_spacing: spacing,
magnet_depth: 2.0,
bar_thickness: 5.0,
bar_height: 15.0,
end_margin: 5.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::magnetic_strip(
&spec,
))
}
"hairdryer_holder" => {
let (dia, depth, wall) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::HairdryerHolderSpec {
barrel_diameter: dia,
holster_depth: depth,
wall_thickness: wall,
floor_thickness: 5.0,
inner_clearance: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::hairdryer_holder(
&spec,
))
}
"kcup_holder" => {
let (rows, cols, dia) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::KcupHolderSpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
capsule_diameter: dia,
capsule_depth: 40.0,
capsule_clearance: 3.5,
wall_thickness: 3.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::kcup_holder(&spec))
}
"hex_key_holder" => {
let (count, min_mm, max_mm) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::HexKeyHolderSpec {
count: self.count(count, 1.0)?,
min_key_mm: min_mm,
max_key_mm: max_mm,
hole_depth: 18.0,
hole_clearance: 0.3,
wall_thickness: 3.0,
floor_thickness: 4.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::hex_key_holder(
&spec,
))
}
"wrap_holder" => {
let (dia, width, wall) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::WrapHolderSpec {
roll_diameter: dia,
roll_width: width,
wall_thickness: wall,
roll_clearance: 1.5,
cradle_depth_ratio: 0.6,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::wrap_holder(&spec))
}
"sock_divider" => {
let (count, width, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SockDividerSpec {
cell_count: self.count(count, 1.0)?,
cell_width: width,
height,
cell_depth: 100.0,
wall_thickness: 2.5,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::sock_divider(&spec))
}
"soap_tray" => {
let (length, width, count) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SoapTraySpec {
tray_length: length,
tray_width: width,
drain_slot_count: self.count(count, 1.0)?,
tray_depth: 12.0,
drain_slot_width: 3.0,
wall_thickness: 2.5,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::soap_tray(&spec))
}
"razor_holder" => {
let (slot_w, slot_d, mount_dia) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::RazorHolderSpec {
slot_width: slot_w,
slot_depth: slot_d,
mount_hole_diameter: mount_dia,
blade_clearance_width: 55.0,
backplate_width: 80.0,
total_height: 60.0,
wall_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::razor_holder(&spec))
}
"chopstick_holder" => {
let (count, width, length) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::ChopstickHolderSpec {
pair_count: self.count(count, 1.0)?,
slot_width: width,
slot_length: length,
slot_depth: 15.0,
wall_thickness: 2.5,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::chopstick_holder(
&spec,
))
}
"swatch_holder" => {
let (rows, cols, width) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SwatchHolderSpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
swatch_width: width,
swatch_height: 70.0,
swatch_thickness: 4.5,
wall_thickness: 2.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::swatch_holder(
&spec,
))
}
"tp_holder" => {
let (inner_dia, roll_w, wall_t) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::TpHolderSpec {
inner_diameter: inner_dia,
roll_width: roll_w,
wall_thickness: wall_t,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::tp_holder(&spec))
}
"sd_card_holder" => {
let (rows, cols, width) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SdCardHolderSpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
card_width: width,
card_height: 32.0,
card_thickness: 2.5,
wall_thickness: 1.5,
floor_thickness: 2.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::sd_card_holder(
&spec,
))
}
"driver_rack" => {
let (count, dia, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::DriverRackSpec {
slot_count: self.count(count, 1.0)?,
slot_diameter: dia,
height,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::driver_rack(&spec))
}
"cotton_dispenser" => {
let (count, inner_dia, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CottonDispenserSpec {
count: self.count(count, 1.0)?,
inner_diameter: inner_dia,
height,
wall_thickness: 2.5,
floor_thickness: 2.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::cotton_dispenser(
&spec,
))
}
"sink_caddy" => {
let (length, width, count) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SinkCaddySpec {
tray_length: length,
tray_width: width,
drain_hole_count: self.count(count, 1.0)?,
tray_depth: 30.0,
drain_hole_diameter: 6.0,
wall_thickness: 2.5,
floor_thickness: 2.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::sink_caddy(&spec))
}
"clamp_rack" => {
let (count, width, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::ClampRackSpec {
hook_count: self.count_smooth_folded(count, 1.0)?,
hook_width: width,
height,
hook_depth: 25.0,
wall_thickness: 5.0,
hook_opening: 15.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::clamp_rack(&spec))
}
"dry_box" => {
let (rows, cols, dia) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::DryBoxSpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
filament_diameter: dia,
spool_width: 70.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::dry_box(&spec))
}
"outdoor_enclosure" => {
let (w, d, h) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::OutdoorEnclosureSpec {
internal_width: w,
internal_depth: d,
internal_height: h,
wall_thickness: 3.5,
floor_thickness: 3.5,
gasket_width: 2.0,
gasket_depth: 1.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::outdoor_enclosure(
&spec,
))
}
"jewelry_stand" => {
let (count, dia, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::JewelryStandSpec {
tier_count: self.count(count, 1.0)?,
bottom_tier_diameter: dia,
height,
tier_thickness: 5.0,
pillar_diameter: 10.0,
tier_ratio: 0.7,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::jewelry_stand(
&spec,
))
}
"phone_dock" => {
let (w, h, cable) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::PhoneDockSpec {
width: w,
upright_height: h,
cable_diameter: cable,
base_depth: 60.0,
base_thickness: 6.0,
upright_thickness: 4.0,
tilt_angle_deg: 15.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::phone_dock(&spec))
}
"cutting_board_rack" => {
let (count, width, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CuttingBoardRackSpec {
slot_count: self.count(count, 1.0)?,
slot_width: width,
height,
slot_depth: 200.0,
wall_thickness: 4.0,
floor_thickness: 8.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::cutting_board_rack(
&spec,
))
}
"tape_dispenser" => {
let (inner_dia, roll_w, wall_t) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::TapeDispenserSpec {
inner_diameter: inner_dia,
roll_width: roll_w,
wall_thickness: wall_t,
outer_diameter: 150.0,
tear_angle_deg: 30.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::tape_dispenser(
&spec,
))
}
"shower_caddy" => {
let (count, length, depth) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::ShowerCaddySpec {
tier_count: self.count(count, 1.0)?,
tier_length: length,
tier_depth: depth,
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,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::shower_caddy(&spec))
}
"caliper_holder" => {
let (jaw, throat, count) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CaliperHolderSpec {
jaw_length: jaw,
throat_depth: throat,
count: self.count(count, 1.0)?,
slot_width: 15.0,
wall_thickness: 5.0,
mount_hole_diameter: 4.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::caliper_holder(
&spec,
))
}
"bag_clip_org" => {
let (count, width, height) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::BagClipOrgSpec {
slot_count: self.count(count, 1.0)?,
slot_width: width,
height,
wall_thickness: 2.5,
floor_thickness: 3.0,
slot_depth: 30.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::bag_clip_org(&spec))
}
"can_rack" => {
let (rows, dia, tilt) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CanRackSpec {
rows: self.count(rows, 1.0)?,
can_diameter: dia,
tilt_angle_deg: tilt,
cans_per_row: 6,
wall_thickness: 3.0,
shelf_thickness: 3.0,
front_lip_height: 15.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::can_rack(&spec))
}
"led_hub_box" => {
let (w, d, h) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::LedHubBoxSpec {
internal_width: w,
internal_depth: d,
internal_height: h,
led_window_width: 40.0,
led_window_height: 15.0,
antenna_hole_diameter: 12.0,
wall_thickness: 3.0,
floor_thickness: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::led_hub_box(&spec))
}
"makeup_organizer" => {
let (rows, cols, size) = self.parse_3f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::MakeupOrganizerSpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
cell_size: size,
cell_depth: 40.0,
wall_thickness: 2.0,
floor_thickness: 2.5,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::makeup_organizer(
&spec,
))
}
"vesa_mount" => {
let (size, plate_t, m_size) = self.parse_3f()?;
let hole = self.metric_size(m_size)?;
let spec = crate::stdlib::hardsurface::pattern_sdf::VesaMountSpec {
vesa_size: size,
plate_thickness: plate_t,
plate_margin: 15.0,
corner_radius: 3.0,
hole_size: hole,
bore_kind: 1,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::vesa_mount(&spec))
}
"l_bracket" => {
let (w, h, plate_t, m_size, holes) = self.parse_5f()?;
let hole = self.metric_size(m_size)?;
let spec = crate::stdlib::hardsurface::pattern_sdf::LBracketSpec {
arm_width: w,
arm_height: h,
plate_thickness: plate_t,
depth: 40.0,
fillet_radius: 3.0,
hole_size: hole,
holes_per_arm: self.count(holes, 1.0)?,
bore_kind: 0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::l_bracket(&spec))
}
"t_slot_bracket_2020" => {
let (arm, depth) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::TSlotBracket2020Spec {
arm_size: arm,
depth,
plate_thickness: 3.0,
fillet_radius: 3.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::t_slot_bracket_2020(&spec))
}
"raspi_mount_plate" => {
let (model, extras) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::RaspiMountPlateSpec {
model: self.count(model, 0.0)?,
extra_m4_holes: self.count(extras, 0.0)?,
plate_thickness: 4.0,
plate_margin: 15.0,
m25_hole_dia: 2.8,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::raspi_mount_plate(
&spec,
))
}
"heat_set_array" => {
let (rows, cols, m_size, pitch, base_t) = self.parse_5f()?;
let m = self.metric_size(m_size)?;
let spec = crate::stdlib::hardsurface::pattern_sdf::HeatSetArraySpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
insert_size: m,
pitch,
base_thickness: base_t,
margin: 10.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::heat_set_array(
&spec,
))
}
"flange_mount" => {
let (od, m_size, count) = self.parse_3f()?;
let m = self.metric_size(m_size)?;
let spec = crate::stdlib::hardsurface::pattern_sdf::FlangeMountSpec {
outer_dia: od,
bolt_size: m,
hole_count: self.count(count, 1.0)?,
thickness: 6.0,
bcd_ratio: 0.7,
center_bore_dia: 0.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::flange_mount(&spec))
}
"dovetail_pair" => {
let (w, h, d, gender) = self.parse_4f()?;
let g = u8::from(gender >= 0.5);
let spec = crate::stdlib::hardsurface::pattern_sdf::DovetailPairSpec {
base_width: w,
height: h,
depth: d,
gender: g,
female_margin: 10.0,
female_plate_thickness: 8.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::dovetail_pair(
&spec,
))
}
"profile_extrusion" => {
let (kind, length) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::ProfileExtrusionSpec {
kind: self.count(kind, 20.0)?,
length,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::profile_extrusion(
&spec,
))
}
"snap_fit_pair" => {
let (l, w, t, hook_d) = self.parse_4f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::SnapFitPairSpec {
length: l,
width: w,
thickness: t,
hook_height: hook_d,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::snap_fit_pair(
&spec,
))
}
"boss_array" => {
let (rows, cols, m_size, height, pitch, base_t) = self.parse_6f()?;
let m = self.metric_size(m_size)?;
let spec = crate::stdlib::hardsurface::pattern_sdf::BossArraySpec {
rows: self.count(rows, 1.0)?,
cols: self.count_grid(rows, cols)?,
screw_size: m,
boss_height: height,
pitch,
base_thickness: base_t,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::boss_array(&spec))
}
"screw_hole" => {
let (m_size, depth) = self.parse_2f()?;
let m = self.metric_size(m_size)?;
Ok(crate::stdlib::hardsurface::fastener::screw_hole(m, depth))
}
"tap_hole" => {
let (m_size, depth) = self.parse_2f()?;
let m = self.metric_size(m_size)?;
Ok(crate::stdlib::hardsurface::fastener::tap_hole(
m,
depth,
crate::stdlib::hardsurface::fastener::DEFAULT_ACCURACY,
))
}
"counterbore" => {
let (m_size, plate_t) = self.parse_2f()?;
let m = self.metric_size(m_size)?;
Ok(crate::stdlib::hardsurface::fastener::counterbore(
m, plate_t,
))
}
"countersink" => {
let (m_size, plate_t) = self.parse_2f()?;
let m = self.metric_size(m_size)?;
Ok(crate::stdlib::hardsurface::fastener::countersink(
m, plate_t,
))
}
"heat_set_hole" => {
let m_size = self.parse_1f()?;
let m = self.metric_size(m_size)?;
Ok(crate::stdlib::hardsurface::fastener::heat_set_insert_hole(
m,
))
}
"bolt" => {
let (m_size, shank) = self.parse_2f()?;
let m = self.metric_size(m_size)?;
Ok(crate::stdlib::hardsurface::fastener::bolt(m, shank))
}
"bracket_l" => {
let (w, h, plate_t, depth, fillet) = self.parse_5f()?;
Ok(crate::stdlib::hardsurface::mount::bracket_l(
w, h, plate_t, depth, fillet,
))
}
"flange_circular" => {
let (od, cb, thick, pcd, count, bolt_dia) = self.parse_6f()?;
Ok(crate::stdlib::hardsurface::mount::flange_circular(
od,
cb,
thick,
pcd,
self.count(count, 1.0)?,
bolt_dia,
))
}
"t_slot_2020" => {
let length = self.parse_1f()?;
Ok(crate::stdlib::hardsurface::joint::t_slot_2020(length))
}
"profile_2020" => {
let length = self.parse_1f()?;
Ok(crate::stdlib::hardsurface::mount::profile_2020(length))
}
"profile_3030" => {
let length = self.parse_1f()?;
Ok(crate::stdlib::hardsurface::mount::profile_3030(length))
}
"dovetail" => {
let (bw, h, d) = self.parse_3f()?;
Ok(crate::stdlib::hardsurface::joint::dovetail(bw, h, d))
}
"slot" => {
let (l, w, d) = self.parse_3f()?;
Ok(crate::stdlib::hardsurface::joint::slot(l, w, d))
}
"snap_fit_annular" => {
let (sd, sl, bh, by) = self.parse_4f()?;
Ok(crate::stdlib::hardsurface::joint::snap_fit_annular(
sd, sl, bh, by,
))
}
"pin_hinge_knuckle" => {
let (pd, kl, ko) = self.parse_3f()?;
Ok(crate::stdlib::hardsurface::joint::pin_hinge_knuckle(
pd, kl, ko,
))
}
"boss" => {
let (dia, height) = self.parse_2f()?;
Ok(crate::stdlib::hardsurface::reinforcement::boss(dia, height))
}
"rib" => {
let (l, h, t) = self.parse_3f()?;
Ok(crate::stdlib::hardsurface::reinforcement::rib(l, h, t))
}
"bearing_seat" => {
let (size, plate_t, style) = self.parse_3f()?;
let bearing =
crate::stdlib::hardsurface::pattern_sdf::BearingKind::from_f32_snap(size);
let plate_margin = 10.0;
let corner_radius = plate_margin * 0.3;
let style_u8 = if style >= 1.5 {
2_u8
} else {
u8::from(style >= 0.5)
};
let spec = crate::stdlib::hardsurface::pattern_sdf::BearingSeatSpec {
bearing,
plate_thickness: plate_t,
plate_margin,
corner_radius,
style: style_u8,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::bearing_seat(&spec))
}
"rack_shelf" => {
let (length, thickness, width, pitch, dia, count) = self.parse_6f()?;
Ok(crate::stdlib::hardsurface::mount::rack_shelf(
length,
thickness,
width,
pitch,
dia,
self.count(count, 0.0)?,
))
}
"cable_grommet" => {
let (od, h) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CableGrommetSpec {
outer_dia: od,
height: h,
flange_thickness: 3.0,
flange_overhang: 4.0,
inner_dia: raw_max_f32(od - 10.0, 4.0, crate::raw_minmax::Reason::NonzeroConst),
};
Ok(crate::stdlib::hardsurface::pattern_sdf::cable_grommet(
&spec,
))
}
"curtain_rod_bracket" => {
let (rod, proj) = self.parse_2f()?;
let spec = crate::stdlib::hardsurface::pattern_sdf::CurtainRodBracketSpec {
rod_dia: rod,
projection: proj,
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,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::curtain_rod_bracket(&spec))
}
"dowel_hole" => {
let (dia, depth) = self.parse_2f()?;
Ok(crate::stdlib::hardsurface::fastener::dowel_hole(dia, depth))
}
"wood_screw_pilot" => {
let (dia, hw) = self.parse_2f()?;
let hardwood = hw >= 0.5;
Ok(crate::stdlib::hardsurface::fastener::wood_screw_pilot(
dia, hardwood,
))
}
"arduino_mount_plate" => {
let (board_type, extras) = self.parse_2f()?;
let board = crate::stdlib::hardsurface::pattern_sdf::ArduinoBoard::from_f32_snap(
board_type,
);
let spec = crate::stdlib::hardsurface::pattern_sdf::ArduinoMountPlateSpec {
board,
plate_thickness: 4.0,
plate_margin: 10.0,
extra_m4_holes: self.count(extras, 0.0)?,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::arduino_mount_plate(&spec))
}
"pixhawk_mount" => {
let (size, damper) = self.parse_2f()?;
let damper_u8 = u8::from(damper >= 0.5);
let spec = crate::stdlib::hardsurface::pattern_sdf::PixhawkMountSpec {
hole_pattern_size: size,
plate_thickness: 4.0,
plate_margin: 12.0,
damper_style: damper_u8,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::pixhawk_mount(
&spec,
))
}
"servo_mount" => {
let servo_type = self.parse_1f()?;
let servo =
crate::stdlib::hardsurface::pattern_sdf::ServoKind::from_f32_snap(servo_type);
let plate_thickness = match servo {
crate::stdlib::hardsurface::pattern_sdf::ServoKind::Sg90 => 3.0,
crate::stdlib::hardsurface::pattern_sdf::ServoKind::Mg996r => 4.0,
};
let spec = crate::stdlib::hardsurface::pattern_sdf::ServoMountSpec {
servo,
plate_thickness,
flange_margin: 5.0,
};
Ok(crate::stdlib::hardsurface::pattern_sdf::servo_mount(&spec))
}
"jst_ph_slot" => {
let pins = self.parse_1f()?;
Ok(crate::stdlib::hardsurface::joint::jst_ph_slot(
self.count(pins, 2.0)?,
))
}
other => Err(ParseError {
message: format!("unknown LOL expression: '{other}'"),
position: self.lexer.position(),
}),
}
}
}
pub fn parse_lol(input: &str) -> Result<SdfNode, ParseError> {
let mut parser = Parser::new(input);
let node = parser.parse_expr()?;
parser.lexer.skip_whitespace();
if parser.lexer.position() < parser.lexer.input.len() {
return Err(ParseError {
message: "unexpected trailing content".into(),
position: parser.lexer.position(),
});
}
Ok(node)
}
impl Parser<'_> {
const fn err<T>(&self, message: String) -> Result<T, ParseError> {
Err(ParseError {
message,
position: self.lexer.position(),
})
}
fn expect_ident(&mut self) -> Result<String, ParseError> {
match self.next()? {
Some(Token::Ident(s)) => Ok(s),
other => self.err(format!("expected identifier, got {other:?}")),
}
}
fn expect_lparen(&mut self, after: &str) -> Result<(), ParseError> {
match self.next()? {
Some(Token::LParen) => Ok(()),
other => self.err(format!("expected '(' after '{after}', got {other:?}")),
}
}
fn expect_uint(&mut self, what: &str) -> Result<u32, ParseError> {
let v = self.expect_number()?;
if v < 0.0 || v.fract() != 0.0 || v > 16_777_216.0 {
return self.err(format!("{what} must be a non-negative integer, got {v}"));
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
Ok(v as u32)
}
fn count(&self, v: f32, min: f32) -> Result<u32, ParseError> {
let v = self.bounded_count(v)?;
Ok(lol_u32(crate::det_math::max(v.round(), min)))
}
fn count_smooth_folded(&self, v: f32, min: f32) -> Result<u32, ParseError> {
if v > MAX_STDLIB_SMOOTH_FOLD_COUNT {
return Err(ParseError {
message: format!(
"個数が上限 {MAX_STDLIB_SMOOTH_FOLD_COUNT} を超える (smooth union を線形に重ねる形状): {v}"
),
position: self.lexer.pos,
});
}
self.count(v, min)
}
fn count_grid(&self, rows: f32, cols: f32) -> Result<u32, ParseError> {
let c = self.count(cols, 1.0)?;
let r = self.count(rows, 1.0)?;
if f64::from(r) * f64::from(c) > f64::from(MAX_STDLIB_GRID_CELLS) {
return Err(ParseError {
message: format!("格子の要素数 {r} x {c} が上限 {MAX_STDLIB_GRID_CELLS} を超える"),
position: self.lexer.pos,
});
}
Ok(c)
}
fn count_trunc(&self, v: f32) -> Result<u32, ParseError> {
Ok(lol_u32(self.bounded_count(v)?))
}
fn spec_error(&self, e: crate::limits::SpecError) -> ParseError {
ParseError {
message: e.to_string(),
position: self.lexer.pos,
}
}
fn bounded_count(&self, v: f32) -> Result<f32, ParseError> {
if !v.is_finite() || v > MAX_STDLIB_COUNT {
return Err(ParseError {
message: format!("個数は有限で {MAX_STDLIB_COUNT} 以下でなければならない: {v}"),
position: self.lexer.pos,
});
}
Ok(v)
}
fn expect_u8(&mut self, what: &str) -> Result<u8, ParseError> {
let v = self.expect_uint(what)?;
u8::try_from(v).map_or_else(|_| self.err(format!("{what} must be 0-255, got {v}")), Ok)
}
fn expect_node_id(&mut self, registry_len: usize) -> Result<NodeId, ParseError> {
let id = self.expect_uint("entity id")?;
if (id as usize) >= registry_len {
return self.err(format!(
"entity id {id} out of range (entities has {registry_len} entries)"
));
}
Ok(id)
}
fn expect_hand(&mut self) -> Result<HandSide, ParseError> {
let s = self.expect_ident()?;
match s.as_str() {
"left" => Ok(HandSide::Left),
"right" => Ok(HandSide::Right),
"both" => Ok(HandSide::Both),
other => self.err(format!("expected hand (left|right|both), got '{other}'")),
}
}
fn expect_vec3(&mut self) -> Result<Vec3, ParseError> {
let x = self.expect_number()?;
self.expect_comma()?;
let y = self.expect_number()?;
self.expect_comma()?;
let z = self.expect_number()?;
Ok(Vec3::new(x, y, z))
}
fn parse_entities(&mut self) -> Result<Vec<SdfNode>, ParseError> {
let name = self.expect_ident()?;
if name != "entities" {
return self.err(format!("expected 'entities', got '{name}'"));
}
self.expect_lparen("entities")?;
let mut out = Vec::new();
if self.at_rparen()? {
self.next()?;
return Ok(out);
}
loop {
out.push(self.parse_expr()?);
match self.next()? {
Some(Token::Comma) => {}
Some(Token::RParen) => return Ok(out),
other => {
return self.err(format!("expected ',' or ')' in entities, got {other:?}"))
}
}
}
}
#[allow(clippy::too_many_lines)] fn parse_intent(&mut self, registry_len: usize) -> Result<IntentNode, ParseError> {
let name = self.expect_ident()?;
self.expect_lparen(&name)?;
match name.as_str() {
"grasp" => {
let target_id = self.expect_node_id(registry_len)?;
self.expect_comma()?;
let hand = self.expect_hand()?;
self.expect_comma()?;
let force = self.parse_1f()?;
Ok(IntentNode::Grasp {
target_id,
hand,
force,
})
}
"release" => {
let target_id = self.expect_node_id(registry_len)?;
self.expect_rparen()?;
Ok(IntentNode::Release { target_id })
}
"catch" => {
let object_id = self.expect_node_id(registry_len)?;
self.expect_rparen()?;
Ok(IntentNode::Catch { object_id })
}
"walk" => {
let destination = self.expect_vec3()?;
self.expect_comma()?;
let speed = self.parse_1f()?;
Ok(IntentNode::Walk { destination, speed })
}
"gaze" => {
let target = self.expect_vec3()?;
self.expect_comma()?;
let duration_ms = self.expect_uint("gaze duration_ms")?;
self.expect_rparen()?;
Ok(IntentNode::Gaze {
target,
duration_ms,
})
}
"point" => {
let target = self.expect_vec3()?;
self.expect_comma()?;
let hand = self.expect_hand()?;
self.expect_rparen()?;
Ok(IntentNode::Point { target, hand })
}
"throw" => {
let target = self.expect_vec3()?;
self.expect_comma()?;
let force = self.expect_number()?;
self.expect_comma()?;
let hand = self.expect_hand()?;
self.expect_rparen()?;
Ok(IntentNode::Throw {
target,
force,
hand,
})
}
"push" | "pull" | "turn" => {
let target_id = self.expect_node_id(registry_len)?;
self.expect_comma()?;
let v = self.expect_vec3()?;
self.expect_comma()?;
let f = self.parse_1f()?;
Ok(match name.as_str() {
"push" => IntentNode::Push {
target_id,
direction: v,
force: f,
},
"pull" => IntentNode::Pull {
target_id,
direction: v,
force: f,
},
_ => IntentNode::Rotate {
target_id,
axis: v,
angle_rad: f,
},
})
}
"align" => {
let target_id = self.expect_node_id(registry_len)?;
self.expect_comma()?;
let reference = self.expect_vec3()?;
self.expect_rparen()?;
Ok(IntentNode::Align {
target_id,
reference,
})
}
"follow" | "avoid" => {
let target_id = self.expect_node_id(registry_len)?;
self.expect_comma()?;
let d = self.parse_1f()?;
Ok(if name == "follow" {
IntentNode::Follow {
target_id,
distance: d,
}
} else {
IntentNode::Avoid {
target_id,
min_distance: d,
}
})
}
"rest" => {
let duration_ms = self.expect_uint("rest duration_ms")?;
self.expect_rparen()?;
Ok(IntentNode::Rest { duration_ms })
}
"latent" => {
let mut values = Vec::new();
loop {
values.push(self.expect_number()?);
match self.next()? {
Some(Token::Comma) => {}
Some(Token::RParen) => break,
other => {
return self
.err(format!("expected ',' or ')' in latent, got {other:?}"))
}
}
}
if values.len() < 4 {
return self.err(format!(
"latent needs at least 4 values, got {}",
values.len()
));
}
Ok(IntentNode::LatentIntent {
values: values.into_boxed_slice(),
semantic_hint: None,
})
}
"seq" | "par" => {
let mut items = Vec::new();
loop {
items.push(self.parse_intent(registry_len)?);
match self.next()? {
Some(Token::Comma) => {}
Some(Token::RParen) => break,
other => {
return self
.err(format!("expected ',' or ')' in {name}, got {other:?}"))
}
}
}
Ok(if name == "seq" {
IntentNode::Sequence(items)
} else {
IntentNode::Parallel(items)
})
}
"music" => {
let mut packet = [0u8; 8];
for (i, slot) in packet.iter_mut().enumerate() {
if i > 0 {
self.expect_comma()?;
}
*slot = self.expect_u8("music byte")?;
}
self.expect_rparen()?;
Ok(IntentNode::Music { packet })
}
other => self.err(format!("unknown intent verb '{other}'")),
}
}
fn parse_program_body(&mut self) -> Result<Program, ParseError> {
self.expect_lparen("program")?;
let sdf = self.parse_expr()?;
let mut builder = ProgramBuilder::new().with_sdf(sdf);
let mut registry_len = 0;
match self.next()? {
Some(Token::RParen) => return Ok(builder.build()),
Some(Token::Comma) => {}
other => return self.err(format!("expected ',' or ')' in program, got {other:?}")),
}
for node in self.parse_entities()? {
let id = builder.register(node);
registry_len = id as usize + 1;
}
match self.next()? {
Some(Token::RParen) => return Ok(builder.build()),
Some(Token::Comma) => {}
other => return self.err(format!("expected ',' or ')' in program, got {other:?}")),
}
let intent = self.parse_intent(registry_len)?;
self.expect_rparen()?;
Ok(builder.with_intent(intent).build())
}
}
pub fn parse_program(input: &str) -> Result<Program, ParseError> {
let mut parser = Parser::new(input);
let is_program = matches!(parser.peek()?, Some(Token::Ident(s)) if s == "program");
let program = if is_program {
parser.next()?;
parser.parse_program_body()?
} else {
Program::sdf_only(parser.parse_expr()?)
};
parser.lexer.skip_whitespace();
if parser.lexer.position() < parser.lexer.input.len() {
return Err(ParseError {
message: "unexpected trailing content".into(),
position: parser.lexer.position(),
});
}
Ok(program)
}
pub fn parse_law(input: &str) -> Result<AuditLaw, ParseError> {
let mut name: Option<String> = None;
let mut clauses: Vec<Clause> = Vec::new();
let mut position = 0_usize;
for raw in input.lines() {
let at = position;
position += raw.len() + 1;
let line = raw.split('#').next().unwrap_or("").trim();
if line.is_empty() {
continue;
}
let mut words = line.split_whitespace();
let head = words.next().unwrap_or("");
let rest: Vec<&str> = words.collect();
match head {
"audit" => {
if rest.len() != 1 {
return Err(law_error("`audit` takes one name", at));
}
if name.is_some() {
return Err(law_error("a second `audit <name>` line", at));
}
name = Some(rest[0].to_owned());
}
"evidence" => {
if rest.len() != 1 {
return Err(law_error("`evidence` takes one metric", at));
}
clauses.push(Clause::Evidence {
metric: rest[0].to_owned(),
});
}
"expect" => clauses.push(
law_expect(&rest)
.ok_or_else(|| law_error("`expect <metric> == <value> [within <tol>]`", at))?,
),
"range" => {
let (key, values) = rest
.split_first()
.ok_or_else(|| law_error("`range <key> <value>...`", at))?;
if values.is_empty() {
return Err(law_error("`range` needs at least one value", at));
}
clauses.push(Clause::Range {
key: (*key).to_owned(),
values: sorted_values(values),
});
}
other => {
return Err(law_error(
&format!("unknown audit law clause `{other}`"),
at,
))
}
}
}
let name = name.ok_or_else(|| law_error("no `audit <name>` line", 0))?;
if clauses.is_empty() {
return Err(law_error(
&format!("`{name}` states nothing (an audit needs at least one clause)"),
0,
));
}
Ok(AuditLaw::new(name, clauses))
}
fn law_error(message: &str, position: usize) -> ParseError {
ParseError {
message: message.to_owned(),
position,
}
}
#[must_use]
pub fn law_number(token: &str) -> Option<f64> {
let b = token.as_bytes();
let mut i = usize::from(matches!(b.first(), Some(b'+' | b'-')));
let digits = |i: &mut usize| {
let start = *i;
while b.get(*i).is_some_and(u8::is_ascii_digit) {
*i += 1;
}
*i - start
};
let int = digits(&mut i);
let frac = if b.get(i) == Some(&b'.') {
i += 1;
digits(&mut i)
} else {
0
};
if int == 0 && frac == 0 {
return None;
}
if matches!(b.get(i), Some(b'e' | b'E')) {
i += 1;
if matches!(b.get(i), Some(b'+' | b'-')) {
i += 1;
}
if digits(&mut i) == 0 {
return None;
}
}
if i != b.len() {
return None;
}
token.parse::<f64>().ok().filter(|v| v.is_finite())
}
fn law_expect(rest: &[&str]) -> Option<Clause> {
let metric = rest.first()?;
if *rest.get(1)? != "==" {
return None;
}
let value = law_number(rest.get(2)?)?;
let tolerance = match rest.len() {
3 => 0.0,
5 if rest[3] == "within" => law_number(rest[4]).filter(|t| *t >= 0.0)?,
_ => return None,
};
Some(Clause::Expect {
metric: (*metric).to_owned(),
value,
tolerance,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_law_number_is_ascii_decimal_and_finite() {
for (t, v) in [
("2", 2.0),
("2.0", 2.0),
("+2", 2.0),
("2e0", 2.0),
("2.", 2.0),
(".5", 0.5),
("-1", -1.0),
("1E-3", 0.001),
("002", 2.0),
] {
assert_eq!(law_number(t), Some(v), "{t}");
}
for t in [
"nan", "NaN", "inf", "-inf", "infinity", "1_0", "\u{ff12}", "\u{662}", "1e400", "0x2",
".", "e5", "1e", "+", "", " 2", "--1", "1.2.3",
] {
assert_eq!(law_number(t), None, "{t:?}");
}
}
#[test]
fn the_name_of_an_audit_is_given_once() {
assert!(parse_law("audit a\naudit b\nevidence m\n").is_err());
assert!(parse_law("audit a\nevidence m\naudit a\n").is_err());
}
#[test]
fn an_expect_clause_takes_only_law_numbers() {
assert!(parse_law("audit a\nexpect m == 2 within 0.5\n").is_ok());
assert!(parse_law("audit a\nexpect m == -2 within 0\n").is_ok());
for bad in [
"expect m == 2 within -1",
"expect m == 2 within -0.5",
"expect m == nan",
"expect m == inf",
"expect m == 2 within inf",
"expect m == 1_0",
"expect m == 2 junk",
] {
assert!(parse_law(&format!("audit a\n{bad}\n")).is_err(), "{bad}");
}
}
#[test]
fn test_sphere() {
let node = parse_lol("sphere(1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!((d - (-1.0)).abs() < 1e-6);
}
#[test]
fn test_box3d() {
let node = parse_lol("box3d(1.0, 1.0, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0); }
#[test]
fn test_union() {
let node = parse_lol("union(sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_smooth_union() {
let node = parse_lol("smooth_union(0.3, sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_translate() {
let node = parse_lol("translate(2.0, 0.0, 0.0, sphere(0.5))").unwrap();
let d_origin = crate::eval(&node, Vec3::ZERO);
let d_offset = crate::eval(&node, Vec3::new(2.0, 0.0, 0.0));
assert!(d_origin > 0.0); assert!(d_offset < 0.0); }
#[test]
fn test_rotate() {
let node = parse_lol("rotate(0.0, 90.0, 0.0, box3d(2.0, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_scale() {
let node = parse_lol("scale(2.0, sphere(1.0))").unwrap();
let d = crate::eval(&node, Vec3::new(1.5, 0.0, 0.0));
assert!(d < 0.0); }
#[test]
fn test_subtract() {
let node = parse_lol("subtract(sphere(1.0), sphere(0.5))").unwrap();
let d_origin = crate::eval(&node, Vec3::ZERO);
assert!(d_origin > 0.0); }
#[test]
fn test_intersection() {
let node = parse_lol("intersection(sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_round() {
let node = parse_lol("round(0.1, box3d(1.0, 1.0, 1.0))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_onion() {
let node = parse_lol("onion(0.1, sphere(1.0))").unwrap();
let d_origin = crate::eval(&node, Vec3::ZERO);
assert!(d_origin > 0.0); }
#[test]
fn test_twist() {
let node = parse_lol("twist(0.5, box3d(1.0, 2.0, 1.0))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_mirror() {
let node =
parse_lol("mirror(1.0, 0.0, 0.0, translate(1.0, 0.0, 0.0, sphere(0.3)))").unwrap();
let d_pos = crate::eval(&node, Vec3::new(1.0, 0.0, 0.0));
let d_neg = crate::eval(&node, Vec3::new(-1.0, 0.0, 0.0));
assert!(d_pos < 0.0);
assert!(d_neg < 0.0); }
#[test]
fn test_polar_repeat() {
let node = parse_lol("polar_repeat(6, translate(2.0, 0.0, 0.0, sphere(0.3)))").unwrap();
let d = crate::eval(&node, Vec3::new(2.0, 0.0, 0.0));
assert!(d < 0.0);
}
#[test]
fn test_torus() {
let node = parse_lol("torus(1.0, 0.3)").unwrap();
let d = crate::eval(&node, Vec3::new(1.0, 0.0, 0.0));
assert!(d < 0.0);
}
#[test]
fn test_cylinder() {
let node = parse_lol("cylinder(0.5, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_capsule() {
let node = parse_lol("capsule(0.3, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_cone() {
let node = parse_lol("cone(1.0, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::new(0.0, 0.5, 0.0));
assert!(d < 0.0);
}
#[test]
fn test_ellipsoid() {
let node = parse_lol("ellipsoid(1.0, 0.5, 0.7)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_octahedron() {
let node = parse_lol("octahedron(1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_heart() {
let node = parse_lol("heart(1.0)").unwrap();
assert!(parse_lol("heart(1.0)").is_ok());
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_egg() {
let node = parse_lol("egg(1.0, 0.3)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_tetrahedron() {
let node = parse_lol("tetrahedron(1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_diamond() {
let node = parse_lol("diamond(0.8, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_box_frame() {
let node = parse_lol("box_frame(1.0, 1.0, 1.0, 0.1)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_helix() {
let node = parse_lol("helix(1.0, 0.1, 1.0, 2.0)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_nary_union() {
let node = parse_lol("union(sphere(1.0), sphere(0.5), sphere(0.3))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_smooth_subtract() {
let node = parse_lol("smooth_subtract(0.1, sphere(1.0), sphere(0.5))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_xor() {
let node = parse_lol("xor(sphere(1.0), sphere(0.8))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_morph() {
let node = parse_lol("morph(0.5, sphere(1.0), box3d(1.0, 1.0, 1.0))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_scale_non_uniform() {
let node = parse_lol("scale_non_uniform(2.0, 1.0, 1.0, sphere(1.0))").unwrap();
let d = crate::eval(&node, Vec3::new(1.5, 0.0, 0.0));
assert!(d < 0.0);
}
#[test]
fn test_negative_number() {
let node = parse_lol("translate(-1.0, -2.0, 0.0, sphere(0.5))").unwrap();
let d = crate::eval(&node, Vec3::new(-1.0, -2.0, 0.0));
assert!(d < 0.0);
}
#[test]
fn test_nested_complex() {
let input = "smooth_union(0.2, \
translate(0.0, 1.0, 0.0, scale_non_uniform(1.5, 0.4, 1.5, sphere(1.0))), \
cylinder(0.3, 0.8))";
let node = parse_lol(input).unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_whitespace_tolerance() {
let input = " smooth_union( 0.3 , sphere( 1.0 ) , box3d( 0.5 , 0.5 , 0.5 ) ) ";
let node = parse_lol(input).unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_multiline() {
let input = "union(\n sphere(1.0),\n translate(0.0, 2.0, 0.0, sphere(0.5))\n)";
let node = parse_lol(input).unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_error_unknown_function() {
let result = parse_lol("foobar(1.0)");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("unknown"));
}
#[test]
fn test_error_missing_rparen() {
let result = parse_lol("sphere(1.0");
assert!(result.is_err());
}
#[test]
fn test_error_trailing_content() {
let result = parse_lol("sphere(1.0) extra");
assert!(result.is_err());
assert!(result.unwrap_err().message.contains("trailing"));
}
#[test]
fn test_chamfer_union() {
let node = parse_lol("chamfer_union(0.1, sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_elongate() {
let node = parse_lol("elongate(0.0, 1.0, 0.0, sphere(0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_extrude() {
let node = parse_lol("extrude(0.5, sphere(1.0))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_taper() {
let node = parse_lol("taper(0.3, cylinder(1.0, 2.0))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_displacement() {
let node = parse_lol("displacement(0.1, sphere(1.0))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_noise() {
let node = parse_lol("noise(0.1, 2.0, 42, sphere(1.0))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_bend() {
let node = parse_lol("bend(0.3, box3d(0.5, 2.0, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_shear() {
let node = parse_lol("shear(0.5, 0.0, 0.0, box3d(1.0, 1.0, 1.0))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_revolution() {
let node = parse_lol("revolution(1.0, sphere(0.3))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_pipe() {
let node = parse_lol("pipe(0.1, sphere(1.0), box3d(1.0, 1.0, 1.0))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_engrave() {
let node = parse_lol("engrave(0.05, sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_animate() {
let node = parse_lol("animate(1.0, 0.5, sphere(1.0))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_with_material() {
let node = parse_lol("with_material(1, sphere(1.0))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!((d - (-1.0)).abs() < 1e-6);
}
#[test]
fn test_snowman_example() {
let input = "union(\
sphere(1.0),\
translate(0.0, 1.3, 0.0, sphere(0.7)),\
translate(0.0, 2.2, 0.0, sphere(0.5))\
)";
let node = parse_lol(input).unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_gear_example() {
let input = "subtract(\
polar_repeat(12, translate(1.5, 0.0, 0.0, cylinder(0.15, 0.2))),\
subtract(cylinder(1.8, 0.2), cylinder(0.5, 0.3))\
)";
let node = parse_lol(input).unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_plane() {
let node = parse_lol("plane(0.0, 1.0, 0.0, 0.0)").unwrap();
let d_below = crate::eval(&node, Vec3::new(0.0, -1.0, 0.0));
let d_above = crate::eval(&node, Vec3::new(0.0, 1.0, 0.0));
assert!(d_below < 0.0);
assert!(d_above > 0.0);
}
#[test]
fn test_pyramid() {
let node = parse_lol("pyramid(1.0)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_hex_prism() {
let node = parse_lol("hex_prism(0.5, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_link() {
let node = parse_lol("link(0.5, 0.5, 0.1)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_capped_cone() {
let node = parse_lol("capped_cone(1.0, 0.8, 0.3)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_rounded_cone() {
let node = parse_lol("rounded_cone(0.5, 0.2, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_rounded_cylinder() {
let node = parse_lol("rounded_cylinder(0.5, 0.05, 1.0)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_tube() {
let node = parse_lol("tube(1.0, 0.1, 1.0)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_barrel() {
let node = parse_lol("barrel(0.8, 1.0, 0.2)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_star_polygon() {
let node = parse_lol("star_polygon(1.0, 5.0, 0.4, 0.3)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_cross_shape() {
let node = parse_lol("cross_shape(1.0, 0.3, 0.05, 0.3)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_rounded_box() {
let node = parse_lol("rounded_box(1.0, 1.0, 1.0, 0.1)").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_capped_torus() {
let node = parse_lol("capped_torus(1.0, 0.3, 1.57)").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_repeat_finite() {
let node = parse_lol("repeat_finite(3.0, 1.0, 3.0, 2.0, 0.0, 2.0, sphere(0.3))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_octant_mirror() {
let node = parse_lol("octant_mirror(translate(1.0, 1.0, 1.0, sphere(0.2)))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_icosahedral_symmetry() {
let node =
parse_lol("icosahedral_symmetry(translate(1.0, 0.0, 0.0, sphere(0.2)))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_surface_roughness() {
let node = parse_lol("surface_roughness(5.0, 0.1, 3, sphere(1.0))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_groove() {
let node = parse_lol("groove(0.1, 0.05, sphere(1.0), box3d(0.8, 0.8, 0.8))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_tongue() {
let node = parse_lol("tongue(0.1, 0.05, sphere(1.0), box3d(0.8, 0.8, 0.8))").unwrap();
let _ = crate::eval(&node, Vec3::ZERO);
}
#[test]
fn test_columns_union() {
let node = parse_lol("columns_union(0.1, 4.0, sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_exp_smooth_union() {
let node = parse_lol("exp_smooth_union(0.3, sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_stairs_union() {
let node = parse_lol("stairs_union(0.2, 4.0, sphere(1.0), box3d(0.5, 0.5, 0.5))").unwrap();
let d = crate::eval(&node, Vec3::ZERO);
assert!(d < 0.0);
}
#[test]
fn test_v10_simple_prims() {
for (name, args) in [
("infinite_cylinder", "0.5"),
("infinite_cone", "0.3"),
("dodecahedron", "1.0"),
("icosahedron", "1.0"),
("truncated_octahedron", "1.0"),
("truncated_icosahedron", "1.0"),
] {
let input = format!("{name}({args})");
assert!(parse_lol(&input).is_ok(), "failed: {input}");
}
}
#[test]
fn test_v10_2f_prims() {
for (name, args) in [
("triangular_prism", "0.5, 1.0"),
("cut_sphere", "1.0, 0.3"),
("solid_angle", "0.5, 1.0"),
("vesica", "1.0, 0.5"),
("gyroid", "3.0, 0.1"),
("schwarz_p", "3.0, 0.1"),
("blobby_cross", "0.5, 1.0"),
("diamond_surface", "3.0, 0.1"),
("neovius", "3.0, 0.1"),
("lidinoid", "3.0, 0.1"),
("iwp", "3.0, 0.1"),
("frd", "3.0, 0.1"),
("fischer_koch_s", "3.0, 0.1"),
("pmy", "3.0, 0.1"),
("circle_2d", "0.5, 1.0"),
] {
let input = format!("{name}({args})");
assert!(parse_lol(&input).is_ok(), "failed: {input}");
}
}
#[test]
fn tpms_scale_and_thickness_reject_degenerate_values() {
let names = [
"gyroid",
"schwarz_p",
"diamond_surface",
"neovius",
"lidinoid",
"iwp",
"frd",
"fischer_koch_s",
"pmy",
];
let bad_scales = ["-0.0", "0.0", "-1.0", "-5e-7"];
for name in names {
for scale in bad_scales {
let input = format!("{name}({scale}, 0.1)");
assert!(
parse_lol(&input).is_err(),
"{input} should be rejected (degenerate scale)"
);
}
for scale in ["1e-45", "1e-10", "5e-7", "1e-6", "3.0"] {
let input = format!("{name}({scale}, 0.1)");
let node = parse_lol(&input).unwrap_or_else(|e| panic!("{input}: {e}"));
let text = crate::emit::to_lol(&node)
.unwrap_or_else(|e| panic!("{input}: to_lol failed: {e}"));
assert!(
parse_lol(&text).is_ok(),
"{input} -> {text:?} should re-parse"
);
assert_eq!(
text, input,
"{input} must round-trip bit-exact through emit/parse"
);
}
}
}
#[test]
fn infill_wrappers_reject_the_same_degenerate_tpms_fields() {
for name in ["lattice_infill", "diamond_infill", "schwarz_infill"] {
for scale in ["-0.0", "0.0", "-1.0", "-5e-7"] {
let input = format!("{name}(1.0, {scale}, 0.1, sphere(1.0))");
assert!(
parse_lol(&input).is_err(),
"{input} should be rejected (degenerate lattice scale)"
);
}
for scale in ["1e-45", "5e-7", "3.0"] {
let input = format!("{name}(1.0, {scale}, 0.1, sphere(1.0))");
assert!(parse_lol(&input).is_ok(), "{input} should still parse");
}
}
}
#[test]
fn tpms_fields_validation_rejects_non_finite_values_directly() {
let p = Parser::new("");
for bad_thickness in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
assert!(
p.validate_tpms_fields(3.0, bad_thickness).is_err(),
"thickness={bad_thickness} should be rejected"
);
}
for bad_scale in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, 0.0, -0.0, -1.0] {
assert!(
p.validate_tpms_fields(bad_scale, 0.1).is_err(),
"scale={bad_scale} should be rejected"
);
}
assert!(p.validate_tpms_fields(3.0, 0.1).is_ok());
assert!(
p.validate_tpms_fields(3.0, -5.0).is_ok(),
"a negative thickness is not restricted"
);
}
#[test]
fn test_v10_3f_prims() {
for (name, args) in [
("cut_hollow_sphere", "1.0, 0.3, 0.1"),
("death_star", "1.0, 0.5, 0.8"),
("rounded_x", "0.5, 0.1, 1.0"),
("pie", "0.5, 1.0, 0.5"),
("tunnel", "0.5, 0.8, 1.0"),
("parabola_segment", "0.5, 0.8, 1.0"),
("regular_polygon", "1.0, 6.0, 0.5"),
("rect_2d", "0.5, 0.3, 1.0"),
("annular_2d", "1.0, 0.1, 0.5"),
] {
let input = format!("{name}({args})");
assert!(parse_lol(&input).is_ok(), "failed: {input}");
}
}
#[test]
fn test_v10_4f_prims() {
for (name, args) in [
("rhombus", "0.5, 0.3, 1.0, 0.05"),
("chamfered_cube", "0.5, 0.5, 0.5, 0.1"),
("trapezoid", "0.5, 0.3, 1.0, 0.5"),
("parallelogram", "0.5, 1.0, 0.2, 0.5"),
("uneven_capsule", "0.3, 0.5, 1.0, 0.5"),
("arc_shape", "0.5, 1.0, 0.1, 0.5"),
("moon", "0.5, 1.0, 0.8, 0.5"),
("stairs_prim", "0.3, 0.2, 5.0, 0.5"),
("rounded_rect_2d", "0.5, 0.3, 0.1, 1.0"),
] {
let input = format!("{name}({args})");
assert!(parse_lol(&input).is_ok(), "failed: {input}");
}
}
#[test]
fn test_v10_5f_prims() {
assert!(parse_lol("horseshoe(0.5, 1.0, 0.3, 0.1, 0.05)").is_ok());
assert!(parse_lol("superellipsoid(0.5, 0.5, 0.5, 1.0, 1.0)").is_ok());
}
#[test]
fn test_v10_segment_2d() {
assert!(parse_lol("segment_2d(0.0, 0.0, 1.0, 1.0, 0.1, 0.5)").is_ok());
}
#[test]
fn test_v10_triangle_bezier() {
assert!(parse_lol("triangle(0.0,0.0,0.0, 1.0,0.0,0.0, 0.5,1.0,0.0)").is_ok());
assert!(parse_lol("bezier(0.0,0.0,0.0, 0.5,1.0,0.0, 1.0,0.0,0.0, 0.1)").is_ok());
}
#[test]
fn test_v10_sweep_bezier() {
assert!(
parse_lol("sweep_bezier(0.0, 0.0, 0.5, 1.0, 1.0, 0.0, circle_2d(0.2, 0.5))").is_ok()
);
}
#[test]
fn test_shopping_cart_coin() {
let node = parse_lol("shopping_cart_coin(22.8, 1.7)").unwrap();
match node {
SdfNode::Cylinder {
radius,
half_height,
} => {
assert!((radius - 11.4).abs() < 1e-4);
assert!((half_height - 0.85).abs() < 1e-4);
}
_ => panic!("expected Cylinder"),
}
}
#[test]
fn test_skadis_panel() {
let node = parse_lol("skadis_panel(300, 5, 5)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_skadis_panel_variadic_defaults() {
assert!(matches!(
parse_lol("skadis_panel()").unwrap(),
SdfNode::Subtraction { .. }
));
assert!(matches!(
parse_lol("skadis_panel(400)").unwrap(),
SdfNode::Subtraction { .. }
));
assert!(matches!(
parse_lol("skadis_panel(10, 10)").unwrap(),
SdfNode::Subtraction { .. }
));
assert!(matches!(
parse_lol("skadis_panel(300, 5, 5)").unwrap(),
SdfNode::Subtraction { .. }
));
}
#[test]
fn test_mug() {
let node = parse_lol("mug(50, 100)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_skadis_hook_l_no_args() {
let node = parse_lol("skadis_hook_l()").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_skadis_hook_j_no_args() {
let node = parse_lol("skadis_hook_j()").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_skadis_hook_s_no_args() {
let node = parse_lol("skadis_hook_s()").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_phase_5_1_high_level_primitives_eval_correctly() {
use alice_sdf::eval;
for lol in [
"shopping_cart_coin(22.8, 1.7)",
"skadis_panel(300, 5, 5)",
"skadis_hook_l()",
"skadis_hook_j()",
"skadis_hook_s()",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_gridfinity_bin_2x2_6u() {
let node = parse_lol("gridfinity_bin(2, 2, 6)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_wall_hook_no_args() {
let node = parse_lol("wall_hook()").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_drawer_organizer_no_args() {
let node = parse_lol("drawer_organizer()").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_shelf_divider_no_args() {
let node = parse_lol("shelf_divider()").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_phase_b_1_b_pattern_sdf_primitives_eval_correctly() {
use alice_sdf::eval;
for lol in [
"gridfinity_bin(2, 2, 6)",
"wall_hook()",
"drawer_organizer()",
"shelf_divider()",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_gridfinity_bin_ex_no_dividers_no_walls() {
let node = parse_lol("gridfinity_bin_ex(2, 2, 6, 0, 0, 0, 0)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_gridfinity_bin_ex_with_dividers_and_walls() {
let node = parse_lol("gridfinity_bin_ex(3, 3, 6, 2, 2, 1.5, 2.0)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_sticky_note_holder_small_square() {
let node = parse_lol("sticky_note_holder(76, 76, 30)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_business_card_holder_jp_meishi() {
let node = parse_lol("business_card_holder(91, 55, 22)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_pen_cup_standard() {
let node = parse_lol("pen_cup(75, 100)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_phone_stand_with_cable_hole() {
let node = parse_lol("phone_stand(14, 100, 18)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_phone_stand_no_cable_hole() {
let node = parse_lol("phone_stand(14, 100, 0)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_phase_b_part2_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"sticky_note_holder(76, 76, 30)",
"business_card_holder(91, 55, 22)",
"pen_cup(75, 100)",
"phone_stand(14, 100, 18)",
"gridfinity_bin_ex(3, 3, 6, 2, 2, 1.5, 2.0)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_headphone_holder_wall_mount() {
let node = parse_lol("headphone_holder(80, 50, 100)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_under_desk_mount_standard() {
let node = parse_lol("under_desk_mount(25, 40, 4)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_under_desk_mount_no_screw() {
let node = parse_lol("under_desk_mount(25, 40, 0)").unwrap();
assert!(matches!(node, SdfNode::SmoothUnion { .. }));
}
#[test]
fn test_desk_shelf_desktop() {
let node = parse_lol("desk_shelf(400, 200, 100)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_monitor_riser_compact() {
let node = parse_lol("monitor_riser(250, 180, 90)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_phase_b2_part2_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"headphone_holder(80, 50, 100)",
"under_desk_mount(25, 40, 4)",
"desk_shelf(400, 200, 100)",
"monitor_riser(250, 180, 90)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_coaster_round() {
let node = parse_lol("coaster(95, 5)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_tissue_box_cover_rectangular_us() {
let node = parse_lol("tissue_box_cover(231, 116, 53)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_storage_box_medium() {
let node = parse_lol("storage_box(150, 100, 60)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint4_household_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"coaster(95, 5)",
"tissue_box_cover(231, 116, 53)",
"storage_box(150, 100, 60)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_cable_clip_hdmi() {
let node = parse_lol("cable_clip(7, 28)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_led_channel_ws2812b() {
let node = parse_lol("led_channel(10, 300)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_card_tray_poker() {
let node = parse_lol("card_tray(63, 88, 30)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_token_well_dice_4() {
let node = parse_lol("token_well(20, 20, 4)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_token_well_count_zero_defaults_to_one() {
let node = parse_lol("token_well(15, 10, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint5_hobby_diy_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"cable_clip(7, 28)",
"led_channel(10, 300)",
"card_tray(63, 88, 30)",
"token_well(20, 20, 4)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_wrench_holder_metric_6() {
let node = parse_lol("wrench_holder(8, 19, 6)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_socket_rail_half_inch_6() {
let node = parse_lol("socket_rail(12.4, 22, 6)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_hex_bit_holder_grid_4x5() {
let node = parse_lol("hex_bit_holder(5, 4, 12)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_hex_bit_holder_count_zero_defaults_to_one() {
let node = parse_lol("hex_bit_holder(0, 0, 12)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_sprint6_tools_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"wrench_holder(8, 19, 6)",
"socket_rail(12.4, 22, 6)",
"hex_bit_holder(5, 4, 12)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_raspi_case_rpi5() {
let node = parse_lol("raspi_case(85, 56, 25)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_esp32_enclosure_devkit() {
let node = parse_lol("esp32_enclosure(51.6, 28.4, 15)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_battery_18650_holder_row_4() {
let node = parse_lol("battery_18650_holder(4, 2.5, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_battery_18650_holder_count_zero_defaults_to_one() {
let node = parse_lol("battery_18650_holder(0, 2.5, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint7_electronics_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"raspi_case(85, 56, 25)",
"esp32_enclosure(51.6, 28.4, 15)",
"battery_18650_holder(4, 2.5, 0)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_toothbrush_holder_manual_4() {
let node = parse_lol("toothbrush_holder(4, 15, 70)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_drill_bit_holder_metric() {
let node = parse_lol("drill_bit_holder(3, 13, 11)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_pliers_rack_standard_6() {
let node = parse_lol("pliers_rack(6, 15, 60)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint8_bathroom_garage_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"toothbrush_holder(4, 15, 70)",
"drill_bit_holder(3, 13, 11)",
"pliers_rack(6, 15, 60)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_spice_rack_standard_6() {
let node = parse_lol("spice_rack(6, 48, 100)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_egg_tray_4x3() {
let node = parse_lol("egg_tray(3, 4, 18)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_utensil_caddy_standard_4() {
let node = parse_lol("utensil_caddy(4, 65, 130)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint9_cable_kitchen_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"spice_rack(6, 48, 100)",
"egg_tray(3, 4, 18)",
"utensil_caddy(4, 65, 130)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_filament_spool_holder_1kg() {
let node = parse_lol("filament_spool_holder(200, 68, 52)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_nozzle_holder_m6_row_8() {
let node = parse_lol("nozzle_holder(8, 8, 6)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_build_plate_rack_standard_5() {
let node = parse_lol("build_plate_rack(5, 15, 200)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint10_printer_modular_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"filament_spool_holder(200, 68, 52)",
"nozzle_holder(8, 8, 6)",
"build_plate_rack(5, 15, 200)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_cutlery_tray_standard_3() {
let node = parse_lol("cutlery_tray(3, 35, 220)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_pill_organizer_weekly_7x2() {
let node = parse_lol("pill_organizer(7, 2, 20)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_magnetic_strip_knife_rail_8() {
let node = parse_lol("magnetic_strip(8, 6, 30)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint11_drawer_wall_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"cutlery_tray(3, 35, 220)",
"pill_organizer(7, 2, 20)",
"magnetic_strip(8, 6, 30)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_hairdryer_holder_dyson() {
let node = parse_lol("hairdryer_holder(85, 110, 3)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_kcup_holder_4x3() {
let node = parse_lol("kcup_holder(3, 4, 53)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_hex_key_holder_metric_9() {
let node = parse_lol("hex_key_holder(9, 1.5, 10)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint12_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"hairdryer_holder(85, 110, 3)",
"kcup_holder(3, 4, 53)",
"hex_key_holder(9, 1.5, 10)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_wrap_holder_foil_12inch() {
let node = parse_lol("wrap_holder(55, 305, 3)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sock_divider_standard_4() {
let node = parse_lol("sock_divider(4, 80, 89)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_soap_tray_dual_bottle() {
let node = parse_lol("soap_tray(200, 90, 6)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint13_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"wrap_holder(55, 305, 3)",
"sock_divider(4, 80, 89)",
"soap_tray(200, 90, 6)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_razor_holder_cartridge() {
let node = parse_lol("razor_holder(12, 22, 4.5)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_chopstick_holder_adult_4() {
let node = parse_lol("chopstick_holder(4, 13, 260)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_swatch_holder_standard_8x4() {
let node = parse_lol("swatch_holder(8, 4, 32)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint14_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"razor_holder(12, 22, 4.5)",
"chopstick_holder(4, 13, 260)",
"swatch_holder(8, 4, 32)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_tp_holder_standard() {
let node = parse_lol("tp_holder(40, 110, 5)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sd_card_holder_full_sd_4x4() {
let node = parse_lol("sd_card_holder(4, 4, 24)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_driver_rack_standard_8() {
let node = parse_lol("driver_rack(8, 25, 100)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint15_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"tp_holder(40, 110, 5)",
"sd_card_holder(4, 4, 24)",
"driver_rack(8, 25, 100)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_cotton_dispenser_standard() {
let node = parse_lol("cotton_dispenser(80, 90, 100)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_sink_caddy_standard_l200() {
let node = parse_lol("sink_caddy(200, 100, 8)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_clamp_rack_standard_5() {
let node = parse_lol("clamp_rack(5, 30, 150)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint16_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"cotton_dispenser(80, 90, 100)",
"sink_caddy(200, 100, 8)",
"clamp_rack(5, 30, 150)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_dry_box_standard_2x2() {
let node = parse_lol("dry_box(2, 2, 68)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_outdoor_enclosure_ip54() {
let node = parse_lol("outdoor_enclosure(120, 80, 45)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_jewelry_stand_standard_3_tier() {
let node = parse_lol("jewelry_stand(3, 100, 100)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_sprint17_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"dry_box(2, 2, 68)",
"outdoor_enclosure(120, 80, 45)",
"jewelry_stand(3, 100, 100)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_phone_dock_standard() {
let node = parse_lol("phone_dock(80, 100, 8)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_cutting_board_rack_standard_3() {
let node = parse_lol("cutting_board_rack(3, 12, 220)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_tape_dispenser_packing_standard() {
let node = parse_lol("tape_dispenser(76, 50, 3)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint18_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"phone_dock(80, 100, 8)",
"cutting_board_rack(3, 12, 220)",
"tape_dispenser(76, 50, 3)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_shower_caddy_standard_2_tier() {
let node = parse_lol("shower_caddy(2, 250, 120)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_caliper_holder_standard_3() {
let node = parse_lol("caliper_holder(150, 40, 3)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_bag_clip_org_standard_8() {
let node = parse_lol("bag_clip_org(8, 8, 100)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint19_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"shower_caddy(2, 250, 120)",
"caliper_holder(150, 40, 3)",
"bag_clip_org(8, 8, 100)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_can_rack_standard_2_tier() {
let node = parse_lol("can_rack(2, 66, 10)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_led_hub_box_standard() {
let node = parse_lol("led_hub_box(80, 60, 30)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_makeup_organizer_standard_3x4() {
let node = parse_lol("makeup_organizer(3, 4, 45)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint20_mix_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"can_rack(2, 66, 10)",
"led_hub_box(80, 60, 30)",
"makeup_organizer(3, 4, 45)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_vesa_mount_75_m4() {
let node = parse_lol("vesa_mount(75, 5, 4)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_vesa_mount_100_m5() {
let node = parse_lol("vesa_mount(100, 6, 5)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_l_bracket_m4_2holes() {
let node = parse_lol("l_bracket(60, 60, 4, 4, 2)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_l_bracket_m5_3holes() {
let node = parse_lol("l_bracket(80, 80, 5, 5, 3)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint21_mechanical_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"vesa_mount(75, 5, 4)",
"vesa_mount(100, 6, 5)",
"l_bracket(60, 60, 4, 4, 2)",
"l_bracket(80, 80, 5, 5, 3)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_t_slot_bracket_2020_standard() {
let node = parse_lol("t_slot_bracket_2020(20, 20)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_raspi_mount_plate_pi4b() {
let node = parse_lol("raspi_mount_plate(4, 4)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_raspi_mount_plate_zero() {
let node = parse_lol("raspi_mount_plate(0, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_heat_set_array_m3_2x2() {
let node = parse_lol("heat_set_array(2, 2, 3, 20, 6)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_flange_mount_od80_m5_4() {
let node = parse_lol("flange_mount(80, 5, 4)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_dovetail_pair_male() {
let node = parse_lol("dovetail_pair(20, 15, 10, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_dovetail_pair_female() {
let node = parse_lol("dovetail_pair(20, 15, 10, 1)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_profile_extrusion_2020() {
let node = parse_lol("profile_extrusion(20, 100)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_profile_extrusion_3030() {
let node = parse_lol("profile_extrusion(30, 100)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_snap_fit_pair_standard() {
let node = parse_lol("snap_fit_pair(20, 5, 2, 1)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_boss_array_m3_2x2() {
let node = parse_lol("boss_array(2, 2, 3, 10, 20, 2)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_sprint21_extra8_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"t_slot_bracket_2020(20, 20)",
"t_slot_bracket_2020(40, 40)",
"raspi_mount_plate(4, 4)",
"raspi_mount_plate(0, 0)",
"heat_set_array(2, 2, 3, 20, 6)",
"heat_set_array(3, 3, 4, 25, 8)",
"flange_mount(80, 5, 4)",
"flange_mount(100, 6, 6)",
"dovetail_pair(20, 15, 10, 0)",
"dovetail_pair(20, 15, 10, 1)",
"profile_extrusion(20, 100)",
"profile_extrusion(30, 100)",
"snap_fit_pair(20, 5, 2, 1)",
"boss_array(2, 2, 3, 10, 20, 2)",
"boss_array(3, 3, 4, 15, 25, 3)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_screw_hole_m4() {
let node = parse_lol("screw_hole(4, 10)").unwrap();
assert!(matches!(node, SdfNode::Cylinder { .. }));
}
#[test]
fn test_screw_hole_m2_5_pi() {
let node = parse_lol("screw_hole(2.5, 8)").unwrap();
assert!(matches!(node, SdfNode::Cylinder { .. }));
}
#[test]
fn test_tap_hole_m3() {
let node = parse_lol("tap_hole(3, 6)").unwrap();
assert!(matches!(node, SdfNode::Cylinder { .. }));
}
#[test]
fn test_counterbore_m4() {
let node = parse_lol("counterbore(4, 8)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_countersink_m4() {
let node = parse_lol("countersink(4, 8)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_heat_set_hole_m3() {
let node = parse_lol("heat_set_hole(3)").unwrap();
assert!(matches!(node, SdfNode::Cylinder { .. }));
}
#[test]
fn test_heat_set_hole_m2_pi() {
let node = parse_lol("heat_set_hole(2)").unwrap();
assert!(matches!(node, SdfNode::Cylinder { .. }));
}
#[test]
fn test_bolt_m5() {
let node = parse_lol("bolt(5, 20)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_bracket_l_raw() {
let node = parse_lol("bracket_l(60, 40, 4, 40, 3)").unwrap();
assert!(matches!(node, SdfNode::SmoothUnion { .. }));
}
#[test]
fn test_flange_circular_raw() {
let node = parse_lol("flange_circular(60, 20, 6, 45, 4, 4.2)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_t_slot_2020_raw() {
let node = parse_lol("t_slot_2020(100)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_profile_2020_raw() {
use alice_sdf::eval;
let node = parse_lol("profile_2020(100)").unwrap();
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite());
}
#[test]
fn test_profile_3030_raw() {
use alice_sdf::eval;
let node = parse_lol("profile_3030(100)").unwrap();
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite());
}
#[test]
fn test_dovetail_raw() {
let node = parse_lol("dovetail(10, 5, 20)").unwrap();
assert!(matches!(node, SdfNode::Intersection { .. }));
}
#[test]
fn test_slot_raw() {
let node = parse_lol("slot(20, 4, 6)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_snap_fit_annular_raw() {
let node = parse_lol("snap_fit_annular(8, 20, 0.5, 7)").unwrap();
assert!(matches!(node, SdfNode::Union { .. }));
}
#[test]
fn test_pin_hinge_knuckle_raw() {
let node = parse_lol("pin_hinge_knuckle(3, 8, 8)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_boss_raw() {
let node = parse_lol("boss(3, 8)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_rib_raw() {
let node = parse_lol("rib(20, 10, 2)").unwrap();
assert!(matches!(node, SdfNode::Box3d { .. }));
}
#[test]
fn test_sprint22_phase_x2_primitives_eval_correctly() {
use alice_sdf::eval;
for lol in [
"screw_hole(4, 10)",
"screw_hole(2.5, 8)",
"tap_hole(3, 6)",
"counterbore(4, 8)",
"countersink(4, 8)",
"heat_set_hole(3)",
"heat_set_hole(2)",
"bolt(5, 20)",
"bracket_l(60, 40, 4, 40, 3)",
"flange_circular(60, 20, 6, 45, 4, 4.2)",
"t_slot_2020(100)",
"profile_2020(100)",
"profile_3030(100)",
"dovetail(10, 5, 20)",
"slot(20, 4, 6)",
"snap_fit_annular(8, 20, 0.5, 7)",
"pin_hinge_knuckle(3, 8, 8)",
"boss(3, 8)",
"rib(20, 10, 2)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_sprint22_composition_example() {
let lol = "subtract(rounded_box(30, 2.5, 30, 3), screw_hole(4, 10))";
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_metric_size_m2_snap_via_screw_hole() {
use alice_sdf::SdfNode as S;
let node = parse_lol("screw_hole(2, 5)").unwrap();
if let S::Cylinder { radius, .. } = node {
assert!(
(radius - 1.1).abs() < 1e-4,
"M2 clearance radius = 1.1, got {radius}"
);
} else {
panic!("expected Cylinder");
}
}
#[test]
fn test_metric_size_m2_5_snap_via_screw_hole() {
use alice_sdf::SdfNode as S;
let node = parse_lol("screw_hole(2.5, 5)").unwrap();
if let S::Cylinder { radius, .. } = node {
assert!(
(radius - 1.35).abs() < 1e-4,
"M2.5 clearance radius = 1.35, got {radius}"
);
} else {
panic!("expected Cylinder");
}
}
#[test]
fn test_bearing_seat_608_press_fit() {
let node = parse_lol("bearing_seat(22, 5, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_bearing_seat_608_slip_fit() {
let node = parse_lol("bearing_seat(22, 5, 1)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_bearing_seat_688_snap() {
let node = parse_lol("bearing_seat(16, 4, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_rack_shelf_raw_5_notch() {
let node = parse_lol("rack_shelf(200, 5, 30, 25, 6, 2)").unwrap();
assert!(matches!(node, SdfNode::Subtraction { .. }));
}
#[test]
fn test_sprint22_mechanical_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"bearing_seat(22, 5, 0)",
"bearing_seat(22, 5, 1)",
"bearing_seat(16, 4, 0)",
"rack_shelf(200, 5, 30, 25, 6, 2)",
"rack_shelf(400, 6, 40, 35, 8, 3)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_cable_grommet_standard_60() {
let node = parse_lol("cable_grommet(60, 15)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_curtain_rod_bracket_standard() {
let node = parse_lol("curtain_rod_bracket(25, 120)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_dowel_hole_standard_8() {
let node = parse_lol("dowel_hole(8, 15)").unwrap();
assert!(matches!(node, SdfNode::Cylinder { .. }));
}
#[test]
fn test_wood_screw_pilot_softwood() {
let node = parse_lol("wood_screw_pilot(4, 0)").unwrap();
if let SdfNode::Cylinder { radius, .. } = node {
assert!((radius - 1.4).abs() < 1e-4, "softwood #8 radius = 1.4");
} else {
panic!("expected Cylinder");
}
}
#[test]
fn test_wood_screw_pilot_hardwood() {
let node = parse_lol("wood_screw_pilot(4, 1)").unwrap();
if let SdfNode::Cylinder { radius, .. } = node {
assert!((radius - 1.8).abs() < 1e-4, "hardwood #8 radius = 1.8");
} else {
panic!("expected Cylinder");
}
}
#[test]
fn test_multidomain_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"cable_grommet(60, 15)",
"cable_grommet(80, 20)",
"curtain_rod_bracket(25, 120)",
"curtain_rod_bracket(30, 150)",
"dowel_hole(8, 15)",
"dowel_hole(6, 10)",
"wood_screw_pilot(4, 0)",
"wood_screw_pilot(5, 1)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
#[test]
fn test_arduino_mount_uno() {
let node = parse_lol("arduino_mount_plate(1, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_arduino_mount_uno_vesa() {
let node = parse_lol("arduino_mount_plate(1, 4)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_arduino_mount_nano() {
let node = parse_lol("arduino_mount_plate(3, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_pixhawk_45_solid() {
let node = parse_lol("pixhawk_mount(45, 0)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_pixhawk_45_damper() {
let node = parse_lol("pixhawk_mount(45, 1)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_servo_sg90() {
let node = parse_lol("servo_mount(1)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_servo_mg996r() {
let node = parse_lol("servo_mount(2)").unwrap();
assert!(matches!(node, SdfNode::Rotate { .. }));
}
#[test]
fn test_jst_ph_slot_4pin() {
let node = parse_lol("jst_ph_slot(4)").unwrap();
assert!(matches!(node, SdfNode::Box3d { .. }));
}
#[test]
fn test_electronics_archetypes_eval_correctly() {
use alice_sdf::eval;
for lol in [
"arduino_mount_plate(1, 0)",
"arduino_mount_plate(1, 4)",
"arduino_mount_plate(2, 0)",
"arduino_mount_plate(3, 0)",
"pixhawk_mount(45, 0)",
"pixhawk_mount(45, 1)",
"pixhawk_mount(30, 0)",
"servo_mount(1)",
"servo_mount(2)",
"jst_ph_slot(2)",
"jst_ph_slot(4)",
] {
let node = parse_lol(lol).unwrap_or_else(|e| panic!("{lol}: {e:?}"));
let d = eval(&node, Vec3::new(0.1, 0.1, 0.1));
assert!(d.is_finite(), "{lol}: non-finite SDF at (0.1,0.1,0.1)");
}
}
}