use ambassador::Delegate;
use derive_builder::Builder;
use derive_more::derive::From;
use crate::{
common::{Point3D, ScadObjectGeneric, Unit, D3},
internal::generate_sentence_repr,
scad_3d::{ScadPrimitive3D, ScadPrimitiveBody3D},
scad_display::{ambassador_impl_ScadDisplay, Identifier, ScadDisplay},
value_type::RoundSize,
};
macro_rules! __impl_primitive_3d {
($prim_ty:ident) => {
__impl_builder_primitive!($prim_ty, D3);
__impl_primitive_to_code!($prim_ty, D3);
};
}
#[derive(Builder, Copy, Clone, Debug, PartialEq)]
pub struct Sphere {
#[builder(setter(custom))]
pub size: RoundSize,
#[builder(setter(into, strip_option), default)]
pub fa: Option<Unit>,
#[builder(setter(into, strip_option), default)]
pub r#fn: Option<u64>,
#[builder(setter(into, strip_option), default)]
pub fs: Option<Unit>,
}
__impl_primitive_3d!(Sphere);
impl SphereBuilder {
pub const fn r(&mut self, value: Unit) -> &mut Self {
let new = self;
new.size = Some(RoundSize::Radius(value));
new
}
pub const fn d(&mut self, value: Unit) -> &mut Self {
let new = self;
new.size = Some(RoundSize::Diameter(value));
new
}
}
impl ScadDisplay for Sphere {
fn repr_scad(&self) -> String {
generate_sentence_repr(
"sphere",
__generate_scad_options!(
(self.size.name(), self.size);
opt: (
("$fa", self.fa);
("$fn", self.r#fn);
("$fs", self.fs);
)
),
)
}
}
impl From<Sphere> for ScadObjectGeneric<D3> {
fn from(val: Sphere) -> Self {
let body = ScadPrimitiveBody3D::from(val);
let primitive = ScadPrimitive3D::new(body);
primitive.into()
}
}
#[derive(Copy, Clone, Debug, PartialEq, Delegate)]
#[delegate(ScadDisplay)]
pub enum CubeSize {
N(Unit),
V(Point3D),
}
impl From<Unit> for CubeSize {
fn from(value: Unit) -> Self {
Self::N(value)
}
}
impl From<Point3D> for CubeSize {
fn from(value: Point3D) -> Self {
Self::V(value)
}
}
impl From<[Unit; 3]> for CubeSize {
fn from(value: [Unit; 3]) -> Self {
let [x, y, z] = value;
Self::V(Point3D::new(x, y, z))
}
}
#[derive(Builder, Copy, Clone, Debug, PartialEq)]
pub struct Cube {
#[builder(setter(into))]
pub size: CubeSize,
#[builder(setter(into, strip_option), default)]
pub center: Option<bool>,
}
__impl_primitive_3d!(Cube);
impl ScadDisplay for Cube {
fn repr_scad(&self) -> String {
generate_sentence_repr(
"cube",
__generate_scad_options!(
("size", self.size); opt: (("center", self.center);)
),
)
}
}
impl From<Cube> for ScadObjectGeneric<D3> {
fn from(val: Cube) -> Self {
let body = ScadPrimitiveBody3D::from(val);
let primitive = ScadPrimitive3D::new(body);
primitive.into()
}
}
#[derive(Copy, Clone, Debug, PartialEq, From)]
pub enum CylinderSize {
Single(RoundSize),
Double((RoundSize, RoundSize)),
}
#[derive(Copy, Clone, Debug, PartialEq, From)]
pub enum CylinderSizeEntry {
Single(Unit),
Double([Unit; 2]),
}
#[derive(Builder, Copy, Clone, Debug, PartialEq)]
pub struct Cylinder {
#[builder(setter(into))]
pub h: Unit,
#[builder(setter(custom))]
pub size: CylinderSize,
#[builder(setter(into, strip_option), default)]
pub center: Option<bool>,
#[builder(setter(into, strip_option), default)]
pub fa: Option<Unit>,
#[builder(setter(into, strip_option), default)]
pub r#fn: Option<u64>,
#[builder(setter(into, strip_option), default)]
pub fs: Option<Unit>,
}
impl CylinderBuilder {
pub fn r<T: Into<CylinderSizeEntry>>(&mut self, value: T) -> &mut Self {
let new = self;
new.size = match value.into() {
CylinderSizeEntry::Single(r) => Some(CylinderSize::Single(RoundSize::Radius(r))),
CylinderSizeEntry::Double([r1, r2]) => Some(CylinderSize::Double((
RoundSize::Radius(r1),
RoundSize::Radius(r2),
))),
};
new
}
pub fn d<T: Into<CylinderSizeEntry>>(&mut self, value: T) -> &mut Self {
let new = self;
new.size = match value.into() {
CylinderSizeEntry::Single(d) => Some(CylinderSize::Single(RoundSize::Diameter(d))),
CylinderSizeEntry::Double([d1, d2]) => Some(CylinderSize::Double((
RoundSize::Diameter(d1),
RoundSize::Diameter(d2),
))),
};
new
}
}
__impl_primitive_3d!(Cylinder);
impl ScadDisplay for Cylinder {
fn repr_scad(&self) -> String {
let size_str = match self.size {
CylinderSize::Single(size) => format!("{} = {}", size.name(), size.repr_scad()),
CylinderSize::Double((size1, size2)) => format!(
"{}1 = {}, {}2 = {}",
size1.name(),
size1.repr_scad(),
size2.name(),
size2.repr_scad()
),
};
let opts = __generate_scad_options!(
("h", self.h);
("" , Identifier(size_str));
opt: (
("center", self.center);
("$fa", self.fa);
("$fn", self.r#fn);
("$fs", self.fs);
)
);
format!(
"cylinder({})",
opts.iter()
.map(ToString::to_string)
.collect::<Vec<_>>()
.join(", ")
)
}
}
impl From<Cylinder> for ScadObjectGeneric<D3> {
fn from(val: Cylinder) -> Self {
let body = ScadPrimitiveBody3D::from(val);
let primitive = ScadPrimitive3D::new(body);
primitive.into()
}
}
#[derive(Clone, Debug, PartialEq, derive_more::Deref)]
pub struct VecPoint3DEntry(pub Vec<Point3D>);
impl From<Vec<[Unit; 3]>> for VecPoint3DEntry {
fn from(value: Vec<[Unit; 3]>) -> Self {
Self(
value
.into_iter()
.map(|[x, y, z]| Point3D::new(x, y, z))
.collect(),
)
}
}
impl From<Vec<Point3D>> for VecPoint3DEntry {
fn from(value: Vec<Point3D>) -> Self {
Self(value)
}
}
impl From<VecPoint3DEntry> for Vec<Point3D> {
fn from(value: VecPoint3DEntry) -> Self {
value.0
}
}
#[derive(Builder, Clone, Debug, PartialEq)]
#[builder(build_fn(validate = "Self::validate"))]
pub struct Polyhedron {
#[builder(setter(custom))]
pub points: Vec<Point3D>,
#[builder(setter(into, strip_option), default)]
pub faces: Option<Vec<Vec<usize>>>,
#[builder(setter(into, strip_option), default)]
pub convexity: Option<u64>,
}
__impl_primitive_3d!(Polyhedron);
impl PolyhedronBuilder {
fn validate(&self) -> Result<(), String> {
(|| -> Option<Result<(), String>> {
let pts: Vec<Point3D> = self.points.clone()?;
let pas: Vec<Vec<usize>> = self.faces.clone()??;
for (i, pa) in pas.into_iter().enumerate() {
for (j, vtx) in pa.into_iter().enumerate() {
if vtx >= pts.len() {
return Some(Err(format!("path index out of bounds: [{i}][{j}]:{vtx}")));
}
}
}
Some(Ok(()))
})()
.unwrap_or(Ok(()))
}
pub fn points<T: Into<VecPoint3DEntry>>(&mut self, value: T) -> &mut Self {
let new = self;
let entry: VecPoint3DEntry = value.into();
new.points = Some(entry.into());
new
}
}
impl ScadDisplay for Polyhedron {
fn repr_scad(&self) -> String {
generate_sentence_repr(
"polyhedron",
__generate_scad_options!(
("points", self.points.clone());
opt: (
("faces", self.faces.clone());
("convexity", self.convexity);
)
),
)
}
}
impl From<Polyhedron> for ScadObjectGeneric<D3> {
fn from(val: Polyhedron) -> Self {
let body = ScadPrimitiveBody3D::from(val);
let primitive = ScadPrimitive3D::new(body);
primitive.into()
}
}
#[derive(Builder, Clone, Debug, PartialEq)]
pub struct Import3D {
#[builder(setter(into))]
pub file: String,
#[builder(setter(into, strip_option), default)]
pub convexity: Option<u64>,
#[builder(setter(into, strip_option), default)]
pub fa: Option<Unit>,
#[builder(setter(into, strip_option), default)]
pub r#fn: Option<u64>,
#[builder(setter(into, strip_option), default)]
pub fs: Option<Unit>,
}
__impl_primitive_3d!(Import3D);
impl ScadDisplay for Import3D {
fn repr_scad(&self) -> String {
generate_sentence_repr(
"import",
__generate_scad_options!(
("", self.file.clone());
opt: (
("convexity", self.convexity);
("$fa", self.fa);
("$fn", self.r#fn);
("$fs", self.fs);
)
),
)
}
}
impl From<Import3D> for ScadObjectGeneric<D3> {
fn from(val: Import3D) -> Self {
let body = ScadPrimitiveBody3D::from(val);
let primitive = ScadPrimitive3D::new(body);
primitive.into()
}
}
#[derive(Builder, Clone, Debug, PartialEq, Eq)]
pub struct Surface {
#[builder(setter(into))]
pub file: String,
#[builder(setter(into, strip_option), default)]
pub center: Option<bool>,
#[builder(setter(into, strip_option), default)]
pub invert: Option<bool>,
#[builder(setter(into, strip_option), default)]
pub convexity: Option<u64>,
}
__impl_primitive_3d!(Surface);
impl ScadDisplay for Surface {
fn repr_scad(&self) -> String {
generate_sentence_repr(
"surface",
__generate_scad_options!(
("file", self.file.clone());
opt: (
("center", self.center);
("invert", self.invert);
("convexity", self.convexity);
)
),
)
}
}
impl From<Surface> for ScadObjectGeneric<D3> {
fn from(val: Surface) -> Self {
let body = ScadPrimitiveBody3D::from(val);
let primitive = ScadPrimitive3D::new(body);
primitive.into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sphere() {
assert_eq!(
SphereBuilder::default().r(3.0).build().unwrap().repr_scad(),
"sphere(r = 3)"
);
assert_eq!(
SphereBuilder::default().d(4.0).build().unwrap().repr_scad(),
"sphere(d = 4)"
);
assert_eq!(
SphereBuilder::default()
.r(3.0)
.fa(0.5)
.r#fn(20_u64)
.build()
.unwrap()
.repr_scad(),
"sphere(r = 3, $fa = 0.5, $fn = 20)"
);
assert_eq!(
SphereBuilder::default()
.r(3.0)
.fs(40.)
.fa(0.5)
.build()
.unwrap()
.repr_scad(),
"sphere(r = 3, $fa = 0.5, $fs = 40)"
);
let _x = SphereBuilder::default()
.fa(0.5)
.r#fn(20_u64)
.fs(40.)
.build()
.unwrap_err();
}
#[test]
fn test_cube() {
assert_eq!(
CubeBuilder::default()
.size(3.0)
.build()
.unwrap()
.repr_scad(),
"cube(size = 3)"
);
assert_eq!(
CubeBuilder::default()
.size([4.0, 2.0, 3.0])
.build()
.unwrap()
.repr_scad(),
"cube(size = [4, 2, 3])"
);
assert_eq!(
CubeBuilder::default()
.size(Point3D::new(4.0, 2.0, 3.0))
.build()
.unwrap()
.repr_scad(),
"cube(size = [4, 2, 3])"
);
assert_eq!(
CubeBuilder::default()
.size(3.0)
.center(true)
.build()
.unwrap()
.repr_scad(),
"cube(size = 3, center = true)"
);
}
#[test]
fn test_cylinder() {
assert_eq!(
CylinderBuilder::default()
.h(5.0)
.r(3.0)
.build()
.unwrap()
.repr_scad(),
"cylinder(h = 5, r = 3)"
);
assert_eq!(
CylinderBuilder::default()
.h(5.0)
.d(3.0)
.build()
.unwrap()
.repr_scad(),
"cylinder(h = 5, d = 3)"
);
assert_eq!(
CylinderBuilder::default()
.h(5.0)
.r([1.0, 2.0])
.build()
.unwrap()
.repr_scad(),
"cylinder(h = 5, r1 = 1, r2 = 2)"
);
assert_eq!(
CylinderBuilder::default()
.h(5.0)
.d([1.0, 2.0])
.build()
.unwrap()
.repr_scad(),
"cylinder(h = 5, d1 = 1, d2 = 2)"
);
assert_eq!(
CylinderBuilder::default()
.h(5.0)
.r(3.0)
.fa(2.0)
.build()
.unwrap()
.repr_scad(),
"cylinder(h = 5, r = 3, $fa = 2)"
);
}
#[test]
fn test_polyhedron() {
let mut p0 = PolyhedronBuilder::default();
_ = p0.points(vec![
Point3D::new(1., 1., 1.),
Point3D::new(-1., 2., -1.),
Point3D::new(0., 0., 0.),
]);
assert_eq!(
PolyhedronBuilder::default()
.points(vec![
Point3D::new(1., 1., 1.),
Point3D::new(-1., 2., -1.),
Point3D::new(0., 0., 0.),
])
.build()
.unwrap()
.repr_scad(),
"polyhedron(points = [[1, 1, 1], [-1, 2, -1], [0, 0, 0]])"
);
{
let mut tmp = p0.clone();
assert_eq!(
tmp.faces(vec![vec![0, 2, 1]]).build().unwrap().repr_scad(),
"polyhedron(points = [[1, 1, 1], [-1, 2, -1], [0, 0, 0]], faces = [[0, 2, 1]])"
);
}
{
let mut tmp = p0.clone();
assert_eq!(
tmp.convexity(2_u64).build().unwrap().repr_scad(),
"polyhedron(points = [[1, 1, 1], [-1, 2, -1], [0, 0, 0]], convexity = 2)"
);
}
let mut p1 = PolyhedronBuilder::default();
_ = p1.points(vec![
[2., 0., 2.],
[1., 1., 1.],
[-1., 1., 0.],
[1., 0., -1.],
[0.5, 0.5, 0.7],
[-0.5, 0.5, -0.3],
]);
{
let mut tmp = p1.clone();
assert_eq!(
tmp.faces([vec![0, 1, 2], vec![3, 4, 5]]).build().unwrap().repr_scad(),
"polyhedron(points = [[2, 0, 2], [1, 1, 1], [-1, 1, 0], [1, 0, -1], [0.5, 0.5, 0.7], [-0.5, 0.5, -0.3]], faces = [[0, 1, 2], [3, 4, 5]])"
);
}
assert_eq!(
p1.clone()
.faces([vec![0, 1, 2], vec![6, 4, 5]])
.build()
.err()
.map(|e| e.to_string())
.unwrap_or_default(),
"path index out of bounds: [1][0]:6"
);
}
#[test]
fn test_import3d() {
assert_eq!(
Import3DBuilder::default()
.file("shape.stl")
.build()
.unwrap()
.repr_scad(),
"import(\"shape.stl\")"
);
assert_eq!(
Import3DBuilder::default()
.file("shape.stl")
.convexity(10_u64)
.build()
.unwrap()
.repr_scad(),
"import(\"shape.stl\", convexity = 10)"
);
}
#[test]
fn test_surface() {
assert_eq!(
SurfaceBuilder::default()
.file("shape.dat")
.build()
.unwrap()
.repr_scad(),
"surface(file = \"shape.dat\")"
);
assert_eq!(
SurfaceBuilder::default()
.file("shape.dat")
.convexity(10_u64)
.center(true)
.invert(true)
.build()
.unwrap()
.repr_scad(),
"surface(file = \"shape.dat\", center = true, invert = true, convexity = 10)"
);
}
}