brep_kernel/blending/blend/
mod.rs1mod carve;
21mod chain;
22mod collapse;
23mod corner;
24mod edge;
25mod fold;
26mod miter;
27mod network;
28mod planar_chart;
29mod restrict;
30pub(crate) mod runout;
31mod stations;
32mod rows;
33mod track_fit;
34
35thread_local! {
36 static REFINE_OFF_FOR_TEST: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
37}
38
39pub(crate) fn station_refinement_on() -> bool {
43 std::env::var("BREP_BLEND_REFINE").as_deref() != Ok("0")
44 && !REFINE_OFF_FOR_TEST.with(|off| off.get())
45}
46
47thread_local! {
48 static BLEND_NOTES: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
49}
50
51pub(crate) fn record_blend_note(note: String) {
56 BLEND_NOTES.with(|notes| notes.borrow_mut().push(note));
57}
58
59pub fn take_blend_notes() -> Vec<String> {
63 BLEND_NOTES.with(|notes| std::mem::take(&mut *notes.borrow_mut()))
64}
65
66pub(crate) fn blend_notes_mark() -> usize {
69 BLEND_NOTES.with(|notes| notes.borrow().len())
70}
71
72pub(crate) fn blend_notes_truncate(mark: usize) {
73 BLEND_NOTES.with(|notes| notes.borrow_mut().truncate(mark));
74}
75
76#[derive(Clone, Debug)]
81pub(crate) struct WallModelReport {
82 pub(crate) name: Option<String>,
83 pub(crate) surface: crate::NurbsSurface,
84 pub(crate) request: f64,
85 pub(crate) residual: f64,
86 pub(crate) detail: Option<String>,
89 pub(crate) budget: crate::ApproximationBudget,
90}
91
92thread_local! {
93 static BLEND_APPROXIMATIONS: std::cell::RefCell<Vec<WallModelReport>> = const { std::cell::RefCell::new(Vec::new()) };
94}
95
96thread_local! {
97 static BLEND_OPERATION_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
98}
99
100pub(crate) struct BlendOperation;
108
109impl BlendOperation {
110 pub(crate) fn enter() -> BlendOperation {
111 BLEND_OPERATION_DEPTH.with(|depth| {
112 if depth.get() == 0 {
113 BLEND_APPROXIMATIONS.with(|reports| reports.borrow_mut().clear());
114 }
115 depth.set(depth.get() + 1);
116 });
117 BlendOperation
118 }
119}
120
121impl Drop for BlendOperation {
122 fn drop(&mut self) {
123 BLEND_OPERATION_DEPTH.with(|depth| depth.set(depth.get().saturating_sub(1)));
124 }
125}
126
127pub(crate) fn record_wall_model_report(report: WallModelReport) {
132 BLEND_APPROXIMATIONS.with(|reports| reports.borrow_mut().push(report));
133}
134
135
136pub(crate) fn blend_approximations_pending() -> bool {
139 BLEND_APPROXIMATIONS.with(|reports| !reports.borrow().is_empty())
140}
141
142pub(crate) fn same_surface(a: &crate::NurbsSurface, b: &crate::NurbsSurface) -> bool {
145 let bits = |values: &[f64]| values.iter().map(|value| value.to_bits()).collect::<Vec<_>>();
146 a.degree_u == b.degree_u
147 && a.degree_v == b.degree_v
148 && bits(&a.knots_u) == bits(&b.knots_u)
149 && bits(&a.knots_v) == bits(&b.knots_v)
150 && a.control_points.len() == b.control_points.len()
151 && a.control_points.iter().zip(&b.control_points).all(|(row_a, row_b)| {
152 row_a.len() == row_b.len()
153 && row_a.iter().zip(row_b).all(|(p, q)| {
154 [p.x, p.y, p.z, p.w].map(f64::to_bits) == [q.x, q.y, q.z, q.w].map(f64::to_bits)
155 })
156 })
157}
158
159fn names_the_wall(face_name: &Option<String>, report_name: &Option<String>) -> bool {
163 match (face_name, report_name) {
164 (Some(face), Some(report)) => {
165 face == report
166 || face.strip_prefix(report.as_str()).and_then(|rest| rest.strip_prefix('[')).and_then(|rest| rest.strip_suffix(']')).is_some_and(
167 |index| !index.is_empty() && index.bytes().all(|byte| byte.is_ascii_digit()),
168 )
169 }
170 (None, None) => true,
171 _ => false,
172 }
173}
174
175pub fn take_blend_approximations(bodies: &[(&str, &crate::BrepSolid)]) -> Vec<crate::Approximation> {
188 let snapshot = BLEND_APPROXIMATIONS.with(|reports| std::mem::take(&mut *reports.borrow_mut()));
189 let mut unique: Vec<WallModelReport> = Vec::new();
190 for report in snapshot {
191 unique.retain(|kept| !(kept.name == report.name && same_surface(&kept.surface, &report.surface)));
192 unique.push(report);
193 }
194 let mut approximations = Vec::new();
195 for (body, solid) in bodies {
196 for report in &unique {
197 let carried = solid.shells.iter().flat_map(|shell| &shell.faces).any(|face| {
198 names_the_wall(&face.name, &report.name) && same_surface(&face.surface, &report.surface)
199 });
200 if carried {
201 let wall = report.name.as_deref().unwrap_or("(unnamed)");
202 let unaccepted = matches!(report.budget.reason, crate::BudgetReason::Unaccepted | crate::BudgetReason::Unread);
203 let budget = match report.budget.mechanism {
204 crate::BudgetMechanism::LocalRounds => format!(
205 "local refinement stopped: {:?} after {} of {} rounds, {} of {} stations per segment",
206 report.budget.reason, report.budget.rounds_used, report.budget.rounds_limit,
207 report.budget.stations, report.budget.station_limit,
208 ),
209 crate::BudgetMechanism::StationRungs => format!(
210 "the open march's station ladder stopped: {:?} after {} of {} global station rungs, {} of {} stations",
211 report.budget.reason, report.budget.rounds_used, report.budget.rounds_limit,
212 report.budget.stations, report.budget.station_limit,
213 ),
214 };
215 let unknown = report.budget.unread > 0;
216 let rails = report.budget.measured_component == crate::MeasuredComponent::Rails;
217 let mut message = if !unaccepted && (rails || unknown) {
218 let what = if rails {
222 format!("the fillet wall {wall}'s support rails stand")
223 } else {
224 format!("the fillet wall {wall} stands")
225 };
226 let against = if rails {
227 "off their exact construction (their carriers / the exact ball contacts)"
228 } else {
229 "from the rolling ball between its rails"
230 };
231 let reading = if unknown {
232 format!(
233 "an UNKNOWN distance {against}: {} interval(s) could not be read (the largest READABLE reading is {:.3e} mm, not the worst)",
234 report.budget.unread, report.residual
235 )
236 } else {
237 format!("{:.3e} mm {against}", report.residual)
238 };
239 format!(
240 "{what} {reading}, against a construction request of {:.3e} mm (accepted at the intersection-fit bar); {budget}",
241 report.request,
242 )
243 } else if unaccepted {
244 format!(
245 "the blend wall {wall} ships from a rung that never met acceptance: its worst reading \
246 {:.3e} mm against its acceptance bar {:.3e} mm; {budget}",
247 report.residual, report.request,
248 )
249 } else {
250 format!(
251 "the fillet wall {wall} stands {:.3e} mm from the rolling ball between its rails, against a \
252 construction request of {:.3e} mm (accepted at the intersection-fit bar); {budget}",
253 report.residual, report.request,
254 )
255 };
256 if let Some(detail) = &report.detail {
257 message = format!("{message} ({detail})");
258 }
259 approximations.push(crate::Approximation {
260 code: if unaccepted { "blend.wall_acceptance" } else { "blend.wall_model" }.to_string(),
261 body: body.to_string(),
262 measured: report.residual,
263 bar: report.request,
264 volume_bound: None,
265 edges: Vec::new(),
266 budget: Some(report.budget.clone()),
267 message,
268 });
269 }
270 }
271 }
272 approximations
273}
274
275
276pub use chain::{blend_smooth_chain, blend_smooth_chain_if_closed};
277pub(crate) use corner::round_concave_chain_corner;
278pub use corner::round_convex_corner;
279pub use edge::{blend_closed_edge, blend_edge_variable, blend_open_edge};
280pub(crate) use collapse::is_full_width_collapse;
281pub(crate) use planar_chart::{
282 fit_planar_charts_to_trims, is_planar_chart_refusal, planar_chart_overrun,
283};
284pub(crate) use track_fit::PCURVE_OFF_FLOOR;
285pub(crate) use edge::{consumed_band, is_consumed_snap, is_rail_collapse};
286pub(crate) use network::{
287 blend_star_network, blend_star_network_with_corner_policy, degenerate_corner_setbacks, mixed_convexity_corner, mixed_convexity_corners, selection_convexity, DegenerateSetback,
288 MixedCorner,
289};
290pub(crate) use miter::{is_marched_fit_off_carriers, is_miter_support_overrun};
291pub(crate) use runout::{build_runout_walls, plan_runout, take_runout_reports, RunoutPlan, RunoutWallReport};
292pub(crate) use fold::{is_wall_fold, WALL_FOLDS};
293pub(crate) use stations::is_ball_off_carrier;