@faicad/faijs-sketch 0.18.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.
- package/LICENSE +28 -0
- package/NOTICE +28 -0
- package/dist/bspline.d.ts +58 -0
- package/dist/bspline.d.ts.map +1 -0
- package/dist/bspline.js +93 -0
- package/dist/bspline.js.map +1 -0
- package/dist/canonical.d.ts +205 -0
- package/dist/canonical.d.ts.map +1 -0
- package/dist/canonical.js +12 -0
- package/dist/canonical.js.map +1 -0
- package/dist/contour.d.ts +43 -0
- package/dist/contour.d.ts.map +1 -0
- package/dist/contour.js +132 -0
- package/dist/contour.js.map +1 -0
- package/dist/faces.d.ts +28 -0
- package/dist/faces.d.ts.map +1 -0
- package/dist/faces.js +46 -0
- package/dist/faces.js.map +1 -0
- package/dist/fcstd-types.d.ts +177 -0
- package/dist/fcstd-types.d.ts.map +1 -0
- package/dist/fcstd-types.js +67 -0
- package/dist/fcstd-types.js.map +1 -0
- package/dist/index.d.ts +36 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/namespace.d.ts +51 -0
- package/dist/namespace.d.ts.map +1 -0
- package/dist/namespace.js +67 -0
- package/dist/namespace.js.map +1 -0
- package/dist/node.d.ts +14 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +34 -0
- package/dist/node.js.map +1 -0
- package/dist/op.d.ts +67 -0
- package/dist/op.d.ts.map +1 -0
- package/dist/op.js +104 -0
- package/dist/op.js.map +1 -0
- package/dist/planegcs-backend.d.ts +46 -0
- package/dist/planegcs-backend.d.ts.map +1 -0
- package/dist/planegcs-backend.js +696 -0
- package/dist/planegcs-backend.js.map +1 -0
- package/dist/project.d.ts +115 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +329 -0
- package/dist/project.js.map +1 -0
- package/dist/solve.d.ts +21 -0
- package/dist/solve.d.ts.map +1 -0
- package/dist/solve.js +102 -0
- package/dist/solve.js.map +1 -0
- package/dist/solver.d.ts +63 -0
- package/dist/solver.d.ts.map +1 -0
- package/dist/solver.js +28 -0
- package/dist/solver.js.map +1 -0
- package/dist/verify.d.ts +53 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +71 -0
- package/dist/verify.js.map +1 -0
- package/package.json +65 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Faicad contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
The brepjs-derived sources (compat facade) in `src/api/` retain their
|
|
26
|
+
Apache-2.0 attribution, which now lives in the standalone
|
|
27
|
+
`@faicad/faijs-brepjs` package (see `@faicad/faijs-brepjs` NOTICE and
|
|
28
|
+
LICENSE). Apache-2.0 text: https://www.apache.org/licenses/LICENSE-2.0
|
package/NOTICE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
faijs — @faicad/faijs NOTICE
|
|
2
|
+
============================
|
|
3
|
+
|
|
4
|
+
This product's brepjs-compat facade contains code ported from the
|
|
5
|
+
`brepjs` project (https://github.com/OpenCascade/brepjs, licensed under
|
|
6
|
+
the Apache License 2.0, locked at upstream commit 8685273a).
|
|
7
|
+
The ported sources now live in the standalone `@faicad/faijs-brepjs`
|
|
8
|
+
package; its NOTICE and LICENSE carry the full attribution.
|
|
9
|
+
|
|
10
|
+
Full Apache-2.0 text: https://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
|
|
12
|
+
Runtime dependencies (not distributed inside this package)
|
|
13
|
+
----------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
The following optional/peer dependencies are loaded by the host at
|
|
16
|
+
runtime and are NOT distributed inside this tarball. They carry their
|
|
17
|
+
own licenses:
|
|
18
|
+
|
|
19
|
+
- `occt-wasm` — JS glue: MIT OR Apache-2.0; the compiled WASM output is
|
|
20
|
+
LGPL-2.1-only (inherited from OCCT, https://dev.opencascade.org).
|
|
21
|
+
The LGPL replacement obligation is satisfied because the host loads
|
|
22
|
+
the `.wasm` from a URL that can be overridden
|
|
23
|
+
(`OcctKernel.init({ wasm: '...' })`).
|
|
24
|
+
- `manifold-3d` — Apache-2.0.
|
|
25
|
+
|
|
26
|
+
`@salusoft89/planegcs` (LGPL-2.0-or-later) is used only by the
|
|
27
|
+
non-shipped `src/fcstd` module and is intentionally not a dependency of
|
|
28
|
+
the published package.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P4 — B-spline curve evaluation for FCStd sketch geometry
|
|
3
|
+
* (Part::GeomBSplineCurve → BSplineCurve element: Poles/Knots/Degree).
|
|
4
|
+
*
|
|
5
|
+
* Contours only need chained endpoints, but a spline interior must be
|
|
6
|
+
* faithfully flattened into the 2D contour it feeds (P4: no-silent-loss —
|
|
7
|
+
* a pole-only bounding approximation would distort Pad/Pocket profiles).
|
|
8
|
+
* Cox–de Boor evaluation over the clamped knot vector is exact at the
|
|
9
|
+
* sampled parameters; sampling density is chord-driven (see sampleBSpline).
|
|
10
|
+
*/
|
|
11
|
+
/** One control pole (2D sketch-local; Z is 0 for planar sketches). */
|
|
12
|
+
export interface BSPole {
|
|
13
|
+
x: number;
|
|
14
|
+
y: number;
|
|
15
|
+
}
|
|
16
|
+
/** Parsed BSplineCurve payload (freeCAD stores knots with multiplicity). */
|
|
17
|
+
export interface BSplineCurveData {
|
|
18
|
+
poles: BSPole[];
|
|
19
|
+
/** expanded knot vector (length === poles + degree + 1 for a clamped spline) */
|
|
20
|
+
knots: number[];
|
|
21
|
+
degree: number;
|
|
22
|
+
periodic: boolean;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Evaluate the B-spline at parameter t via Cox–de Boor recursion.
|
|
26
|
+
* Outside the valid domain [knots[degree], knots[n]] the value is clamped.
|
|
27
|
+
*
|
|
28
|
+
* @param d - the parsed BSplineCurve payload (poles/knots/degree).
|
|
29
|
+
* @param t - the parameter to evaluate (clamped to the valid domain).
|
|
30
|
+
* @returns the evaluated pole (2D point) on the spline.
|
|
31
|
+
*/
|
|
32
|
+
export declare function evalBSpline(d: BSplineCurveData, t: number): BSPole;
|
|
33
|
+
/**
|
|
34
|
+
* Sample the spline into a polyline. `min` samples per span, at least the
|
|
35
|
+
* pole count (a degree-3 spline needs ≥ poles points to show its shape).
|
|
36
|
+
* Endpoints are exact (clamped knot vector).
|
|
37
|
+
*
|
|
38
|
+
* @param d - the parsed BSplineCurve payload (poles/knots/degree).
|
|
39
|
+
* @param perSpan - minimum samples per knot span (default 6).
|
|
40
|
+
* @returns the sampled polyline points (first/last are the exact endpoints).
|
|
41
|
+
*/
|
|
42
|
+
export declare function sampleBSpline(d: BSplineCurveData, perSpan?: number): BSPole[];
|
|
43
|
+
/**
|
|
44
|
+
* Flatten the spline into consecutive straight contour segments (P4: the
|
|
45
|
+
* contour chain only understands line/arc; a sampled polyline preserves the
|
|
46
|
+
* profile within chord tolerance).
|
|
47
|
+
*
|
|
48
|
+
* @param d - the parsed BSplineCurve payload (poles/knots/degree).
|
|
49
|
+
* @param perSpan - minimum samples per knot span (passed to sampleBSpline).
|
|
50
|
+
* @returns the consecutive line segments approximating the spline.
|
|
51
|
+
*/
|
|
52
|
+
export declare function bsplineToSegments(d: BSplineCurveData, perSpan?: number): {
|
|
53
|
+
x1: number;
|
|
54
|
+
y1: number;
|
|
55
|
+
x2: number;
|
|
56
|
+
y2: number;
|
|
57
|
+
}[];
|
|
58
|
+
//# sourceMappingURL=bspline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bspline.d.ts","sourceRoot":"","sources":["../src/bspline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,sEAAsE;AACtE,MAAM,WAAW,MAAM;IACrB,CAAC,EAAE,MAAM,CAAA;IACT,CAAC,EAAE,MAAM,CAAA;CACV;AAED,4EAA4E;AAC5E,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,gFAAgF;IAChF,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CA6BlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,SAAI,GAAG,MAAM,EAAE,CAcxE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,gBAAgB,EAAE,OAAO,SAAI,GAAG;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,CAOxH"}
|
package/dist/bspline.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* P4 — B-spline curve evaluation for FCStd sketch geometry
|
|
3
|
+
* (Part::GeomBSplineCurve → BSplineCurve element: Poles/Knots/Degree).
|
|
4
|
+
*
|
|
5
|
+
* Contours only need chained endpoints, but a spline interior must be
|
|
6
|
+
* faithfully flattened into the 2D contour it feeds (P4: no-silent-loss —
|
|
7
|
+
* a pole-only bounding approximation would distort Pad/Pocket profiles).
|
|
8
|
+
* Cox–de Boor evaluation over the clamped knot vector is exact at the
|
|
9
|
+
* sampled parameters; sampling density is chord-driven (see sampleBSpline).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Evaluate the B-spline at parameter t via Cox–de Boor recursion.
|
|
13
|
+
* Outside the valid domain [knots[degree], knots[n]] the value is clamped.
|
|
14
|
+
*
|
|
15
|
+
* @param d - the parsed BSplineCurve payload (poles/knots/degree).
|
|
16
|
+
* @param t - the parameter to evaluate (clamped to the valid domain).
|
|
17
|
+
* @returns the evaluated pole (2D point) on the spline.
|
|
18
|
+
*/
|
|
19
|
+
export function evalBSpline(d, t) {
|
|
20
|
+
const { poles, knots, degree: p } = d;
|
|
21
|
+
const n = knots.length - p - 1; // last span index
|
|
22
|
+
const lo = knots[p];
|
|
23
|
+
const hi = knots[n];
|
|
24
|
+
const u = Math.min(Math.max(t, lo), hi);
|
|
25
|
+
// find span k such that knots[k] <= u < knots[k+1] (clamped: hi lands in last span)
|
|
26
|
+
let k = p;
|
|
27
|
+
for (let i = p; i < n; i++) {
|
|
28
|
+
if (u >= knots[i] && u < knots[i + 1]) {
|
|
29
|
+
k = i;
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// de Boor: d[j] = poles[j + k - p], j = 0..p; lerp over levels
|
|
34
|
+
const dd = [];
|
|
35
|
+
for (let j = 0; j <= p; j++)
|
|
36
|
+
dd.push({ ...poles[j + k - p] });
|
|
37
|
+
for (let r = 1; r <= p; r++) {
|
|
38
|
+
for (let j = p; j >= r; j--) {
|
|
39
|
+
const i = j + k - p;
|
|
40
|
+
const denom = knots[i + p - r + 1] - knots[i];
|
|
41
|
+
const a = denom === 0 ? 0 : (u - knots[i]) / denom;
|
|
42
|
+
dd[j] = {
|
|
43
|
+
x: (1 - a) * dd[j - 1].x + a * dd[j].x,
|
|
44
|
+
y: (1 - a) * dd[j - 1].y + a * dd[j].y,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return dd[p];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Sample the spline into a polyline. `min` samples per span, at least the
|
|
52
|
+
* pole count (a degree-3 spline needs ≥ poles points to show its shape).
|
|
53
|
+
* Endpoints are exact (clamped knot vector).
|
|
54
|
+
*
|
|
55
|
+
* @param d - the parsed BSplineCurve payload (poles/knots/degree).
|
|
56
|
+
* @param perSpan - minimum samples per knot span (default 6).
|
|
57
|
+
* @returns the sampled polyline points (first/last are the exact endpoints).
|
|
58
|
+
*/
|
|
59
|
+
export function sampleBSpline(d, perSpan = 6) {
|
|
60
|
+
const { poles, knots, degree: p } = d;
|
|
61
|
+
if (poles.length === 0)
|
|
62
|
+
return [];
|
|
63
|
+
if (knots.length < p + 2)
|
|
64
|
+
return [...poles]; // degenerate: treat as control polygon
|
|
65
|
+
const lo = knots[p];
|
|
66
|
+
const hi = knots[knots.length - p - 1];
|
|
67
|
+
const spans = Math.max(1, knots.length - 2 * p - 1);
|
|
68
|
+
const count = Math.max(poles.length, spans * perSpan + 1);
|
|
69
|
+
const out = [];
|
|
70
|
+
for (let i = 0; i < count; i++) {
|
|
71
|
+
const t = lo + ((hi - lo) * i) / (count - 1);
|
|
72
|
+
out.push(evalBSpline(d, t));
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Flatten the spline into consecutive straight contour segments (P4: the
|
|
78
|
+
* contour chain only understands line/arc; a sampled polyline preserves the
|
|
79
|
+
* profile within chord tolerance).
|
|
80
|
+
*
|
|
81
|
+
* @param d - the parsed BSplineCurve payload (poles/knots/degree).
|
|
82
|
+
* @param perSpan - minimum samples per knot span (passed to sampleBSpline).
|
|
83
|
+
* @returns the consecutive line segments approximating the spline.
|
|
84
|
+
*/
|
|
85
|
+
export function bsplineToSegments(d, perSpan = 6) {
|
|
86
|
+
const pts = sampleBSpline(d, perSpan);
|
|
87
|
+
const segs = [];
|
|
88
|
+
for (let i = 1; i < pts.length; i++) {
|
|
89
|
+
segs.push({ x1: pts[i - 1].x, y1: pts[i - 1].y, x2: pts[i].x, y2: pts[i].y });
|
|
90
|
+
}
|
|
91
|
+
return segs;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=bspline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bspline.js","sourceRoot":"","sources":["../src/bspline.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * P4 — B-spline curve evaluation for FCStd sketch geometry\n * (Part::GeomBSplineCurve → BSplineCurve element: Poles/Knots/Degree).\n *\n * Contours only need chained endpoints, but a spline interior must be\n * faithfully flattened into the 2D contour it feeds (P4: no-silent-loss —\n * a pole-only bounding approximation would distort Pad/Pocket profiles).\n * Cox–de Boor evaluation over the clamped knot vector is exact at the\n * sampled parameters; sampling density is chord-driven (see sampleBSpline).\n */\n\n/** One control pole (2D sketch-local; Z is 0 for planar sketches). */\nexport interface BSPole {\n x: number\n y: number\n}\n\n/** Parsed BSplineCurve payload (freeCAD stores knots with multiplicity). */\nexport interface BSplineCurveData {\n poles: BSPole[]\n /** expanded knot vector (length === poles + degree + 1 for a clamped spline) */\n knots: number[]\n degree: number\n periodic: boolean\n}\n\n/**\n * Evaluate the B-spline at parameter t via Cox–de Boor recursion.\n * Outside the valid domain [knots[degree], knots[n]] the value is clamped.\n *\n * @param d - the parsed BSplineCurve payload (poles/knots/degree).\n * @param t - the parameter to evaluate (clamped to the valid domain).\n * @returns the evaluated pole (2D point) on the spline.\n */\nexport function evalBSpline(d: BSplineCurveData, t: number): BSPole {\n const { poles, knots, degree: p } = d;\n const n = knots.length - p - 1; // last span index\n const lo = knots[p]!;\n const hi = knots[n]!;\n const u = Math.min(Math.max(t, lo), hi);\n // find span k such that knots[k] <= u < knots[k+1] (clamped: hi lands in last span)\n let k = p;\n for (let i = p; i < n; i++) {\n if (u >= knots[i]! && u < knots[i + 1]!) {\n k = i;\n break;\n }\n }\n // de Boor: d[j] = poles[j + k - p], j = 0..p; lerp over levels\n const dd: BSPole[] = [];\n for (let j = 0; j <= p; j++) dd.push({ ...poles[j + k - p]! });\n for (let r = 1; r <= p; r++) {\n for (let j = p; j >= r; j--) {\n const i = j + k - p;\n const denom = knots[i + p - r + 1]! - knots[i]!;\n const a = denom === 0 ? 0 : (u - knots[i]!) / denom;\n dd[j] = {\n x: (1 - a) * dd[j - 1]!.x + a * dd[j]!.x,\n y: (1 - a) * dd[j - 1]!.y + a * dd[j]!.y,\n };\n }\n }\n return dd[p]!;\n}\n\n/**\n * Sample the spline into a polyline. `min` samples per span, at least the\n * pole count (a degree-3 spline needs ≥ poles points to show its shape).\n * Endpoints are exact (clamped knot vector).\n *\n * @param d - the parsed BSplineCurve payload (poles/knots/degree).\n * @param perSpan - minimum samples per knot span (default 6).\n * @returns the sampled polyline points (first/last are the exact endpoints).\n */\nexport function sampleBSpline(d: BSplineCurveData, perSpan = 6): BSPole[] {\n const { poles, knots, degree: p } = d;\n if (poles.length === 0) return [];\n if (knots.length < p + 2) return [...poles]; // degenerate: treat as control polygon\n const lo = knots[p]!;\n const hi = knots[knots.length - p - 1]!;\n const spans = Math.max(1, knots.length - 2 * p - 1);\n const count = Math.max(poles.length, spans * perSpan + 1);\n const out: BSPole[] = [];\n for (let i = 0; i < count; i++) {\n const t = lo + ((hi - lo) * i) / (count - 1);\n out.push(evalBSpline(d, t));\n }\n return out;\n}\n\n/**\n * Flatten the spline into consecutive straight contour segments (P4: the\n * contour chain only understands line/arc; a sampled polyline preserves the\n * profile within chord tolerance).\n *\n * @param d - the parsed BSplineCurve payload (poles/knots/degree).\n * @param perSpan - minimum samples per knot span (passed to sampleBSpline).\n * @returns the consecutive line segments approximating the spline.\n */\nexport function bsplineToSegments(d: BSplineCurveData, perSpan = 6): { x1: number; y1: number; x2: number; y2: number }[] {\n const pts = sampleBSpline(d, perSpan);\n const segs: { x1: number; y1: number; x2: number; y2: number }[] = [];\n for (let i = 1; i < pts.length; i++) {\n segs.push({ x1: pts[i - 1]!.x, y1: pts[i - 1]!.y, x2: pts[i]!.x, y2: pts[i]!.y });\n }\n return segs;\n}\n"]}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canonical — the faijs canonical sketch model (§4 of the sketch library plan).
|
|
3
|
+
*
|
|
4
|
+
* One model, projected outward: the FreeCAD side (`fcstd-types.ts`) and the
|
|
5
|
+
* CadQuery side (`./project.ts`) are projections of this shape, not parallel
|
|
6
|
+
* implementations. Geometry declarations carry coordinates that ARE the solve
|
|
7
|
+
* initial guess; constraints pull them exact.
|
|
8
|
+
*
|
|
9
|
+
* Units follow the project contract: lengths in mm, angles in degrees.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* A position on an element. `number` is the 0..1 parametric position (0 =
|
|
13
|
+
* start, 1 = end, 0.5 = arc/line midpoint). `'center'` is the circle/arc/ellipse
|
|
14
|
+
* centre — semantically distinct from `'mid'`.
|
|
15
|
+
*/
|
|
16
|
+
export type At = number | 'start' | 'end' | 'mid' | 'center';
|
|
17
|
+
/** An element reference: `tag` (named) or `index` (array position); `at` omitted = whole element. */
|
|
18
|
+
export type Ref = {
|
|
19
|
+
tag: string;
|
|
20
|
+
at?: At;
|
|
21
|
+
} | {
|
|
22
|
+
index: number;
|
|
23
|
+
at?: At;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Canonical sketch geometry. First release supports `line` + `circle` + `arc`;
|
|
27
|
+
* `point` / `ellipse` / `bspline` are reserved schema (a hit reports
|
|
28
|
+
* `E_SKETCHC_UNSUPPORTED_GEOM`).
|
|
29
|
+
*/
|
|
30
|
+
export type SketchGeom = {
|
|
31
|
+
tag?: string;
|
|
32
|
+
kind: 'line';
|
|
33
|
+
x1: number;
|
|
34
|
+
y1: number;
|
|
35
|
+
x2: number;
|
|
36
|
+
y2: number;
|
|
37
|
+
} | {
|
|
38
|
+
tag?: string;
|
|
39
|
+
kind: 'circle';
|
|
40
|
+
cx: number;
|
|
41
|
+
cy: number;
|
|
42
|
+
r: number;
|
|
43
|
+
} | {
|
|
44
|
+
tag?: string;
|
|
45
|
+
kind: 'arc';
|
|
46
|
+
cx: number;
|
|
47
|
+
cy: number;
|
|
48
|
+
r: number;
|
|
49
|
+
a0: number;
|
|
50
|
+
a1: number;
|
|
51
|
+
ccw?: boolean;
|
|
52
|
+
} | {
|
|
53
|
+
tag?: string;
|
|
54
|
+
kind: 'point';
|
|
55
|
+
x: number;
|
|
56
|
+
y: number;
|
|
57
|
+
} | {
|
|
58
|
+
tag?: string;
|
|
59
|
+
kind: 'ellipse';
|
|
60
|
+
cx: number;
|
|
61
|
+
cy: number;
|
|
62
|
+
rx: number;
|
|
63
|
+
ry: number;
|
|
64
|
+
a0?: number;
|
|
65
|
+
a1?: number;
|
|
66
|
+
/** major-axis rotation, radians */
|
|
67
|
+
angle?: number;
|
|
68
|
+
} | {
|
|
69
|
+
tag?: string;
|
|
70
|
+
kind: 'bspline';
|
|
71
|
+
poles: {
|
|
72
|
+
x: number;
|
|
73
|
+
y: number;
|
|
74
|
+
}[];
|
|
75
|
+
knots: number[];
|
|
76
|
+
degree: number;
|
|
77
|
+
periodic?: boolean;
|
|
78
|
+
};
|
|
79
|
+
/** Canonical constraint kinds (string enums; integer enums only appear at projection boundaries). */
|
|
80
|
+
export type SketchConstraint = {
|
|
81
|
+
kind: 'fixed';
|
|
82
|
+
of: Ref;
|
|
83
|
+
} | {
|
|
84
|
+
kind: 'coincident';
|
|
85
|
+
a: Ref;
|
|
86
|
+
b: Ref;
|
|
87
|
+
} | {
|
|
88
|
+
kind: 'horizontal';
|
|
89
|
+
of: Ref;
|
|
90
|
+
} | {
|
|
91
|
+
kind: 'vertical';
|
|
92
|
+
of: Ref;
|
|
93
|
+
} | {
|
|
94
|
+
kind: 'parallel';
|
|
95
|
+
a: Ref;
|
|
96
|
+
b: Ref;
|
|
97
|
+
} | {
|
|
98
|
+
kind: 'perpendicular';
|
|
99
|
+
a: Ref;
|
|
100
|
+
b: Ref;
|
|
101
|
+
} | {
|
|
102
|
+
kind: 'tangent';
|
|
103
|
+
a: Ref;
|
|
104
|
+
b: Ref;
|
|
105
|
+
} | {
|
|
106
|
+
kind: 'distance';
|
|
107
|
+
a: Ref;
|
|
108
|
+
b: Ref;
|
|
109
|
+
value: number;
|
|
110
|
+
} | {
|
|
111
|
+
kind: 'distanceX';
|
|
112
|
+
a: Ref;
|
|
113
|
+
b: Ref;
|
|
114
|
+
value: number;
|
|
115
|
+
} | {
|
|
116
|
+
kind: 'distanceY';
|
|
117
|
+
a: Ref;
|
|
118
|
+
b: Ref;
|
|
119
|
+
value: number;
|
|
120
|
+
} | {
|
|
121
|
+
kind: 'length';
|
|
122
|
+
of: Ref;
|
|
123
|
+
value: number;
|
|
124
|
+
} | {
|
|
125
|
+
kind: 'angle';
|
|
126
|
+
a: Ref;
|
|
127
|
+
b: Ref;
|
|
128
|
+
value: number;
|
|
129
|
+
} | {
|
|
130
|
+
kind: 'orientation';
|
|
131
|
+
of: Ref;
|
|
132
|
+
dir: [number, number];
|
|
133
|
+
} | {
|
|
134
|
+
kind: 'radius';
|
|
135
|
+
of: Ref;
|
|
136
|
+
value: number;
|
|
137
|
+
} | {
|
|
138
|
+
kind: 'diameter';
|
|
139
|
+
of: Ref;
|
|
140
|
+
value: number;
|
|
141
|
+
} | {
|
|
142
|
+
kind: 'arcAngle';
|
|
143
|
+
of: Ref;
|
|
144
|
+
value: number;
|
|
145
|
+
} | {
|
|
146
|
+
kind: 'equal';
|
|
147
|
+
a: Ref;
|
|
148
|
+
b: Ref;
|
|
149
|
+
} | {
|
|
150
|
+
kind: 'pointOnObject';
|
|
151
|
+
p: Ref;
|
|
152
|
+
on: Ref;
|
|
153
|
+
} | {
|
|
154
|
+
kind: 'symmetric';
|
|
155
|
+
p1: Ref;
|
|
156
|
+
p2: Ref;
|
|
157
|
+
about: Ref;
|
|
158
|
+
};
|
|
159
|
+
/** The `kind` string of a canonical constraint. */
|
|
160
|
+
export type SketchConstraintKind = SketchConstraint['kind'];
|
|
161
|
+
/** A canonical sketch: geometry declarations plus constraints. */
|
|
162
|
+
export interface SketchInput {
|
|
163
|
+
geoms: SketchGeom[];
|
|
164
|
+
constraints?: SketchConstraint[];
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Solve status (§5): under- and over-constraint are allowed, but never silent.
|
|
168
|
+
*/
|
|
169
|
+
export type SolveStatus =
|
|
170
|
+
/** all constraints satisfied, zero remaining DoF */
|
|
171
|
+
'solved'
|
|
172
|
+
/** free DoF remain; solved to a minimal-displacement solution */
|
|
173
|
+
| 'underconstrained'
|
|
174
|
+
/** redundant constraints were dropped; solved */
|
|
175
|
+
| 'redundant'
|
|
176
|
+
/** constraints conflict; best-effort least-squares solution returned */
|
|
177
|
+
| 'conflicting'
|
|
178
|
+
/** solver failed numerically (not an under/over-constraint cause) */
|
|
179
|
+
| 'failed';
|
|
180
|
+
/**
|
|
181
|
+
* Result of one canonical solve: solved geometry plus explicit diagnostics.
|
|
182
|
+
*
|
|
183
|
+
* `converged` is false only for `failed` — under/redundant/conflicting all
|
|
184
|
+
* produce geometry (allowed ≠ silent: the status and diagnostics carry the
|
|
185
|
+
* story).
|
|
186
|
+
*/
|
|
187
|
+
export interface SolveOutcome {
|
|
188
|
+
/** solved geometry, same order/index as input */
|
|
189
|
+
geoms: SketchGeom[];
|
|
190
|
+
/** true unless `status === 'failed'` */
|
|
191
|
+
converged: boolean;
|
|
192
|
+
/** explicit solve status (§5) */
|
|
193
|
+
status: SolveStatus;
|
|
194
|
+
/** remaining degrees of freedom when known (-1 = not reported) */
|
|
195
|
+
dof: number;
|
|
196
|
+
/** constraint indices the solver flagged (conflicting/redundant) */
|
|
197
|
+
problemConstraints: number[];
|
|
198
|
+
/** constraint indices dropped because they could not be projected/pushed */
|
|
199
|
+
droppedConstraints: number[];
|
|
200
|
+
/** per-problem-constraint residual magnitude when available */
|
|
201
|
+
residuals?: number[];
|
|
202
|
+
/** human-readable detail for a non-solved status */
|
|
203
|
+
reason?: string;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=canonical.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.d.ts","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* canonical — the faijs canonical sketch model (§4 of the sketch library plan).
|
|
3
|
+
*
|
|
4
|
+
* One model, projected outward: the FreeCAD side (`fcstd-types.ts`) and the
|
|
5
|
+
* CadQuery side (`./project.ts`) are projections of this shape, not parallel
|
|
6
|
+
* implementations. Geometry declarations carry coordinates that ARE the solve
|
|
7
|
+
* initial guess; constraints pull them exact.
|
|
8
|
+
*
|
|
9
|
+
* Units follow the project contract: lengths in mm, angles in degrees.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG","sourcesContent":["/**\n * canonical — the faijs canonical sketch model (§4 of the sketch library plan).\n *\n * One model, projected outward: the FreeCAD side (`fcstd-types.ts`) and the\n * CadQuery side (`./project.ts`) are projections of this shape, not parallel\n * implementations. Geometry declarations carry coordinates that ARE the solve\n * initial guess; constraints pull them exact.\n *\n * Units follow the project contract: lengths in mm, angles in degrees.\n */\n\n/**\n * A position on an element. `number` is the 0..1 parametric position (0 =\n * start, 1 = end, 0.5 = arc/line midpoint). `'center'` is the circle/arc/ellipse\n * centre — semantically distinct from `'mid'`.\n */\nexport type At = number | 'start' | 'end' | 'mid' | 'center'\n\n/** An element reference: `tag` (named) or `index` (array position); `at` omitted = whole element. */\nexport type Ref = { tag: string; at?: At } | { index: number; at?: At }\n\n/**\n * Canonical sketch geometry. First release supports `line` + `circle` + `arc`;\n * `point` / `ellipse` / `bspline` are reserved schema (a hit reports\n * `E_SKETCHC_UNSUPPORTED_GEOM`).\n */\nexport type SketchGeom =\n | { tag?: string; kind: 'line'; x1: number; y1: number; x2: number; y2: number }\n | { tag?: string; kind: 'circle'; cx: number; cy: number; r: number }\n | { tag?: string; kind: 'arc'; cx: number; cy: number; r: number; a0: number; a1: number; ccw?: boolean }\n | { tag?: string; kind: 'point'; x: number; y: number }\n | {\n tag?: string\n kind: 'ellipse'\n cx: number\n cy: number\n rx: number\n ry: number\n a0?: number\n a1?: number\n /** major-axis rotation, radians */\n angle?: number\n }\n | {\n tag?: string\n kind: 'bspline'\n poles: { x: number; y: number }[]\n knots: number[]\n degree: number\n periodic?: boolean\n }\n\n/** Canonical constraint kinds (string enums; integer enums only appear at projection boundaries). */\nexport type SketchConstraint =\n | { kind: 'fixed'; of: Ref }\n | { kind: 'coincident'; a: Ref; b: Ref }\n | { kind: 'horizontal'; of: Ref }\n | { kind: 'vertical'; of: Ref }\n | { kind: 'parallel'; a: Ref; b: Ref }\n | { kind: 'perpendicular'; a: Ref; b: Ref }\n | { kind: 'tangent'; a: Ref; b: Ref }\n | { kind: 'distance'; a: Ref; b: Ref; value: number }\n | { kind: 'distanceX'; a: Ref; b: Ref; value: number }\n | { kind: 'distanceY'; a: Ref; b: Ref; value: number }\n | { kind: 'length'; of: Ref; value: number }\n | { kind: 'angle'; a: Ref; b: Ref; value: number }\n | { kind: 'orientation'; of: Ref; dir: [number, number] }\n | { kind: 'radius'; of: Ref; value: number }\n | { kind: 'diameter'; of: Ref; value: number }\n | { kind: 'arcAngle'; of: Ref; value: number }\n | { kind: 'equal'; a: Ref; b: Ref }\n | { kind: 'pointOnObject'; p: Ref; on: Ref }\n | { kind: 'symmetric'; p1: Ref; p2: Ref; about: Ref }\n\n/** The `kind` string of a canonical constraint. */\nexport type SketchConstraintKind = SketchConstraint['kind']\n\n/** A canonical sketch: geometry declarations plus constraints. */\nexport interface SketchInput {\n geoms: SketchGeom[]\n constraints?: SketchConstraint[]\n}\n\n/**\n * Solve status (§5): under- and over-constraint are allowed, but never silent.\n */\nexport type SolveStatus =\n /** all constraints satisfied, zero remaining DoF */\n | 'solved'\n /** free DoF remain; solved to a minimal-displacement solution */\n | 'underconstrained'\n /** redundant constraints were dropped; solved */\n | 'redundant'\n /** constraints conflict; best-effort least-squares solution returned */\n | 'conflicting'\n /** solver failed numerically (not an under/over-constraint cause) */\n | 'failed'\n\n/**\n * Result of one canonical solve: solved geometry plus explicit diagnostics.\n *\n * `converged` is false only for `failed` — under/redundant/conflicting all\n * produce geometry (allowed ≠ silent: the status and diagnostics carry the\n * story).\n */\nexport interface SolveOutcome {\n /** solved geometry, same order/index as input */\n geoms: SketchGeom[]\n /** true unless `status === 'failed'` */\n converged: boolean\n /** explicit solve status (§5) */\n status: SolveStatus\n /** remaining degrees of freedom when known (-1 = not reported) */\n dof: number\n /** constraint indices the solver flagged (conflicting/redundant) */\n problemConstraints: number[]\n /** constraint indices dropped because they could not be projected/pushed */\n droppedConstraints: number[]\n /** per-problem-constraint residual magnitude when available */\n residuals?: number[]\n /** human-readable detail for a non-solved status */\n reason?: string\n}"]}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* M3.6 — contour extraction: solved sketch geometry → closed loops → 2D
|
|
3
|
+
* contour (R6: pure data, no OCCT dependency; the cad-face wiring that turns
|
|
4
|
+
* contours into Blueprint/extrude inputs happens in M4.6/M5).
|
|
5
|
+
*
|
|
6
|
+
* Extraction: collect endpoints of every non-construction geometry, chain
|
|
7
|
+
* segments sharing endpoints (tolerance-based), keep closed loops.
|
|
8
|
+
*/
|
|
9
|
+
import type { FcstdSketchGeom } from './fcstd-types.js';
|
|
10
|
+
/** One segment of a 2D contour: either a straight line or a circular arc (planar, sketch-local coordinates). */
|
|
11
|
+
export type ContourSeg = {
|
|
12
|
+
kind: 'line';
|
|
13
|
+
x1: number;
|
|
14
|
+
y1: number;
|
|
15
|
+
x2: number;
|
|
16
|
+
y2: number;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'arc';
|
|
19
|
+
cx: number;
|
|
20
|
+
cy: number;
|
|
21
|
+
radius: number;
|
|
22
|
+
startAngle: number;
|
|
23
|
+
endAngle: number;
|
|
24
|
+
ccw: boolean;
|
|
25
|
+
x1: number;
|
|
26
|
+
y1: number;
|
|
27
|
+
x2: number;
|
|
28
|
+
y2: number;
|
|
29
|
+
};
|
|
30
|
+
/** A chained run of segments; `closed` when the chain's ends meet (R6: pure data, no OCCT dependency). */
|
|
31
|
+
export interface Contour {
|
|
32
|
+
/** chained segments in traversal order */
|
|
33
|
+
segments: ContourSeg[];
|
|
34
|
+
/** true when the chain's endpoints meet within JOIN_TOL */
|
|
35
|
+
closed: boolean;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Extract closed contours (chains of segments whose endpoints meet).
|
|
39
|
+
* @param geoms solved sketch geometry to chain (non-construction segments only)
|
|
40
|
+
* @returns closed loops plus self-closed circles (open chains are dropped)
|
|
41
|
+
*/
|
|
42
|
+
export declare function extractContours(geoms: FcstdSketchGeom[]): Contour[];
|
|
43
|
+
//# sourceMappingURL=contour.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contour.d.ts","sourceRoot":"","sources":["../src/contour.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxD,gHAAgH;AAChH,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAChE;IACE,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,OAAO,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;CACZ,CAAC;AAEN,0GAA0G;AAC1G,MAAM,WAAW,OAAO;IACtB,0CAA0C;IAC1C,QAAQ,EAAE,UAAU,EAAE,CAAC;IACvB,2DAA2D;IAC3D,MAAM,EAAE,OAAO,CAAC;CACjB;AAwCD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,OAAO,EAAE,CA8EnE"}
|
package/dist/contour.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { bsplineToSegments } from './bspline.js';
|
|
2
|
+
const JOIN_TOL = 1e-7;
|
|
3
|
+
function segEnds(g) {
|
|
4
|
+
switch (g.kind) {
|
|
5
|
+
case 'line':
|
|
6
|
+
return [
|
|
7
|
+
{ kind: 'line', x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2 },
|
|
8
|
+
{ x: g.x1, y: g.y1 },
|
|
9
|
+
{ x: g.x2, y: g.y2 },
|
|
10
|
+
];
|
|
11
|
+
case 'arc':
|
|
12
|
+
return [
|
|
13
|
+
{
|
|
14
|
+
kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,
|
|
15
|
+
startAngle: g.startAngle, endAngle: g.endAngle, ccw: true,
|
|
16
|
+
x1: g.x1, y1: g.y1, x2: g.x2, y2: g.y2,
|
|
17
|
+
},
|
|
18
|
+
{ x: g.x1, y: g.y1 },
|
|
19
|
+
{ x: g.x2, y: g.y2 },
|
|
20
|
+
];
|
|
21
|
+
case 'bspline': {
|
|
22
|
+
// P4: flatten the spline into a sampled polyline. Only the FIRST
|
|
23
|
+
// sub-segment is returned here so the pool gets exactly one entry per
|
|
24
|
+
// geometry (no index duplication); extractContours splices the full
|
|
25
|
+
// polyline run in its place (see expandBSpline below).
|
|
26
|
+
const segs = bsplineToSegments({
|
|
27
|
+
poles: g.poles, knots: g.knots, degree: g.degree, periodic: g.periodic,
|
|
28
|
+
});
|
|
29
|
+
const first = segs[0];
|
|
30
|
+
const last = segs[segs.length - 1];
|
|
31
|
+
if (!first || !last)
|
|
32
|
+
return undefined;
|
|
33
|
+
return [{ kind: 'line', x1: first.x1, y1: first.y1, x2: last.x2, y2: last.y2 }, { x: first.x1, y: first.y1 }, { x: last.x2, y: last.y2 }];
|
|
34
|
+
}
|
|
35
|
+
default:
|
|
36
|
+
return undefined; // circles are closed on their own; points don't join
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract closed contours (chains of segments whose endpoints meet).
|
|
41
|
+
* @param geoms solved sketch geometry to chain (non-construction segments only)
|
|
42
|
+
* @returns closed loops plus self-closed circles (open chains are dropped)
|
|
43
|
+
*/
|
|
44
|
+
export function extractContours(geoms) {
|
|
45
|
+
const pool = [];
|
|
46
|
+
for (const g of geoms) {
|
|
47
|
+
const s = segEnds(g);
|
|
48
|
+
if (!s)
|
|
49
|
+
continue;
|
|
50
|
+
// P4: keep the full sampled polyline for splines — after chaining, the
|
|
51
|
+
// winning seg is expanded back into all sub-segments (no interior loss).
|
|
52
|
+
const spline = g.kind === 'bspline'
|
|
53
|
+
? bsplineToSegments({ poles: g.poles, knots: g.knots, degree: g.degree, periodic: g.periodic })
|
|
54
|
+
: undefined;
|
|
55
|
+
pool.push({ seg: s[0], a: s[1], b: s[2], used: false, spline });
|
|
56
|
+
}
|
|
57
|
+
const near = (p, q) => Math.hypot(p.x - q.x, p.y - q.y) <= JOIN_TOL;
|
|
58
|
+
const contours = [];
|
|
59
|
+
for (const start of pool) {
|
|
60
|
+
if (start.used)
|
|
61
|
+
continue;
|
|
62
|
+
start.used = true;
|
|
63
|
+
const head = start.a;
|
|
64
|
+
// GOTCHA (slittingsaw corpus, 2026-09-20): junction points with free
|
|
65
|
+
// branches (three+ segments sharing an endpoint) defeat first-come
|
|
66
|
+
// chaining — a branch to a dead end consumed the segments and the real
|
|
67
|
+
// loop never closed. DFS with backtracking: try each candidate
|
|
68
|
+
// continuation; the FIRST chain that closes at the head wins; a dead
|
|
69
|
+
// branch returns its segments to the pool. Closure-stop (tap GOTCHA) and
|
|
70
|
+
// pool-restart are both subsumed by the DFS ordering.
|
|
71
|
+
const dfs = (tail, walk) => {
|
|
72
|
+
if (walk.length > 0 && near(tail, head))
|
|
73
|
+
return walk;
|
|
74
|
+
for (const cand of pool) {
|
|
75
|
+
if (cand.used)
|
|
76
|
+
continue;
|
|
77
|
+
cand.used = true;
|
|
78
|
+
if (near(tail, cand.a)) {
|
|
79
|
+
const r = dfs(cand.b, [...walk, cand.seg]);
|
|
80
|
+
if (r)
|
|
81
|
+
return r;
|
|
82
|
+
}
|
|
83
|
+
else if (near(tail, cand.b)) {
|
|
84
|
+
const r = dfs(cand.a, [...walk, reverseSeg(cand.seg)]);
|
|
85
|
+
if (r)
|
|
86
|
+
return r;
|
|
87
|
+
}
|
|
88
|
+
cand.used = false; // dead branch — return the segment to the pool
|
|
89
|
+
}
|
|
90
|
+
return undefined;
|
|
91
|
+
};
|
|
92
|
+
const solved = dfs(start.b, [start.seg]);
|
|
93
|
+
if (solved && solved.length > 1) {
|
|
94
|
+
// P4: replace spline proxy segs with their full sampled polyline runs.
|
|
95
|
+
// The proxy seg for geometry index i sits in pool order; recover the
|
|
96
|
+
// spline runs by matching the seg reference against pool entries.
|
|
97
|
+
const expanded = [];
|
|
98
|
+
for (const seg of solved) {
|
|
99
|
+
const entry = pool.find((e) => e.seg === seg && e.spline);
|
|
100
|
+
if (entry?.spline) {
|
|
101
|
+
for (const sub of entry.spline) {
|
|
102
|
+
expanded.push({ kind: 'line', x1: sub.x1, y1: sub.y1, x2: sub.x2, y2: sub.y2 });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
expanded.push(seg);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
contours.push({ segments: expanded, closed: true });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// circles are self-closed contours
|
|
113
|
+
for (const g of geoms) {
|
|
114
|
+
if (g.kind === 'circle' && g.radius > 0) {
|
|
115
|
+
contours.push({
|
|
116
|
+
segments: [{
|
|
117
|
+
kind: 'arc', cx: g.cx, cy: g.cy, radius: g.radius,
|
|
118
|
+
startAngle: 0, endAngle: Math.PI * 2, ccw: true,
|
|
119
|
+
x1: g.cx + g.radius, y1: g.cy, x2: g.cx + g.radius, y2: g.cy,
|
|
120
|
+
}],
|
|
121
|
+
closed: true,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return contours;
|
|
126
|
+
}
|
|
127
|
+
function reverseSeg(s) {
|
|
128
|
+
if (s.kind === 'line')
|
|
129
|
+
return { kind: 'line', x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };
|
|
130
|
+
return { ...s, startAngle: s.endAngle, endAngle: s.startAngle, ccw: !s.ccw, x1: s.x2, y1: s.y2, x2: s.x1, y2: s.y1 };
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=contour.js.map
|