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