Skip to main content

brep_ransac/
options.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
4/// Selects which geometric samples are used during fitting.
5pub enum SamplingMode {
6    /// Sample one point at each triangle centroid.
7    TriangleCentroids,
8    /// Sample mesh vertices.
9    Vertices,
10    /// Sample both triangle centroids and mesh vertices.
11    CentroidsAndVertices,
12}
13
14#[derive(Clone, Debug, Deserialize, Serialize)]
15#[serde(default)]
16/// Controls surface recognition, refinement, and region discovery.
17pub struct RecognitionOptions {
18    /// Maximum absolute positional residual accepted as an inlier.
19    pub distance_tolerance: f64,
20    /// Scale-relative positional tolerance added to the absolute tolerance.
21    pub relative_tolerance: f64,
22    /// Maximum accepted normal-angle residual, in radians.
23    pub normal_tolerance: f64,
24    /// Minimum number of supporting triangles required for a fit.
25    pub minimum_support: usize,
26    /// Minimum total supporting triangle area required for a fit.
27    pub minimum_support_area: f64,
28    /// Desired probability that adaptive generic hypothesis generation has
29    /// sampled a seed from the best support observed so far. This controls
30    /// support visitation, not whether a boundary/degenerate seed yields a
31    /// usable hypothesis and not the reported residual-evidence quality score.
32    pub confidence: f64,
33    /// Seed for deterministic hypothesis sampling, or `None` for entropy-based
34    /// sampling.
35    pub deterministic_seed: Option<u64>,
36    /// Maximum number of generic recognition hypotheses to generate.
37    pub max_hypotheses: usize,
38    /// Maximum number of numerical refinement iterations per candidate.
39    pub max_refinement_iterations: usize,
40    /// Dihedral-angle threshold, in radians, used to identify feature edges.
41    pub feature_angle: f64,
42    /// Whether region traversal stops at detected feature edges.
43    pub respect_features: bool,
44    /// Whether to split the input selection into recognized surface regions.
45    pub discover_regions: bool,
46    /// Merge disconnected regions only when a joint fit validates that they
47    /// lie on one carrier and have the same observed orientation.
48    pub allow_disconnected_same_surface: bool,
49    /// Collect wall-clock phase timings in fit diagnostics. Disabled by
50    /// default so deterministic result comparisons do not contain clock data.
51    pub collect_phase_timings: bool,
52    /// Geometric sampling strategy used by fitting and validation.
53    pub sampling: SamplingMode,
54}
55
56impl Default for RecognitionOptions {
57    fn default() -> Self {
58        Self {
59            distance_tolerance: 1.0e-6,
60            relative_tolerance: 1.0e-8,
61            normal_tolerance: 5.0_f64.to_radians(),
62            minimum_support: 6,
63            minimum_support_area: 0.0,
64            confidence: 0.999,
65            deterministic_seed: Some(0x4341_4452_414e_5341),
66            max_hypotheses: 512,
67            // Small, partial torus patches have a shallow coupled
68            // center/axis/radii valley.  They can require well over 40
69            // monotonically improving LM steps to reach CAD-level accuracy.
70            max_refinement_iterations: 160,
71            feature_angle: 30.0_f64.to_radians(),
72            respect_features: true,
73            discover_regions: true,
74            allow_disconnected_same_surface: false,
75            collect_phase_timings: false,
76            sampling: SamplingMode::CentroidsAndVertices,
77        }
78    }
79}
80
81impl RecognitionOptions {
82    pub(crate) fn validate(&self) -> Result<(), crate::RecognitionError> {
83        if !self.distance_tolerance.is_finite() || self.distance_tolerance <= 0.0 {
84            return Err(crate::RecognitionError::InvalidOptions(
85                "distance_tolerance must be finite and positive".into(),
86            ));
87        }
88        if !self.relative_tolerance.is_finite() || self.relative_tolerance < 0.0 {
89            return Err(crate::RecognitionError::InvalidOptions(
90                "relative_tolerance must be finite and non-negative".into(),
91            ));
92        }
93        if !(0.0..=std::f64::consts::PI).contains(&self.normal_tolerance) {
94            return Err(crate::RecognitionError::InvalidOptions(
95                "normal_tolerance must be in [0, pi]".into(),
96            ));
97        }
98        if self.minimum_support == 0 {
99            return Err(crate::RecognitionError::InvalidOptions(
100                "minimum_support must be non-zero".into(),
101            ));
102        }
103        if !self.minimum_support_area.is_finite() || self.minimum_support_area < 0.0 {
104            return Err(crate::RecognitionError::InvalidOptions(
105                "minimum_support_area must be finite and non-negative".into(),
106            ));
107        }
108        if !(0.0..1.0).contains(&self.confidence) {
109            return Err(crate::RecognitionError::InvalidOptions(
110                "confidence must be in [0, 1)".into(),
111            ));
112        }
113        if self.max_hypotheses == 0 || self.max_refinement_iterations == 0 {
114            return Err(crate::RecognitionError::InvalidOptions(
115                "iteration limits must be non-zero".into(),
116            ));
117        }
118        if !(0.0..=std::f64::consts::PI).contains(&self.feature_angle) {
119            return Err(crate::RecognitionError::InvalidOptions(
120                "feature_angle must be finite and in [0, pi]".into(),
121            ));
122        }
123        Ok(())
124    }
125}
126
127// BREP private tests: 9cd7901a29d6c26d