@faicad/cq-compat 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/assembly-compare.d.ts +135 -0
  2. package/dist/assembly-compare.d.ts.map +1 -0
  3. package/dist/assembly-compare.js +266 -0
  4. package/dist/assembly-compare.js.map +1 -0
  5. package/dist/assembly.d.ts +102 -0
  6. package/dist/assembly.d.ts.map +1 -0
  7. package/dist/assembly.js +403 -0
  8. package/dist/assembly.js.map +1 -0
  9. package/dist/browser.d.ts +13 -0
  10. package/dist/browser.d.ts.map +1 -0
  11. package/dist/browser.js +13 -0
  12. package/dist/browser.js.map +1 -0
  13. package/dist/gear-test-harness.d.ts +79 -0
  14. package/dist/gear-test-harness.d.ts.map +1 -0
  15. package/dist/gear-test-harness.js +112 -0
  16. package/dist/gear-test-harness.js.map +1 -0
  17. package/dist/gears.d.ts +226 -0
  18. package/dist/gears.d.ts.map +1 -0
  19. package/dist/gears.js +274 -0
  20. package/dist/gears.js.map +1 -0
  21. package/dist/geom-types.d.ts +7 -0
  22. package/dist/geom-types.d.ts.map +1 -0
  23. package/dist/geom-types.js +2 -0
  24. package/dist/geom-types.js.map +1 -0
  25. package/dist/index.d.ts +26 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +23 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/step-compare.d.ts +83 -0
  30. package/dist/step-compare.d.ts.map +1 -0
  31. package/dist/step-compare.js +140 -0
  32. package/dist/step-compare.js.map +1 -0
  33. package/dist/transpile.d.ts +42 -0
  34. package/dist/transpile.d.ts.map +1 -0
  35. package/dist/transpile.js +557 -0
  36. package/dist/transpile.js.map +1 -0
  37. package/dist/workplane.d.ts +1216 -0
  38. package/dist/workplane.d.ts.map +1 -0
  39. package/dist/workplane.js +4026 -0
  40. package/dist/workplane.js.map +1 -0
  41. package/package.json +48 -0
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Shared harness for the E1–E4 gear-extension ops.
3
+ *
4
+ * These ops call the occt-wasm kernel directly via `getKernel()` (the native
5
+ * singleton that `registerOcctBrepEngine()` initializes) and wrap results with
6
+ * `fromHandle`. `fromHandle` needs `getBackends().kernel.brep` to be a MESHABLE
7
+ * kernel — the raw occt-wasm `OcctKernel` exposes `meshShape`, so we wire it
8
+ * straight into `configureBackends`. This is intentionally the SAME native
9
+ * instance the ops use, so handles stay consistent (no second kernel, no
10
+ * `createRuntime`, which only lazily builds its brep chain inside `execute`).
11
+ *
12
+ * This file is NOT a test (no `*.test.ts`); it is imported by the E1–E4 specs.
13
+ */
14
+ import { registerOcctBrepEngine, configureBackends, CONTRACT_VERSION } from '@faicad/faijs';
15
+ import { getKernel } from '@faicad/faijs/occt-kernel/occtKernel';
16
+ import { brepOf } from '@faicad/faijs/shape';
17
+ export { brepOf };
18
+ /**
19
+ * Initialise the native occt-wasm kernel for the E1–E4 specs.
20
+ *
21
+ * Calls `registerOcctBrepEngine()` (which boots the kernel singleton and
22
+ * registers the OCCT engine) and then wires that SAME instance into the
23
+ * backend registry via `configureBackends`, so `fromHandle` can resolve a
24
+ * meshable kernel. Must be awaited in `beforeAll` before any op is invoked.
25
+ *
26
+ * @returns a promise that resolves once the kernel and backends are ready
27
+ */
28
+ export async function setupNativeKernel() {
29
+ await registerOcctBrepEngine();
30
+ const k = getKernel();
31
+ configureBackends({
32
+ contractVersion: CONTRACT_VERSION,
33
+ // Mirror the OCCT engine capabilities declared in registerOcctBrepEngine so
34
+ // dual-ops that gate on a capability (e.g. cut → 'evolution') dispatch to
35
+ // brep instead of throwing E_BREP_UNSUPPORTED in the bare-kernel test path.
36
+ config: {
37
+ mode: 'brep',
38
+ brepEngineId: 'occt',
39
+ brepCapabilities: {
40
+ evolution: true,
41
+ heal: true,
42
+ directEdit: true,
43
+ advSurface: true,
44
+ assembly: true,
45
+ meshLift: true,
46
+ },
47
+ },
48
+ kernel: { brep: k, csg: undefined, sdf: undefined },
49
+ fonts: undefined,
50
+ texture: undefined,
51
+ assets: undefined,
52
+ events: undefined,
53
+ });
54
+ }
55
+ /**
56
+ * Return the shared native occt-wasm kernel singleton.
57
+ *
58
+ * The same instance the E1–E4 ops obtain internally via `getKernel()`, so
59
+ * fixtures built here and op outputs share one handle space.
60
+ *
61
+ * @returns the live `OcctKernel` instance (valid after {@link setupNativeKernel})
62
+ */
63
+ export function kernel() {
64
+ return getKernel();
65
+ }
66
+ /**
67
+ * Build a minimal valid root `Workplane` without touching the `cad.*` backend.
68
+ *
69
+ * Defaults to the XY plane at the origin (normal +Z, xDir +X, yDir +Y) with no
70
+ * shape and no pending wires; pass `over` to override any field (e.g.
71
+ * `{ shape }` or `{ normal, origin }`).
72
+ *
73
+ * @param over - partial overrides merged onto the default workplane
74
+ * @returns a `Workplane` literal suitable as the first argument of an op
75
+ */
76
+ export function mkWP(over = {}) {
77
+ return {
78
+ __cq: true,
79
+ plane: 'XY',
80
+ origin: [0, 0, 0],
81
+ normal: [0, 0, 1],
82
+ xDir: [1, 0, 0],
83
+ yDir: [0, 1, 0],
84
+ shape: null,
85
+ faceSel: null,
86
+ edgeSel: null,
87
+ vertexSel: null,
88
+ pts: [],
89
+ forConstruction: false,
90
+ ...over,
91
+ };
92
+ }
93
+ /**
94
+ * Compute the axis-aligned bounding box of a shape through the native kernel.
95
+ *
96
+ * @param shape - a faijs `Shape` carrying a BREP handle (see `brepOf`)
97
+ * @returns the six-field bounding box
98
+ */
99
+ export function bbox(shape) {
100
+ return kernel().getBoundingBox(brepOf(shape));
101
+ }
102
+ /**
103
+ * Surface area of a face (or total area of a shell/solid), via the native
104
+ * kernel's `getSurfaceArea`.
105
+ *
106
+ * @param shape - a faijs `Shape` carrying a BREP handle (see `brepOf`)
107
+ * @returns the area in model units squared
108
+ */
109
+ export function area(shape) {
110
+ return kernel().getSurfaceArea(brepOf(shape));
111
+ }
112
+ //# sourceMappingURL=gear-test-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gear-test-harness.js","sourceRoot":"","sources":["../src/gear-test-harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAC3F,OAAO,EAAE,SAAS,EAAE,MAAM,sCAAsC,CAAA;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,CAAA;AAKjB;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,sBAAsB,EAAE,CAAA;IAC9B,MAAM,CAAC,GAAG,SAAS,EAA2B,CAAA;IAC9C,iBAAiB,CAAC;QAChB,eAAe,EAAE,gBAAgB;QACjC,4EAA4E;QAC5E,0EAA0E;QAC1E,4EAA4E;QAC5E,MAAM,EAAE;YACN,IAAI,EAAE,MAAM;YACZ,YAAY,EAAE,MAAM;YACpB,gBAAgB,EAAE;gBAChB,SAAS,EAAE,IAAI;gBACf,IAAI,EAAE,IAAI;gBACV,UAAU,EAAE,IAAI;gBAChB,UAAU,EAAE,IAAI;gBAChB,QAAQ,EAAE,IAAI;gBACd,QAAQ,EAAE,IAAI;aACf;SACF;QACD,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE;QACnD,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,SAAS;QAClB,MAAM,EAAE,SAAS;QACjB,MAAM,EAAE,SAAS;KAClB,CAAC,CAAA;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM;IACpB,OAAO,SAAS,EAA2B,CAAA;AAC7C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,IAAI,CAAC,OAA2B,EAAE;IAChD,OAAO;QACL,IAAI,EAAE,IAAI;QACV,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACjB,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACjB,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACf,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QACf,KAAK,EAAE,IAAI;QACX,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,IAAI;QACb,SAAS,EAAE,IAAI;QACf,GAAG,EAAE,EAAE;QACP,eAAe,EAAE,KAAK;QACtB,GAAG,IAAI;KACR,CAAA;AACH,CAAC;AAiBD;;;;;GAKG;AACH,MAAM,UAAU,IAAI,CAAC,KAAY;IAC/B,OAAO,MAAM,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAA2B,CAAS,CAAA;AACjF,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,IAAI,CAAC,KAAY;IAC/B,OAAO,MAAM,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,KAAK,CAA2B,CAAC,CAAA;AACzE,CAAC","sourcesContent":["/**\n * Shared harness for the E1–E4 gear-extension ops.\n *\n * These ops call the occt-wasm kernel directly via `getKernel()` (the native\n * singleton that `registerOcctBrepEngine()` initializes) and wrap results with\n * `fromHandle`. `fromHandle` needs `getBackends().kernel.brep` to be a MESHABLE\n * kernel — the raw occt-wasm `OcctKernel` exposes `meshShape`, so we wire it\n * straight into `configureBackends`. This is intentionally the SAME native\n * instance the ops use, so handles stay consistent (no second kernel, no\n * `createRuntime`, which only lazily builds its brep chain inside `execute`).\n *\n * This file is NOT a test (no `*.test.ts`); it is imported by the E1–E4 specs.\n */\n\nimport { registerOcctBrepEngine, configureBackends, CONTRACT_VERSION } from '@faicad/faijs'\nimport { getKernel } from '@faicad/faijs/occt-kernel/occtKernel'\nimport { brepOf } from '@faicad/faijs/shape'\nexport { brepOf }\nimport type { OcctKernel, ShapeHandle } from 'occt-wasm'\nimport type { Shape } from '@faicad/faijs/mesh/types'\nimport type { Workplane } from './workplane'\n\n/**\n * Initialise the native occt-wasm kernel for the E1–E4 specs.\n *\n * Calls `registerOcctBrepEngine()` (which boots the kernel singleton and\n * registers the OCCT engine) and then wires that SAME instance into the\n * backend registry via `configureBackends`, so `fromHandle` can resolve a\n * meshable kernel. Must be awaited in `beforeAll` before any op is invoked.\n *\n * @returns a promise that resolves once the kernel and backends are ready\n */\nexport async function setupNativeKernel(): Promise<void> {\n await registerOcctBrepEngine()\n const k = getKernel() as unknown as OcctKernel\n configureBackends({\n contractVersion: CONTRACT_VERSION,\n // Mirror the OCCT engine capabilities declared in registerOcctBrepEngine so\n // dual-ops that gate on a capability (e.g. cut → 'evolution') dispatch to\n // brep instead of throwing E_BREP_UNSUPPORTED in the bare-kernel test path.\n config: {\n mode: 'brep',\n brepEngineId: 'occt',\n brepCapabilities: {\n evolution: true,\n heal: true,\n directEdit: true,\n advSurface: true,\n assembly: true,\n meshLift: true,\n },\n },\n kernel: { brep: k, csg: undefined, sdf: undefined },\n fonts: undefined,\n texture: undefined,\n assets: undefined,\n events: undefined,\n })\n}\n\n/**\n * Return the shared native occt-wasm kernel singleton.\n *\n * The same instance the E1–E4 ops obtain internally via `getKernel()`, so\n * fixtures built here and op outputs share one handle space.\n *\n * @returns the live `OcctKernel` instance (valid after {@link setupNativeKernel})\n */\nexport function kernel(): OcctKernel {\n return getKernel() as unknown as OcctKernel\n}\n\n/**\n * Build a minimal valid root `Workplane` without touching the `cad.*` backend.\n *\n * Defaults to the XY plane at the origin (normal +Z, xDir +X, yDir +Y) with no\n * shape and no pending wires; pass `over` to override any field (e.g.\n * `{ shape }` or `{ normal, origin }`).\n *\n * @param over - partial overrides merged onto the default workplane\n * @returns a `Workplane` literal suitable as the first argument of an op\n */\nexport function mkWP(over: Partial<Workplane> = {}): Workplane {\n return {\n __cq: true,\n plane: 'XY',\n origin: [0, 0, 0],\n normal: [0, 0, 1],\n xDir: [1, 0, 0],\n yDir: [0, 1, 0],\n shape: null,\n faceSel: null,\n edgeSel: null,\n vertexSel: null,\n pts: [],\n forConstruction: false,\n ...over,\n }\n}\n\n/**\n * Axis-aligned bounding box of a shape, in the kernel's field naming.\n *\n * Mirrors the `BoundingBox` record returned by the native\n * `getBoundingBox` call (all six min/max fields, no `minX`-style aliases).\n */\nexport type BBox = {\n xmin: number\n xmax: number\n ymin: number\n ymax: number\n zmin: number\n zmax: number\n}\n\n/**\n * Compute the axis-aligned bounding box of a shape through the native kernel.\n *\n * @param shape - a faijs `Shape` carrying a BREP handle (see `brepOf`)\n * @returns the six-field bounding box\n */\nexport function bbox(shape: Shape): BBox {\n return kernel().getBoundingBox(brepOf(shape) as unknown as ShapeHandle) as BBox\n}\n\n/**\n * Surface area of a face (or total area of a shell/solid), via the native\n * kernel's `getSurfaceArea`.\n *\n * @param shape - a faijs `Shape` carrying a BREP handle (see `brepOf`)\n * @returns the area in model units squared\n */\nexport function area(shape: Shape): number {\n return kernel().getSurfaceArea(brepOf(shape) as unknown as ShapeHandle)\n}\n"]}
@@ -0,0 +1,226 @@
1
+ /**
2
+ * gears — raw-handle primitives for the cq_gears port (E1–E6 companion layer).
3
+ *
4
+ * cq_gears' tooth faces are B-spline patches built from (u,v) point grids and
5
+ * assembled by sewing — these need raw kernel construction primitives that no
6
+ * Workplane op exposes (bsplineSurface / approximatePoints / interpolatePoints,
7
+ * tolerant edge chaining, sew+makeSolid). The fai_cq_gears v1 port probed them
8
+ * in its own `kernel.ts` / `geom-build.ts` / `spline-face.ts`; per the port
9
+ * plan (§4) the implementations must live INSIDE cq-compat — this file is that
10
+ * landing spot. fai_cq_gears only imports from here; it must not touch
11
+ * occt-wasm directly.
12
+ *
13
+ * Everything here is a verbatim port of the proven v1 implementations (same
14
+ * kernel call sequence → bit-identical geometry).
15
+ */
16
+ import type { BrepEngineApi, BrepHandle, BrepVec3 } from '@faicad/faijs';
17
+ import type { Vec3 } from './geom-types.js';
18
+ /** Rotation/mirror axis (occt-wasm shape). */
19
+ export interface GearAxis {
20
+ point: BrepVec3;
21
+ direction: BrepVec3;
22
+ }
23
+ /**
24
+ * Raw occt-wasm capability surface — superset of `BrepEngineApi`.
25
+ *
26
+ * Declares exactly the methods the cq_gears port consumes; the probe test in
27
+ * fai_cq_gears asserts each one exists on the live kernel, so a kernel upgrade
28
+ * that renames a binding turns red immediately instead of failing at runtime
29
+ * with `undefined is not a function`.
30
+ */
31
+ export interface GearKernel extends BrepEngineApi {
32
+ /** `GeomAPI_PointsToBSplineSurface`: point grid → B-spline surface → Face. */
33
+ bsplineSurface(points: BrepVec3[], rows: number, cols: number): BrepHandle;
34
+ /** `GeomAPI_PointsToBSpline`: point list → approximated B-spline curve (with Tol3D). */
35
+ approximatePoints(points: BrepVec3[], tolerance?: number): BrepHandle;
36
+ /** Cubic B-spline interpolation curve through all points. */
37
+ interpolatePoints(points: BrepVec3[], periodic?: boolean): BrepHandle;
38
+ /** `BRepBuilderAPI_Sewing`: faces → sewn shell (cq `make_shell` analogue). */
39
+ sew(shapes: BrepHandle[], tolerance?: number): BrepHandle;
40
+ makeSolid(shell: BrepHandle): BrepHandle;
41
+ /** TopAbs_ShapeEnum type name ("solid" / "shell" / "face" …, for diagnostics/assertions). */
42
+ getShapeType(shape: BrepHandle): string;
43
+ buildSolidFromFaces(faces: BrepHandle[], tolerance?: number): BrepHandle;
44
+ makeNonPlanarFace(wire: BrepHandle): BrepHandle;
45
+ makeFaceOnSurface(face: BrepHandle, wire: BrepHandle): BrepHandle;
46
+ outerWire(face: BrepHandle): BrepHandle;
47
+ healWire(wire: BrepHandle, tolerance?: number): BrepHandle;
48
+ healFace(face: BrepHandle, tolerance?: number): BrepHandle;
49
+ rotate(shape: BrepHandle, axis: GearAxis, angleRad: number): BrepHandle;
50
+ /** `BRepPrimAPI_MakeRevol`: face/wire revolved around `axis` (cq `Workplane.revolve`, 360°=2π). */
51
+ revolve(shape: BrepHandle, axis: GearAxis, angleRad: number): BrepHandle;
52
+ mirror(shape: BrepHandle, point: BrepVec3, normal: BrepVec3): BrepHandle;
53
+ reverseShape(shape: BrepHandle): BrepHandle;
54
+ vertexPosition(vertex: BrepHandle): BrepVec3;
55
+ split(shape: BrepHandle, tools: BrepHandle[]): BrepHandle;
56
+ fillet(solid: BrepHandle, edges: BrepHandle[], radius: number): BrepHandle;
57
+ shell(solid: BrepHandle, facesToRemove: BrepHandle[], thickness: number, tolerance: number): BrepHandle;
58
+ thicken(shape: BrepHandle, thickness: number, tolerance: number): BrepHandle;
59
+ makeCircleArc(center: BrepVec3, normal: BrepVec3, radius: number, startAngle: number, endAngle: number): BrepHandle;
60
+ makeHelixWire(origin: BrepVec3, axis: BrepVec3, pitch: number, height: number, radius: number): BrepHandle;
61
+ getSurfaceArea(shape: BrepHandle): number;
62
+ projectPointOnFace(face: BrepHandle, point: BrepVec3): BrepVec3;
63
+ surfaceType(face: BrepHandle): string;
64
+ subShapeCount(shape: BrepHandle, type: 'vertex' | 'edge' | 'wire' | 'face' | 'shell' | 'solid'): number;
65
+ isFace(shape: BrepHandle): boolean;
66
+ isWire(shape: BrepHandle): boolean;
67
+ isShell(shape: BrepHandle): boolean;
68
+ isEdge(shape: BrepHandle): boolean;
69
+ }
70
+ /**
71
+ * Get the raw occt-wasm kernel (process-wide singleton).
72
+ *
73
+ * Same instance as `initOcctWasm()` — this only asserts the richer
74
+ * `GearKernel` type, never constructs a second kernel (handles are indices
75
+ * into one kernel arena and must not cross instances).
76
+ *
77
+ * @returns the raw kernel (promise; initializes wasm on first call)
78
+ */
79
+ export declare function getGearKernel(): Promise<GearKernel>;
80
+ /** The two endpoints of an edge (order = the edge's parameter direction). */
81
+ export interface GearEdgeEnds {
82
+ edge: BrepHandle;
83
+ a: Vec3;
84
+ b: Vec3;
85
+ }
86
+ /**
87
+ * Endpoints of an edge, via the curve parameter domain.
88
+ *
89
+ * ⚠️ Endpoint red line (probed 2026-09-11): `getSubShapes(edge, 'vertex')`
90
+ * returns fewer than 2 vertices for **B-spline curve edges** (OCCT stores no
91
+ * explicit vertices on them), which used to make edge chaining treat every
92
+ * edge as its own wire. The correct path is `curveParameters(edge)` →
93
+ * `curvePointAtParam(edge, param)` — valid for ALL edge kinds (line / arc /
94
+ * B-spline); the vertex fallback only covers degenerate edges without an
95
+ * underlying curve.
96
+ *
97
+ * @param kernel raw OCCT kernel
98
+ * @param edge edge handle
99
+ * @returns both endpoints (closed edges: first == last)
100
+ */
101
+ export declare function gearEdgeEnds(kernel: GearKernel, edge: BrepHandle): GearEdgeEnds;
102
+ /**
103
+ * TS port of OCCT `ShapeAnalysis_FreeBounds::ConnectEdgesToWires` — chain
104
+ * unordered edges into wires by endpoint proximity within `tol`.
105
+ *
106
+ * faijs/occt-wasm has no such binding and `makeWire(edges)` is
107
+ * `BRepBuilderAPI_MakeWire` — measured (2026-09-08) to silently drop edges on
108
+ * unordered input (68 edges → 9-edge broken wire). Endpoint pairing is O(n²)
109
+ * (n = 4·z teeth, at most a few hundred — fine).
110
+ *
111
+ * Gaps above OCCT precision (~1e-7) get a bridging line segment, matching
112
+ * upstream ConnectEdgesToWires behaviour.
113
+ *
114
+ * @param kernel raw OCCT kernel
115
+ * @param edges unordered edge set
116
+ * @param tol endpoint pairing tolerance (mm); cq uses `wire_comb_tol = 1e-2`
117
+ * @returns one or more wires, each with its edges chained head-to-tail
118
+ */
119
+ export declare function connectEdgesToWires(kernel: GearKernel, edges: BrepHandle[], tol: number): BrepHandle[];
120
+ /**
121
+ * cq `Face.makeFromWires(outer, holes)` analogue.
122
+ *
123
+ * @param kernel raw OCCT kernel
124
+ * @param outer outer wire
125
+ * @param holes inner hole wires (may be empty)
126
+ * @returns face handle
127
+ */
128
+ export declare function gearFaceFromWires(kernel: GearKernel, outer: BrepHandle, holes?: BrepHandle[]): BrepHandle;
129
+ /**
130
+ * cq `make_shell(faces, tol)` + `Solid.makeSolid(shell)` analogue.
131
+ *
132
+ * A non-shell sew result is NOT silently swallowed — that is a construction
133
+ * failure signal.
134
+ *
135
+ * @param kernel raw OCCT kernel
136
+ * @param faces faces to sew
137
+ * @param sewingTol sewing tolerance (mm, cq `shell_sewing_tol`)
138
+ * @returns solid built from the sewn shell
139
+ */
140
+ export declare function gearShellToSolid(kernel: GearKernel, faces: BrepHandle[], sewingTol: number): BrepHandle;
141
+ /**
142
+ * Tooth-face B-spline face strategies (mirrors cq `Face.makeSplineApprox`).
143
+ *
144
+ * | strategy | how | semantic delta vs cq |
145
+ * |---|---|---|
146
+ * | `grid-approx` | `kernel.bsplineSurface(flat, rows, cols)` (also `GeomAPI_PointsToBSplineSurface`) | OCCT default DegMin/DegMax/Tol3D; cq passes 3/8/1e-2 explicitly |
147
+ * | `row-approx-loft` | per-row `approximatePoints(row, tol)` → `loft(wires, false, false)` | curve-level tol same name/meaning; surface is skinned, not fitted at once |
148
+ * | `row-interp-loft` | per-row `interpolatePoints(row)` → `loft` | passes through every sample point (interpolation) |
149
+ */
150
+ export type GearSplineFaceStrategy = 'grid-approx' | 'row-approx-loft' | 'row-interp-loft';
151
+ /** All selectable strategies (tests iterate in this order for the deviation table). */
152
+ export declare const GEAR_SPLINE_FACE_STRATEGIES: readonly GearSplineFaceStrategy[];
153
+ /**
154
+ * Default strategy chosen by P0 measurement (2026-09-08).
155
+ *
156
+ * Per the measured table in the fai_cq_gears spike: S2's area relative
157
+ * deviation is 5.6e-7 / max sample-point distance 2.6e-6 mm — about three
158
+ * orders of magnitude better than S1/S3. See
159
+ * `docs/analysis/2026-09-08-fai-cq-gears-spike.md`.
160
+ */
161
+ export declare const DEFAULT_GEAR_SPLINE_FACE_STRATEGY: GearSplineFaceStrategy;
162
+ /** Face building options (tolerance + degrees, aligned with cq `makeSplineApprox`). */
163
+ export interface GearSplineFaceOptions {
164
+ /** Approximation tolerance (mm). cq's `spline_approx_tol`, default 1e-2. */
165
+ tolerance?: number;
166
+ /** Surface minimum degree (cq: 3) — only meaningful for grid-approx/row-approx-loft. */
167
+ minDeg?: number;
168
+ /** Surface maximum degree (cq: 8). */
169
+ maxDeg?: number;
170
+ }
171
+ /** A row×col point grid with its dimensions (structural — no gear types here). */
172
+ export interface GearSplineGrid {
173
+ /** `rows` point rows of `cols` points each. */
174
+ points: Vec3[][];
175
+ rows: number;
176
+ cols: number;
177
+ }
178
+ /**
179
+ * Build a face from a row×col point grid with the given strategy.
180
+ *
181
+ * @throws kernel errors propagate verbatim (never swallowed — red line)
182
+ *
183
+ * @param kernel raw OCCT kernel
184
+ * @param grid row×col point grid
185
+ * @param strategy face building strategy
186
+ * @param options tolerance/degree options
187
+ * @returns single face handle (Face)
188
+ */
189
+ export declare function buildGearSplineFace(kernel: GearKernel, grid: GearSplineGrid, strategy: GearSplineFaceStrategy, options?: GearSplineFaceOptions): BrepHandle;
190
+ /**
191
+ * Reduce a "single-face shell" to a Face; a Face passes through unchanged.
192
+ *
193
+ * @throws when the face count is not 1 (silently taking the first would mask problems)
194
+ *
195
+ * @param kernel raw OCCT kernel
196
+ * @param shape any shape (Face / single-face shell / …)
197
+ * @returns the sole Face
198
+ */
199
+ export declare function soleGearFace(kernel: GearKernel, shape: BrepHandle): BrepHandle;
200
+ /** Distance from a point to a face (`projectPointOnFace`).
201
+ *
202
+ * @param kernel raw OCCT kernel
203
+ * @param face target face
204
+ * @param p query point
205
+ * @returns euclidean distance (mm)
206
+ */
207
+ export declare function gearDistanceToFace(kernel: GearKernel, face: BrepHandle, p: Vec3): number;
208
+ /** Distance statistics (max / RMS / sample count). */
209
+ export interface GearDeviationStats {
210
+ max: number;
211
+ rms: number;
212
+ n: number;
213
+ }
214
+ /**
215
+ * Distance statistics of reference sample points against a face.
216
+ *
217
+ * Parameterization-independent: samples come from the cq side's (u,v) grid, so
218
+ * the two surfaces may have different parameterizations.
219
+ *
220
+ * @param kernel raw OCCT kernel
221
+ * @param face face under test
222
+ * @param samplePoints reference sample points (3D)
223
+ * @returns max distance / RMS / sample count
224
+ */
225
+ export declare function gearFaceDeviation(kernel: GearKernel, face: BrepHandle, samplePoints: Vec3[]): GearDeviationStats;
226
+ //# sourceMappingURL=gears.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gears.d.ts","sourceRoot":"","sources":["../src/gears.ts"],"names":[],"mappings":"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"}
package/dist/gears.js ADDED
@@ -0,0 +1,274 @@
1
+ /**
2
+ * gears — raw-handle primitives for the cq_gears port (E1–E6 companion layer).
3
+ *
4
+ * cq_gears' tooth faces are B-spline patches built from (u,v) point grids and
5
+ * assembled by sewing — these need raw kernel construction primitives that no
6
+ * Workplane op exposes (bsplineSurface / approximatePoints / interpolatePoints,
7
+ * tolerant edge chaining, sew+makeSolid). The fai_cq_gears v1 port probed them
8
+ * in its own `kernel.ts` / `geom-build.ts` / `spline-face.ts`; per the port
9
+ * plan (§4) the implementations must live INSIDE cq-compat — this file is that
10
+ * landing spot. fai_cq_gears only imports from here; it must not touch
11
+ * occt-wasm directly.
12
+ *
13
+ * Everything here is a verbatim port of the proven v1 implementations (same
14
+ * kernel call sequence → bit-identical geometry).
15
+ */
16
+ import { initOcctWasm } from '@faicad/faijs';
17
+ let gearKernelPromise = null;
18
+ /**
19
+ * Get the raw occt-wasm kernel (process-wide singleton).
20
+ *
21
+ * Same instance as `initOcctWasm()` — this only asserts the richer
22
+ * `GearKernel` type, never constructs a second kernel (handles are indices
23
+ * into one kernel arena and must not cross instances).
24
+ *
25
+ * @returns the raw kernel (promise; initializes wasm on first call)
26
+ */
27
+ export function getGearKernel() {
28
+ if (!gearKernelPromise) {
29
+ gearKernelPromise = initOcctWasm().then((k) => k);
30
+ }
31
+ return gearKernelPromise;
32
+ }
33
+ function gearDist(a, b) {
34
+ return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
35
+ }
36
+ /**
37
+ * Endpoints of an edge, via the curve parameter domain.
38
+ *
39
+ * ⚠️ Endpoint red line (probed 2026-09-11): `getSubShapes(edge, 'vertex')`
40
+ * returns fewer than 2 vertices for **B-spline curve edges** (OCCT stores no
41
+ * explicit vertices on them), which used to make edge chaining treat every
42
+ * edge as its own wire. The correct path is `curveParameters(edge)` →
43
+ * `curvePointAtParam(edge, param)` — valid for ALL edge kinds (line / arc /
44
+ * B-spline); the vertex fallback only covers degenerate edges without an
45
+ * underlying curve.
46
+ *
47
+ * @param kernel raw OCCT kernel
48
+ * @param edge edge handle
49
+ * @returns both endpoints (closed edges: first == last)
50
+ */
51
+ export function gearEdgeEnds(kernel, edge) {
52
+ try {
53
+ const { first, last } = kernel.curveParameters(edge);
54
+ return {
55
+ edge,
56
+ a: kernel.curvePointAtParam(edge, first),
57
+ b: kernel.curvePointAtParam(edge, last),
58
+ };
59
+ }
60
+ catch {
61
+ // Fallback: rare degenerate edges without an underlying curve.
62
+ const vs = kernel.getSubShapes(edge, 'vertex');
63
+ if (vs.length < 2) {
64
+ const p = kernel.vertexPosition(vs[0]);
65
+ return { edge, a: p, b: p };
66
+ }
67
+ return { edge, a: kernel.vertexPosition(vs[0]), b: kernel.vertexPosition(vs[1]) };
68
+ }
69
+ }
70
+ /**
71
+ * TS port of OCCT `ShapeAnalysis_FreeBounds::ConnectEdgesToWires` — chain
72
+ * unordered edges into wires by endpoint proximity within `tol`.
73
+ *
74
+ * faijs/occt-wasm has no such binding and `makeWire(edges)` is
75
+ * `BRepBuilderAPI_MakeWire` — measured (2026-09-08) to silently drop edges on
76
+ * unordered input (68 edges → 9-edge broken wire). Endpoint pairing is O(n²)
77
+ * (n = 4·z teeth, at most a few hundred — fine).
78
+ *
79
+ * Gaps above OCCT precision (~1e-7) get a bridging line segment, matching
80
+ * upstream ConnectEdgesToWires behaviour.
81
+ *
82
+ * @param kernel raw OCCT kernel
83
+ * @param edges unordered edge set
84
+ * @param tol endpoint pairing tolerance (mm); cq uses `wire_comb_tol = 1e-2`
85
+ * @returns one or more wires, each with its edges chained head-to-tail
86
+ */
87
+ export function connectEdgesToWires(kernel, edges, tol) {
88
+ if (!Number.isFinite(tol) || tol <= 0) {
89
+ throw new Error(`connectEdgesToWires: tol must be a positive finite number, got ${String(tol)}`);
90
+ }
91
+ const pool = edges.map((e) => gearEdgeEnds(kernel, e));
92
+ const used = new Array(pool.length).fill(false);
93
+ const wires = [];
94
+ for (let start = 0; start < pool.length; start++) {
95
+ if (used[start])
96
+ continue;
97
+ used[start] = true;
98
+ const chain = [pool[start].edge];
99
+ let tail = pool[start].b;
100
+ const head = pool[start].a;
101
+ // Chain forward: find an unused edge whose endpoint touches `tail` within tol.
102
+ for (;;) {
103
+ let found = -1;
104
+ let best = Infinity;
105
+ for (let j = 0; j < pool.length; j++) {
106
+ if (used[j])
107
+ continue;
108
+ const dA = gearDist(pool[j].a, tail);
109
+ const dB = gearDist(pool[j].b, tail);
110
+ const d = Math.min(dA, dB);
111
+ if (d <= tol && d < best) {
112
+ best = d;
113
+ found = j;
114
+ }
115
+ }
116
+ if (found < 0)
117
+ break;
118
+ used[found] = true;
119
+ const e = pool[found];
120
+ const flip = gearDist(e.a, tail) <= gearDist(e.b, tail);
121
+ if (best > 1e-7) {
122
+ chain.push(kernel.makeLineEdge(tail, flip ? e.a : e.b));
123
+ }
124
+ // Edge points the wrong way: reverseShape makes a reversed copy.
125
+ chain.push(flip ? e.edge : kernel.reverseShape(e.edge));
126
+ tail = flip ? e.b : e.a;
127
+ // Back at the start ⇒ closed loop, stop.
128
+ if (gearDist(tail, head) <= tol)
129
+ break;
130
+ }
131
+ // Close the loop: bridge the tail-to-head gap too.
132
+ const endGap = gearDist(tail, head);
133
+ if (chain.length > 1 && endGap > 1e-7 && endGap <= tol) {
134
+ chain.push(kernel.makeLineEdge(tail, head));
135
+ }
136
+ wires.push(kernel.makeWire(chain));
137
+ }
138
+ return wires;
139
+ }
140
+ /**
141
+ * cq `Face.makeFromWires(outer, holes)` analogue.
142
+ *
143
+ * @param kernel raw OCCT kernel
144
+ * @param outer outer wire
145
+ * @param holes inner hole wires (may be empty)
146
+ * @returns face handle
147
+ */
148
+ export function gearFaceFromWires(kernel, outer, holes = []) {
149
+ const f = kernel.makeFace(outer);
150
+ return holes.length > 0 ? kernel.addHolesInFace(f, holes) : f;
151
+ }
152
+ /**
153
+ * cq `make_shell(faces, tol)` + `Solid.makeSolid(shell)` analogue.
154
+ *
155
+ * A non-shell sew result is NOT silently swallowed — that is a construction
156
+ * failure signal.
157
+ *
158
+ * @param kernel raw OCCT kernel
159
+ * @param faces faces to sew
160
+ * @param sewingTol sewing tolerance (mm, cq `shell_sewing_tol`)
161
+ * @returns solid built from the sewn shell
162
+ */
163
+ export function gearShellToSolid(kernel, faces, sewingTol) {
164
+ const shell = kernel.sew(faces, sewingTol);
165
+ if (!kernel.isShell(shell) && !kernel.isSolid(shell)) {
166
+ throw new Error(`gearShellToSolid: sew did not produce a shell (got ${String(kernel.getShapeType(shell))})`);
167
+ }
168
+ return kernel.makeSolid(shell);
169
+ }
170
+ /** All selectable strategies (tests iterate in this order for the deviation table). */
171
+ export const GEAR_SPLINE_FACE_STRATEGIES = [
172
+ 'grid-approx',
173
+ 'row-approx-loft',
174
+ 'row-interp-loft',
175
+ ];
176
+ /**
177
+ * Default strategy chosen by P0 measurement (2026-09-08).
178
+ *
179
+ * Per the measured table in the fai_cq_gears spike: S2's area relative
180
+ * deviation is 5.6e-7 / max sample-point distance 2.6e-6 mm — about three
181
+ * orders of magnitude better than S1/S3. See
182
+ * `docs/analysis/2026-09-08-fai-cq-gears-spike.md`.
183
+ */
184
+ export const DEFAULT_GEAR_SPLINE_FACE_STRATEGY = 'row-approx-loft';
185
+ /**
186
+ * Build a face from a row×col point grid with the given strategy.
187
+ *
188
+ * @throws kernel errors propagate verbatim (never swallowed — red line)
189
+ *
190
+ * @param kernel raw OCCT kernel
191
+ * @param grid row×col point grid
192
+ * @param strategy face building strategy
193
+ * @param options tolerance/degree options
194
+ * @returns single face handle (Face)
195
+ */
196
+ export function buildGearSplineFace(kernel, grid, strategy, options = {}) {
197
+ const tol = options.tolerance ?? 1e-2;
198
+ switch (strategy) {
199
+ case 'grid-approx':
200
+ return buildGearGridApprox(kernel, grid);
201
+ case 'row-approx-loft':
202
+ return buildGearRowLoft(kernel, grid, (row) => kernel.approximatePoints(row, tol));
203
+ case 'row-interp-loft':
204
+ return buildGearRowLoft(kernel, grid, (row) => kernel.interpolatePoints(row, false));
205
+ }
206
+ }
207
+ /** S1: fit the whole grid to one B-spline surface at once. */
208
+ function buildGearGridApprox(kernel, grid) {
209
+ const flat = [];
210
+ for (const row of grid.points)
211
+ for (const p of row)
212
+ flat.push(p);
213
+ return kernel.bsplineSurface(flat, grid.rows, grid.cols);
214
+ }
215
+ /** S2/S3: per-row curve → `loft` skinning. */
216
+ function buildGearRowLoft(kernel, grid, makeCurve) {
217
+ const wires = grid.points.map((row) => kernel.makeWire([makeCurve(row)]));
218
+ // `loft(wires, isSolid=false, ruled=false)` returns a **shell** (measured
219
+ // 2026-09-08), while cq's tooth face is a TopoDS_Face; downstream sew /
220
+ // projectPointOnFace require a Face, so extract the sole face.
221
+ return soleGearFace(kernel, kernel.loft(wires, false, false));
222
+ }
223
+ /**
224
+ * Reduce a "single-face shell" to a Face; a Face passes through unchanged.
225
+ *
226
+ * @throws when the face count is not 1 (silently taking the first would mask problems)
227
+ *
228
+ * @param kernel raw OCCT kernel
229
+ * @param shape any shape (Face / single-face shell / …)
230
+ * @returns the sole Face
231
+ */
232
+ export function soleGearFace(kernel, shape) {
233
+ if (kernel.isFace(shape))
234
+ return shape;
235
+ const faces = kernel.getSubShapes(shape, 'face');
236
+ if (faces.length !== 1) {
237
+ throw new Error(`soleGearFace: expected a single face, got ${faces.length} (shapeType=${String(kernel.getShapeType(shape))})`);
238
+ }
239
+ return faces[0];
240
+ }
241
+ /** Distance from a point to a face (`projectPointOnFace`).
242
+ *
243
+ * @param kernel raw OCCT kernel
244
+ * @param face target face
245
+ * @param p query point
246
+ * @returns euclidean distance (mm)
247
+ */
248
+ export function gearDistanceToFace(kernel, face, p) {
249
+ const q = kernel.projectPointOnFace(face, p);
250
+ return Math.hypot(q.x - p.x, q.y - p.y, q.z - p.z);
251
+ }
252
+ /**
253
+ * Distance statistics of reference sample points against a face.
254
+ *
255
+ * Parameterization-independent: samples come from the cq side's (u,v) grid, so
256
+ * the two surfaces may have different parameterizations.
257
+ *
258
+ * @param kernel raw OCCT kernel
259
+ * @param face face under test
260
+ * @param samplePoints reference sample points (3D)
261
+ * @returns max distance / RMS / sample count
262
+ */
263
+ export function gearFaceDeviation(kernel, face, samplePoints) {
264
+ let max = 0;
265
+ let sumSq = 0;
266
+ for (const p of samplePoints) {
267
+ const d = gearDistanceToFace(kernel, face, p);
268
+ if (d > max)
269
+ max = d;
270
+ sumSq += d * d;
271
+ }
272
+ return { max, rms: Math.sqrt(sumSq / samplePoints.length), n: samplePoints.length };
273
+ }
274
+ //# sourceMappingURL=gears.js.map