use crate::geometry::Geometry;
use crate::{objects::Cycle, storage::Handle};
use crate::{objects::Sketch, validate_references};
use fj_math::Winding;
use super::{
references::{ReferenceCountError, ReferenceCounter},
Validate, ValidationConfig, ValidationError,
};
impl Validate for Sketch {
fn validate(
&self,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
geometry: &Geometry,
) {
SketchValidationError::check_object_references(self, config, errors);
SketchValidationError::check_exterior_cycles(
self, geometry, config, errors,
);
SketchValidationError::check_interior_cycles(
self, geometry, config, errors,
);
}
}
#[derive(Clone, Debug, thiserror::Error)]
pub enum SketchValidationError {
#[error("Object within sketch referenced by more than one other Object")]
MultipleReferences(#[from] ReferenceCountError),
#[error(
"Exterior cycle within sketch region has clockwise winding\n
Cycle: {cycle:#?}"
)]
ClockwiseExteriorCycle {
cycle: Handle<Cycle>,
},
#[error(
"Interior cycle within sketch region has counter-clockwise winding\n
Cycle: {cycle:#?}"
)]
CounterClockwiseInteriorCycle {
cycle: Handle<Cycle>,
},
}
impl SketchValidationError {
fn check_object_references(
sketch: &Sketch,
_config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
let mut referenced_edges = ReferenceCounter::new();
let mut referenced_cycles = ReferenceCounter::new();
sketch.regions().iter().for_each(|r| {
r.all_cycles().for_each(|c| {
referenced_cycles.add_reference(c.clone(), r.clone());
c.half_edges().into_iter().for_each(|e| {
referenced_edges.add_reference(e.clone(), c.clone());
})
})
});
validate_references!(
errors, SketchValidationError;
referenced_edges, HalfEdge;
referenced_cycles, Cycle;
);
}
fn check_exterior_cycles(
sketch: &Sketch,
geometry: &Geometry,
_config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
sketch.regions().iter().for_each(|region| {
let cycle = region.exterior();
if cycle.winding(geometry) == Winding::Cw {
errors.push(ValidationError::Sketch(
SketchValidationError::ClockwiseExteriorCycle {
cycle: cycle.clone(),
},
))
}
});
}
fn check_interior_cycles(
sketch: &Sketch,
geometry: &Geometry,
_config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
sketch.regions().iter().for_each(|region| {
region
.interiors()
.iter()
.filter(|interior| interior.winding(geometry) == Winding::Ccw)
.for_each(|cycle| {
errors.push(ValidationError::Sketch(
SketchValidationError::CounterClockwiseInteriorCycle {
cycle: cycle.clone(),
},
));
})
});
}
}
#[cfg(test)]
mod tests {
use crate::{
assert_contains_err,
objects::{Cycle, HalfEdge, Region, Sketch, Vertex},
operations::{
build::BuildHalfEdge, build::BuildRegion, insert::Insert,
},
validate::{
references::ReferenceCountError, SketchValidationError, Validate,
ValidationError,
},
Core,
};
#[test]
fn should_find_cycle_multiple_references() -> anyhow::Result<()> {
let mut core = Core::new();
let region = <Region as BuildRegion>::circle([0., 0.], 1., &mut core)
.insert(&mut core);
let valid_sketch = Sketch::new(vec![region.clone()]).insert(&mut core);
valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
let shared_cycle = region.exterior();
let invalid_sketch = Sketch::new(vec![
Region::new(shared_cycle.clone(), vec![]).insert(&mut core),
Region::new(shared_cycle.clone(), vec![]).insert(&mut core),
]);
assert_contains_err!(
core,
invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences(
ReferenceCountError::Cycle { references: _ }
))
);
Ok(())
}
#[test]
fn should_find_half_edge_multiple_references() -> anyhow::Result<()> {
let mut core = Core::new();
let region = <Region as BuildRegion>::polygon(
[[0., 0.], [1., 1.], [0., 1.]],
&mut core,
)
.insert(&mut core);
let valid_sketch = Sketch::new(vec![region.clone()]).insert(&mut core);
valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
let exterior = region.exterior();
let cloned_edges: Vec<_> =
exterior.half_edges().iter().cloned().collect();
let interior = Cycle::new(cloned_edges).insert(&mut core);
let invalid_sketch =
Sketch::new(vec![
Region::new(exterior.clone(), vec![interior]).insert(&mut core)
]);
assert_contains_err!(
core,
invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences(
ReferenceCountError::HalfEdge { references: _ }
))
);
Ok(())
}
#[test]
fn should_find_clockwise_exterior_cycle() -> anyhow::Result<()> {
let mut core = Core::new();
let valid_outer_circle = HalfEdge::circle([0., 0.], 1., &mut core);
let valid_exterior =
Cycle::new(vec![valid_outer_circle.clone()]).insert(&mut core);
let valid_sketch =
Sketch::new(vec![
Region::new(valid_exterior.clone(), vec![]).insert(&mut core)
]);
valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
let invalid_outer_circle = HalfEdge::from_sibling(
&valid_outer_circle,
Vertex::new().insert(&mut core),
&mut core,
);
let invalid_exterior =
Cycle::new(vec![invalid_outer_circle.clone()]).insert(&mut core);
let invalid_sketch =
Sketch::new(vec![
Region::new(invalid_exterior.clone(), vec![]).insert(&mut core)
]);
assert_contains_err!(
core,
invalid_sketch,
ValidationError::Sketch(
SketchValidationError::ClockwiseExteriorCycle { cycle: _ }
)
);
Ok(())
}
#[test]
fn should_find_counterclockwise_interior_cycle() -> anyhow::Result<()> {
let mut core = Core::new();
let outer_circle = HalfEdge::circle([0., 0.], 2., &mut core);
let inner_circle = HalfEdge::circle([0., 0.], 1., &mut core);
let cw_inner_circle = HalfEdge::from_sibling(
&inner_circle,
Vertex::new().insert(&mut core),
&mut core,
);
let exterior = Cycle::new(vec![outer_circle.clone()]).insert(&mut core);
let valid_interior =
Cycle::new(vec![cw_inner_circle.clone()]).insert(&mut core);
let valid_sketch = Sketch::new(vec![Region::new(
exterior.clone(),
vec![valid_interior],
)
.insert(&mut core)]);
valid_sketch.validate_and_return_first_error(&core.layers.geometry)?;
let invalid_interior =
Cycle::new(vec![inner_circle.clone()]).insert(&mut core);
let invalid_sketch = Sketch::new(vec![Region::new(
exterior.clone(),
vec![invalid_interior],
)
.insert(&mut core)]);
assert_contains_err!(
core,
invalid_sketch,
ValidationError::Sketch(
SketchValidationError::CounterClockwiseInteriorCycle {
cycle: _
}
)
);
Ok(())
}
}