diff --git a/src/errors.rs b/src/errors.rs index 4549eca..0d482ad 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,37 +1,59 @@ use crate::float_types::Real; use nalgebra::Point3; +use crate::float_types::rapier3d::prelude::TriMeshBuilderError; -/// All the possible validation issues we might encounter, -#[derive(Debug, Clone, PartialEq)] +/// The coordinate has a NaN or infinite +#[derive(Clone, Debug, thiserror::Error, PartialEq)] +pub enum PointError { + #[error("Point({}) has NaN fields", .0)] + NaN(Point3), + #[error("Point({}) has Infinite fields", .0)] + Infinite(Point3), +} + +/// All the possible validation issues we might encounter, with genaric errors and wrappers for suberrors +#[derive(Clone, Debug, thiserror::Error, PartialEq)] +#[non_exhaustive] pub enum ValidationError { - /// (RepeatedPoint) Two consecutive coords are identical + /// A [`PlaneError`](crate::plane::PlaneError) + #[error(transparent)] + PlaneError(#[from] crate::mesh::plane::PlaneError), + /// `name` must not be less then `min` + #[error("{} must be not be less then {}", .name, .min)] + FieldLessThen { name: &'static str, min: i32 }, + /// `name` must not be less then `min` + #[error("{} must be not be less then {}", .name, .min)] + FieldLessThenFloat { name: &'static str, min: Real }, + /// If a required index is higher then len + /// `name` must not be less or equal to 0.0 + #[error("{} must be not be >= 0", .name)] + Zero { name: &'static str }, + #[error("Face index {} is out of range (points.len = {})", .index, .len)] + IndexOutOfRange { index: usize, len: usize }, + /// A `Polygon` is non-planar or not on it's plane + #[error("A Polygon is non-planar or not on it's plane")] + NonPlanar, + /// Two consecutive coords are identical + #[error("Point({}) is repeated consecutively", .0)] RepeatedPoint(Point3), - /// (HoleOutsideShell) A hole is *not* contained by its outer shell - HoleOutsideShell(Point3), - /// (NestedHoles) A hole is nested inside another hole - NestedHoles(Point3), - /// (DisconnectedInterior) The interior is disconnected - DisconnectedInterior(Point3), - /// (SelfIntersection) A polygon self‐intersects - SelfIntersection(Point3), - /// (RingSelfIntersection) A linear ring has a self‐intersection - RingSelfIntersection(Point3), - /// (NestedShells) Two outer shells are nested incorrectly - NestedShells(Point3), - /// (TooFewPoints) A ring or line has fewer than the minimal #points - TooFewPoints(Point3), - /// (InvalidCoordinate) The coordinate has a NaN or infinite - InvalidCoordinate(Point3), - /// (RingNotClosed) The ring's first/last points differ - RingNotClosed(Point3), - /// (MismatchedVertices) operation requires polygons with same number of vertices - MismatchedVertices, - /// (IndexOutOfRange) operation requires polygons with same number of vertices - IndexOutOfRange, - /// (InvalidArguments) operation requires polygons with same number of vertices - InvalidArguments, - /// In general, anything else - Other(String, Option>), + /// A `Polygon`'s first/last points differ + #[error("Polygon not closed first({}) and last({}) points differ", .first, .last)] + NotClosed { first: Point3, last: Point3 }, + /// A operation requires polygons with same number of vertices + #[error("A operation requires polygons with same number of vertices, {} != {}", .0, .1)] + MismatchedVertices(usize, usize), + /// The coordinate has a NaN or infinite + #[error(transparent)] + InvalidCoordinate(#[from] PointError), + + // FIXME: Uncomment when https://github.com/georust/geo/pull/1375 is merged + // /// An error from spade triangulation + // #[cfg(feature = "delaunay")] + // #[error(transparent)] + // TriangulationError(#[from] geo::triangulate_spade::TriangulationError), + /// An inconsistency while building a triangle mesh + #[error(transparent)] + TriMeshError(#[from] TriMeshBuilderError), } // Plane::from_points "Degenerate polygon: vertices do not define a plane" diff --git a/src/main.rs b/src/main.rs index 8eca3a6..d8f891e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -199,7 +199,7 @@ fn main() { ]; let poly = Mesh::polyhedron(&points, &faces, None); #[cfg(feature = "stl-io")] - let _ = fs::write("stl/tetrahedron.stl", poly.to_stl_ascii("tetrahedron")); + let _ = fs::write("stl/tetrahedron.stl", poly.unwrap().to_stl_ascii("tetrahedron")); // 13) Text example (2D). Provide a valid TTF font data below: // (Replace "asar.ttf" with a real .ttf file in your project.) diff --git a/src/mesh/plane.rs b/src/mesh/plane.rs index bfb9c5c..1d290fe 100644 --- a/src/mesh/plane.rs +++ b/src/mesh/plane.rs @@ -88,6 +88,16 @@ pub const BACK: i8 = 2; /// on both the front **and** the back. pub const SPANNING: i8 = 3; +#[derive(Clone, Debug, thiserror::Error, PartialEq)] +pub enum PlaneError { + #[error("Degenerate polygon: vertices do not define a plane")] + /// If input vertices do not define a plane + DegenerateFromPoints, + /// If the normal of a plane is to smaller then an epsilon + #[error("DegenerateNormal: the normal of the plane, {}, is to small", .0)] + DegenerateNormal(Vector3), +} + /// A plane in 3D space defined by three points #[derive(Debug, Clone)] pub struct Plane { diff --git a/src/mesh/shapes.rs b/src/mesh/shapes.rs index bab2324..32d7bee 100644 --- a/src/mesh/shapes.rs +++ b/src/mesh/shapes.rs @@ -1,5 +1,6 @@ //! 3D Shapes as `Mesh`s +use crate::errors::ValidationError; use crate::float_types::{EPSILON, PI, Real, TAU}; use crate::mesh::Mesh; use crate::mesh::polygon::Polygon; @@ -484,7 +485,7 @@ impl Mesh { for &idx in face { // Ensure the index is valid if idx >= points.len() { - return Err(ValidationError::IndexOutOfRange); + return Err(ValidationError::IndexOutOfRange { index: idx, len: points.len() }); } let [x, y, z] = points[idx]; face_vertices.push(Vertex::new( @@ -729,7 +730,7 @@ impl Mesh { .iter() .map(|&[x, y, z]| [x * radius, y * radius, z * radius]) .collect(); - Self::polyhedron(&scaled, &faces, metadata) + Self::polyhedron(&scaled, &faces, metadata).unwrap() } /// Regular icosahedron scaled by `radius` @@ -785,7 +786,7 @@ impl Mesh { let faces_vec: Vec> = faces.iter().map(|f| f.to_vec()).collect(); - Self::polyhedron(&pts, &faces_vec, metadata).scale(factor, factor, factor) + Self::polyhedron(&pts, &faces_vec, metadata).unwrap().scale(factor, factor, factor) } /// Torus centred at the origin in the *XY* plane. diff --git a/src/sketch/extrudes.rs b/src/sketch/extrudes.rs index 2565bc5..eb98828 100644 --- a/src/sketch/extrudes.rs +++ b/src/sketch/extrudes.rs @@ -263,7 +263,7 @@ impl Sketch { ) -> Result, ValidationError> { let n = bottom.vertices.len(); if n != top.vertices.len() { - return Err(ValidationError::MismatchedVertices); + return Err(ValidationError::MismatchedVertices(n, top.vertices.len())); } // Conditionally flip the bottom polygon if requested. @@ -553,7 +553,7 @@ impl Sketch { /// Returns Mesh with revolution surfaces only pub fn revolve(&self, angle_degs: Real, segments: usize) -> Result, ValidationError> { if segments < 2 { - return Err(ValidationError::InvalidArguments); + return Err(ValidationError::FieldLessThen { name: "segments", min: 2 }); } let angle_radians = angle_degs.to_radians();