Skip to content

Commit 1977b97

Browse files
authored
Merge pull request #2277 from hannobraun/validation
Port face boundary check to new validation infrastructure
2 parents fee4dba + d3ecc7a commit 1977b97

File tree

5 files changed

+97
-55
lines changed

5 files changed

+97
-55
lines changed

crates/fj-core/src/validate/face.rs

Lines changed: 11 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,31 @@ use fj_math::Winding;
33
use crate::{
44
geometry::Geometry,
55
objects::Face,
6-
validation::{ValidationConfig, ValidationError},
6+
validation::{
7+
checks::FaceHasNoBoundary, ValidationCheck, ValidationConfig,
8+
ValidationError,
9+
},
710
};
811

912
use super::Validate;
1013

1114
impl Validate for Face {
1215
fn validate(
1316
&self,
14-
_: &ValidationConfig,
17+
config: &ValidationConfig,
1518
errors: &mut Vec<ValidationError>,
1619
geometry: &Geometry,
1720
) {
18-
FaceValidationError::check_boundary(self, errors);
21+
errors.extend(
22+
FaceHasNoBoundary::check(self, geometry, config).map(Into::into),
23+
);
1924
FaceValidationError::check_interior_winding(self, geometry, errors);
2025
}
2126
}
2227

2328
/// [`Face`] validation error
2429
#[derive(Clone, Debug, thiserror::Error)]
2530
pub enum FaceValidationError {
26-
/// The [`Face`] has no exterior cycle
27-
#[error("The `Face` has no exterior cycle")]
28-
MissingBoundary,
29-
3031
/// Interior of [`Face`] has invalid winding; must be opposite of exterior
3132
#[error(
3233
"Interior of `Face` has invalid winding; must be opposite of exterior\n\
@@ -47,15 +48,6 @@ pub enum FaceValidationError {
4748
}
4849

