1use crate::geometry::Geometry;
2use crate::{objects::Cycle, storage::Handle};
3use crate::{objects::Sketch, validate_references};
4use fj_math::Winding;
5
6use super::{
7 references::{ReferenceCountError, ReferenceCounter},
8 Validate, ValidationConfig, ValidationError,
9};
10
11impl Validate for Sketch {
12 fn validate(
13 &self,
14 config: &ValidationConfig,
15 errors: &mut Vec<ValidationError>,
16 geometry: &Geometry,
17 ) {
18 SketchValidationError::check_object_references(self, config, errors);
19 SketchValidationError::check_exterior_cycles(
20 self, geometry, config, errors,
21 );
22 SketchValidationError::check_interior_cycles(
23 self, geometry, config, errors,
24 );
25 }
26}
27
28#[derive(Clone, Debug, thiserror::Error)]
30pub enum SketchValidationError {
31 #[error("Object within sketch referenced by more than one other Object")]
33 MultipleReferences(#[from] ReferenceCountError),
34 #[error(
36 "Exterior cycle within sketch region has clockwise winding\n
37 Cycle: {cycle:#?}"
38 )]
39 ClockwiseExteriorCycle {
40 cycle: Handle<Cycle>,
42 },
43 #[error(
45 "Interior cycle within sketch region has counter-clockwise winding\n
46 Cycle: {cycle:#?}"
47 )]
48 CounterClockwiseInteriorCycle {
49 cycle: Handle<Cycle>,
51 },
52}
53
54impl SketchValidationError {
55 fn check_object_references(
56 sketch: &Sketch,
57 _config: &ValidationConfig,
58 errors: &mut Vec<ValidationError>,
59 ) {
60 let mut referenced_edges = ReferenceCounter::new();
61 let mut referenced_cycles = ReferenceCounter::new();
62
63 sketch.regions().iter().for_each(|r| {
64 r.all_cycles().for_each(|c| {
65 referenced_cycles.add_reference(c.clone(), r.clone());
66 c.half_edges().into_iter().for_each(|e| {
67 referenced_edges.add_reference(e.clone(), c.clone());
68 })
69 })
70 });
71
72 validate_references!(
73 errors, SketchValidationError;
74 referenced_edges, HalfEdge;
75 referenced_cycles, Cycle;
76 );
77 }
78
79 fn check_exterior_cycles(
80 sketch: &Sketch,
81 geometry: &Geometry,
82 _config: &ValidationConfig,
83 errors: &mut Vec<ValidationError>,
84 ) {
85 sketch.regions().iter().for_each(|region| {
86 let cycle = region.exterior();
87 if cycle.winding(geometry) == Winding::Cw {
88 errors.push(ValidationError::Sketch(
89 SketchValidationError::ClockwiseExteriorCycle {
90 cycle: cycle.clone(),
91 },
92 ))
93 }
94 });
95 }
96
97 fn check_interior_cycles(
98 sketch: &Sketch,
99 geometry: &Geometry,
100 _config: &ValidationConfig,
101 errors: &mut Vec<ValidationError>,
102 ) {
103 sketch.regions().iter().for_each(|region| {
104 region
105 .interiors()
106 .iter()
107 .filter(|interior| interior.winding(geometry) == Winding::Ccw)
108 .for_each(|cycle| {
109 errors.push(ValidationError::Sketch(
110 SketchValidationError::CounterClockwiseInteriorCycle {
111 cycle: cycle.clone(),
112 },
113 ));
114 })
115 });
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use crate::{
122 assert_contains_err,
123 objects::{Cycle, HalfEdge, Region, Sketch, Vertex},
124 operations::{
125 build::BuildHalfEdge, build::BuildRegion, insert::Insert,
126 },
127 validate::{
128 references::ReferenceCountError, SketchValidationError, Validate,
129 ValidationError,
130 },
131 Core,
132 };
133
134 #[test]
135 fn should_find_cycle_multiple_references() -> anyhow::Result<()> {
136 let mut core = Core::new();
137
138 let region = <Region as BuildRegion>::circle([0., 0.], 1., &mut core)
139 .insert(&mut core);
140 let valid_sketch = Sketch::new(vec![region.clone()]).insert(&mut core);
141 valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
142
143 let shared_cycle = region.exterior();
144 let invalid_sketch = Sketch::new(vec![
145 Region::new(shared_cycle.clone(), vec![]).insert(&mut core),
146 Region::new(shared_cycle.clone(), vec![]).insert(&mut core),
147 ]);
148 assert_contains_err!(
149 core,
150 invalid_sketch,
151 ValidationError::Sketch(SketchValidationError::MultipleReferences(
152 ReferenceCountError::Cycle { references: _ }
153 ))
154 );
155
156 Ok(())
157 }
158
159 #[test]
160 fn should_find_half_edge_multiple_references() -> anyhow::Result<()> {
161 let mut core = Core::new();
162
163 let region = <Region as BuildRegion>::polygon(
164 [[0., 0.], [1., 1.], [0., 1.]],
165 &mut core,
166 )
167 .insert(&mut core);
168 let valid_sketch = Sketch::new(vec![region.clone()]).insert(&mut core);
169 valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
170
171 let exterior = region.exterior();
172 let cloned_edges: Vec<_> =
173 exterior.half_edges().iter().cloned().collect();
174 let interior = Cycle::new(cloned_edges).insert(&mut core);
175
176 let invalid_sketch =
177 Sketch::new(vec![
178 Region::new(exterior.clone(), vec![interior]).insert(&mut core)
179 ]);
180 assert_contains_err!(
181 core,
182 invalid_sketch,
183 ValidationError::Sketch(SketchValidationError::MultipleReferences(
184 ReferenceCountError::HalfEdge { references: _ }
185 ))
186 );
187
188 Ok(())
189 }
190
191 #[test]
192 fn should_find_clockwise_exterior_cycle() -> anyhow::Result<()> {
193 let mut core = Core::new();
194
195 let valid_outer_circle = HalfEdge::circle([0., 0.], 1., &mut core);
196 let valid_exterior =
197 Cycle::new(vec![valid_outer_circle.clone()]).insert(&mut core);
198 let valid_sketch =
199 Sketch::new(vec![
200 Region::new(valid_exterior.clone(), vec![]).insert(&mut core)
201 ]);
202 valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
203
204 let invalid_outer_circle = HalfEdge::from_sibling(
205 &valid_outer_circle,
206 Vertex::new().insert(&mut core),
207 &mut core,
208 );
209 let invalid_exterior =
210 Cycle::new(vec![invalid_outer_circle.clone()]).insert(&mut core);
211 let invalid_sketch =
212 Sketch::new(vec![
213 Region::new(invalid_exterior.clone(), vec![]).insert(&mut core)
214 ]);
215 assert_contains_err!(
216 core,
217 invalid_sketch,
218 ValidationError::Sketch(
219 SketchValidationError::ClockwiseExteriorCycle { cycle: _ }
220 )
221 );
222
223 Ok(())
224 }
225
226 #[test]
227 fn should_find_counterclockwise_interior_cycle() -> anyhow::Result<()> {
228 let mut core = Core::new();
229
230 let outer_circle = HalfEdge::circle([0., 0.], 2., &mut core);
231 let inner_circle = HalfEdge::circle([0., 0.], 1., &mut core);
232 let cw_inner_circle = HalfEdge::from_sibling(
233 &inner_circle,
234 Vertex::new().insert(&mut core),
235 &mut core,
236 );
237 let exterior = Cycle::new(vec![outer_circle.clone()]).insert(&mut core);
238
239 let valid_interior =
240 Cycle::new(vec![cw_inner_circle.clone()]).insert(&mut core);
241 let valid_sketch = Sketch::new(vec![Region::new(
242 exterior.clone(),
243 vec![valid_interior],
244 )
245 .insert(&mut core)]);
246 valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
247
248 let invalid_interior =
249 Cycle::new(vec![inner_circle.clone()]).insert(&mut core);
250 let invalid_sketch = Sketch::new(vec![Region::new(
251 exterior.clone(),
252 vec![invalid_interior],
253 )
254 .insert(&mut core)]);
255 assert_contains_err!(
256 core,
257 invalid_sketch,
258 ValidationError::Sketch(
259 SketchValidationError::CounterClockwiseInteriorCycle {
260 cycle: _
261 }
262 )
263 );
264 Ok(())
265 }
266}