Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Put new validation infrastructure in place #705

Merged
merged 9 commits into from
Jun 20, 2022
3 changes: 2 additions & 1 deletion crates/fj-kernel/src/algorithms/sweep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use fj_math::{Line, Scalar, Transform, Triangle, Vector};

use crate::{
objects::{Curve, Cycle, Edge, Face, Surface, SweptCurve, Vertex},
shape::{Handle, LocalForm, Mapping, Shape, ValidationError},
shape::{Handle, LocalForm, Mapping, Shape},
validation::ValidationError,
};

use super::{transform_shape, CycleApprox, Tolerance};
Expand Down
3 changes: 2 additions & 1 deletion crates/fj-kernel/src/algorithms/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use fj_math::Transform;

use crate::{
objects::{Curve, Face, Surface, Vertex},
shape::{Shape, ValidationError},
shape::Shape,
validation::ValidationError,
};

/// Transform the geometry of the shape
Expand Down
3 changes: 2 additions & 1 deletion crates/fj-kernel/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ use fj_math::{Circle, Line, Point, Scalar, Vector};

use crate::{
objects::{Curve, Cycle, Edge, Face, Surface, Vertex, VerticesOfEdge},
shape::{Handle, LocalForm, Shape, ValidationError, ValidationResult},
shape::{Handle, LocalForm, Shape, ValidationResult},
validation::ValidationError,
};

/// API for building a [`Vertex`]
Expand Down
1 change: 1 addition & 0 deletions crates/fj-kernel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,4 @@ pub mod builder;
pub mod geometry;
pub mod objects;
pub mod shape;
pub mod validation;
10 changes: 7 additions & 3 deletions crates/fj-kernel/src/shape/api.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
use fj_math::Scalar;

use crate::objects::{Curve, Cycle, Edge, Face, Surface, Vertex};
use crate::{
objects::{Curve, Cycle, Edge, Face, Surface, Vertex},
validation::ValidationError,
};

use super::{
stores::{Store, Stores},
Handle, Iter, Mapping, Object, Update, ValidationError, ValidationResult,
Handle, Iter, Mapping, Object, Update, ValidationResult,
};

