axiolid_brep_audit/
report.rs

1//! What a geometric audit found.
2
3use axiolid_core::Scalar;
4
5/// One geometric inconsistency, with enough detail to locate it.
6#[derive(Debug, Clone, Copy, PartialEq)]
7pub enum GeometricDefect {
8    /// An edge endpoint does not lie on the edge's own 3D curve.
9    VertexOffCurve {
10        /// Index of the offending edge.
11        edge: usize,
12        /// Distance from the vertex to the curve point, in model units.
13        error: Scalar,
14    },
15    /// A pcurve, lifted through its face surface, does not follow the edge.
16    ///
17    /// This is the defect a purely topological audit cannot see: the trim
18    /// curve resolves and the loop closes, but the face boundary runs
19    /// somewhere other than the edge it claims to trim.
20    PcurveOffCurve {
21        /// Index of the owning loop.
22        loop_id: usize,
23        /// Position of the edge use within that loop.
24        use_index: usize,
25        /// Worst sampled deviation, in model units.
26        error: Scalar,
27    },
28    /// An edge's 3D curve could not be evaluated on its stated interval.
29    UnevaluableCurve {
30        /// Index of the offending edge.
31        edge: usize,
32    },
33    /// A pcurve could not be evaluated on its stated interval.
34    UnevaluablePcurve {
35        /// Index of the owning loop.
36        loop_id: usize,
37        /// Position of the edge use within that loop.
38        use_index: usize,
39    },
40    /// A face surface could not be evaluated at a pcurve sample.
41    UnevaluableSurface {
42        /// Index of the owning loop.
43        loop_id: usize,
44        /// Position of the edge use within that loop.
45        use_index: usize,
46    },
47}
48
49/// Result of a geometric audit.
50///
51/// Unlike the topological audit this is tolerance-dependent, so a clean
52/// result is a statement about agreement AT A TOLERANCE, not an absolute one.
53#[derive(Debug, Clone, Default, PartialEq)]
54pub struct GeometricHealth {
55    defects: Vec<GeometricDefect>,
56}
57
58impl GeometricHealth {
59    /// Record one defect.
60    pub(crate) fn push(&mut self, defect: GeometricDefect) {
61        self.defects.push(defect);
62    }
63
64    /// Every defect found, in discovery order.
65    #[must_use]
66    pub fn defects(&self) -> &[GeometricDefect] {
67        &self.defects
68    }
69
70    /// Whether the geometry is consistent at the audited tolerance.
71    #[must_use]
72    pub fn is_consistent(&self) -> bool {
73        self.defects.is_empty()
74    }
75
76    /// The largest positional disagreement found, if any was measured.
77    ///
78    /// Useful for reporting how far off a rejected solid was, rather than
79    /// only that it was rejected.
80    #[must_use]
81    pub fn worst_error(&self) -> Option<Scalar> {
82        self.defects
83            .iter()
84            .filter_map(|defect| match defect {
85                GeometricDefect::VertexOffCurve { error, .. }
86                | GeometricDefect::PcurveOffCurve { error, .. } => Some(*error),
87                _ => None,
88            })
89            .max_by(Scalar::total_cmp)
90    }
91}