From 353601293596261ded4d6392c93c8de2c49cd191 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 21 Jun 2025 17:19:05 +0200 Subject: [PATCH 01/10] refactor: compare float structs using approx crate --- Cargo.lock | 26 +++++++++++-- Cargo.toml | 2 +- src/core/angle.rs | 26 +++++++++++++ src/core/dir.rs | 65 +++++++++++++++++++------------ src/core/length.rs | 32 +++++++++++++-- src/core/path.rs | 35 ++++++----------- src/core/point.rs | 31 +++++++++++++++ src/meshes/render_mesh.rs | 24 ++++++------ src/parts/part.rs | 17 ++++---- src/parts/primitives/cube.rs | 3 +- src/parts/primitives/cuboid.rs | 12 +++--- src/parts/primitives/cylinder.rs | 15 +++---- src/parts/primitives/sphere.rs | 10 +++-- src/sketches/primitives/circle.rs | 10 +++-- src/sketches/primitives/square.rs | 3 +- src/sketches/sketch.rs | 3 +- 16 files changed, 218 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e79e0b6..1e3bdea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,7 +12,7 @@ checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" name = "anvil" version = "0.1.0" dependencies = [ - "assert_float_eq", + "approx", "cxx", "iter_fixed", "opencascade-sys", @@ -21,10 +21,19 @@ dependencies = [ ] [[package]] -name = "assert_float_eq" -version = "1.1.4" +name = "approx" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d2119f741b79fe9907f5396d19bffcb46568cfcc315e78677d731972ac7085" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" @@ -219,6 +228,15 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "occt-sys" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index 749c06d..464e6d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ default = ["builtin"] builtin = [ "opencascade-sys/builtin" ] [dependencies] +approx = "0.5" cxx = "1" iter_fixed = "0.4.0" opencascade-sys = { git = "https://github.com/bschwind/opencascade-rs", rev = "c30da56647c2a60393984458439180886ecaf951" } @@ -40,4 +41,3 @@ tempfile = "3.19.1" [dev-dependencies] tempdir = "0.3.7" -assert_float_eq = "1.1.4" diff --git a/src/core/angle.rs b/src/core/angle.rs index 25b0cbf..ce27203 100644 --- a/src/core/angle.rs +++ b/src/core/angle.rs @@ -1,6 +1,8 @@ use core::f64; use std::ops::{Add, Div, Mul, Neg, Sub}; +use approx::{AbsDiffEq, RelativeEq}; + use super::IntoF64; /// A physical angle (i.e. a distance). @@ -192,6 +194,30 @@ impl Neg for Angle { } } +impl AbsDiffEq for Angle { + type Epsilon = f64; + fn default_epsilon() -> Self::Epsilon { + f64::default_epsilon() + } + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + f64::abs_diff_eq(&self.rad, &other.rad, epsilon) + } +} + +impl RelativeEq for Angle { + fn default_max_relative() -> Self::Epsilon { + f64::default_max_relative() + } + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + f64::relative_eq(&self.rad, &other.rad, epsilon, max_relative) + } +} + /// Import this trait to easily convert numbers into `Angle`s. /// /// ```rust diff --git a/src/core/dir.rs b/src/core/dir.rs index 6f608d4..e38cbf2 100644 --- a/src/core/dir.rs +++ b/src/core/dir.rs @@ -1,5 +1,6 @@ use std::ops::{Add, Mul, Sub}; +use approx::{AbsDiffEq, RelativeEq}; use cxx::UniquePtr; use iter_fixed::IntoIteratorFixed; use opencascade_sys::ffi; @@ -63,23 +64,6 @@ impl Dir { pub fn dot(&self, other: Self) -> f64 { self.0.into_iter().zip(other.0).map(|(a, b)| a * b).sum() } - - /// Return true if this `Dir` has less than a 0.000001% difference to another. - /// - /// ```rust - /// use anvil::dir; - /// - /// assert!(dir!(1, 1).approx_eq(dir!(1.00000001, 1))); - /// assert!(!dir!(1, 1).approx_eq(dir!(0.5, 1))); - /// ``` - pub fn approx_eq(&self, other: Dir) -> bool { - for (s, o) in self.0.iter().zip(other.0) { - if (s / o - 1.).abs() > 0.0000001 { - return false; - } - } - true - } } impl Dir<2> { @@ -96,15 +80,16 @@ impl Dir<2> { /// /// ```rust /// use anvil::{dir, IntoAngle}; + /// use approx::assert_relative_eq; /// - /// assert!((dir!(1, 0).angle() - 0.deg()).rad().abs() < 1e-9); - /// assert!((dir!(1, 1).angle() - 45.deg()).rad().abs() < 1e-9); - /// assert!((dir!(0, 1).angle() - 90.deg()).rad().abs() < 1e-9); - /// assert!((dir!(-1, 1).angle() - 135.deg()).rad().abs() < 1e-9); - /// assert!((dir!(-1, 0).angle() - 180.deg()).rad().abs() < 1e-9); - /// assert!((dir!(-1, -1).angle() - 225.deg()).rad().abs() < 1e-9); - /// assert!((dir!(0, -1).angle() - 270.deg()).rad().abs() < 1e-9); - /// assert!((dir!(1, -1).angle() - 315.deg()).rad().abs() < 1e-9); + /// assert_relative_eq!(dir!(1, 0).angle(), 0.deg()); + /// assert_relative_eq!(dir!(1, 1).angle(), 45.deg()); + /// assert_relative_eq!(dir!(0, 1).angle(), 90.deg()); + /// assert_relative_eq!(dir!(-1, 1).angle(), 135.deg()); + /// assert_relative_eq!(dir!(-1, 0).angle(), 180.deg()); + /// assert_relative_eq!(dir!(-1, -1).angle(), 225.deg()); + /// assert_relative_eq!(dir!(0, -1).angle(), 270.deg()); + /// assert_relative_eq!(dir!(1, -1).angle(), 315.deg()); /// ``` pub fn angle(&self) -> Angle { let angle = Angle::from_rad(self.y().atan2(self.x())); @@ -259,6 +244,36 @@ impl Mul for Dir { } } +impl AbsDiffEq for Dir { + type Epsilon = f64; + fn default_epsilon() -> Self::Epsilon { + f64::default_epsilon() + } + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + self.0 + .iter() + .zip(other.0.iter()) + .all(|(a, b)| f64::abs_diff_eq(a, b, epsilon)) + } +} + +impl RelativeEq for Dir { + fn default_max_relative() -> Self::Epsilon { + f64::default_max_relative() + } + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + self.0 + .iter() + .zip(other.0.iter()) + .all(|(a, b)| f64::relative_eq(a, b, epsilon, max_relative)) + } +} + /// Macro for simplifying `Dir` construction for static values. /// /// ```rust diff --git a/src/core/length.rs b/src/core/length.rs index fb6ee05..f650d0b 100644 --- a/src/core/length.rs +++ b/src/core/length.rs @@ -1,3 +1,4 @@ +use approx::{AbsDiffEq, RelativeEq}; use std::{ fmt::Debug, ops::{Add, Div, Mul, Neg, Sub}, @@ -12,6 +13,7 @@ use crate::{Dir, IntoF64, Point}; /// /// ```rust /// use anvil::Length; +/// use approx::assert_relative_eq; /// /// // You can construct a `Length` using the Length::from_[unit] methods like /// let meters_length = Length::from_m(1.2); @@ -19,9 +21,9 @@ use crate::{Dir, IntoF64, Point}; /// let inches_length = Length::from_in(12.); /// /// // To get back a `Length` value in a specific unit, call the Length.[unit] method -/// assert_eq!(meters_length.cm(), 120.); -/// assert_eq!(centimeters_length.m(), 0.045); -/// assert!((inches_length.ft() - 1.).abs() < 1e-9); +/// assert_relative_eq!(meters_length.cm(), 120.); +/// assert_relative_eq!(centimeters_length.m(), 0.045); +/// assert_relative_eq!(inches_length.ft(), 1.); /// /// // Length construction can be simplified using the `IntoLength` trait. /// use anvil::IntoLength; @@ -290,6 +292,30 @@ impl Neg for Length { } } +impl AbsDiffEq for Length { + type Epsilon = f64; + fn default_epsilon() -> Self::Epsilon { + f64::default_epsilon() + } + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + f64::abs_diff_eq(&self.meters, &other.meters, epsilon) + } +} + +impl RelativeEq for Length { + fn default_max_relative() -> Self::Epsilon { + f64::default_max_relative() + } + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + f64::relative_eq(&self.meters, &other.meters, epsilon, max_relative) + } +} + /// Return true if any IntoLength in the input array is zero. pub fn is_zero(lengths: &[Length]) -> bool { for length in lengths { diff --git a/src/core/path.rs b/src/core/path.rs index d89482f..c086f0a 100644 --- a/src/core/path.rs +++ b/src/core/path.rs @@ -212,49 +212,38 @@ impl Path { #[cfg(test)] mod tests { - use assert_float_eq::assert_float_relative_eq; - use super::*; use crate::{IntoAngle, IntoLength, dir, point}; - - fn assert_dir_eq(dir1: Dir<2>, dir2: Dir<2>) { - assert_float_relative_eq!(dir1.x(), dir2.x()); - assert_float_relative_eq!(dir1.y(), dir2.y()); - } - - fn assert_point_eq(point1: Point<2>, point2: Point<2>) { - assert_float_relative_eq!(point1.x().m(), point2.x().m()); - assert_float_relative_eq!(point1.y().m(), point2.y().m()); - } + use approx::assert_relative_eq; #[test] fn end_arc_positive_radius_angle() { let path = Path::at(point!(0, 0)).arc_by(1.m(), 90.deg()); - assert_point_eq(path.end(), point!(1.m(), 1.m())) + assert_relative_eq!(path.end(), point!(1.m(), 1.m())) } #[test] fn end_arc_positive_radius_negative_angle() { let path = Path::at(point!(0, 0)).arc_by(1.m(), -90.deg()); - assert_point_eq(path.end(), point!(-1.m(), 1.m())) + assert_relative_eq!(path.end(), point!(-1.m(), 1.m())) } #[test] fn end_arc_negative_radius_positive_angle() { let path = Path::at(point!(0, 0)).arc_by(-1.m(), 90.deg()); - assert_point_eq(path.end(), point!(1.m(), -1.m())) + assert_relative_eq!(path.end(), point!(1.m(), -1.m())) } #[test] fn end_arc_negative_radius_angle() { let path = Path::at(point!(0, 0)).arc_by(-1.m(), -90.deg()); - assert_point_eq(path.end(), point!(-1.m(), -1.m())) + assert_relative_eq!(path.end(), point!(-1.m(), -1.m())) } #[test] fn end_arc_negative_radius_positive_angle_45deg() { let path = Path::at(point!(0.m(), 1.m())).arc_by(-1.m(), 45.deg()); - assert_point_eq( + assert_relative_eq!( path.end(), point!(1.m() / f64::sqrt(2.), 1.m() / f64::sqrt(2.)), ) @@ -263,36 +252,36 @@ mod tests { #[test] fn end_direction_empty_path() { let path = Path::at(point!(0, 0)); - assert_dir_eq(path.end_direction(), dir!(1, 0)) + assert_relative_eq!(path.end_direction(), dir!(1, 0)) } #[test] fn end_direction_line() { let path = Path::at(point!(0, 0)).line_to(point!(1.m(), 1.m())); - assert_dir_eq(path.end_direction(), dir!(1, 1)) + assert_relative_eq!(path.end_direction(), dir!(1, 1)) } #[test] fn end_direction_arc_positive_radius_angle() { let path = Path::at(point!(0, 0)).arc_by(1.m(), 45.deg()); - assert_dir_eq(path.end_direction(), dir!(1, 1)) + assert_relative_eq!(path.end_direction(), dir!(1, 1), epsilon = 1e-9) } #[test] fn end_direction_arc_positive_radius_negative_angle() { let path = Path::at(point!(0, 0)).arc_by(1.m(), -45.deg()); - assert_dir_eq(path.end_direction(), dir!(-1, 1)) + assert_relative_eq!(path.end_direction(), dir!(-1, 1), epsilon = 1e-9) } #[test] fn end_direction_arc_negative_radius_positive_angle() { let path = Path::at(point!(0, 0)).arc_by(-1.m(), 45.deg()); - assert_dir_eq(path.end_direction(), dir!(1, -1)) + assert_relative_eq!(path.end_direction(), dir!(1, -1)) } #[test] fn end_direction_arc_negative_radius_angle() { let path = Path::at(point!(0, 0)).arc_by(-1.m(), -45.deg()); - assert_dir_eq(path.end_direction(), dir!(-1, -1)) + assert_relative_eq!(path.end_direction(), dir!(-1, -1)) } } diff --git a/src/core/point.rs b/src/core/point.rs index 683f817..f8641c9 100644 --- a/src/core/point.rs +++ b/src/core/point.rs @@ -1,5 +1,6 @@ use std::ops::{Add, Div, Mul, Sub}; +use approx::{AbsDiffEq, RelativeEq}; use cxx::UniquePtr; use iter_fixed::IntoIteratorFixed; use opencascade_sys::ffi; @@ -268,6 +269,36 @@ impl Div for Point { } } +impl AbsDiffEq for Point { + type Epsilon = f64; + fn default_epsilon() -> Self::Epsilon { + f64::default_epsilon() + } + fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { + self.0 + .iter() + .zip(other.0.iter()) + .all(|(a, b)| Length::abs_diff_eq(a, b, epsilon)) + } +} + +impl RelativeEq for Point { + fn default_max_relative() -> Self::Epsilon { + f64::default_max_relative() + } + fn relative_eq( + &self, + other: &Self, + epsilon: Self::Epsilon, + max_relative: Self::Epsilon, + ) -> bool { + self.0 + .iter() + .zip(other.0.iter()) + .all(|(a, b)| Length::relative_eq(a, b, epsilon, max_relative)) + } +} + /// Macro for simplifying `Point` construction for static values. /// /// # Examples diff --git a/src/meshes/render_mesh.rs b/src/meshes/render_mesh.rs index f98bbed..a7340e4 100644 --- a/src/meshes/render_mesh.rs +++ b/src/meshes/render_mesh.rs @@ -55,14 +55,15 @@ impl RenderMesh { /// /// ```rust /// use anvil::{Cube, IntoLength, Plane, Rectangle, RenderMesh}; + /// use approx::assert_relative_eq; /// /// let rect = Rectangle::from_dim(2.m(), 3.m()); /// let mesh = RenderMesh::try_from(rect.to_face(Plane::xy()).unwrap()).unwrap(); - /// assert!((mesh.area() - 6.).abs() < 0.0001); + /// assert_relative_eq!(mesh.area(), 6.); /// /// let cube = Cube::from_size(2.m()); /// let mesh = RenderMesh::try_from(cube).unwrap(); - /// assert!((mesh.area() - 24.).abs() < 0.0001); + /// assert_relative_eq!(mesh.area(), 24.); /// ``` pub fn area(&self) -> f64 { let mut total_area = 0.; @@ -96,13 +97,15 @@ impl RenderMesh { /// /// ```rust /// use anvil::{IntoLength, Plane, Rectangle, RenderMesh, point}; + /// use approx::assert_relative_eq; /// /// let rect = Rectangle::from_dim(1.m(), 1.m()).move_to(point!(2.m(), 3.m())); /// let mesh = RenderMesh::try_from(rect.to_face(Plane::xy()).unwrap()).unwrap(); /// let mesh_center = mesh.center(); - /// assert!((mesh_center.x() - 2.m()).abs() < 0.0001.m()); - /// assert!((mesh_center.y() - 3.m()).abs() < 0.0001.m()); - /// assert!(mesh_center.z().abs() < 0.0001.m()); + /// assert_relative_eq!( + /// mesh_center, + /// point!(2.m(), 3.m(), 0.m()) + /// ); /// ``` pub fn center(&self) -> Point<3> { let mut sum_of_points = Point::<3>::origin(); @@ -261,6 +264,8 @@ fn merge(meshes: Vec) -> RenderMesh { mod tests { use core::f64; + use approx::{assert_abs_diff_eq, assert_relative_eq}; + use crate::{Axis, Circle, Cube, IntoAngle, IntoLength, Path, Plane, Rectangle, dir, point}; use super::*; @@ -315,11 +320,8 @@ mod tests { fn circle() { let mesh = RenderMesh::try_from(Circle::from_radius(1.m()).to_face(Plane::xy()).unwrap()).unwrap(); - assert!(mesh.center().x().abs().m() < 0.00001); - assert!(mesh.center().y().abs().m() < 0.00001); - assert!(mesh.center().z().abs().m() < 0.00001); - assert!((mesh.area() - f64::consts::PI).abs() < 0.00001); - + assert_abs_diff_eq!(mesh.center(), point!(0, 0, 0), epsilon = 1e-6); + assert_abs_diff_eq!(mesh.area(), f64::consts::PI, epsilon = 1e-4); assert_eq!(mesh.normals(), &vec![dir!(0, 0, -1); mesh.normals().len()]); } @@ -332,7 +334,7 @@ mod tests { RenderMesh::try_from(cube.faces().collect::>().first().unwrap().clone()) .unwrap(); for normal in mesh.normals { - assert!(normal.approx_eq(dir!(-1, -1, 0))) + assert_relative_eq!(normal, dir!(-1, -1, 0)) } } diff --git a/src/parts/part.rs b/src/parts/part.rs index bcd006f..2fa4c54 100644 --- a/src/parts/part.rs +++ b/src/parts/part.rs @@ -279,9 +279,10 @@ impl Part { /// /// ```rust /// use anvil::{Cuboid, IntoLength}; + /// use approx::assert_relative_eq; /// /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// assert!((cuboid.volume() - 1.).abs() < 1e-9) + /// assert_relative_eq!(cuboid.volume(), 1.); /// ``` pub fn volume(&self) -> f64 { match &self.inner { @@ -445,40 +446,42 @@ fn round(x: f64, n_digits: u8) -> f64 { #[cfg(test)] mod tests { + use approx::assert_relative_eq; + use super::*; use crate::{Cuboid, IntoLength, Sphere, point}; #[test] fn eq_both_none() { - assert!(Part::empty() == Part::empty()) + assert_eq!(Part::empty(), Part::empty()) } #[test] fn eq_both_cuboid() { let cuboid1 = Cuboid::from_m(1., 1., 1.); let cuboid2 = Cuboid::from_m(1., 1., 1.); - assert!(cuboid1 == cuboid2) + assert_eq!(cuboid1, cuboid2) } #[test] fn neq_both_cuboid() { let cuboid1 = Cuboid::from_m(1., 1., 1.); let cuboid2 = Cuboid::from_m(2., 2., 2.); - assert!(cuboid1 != cuboid2) + assert_ne!(cuboid1, cuboid2) } #[test] fn eq_both_sphere() { let sphere1 = Sphere::from_radius(2.m()); let sphere2 = Sphere::from_radius(2.m()); - assert!(sphere1 == sphere2) + assert_eq!(sphere1, sphere2) } #[test] fn neq_both_sphere() { let sphere1 = Sphere::from_radius(1.m()); let sphere2 = Sphere::from_radius(2.m()); - assert!(sphere1 != sphere2) + assert_ne!(sphere1, sphere2) } #[test] @@ -494,7 +497,7 @@ mod tests { #[test] fn volume() { let cuboid = Cuboid::from_m(1., 1., 1.); - assert!((cuboid.volume() - 1.).abs() < 1e-9) + assert_relative_eq!(cuboid.volume(), 1.) } #[test] diff --git a/src/parts/primitives/cube.rs b/src/parts/primitives/cube.rs index 78e6d18..1355234 100644 --- a/src/parts/primitives/cube.rs +++ b/src/parts/primitives/cube.rs @@ -12,10 +12,11 @@ impl Cube { /// # Example /// ```rust /// use anvil::{Cube, IntoLength, Part, point}; + /// use approx::assert_relative_eq; /// /// let part = Cube::from_size(1.m()); /// assert_eq!(part.center(), Ok(point!(0, 0, 0))); - /// assert!((part.volume() - 1.).abs() < 1e-5); + /// assert_relative_eq!(part.volume(), 1.); /// ``` pub fn from_size(size: Length) -> Part { Cuboid::from_dim(size, size, size) diff --git a/src/parts/primitives/cuboid.rs b/src/parts/primitives/cuboid.rs index 6270a87..fd274da 100644 --- a/src/parts/primitives/cuboid.rs +++ b/src/parts/primitives/cuboid.rs @@ -14,10 +14,11 @@ impl Cuboid { /// # Example /// ```rust /// use anvil::{Cuboid, IntoLength, Part, point}; + /// use approx::assert_relative_eq; /// /// let part = Cuboid::from_dim(1.m(), 2.m(), 3.m()); /// assert_eq!(part.center(), Ok(point!(0, 0, 0))); - /// assert!((part.volume() - 6.).abs() < 1e-5); + /// assert_relative_eq!(part.volume(), 6.); /// ``` pub fn from_dim(x: Length, y: Length, z: Length) -> Part { Self::from_corners( @@ -30,10 +31,11 @@ impl Cuboid { /// # Example /// ```rust /// use anvil::{Cuboid, IntoLength, Part, point}; + /// use approx::assert_relative_eq; /// /// let part = Cuboid::from_corners(point!(0, 0, 0), point!(2.m(), 2.m(), 2.m())); /// assert_eq!(part.center(), Ok(point!(1.m(), 1.m(), 1.m()))); - /// assert!((part.volume() - 8.).abs() < 1e-5); + /// assert_relative_eq!(part.volume(), 8.); /// ``` pub fn from_corners(corner1: Point<3>, corner2: Point<3>) -> Part { let volume_is_zero = is_zero(&[ @@ -101,8 +103,8 @@ mod tests { #[test] fn from_dim_empty() { - assert!(Cuboid::from_dim(0.m(), 1.m(), 1.m()) == Part::empty()); - assert!(Cuboid::from_dim(1.m(), 0.m(), 1.m()) == Part::empty()); - assert!(Cuboid::from_dim(1.m(), 1.m(), 0.m()) == Part::empty()) + assert_eq!(Cuboid::from_dim(0.m(), 1.m(), 1.m()), Part::empty()); + assert_eq!(Cuboid::from_dim(1.m(), 0.m(), 1.m()), Part::empty()); + assert_eq!(Cuboid::from_dim(1.m(), 1.m(), 0.m()), Part::empty()) } } diff --git a/src/parts/primitives/cylinder.rs b/src/parts/primitives/cylinder.rs index 1a3588e..39a2d97 100644 --- a/src/parts/primitives/cylinder.rs +++ b/src/parts/primitives/cylinder.rs @@ -10,13 +10,13 @@ pub struct Cylinder; impl Cylinder { /// Construct a centered cylindrical `Part` from a given radius. /// - /// # Example /// ```rust /// use anvil::{Cylinder, IntoLength, Point, Part}; + /// use approx::assert_relative_eq; /// /// let part = Cylinder::from_radius(1.m(), 2.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert!((part.volume() - 6.28319).abs() < 1e-5); + /// assert_relative_eq!(part.volume(), 6.283185307179587); /// ``` pub fn from_radius(radius: Length, height: Length) -> Part { if is_zero(&[radius, height]) { @@ -35,10 +35,11 @@ impl Cylinder { /// # Example /// ```rust /// use anvil::{Cylinder, IntoLength, Point, Part}; + /// use approx::assert_relative_eq; /// /// let part = Cylinder::from_diameter(1.m(), 2.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert!((part.volume() - 1.57080).abs() < 1e-5); + /// assert_relative_eq!(part.volume(), 1.5707963267948968); /// ``` pub fn from_diameter(diameter: Length, height: Length) -> Part { Self::from_radius(diameter / 2., height) @@ -52,13 +53,13 @@ mod tests { #[test] fn from_radius_empty() { - assert!(Cylinder::from_radius(0.m(), 1.m()) == Part::empty()); - assert!(Cylinder::from_radius(1.m(), 0.m()) == Part::empty()); + assert_eq!(Cylinder::from_radius(0.m(), 1.m()), Part::empty()); + assert_eq!(Cylinder::from_radius(1.m(), 0.m()), Part::empty()); } #[test] fn from_diameter_empty() { - assert!(Cylinder::from_diameter(0.m(), 1.m()) == Part::empty()); - assert!(Cylinder::from_diameter(1.m(), 0.m()) == Part::empty()); + assert_eq!(Cylinder::from_diameter(0.m(), 1.m()), Part::empty()); + assert_eq!(Cylinder::from_diameter(1.m(), 0.m()), Part::empty()); } } diff --git a/src/parts/primitives/sphere.rs b/src/parts/primitives/sphere.rs index 3b24fb9..0a6e93a 100644 --- a/src/parts/primitives/sphere.rs +++ b/src/parts/primitives/sphere.rs @@ -13,10 +13,11 @@ impl Sphere { /// # Example /// ```rust /// use anvil::{Sphere, IntoLength, Point, Part}; + /// use approx::assert_relative_eq; /// /// let part = Sphere::from_radius(1.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert!((part.volume() - 4.18879).abs() < 1e-5); + /// assert_relative_eq!(part.volume(), 4.188790204786391); /// ``` pub fn from_radius(radius: Length) -> Part { if is_zero(&[radius]) { @@ -33,10 +34,11 @@ impl Sphere { /// # Example /// ```rust /// use anvil::{Sphere, IntoLength, Point, Part}; + /// use approx::assert_relative_eq; /// /// let part = Sphere::from_diameter(1.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert!((part.volume() - 0.523599).abs() < 1e-5); + /// assert_relative_eq!(part.volume(), 0.5235987755982989); /// ``` pub fn from_diameter(diameter: Length) -> Part { Self::from_radius(diameter / 2.) @@ -50,11 +52,11 @@ mod tests { #[test] fn from_radius_empty() { - assert!(Sphere::from_radius(0.m()) == Part::empty()) + assert_eq!(Sphere::from_radius(0.m()), Part::empty()) } #[test] fn from_diameter_empty() { - assert!(Sphere::from_diameter(0.m()) == Part::empty()) + assert_eq!(Sphere::from_diameter(0.m()), Part::empty()) } } diff --git a/src/sketches/primitives/circle.rs b/src/sketches/primitives/circle.rs index 76bcf98..e1eed23 100644 --- a/src/sketches/primitives/circle.rs +++ b/src/sketches/primitives/circle.rs @@ -11,10 +11,12 @@ impl Circle { /// /// # Example /// ```rust + /// use core::f64; /// use anvil::{Circle, IntoLength, Point}; + /// use approx::assert_relative_eq; /// /// let circle = Circle::from_radius(1.m()); - /// assert!((circle.area() - 3.141593).abs() < 1e-5); + /// assert_relative_eq!(circle.area(), f64::consts::PI); /// assert_eq!(circle.center(), Ok(Point::<2>::origin())); /// ``` pub fn from_radius(radius: Length) -> Sketch { @@ -34,10 +36,12 @@ impl Circle { /// /// # Example /// ```rust + /// use core::f64; /// use anvil::{Circle, IntoLength, Point}; + /// use approx::assert_relative_eq; /// - /// let circle = Circle::from_diameter(1.m()); - /// assert!((circle.area() - 0.785398).abs() < 1e-5); + /// let circle = Circle::from_diameter(2.m()); + /// assert_relative_eq!(circle.area(), f64::consts::PI); /// assert_eq!(circle.center(), Ok(Point::<2>::origin())); /// ``` pub fn from_diameter(diameter: Length) -> Sketch { diff --git a/src/sketches/primitives/square.rs b/src/sketches/primitives/square.rs index f2952a8..6bebb9d 100644 --- a/src/sketches/primitives/square.rs +++ b/src/sketches/primitives/square.rs @@ -12,10 +12,11 @@ impl Square { /// # Example /// ```rust /// use anvil::{Square, IntoLength, Sketch, point}; + /// use approx::assert_relative_eq; /// /// let Sketch = Square::from_size(1.m()); /// assert_eq!(Sketch.center(), Ok(point!(0, 0))); - /// assert!((Sketch.area() - 1.).abs() < 1e-5); + /// assert_relative_eq!(Sketch.area(), 1.); /// ``` pub fn from_size(size: Length) -> Sketch { Rectangle::from_dim(size, size) diff --git a/src/sketches/sketch.rs b/src/sketches/sketch.rs index 523eb7b..cebf95f 100644 --- a/src/sketches/sketch.rs +++ b/src/sketches/sketch.rs @@ -32,9 +32,10 @@ impl Sketch { /// /// ```rust /// use anvil::{Rectangle, IntoLength}; + /// use approx::assert_relative_eq; /// /// let sketch = Rectangle::from_dim(2.m(), 3.m()); - /// assert!((sketch.area() - 6.).abs() < 1e-9) + /// assert_relative_eq!(sketch.area(), 6.) /// ``` pub fn area(&self) -> f64 { match self.to_occt(Plane::xy()) { From 46f6426131e9db679e3dfe6b10e24828da0ec624 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sat, 21 Jun 2025 17:52:11 +0200 Subject: [PATCH 02/10] refactor: separate Part methods into modules --- src/parts/methods/add.rs | 30 ++ src/parts/methods/center.rs | 58 +++ src/parts/methods/circular_pattern.rs | 28 ++ src/parts/methods/clone.rs | 10 + src/parts/methods/debug.rs | 11 + src/parts/methods/empty.rs | 28 ++ src/parts/methods/eq.rs | 56 +++ src/parts/methods/faces.rs | 16 + src/parts/methods/intersect.rs | 27 ++ src/parts/methods/linear_pattern.rs | 38 ++ src/parts/methods/mod.rs | 18 + src/parts/methods/move_by.rs | 25 ++ src/parts/methods/move_to.rs | 63 ++++ src/parts/methods/rotate_around.rs | 32 ++ src/parts/methods/scale.rs | 31 ++ src/parts/methods/step.rs | 29 ++ src/parts/methods/stl.rs | 64 ++++ src/parts/methods/subtract.rs | 28 ++ src/parts/methods/volume.rs | 25 ++ src/parts/mod.rs | 1 + src/parts/part.rs | 521 -------------------------- 21 files changed, 618 insertions(+), 521 deletions(-) create mode 100644 src/parts/methods/add.rs create mode 100644 src/parts/methods/center.rs create mode 100644 src/parts/methods/circular_pattern.rs create mode 100644 src/parts/methods/clone.rs create mode 100644 src/parts/methods/debug.rs create mode 100644 src/parts/methods/empty.rs create mode 100644 src/parts/methods/eq.rs create mode 100644 src/parts/methods/faces.rs create mode 100644 src/parts/methods/intersect.rs create mode 100644 src/parts/methods/linear_pattern.rs create mode 100644 src/parts/methods/mod.rs create mode 100644 src/parts/methods/move_by.rs create mode 100644 src/parts/methods/move_to.rs create mode 100644 src/parts/methods/rotate_around.rs create mode 100644 src/parts/methods/scale.rs create mode 100644 src/parts/methods/step.rs create mode 100644 src/parts/methods/stl.rs create mode 100644 src/parts/methods/subtract.rs create mode 100644 src/parts/methods/volume.rs diff --git a/src/parts/methods/add.rs b/src/parts/methods/add.rs new file mode 100644 index 0000000..72ddb15 --- /dev/null +++ b/src/parts/methods/add.rs @@ -0,0 +1,30 @@ +use opencascade_sys::ffi; + +use crate::Part; + +impl Part { + /// Merge this `Part` with another. + /// + /// ```rust + /// use anvil::{Cuboid, point, IntoLength}; + /// + /// let cuboid1 = Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 1.m())); + /// let cuboid2 = Cuboid::from_corners(point!(0.m(), 0.m(), 1.m()), point!(1.m(), 1.m(), 2.m())); + /// + /// assert_eq!( + /// cuboid1.add(&cuboid2), + /// Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 2.m())) + /// ) + /// ``` + pub fn add(&self, other: &Self) -> Self { + match (&self.inner, &other.inner) { + (Some(self_inner), Some(other_inner)) => { + let mut fuse_operation = ffi::BRepAlgoAPI_Fuse_ctor(self_inner, other_inner); + Self::from_occt(fuse_operation.pin_mut().Shape()) + } + (Some(_), None) => self.clone(), + (None, Some(_)) => other.clone(), + (None, None) => self.clone(), + } + } +} diff --git a/src/parts/methods/center.rs b/src/parts/methods/center.rs new file mode 100644 index 0000000..8fb8273 --- /dev/null +++ b/src/parts/methods/center.rs @@ -0,0 +1,58 @@ +use opencascade_sys::ffi; + +use crate::{Error, Length, Part, Point, point}; + +impl Part { + /// Return the center of mass of the `Part`. + /// + /// If the `Part` is empty, an `Err(Error::EmptyPart)` is returned. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength, point}; + /// + /// let centered_cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + /// assert_eq!(centered_cuboid.center(), Ok(point!(0, 0, 0))); + /// + /// let non_centered_cuboid = Cuboid::from_corners( + /// point!(0, 0, 0), + /// point!(2.m(), 2.m(), 2.m()) + /// ); + /// assert_eq!(non_centered_cuboid.center(), Ok(point!(1.m(), 1.m(), 1.m()))); + /// ``` + pub fn center(&self) -> Result, Error> { + match &self.inner { + Some(inner) => { + let mut gprops = ffi::GProp_GProps_ctor(); + ffi::BRepGProp_VolumeProperties(inner, gprops.pin_mut()); + let centre_of_mass = ffi::GProp_GProps_CentreOfMass(&gprops); + + Ok(point!( + Length::from_m(round(centre_of_mass.X(), 9)), + Length::from_m(round(centre_of_mass.Y(), 9)), + Length::from_m(round(centre_of_mass.Z(), 9)) + )) + } + None => Err(Error::EmptyPart), + } + } +} + +fn round(x: f64, n_digits: u8) -> f64 { + (x * f64::from(10 ^ n_digits)).round() / f64::from(10 ^ n_digits) +} +#[cfg(test)] +mod tests { + use crate::{Cuboid, IntoLength, point}; + + #[test] + fn centre_at_origin() { + let cuboid = Cuboid::from_m(1., 1., 1.); + assert_eq!(cuboid.center(), Ok(point!(0, 0, 0))) + } + + #[test] + fn centre_not_at_origin() { + let cuboid = Cuboid::from_corners(point!(0, 0, 0), point!(2.m(), 2.m(), 2.m())); + assert_eq!(cuboid.center(), Ok(point!(1.m(), 1.m(), 1.m()))) + } +} diff --git a/src/parts/methods/circular_pattern.rs b/src/parts/methods/circular_pattern.rs new file mode 100644 index 0000000..381f877 --- /dev/null +++ b/src/parts/methods/circular_pattern.rs @@ -0,0 +1,28 @@ +use crate::{Axis, IntoAngle, Part}; + +impl Part { + /// Create multiple instances of the `Part` spaced evenly around a point. + /// + /// ```rust + /// use anvil::{Axis, Cuboid, IntoAngle, IntoLength, point}; + /// + /// let cuboid = Cuboid::from_corners(point!(1.m(), 1.m(), 0.m()), point!(2.m(), 2.m(), 1.m())); + /// assert_eq!( + /// cuboid.circular_pattern(Axis::<3>::z(), 4), + /// cuboid + /// .add(&cuboid.rotate_around(Axis::<3>::z(), 90.deg())) + /// .add(&cuboid.rotate_around(Axis::<3>::z(), 180.deg())) + /// .add(&cuboid.rotate_around(Axis::<3>::z(), 270.deg())) + /// ) + /// ``` + pub fn circular_pattern(&self, around: Axis<3>, instances: u8) -> Self { + let angle_step = 360.deg() / instances as f64; + let mut new_shape = self.clone(); + let mut angle = 0.rad(); + for _ in 0..instances { + new_shape = new_shape.add(&self.rotate_around(around, angle)); + angle = angle + angle_step; + } + new_shape + } +} diff --git a/src/parts/methods/clone.rs b/src/parts/methods/clone.rs new file mode 100644 index 0000000..66643f9 --- /dev/null +++ b/src/parts/methods/clone.rs @@ -0,0 +1,10 @@ +use crate::Part; + +impl Clone for Part { + fn clone(&self) -> Self { + match &self.inner { + Some(inner) => Self::from_occt(inner), + None => Part { inner: None }, + } + } +} diff --git a/src/parts/methods/debug.rs b/src/parts/methods/debug.rs new file mode 100644 index 0000000..5141f9f --- /dev/null +++ b/src/parts/methods/debug.rs @@ -0,0 +1,11 @@ +use std::fmt::Debug; + +use crate::Part; + +impl Debug for Part { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Shape") + .field("stl", &self.stl().expect("")) + .finish() + } +} diff --git a/src/parts/methods/empty.rs b/src/parts/methods/empty.rs new file mode 100644 index 0000000..c280ea9 --- /dev/null +++ b/src/parts/methods/empty.rs @@ -0,0 +1,28 @@ +use crate::Part; + +impl Part { + /// Construct an empty `Part` which can be used for merging with other parts. + /// + /// ```rust + /// use anvil::Part; + /// + /// let part = Part::empty(); + /// assert_eq!(part.volume(), 0.); + /// ``` + pub fn empty() -> Self { + Self { inner: None } + } + + /// Return true if this `Part` is empty. + /// + /// ```rust + /// use anvil::{Cube, IntoLength, Part}; + /// + /// let cube = Cube::from_size(1.m()); + /// assert!(!cube.is_empty()); + /// assert!(cube.subtract(&cube).is_empty()); + /// ``` + pub fn is_empty(&self) -> bool { + self.volume() < 1e-9 + } +} diff --git a/src/parts/methods/eq.rs b/src/parts/methods/eq.rs new file mode 100644 index 0000000..44aaf3b --- /dev/null +++ b/src/parts/methods/eq.rs @@ -0,0 +1,56 @@ +use crate::Part; + +impl PartialEq for Part { + fn eq(&self, other: &Self) -> bool { + match (&self.inner, &other.inner) { + (Some(_), Some(_)) => { + let intersection = self.intersect(other); + + (intersection.volume() - self.volume()).abs() < intersection.volume() * 1e-7 + && (intersection.volume() - other.volume()).abs() < intersection.volume() * 1e-7 + } + (Some(_), None) => false, + (None, Some(_)) => false, + (None, None) => true, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Cuboid, IntoLength, Sphere}; + + #[test] + fn eq_both_none() { + assert_eq!(Part::empty(), Part::empty()) + } + + #[test] + fn eq_both_cuboid() { + let cuboid1 = Cuboid::from_m(1., 1., 1.); + let cuboid2 = Cuboid::from_m(1., 1., 1.); + assert_eq!(cuboid1, cuboid2) + } + + #[test] + fn neq_both_cuboid() { + let cuboid1 = Cuboid::from_m(1., 1., 1.); + let cuboid2 = Cuboid::from_m(2., 2., 2.); + assert_ne!(cuboid1, cuboid2) + } + + #[test] + fn eq_both_sphere() { + let sphere1 = Sphere::from_radius(2.m()); + let sphere2 = Sphere::from_radius(2.m()); + assert_eq!(sphere1, sphere2) + } + + #[test] + fn neq_both_sphere() { + let sphere1 = Sphere::from_radius(1.m()); + let sphere2 = Sphere::from_radius(2.m()); + assert_ne!(sphere1, sphere2) + } +} diff --git a/src/parts/methods/faces.rs b/src/parts/methods/faces.rs new file mode 100644 index 0000000..9404408 --- /dev/null +++ b/src/parts/methods/faces.rs @@ -0,0 +1,16 @@ +use crate::{FaceIterator, Part}; + +impl Part { + /// Return the faces spanned by this `Part`. + /// + /// ```rust + /// use anvil::{Cube, Cylinder, IntoLength, Sphere}; + /// + /// assert_eq!(Cube::from_size(1.m()).faces().len(), 6); + /// assert_eq!(Cylinder::from_radius(1.m(), 1.m()).faces().len(), 3); + /// assert_eq!(Sphere::from_radius(1.m()).faces().len(), 1); + /// ``` + pub fn faces(&self) -> FaceIterator { + self.into() + } +} diff --git a/src/parts/methods/intersect.rs b/src/parts/methods/intersect.rs new file mode 100644 index 0000000..85e14b7 --- /dev/null +++ b/src/parts/methods/intersect.rs @@ -0,0 +1,27 @@ +use opencascade_sys::ffi; + +use crate::Part; + +impl Part { + /// Return the `Part` that is created from the overlapping volume between this one and another. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength}; + /// + /// let cuboid1 = Cuboid::from_dim(5.m(), 5.m(), 1.m()); + /// let cuboid2 = Cuboid::from_dim(1.m(), 1.m(), 5.m()); + /// assert_eq!( + /// cuboid1.intersect(&cuboid2), + /// Cuboid::from_dim(1.m(), 1.m(), 1.m()) + /// ) + /// ``` + pub fn intersect(&self, other: &Self) -> Self { + match (&self.inner, &other.inner) { + (Some(self_inner), Some(other_inner)) => { + let mut fuse_operation = ffi::BRepAlgoAPI_Common_ctor(self_inner, other_inner); + Self::from_occt(fuse_operation.pin_mut().Shape()) + } + _ => Part { inner: None }, + } + } +} diff --git a/src/parts/methods/linear_pattern.rs b/src/parts/methods/linear_pattern.rs new file mode 100644 index 0000000..a1a20e1 --- /dev/null +++ b/src/parts/methods/linear_pattern.rs @@ -0,0 +1,38 @@ +use crate::{Axis, Length, Part, Point}; + +impl Part { + /// Create multiple instances of the `Part` spaced evenly until a point. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength, point}; + /// + /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + /// assert_eq!( + /// cuboid.linear_pattern(point!(4.m(), 0.m(), 0.m()), 5), + /// cuboid + /// .add(&cuboid.move_to(point!(1.m(), 0.m(), 0.m()))) + /// .add(&cuboid.move_to(point!(2.m(), 0.m(), 0.m()))) + /// .add(&cuboid.move_to(point!(3.m(), 0.m(), 0.m()))) + /// .add(&cuboid.move_to(point!(4.m(), 0.m(), 0.m()))) + /// ) + /// ``` + pub fn linear_pattern(&self, until: Point<3>, instances: u8) -> Self { + let start = match self.center() { + Ok(p) => p, + Err(_) => return self.clone(), + }; + let axis = match Axis::<3>::between(start, until) { + Ok(axis) => axis, + Err(_) => return self.clone(), + }; + + let len_step = (start - until).distance_to(Point::<3>::origin()) / instances as f64; + let mut new_part = self.clone(); + let mut pos = Length::zero(); + for _ in 0..instances { + pos = pos + len_step; + new_part = new_part.add(&self.move_to(axis.point_at(pos))); + } + new_part + } +} diff --git a/src/parts/methods/mod.rs b/src/parts/methods/mod.rs new file mode 100644 index 0000000..1a10c27 --- /dev/null +++ b/src/parts/methods/mod.rs @@ -0,0 +1,18 @@ +mod add; +mod center; +mod circular_pattern; +mod clone; +mod debug; +mod empty; +mod eq; +mod faces; +mod intersect; +mod linear_pattern; +mod move_by; +mod move_to; +mod rotate_around; +mod scale; +mod step; +mod stl; +mod subtract; +mod volume; diff --git a/src/parts/methods/move_by.rs b/src/parts/methods/move_by.rs new file mode 100644 index 0000000..3eb7051 --- /dev/null +++ b/src/parts/methods/move_by.rs @@ -0,0 +1,25 @@ +use crate::{Length, Part, point}; + +impl Part { + /// Return a clone of this `Part` moved by a specified amount in each axis. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength, point}; + /// + /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + /// let moved_cuboid = cuboid + /// .move_by(1.m(), 0.m(), 3.m()) + /// .move_by(0.m(), 1.m(), 0.m()); + /// assert_eq!( + /// moved_cuboid.center(), + /// Ok(point!(1.m(), 1.m(), 3.m())) + /// ) + /// ``` + pub fn move_by(&self, dx: Length, dy: Length, dz: Length) -> Self { + let center = match self.center() { + Ok(c) => c, + Err(_) => return self.clone(), + }; + self.move_to(center + point!(dx, dy, dz)) + } +} diff --git a/src/parts/methods/move_to.rs b/src/parts/methods/move_to.rs new file mode 100644 index 0000000..692cd95 --- /dev/null +++ b/src/parts/methods/move_to.rs @@ -0,0 +1,63 @@ +use opencascade_sys::ffi; + +use crate::{Part, Point}; + +impl Part { + /// Return a clone of this `Part` with the center moved to a specified point. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength, point}; + /// + /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + /// let moved_cuboid = cuboid.move_to(point!(2.m(), 2.m(), 2.m())); + /// assert_eq!(cuboid.center(), Ok(point!(0, 0, 0))); + /// assert_eq!(moved_cuboid.center(), Ok(point!(2.m(), 2.m(), 2.m()))); + /// ``` + pub fn move_to(&self, loc: Point<3>) -> Self { + match &self.inner { + Some(inner) => { + let move_vec = (loc - self.center().unwrap()).to_occt_vec(); + let mut transform = ffi::new_transform(); + transform.pin_mut().set_translation_vec(&move_vec); + let mut operation = ffi::BRepBuilderAPI_Transform_ctor(inner, &transform, false); + Self::from_occt(operation.pin_mut().Shape()) + } + None => Self { inner: None }, + } + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::{Axis, Cuboid, IntoAngle, IntoLength, point}; + + #[test] + fn move_to_deepcopied() { + let cuboid1 = Cuboid::from_m(1., 1., 1.); + let loc = point!(2.m(), 2.m(), 2.m()); + let cuboid2 = cuboid1.move_to(loc); + + assert_eq!(cuboid1.center(), Ok(Point::<3>::origin())); + assert_eq!(cuboid2.center(), Ok(loc)); + } + + #[test] + fn part_move_to_twice() { + let part = Cuboid::from_m(1., 1., 1.); + assert_eq!( + part.move_to(point!(1.m(), 1.m(), 1.m())) + .move_to(point!(-1.m(), -1.m(), -1.m())), + Cuboid::from_m(1., 1., 1.).move_to(point!(-1.m(), -1.m(), -1.m())), + ) + } + + #[test] + fn move_after_rotate_should_not_reset_rotate() { + let part = Cuboid::from_m(1., 1., 2.); + assert_eq!( + part.rotate_around(Axis::<3>::y(), 90.deg()) + .move_to(Point::<3>::origin()), + Cuboid::from_m(2., 1., 1.) + ) + } +} diff --git a/src/parts/methods/rotate_around.rs b/src/parts/methods/rotate_around.rs new file mode 100644 index 0000000..f56317b --- /dev/null +++ b/src/parts/methods/rotate_around.rs @@ -0,0 +1,32 @@ +use opencascade_sys::ffi; + +use crate::{Angle, Axis, Part}; + +impl Part { + /// Return a clone of this `Part` rotated around an `Axis::<3>`. + /// + /// For positive angles, the right-hand-rule applies for the direction of rotation. + /// + /// ```rust + /// use anvil::{Axis, Cuboid, IntoAngle, IntoLength, point}; + /// + /// let cuboid = Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 1.m())); + /// assert_eq!( + /// cuboid.rotate_around(Axis::<3>::x(), 90.deg()), + /// Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), -1.m(), 1.m())) + /// ) + /// ``` + pub fn rotate_around(&self, axis: Axis<3>, angle: Angle) -> Self { + match &self.inner { + Some(inner) => { + let mut transform = ffi::new_transform(); + transform + .pin_mut() + .SetRotation(&axis.to_occt_ax1(), angle.rad()); + let mut operation = ffi::BRepBuilderAPI_Transform_ctor(inner, &transform, false); + Self::from_occt(operation.pin_mut().Shape()) + } + None => Self { inner: None }, + } + } +} diff --git a/src/parts/methods/scale.rs b/src/parts/methods/scale.rs new file mode 100644 index 0000000..80218e7 --- /dev/null +++ b/src/parts/methods/scale.rs @@ -0,0 +1,31 @@ +use opencascade_sys::ffi; + +use crate::Part; + +impl Part { + /// Return a clone of this `Part` with the size scaled by a factor. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength}; + /// + /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + /// assert_eq!( + /// cuboid.scale(2.), + /// Cuboid::from_dim(2.m(), 2.m(), 2.m()) + /// ) + /// ``` + pub fn scale(&self, factor: f64) -> Self { + match &self.inner { + Some(inner) => { + let mut transform = ffi::new_transform(); + transform.pin_mut().SetScale( + &self.center().expect("shape is not empty").to_occt_point(), + factor, + ); + let mut operation = ffi::BRepBuilderAPI_Transform_ctor(inner, &transform, false); + Self::from_occt(operation.pin_mut().Shape()) + } + None => Self { inner: None }, + } + } +} diff --git a/src/parts/methods/step.rs b/src/parts/methods/step.rs new file mode 100644 index 0000000..1f2a903 --- /dev/null +++ b/src/parts/methods/step.rs @@ -0,0 +1,29 @@ +use std::path::Path; + +use opencascade_sys::ffi; + +use crate::{Error, Part}; + +impl Part { + /// Write the `Part` to a file in the STEP format. + pub fn write_step(&self, path: impl AsRef) -> Result<(), Error> { + match &self.scale(1000.).inner { + Some(inner) => { + let mut writer = ffi::STEPControl_Writer_ctor(); + let status = ffi::transfer_shape(writer.pin_mut(), inner); + if status != ffi::IFSelect_ReturnStatus::IFSelect_RetDone { + return Err(Error::StepWrite(path.as_ref().to_path_buf())); + } + let status = ffi::write_step( + writer.pin_mut(), + path.as_ref().to_string_lossy().to_string(), + ); + if status != ffi::IFSelect_ReturnStatus::IFSelect_RetDone { + return Err(Error::StepWrite(path.as_ref().to_path_buf())); + } + } + None => return Err(Error::EmptyPart), + } + Ok(()) + } +} diff --git a/src/parts/methods/stl.rs b/src/parts/methods/stl.rs new file mode 100644 index 0000000..c36fad1 --- /dev/null +++ b/src/parts/methods/stl.rs @@ -0,0 +1,64 @@ +use std::{ + fs, + io::{self, BufRead}, + path::Path, +}; + +use opencascade_sys::ffi; +use tempfile::NamedTempFile; + +use crate::{Error, Part}; + +impl Part { + /// Write the `Part` to a file in the STL format. + pub fn write_stl(&self, path: impl AsRef) -> Result<(), Error> { + self.write_stl_with_tolerance(path, 0.0001) + } + + /// Write the `Part` to a file in the STL format with a specified tolerance. + /// + /// Smaller tolerances lead to higher precision in rounded shapes, but also larger file size. + pub fn write_stl_with_tolerance( + &self, + path: impl AsRef, + tolerance: f64, + ) -> Result<(), Error> { + match &self.inner { + Some(inner) => { + let mut writer = ffi::StlAPI_Writer_ctor(); + let mesh = ffi::BRepMesh_IncrementalMesh_ctor(inner, tolerance); + let success = ffi::write_stl( + writer.pin_mut(), + mesh.Shape(), + path.as_ref().to_string_lossy().to_string(), + ); + if success { + Ok(()) + } else { + Err(Error::StlWrite(path.as_ref().to_path_buf())) + } + } + None => Err(Error::EmptyPart), + } + } + /// Return the STL lines that describe this `Part`. + pub fn stl(&self) -> Result, Error> { + match &self.inner { + Some(_) => { + let temp_file = NamedTempFile::new().expect("could not create tempfile"); + let path = temp_file.path(); + + self.write_stl(path)?; + + let file = fs::File::open(path).map_err(|_| Error::StlWrite(path.into()))?; + let lines = io::BufReader::new(file) + .lines() + .collect::, _>>() + .map_err(|_| Error::StlWrite(path.into()))?; + + Ok(lines) + } + None => Err(Error::EmptyPart), + } + } +} diff --git a/src/parts/methods/subtract.rs b/src/parts/methods/subtract.rs new file mode 100644 index 0000000..fcbf945 --- /dev/null +++ b/src/parts/methods/subtract.rs @@ -0,0 +1,28 @@ +use opencascade_sys::ffi; + +use crate::Part; + +impl Part { + /// Return a copy of this `Part` with the intersection of another removed. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength, point}; + /// + /// let cuboid1 = Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 2.m())); + /// let cuboid2 = Cuboid::from_corners(point!(0.m(), 0.m(), 1.m()), point!(1.m(), 1.m(), 2.m())); + /// assert_eq!( + /// cuboid1.subtract(&cuboid2), + /// Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 1.m())) + /// ); + /// ``` + pub fn subtract(&self, other: &Self) -> Self { + match (&self.inner, &other.inner) { + (Some(self_inner), Some(other_inner)) => { + let mut fuse_operation = ffi::BRepAlgoAPI_Cut_ctor(self_inner, other_inner); + Self::from_occt(fuse_operation.pin_mut().Shape()) + } + (Some(_), None) => self.clone(), + (None, _) => Part { inner: None }, + } + } +} diff --git a/src/parts/methods/volume.rs b/src/parts/methods/volume.rs new file mode 100644 index 0000000..63ccd9a --- /dev/null +++ b/src/parts/methods/volume.rs @@ -0,0 +1,25 @@ +use opencascade_sys::ffi; + +use crate::Part; + +impl Part { + /// Return the volume occupied by this `Part` in cubic meters. + /// + /// ```rust + /// use anvil::{Cuboid, IntoLength}; + /// use approx::assert_relative_eq; + /// + /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + /// assert_relative_eq!(cuboid.volume(), 1.); + /// ``` + pub fn volume(&self) -> f64 { + match &self.inner { + Some(inner) => { + let mut gprops = ffi::GProp_GProps_ctor(); + ffi::BRepGProp_VolumeProperties(inner, gprops.pin_mut()); + gprops.Mass() + } + None => 0., + } + } +} diff --git a/src/parts/mod.rs b/src/parts/mod.rs index 53d2c19..2e3dba0 100644 --- a/src/parts/mod.rs +++ b/src/parts/mod.rs @@ -1,3 +1,4 @@ +mod methods; mod part; pub mod primitives; diff --git a/src/parts/part.rs b/src/parts/part.rs index 2fa4c54..aa9251d 100644 --- a/src/parts/part.rs +++ b/src/parts/part.rs @@ -1,534 +1,13 @@ -use std::{ - fmt::Debug, - fs, - io::{self, BufRead}, - path::Path, -}; - use cxx::UniquePtr; use opencascade_sys::ffi; -use tempfile::NamedTempFile; - -use crate::{Angle, Axis, Error, FaceIterator, IntoAngle, Length, Point, point}; /// A 3D object in space. pub struct Part { pub(crate) inner: Option>, } impl Part { - /// Construct an empty `Part` which can be used for merging with other parts. - /// - /// ```rust - /// use anvil::Part; - /// - /// let part = Part::empty(); - /// assert_eq!(part.volume(), 0.); - /// ``` - pub fn empty() -> Self { - Self { inner: None } - } - - /// Return true if this `Part` is empty. - /// - /// ```rust - /// use anvil::{Cube, IntoLength, Part}; - /// - /// let cube = Cube::from_size(1.m()); - /// assert!(!cube.is_empty()); - /// assert!(cube.subtract(&cube).is_empty()); - /// ``` - pub fn is_empty(&self) -> bool { - self.volume() < 1e-9 - } - - /// Merge this `Part` with another. - /// - /// ```rust - /// use anvil::{Cuboid, point, IntoLength}; - /// - /// let cuboid1 = Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 1.m())); - /// let cuboid2 = Cuboid::from_corners(point!(0.m(), 0.m(), 1.m()), point!(1.m(), 1.m(), 2.m())); - /// - /// assert_eq!( - /// cuboid1.add(&cuboid2), - /// Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 2.m())) - /// ) - /// ``` - pub fn add(&self, other: &Self) -> Self { - match (&self.inner, &other.inner) { - (Some(self_inner), Some(other_inner)) => { - let mut fuse_operation = ffi::BRepAlgoAPI_Fuse_ctor(self_inner, other_inner); - Self::from_occt(fuse_operation.pin_mut().Shape()) - } - (Some(_), None) => self.clone(), - (None, Some(_)) => other.clone(), - (None, None) => self.clone(), - } - } - - /// Create multiple instances of the `Part` spaced evenly around a point. - /// - /// ```rust - /// use anvil::{Axis, Cuboid, IntoAngle, IntoLength, point}; - /// - /// let cuboid = Cuboid::from_corners(point!(1.m(), 1.m(), 0.m()), point!(2.m(), 2.m(), 1.m())); - /// assert_eq!( - /// cuboid.circular_pattern(Axis::<3>::z(), 4), - /// cuboid - /// .add(&cuboid.rotate_around(Axis::<3>::z(), 90.deg())) - /// .add(&cuboid.rotate_around(Axis::<3>::z(), 180.deg())) - /// .add(&cuboid.rotate_around(Axis::<3>::z(), 270.deg())) - /// ) - /// ``` - pub fn circular_pattern(&self, around: Axis<3>, instances: u8) -> Self { - let angle_step = 360.deg() / instances as f64; - let mut new_shape = self.clone(); - let mut angle = 0.rad(); - for _ in 0..instances { - new_shape = new_shape.add(&self.rotate_around(around, angle)); - angle = angle + angle_step; - } - new_shape - } - /// Return the faces spanned by this `Part`. - /// - /// ```rust - /// use anvil::{Cube, Cylinder, IntoLength, Sphere}; - /// - /// assert_eq!(Cube::from_size(1.m()).faces().len(), 6); - /// assert_eq!(Cylinder::from_radius(1.m(), 1.m()).faces().len(), 3); - /// assert_eq!(Sphere::from_radius(1.m()).faces().len(), 1); - /// ``` - pub fn faces(&self) -> FaceIterator { - self.into() - } - /// Return the `Part` that is created from the overlapping volume between this one and another. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength}; - /// - /// let cuboid1 = Cuboid::from_dim(5.m(), 5.m(), 1.m()); - /// let cuboid2 = Cuboid::from_dim(1.m(), 1.m(), 5.m()); - /// assert_eq!( - /// cuboid1.intersect(&cuboid2), - /// Cuboid::from_dim(1.m(), 1.m(), 1.m()) - /// ) - /// ``` - pub fn intersect(&self, other: &Self) -> Self { - match (&self.inner, &other.inner) { - (Some(self_inner), Some(other_inner)) => { - let mut fuse_operation = ffi::BRepAlgoAPI_Common_ctor(self_inner, other_inner); - Self::from_occt(fuse_operation.pin_mut().Shape()) - } - _ => Part { inner: None }, - } - } - - /// Create multiple instances of the `Part` spaced evenly until a point. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength, point}; - /// - /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// assert_eq!( - /// cuboid.linear_pattern(point!(4.m(), 0.m(), 0.m()), 5), - /// cuboid - /// .add(&cuboid.move_to(point!(1.m(), 0.m(), 0.m()))) - /// .add(&cuboid.move_to(point!(2.m(), 0.m(), 0.m()))) - /// .add(&cuboid.move_to(point!(3.m(), 0.m(), 0.m()))) - /// .add(&cuboid.move_to(point!(4.m(), 0.m(), 0.m()))) - /// ) - /// ``` - pub fn linear_pattern(&self, until: Point<3>, instances: u8) -> Self { - let start = match self.center() { - Ok(p) => p, - Err(_) => return self.clone(), - }; - let axis = match Axis::<3>::between(start, until) { - Ok(axis) => axis, - Err(_) => return self.clone(), - }; - - let len_step = (start - until).distance_to(Point::<3>::origin()) / instances as f64; - let mut new_part = self.clone(); - let mut pos = Length::zero(); - for _ in 0..instances { - pos = pos + len_step; - new_part = new_part.add(&self.move_to(axis.point_at(pos))); - } - new_part - } - /// Return a clone of this `Part` moved by a specified amount in each axis. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength, point}; - /// - /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// let moved_cuboid = cuboid - /// .move_by(1.m(), 0.m(), 3.m()) - /// .move_by(0.m(), 1.m(), 0.m()); - /// assert_eq!( - /// moved_cuboid.center(), - /// Ok(point!(1.m(), 1.m(), 3.m())) - /// ) - /// ``` - pub fn move_by(&self, dx: Length, dy: Length, dz: Length) -> Self { - let center = match self.center() { - Ok(c) => c, - Err(_) => return self.clone(), - }; - self.move_to(center + point!(dx, dy, dz)) - } - /// Return a clone of this `Part` with the center moved to a specified point. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength, point}; - /// - /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// let moved_cuboid = cuboid.move_to(point!(2.m(), 2.m(), 2.m())); - /// assert_eq!(cuboid.center(), Ok(point!(0, 0, 0))); - /// assert_eq!(moved_cuboid.center(), Ok(point!(2.m(), 2.m(), 2.m()))); - /// ``` - pub fn move_to(&self, loc: Point<3>) -> Self { - match &self.inner { - Some(inner) => { - let move_vec = (loc - self.center().unwrap()).to_occt_vec(); - let mut transform = ffi::new_transform(); - transform.pin_mut().set_translation_vec(&move_vec); - let mut operation = ffi::BRepBuilderAPI_Transform_ctor(inner, &transform, false); - Self::from_occt(operation.pin_mut().Shape()) - } - None => Self { inner: None }, - } - } - /// Return a clone of this `Part` rotated around an `Axis::<3>`. - /// - /// For positive angles, the right-hand-rule applies for the direction of rotation. - /// - /// ```rust - /// use anvil::{Axis, Cuboid, IntoAngle, IntoLength, point}; - /// - /// let cuboid = Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 1.m())); - /// assert_eq!( - /// cuboid.rotate_around(Axis::<3>::x(), 90.deg()), - /// Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), -1.m(), 1.m())) - /// ) - /// ``` - pub fn rotate_around(&self, axis: Axis<3>, angle: Angle) -> Self { - match &self.inner { - Some(inner) => { - let mut transform = ffi::new_transform(); - transform - .pin_mut() - .SetRotation(&axis.to_occt_ax1(), angle.rad()); - let mut operation = ffi::BRepBuilderAPI_Transform_ctor(inner, &transform, false); - Self::from_occt(operation.pin_mut().Shape()) - } - None => Self { inner: None }, - } - } - /// Return a clone of this `Part` with the size scaled by a factor. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength}; - /// - /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// assert_eq!( - /// cuboid.scale(2.), - /// Cuboid::from_dim(2.m(), 2.m(), 2.m()) - /// ) - /// ``` - pub fn scale(&self, factor: f64) -> Self { - match &self.inner { - Some(inner) => { - let mut transform = ffi::new_transform(); - transform.pin_mut().SetScale( - &self.center().expect("shape is not empty").to_occt_point(), - factor, - ); - let mut operation = ffi::BRepBuilderAPI_Transform_ctor(inner, &transform, false); - Self::from_occt(operation.pin_mut().Shape()) - } - None => Self { inner: None }, - } - } - /// Return a copy of this `Part` with the intersection of another removed. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength, point}; - /// - /// let cuboid1 = Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 2.m())); - /// let cuboid2 = Cuboid::from_corners(point!(0.m(), 0.m(), 1.m()), point!(1.m(), 1.m(), 2.m())); - /// assert_eq!( - /// cuboid1.subtract(&cuboid2), - /// Cuboid::from_corners(point!(0, 0, 0), point!(1.m(), 1.m(), 1.m())) - /// ); - /// ``` - pub fn subtract(&self, other: &Self) -> Self { - match (&self.inner, &other.inner) { - (Some(self_inner), Some(other_inner)) => { - let mut fuse_operation = ffi::BRepAlgoAPI_Cut_ctor(self_inner, other_inner); - Self::from_occt(fuse_operation.pin_mut().Shape()) - } - (Some(_), None) => self.clone(), - (None, _) => Part { inner: None }, - } - } - - /// Return the volume occupied by this `Part` in cubic meters. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength}; - /// use approx::assert_relative_eq; - /// - /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// assert_relative_eq!(cuboid.volume(), 1.); - /// ``` - pub fn volume(&self) -> f64 { - match &self.inner { - Some(inner) => { - let mut gprops = ffi::GProp_GProps_ctor(); - ffi::BRepGProp_VolumeProperties(inner, gprops.pin_mut()); - gprops.Mass() - } - None => 0., - } - } - /// Return the center of mass of the `Part`. - /// - /// If the `Part` is empty, an `Err(Error::EmptyPart)` is returned. - /// - /// ```rust - /// use anvil::{Cuboid, IntoLength, point}; - /// - /// let centered_cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// assert_eq!(centered_cuboid.center(), Ok(point!(0, 0, 0))); - /// - /// let non_centered_cuboid = Cuboid::from_corners( - /// point!(0, 0, 0), - /// point!(2.m(), 2.m(), 2.m()) - /// ); - /// assert_eq!(non_centered_cuboid.center(), Ok(point!(1.m(), 1.m(), 1.m()))); - /// ``` - pub fn center(&self) -> Result, Error> { - match &self.inner { - Some(inner) => { - let mut gprops = ffi::GProp_GProps_ctor(); - ffi::BRepGProp_VolumeProperties(inner, gprops.pin_mut()); - let centre_of_mass = ffi::GProp_GProps_CentreOfMass(&gprops); - - Ok(point!( - Length::from_m(round(centre_of_mass.X(), 9)), - Length::from_m(round(centre_of_mass.Y(), 9)), - Length::from_m(round(centre_of_mass.Z(), 9)) - )) - } - None => Err(Error::EmptyPart), - } - } - - /// Write the `Part` to a file in the STEP format. - pub fn write_step(&self, path: impl AsRef) -> Result<(), Error> { - match &self.scale(1000.).inner { - Some(inner) => { - let mut writer = ffi::STEPControl_Writer_ctor(); - let status = ffi::transfer_shape(writer.pin_mut(), inner); - if status != ffi::IFSelect_ReturnStatus::IFSelect_RetDone { - return Err(Error::StepWrite(path.as_ref().to_path_buf())); - } - let status = ffi::write_step( - writer.pin_mut(), - path.as_ref().to_string_lossy().to_string(), - ); - if status != ffi::IFSelect_ReturnStatus::IFSelect_RetDone { - return Err(Error::StepWrite(path.as_ref().to_path_buf())); - } - } - None => return Err(Error::EmptyPart), - } - Ok(()) - } - - /// Write the `Part` to a file in the STL format. - pub fn write_stl(&self, path: impl AsRef) -> Result<(), Error> { - self.write_stl_with_tolerance(path, 0.0001) - } - - /// Write the `Part` to a file in the STL format with a specified tolerance. - /// - /// Smaller tolerances lead to higher precision in rounded shapes, but also larger file size. - pub fn write_stl_with_tolerance( - &self, - path: impl AsRef, - tolerance: f64, - ) -> Result<(), Error> { - match &self.inner { - Some(inner) => { - let mut writer = ffi::StlAPI_Writer_ctor(); - let mesh = ffi::BRepMesh_IncrementalMesh_ctor(inner, tolerance); - let success = ffi::write_stl( - writer.pin_mut(), - mesh.Shape(), - path.as_ref().to_string_lossy().to_string(), - ); - if success { - Ok(()) - } else { - Err(Error::StlWrite(path.as_ref().to_path_buf())) - } - } - None => Err(Error::EmptyPart), - } - } - /// Return the STL lines that describe this `Part`. - pub fn stl(&self) -> Result, Error> { - match &self.inner { - Some(_) => { - let temp_file = NamedTempFile::new().expect("could not create tempfile"); - let path = temp_file.path(); - - self.write_stl(path)?; - - let file = fs::File::open(path).map_err(|_| Error::StlWrite(path.into()))?; - let lines = io::BufReader::new(file) - .lines() - .collect::, _>>() - .map_err(|_| Error::StlWrite(path.into()))?; - - Ok(lines) - } - None => Err(Error::EmptyPart), - } - } - pub(crate) fn from_occt(part: &ffi::TopoDS_Shape) -> Self { let inner = ffi::TopoDS_Shape_to_owned(part); Self { inner: Some(inner) } } } - -impl Clone for Part { - fn clone(&self) -> Self { - match &self.inner { - Some(inner) => Self::from_occt(inner), - None => Part { inner: None }, - } - } -} - -impl PartialEq for Part { - fn eq(&self, other: &Self) -> bool { - match (&self.inner, &other.inner) { - (Some(_), Some(_)) => { - let intersection = self.intersect(other); - - (intersection.volume() - self.volume()).abs() < intersection.volume() * 1e-7 - && (intersection.volume() - other.volume()).abs() < intersection.volume() * 1e-7 - } - (Some(_), None) => false, - (None, Some(_)) => false, - (None, None) => true, - } - } -} - -impl Debug for Part { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Shape") - .field("stl", &self.stl().expect("")) - .finish() - } -} - -fn round(x: f64, n_digits: u8) -> f64 { - (x * f64::from(10 ^ n_digits)).round() / f64::from(10 ^ n_digits) -} - -#[cfg(test)] -mod tests { - use approx::assert_relative_eq; - - use super::*; - use crate::{Cuboid, IntoLength, Sphere, point}; - - #[test] - fn eq_both_none() { - assert_eq!(Part::empty(), Part::empty()) - } - - #[test] - fn eq_both_cuboid() { - let cuboid1 = Cuboid::from_m(1., 1., 1.); - let cuboid2 = Cuboid::from_m(1., 1., 1.); - assert_eq!(cuboid1, cuboid2) - } - - #[test] - fn neq_both_cuboid() { - let cuboid1 = Cuboid::from_m(1., 1., 1.); - let cuboid2 = Cuboid::from_m(2., 2., 2.); - assert_ne!(cuboid1, cuboid2) - } - - #[test] - fn eq_both_sphere() { - let sphere1 = Sphere::from_radius(2.m()); - let sphere2 = Sphere::from_radius(2.m()); - assert_eq!(sphere1, sphere2) - } - - #[test] - fn neq_both_sphere() { - let sphere1 = Sphere::from_radius(1.m()); - let sphere2 = Sphere::from_radius(2.m()); - assert_ne!(sphere1, sphere2) - } - - #[test] - fn move_to_deepcopied() { - let cuboid1 = Cuboid::from_m(1., 1., 1.); - let loc = point!(2.m(), 2.m(), 2.m()); - let cuboid2 = cuboid1.move_to(loc); - - assert_eq!(cuboid1.center(), Ok(Point::<3>::origin())); - assert_eq!(cuboid2.center(), Ok(loc)); - } - - #[test] - fn volume() { - let cuboid = Cuboid::from_m(1., 1., 1.); - assert_relative_eq!(cuboid.volume(), 1.) - } - - #[test] - fn centre_of_mass_at_origin() { - let cuboid = Cuboid::from_m(1., 1., 1.); - assert_eq!(cuboid.center(), Ok(point!(0, 0, 0))) - } - - #[test] - fn centre_of_mass_not_at_origin() { - let cuboid = Cuboid::from_corners(point!(0, 0, 0), point!(2.m(), 2.m(), 2.m())); - assert_eq!(cuboid.center(), Ok(point!(1.m(), 1.m(), 1.m()))) - } - - #[test] - fn part_move_to_twice() { - let part = Cuboid::from_m(1., 1., 1.); - assert_eq!( - part.move_to(point!(1.m(), 1.m(), 1.m())) - .move_to(point!(-1.m(), -1.m(), -1.m())), - Cuboid::from_m(1., 1., 1.).move_to(point!(-1.m(), -1.m(), -1.m())), - ) - } - - #[test] - fn move_after_rotate_should_not_reset_rotate() { - let part = Cuboid::from_m(1., 1., 2.); - assert_eq!( - part.rotate_around(Axis::<3>::y(), 90.deg()) - .move_to(Point::<3>::origin()), - Cuboid::from_m(2., 1., 1.) - ) - } -} From c8ba1dc4ee4c1d2fbe472e4f4eeee6e9c833c4aa Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 26 Jun 2025 12:55:40 +0200 Subject: [PATCH 03/10] refactor: replace custom Length with uom --- Cargo.lock | 17 ++ Cargo.toml | 1 + src/core/edge.rs | 28 ++- src/core/length.rs | 338 +++------------------------ src/core/path.rs | 5 +- src/core/point.rs | 41 +++- src/meshes/render_mesh.rs | 19 +- src/parts/methods/center.rs | 7 +- src/parts/methods/linear_pattern.rs | 6 +- src/parts/primitives/cuboid.rs | 27 ++- src/parts/primitives/cylinder.rs | 9 +- src/parts/primitives/sphere.rs | 3 +- src/sketches/primitives/circle.rs | 12 +- src/sketches/primitives/rectangle.rs | 6 +- src/sketches/sketch.rs | 11 +- 15 files changed, 167 insertions(+), 363 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e3bdea..538ba8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "opencascade-sys", "tempdir", "tempfile", + "uom", ] [[package]] @@ -433,6 +434,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -445,6 +452,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" +[[package]] +name = "uom" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd5cfe7d84f6774726717f358a37f5bca8fca273bed4de40604ad129d1107b49" +dependencies = [ + "num-traits", + "typenum", +] + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 464e6d0..e3bc830 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ cxx = "1" iter_fixed = "0.4.0" opencascade-sys = { git = "https://github.com/bschwind/opencascade-rs", rev = "c30da56647c2a60393984458439180886ecaf951" } tempfile = "3.19.1" +uom = "0.37.0" [dev-dependencies] tempdir = "0.3.7" diff --git a/src/core/edge.rs b/src/core/edge.rs index 00d6b18..93079f9 100644 --- a/src/core/edge.rs +++ b/src/core/edge.rs @@ -2,6 +2,7 @@ use core::f64; use cxx::UniquePtr; use opencascade_sys::ffi; +use uom::si::length::meter; use crate::{Angle, Axis, Dir, Error, Length, Plane, Point}; @@ -19,6 +20,7 @@ impl Edge { /// /// ```rust /// use anvil::{Edge, IntoLength, point}; + /// use uom::si::length::meter; /// /// let edge = Edge::Line(point!(1.m(), 1.m()), point!(2.m(), 2.m())); /// assert_eq!(edge.start(), point!(1.m(), 1.m())) @@ -33,6 +35,7 @@ impl Edge { /// /// ```rust /// use anvil::{Edge, IntoLength, point}; + /// use uom::si::length::meter; /// /// let edge = Edge::Line(point!(1.m(), 1.m()), point!(2.m(), 2.m())); /// assert_eq!(edge.end(), point!(2.m(), 2.m())) @@ -49,6 +52,7 @@ impl Edge { /// ```rust /// use core::f64; /// use anvil::{Edge, IntoLength, point}; + /// use uom::si::length::meter; /// /// let line = Edge::Line(point!(1.m(), 0.m()), point!(1.m(), 2.m())); /// assert_eq!(line.len(), 2.m()); @@ -60,9 +64,9 @@ impl Edge { match self { Self::Arc(start, mid, end) => { // Works for now but needs to be refactored in the future - let (x1, y1) = (start.x().m(), start.y().m()); - let (x2, y2) = (mid.x().m(), mid.y().m()); - let (x3, y3) = (end.x().m(), end.y().m()); + let (x1, y1) = (start.x().get::(), start.y().get::()); + let (x2, y2) = (mid.x().get::(), mid.y().get::()); + let (x3, y3) = (end.x().get::(), end.y().get::()); let b = (x1.powi(2) + y1.powi(2)) * (y3 - y2) + (x2.powi(2) + y2.powi(2)) * (y1 - y3) @@ -73,7 +77,7 @@ impl Edge { let denom = 2.0 * (x1 * (y2 - y3) + x2 * (y3 - y1) + x3 * (y1 - y2)); if denom.abs() < f64::EPSILON { - return Length::zero(); + return Length::new::(0.); } let cx = -b / denom; let cy = -c / denom; @@ -96,11 +100,13 @@ impl Edge { angle = f64::consts::TAU - angle; } - Length::from_m(r * angle) + Length::new::(r * angle) } Self::Line(start, end) => { let diff = *start - *end; - Length::from_m(f64::sqrt(diff.x().m().powi(2) + diff.y().m().powi(2))) + Length::new::(f64::sqrt( + diff.x().get::().powi(2) + diff.y().get::().powi(2), + )) } } } @@ -109,6 +115,7 @@ impl Edge { /// /// ```rust /// use anvil::{Edge, IntoLength, dir, point}; + /// use uom::si::length::meter; /// /// let line = Edge::Line(point!(0, 0), point!(1.m(), 2.m())); /// assert_eq!(line.end_direction(), Ok(dir!(1, 2))); @@ -131,14 +138,15 @@ impl Edge { Ok(Dir::from(end_angle + Angle::from_deg(90.))) } } - Self::Line(start, end) => { - Dir::<2>::try_from([(*end - *start).x().m(), (*end - *start).y().m()]) - } + Self::Line(start, end) => Dir::<2>::try_from([ + (*end - *start).x().get::(), + (*end - *start).y().get::(), + ]), } } pub(crate) fn to_occt(&self, plane: Plane) -> Option> { - if self.len() == Length::zero() { + if self.len() == Length::new::(0.) { return None; } match self { diff --git a/src/core/length.rs b/src/core/length.rs index f650d0b..f36a8e9 100644 --- a/src/core/length.rs +++ b/src/core/length.rs @@ -1,8 +1,6 @@ -use approx::{AbsDiffEq, RelativeEq}; -use std::{ - fmt::Debug, - ops::{Add, Div, Mul, Neg, Sub}, -}; +use std::ops::Mul; + +use uom::si::length::{centimeter, decimeter, foot, inch, meter, millimeter, yard}; use crate::{Dir, IntoF64, Point}; @@ -10,256 +8,7 @@ use crate::{Dir, IntoF64, Point}; /// /// Length exists to remove ambiguity about distance units, which are not supported by default by /// major CAD kernels. -/// -/// ```rust -/// use anvil::Length; -/// use approx::assert_relative_eq; -/// -/// // You can construct a `Length` using the Length::from_[unit] methods like -/// let meters_length = Length::from_m(1.2); -/// let centimeters_length = Length::from_cm(4.5); -/// let inches_length = Length::from_in(12.); -/// -/// // To get back a `Length` value in a specific unit, call the Length.[unit] method -/// assert_relative_eq!(meters_length.cm(), 120.); -/// assert_relative_eq!(centimeters_length.m(), 0.045); -/// assert_relative_eq!(inches_length.ft(), 1.); -/// -/// // Length construction can be simplified using the `IntoLength` trait. -/// use anvil::IntoLength; -/// -/// assert_eq!(1.2.m(), Length::from_m(1.2)); -/// assert_eq!(4.5.cm(), Length::from_cm(4.5)); -/// assert_eq!(12.in_(), Length::from_in(12.)); -/// ``` -#[derive(PartialEq, Copy, Clone, PartialOrd)] -pub struct Length { - meters: f64, -} -impl Length { - /// Construct a `Length` with a value of zero. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::zero(); - /// assert_eq!(len.m(), 0.); - /// ``` - pub const fn zero() -> Self { - Self { meters: 0. } - } - /// Construct a `Length` from a value of unit meters. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::from_m(3.2); - /// assert_eq!(len.mm(), 3200.); - /// ``` - pub const fn from_m(value: f64) -> Self { - Self { meters: value } - } - /// Return the value of this `Length` in millimeters. - pub const fn m(&self) -> f64 { - self.meters - } - /// Construct a `Length` from a value of unit yards. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::from_yd(1.); - /// assert_eq!(len.m(), 0.9144); - /// ``` - pub const fn from_yd(value: f64) -> Self { - Self::from_m(value * 0.9144) - } - /// Return the value of this `Length` in yards. - pub const fn yd(&self) -> f64 { - self.m() / 0.9144 - } - /// Construct a `Length` from a value of unit feet. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::from_ft(1.); - /// assert_eq!(len.cm(), 30.48); - /// ``` - pub const fn from_ft(value: f64) -> Self { - Self::from_m(value * 0.3048) - } - /// Return the value of this `Length` in feet. - pub const fn ft(&self) -> f64 { - self.m() / 0.3048 - } - /// Construct a `Length` from a value of unit decimeters. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::from_dm(5.1); - /// assert_eq!(len.mm(), 510.); - /// ``` - pub const fn from_dm(value: f64) -> Self { - Self::from_m(value / 10.) - } - /// Return the value of this `Length` in decimeters. - pub const fn dm(&self) -> f64 { - self.m() * 10. - } - /// Construct a `Length` from a value of unit inches. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::from_in(1.); - /// assert_eq!(len.cm(), 2.54); - /// ``` - pub const fn from_in(value: f64) -> Self { - Self::from_m(value * 0.0254) - } - /// Return the value of this `Length` in inches. - /// - /// This method breaks the pattern with the trailing underscore, because `in` is a reserved - /// keyword in Rust. - pub const fn in_(&self) -> f64 { - self.m() / 0.0254 - } - /// Construct a `Length` from a value of unit centimeters. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::from_cm(5.1); - /// assert_eq!(len.mm(), 51.); - /// ``` - pub const fn from_cm(value: f64) -> Self { - Self::from_m(value / 100.) - } - /// Return the value of this `Length` in centimeters. - pub const fn cm(&self) -> f64 { - self.m() * 100. - } - /// Construct a `Length` from a value of unit millimeters. - /// - /// # Example - /// ```rust - /// use anvil::Length; - /// - /// let len = Length::from_mm(5.4); - /// assert_eq!(len.m(), 0.0054); - /// ``` - pub const fn from_mm(value: f64) -> Self { - Self::from_m(value / 1000.) - } - /// Return the value of this `Length` in millimeters. - pub const fn mm(&self) -> f64 { - self.m() * 1000. - } - - /// Return the absolute value of this `Length`. - /// - /// ```rust - /// use anvil::IntoLength; - /// - /// assert_eq!((-5).m().abs(), 5.m()); - /// assert_eq!(5.m().abs(), 5.m()); - /// ``` - pub const fn abs(&self) -> Self { - Self { - meters: self.meters.abs(), - } - } - /// Return the smaller of two lengths. - /// - /// # Example - /// ```rust - /// use anvil::IntoLength; - /// - /// let len1 = 1.m(); - /// let len2 = 2.m(); - /// assert_eq!(len1.min(&len2), len1); - /// assert_eq!(len2.min(&len1), len1); - /// ``` - pub const fn min(&self, other: &Self) -> Self { - Length::from_m(self.m().min(other.m())) - } - /// Return the larger of two lengths. - /// - /// # Example - /// ```rust - /// use anvil::IntoLength; - /// - /// let len1 = 1.m(); - /// let len2 = 2.m(); - /// assert_eq!(len1.max(&len2), len2); - /// assert_eq!(len2.max(&len1), len2); - /// ``` - pub const fn max(&self, other: &Self) -> Self { - Length::from_m(self.m().max(other.m())) - } -} -impl Debug for Length { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(format!("{}m", self.m()).as_str()) - } -} - -impl Add for Length { - type Output = Length; - fn add(self, other: Length) -> Length { - Length::from_m(self.m() + other.m()) - } -} - -impl Sub for Length { - type Output = Length; - fn sub(self, other: Length) -> Length { - Length::from_m(self.m() - other.m()) - } -} - -impl Mul for Length { - type Output = Length; - fn mul(self, other: f64) -> Length { - Length::from_m(self.m() * other) - } -} - -impl Mul for f64 { - type Output = Length; - fn mul(self, other: Length) -> Length { - other * self - } -} - -impl Div for Length { - type Output = Length; - fn div(self, other: f64) -> Length { - Length::from_m(self.m() / other) - } -} - -impl Div for Length { - type Output = f64; - /// Divide this `Length` by another `Length`. - /// ```rust - /// use anvil::IntoLength; - /// - /// assert_eq!(6.m() / 2.m(), 3.) - /// ``` - fn div(self, other: Length) -> f64 { - self.meters / other.meters - } -} +pub type Length = uom::si::f64::Length; impl Mul> for Length { type Output = Point; @@ -285,41 +34,10 @@ impl Mul> for Length { } } -impl Neg for Length { - type Output = Length; - fn neg(self) -> Self::Output { - self * -1. - } -} - -impl AbsDiffEq for Length { - type Epsilon = f64; - fn default_epsilon() -> Self::Epsilon { - f64::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - f64::abs_diff_eq(&self.meters, &other.meters, epsilon) - } -} - -impl RelativeEq for Length { - fn default_max_relative() -> Self::Epsilon { - f64::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - f64::relative_eq(&self.meters, &other.meters, epsilon, max_relative) - } -} - /// Return true if any IntoLength in the input array is zero. pub fn is_zero(lengths: &[Length]) -> bool { for length in lengths { - if length.m() == 0. { + if length.get::() == 0. { return true; } } @@ -330,80 +48,88 @@ pub fn is_zero(lengths: &[Length]) -> bool { /// /// ```rust /// use anvil::{IntoLength, Length}; +/// use uom::si::length::{foot, meter}; /// -/// assert_eq!(5.m(), Length::from_m(5.)); -/// assert_eq!(5.123.ft(), Length::from_ft(5.123)); +/// assert_eq!(5.m(), Length::new::(5.)); +/// assert_eq!(5.123.ft(), Length::new::(5.123)); /// ``` pub trait IntoLength: IntoF64 { /// Convert this number into a `Length` in yard. /// /// ```rust /// use anvil::{IntoLength, Length}; + /// use uom::si::length::yard; /// - /// assert_eq!(5.yd(), Length::from_yd(5.)); + /// assert_eq!(5.yd(), Length::new::(5.)); /// ``` fn yd(&self) -> Length { - Length::from_yd(self.to_f64()) + Length::new::(self.to_f64()) } /// Convert this number into a `Length` in meters. /// /// ```rust /// use anvil::{IntoLength, Length}; + /// use uom::si::length::meter; /// - /// assert_eq!(5.m(), Length::from_m(5.)); + /// assert_eq!(5.m(), Length::new::(5.)); /// ``` fn m(&self) -> Length { - Length::from_m(self.to_f64()) + Length::new::(self.to_f64()) } /// Convert this number into a `Length` in feet. /// /// ```rust /// use anvil::{IntoLength, Length}; + /// use uom::si::length::foot; /// - /// assert_eq!(5.ft(), Length::from_ft(5.)); + /// assert_eq!(5.ft(), Length::new::(5.)); /// ``` fn ft(&self) -> Length { - Length::from_ft(self.to_f64()) + Length::new::(self.to_f64()) } /// Convert this number into a `Length` in decimeters. /// /// ```rust /// use anvil::{IntoLength, Length}; + /// use uom::si::length::decimeter; /// - /// assert_eq!(5.dm(), Length::from_dm(5.)); + /// assert_eq!(5.dm(), Length::new::(5.)); /// ``` fn dm(&self) -> Length { - Length::from_dm(self.to_f64()) + Length::new::(self.to_f64()) } /// Convert this number into a `Length` in inches. /// /// ```rust /// use anvil::{IntoLength, Length}; + /// use uom::si::length::inch; /// - /// assert_eq!(5.in_(), Length::from_in(5.)); + /// assert_eq!(5.in_(), Length::new::(5.)); /// ``` fn in_(&self) -> Length { - Length::from_in(self.to_f64()) + Length::new::(self.to_f64()) } /// Convert this number into a `Length` in centimeters. /// /// ```rust /// use anvil::{IntoLength, Length}; + /// use uom::si::length::centimeter; /// - /// assert_eq!(5.cm(), Length::from_cm(5.)); + /// assert_eq!(5.cm(), Length::new::(5.)); /// ``` fn cm(&self) -> Length { - Length::from_cm(self.to_f64()) + Length::new::(self.to_f64()) } /// Convert this number into a `Length` in millimeters. /// /// ```rust /// use anvil::{IntoLength, Length}; + /// use uom::si::length::millimeter; /// - /// assert_eq!(5.mm(), Length::from_mm(5.)); + /// assert_eq!(5.mm(), Length::new::(5.)); /// ``` fn mm(&self) -> Length { - Length::from_mm(self.to_f64()) + Length::new::(self.to_f64()) } } @@ -438,12 +164,12 @@ mod tests { #[test] fn multiply_with_f64() { - assert_eq!(5.m() * 4., Length::from_m(20.)); - assert_eq!(4. * 5.m(), Length::from_m(20.)); + assert_eq!(5.m() * 4., 20.m()); + assert_eq!(4. * 5.m(), 20.m()); } #[test] fn divide_with_f64() { - assert_eq!(Length::from_m(6.) / 2., 3.m()); + assert_eq!(6.m() / 2., 3.m()); } } diff --git a/src/core/path.rs b/src/core/path.rs index c086f0a..c30acaa 100644 --- a/src/core/path.rs +++ b/src/core/path.rs @@ -1,4 +1,5 @@ use crate::{Angle, Axis, Dir, Edge, Length, Point, Sketch}; +use uom::si::length::meter; /// A continuous series of edges (i.e. lines, arcs, ...). #[derive(Debug, PartialEq, Clone)] @@ -75,13 +76,13 @@ impl Path { /// ) /// ``` pub fn arc_by(&self, radius: Length, angle: Angle) -> Self { - if radius == Length::zero() || angle == Angle::zero() { + if radius == Length::new::(0.) || angle == Angle::zero() { return self.clone(); } let center = self.cursor + self.end_direction().rotate(Angle::from_deg(90.)) * radius; let center_cursor_axis = Axis::<2>::between(center, self.cursor).expect("zero radius already checked"); - let direction_factor = radius / radius.abs(); + let direction_factor: f64 = (radius / radius.abs()).into(); let interim_point = center + center_cursor_axis diff --git a/src/core/point.rs b/src/core/point.rs index f8641c9..fa91886 100644 --- a/src/core/point.rs +++ b/src/core/point.rs @@ -4,6 +4,7 @@ use approx::{AbsDiffEq, RelativeEq}; use cxx::UniquePtr; use iter_fixed::IntoIteratorFixed; use opencascade_sys::ffi; +use uom::si::length::meter; use crate::{Dir, Error, Length, Plane}; @@ -70,7 +71,7 @@ impl Point { /// assert_eq!(point3d.z(), 0.m()); /// ``` pub fn origin() -> Self { - Self([Length::zero(); DIM]) + Self([Length::new::(0.); DIM]) } /// Return the absolute distance between this `Point` to another. @@ -88,14 +89,24 @@ impl Point { /// assert_eq!(point3.distance_to(point!(0, 0, 0)), 5.m()); /// ``` pub fn distance_to(&self, other: Self) -> Length { - Length::from_m(f64::sqrt( - (*self - other).0.iter().map(|n| n.m().powi(2)).sum(), + Length::new::(f64::sqrt( + (*self - other) + .0 + .iter() + .map(|n| n.get::().powi(2)) + .sum(), )) } /// Return the direction this `Point` lies in with respect to another point. pub fn direction_from(&self, other: Self) -> Result, Error> { - Dir::::try_from((*self - other).0.into_iter_fixed().map(|n| n.m()).collect()) + Dir::::try_from( + (*self - other) + .0 + .into_iter_fixed() + .map(|n| n.get::()) + .collect(), + ) } } @@ -130,10 +141,18 @@ impl Point<3> { } pub(crate) fn to_occt_point(self) -> UniquePtr { - ffi::new_point(self.x().m(), self.y().m(), self.z().m()) + ffi::new_point( + self.x().get::(), + self.y().get::(), + self.z().get::(), + ) } pub(crate) fn to_occt_vec(self) -> UniquePtr { - ffi::new_vec(self.x().m(), self.y().m(), self.z().m()) + ffi::new_vec( + self.x().get::(), + self.y().get::(), + self.z().get::(), + ) } } @@ -278,7 +297,7 @@ impl AbsDiffEq for Point { self.0 .iter() .zip(other.0.iter()) - .all(|(a, b)| Length::abs_diff_eq(a, b, epsilon)) + .all(|(a, b)| a.get::().abs_diff_eq(&b.get::(), epsilon)) } } @@ -292,10 +311,10 @@ impl RelativeEq for Point { epsilon: Self::Epsilon, max_relative: Self::Epsilon, ) -> bool { - self.0 - .iter() - .zip(other.0.iter()) - .all(|(a, b)| Length::relative_eq(a, b, epsilon, max_relative)) + self.0.iter().zip(other.0.iter()).all(|(a, b)| { + a.get::() + .relative_eq(&b.get::(), epsilon, max_relative) + }) } } diff --git a/src/meshes/render_mesh.rs b/src/meshes/render_mesh.rs index a7340e4..71136b0 100644 --- a/src/meshes/render_mesh.rs +++ b/src/meshes/render_mesh.rs @@ -1,8 +1,14 @@ use opencascade_sys::ffi; +use uom::lib::marker::PhantomData; +use uom::si::length::meter; use crate::{Dir, Error, Face, IntoLength, Length, Part, Point}; -const DEFAULT_TOLERANCE: Length = Length::from_m(0.000001); +const DEFAULT_TOLERANCE: Length = Length { + dimension: PhantomData, + units: PhantomData, + value: 0.000001, +}; /// A triangular mesh of one or more `Face`s optimized for 3D rendering. #[derive(Clone, Debug, PartialEq)] @@ -84,9 +90,12 @@ impl RenderMesh { let edge_2 = point3 - point1; let cross = ( - edge_1.y().m() * edge_2.z().m() - edge_1.z().m() * edge_2.y().m(), - edge_1.z().m() * edge_2.x().m() - edge_1.x().m() * edge_2.z().m(), - edge_1.x().m() * edge_2.y().m() - edge_1.y().m() * edge_2.x().m(), + edge_1.y().get::() * edge_2.z().get::() + - edge_1.z().get::() * edge_2.y().get::(), + edge_1.z().get::() * edge_2.x().get::() + - edge_1.x().get::() * edge_2.z().get::(), + edge_1.x().get::() * edge_2.y().get::() + - edge_1.y().get::() * edge_2.x().get::(), ); total_area += 0.5 * f64::sqrt(cross.0.powi(2) + cross.1.powi(2) + cross.2.powi(2)); @@ -149,7 +158,7 @@ impl TryFrom<(Face, Length)> for RenderMesh { fn try_from((face, tolerance): (Face, Length)) -> Result { let mesh = ffi::BRepMesh_IncrementalMesh_ctor( ffi::cast_face_to_shape(face.0.as_ref().unwrap()), - tolerance.m(), + tolerance.get::(), ); let face = ffi::TopoDS_cast_to_face(mesh.as_ref().unwrap().Shape()); let mut location = ffi::TopLoc_Location_ctor(); diff --git a/src/parts/methods/center.rs b/src/parts/methods/center.rs index 8fb8273..d3aaaa2 100644 --- a/src/parts/methods/center.rs +++ b/src/parts/methods/center.rs @@ -1,4 +1,5 @@ use opencascade_sys::ffi; +use uom::si::length::meter; use crate::{Error, Length, Part, Point, point}; @@ -27,9 +28,9 @@ impl Part { let centre_of_mass = ffi::GProp_GProps_CentreOfMass(&gprops); Ok(point!( - Length::from_m(round(centre_of_mass.X(), 9)), - Length::from_m(round(centre_of_mass.Y(), 9)), - Length::from_m(round(centre_of_mass.Z(), 9)) + Length::new::(round(centre_of_mass.X(), 9)), + Length::new::(round(centre_of_mass.Y(), 9)), + Length::new::(round(centre_of_mass.Z(), 9)) )) } None => Err(Error::EmptyPart), diff --git a/src/parts/methods/linear_pattern.rs b/src/parts/methods/linear_pattern.rs index a1a20e1..8ce5f9c 100644 --- a/src/parts/methods/linear_pattern.rs +++ b/src/parts/methods/linear_pattern.rs @@ -1,3 +1,5 @@ +use uom::si::length::meter; + use crate::{Axis, Length, Part, Point}; impl Part { @@ -28,9 +30,9 @@ impl Part { let len_step = (start - until).distance_to(Point::<3>::origin()) / instances as f64; let mut new_part = self.clone(); - let mut pos = Length::zero(); + let mut pos = Length::new::(0.); for _ in 0..instances { - pos = pos + len_step; + pos += len_step; new_part = new_part.add(&self.move_to(axis.point_at(pos))); } new_part diff --git a/src/parts/primitives/cuboid.rs b/src/parts/primitives/cuboid.rs index fd274da..f6c824d 100644 --- a/src/parts/primitives/cuboid.rs +++ b/src/parts/primitives/cuboid.rs @@ -1,4 +1,5 @@ use opencascade_sys::ffi; +use uom::si::length::{meter, millimeter}; use crate::{Length, Part, Point, core::is_zero, point}; @@ -47,12 +48,12 @@ impl Cuboid { return Part::empty(); } - let min_x = corner1.x().min(&corner2.x()).m(); - let min_y = corner1.y().min(&corner2.y()).m(); - let min_z = corner1.z().min(&corner2.z()).m(); - let max_x = corner1.x().max(&corner2.x()).m(); - let max_y = corner1.y().max(&corner2.y()).m(); - let max_z = corner1.z().max(&corner2.z()).m(); + let min_x = corner1.x().min(corner2.x()).get::(); + let min_y = corner1.y().min(corner2.y()).get::(); + let min_z = corner1.z().min(corner2.z()).get::(); + let max_x = corner1.x().max(corner2.x()).get::(); + let max_y = corner1.y().max(corner2.y()).get::(); + let max_z = corner1.z().max(corner2.z()).get::(); let point = ffi::new_point(min_x, min_y, min_z); let mut cuboid = @@ -75,7 +76,12 @@ impl Cuboid { /// ) /// ``` pub fn from_m(x: f64, y: f64, z: f64) -> Part { - Self::from_dim(Length::from_m(x), Length::from_m(y), Length::from_m(z)) + // todo: remove + Self::from_dim( + Length::new::(x), + Length::new::(y), + Length::new::(z), + ) } /// Construct a centered cuboidal `Part` directly from the x, y, and z millimeter values. /// @@ -92,7 +98,12 @@ impl Cuboid { /// ) /// ``` pub fn from_mm(x: f64, y: f64, z: f64) -> Part { - Self::from_dim(Length::from_mm(x), Length::from_mm(y), Length::from_mm(z)) + // todo: remove + Self::from_dim( + Length::new::(x), + Length::new::(y), + Length::new::(z), + ) } } diff --git a/src/parts/primitives/cylinder.rs b/src/parts/primitives/cylinder.rs index 39a2d97..a621d47 100644 --- a/src/parts/primitives/cylinder.rs +++ b/src/parts/primitives/cylinder.rs @@ -1,5 +1,7 @@ -use crate::{Length, Part, core::is_zero}; use opencascade_sys::ffi; +use uom::si::length::meter; + +use crate::{Length, Part, core::is_zero}; /// Builder for a cylindrical `Part`. /// @@ -23,10 +25,11 @@ impl Cylinder { return Part::empty(); } let axis = ffi::gp_Ax2_ctor( - &ffi::new_point(0., 0., -height.m() / 2.), + &ffi::new_point(0., 0., -height.get::() / 2.), &ffi::gp_Dir_ctor(0., 0., 1.), ); - let mut make = ffi::BRepPrimAPI_MakeCylinder_ctor(&axis, radius.m(), height.m()); + let mut make = + ffi::BRepPrimAPI_MakeCylinder_ctor(&axis, radius.get::(), height.get::()); Part::from_occt(make.pin_mut().Shape()) } diff --git a/src/parts/primitives/sphere.rs b/src/parts/primitives/sphere.rs index 0a6e93a..6fd9875 100644 --- a/src/parts/primitives/sphere.rs +++ b/src/parts/primitives/sphere.rs @@ -1,5 +1,6 @@ use crate::{Length, Part, core::is_zero}; use opencascade_sys::ffi; +use uom::si::length::meter; /// Builder for a spherical `Part`. /// @@ -26,7 +27,7 @@ impl Sphere { let axis = ffi::gp_Ax2_ctor(&ffi::new_point(0., 0., 0.), &ffi::gp_Dir_ctor(0., 0., 1.)); let mut make_sphere = - ffi::BRepPrimAPI_MakeSphere_ctor(&axis, radius.m(), std::f64::consts::TAU); + ffi::BRepPrimAPI_MakeSphere_ctor(&axis, radius.get::(), std::f64::consts::TAU); Part::from_occt(make_sphere.pin_mut().Shape()) } /// Construct a centered spherical `Part` from a given diameter. diff --git a/src/sketches/primitives/circle.rs b/src/sketches/primitives/circle.rs index e1eed23..4fbf8a5 100644 --- a/src/sketches/primitives/circle.rs +++ b/src/sketches/primitives/circle.rs @@ -1,3 +1,5 @@ +use uom::si::length::meter; + use crate::{Length, Path, Point, Sketch}; /// Builder for a circular `Sketch`. @@ -20,14 +22,14 @@ impl Circle { /// assert_eq!(circle.center(), Ok(Point::<2>::origin())); /// ``` pub fn from_radius(radius: Length) -> Sketch { - Path::at(Point::<2>::new([radius * -1., Length::zero()])) + Path::at(Point::<2>::new([radius * -1., Length::new::(0.)])) .arc_points( - Point::<2>::new([Length::zero(), radius]), - Point::<2>::new([radius, Length::zero()]), + Point::<2>::new([Length::new::(0.), radius]), + Point::<2>::new([radius, Length::new::(0.)]), ) .arc_points( - Point::<2>::new([Length::zero(), radius * -1.]), - Point::<2>::new([radius * -1., Length::zero()]), + Point::<2>::new([Length::new::(0.), radius * -1.]), + Point::<2>::new([radius * -1., Length::new::(0.)]), ) .close() } diff --git a/src/sketches/primitives/rectangle.rs b/src/sketches/primitives/rectangle.rs index e183706..bb19b99 100644 --- a/src/sketches/primitives/rectangle.rs +++ b/src/sketches/primitives/rectangle.rs @@ -1,3 +1,5 @@ +use uom::si::length::{meter, millimeter}; + use crate::{Length, Path, Point, Sketch, point}; /// Builder for a rectangular `Sketch`. @@ -56,7 +58,7 @@ impl Rectangle { /// ) /// ``` pub fn from_m(x: f64, y: f64) -> Sketch { - Self::from_dim(Length::from_m(x), Length::from_m(y)) + Self::from_dim(Length::new::(x), Length::new::(y)) } /// Construct a centered rectangular `Sketch` directly from the x and y millimeter values. @@ -74,7 +76,7 @@ impl Rectangle { /// ) /// ``` pub fn from_mm(x: f64, y: f64) -> Sketch { - Self::from_dim(Length::from_mm(x), Length::from_mm(y)) + Self::from_dim(Length::new::(x), Length::new::(y)) } } diff --git a/src/sketches/sketch.rs b/src/sketches/sketch.rs index cebf95f..6672584 100644 --- a/src/sketches/sketch.rs +++ b/src/sketches/sketch.rs @@ -2,6 +2,7 @@ use std::vec; use cxx::UniquePtr; use opencascade_sys::ffi; +use uom::si::length::meter; use crate::{Angle, Axis, Edge, Error, Face, IntoAngle, IntoLength, Length, Part, Plane, Point}; @@ -149,9 +150,9 @@ impl Sketch { let len_step = (start - until).distance_to(Point::<2>::origin()) / instances as f64; let mut new_part = self.clone(); - let mut pos = Length::zero(); + let mut pos = Length::new::(0.); for _ in 0..instances { - pos = pos + len_step; + pos += len_step; new_part = new_part.add(&self.move_to(axis.point_at(pos))); } new_part @@ -278,7 +279,7 @@ impl Sketch { /// ); /// ``` pub fn extrude(&self, plane: Plane, thickness: Length) -> Result { - if thickness == Length::zero() { + if thickness == Length::new::(0.) { return Err(Error::EmptySketch); } @@ -551,7 +552,7 @@ mod tests { fn extrude_zero_thickness() { let sketch = Rectangle::from_dim(1.m(), 2.m()); assert_eq!( - sketch.extrude(Plane::xy(), Length::zero()), + sketch.extrude(Plane::xy(), Length::new::(0.)), Err(Error::EmptySketch) ) } @@ -564,7 +565,7 @@ mod tests { .line_to(point!(0.m(), 2.m())) .close(); assert_eq!( - sketch.extrude(Plane::xz(), Length::from_m(-3.)), + sketch.extrude(Plane::xz(), Length::new::(-3.)), Ok(Cuboid::from_corners( Point::<3>::origin(), point!(1.m(), 3.m(), 2.m()) From 773e79df870e9d3f5b47a4bf9cf89a6f0efacad5 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sun, 29 Jun 2025 12:23:00 +0200 Subject: [PATCH 04/10] refactor: replace custom Angle with uom --- src/core/angle.rs | 227 ++------------------------ src/core/dir.rs | 26 +-- src/core/edge.rs | 9 +- src/core/path.rs | 10 +- src/parts/methods/circular_pattern.rs | 2 +- src/parts/methods/rotate_around.rs | 3 +- src/sketches/sketch.rs | 5 +- 7 files changed, 41 insertions(+), 241 deletions(-) diff --git a/src/core/angle.rs b/src/core/angle.rs index ce27203..0824e9a 100644 --- a/src/core/angle.rs +++ b/src/core/angle.rs @@ -1,7 +1,6 @@ use core::f64; -use std::ops::{Add, Div, Mul, Neg, Sub}; -use approx::{AbsDiffEq, RelativeEq}; +use uom::si::angle::{degree, radian}; use super::IntoF64; @@ -9,243 +8,39 @@ use super::IntoF64; /// /// Angle exists to remove ambiguity about angle units, which are not supported by default by /// major CAD kernels. -/// -/// ```rust -/// use anvil::Angle; -/// -/// // You can construct an angle using the Angle::from_[unit] methods: -/// let degrees_angle = Angle::from_deg(1.2); -/// let radians_angle = Angle::from_rad(3.4); -/// -/// // To get back a angle value in a specific unit, call the Angle.[unit] method -/// assert_eq!(degrees_angle.deg(), 1.2); -/// assert_eq!(radians_angle.rad(), 3.4); -/// -/// // Angle construction can be simplified using the `IntoAngle` trait. -/// use anvil::IntoAngle; -/// -/// assert_eq!(1.2.deg(), Angle::from_deg(1.2)); -/// assert_eq!(4.5.rad(), Angle::from_rad(4.5)); -/// ``` -#[derive(Debug, PartialEq, Copy, Clone, PartialOrd)] -pub struct Angle { - rad: f64, -} -impl Angle { - /// Construct a `Angle` with a value of zero. - /// - /// # Example - /// ```rust - /// use anvil::Angle; - /// - /// let angle = Angle::zero(); - /// assert_eq!(angle.deg(), 0.); - /// ``` - pub fn zero() -> Self { - Self { rad: 0. } - } - /// Construct a `Angle` from a value in radians. - /// - /// # Example - /// ```rust - /// use core::f64; - /// use anvil::Angle; - /// - /// let angle = Angle::from_rad(f64::consts::PI); - /// assert_eq!(angle.deg(), 180.); - /// ``` - pub fn from_rad(value: f64) -> Self { - Self { - rad: value % f64::consts::TAU, - } - } - /// Return the value of this angle in radians. - pub fn rad(&self) -> f64 { - self.rad - } - /// Construct a `Angle` from a value in degrees. - /// - /// # Example - /// ```rust - /// use core::f64; - /// use anvil::Angle; - /// - /// let angle = Angle::from_deg(180.); - /// assert_eq!(angle.rad(), f64::consts::PI); - /// ``` - pub fn from_deg(value: f64) -> Self { - Angle { - rad: value / 360. * f64::consts::TAU, - } - } - /// Return the value of this angle in degrees. - pub fn deg(&self) -> f64 { - self.rad / f64::consts::TAU * 360. - } - - /// Return the absolute value of this `Angle`. - /// - /// ```rust - /// use anvil::IntoAngle; - /// - /// assert_eq!((-45).deg().abs(), 45.deg()); - /// assert_eq!(10.deg().abs(), 10.deg()); - /// ``` - pub fn abs(&self) -> Self { - Self { - rad: self.rad.abs(), - } - } - - /// Return the smaller of two angles. - /// - /// # Example - /// ```rust - /// use anvil::IntoAngle; - /// - /// let angle1 = 1.deg(); - /// let angle2 = 2.deg(); - /// assert_eq!(angle1.min(&angle2), angle1); - /// assert_eq!(angle2.min(&angle1), angle1); - /// ``` - pub fn min(&self, other: &Self) -> Self { - Angle { - rad: self.rad.min(other.rad), - } - } - /// Return the larger of two lengths. - /// - /// # Example - /// ```rust - /// use anvil::IntoAngle; - /// - /// let angle1 = 1.deg(); - /// let angle2 = 2.deg(); - /// assert_eq!(angle1.max(&angle2), angle2); - /// assert_eq!(angle2.max(&angle1), angle2); - /// ``` - pub fn max(&self, other: &Self) -> Self { - Angle { - rad: self.rad.max(other.rad), - } - } -} - -impl Add for Angle { - type Output = Angle; - fn add(self, other: Angle) -> Angle { - Angle { - rad: self.rad + other.rad, - } - } -} - -impl Sub for Angle { - type Output = Angle; - fn sub(self, other: Angle) -> Angle { - Angle { - rad: self.rad - other.rad, - } - } -} - -impl Mul for Angle { - type Output = Angle; - fn mul(self, other: f64) -> Angle { - Angle { - rad: self.rad * other, - } - } -} - -impl Mul for f64 { - type Output = Angle; - fn mul(self, other: Angle) -> Angle { - other * self - } -} - -impl Div for Angle { - type Output = Angle; - fn div(self, other: f64) -> Angle { - Angle { - rad: self.rad / other, - } - } -} - -impl Div for Angle { - type Output = f64; - /// Divide a `Angle` by another `Angle`. - /// ```rust - /// use anvil::IntoAngle; - /// - /// assert_eq!(6.deg() / 2.deg(), 3.) - /// ``` - fn div(self, other: Angle) -> f64 { - self.rad / other.rad - } -} - -impl Neg for Angle { - type Output = Angle; - fn neg(self) -> Self::Output { - self * -1. - } -} - -impl AbsDiffEq for Angle { - type Epsilon = f64; - fn default_epsilon() -> Self::Epsilon { - f64::default_epsilon() - } - fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool { - f64::abs_diff_eq(&self.rad, &other.rad, epsilon) - } -} - -impl RelativeEq for Angle { - fn default_max_relative() -> Self::Epsilon { - f64::default_max_relative() - } - fn relative_eq( - &self, - other: &Self, - epsilon: Self::Epsilon, - max_relative: Self::Epsilon, - ) -> bool { - f64::relative_eq(&self.rad, &other.rad, epsilon, max_relative) - } -} +pub type Angle = uom::si::f64::Angle; /// Import this trait to easily convert numbers into `Angle`s. /// /// ```rust /// use anvil::{Angle, IntoAngle}; +/// use uom::si::angle::{degree, radian}; /// -/// assert_eq!(5.deg(), Angle::from_deg(5.)); -/// assert_eq!(5.123.rad(), Angle::from_rad(5.123)); +/// assert_eq!(5.deg(), Angle::new::(5.)); +/// assert_eq!(5.123.rad(), Angle::new::(5.123)); /// ``` pub trait IntoAngle: IntoF64 { /// Convert this number into a `Angle` in degrees. /// /// ```rust /// use anvil::{IntoAngle, Angle}; + /// use uom::si::angle::degree; /// - /// assert_eq!(5.deg(), Angle::from_deg(5.)); + /// assert_eq!(5.deg(), Angle::new::(5.)); /// ``` fn deg(&self) -> Angle { - Angle::from_deg(self.to_f64()) + Angle::new::(self.to_f64()) } /// Convert this number into a `Angle` in radians. /// /// ```rust /// use anvil::{IntoAngle, Angle}; + /// use uom::si::angle::radian; /// - /// assert_eq!(5.rad(), Angle::from_rad(5.)); + /// assert_eq!(5.rad(), Angle::new::(5.)); /// ``` fn rad(&self) -> Angle { - Angle::from_rad(self.to_f64()) + Angle::new::(self.to_f64()) } } diff --git a/src/core/dir.rs b/src/core/dir.rs index e38cbf2..141ee9b 100644 --- a/src/core/dir.rs +++ b/src/core/dir.rs @@ -4,6 +4,7 @@ use approx::{AbsDiffEq, RelativeEq}; use cxx::UniquePtr; use iter_fixed::IntoIteratorFixed; use opencascade_sys::ffi; +use uom::si::angle::radian; use crate::{Angle, Error, Length, Point}; @@ -80,21 +81,20 @@ impl Dir<2> { /// /// ```rust /// use anvil::{dir, IntoAngle}; - /// use approx::assert_relative_eq; /// - /// assert_relative_eq!(dir!(1, 0).angle(), 0.deg()); - /// assert_relative_eq!(dir!(1, 1).angle(), 45.deg()); - /// assert_relative_eq!(dir!(0, 1).angle(), 90.deg()); - /// assert_relative_eq!(dir!(-1, 1).angle(), 135.deg()); - /// assert_relative_eq!(dir!(-1, 0).angle(), 180.deg()); - /// assert_relative_eq!(dir!(-1, -1).angle(), 225.deg()); - /// assert_relative_eq!(dir!(0, -1).angle(), 270.deg()); - /// assert_relative_eq!(dir!(1, -1).angle(), 315.deg()); + /// assert_eq!(dir!(1, 0).angle(), 0.deg()); + /// assert_eq!(dir!(1, 1).angle(), 45.deg()); + /// assert_eq!(dir!(0, 1).angle(), 90.deg()); + /// assert_eq!(dir!(-1, 1).angle(), 135.deg()); + /// assert_eq!(dir!(-1, 0).angle(), 180.deg()); + /// assert_eq!(dir!(-1, -1).angle(), 225.deg()); + /// assert_eq!(dir!(0, -1).angle(), 270.deg()); + /// assert_eq!(dir!(1, -1).angle(), 315.deg()); /// ``` pub fn angle(&self) -> Angle { - let angle = Angle::from_rad(self.y().atan2(self.x())); - if angle.rad() < 0. { - Angle::from_rad(angle.rad() + std::f64::consts::TAU) + let angle = Angle::new::(self.y().atan2(self.x())); + if angle.get::() < 0. { + angle + Angle::FULL_TURN } else { angle } @@ -112,7 +112,7 @@ impl From for Dir<2> { /// An angle of 0 points in the positive x-direction and positive angles rotate counter /// clockwise. fn from(value: Angle) -> Self { - Self([f64::cos(value.rad()), f64::sin(value.rad())]) + Self([value.cos().into(), value.sin().into()]) } } diff --git a/src/core/edge.rs b/src/core/edge.rs index 93079f9..3b44f9e 100644 --- a/src/core/edge.rs +++ b/src/core/edge.rs @@ -2,6 +2,7 @@ use core::f64; use cxx::UniquePtr; use opencascade_sys::ffi; +use uom::si::angle::degree; use uom::si::length::meter; use crate::{Angle, Axis, Dir, Error, Length, Plane, Point}; @@ -133,9 +134,9 @@ impl Edge { && interior_angle > end_angle; if arc_is_clockwise { - Ok(Dir::from(end_angle - Angle::from_deg(90.))) + Ok(Dir::from(end_angle - Angle::new::(90.))) } else { - Ok(Dir::from(end_angle + Angle::from_deg(90.))) + Ok(Dir::from(end_angle + Angle::new::(90.))) } } Self::Line(start, end) => Dir::<2>::try_from([ @@ -202,11 +203,11 @@ fn arc_center_radius( let start_interior_axis = Axis::<2>::new( start_interior_midpoint, - start_interior_direction.rotate(Angle::from_deg(90.)), + start_interior_direction.rotate(Angle::new::(90.)), ); let interior_end_axis = Axis::<2>::new( interior_end_midpoint, - interior_end_direction.rotate(Angle::from_deg(90.)), + interior_end_direction.rotate(Angle::new::(90.)), ); let center = start_interior_axis diff --git a/src/core/path.rs b/src/core/path.rs index c30acaa..8c6810d 100644 --- a/src/core/path.rs +++ b/src/core/path.rs @@ -1,6 +1,8 @@ -use crate::{Angle, Axis, Dir, Edge, Length, Point, Sketch}; +use uom::si::angle::{degree, radian}; use uom::si::length::meter; +use crate::{Angle, Axis, Dir, Edge, Length, Point, Sketch}; + /// A continuous series of edges (i.e. lines, arcs, ...). #[derive(Debug, PartialEq, Clone)] pub struct Path { @@ -76,10 +78,10 @@ impl Path { /// ) /// ``` pub fn arc_by(&self, radius: Length, angle: Angle) -> Self { - if radius == Length::new::(0.) || angle == Angle::zero() { + if radius == Length::new::(0.) || angle == Angle::new::(0.) { return self.clone(); } - let center = self.cursor + self.end_direction().rotate(Angle::from_deg(90.)) * radius; + let center = self.cursor + self.end_direction().rotate(Angle::new::(90.)) * radius; let center_cursor_axis = Axis::<2>::between(center, self.cursor).expect("zero radius already checked"); let direction_factor: f64 = (radius / radius.abs()).into(); @@ -181,7 +183,7 @@ impl Path { Some(last_edge) => last_edge .end_direction() .expect("edge has already been checked for zero length"), - None => Dir::from(Angle::zero()), + None => Dir::from(Angle::new::(0.)), } } diff --git a/src/parts/methods/circular_pattern.rs b/src/parts/methods/circular_pattern.rs index 381f877..d84333c 100644 --- a/src/parts/methods/circular_pattern.rs +++ b/src/parts/methods/circular_pattern.rs @@ -21,7 +21,7 @@ impl Part { let mut angle = 0.rad(); for _ in 0..instances { new_shape = new_shape.add(&self.rotate_around(around, angle)); - angle = angle + angle_step; + angle += angle_step; } new_shape } diff --git a/src/parts/methods/rotate_around.rs b/src/parts/methods/rotate_around.rs index f56317b..3d42c3a 100644 --- a/src/parts/methods/rotate_around.rs +++ b/src/parts/methods/rotate_around.rs @@ -1,4 +1,5 @@ use opencascade_sys::ffi; +use uom::si::angle::radian; use crate::{Angle, Axis, Part}; @@ -22,7 +23,7 @@ impl Part { let mut transform = ffi::new_transform(); transform .pin_mut() - .SetRotation(&axis.to_occt_ax1(), angle.rad()); + .SetRotation(&axis.to_occt_ax1(), angle.get::()); let mut operation = ffi::BRepBuilderAPI_Transform_ctor(inner, &transform, false); Self::from_occt(operation.pin_mut().Shape()) } diff --git a/src/sketches/sketch.rs b/src/sketches/sketch.rs index 6672584..a581996 100644 --- a/src/sketches/sketch.rs +++ b/src/sketches/sketch.rs @@ -2,6 +2,7 @@ use std::vec; use cxx::UniquePtr; use opencascade_sys::ffi; +use uom::si::angle::radian; use uom::si::length::meter; use crate::{Angle, Axis, Edge, Error, Face, IntoAngle, IntoLength, Length, Part, Plane, Point}; @@ -101,7 +102,7 @@ impl Sketch { let mut angle = 0.rad(); for _ in 0..instances { new_shape = new_shape.add(&self.rotate_around(around, angle)); - angle = angle + angle_step; + angle += angle_step; } new_shape } @@ -436,7 +437,7 @@ impl SketchAction { direction: plane.normal(), } .to_occt_ax1(), - angle.rad(), + angle.get::(), ); let mut operation = ffi::BRepBuilderAPI_Transform_ctor(&shape, &transform, false); From ae01fd26e854d22c8c8997157d7b409555259916 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sun, 29 Jun 2025 12:58:09 +0200 Subject: [PATCH 05/10] refactor: use uom Volume --- src/parts/methods/empty.rs | 8 ++++++-- src/parts/methods/volume.rs | 12 ++++++++---- src/parts/primitives/cube.rs | 4 +++- src/parts/primitives/cuboid.rs | 9 ++++++--- src/parts/primitives/cylinder.rs | 9 ++++++--- src/parts/primitives/sphere.rs | 14 ++++++++++++-- 6 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/parts/methods/empty.rs b/src/parts/methods/empty.rs index c280ea9..ab6a115 100644 --- a/src/parts/methods/empty.rs +++ b/src/parts/methods/empty.rs @@ -1,3 +1,5 @@ +use uom::si::volume::cubic_meter; + use crate::Part; impl Part { @@ -5,9 +7,11 @@ impl Part { /// /// ```rust /// use anvil::Part; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// /// let part = Part::empty(); - /// assert_eq!(part.volume(), 0.); + /// assert_eq!(part.volume(), Volume::new::(0.)); /// ``` pub fn empty() -> Self { Self { inner: None } @@ -23,6 +27,6 @@ impl Part { /// assert!(cube.subtract(&cube).is_empty()); /// ``` pub fn is_empty(&self) -> bool { - self.volume() < 1e-9 + self.volume().get::() < 1e-9 } } diff --git a/src/parts/methods/volume.rs b/src/parts/methods/volume.rs index 63ccd9a..e42c991 100644 --- a/src/parts/methods/volume.rs +++ b/src/parts/methods/volume.rs @@ -1,4 +1,6 @@ use opencascade_sys::ffi; +use uom::si::f64::Volume; +use uom::si::volume::cubic_meter; use crate::Part; @@ -7,19 +9,21 @@ impl Part { /// /// ```rust /// use anvil::{Cuboid, IntoLength}; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// use approx::assert_relative_eq; /// /// let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); - /// assert_relative_eq!(cuboid.volume(), 1.); + /// assert_relative_eq!(cuboid.volume().value, Volume::new::(1.).value); /// ``` - pub fn volume(&self) -> f64 { + pub fn volume(&self) -> Volume { match &self.inner { Some(inner) => { let mut gprops = ffi::GProp_GProps_ctor(); ffi::BRepGProp_VolumeProperties(inner, gprops.pin_mut()); - gprops.Mass() + Volume::new::(gprops.Mass()) } - None => 0., + None => Volume::new::(0.), } } } diff --git a/src/parts/primitives/cube.rs b/src/parts/primitives/cube.rs index 1355234..26282cb 100644 --- a/src/parts/primitives/cube.rs +++ b/src/parts/primitives/cube.rs @@ -12,11 +12,13 @@ impl Cube { /// # Example /// ```rust /// use anvil::{Cube, IntoLength, Part, point}; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// use approx::assert_relative_eq; /// /// let part = Cube::from_size(1.m()); /// assert_eq!(part.center(), Ok(point!(0, 0, 0))); - /// assert_relative_eq!(part.volume(), 1.); + /// assert_relative_eq!(part.volume().value, Volume::new::(1.).value); /// ``` pub fn from_size(size: Length) -> Part { Cuboid::from_dim(size, size, size) diff --git a/src/parts/primitives/cuboid.rs b/src/parts/primitives/cuboid.rs index f6c824d..5306fb7 100644 --- a/src/parts/primitives/cuboid.rs +++ b/src/parts/primitives/cuboid.rs @@ -15,11 +15,12 @@ impl Cuboid { /// # Example /// ```rust /// use anvil::{Cuboid, IntoLength, Part, point}; - /// use approx::assert_relative_eq; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// /// let part = Cuboid::from_dim(1.m(), 2.m(), 3.m()); /// assert_eq!(part.center(), Ok(point!(0, 0, 0))); - /// assert_relative_eq!(part.volume(), 6.); + /// assert_eq!(part.volume(), Volume::new::(6.)); /// ``` pub fn from_dim(x: Length, y: Length, z: Length) -> Part { Self::from_corners( @@ -32,11 +33,13 @@ impl Cuboid { /// # Example /// ```rust /// use anvil::{Cuboid, IntoLength, Part, point}; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// use approx::assert_relative_eq; /// /// let part = Cuboid::from_corners(point!(0, 0, 0), point!(2.m(), 2.m(), 2.m())); /// assert_eq!(part.center(), Ok(point!(1.m(), 1.m(), 1.m()))); - /// assert_relative_eq!(part.volume(), 8.); + /// assert_relative_eq!(part.volume().value, Volume::new::(8.).value); /// ``` pub fn from_corners(corner1: Point<3>, corner2: Point<3>) -> Part { let volume_is_zero = is_zero(&[ diff --git a/src/parts/primitives/cylinder.rs b/src/parts/primitives/cylinder.rs index a621d47..5ba069f 100644 --- a/src/parts/primitives/cylinder.rs +++ b/src/parts/primitives/cylinder.rs @@ -14,11 +14,12 @@ impl Cylinder { /// /// ```rust /// use anvil::{Cylinder, IntoLength, Point, Part}; - /// use approx::assert_relative_eq; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// /// let part = Cylinder::from_radius(1.m(), 2.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert_relative_eq!(part.volume(), 6.283185307179587); + /// assert_eq!(part.volume(), Volume::new::(6.283185307179587)); /// ``` pub fn from_radius(radius: Length, height: Length) -> Part { if is_zero(&[radius, height]) { @@ -38,11 +39,13 @@ impl Cylinder { /// # Example /// ```rust /// use anvil::{Cylinder, IntoLength, Point, Part}; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// use approx::assert_relative_eq; /// /// let part = Cylinder::from_diameter(1.m(), 2.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert_relative_eq!(part.volume(), 1.5707963267948968); + /// assert_eq!(part.volume().value, Volume::new::(1.5707963267948968).value); /// ``` pub fn from_diameter(diameter: Length, height: Length) -> Part { Self::from_radius(diameter / 2., height) diff --git a/src/parts/primitives/sphere.rs b/src/parts/primitives/sphere.rs index 6fd9875..e1f8ed6 100644 --- a/src/parts/primitives/sphere.rs +++ b/src/parts/primitives/sphere.rs @@ -14,11 +14,16 @@ impl Sphere { /// # Example /// ```rust /// use anvil::{Sphere, IntoLength, Point, Part}; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// use approx::assert_relative_eq; /// /// let part = Sphere::from_radius(1.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert_relative_eq!(part.volume(), 4.188790204786391); + /// assert_relative_eq!( + /// part.volume().value, + /// Volume::new::(4.188790204786391).value + /// ); /// ``` pub fn from_radius(radius: Length) -> Part { if is_zero(&[radius]) { @@ -35,11 +40,16 @@ impl Sphere { /// # Example /// ```rust /// use anvil::{Sphere, IntoLength, Point, Part}; + /// use uom::si::volume::cubic_meter; + /// use uom::si::f64::Volume; /// use approx::assert_relative_eq; /// /// let part = Sphere::from_diameter(1.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert_relative_eq!(part.volume(), 0.5235987755982989); + /// assert_relative_eq!( + /// part.volume().value, + /// Volume::new::(0.5235987755982989).value + /// ); /// ``` pub fn from_diameter(diameter: Length) -> Part { Self::from_radius(diameter / 2.) From d0f40ced2300e42c8cd9e4c11964d34a4fa5ff59 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sun, 29 Jun 2025 13:15:07 +0200 Subject: [PATCH 06/10] refactor: use uom Area --- src/sketches/primitives/circle.rs | 14 ++++++++++++-- src/sketches/primitives/rectangle.rs | 16 ++++++++++++++-- src/sketches/primitives/square.rs | 13 +++++++++---- src/sketches/sketch.rs | 26 +++++++++++++++----------- 4 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/sketches/primitives/circle.rs b/src/sketches/primitives/circle.rs index 4fbf8a5..beabd3e 100644 --- a/src/sketches/primitives/circle.rs +++ b/src/sketches/primitives/circle.rs @@ -16,10 +16,15 @@ impl Circle { /// use core::f64; /// use anvil::{Circle, IntoLength, Point}; /// use approx::assert_relative_eq; + /// use uom::si::area::square_meter; + /// use uom::si::f64::Area; /// /// let circle = Circle::from_radius(1.m()); - /// assert_relative_eq!(circle.area(), f64::consts::PI); /// assert_eq!(circle.center(), Ok(Point::<2>::origin())); + /// assert_relative_eq!( + /// circle.area().value, + /// Area::new::(f64::consts::PI).value + /// ); /// ``` pub fn from_radius(radius: Length) -> Sketch { Path::at(Point::<2>::new([radius * -1., Length::new::(0.)])) @@ -41,10 +46,15 @@ impl Circle { /// use core::f64; /// use anvil::{Circle, IntoLength, Point}; /// use approx::assert_relative_eq; + /// use uom::si::area::square_meter; + /// use uom::si::f64::Area; /// /// let circle = Circle::from_diameter(2.m()); - /// assert_relative_eq!(circle.area(), f64::consts::PI); /// assert_eq!(circle.center(), Ok(Point::<2>::origin())); + /// assert_relative_eq!( + /// circle.area().value, + /// Area::new::(f64::consts::PI).value + /// ); /// ``` pub fn from_diameter(diameter: Length) -> Sketch { Self::from_radius(diameter / 2.) diff --git a/src/sketches/primitives/rectangle.rs b/src/sketches/primitives/rectangle.rs index bb19b99..1548dbf 100644 --- a/src/sketches/primitives/rectangle.rs +++ b/src/sketches/primitives/rectangle.rs @@ -14,10 +14,16 @@ impl Rectangle { /// # Example /// ```rust /// use anvil::{IntoLength, Rectangle, point}; + /// use approx::assert_relative_eq; + /// use uom::si::area::square_meter; + /// use uom::si::f64::Area; /// /// let rect = Rectangle::from_dim(1.m(), 1.m()); - /// assert_eq!(rect.area(), 1.); /// assert_eq!(rect.center(), Ok(point!(0, 0))); + /// assert_relative_eq!( + /// rect.area().value, + /// Area::new::(1.).value + /// ); /// ``` pub fn from_dim(x: Length, y: Length) -> Sketch { Self::from_corners(point!(x * -0.5, y * -0.5), point!(x * 0.5, y * 0.5)) @@ -28,9 +34,15 @@ impl Rectangle { /// # Example /// ```rust /// use anvil::{IntoLength, Rectangle, point}; + /// use approx::assert_relative_eq; + /// use uom::si::area::square_meter; + /// use uom::si::f64::Area; /// /// let rect = Rectangle::from_corners(point!(0, 0), point!(2.m(), 2.m())); - /// assert_eq!(rect.area(), 4.); + /// assert_relative_eq!( + /// rect.area().value, + /// Area::new::(4.).value + /// ); /// ``` pub fn from_corners(corner1: Point<2>, corner2: Point<2>) -> Sketch { if corner1.x() == corner2.x() || corner1.y() == corner2.y() { diff --git a/src/sketches/primitives/square.rs b/src/sketches/primitives/square.rs index 6bebb9d..f982179 100644 --- a/src/sketches/primitives/square.rs +++ b/src/sketches/primitives/square.rs @@ -11,12 +11,17 @@ impl Square { /// /// # Example /// ```rust - /// use anvil::{Square, IntoLength, Sketch, point}; + /// use anvil::{Square, IntoLength, point}; /// use approx::assert_relative_eq; + /// use uom::si::area::square_meter; + /// use uom::si::f64::Area; /// - /// let Sketch = Square::from_size(1.m()); - /// assert_eq!(Sketch.center(), Ok(point!(0, 0))); - /// assert_relative_eq!(Sketch.area(), 1.); + /// let square = Square::from_size(1.m()); + /// assert_eq!(square.center(), Ok(point!(0, 0))); + /// assert_relative_eq!( + /// square.area().value, + /// Area::new::(1.).value + /// ); /// ``` pub fn from_size(size: Length) -> Sketch { Rectangle::from_dim(size, size) diff --git a/src/sketches/sketch.rs b/src/sketches/sketch.rs index a581996..1991582 100644 --- a/src/sketches/sketch.rs +++ b/src/sketches/sketch.rs @@ -3,6 +3,8 @@ use std::vec; use cxx::UniquePtr; use opencascade_sys::ffi; use uom::si::angle::radian; +use uom::si::area::square_meter; +use uom::si::f64::Area; use uom::si::length::meter; use crate::{Angle, Axis, Edge, Error, Face, IntoAngle, IntoLength, Length, Part, Plane, Point}; @@ -15,9 +17,11 @@ impl Sketch { /// /// ```rust /// use anvil::Sketch; + /// use uom::si::area::square_meter; + /// use uom::si::f64::Area; /// /// let sketch = Sketch::empty(); - /// assert_eq!(sketch.area(), 0.); + /// assert_eq!(sketch.area(), Area::new::(0.)); /// ``` pub fn empty() -> Self { Self(vec![]) @@ -30,19 +34,19 @@ impl Sketch { /// Return the area occupied by this `Sketch` in square meters. /// - /// Warning: the area is susceptibility to floating point errors. - /// /// ```rust /// use anvil::{Rectangle, IntoLength}; + /// use uom::si::f64::Area; + /// use uom::si::area::square_meter; /// use approx::assert_relative_eq; /// /// let sketch = Rectangle::from_dim(2.m(), 3.m()); - /// assert_relative_eq!(sketch.area(), 6.) + /// assert_relative_eq!(sketch.area().value, Area::new::(6.).value) /// ``` - pub fn area(&self) -> f64 { + pub fn area(&self) -> Area { match self.to_occt(Plane::xy()) { Ok(occt) => occt_area(&occt), - Err(_) => 0., + Err(_) => Area::new::(0.), } } /// Return the center of mass of the `Sketch`. @@ -327,8 +331,8 @@ impl PartialEq for Sketch { match self.intersect(other).to_occt(Plane::xy()) { Ok(intersection) => { - (occt_area(&intersection) - self.area()).abs() < 1e-7 - && (occt_area(&intersection) - other.area()).abs() < 1e-7 + (occt_area(&intersection) - self.area()).abs().value < 1e-7 + && (occt_area(&intersection) - other.area()).abs().value < 1e-7 } Err(_) => true, } @@ -356,10 +360,10 @@ fn edges_to_occt(edges: &[Edge], plane: Plane) -> Result f64 { +fn occt_area(occt: &ffi::TopoDS_Shape) -> Area { let mut gprops = ffi::GProp_GProps_ctor(); ffi::BRepGProp_SurfaceProperties(occt, gprops.pin_mut()); - gprops.Mass() + Area::new::(gprops.Mass()) } fn occt_center(occt: &ffi::TopoDS_Shape) -> Point<3> { @@ -405,7 +409,7 @@ impl SketchAction { (Some(self_shape), Some(other_shape)) => { let mut operation = ffi::BRepAlgoAPI_Common_ctor(&self_shape, &other_shape); let new_shape = ffi::TopoDS_Shape_to_owned(operation.pin_mut().Shape()); - if occt_area(&new_shape) == 0. { + if occt_area(&new_shape) == Area::new::(0.) { None } else { Some(new_shape) From 91fc5223ff476d9d39651a8e66ce0c05434e78b3 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sun, 29 Jun 2025 13:17:52 +0200 Subject: [PATCH 07/10] refactor: remove Cuboid::from_m and Cuboid::from_mm --- src/parts/methods/center.rs | 2 +- src/parts/methods/eq.rs | 8 +++--- src/parts/methods/move_to.rs | 10 ++++---- src/parts/primitives/cuboid.rs | 46 +--------------------------------- 4 files changed, 11 insertions(+), 55 deletions(-) diff --git a/src/parts/methods/center.rs b/src/parts/methods/center.rs index d3aaaa2..3bf1acd 100644 --- a/src/parts/methods/center.rs +++ b/src/parts/methods/center.rs @@ -47,7 +47,7 @@ mod tests { #[test] fn centre_at_origin() { - let cuboid = Cuboid::from_m(1., 1., 1.); + let cuboid = Cuboid::from_dim(1.m(), 1.m(), 1.m()); assert_eq!(cuboid.center(), Ok(point!(0, 0, 0))) } diff --git a/src/parts/methods/eq.rs b/src/parts/methods/eq.rs index 44aaf3b..258b681 100644 --- a/src/parts/methods/eq.rs +++ b/src/parts/methods/eq.rs @@ -28,15 +28,15 @@ mod tests { #[test] fn eq_both_cuboid() { - let cuboid1 = Cuboid::from_m(1., 1., 1.); - let cuboid2 = Cuboid::from_m(1., 1., 1.); + let cuboid1 = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + let cuboid2 = Cuboid::from_dim(1.m(), 1.m(), 1.m()); assert_eq!(cuboid1, cuboid2) } #[test] fn neq_both_cuboid() { - let cuboid1 = Cuboid::from_m(1., 1., 1.); - let cuboid2 = Cuboid::from_m(2., 2., 2.); + let cuboid1 = Cuboid::from_dim(1.m(), 1.m(), 1.m()); + let cuboid2 = Cuboid::from_dim(2.m(), 2.m(), 2.m()); assert_ne!(cuboid1, cuboid2) } diff --git a/src/parts/methods/move_to.rs b/src/parts/methods/move_to.rs index 692cd95..1736f0c 100644 --- a/src/parts/methods/move_to.rs +++ b/src/parts/methods/move_to.rs @@ -33,7 +33,7 @@ mod tests { #[test] fn move_to_deepcopied() { - let cuboid1 = Cuboid::from_m(1., 1., 1.); + let cuboid1 = Cuboid::from_dim(1.m(), 1.m(), 1.m()); let loc = point!(2.m(), 2.m(), 2.m()); let cuboid2 = cuboid1.move_to(loc); @@ -43,21 +43,21 @@ mod tests { #[test] fn part_move_to_twice() { - let part = Cuboid::from_m(1., 1., 1.); + let part = Cuboid::from_dim(1.m(), 1.m(), 1.m()); assert_eq!( part.move_to(point!(1.m(), 1.m(), 1.m())) .move_to(point!(-1.m(), -1.m(), -1.m())), - Cuboid::from_m(1., 1., 1.).move_to(point!(-1.m(), -1.m(), -1.m())), + Cuboid::from_dim(1.m(), 1.m(), 1.m()).move_to(point!(-1.m(), -1.m(), -1.m())), ) } #[test] fn move_after_rotate_should_not_reset_rotate() { - let part = Cuboid::from_m(1., 1., 2.); + let part = Cuboid::from_dim(1.m(), 1.m(), 2.m()); assert_eq!( part.rotate_around(Axis::<3>::y(), 90.deg()) .move_to(Point::<3>::origin()), - Cuboid::from_m(2., 1., 1.) + Cuboid::from_dim(2.m(), 1.m(), 1.m()) ) } } diff --git a/src/parts/primitives/cuboid.rs b/src/parts/primitives/cuboid.rs index 5306fb7..ec7630c 100644 --- a/src/parts/primitives/cuboid.rs +++ b/src/parts/primitives/cuboid.rs @@ -1,5 +1,5 @@ use opencascade_sys::ffi; -use uom::si::length::{meter, millimeter}; +use uom::si::length::meter; use crate::{Length, Part, Point, core::is_zero, point}; @@ -64,50 +64,6 @@ impl Cuboid { Part::from_occt(cuboid.pin_mut().Shape()) } - /// Construct a centered cuboidal `Part` directly from the x, y, and z meter values. - /// - /// This function is primarily intended to simplify tests and should not be exptected in - /// similar structs. - /// - /// # Example - /// ```rust - /// use anvil::{Cuboid, IntoLength, Part}; - /// - /// assert_eq!( - /// Cuboid::from_m(1., 2., 3.), - /// Cuboid::from_dim(1.m(), 2.m(), 3.m()) - /// ) - /// ``` - pub fn from_m(x: f64, y: f64, z: f64) -> Part { - // todo: remove - Self::from_dim( - Length::new::(x), - Length::new::(y), - Length::new::(z), - ) - } - /// Construct a centered cuboidal `Part` directly from the x, y, and z millimeter values. - /// - /// This function is primarily intended to simplify tests and should not be exptected in - /// similar structs. - /// - /// # Example - /// ```rust - /// use anvil::{Cuboid, IntoLength, Part}; - /// - /// assert_eq!( - /// Cuboid::from_mm(1., 2., 3.), - /// Cuboid::from_dim(1.mm(), 2.mm(), 3.mm()) - /// ) - /// ``` - pub fn from_mm(x: f64, y: f64, z: f64) -> Part { - // todo: remove - Self::from_dim( - Length::new::(x), - Length::new::(y), - Length::new::(z), - ) - } } #[cfg(test)] From 5870723a7c4462a3c14c71b5672980583d87a968 Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Sun, 29 Jun 2025 13:18:28 +0200 Subject: [PATCH 08/10] refactor: remove Rectangle::from_m and Rectangle::from_mm --- src/sketches/primitives/rectangle.rs | 38 ---------------------------- 1 file changed, 38 deletions(-) diff --git a/src/sketches/primitives/rectangle.rs b/src/sketches/primitives/rectangle.rs index 1548dbf..230c6a5 100644 --- a/src/sketches/primitives/rectangle.rs +++ b/src/sketches/primitives/rectangle.rs @@ -1,5 +1,3 @@ -use uom::si::length::{meter, millimeter}; - use crate::{Length, Path, Point, Sketch, point}; /// Builder for a rectangular `Sketch`. @@ -54,42 +52,6 @@ impl Rectangle { .line_to(Point::<2>::new([corner1.x(), corner2.y()])) .close() } - - /// Construct a centered rectangular `Sketch` directly from the x and y meter values. - /// - /// This function is primarily intended to simplify tests and should not be exptected in - /// similar structs. - /// - /// # Example - /// ```rust - /// use anvil::{IntoLength, Rectangle}; - /// - /// assert_eq!( - /// Rectangle::from_m(1., 2.), - /// Rectangle::from_dim(1.m(), 2.m()) - /// ) - /// ``` - pub fn from_m(x: f64, y: f64) -> Sketch { - Self::from_dim(Length::new::(x), Length::new::(y)) - } - - /// Construct a centered rectangular `Sketch` directly from the x and y millimeter values. - /// - /// This function is primarily intended to simplify tests and should not be exptected in - /// similar structs. - /// - /// # Example - /// ```rust - /// use anvil::{IntoLength, Rectangle}; - /// - /// assert_eq!( - /// Rectangle::from_mm(1., 2.), - /// Rectangle::from_dim(1.mm(), 2.mm()) - /// ) - /// ``` - pub fn from_mm(x: f64, y: f64) -> Sketch { - Self::from_dim(Length::new::(x), Length::new::(y)) - } } #[cfg(test)] From fd0e4613c263dd7f79afcdf359ab28865eeb035b Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Thu, 3 Jul 2025 21:14:57 +0200 Subject: [PATCH 09/10] fix: pipeline floating point error --- src/parts/primitives/cylinder.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/parts/primitives/cylinder.rs b/src/parts/primitives/cylinder.rs index 5ba069f..7bf554c 100644 --- a/src/parts/primitives/cylinder.rs +++ b/src/parts/primitives/cylinder.rs @@ -16,10 +16,14 @@ impl Cylinder { /// use anvil::{Cylinder, IntoLength, Point, Part}; /// use uom::si::volume::cubic_meter; /// use uom::si::f64::Volume; + /// use approx::assert_relative_eq; /// /// let part = Cylinder::from_radius(1.m(), 2.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert_eq!(part.volume(), Volume::new::(6.283185307179587)); + /// assert_relative_eq!( + /// part.volume().value, + /// Volume::new::(6.283185307179587).value + /// ); /// ``` pub fn from_radius(radius: Length, height: Length) -> Part { if is_zero(&[radius, height]) { @@ -36,7 +40,6 @@ impl Cylinder { /// Construct a centered cylindrical `Part` from a given diameter. /// - /// # Example /// ```rust /// use anvil::{Cylinder, IntoLength, Point, Part}; /// use uom::si::volume::cubic_meter; @@ -45,7 +48,10 @@ impl Cylinder { /// /// let part = Cylinder::from_diameter(1.m(), 2.m()); /// assert_eq!(part.center(), Ok(Point::<3>::origin())); - /// assert_eq!(part.volume().value, Volume::new::(1.5707963267948968).value); + /// assert_relative_eq!( + /// part.volume().value, + /// Volume::new::(1.5707963267948968).value + /// ); /// ``` pub fn from_diameter(diameter: Length, height: Length) -> Part { Self::from_radius(diameter / 2., height) From c75f13d71f239d51d9da07e6f45785a14181b59e Mon Sep 17 00:00:00 2001 From: unexcellent <> Date: Wed, 9 Jul 2025 19:47:55 +0200 Subject: [PATCH 10/10] fix: pipeline floating point error for good (hopefully) --- src/parts/primitives/cylinder.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/parts/primitives/cylinder.rs b/src/parts/primitives/cylinder.rs index 7bf554c..0a4d941 100644 --- a/src/parts/primitives/cylinder.rs +++ b/src/parts/primitives/cylinder.rs @@ -22,7 +22,8 @@ impl Cylinder { /// assert_eq!(part.center(), Ok(Point::<3>::origin())); /// assert_relative_eq!( /// part.volume().value, - /// Volume::new::(6.283185307179587).value + /// Volume::new::(6.283185307179585).value, + /// max_relative = 1e-9 /// ); /// ``` pub fn from_radius(radius: Length, height: Length) -> Part { @@ -50,7 +51,8 @@ impl Cylinder { /// assert_eq!(part.center(), Ok(Point::<3>::origin())); /// assert_relative_eq!( /// part.volume().value, - /// Volume::new::(1.5707963267948968).value + /// Volume::new::(1.5707963267948963).value, + /// max_relative = 1e-9 /// ); /// ``` pub fn from_diameter(diameter: Length, height: Length) -> Part {