brep_kernel/csg/imprint.rs
1use crate::classification::{parameter_point_in_face, PolygonClass};
2use crate::curve::KNOT_IDENTITY_TOL;
3use crate::spatial::{Aabb, Bvh};
4use crate::tolerance::{
5 assembler_weld, merge_scale, solid_scale, COINCIDENCE_DISTANCE_FLOOR, WELD_FLOOR,
6};
7use crate::topology::{BrepSolid, EdgeRecord, FaceRecord};
8use crate::{
9 build_pcurve_on_surface, classify_surface_pair_cached,
10 fit_polyline, fit_polyline_of_degree, intersect_curve_surface, PolylineFit,
11 intersect_curves, intersect_surfaces, intersect_surfaces_supplemental, project_point_to_curve,
12 project_point_to_surface, project_point_to_surface_seeded, KnotVector, NurbsCurve, NurbsSurface,
13 SurfaceClassifyData,
14 SurfaceIntersectionOptions, SurfacePairClassification, SurfacePairRelation, Vec2, Vec3, Vec4,
15};
16use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
17use serde::{Deserialize, Serialize};
18use std::time::Duration;
19use web_time::Instant;
20
21/// Per-step wall-clock accumulators for `build_imprints`, printed to stderr
22/// when `BREP_PROFILE` is set. Native-only in practice (no env vars in wasm).
23#[derive(Default)]
24struct ImprintProfile {
25 enabled: bool,
26 pairs: u64,
27 marched_pairs: u64,
28 whole_reading_reuses: u64,
29 classify: Duration,
30 cosurface: Duration,
31 lies_on: Duration,
32 planar_iso: Duration,
33 analytic: Duration,
34 seeds: Duration,
35 march: Duration,
36 clip_and_fit: Duration,
37 process_curve: Duration,
38 /// `clip_and_fit` split: the swapped-order rescue marches, the trim clip,
39 /// the shared-boundary test, the polyline fit, and `process_curve`.
40 rescue_march: Duration,
41 clip: Duration,
42 shared_boundary: Duration,
43 fit: Duration,
44 process: Duration,
45 hermite_runs: u64,
46 fit_runs: u64,
47 hermite: Duration,
48 /// Appended at the end.
49 pairs_fits: u64,
50 exact_lane_fits: u64,
51 /// Slice 1: planar-iso sections whose exact pcurves were handed to the
52 /// builder, and those declined (a lifted chart, or no pcurve built).
53 exact_iso_sections: u64,
54 exact_iso_declined: u64,
55}
56
57impl ImprintProfile {
58 fn new() -> Self {
59 Self {
60 enabled: std::env::var("BREP_PROFILE").is_ok(),
61 ..Self::default()
62 }
63 }
64
65 fn lap(&self, started: &mut Option<Instant>) -> Duration {
66 if !self.enabled {
67 return Duration::ZERO;
68 }
69 let now = Instant::now();
70 let elapsed = started.map(|s| now - s).unwrap_or_default();
71 *started = Some(now);
72 elapsed
73 }
74
75 fn report(&self) {
76 if !self.enabled {
77 return;
78 }
79 eprintln!("imprint.whole_reading_reuses count={}", self.whole_reading_reuses);
80 let ms = |d: Duration| d.as_secs_f64() * 1_000.0;
81 eprintln!(
82 "imprint.profile pairs={} marched={} classify={:.2} cosurface={:.2} lies_on={:.2} planar_iso={:.2} analytic={:.2} seeds={:.2} march={:.2} clip_fit={:.2} process_curve={:.2}",
83 self.pairs,
84 self.marched_pairs,
85 ms(self.classify),
86 ms(self.cosurface),
87 ms(self.lies_on),
88 ms(self.planar_iso),
89 ms(self.analytic),
90 ms(self.seeds),
91 ms(self.march),
92 ms(self.clip_and_fit),
93 ms(self.process_curve),
94 );
95 eprintln!(
96 "imprint.clip_fit rescue_march={:.2} clip={:.2} shared_boundary={:.2} fit={:.2} process={:.2} hermite={:.2} hermite_runs={} fit_runs={}",
97 ms(self.rescue_march),
98 ms(self.clip),
99 ms(self.shared_boundary),
100 ms(self.fit),
101 ms(self.process),
102 ms(self.hermite),
103 self.hermite_runs,
104 self.fit_runs,
105 );
106 eprintln!(
107 "imprint.fits pairs={} exact_lanes={} exact_iso_sections={} exact_iso_declined={}",
108 self.pairs_fits,
109 self.exact_lane_fits,
110 self.exact_iso_sections,
111 self.exact_iso_declined,
112 );
113 }
114}
115
116/// Coarse observation of optional contact/graph calls, including early
117/// declines. It owns no geometry and does not change caller diagnostics.
118struct ImprintSubphaseClock {
119 started: Option<Instant>,
120 label: &'static str,
121}
122impl ImprintSubphaseClock {
123 fn new(label: &'static str) -> Self {
124 Self { started: std::env::var_os("BREP_PROFILE").is_some().then(Instant::now), label }
125 }
126}
127impl Drop for ImprintSubphaseClock {
128 fn drop(&mut self) {
129 if let Some(started) = self.started {
130 eprintln!("imprint.subphase label={} ms={:.3}", self.label, started.elapsed().as_secs_f64() * 1000.0);
131 }
132 }
133}
134
135#[derive(Clone, Debug, Deserialize)]
136pub struct ImprintOptions {
137 #[serde(default = "default_tolerance")]
138 pub tolerance: f64,
139 #[serde(default = "default_maximum_fit_points")]
140 pub maximum_fit_points: usize,
141 #[serde(default)]
142 pub local_fit: bool,
143 #[serde(default)]
144 pub fit_chunk_points: Option<usize>,
145 pub maximum_ssi_step: Option<f64>,
146 /// `None`, the default, marches exactly as before. `BREP_SPAN_BUDGET`
147 /// overrides either way: `fit` is the pair's `intersection_fit` at the
148 /// operands' scale, a number is an absolute budget, `off` is none.
149 /// `BREP_SPAN_CENSUS=<file>` appends one line per clipped section with its
150 /// stations, deviation and refine route (`span-budget census:`), labelled
151 /// by `BREP_CENSUS_LABEL`.
152 #[serde(default)]
153 pub span_budget: Option<f64>,
154 /// Implies a span budget of the pair's `intersection_fit` when none is
155 /// set. Off by default; `BREP_HERMITE_SECTIONS` overrides either way (`0`
156 /// off, anything else on).
157 #[serde(default)]
158 pub hermite_sections: bool,
159 /// The TANGENT-CURVE lane: where an exact lane proves a pair's transverse
160 /// intersection empty, or the tangential-only gate would skip it, and the
161 /// pair classification reads near-tangent, look for an EXTENDED contact
162 /// (`crate::tangent_curve`), march it with one planar carrier, and record it
163 /// as a typed tangent section ([`ImprintResultRecord::tangent_sections`]).
164 /// The boolean then names the contact in any refusal it raises on the pair
165 /// instead of leaking the arrangement's own message. On by default since the
166 /// census of 2026-10-10 (every manifest leaf identical with the lane on and
167 /// off, `hlr_tests` green both ways); the section is recorded, never
168 /// imprinted, so nothing the boolean builds changes.
169 /// `BREP_TANGENT_CURVE_LANE=0` is the escape hatch. Appended at the end of
170 /// the struct (2026-10-10).
171 #[serde(default = "default_tangent_curve_lane")]
172 pub tangent_curve_lane: bool,
173 /// EXACT PCURVES FOR THE PLANAR-ISO LANE (slice 1, 2026-10-11, the
174 /// catalogue's P3 applied to the exact lane): a section the planar-iso
175 /// lane answers is an iso-curve of the ruled carrier and an affine image
176 /// on the plane, so both pcurves are written down on the curve's own
177 /// knots (`support::planar_iso_section`) instead of fitted, and the
178 /// junction pass moves their ends instead of re-marching them. Off by
179 /// default (the fitted pcurves are the shipped bytes);
180 /// `BREP_EXACT_ISO_PCURVES` overrides either way: `0`/`off`, `on`, or
181 /// `check`, which fits as before and appends one census line per pcurve
182 /// comparing the fitted and the exact one in 3-D
183 /// (`BREP_EXACT_ISO_CENSUS=<file>`, labelled by `BREP_CENSUS_LABEL`). A
184 /// face read in a lifted chart declines the exact pcurve (the iso line
185 /// is in the carrier's frame). Appended at the end of the struct.
186 #[serde(default)]
187 pub exact_iso_pcurves: bool,
188 /// MARCH STARTS: every surface/surface march the imprint runs traces
189 /// its starts in order (explicit trim-boundary pierces first, the most
190 /// transverse first, a run-through start consumed with a snapped
191 /// station) and seeds its interior candidates from the two carriers'
192 /// knot-span leaves instead of a fixed grid
193 /// ([`SurfaceIntersectionOptions::ordered_starts`],
194 /// [`SurfaceIntersectionOptions::tree_seeds`]). Off by default (the
195 /// grid-then-seeds march is the shipped one, byte for byte);
196 /// `BREP_MARCH_STARTS` overrides either way: `0`/`off`, `order` (A3's
197 /// ordering alone), `snap` (the ordering with run-through starts
198 /// consumed), `tree` (A4 alone), `order+tree`, anything else all three.
199 /// Appended at the end of the struct.
200 #[serde(default)]
201 pub march_starts: bool,
202 /// Candidates for one junction are captured within the wide residual
203 /// band; two combine only when they are connected (share a support face)
204 /// and lie within the strict radius built from their own evidence radii;
205 /// the surviving position is the lowest-dimensional candidate's, an
206 /// operand vertex over an operand edge's crossing over a section end on
207 /// two carriers. Off by default until the census in the record promotes
208 /// it; `BREP_JUNCTION_COMBINE` overrides either way (`0`/`off`, `check`
209 /// for the census that logs every junction's candidates to
210 /// `BREP_JUNCTION_CENSUS` without moving anything, anything else on).
211 /// Appended at the end of the struct (2026-10-11).
212 #[serde(default)]
213 pub two_radius_junctions: bool,
214 /// IMPRINT THE TANGENT SECTION (the pinched result): a planar-carrier
215 /// contact the tangent-curve lane traced is also IMPRINTED —
216 /// `process_curve` on the clipped run, so the contact becomes a section
217 /// piece on both carriers, the operand edges its ends lie on are split
218 /// there, and every earlier piece the ends lie on (the through-bore's cap
219 /// circles) is split at them — and its piece ids are recorded in
220 /// [`TangentSectionRecord::piece_ids`]. The boolean's assembler then
221 /// pairs the four coedges of such a piece by MATERIAL SIDE (two edge
222 /// records on one locus, each manifold) instead of by arrival, so the
223 /// grazing through-bore builds as one shell whose tangent edge is carried
224 /// twice. On by default since the census of 2026-10-11 (the battery
225 /// moving only the pins that asserted the old refusal and the recentre
226 /// pin whose unguarded far fillet now completes);
227 /// `BREP_TANGENT_CURVE_IMPRINT=0` is the escape hatch. Requires
228 /// `tangent_curve_lane`. Appended at the end of the struct (2026-10-11).
229 #[serde(default = "default_tangent_curve_imprint")]
230 pub tangent_curve_imprint: bool,
231}
232
233fn default_tolerance() -> f64 {
234 1e-7
235}
236
237fn default_tangent_curve_lane() -> bool {
238 std::env::var("BREP_TANGENT_CURVE_LANE").as_deref() != Ok("0")
239}
240
241fn default_tangent_curve_imprint() -> bool {
242 std::env::var("BREP_TANGENT_CURVE_IMPRINT").as_deref() != Ok("0")
243}
244
245fn default_maximum_fit_points() -> usize {
246 80
247}
248
249/// Angular band inside which two surface normals count as PARALLEL when a face
250/// pair is classified (`|n_a × n_b| <= PAIR_ANGULAR_TOLERANCE` ⇒ the carriers
251/// graze rather than cross there). Single source for the pair classifier's
252/// angular tolerance and for the near-tangency test that picks the march step.
253const PAIR_ANGULAR_TOLERANCE: f64 = 1e-4;
254
255/// Fraction of the part extent used as the FLOOR of the near-tangency march
256/// step when the two carriers actually touch (`minimum_separation == 0`), so
257/// the reachable curve length is a fixed fraction of the model rather than an
258/// absolute distance. See `march_maximum_step`.
259const NEAR_TANGENT_STEP_FRACTION: f64 = 1e-3;
260
261/// Span band for the B2 shared-section-edge decision, as a FRACTION of the part
262/// extent (so it tracks feature size, not an absolute distance). A SSI section
263/// is declared coincident with an existing boundary edge only when it lies
264/// within `SHARED_SECTION_BAND_FRACTION * extent` of that edge along its whole
265/// span in BOTH directions AND their endpoints already coincide within the weld
266/// radius. Sized to admit a genuine near-tangent GRAZE (the marched/analytic
267/// section drifts from the vendor edge by a graze-scale gap two-three orders
268/// above the fit residual — the B1-falsification measurement) while staying far
269/// below the feature scale at which a distinct same-endpoint edge diverges.
270/// Measured on fixture 09 (extent ~3.23, band ~3.23e-3): the two genuine graze
271/// gaps are 8.2e-4 and 1.67e-3 (ratios 2.5e-4 / 5.2e-4) — comfortably inside;
272/// fixtures 12/04 (the wrong-reuse tripwires) produce ZERO reuses and the
273/// prim×prim fuzz shows 0/2800 outcome changes, so the band is well separated
274/// from any distinct-edge scale.
275const SHARED_SECTION_BAND_FRACTION: f64 = 1e-3;
276
277/// Maximum march step for one face pair.
278///
279/// A NEAR-TANGENT pair marches finely: where the carriers graze, the corrector
280/// can hop between the two sheets of the contact, so the step stays inside the
281/// separation band. Two things that step must NOT be:
282///
283/// * **applied to a pair that only crosses.** A pair is classified `Singular`
284/// when any sample near the contact has a DEGENERATE normal (a pole of a
285/// revolution carrier) — that says nothing about tangency, and the pair can
286/// cross at 90°. Marching a transverse crossing at the tangency step crawls,
287/// and a curve longer than `maximum_steps × step` then dies as "trace
288/// exhausted" (2026-08-06 report: a draft-angle extrude wall against a
289/// revolve-of-revolve face — a 2.0-unit curve needing 4014 steps of 4000,
290/// 10 points once marched at its own scale). The step is therefore gated on
291/// the classification's own tangency evidence, not on the relation label.
292/// * **an absolute distance.** A fixed floor is a different fraction of every
293/// model, so the reachable curve length depended on part size. The floor is
294/// a fraction of the part extent instead; `solid_scale` is at least 1, so
295/// unit-scale parts keep the historical `1e-3` exactly.
296fn march_maximum_step(
297 classification: &SurfacePairClassification,
298 requested: Option<f64>,
299 scale: f64,
300) -> Option<f64> {
301 let grazes = matches!(
302 classification.relation,
303 SurfacePairRelation::NearTangent | SurfacePairRelation::Singular
304 ) && classification.minimum_normal_cross <= PAIR_ANGULAR_TOLERANCE;
305 if !grazes {
306 return requested;
307 }
308 Some(
309 requested.unwrap_or(
310 classification
311 .minimum_separation
312 .max(NEAR_TANGENT_STEP_FRACTION * scale),
313 ) * 0.5,
314 )
315}
316
317impl Default for ImprintOptions {
318 fn default() -> Self {
319 Self {
320 tolerance: default_tolerance(),
321 maximum_fit_points: default_maximum_fit_points(),
322 local_fit: false,
323 fit_chunk_points: None,
324 maximum_ssi_step: None,
325 span_budget: None,
326 hermite_sections: false,
327 tangent_curve_lane: default_tangent_curve_lane(),
328 tangent_curve_imprint: default_tangent_curve_imprint(),
329 exact_iso_pcurves: false,
330 march_starts: false,
331 two_radius_junctions: false,
332 }
333 }
334}
335
336#[derive(Clone, Debug, Deserialize, Serialize)]
337pub struct ImprintVertex {
338 pub id: u64,
339 pub point: Vec3,
340}
341
342#[derive(Clone, Debug, Deserialize, Serialize)]
343pub struct FacePcurve {
344 pub operand: u8,
345 pub face_id: u64,
346 pub pcurve: NurbsCurve,
347}
348
349#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash, Serialize)]
350pub struct FaceKey {
351 pub operand: u8,
352 pub face_id: u64,
353}
354
355#[derive(Clone, Debug, Deserialize, Serialize)]
356pub struct ImprintPieceRecord {
357 pub id: u64,
358 pub curve: NurbsCurve,
359 pub t0: f64,
360 pub t1: f64,
361 pub start_vertex_id: u64,
362 pub end_vertex_id: u64,
363 pub pcurves: Vec<FacePcurve>,
364 pub support_faces: [FaceKey; 2],
365 /// OCCT common-block / `IsExistingPaveBlock` (B2): when this SSI section
366 /// curve was found to COINCIDE along its whole span with an EXISTING
367 /// boundary edge of one of its support faces, the section is not a new
368 /// 1-cell — it IS that boundary edge. Records `(operand, edge_id, aligned)`
369 /// of the existing edge to REUSE; `aligned` is whether the section's
370 /// start→end runs the same direction as the edge's start→end. The
371 /// assembler then resolves this piece to the shared boundary edge's
372 /// identity so both operands reference ONE edge (no duplicate section /
373 /// one-use edge). `None` = an ordinary freshly-minted section (the
374 /// historical behaviour; also what `BREP_SHARED_SECTION_EDGE=0` forces).
375 #[serde(default, skip_serializing_if = "Option::is_none")]
376 pub shared_edge: Option<(u8, u64, bool)>,
377}
378
379#[derive(Clone, Debug, Deserialize, Serialize)]
380pub struct EdgeSplitRecord {
381 pub operand: u8,
382 pub edge_id: u64,
383 pub parameters: Vec<f64>,
384}
385
386#[derive(Clone, Debug, Deserialize, Serialize)]
387pub struct FaceImprints {
388 pub operand: u8,
389 pub face_id: u64,
390 pub piece_ids: Vec<u64>,
391}
392
393#[derive(Clone, Debug, Deserialize, Serialize)]
394pub struct ImprintResultRecord {
395 pub vertices: Vec<ImprintVertex>,
396 pub pieces: Vec<ImprintPieceRecord>,
397 pub by_face: Vec<FaceImprints>,
398 pub edge_splits: Vec<EdgeSplitRecord>,
399 /// Operand edges geometrically OVERLAPPED by a section curve (the
400 /// cosurface/coincident locus: an inscribed sphere's contact circle
401 /// riding a cap ring). Fragment-selection fate must never flood across
402 /// them — the two sides of such an edge can lie on opposite sides of
403 /// the other operand (the cap disc inside, the wall outside).
404 #[serde(default)]
405 pub barrier_edges: Vec<(u8, u64)>,
406 /// Every ISOLATED TANGENT NODE the imprint admitted: a point where the two
407 /// carriers touch with parallel normals and the section crosses itself, so
408 /// the pair was marched rather than refused (see
409 /// `imprint/tangent_contact.rs`). The boolean attributes a later tearing to
410 /// these rather than reporting an anonymous degeneracy — the second-order
411 /// filter cannot decide a third-order singularity, so the assembly is the
412 /// authority on whether a node it admitted was really imprintable.
413 #[serde(default)]
414 pub tangent_nodes: Vec<Vec3>,
415 /// True when the imprint saw ANY surface-contact evidence — an accepted
416 /// pierce seed, a traced SSI branch (even one later clipped away), or a
417 /// minted piece. Pure material disjointness leaves none, while a
418 /// silently-lost section always leaves at least the upstream evidence, so
419 /// the boolean's legitimate-empty adjudication requires this to be false.
420 #[serde(default)]
421 pub section_evidence: bool,
422 /// Every face pair the imprint read as ONE surface and exchanged boundary
423 /// curves across instead of intersecting (`cosurface_pair`, or the sampled
424 /// pair classification's `Cosurface`). Selection holds a fragment whose
425 /// point lies in such a partner's trim On against it, so the coincidence
426 /// the sections were built on is the one the fragments are kept by.
427 #[serde(default)]
428 pub cosurface_pairs: Vec<(FaceKey, FaceKey)>,
429 /// Every EXTENDED tangential contact the tangent-curve lane recognised
430 /// ([`ImprintOptions::tangent_curve_lane`]): the carriers touch along a
431 /// curve inside both trims. A planar-carrier contact is marched and the
432 /// clipped run described; a curved/curved contact is named at its point
433 /// and not traced. None of these is imprinted yet — the record exists so
434 /// the boolean can attribute a refusal on the pair to the contact by name.
435 /// Appended at the end of the struct (2026-10-10).
436 #[serde(default)]
437 pub tangent_sections: Vec<TangentSectionRecord>,
438}
439
440/// One extended tangential contact the imprint recognised (see
441/// [`ImprintResultRecord::tangent_sections`]).
442#[derive(Clone, Debug, Deserialize, Serialize)]
443pub struct TangentSectionRecord {
444 pub first: FaceKey,
445 pub second: FaceKey,
446 /// Ends of the longest run of the traced contact inside both trims (the
447 /// contact point itself when the contact was not traced).
448 pub start: Vec3,
449 pub end: Vec3,
450 /// Chord length of that run.
451 pub length: f64,
452 /// The run is the whole traced curve and the curve closed on itself.
453 pub closed: bool,
454 /// Stations of the run.
455 pub stations: usize,
456 /// One carrier is a plane: the branch the lane traces. `false` names a
457 /// curved/curved contact the lane recognises but does not trace.
458 pub planar: bool,
459 /// The faces the contact INVOLVES: the two carriers' faces, and every face
460 /// of either operand that shares an edge the run's ends lie on (the
461 /// through-bore's contact line ends on the wall's top and bottom edges,
462 /// so the box's top and bottom faces are involved; a blind pocket's ends
463 /// lie on the bore's cap rings, so the caps are). The boolean's
464 /// attribution rewrites a refusal only when a face it names is in here.
465 #[serde(default)]
466 pub faces: Vec<FaceKey>,
467 /// The section pieces the contact was imprinted as
468 /// ([`ImprintOptions::tangent_curve_imprint`]): `process_curve`'s pieces
469 /// on the clipped run, in mint order. Empty when the section was recorded
470 /// only. The assembler pairs the coedges of these pieces by material side.
471 /// Appended at the end of the struct (2026-10-11).
472 #[serde(default, skip_serializing_if = "Vec::is_empty")]
473 pub piece_ids: Vec<u64>,
474 /// GRAZING PIERCES WITHOUT A SECTION (2026-10-17, the revolve-into-bore
475 /// report): a trim edge of one face pierces the other carrier INSIDE both
476 /// trims, but at an angle below the seed gate, and the march found no
477 /// branch for the pair. The pierce proves a section crosses the faces
478 /// there; the empty march says the crossing is too near tangency to walk
479 /// (a third-order contact: the branch is born tangent to a contact curve
480 /// on a shared boundary, `boolean::attribute_to_tangent_node`). Each
481 /// entry is the pierce point and its `|edge_tangent · normal|`. Such a
482 /// record is attribution only — nothing is imprinted — and names the
483 /// contact when the assembly tears downstream of it. Appended at the end
484 /// of the struct.
485 #[serde(default, skip_serializing_if = "Vec::is_empty")]
486 pub grazing: Vec<(Vec3, f64)>,
487}
488
489impl TangentSectionRecord {
490 /// A record standing on its grazing pierces ALONE — the lane found no
491 /// contact to name, so the driver set its ends to the pierces (its
492 /// `start` IS the first pierce, bit for bit, and nothing was traced). The
493 /// boolean's attribution reads such a record only as a fallback: a
494 /// contact the lane found, or a tangent node the imprint classified, is
495 /// the nearer cause and keeps its own wording.
496 pub fn pierce_only(&self) -> bool {
497 self.stations == 0
498 && self
499 .grazing
500 .first()
501 .is_some_and(|(point, _)| point.sub(self.start).length() == 0.0)
502 }
503}
504
505#[path = "imprint/ruled_torus.rs"]
506mod ruled_torus;
507pub(crate) use ruled_torus::{circular_revolution_profile, construct_ruled_torus, RuledTorusBoundary};
508
509#[path = "imprint/support.rs"]
510mod support;
511#[path = "imprint/builder.rs"]
512mod builder;
513#[path = "imprint/junctions.rs"]
514mod junctions;
515#[path = "imprint/self_touch.rs"]
516mod self_touch;
517#[path = "imprint/sections.rs"]
518mod sections;
519#[path = "imprint/tangent_contact.rs"]
520mod tangent_contact;
521#[path = "imprint/driver.rs"]
522mod driver;
523#[path = "imprint/hermite.rs"]
524mod hermite;
525
526#[path = "imprint/gate_census.rs"]
527mod gate_census;
528
529use builder::*;
530use junctions::*;
531use sections::*;
532use support::*;
533
534#[path = "imprint/section_graph.rs"]
535mod section_graph;
536pub(crate) use section_graph::{plane_section_graph, plane_section_graph_range, plane_section_graph_range_bounded};
537
538/// Reuse imprint's exact ruled-carrier section at the assembly repair boundary.
539/// This exposes the construction, not its arrangement or acceptance policy.
540pub(crate) fn planar_carrier_section(
541 first: &NurbsSurface,
542 second: &NurbsSurface,
543 tolerance: f64,
544) -> Result<Option<NurbsCurve>, crate::KernelRefusal> {
545 support::planar_iso_intersection(first,second,tolerance)
546}
547
548use tangent_contact::classify_tangent_contact;
549
550pub use driver::build_imprints;
551pub(crate) use driver::{build_carrier_imprints, take_rescue_firings};
552pub(crate) use self_touch::self_touch_edge_splits;
553
554
555#[path = "imprint/pcurve_cache.rs"]
556mod pcurve_cache;
557use pcurve_cache::cached_build_pcurve as build_pcurve_on_surface_marched;