Skip to main content

brep_kernel/
lib.rs

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