brep_kernel/props/mass_properties/
measures.rs1use super::*;
2use super::mass_profile;
3
4pub fn face_area(face: &FaceRecord) -> Result<f64, String> {
5 if is_affine(&face.surface)? {
6 return Ok(closed_parameter_space_area(face)?.abs() * planar_metric(face)?);
7 }
8 if let Some(values) = biperiodic_band_integral(face, &[Integrand::Area])? {
9 return Ok(values[0]);
10 }
11 if is_untrimmed(face)? {
12 integrate_untrimmed(face, Integrand::Area)
13 } else {
14 integrate_trimmed(face, Integrand::Area)
15 }
16}
17
18pub fn curve_arc_length(curve: &NurbsCurve, t0: f64, t1: f64) -> Result<f64, String> {
25 let (lo, hi) = (t0.min(t1), t0.max(t1));
26 if !(hi > lo) || !hi.is_finite() || !lo.is_finite() {
27 return Ok(0.0);
28 }
29 let mut breaks: Vec<f64> = vec![lo, hi];
32 for &knot in &curve.knots {
33 if knot > lo + 1e-12 && knot < hi - 1e-12 {
34 breaks.push(knot);
35 }
36 }
37 breaks.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
38 breaks.dedup_by(|a, b| (*a - *b).abs() <= 1e-12);
39
40 let mut length = 0.0;
41 for window in breaks.windows(2) {
42 let (a, b) = (window[0], window[1]);
43 if !(b > a) {
44 continue;
45 }
46 for sub in 0..2 {
52 let sa = a + (b - a) * sub as f64 / 2.0;
53 let sb = a + (b - a) * (sub + 1) as f64 / 2.0;
54 for panel in rule::curve_panels(curve, sa, sb)? {
55 let half = 0.5 * (panel.hi - panel.lo);
56 let middle = 0.5 * (panel.lo + panel.hi);
57 let (nodes, weights) = panel.nodes();
58 for index in 0..nodes.len() {
59 let t = middle + half * nodes[index];
60 let speed = curve.derivatives(t, 1)?[1].length();
61 length += weights[index] * half * speed;
62 }
63 }
64 }
65 }
66 Ok(length)
67}
68
69pub fn edge_arc_length(edge: &EdgeRecord) -> Result<f64, String> {
72 if edge.degenerate {
73 return Ok(0.0);
74 }
75 curve_arc_length(&edge.curve, edge.t0, edge.t1)
76}
77
78pub fn solid_edge_length_total(solid: &BrepSolid) -> Result<f64, String> {
82 let mut total = 0.0;
83 for edge in &solid.edges {
84 total += edge_arc_length(edge)?;
85 }
86 Ok(total)
87}
88
89pub fn face_boundary_length(solid: &BrepSolid, face: &FaceRecord) -> Result<f64, String> {
94 let mut edge_ids: Vec<u64> = Vec::new();
95 for loop_record in &face.loops {
96 for coedge in &loop_record.coedges {
97 if !edge_ids.contains(&coedge.edge_id) {
98 edge_ids.push(coedge.edge_id);
99 }
100 }
101 }
102 let mut total = 0.0;
103 for id in edge_ids {
104 if let Some(edge) = solid.edges.iter().find(|edge| edge.id == id) {
105 total += edge_arc_length(edge)?;
106 }
107 }
108 Ok(total)
109}
110
111pub fn face_volume_contribution(face: &FaceRecord) -> Result<f64, String> {
112 if is_affine(&face.surface)? {
113 let signed_area = closed_parameter_space_area(face)? * planar_metric(face)?;
114 let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
115 let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
116 let u = ku.domain()[0];
117 let v = kv.domain()[0];
118 let point = face.surface.evaluate(u, v)?;
119 let normal = face.surface.normal(u, v)?;
120 return Ok(point.dot(normal) * signed_area / 3.0);
121 }
122 if let Some(values) = biperiodic_band_integral(face, &[Integrand::Volume])? {
123 return Ok(values[0] / 3.0);
124 }
125 let integral = if is_untrimmed(face)? {
126 integrate_untrimmed(face, Integrand::Volume)?
127 } else {
128 integrate_trimmed(face, Integrand::Volume)?
129 };
130 Ok(integral / 3.0)
131}
132
133pub(super) fn affine_area_and_volume_about(
139 face: &FaceRecord,
140 reference: Vec3,
141) -> Result<(f64, f64), String> {
142 let parameter_area = closed_parameter_space_area(face)?;
143 let metric = planar_metric(face)?;
144 let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
145 let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
146 let u = ku.domain()[0];
147 let v = kv.domain()[0];
148 let point = face.surface.evaluate(u, v)?;
149 let normal = face.surface.normal(u, v)?;
150 let signed_area = parameter_area * metric;
151 Ok((
152 parameter_area.abs() * metric,
153 point.sub(reference).dot(normal) * signed_area / 3.0,
154 ))
155}
156
157pub(super) fn face_volume_contribution_about(face: &FaceRecord, reference: Vec3) -> Result<f64, String> {
158 if is_affine(&face.surface)? {
159 let signed_area = closed_parameter_space_area(face)? * planar_metric(face)?;
160 let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
161 let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
162 let u = ku.domain()[0];
163 let v = kv.domain()[0];
164 let point = face.surface.evaluate(u, v)?;
165 let normal = face.surface.normal(u, v)?;
166 return Ok(point.sub(reference).dot(normal) * signed_area / 3.0);
167 }
168 let kind = Integrand::VolumeAbout(reference);
169 let gate_started = super::profile::profile_started();
170 if let Some(values) = biperiodic_band_integral(face, &[kind])? {
171 mass_profile(|p| {
172 p.route_biperiodic += 1;
173 p.gate_ms += super::profile::elapsed_ms(gate_started);
174 });
175 return Ok(values[0] / 3.0);
176 }
177 let integral = if is_untrimmed(face)? {
178 mass_profile(|p| {
179 p.route_untrimmed += 1;
180 p.gate_ms += super::profile::elapsed_ms(gate_started);
181 });
182 let started = super::profile::profile_started();
183 let value = integrate_untrimmed(face, kind)?;
184 mass_profile(|p| p.untrimmed_ms += super::profile::elapsed_ms(started));
185 value
186 } else {
187 mass_profile(|p| {
188 p.trimmed_faces += 1;
189 p.gate_ms += super::profile::elapsed_ms(gate_started);
190 });
191 integrate_trimmed(face, kind)?
192 };
193 Ok(integral / 3.0)
194}
195
196pub(super) fn face_volume_terms_about(face: &FaceRecord, reference: Vec3) -> Result<[f64; 4], String> {
201 if is_affine(&face.surface)? {
202 let signed_area = closed_parameter_space_area(face)? * planar_metric(face)?;
203 let ku = crate::KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)?;
204 let kv = crate::KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)?;
205 let (u, v) = (ku.domain()[0], kv.domain()[0]);
206 let point = face.surface.evaluate(u, v)?;
207 let normal = face.surface.normal(u, v)?;
208 return Ok([
209 point.sub(reference).dot(normal) * signed_area / 3.0,
210 normal.x * signed_area / 3.0,
211 normal.y * signed_area / 3.0,
212 normal.z * signed_area / 3.0,
213 ]);
214 }
215 let kinds = [Integrand::VolumeAbout(reference), Integrand::NormalX, Integrand::NormalY, Integrand::NormalZ];
216 let gate_started = super::profile::profile_started();
217 let values = if let Some(values) = biperiodic_band_integral(face, &kinds)? {
218 mass_profile(|p| {
219 p.route_biperiodic += 1;
220 p.gate_ms += super::profile::elapsed_ms(gate_started);
221 });
222 values
223 } else if is_untrimmed(face)? {
224 mass_profile(|p| {
225 p.route_untrimmed += 1;
226 p.gate_ms += super::profile::elapsed_ms(gate_started);
227 });
228 let started = super::profile::profile_started();
229 let values = integrate_untrimmed_multi(face, &kinds)?;
230 mass_profile(|p| p.untrimmed_ms += super::profile::elapsed_ms(started));
231 values
232 } else {
233 mass_profile(|p| {
234 p.trimmed_faces += 1;
235 p.gate_ms += super::profile::elapsed_ms(gate_started);
236 });
237 integrate_trimmed_multi(face, &kinds)?
238 };
239 Ok([values[0] / 3.0, values[1] / 3.0, values[2] / 3.0, values[3] / 3.0])
240}