1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
use fj_math::Winding;
use crate::objects::Face;
use super::{Validate, ValidationConfig, ValidationError};
impl Validate for Face {
fn validate_with_config(
&self,
_: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
FaceValidationError::check_interior_winding(self, errors);
}
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum FaceValidationError {
#[error(
"Interior of `Face` has invalid winding; must be opposite of exterior\n\
- Winding of exterior cycle: {exterior_winding:#?}\n\
- Winding of interior cycle: {interior_winding:#?}\n\
- `Face`: {face:#?}"
)]
InvalidInteriorWinding {
exterior_winding: Winding,
interior_winding: Winding,
face: Face,
},
}
impl FaceValidationError {
fn check_interior_winding(face: &Face, errors: &mut Vec<ValidationError>) {
if face.exterior().half_edges().count() == 0 {
return;
}
let exterior_winding = face.exterior().winding();
for interior in face.interiors() {
if interior.half_edges().count() == 0 {
continue;
}
let interior_winding = interior.winding();
if exterior_winding == interior_winding {
errors.push(
Self::InvalidInteriorWinding {
exterior_winding,
interior_winding,
face: face.clone(),
}
.into(),
);
}
}
}
}
#[cfg(test)]
mod tests {
use crate::{
algorithms::reverse::Reverse,
builder::{CycleBuilder, FaceBuilder},
objects::Face,
services::Services,
validate::{FaceValidationError, Validate, ValidationError},
};
#[test]
fn face_invalid_interior_winding() -> anyhow::Result<()> {
let mut services = Services::new();
let valid = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon([
[0., 0.],
[3., 0.],
[0., 3.],
]))
.with_interior(CycleBuilder::polygon([
[1., 1.],
[1., 2.],
[2., 1.],
]))
.build(&mut services.objects);
let invalid = {
let interiors = valid
.interiors()
.cloned()
.map(|cycle| cycle.reverse(&mut services.objects))
.collect::<Vec<_>>();
Face::new(
valid.surface().clone(),
valid.exterior().clone(),
interiors,
valid.color(),
)
};
valid.validate_and_return_first_error()?;
assert!(matches!(
invalid.validate_and_return_first_error(),
Err(ValidationError::Face(
FaceValidationError::InvalidInteriorWinding { .. }
))
));
Ok(())
}
}