Skip to main content

brep_kernel/
lib.rs

1use serde::{Deserialize, Serialize};
2use wasm_bindgen::prelude::*;
3
4/// The wasm panic hook every `#[wasm_bindgen]` entry point installs. Replaces
5/// the `console_error_panic_hook` crate for the whole BREP family — reached as
6/// `brep_kernel::panic_hook` and, through the render engine's re-export, as
7/// `brep_render::brep_kernel::panic_hook`.
8pub mod panic_hook;
9
10#[path = "geometry/tolerance.rs"]
11mod tolerance;
12pub use tolerance::{
13    curve_model_scale, model_scale, report_scale_migration, solid_model_scale, solid_scale,
14    offset_construction_band, vertex_tolerance_from_edges, KernelTolerances,
15    MeasuredTolerance, OFFSET_CONSTRUCTION_FLOOR, OFFSET_CONSTRUCTION_REL,
16    VERTEX_MATCH_FLOOR,
17};
18// Per-entity measured tolerances (`per-entity-tolerances.md` slice S1): the
19// lazy, capped band an individual edge or vertex earns from its own redundant
20// representations, on top of the global policy above. Measurement only — no
21// record field, no serialization; see the module doc for invariants I1/I2.
22#[path = "geometry/entity_tolerance.rs"]
23mod entity_tolerance;
24pub use entity_tolerance::{EntityTolerances, EDGE_CAP_FRACTION, VERTEX_CAP_FRACTION};
25#[path = "props/diagnostics.rs"]
26mod diagnostics;
27pub use diagnostics::{
28    DiagnosticEvent, DiagnosticSeverity, KernelDiagnostics, KernelOutcome, KernelRefusal, KernelStage,
29    OrRefuse, RefusalClass,
30};
31#[path = "geometry/polygon.rs"]
32mod polygon;
33#[path = "geometry/curve.rs"]
34mod curve;
35pub use curve::{
36    make_arc, make_circle, make_hyperbola, make_line, make_parabola, uniform_clamped_knots,
37    KnotVector, NurbsCurve, Vec4,
38};
39#[path = "blending/blend/mod.rs"]
40mod blend;
41pub use blend::{
42    blend_closed_edge, blend_edge_variable, blend_open_edge, blend_smooth_chain,
43    round_convex_corner,
44};
45#[path = "blending/fillet.rs"]
46mod fillet;
47pub use fillet::{
48    chamfer_edge, chamfer_edge_angle, chamfer_edge_asymmetric, chamfer_edges_angle,
49    chamfer_edges_asymmetric, fillet_edge, fillet_edges, fillet_edges_variable,
50    fillet_edges_variable_law, fillet_edges_variable_vertex_radii,
51};
52#[path = "blending/law.rs"]
53mod law;
54pub use law::{LawSegment, RadiusLaw};
55#[path = "geometry/fit.rs"]
56mod fit;
57pub use fit::{
58    fit_polyline, interpolate_curve, interpolate_curve_closed, interpolate_curve_local,
59    interpolate_curve_thinned, interpolate_curve_with_end_tangents, simplify_polyline,
60    solve_banded, solve_dense, PolylineFit,
61};
62#[path = "geometry/image_curve.rs"]
63mod image_curve;
64pub use image_curve::{affine_image_curve, image_curve, image_curve_pair, ImageCurve, ImageCurveTier};
65#[path = "geometry/surface.rs"]
66mod surface;
67pub use surface::{
68    carrier_preview_patch, make_cone_surface, make_cylinder_surface, make_extrusion, make_plane,
69    make_revolution, make_sphere_surface, make_sphere_surface_framed, make_torus_surface,
70    NurbsSurface,
71};
72#[path = "geometry/analytic_surface.rs"]
73mod analytic_surface;
74pub use analytic_surface::{
75    circle_angle_to_parameter, intersect_analytic_pair, revolution_structure, AnalyticSurface,
76    RevolutionFrame, RevolutionStructure,
77};
78#[path = "geometry/sphere_chart.rs"]
79mod sphere_chart;
80pub use sphere_chart::{
81    canonicalize_points, chart_grid_divisions, chart_grid_parameters, corner_index,
82    cube_edge_corner, cube_edge_divisions, cube_edge_index, cube_edge_parameters,
83    cube_edge_parts, Chart, ChartSegment, ChartSide, SphereAtlas,
84    SphericalRegion, CHART_COUNT, CUBE_CORNER_COUNT, CUBE_EDGE_COUNT,
85};
86#[path = "brep/solid_codec.rs"]
87mod solid_codec;
88pub use solid_codec::{decode_solid, encode_solid, SolidNames, SOLID_CODEC_VERSION};
89#[path = "brep/topology.rs"]
90mod topology;
91pub use topology::{
92    make_box_brep, make_cylinder_brep, make_pyramid_brep, BrepSolid, FaceRecord, IssueKind,
93    ValidationReport,
94};
95#[path = "brep/soundness.rs"]
96mod soundness;
97pub use soundness::{
98    solid_connectivity, solid_self_intersections, ConnectivityReport, FaceCrossing,
99    SelfIntersectionOptions, SelfIntersectionReport, ShellConnectivity,
100};
101#[path = "brep/topology_arena.rs"]
102mod topology_arena;
103pub use topology_arena::{
104    ArenaCoedge, ArenaEdge, ArenaFace, ArenaLoop, ArenaShell, ArenaVertex, CoedgeId, EdgeId,
105    FaceId, LoopId, ShellId, TopologyArena, VertexId,
106};
107#[path = "brep/analytic_topology.rs"]
108mod analytic_topology;
109pub use analytic_topology::{
110    make_cone_brep, make_sphere_brep, make_sphere_brep_framed, make_torus_brep,
111};
112#[path = "construction/sweep_topology.rs"]
113mod sweep_topology;
114pub use sweep_topology::{
115    extrude_profile_brep, extrude_profile_brep_draft, fit_helix_curve, helix_sample_points,
116    profile_anchor, rib_from_profile, RibExtrusion,
117    sweep_profile_along_chain, sweep_profile_along_chain_with_stations, sweep_profile_along_path,
118    sweep_profile_along_path_anchored,
119    sweep_profile_helix,
120    sweep_profile_twisted, sweep_profile_twisted_anchored, ProfileAnchor, SectionPlacement,
121};
122#[path = "construction/revolve_topology.rs"]
123mod revolve_topology;
124pub use revolve_topology::{revolve_profile_brep, revolve_profile_brep_named};
125#[path = "construction/loft_topology.rs"]
126mod loft_topology;
127pub use loft_topology::{
128    loft_profile_brep, loft_profile_brep_closed, loft_profile_brep_guided,
129    loft_profile_brep_guided_frame, loft_profile_brep_tangent,
130};
131#[path = "brep/transform_topology.rs"]
132mod transform_topology;
133pub use transform_topology::{mirror_brep, transform_brep, AffineTransform};
134#[path = "edit/split.rs"]
135mod split;
136pub use split::{
137    split_solid_by_face_surface, split_solid_by_plane, split_solid_by_surface, SplitSurface,
138};
139#[path = "props/mass_properties.rs"]
140mod mass_properties;
141pub use mass_properties::{
142    curve_arc_length, edge_arc_length, face_area, face_boundary_length, face_volume_contribution,
143    parameter_space_area, solid_edge_length_total, solid_mass_properties,
144    solid_mass_properties_full, solid_signed_volume, trim_polygons, DensityMassProperties,
145    FullMassProperties, MassProperties,
146};
147#[path = "meshing/tessellation.rs"]
148mod tessellation;
149pub use tessellation::{tessellate_brep, tessellate_face, TessellationOptions};
150#[path = "meshing/watertight_tessellation/mod.rs"]
151mod watertight_tessellation;
152pub use watertight_tessellation::{
153    sample_edge_polylines, sample_edges_encoded, tessellate_brep_watertight,
154    tessellate_brep_watertight_face_stride, tessellate_brep_watertight_face_stride_with_samples,
155};
156// The Rust feature-history execution engine (migration-plan Stage 4/5 foundation).
157// Deserializes the serialized `{ type, inputParams, ... }` descriptor shape, runs
158// each feature against a live scene-map of resident handles, and returns per-feature
159// results (handles + face/edge names). See feature_pipeline/mod.rs for the contract.
160#[path = "feature_pipeline/mod.rs"]
161mod feature_pipeline;
162pub use feature_pipeline::execute_history_json;
163pub use feature_pipeline::{first_reference_name, reference_names};
164// Typed, in-process pipeline surface for native consumers (brep-render): run a
165// whole history and read the results without a JSON round trip.
166pub use feature_pipeline::{
167    clear_history_cache, execute_history, execute_history_observed, AddedSolid, Axis,
168    ComponentRecord, FeatureDescriptor, FeatureResult, Frame, HistoryProgress, HistoryRequest,
169    HistoryResult, PortKind, PortRecord, ProfileLoop, ScenePoint, SketchProfile,
170};
171// Wire harness: the document's `wireHarness` block (connections), the tail's
172// routing report (per-connection status / length / route, per-segment bundle),
173// and the sided-port vocabulary the spline attachments share.
174pub use feature_pipeline::wire_harness::{
175    bundle_diameter, Attachment as SplineAttachment, PortSide, RouteResult, RouteStatus,
176    WireHarnessBundle, WireHarnessConnection,
177    WireHarnessEndpoint, WireHarnessReport, WireHarnessState, WIRE_HARNESS_FEATURE_ID,
178    WIRE_HARNESS_FEATURE_TYPE,
179};
180// PMI: the document's `pmi` block (views + annotations), the type table with
181// its schemas / selection predicates, the tail's resolved report, and the
182// presentation layout shared by the viewport and the AP242 export.
183pub use feature_pipeline::pmi::{
184    clamp_text_size, format_dimension, format_number, pmi_schema_catalogue, pmi_type,
185    resolve_state as pmi_resolve_state, FcfFrame, PmiAnnotation, PmiAnnotationReport, PmiCamera,
186    PmiDisplay, PmiGeometry, PmiPlane, PmiProjection, PmiReport, PmiState, PmiStatus, PmiTypeDef, PmiView,
187    PmiViewReport, ToleranceBlock, ToleranceMode, PMI_TYPES,
188};
189pub use feature_pipeline::pmi::layout::{present as pmi_present, LayoutStyle as PmiLayoutStyle, Presentation as PmiPresentation};
190pub use feature_pipeline::pmi::annotations::fcf::{characteristic as pmi_characteristic, Characteristic as PmiCharacteristic, CHARACTERISTICS as PMI_CHARACTERISTICS};
191// The sketch loop-id write-back: stamps each closed loop's stable id onto its
192// geometries so per-loop face names survive deleting the edge an id came from.
193// The editor calls it when a sketch is committed.
194pub use feature_pipeline::assign_sketch_loop_ids;
195// Sheet-metal flat-pattern (unfold) → 2D vector export, keyed by resident handle.
196pub use feature_pipeline::{flat_pattern_dxf, flat_pattern_svg, is_sheet_metal_handle};
197// Native IMPORT3D payload: finished solids → the `io/snapshot` container with
198// IMPORT3D's naming stamped on, i.e. the `inputParams.nativeBrep` an import lane
199// bakes into a part document (`features/import3d.rs` reads it back verbatim).
200pub use feature_pipeline::{native_import_payload, native_import_payload_with_appearance};
201// The scene-metadata isolation bracket a payload producer holds across the
202// encode, so ambient records of the LIVE document never leak into a new part.
203pub use feature_pipeline::IsolatedSceneMetadata;
204// The imported-colour read of the name-keyed scene-metadata store. Thread-local
205// like the rest of it, so an off-thread runner reads it and ships the result.
206pub use feature_pipeline::scene_metadata_colors_json;
207// The assemblies parts library (unique part payloads for ACOMP instances):
208// insert / update-refresh mutations and the save-side serialization surface.
209pub use feature_pipeline::{
210    add_part_to_library, install_parts_library, missing_library_parts, parts_library_json,
211    parts_library_map, parts_library_revision, refresh_library_entry, stable_json_hash, PartsLibraryEntry,
212    PartsLibraryMap,
213};
214// Assembly constraint state + exported ABI (state/statuses/DOF/overlay reads,
215// constraint CRUD with auto-solve, the document pose/isFixed write-back fold).
216pub use feature_pipeline::assembly::{
217    assembly_add_constraint_json, assembly_apply_document_json,
218    assembly_apply_inferred_constraints_json, assembly_dof_json,
219    assembly_infer_constraints_json, assembly_inferable_types_json,
220    assembly_move_constraint_json, assembly_overlay_json, assembly_pose_updates_json,
221    assembly_remove_constraint_json, assembly_run_solve_json,
222    assembly_set_constraint_enabled_json, assembly_set_constraint_open_json,
223    assembly_state_json, assembly_statuses_json, assembly_update_constraint_json,
224    constraint_schema_catalogue,
225};
226pub use feature_pipeline::{AssemblyState, ConstraintEntry};
227// The ONE matrix → `{translate, rotateEulerDeg}` (intrinsic XYZ, degrees) pose
228// encoder, shared by the solver write-back and any out-of-crate ACOMP author.
229pub use feature_pipeline::assembly::transform_to_pose_params;
230// The feature-schema catalogue (feature definitions: name + `inputParamsSchema`),
231// so a native UI can drive schema-driven feature dialogs without the JSON export.
232pub use feature_pipeline::feature_schema_catalogue;
233// Selection-context applicability: the context bar's per-feature/-constraint
234// show/no-show predicates, plus the constraint type table they pair with.
235pub use feature_pipeline::assembly::{constraint_type, ConstraintTypeDef, CONSTRAINT_TYPES};
236pub use feature_pipeline::{feature_context_applicable, SelectionProbe};
237// The pipeline expression evaluator (the history's `expressions` + `configurator`
238// variable sheet), so a native UI (brep-render's engine-native sketcher) can
239// evaluate a LIVE dimension `valueExpr` against the same variables a committed
240// solve would (`features/sketch.rs` `pre_evaluate_expressions`). `Env::build`/
241// `eval` are pure arithmetic (no `std::time`), so this stays wasm-clean.
242pub use feature_pipeline::Env;
243
244/// One-shot: build an [`Env`] from a history's `expressions` source + its
245/// `configurator` JSON, then evaluate `source` to a scalar. The small surface the
246/// engine-native sketcher's live dimension-value edit uses (deliverable S5.0) —
247/// equivalent to `Env::build(expressions, configurator_json)?.eval(source)`.
248pub fn eval_expression(
249    expressions: &str,
250    configurator_json: &serde_json::Value,
251    source: &str,
252) -> Result<f64, String> {
253    Env::build(expressions, configurator_json).and_then(|env| env.eval(source))
254}
255
256// In the shared-memory-threaded wasm build, re-export wasm-bindgen-rayon's
257// thread-pool initializer as `initThreadPool(numThreads)`. The JS side must
258// await it once after wasm init (needs a cross-origin-isolated page —
259// COOP/COEP — for SharedArrayBuffer). With the pool live, the ordinary
260// `tessellate_watertight_buffers` parallelizes its per-face loop across threads
261// (see `tessellate_faces_stride` under the `parallel` feature).
262#[cfg(feature = "wasm-threads")]
263pub use wasm_bindgen_rayon::init_thread_pool;
264#[path = "geometry/projection.rs"]
265mod projection;
266pub use projection::{
267    project_point_to_curve, project_point_to_surface, project_point_to_surface_seeded,
268    CurveProjection, SurfaceProjection,
269};
270#[path = "intersect/curve_surface_intersection.rs"]
271mod curve_surface_intersection;
272pub use curve_surface_intersection::{intersect_curve_surface, CurveSurfaceIntersection};
273#[path = "intersect/curve_curve_intersection.rs"]
274mod curve_curve_intersection;
275pub use curve_curve_intersection::{intersect_curves, CurveCurveIntersection};
276#[path = "intersect/surface_surface_intersection.rs"]
277mod surface_surface_intersection;
278pub use surface_surface_intersection::{
279    intersect_surfaces, intersect_surfaces_supplemental, SurfaceIntersectionCurve,
280    SurfaceIntersectionOptions,
281};
282pub(crate) use surface_surface_intersection::TRANSVERSE_SEED_CROSS;
283#[path = "brep/classification.rs"]
284mod classification;
285pub use classification::{
286    classify_point, parameter_point_in_face, PointClass, PointClassification, PolygonClass,
287    SolidClassifier,
288};
289#[path = "geometry/spatial.rs"]
290mod spatial;
291pub use spatial::{Aabb, Bvh};
292#[path = "brep/pair_classification.rs"]
293mod pair_classification;
294pub use pair_classification::{
295    classify_surface_pair, classify_surface_pair_cached, SurfaceClassifyData,
296    SurfacePairClassification, SurfacePairRelation,
297};
298#[path = "intersect/arrangement.rs"]
299mod arrangement;
300pub use arrangement::{
301    arrange_segments, point_in_polygon, segment_intersection, ArrangementPiece, ArrangementRegion,
302    CycleUse, Segment2, Vec2,
303};
304#[path = "geometry/pcurve.rs"]
305mod pcurve;
306pub use pcurve::{
307    build_pcurve_on_surface, build_pcurve_on_surface_marched, build_pcurve_on_surface_range,
308    build_pcurve_on_surface_range_dense,
309};
310#[path = "csg/imprint.rs"]
311mod imprint;
312pub use imprint::{
313    build_imprints, EdgeSplitRecord, FaceImprints, FaceKey, FacePcurve, ImprintOptions,
314    ImprintPieceRecord, ImprintResultRecord, ImprintVertex,
315};
316#[path = "csg/edge_split.rs"]
317mod edge_split;
318pub use edge_split::{apply_edge_splits, apply_edge_splits_with_map};
319#[path = "csg/fragment.rs"]
320mod fragment;
321pub use fragment::{
322    fragment_face, fragment_solid, FaceFragmentRecord, FragmentCoedge, FragmentEdgeSource,
323    FragmentLoop,
324};
325#[path = "csg/boolean/mod.rs"]
326mod boolean;
327pub use boolean::{
328    boolean_operation, boolean_operation_nary, boolean_operation_with_diagnostics,
329    BooleanOperation, BooleanOptions,
330};
331#[path = "csg/oracle.rs"]
332mod oracle;
333pub use oracle::{
334    boolean_residual_fusables, boolean_semantic_disagreement, boolean_semantic_disagreement_nary,
335    OracleReport, ResidualFusable, ResidualKind, SemanticDisagreement, DISAGREEMENT_THRESHOLD,
336};
337#[path = "healing/heal.rs"]
338mod heal;
339// The pointwise offset evaluator — ONE definition of "the offset of a surface
340// at a parameter", shared by `offset_surface`'s Greville sampling, the blend
341// march's tangency residual, and the push-face offset residual gates. Declared
342// before `offset` because that module is its first consumer.
343#[path = "offset/point.rs"]
344mod offset_point;
345pub use offset_point::{OffsetEvaluator, OffsetNormal, OffsetSample};
346// The LOCUS-level offset seam — ONE definition of "intersect two offset
347// analytic carriers in closed form", shared by the blend's corner closures.
348// The pointwise evaluator above answers "where is the offset at this (u, v)";
349// this answers "where do two offsets MEET", which is what the corner solves
350// need and what no pointwise evaluator can produce (audit §9.4, §11).
351// Crate-internal: no consumer outside the kernel names these.
352#[path = "offset/analytic_pair.rs"]
353mod offset_analytic_pair;
354// The shared re-trim helpers — ONE definition of "rebuild a face's carrier and
355// pcurves around a boundary that has already moved", shared by push-face,
356// face-move and the direct-edit heals. This is the RE-INTERSECTION sense of
357// trimming; offset-shell's parametric-image trimming is a different operation
358// and deliberately not here (see the module doc). Crate-internal: no consumer
359// outside the kernel names these.
360#[path = "offset/retrim.rs"]
361mod offset_retrim;
362// The RE-INTERSECTION seam — ONE definition of "intersect two carriers to
363// remake a trim boundary", with the exact analytic lane first and the general
364// marched lane behind it. `offset_retrim` above re-trims a face around a
365// boundary that has ALREADY moved; this is the step that moves it, and it is
366// what makes a neighbour re-intersection surface-type-blind the way
367// offset-shell's imprint driver already is. Crate-internal: no consumer outside
368// the kernel names these.
369#[path = "offset/reintersect.rs"]
370mod offset_reintersect;
371// The MEASURED half of the tolerance model: the deviation actually observed
372// between an offset construction and the geometry it was built to reproduce.
373// Everything in `geometry/tolerance.rs` above `MeasuredTolerance` DERIVES a
374// band from a size before the geometry exists; this measures what the
375// construction then did, and compares it against that band. The comparison is
376// not itself a gate: the deviation rides the transient construction result
377// (`OffsetFaceCarrier::deviation`, `#[serde(skip)]`) and refusing on an
378// exceedance is the caller's call, not this module's. Declared before `offset`
379// because that module is its first consumer.
380#[path = "offset/measure.rs"]
381mod offset_measure;
382pub use offset_measure::{
383    measure_edge_against_pcurve_image, measure_surface_fit_against_pointwise_offset,
384    span_midpoint_error, vertex_endpoint_gap,
385};
386#[path = "offset/offset.rs"]
387mod offset;
388pub use offset::{
389    offset_face_carrier, offset_face_carrier_measured, offset_face_carrier_sided, offset_surface,
390    offset_surface_measured, offset_surface_measured_sided, CarrierDeviation, CarrierExtension,
391    MeasuredOffsetSurface, OffsetFaceCarrier, OffsetSurfaceLane,
392};
393#[path = "offset/thicken.rs"]
394mod thicken;
395pub use thicken::{thicken_face_sheet, thicken_trimmed_sheet};
396#[path = "healing/coalesce.rs"]
397mod coalesce;
398pub use coalesce::{concatenate_exact_curve_pieces, merge_curve_continuation_edges};
399#[path = "healing/face_merge.rs"]
400mod face_merge;
401// Retained for potential mesh-import repair; no exact operation may fall
402// back to it (offset shell must produce real surfaces or fail loudly).
403#[allow(dead_code)]
404#[path = "healing/faceted_repair.rs"]
405mod faceted_repair;
406pub use face_merge::{merge_same_surface_faces, merge_same_surface_faces_excluding};
407pub use faceted_repair::mesh_to_faceted_brep;
408#[path = "offset/offset_shell.rs"]
409mod offset_shell;
410pub use offset_shell::{
411    offset_shell, offset_shell_with_diagnostics, OffsetFaceRole, OffsetShellFaceImageRecord,
412    OffsetShellResultRecord,
413};
414#[path = "io/step_matrix.rs"]
415mod step_matrix;
416#[path = "io/step.rs"]
417mod step;
418pub use step::{
419    assembly_export_tree, audit_step_manifold, audit_step_pcurves, export_step,
420    export_step_assembly, export_step_assembly_report, export_step_report,
421    export_step_report_named, Mat4, StepAssemblyExport, StepExportOccurrence, StepExportProduct,
422    StepExportReport, StepPmi,
423};
424// Imported appearance (colour) — the carrier between an importer that READ a
425// colour and the pipeline that stamps it as name-keyed scene metadata. Not a
426// storage layer: see the module doc for the `{"color": "#RRGGBB"}` convention.
427#[path = "io/appearance.rs"]
428mod appearance;
429pub use appearance::{BodyAppearance, ImportedColor, COLOR_METADATA_KEY};
430#[path = "io/step_import/mod.rs"]
431mod step_import;
432pub use step_import::{
433    import_step, import_step_report, import_step_with_appearance, read_step_assembly,
434    read_step_pmi, StepAssembly, StepOccurrence, StepProduct,
435};
436/// Test seams for the Part 21 text codec of the PMI writer / reader.
437#[doc(hidden)]
438pub fn io_step_text_for_tests(value: &str) -> String {
439    step::pmi::step_text(value)
440}
441#[doc(hidden)]
442pub fn io_step_decode_text_for_tests(value: &str) -> String {
443    step_import::decode_step_text_for_tests(value)
444}
445#[path = "io/iges/mod.rs"]
446mod iges;
447pub use iges::{export_iges, import_iges};
448#[path = "io/mesh_io.rs"]
449mod mesh_io;
450pub use mesh_io::{
451    read_binary_stl, read_obj, write_binary_stl, write_obj, ObjReadResult, StlReadResult,
452};
453// Native serialized exact-BREP snapshot: the assemblies parts-library fast
454// lane (instant component insert from a cached payload; fails detectably so
455// the ACOMP self-heal lane can re-execute the embedded part document).
456#[path = "io/snapshot.rs"]
457mod snapshot;
458pub use snapshot::{
459    restore_solids, snapshot_resident_solids, snapshot_solids, RestoredSnapshot, RestoredSolid,
460    SNAPSHOT_FORMAT_VERSION,
461};
462#[path = "solvers/linear_algebra.rs"]
463mod solver_linear_algebra;
464#[path = "solvers/sketch_solver.rs"]
465mod sketch_solver;
466pub use sketch_solver::{
467    sketch_id_key, solve_sketch, solve_sketch_from_json, SketchSolverSettings, SolveSketchRequest,
468};
469#[path = "solvers/assembly_solver.rs"]
470mod assembly_solver;
471pub use assembly_solver::{
472    solve_assembly, AssemblyBody, AssemblyMate, AssemblySolution, AssemblySolveOptions, BodyPose,
473    MateAlign, MateAxis, MateKind, MatePlane, MateResidualReport, SolveStrategy,
474};
475// Assembly selection resolution (build-spec §5): a namespaced selection ref
476// resolved to an analytic frame (plane/axis/sphere/circle/line/point) read from
477// the exact BREP, feeding `MateKind` inputs in component-local coordinates.
478#[path = "solvers/assembly_resolve.rs"]
479mod assembly_resolve;
480pub use assembly_resolve::{
481    is_component_reference, resolve_component_point, resolve_edge_selection,
482    resolve_face_selection, resolve_named_selection, resolve_vertex_selection,
483    split_component_namespace, ResolveError, SelectionGeometry,
484};
485#[path = "edit/direct_edit.rs"]
486mod direct_edit;
487pub use direct_edit::{
488    delete_face_and_heal, delete_faces_and_heal, move_faces, offset_freeform_face,
489    offset_revolution_face, offset_ruled_face, offset_sphere_face, offset_torus_face,
490    resolve_face_by_point,
491};
492#[path = "healing/sew.rs"]
493mod sew;
494pub use sew::{sew_solid, split_pinched_vertices, SewReport};
495#[path = "meshing/mesh_weld.rs"]
496mod mesh_weld;
497#[path = "meshing/mesh_segment.rs"]
498mod mesh_segment;
499pub use mesh_segment::{
500    mesh_regions_to_brep, segment_mesh_faces, MeshRegion, MeshSegmentation, RegionCarrier,
501    SegmentOptions, UNASSIGNED_REGION,
502};
503
504#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)]
505pub struct Vec3 {
506    pub x: f64,
507    pub y: f64,
508    pub z: f64,
509}
510
511impl Vec3 {
512    pub fn new(x: f64, y: f64, z: f64) -> Self {
513        Self { x, y, z }
514    }
515    pub fn sub(self, rhs: Self) -> Self {
516        Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
517    }
518    pub fn add(self, rhs: Self) -> Self {
519        Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
520    }
521    pub fn scale(self, factor: f64) -> Self {
522        Self::new(self.x * factor, self.y * factor, self.z * factor)
523    }
524    pub fn cross(self, rhs: Self) -> Self {
525        Self::new(
526            self.y * rhs.z - self.z * rhs.y,
527            self.z * rhs.x - self.x * rhs.z,
528            self.x * rhs.y - self.y * rhs.x,
529        )
530    }
531    pub fn dot(self, rhs: Self) -> f64 {
532        self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
533    }
534    pub fn length(self) -> f64 {
535        self.dot(self).sqrt()
536    }
537    pub fn length_squared(self) -> f64 {
538        self.dot(self)
539    }
540    pub fn normalized(self) -> Result<Self, String> {
541        let length = self.length();
542        if length <= 1e-12 {
543            return Err("Vec3.normalized: zero-length vector".into());
544        }
545        Ok(self.scale(1.0 / length))
546    }
547    pub fn perpendicular(self) -> Result<Self, String> {
548        let x = self.x.abs();
549        let y = self.y.abs();
550        let z = self.z.abs();
551        let axis = if x <= y && x <= z {
552            Self::new(1.0, 0.0, 0.0)
553        } else if y <= z {
554            Self::new(0.0, 1.0, 0.0)
555        } else {
556            Self::new(0.0, 0.0, 1.0)
557        };
558        self.cross(axis).normalized()
559    }
560}
561
562#[derive(Clone, Debug, Default, Deserialize, Serialize)]
563pub struct Mesh {
564    pub positions: Vec<f64>,
565    pub normals: Vec<f64>,
566    pub indices: Vec<u32>,
567    pub face_ids: Vec<u32>,
568}
569
570impl Mesh {
571    fn push_vertex(&mut self, point: Vec3, normal: Vec3) -> u32 {
572        self.positions.extend([point.x, point.y, point.z]);
573        self.normals.extend([normal.x, normal.y, normal.z]);
574        (self.positions.len() / 3 - 1) as u32
575    }
576
577    fn quad(&mut self, points: [Vec3; 4], normal: Vec3, face_id: u32) {
578        let base = self.push_vertex(points[0], normal);
579        for point in points.iter().skip(1) {
580            self.push_vertex(*point, normal);
581        }
582        self.indices
583            .extend([base, base + 1, base + 2, base, base + 2, base + 3]);
584        self.face_ids.extend([face_id, face_id]);
585    }
586
587    pub fn signed_volume(&self) -> f64 {
588        self.indices
589            .chunks_exact(3)
590            .map(|tri| {
591                let point = |index: u32| {
592                    let i = index as usize * 3;
593                    Vec3::new(
594                        self.positions[i],
595                        self.positions[i + 1],
596                        self.positions[i + 2],
597                    )
598                };
599                point(tri[0]).dot(point(tri[1]).cross(point(tri[2]))) / 6.0
600            })
601            .sum()
602    }
603
604    pub fn validate(&self) -> Result<(), String> {
605        if self.positions.len() % 3 != 0 || self.normals.len() != self.positions.len() {
606            return Err("invalid vertex or normal buffer".into());
607        }
608        self.validate_geometry()
609    }
610
611    /// Geometry-only validation for consumers that use positions + indices but
612    /// NOT normals (e.g. `signed_volume`, whose tetrahedron sum never reads a
613    /// normal). Mesh-only solids such as sheet-metal bodies supply metrics
614    /// meshes with an empty normal buffer, which is legitimate for volume.
615    pub fn validate_geometry(&self) -> Result<(), String> {
616        if self.positions.len() % 3 != 0 {
617            return Err("invalid vertex buffer".into());
618        }
619        if self.indices.len() % 3 != 0 {
620            return Err("index buffer is not triangular".into());
621        }
622        let count = self.positions.len() / 3;
623        if self.indices.iter().any(|index| *index as usize >= count) {
624            return Err("index outside vertex buffer".into());
625        }
626        if !self.positions.iter().all(|value| value.is_finite()) {
627            return Err("non-finite vertex".into());
628        }
629        Ok(())
630    }
631}
632
633pub fn make_box(size_x: f64, size_y: f64, size_z: f64) -> Mesh {
634    let x = size_x.abs();
635    let y = size_y.abs();
636    let z = size_z.abs();
637    let p = [
638        Vec3::new(0.0, 0.0, 0.0),
639        Vec3::new(x, 0.0, 0.0),
640        Vec3::new(x, y, 0.0),
641        Vec3::new(0.0, y, 0.0),
642        Vec3::new(0.0, 0.0, z),
643        Vec3::new(x, 0.0, z),
644        Vec3::new(x, y, z),
645        Vec3::new(0.0, y, z),
646    ];
647    let mut mesh = Mesh::default();
648    mesh.quad([p[0], p[3], p[2], p[1]], Vec3::new(0.0, 0.0, -1.0), 0);
649    mesh.quad([p[4], p[5], p[6], p[7]], Vec3::new(0.0, 0.0, 1.0), 1);
650    mesh.quad([p[0], p[1], p[5], p[4]], Vec3::new(0.0, -1.0, 0.0), 2);
651    mesh.quad([p[3], p[7], p[6], p[2]], Vec3::new(0.0, 1.0, 0.0), 3);
652    mesh.quad([p[0], p[4], p[7], p[3]], Vec3::new(-1.0, 0.0, 0.0), 4);
653    mesh.quad([p[1], p[2], p[6], p[5]], Vec3::new(1.0, 0.0, 0.0), 5);
654    mesh
655}
656
657pub fn make_cylinder(radius: f64, height: f64, segments: usize) -> Mesh {
658    let radius = radius.abs();
659    let height = height.abs();
660    let segments = segments.max(3);
661    let mut mesh = Mesh::default();
662    for i in 0..segments {
663        let a = std::f64::consts::TAU * i as f64 / segments as f64;
664        let b = std::f64::consts::TAU * (i + 1) as f64 / segments as f64;
665        let (sa, ca) = a.sin_cos();
666        let (sb, cb) = b.sin_cos();
667        let pa = Vec3::new(radius * ca, 0.0, -radius * sa);
668        let pb = Vec3::new(radius * cb, 0.0, -radius * sb);
669        let ta = Vec3::new(pa.x, height, pa.z);
670        let tb = Vec3::new(pb.x, height, pb.z);
671        mesh.quad(
672            [pa, pb, tb, ta],
673            Vec3::new((ca + cb) * 0.5, 0.0, -(sa + sb) * 0.5),
674            0,
675        );
676        let bottom = mesh.push_vertex(Vec3::default(), Vec3::new(0.0, -1.0, 0.0));
677        let bi = mesh.push_vertex(pb, Vec3::new(0.0, -1.0, 0.0));
678        let bj = mesh.push_vertex(pa, Vec3::new(0.0, -1.0, 0.0));
679        mesh.indices.extend([bottom, bi, bj]);
680        mesh.face_ids.push(1);
681        let top = mesh.push_vertex(Vec3::new(0.0, height, 0.0), Vec3::new(0.0, 1.0, 0.0));
682        let ti = mesh.push_vertex(ta, Vec3::new(0.0, 1.0, 0.0));
683        let tj = mesh.push_vertex(tb, Vec3::new(0.0, 1.0, 0.0));
684        mesh.indices.extend([top, ti, tj]);
685        mesh.face_ids.push(2);
686    }
687    mesh
688}
689
690
691mod abi;
692pub use abi::*;
693
694// BREP private tests: e9a96ee1118285df
695// BREP private tests: 802ecfa7fc0314e2