Skip to main content

brep_kernel/
lib.rs

1
2use serde::{Deserialize, Serialize};
3use wasm_bindgen::prelude::*;
4
5/// One environment variable read through its call site's once-cell
6/// ([`env_read::read`], lane O row 19): `std::env::var(name).ok()` as an
7/// `Option<Cow<str>>`, kept for the process when `BREP_ENV_READ_ONCE` is on.
8/// Only for variables nothing sets at run time.
9macro_rules! env_read {
10    ($name:literal) => {{
11        static CELL: std::sync::OnceLock<Option<String>> = std::sync::OnceLock::new();
12        $crate::env_read::read($name, &CELL)
13    }};
14}
15/// One on/off switch (consolidation S1): `switch!("BREP_X", default)` reads
16/// `BREP_X` through its call site's once-cell ([`env_read::switch`], kept for
17/// the process while `BREP_ENV_READ_ONCE` is on); `switch!(live "BREP_X",
18/// default)` reads it on every call, for a variable a probe sets at run time.
19/// One grammar (owner decision 2026-11-01): `0|off|false` off, `1|on|true`
20/// on, unset or anything else the default. In the kernel's tests one keyed
21/// override (`env_read::with_switch_for_test`) forces either form.
22#[macro_export]
23macro_rules! switch {
24    (live $name:expr, $default:expr) => {
25        $crate::env_read::switch_live($name, $default, $crate::env_read::Grammar::Switch)
26    };
27    ($name:expr, $default:expr) => {{
28        static CELL: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
29        $crate::env_read::switch($name, &CELL, $default, $crate::env_read::Grammar::Switch)
30    }};
31}
32
33/// One debug, trace or `BREP_NO_*` flag (consolidation S1): on when its
34/// variable is set to anything but `0`, `off` or `false`.
35/// `debug_flag!(live "BREP_X")` reads it on every call.
36#[macro_export]
37macro_rules! debug_flag {
38    (live $name:expr) => {
39        $crate::env_read::switch_live($name, false, $crate::env_read::Grammar::Flag)
40    };
41    ($name:expr) => {{
42        static CELL: std::sync::OnceLock<Option<bool>> = std::sync::OnceLock::new();
43        $crate::env_read::switch($name, &CELL, false, $crate::env_read::Grammar::Flag)
44    }};
45}
46
47/// One `0|off / check / on` mode (consolidation X1): an
48/// [`env_read::Mode`]; unset, `On` when `default_on` and `Off` otherwise.
49/// `mode_switch!(live "BREP_X", default_on)` reads it on every call.
50#[macro_export]
51macro_rules! mode_switch {
52    (live $name:expr, $default_on:expr) => {
53        $crate::env_read::mode_live($name, $default_on)
54    };
55    ($name:expr, $default_on:expr) => {{
56        static CELL: std::sync::OnceLock<Option<$crate::env_read::Mode>> = std::sync::OnceLock::new();
57        $crate::env_read::mode($name, &CELL, $default_on)
58    }};
59}
60
61/// `eprintln!` for a `BREP_PROFILE` line: buffered while a
62/// [`profile::ProfileSink`] is open (consolidation I8).
63macro_rules! profile_line {
64    ($($arg:tt)*) => { $crate::profile::profile_emit(format_args!($($arg)*)) };
65}
66
67// The wasm global allocator, when the `wasm-talc` feature asks for it.
68#[cfg(all(feature = "wasm-talc", target_family = "wasm", not(target_feature = "atomics")))]
69#[global_allocator]
70static TALC: talc::wasm::WasmDynamicTalc = talc::wasm::new_wasm_dynamic_allocator();
71
72// The native global allocator, when the `native-rusty-alloc` feature asks for it.
73#[cfg(all(feature = "native-rusty-alloc", not(target_family = "wasm")))]
74#[cfg(not(feature = "profile-wall"))]
75#[global_allocator]
76static RUSTY_ALLOC: rusty_alloc_api::RustyAlloc = rusty_alloc_api::RustyAlloc;
77
78#[cfg(all(feature = "native-rusty-alloc", not(target_family = "wasm"), feature = "profile-wall"))]
79#[global_allocator]
80static RUSTY_ALLOC: wall_profile::Allocator<rusty_alloc_api::RustyAlloc> = wall_profile::Allocator(rusty_alloc_api::RustyAlloc);
81
82mod profile;
83#[cfg(feature = "profile-wall")]
84mod wall_profile;
85#[cfg(not(feature = "profile-wall"))]
86#[path = "wall_profile_off.rs"]
87mod wall_profile;
88#[cfg(feature = "profile-wall")]
89mod reader_profile;
90#[cfg(not(feature = "profile-wall"))]
91#[path = "reader_profile_off.rs"]
92mod reader_profile;
93
94
95/// Switch reading for the kernel's dependants ([`switch!`],
96/// [`debug_flag!`], [`mode_switch!`]); not a stable interface.
97#[doc(hidden)]
98pub mod env_read;
99
100/// The wasm panic hook every `#[wasm_bindgen]` entry point installs. Replaces
101/// the `console_error_panic_hook` crate for the whole BREP family — reached as
102/// `brep_kernel::panic_hook` and, through the render engine's re-export, as
103/// `brep_render::brep_kernel::panic_hook`.
104pub mod panic_hook;
105
106/// Plain-text formats shared by the CAD app and the PLM server
107/// (`text::delimited`: CSV and tab-separated text).
108pub mod text;
109
110#[path = "geometry/tolerance.rs"]
111mod tolerance;
112pub use tolerance::{
113    curve_model_scale, model_scale, report_scale_migration, solid_model_scale, solid_scale,
114    offset_construction_band, vertex_tolerance_from_edges, KernelTolerances,
115    MeasuredTolerance, OFFSET_CONSTRUCTION_FLOOR, OFFSET_CONSTRUCTION_REL,
116    VERTEX_MATCH_FLOOR, VOLUME_DRIFT_ABS, VOLUME_DRIFT_REL,
117};
118// Per-entity measured tolerances: the lazy, capped band an individual edge or
119// vertex earns from its own redundant representations, on top of the global
120// policy above. Measurement only — no record field, no serialization; see the
121// module doc for invariants I1/I2.
122#[path = "geometry/entity_tolerance.rs"]
123mod entity_tolerance;
124pub use entity_tolerance::{EntityTolerances, EDGE_CAP_FRACTION, VERTEX_CAP_FRACTION};
125#[path = "props/diagnostics.rs"]
126mod diagnostics;
127pub use diagnostics::{
128    Approximation, ApproximationBudget, Backing, BudgetMechanism, BudgetReason, MeasuredComponent, DiagnosticEvent, DiagnosticSeverity, EdgeOrigin, KernelDiagnostics,
129    KernelOutcome, KernelRefusal, OffCarrierEdge,
130    KernelStage, OrRefuse, RefusalClass, SoundnessDefect, UndecidedCause,
131};
132#[path = "geometry/polygon.rs"]
133pub mod polygon;
134#[path = "geometry/curve.rs"]
135mod curve;
136pub use curve::{
137    make_arc, make_circle, make_hyperbola, make_line, make_parabola, uniform_clamped_knots,
138    KnotVector, NurbsCurve, Vec4, KNOT_DEDUP_EPS, KNOT_IDENTITY_TOL,
139};
140#[path = "blending/blend/mod.rs"]
141mod blend;
142pub use blend::{
143    take_blend_approximations, take_blend_notes,
144    blend_timing_capture, take_blend_timings, BlendTiming,
145    blend_closed_edge, blend_edge_variable, blend_open_edge, blend_smooth_chain,
146    round_convex_corner,
147};
148#[path = "blending/fillet.rs"]
149mod fillet;
150pub use fillet::{
151    chamfer_edge, chamfer_edge_angle, chamfer_edge_asymmetric, chamfer_edges_angle,
152    chamfer_edges_asymmetric, fillet_edge, fillet_edges, fillet_edges_reported, fillet_edges_reported_at_offset,
153    fillet_edges_variable, fillet_edges_variable_law, fillet_edges_variable_vertex_radii,
154    BlendSelection,
155};
156#[path = "blending/law.rs"]
157mod law;
158pub use law::{LawSegment, RadiusLaw};
159#[path = "geometry/fit.rs"]
160mod fit;
161pub use fit::{
162    fit_polyline, fit_polyline_at, fit_polyline_of_degree, interpolate_curve, interpolate_curve_closed,
163    interpolate_curve_local, interpolate_curve_thinned, interpolate_curve_with_end_tangents,
164    simplify_polyline, simplify_polyline_indices, solve_banded, solve_dense, PolylineFit,
165    PolylineFitExit, PolylineFitLedger, PolylineFitReport, PolylineFitScope, MAX_FIT_STATIONS,
166};
167#[path = "geometry/image_curve.rs"]
168mod image_curve;
169pub use image_curve::{affine_image_curve, image_curve, image_curve_pair, ImageCurve, ImageCurveTier};
170#[path = "geometry/surface.rs"]
171mod surface;
172pub use surface::{
173    carrier_preview_patch, make_cone_surface, make_cylinder_surface, make_extrusion, make_plane,
174    make_revolution, make_sphere_surface, make_sphere_surface_framed, make_torus_surface,
175    ExtendRefusal, NurbsSurface, SurfaceSide, WeightSite, MAXIMUM_FOLD_ANGLE,
176    MAXIMUM_GROWTH_RATIO,
177};
178#[path = "geometry/control_cage.rs"]
179mod control_cage;
180pub use control_cage::{
181    CagePoint, CagePrimitive, ControlCage, CAGE_DEGREE, MAX_RINGS, MAX_SEGMENTS, MIN_RINGS,
182    MIN_SEGMENTS,
183};
184#[path = "geometry/coons.rs"]
185mod coons;
186pub use coons::{
187    coons_bilinear, coons_bilinear_reported, coons_with_ribbons, CompatWork, CoonsRefusal,
188    CoonsReport, RibbonReport,
189};
190#[path = "geometry/gordon.rs"]
191mod gordon;
192pub use gordon::{gordon_surface, gordon_surface_reported, GordonRefusal, GordonReport};
193#[path = "geometry/analytic_surface.rs"]
194mod analytic_surface;
195pub use analytic_surface::{
196    circle_angle_to_parameter, intersect_analytic_pair, plane_ruled_section_arc,
197    revolution_structure, AnalyticSurface, RevolutionFrame, RevolutionStructure,
198};
199pub(crate) use analytic_surface::{
200    canonical_carriers_enabled, canonicalise_carriers, intersect_analytic_pair_with, ruled_gate,
201    ruled_gate_census, CanonicalReport, CanonicalRewrite, RuledGate,
202};
203#[path = "geometry/sphere_chart.rs"]
204mod sphere_chart;
205pub use sphere_chart::{
206    canonicalize_points, chart_grid_divisions, chart_grid_parameters, corner_index,
207    cube_edge_corner, cube_edge_divisions, cube_edge_index, cube_edge_parameters,
208    cube_edge_parts, Chart, ChartSegment, ChartSide, SphereAtlas,
209    SphericalRegion, CHART_COUNT, CUBE_CORNER_COUNT, CUBE_EDGE_COUNT,
210};
211#[path = "brep/solid_codec.rs"]
212mod solid_codec;
213pub use solid_codec::{decode_solid, encode_solid, SolidNames, SOLID_CODEC_VERSION, SOLID_CODEC_VERSION_KIND};
214#[path = "brep/topology.rs"]
215mod topology;
216pub use topology::{
217    make_box_brep, make_cylinder_brep, make_pyramid_brep, topology_kind, BodyKind, BrepSolid, CellRecord, CoedgeRecord,
218    FaceRecord, IssueKind, KindReport, LoopRecord, TopologyKind, ValidationReport,
219};
220#[path = "brep/soundness.rs"]
221mod soundness;
222pub use soundness::{
223    face_self_intersections, loop_self_crossings, shell_vector_areas, solid_connectivity,
224    solid_euler, solid_self_intersections, ConnectivityReport, CrossingConfirmation, EulerReport,
225    CertificateScope, CertifiedFacePair, FaceCrossing, FaceFold, FaceVectorArea,
226    LoopCrossing, LoopCrossingReport, SelfIntersectionOptions, SelfIntersectionReport,
227    ClosureReading, ShellConnectivity, ShellVectorArea, VectorAreaReport, CrossingUndecidedCause, UndecidedCrossing,
228    MAX_LISTED_UNDECIDED,
229};
230#[path = "brep/topology_arena.rs"]
231mod topology_arena;
232pub use topology_arena::{
233    ArenaCoedge, ArenaEdge, ArenaFace, ArenaLoop, ArenaShell, ArenaVertex, CoedgeId, EdgeId,
234    FaceId, LoopId, ShellId, TopologyArena, VertexId,
235};
236#[path = "brep/analytic_topology.rs"]
237mod analytic_topology;
238pub use analytic_topology::{
239    make_cone_brep, make_sphere_brep, make_sphere_brep_framed, make_torus_brep,
240};
241#[path = "construction/solid_sweep.rs"]
242mod solid_sweep;
243pub use solid_sweep::{
244    sweep_solid_within, OutlinePiece, ScrewStep, SolidMotion, SweepClip, SweptTool, SOLID_SWEEP_REFUSAL,
245};
246#[path = "construction/solid_sweep_caps.rs"]
247mod solid_sweep_caps;
248pub use solid_sweep_caps::{sweep_solid, sweep_prism_along, sweep_solid_along, sweep_solid_clipped, sweep_solid_parts, sweep_solid_rising, sweep_solid_tumbled, FrameCurve, RisingTurn, TwoTurns, SolidTool, SOLID_SWEEP_WHOLE_REFUSAL};
249#[path = "geometry/closed_curves.rs"]
250mod closed_curves;
251pub use closed_curves::{make_ellipse, make_ellipse_arc, make_ellipsoid_brep, make_periodic_bspline};
252#[path = "construction/surface_faces.rs"]
253mod surface_faces;
254pub use surface_faces::{face_from_surface, face_from_surface_trimmed, solid_from_sheets, SURFACE_FACE_REFUSAL};
255#[path = "construction/polygon_solids.rs"]
256mod polygon_solids;
257pub use polygon_solids::{make_pyramid_on_polygon, solid_from_polygons};
258#[path = "construction/loft_guides.rs"]
259mod loft_guides;
260pub use loft_guides::{loft_profile_brep_on_guides, LOFT_ON_GUIDES_REFUSAL};
261#[path = "construction/extrude_up_to.rs"]
262mod extrude_up_to;
263pub use extrude_up_to::{extrude_profile_up_to_face, solid_pieces, EXTRUDE_UP_TO_REFUSAL};
264#[path = "offset/thicken_faces.rs"]
265mod thicken_faces;
266pub use thicken_faces::{extrude_curves_to_sheet, thicken_sheet_faces, THICKEN_FACES_REFUSAL};
267#[path = "offset/shell_rounded.rs"]
268mod shell_rounded;
269pub use shell_rounded::{offset_shell_rounded, SHELL_ROUNDED_REFUSAL};
270#[path = "construction/sweep_round_corners.rs"]
271mod sweep_round_corners;
272pub use sweep_round_corners::{sweep_profile_round_corners, SWEEP_ROUND_CORNERS_REFUSAL};
273#[path = "construction/sweep_ellipsoid.rs"]
274mod sweep_ellipsoid;
275pub use sweep_ellipsoid::sweep_ellipsoid_along_line;
276#[path = "construction/sweep_topology.rs"]
277mod sweep_topology;
278pub(crate) use sweep_topology::is_sweep_tight_bend;
279pub use sweep_topology::{
280    extrude_profile_brep, extrude_profile_brep_draft, fit_helix_curve, helix_sample_points,
281    profile_anchor, rib_from_profile, sweep_bend_profile, sweep_closure, BendStation,
282    JointContinuity, PathJoint, SweepClosure,
283    RibExtrusion, RibNames, SweepPath, MAX_JOINT_TANGENT_BREAK, SWEEP_TIGHT_BEND_REFUSAL,
284    SWEEP_TWIST_CLOSURE_REFUSAL,
285    build_swept_envelope, recognize_swept_envelope, swept_envelope_volume, SweptEnvelope,
286    SWEEP_ENVELOPE_REFUSAL, sweep_envelope_bodies,
287    sweep_profile_along_chain, sweep_profile_along_chain_reported,
288    sweep_profile_along_chain_with_stations, sweep_profile_along_chain_with_stations_reported,
289    sweep_profile_along_path,
290    sweep_profile_along_path_anchored,
291    sweep_profile_helix,
292    sweep_profile_twisted, sweep_profile_twisted_anchored, sweep_profile_twisted_scaled,
293    ProfileAnchor, SectionPlacement,
294    StationBudget, SweepStationReport, SweptChain, SWEEP_DEFAULT_TOLERANCE, SWEEP_MAX_STATIONS,
295    SWEEP_REFINEMENT_ROUNDS,
296};
297#[path = "construction/revolve_topology.rs"]
298mod revolve_topology;
299pub use revolve_topology::{revolve_profile_brep, revolve_profile_brep_named};
300#[path = "construction/loft_topology.rs"]
301mod loft_topology;
302pub use loft_topology::{
303    loft_profile_brep, loft_profile_brep_closed, loft_profile_brep_guided,
304    loft_profile_brep_guided_frame, loft_profile_brep_tangent,
305};
306pub(crate) use loft_topology::loft_profile_brep_closed_shifted;
307#[path = "brep/transform_topology.rs"]
308mod transform_topology;
309#[path = "brep/recentre.rs"]
310mod recentre;
311pub use transform_topology::{mirror_brep, transform_brep, AffineTransform};
312#[path = "edit/split.rs"]
313mod split;
314pub use split::{
315    split_solid_by_face_surface, split_solid_by_plane, split_solid_by_surface, SplitSurface,
316};
317#[path = "props/mass_properties.rs"]
318mod mass_properties;
319pub use mass_properties::{
320    curve_arc_length, edge_arc_length, face_area, face_boundary_length, face_volume_contribution,
321    face_set_identity, mass_caller, parameter_space_area, solid_edge_length_total,
322    solid_mass_properties,
323    solid_mass_properties_full, signed_volume_against, solid_signed_volume, solid_signed_volume_for_sign, trim_polygons,
324    DensityMassProperties,
325    FaceSetIdentity, FullMassProperties, MassCaller, MassProperties, MassPropertiesCache,
326};
327#[path = "props/carrier_dimensions.rs"]
328mod carrier_dimensions;
329pub use carrier_dimensions::{curve_carrier_dimensions, surface_carrier_dimensions, CarrierDimensions};
330#[path = "meshing/tessellation.rs"]
331mod tessellation;
332pub use tessellation::{tessellate_brep, tessellate_face, TessellationOptions};
333#[path = "meshing/watertight_tessellation/mod.rs"]
334mod watertight_tessellation;
335pub use watertight_tessellation::{
336    sample_edge_polyline_checked, EdgeChordError, EdgeChordFailure, TessellationError,
337    tessellate_brep_watertight_checked,
338    sample_edge_polylines, sample_edges_encoded, tessellate_brep_watertight,
339    tessellate_brep_watertight_face_stride, tessellate_brep_watertight_face_stride_with_samples,
340};
341// The Rust feature-history execution engine (migration-plan Stage 4/5 foundation).
342// Deserializes the serialized `{ type, inputParams, ... }` descriptor shape, runs
343// each feature against a live scene-map of resident handles, and returns per-feature
344// results (handles + face/edge names). See feature_pipeline/mod.rs for the contract.
345#[path = "feature_pipeline/mod.rs"]
346pub mod feature_pipeline;
347pub use feature_pipeline::execute_history_json;
348pub use feature_pipeline::{first_reference_name, reference_names};
349// Transform Face's default pivot (the selection's boundary centre) over the
350// results of a replayed prefix, so the gizmo and the stored `pivot` agree with
351// the feature by construction.
352pub use feature_pipeline::face_transform_pivot;
353// Typed, in-process pipeline surface for native consumers (brep-render): run a
354// whole history and read the results without a JSON round trip.
355pub use feature_pipeline::{
356    clear_history_cache, execute_history, execute_history_observed, AddedSolid, Axis,
357    ComponentRecord, FeatureDescriptor, FeatureRefusal, FeatureResult, Frame, Fulfilment,
358    HistoryProgress, HistoryRequest, Rejected, RejectedKind,
359    HistoryResult, PortKind, PortRecord, ProfileLoop, ScenePoint, SketchProfile,
360};
361// Wire harness: the document's `wireHarness` block (connections), the tail's
362// routing report (per-connection status / length / route, per-segment bundle),
363// and the sided-port vocabulary the spline attachments share.
364pub use feature_pipeline::wire_harness::{
365    bundle_diameter, Attachment as SplineAttachment, BundleStatus, PortSide, RouteResult,
366    RouteStatus,
367    WireHarnessBundle, WireHarnessConnection,
368    WireHarnessEndpoint, WireHarnessReport, WireHarnessState, BUNDLE_SOLID_PREFIX,
369    WIRE_HARNESS_FEATURE_ID, WIRE_HARNESS_FEATURE_TYPE,
370};
371// A part's pins ARE its declared connection points, bound BY NAME within one
372// symbol unit and its port group: the report of what does not pair, following
373// an edit on one side to the other, the unit-to-group mapping, and a placed
374// part's pin resolved to a point ADDRESS (refused by name when it names none).
375pub use feature_pipeline::part_pins::{
376    declarations as declared_ports, declared_endpoints as declared_point_endpoints,
377    declared_points, endpoint_occurrence, follow_pin_edit, follow_point_edit, pin_labels,
378    pin_point_report, pins,
379    point_renames, port_group_for_unit, resolve_component_pin, resolve_part_pin,
380    unit_count as symbol_unit_count, unit_groups, DeclaredPin, DeclaredPoint, PinPoint,
381    PinPointChange, PinPointProblem, PinPointReport, PointRenames, DEFAULT_PORT_NAME,
382    DEFAULT_PORT_PURPOSE, PADS_BLOCK, SYMBOL_BLOCK,
383};
384// The declared-ports block itself: the schema a part document carries, the
385// address scheme, the unit map and the encapsulation-boundary rule — and the
386// SEAT, the frame a point's placement is an offset in, with the two mappings
387// an editor needs to put a gizmo on one and write the drag back.
388pub use feature_pipeline::ports::{
389    address as port_address, document_is_boundary, seat_of as port_seat_of, seat_point,
390    seat_vector, seated_direction, split_address, unit_map, unseat_point, unseat_vector,
391    world_seat, PortDeclaration, PortPoint, PortPointRow, PortSeat, PortsReport, UnitProblem,
392    BOUNDARY_KEY, PORTS_BLOCK, PORTS_FEATURE_ID, PORTS_FEATURE_TYPE,
393};
394// PMI: the document's `pmi` block (views + annotations), the type table with
395// its schemas / selection predicates, the tail's resolved report, and the
396// presentation layout shared by the viewport and the AP242 export.
397pub use feature_pipeline::pmi::{
398    balloon_head as pmi_balloon_head, clamp_text_size, format_dimension, format_number,
399    nearest_point_on_edge, nearest_point_on_solid, pmi_schema_catalogue, pmi_type,
400    nearest_displayed_point, nearest_displayed_point_on_edge, pose_apply as pmi_pose_apply, pose_unapply as pmi_pose_unapply,
401    resolve_state as pmi_resolve_state, entry_names_solid, baseline_hide_id, BalloonAnchor, BalloonAnchorMode, CenterCircle, FcfFrame, HalfSpace as PmiHalfSpace, HeadScope as PmiHeadScope, PmiAnnotation,
402    PmiAnnotationReport, PmiCamera, PmiDisplay, PmiGeometry, PmiPlane, PmiPose, PmiProjection, PmiReport,
403    PmiState, PmiStatus, PmiTypeDef, PmiView, PmiViewReport, SolidPoses as PmiSolidPoses, ToleranceBlock, ToleranceMode, PMI_TYPES,
404};
405pub use feature_pipeline::pmi::layout::{present as pmi_present, LayoutStyle as PmiLayoutStyle, Presentation as PmiPresentation};
406pub use feature_pipeline::pmi::annotations::fcf::{characteristic as pmi_characteristic, Characteristic as PmiCharacteristic, CHARACTERISTICS as PMI_CHARACTERISTICS};
407// The sketch loop-id write-back: stamps each closed loop's stable id onto its
408// geometries so per-loop face names survive deleting the edge an id came from.
409// The editor calls it when a sketch is committed.
410pub use feature_pipeline::assign_sketch_loop_ids;
411// The 3D sketch feature's reference resolver and record shape, shared with the
412// engine-native 3D sketch editor so the editor materializes exactly what the
413// feature re-resolves on every run.
414pub use feature_pipeline::features::sketch3d::{
415    reference_record as sketch3d_reference_record, resolve_reference as resolve_sketch3d_reference,
416    ResolvedReference as Sketch3dResolvedReference, REFERENCE_TIP_LENGTH as SKETCH3D_REFERENCE_TIP_LENGTH,
417    common_edge_end as sketch3d_common_edge_end, curve_reference_controls as sketch3d_curve_reference_controls, REFERENCE_ANCHOR_EDGES_KEY as SKETCH3D_ANCHOR_EDGES_KEY,
418    SKETCH3D_DOC_KEY, SKETCH3D_EDIT_BUTTON, SKETCH3D_FEATURE_TYPE, SKETCH3D_REFERENCES_PARAM,
419    SKETCH3D_REFS_KEY,
420};
421// Sheet-metal flat-pattern (unfold) → 2D vector export, keyed by resident handle.
422pub use feature_pipeline::{flat_pattern_dxf, flat_pattern_svg, is_sheet_metal_handle};
423// The part Pattern's per-instance suppression list.
424pub use feature_pipeline::{pattern_component_instance, pattern_parse_suppressed};
425// Native IMPORT3D payload: finished solids → the `io/snapshot` container with
426// IMPORT3D's naming stamped on, i.e. the `inputParams.nativeBrep` an import lane
427// bakes into a part document (`features/import3d.rs` reads it back verbatim).
428pub use feature_pipeline::{native_import_payload, native_import_payload_with_appearance};
429// The scene-metadata isolation bracket a payload producer holds across the
430// encode, so ambient records of the LIVE document never leak into a new part.
431pub use feature_pipeline::IsolatedSceneMetadata;
432// The imported-colour read of the name-keyed scene-metadata store. Thread-local
433// like the rest of it, so an off-thread runner reads it and ships the result.
434pub use feature_pipeline::scene_metadata_colors_json;
435// Family tables (rows, the expression rewrite, the persisted fingerprints): one
436// copy for the CAD app and the PLM server.
437pub use feature_pipeline::family;
438// The assemblies parts library (unique part payloads for ACOMP instances):
439// insert / update-refresh mutations and the save-side serialization surface.
440pub use feature_pipeline::{
441    add_part_to_library, add_part_to_library_impl, install_parts_library, missing_library_parts,
442    parts_library_json, parts_library_map, parts_library_revision, refresh_library_entry,
443    refresh_library_entry_impl, same_build, follow_saved_rows, follow_saved_rows_known, kernel_source_stamp, stable_json_hash, PartsLibraryEntry, PartsLibraryMap,
444};
445// Assembly constraint state + exported ABI (state/statuses/DOF/overlay reads,
446// constraint CRUD with auto-solve, the document pose/isFixed write-back fold).
447pub use feature_pipeline::assembly::{
448    assembly_add_constraint_json, assembly_apply_document_json,
449    assembly_apply_inferred_constraints_json, assembly_component_entities_json, assembly_dof_json,
450    assembly_infer_constraints_json, assembly_inferable_types_json,
451    assembly_move_constraint_json, assembly_overlay_json, assembly_pose_updates_json,
452    assembly_drag_solve_json, assembly_frame_solve_json, assembly_remove_constraint_json, assembly_resolve_refs_json,
453    assembly_run_solve_json,
454    assembly_set_constraint_enabled_json, assembly_set_constraint_open_json,
455    assembly_state_json, assembly_statuses_json, assembly_update_constraint_json,
456    constraint_hidden_params, constraint_schema_catalogue,
457    // The native doors: the same mutations with their errors as text. A native
458    // caller must use these — a `JsValue` error aborts a native build.
459    assembly_add_constraint_impl, assembly_apply_document_impl, assembly_move_constraint_impl,
460    assembly_drag_solve_impl, assembly_frame_solve_impl, assembly_flip_plan_json, assembly_rebase_mechanical_impl, assembly_remove_constraint_impl, assembly_run_solve_impl,
461    assembly_set_constraint_enabled_impl, assembly_set_constraint_open_impl,
462    assembly_update_constraint_impl,
463    assembly_session_export, assembly_session_import, assembly_session_serial, RetiredSession, SessionTransfer,
464};
465pub use feature_pipeline::{AssemblyState, ConstraintEntry};
466// The ONE matrix → `{translate, rotateEulerDeg}` (intrinsic XYZ, degrees) pose
467// encoder, shared by the solver write-back and any out-of-crate ACOMP author.
468pub use feature_pipeline::assembly::transform_to_pose_params;
469// The feature-schema catalogue (feature definitions: name + `inputParamsSchema`),
470// so a native UI can drive schema-driven feature dialogs without the JSON export.
471pub use feature_pipeline::feature_schema_catalogue;
472// The dialog field-visibility hook that pairs with the catalogue: given a feature
473// type + current param values, which params the schema dialog should hide.
474pub use feature_pipeline::feature_hidden_params;
475// Selection-context applicability: the context bar's per-feature/-constraint
476// show/no-show predicates, plus the constraint type table they pair with.
477pub use feature_pipeline::assembly::{constraint_type, ConstraintTypeDef, CONSTRAINT_TYPES, FIXED_IS_NOT_A_CONSTRAINT};
478pub use feature_pipeline::{feature_context_applicable, SelectionProbe};
479// The pipeline expression evaluator (the history's `expressions` + `configurator`
480// variable sheet), so a native UI (brep-render's engine-native sketcher) can
481// evaluate a LIVE dimension `valueExpr` against the same variables a committed
482// solve would (`features/sketch.rs` `pre_evaluate_expressions`). `Env::build`/
483// `eval` are pure arithmetic (no `std::time`), so this stays wasm-clean.
484pub use feature_pipeline::Env;
485
486/// One-shot: build an [`Env`] from a history's `expressions` source + its
487/// `configurator` JSON, then evaluate `source` to a scalar. The small surface the
488/// engine-native sketcher's live dimension-value edit uses (deliverable S5.0) —
489/// equivalent to `Env::build(expressions, configurator_json)?.eval(source)`.
490pub fn eval_expression(
491    expressions: &str,
492    configurator_json: &serde_json::Value,
493    source: &str,
494) -> Result<f64, String> {
495    Env::build(expressions, configurator_json).and_then(|env| env.eval(source))
496}
497
498// In the shared-memory-threaded wasm build, re-export wasm-bindgen-rayon's
499// thread-pool initializer as `initThreadPool(numThreads)`. The JS side must
500// await it once after wasm init (needs a cross-origin-isolated page —
501// COOP/COEP — for SharedArrayBuffer). With the pool live, the ordinary
502// `tessellate_watertight_buffers` parallelizes its per-face loop across threads
503// (see `tessellate_faces_stride` under the `parallel` feature).
504#[cfg(feature = "wasm-threads")]
505pub use wasm_bindgen_rayon::init_thread_pool;
506#[path = "geometry/projection.rs"]
507mod projection;
508#[path = "geometry/projection_profile.rs"]
509mod projection_profile;
510#[doc(hidden)]
511pub use projection_profile::report as projection_profile_report;
512#[path = "geometry/affine_sphere.rs"]
513mod affine_sphere;
514pub use projection::{
515    project_point_to_curve, project_point_to_surface, project_point_to_surface_seeded,
516    CurveProjection, SurfaceProjection,
517};
518#[doc(hidden)]
519pub use projection::{
520    project_point_to_surface_basin_census, project_point_to_surface_general_lanes, BasinCensus,
521    GlobalStats,
522};
523#[path = "intersect/curve_surface_intersection.rs"]
524mod curve_surface_intersection;
525pub use curve_surface_intersection::{intersect_curve_surface, CurveSurfaceIntersection};
526#[path = "intersect/exact_line_surface.rs"]
527mod exact_line_surface;
528#[path = "intersect/curve_curve_intersection.rs"]
529mod curve_curve_intersection;
530pub use curve_curve_intersection::{intersect_curves, CurveCurveIntersection};
531#[path = "intersect/surface_surface_intersection.rs"]
532mod surface_surface_intersection;
533pub use surface_surface_intersection::{
534    intersect_surfaces, intersect_surfaces_supplemental, SurfaceIntersectionCurve,
535    SurfaceIntersectionOptions,
536};
537pub(crate) use surface_surface_intersection::TRANSVERSE_SEED_CROSS;
538#[path = "intersect/tangent_curve.rs"]
539mod tangent_curve;
540#[path = "intersect/crossing_node.rs"]
541mod crossing_node;
542pub use crossing_node::{crossing_node_split_enabled, split_at_crossing_nodes};
543pub use tangent_curve::{
544    classify_tangent_contact_at, find_tangent_contact, trace_tangent_curve, TangentContact,
545    EXTENDED_RANK_FLOOR,
546};
547#[path = "brep/classification.rs"]
548mod classification;
549pub use classification::{
550    classify_point, containment_lane, horizon_cross_frame, parameter_point_in_face,
551    trim_sample_count, trim_station, ContainmentLane, PointClass, PointClassification,
552    PolygonClass, SolidClassifier, COVERING_RIM_STRIP_SWITCH, HORIZON_CONTAINMENT_SWITCH,
553    HORIZON_CROSS_FRAME_SWITCH, HORIZON_SINGLE_LOOP_SWITCH, NO_SPHERE_CHART_TRIM_SWITCH,
554    SEAM_BAND_MERGED_SWITCH,
555};
556#[doc(hidden)]
557pub use classification::parameter_point_in_face_scan;
558#[path = "geometry/spatial.rs"]
559mod spatial;
560pub use spatial::{Aabb, Bvh};
561#[path = "brep/pair_classification.rs"]
562mod pair_classification;
563pub use pair_classification::{
564    classify_surface_pair, classify_surface_pair_cached, SurfaceClassifyData,
565    SurfacePairClassification, SurfacePairRelation,
566};
567#[path = "intersect/arrangement.rs"]
568mod arrangement;
569pub use arrangement::{
570    arrange_segments, point_in_polygon, segment_intersection, ArrangementPiece, ArrangementRegion,
571    CycleUse, Segment2, Vec2,
572};
573#[path = "geometry/pcurve.rs"]
574mod pcurve;
575pub use pcurve::{
576    build_pcurve_on_surface, build_pcurve_on_surface_marched, build_pcurve_on_surface_range,
577    build_pcurve_on_surface_range_dense, build_pcurve_on_surface_stations, fit_pcurve_on_surface,
578    fit_pcurve_on_surface_marched, fit_pcurve_on_surface_range, fit_pcurve_on_surface_range_dense,
579    fit_pcurve_on_surface_stations, PcurveFit, PcurveFitExit, PcurveFitLedger, PcurveFitReport,
580    PcurveFitScope, PCURVE_OFF_BAR, PCURVE_REFINEMENT_TOLERANCE,
581};
582#[path = "csg/imprint.rs"]
583mod imprint;
584pub use imprint::{
585    build_imprints, EdgeSplitRecord, FaceImprints, FaceKey, FacePcurve, ImprintOptions,
586    ImprintPieceRecord, ImprintResultRecord, ImprintVertex, TangentSectionRecord,
587};
588#[path = "csg/feature_scale.rs"]
589mod feature_scale;
590#[path = "csg/edge_split.rs"]
591mod edge_split;
592pub use edge_split::{apply_edge_splits, apply_edge_splits_with_map};
593#[path = "csg/fragment.rs"]
594mod fragment;
595pub use fragment::{
596    fragment_face, fragment_solid, FaceFragmentRecord, FragmentCoedge, FragmentEdgeSource,
597    FragmentLoop, FragmentOrder,
598};
599#[path = "csg/boolean/mod.rs"]
600mod boolean;
601pub use boolean::{
602    boolean_operation, boolean_operation_nary, boolean_operation_nary_with_options, boolean_operation_with_diagnostics, boolean_operation_with_diagnostics_at_offset, boolean_split_operands,
603    BooleanOperation, BooleanOptions,
604};
605#[path = "csg/oracle.rs"]
606mod oracle;
607pub use oracle::{
608    boolean_residual_fusables, boolean_semantic_disagreement, boolean_semantic_disagreement_nary,
609    OracleReport, ResidualFusable, ResidualKind, SemanticDisagreement, DISAGREEMENT_THRESHOLD,
610};
611#[path = "healing/heal.rs"]
612mod heal;
613// The content-keyed memo of the per-coedge readings the heal pre-pass and
614// the assembly's inherited-trim challenger take on every boolean.
615#[path = "healing/reading_memo.rs"]
616mod reading_memo;
617// The soundness ACCEPTANCE: the one place a construction lane asks "is this
618// result sound enough to return", repairs it where it can and refuses it by
619// name where it cannot. Declared beside the heal pre-pass it is the mirror of
620// — that one removes noise from an operand before the work, this one refuses a
621// wrong answer after it.
622#[path = "healing/accept.rs"]
623mod accept;
624pub use accept::{
625    accept_sound, accept_sound_against, accept_sound_against_scoped, resolve_face_crossing, resolve_face_crossing_between,
626    take_crossing_repairs,
627    CrossingRepairRecord,
628};
629// The pointwise offset evaluator — ONE definition of "the offset of a surface
630// at a parameter", shared by `offset_surface`'s Greville sampling, the blend
631// march's tangency residual, and the push-face offset residual gates. Declared
632// before `offset` because that module is its first consumer.
633#[path = "offset/point.rs"]
634mod offset_point;
635pub use offset_point::{OffsetEvaluator, OffsetNormal, OffsetSample};
636// The LOCUS-level offset seam — ONE definition of "intersect two offset
637// analytic carriers in closed form", shared by the blend's corner closures.
638// The pointwise evaluator above answers "where is the offset at this (u, v)";
639// this answers "where do two offsets MEET", which is what the corner solves
640// need and what no pointwise evaluator can produce (audit §9.4, §11).
641// Crate-internal: no consumer outside the kernel names these.
642#[path = "offset/analytic_pair.rs"]
643mod offset_analytic_pair;
644// The shared re-trim helpers — ONE definition of "rebuild a face's carrier and
645// pcurves around a boundary that has already moved", shared by push-face,
646// face-move and the direct-edit heals. This is the RE-INTERSECTION sense of
647// trimming; offset-shell's parametric-image trimming is a different operation
648// and deliberately not here (see the module doc). Crate-internal: no consumer
649// outside the kernel names these.
650#[path = "offset/retrim.rs"]
651mod offset_retrim;
652// The RE-INTERSECTION seam — ONE definition of "intersect two carriers to
653// remake a trim boundary", with the exact analytic lane first and the general
654// marched lane behind it. `offset_retrim` above re-trims a face around a
655// boundary that has ALREADY moved; this is the step that moves it, and it is
656// what makes a neighbour re-intersection surface-type-blind the way
657// offset-shell's imprint driver already is. Crate-internal: no consumer outside
658// the kernel names these.
659#[path = "offset/reintersect.rs"]
660mod offset_reintersect;
661// The MEASURED half of the tolerance model: the deviation actually observed
662// between an offset construction and the geometry it was built to reproduce.
663// Everything in `geometry/tolerance.rs` above `MeasuredTolerance` DERIVES a
664// band from a size before the geometry exists; this measures what the
665// construction then did, and compares it against that band. The comparison is
666// not itself a gate: the deviation rides the transient construction result
667// (`OffsetFaceCarrier::deviation`, `#[serde(skip)]`) and refusing on an
668// exceedance is the caller's call, not this module's. Declared before `offset`
669// because that module is its first consumer.
670#[path = "offset/measure.rs"]
671mod offset_measure;
672pub use offset_measure::{
673    measure_edge_against_pcurve_image, measure_surface_fit_against_pointwise_offset,
674    span_midpoint_error, vertex_endpoint_gap,
675};
676#[path = "offset/offset.rs"]
677mod offset;
678pub use offset::{
679    offset_face_carrier, offset_face_carrier_measured, offset_face_carrier_sided, offset_surface,
680    offset_surface_measured, offset_surface_measured_sided, CarrierDeviation, CarrierExtension,
681    MeasuredOffsetSurface, OffsetFaceCarrier, OffsetSurfaceLane,
682};
683// Region-scoped offset REGULARITY — ONE definition of "does the equidistant
684// surface fold over THIS trimmed region", shared by the sheet thickener's gate
685// and offset-shell's carrier-collapse lane. Both used to sweep a fixed grid
686// over the whole surface DOMAIN, which answers about the carrier instead of the
687// face. Crate-internal: no consumer outside the kernel names these.
688#[path = "offset/regularity.rs"]
689mod offset_regularity;
690// The fold LOCUS — the curve `1 − d·k = 0` itself, marched out of the brackets
691// the regularity scan above already finds on its own grid. `regularity` answers
692// "does this region fold"; this answers "WHERE does it stop folding", which is
693// the boundary a partially-folded trim has to be split along. Crate-internal:
694// no consumer outside the kernel names these.
695#[path = "offset/fold_locus.rs"]
696mod offset_fold_locus;
697// The CARVE — split a trim along the fold locus, keep the regular side, hand
698// back the folded one. The two modules above decide THAT a region folds and
699// WHERE it stops; this is what both are for, and it is shared by the sheet
700// thickener and offset shell for the same reason the scan is. Crate-internal:
701// no consumer outside the kernel names these.
702#[path = "offset/carve.rs"]
703mod offset_carve;
704#[path = "offset/thicken.rs"]
705mod thicken;
706pub use thicken::{thicken_face_sheet, thicken_trimmed_sheet};
707#[path = "healing/coalesce.rs"]
708mod coalesce;
709pub use coalesce::{concatenate_exact_curve_pieces, merge_curve_continuation_edges};
710#[path = "healing/face_merge.rs"]
711mod face_merge;
712// Retained for potential mesh-import repair; no exact operation may fall
713// back to it (offset shell must produce real surfaces or fail loudly).
714#[allow(dead_code)]
715#[path = "healing/faceted_repair.rs"]
716mod faceted_repair;
717pub use face_merge::{merge_faces_within, merge_same_surface_faces, merge_same_surface_faces_excluding, merge_same_surface_faces_scoped};
718pub use faceted_repair::{mesh_to_faceted_brep, repair_triangle_soup, MeshRepairReport};
719#[path = "offset/offset_shell.rs"]
720mod offset_shell;
721pub use offset_shell::{
722    offset_shell, offset_shell_by_boolean, offset_shell_closed, offset_shell_with_diagnostics, offset_solid, OffsetCorners,
723    OffsetFaceRole, OffsetShellFaceImageRecord, OffsetShellResultRecord,
724};
725pub(crate) use offset_shell::{is_offset_shell_no_retained_face, is_offset_shell_open_curved_rim};
726#[path = "io/step_matrix.rs"]
727mod step_matrix;
728#[path = "io/step.rs"]
729mod step;
730pub use step::{
731    assembly_export_tree, audit_step_manifold, audit_step_pcurves, export_step,
732    export_step_assembly, export_step_assembly_report, export_step_report,
733    export_step_report_named, Mat4, StepAssemblyExport, StepColors, StepExportOccurrence,
734    StepExportProduct, StepExportReport, StepPmi, PART_ATTRIBUTES, PART_NUMBER,
735};
736// Imported appearance (colour) — the carrier between an importer that READ a
737// colour and the pipeline that stamps it as name-keyed scene metadata. Not a
738// storage layer: see the module doc for the `{"color": "#RRGGBB"}` convention.
739#[path = "io/appearance.rs"]
740mod appearance;
741pub use appearance::{BodyAppearance, ImportedColor, COLOR_METADATA_KEY};
742#[path = "io/step_import/mod.rs"]
743mod step_import;
744pub use step_import::{
745    import_step, import_step_bodies, import_step_report, import_step_trim_readings, import_step_with_appearance,
746    occurrence_ref_parts, read_step_assembly, read_step_pmi, rewrite_occurrence_refs,
747    ImportedStep, StepAssembly, StepBodyError, StepBodyTrimReadings, StepEdgeReading, StepFaceReading, StepImportReport,
748    StepOccurrence, StepProduct, StepSuppliedTrim, StepTrimReadings, OCCURRENCE_REF_PREFIX,
749    STATED_PRECISION_INCONSISTENCY_RATIO,
750};
751/// Test seams for the Part 21 text codec of the PMI writer / reader.
752#[doc(hidden)]
753pub fn io_step_text_for_tests(value: &str) -> String {
754    step::pmi::step_text(value)
755}
756#[doc(hidden)]
757pub fn io_step_decode_text_for_tests(value: &str) -> String {
758    step_import::decode_step_text_for_tests(value)
759}
760#[path = "io/iges/mod.rs"]
761mod iges;
762pub use iges::{
763    export_iges, export_iges_assembly, export_iges_bodies, import_iges, import_iges_report,
764    read_iges_assembly, IgesBody, IgesImportReport,
765};
766#[path = "io/mesh_io.rs"]
767mod mesh_io;
768pub use mesh_io::{
769    parse_stl_triangles, read_obj, read_stl, write_ascii_stl, write_binary_stl, write_obj,
770    ObjReadResult, StlError, StlFormat, StlReadResult, StlTriangles,
771};
772// 3MF (3D Manufacturing Format) import, core specification only: the OPC/ZIP
773// container, the model XML, and the build's instances flattened into one
774// millimetre triangle soup for the SAME mesh-to-solid chain STL and OBJ use.
775// The container, the deflate decoder and the XML reader are hand-rolled and
776// bounded — see the module doc for the dependency review behind that.
777#[path = "io/three_mf/mod.rs"]
778mod three_mf;
779pub use three_mf::{
780    read_3mf, write_3mf, ThreeMfError, ThreeMfInstance, ThreeMfMesh, ThreeMfReadResult,
781    ThreeMfSolid, ThreeMfUnit, ThreeMfWriteReport,
782};
783// Binary glTF 2.0 of the DISPLAY mesh — tessellated exchange for viewers, with
784// the axis convention and the unit carried by the root node's transform and the
785// colours converted from sRGB to glTF's linear `baseColorFactor`.
786#[path = "io/glb.rs"]
787mod glb;
788pub use glb::{write_glb, GlbReport, GlbSolid, GlbUpAxis, GLB_MAGIC};
789// Native serialized exact-BREP snapshot: the assemblies parts-library fast
790// lane (instant component insert from a cached payload; fails detectably so
791// the ACOMP self-heal lane can re-execute the embedded part document).
792#[path = "io/snapshot.rs"]
793mod snapshot;
794pub use snapshot::{
795    restore_solids, snapshot_resident_solids, snapshot_solids, RestoredSnapshot, RestoredSolid,
796    SNAPSHOT_FORMAT_VERSION,
797};
798#[path = "solvers/linear_algebra.rs"]
799mod solver_linear_algebra;
800#[path = "solvers/sketch_solver.rs"]
801mod sketch_solver;
802pub use sketch_solver::{
803    is_sketch_origin_point_id, sketch_id_key, mint_sketch_id, solve_sketch, solve_sketch_from_json,
804    SketchSolverSettings, SolveSketchRequest, SKETCH_ORIGIN_POINT_ID,
805};
806#[path = "solvers/assembly_solver.rs"]
807mod assembly_solver;
808pub use assembly_solver::{
809    solve_assembly, AssemblyBody, AssemblyMate, AssemblySolution, AssemblySolveOptions, BodyFreedom, BodyPose,
810    LinearAlgebra, MateAlign, MateAxis, MateCurve, MateKind, MatePlane, MateResidualReport,
811    SolveStrategy, SymmetricElement,
812};
813// Assembly selection resolution: a namespaced selection ref resolved to an
814// analytic frame (plane/axis/sphere/circle/line/point) read from the exact
815// BREP, feeding `MateKind` inputs in component-local coordinates.
816#[path = "solvers/sketch3d_solver.rs"]
817mod sketch3d_solver;
818pub use sketch3d_solver::{
819    sketch3d_constraint_type, solve_sketch3d, solve_sketch3d_doc, solve_sketch3d_from_json,
820    Sketch3dConstraint, Sketch3dConstraintType, Sketch3dDiagnostics, Sketch3dDoc,
821    Sketch3dGeometry, Sketch3dPoint, Sketch3dSolveOptions, SolveSketch3dRequest,
822    SKETCH3D_CONSTRAINT_TYPES,
823};
824#[path = "solvers/assembly_resolve.rs"]
825mod assembly_resolve;
826pub use assembly_resolve::{
827    is_component_reference, resolve_component_point, resolve_edge_selection,
828    resolve_face_selection, resolve_named_selection, resolve_vertex_selection,
829    split_component_namespace, ResolveError, SelectionGeometry,
830};
831#[path = "edit/direct_edit.rs"]
832mod direct_edit;
833pub use direct_edit::{
834    delete_face_and_heal, delete_faces_and_heal, draft_faces, draft_faces_parted, move_faces, offset_freeform_face,
835    offset_revolution_face, offset_ruled_face, offset_sphere_face, offset_torus_face,
836    recut_moved_planes, recut_moved_planes_rigid, reblend_lost_fillets, resolve_face_by_point, rotate_faces,
837    route_reading, RouteReading,
838};
839pub use direct_edit::{face_refusal_slugs, face_transform_reason, FaceTransformReason};
840pub use direct_edit::{
841    refit_faces_as_one, refit_refusal_phrases, RefitCarrier, RefitReport, RefitSurfaceKind,
842};
843#[path = "healing/sew.rs"]
844mod sew;
845pub use sew::{sew_solid, split_pinched_vertices, SewReport};
846#[path = "meshing/mesh_weld.rs"]
847mod mesh_weld;
848pub use mesh_weld::{canonical_bits, VertexWeld};
849#[path = "meshing/mesh_segment.rs"]
850mod mesh_segment;
851pub use mesh_segment::{
852    mesh_regions_to_brep, segment_mesh_faces, MeshRegion, MeshSegmentation, RegionCarrier,
853    SegmentOptions, UNASSIGNED_REGION,
854};
855
856#[path = "meshing/spherical_union.rs"]
857mod spherical_union;
858pub use spherical_union::reconstruct_spherical_mesh_union;
859
860#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
861pub struct Vec3 {
862    pub x: f64,
863    pub y: f64,
864    pub z: f64,
865}
866
867impl Vec3 {
868    pub const ZERO: Self = Self { x: 0.0, y: 0.0, z: 0.0 };
869    pub const X: Self = Self { x: 1.0, y: 0.0, z: 0.0 };
870    pub const Y: Self = Self { x: 0.0, y: 1.0, z: 0.0 };
871    pub const Z: Self = Self { x: 0.0, y: 0.0, z: 1.0 };
872
873    /// Normalize using the standard vector guard, or return the caller's fallback.
874    pub fn normalized_or(self, fallback: Self) -> Self {
875        self.normalized().unwrap_or(fallback)
876    }
877
878    /// Rodrigues rotation about a unit axis through the origin.
879    #[inline]
880    pub fn rotate_about(self, axis: Self, angle: f64) -> Self {
881        let (sin, cos) = angle.sin_cos();
882        self.scale(cos)
883            .add(axis.cross(self).scale(sin))
884            .add(axis.scale(axis.dot(self) * (1.0 - cos)))
885    }
886
887    /// Distance to a segment. `guard` is the caller's squared-length threshold.
888    #[inline]
889    pub fn distance_to_segment(self, start: Self, end: Self, guard: f64) -> f64 {
890        let segment = end.sub(start);
891        let length_squared = segment.dot(segment);
892        if length_squared <= guard {
893            return self.sub(start).length();
894        }
895        let parameter = (self.sub(start).dot(segment) / length_squared).clamp(0.0, 1.0);
896        self.sub(start.add(segment.scale(parameter))).length()
897    }
898
899    /// Squared distance, retaining the seed sweep's projection on zero-length segments.
900    #[inline]
901    pub fn distance_to_segment_squared(self, start: Self, end: Self) -> f64 {
902        let segment = end.sub(start);
903        let length_squared = segment.dot(segment);
904        let parameter = if length_squared > 0.0 {
905            (self.sub(start).dot(segment) / length_squared).clamp(0.0, 1.0)
906        } else {
907            0.0
908        };
909        let residual = self.sub(start.add(segment.scale(parameter)));
910        residual.dot(residual)
911    }
912
913    pub fn new(x: f64, y: f64, z: f64) -> Self {
914        Self { x, y, z }
915    }
916    pub fn sub(self, rhs: Self) -> Self {
917        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
918    }
919    pub fn add(self, rhs: Self) -> Self {
920        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
921    }
922    pub fn scale(self, factor: f64) -> Self {
923        Self::new(self.x * factor, self.y * factor, self.z * factor)
924    }
925    pub fn cross(self, rhs: Self) -> Self {
926        Self::new(
927            self.y * rhs.z - self.z * rhs.y,
928            self.z * rhs.x - self.x * rhs.z,
929            self.x * rhs.y - self.y * rhs.x,
930        )
931    }
932    pub fn dot(self, rhs: Self) -> f64 {
933        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
934    }
935    pub fn length(self) -> f64 {
936        self.dot(self).sqrt()
937    }
938    pub fn length_squared(self) -> f64 {
939        self.dot(self)
940    }
941    pub fn normalized(self) -> Result<Self, String> {
942        let length = self.length();
943        if length <= 1e-12 {
944            return Err("Vec3.normalized: zero-length vector".into());
945        }
946        Ok(self.scale(1.0 / length))
947    }
948    pub fn perpendicular(self) -> Result<Self, String> {
949        let x = self.x.abs();
950        let y = self.y.abs();
951        let z = self.z.abs();
952        let axis = if x <= y && x <= z {
953            Self::new(1.0, 0.0, 0.0)
954        } else if y <= z {
955            Self::new(0.0, 1.0, 0.0)
956        } else {
957            Self::new(0.0, 0.0, 1.0)
958        };
959        self.cross(axis).normalized()
960    }
961}
962
963#[derive(Clone, Debug, Default, Deserialize, Serialize)]
964pub struct Mesh {
965    pub positions: Vec<f64>,
966    pub normals: Vec<f64>,
967    pub indices: Vec<u32>,
968    pub face_ids: Vec<u32>,
969}
970
971impl Mesh {
972    fn push_vertex(&mut self, point: Vec3, normal: Vec3) -> u32 {
973        self.positions.extend([point.x, point.y, point.z]);
974        self.normals.extend([normal.x, normal.y, normal.z]);
975        (self.positions.len() / 3 - 1) as u32
976    }
977
978    fn quad(&mut self, points: [Vec3; 4], normal: Vec3, face_id: u32) {
979        let base = self.push_vertex(points[0], normal);
980        for point in points.iter().skip(1) {
981            self.push_vertex(*point, normal);
982        }
983        self.indices
984            .extend([base, base + 1, base + 2, base, base + 2, base + 3]);
985        self.face_ids.extend([face_id, face_id]);
986    }
987
988    pub fn signed_volume(&self) -> f64 {
989        self.indices
990            .chunks_exact(3)
991            .map(|tri| {
992                let point = |index: u32| {
993                    let i = index as usize * 3;
994                    Vec3::new(
995                        self.positions[i],
996                        self.positions[i + 1],
997                        self.positions[i + 2],
998                    )
999                };
1000                point(tri[0]).dot(point(tri[1]).cross(point(tri[2]))) / 6.0
1001            })
1002            .sum()
1003    }
1004
1005    pub fn validate(&self) -> Result<(), String> {
1006        if self.positions.len() % 3 != 0 || self.normals.len() != self.positions.len() {
1007            return Err("invalid vertex or normal buffer".into());
1008        }
1009        self.validate_geometry()
1010    }
1011
1012    /// Geometry-only validation for consumers that use positions + indices but
1013    /// NOT normals (e.g. `signed_volume`, whose tetrahedron sum never reads a
1014    /// normal). Mesh-only solids such as sheet-metal bodies supply metrics
1015    /// meshes with an empty normal buffer, which is legitimate for volume.
1016    pub fn validate_geometry(&self) -> Result<(), String> {
1017        if self.positions.len() % 3 != 0 {
1018            return Err("invalid vertex buffer".into());
1019        }
1020        if self.indices.len() % 3 != 0 {
1021            return Err("index buffer is not triangular".into());
1022        }
1023        let count = self.positions.len() / 3;
1024        if self.indices.iter().any(|index| *index as usize >= count) {
1025            return Err("index outside vertex buffer".into());
1026        }
1027        if !self.positions.iter().all(|value| value.is_finite()) {
1028            return Err("non-finite vertex".into());
1029        }
1030        Ok(())
1031    }
1032}
1033
1034pub fn make_box(size_x: f64, size_y: f64, size_z: f64) -> Mesh {
1035    let x = size_x.abs();
1036    let y = size_y.abs();
1037    let z = size_z.abs();
1038    let p = [
1039        Vec3::new(0.0, 0.0, 0.0),
1040        Vec3::new(x, 0.0, 0.0),
1041        Vec3::new(x, y, 0.0),
1042        Vec3::new(0.0, y, 0.0),
1043        Vec3::new(0.0, 0.0, z),
1044        Vec3::new(x, 0.0, z),
1045        Vec3::new(x, y, z),
1046        Vec3::new(0.0, y, z),
1047    ];
1048    let mut mesh = Mesh::default();
1049    mesh.quad([p[0], p[3], p[2], p[1]], Vec3::new(0.0, 0.0, -1.0), 0);
1050    mesh.quad([p[4], p[5], p[6], p[7]], Vec3::new(0.0, 0.0, 1.0), 1);
1051    mesh.quad([p[0], p[1], p[5], p[4]], Vec3::new(0.0, -1.0, 0.0), 2);
1052    mesh.quad([p[3], p[7], p[6], p[2]], Vec3::new(0.0, 1.0, 0.0), 3);
1053    mesh.quad([p[0], p[4], p[7], p[3]], Vec3::new(-1.0, 0.0, 0.0), 4);
1054    mesh.quad([p[1], p[2], p[6], p[5]], Vec3::new(1.0, 0.0, 0.0), 5);
1055    mesh
1056}
1057
1058pub fn make_cylinder(radius: f64, height: f64, segments: usize) -> Mesh {
1059    let radius = radius.abs();
1060    let height = height.abs();
1061    let segments = segments.max(3);
1062    let mut mesh = Mesh::default();
1063    for i in 0..segments {
1064        let a = std::f64::consts::TAU * i as f64 / segments as f64;
1065        let b = std::f64::consts::TAU * (i + 1) as f64 / segments as f64;
1066        let (sa, ca) = a.sin_cos();
1067        let (sb, cb) = b.sin_cos();
1068        let pa = Vec3::new(radius * ca, 0.0, -radius * sa);
1069        let pb = Vec3::new(radius * cb, 0.0, -radius * sb);
1070        let ta = Vec3::new(pa.x, height, pa.z);
1071        let tb = Vec3::new(pb.x, height, pb.z);
1072        mesh.quad(
1073            [pa, pb, tb, ta],
1074            Vec3::new((ca + cb) * 0.5, 0.0, -(sa + sb) * 0.5),
1075            0,
1076        );
1077        let bottom = mesh.push_vertex(Vec3::default(), Vec3::new(0.0, -1.0, 0.0));
1078        let bi = mesh.push_vertex(pb, Vec3::new(0.0, -1.0, 0.0));
1079        let bj = mesh.push_vertex(pa, Vec3::new(0.0, -1.0, 0.0));
1080        mesh.indices.extend([bottom, bi, bj]);
1081        mesh.face_ids.push(1);
1082        let top = mesh.push_vertex(Vec3::new(0.0, height, 0.0), Vec3::new(0.0, 1.0, 0.0));
1083        let ti = mesh.push_vertex(ta, Vec3::new(0.0, 1.0, 0.0));
1084        let tj = mesh.push_vertex(tb, Vec3::new(0.0, 1.0, 0.0));
1085        mesh.indices.extend([top, ti, tj]);
1086        mesh.face_ids.push(2);
1087    }
1088    mesh
1089}
1090
1091
1092mod abi;
1093pub use abi::*;
1094
1095
1096#[path = "healing/curve_helpers.rs"]
1097mod curve_helpers;
1098
1099#[path = "geometry/eigen2.rs"]
1100mod eigen2;
1101
1102#[path = "geometry/union_find.rs"]
1103pub mod union_find;
1104pub use union_find::UnionFind;
1105
1106#[path = "geometry/poly.rs"]
1107pub mod poly;
1108
1109pub use analytic_surface::{circumcenter, circumcenter_2d, recognize_circle, RecognizedCircle};
1110
1111#[path = "geometry/triangle.rs"]
1112mod triangle;
1113pub use triangle::closest_point_on_triangle;
1114
1115pub use tolerance::RIGID_TOL;
1116
1117impl std::ops::Index<usize> for Vec3 {
1118    type Output = f64;
1119    fn index(&self, index: usize) -> &Self::Output {
1120        match index { 0 => &self.x, 1 => &self.y, 2 => &self.z, _ => panic!("Vec3 index out of bounds: {index}") }
1121    }
1122}
1123impl std::ops::IndexMut<usize> for Vec3 {
1124    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
1125        match index { 0 => &mut self.x, 1 => &mut self.y, 2 => &mut self.z, _ => panic!("Vec3 index out of bounds: {index}") }
1126    }
1127}