1mod carve;
21mod chain;
22mod collapse;
23mod corner;
24mod edge;
25mod fold;
26mod miter;
27mod network;
28pub(crate) use network::STRIPE_PINCH_TAPER_WHAT;
29mod planar_chart;
30mod restrict;
31pub(super) mod rollover;
32mod rollover_surgery;
33pub(crate) mod runout;
34mod stations;
35mod rows;
36mod track_fit;
37
38pub(crate) const TANGENTIAL_TRANSVERSE_END: &str = "tangential_transverse_end";
41
42pub(crate) fn is_tangential_transverse_end(refusal: &crate::KernelRefusal) -> bool {
43 matches!(&refusal.class, crate::RefusalClass::UnsupportedGeometry { what }
44 if what == TANGENTIAL_TRANSVERSE_END)
45}
46
47thread_local! {
48 static REFINE_OFF_FOR_TEST: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
49}
50
51pub(crate) fn station_refinement_on() -> bool {
55 std::env::var("BREP_BLEND_REFINE").as_deref() != Ok("0")
56 && !REFINE_OFF_FOR_TEST.with(|off| off.get())
57}
58
59thread_local! {
60 static BLEND_NOTES: std::cell::RefCell<Vec<String>> = const { std::cell::RefCell::new(Vec::new()) };
61}
62
63thread_local! {
64 static OPEN_PAD_NETWORK: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
67}
68
69pub(crate) fn with_open_pad_network<T>(build: impl FnOnce() -> T) -> T {
81 struct Reset(bool);
84 impl Drop for Reset {
85 fn drop(&mut self) {
86 OPEN_PAD_NETWORK.with(|flag| flag.set(self.0));
87 }
88 }
89 let _reset = Reset(OPEN_PAD_NETWORK.with(|flag| flag.replace(true)));
90 build()
91}
92
93pub(in crate::blend) fn open_pad_network_on() -> bool {
96 OPEN_PAD_NETWORK.with(|flag| flag.get())
97}
98
99
100thread_local! {
101 static EXACT_LANE_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
102 static EXACT_REVOLUTION_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
103 static EXACT_RIM_LANE_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
104 static ACCEPT_ONCE_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
105 static CHAIN_ARC_STATIONS_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
106 static TAPER_NETWORK_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
107 static ROLLOVER_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
108 static EXACT_TRIM_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
109 static UNEQUAL_MITER_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
110 static NO_COMMON_BALL_STAR_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
111 static CROSSED_RAILS_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
112}
113
114const CROSSED_RAILS_DEFAULT: bool = true;
120
121pub(crate) fn crossed_rails_on() -> bool {
122 if let Some(forced) = CROSSED_RAILS_FOR_TEST.with(|forced| forced.get()) {
123 return forced;
124 }
125 match std::env::var("BREP_BLEND_CROSSED_RAILS").as_deref() {
126 Ok("0") => false,
127 Ok("1") => true,
128 _ => CROSSED_RAILS_DEFAULT,
129 }
130}
131
132const NO_COMMON_BALL_STAR_DEFAULT: bool = true;
137
138pub(crate) fn no_common_ball_star_on() -> bool {
139 if let Some(forced) = NO_COMMON_BALL_STAR_FOR_TEST.with(|forced| forced.get()) {
140 return forced;
141 }
142 match std::env::var("BREP_BLEND_RIBBON_STAR").as_deref() {
143 Ok("0") => false,
144 Ok("1") => true,
145 _ => NO_COMMON_BALL_STAR_DEFAULT,
146 }
147}
148
149const UNEQUAL_MITER_DEFAULT: bool = true;
158
159pub(crate) fn unequal_miter_on() -> bool {
160 if let Some(forced) = UNEQUAL_MITER_FOR_TEST.with(|forced| forced.get()) {
161 return forced;
162 }
163 match std::env::var("BREP_BLEND_UNEQUAL_MITER").as_deref() {
164 Ok("0") => false,
165 Ok("1") => true,
166 _ => UNEQUAL_MITER_DEFAULT,
167 }
168}
169
170const ROLLOVER_DEFAULT: bool = true;
176
177pub(crate) fn rollover_on() -> bool {
178 if let Some(forced) = ROLLOVER_FOR_TEST.with(|forced| forced.get()) {
179 return forced;
180 }
181 match std::env::var("BREP_BLEND_ROLLOVER").as_deref() {
182 Ok("0") => false,
183 Ok("1") => true,
184 _ => ROLLOVER_DEFAULT,
185 }
186}
187
188const EXACT_LANE_DEFAULT: bool = true;
196
197const EXACT_RIM_LANE_DEFAULT: bool = true;
201
202pub(crate) fn exact_rim_lane_on() -> bool {
203 if let Some(forced) = EXACT_RIM_LANE_FOR_TEST.with(|forced| forced.get()) {
204 return forced;
205 }
206 match std::env::var("BREP_BLEND_EXACT_RIM").as_deref() {
207 Ok("0") => false,
208 Ok("1") => true,
209 _ => EXACT_RIM_LANE_DEFAULT,
210 }
211}
212
213pub(crate) fn exact_lane_on() -> bool {
214 if let Some(forced) = EXACT_LANE_FOR_TEST.with(|forced| forced.get()) {
215 return forced;
216 }
217 match std::env::var("BREP_BLEND_EXACT_LANE").as_deref() {
218 Ok("0") => false,
219 Ok("1") => true,
220 _ => EXACT_LANE_DEFAULT,
221 }
222}
223
224const EXACT_REVOLUTION_DEFAULT: bool = true;
233
234pub(crate) fn exact_revolution_on() -> bool {
235 if let Some(forced) = EXACT_REVOLUTION_FOR_TEST.with(|forced| forced.get()) {
236 return forced;
237 }
238 match std::env::var("BREP_BLEND_EXACT_REVOLUTION").as_deref() {
239 Ok("0") => false,
240 Ok("1") => true,
241 _ => EXACT_REVOLUTION_DEFAULT,
242 }
243}
244
245const EXACT_TRIM_DEFAULT: bool = true;
261
262pub(crate) fn exact_trim_on() -> bool {
263 if let Some(forced) = EXACT_TRIM_FOR_TEST.with(|forced| forced.get()) {
264 return forced;
265 }
266 match std::env::var("BREP_BLEND_EXACT_TRIM").as_deref() {
267 Ok("0") => false,
268 Ok("1") => true,
269 _ => EXACT_TRIM_DEFAULT,
270 }
271}
272
273const ACCEPT_ONCE_DEFAULT: bool = true;
276
277pub(crate) fn accept_once_on() -> bool {
278 if let Some(forced) = ACCEPT_ONCE_FOR_TEST.with(|forced| forced.get()) {
279 return forced;
280 }
281 match std::env::var("BREP_BLEND_ACCEPT_ONCE").as_deref() {
282 Ok("0") => false,
283 Ok("1") => true,
284 _ => ACCEPT_ONCE_DEFAULT,
285 }
286}
287
288const CHAIN_ARC_STATIONS_DEFAULT: bool = false;
293
294pub(crate) fn chain_arc_stations_on() -> bool {
295 if let Some(forced) = CHAIN_ARC_STATIONS_FOR_TEST.with(|forced| forced.get()) {
296 return forced;
297 }
298 match std::env::var("BREP_BLEND_CHAIN_ARC_STATIONS").as_deref() {
299 Ok("0") => false,
300 Ok("1") => true,
301 _ => CHAIN_ARC_STATIONS_DEFAULT,
302 }
303}
304
305const TAPER_NETWORK_DEFAULT: bool = true;
308
309pub(crate) fn taper_network_on() -> bool {
310 if let Some(forced) = TAPER_NETWORK_FOR_TEST.with(|forced| forced.get()) {
311 return forced;
312 }
313 match std::env::var("BREP_BLEND_TAPER_NETWORK").as_deref() {
314 Ok("0") => false,
315 Ok("1") => true,
316 _ => TAPER_NETWORK_DEFAULT,
317 }
318}
319
320#[derive(Clone, Debug, PartialEq)]
321pub(crate) enum StripeRadius {
322 Constant(f64),
323 Stops(Vec<(f64, f64)>),
327}
328
329impl StripeRadius {
330 pub(crate) fn from_stops(stops: &[(f64, f64)]) -> Self {
333 let mut sorted = stops.to_vec();
334 sorted.sort_by(|a, b| a.0.total_cmp(&b.0));
335 match sorted.first() {
336 Some(&(_, first)) if sorted.iter().all(|(_, r)| (r - first).abs() <= 1e-12 * (1.0 + first.abs())) => {
337 StripeRadius::Constant(first)
338 }
339 _ => StripeRadius::Stops(sorted),
340 }
341 }
342
343 pub(crate) fn at_fraction(&self, fraction: f64) -> f64 {
345 match self {
346 StripeRadius::Constant(radius) => *radius,
347 StripeRadius::Stops(stops) => {
348 let fraction = fraction.clamp(0.0, 1.0);
349 if fraction <= stops[0].0 {
350 return stops[0].1;
351 }
352 for pair in stops.windows(2) {
353 if fraction <= pair[1].0 {
354 let width = (pair[1].0 - pair[0].0).max(1e-12);
355 return pair[0].1 + (pair[1].1 - pair[0].1) * (fraction - pair[0].0) / width;
356 }
357 }
358 stops[stops.len() - 1].1
359 }
360 }
361 }
362
363 pub(crate) fn constant(&self) -> Option<f64> {
365 match self {
366 StripeRadius::Constant(radius) => Some(*radius),
367 StripeRadius::Stops(_) => None,
368 }
369 }
370
371 pub(crate) fn largest(&self) -> f64 {
373 match self {
374 StripeRadius::Constant(radius) => *radius,
375 StripeRadius::Stops(stops) => stops.iter().map(|(_, r)| *r).fold(0.0, f64::max),
376 }
377 }
378
379 pub(crate) fn end(&self, at_start: bool) -> f64 {
383 self.at_fraction(if at_start { 0.0 } else { 1.0 })
384 }
385
386 pub(crate) fn is_positive(&self) -> bool {
388 match self {
389 StripeRadius::Constant(radius) => radius.is_finite() && *radius > 0.0,
390 StripeRadius::Stops(stops) => !stops.is_empty() && stops.iter().all(|(_, r)| r.is_finite() && *r > 0.0),
391 }
392 }
393}
394
395
396
397
398
399
400
401
402pub(crate) fn record_blend_note(note: String) {
407 BLEND_NOTES.with(|notes| notes.borrow_mut().push(note));
408}
409
410pub fn take_blend_notes() -> Vec<String> {
414 BLEND_NOTES.with(|notes| std::mem::take(&mut *notes.borrow_mut()))
415}
416
417pub(crate) fn blend_notes_mark() -> usize {
420 BLEND_NOTES.with(|notes| notes.borrow().len())
421}
422
423pub(crate) fn blend_notes_truncate(mark: usize) {
424 BLEND_NOTES.with(|notes| notes.borrow_mut().truncate(mark));
425}
426
427#[derive(Clone, Debug)]
432pub(crate) struct WallModelReport {
433 pub(crate) name: Option<String>,
434 pub(crate) surface: crate::NurbsSurface,
435 pub(crate) request: f64,
436 pub(crate) residual: f64,
437 pub(crate) detail: Option<String>,
440 pub(crate) budget: crate::ApproximationBudget,
441}
442
443thread_local! {
444 static BLEND_APPROXIMATIONS: std::cell::RefCell<Vec<WallModelReport>> = const { std::cell::RefCell::new(Vec::new()) };
445}
446
447thread_local! {
448 static BLEND_OPERATION_DEPTH: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
449}
450
451pub(crate) struct BlendOperation;
459
460impl BlendOperation {
461 pub(crate) fn enter() -> BlendOperation {
462 BLEND_OPERATION_DEPTH.with(|depth| {
463 if depth.get() == 0 {
464 BLEND_APPROXIMATIONS.with(|reports| reports.borrow_mut().clear());
465 timing::begin_operation();
466 }
467 depth.set(depth.get() + 1);
468 });
469 BlendOperation
470 }
471}
472
473impl Drop for BlendOperation {
474 fn drop(&mut self) {
475 BLEND_OPERATION_DEPTH.with(|depth| {
476 depth.set(depth.get().saturating_sub(1));
477 if depth.get() == 0 {
478 timing::end_operation();
479 }
480 });
481 }
482}
483
484pub(crate) mod timing {
494 use std::cell::{Cell, RefCell};
495 use web_time::Instant;
496
497 #[derive(Clone, Debug, PartialEq)]
500 pub struct BlendTiming {
501 pub measure: String,
502 pub ms: f64,
503 pub count: u64,
504 }
505
506 thread_local! {
507 static CAPTURE: Cell<bool> = const { Cell::new(false) };
508 static ENABLED: Cell<bool> = const { Cell::new(false) };
509 static TO_STDERR: Cell<bool> = const { Cell::new(false) };
510 static LEDGER: RefCell<Vec<BlendTiming>> = const { RefCell::new(Vec::new()) };
511 }
512
513 pub fn blend_timing_capture(on: bool) {
516 CAPTURE.with(|capture| capture.set(on));
517 }
518
519 pub fn take_blend_timings() -> Vec<BlendTiming> {
522 let mut entries = LEDGER.with(|ledger| std::mem::take(&mut *ledger.borrow_mut()));
523 entries.sort_by(|a, b| a.measure.cmp(&b.measure));
524 entries
525 }
526
527 pub(crate) fn enabled() -> bool {
529 ENABLED.with(|enabled| enabled.get())
530 }
531
532 pub(super) fn begin_operation() {
533 let profile = std::env::var_os("BREP_PROFILE").is_some();
534 TO_STDERR.with(|to_stderr| to_stderr.set(profile));
535 let on = profile || CAPTURE.with(|capture| capture.get());
536 ENABLED.with(|enabled| enabled.set(on));
537 if on {
538 LEDGER.with(|ledger| ledger.borrow_mut().clear());
539 }
540 }
541
542 pub(super) fn end_operation() {
543 if TO_STDERR.with(|to_stderr| to_stderr.get()) {
544 let mut entries = LEDGER.with(|ledger| ledger.borrow().clone());
545 entries.sort_by(|a, b| a.measure.cmp(&b.measure));
546 for entry in entries {
547 if entry.measure.starts_with("timing.") {
548 eprintln!("blend.{}={:.3} n={}", entry.measure, entry.ms, entry.count);
549 } else {
550 eprintln!("blend.{}={}", entry.measure, entry.count);
551 }
552 }
553 }
554 ENABLED.with(|enabled| enabled.set(false));
555 }
556
557 fn add(measure: &str, ms: f64, count: u64) {
558 LEDGER.with(|ledger| {
559 let mut ledger = ledger.borrow_mut();
560 match ledger.iter_mut().find(|entry| entry.measure == measure) {
561 Some(entry) => {
562 entry.ms += ms;
563 entry.count += count;
564 }
565 None => ledger.push(BlendTiming { measure: measure.to_string(), ms, count }),
566 }
567 });
568 }
569
570 pub(crate) fn record_ms(measure: &str, ms: f64) {
572 if enabled() {
573 add(measure, ms, 1);
574 }
575 }
576
577 pub(crate) fn record_count(measure: &str, n: u64) {
579 if enabled() {
580 add(measure, 0.0, n);
581 }
582 }
583
584 pub(crate) struct BlendTimer {
587 measure: &'static str,
588 started: Option<Instant>,
589 }
590
591 impl BlendTimer {
592 pub(crate) fn start(measure: &'static str) -> BlendTimer {
593 BlendTimer { measure, started: enabled().then(Instant::now) }
594 }
595
596 pub(crate) fn elapsed_ms(&self) -> f64 {
598 self.started.map_or(0.0, |started| started.elapsed().as_secs_f64() * 1_000.0)
599 }
600
601 pub(crate) fn stop(mut self) -> f64 {
603 let ms = self.elapsed_ms();
604 if self.started.take().is_some() {
605 add(self.measure, ms, 1);
606 }
607 ms
608 }
609 }
610
611 impl Drop for BlendTimer {
612 fn drop(&mut self) {
613 if let Some(started) = self.started.take() {
614 add(self.measure, started.elapsed().as_secs_f64() * 1_000.0, 1);
615 }
616 }
617 }
618}
619pub use timing::{blend_timing_capture, take_blend_timings, BlendTiming};
620pub(crate) use timing::{record_count, BlendTimer};
621
622pub(crate) fn record_wall_model_report(report: WallModelReport) {
627 BLEND_APPROXIMATIONS.with(|reports| reports.borrow_mut().push(report));
628}
629
630
631pub(crate) fn blend_approximations_pending() -> bool {
634 BLEND_APPROXIMATIONS.with(|reports| !reports.borrow().is_empty())
635}
636
637pub(crate) fn same_surface(a: &crate::NurbsSurface, b: &crate::NurbsSurface) -> bool {
640 let bits = |values: &[f64]| values.iter().map(|value| value.to_bits()).collect::<Vec<_>>();
641 a.degree_u == b.degree_u
642 && a.degree_v == b.degree_v
643 && bits(&a.knots_u) == bits(&b.knots_u)
644 && bits(&a.knots_v) == bits(&b.knots_v)
645 && a.control_points.len() == b.control_points.len()
646 && a.control_points.iter().zip(&b.control_points).all(|(row_a, row_b)| {
647 row_a.len() == row_b.len()
648 && row_a.iter().zip(row_b).all(|(p, q)| {
649 [p.x, p.y, p.z, p.w].map(f64::to_bits) == [q.x, q.y, q.z, q.w].map(f64::to_bits)
650 })
651 })
652}
653
654fn names_the_wall(face_name: &Option<String>, report_name: &Option<String>) -> bool {
658 match (face_name, report_name) {
659 (Some(face), Some(report)) => {
660 face == report
661 || face.strip_prefix(report.as_str()).and_then(|rest| rest.strip_prefix('[')).and_then(|rest| rest.strip_suffix(']')).is_some_and(
662 |index| !index.is_empty() && index.bytes().all(|byte| byte.is_ascii_digit()),
663 )
664 }
665 (None, None) => true,
666 _ => false,
667 }
668}
669
670pub fn take_blend_approximations(bodies: &[(&str, &crate::BrepSolid)]) -> Vec<crate::Approximation> {
683 let snapshot = BLEND_APPROXIMATIONS.with(|reports| std::mem::take(&mut *reports.borrow_mut()));
684 let mut unique: Vec<WallModelReport> = Vec::new();
685 for report in snapshot {
686 unique.retain(|kept| !(kept.name == report.name && same_surface(&kept.surface, &report.surface)));
687 unique.push(report);
688 }
689 let mut approximations = Vec::new();
690 for (body, solid) in bodies {
691 for report in &unique {
692 let carried = solid.shells.iter().flat_map(|shell| &shell.faces).any(|face| {
693 names_the_wall(&face.name, &report.name) && same_surface(&face.surface, &report.surface)
694 });
695 if carried {
696 let wall = report.name.as_deref().unwrap_or("(unnamed)");
697 let unaccepted = matches!(report.budget.reason, crate::BudgetReason::Unaccepted | crate::BudgetReason::Unread);
698 let budget = match report.budget.mechanism {
699 crate::BudgetMechanism::LocalRounds => format!(
700 "local refinement stopped: {:?} after {} of {} rounds, {} of {} stations per segment",
701 report.budget.reason, report.budget.rounds_used, report.budget.rounds_limit,
702 report.budget.stations, report.budget.station_limit,
703 ),
704 crate::BudgetMechanism::StationRungs => format!(
705 "the open march's station ladder stopped: {:?} after {} of {} global station rungs, {} of {} stations",
706 report.budget.reason, report.budget.rounds_used, report.budget.rounds_limit,
707 report.budget.stations, report.budget.station_limit,
708 ),
709 };
710 let unknown = report.budget.unread > 0;
711 let rails = report.budget.measured_component == crate::MeasuredComponent::Rails;
712 let mut message = if !unaccepted && (rails || unknown) {
713 let what = if rails {
717 format!("the fillet wall {wall}'s support rails stand")
718 } else {
719 format!("the fillet wall {wall} stands")
720 };
721 let against = if rails {
722 "off their exact construction (their carriers / the exact ball contacts)"
723 } else {
724 "from the rolling ball between its rails"
725 };
726 let reading = if unknown {
727 format!(
728 "an UNKNOWN distance {against}: {} interval(s) could not be read (the largest READABLE reading is {:.3e} mm, not the worst)",
729 report.budget.unread, report.residual
730 )
731 } else {
732 format!("{:.3e} mm {against}", report.residual)
733 };
734 format!(
735 "{what} {reading}, against a construction request of {:.3e} mm (accepted at the intersection-fit bar); {budget}",
736 report.request,
737 )
738 } else if unaccepted {
739 format!(
740 "the blend wall {wall} ships from a rung that never met acceptance: its worst reading \
741 {:.3e} mm against its acceptance bar {:.3e} mm; {budget}",
742 report.residual, report.request,
743 )
744 } else {
745 format!(
746 "the fillet wall {wall} stands {:.3e} mm from the rolling ball between its rails, against a \
747 construction request of {:.3e} mm (accepted at the intersection-fit bar); {budget}",
748 report.residual, report.request,
749 )
750 };
751 if let Some(detail) = &report.detail {
752 message = format!("{message} ({detail})");
753 }
754 approximations.push(crate::Approximation {
755 code: if unaccepted { "blend.wall_acceptance" } else { "blend.wall_model" }.to_string(),
756 body: body.to_string(),
757 measured: report.residual,
758 bar: report.request,
759 volume_bound: None,
760 edges: Vec::new(),
761 budget: Some(report.budget.clone()),
762 message,
763 });
764 }
765 }
766 }
767 approximations
768}
769
770
771pub use chain::{blend_smooth_chain, blend_smooth_chain_if_closed};
772pub(crate) use corner::round_concave_chain_corner;
773pub use corner::round_convex_corner;
774pub use edge::{blend_closed_edge, blend_closed_edge_knot_aware, blend_edge_variable, blend_open_edge, is_pcurve_floor_refusal, with_pcurve_knot_retry};
775pub(crate) use collapse::is_full_width_collapse;
776pub(crate) use planar_chart::{
777 fit_planar_charts_to_trims, is_planar_chart_refusal, planar_chart_overrun,
778};
779pub(crate) use track_fit::PCURVE_OFF_FLOOR;
780pub(crate) use edge::{consumed_band, is_consumed_snap, is_rail_collapse};
781pub(crate) use network::{
782 blend_star_network, blend_star_network_laws, blend_star_network_with_corner_policy, degenerate_corner_setbacks, mixed_convexity_corner, mixed_convexity_corners, selection_convexity, DegenerateSetback,
783 MixedCorner,
784};
785pub(crate) use miter::{is_marched_fit_off_carriers, is_miter_support_overrun};
786pub(crate) fn selection_mates<'a>(
789 solid: &'a crate::topology::BrepSolid,
790 edge_ids: &[u64],
791 radius: f64,
792) -> Result<Vec<(&'a crate::topology::EdgeRecord, [&'a crate::topology::FaceRecord; 2])>, crate::KernelRefusal> {
793 Ok(network::stripe_mates(solid, edge_ids, radius)?
794 .into_iter()
795 .map(|(edge, first, second)| (edge, [first.face, second.face]))
796 .collect())
797}
798
799pub(crate) use runout::{build_runout_walls, plan_runout, take_runout_reports, RunoutPlan, RunoutWallReport};
800pub(crate) use fold::{is_rail_fold, is_wall_fold, wall_loop_refusal, WALL_FOLDS};
801pub(crate) use stations::is_ball_off_carrier;
802pub(crate) use network::is_reentrant_concave;
805
806const CLOSED_TRIM_READ_FIRST_DEFAULT: bool = true;
824
825thread_local! {
826 static CLOSED_TRIM_READ_FIRST_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
827}
828
829pub(crate) fn closed_trim_read_first_on() -> bool {
830 if let Some(forced) = CLOSED_TRIM_READ_FIRST_FOR_TEST.with(|forced| forced.get()) {
831 return forced;
832 }
833 match std::env::var("BREP_BLEND_CLOSED_TRIM_READ_FIRST").as_deref() {
834 Ok("0") => false,
835 Ok("1") => true,
836 _ => CLOSED_TRIM_READ_FIRST_DEFAULT,
837 }
838}
839
840
841const CLOSED_WALL_PREDICT_DEFAULT: bool = true;
856
857thread_local! {
858 static CLOSED_WALL_PREDICT_FOR_TEST: std::cell::Cell<Option<bool>> = const { std::cell::Cell::new(None) };
859}
860
861pub(crate) fn closed_wall_predict_on() -> bool {
862 if let Some(forced) = CLOSED_WALL_PREDICT_FOR_TEST.with(|forced| forced.get()) {
863 return forced;
864 }
865 match std::env::var("BREP_BLEND_CLOSED_WALL_PREDICT").as_deref() {
866 Ok("0") => false,
867 Ok("1") => true,
868 _ => CLOSED_WALL_PREDICT_DEFAULT,
869 }
870}
871
872
873pub(crate) use corner::{open_mixed_corner_on, round_mixed_concave_open_corner};
875pub(crate) mod taper_runout;
880
881pub(crate) mod relief;