#[cfg(feature = "color")]
use crate::common::color::Color;
use crate::common::error::Error;
use crate::common::mesh::Mesh;
use glam::{DMat3, DQuat, DVec3};
pub trait Transform: Sized {
fn translate(self, translation: DVec3) -> Self;
fn rotate(self, axis_origin: DVec3, axis_direction: DVec3, angle: f64) -> Self;
fn rotate_x(self, angle: f64) -> Self { self.rotate(DVec3::ZERO, DVec3::X, angle) }
fn rotate_y(self, angle: f64) -> Self { self.rotate(DVec3::ZERO, DVec3::Y, angle) }
fn rotate_z(self, angle: f64) -> Self { self.rotate(DVec3::ZERO, DVec3::Z, angle) }
fn scale(self, center: DVec3, factor: f64) -> Self;
fn mirror(self, plane_origin: DVec3, plane_normal: DVec3) -> Self;
fn align_x(self, new_x: DVec3, y_hint: DVec3) -> Self {
let x = new_x.try_normalize().expect("align_x: new_x is zero");
let z = x.cross(y_hint).try_normalize().expect("align_x: y_hint parallel to new_x");
let (axis, angle) = DQuat::from_mat3(&DMat3::from_cols(x, z.cross(x), z)).to_axis_angle();
self.rotate(DVec3::ZERO, axis, angle)
}
fn align_y(self, new_y: DVec3, z_hint: DVec3) -> Self {
let y = new_y.try_normalize().expect("align_y: new_y is zero");
let x = y.cross(z_hint).try_normalize().expect("align_y: z_hint parallel to new_y");
let (axis, angle) = DQuat::from_mat3(&DMat3::from_cols(x, y, x.cross(y))).to_axis_angle();
self.rotate(DVec3::ZERO, axis, angle)
}
fn align_z(self, new_z: DVec3, x_hint: DVec3) -> Self {
let z = new_z.try_normalize().expect("align_z: new_z is zero");
let y = z.cross(x_hint).try_normalize().expect("align_z: x_hint parallel to new_z");
let (axis, angle) = DQuat::from_mat3(&DMat3::from_cols(y.cross(z), y, z)).to_axis_angle();
self.rotate(DVec3::ZERO, axis, angle)
}
}
#[derive(Clone, Copy)]
pub enum ProfileOrient<'a> {
Fixed,
Torsion,
Up(DVec3),
Auxiliary(&'a [crate::Edge]),
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BSplineEnd {
Periodic,
NotAKnot,
Clamped {
start: DVec3,
end: DVec3,
},
}
pub trait EdgeExt: Transform {
type Elem: EdgeStruct;
fn start_point(&self) -> DVec3;
fn start_tangent(&self) -> DVec3;
fn is_closed(&self) -> bool;
fn approximation_segments(&self, tolerance: f64) -> Vec<DVec3>;
}
pub trait EdgeStruct: Sized + Clone + EdgeExt {
fn helix(radius: f64, pitch: f64, height: f64, axis: DVec3, x_ref: DVec3) -> Result<Self, Error>;
fn polygon(points: impl IntoIterator<Item = DVec3>) -> Result<Vec<Self>, Error>;
fn circle(radius: f64, axis: DVec3) -> Result<Self, Error>;
fn line(a: DVec3, b: DVec3) -> Result<Self, Error>;
fn arc_3pts(start: DVec3, mid: DVec3, end: DVec3) -> Result<Self, Error>;
fn bspline(points: impl IntoIterator<Item = DVec3>, end: BSplineEnd) -> Result<Self, Error>;
}
pub trait SolidStruct: Sized + Clone + SolidExt {
type Edge: EdgeStruct;
type Face;
fn cube(x: f64, y: f64, z: f64) -> Self;
fn sphere(radius: f64) -> Self;
fn cylinder(r: f64, axis: DVec3, h: f64) -> Self;
fn cone(r1: f64, r2: f64, axis: DVec3, h: f64) -> Self;
fn torus(r1: f64, r2: f64, axis: DVec3) -> Self;
fn half_space(plane_origin: DVec3, plane_normal: DVec3) -> Self;
fn faces(&self) -> Vec<Self::Face>;
fn edges(&self) -> Vec<Self::Edge>;
fn extrude<'a>(profile: impl IntoIterator<Item = &'a Self::Edge>, dir: DVec3) -> Result<Self, Error> where Self::Edge: 'a;
fn sweep<'a, 'b, 'c>(profile: impl IntoIterator<Item = &'a Self::Edge>, spine: impl IntoIterator<Item = &'b Self::Edge>, orient: ProfileOrient<'c>) -> Result<Self, Error> where Self::Edge: 'a + 'b;
fn loft<'a, S, I>(sections: S) -> Result<Self, Error> where S: IntoIterator<Item = I>, I: IntoIterator<Item = &'a Self::Edge>, Self::Edge: 'a;
fn bspline<const M: usize, const N: usize>(grid: [[DVec3; N]; M], periodic: bool) -> Result<Self, Error>;
fn boolean_union<'a, 'b>(a: impl IntoIterator<Item = &'a Self>, b: impl IntoIterator<Item = &'b Self>) -> Result<(Vec<Self>, [Vec<u64>; 2]), Error> where Self: 'a + 'b;
fn boolean_subtract<'a, 'b>(a: impl IntoIterator<Item = &'a Self>, b: impl IntoIterator<Item = &'b Self>) -> Result<(Vec<Self>, [Vec<u64>; 2]), Error> where Self: 'a + 'b;
fn boolean_intersect<'a, 'b>(a: impl IntoIterator<Item = &'a Self>, b: impl IntoIterator<Item = &'b Self>) -> Result<(Vec<Self>, [Vec<u64>; 2]), Error> where Self: 'a + 'b;
}
pub trait SolidExt: Transform {
type Elem: SolidStruct;
fn clean(&self) -> Result<Self, Error>;
fn volume(&self) -> f64;
fn bounding_box(&self) -> [DVec3; 2];
fn contains(&self, point: DVec3) -> bool;
fn shell_count(&self) -> u32;
#[cfg(feature = "color")]
fn color(self, color: impl Into<Color>) -> Self;
#[cfg(feature = "color")]
fn color_clear(self) -> Self;
fn union_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a Self::Elem>) -> Result<(Vec<Self::Elem>, [Vec<u64>; 2]), Error> where Self::Elem: 'a;
fn subtract_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a Self::Elem>) -> Result<(Vec<Self::Elem>, [Vec<u64>; 2]), Error> where Self::Elem: 'a;
fn intersect_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a Self::Elem>) -> Result<(Vec<Self::Elem>, [Vec<u64>; 2]), Error> where Self::Elem: 'a;
fn union<'a>(&self, tool: impl IntoIterator<Item = &'a Self::Elem>) -> Result<Vec<Self::Elem>, Error> where Self::Elem: 'a { Ok(self.union_with_metadata(tool)?.0) }
fn subtract<'a>(&self, tool: impl IntoIterator<Item = &'a Self::Elem>) -> Result<Vec<Self::Elem>, Error> where Self::Elem: 'a { Ok(self.subtract_with_metadata(tool)?.0) }
fn intersect<'a>(&self, tool: impl IntoIterator<Item = &'a Self::Elem>) -> Result<Vec<Self::Elem>, Error> where Self::Elem: 'a { Ok(self.intersect_with_metadata(tool)?.0) }
}
impl<T: Transform> Transform for Vec<T> {
fn translate(self, v: DVec3) -> Self { self.into_iter().map(|s| s.translate(v)).collect() }
fn rotate(self, o: DVec3, d: DVec3, a: f64) -> Self { self.into_iter().map(|s| s.rotate(o, d, a)).collect() }
fn scale(self, c: DVec3, f: f64) -> Self { self.into_iter().map(|s| s.scale(c, f)).collect() }
fn mirror(self, o: DVec3, n: DVec3) -> Self { self.into_iter().map(|s| s.mirror(o, n)).collect() }
}
impl<T: SolidStruct> SolidExt for Vec<T> {
type Elem = T;
fn clean(&self) -> Result<Self, Error> { self.iter().map(|s| s.clean()).collect() }
fn volume(&self) -> f64 { self.iter().map(|s| s.volume()).sum() }
fn bounding_box(&self) -> [DVec3; 2] {
self.iter().map(|s| s.bounding_box())
.reduce(|[amin, amax], [bmin, bmax]| [amin.min(bmin), amax.max(bmax)])
.unwrap_or([DVec3::ZERO, DVec3::ZERO])
}
fn contains(&self, p: DVec3) -> bool { self.iter().any(|s| s.contains(p)) }
fn shell_count(&self) -> u32 { self.iter().map(|s| s.shell_count()).sum() }
#[cfg(feature = "color")]
fn color(self, color: impl Into<Color>) -> Self {
let c: Color = color.into();
self.into_iter().map(|s| s.color(c)).collect()
}
#[cfg(feature = "color")]
fn color_clear(self) -> Self {
self.into_iter().map(|s| s.color_clear()).collect()
}
fn union_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a T>) -> Result<(Vec<T>, [Vec<u64>; 2]), Error> where T: 'a {
T::boolean_union(self.iter(), tool)
}
fn subtract_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a T>) -> Result<(Vec<T>, [Vec<u64>; 2]), Error> where T: 'a {
T::boolean_subtract(self.iter(), tool)
}
fn intersect_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a T>) -> Result<(Vec<T>, [Vec<u64>; 2]), Error> where T: 'a {
T::boolean_intersect(self.iter(), tool)
}
}
impl<T: Transform, const N: usize> Transform for [T; N] {
fn translate(self, v: DVec3) -> Self { self.map(|s| s.translate(v)) }
fn rotate(self, o: DVec3, d: DVec3, a: f64) -> Self { self.map(|s| s.rotate(o, d, a)) }
fn scale(self, c: DVec3, f: f64) -> Self { self.map(|s| s.scale(c, f)) }
fn mirror(self, o: DVec3, n: DVec3) -> Self { self.map(|s| s.mirror(o, n)) }
}
impl<T: SolidStruct, const N: usize> SolidExt for [T; N] {
type Elem = T;
fn clean(&self) -> Result<Self, Error> {
let v: Result<Vec<T>, Error> = self.iter().map(|s| s.clean()).collect();
v?.try_into().map_err(|_| unreachable!())
}
fn volume(&self) -> f64 { self.iter().map(|s| s.volume()).sum() }
fn bounding_box(&self) -> [DVec3; 2] {
self.iter().map(|s| s.bounding_box())
.reduce(|[amin, amax], [bmin, bmax]| [amin.min(bmin), amax.max(bmax)])
.unwrap_or([DVec3::ZERO, DVec3::ZERO])
}
fn contains(&self, p: DVec3) -> bool { self.iter().any(|s| s.contains(p)) }
fn shell_count(&self) -> u32 { self.iter().map(|s| s.shell_count()).sum() }
#[cfg(feature = "color")]
fn color(self, color: impl Into<Color>) -> Self {
let c: Color = color.into();
self.map(|s| s.color(c))
}
#[cfg(feature = "color")]
fn color_clear(self) -> Self {
self.map(|s| s.color_clear())
}
fn union_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a T>) -> Result<(Vec<T>, [Vec<u64>; 2]), Error> where T: 'a {
T::boolean_union(self.iter(), tool)
}
fn subtract_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a T>) -> Result<(Vec<T>, [Vec<u64>; 2]), Error> where T: 'a {
T::boolean_subtract(self.iter(), tool)
}
fn intersect_with_metadata<'a>(&self, tool: impl IntoIterator<Item = &'a T>) -> Result<(Vec<T>, [Vec<u64>; 2]), Error> where T: 'a {
T::boolean_intersect(self.iter(), tool)
}
}
impl<T: EdgeStruct> EdgeExt for Vec<T> {
type Elem = T;
fn start_point(&self) -> DVec3 {
self.first().map(|e| e.start_point()).unwrap_or(DVec3::ZERO)
}
fn start_tangent(&self) -> DVec3 {
self.first().map(|e| e.start_tangent()).unwrap_or(DVec3::ZERO)
}
fn is_closed(&self) -> bool {
match self.len() {
0 => false,
1 => self[0].is_closed(),
_ => {
let start = self[0].start_point();
let last_pts = self[self.len() - 1].approximation_segments(1e-3);
let end = last_pts.last().copied().unwrap_or(DVec3::ZERO);
(start - end).length() < 1e-6
}
}
}
fn approximation_segments(&self, tolerance: f64) -> Vec<DVec3> {
let mut out: Vec<DVec3> = Vec::new();
for e in self {
let pts = e.approximation_segments(tolerance);
if let Some((first, rest)) = pts.split_first() {
if out.last().map(|p| (*p - *first).length() < 1e-9).unwrap_or(false) {
out.extend_from_slice(rest);
} else {
out.push(*first);
out.extend_from_slice(rest);
}
}
}
out
}
}
impl<T: EdgeStruct, const N: usize> EdgeExt for [T; N] {
type Elem = T;
fn start_point(&self) -> DVec3 {
self.first().map(|e| e.start_point()).unwrap_or(DVec3::ZERO)
}
fn start_tangent(&self) -> DVec3 {
self.first().map(|e| e.start_tangent()).unwrap_or(DVec3::ZERO)
}
fn is_closed(&self) -> bool {
match N {
0 => false,
1 => self[0].is_closed(),
_ => {
let start = self[0].start_point();
let last_pts = self[N - 1].approximation_segments(1e-3);
let end = last_pts.last().copied().unwrap_or(DVec3::ZERO);
(start - end).length() < 1e-6
}
}
}
fn approximation_segments(&self, tolerance: f64) -> Vec<DVec3> {
let mut out: Vec<DVec3> = Vec::new();
for e in self {
let pts = e.approximation_segments(tolerance);
if let Some((first, rest)) = pts.split_first() {
if out.last().map(|p| (*p - *first).length() < 1e-9).unwrap_or(false) {
out.extend_from_slice(rest);
} else {
out.push(*first);
out.extend_from_slice(rest);
}
}
}
out
}
}
#[allow(non_camel_case_types)]
pub trait IoModule {
type Solid: SolidStruct;
fn read_step<R: std::io::Read>(reader: &mut R) -> Result<Vec<Self::Solid>, Error>;
fn read_brep_binary<R: std::io::Read>(reader: &mut R) -> Result<Vec<Self::Solid>, Error>;
fn read_brep_text<R: std::io::Read>(reader: &mut R) -> Result<Vec<Self::Solid>, Error>;
fn write_step<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self::Solid>, writer: &mut W) -> Result<(), Error> where Self::Solid: 'a;
fn write_brep_binary<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self::Solid>, writer: &mut W) -> Result<(), Error> where Self::Solid: 'a;
fn write_brep_text<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self::Solid>, writer: &mut W) -> Result<(), Error> where Self::Solid: 'a;
fn mesh<'a>(solids: impl IntoIterator<Item = &'a Self::Solid>, tolerance: f64) -> Result<Mesh, Error> where Self::Solid: 'a;
fn write_svg<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self::Solid>, direction: DVec3, tolerance: f64, hidden_lines: bool, shading: bool, writer: &mut W) -> Result<(), Error> where Self::Solid: 'a { writer.write_all(Self::mesh(solids, tolerance)?.to_svg(direction, hidden_lines, shading).as_bytes()).map_err(|_| Error::SvgExportFailed) }
fn write_stl<'a, W: std::io::Write>(solids: impl IntoIterator<Item = &'a Self::Solid>, tolerance: f64, writer: &mut W) -> Result<(), Error> where Self::Solid: 'a { Self::mesh(solids, tolerance)?.write_stl(writer) }
}