/// The boundary representation of a shape
Expand Down Expand Up @@ -293,7 +296,8 @@ mod tests {

use crate::{
objects::{Curve, Cycle, Edge, Face, Surface, Vertex, VerticesOfEdge},
shape::{Handle, LocalForm, Shape, ValidationError, ValidationResult},
shape::{Handle, LocalForm, Shape, ValidationResult},
validation::ValidationError,
};

#[test]
Expand Down
2 changes: 1 addition & 1 deletion crates/fj-kernel/src/shape/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ pub use self::{
update::Update,
validate::{
CoherenceIssues, CoherenceMismatch, DuplicateEdge, StructuralIssues,
UniquenessIssues, ValidationError, ValidationResult,
UniquenessIssues, ValidationResult,
},
};
8 changes: 4 additions & 4 deletions crates/fj-kernel/src/shape/object.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::objects::{
Curve, Cycle, Edge, Face, Surface, Vertex, VerticesOfEdge,
use crate::{
objects::{Curve, Cycle, Edge, Face, Surface, Vertex, VerticesOfEdge},
validation::ValidationError,
};

use super::{
validate::Validate, Handle, LocalForm, Mapping, Shape, ValidationError,
ValidationResult,
validate::Validate, Handle, LocalForm, Mapping, Shape, ValidationResult,
};

/// Marker trait for geometric and topological objects
Expand Down
4 changes: 3 additions & 1 deletion crates/fj-kernel/src/shape/update.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
use fj_math::Scalar;

use super::{stores::Stores, validate::Validate as _, Object, ValidationError};
use crate::validation::ValidationError;

use super::{stores::Stores, validate::Validate as _, Object};

/// API to update a `Shape`
///
Expand Down
99 changes: 4 additions & 95 deletions crates/fj-kernel/src/shape/validate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ pub use self::{

use fj_math::Scalar;

use crate::objects::{Curve, Cycle, Edge, Face, Surface, Vertex};
use crate::{
objects::{Curve, Cycle, Edge, Face, Surface, Vertex},
validation::ValidationError,
};

use super::{stores::Stores, Handle, Object};

Expand Down Expand Up @@ -122,97 +125,3 @@ impl Validate for Face {

/// Returned by the various `add_` methods of the [`Shape`] API
pub type ValidationResult<T> = Result<Handle<T>, ValidationError>;

/// An error that can occur during a validation
#[allow(clippy::large_enum_variant)]
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
/// Coherence validation failed
///
/// Coherence validation verifies, that local forms of an objects are
/// consistent with their canonical forms.
#[error("Coherence validation failed")]
Coherence(#[from] CoherenceIssues),

/// Geometric validation failed
///
/// Geometric validation verifies, that various geometric constraints of an
/// object are upheld. For example, edges or faces might not be allowed to
/// intersect.
#[error("Geometric validation failed")]
Geometric,

/// Structural validation failed
///
/// Structural validation verifies, that all the object that an object
/// refers to are already part of the shape.
#[error("Structural validation failed")]
Structural(#[from] StructuralIssues),

/// Uniqueness validation failed
///
/// Uniqueness validation verifies, that an object is unique. Uniqueness is
/// only required for topological objects, as there's no harm in geometric
/// objects being duplicated.
#[error("Uniqueness validation failed")]
Uniqueness(#[from] UniquenessIssues),
}

impl ValidationError {
/// Indicate whether validation found a missing curve
#[cfg(test)]
pub fn missing_curve(&self, curve: &Handle<Curve<3>>) -> bool {
if let Self::Structural(StructuralIssues { missing_curve, .. }) = self {
return missing_curve.as_ref() == Some(curve);
}

false
}

/// Indicate whether validation found a missing vertex
#[cfg(test)]
pub fn missing_vertex(&self, vertex: &Handle<Vertex>) -> bool {
if let Self::Structural(StructuralIssues {
missing_vertices, ..
}) = self
{
return missing_vertices.contains(vertex);
}

false
}

/// Indicate whether validation found a missing edge
#[cfg(test)]
pub fn missing_edge(&self, edge: &Handle<Edge<3>>) -> bool {
if let Self::Structural(StructuralIssues { missing_edges, .. }) = self {
return missing_edges.contains(edge);
}

false
}

/// Indicate whether validation found a missing surface
#[cfg(test)]
pub fn missing_surface(&self, surface: &Handle<Surface>) -> bool {
if let Self::Structural(StructuralIssues {
missing_surface, ..
}) = self
{
return missing_surface.as_ref() == Some(surface);
}

false
}

/// Indicate whether validation found a missing cycle
#[cfg(test)]
pub fn missing_cycle(&self, cycle: &Handle<Cycle<3>>) -> bool {
if let Self::Structural(StructuralIssues { missing_cycles, .. }) = self
{
return missing_cycles.contains(cycle);
}

false
}
}
166 changes: 166 additions & 0 deletions crates/fj-kernel/src/validation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
//! Infrastructure for validating shapes
//!
//! Validation enforces various constraints about shapes and the objects that
//! constitute them. These constraints fall into 4 categories:
//!
//! - **Coherence:** Local forms of objects must be consistent with their
//! canonical forms.
//! - **Geometric:** Comprises various object-specific constraints, for example
//! edges or faces might not be allowed to intersect.
//! - **Structural:** All other objects that an object references must be part
//! of the same shape.
//! - **Uniqueness:** Objects within a shape must be unique.
//!
//! Please note that not all of these validation categories are fully
//! implemented, as of this writing.
//!
//! # Implementation Note
//!
//! There is an ongoing effort to abolish [`Shape`] and replace it with a much
//! simpler data structure:
//! https://github.com/hannobraun/Fornjot/issues/697
//!
//! Once completed, this would make structural and uniqueness validation moot.
//!
//! [`Shape`]: crate::shape::Shape

use std::ops::Deref;

use fj_math::Scalar;

use crate::{
objects::{Curve, Cycle, Edge, Surface, Vertex},
shape::{
CoherenceIssues, Handle, Shape, StructuralIssues, UniquenessIssues,
},
};

/// Validate the given [`Shape`]
pub fn validate(
shape: Shape,
_: &Config,
) -> Result<Validated<Shape>, ValidationError> {
Ok(Validated(shape))
}

/// Configuration required for the validation process
#[derive(Debug, Clone, Copy)]
pub struct Config {
/// The minimum distance between distinct objects
///
/// Objects whose distance is less than the value defined in this field, are
/// considered identical.
pub distinct_min_distance: Scalar,

/// The maximum distance between identical objects
///
/// Objects that are considered identical might still have a distance
/// between them, due to inaccuracies of the numerical representation. If
/// that distance is less than the one defined in this field, can not be
/// considered identical.
pub identical_max_distance: Scalar,
}

impl Default for Config {
fn default() -> Self {
Self {
distinct_min_distance: Scalar::from_f64(5e-7), // 0.5 µm,
identical_max_distance: Scalar::from_f64(5e-14),
}
}
}

/// Wrapper around an object that indicates the object has been validated
///
/// Returned by implementations of `Validate`.
pub struct Validated<T>(T);

impl<T> Validated<T> {
/// Consume this instance of `Validated` and return the wrapped object
pub fn into_inner(self) -> T {
self.0
}
}

impl<T> Deref for Validated<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// An error that can occur during a validation
#[allow(clippy::large_enum_variant)]
#[derive(Debug, thiserror::Error)]
pub enum ValidationError {
/// Coherence validation failed
#[error("Coherence validation failed")]
Coherence(#[from] CoherenceIssues),

/// Geometric validation failed
#[error("Geometric validation failed")]
Geometric,

/// Structural validation failed
#[error("Structural validation failed")]
Structural(#[from] StructuralIssues),

/// Uniqueness validation failed
#[error("Uniqueness validation failed")]
Uniqueness(#[from] UniquenessIssues),
}

impl ValidationError {
/// Indicate whether validation found a missing curve
pub fn missing_curve(&self, curve: &Handle<Curve<3>>) -> bool {
if let Self::Structural(StructuralIssues { missing_curve, .. }) = self {
return missing_curve.as_ref() == Some(curve);
}

false
}

/// Indicate whether validation found a missing vertex
pub fn missing_vertex(&self, vertex: &Handle<Vertex>) -> bool {
if let Self::Structural(StructuralIssues {
missing_vertices, ..
}) = self
{
return missing_vertices.contains(vertex);
}

false
}

/// Indicate whether validation found a missing edge
pub fn missing_edge(&self, edge: &Handle<Edge<3>>) -> bool {
if let Self::Structural(StructuralIssues { missing_edges, .. }) = self {
return missing_edges.contains(edge);
}

false
}

/// Indicate whether validation found a missing surface
pub fn missing_surface(&self, surface: &Handle<Surface>) -> bool {
if let Self::Structural(StructuralIssues {
missing_surface, ..
}) = self
{
return missing_surface.as_ref() == Some(surface);
}

false
}

/// Indicate whether validation found a missing cycle
pub fn missing_cycle(&self, cycle: &Handle<Cycle<3>>) -> bool {
if let Self::Structural(StructuralIssues { missing_cycles, .. }) = self
{
return missing_cycles.contains(cycle);
}

false
}
}
Loading