forgecad 0.9.15 → 0.10.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/dist/assets/{AdminPage-CDyGUinA.js → AdminPage-DwYHz72L.js} +1 -1
- package/dist/assets/{BenchmarkPage-DfPMY_-d.js → BenchmarkPage-a9_f-1US.js} +1 -1
- package/dist/assets/{BlogPage-kF0fkdJT.js → BlogPage-DodHpvmf.js} +1 -1
- package/dist/assets/{DocsPage-B954L3YN.js → DocsPage-B5LePEuj.js} +8 -858
- package/dist/assets/{EditorApp-CuDLxKqL.css → EditorApp-BpjZgzk0.css} +148 -0
- package/dist/assets/EditorApp-QXsAISLR.js +16307 -0
- package/dist/assets/{EmbedViewer-C77B-TrF.js → EmbedViewer-DdEHGUMU.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-Cr6fXMDj.js → LandingPageProofDriven-yhhOodbf.js} +2 -2
- package/dist/assets/{LegalPage-Dzklqmmg.js → LegalPage-5RbKRGYK.js} +1 -1
- package/dist/assets/{PricingPage-zWXkvlwl.js → PricingPage-E3Rma7aV.js} +1 -1
- package/dist/assets/{SettingsPage-Bz0of4KQ.js → SettingsPage-BJZcM97j.js} +1 -1
- package/dist/assets/{app-D3kDkggg.js → app-DSYrDg0V.js} +1846 -352
- package/dist/assets/cli/{render-DSY3mMQa.js → render-ZMHR9HkV.js} +161 -70
- package/dist/assets/{constructionHistoryWorker-gpDo-uH2.js → constructionHistoryWorker-AwMMWSxg.js} +1104 -349
- package/dist/assets/{evalWorker-CU0Ke6DP.js → evalWorker-DbNs7Dkp.js} +5155 -3772
- package/dist/assets/{inspectWorker-COyp8XXA.js → inspectWorker-CZsCFtQT.js} +1415 -439
- package/dist/assets/{targets-B9sGB5nB.js → jointPose-DO6mnXn_.js} +71 -3
- package/dist/assets/{manifold-DNkrUWpA.js → manifold-BGlQBBH9.js} +1 -1
- package/dist/assets/{manifold-BRI5prcH.js → manifold-BU-tJwQh.js} +1 -1
- package/dist/assets/{manifold-C-3h2M7p.js → manifold-fy2MV7K1.js} +2 -2
- package/dist/assets/{reportWorker-CdBz5bNg.js → reportWorker-DO6hcQbh.js} +8474 -4549
- package/dist/assets/{scalar-sampling-budget-wJF98aY9.js → scalar-sampling-budget-o90NSNmF.js} +5347 -3906
- package/dist/assets/{scanProxyWorker-B-9VbLIs.js → scanProxyWorker-2GtDLk-R.js} +19 -6
- package/dist/assets/{javascript-1kQXfVaz.js → typescript-DBQ6RN5l.js} +874 -22
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +3 -3
- package/dist/docs-raw/AI/usage.md +3 -1
- package/dist/docs-raw/CLI.md +65 -239
- package/dist/docs-raw/README.md +6 -0
- package/dist/docs-raw/component-model.md +17 -150
- package/dist/docs-raw/generated/assembly.md +159 -520
- package/dist/docs-raw/generated/concepts.md +245 -3491
- package/dist/docs-raw/generated/core.md +277 -1251
- package/dist/docs-raw/generated/curves.md +387 -1608
- package/dist/docs-raw/generated/legacy.md +162 -0
- package/dist/docs-raw/generated/lib.md +238 -112
- package/dist/docs-raw/generated/output.md +51 -76
- package/dist/docs-raw/generated/runtime-names.md +30 -22
- package/dist/docs-raw/generated/sdf.md +68 -284
- package/dist/docs-raw/generated/sheet-metal.md +68 -335
- package/dist/docs-raw/generated/sketch.md +240 -1161
- package/dist/docs-raw/generated/viewport.md +75 -316
- package/dist/docs-raw/generated/wood.md +21 -49
- package/dist/docs-raw/guides/coordinate-system.md +4 -42
- package/dist/docs-raw/guides/inspection-bundles.md +44 -442
- package/dist/docs-raw/guides/joint-design.md +18 -79
- package/dist/docs-raw/guides/positioning.md +21 -143
- package/dist/docs-raw/guides/scene-presentation.md +89 -0
- package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +25 -111
- package/dist/docs-raw/skills/forgecad-blockout-model.md +20 -117
- package/dist/docs-raw/skills/forgecad-component-model.md +23 -107
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +47 -155
- package/dist/docs-raw/skills/forgecad-image-replicator.md +26 -143
- package/dist/docs-raw/skills/forgecad-lld.md +19 -113
- package/dist/docs-raw/skills/forgecad-make-a-model.md +113 -532
- package/dist/docs-raw/skills/forgecad-model-grader.md +38 -108
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +24 -211
- package/dist/docs-raw/skills/forgecad-project.md +13 -129
- package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +42 -134
- package/dist/docs-raw/skills/forgecad-render-inspect.md +27 -174
- package/dist/docs-raw/skills/forgecad-visual-spec.md +32 -112
- package/dist/docs-raw/skills/forgecad.md +19 -18
- package/dist/docs-raw/skills/index.md +2 -0
- package/dist/docs-raw/welcome.md +4 -2
- package/dist/index.html +1 -1
- package/dist/llms.txt +1 -2
- package/dist/sitemap.xml +13 -13
- package/dist-cli/{check-compiler-SDX5QIXI.js → check-compiler-JTVBITCR.js} +1 -1
- package/dist-cli/{check-query-propagation-EAYEFT77.js → check-query-propagation-3FFLSMVN.js} +1 -1
- package/dist-cli/{chunk-N4O47JLF.js → chunk-OAN5T4XD.js} +5722 -4287
- package/dist-cli/forgecad.js +2195 -656
- package/dist-skill/CONTEXT.md +1778 -7912
- package/dist-skill/SKILL.md +15 -15
- package/dist-skill/docs/API/core/concepts.md +27 -157
- package/dist-skill/docs/CLI.md +65 -239
- package/dist-skill/docs/generated/assembly.md +160 -493
- package/dist-skill/docs/generated/core.md +277 -1251
- package/dist-skill/docs/generated/curves.md +387 -1609
- package/dist-skill/docs/generated/lib.md +238 -112
- package/dist-skill/docs/generated/output.md +51 -76
- package/dist-skill/docs/generated/runtime-names.md +16 -22
- package/dist-skill/docs/generated/sdf.md +68 -284
- package/dist-skill/docs/generated/sheet-metal.md +68 -335
- package/dist-skill/docs/generated/sketch.md +240 -1160
- package/dist-skill/docs/generated/viewport.md +75 -223
- package/dist-skill/docs/generated/wood.md +21 -49
- package/dist-skill/docs/guides/coordinate-system.md +4 -42
- package/dist-skill/docs/guides/inspection-bundles.md +44 -442
- package/dist-skill/docs/guides/joint-design.md +18 -79
- package/dist-skill/docs/guides/positioning.md +21 -143
- package/dist-skill/docs/guides/scene-presentation.md +89 -0
- package/dist-skill/docs/guides/surface-members.md +26 -0
- package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +23 -111
- package/dist-skill/library/forgecad-blockout-model/SKILL.md +18 -117
- package/dist-skill/library/forgecad-component-model/SKILL.md +21 -107
- package/dist-skill/library/forgecad-high-level-spec/SKILL.md +45 -155
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +24 -143
- package/dist-skill/library/forgecad-lld/SKILL.md +17 -113
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +111 -532
- package/dist-skill/library/forgecad-model-grader/SKILL.md +36 -108
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +35 -224
- package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +43 -271
- package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +30 -99
- package/dist-skill/library/forgecad-project/SKILL.md +13 -131
- package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +29 -123
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +25 -174
- package/dist-skill/library/forgecad-visual-spec/SKILL.md +30 -111
- package/dist-skill/website/skills/forgecad-3d-reconstruction.md +58 -0
- package/dist-skill/website/skills/forgecad-blockout-model.md +49 -0
- package/dist-skill/website/skills/forgecad-component-model.md +53 -0
- package/dist-skill/website/skills/forgecad-high-level-spec.md +101 -0
- package/dist-skill/website/skills/forgecad-image-replicator.md +63 -0
- package/dist-skill/website/skills/forgecad-lld.md +41 -0
- package/dist-skill/website/skills/forgecad-make-a-model.md +186 -0
- package/dist-skill/website/skills/forgecad-model-grader.md +82 -0
- package/dist-skill/website/skills/forgecad-prepare-prompt.md +63 -0
- package/dist-skill/website/skills/forgecad-project.md +26 -0
- package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +60 -0
- package/dist-skill/website/skills/forgecad-render-inspect.md +80 -0
- package/dist-skill/website/skills/forgecad-visual-spec.md +71 -0
- package/dist-skill/website/skills/forgecad.md +122 -0
- package/dist-skill/website/skills/index.md +26 -0
- package/examples/api/comparison-imported-sphere-candidate.forge.js +1 -1
- package/examples/api/conformal-product-ribbon.forge.js +1 -1
- package/examples/api/exact-sheet-shell-assembly.forge.js +1 -1
- package/examples/api/extrude-options.forge.js +4 -2
- package/examples/api/field-loft-drive-tip.forge.js +40 -0
- package/examples/api/guided-loft-olive-oil-bottle.forge.js +1 -1
- package/examples/api/helix-basics.forge.js +2 -2
- package/examples/api/highlight-debug.forge.js +10 -10
- package/examples/api/mesh-import-slats.forge.js +1 -1
- package/examples/api/real-product-curves.forge.js +1 -1
- package/examples/api/route3d-elbow.forge.js +3 -0
- package/examples/api/sculpt-box-circle-booleans.forge.js +1 -1
- package/examples/api/sdf-shapes.forge.js +2 -5
- package/examples/api/sketch-rounding-strategies.forge.js +6 -6
- package/examples/api/surface-member-bottle-cage.forge.js +3 -3
- package/examples/api/surface-member-conformal-product-ribbon.forge.js +3 -3
- package/examples/api/surface-member-razor-inlay.forge.js +1 -1
- package/examples/api/variable-sweep-test.forge.js +4 -2
- package/examples/mechanical/airplane-propeller.forge.js +74 -39
- package/examples/nurbs-surface.forge.js +1 -1
- package/examples/products/iphone.forge.js +1 -1
- package/package.json +4 -1
- package/dist/assets/EditorApp-Beb-IZ0y.js +0 -14014
- package/dist/docs-raw/guides/geometry-conventions.md +0 -52
- package/dist/docs-raw/guides/modeling-recipes.md +0 -78
- package/dist-skill/docs/guides/geometry-conventions.md +0 -52
- package/dist-skill/docs/guides/modeling-recipes.md +0 -78
- package/dist-skill/library/forgecad-visual-spec/references/prompt-template.md +0 -79
- package/examples/api/bolted-service-cover.forge.js +0 -17
- package/examples/api/cable-gland-anchor.forge.js +0 -14
- package/examples/api/captured-cartridge-guide.forge.js +0 -14
- package/examples/api/captured-linear-slide.forge.js +0 -13
- package/examples/api/clevis-pin-joint.forge.js +0 -13
- package/examples/api/datum-enclosure.forge.js +0 -16
- package/examples/api/hose-barb-port.forge.js +0 -14
- package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
- package/examples/api/living-hinge-cover.forge.js +0 -14
- package/examples/api/pcb-terminal-block.forge.js +0 -22
- package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
- package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
- package/examples/api/routed-tube-clip.forge.js +0 -15
- package/examples/api/seated-bearing-stack.forge.js +0 -30
- package/examples/api/snap-latch-cover.forge.js +0 -14
- package/examples/api/thumb-screw-clamp.forge.js +0 -15
package/dist/assets/{constructionHistoryWorker-gpDo-uH2.js → constructionHistoryWorker-AwMMWSxg.js}
RENAMED
|
@@ -7603,7 +7603,7 @@ function requireNonZeroFiniteScale3(value, label) {
|
|
|
7603
7603
|
}
|
|
7604
7604
|
return scale2;
|
|
7605
7605
|
}
|
|
7606
|
-
const EPS$
|
|
7606
|
+
const EPS$3 = 1e-10;
|
|
7607
7607
|
function subVec3(a2, b) {
|
|
7608
7608
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
7609
7609
|
}
|
|
@@ -7629,7 +7629,7 @@ function projectRadial(v, axis) {
|
|
|
7629
7629
|
function signedAngleAroundAxis(from, to, axis) {
|
|
7630
7630
|
const fromLen = lengthVec3$1(from);
|
|
7631
7631
|
const toLen = lengthVec3$1(to);
|
|
7632
|
-
if (fromLen < EPS$
|
|
7632
|
+
if (fromLen < EPS$3 || toLen < EPS$3) return 0;
|
|
7633
7633
|
const fn = scaleVec3(from, 1 / fromLen);
|
|
7634
7634
|
const tn = scaleVec3(to, 1 / toLen);
|
|
7635
7635
|
const sin2 = dotVec3$4(axis, crossVec3$2(fn, tn));
|
|
@@ -7650,19 +7650,19 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
|
|
|
7650
7650
|
const targetDecomp = projectRadial(target, unitAxis);
|
|
7651
7651
|
const movingRadialLen = lengthVec3$1(movingDecomp.radial);
|
|
7652
7652
|
const targetRadialLen = lengthVec3$1(targetDecomp.radial);
|
|
7653
|
-
if (movingRadialLen < EPS$
|
|
7654
|
-
if (mode === "line" && targetRadialLen >= EPS$
|
|
7653
|
+
if (movingRadialLen < EPS$3) {
|
|
7654
|
+
if (mode === "line" && targetRadialLen >= EPS$3) {
|
|
7655
7655
|
throw new Error("rotateAroundTo(...): moving point lies on the rotation axis, so line alignment is impossible");
|
|
7656
7656
|
}
|
|
7657
7657
|
return 0;
|
|
7658
7658
|
}
|
|
7659
7659
|
if (mode === "plane") {
|
|
7660
|
-
if (targetRadialLen < EPS$
|
|
7660
|
+
if (targetRadialLen < EPS$3) {
|
|
7661
7661
|
throw new Error("rotateAroundTo(...): target point lies on the rotation axis, so the target plane is undefined");
|
|
7662
7662
|
}
|
|
7663
7663
|
return signedAngleAroundAxis(movingDecomp.radial, targetDecomp.radial, unitAxis);
|
|
7664
7664
|
}
|
|
7665
|
-
if (targetRadialLen < EPS$
|
|
7665
|
+
if (targetRadialLen < EPS$3) {
|
|
7666
7666
|
throw new Error("rotateAroundTo(...): target line lies on the rotation axis, but the moving point does not");
|
|
7667
7667
|
}
|
|
7668
7668
|
const axialTol = 1e-8 * Math.max(1, Math.abs(movingDecomp.axial), Math.abs(targetDecomp.axial));
|
|
@@ -7699,7 +7699,7 @@ function multiplyMat4(a2, b) {
|
|
|
7699
7699
|
}
|
|
7700
7700
|
function normalizeVec3$3(v) {
|
|
7701
7701
|
const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
7702
|
-
if (len < EPS$
|
|
7702
|
+
if (len < EPS$3) throw new Error("Axis must be non-zero");
|
|
7703
7703
|
return [v[0] / len, v[1] / len, v[2] / len];
|
|
7704
7704
|
}
|
|
7705
7705
|
function transformPoint(m2, p2, w2) {
|
|
@@ -7729,7 +7729,7 @@ function invertMat4(m2) {
|
|
|
7729
7729
|
const b10 = a21 * a33 - a23 * a31;
|
|
7730
7730
|
const b11 = a22 * a33 - a23 * a32;
|
|
7731
7731
|
const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
|
7732
|
-
if (Math.abs(det) < EPS$
|
|
7732
|
+
if (Math.abs(det) < EPS$3) throw new Error("Transform matrix is not invertible");
|
|
7733
7733
|
const invDet = 1 / det;
|
|
7734
7734
|
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
|
|
7735
7735
|
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * invDet;
|
|
@@ -7762,6 +7762,32 @@ class Transform {
|
|
|
7762
7762
|
static from(input) {
|
|
7763
7763
|
return input instanceof Transform ? input : new Transform(requireFiniteMat4(input, "Transform.from() matrix"));
|
|
7764
7764
|
}
|
|
7765
|
+
/**
|
|
7766
|
+
* Compose transforms in chain order: `Transform.compose(a, b, c)` applies
|
|
7767
|
+
* `a`, then `b`, then `c` — the same left-to-right order as
|
|
7768
|
+
* `Transform.from(a).mul(b).mul(c)`.
|
|
7769
|
+
*
|
|
7770
|
+
* Prefer this over manual `.mul()` chains when composing 3+ transforms
|
|
7771
|
+
* (e.g. kinematics: `local -> childBase -> jointMotion -> jointFrame ->
|
|
7772
|
+
* parentWorld`); the variadic form makes the application order explicit and
|
|
7773
|
+
* prevents order mistakes.
|
|
7774
|
+
*
|
|
7775
|
+
* **Example**
|
|
7776
|
+
*
|
|
7777
|
+
* ```ts
|
|
7778
|
+
* const world = Transform.compose(childBase, jointMotion, jointFrame, parentWorld);
|
|
7779
|
+
* ```
|
|
7780
|
+
*
|
|
7781
|
+
* @param steps Transforms (or raw 4x4 matrices) applied left to right.
|
|
7782
|
+
* @returns The composed transform. With no arguments, the identity.
|
|
7783
|
+
*/
|
|
7784
|
+
static compose(...steps) {
|
|
7785
|
+
let acc = Transform.identity();
|
|
7786
|
+
for (const step of steps) {
|
|
7787
|
+
acc = acc.mul(step);
|
|
7788
|
+
}
|
|
7789
|
+
return acc;
|
|
7790
|
+
}
|
|
7765
7791
|
/** Create a translation transform. */
|
|
7766
7792
|
static translation(x2, y2, z2) {
|
|
7767
7793
|
return new Transform([
|
|
@@ -7845,6 +7871,7 @@ class Transform {
|
|
|
7845
7871
|
return this.rotateAxis([0, 0, 1], angleDeg, pivot);
|
|
7846
7872
|
}
|
|
7847
7873
|
/** Scale after the current transform. */
|
|
7874
|
+
// biome-ignore lint/suspicious/useAdjacentOverloadSignatures: Static Transform.scale() and chainable instance scale() intentionally share the CAD API name.
|
|
7848
7875
|
scale(v) {
|
|
7849
7876
|
return this.mul(Transform.scale(v));
|
|
7850
7877
|
}
|
|
@@ -9020,6 +9047,8 @@ function cloneSdfNode(node) {
|
|
|
9020
9047
|
return { kind: "sdf:circularArray", child: cloneSdfNode(node.child), count: node.count, offset: node.offset };
|
|
9021
9048
|
case "sdf:shell":
|
|
9022
9049
|
return { kind: "sdf:shell", child: cloneSdfNode(node.child), thickness: node.thickness };
|
|
9050
|
+
case "sdf:offset":
|
|
9051
|
+
return { kind: "sdf:offset", child: cloneSdfNode(node.child), distance: node.distance };
|
|
9023
9052
|
case "sdf:displace":
|
|
9024
9053
|
return {
|
|
9025
9054
|
kind: "sdf:displace",
|
|
@@ -9104,7 +9133,7 @@ function cloneSdfNode(node) {
|
|
|
9104
9133
|
}
|
|
9105
9134
|
}
|
|
9106
9135
|
const SHEET_METAL_EDGES = ["top", "right", "bottom", "left"];
|
|
9107
|
-
const EPS$
|
|
9136
|
+
const EPS$2 = 1e-9;
|
|
9108
9137
|
function isFinitePositive$1(value) {
|
|
9109
9138
|
return Number.isFinite(value) && value > 0;
|
|
9110
9139
|
}
|
|
@@ -9145,7 +9174,7 @@ function edgeDisplayName(edge) {
|
|
|
9145
9174
|
return `sheetMetal().flange("${edge}", ...)`;
|
|
9146
9175
|
}
|
|
9147
9176
|
function normalizeAngle(angleDeg) {
|
|
9148
|
-
return Math.abs(angleDeg) <= EPS$
|
|
9177
|
+
return Math.abs(angleDeg) <= EPS$2 ? 0 : angleDeg;
|
|
9149
9178
|
}
|
|
9150
9179
|
function validateSheetMetalModel(model) {
|
|
9151
9180
|
if (!isFinitePositive$1(model.panel.width) || !isFinitePositive$1(model.panel.height)) {
|
|
@@ -9157,7 +9186,7 @@ function validateSheetMetalModel(model) {
|
|
|
9157
9186
|
if (!isFiniteNonNegative(model.bendRadius)) {
|
|
9158
9187
|
return "sheetMetal() requires a finite non-negative bendRadius.";
|
|
9159
9188
|
}
|
|
9160
|
-
if (model.bendRadius <= EPS$
|
|
9189
|
+
if (model.bendRadius <= EPS$2) {
|
|
9161
9190
|
return "sheetMetal() v1 requires a positive bendRadius so the bend region stays explicit instead of collapsing into a sharp fold.";
|
|
9162
9191
|
}
|
|
9163
9192
|
if (model.bendAllowance.kind !== "k-factor") {
|
|
@@ -9219,7 +9248,7 @@ function deriveSheetMetalModel(model) {
|
|
|
9219
9248
|
const trimEnd = flanges.has(adjacent.end) ? model.cornerRelief.size : 0;
|
|
9220
9249
|
const fullLength = edge === "top" || edge === "bottom" ? model.panel.width : model.panel.height;
|
|
9221
9250
|
const span = fullLength - trimStart - trimEnd;
|
|
9222
|
-
if (!(span > EPS$
|
|
9251
|
+
if (!(span > EPS$2)) {
|
|
9223
9252
|
throw new Error(
|
|
9224
9253
|
`${edgeDisplayName(edge)} loses all usable span after applying the defended rectangular corner relief size ${model.cornerRelief.size}.`
|
|
9225
9254
|
);
|
|
@@ -9265,7 +9294,7 @@ function transformPlacement(origin, u2, v, normal) {
|
|
|
9265
9294
|
};
|
|
9266
9295
|
}
|
|
9267
9296
|
function translatePlan(plan, x2, y2, z2) {
|
|
9268
|
-
if (Math.abs(x2) <= EPS$
|
|
9297
|
+
if (Math.abs(x2) <= EPS$2 && Math.abs(y2) <= EPS$2 && Math.abs(z2) <= EPS$2) return cloneShapeCompilePlan(plan);
|
|
9269
9298
|
return appendShapeCompileTransform(cloneShapeCompilePlan(plan), {
|
|
9270
9299
|
kind: "translate",
|
|
9271
9300
|
x: x2,
|
|
@@ -10437,6 +10466,8 @@ function cloneShapeCompilePlan(plan) {
|
|
|
10437
10466
|
heights: plan.heights.map((height) => height),
|
|
10438
10467
|
edgeLength: plan.edgeLength,
|
|
10439
10468
|
boundsPadding: plan.boundsPadding,
|
|
10469
|
+
...plan.forceField ? { forceField: true } : {},
|
|
10470
|
+
...plan.meshing ? { meshing: cloneSdfCompileMeshingSettings(plan.meshing) } : {},
|
|
10440
10471
|
edgeLabels: plan.edgeLabels ? { ...plan.edgeLabels } : void 0,
|
|
10441
10472
|
capLabels: plan.capLabels ? { ...plan.capLabels } : void 0
|
|
10442
10473
|
};
|
|
@@ -10704,7 +10735,6 @@ function cloneShapeCompilePlan(plan) {
|
|
|
10704
10735
|
default:
|
|
10705
10736
|
assertExhaustive(plan);
|
|
10706
10737
|
}
|
|
10707
|
-
if (plan._occtCache) result._occtCache = plan._occtCache;
|
|
10708
10738
|
return result;
|
|
10709
10739
|
}
|
|
10710
10740
|
function appendProfileCompileTransform(plan, step) {
|
|
@@ -10717,22 +10747,31 @@ function appendShapeCompileTransform(plan, step) {
|
|
|
10717
10747
|
if (plan.kind === "transform") {
|
|
10718
10748
|
return {
|
|
10719
10749
|
kind: "transform",
|
|
10720
|
-
base:
|
|
10721
|
-
steps: [...plan.steps
|
|
10750
|
+
base: plan.base,
|
|
10751
|
+
steps: [...plan.steps, cloneShapeTransform(step)]
|
|
10722
10752
|
};
|
|
10723
10753
|
}
|
|
10724
10754
|
return {
|
|
10725
10755
|
kind: "transform",
|
|
10726
|
-
base:
|
|
10756
|
+
base: plan,
|
|
10727
10757
|
steps: [cloneShapeTransform(step)]
|
|
10728
10758
|
};
|
|
10729
10759
|
}
|
|
10730
10760
|
function appendShapeCompileTransforms(plan, steps) {
|
|
10731
|
-
|
|
10732
|
-
|
|
10733
|
-
|
|
10761
|
+
if (!plan) return null;
|
|
10762
|
+
if (steps.length === 0) return plan;
|
|
10763
|
+
if (plan.kind === "transform") {
|
|
10764
|
+
return {
|
|
10765
|
+
kind: "transform",
|
|
10766
|
+
base: plan.base,
|
|
10767
|
+
steps: [...plan.steps, ...steps.map(cloneShapeTransform)]
|
|
10768
|
+
};
|
|
10734
10769
|
}
|
|
10735
|
-
return
|
|
10770
|
+
return {
|
|
10771
|
+
kind: "transform",
|
|
10772
|
+
base: plan,
|
|
10773
|
+
steps: steps.map(cloneShapeTransform)
|
|
10774
|
+
};
|
|
10736
10775
|
}
|
|
10737
10776
|
function wrapShapeCompilePlanWithQueryOwner(plan, owner) {
|
|
10738
10777
|
if (!plan) return null;
|
|
@@ -10806,6 +10845,8 @@ function buildLoftShapeCompilePlan(profiles, heights, options) {
|
|
|
10806
10845
|
heights: heights.map((height) => canonicalNumber(height)),
|
|
10807
10846
|
edgeLength: canonicalNumber(options.edgeLength),
|
|
10808
10847
|
boundsPadding: canonicalNumber(options.boundsPadding),
|
|
10848
|
+
...options.forceField ? { forceField: true } : {},
|
|
10849
|
+
...options.meshing ? { meshing: cloneSdfCompileMeshingSettings(options.meshing) } : {},
|
|
10809
10850
|
edgeLabels: options.edgeLabels ? { ...options.edgeLabels } : void 0
|
|
10810
10851
|
};
|
|
10811
10852
|
}
|
|
@@ -10907,14 +10948,14 @@ function sub$2(a2, b) {
|
|
|
10907
10948
|
function dot$3(a2, b) {
|
|
10908
10949
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
10909
10950
|
}
|
|
10910
|
-
function cross$
|
|
10951
|
+
function cross$3(a2, b) {
|
|
10911
10952
|
return [a2[1] * b[2] - a2[2] * b[1], a2[2] * b[0] - a2[0] * b[2], a2[0] * b[1] - a2[1] * b[0]];
|
|
10912
10953
|
}
|
|
10913
10954
|
function rotateAroundAxis(v, axis, angleRad) {
|
|
10914
10955
|
const c2 = Math.cos(angleRad);
|
|
10915
10956
|
const s = Math.sin(angleRad);
|
|
10916
10957
|
const term1 = scale$2(v, c2);
|
|
10917
|
-
const term2 = scale$2(cross$
|
|
10958
|
+
const term2 = scale$2(cross$3(axis, v), s);
|
|
10918
10959
|
const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
|
|
10919
10960
|
return add$2(add$2(term1, term2), term3);
|
|
10920
10961
|
}
|
|
@@ -11190,13 +11231,13 @@ function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
|
|
|
11190
11231
|
pts.push(evalPathAt(path, 1));
|
|
11191
11232
|
return pts;
|
|
11192
11233
|
}
|
|
11193
|
-
const EPS$
|
|
11234
|
+
const EPS$1 = 1e-8;
|
|
11194
11235
|
function midpoint$1(start, end) {
|
|
11195
11236
|
return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
|
|
11196
11237
|
}
|
|
11197
11238
|
function normalize$3(v) {
|
|
11198
11239
|
const len = Math.hypot(v[0], v[1], v[2]);
|
|
11199
|
-
if (len <= EPS$
|
|
11240
|
+
if (len <= EPS$1) throw new Error("Edge feature selection requires a non-zero direction vector");
|
|
11200
11241
|
return [v[0] / len, v[1] / len, v[2] / len];
|
|
11201
11242
|
}
|
|
11202
11243
|
function subtract(a2, b) {
|
|
@@ -11275,7 +11316,7 @@ function rigidTransformForEdgeStep(step) {
|
|
|
11275
11316
|
case "mirror": {
|
|
11276
11317
|
const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
|
|
11277
11318
|
const len = Math.hypot(nx0, ny0, nz0);
|
|
11278
|
-
if (len <= EPS$
|
|
11319
|
+
if (len <= EPS$1) return Transform.identity();
|
|
11279
11320
|
const nx = nx0 / len;
|
|
11280
11321
|
const ny = ny0 / len;
|
|
11281
11322
|
const nz = nz0 / len;
|
|
@@ -11572,7 +11613,7 @@ function isRectangleProfile(points) {
|
|
|
11572
11613
|
return [next[0] - point[0], next[1] - point[1]];
|
|
11573
11614
|
});
|
|
11574
11615
|
const lengths = vectors.map(([x2, y2]) => Math.hypot(x2, y2));
|
|
11575
|
-
if (lengths.some((length4) => length4 <= EPS$
|
|
11616
|
+
if (lengths.some((length4) => length4 <= EPS$1)) return false;
|
|
11576
11617
|
const dot01 = vectors[0][0] * vectors[1][0] + vectors[0][1] * vectors[1][1];
|
|
11577
11618
|
const dot12 = vectors[1][0] * vectors[2][0] + vectors[1][1] * vectors[2][1];
|
|
11578
11619
|
const dot23 = vectors[2][0] * vectors[3][0] + vectors[2][1] * vectors[3][1];
|
|
@@ -13511,7 +13552,9 @@ function lowerLoftShellToConcretePlan(plan, thickness, openFaces) {
|
|
|
13511
13552
|
profiles: innerProfiles,
|
|
13512
13553
|
heights: innerHeights,
|
|
13513
13554
|
edgeLength: plan.edgeLength,
|
|
13514
|
-
boundsPadding: plan.boundsPadding
|
|
13555
|
+
boundsPadding: plan.boundsPadding,
|
|
13556
|
+
...plan.forceField ? { forceField: true } : {},
|
|
13557
|
+
...plan.meshing ? { meshing: { ...plan.meshing } } : {}
|
|
13515
13558
|
};
|
|
13516
13559
|
return { ok: true, plan: buildBooleanShapeCompilePlan("difference", [plan, inner]) };
|
|
13517
13560
|
}
|
|
@@ -13649,6 +13692,197 @@ function lowerShellShapeCompilePlanToConcretePlan(plan) {
|
|
|
13649
13692
|
}
|
|
13650
13693
|
return lowerBaseShellPlanToConcretePlan(plan.base, plan.thickness, normalizeShellOpenFaces(plan.openFaces));
|
|
13651
13694
|
}
|
|
13695
|
+
function cyrb53(str, seed) {
|
|
13696
|
+
let h1 = 3735928559 ^ seed;
|
|
13697
|
+
let h2 = 1103547991 ^ seed;
|
|
13698
|
+
for (let i = 0; i < str.length; i++) {
|
|
13699
|
+
const ch = str.charCodeAt(i);
|
|
13700
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
13701
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
13702
|
+
}
|
|
13703
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
13704
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
13705
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
13706
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
13707
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
13708
|
+
}
|
|
13709
|
+
function hash128(str) {
|
|
13710
|
+
return cyrb53(str, 2654435769).toString(36) + cyrb53(str, 2246822507).toString(36);
|
|
13711
|
+
}
|
|
13712
|
+
function isPlainValue(value) {
|
|
13713
|
+
return value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string";
|
|
13714
|
+
}
|
|
13715
|
+
function createStructuralHasher(normalize2) {
|
|
13716
|
+
const memo = /* @__PURE__ */ new WeakMap();
|
|
13717
|
+
const skipKey = normalize2 == null ? void 0 : normalize2.skipKey;
|
|
13718
|
+
const unwrap = normalize2 == null ? void 0 : normalize2.unwrap;
|
|
13719
|
+
const entriesSource = normalize2 == null ? void 0 : normalize2.entriesSource;
|
|
13720
|
+
return function hashValue(root) {
|
|
13721
|
+
if (isPlainValue(root)) return JSON.stringify(root);
|
|
13722
|
+
if (root === void 0 || typeof root === "function" || typeof root === "symbol") return "null";
|
|
13723
|
+
const inProgress = /* @__PURE__ */ new Set();
|
|
13724
|
+
const stack = [];
|
|
13725
|
+
const attach = (frame, key, token) => {
|
|
13726
|
+
if (frame.isArray) frame.parts.push(token);
|
|
13727
|
+
else frame.parts.push(`${JSON.stringify(key)}:${token}`);
|
|
13728
|
+
};
|
|
13729
|
+
const open = (objIn, key) => {
|
|
13730
|
+
const aliases = [];
|
|
13731
|
+
let current = objIn;
|
|
13732
|
+
while (current !== null && typeof current === "object" && !Array.isArray(current) && unwrap) {
|
|
13733
|
+
const cached2 = memo.get(current);
|
|
13734
|
+
if (cached2 !== void 0) {
|
|
13735
|
+
for (const alias of aliases) memo.set(alias, cached2);
|
|
13736
|
+
return cached2;
|
|
13737
|
+
}
|
|
13738
|
+
const replaced = unwrap(current);
|
|
13739
|
+
if (replaced === void 0 || replaced === current) break;
|
|
13740
|
+
aliases.push(current);
|
|
13741
|
+
if (aliases.includes(replaced)) {
|
|
13742
|
+
throw new Error("planHash: cycle detected through unwrap chain");
|
|
13743
|
+
}
|
|
13744
|
+
current = replaced;
|
|
13745
|
+
}
|
|
13746
|
+
if (isPlainValue(current)) {
|
|
13747
|
+
const token = JSON.stringify(current);
|
|
13748
|
+
for (const alias of aliases) memo.set(alias, token);
|
|
13749
|
+
return token;
|
|
13750
|
+
}
|
|
13751
|
+
if (current === void 0 || typeof current === "function" || typeof current === "symbol") {
|
|
13752
|
+
for (const alias of aliases) memo.set(alias, "null");
|
|
13753
|
+
return "null";
|
|
13754
|
+
}
|
|
13755
|
+
const obj = current;
|
|
13756
|
+
const cached = memo.get(obj);
|
|
13757
|
+
if (cached !== void 0) {
|
|
13758
|
+
for (const alias of aliases) memo.set(alias, cached);
|
|
13759
|
+
return cached;
|
|
13760
|
+
}
|
|
13761
|
+
if (inProgress.has(obj)) {
|
|
13762
|
+
throw new Error(`planHash: cycle detected in plan data (kind=${String(obj.kind)})`);
|
|
13763
|
+
}
|
|
13764
|
+
inProgress.add(obj);
|
|
13765
|
+
aliases.push(obj);
|
|
13766
|
+
let entriesNode = obj;
|
|
13767
|
+
if (!Array.isArray(obj) && entriesSource) {
|
|
13768
|
+
const substitute = entriesSource(obj);
|
|
13769
|
+
if (substitute) entriesNode = substitute;
|
|
13770
|
+
}
|
|
13771
|
+
let pending;
|
|
13772
|
+
if (Array.isArray(entriesNode)) {
|
|
13773
|
+
pending = entriesNode.map((value) => ({ value })).reverse();
|
|
13774
|
+
} else {
|
|
13775
|
+
const entries = Object.entries(entriesNode).sort(([left], [right]) => left.localeCompare(right));
|
|
13776
|
+
pending = [];
|
|
13777
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
13778
|
+
const [entryKey, value] = entries[i];
|
|
13779
|
+
if (skipKey == null ? void 0 : skipKey(entryKey)) continue;
|
|
13780
|
+
pending.push({ value, key: entryKey });
|
|
13781
|
+
}
|
|
13782
|
+
}
|
|
13783
|
+
stack.push({ pending, parts: [], isArray: Array.isArray(entriesNode), aliases, key });
|
|
13784
|
+
return null;
|
|
13785
|
+
};
|
|
13786
|
+
const rootToken = open(root, void 0);
|
|
13787
|
+
if (rootToken !== null) return rootToken;
|
|
13788
|
+
let result = "null";
|
|
13789
|
+
while (stack.length > 0) {
|
|
13790
|
+
const frame = stack[stack.length - 1];
|
|
13791
|
+
const next = frame.pending.pop();
|
|
13792
|
+
if (next) {
|
|
13793
|
+
if (isPlainValue(next.value)) {
|
|
13794
|
+
attach(frame, next.key, JSON.stringify(next.value));
|
|
13795
|
+
continue;
|
|
13796
|
+
}
|
|
13797
|
+
if (next.value === void 0 || typeof next.value === "function" || typeof next.value === "symbol") {
|
|
13798
|
+
if (frame.isArray) frame.parts.push("null");
|
|
13799
|
+
continue;
|
|
13800
|
+
}
|
|
13801
|
+
const token2 = open(next.value, next.key);
|
|
13802
|
+
if (token2 !== null) attach(frame, next.key, token2);
|
|
13803
|
+
continue;
|
|
13804
|
+
}
|
|
13805
|
+
const canonical = frame.isArray ? `[${frame.parts.join(",")}]` : `{${frame.parts.join(",")}}`;
|
|
13806
|
+
const token = "#" + hash128(canonical);
|
|
13807
|
+
for (const alias of frame.aliases) {
|
|
13808
|
+
memo.set(alias, token);
|
|
13809
|
+
inProgress.delete(alias);
|
|
13810
|
+
}
|
|
13811
|
+
stack.pop();
|
|
13812
|
+
if (stack.length === 0) result = token;
|
|
13813
|
+
else attach(stack[stack.length - 1], frame.key, token);
|
|
13814
|
+
}
|
|
13815
|
+
return result;
|
|
13816
|
+
};
|
|
13817
|
+
}
|
|
13818
|
+
const PLAN_BOOKKEEPING_KEY = (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation";
|
|
13819
|
+
const encodedLengthMemo = /* @__PURE__ */ new WeakMap();
|
|
13820
|
+
function estimatePlanEncodedLength(value) {
|
|
13821
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") return 4;
|
|
13822
|
+
if (value === null || typeof value === "boolean") return 5;
|
|
13823
|
+
if (typeof value === "number") return 8;
|
|
13824
|
+
if (typeof value === "string") return value.length + 2;
|
|
13825
|
+
const root = value;
|
|
13826
|
+
const known = encodedLengthMemo.get(root);
|
|
13827
|
+
if (known !== void 0) return known;
|
|
13828
|
+
const childValues = (node) => {
|
|
13829
|
+
if (Array.isArray(node)) return node;
|
|
13830
|
+
const values = [];
|
|
13831
|
+
for (const [key, item] of Object.entries(node)) {
|
|
13832
|
+
if (PLAN_BOOKKEEPING_KEY(key)) continue;
|
|
13833
|
+
values.push(item);
|
|
13834
|
+
}
|
|
13835
|
+
return values;
|
|
13836
|
+
};
|
|
13837
|
+
const order = [];
|
|
13838
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13839
|
+
const discover = [root];
|
|
13840
|
+
while (discover.length > 0) {
|
|
13841
|
+
const node = discover.pop();
|
|
13842
|
+
if (seen.has(node) || encodedLengthMemo.has(node)) continue;
|
|
13843
|
+
seen.add(node);
|
|
13844
|
+
order.push(node);
|
|
13845
|
+
for (const child of childValues(node)) {
|
|
13846
|
+
if (child !== null && typeof child === "object") discover.push(child);
|
|
13847
|
+
}
|
|
13848
|
+
}
|
|
13849
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
13850
|
+
const node = order[i];
|
|
13851
|
+
let total = 2;
|
|
13852
|
+
if (Array.isArray(node)) {
|
|
13853
|
+
for (const item of node) {
|
|
13854
|
+
total += 1 + (item !== null && typeof item === "object" ? encodedLengthMemo.get(item) : estimatePlanEncodedLength(item));
|
|
13855
|
+
}
|
|
13856
|
+
} else {
|
|
13857
|
+
for (const [key, item] of Object.entries(node)) {
|
|
13858
|
+
if (PLAN_BOOKKEEPING_KEY(key)) continue;
|
|
13859
|
+
total += key.length + 4 + (item !== null && typeof item === "object" ? encodedLengthMemo.get(item) : estimatePlanEncodedLength(item));
|
|
13860
|
+
}
|
|
13861
|
+
}
|
|
13862
|
+
encodedLengthMemo.set(node, total);
|
|
13863
|
+
}
|
|
13864
|
+
return encodedLengthMemo.get(root);
|
|
13865
|
+
}
|
|
13866
|
+
function deepFreezePlanData(value) {
|
|
13867
|
+
if (value === null || typeof value !== "object") return value;
|
|
13868
|
+
const stack = [value];
|
|
13869
|
+
while (stack.length > 0) {
|
|
13870
|
+
const node = stack.pop();
|
|
13871
|
+
if (Object.isFrozen(node)) continue;
|
|
13872
|
+
Object.freeze(node);
|
|
13873
|
+
if (Array.isArray(node)) {
|
|
13874
|
+
for (const item of node) {
|
|
13875
|
+
if (item !== null && typeof item === "object") stack.push(item);
|
|
13876
|
+
}
|
|
13877
|
+
continue;
|
|
13878
|
+
}
|
|
13879
|
+
for (const [key, item] of Object.entries(node)) {
|
|
13880
|
+
if (PLAN_BOOKKEEPING_KEY(key)) continue;
|
|
13881
|
+
if (item !== null && typeof item === "object") stack.push(item);
|
|
13882
|
+
}
|
|
13883
|
+
}
|
|
13884
|
+
return value;
|
|
13885
|
+
}
|
|
13652
13886
|
const SHAPE_GEOMETRY_CACHE_KEY_VERSION = "shape-geometry-v1";
|
|
13653
13887
|
const SHAPE_BUILD_CACHE_POLICY = "live-structural-lru-v1";
|
|
13654
13888
|
const STRUCTURAL_CACHE_BUDGET_MB = 64;
|
|
@@ -13694,60 +13928,26 @@ function recordEvent(event) {
|
|
|
13694
13928
|
runEvents.push(next);
|
|
13695
13929
|
if (runEvents.length > MAX_RECORDED_EVENTS) runEvents = runEvents.slice(-MAX_RECORDED_EVENTS);
|
|
13696
13930
|
}
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
for (const [key, item] of entries) {
|
|
13714
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
13715
|
-
const encoded = stableGeometryEncode(item, false);
|
|
13716
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
13717
|
-
}
|
|
13718
|
-
return `{${encodedEntries.join(",")}}`;
|
|
13719
|
-
}
|
|
13720
|
-
function stableCacheOpportunityEncode(value, arrayMember) {
|
|
13721
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
13722
|
-
return arrayMember ? "null" : void 0;
|
|
13723
|
-
}
|
|
13724
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
13725
|
-
return JSON.stringify(value);
|
|
13726
|
-
}
|
|
13727
|
-
if (Array.isArray(value)) {
|
|
13728
|
-
return `[${value.map((item) => stableCacheOpportunityEncode(item, true) ?? "null").join(",")}]`;
|
|
13729
|
-
}
|
|
13730
|
-
const record = value;
|
|
13731
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
13732
|
-
return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
13733
|
-
}
|
|
13734
|
-
let encodedRecord = record;
|
|
13735
|
-
if (record.kind === "transform" && record.base) {
|
|
13736
|
-
const retainedSteps = Array.isArray(record.steps) ? record.steps.filter((step) => step.kind === "scale") : [];
|
|
13737
|
-
if (retainedSteps.length === 0) return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
13738
|
-
encodedRecord = { kind: "transform", base: record.base, steps: retainedSteps };
|
|
13739
|
-
}
|
|
13740
|
-
const entries = Object.entries(encodedRecord).sort(([left], [right]) => left.localeCompare(right));
|
|
13741
|
-
const encodedEntries = [];
|
|
13742
|
-
for (const [key, item] of entries) {
|
|
13743
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
13744
|
-
const encoded = stableCacheOpportunityEncode(item, false);
|
|
13745
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
13931
|
+
const skipBookkeepingKey = (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation";
|
|
13932
|
+
const geometryPlanHasher = createStructuralHasher({
|
|
13933
|
+
skipKey: skipBookkeepingKey,
|
|
13934
|
+
unwrap: (record) => record.kind === "queryOwner" && record.base ? record.base : void 0
|
|
13935
|
+
});
|
|
13936
|
+
const scaleOnlySteps = (record) => Array.isArray(record.steps) ? record.steps.filter((step) => step.kind === "scale") : [];
|
|
13937
|
+
createStructuralHasher({
|
|
13938
|
+
skipKey: skipBookkeepingKey,
|
|
13939
|
+
unwrap: (record) => {
|
|
13940
|
+
if (record.kind === "queryOwner" && record.base) return record.base;
|
|
13941
|
+
if (record.kind === "transform" && record.base && scaleOnlySteps(record).length === 0) return record.base;
|
|
13942
|
+
return void 0;
|
|
13943
|
+
},
|
|
13944
|
+
entriesSource: (record) => {
|
|
13945
|
+
if (record.kind !== "transform" || !record.base) return void 0;
|
|
13946
|
+
return { kind: "transform", base: record.base, steps: scaleOnlySteps(record) };
|
|
13746
13947
|
}
|
|
13747
|
-
|
|
13748
|
-
}
|
|
13948
|
+
});
|
|
13749
13949
|
function shapeGeometryCacheKey(plan) {
|
|
13750
|
-
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${
|
|
13950
|
+
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${geometryPlanHasher(plan)}`;
|
|
13751
13951
|
}
|
|
13752
13952
|
function planComplexityScore(value) {
|
|
13753
13953
|
if (!value || typeof value !== "object") return 0;
|
|
@@ -13806,8 +14006,7 @@ function planComplexityScore(value) {
|
|
|
13806
14006
|
}
|
|
13807
14007
|
}
|
|
13808
14008
|
function estimateCacheRetainedMb(plan) {
|
|
13809
|
-
|
|
13810
|
-
const encodedLength = ((_a3 = stableCacheOpportunityEncode(plan, false)) == null ? void 0 : _a3.length) ?? 0;
|
|
14009
|
+
const encodedLength = estimatePlanEncodedLength(plan);
|
|
13811
14010
|
const serializedComplexityMb = encodedLength / 24e3;
|
|
13812
14011
|
return round2(0.08 + planComplexityScore(plan) * 0.09 + serializedComplexityMb);
|
|
13813
14012
|
}
|
|
@@ -13825,26 +14024,62 @@ function splitCacheablePlacement(plan) {
|
|
|
13825
14024
|
}
|
|
13826
14025
|
return { basePlan: plan, placementSteps: [] };
|
|
13827
14026
|
}
|
|
14027
|
+
const uncacheableReasonMemo = /* @__PURE__ */ new WeakMap();
|
|
13828
14028
|
function findUncacheableReason(value) {
|
|
13829
14029
|
if (value === void 0 || value === null) return null;
|
|
13830
14030
|
if (typeof value === "function" || typeof value === "symbol") return "plan contains runtime-only values";
|
|
13831
14031
|
if (typeof value !== "object") return null;
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
14032
|
+
const root = value;
|
|
14033
|
+
const known = uncacheableReasonMemo.get(root);
|
|
14034
|
+
if (known !== void 0) return known;
|
|
14035
|
+
const order = [];
|
|
14036
|
+
const seen = /* @__PURE__ */ new Set();
|
|
14037
|
+
const discover = [root];
|
|
14038
|
+
let reason = null;
|
|
14039
|
+
while (discover.length > 0 && reason === null) {
|
|
14040
|
+
const node = discover.pop();
|
|
14041
|
+
if (seen.has(node)) continue;
|
|
14042
|
+
const cached = uncacheableReasonMemo.get(node);
|
|
14043
|
+
if (cached !== void 0) {
|
|
14044
|
+
if (cached !== null) reason = cached;
|
|
14045
|
+
continue;
|
|
14046
|
+
}
|
|
14047
|
+
seen.add(node);
|
|
14048
|
+
if (ArrayBuffer.isView(node) || node instanceof ArrayBuffer) {
|
|
14049
|
+
reason = "plan contains binary file data";
|
|
14050
|
+
break;
|
|
14051
|
+
}
|
|
14052
|
+
order.push(node);
|
|
14053
|
+
if (Array.isArray(node)) {
|
|
14054
|
+
for (const item of node) {
|
|
14055
|
+
if (typeof item === "function" || typeof item === "symbol") {
|
|
14056
|
+
reason = "plan contains runtime-only values";
|
|
14057
|
+
break;
|
|
14058
|
+
}
|
|
14059
|
+
if (item !== null && typeof item === "object") discover.push(item);
|
|
14060
|
+
}
|
|
14061
|
+
continue;
|
|
14062
|
+
}
|
|
14063
|
+
const record = node;
|
|
14064
|
+
if (record.kind === "importedMesh" || record.kind === "importedStep") {
|
|
14065
|
+
reason = "plan depends on imported file contents";
|
|
14066
|
+
break;
|
|
14067
|
+
}
|
|
14068
|
+
for (const [key, item] of Object.entries(record)) {
|
|
14069
|
+
if (skipBookkeepingKey(key)) continue;
|
|
14070
|
+
if (typeof item === "function" || typeof item === "symbol") {
|
|
14071
|
+
reason = "plan contains runtime-only values";
|
|
14072
|
+
break;
|
|
14073
|
+
}
|
|
14074
|
+
if (item !== null && typeof item === "object") discover.push(item);
|
|
13837
14075
|
}
|
|
13838
|
-
return null;
|
|
13839
14076
|
}
|
|
13840
|
-
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
const reason = findUncacheableReason(item);
|
|
13845
|
-
if (reason) return reason;
|
|
14077
|
+
if (reason === null) {
|
|
14078
|
+
for (const node of order) uncacheableReasonMemo.set(node, null);
|
|
14079
|
+
} else {
|
|
14080
|
+
uncacheableReasonMemo.set(root, reason);
|
|
13846
14081
|
}
|
|
13847
|
-
return
|
|
14082
|
+
return reason;
|
|
13848
14083
|
}
|
|
13849
14084
|
function applyPlacementStep(backend, step) {
|
|
13850
14085
|
switch (step.kind) {
|
|
@@ -14521,8 +14756,9 @@ function analyzeNodeUV(node, toLocal) {
|
|
|
14521
14756
|
if (result.majorRadius !== void 0) result.majorRadius *= node.factor;
|
|
14522
14757
|
return result;
|
|
14523
14758
|
}
|
|
14524
|
-
// ── Shell — UV comes from the inner shape ──
|
|
14759
|
+
// ── Shell / offset — UV comes from the inner shape ──
|
|
14525
14760
|
case "sdf:shell":
|
|
14761
|
+
case "sdf:offset":
|
|
14526
14762
|
return analyzeNodeUV(node.child, toLocal);
|
|
14527
14763
|
// ── CSG — take UV from the first (primary) child ──
|
|
14528
14764
|
case "sdf:union":
|
|
@@ -15058,6 +15294,11 @@ function compileSdfNode3(node) {
|
|
|
15058
15294
|
const t = node.thickness * 0.5;
|
|
15059
15295
|
return (x2, y2, z2) => abs(fn(x2, y2, z2)) - t;
|
|
15060
15296
|
}
|
|
15297
|
+
case "sdf:offset": {
|
|
15298
|
+
const fn = compileSdfNode3(node.child);
|
|
15299
|
+
const d2 = node.distance;
|
|
15300
|
+
return (x2, y2, z2) => fn(x2, y2, z2) - d2;
|
|
15301
|
+
}
|
|
15061
15302
|
case "sdf:displace": {
|
|
15062
15303
|
const fn = compileSdfNode3(node.child);
|
|
15063
15304
|
const constEntries = Object.entries(node.constants ?? {});
|
|
@@ -15551,6 +15792,10 @@ function emitSdfProgramNode(b, node, x2, y2, z2) {
|
|
|
15551
15792
|
const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
15552
15793
|
return b.sub(b.abs(child), b.constant(node.thickness * 0.5));
|
|
15553
15794
|
}
|
|
15795
|
+
case "sdf:offset": {
|
|
15796
|
+
const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
15797
|
+
return b.sub(child, b.constant(node.distance));
|
|
15798
|
+
}
|
|
15554
15799
|
case "sdf:onion": {
|
|
15555
15800
|
let d2 = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
15556
15801
|
for (let i = 0; i < node.layers; i++) d2 = b.sub(b.abs(d2), b.constant(node.thickness));
|
|
@@ -15699,6 +15944,7 @@ function getUnsupportedSdfProgramReason(node) {
|
|
|
15699
15944
|
case "sdf:bend":
|
|
15700
15945
|
case "sdf:repeat":
|
|
15701
15946
|
case "sdf:shell":
|
|
15947
|
+
case "sdf:offset":
|
|
15702
15948
|
case "sdf:onion":
|
|
15703
15949
|
return getUnsupportedSdfProgramReason(node.child);
|
|
15704
15950
|
default:
|
|
@@ -16140,7 +16386,19 @@ function simplifyMesh(triVerts, vertProperties, targetRatio, maxError) {
|
|
|
16140
16386
|
if (!_simplifier) {
|
|
16141
16387
|
throw new Error("meshoptimizer not initialized — call initMeshoptimizer() first");
|
|
16142
16388
|
}
|
|
16143
|
-
|
|
16389
|
+
if (triVerts.length === 0 || triVerts.length % 3 !== 0) {
|
|
16390
|
+
throw new Error("Mesh simplification requires triangle indices in groups of 3");
|
|
16391
|
+
}
|
|
16392
|
+
if (!Number.isFinite(targetRatio) || targetRatio <= 0) {
|
|
16393
|
+
throw new Error("Mesh simplification targetRatio must be a positive finite number");
|
|
16394
|
+
}
|
|
16395
|
+
if (!Number.isFinite(maxError) || maxError < 0) {
|
|
16396
|
+
throw new Error("Mesh simplification maxError must be a non-negative finite number");
|
|
16397
|
+
}
|
|
16398
|
+
const inputTriangleCount = triVerts.length / 3;
|
|
16399
|
+
const targetTriangleCount = Math.max(1, Math.min(inputTriangleCount, Math.floor(inputTriangleCount * targetRatio)));
|
|
16400
|
+
if (targetTriangleCount >= inputTriangleCount) return triVerts;
|
|
16401
|
+
const targetIndexCount = targetTriangleCount * 3;
|
|
16144
16402
|
const [simplified] = _simplifier.simplify(
|
|
16145
16403
|
triVerts,
|
|
16146
16404
|
vertProperties,
|
|
@@ -17117,117 +17375,10 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
17117
17375
|
edgeLength: options.edgeLength
|
|
17118
17376
|
};
|
|
17119
17377
|
}
|
|
17120
|
-
const
|
|
17121
|
-
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
const dists = [0];
|
|
17125
|
-
for (let i = 0; i < poly.length; i++) {
|
|
17126
|
-
const p1 = poly[i];
|
|
17127
|
-
const p2 = poly[(i + 1) % poly.length];
|
|
17128
|
-
const dx = p2[0] - p1[0];
|
|
17129
|
-
const dy = p2[1] - p1[1];
|
|
17130
|
-
const d2 = Math.sqrt(dx * dx + dy * dy);
|
|
17131
|
-
dists.push(dists[dists.length - 1] + d2);
|
|
17132
|
-
}
|
|
17133
|
-
const totalDist = dists[dists.length - 1];
|
|
17134
|
-
if (totalDist < 1e-12) {
|
|
17135
|
-
return Array.from({ length: targetCount }, () => [poly[0][0], poly[0][1]]);
|
|
17136
|
-
}
|
|
17137
|
-
const out = [];
|
|
17138
|
-
for (let i = 0; i < targetCount; i++) {
|
|
17139
|
-
const targetDist = i / targetCount * totalDist;
|
|
17140
|
-
let low = 0;
|
|
17141
|
-
let high = dists.length - 1;
|
|
17142
|
-
while (low < high) {
|
|
17143
|
-
const mid = low + high >> 1;
|
|
17144
|
-
if (dists[mid] <= targetDist) {
|
|
17145
|
-
low = mid + 1;
|
|
17146
|
-
} else {
|
|
17147
|
-
high = mid;
|
|
17148
|
-
}
|
|
17149
|
-
}
|
|
17150
|
-
const seg = low - 1;
|
|
17151
|
-
const t = (targetDist - dists[seg]) / (dists[seg + 1] - dists[seg]);
|
|
17152
|
-
const p1 = poly[seg % poly.length];
|
|
17153
|
-
const p2 = poly[(seg + 1) % poly.length];
|
|
17154
|
-
out.push([p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t]);
|
|
17155
|
-
}
|
|
17156
|
-
return out;
|
|
17157
|
-
}
|
|
17158
|
-
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid(poly)) {
|
|
17159
|
-
if (poly.length < 3 || targetCount <= 0) return null;
|
|
17160
|
-
if (!isConvexPolygon(poly)) return null;
|
|
17161
|
-
const out = [];
|
|
17162
|
-
for (let index2 = 0; index2 < targetCount; index2 += 1) {
|
|
17163
|
-
const angle = index2 / targetCount * Math.PI * 2;
|
|
17164
|
-
const point = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
17165
|
-
if (!point) return null;
|
|
17166
|
-
out.push(point);
|
|
17167
|
-
}
|
|
17168
|
-
return out;
|
|
17169
|
-
}
|
|
17170
|
-
function rayPolygonIntersection(origin, direction, poly) {
|
|
17171
|
-
let bestT = Infinity;
|
|
17172
|
-
let best = null;
|
|
17173
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
17174
|
-
const a2 = poly[index2];
|
|
17175
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
17176
|
-
const edge = [b[0] - a2[0], b[1] - a2[1]];
|
|
17177
|
-
const denom = cross$3(direction, edge);
|
|
17178
|
-
if (Math.abs(denom) < EPS$1) continue;
|
|
17179
|
-
const delta = [a2[0] - origin[0], a2[1] - origin[1]];
|
|
17180
|
-
const rayT = cross$3(delta, edge) / denom;
|
|
17181
|
-
const edgeT = cross$3(delta, direction) / denom;
|
|
17182
|
-
if (rayT >= -EPS$1 && edgeT >= -EPS$1 && edgeT <= 1 + EPS$1 && rayT < bestT) {
|
|
17183
|
-
bestT = rayT;
|
|
17184
|
-
best = [origin[0] + direction[0] * rayT, origin[1] + direction[1] * rayT];
|
|
17185
|
-
}
|
|
17186
|
-
}
|
|
17187
|
-
return best;
|
|
17188
|
-
}
|
|
17189
|
-
function polygonCentroid(poly) {
|
|
17190
|
-
let area2 = 0;
|
|
17191
|
-
let cx = 0;
|
|
17192
|
-
let cy = 0;
|
|
17193
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
17194
|
-
const a2 = poly[index2];
|
|
17195
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
17196
|
-
const crossValue = cross$3(a2, b);
|
|
17197
|
-
area2 += crossValue;
|
|
17198
|
-
cx += (a2[0] + b[0]) * crossValue;
|
|
17199
|
-
cy += (a2[1] + b[1]) * crossValue;
|
|
17200
|
-
}
|
|
17201
|
-
if (Math.abs(area2) < EPS$1) return averagePoint(poly);
|
|
17202
|
-
return [cx / (3 * area2), cy / (3 * area2)];
|
|
17203
|
-
}
|
|
17204
|
-
function averagePoint(poly) {
|
|
17205
|
-
let x2 = 0;
|
|
17206
|
-
let y2 = 0;
|
|
17207
|
-
for (const point of poly) {
|
|
17208
|
-
x2 += point[0];
|
|
17209
|
-
y2 += point[1];
|
|
17210
|
-
}
|
|
17211
|
-
return [x2 / poly.length, y2 / poly.length];
|
|
17212
|
-
}
|
|
17213
|
-
function isConvexPolygon(poly) {
|
|
17214
|
-
let sign = 0;
|
|
17215
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
17216
|
-
const a2 = poly[index2];
|
|
17217
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
17218
|
-
const c2 = poly[(index2 + 2) % poly.length];
|
|
17219
|
-
const turn = cross$3([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
|
|
17220
|
-
if (Math.abs(turn) < EPS$1) continue;
|
|
17221
|
-
const currentSign = Math.sign(turn);
|
|
17222
|
-
if (sign !== 0 && currentSign !== sign) return false;
|
|
17223
|
-
sign = currentSign;
|
|
17224
|
-
}
|
|
17225
|
-
return sign !== 0;
|
|
17226
|
-
}
|
|
17227
|
-
function cross$3(a2, b) {
|
|
17228
|
-
return a2[0] * b[1] - a2[1] * b[0];
|
|
17229
|
-
}
|
|
17230
|
-
function loftStitched(profiles, heights, wasm) {
|
|
17378
|
+
const CORNER_TURN_DEG = 30;
|
|
17379
|
+
const SPAN_ANGLE_PER_RING_DEG = 3;
|
|
17380
|
+
const MAX_SPAN_SUBDIVISION = 24;
|
|
17381
|
+
function loftStitched(profiles, heights, wasm, options = {}) {
|
|
17231
17382
|
if (profiles.length < 2) return null;
|
|
17232
17383
|
const classified = profiles.map((loops) => classifyLoops(loops));
|
|
17233
17384
|
const outerCount = classified[0].outers.length;
|
|
@@ -17242,7 +17393,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
17242
17393
|
const holeGroups = holeCount > 0 ? matchLoopsAcrossProfiles(classified.map((c2) => c2.holes)) : [];
|
|
17243
17394
|
const outerSolids = [];
|
|
17244
17395
|
for (const group of outerGroups) {
|
|
17245
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
17396
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
17246
17397
|
if (!solid) {
|
|
17247
17398
|
for (const s of outerSolids) s.delete();
|
|
17248
17399
|
return null;
|
|
@@ -17259,7 +17410,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
17259
17410
|
if (holeGroups.length > 0) {
|
|
17260
17411
|
const holeSolids = [];
|
|
17261
17412
|
for (const group of holeGroups) {
|
|
17262
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
17413
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
17263
17414
|
if (!solid) {
|
|
17264
17415
|
result.delete();
|
|
17265
17416
|
for (const s of holeSolids) s.delete();
|
|
@@ -17345,68 +17496,527 @@ function signedArea$3(loop) {
|
|
|
17345
17496
|
}
|
|
17346
17497
|
return area * 0.5;
|
|
17347
17498
|
}
|
|
17348
|
-
function
|
|
17499
|
+
function detectCorners(loop) {
|
|
17500
|
+
const corners = [];
|
|
17501
|
+
const n = loop.length;
|
|
17502
|
+
const threshold = CORNER_TURN_DEG * Math.PI / 180;
|
|
17503
|
+
for (let i = 0; i < n; i++) {
|
|
17504
|
+
const prev = loop[(i - 1 + n) % n];
|
|
17505
|
+
const curr = loop[i];
|
|
17506
|
+
const next = loop[(i + 1) % n];
|
|
17507
|
+
const ax = curr[0] - prev[0];
|
|
17508
|
+
const ay = curr[1] - prev[1];
|
|
17509
|
+
const bx = next[0] - curr[0];
|
|
17510
|
+
const by = next[1] - curr[1];
|
|
17511
|
+
const la = Math.hypot(ax, ay);
|
|
17512
|
+
const lb = Math.hypot(bx, by);
|
|
17513
|
+
if (la < 1e-12 || lb < 1e-12) continue;
|
|
17514
|
+
const dot2 = (ax * bx + ay * by) / (la * lb);
|
|
17515
|
+
const turn = Math.acos(Math.min(1, Math.max(-1, dot2)));
|
|
17516
|
+
if (turn > threshold) corners.push(i);
|
|
17517
|
+
}
|
|
17518
|
+
return corners;
|
|
17519
|
+
}
|
|
17520
|
+
function cumulativeArcLength(loop) {
|
|
17521
|
+
const dists = [0];
|
|
17522
|
+
for (let i = 0; i < loop.length; i++) {
|
|
17523
|
+
const p1 = loop[i];
|
|
17524
|
+
const p2 = loop[(i + 1) % loop.length];
|
|
17525
|
+
dists.push(dists[i] + Math.hypot(p2[0] - p1[0], p2[1] - p1[1]));
|
|
17526
|
+
}
|
|
17527
|
+
return { dists, total: dists[loop.length] };
|
|
17528
|
+
}
|
|
17529
|
+
function pointAtArcLength(loop, dists, total, s) {
|
|
17530
|
+
if (total < 1e-12) return [loop[0][0], loop[0][1]];
|
|
17531
|
+
let target = s % total;
|
|
17532
|
+
if (target < 0) target += total;
|
|
17533
|
+
let low = 0;
|
|
17534
|
+
let high = dists.length - 1;
|
|
17535
|
+
while (low < high) {
|
|
17536
|
+
const mid = low + high >> 1;
|
|
17537
|
+
if (dists[mid] <= target) low = mid + 1;
|
|
17538
|
+
else high = mid;
|
|
17539
|
+
}
|
|
17540
|
+
const seg = low - 1;
|
|
17541
|
+
const segLen = dists[seg + 1] - dists[seg];
|
|
17542
|
+
const t = segLen < 1e-12 ? 0 : (target - dists[seg]) / segLen;
|
|
17543
|
+
const p1 = loop[seg % loop.length];
|
|
17544
|
+
const p2 = loop[(seg + 1) % loop.length];
|
|
17545
|
+
return [p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t];
|
|
17546
|
+
}
|
|
17547
|
+
function refineParams(params, rangeEnd, maxGap) {
|
|
17548
|
+
if (!Number.isFinite(maxGap)) return params;
|
|
17549
|
+
const out = [];
|
|
17550
|
+
for (let i = 0; i < params.length; i++) {
|
|
17551
|
+
const a2 = params[i];
|
|
17552
|
+
const b = i + 1 < params.length ? params[i + 1] : rangeEnd;
|
|
17553
|
+
out.push(a2);
|
|
17554
|
+
const gap = b - a2;
|
|
17555
|
+
if (gap > maxGap) {
|
|
17556
|
+
const pieces = Math.min(64, Math.ceil(gap / maxGap));
|
|
17557
|
+
for (let k2 = 1; k2 < pieces; k2++) out.push(a2 + gap * k2 / pieces);
|
|
17558
|
+
}
|
|
17559
|
+
}
|
|
17560
|
+
return out;
|
|
17561
|
+
}
|
|
17562
|
+
function turnAngle(prev, curr, next) {
|
|
17563
|
+
const ax = curr[0] - prev[0];
|
|
17564
|
+
const ay = curr[1] - prev[1];
|
|
17565
|
+
const bx = next[0] - curr[0];
|
|
17566
|
+
const by = next[1] - curr[1];
|
|
17567
|
+
const la = Math.hypot(ax, ay);
|
|
17568
|
+
const lb = Math.hypot(bx, by);
|
|
17569
|
+
if (la < 1e-12 || lb < 1e-12) return 0;
|
|
17570
|
+
const dot2 = (ax * bx + ay * by) / (la * lb);
|
|
17571
|
+
return Math.acos(Math.min(1, Math.max(-1, dot2)));
|
|
17572
|
+
}
|
|
17573
|
+
function maxTurnPerColumn(rings) {
|
|
17574
|
+
const N = rings[0].length;
|
|
17575
|
+
const out = new Float64Array(N);
|
|
17576
|
+
for (const ring of rings) {
|
|
17577
|
+
for (let j = 0; j < N; j++) {
|
|
17578
|
+
const turn = turnAngle(ring[(j - 1 + N) % N], ring[j], ring[(j + 1) % N]);
|
|
17579
|
+
if (turn > out[j]) out[j] = turn;
|
|
17580
|
+
}
|
|
17581
|
+
}
|
|
17582
|
+
return out;
|
|
17583
|
+
}
|
|
17584
|
+
const CURVE_TURN_EPS = 0.5 * Math.PI / 180;
|
|
17585
|
+
function maxQuadDeviation(rings, heights, colA, colB) {
|
|
17586
|
+
let worst = 0;
|
|
17587
|
+
for (let i = 0; i < rings.length - 1; i++) {
|
|
17588
|
+
const a2 = [rings[i][colA][0], rings[i][colA][1], heights[i]];
|
|
17589
|
+
const b = [rings[i][colB][0], rings[i][colB][1], heights[i]];
|
|
17590
|
+
const c2 = [rings[i + 1][colB][0], rings[i + 1][colB][1], heights[i + 1]];
|
|
17591
|
+
const d2 = [rings[i + 1][colA][0], rings[i + 1][colA][1], heights[i + 1]];
|
|
17592
|
+
const n = cross3$5(sub3$1(b, a2), sub3$1(d2, a2));
|
|
17593
|
+
const len = Math.hypot(n[0], n[1], n[2]);
|
|
17594
|
+
if (len < 1e-12) continue;
|
|
17595
|
+
const deviation = Math.abs((n[0] * (c2[0] - a2[0]) + n[1] * (c2[1] - a2[1]) + n[2] * (c2[2] - a2[2])) / len);
|
|
17596
|
+
if (deviation > worst) worst = deviation;
|
|
17597
|
+
}
|
|
17598
|
+
return worst;
|
|
17599
|
+
}
|
|
17600
|
+
function buildCompatibleRings(loops, heights, edgeLength2) {
|
|
17601
|
+
const cornerSets = loops.map(detectCorners);
|
|
17602
|
+
const cornerCount = cornerSets[0].length;
|
|
17603
|
+
const cornersMatch = cornerCount > 0 && cornerSets.every((c2) => c2.length === cornerCount);
|
|
17604
|
+
if (cornersMatch) {
|
|
17605
|
+
const anchored = buildCornerAnchoredRings(loops, heights, cornerSets, edgeLength2);
|
|
17606
|
+
if (anchored) return anchored;
|
|
17607
|
+
}
|
|
17608
|
+
return buildSeamAlignedRings(loops, heights, edgeLength2);
|
|
17609
|
+
}
|
|
17610
|
+
function buildCornerAnchoredRings(loops, heights, cornerSets, edgeLength2) {
|
|
17611
|
+
const cornerCount = cornerSets[0].length;
|
|
17612
|
+
const arcs = loops.map((loop) => cumulativeArcLength(loop));
|
|
17613
|
+
const alignedCorners = [cornerSets[0]];
|
|
17614
|
+
for (let i = 1; i < loops.length; i++) {
|
|
17615
|
+
const prev = alignedCorners[i - 1];
|
|
17616
|
+
const prevLoop = loops[i - 1];
|
|
17617
|
+
const curr = cornerSets[i];
|
|
17618
|
+
const loop = loops[i];
|
|
17619
|
+
let best = curr;
|
|
17620
|
+
let bestCost = Infinity;
|
|
17621
|
+
for (let r = 0; r < cornerCount; r++) {
|
|
17622
|
+
const rotated = curr.map((_2, k2) => curr[(k2 + r) % cornerCount]);
|
|
17623
|
+
let cost = 0;
|
|
17624
|
+
for (let k2 = 0; k2 < cornerCount; k2++) {
|
|
17625
|
+
const a2 = prevLoop[prev[k2]];
|
|
17626
|
+
const b = loop[rotated[k2]];
|
|
17627
|
+
cost += (a2[0] - b[0]) ** 2 + (a2[1] - b[1]) ** 2;
|
|
17628
|
+
}
|
|
17629
|
+
if (cost < bestCost) {
|
|
17630
|
+
bestCost = cost;
|
|
17631
|
+
best = rotated;
|
|
17632
|
+
}
|
|
17633
|
+
}
|
|
17634
|
+
alignedCorners.push(best);
|
|
17635
|
+
}
|
|
17636
|
+
const segParams = [];
|
|
17637
|
+
const segLengths = [];
|
|
17638
|
+
for (let i = 0; i < loops.length; i++) {
|
|
17639
|
+
const loop = loops[i];
|
|
17640
|
+
const { dists, total } = arcs[i];
|
|
17641
|
+
if (total < 1e-9) return null;
|
|
17642
|
+
const corners = alignedCorners[i];
|
|
17643
|
+
const stationSegs = [];
|
|
17644
|
+
const stationLens = [];
|
|
17645
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
17646
|
+
const from = corners[s];
|
|
17647
|
+
const to = corners[(s + 1) % cornerCount];
|
|
17648
|
+
const start = dists[from];
|
|
17649
|
+
let end = dists[to];
|
|
17650
|
+
if (to <= from) end += total;
|
|
17651
|
+
const len = end - start;
|
|
17652
|
+
if (len < 1e-9) return null;
|
|
17653
|
+
const interior = [];
|
|
17654
|
+
const n = loop.length;
|
|
17655
|
+
for (let v = (from + 1) % n; v !== to; v = (v + 1) % n) {
|
|
17656
|
+
let d2 = dists[v];
|
|
17657
|
+
if (d2 < start) d2 += total;
|
|
17658
|
+
const p2 = (d2 - start) / len;
|
|
17659
|
+
if (p2 > 1e-9 && p2 < 1 - 1e-9) interior.push(p2);
|
|
17660
|
+
}
|
|
17661
|
+
stationSegs.push(interior);
|
|
17662
|
+
stationLens.push(len);
|
|
17663
|
+
}
|
|
17664
|
+
segParams.push(stationSegs);
|
|
17665
|
+
segLengths.push(stationLens);
|
|
17666
|
+
}
|
|
17667
|
+
let masterParams = [];
|
|
17668
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
17669
|
+
let bestStation = 0;
|
|
17670
|
+
for (let i = 1; i < loops.length; i++) {
|
|
17671
|
+
if (segParams[i][s].length > segParams[bestStation][s].length) bestStation = i;
|
|
17672
|
+
}
|
|
17673
|
+
masterParams.push([0, ...segParams[bestStation][s]]);
|
|
17674
|
+
}
|
|
17675
|
+
const sampleRings = (paramsBySegment) => {
|
|
17676
|
+
const rings2 = [];
|
|
17677
|
+
for (let i = 0; i < loops.length; i++) {
|
|
17678
|
+
const loop = loops[i];
|
|
17679
|
+
const { dists, total } = arcs[i];
|
|
17680
|
+
const corners = alignedCorners[i];
|
|
17681
|
+
const ring = [];
|
|
17682
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
17683
|
+
const from = corners[s];
|
|
17684
|
+
const to = corners[(s + 1) % cornerCount];
|
|
17685
|
+
const start = dists[from];
|
|
17686
|
+
let end = dists[to];
|
|
17687
|
+
if (to <= from) end += total;
|
|
17688
|
+
const len = end - start;
|
|
17689
|
+
for (const p2 of paramsBySegment[s]) {
|
|
17690
|
+
if (p2 === 0) {
|
|
17691
|
+
ring.push([loop[from][0], loop[from][1]]);
|
|
17692
|
+
} else {
|
|
17693
|
+
ring.push(pointAtArcLength(loop, dists, total, start + p2 * len));
|
|
17694
|
+
}
|
|
17695
|
+
}
|
|
17696
|
+
}
|
|
17697
|
+
rings2.push(ring);
|
|
17698
|
+
}
|
|
17699
|
+
return rings2;
|
|
17700
|
+
};
|
|
17701
|
+
let rings = sampleRings(masterParams);
|
|
17702
|
+
if (edgeLength2 && edgeLength2 > 0) {
|
|
17703
|
+
const ringLength = rings[0].length;
|
|
17704
|
+
const turns = maxTurnPerColumn(rings);
|
|
17705
|
+
const segStart = [];
|
|
17706
|
+
{
|
|
17707
|
+
let col = 0;
|
|
17708
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
17709
|
+
segStart.push(col);
|
|
17710
|
+
col += masterParams[s].length;
|
|
17711
|
+
}
|
|
17712
|
+
}
|
|
17713
|
+
let changed = false;
|
|
17714
|
+
const refined = [];
|
|
17715
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
17716
|
+
const params = masterParams[s];
|
|
17717
|
+
let maxLen = 0;
|
|
17718
|
+
for (let i = 0; i < loops.length; i++) maxLen = Math.max(maxLen, segLengths[i][s]);
|
|
17719
|
+
const out = [];
|
|
17720
|
+
for (let k2 = 0; k2 < params.length; k2++) {
|
|
17721
|
+
const a2 = params[k2];
|
|
17722
|
+
const b = k2 + 1 < params.length ? params[k2 + 1] : 1;
|
|
17723
|
+
out.push(a2);
|
|
17724
|
+
const gapLen = (b - a2) * maxLen;
|
|
17725
|
+
if (gapLen <= edgeLength2) continue;
|
|
17726
|
+
const colA = segStart[s] + k2;
|
|
17727
|
+
const colB = k2 + 1 < params.length ? colA + 1 : segStart[(s + 1) % cornerCount] % ringLength;
|
|
17728
|
+
const curved = k2 > 0 && turns[colA] > CURVE_TURN_EPS || k2 + 1 < params.length && turns[colB] > CURVE_TURN_EPS;
|
|
17729
|
+
const twisted = !curved && maxQuadDeviation(rings, heights, colA, colB) > edgeLength2 * 0.05;
|
|
17730
|
+
if (!curved && !twisted) continue;
|
|
17731
|
+
const pieces = Math.min(64, Math.ceil(gapLen / edgeLength2));
|
|
17732
|
+
for (let p2 = 1; p2 < pieces; p2++) out.push(a2 + (b - a2) * p2 / pieces);
|
|
17733
|
+
changed = true;
|
|
17734
|
+
}
|
|
17735
|
+
refined.push(out);
|
|
17736
|
+
}
|
|
17737
|
+
if (changed) {
|
|
17738
|
+
masterParams = refined;
|
|
17739
|
+
rings = sampleRings(masterParams);
|
|
17740
|
+
}
|
|
17741
|
+
}
|
|
17742
|
+
const cornerColumns = [];
|
|
17743
|
+
{
|
|
17744
|
+
let col = 0;
|
|
17745
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
17746
|
+
cornerColumns.push(col);
|
|
17747
|
+
col += masterParams[s].length;
|
|
17748
|
+
}
|
|
17749
|
+
}
|
|
17750
|
+
return { rings, cornerColumns };
|
|
17751
|
+
}
|
|
17752
|
+
function buildSeamAlignedRings(loops, _heights, edgeLength2) {
|
|
17753
|
+
const arcs = loops.map((loop) => cumulativeArcLength(loop));
|
|
17754
|
+
let bestStation = 0;
|
|
17755
|
+
for (let i = 1; i < loops.length; i++) {
|
|
17756
|
+
if (loops[i].length > loops[bestStation].length) bestStation = i;
|
|
17757
|
+
}
|
|
17758
|
+
const { dists: masterDists, total: masterTotal } = arcs[bestStation];
|
|
17759
|
+
if (masterTotal < 1e-9) return null;
|
|
17760
|
+
let params = loops[bestStation].map((_2, v) => masterDists[v] / masterTotal);
|
|
17761
|
+
if (params.length < 24) {
|
|
17762
|
+
params = refineParams(params, 1, 1 / 24);
|
|
17763
|
+
}
|
|
17764
|
+
const sampleRings = (paramList) => loops.map((loop, i) => {
|
|
17765
|
+
const { dists, total } = arcs[i];
|
|
17766
|
+
if (total < 1e-9) {
|
|
17767
|
+
return paramList.map(() => [loop[0][0], loop[0][1]]);
|
|
17768
|
+
}
|
|
17769
|
+
return paramList.map((p2) => pointAtArcLength(loop, dists, total, p2 * total));
|
|
17770
|
+
});
|
|
17771
|
+
let rings = sampleRings(params);
|
|
17772
|
+
if (edgeLength2 && edgeLength2 > 0) {
|
|
17773
|
+
let maxPerimeter = 0;
|
|
17774
|
+
for (const { total } of arcs) maxPerimeter = Math.max(maxPerimeter, total);
|
|
17775
|
+
const turns = maxTurnPerColumn(rings);
|
|
17776
|
+
const N0 = params.length;
|
|
17777
|
+
const out = [];
|
|
17778
|
+
let changed = false;
|
|
17779
|
+
for (let k2 = 0; k2 < N0; k2++) {
|
|
17780
|
+
const a2 = params[k2];
|
|
17781
|
+
const b = k2 + 1 < N0 ? params[k2 + 1] : 1;
|
|
17782
|
+
out.push(a2);
|
|
17783
|
+
const gapLen = (b - a2) * maxPerimeter;
|
|
17784
|
+
if (gapLen <= edgeLength2) continue;
|
|
17785
|
+
if (turns[k2] <= CURVE_TURN_EPS && turns[(k2 + 1) % N0] <= CURVE_TURN_EPS) continue;
|
|
17786
|
+
const pieces = Math.min(64, Math.ceil(gapLen / edgeLength2));
|
|
17787
|
+
for (let p2 = 1; p2 < pieces; p2++) out.push(a2 + (b - a2) * p2 / pieces);
|
|
17788
|
+
changed = true;
|
|
17789
|
+
}
|
|
17790
|
+
if (changed) {
|
|
17791
|
+
params = out;
|
|
17792
|
+
rings = sampleRings(params);
|
|
17793
|
+
}
|
|
17794
|
+
}
|
|
17795
|
+
const N = params.length;
|
|
17796
|
+
for (let i = 1; i < rings.length; i++) {
|
|
17797
|
+
const prev = rings[i - 1];
|
|
17798
|
+
const curr = rings[i];
|
|
17799
|
+
let bestShift = 0;
|
|
17800
|
+
let bestCost = Infinity;
|
|
17801
|
+
for (let shift = 0; shift < N; shift++) {
|
|
17802
|
+
let cost = 0;
|
|
17803
|
+
for (let j = 0; j < N; j++) {
|
|
17804
|
+
const a2 = prev[j];
|
|
17805
|
+
const b = curr[(j + shift) % N];
|
|
17806
|
+
cost += (a2[0] - b[0]) ** 2 + (a2[1] - b[1]) ** 2;
|
|
17807
|
+
if (cost >= bestCost) break;
|
|
17808
|
+
}
|
|
17809
|
+
if (cost < bestCost) {
|
|
17810
|
+
bestCost = cost;
|
|
17811
|
+
bestShift = shift;
|
|
17812
|
+
}
|
|
17813
|
+
}
|
|
17814
|
+
if (bestShift !== 0) {
|
|
17815
|
+
rings[i] = curr.map((_2, j) => curr[(j + bestShift) % N]);
|
|
17816
|
+
}
|
|
17817
|
+
}
|
|
17818
|
+
return { rings, cornerColumns: [] };
|
|
17819
|
+
}
|
|
17820
|
+
function buildSpanRows(rings, heights) {
|
|
17821
|
+
const R = rings.length;
|
|
17822
|
+
const N = rings[0].length;
|
|
17823
|
+
const stations = rings.map((ring, i) => ring.map(([x2, y2]) => [x2, y2, heights[i]]));
|
|
17824
|
+
const t = [0];
|
|
17825
|
+
for (let i = 0; i < R - 1; i++) {
|
|
17826
|
+
let sum2 = 0;
|
|
17827
|
+
for (let j = 0; j < N; j++) {
|
|
17828
|
+
sum2 += dist3(stations[i][j], stations[i + 1][j]);
|
|
17829
|
+
}
|
|
17830
|
+
const avg = Math.max(sum2 / N, 1e-9);
|
|
17831
|
+
t.push(t[i] + Math.sqrt(avg));
|
|
17832
|
+
}
|
|
17833
|
+
const tangents = [];
|
|
17834
|
+
for (let i = 0; i < R; i++) {
|
|
17835
|
+
const row = [];
|
|
17836
|
+
for (let j = 0; j < N; j++) {
|
|
17837
|
+
row.push(stationTangent(stations, t, i, j));
|
|
17838
|
+
}
|
|
17839
|
+
tangents.push(row);
|
|
17840
|
+
}
|
|
17841
|
+
const rows = [{ points: stations[0], tangents: tangents[0], isStation: true }];
|
|
17842
|
+
for (let i = 0; i < R - 1; i++) {
|
|
17843
|
+
const h = t[i + 1] - t[i];
|
|
17844
|
+
let maxAngle = 0;
|
|
17845
|
+
const stride = Math.max(1, Math.floor(N / 16));
|
|
17846
|
+
for (let j = 0; j < N; j += stride) {
|
|
17847
|
+
maxAngle = Math.max(maxAngle, angleBetween(tangents[i][j], tangents[i + 1][j]));
|
|
17848
|
+
}
|
|
17849
|
+
const k2 = Math.min(MAX_SPAN_SUBDIVISION, Math.max(1, Math.ceil(maxAngle / SPAN_ANGLE_PER_RING_DEG)));
|
|
17850
|
+
for (let s = 1; s < k2; s++) {
|
|
17851
|
+
const u2 = s / k2;
|
|
17852
|
+
const points = [];
|
|
17853
|
+
const rowTangents = [];
|
|
17854
|
+
for (let j = 0; j < N; j++) {
|
|
17855
|
+
const { point, tangent } = hermite(stations[i][j], tangents[i][j], stations[i + 1][j], tangents[i + 1][j], h, u2);
|
|
17856
|
+
points.push(point);
|
|
17857
|
+
rowTangents.push(tangent);
|
|
17858
|
+
}
|
|
17859
|
+
rows.push({ points, tangents: rowTangents, isStation: false });
|
|
17860
|
+
}
|
|
17861
|
+
rows.push({ points: stations[i + 1], tangents: tangents[i + 1], isStation: true });
|
|
17862
|
+
}
|
|
17863
|
+
return rows;
|
|
17864
|
+
}
|
|
17865
|
+
function stationTangent(stations, t, i, j) {
|
|
17866
|
+
const R = stations.length;
|
|
17867
|
+
if (i === 0) {
|
|
17868
|
+
return scale3$1(sub3$1(stations[1][j], stations[0][j]), 1 / (t[1] - t[0]));
|
|
17869
|
+
}
|
|
17870
|
+
if (i === R - 1) {
|
|
17871
|
+
return scale3$1(sub3$1(stations[R - 1][j], stations[R - 2][j]), 1 / (t[R - 1] - t[R - 2]));
|
|
17872
|
+
}
|
|
17873
|
+
const hPrev = t[i] - t[i - 1];
|
|
17874
|
+
const hNext = t[i + 1] - t[i];
|
|
17875
|
+
const dPrev = scale3$1(sub3$1(stations[i][j], stations[i - 1][j]), 1 / hPrev);
|
|
17876
|
+
const dNext = scale3$1(sub3$1(stations[i + 1][j], stations[i][j]), 1 / hNext);
|
|
17877
|
+
return scale3$1(add3$1(scale3$1(dPrev, hNext), scale3$1(dNext, hPrev)), 1 / (hPrev + hNext));
|
|
17878
|
+
}
|
|
17879
|
+
function hermite(p0, m0, p1, m1, h, u2) {
|
|
17880
|
+
const u22 = u2 * u2;
|
|
17881
|
+
const u3 = u22 * u2;
|
|
17882
|
+
const h00 = 2 * u3 - 3 * u22 + 1;
|
|
17883
|
+
const h10 = u3 - 2 * u22 + u2;
|
|
17884
|
+
const h01 = -2 * u3 + 3 * u22;
|
|
17885
|
+
const h11 = u3 - u22;
|
|
17886
|
+
const d00 = 6 * u22 - 6 * u2;
|
|
17887
|
+
const d10 = 3 * u22 - 4 * u2 + 1;
|
|
17888
|
+
const d01 = -6 * u22 + 6 * u2;
|
|
17889
|
+
const d11 = 3 * u22 - 2 * u2;
|
|
17890
|
+
const point = [
|
|
17891
|
+
h00 * p0[0] + h10 * h * m0[0] + h01 * p1[0] + h11 * h * m1[0],
|
|
17892
|
+
h00 * p0[1] + h10 * h * m0[1] + h01 * p1[1] + h11 * h * m1[1],
|
|
17893
|
+
h00 * p0[2] + h10 * h * m0[2] + h01 * p1[2] + h11 * h * m1[2]
|
|
17894
|
+
];
|
|
17895
|
+
const tangent = [
|
|
17896
|
+
d00 * p0[0] / h + d10 * m0[0] + d01 * p1[0] / h + d11 * m1[0],
|
|
17897
|
+
d00 * p0[1] / h + d10 * m0[1] + d01 * p1[1] / h + d11 * m1[1],
|
|
17898
|
+
d00 * p0[2] / h + d10 * m0[2] + d01 * p1[2] / h + d11 * m1[2]
|
|
17899
|
+
];
|
|
17900
|
+
return { point, tangent };
|
|
17901
|
+
}
|
|
17902
|
+
function stitchSingleLoopLoft(loops, heights, wasm, options) {
|
|
17349
17903
|
const normalizedLoops = loops.map((loop) => {
|
|
17350
17904
|
const area = signedArea$3(loop);
|
|
17351
17905
|
return area < 0 ? [...loop].reverse() : loop;
|
|
17352
17906
|
});
|
|
17353
|
-
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
|
|
17357
|
-
|
|
17358
|
-
const
|
|
17359
|
-
const
|
|
17360
|
-
const
|
|
17361
|
-
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
const
|
|
17366
|
-
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17907
|
+
const compatible = buildCompatibleRings(normalizedLoops, heights, options.edgeLength);
|
|
17908
|
+
if (!compatible) return null;
|
|
17909
|
+
const { rings, cornerColumns } = compatible;
|
|
17910
|
+
const N = rings[0].length;
|
|
17911
|
+
if (N < 3) return null;
|
|
17912
|
+
const rows = buildSpanRows(rings, heights);
|
|
17913
|
+
const R = rows.length;
|
|
17914
|
+
const cornerSet = new Set(cornerColumns);
|
|
17915
|
+
const vertProps = [];
|
|
17916
|
+
let vertCount = 0;
|
|
17917
|
+
const fwdIdx = [];
|
|
17918
|
+
const bwdIdx = [];
|
|
17919
|
+
const pushVert = (p2, n) => {
|
|
17920
|
+
vertProps.push(p2[0], p2[1], p2[2], n[0], n[1], n[2]);
|
|
17921
|
+
return vertCount++;
|
|
17922
|
+
};
|
|
17923
|
+
for (let r = 0; r < R; r++) {
|
|
17924
|
+
const { points, tangents } = rows[r];
|
|
17925
|
+
const fwd = new Array(N);
|
|
17926
|
+
const bwd = new Array(N);
|
|
17927
|
+
for (let j = 0; j < N; j++) {
|
|
17928
|
+
const prev = points[(j - 1 + N) % N];
|
|
17929
|
+
const curr = points[j];
|
|
17930
|
+
const next = points[(j + 1) % N];
|
|
17931
|
+
if (cornerSet.has(j)) {
|
|
17932
|
+
const nFwd = surfaceNormal(sub3$1(next, curr), tangents[j]);
|
|
17933
|
+
const nBwd = surfaceNormal(sub3$1(curr, prev), tangents[j]);
|
|
17934
|
+
fwd[j] = pushVert(curr, nFwd);
|
|
17935
|
+
bwd[j] = pushVert(curr, nBwd);
|
|
17936
|
+
} else {
|
|
17937
|
+
const idx = pushVert(curr, surfaceNormal(sub3$1(next, prev), tangents[j]));
|
|
17938
|
+
fwd[j] = idx;
|
|
17939
|
+
bwd[j] = idx;
|
|
17940
|
+
}
|
|
17370
17941
|
}
|
|
17942
|
+
fwdIdx.push(fwd);
|
|
17943
|
+
bwdIdx.push(bwd);
|
|
17371
17944
|
}
|
|
17372
|
-
|
|
17373
|
-
|
|
17374
|
-
const nextIdx = (i + 1) * N;
|
|
17945
|
+
const triangles = [];
|
|
17946
|
+
for (let r = 0; r < R - 1; r++) {
|
|
17375
17947
|
for (let j = 0; j < N; j++) {
|
|
17376
17948
|
const j1 = (j + 1) % N;
|
|
17377
|
-
const v0 =
|
|
17378
|
-
const
|
|
17379
|
-
const v2 =
|
|
17380
|
-
const
|
|
17949
|
+
const v0 = fwdIdx[r][j];
|
|
17950
|
+
const v3 = bwdIdx[r][j1];
|
|
17951
|
+
const v2 = bwdIdx[r + 1][j1];
|
|
17952
|
+
const v1 = fwdIdx[r + 1][j];
|
|
17381
17953
|
triangles.push(v0, v3, v2);
|
|
17382
17954
|
triangles.push(v0, v2, v1);
|
|
17383
17955
|
}
|
|
17384
17956
|
}
|
|
17385
|
-
const
|
|
17386
|
-
const
|
|
17387
|
-
|
|
17957
|
+
const bottomRing = rows[0].points;
|
|
17958
|
+
const topRing = rows[R - 1].points;
|
|
17959
|
+
const bottom2D = bottomRing.map(([x2, y2]) => [x2, y2]);
|
|
17960
|
+
const bottomTris = wasm.triangulate([bottom2D]);
|
|
17961
|
+
const bottomBase = vertCount;
|
|
17962
|
+
for (const p2 of bottomRing) pushVert(p2, [0, 0, -1]);
|
|
17963
|
+
for (const tri of bottomTris) {
|
|
17388
17964
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
17389
|
-
triangles.push(v0, v2, v1);
|
|
17965
|
+
triangles.push(bottomBase + v0, bottomBase + v2, bottomBase + v1);
|
|
17390
17966
|
}
|
|
17391
|
-
const
|
|
17392
|
-
const
|
|
17393
|
-
const
|
|
17394
|
-
for (const
|
|
17967
|
+
const top2D = topRing.map(([x2, y2]) => [x2, y2]);
|
|
17968
|
+
const topTris = wasm.triangulate([top2D]);
|
|
17969
|
+
const topBase = vertCount;
|
|
17970
|
+
for (const p2 of topRing) pushVert(p2, [0, 0, 1]);
|
|
17971
|
+
for (const tri of topTris) {
|
|
17395
17972
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
17396
|
-
triangles.push(
|
|
17973
|
+
triangles.push(topBase + v0, topBase + v1, topBase + v2);
|
|
17397
17974
|
}
|
|
17398
17975
|
const mesh = new wasm.Mesh({
|
|
17399
|
-
numProp:
|
|
17400
|
-
vertProperties: new Float32Array(
|
|
17976
|
+
numProp: 6,
|
|
17977
|
+
vertProperties: new Float32Array(vertProps),
|
|
17401
17978
|
triVerts: new Uint32Array(triangles)
|
|
17402
17979
|
});
|
|
17403
17980
|
try {
|
|
17981
|
+
mesh.merge();
|
|
17404
17982
|
const manifold = new wasm.Manifold(mesh);
|
|
17405
17983
|
return manifold;
|
|
17406
17984
|
} catch (_e2) {
|
|
17407
17985
|
return null;
|
|
17408
17986
|
}
|
|
17409
17987
|
}
|
|
17988
|
+
function surfaceNormal(chord, span) {
|
|
17989
|
+
const n = cross3$5(chord, span);
|
|
17990
|
+
const len = Math.hypot(n[0], n[1], n[2]);
|
|
17991
|
+
if (len < 1e-12) {
|
|
17992
|
+
const radial = Math.hypot(chord[0], chord[1]);
|
|
17993
|
+
if (radial > 1e-12) return [chord[1] / radial, -chord[0] / radial, 0];
|
|
17994
|
+
return [0, 0, 1];
|
|
17995
|
+
}
|
|
17996
|
+
return [n[0] / len, n[1] / len, n[2] / len];
|
|
17997
|
+
}
|
|
17998
|
+
function sub3$1(a2, b) {
|
|
17999
|
+
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
18000
|
+
}
|
|
18001
|
+
function add3$1(a2, b) {
|
|
18002
|
+
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
18003
|
+
}
|
|
18004
|
+
function scale3$1(a2, s) {
|
|
18005
|
+
return [a2[0] * s, a2[1] * s, a2[2] * s];
|
|
18006
|
+
}
|
|
18007
|
+
function cross3$5(a2, b) {
|
|
18008
|
+
return [a2[1] * b[2] - a2[2] * b[1], a2[2] * b[0] - a2[0] * b[2], a2[0] * b[1] - a2[1] * b[0]];
|
|
18009
|
+
}
|
|
18010
|
+
function dist3(a2, b) {
|
|
18011
|
+
return Math.hypot(a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]);
|
|
18012
|
+
}
|
|
18013
|
+
function angleBetween(a2, b) {
|
|
18014
|
+
const la = Math.hypot(a2[0], a2[1], a2[2]);
|
|
18015
|
+
const lb = Math.hypot(b[0], b[1], b[2]);
|
|
18016
|
+
if (la < 1e-12 || lb < 1e-12) return 0;
|
|
18017
|
+
const dot2 = (a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]) / (la * lb);
|
|
18018
|
+
return Math.acos(Math.min(1, Math.max(-1, dot2))) * 180 / Math.PI;
|
|
18019
|
+
}
|
|
17410
18020
|
let _wasm$1 = null;
|
|
17411
18021
|
async function initManifoldWasm() {
|
|
17412
18022
|
if (_wasm$1) return _wasm$1;
|
|
@@ -17512,7 +18122,9 @@ const _ManifoldShapeBackend = class _ManifoldShapeBackend {
|
|
|
17512
18122
|
return this.getLiveManifold("numTri()").numTri();
|
|
17513
18123
|
}
|
|
17514
18124
|
getMesh() {
|
|
17515
|
-
|
|
18125
|
+
const manifold = this.getLiveManifold("getMesh()");
|
|
18126
|
+
const mesh = manifold.numProp() >= 3 ? manifold.getMesh(0) : manifold.getMesh();
|
|
18127
|
+
return mesh;
|
|
17516
18128
|
}
|
|
17517
18129
|
slice(offset) {
|
|
17518
18130
|
return this.getLiveManifold("slice()").slice(offset);
|
|
@@ -17561,6 +18173,43 @@ function requireManifoldShapeBackend(backend, apiName = "requireManifoldShapeBac
|
|
|
17561
18173
|
}
|
|
17562
18174
|
throw new Error(`${apiName} currently requires a Manifold-backed runtime shape.`);
|
|
17563
18175
|
}
|
|
18176
|
+
function resamplePolygon(poly, targetCount) {
|
|
18177
|
+
if (poly.length < 2) return poly;
|
|
18178
|
+
if (targetCount <= 0) return [];
|
|
18179
|
+
const dists = [0];
|
|
18180
|
+
for (let i = 0; i < poly.length; i++) {
|
|
18181
|
+
const p1 = poly[i];
|
|
18182
|
+
const p2 = poly[(i + 1) % poly.length];
|
|
18183
|
+
const dx = p2[0] - p1[0];
|
|
18184
|
+
const dy = p2[1] - p1[1];
|
|
18185
|
+
const d2 = Math.sqrt(dx * dx + dy * dy);
|
|
18186
|
+
dists.push(dists[dists.length - 1] + d2);
|
|
18187
|
+
}
|
|
18188
|
+
const totalDist = dists[dists.length - 1];
|
|
18189
|
+
if (totalDist < 1e-12) {
|
|
18190
|
+
return Array.from({ length: targetCount }, () => [poly[0][0], poly[0][1]]);
|
|
18191
|
+
}
|
|
18192
|
+
const out = [];
|
|
18193
|
+
for (let i = 0; i < targetCount; i++) {
|
|
18194
|
+
const targetDist = i / targetCount * totalDist;
|
|
18195
|
+
let low = 0;
|
|
18196
|
+
let high = dists.length - 1;
|
|
18197
|
+
while (low < high) {
|
|
18198
|
+
const mid = low + high >> 1;
|
|
18199
|
+
if (dists[mid] <= targetDist) {
|
|
18200
|
+
low = mid + 1;
|
|
18201
|
+
} else {
|
|
18202
|
+
high = mid;
|
|
18203
|
+
}
|
|
18204
|
+
}
|
|
18205
|
+
const seg = low - 1;
|
|
18206
|
+
const t = (targetDist - dists[seg]) / (dists[seg + 1] - dists[seg]);
|
|
18207
|
+
const p1 = poly[seg % poly.length];
|
|
18208
|
+
const p2 = poly[(seg + 1) % poly.length];
|
|
18209
|
+
out.push([p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t]);
|
|
18210
|
+
}
|
|
18211
|
+
return out;
|
|
18212
|
+
}
|
|
17564
18213
|
function sweepStitched(profilePolygons, pathPoints, up, wasm) {
|
|
17565
18214
|
if (pathPoints.length < 2) return null;
|
|
17566
18215
|
if (profilePolygons.length === 0) return null;
|
|
@@ -18258,7 +18907,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
|
|
|
18258
18907
|
throw new Error("offsetSolid() collapsed the compatible-loft height span.");
|
|
18259
18908
|
}
|
|
18260
18909
|
const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
|
|
18261
|
-
const stitched = loftStitched(offsetPolygons, heights, wasm);
|
|
18910
|
+
const stitched = loftStitched(offsetPolygons, heights, wasm, { edgeLength: plan.edgeLength });
|
|
18262
18911
|
if (!stitched) {
|
|
18263
18912
|
throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
|
|
18264
18913
|
}
|
|
@@ -18393,12 +19042,12 @@ function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
|
18393
19042
|
disposeWasmObject(crossSection);
|
|
18394
19043
|
}
|
|
18395
19044
|
});
|
|
18396
|
-
if (inputPolygons.length >= 2) {
|
|
18397
|
-
const stitched = loftStitched(inputPolygons, plan.heights, wasm);
|
|
19045
|
+
if (!plan.forceField && inputPolygons.length >= 2) {
|
|
19046
|
+
const stitched = loftStitched(inputPolygons, plan.heights, wasm, { edgeLength: plan.edgeLength });
|
|
18398
19047
|
if (stitched) return stitched;
|
|
18399
19048
|
}
|
|
18400
19049
|
const input = buildLoftLevelSetInput(inputPolygons, plan.heights, { edgeLength: plan.edgeLength, boundsPadding: plan.boundsPadding });
|
|
18401
|
-
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
19050
|
+
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm, plan.meshing);
|
|
18402
19051
|
}
|
|
18403
19052
|
function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
18404
19053
|
const crossSection = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
@@ -18663,7 +19312,7 @@ function lowerFromSlicesToManifold(plan, wasm) {
|
|
|
18663
19312
|
}
|
|
18664
19313
|
});
|
|
18665
19314
|
const heights = sorted.map((s) => s.offset);
|
|
18666
|
-
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm) : null;
|
|
19315
|
+
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm, { edgeLength: plan.edgeLength }) : null;
|
|
18667
19316
|
if (stitched) {
|
|
18668
19317
|
solid = stitched;
|
|
18669
19318
|
} else {
|
|
@@ -19104,17 +19753,21 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
19104
19753
|
if (diagnostics) diagnostics.projectionMs = nowMs() - projectionStart;
|
|
19105
19754
|
const simplificationStart = nowMs();
|
|
19106
19755
|
if (((meshing == null ? void 0 : meshing.simplify) ?? "safe") !== "off" && snMesh.numTris > 100) {
|
|
19107
|
-
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties,
|
|
19756
|
+
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties, vertProps6, edgeLength2, meshing == null ? void 0 : meshing.maxTriangles);
|
|
19108
19757
|
}
|
|
19109
19758
|
if (diagnostics) {
|
|
19110
19759
|
diagnostics.simplificationMs = nowMs() - simplificationStart;
|
|
19111
19760
|
diagnostics.trianglesAfterSimplification = triVerts.length / 3;
|
|
19112
19761
|
}
|
|
19113
19762
|
if ((meshing == null ? void 0 : meshing.maxTriangles) !== void 0 && triVerts.length / 3 > meshing.maxTriangles) {
|
|
19763
|
+
const verb = (meshing.simplify ?? "safe") === "off" ? "produced" : "could only simplify to";
|
|
19114
19764
|
throw new Error(
|
|
19115
|
-
`SDF meshing
|
|
19765
|
+
`SDF meshing ${verb} ${triVerts.length / 3} safe triangles, above maxTriangles=${meshing.maxTriangles}. Increase maxTriangles or use a larger edgeLength.`
|
|
19116
19766
|
);
|
|
19117
19767
|
}
|
|
19768
|
+
if (!isClosedConsistentlyWoundTriangleMesh(triVerts, vertProps6.length / 6)) {
|
|
19769
|
+
throw new Error("SDF meshing produced an open, non-manifold, or inconsistently wound triangle mesh.");
|
|
19770
|
+
}
|
|
19118
19771
|
const wasmMesh = new wasm.Mesh({
|
|
19119
19772
|
numProp: 6,
|
|
19120
19773
|
vertProperties: vertProps6,
|
|
@@ -19132,28 +19785,102 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
19132
19785
|
disposeWasmObject(wasmMesh);
|
|
19133
19786
|
}
|
|
19134
19787
|
}
|
|
19135
|
-
function simplifySdfMesh(triVerts, vertProperties,
|
|
19788
|
+
function simplifySdfMesh(triVerts, vertProperties, finalVertProperties, edgeLength2, maxTriangles) {
|
|
19136
19789
|
const maxError = edgeLength2 * 0.15;
|
|
19137
19790
|
const inputTriangles = triVerts.length / 3;
|
|
19138
|
-
const
|
|
19139
|
-
const
|
|
19791
|
+
const ratios = buildSimplificationRatios(inputTriangles, maxTriangles);
|
|
19792
|
+
const vertexCount = finalVertProperties.length / 6;
|
|
19793
|
+
let bestValid = null;
|
|
19140
19794
|
for (const ratio of ratios) {
|
|
19141
|
-
let simplified
|
|
19142
|
-
simplified = filterDegenerateTriangles(simplified);
|
|
19143
|
-
let mesh = null;
|
|
19144
|
-
let manifold = null;
|
|
19795
|
+
let simplified;
|
|
19145
19796
|
try {
|
|
19146
|
-
|
|
19147
|
-
manifold = new wasm.Manifold(mesh);
|
|
19148
|
-
return simplified;
|
|
19797
|
+
simplified = simplifyMesh(triVerts, vertProperties, ratio, maxError);
|
|
19149
19798
|
} catch {
|
|
19150
|
-
|
|
19151
|
-
|
|
19152
|
-
|
|
19799
|
+
continue;
|
|
19800
|
+
}
|
|
19801
|
+
simplified = filterDegenerateTriangles(simplified);
|
|
19802
|
+
if (isClosedConsistentlyWoundTriangleMesh(simplified, vertexCount)) {
|
|
19803
|
+
if (maxTriangles === void 0 || simplified.length / 3 <= maxTriangles) {
|
|
19804
|
+
return simplified;
|
|
19805
|
+
}
|
|
19806
|
+
if (!bestValid || simplified.length < bestValid.length) {
|
|
19807
|
+
bestValid = simplified;
|
|
19808
|
+
}
|
|
19153
19809
|
}
|
|
19154
19810
|
}
|
|
19811
|
+
if (bestValid) {
|
|
19812
|
+
return bestValid;
|
|
19813
|
+
}
|
|
19155
19814
|
return triVerts;
|
|
19156
19815
|
}
|
|
19816
|
+
function buildSimplificationRatios(inputTriangles, maxTriangles) {
|
|
19817
|
+
if (maxTriangles === void 0 || maxTriangles >= inputTriangles) {
|
|
19818
|
+
return [0.5, 0.75];
|
|
19819
|
+
}
|
|
19820
|
+
const requestedRatio = Math.max(1 / inputTriangles, Math.min(0.5, maxTriangles / inputTriangles));
|
|
19821
|
+
const candidates = [
|
|
19822
|
+
requestedRatio,
|
|
19823
|
+
0.05,
|
|
19824
|
+
0.04,
|
|
19825
|
+
0.03,
|
|
19826
|
+
0.02,
|
|
19827
|
+
0.015,
|
|
19828
|
+
0.01,
|
|
19829
|
+
5e-3,
|
|
19830
|
+
requestedRatio * 0.85,
|
|
19831
|
+
requestedRatio * 0.7,
|
|
19832
|
+
requestedRatio * 0.55,
|
|
19833
|
+
requestedRatio * 0.4,
|
|
19834
|
+
requestedRatio * 0.25,
|
|
19835
|
+
requestedRatio * 0.1,
|
|
19836
|
+
0.5,
|
|
19837
|
+
0.75
|
|
19838
|
+
];
|
|
19839
|
+
const seen = /* @__PURE__ */ new Set();
|
|
19840
|
+
const ratios = [];
|
|
19841
|
+
for (const ratio of candidates) {
|
|
19842
|
+
const rounded = Number(Math.max(1 / inputTriangles, Math.min(0.75, ratio)).toFixed(6));
|
|
19843
|
+
if (seen.has(rounded)) continue;
|
|
19844
|
+
seen.add(rounded);
|
|
19845
|
+
ratios.push(rounded);
|
|
19846
|
+
}
|
|
19847
|
+
return ratios;
|
|
19848
|
+
}
|
|
19849
|
+
function isClosedConsistentlyWoundTriangleMesh(triVerts, vertexCount) {
|
|
19850
|
+
if (triVerts.length === 0 || triVerts.length % 3 !== 0) return false;
|
|
19851
|
+
const edgeUse = /* @__PURE__ */ new Map();
|
|
19852
|
+
for (let i = 0; i < triVerts.length; i += 3) {
|
|
19853
|
+
const a2 = triVerts[i];
|
|
19854
|
+
const b = triVerts[i + 1];
|
|
19855
|
+
const c2 = triVerts[i + 2];
|
|
19856
|
+
if (!isValidMeshIndex(a2, vertexCount) || !isValidMeshIndex(b, vertexCount) || !isValidMeshIndex(c2, vertexCount)) return false;
|
|
19857
|
+
if (a2 === b || b === c2 || a2 === c2) return false;
|
|
19858
|
+
for (const [from, to] of [
|
|
19859
|
+
[a2, b],
|
|
19860
|
+
[b, c2],
|
|
19861
|
+
[c2, a2]
|
|
19862
|
+
]) {
|
|
19863
|
+
const lo = Math.min(from, to);
|
|
19864
|
+
const hi = Math.max(from, to);
|
|
19865
|
+
const key = `${lo}/${hi}`;
|
|
19866
|
+
const winding = from === lo ? 1 : -1;
|
|
19867
|
+
const entry = edgeUse.get(key);
|
|
19868
|
+
if (entry) {
|
|
19869
|
+
entry.count += 1;
|
|
19870
|
+
entry.winding += winding;
|
|
19871
|
+
} else {
|
|
19872
|
+
edgeUse.set(key, { count: 1, winding });
|
|
19873
|
+
}
|
|
19874
|
+
}
|
|
19875
|
+
}
|
|
19876
|
+
for (const entry of edgeUse.values()) {
|
|
19877
|
+
if (entry.count !== 2 || entry.winding !== 0) return false;
|
|
19878
|
+
}
|
|
19879
|
+
return true;
|
|
19880
|
+
}
|
|
19881
|
+
function isValidMeshIndex(value, vertexCount) {
|
|
19882
|
+
return Number.isInteger(value) && value >= 0 && value < vertexCount;
|
|
19883
|
+
}
|
|
19157
19884
|
function filterDegenerateTriangles(triVerts) {
|
|
19158
19885
|
let writeIdx = 0;
|
|
19159
19886
|
for (let i = 0; i < triVerts.length; i += 3) {
|
|
@@ -20367,11 +21094,12 @@ function profileMayContainInteriorLoopsForOCCT(plan) {
|
|
|
20367
21094
|
return false;
|
|
20368
21095
|
}
|
|
20369
21096
|
}
|
|
21097
|
+
const occtLoweredCache = /* @__PURE__ */ new WeakMap();
|
|
20370
21098
|
function lowerShapeCompilePlanToOCCT(plan, oc) {
|
|
20371
|
-
const cached = plan
|
|
21099
|
+
const cached = occtLoweredCache.get(plan);
|
|
20372
21100
|
if (cached) return cached;
|
|
20373
21101
|
const shape = _lowerShapeCompilePlanToOCCTInner(plan, oc);
|
|
20374
|
-
plan
|
|
21102
|
+
occtLoweredCache.set(plan, shape);
|
|
20375
21103
|
return shape;
|
|
20376
21104
|
}
|
|
20377
21105
|
function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
|
|
@@ -36471,29 +37199,11 @@ function lowerExactSlicedShapeCompilePlanToTruckProfileBackend(plan, offset) {
|
|
|
36471
37199
|
return profilePlan ? lowerProfileCompilePlanToTruckProfileBackend(profilePlan) : null;
|
|
36472
37200
|
}
|
|
36473
37201
|
const SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION = "shape-plan-v1";
|
|
36474
|
-
|
|
36475
|
-
|
|
36476
|
-
|
|
36477
|
-
}
|
|
36478
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
36479
|
-
return JSON.stringify(value);
|
|
36480
|
-
}
|
|
36481
|
-
if (Array.isArray(value)) {
|
|
36482
|
-
return `[${value.map((item) => stableJsonEncode(item, true) ?? "null").join(",")}]`;
|
|
36483
|
-
}
|
|
36484
|
-
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
36485
|
-
const encodedEntries = [];
|
|
36486
|
-
for (const [key, item] of entries) {
|
|
36487
|
-
const encoded = stableJsonEncode(item, false);
|
|
36488
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
36489
|
-
}
|
|
36490
|
-
return `{${encodedEntries.join(",")}}`;
|
|
36491
|
-
}
|
|
36492
|
-
function stableJsonStringify(value) {
|
|
36493
|
-
return stableJsonEncode(value, false) ?? "null";
|
|
36494
|
-
}
|
|
37202
|
+
const exactPlanHasher = createStructuralHasher({
|
|
37203
|
+
skipKey: (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation"
|
|
37204
|
+
});
|
|
36495
37205
|
function shapeCompilePlanCacheKey(plan) {
|
|
36496
|
-
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${
|
|
37206
|
+
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${exactPlanHasher(plan)}`;
|
|
36497
37207
|
}
|
|
36498
37208
|
function formatFaceQuery(query) {
|
|
36499
37209
|
const parts = [];
|
|
@@ -39346,7 +40056,7 @@ class ShapeGroup {
|
|
|
39346
40056
|
};
|
|
39347
40057
|
return this.attachTo(parent, face, opp[face], uvMap[face](u2, v, p2));
|
|
39348
40058
|
}
|
|
39349
|
-
/** Rotate the group around an arbitrary axis through the origin. */
|
|
40059
|
+
/** Rotate the group around an arbitrary axis through the origin. Unlike `scale()`/`mirror()` (bounding-box center) and `Sketch.rotate()`, this pivots at the world origin — pass `options.pivot` to rotate in place. */
|
|
39350
40060
|
rotate(axis, angleDeg, options) {
|
|
39351
40061
|
requireRotateAxis(axis, "ShapeGroup.rotate()");
|
|
39352
40062
|
requireFiniteAngle(angleDeg, "ShapeGroup.rotate()");
|
|
@@ -39591,6 +40301,11 @@ class ShapeGroup {
|
|
|
39591
40301
|
* Position this group by matching connectors to a target.
|
|
39592
40302
|
* Connector names support dotted paths into named children: "ChildName.connectorName".
|
|
39593
40303
|
*
|
|
40304
|
+
* Alignment: with a single connector pair, the group translates and rotates so the connector
|
|
40305
|
+
* origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,
|
|
40306
|
+
* the connector origins define the rigid transform — still author meaningful `axis`/`up` values
|
|
40307
|
+
* so the same connectors remain useful for `connect()`, audits, and future matching.
|
|
40308
|
+
*
|
|
39594
40309
|
* Overloads:
|
|
39595
40310
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
39596
40311
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -41255,10 +41970,10 @@ function rigidTransformStepsFromMatrix(m2) {
|
|
|
41255
41970
|
return [{ kind: "workplanePlacement", matrix: Array.from(m2) }];
|
|
41256
41971
|
}
|
|
41257
41972
|
async function initKernel() {
|
|
41258
|
-
const [manifoldModule] = await Promise.all([initManifoldWasm(), initMeshoptimizer()
|
|
41973
|
+
const [manifoldModule] = await Promise.all([initManifoldWasm(), initMeshoptimizer()]);
|
|
41259
41974
|
return manifoldModule;
|
|
41260
41975
|
}
|
|
41261
|
-
const DEFAULT_ACTIVE_BACKEND = "
|
|
41976
|
+
const DEFAULT_ACTIVE_BACKEND = "manifold";
|
|
41262
41977
|
let _activeBackend = DEFAULT_ACTIVE_BACKEND;
|
|
41263
41978
|
let _runtimeWarn = (msg) => console.warn(msg);
|
|
41264
41979
|
async function activateBackend(backend) {
|
|
@@ -41285,6 +42000,7 @@ const _shapePlacementRefs = /* @__PURE__ */ new WeakMap();
|
|
|
41285
42000
|
const _shapeExplodeHint = /* @__PURE__ */ new WeakMap();
|
|
41286
42001
|
const _shapeRuntimeBackends = /* @__PURE__ */ new WeakMap();
|
|
41287
42002
|
const _shapeTopology = /* @__PURE__ */ new WeakMap();
|
|
42003
|
+
const _shapeLineageTokens = /* @__PURE__ */ new WeakMap();
|
|
41288
42004
|
const _shapeFaceLabels = /* @__PURE__ */ new WeakMap();
|
|
41289
42005
|
const _shapeReferenceNames = /* @__PURE__ */ new WeakMap();
|
|
41290
42006
|
const _shapeReferenceAliases = /* @__PURE__ */ new WeakMap();
|
|
@@ -41345,6 +42061,10 @@ function copyShapeReferenceMetadata(source, target) {
|
|
|
41345
42061
|
const aliases = cloneReferenceAliases(_shapeReferenceAliases.get(source));
|
|
41346
42062
|
if (aliases && aliases.size > 0) _shapeReferenceAliases.set(target, aliases);
|
|
41347
42063
|
}
|
|
42064
|
+
function copyShapeLineage(source, target) {
|
|
42065
|
+
const token = _shapeLineageTokens.get(source);
|
|
42066
|
+
if (token) _shapeLineageTokens.set(target, token);
|
|
42067
|
+
}
|
|
41348
42068
|
function assertNonEmptyReferenceName(name, apiName) {
|
|
41349
42069
|
const trimmed = name.trim();
|
|
41350
42070
|
if (!trimmed) throw new Error(`${apiName} requires a non-empty reference name.`);
|
|
@@ -41413,50 +42133,25 @@ function setShapeRuntimeBackendInternal(shape, payload) {
|
|
|
41413
42133
|
return shape;
|
|
41414
42134
|
}
|
|
41415
42135
|
function setShapeCompilePlanInternal(shape, plan) {
|
|
41416
|
-
_shapeCompilePlans.set(shape,
|
|
42136
|
+
_shapeCompilePlans.set(shape, deepFreezePlanData(plan));
|
|
41417
42137
|
recordShapeSourceSpanInternal(shape, plan);
|
|
41418
42138
|
return shape;
|
|
41419
42139
|
}
|
|
41420
|
-
function cloneShapeSourceSpanRecords(records) {
|
|
41421
|
-
return (records ?? []).map((record) => ({
|
|
41422
|
-
planCacheKey: record.planCacheKey,
|
|
41423
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41424
|
-
}));
|
|
41425
|
-
}
|
|
41426
42140
|
function upsertShapeSourceSpanRecord(shape, record) {
|
|
41427
|
-
|
|
41428
|
-
if (records
|
|
41429
|
-
|
|
41430
|
-
|
|
41431
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41432
|
-
});
|
|
41433
|
-
_shapeSourceSpans.set(shape, records);
|
|
41434
|
-
}
|
|
41435
|
-
function stableTraceSourcePlanEncode(value, arrayMember) {
|
|
41436
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
41437
|
-
return arrayMember ? "null" : void 0;
|
|
41438
|
-
}
|
|
41439
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
41440
|
-
return JSON.stringify(value);
|
|
42141
|
+
let records = _shapeSourceSpans.get(shape);
|
|
42142
|
+
if (!records) {
|
|
42143
|
+
records = /* @__PURE__ */ new Map();
|
|
42144
|
+
_shapeSourceSpans.set(shape, records);
|
|
41441
42145
|
}
|
|
41442
|
-
if (
|
|
41443
|
-
|
|
41444
|
-
}
|
|
41445
|
-
const record = value;
|
|
41446
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
41447
|
-
return stableTraceSourcePlanEncode(record.base, arrayMember);
|
|
41448
|
-
}
|
|
41449
|
-
const entries = Object.entries(record).sort(([left], [right]) => left.localeCompare(right));
|
|
41450
|
-
const encodedEntries = [];
|
|
41451
|
-
for (const [key, item] of entries) {
|
|
41452
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
41453
|
-
const encoded = stableTraceSourcePlanEncode(item, false);
|
|
41454
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
41455
|
-
}
|
|
41456
|
-
return `{${encodedEntries.join(",")}}`;
|
|
42146
|
+
if (records.has(record.planCacheKey)) return;
|
|
42147
|
+
records.set(record.planCacheKey, Object.freeze({ ...record.sourceSpan }));
|
|
41457
42148
|
}
|
|
42149
|
+
const traceSourcePlanHasher = createStructuralHasher({
|
|
42150
|
+
skipKey: (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation",
|
|
42151
|
+
unwrap: (record) => record.kind === "queryOwner" && record.base ? record.base : void 0
|
|
42152
|
+
});
|
|
41458
42153
|
function normalizedTraceSourcePlanCacheKey(plan) {
|
|
41459
|
-
return `shape-plan-v1:${
|
|
42154
|
+
return `shape-plan-v1:${traceSourcePlanHasher(plan)}`;
|
|
41460
42155
|
}
|
|
41461
42156
|
function recordShapeSourceSpanInternal(shape, plan) {
|
|
41462
42157
|
if (!hasActiveRuntimeSourceResolver()) return;
|
|
@@ -41476,13 +42171,20 @@ function recordShapeSourceSpanInternal(shape, plan) {
|
|
|
41476
42171
|
}
|
|
41477
42172
|
}
|
|
41478
42173
|
function copyShapeSourceSpans(source, target) {
|
|
41479
|
-
const records =
|
|
41480
|
-
if (records.
|
|
42174
|
+
const records = _shapeSourceSpans.get(source);
|
|
42175
|
+
if (records && records.size > 0) _shapeSourceSpans.set(target, new Map(records));
|
|
41481
42176
|
}
|
|
41482
42177
|
function mergeShapeSourceSpans(sources, target) {
|
|
42178
|
+
let records = _shapeSourceSpans.get(target);
|
|
41483
42179
|
for (const source of sources) {
|
|
41484
|
-
|
|
41485
|
-
|
|
42180
|
+
const sourceRecords = _shapeSourceSpans.get(source);
|
|
42181
|
+
if (!sourceRecords) continue;
|
|
42182
|
+
if (!records) {
|
|
42183
|
+
records = /* @__PURE__ */ new Map();
|
|
42184
|
+
_shapeSourceSpans.set(target, records);
|
|
42185
|
+
}
|
|
42186
|
+
for (const [key, span] of sourceRecords) {
|
|
42187
|
+
if (!records.has(key)) records.set(key, span);
|
|
41486
42188
|
}
|
|
41487
42189
|
}
|
|
41488
42190
|
}
|
|
@@ -41517,7 +42219,7 @@ function getShapeRuntimeBackendInternal(shape) {
|
|
|
41517
42219
|
function getShapeCompilePlanInternal(shape) {
|
|
41518
42220
|
const stored = _shapeCompilePlans.get(shape);
|
|
41519
42221
|
if (!stored) throw new Error("Shape has no compile plan — every Shape must have an explicit plan set via setShapeCompilePlanInternal()");
|
|
41520
|
-
return
|
|
42222
|
+
return stored;
|
|
41521
42223
|
}
|
|
41522
42224
|
function getShapePlacementRefsInternal(shape) {
|
|
41523
42225
|
return clonePlacementReferences(_shapePlacementRefs.get(shape) ?? createPlacementReferences());
|
|
@@ -42209,6 +42911,30 @@ function checkLabelCollisions(operation2, plans) {
|
|
|
42209
42911
|
seen.push(...labels);
|
|
42210
42912
|
}
|
|
42211
42913
|
}
|
|
42914
|
+
function formatDiagnosticNumber(value) {
|
|
42915
|
+
if (!Number.isFinite(value)) return String(value);
|
|
42916
|
+
const rounded = Math.abs(value) < 5e-4 ? 0 : value;
|
|
42917
|
+
return Number(rounded.toFixed(3)).toString();
|
|
42918
|
+
}
|
|
42919
|
+
function formatDiagnosticVec(values) {
|
|
42920
|
+
return `[${values.slice(0, 3).map(formatDiagnosticNumber).join(",")}]`;
|
|
42921
|
+
}
|
|
42922
|
+
function formatShapeBoundsForDiagnostic(shape) {
|
|
42923
|
+
try {
|
|
42924
|
+
const bounds = shape.boundingBox();
|
|
42925
|
+
return `bounds=${formatDiagnosticVec(bounds.min)}..${formatDiagnosticVec(bounds.max)}`;
|
|
42926
|
+
} catch (error) {
|
|
42927
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42928
|
+
return `bounds=unavailable(${message})`;
|
|
42929
|
+
}
|
|
42930
|
+
}
|
|
42931
|
+
function formatShapeOperandForDiagnostic(role, shape) {
|
|
42932
|
+
const name = _shapeReferenceNames.get(shape);
|
|
42933
|
+
return `${role}=${name ? `${name} ` : ""}${formatShapeBoundsForDiagnostic(shape)}`;
|
|
42934
|
+
}
|
|
42935
|
+
function formatSourceSpanForDiagnostic(sourceSpan) {
|
|
42936
|
+
return sourceSpan ? ` source=${sourceSpan.fileName}:${sourceSpan.line}:${sourceSpan.column}` : "";
|
|
42937
|
+
}
|
|
42212
42938
|
function withCopiedDimensions(source, out) {
|
|
42213
42939
|
setShapeDimensionsInternal(out, cloneDimensions(getShapeDimensionsInternal(source), true));
|
|
42214
42940
|
setShapeGeometryInfoInternal(out, getShapeGeometryInfoInternal(source));
|
|
@@ -42223,6 +42949,7 @@ function withCopiedDimensions(source, out) {
|
|
|
42223
42949
|
const sourceLabels = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42224
42950
|
if (sourceLabels) _shapeFaceLabels.set(out, sourceLabels);
|
|
42225
42951
|
copyShapeReferenceMetadata(source, out);
|
|
42952
|
+
copyShapeLineage(source, out);
|
|
42226
42953
|
copyShapeSourceSpans(source, out);
|
|
42227
42954
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42228
42955
|
}
|
|
@@ -42252,6 +42979,7 @@ function withTransformedDimensions(source, out, m2) {
|
|
|
42252
42979
|
const sourceLabelsT = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42253
42980
|
if (sourceLabelsT) _shapeFaceLabels.set(out, sourceLabelsT);
|
|
42254
42981
|
copyShapeReferenceMetadata(source, out);
|
|
42982
|
+
copyShapeLineage(source, out);
|
|
42255
42983
|
copyShapeSourceSpans(source, out);
|
|
42256
42984
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42257
42985
|
}
|
|
@@ -42679,6 +43407,7 @@ class Shape {
|
|
|
42679
43407
|
this.colorHex = color;
|
|
42680
43408
|
setShapeRuntimeBackendInternal(this, payload);
|
|
42681
43409
|
setShapeGeometryInfoInternal(this, createGeometryInfo(geometryInfo));
|
|
43410
|
+
_shapeLineageTokens.set(this, {});
|
|
42682
43411
|
}
|
|
42683
43412
|
/** @internal Use .color() instead. */
|
|
42684
43413
|
setColor(value) {
|
|
@@ -42797,6 +43526,12 @@ class Shape {
|
|
|
42797
43526
|
* with `union()` / `difference()` to avoid collisions. Collision detection throws a
|
|
42798
43527
|
* clear error with a fix suggestion.
|
|
42799
43528
|
*
|
|
43529
|
+
* Boolean survival: `union()` and `intersection()` carry labels from every operand;
|
|
43530
|
+
* `difference()` carries only the base (first) operand's labels — cutter labels are
|
|
43531
|
+
* dropped. A surviving label addresses whatever portion of its face survives the
|
|
43532
|
+
* boolean; cutters may split or erase it, and a lineage shared by multiple union
|
|
43533
|
+
* operands resolves as a face set rather than a single face.
|
|
43534
|
+
*
|
|
42800
43535
|
* For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's
|
|
42801
43536
|
* compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops)
|
|
42802
43537
|
* are resolved via coplanar triangle clustering.
|
|
@@ -43213,7 +43948,7 @@ class Shape {
|
|
|
43213
43948
|
const tbb = s.boundingBox();
|
|
43214
43949
|
return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
|
|
43215
43950
|
}
|
|
43216
|
-
/** Rotate around an arbitrary axis through the origin. */
|
|
43951
|
+
/** Rotate around an arbitrary axis through the origin. Unlike `Sketch.rotate()` (bounding-box center), this pivots at the world origin — pass `options.pivot` to rotate in place. */
|
|
43217
43952
|
rotate(axis, angleDeg, options) {
|
|
43218
43953
|
validateRotateAxis(axis, "Shape.rotate()");
|
|
43219
43954
|
validateRotateAngle(angleDeg, "Shape.rotate()");
|
|
@@ -43400,7 +44135,7 @@ class Shape {
|
|
|
43400
44135
|
* Warn if a boolean operation had no geometric effect.
|
|
43401
44136
|
* Compares volumes before and after; if they match within tolerance, the operation was a no-op.
|
|
43402
44137
|
*/
|
|
43403
|
-
static _checkBooleanNoOp(op, base, result) {
|
|
44138
|
+
static _checkBooleanNoOp(op, base, result, tools = []) {
|
|
43404
44139
|
try {
|
|
43405
44140
|
if (op === "intersection") {
|
|
43406
44141
|
if (result.isEmpty()) {
|
|
@@ -43413,8 +44148,15 @@ class Shape {
|
|
|
43413
44148
|
const volAfter = result.volume();
|
|
43414
44149
|
const tol = Math.max(volBefore * 1e-4, 1e-3);
|
|
43415
44150
|
if (Math.abs(volBefore - volAfter) < tol) {
|
|
44151
|
+
const sourceSpan = captureRuntimeSourceSpan();
|
|
44152
|
+
const operandContext = [
|
|
44153
|
+
formatShapeOperandForDiagnostic("base", base),
|
|
44154
|
+
...tools.map((tool, index2) => formatShapeOperandForDiagnostic(`tool[${index2 + 1}]`, tool))
|
|
44155
|
+
].join(" ");
|
|
43416
44156
|
_runtimeWarn(
|
|
43417
|
-
`subtract() had no effect — the tool may not overlap the base shape. Base vol=${volBefore.toFixed(1)}mm³, result vol=${volAfter.toFixed(1)}mm
|
|
44157
|
+
`subtract() had no effect — the tool may not overlap the base shape. Base vol=${volBefore.toFixed(1)}mm³, result vol=${volAfter.toFixed(1)}mm³.${formatSourceSpanForDiagnostic(sourceSpan)} ${operandContext}`,
|
|
44158
|
+
"boolean.difference.noEffect",
|
|
44159
|
+
sourceSpan ? { sourceSpan } : void 0
|
|
43418
44160
|
);
|
|
43419
44161
|
}
|
|
43420
44162
|
}
|
|
@@ -43474,7 +44216,7 @@ class Shape {
|
|
|
43474
44216
|
),
|
|
43475
44217
|
nextPlan
|
|
43476
44218
|
);
|
|
43477
|
-
Shape._checkBooleanNoOp("difference", this, resultShape);
|
|
44219
|
+
Shape._checkBooleanNoOp("difference", this, resultShape, shapes.slice(1));
|
|
43478
44220
|
return resultShape;
|
|
43479
44221
|
}
|
|
43480
44222
|
/** Keep only the overlap with other shapes. Method form of intersection(). */
|
|
@@ -43877,6 +44619,11 @@ class Shape {
|
|
|
43877
44619
|
/**
|
|
43878
44620
|
* Position this shape by matching connectors to a target.
|
|
43879
44621
|
*
|
|
44622
|
+
* Alignment: with a single connector pair, the shape translates and rotates so the connector
|
|
44623
|
+
* origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,
|
|
44624
|
+
* the connector origins define the rigid transform — still author meaningful `axis`/`up` values
|
|
44625
|
+
* so the same connectors remain useful for `connect()`, audits, and future matching.
|
|
44626
|
+
*
|
|
43880
44627
|
* Overloads:
|
|
43881
44628
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
43882
44629
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -44254,15 +45001,23 @@ function computeGeometryArrays(mesh, options = {}) {
|
|
|
44254
45001
|
normals[o + 7] = vertNormals[i2 * 3 + 1];
|
|
44255
45002
|
normals[o + 8] = vertNormals[i2 * 3 + 2];
|
|
44256
45003
|
} else if (numProp >= 6) {
|
|
44257
|
-
|
|
44258
|
-
|
|
44259
|
-
|
|
44260
|
-
|
|
44261
|
-
|
|
44262
|
-
|
|
44263
|
-
|
|
44264
|
-
|
|
44265
|
-
|
|
45004
|
+
const corners = [i0, i1, i2];
|
|
45005
|
+
for (let v = 0; v < 3; v++) {
|
|
45006
|
+
const base = corners[v] * numProp;
|
|
45007
|
+
const nx = vertProperties[base + 3];
|
|
45008
|
+
const ny = vertProperties[base + 4];
|
|
45009
|
+
const nz = vertProperties[base + 5];
|
|
45010
|
+
const oc = o + v * 3;
|
|
45011
|
+
if (nx * nx + ny * ny + nz * nz > 1e-12) {
|
|
45012
|
+
normals[oc] = nx;
|
|
45013
|
+
normals[oc + 1] = ny;
|
|
45014
|
+
normals[oc + 2] = nz;
|
|
45015
|
+
} else {
|
|
45016
|
+
normals[oc] = fnx;
|
|
45017
|
+
normals[oc + 1] = fny;
|
|
45018
|
+
normals[oc + 2] = fnz;
|
|
45019
|
+
}
|
|
45020
|
+
}
|
|
44266
45021
|
} else {
|
|
44267
45022
|
normals[o] = fnx;
|
|
44268
45023
|
normals[o + 1] = fny;
|