4950
impl FaceValidationError {
50-
fn check_boundary(face: &Face, errors: &mut Vec<ValidationError>) {
51-
if face.region().exterior().half_edges().is_empty() {
52-
errors.push(ValidationError::from(Self::MissingBoundary));
53-
}
54-
55-
// Checking *that* a boundary exists is enough. There are validation
56-
// checks for `Cycle` to make sure that the cycle is closed properly.
57-
}
58-
5951
fn check_interior_winding(
6052
face: &Face,
6153
geometry: &Geometry,
@@ -95,50 +87,19 @@ impl FaceValidationError {
9587
mod tests {
9688
use crate::{
9789
assert_contains_err,
98-
objects::{Cycle, Face, HalfEdge, Region},
90+
objects::{Cycle, Face, Region},
9991
operations::{
100-
build::{BuildCycle, BuildFace, BuildHalfEdge},
92+
build::{BuildCycle, BuildFace},
10193
derive::DeriveFrom,
10294
insert::Insert,
10395
reverse::Reverse,
104-
update::{UpdateCycle, UpdateFace, UpdateRegion},
96+
update::{UpdateFace, UpdateRegion},
10597
},
10698
validate::{FaceValidationError, Validate},
10799
validation::ValidationError,
108100
Core,
109101
};
110102

111-
#[test]
112-
fn boundary() -> anyhow::Result<()> {
113-
let mut core = Core::new();
114-
115-
let invalid =
116-
Face::unbound(core.layers.objects.surfaces.xy_plane(), &mut core);
117-
let valid = invalid.update_region(
118-
|region, core| {
119-
region.update_exterior(
120-
|cycle, core| {
121-
cycle.add_half_edges(
122-
[HalfEdge::circle([0., 0.], 1., core)],
123-
core,
124-
)
125-
},
126-
core,
127-
)
128-
},
129-
&mut core,
130-
);
131-
132-
valid.validate_and_return_first_error(&core.layers.geometry)?;
133-
assert_contains_err!(
134-
core,
135-
invalid,
136-
ValidationError::Face(FaceValidationError::MissingBoundary)
137-
);
138-
139-
Ok(())
140-
}
141-
142103
#[test]
143104
fn interior_winding() -> anyhow::Result<()> {
144105
let mut core = Core::new();
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use crate::{
2+
geometry::Geometry,
3+
objects::Face,
4+
validation::{ValidationCheck, ValidationConfig},
5+
};
6+
7+
/// [`Face`] has no boundary
8+
///
9+
/// A face must have a boundary, meaning its exterior cycle must not be empty.
10+
/// Checking *that* the exterior cycle is not empty is enough, as
11+
/// [`AdjacentHalfEdgesNotConnected`] makes sure that any cycle that is not
12+
/// empty, is closed.
13+
///
14+
/// [`AdjacentHalfEdgesNotConnected`]: super::AdjacentHalfEdgesNotConnected
15+
#[derive(Clone, Debug, thiserror::Error)]
16+
#[error("`Face` has no boundary")]
17+
pub struct FaceHasNoBoundary {}
18+
19+
impl ValidationCheck<Face> for FaceHasNoBoundary {
20+
fn check(
21+
object: &Face,
22+
_: &Geometry,
23+
_: &ValidationConfig,
24+
) -> impl Iterator<Item = Self> {
25+
let error = if object.region().exterior().half_edges().is_empty() {
26+
Some(FaceHasNoBoundary {})
27+
} else {
28+
None
29+
};
30+
31+
error.into_iter()
32+
}
33+
}
34+
35+
#[cfg(test)]
36+
mod tests {
37+
use crate::{
38+
objects::{Cycle, Face},
39+
operations::{
40+
build::{BuildCycle, BuildFace},
41+
update::{UpdateFace, UpdateRegion},
42+
},
43+
validation::{checks::FaceHasNoBoundary, ValidationCheck},
44+
Core,
45+
};
46+
47+
#[test]
48+
fn face_has_no_boundary() -> anyhow::Result<()> {
49+
let mut core = Core::new();
50+
51+
let valid = Face::circle(
52+
core.layers.objects.surfaces.xy_plane(),
53+
[0., 0.],
54+
1.,
55+
&mut core,
56+
);
57+
FaceHasNoBoundary::check_and_return_first_error(
58+
&valid,
59+
&core.layers.geometry,
60+
)?;
61+
62+
let invalid = valid.update_region(
63+
|region, core| region.update_exterior(|_, _| Cycle::empty(), core),
64+
&mut core,
65+
);
66+
FaceHasNoBoundary::check_and_expect_one_error(
67+
&invalid,
68+
&core.layers.geometry,
69+
);
70+
71+
Ok(())
72+
}
73+
}

crates/fj-core/src/validation/checks/half_edge_connection.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ mod tests {
8888
use super::AdjacentHalfEdgesNotConnected;
8989

9090
#[test]
91-
fn adjacent_half_edges_connected() -> anyhow::Result<()> {
91+
fn adjacent_half_edges_not_connected() -> anyhow::Result<()> {
9292
let mut core = Core::new();
9393

9494
let valid = Cycle::polygon([[0., 0.], [1., 0.], [1., 1.]], &mut core);

crates/fj-core/src/validation/checks/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
//!
33
//! See documentation of [parent module](super) for more information.
44
5+
mod face_boundary;
56
mod half_edge_connection;
67

7-
pub use self::half_edge_connection::AdjacentHalfEdgesNotConnected;
8+
pub use self::{
9+
face_boundary::FaceHasNoBoundary,
10+
half_edge_connection::AdjacentHalfEdgesNotConnected,
11+
};

crates/fj-core/src/validation/error.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ use crate::validate::{
55
SketchValidationError, SolidValidationError,
66
};
77

8-
use super::checks::AdjacentHalfEdgesNotConnected;
8+
use super::checks::{AdjacentHalfEdgesNotConnected, FaceHasNoBoundary};
99

1010
/// An error that can occur during a validation
1111
#[derive(Clone, Debug, thiserror::Error)]
1212
pub enum ValidationError {
13-
/// `HalfEdge`s in `Cycle` not connected
13+
/// Adjacent half-edges are not connected
1414
#[error(transparent)]
15-
HalfEdgesInCycleNotConnected(#[from] AdjacentHalfEdgesNotConnected),
15+
AdjacentHalfEdgesNotConnected(#[from] AdjacentHalfEdgesNotConnected),
16+
17+
/// Face has no boundary
18+
#[error(transparent)]
19+
FaceHasNoBoundary(#[from] FaceHasNoBoundary),
1620

1721
/// `Edge` validation error
1822
#[error("`Edge` validation error")]

0 commit comments

Comments
 (0)