geop_core_topology/validation/
mod.rs1mod check_curves_and_surfaces_vertices;
8mod curve_and_surface_sampling_check;
9mod disjointness_check;
10mod face_face_numerical_intersection;
11mod face_orientation;
12mod faces_have_interior;
13mod holes_inside_outer;
14mod manifold_check;
15pub mod numerical_accuracy;
16mod parameters;
17mod pcurve_loop_continuity;
18mod pointer_check;
19mod two_way_references;
20
21pub use disjointness_check::{body_crossing, edge_crosses_face};
22pub use face_orientation::{Inward, Outward, normal_points_outward};
23pub use manifold_check::validate_manifold;
24pub use parameters::ValidationParameters;
25
26use std::collections::{BTreeMap, HashMap};
27
28use crate::{Body, CoedgeGeometry, EdgeId, FaceId, Model, VertexId};
29use geop_core_math::geop_error::GeopError;
30
31#[derive(Default)]
33pub(crate) struct BodyGroup {
34 pub vertices: Vec<VertexId>,
35 pub edges: Vec<EdgeId>,
36 pub faces: Vec<FaceId>,
37}
38
39pub(crate) fn body_groups(model: &Model) -> Vec<BodyGroup> {
47 let body_of_face = |face: FaceId| model.body_of_face(face).ok();
48 let body_of_coedge = |c: &crate::Coedge| body_of_face(c.face);
49 let mut edge_body: HashMap<EdgeId, Option<Body>> = HashMap::new();
50 let mut vertex_body: HashMap<VertexId, Option<Body>> = HashMap::new();
51 for coedge in model.coedges.values() {
52 let body = body_of_coedge(coedge);
53 match coedge.geometry {
54 CoedgeGeometry::Edge(e) => {
55 edge_body.entry(e).or_insert(body);
56 }
57 CoedgeGeometry::Vertex(v) => {
58 vertex_body.entry(v).or_insert(body);
59 }
60 }
61 }
62 for (&id, edge) in &model.edges {
63 let body = edge_body.get(&id).copied().flatten();
64 for v in [edge.start_vertex, edge.end_vertex] {
65 vertex_body.entry(v).or_insert(body);
66 }
67 }
68 let key = |body: Option<Body>| match body {
69 None => (0, 0),
70 Some(Body::Solid(s)) => (1, s.0),
71 Some(Body::Sheet(s)) => (2, s.0),
72 };
73 let mut vertex_wire = HashMap::new();
74 let mut edge_wire = HashMap::new();
75 for (&id, wire) in &model.wires {
76 vertex_wire.extend(wire.vertices.iter().map(|&v| (v, (3, id.0))));
77 edge_wire.extend(wire.edges.iter().map(|&e| (e, (3, id.0))));
78 }
79 let mut groups: BTreeMap<(u8, u64), BodyGroup> = BTreeMap::new();
80 for &id in model.vertices.keys() {
81 let group = vertex_wire
82 .get(&id)
83 .copied()
84 .unwrap_or_else(|| key(vertex_body.get(&id).copied().flatten()));
85 groups.entry(group).or_default().vertices.push(id);
86 }
87 for &id in model.edges.keys() {
88 let group = edge_wire
89 .get(&id)
90 .copied()
91 .unwrap_or_else(|| key(edge_body.get(&id).copied().flatten()));
92 groups.entry(group).or_default().edges.push(id);
93 }
94 for &id in model.faces.keys() {
95 groups
96 .entry(key(body_of_face(id)))
97 .or_default()
98 .faces
99 .push(id);
100 }
101 groups.into_values().collect()
102}
103
104pub fn validate(params: &ValidationParameters, model: &Model) -> Result<(), Vec<GeopError>> {
105 let mut errors = Vec::new();
106
107 numerical_accuracy::check_numerical_accuracy(params, &mut errors, model);
109 pointer_check::check_pointers(params, &mut errors, model);
110 two_way_references::check_two_way_references(params, &mut errors, model);
111 check_curves_and_surfaces_vertices::check_curves_and_surfaces_vertices(
112 params,
113 &mut errors,
114 model,
115 );
116 curve_and_surface_sampling_check::check_curve_and_surface_sampling(params, &mut errors, model);
117 pcurve_loop_continuity::check_pcurve_loop_continuity(params, &mut errors, model);
118 disjointness_check::check_vertices_disjoint(params, &mut errors, model);
119 disjointness_check::check_edges_disjoint(params, &mut errors, model);
120 disjointness_check::check_edges_and_faces_consistent(params, &mut errors, model);
121 face_face_numerical_intersection::check_face_face_numerical_intersection(
122 params,
123 &mut errors,
124 model,
125 );
126 holes_inside_outer::check_holes_inside_outer(params, &mut errors, model);
127 face_orientation::check_loop_winding(params, &mut errors, model);
128 face_orientation::check_normals_point_outward(params, &mut errors, model);
129
130 if errors.is_empty() {
131 Ok(())
132 } else {
133 Err(errors)
134 }
135}
136
137pub fn validate_fast(params: &ValidationParameters, model: &Model) -> Result<(), Vec<GeopError>> {
148 let mut errors = Vec::new();
149
150 numerical_accuracy::check_numerical_accuracy(params, &mut errors, model);
153 pointer_check::check_pointers(params, &mut errors, model);
154 two_way_references::check_two_way_references(params, &mut errors, model);
155 check_curves_and_surfaces_vertices::check_curves_and_surfaces_vertices(
156 params,
157 &mut errors,
158 model,
159 );
160 pcurve_loop_continuity::check_pcurve_loop_continuity(params, &mut errors, model);
161 faces_have_interior::check_faces_have_interior(params, &mut errors, model);
162
163 if errors.is_empty() {
164 Ok(())
165 } else {
166 Err(errors)
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use super::{ValidationParameters, validate};
173 use crate::Model;
174 use crate::test_fixtures::test_cube_solid;
175 use geop_core_math::{scalars::Scalar, vector::Vector3};
176
177 #[test]
178 fn cube_passes_full_validate() {
179 let mut model = Model::new();
180 test_cube_solid(&mut model);
181 validate(&ValidationParameters::default(), &model).unwrap();
182 }
183
184 #[test]
185 fn independent_failures_are_all_reported() {
186 let mut model = Model::new();
187 test_cube_solid(&mut model);
188
189 let face_id = *model.faces.keys().next().unwrap();
193 let shell_id = model.faces[&face_id].shell;
194 model
195 .shells
196 .get_mut(&shell_id)
197 .unwrap()
198 .faces
199 .retain(|&f| f != face_id);
200
201 let vertex_id = *model.vertices.keys().next().unwrap();
202 model.vertices.get_mut(&vertex_id).unwrap().point =
203 Vector3::from_array([Scalar::from_f64(42.0); 3]);
204
205 let errors = validate(&ValidationParameters::default(), &model).unwrap_err();
209 assert!(errors.len() >= 2);
210 }
211}