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