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
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>) {
let exterior_winding = face.exterior().winding();
for interior in face.interiors() {
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,
partial::{PartialFace, PartialObject},
services::Services,
validate::{FaceValidationError, Validate, ValidationError},
};
#[test]
fn face_invalid_interior_winding() -> anyhow::Result<()> {
let mut services = Services::new();
let valid = {
let mut face = PartialFace::new(&mut services.objects);
face.surface = Some(services.objects.surfaces.xy_plane());
face.exterior.write().update_as_polygon_from_points(
[[0., 0.], [3., 0.], [0., 3.]],
&mut services.objects,
);
face.add_interior(&mut services.objects)
.write()
.update_as_polygon_from_points(
[[1., 1.], [1., 2.], [2., 1.]],
&mut services.objects,
);
face.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(())
}
}