brep_kernel/props/
carrier_dimensions.rs1use crate::{AnalyticSurface, NurbsCurve, NurbsSurface, RevolutionFrame, Vec3};
12
13const SAMPLES: usize = 17;
15
16const RELATIVE_TOLERANCE: f64 = 1e-7;
18
19#[derive(Clone, Debug, PartialEq)]
22pub struct CarrierDimensions {
23 pub label: &'static str,
25 pub values: Vec<(&'static str, f64)>,
28}
29
30fn round_dimensions(radius: f64) -> Vec<(&'static str, f64)> {
31 vec![("radius", radius), ("diameter", 2.0 * radius)]
32}
33
34pub fn surface_carrier_dimensions(surface: &NurbsSurface) -> Option<CarrierDimensions> {
38 match surface.analytic()? {
39 AnalyticSurface::Plane { .. } => None,
40 AnalyticSurface::RuledRevolution { rho0, rho1, height, .. } => {
41 Some(ruled_dimensions(*rho0, *rho1, *height))
42 }
43 AnalyticSurface::Sphere { radius, .. } => Some(CarrierDimensions {
44 label: "Sphere",
45 values: round_dimensions(*radius),
46 }),
47 AnalyticSurface::Torus { major_radius, minor_radius, .. } => Some(torus(*major_radius, *minor_radius)),
48 AnalyticSurface::Revolution { frame, generatrix, .. } => revolution_dimensions(frame, generatrix),
49 }
50}
51
52fn torus(major_radius: f64, minor_radius: f64) -> CarrierDimensions {
53 CarrierDimensions {
54 label: "Torus",
55 values: vec![("majorRadius", major_radius), ("minorRadius", minor_radius)],
56 }
57}
58
59fn ruled_dimensions(rho0: f64, rho1: f64, height: f64) -> CarrierDimensions {
62 let scale = rho0.abs().max(rho1.abs()).max(height.abs()).max(1.0);
63 if (rho0 - rho1).abs() <= RELATIVE_TOLERANCE * scale {
64 return CarrierDimensions {
65 label: "Cylinder",
66 values: round_dimensions(0.5 * (rho0 + rho1).abs()),
67 };
68 }
69 CarrierDimensions {
70 label: "Cone",
71 values: vec![("halfAngleDeg", (rho1 - rho0).abs().atan2(height.abs()).to_degrees())],
72 }
73}
74
75fn revolution_dimensions(frame: &RevolutionFrame, generatrix: &NurbsCurve) -> Option<CarrierDimensions> {
79 let [t0, t1] = generatrix.domain().ok()?;
80 let mut points = Vec::with_capacity(SAMPLES);
81 for index in 0..SAMPLES {
82 let t = t0 + (t1 - t0) * index as f64 / (SAMPLES - 1) as f64;
83 let d = generatrix.evaluate(t).ok()?.sub(frame.origin);
84 let axial = d.dot(frame.axis);
85 let radial = d.sub(frame.axis.scale(axial)).length();
86 points.push(Vec3::new(radial, axial, 0.0));
87 }
88 let (first, last) = (points[0], points[SAMPLES - 1]);
89 let scale = points.iter().map(|p| p.x.abs().max(p.y.abs())).fold(1.0, f64::max);
90 let tolerance = RELATIVE_TOLERANCE * scale;
91 if on_line(&points, tolerance) {
92 return Some(ruled_dimensions(first.x, last.x, last.y - first.y));
93 }
94 let (centre, radius) = circle_through(&points, tolerance)?;
95 if centre.x.abs() <= tolerance {
96 return Some(CarrierDimensions { label: "Sphere", values: round_dimensions(radius) });
97 }
98 Some(torus(centre.x, radius))
99}
100
101pub fn curve_carrier_dimensions(curve: &NurbsCurve, t0: f64, t1: f64) -> Option<CarrierDimensions> {
104 let (lo, hi) = (t0.min(t1), t0.max(t1));
105 if !(hi > lo) {
106 return None;
107 }
108 let mut points = Vec::with_capacity(SAMPLES);
109 for index in 0..SAMPLES {
110 points.push(curve.evaluate(lo + (hi - lo) * index as f64 / (SAMPLES - 1) as f64).ok()?);
111 }
112 let scale = points
113 .iter()
114 .map(|p| p.x.abs().max(p.y.abs()).max(p.z.abs()))
115 .fold(1.0, f64::max);
116 let tolerance = RELATIVE_TOLERANCE * scale;
117 if on_line(&points, tolerance) {
118 return None;
119 }
120 let (centre, radius) = circle_through(&points, tolerance)?;
121 let mut values = round_dimensions(radius);
122 let closed = points[0].sub(points[SAMPLES - 1]).length() <= tolerance;
123 if closed {
124 return Some(CarrierDimensions { label: "Circle", values });
125 }
126 let sweep: f64 = points
129 .windows(2)
130 .map(|pair| {
131 let a = pair[0].sub(centre);
132 let b = pair[1].sub(centre);
133 a.cross(b).length().atan2(a.dot(b))
134 })
135 .sum();
136 values.push(("sweepDeg", sweep.to_degrees()));
137 Some(CarrierDimensions { label: "Arc", values })
138}
139
140fn on_line(points: &[Vec3], tolerance: f64) -> bool {
143 let first = points[0];
144 let Some(far) = points
145 .iter()
146 .copied()
147 .max_by(|a, b| a.sub(first).length().total_cmp(&b.sub(first).length()))
148 else {
149 return true;
150 };
151 let Ok(direction) = far.sub(first).normalized() else {
152 return true;
153 };
154 points.iter().all(|p| {
155 let d = p.sub(first);
156 d.sub(direction.scale(d.dot(direction))).length() <= tolerance
157 })
158}
159
160fn circle_through(points: &[Vec3], tolerance: f64) -> Option<(Vec3, f64)> {
163 let n = points.len();
164 let closed = points[0].sub(points[n - 1]).length() <= tolerance;
165 let (a, b, c) = if closed {
167 (points[0], points[n / 3], points[2 * n / 3])
168 } else {
169 (points[0], points[n / 2], points[n - 1])
170 };
171 let ab = b.sub(a);
172 let ac = c.sub(a);
173 let normal = ab.cross(ac);
174 let centre = crate::circumcenter(a, b, c)?;
175 let radius = a.sub(centre).length();
176 let unit_normal = normal.normalized().ok()?;
177 let on_circle = points.iter().all(|p| {
178 let d = p.sub(centre);
179 (d.length() - radius).abs() <= tolerance && d.dot(unit_normal).abs() <= tolerance
180 });
181 on_circle.then_some((centre, radius))
182}
183