fj_core/validate/
sketch.rs

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/// [`Sketch`] validation failed
29#[derive(Clone, Debug, thiserror::Error)]
30pub enum SketchValidationError {
31    /// Object within sketch referenced by more than one other object
32    #[error("Object within sketch referenced by more than one other Object")]
33    MultipleReferences(#[from] ReferenceCountError),
34    /// Region within sketch has exterior cycle with clockwise winding
35    #[error(
36        "Exterior cycle within sketch region has clockwise winding\n
37        Cycle: {cycle:#?}"
38    )]
39    ClockwiseExteriorCycle {
40        /// Cycle with clockwise winding
41        cycle: Handle<Cycle>,
42    },
43    /// Region within sketch has interior cycle with counter-clockwise winding
44    #[error(
45        "Interior cycle within sketch region has counter-clockwise winding\n
46        Cycle: {cycle:#?}"
47    )]
48    CounterClockwiseInteriorCycle {
49        /// Cycle with counter-clockwise winding
50        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}