mirror of
https://github.com/hannobraun/Fornjot
synced 2025-01-11 02:37:00 +00:00
Merge pull request #2277 from hannobraun/validation
Port face boundary check to new validation infrastructure
This commit is contained in:
commit
1977b97024
@ -3,7 +3,10 @@ use fj_math::Winding;
|
|||||||
use crate::{
|
use crate::{
|
||||||
geometry::Geometry,
|
geometry::Geometry,
|
||||||
objects::Face,
|
objects::Face,
|
||||||
validation::{ValidationConfig, ValidationError},
|
validation::{
|
||||||
|
checks::FaceHasNoBoundary, ValidationCheck, ValidationConfig,
|
||||||
|
ValidationError,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::Validate;
|
use super::Validate;
|
||||||
@ -11,11 +14,13 @@ use super::Validate;
|
|||||||
impl Validate for Face {
|
impl Validate for Face {
|
||||||
fn validate(
|
fn validate(
|
||||||
&self,
|
&self,
|
||||||
_: &ValidationConfig,
|
config: &ValidationConfig,
|
||||||
errors: &mut Vec<ValidationError>,
|
errors: &mut Vec<ValidationError>,
|
||||||
geometry: &Geometry,
|
geometry: &Geometry,
|
||||||
) {
|
) {
|
||||||
FaceValidationError::check_boundary(self, errors);
|
errors.extend(
|
||||||
|
FaceHasNoBoundary::check(self, geometry, config).map(Into::into),
|
||||||
|
);
|
||||||
FaceValidationError::check_interior_winding(self, geometry, errors);
|
FaceValidationError::check_interior_winding(self, geometry, errors);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,10 +28,6 @@ impl Validate for Face {
|
|||||||
/// [`Face`] validation error
|
/// [`Face`] validation error
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
pub enum FaceValidationError {
|
pub enum FaceValidationError {
|
||||||
/// The [`Face`] has no exterior cycle
|
|
||||||
#[error("The `Face` has no exterior cycle")]
|
|
||||||
MissingBoundary,
|
|
||||||
|
|
||||||
/// Interior of [`Face`] has invalid winding; must be opposite of exterior
|
/// Interior of [`Face`] has invalid winding; must be opposite of exterior
|
||||||
#[error(
|
#[error(
|
||||||
"Interior of `Face` has invalid winding; must be opposite of exterior\n\
|
"Interior of `Face` has invalid winding; must be opposite of exterior\n\
|
||||||
@ -47,15 +48,6 @@ pub enum FaceValidationError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FaceValidationError {
|
impl FaceValidationError {
|
||||||
fn check_boundary(face: &Face, errors: &mut Vec<ValidationError>) {
|
|
||||||
if face.region().exterior().half_edges().is_empty() {
|
|
||||||
errors.push(ValidationError::from(Self::MissingBoundary));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checking *that* a boundary exists is enough. There are validation
|
|
||||||
// checks for `Cycle` to make sure that the cycle is closed properly.
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_interior_winding(
|
fn check_interior_winding(
|
||||||
face: &Face,
|
face: &Face,
|
||||||
geometry: &Geometry,
|
geometry: &Geometry,
|
||||||
@ -95,50 +87,19 @@ impl FaceValidationError {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use crate::{
|
use crate::{
|
||||||
assert_contains_err,
|
assert_contains_err,
|
||||||
objects::{Cycle, Face, HalfEdge, Region},
|
objects::{Cycle, Face, Region},
|
||||||
operations::{
|
operations::{
|
||||||
build::{BuildCycle, BuildFace, BuildHalfEdge},
|
build::{BuildCycle, BuildFace},
|
||||||
derive::DeriveFrom,
|
derive::DeriveFrom,
|
||||||
insert::Insert,
|
insert::Insert,
|
||||||
reverse::Reverse,
|
reverse::Reverse,
|
||||||
update::{UpdateCycle, UpdateFace, UpdateRegion},
|
update::{UpdateFace, UpdateRegion},
|
||||||
},
|
},
|
||||||
validate::{FaceValidationError, Validate},
|
validate::{FaceValidationError, Validate},
|
||||||
validation::ValidationError,
|
validation::ValidationError,
|
||||||
Core,
|
Core,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn boundary() -> anyhow::Result<()> {
|
|
||||||
let mut core = Core::new();
|
|
||||||
|
|
||||||
let invalid =
|
|
||||||
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core);
|
|
||||||
let valid = invalid.update_region(
|
|
||||||
|region, core| {
|
|
||||||
region.update_exterior(
|
|
||||||
|cycle, core| {
|
|
||||||
cycle.add_half_edges(
|
|
||||||
[HalfEdge::circle([0., 0.], 1., core)],
|
|
||||||
core,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
core,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
&mut core,
|
|
||||||
);
|
|
||||||
|
|
||||||
valid.validate_and_return_first_error(&core.layers.geometry)?;
|
|
||||||
assert_contains_err!(
|
|
||||||
core,
|
|
||||||
invalid,
|
|
||||||
ValidationError::Face(FaceValidationError::MissingBoundary)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interior_winding() -> anyhow::Result<()> {
|
fn interior_winding() -> anyhow::Result<()> {
|
||||||
let mut core = Core::new();
|
let mut core = Core::new();
|
||||||
|
73
crates/fj-core/src/validation/checks/face_boundary.rs
Normal file
73
crates/fj-core/src/validation/checks/face_boundary.rs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
use crate::{
|
||||||
|
geometry::Geometry,
|
||||||
|
objects::Face,
|
||||||
|
validation::{ValidationCheck, ValidationConfig},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// [`Face`] has no boundary
|
||||||
|
///
|
||||||
|
/// A face must have a boundary, meaning its exterior cycle must not be empty.
|
||||||
|
/// Checking *that* the exterior cycle is not empty is enough, as
|
||||||
|
/// [`AdjacentHalfEdgesNotConnected`] makes sure that any cycle that is not
|
||||||
|
/// empty, is closed.
|
||||||
|
///
|
||||||
|
/// [`AdjacentHalfEdgesNotConnected`]: super::AdjacentHalfEdgesNotConnected
|
||||||
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
|
#[error("`Face` has no boundary")]
|
||||||
|
pub struct FaceHasNoBoundary {}
|
||||||
|
|
||||||
|
impl ValidationCheck<Face> for FaceHasNoBoundary {
|
||||||
|
fn check(
|
||||||
|
object: &Face,
|
||||||
|
_: &Geometry,
|
||||||
|
_: &ValidationConfig,
|
||||||
|
) -> impl Iterator<Item = Self> {
|
||||||
|
let error = if object.region().exterior().half_edges().is_empty() {
|
||||||
|
Some(FaceHasNoBoundary {})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
error.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
objects::{Cycle, Face},
|
||||||
|
operations::{
|
||||||
|
build::{BuildCycle, BuildFace},
|
||||||
|
update::{UpdateFace, UpdateRegion},
|
||||||
|
},
|
||||||
|
validation::{checks::FaceHasNoBoundary, ValidationCheck},
|
||||||
|
Core,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn face_has_no_boundary() -> anyhow::Result<()> {
|
||||||
|
let mut core = Core::new();
|
||||||
|
|
||||||
|
let valid = Face::circle(
|
||||||
|
core.layers.objects.surfaces.xy_plane(),
|
||||||
|
[0., 0.],
|
||||||
|
1.,
|
||||||
|
&mut core,
|
||||||
|
);
|
||||||
|
FaceHasNoBoundary::check_and_return_first_error(
|
||||||
|
&valid,
|
||||||
|
&core.layers.geometry,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let invalid = valid.update_region(
|
||||||
|
|region, core| region.update_exterior(|_, _| Cycle::empty(), core),
|
||||||
|
&mut core,
|
||||||
|
);
|
||||||
|
FaceHasNoBoundary::check_and_expect_one_error(
|
||||||
|
&invalid,
|
||||||
|
&core.layers.geometry,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -88,7 +88,7 @@ mod tests {
|
|||||||
use super::AdjacentHalfEdgesNotConnected;
|
use super::AdjacentHalfEdgesNotConnected;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn adjacent_half_edges_connected() -> anyhow::Result<()> {
|
fn adjacent_half_edges_not_connected() -> anyhow::Result<()> {
|
||||||
let mut core = Core::new();
|
let mut core = Core::new();
|
||||||
|
|
||||||
let valid = Cycle::polygon([[0., 0.], [1., 0.], [1., 1.]], &mut core);
|
let valid = Cycle::polygon([[0., 0.], [1., 0.], [1., 1.]], &mut core);
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
//!
|
//!
|
||||||
//! See documentation of [parent module](super) for more information.
|
//! See documentation of [parent module](super) for more information.
|
||||||
|
|
||||||
|
mod face_boundary;
|
||||||
mod half_edge_connection;
|
mod half_edge_connection;
|
||||||
|
|
||||||
pub use self::half_edge_connection::AdjacentHalfEdgesNotConnected;
|
pub use self::{
|
||||||
|
face_boundary::FaceHasNoBoundary,
|
||||||
|
half_edge_connection::AdjacentHalfEdgesNotConnected,
|
||||||
|
};
|
||||||
|
@ -5,14 +5,18 @@ use crate::validate::{
|
|||||||
SketchValidationError, SolidValidationError,
|
SketchValidationError, SolidValidationError,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::checks::AdjacentHalfEdgesNotConnected;
|
use super::checks::{AdjacentHalfEdgesNotConnected, FaceHasNoBoundary};
|
||||||
|
|
||||||
/// An error that can occur during a validation
|
/// An error that can occur during a validation
|
||||||
#[derive(Clone, Debug, thiserror::Error)]
|
#[derive(Clone, Debug, thiserror::Error)]
|
||||||
pub enum ValidationError {
|
pub enum ValidationError {
|
||||||
/// `HalfEdge`s in `Cycle` not connected
|
/// Adjacent half-edges are not connected
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
HalfEdgesInCycleNotConnected(#[from] AdjacentHalfEdgesNotConnected),
|
AdjacentHalfEdgesNotConnected(#[from] AdjacentHalfEdgesNotConnected),
|
||||||
|
|
||||||
|
/// Face has no boundary
|
||||||
|
#[error(transparent)]
|
||||||
|
FaceHasNoBoundary(#[from] FaceHasNoBoundary),
|
||||||
|
|
||||||
/// `Edge` validation error
|
/// `Edge` validation error
|
||||||
#[error("`Edge` validation error")]
|
#[error("`Edge` validation error")]
|
||||||
|
Loading…
Reference in New Issue
Block a user