forgecad 0.9.16 → 0.10.1
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-CXvls4-J.js → AdminPage-DcCnj0qo.js} +1 -1
- package/dist/assets/{BenchmarkPage-B27zk8xL.js → BenchmarkPage-BVEpJSVk.js} +1 -1
- package/dist/assets/{BlogPage-CMAVvgQL.js → BlogPage-DHaGP50_.js} +1 -1
- package/dist/assets/{DocsPage-knf4I4h7.js → DocsPage-CDoxHkz8.js} +40 -859
- package/dist/assets/EditorApp-BJ0Dloyh.js +16446 -0
- package/dist/assets/{EmbedViewer-D7ZGlFjx.js → EmbedViewer-CRKZbY0y.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-CnevhTE8.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
- package/dist/assets/{LegalPage-BPTUmqeg.js → LegalPage-B-u6FrVv.js} +1 -1
- package/dist/assets/{PricingPage-B0D4goG_.js → PricingPage-CzpZ6-Ce.js} +1 -1
- package/dist/assets/{SettingsPage-CFF-UgjI.js → SettingsPage-CIZSSAd0.js} +1 -1
- package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
- package/dist/assets/{app-T0pDcSX4.js → app-DaTMg3nH.js} +1310 -290
- package/dist/assets/cli/{render-C5pcIISc.js → render-DPf4AYJK.js} +55 -60
- package/dist/assets/{constructionHistoryWorker-Ba2Hm58b.js → constructionHistoryWorker-AwMMWSxg.js} +1103 -349
- package/dist/assets/{evalWorker-vkx310U2.js → evalWorker-CjZZWRWW.js} +5209 -2643
- package/dist/assets/{inspectWorker-BuTJDVX6.js → inspectWorker-CZsCFtQT.js} +1163 -409
- package/dist/assets/{jointPose-B_Cgedn9.js → jointPose-DzQOViQH.js} +1 -1
- package/dist/assets/{manifold-BWgsjmAM.js → manifold-BYlzU521.js} +1 -1
- package/dist/assets/{manifold-D6IFSkhH.js → manifold-DgXo0T5P.js} +2 -2
- package/dist/assets/{manifold-rZexZI0G.js → manifold-K1SkarlQ.js} +1 -1
- package/dist/assets/{reportWorker-0AGij1Ru.js → reportWorker-B9nWwSrB.js} +8501 -3393
- package/dist/assets/{scalar-sampling-budget-J5cuzxT1.js → scalar-sampling-budget-prBw_s8t.js} +6067 -3479
- package/dist/assets/{scanProxyWorker-Vl4Wxa1y.js → scanProxyWorker-2GtDLk-R.js} +1 -1
- 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 +1 -1
- package/dist/docs-raw/CLI.md +77 -240
- package/dist/docs-raw/README.md +6 -0
- package/dist/docs-raw/component-model.md +17 -150
- package/dist/docs-raw/generated/assembly.md +188 -582
- package/dist/docs-raw/generated/concepts.md +259 -3501
- package/dist/docs-raw/generated/core.md +283 -1250
- 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 +227 -85
- package/dist/docs-raw/generated/output.md +35 -99
- package/dist/docs-raw/generated/runtime-names.md +23 -23
- 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/guides/simready-quickstart.md +171 -0
- package/dist/docs-raw/simulation-workflow.md +273 -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 +112 -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 -131
- 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 +2 -2
- package/dist/index.html +2 -2
- package/dist/llms.txt +1 -2
- package/dist/sitemap.xml +25 -13
- package/dist-cli/{check-compiler-SYQ2PWOB.js → check-compiler-II7NLPAB.js} +1 -1
- package/dist-cli/{check-query-propagation-HIAGV62W.js → check-query-propagation-7462TR3R.js} +1 -1
- package/dist-cli/{chunk-SPZE3DUY.js → chunk-UWTJCGXF.js} +5848 -2915
- package/dist-cli/forgecad.js +3496 -703
- package/dist-skill/CONTEXT.md +1797 -7963
- package/dist-skill/SKILL.md +15 -15
- package/dist-skill/docs/API/core/concepts.md +27 -157
- package/dist-skill/docs/CLI.md +77 -240
- package/dist-skill/docs/generated/assembly.md +182 -532
- package/dist-skill/docs/generated/core.md +283 -1250
- package/dist-skill/docs/generated/curves.md +387 -1609
- package/dist-skill/docs/generated/lib.md +227 -85
- package/dist-skill/docs/generated/output.md +35 -99
- package/dist-skill/docs/generated/runtime-names.md +16 -21
- 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 +110 -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 -133
- 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/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/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 +3 -3
- 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/examples/robotics/README.md +46 -0
- package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
- package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
- package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
- package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
- package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
- package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
- package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
- package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
- package/examples/robotics/simready-asset-crate.forge.js +79 -0
- package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
- package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
- package/package.json +1 -1
- package/dist/assets/EditorApp-BHMQlJ-D.js +0 -14686
- 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/dist/assets/{constructionHistoryWorker-Ba2Hm58b.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([
|
|
@@ -9021,6 +9047,8 @@ function cloneSdfNode(node) {
|
|
|
9021
9047
|
return { kind: "sdf:circularArray", child: cloneSdfNode(node.child), count: node.count, offset: node.offset };
|
|
9022
9048
|
case "sdf:shell":
|
|
9023
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 };
|
|
9024
9052
|
case "sdf:displace":
|
|
9025
9053
|
return {
|
|
9026
9054
|
kind: "sdf:displace",
|
|
@@ -9105,7 +9133,7 @@ function cloneSdfNode(node) {
|
|
|
9105
9133
|
}
|
|
9106
9134
|
}
|
|
9107
9135
|
const SHEET_METAL_EDGES = ["top", "right", "bottom", "left"];
|
|
9108
|
-
const EPS$
|
|
9136
|
+
const EPS$2 = 1e-9;
|
|
9109
9137
|
function isFinitePositive$1(value) {
|
|
9110
9138
|
return Number.isFinite(value) && value > 0;
|
|
9111
9139
|
}
|
|
@@ -9146,7 +9174,7 @@ function edgeDisplayName(edge) {
|
|
|
9146
9174
|
return `sheetMetal().flange("${edge}", ...)`;
|
|
9147
9175
|
}
|
|
9148
9176
|
function normalizeAngle(angleDeg) {
|
|
9149
|
-
return Math.abs(angleDeg) <= EPS$
|
|
9177
|
+
return Math.abs(angleDeg) <= EPS$2 ? 0 : angleDeg;
|
|
9150
9178
|
}
|
|
9151
9179
|
function validateSheetMetalModel(model) {
|
|
9152
9180
|
if (!isFinitePositive$1(model.panel.width) || !isFinitePositive$1(model.panel.height)) {
|
|
@@ -9158,7 +9186,7 @@ function validateSheetMetalModel(model) {
|
|
|
9158
9186
|
if (!isFiniteNonNegative(model.bendRadius)) {
|
|
9159
9187
|
return "sheetMetal() requires a finite non-negative bendRadius.";
|
|
9160
9188
|
}
|
|
9161
|
-
if (model.bendRadius <= EPS$
|
|
9189
|
+
if (model.bendRadius <= EPS$2) {
|
|
9162
9190
|
return "sheetMetal() v1 requires a positive bendRadius so the bend region stays explicit instead of collapsing into a sharp fold.";
|
|
9163
9191
|
}
|
|
9164
9192
|
if (model.bendAllowance.kind !== "k-factor") {
|
|
@@ -9220,7 +9248,7 @@ function deriveSheetMetalModel(model) {
|
|
|
9220
9248
|
const trimEnd = flanges.has(adjacent.end) ? model.cornerRelief.size : 0;
|
|
9221
9249
|
const fullLength = edge === "top" || edge === "bottom" ? model.panel.width : model.panel.height;
|
|
9222
9250
|
const span = fullLength - trimStart - trimEnd;
|
|
9223
|
-
if (!(span > EPS$
|
|
9251
|
+
if (!(span > EPS$2)) {
|
|
9224
9252
|
throw new Error(
|
|
9225
9253
|
`${edgeDisplayName(edge)} loses all usable span after applying the defended rectangular corner relief size ${model.cornerRelief.size}.`
|
|
9226
9254
|
);
|
|
@@ -9266,7 +9294,7 @@ function transformPlacement(origin, u2, v, normal) {
|
|
|
9266
9294
|
};
|
|
9267
9295
|
}
|
|
9268
9296
|
function translatePlan(plan, x2, y2, z2) {
|
|
9269
|
-
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);
|
|
9270
9298
|
return appendShapeCompileTransform(cloneShapeCompilePlan(plan), {
|
|
9271
9299
|
kind: "translate",
|
|
9272
9300
|
x: x2,
|
|
@@ -10438,6 +10466,8 @@ function cloneShapeCompilePlan(plan) {
|
|
|
10438
10466
|
heights: plan.heights.map((height) => height),
|
|
10439
10467
|
edgeLength: plan.edgeLength,
|
|
10440
10468
|
boundsPadding: plan.boundsPadding,
|
|
10469
|
+
...plan.forceField ? { forceField: true } : {},
|
|
10470
|
+
...plan.meshing ? { meshing: cloneSdfCompileMeshingSettings(plan.meshing) } : {},
|
|
10441
10471
|
edgeLabels: plan.edgeLabels ? { ...plan.edgeLabels } : void 0,
|
|
10442
10472
|
capLabels: plan.capLabels ? { ...plan.capLabels } : void 0
|
|
10443
10473
|
};
|
|
@@ -10705,7 +10735,6 @@ function cloneShapeCompilePlan(plan) {
|
|
|
10705
10735
|
default:
|
|
10706
10736
|
assertExhaustive(plan);
|
|
10707
10737
|
}
|
|
10708
|
-
if (plan._occtCache) result._occtCache = plan._occtCache;
|
|
10709
10738
|
return result;
|
|
10710
10739
|
}
|
|
10711
10740
|
function appendProfileCompileTransform(plan, step) {
|
|
@@ -10718,22 +10747,31 @@ function appendShapeCompileTransform(plan, step) {
|
|
|
10718
10747
|
if (plan.kind === "transform") {
|
|
10719
10748
|
return {
|
|
10720
10749
|
kind: "transform",
|
|
10721
|
-
base:
|
|
10722
|
-
steps: [...plan.steps
|
|
10750
|
+
base: plan.base,
|
|
10751
|
+
steps: [...plan.steps, cloneShapeTransform(step)]
|
|
10723
10752
|
};
|
|
10724
10753
|
}
|
|
10725
10754
|
return {
|
|
10726
10755
|
kind: "transform",
|
|
10727
|
-
base:
|
|
10756
|
+
base: plan,
|
|
10728
10757
|
steps: [cloneShapeTransform(step)]
|
|
10729
10758
|
};
|
|
10730
10759
|
}
|
|
10731
10760
|
function appendShapeCompileTransforms(plan, steps) {
|
|
10732
|
-
|
|
10733
|
-
|
|
10734
|
-
|
|
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
|
+
};
|
|
10735
10769
|
}
|
|
10736
|
-
return
|
|
10770
|
+
return {
|
|
10771
|
+
kind: "transform",
|
|
10772
|
+
base: plan,
|
|
10773
|
+
steps: steps.map(cloneShapeTransform)
|
|
10774
|
+
};
|
|
10737
10775
|
}
|
|
10738
10776
|
function wrapShapeCompilePlanWithQueryOwner(plan, owner) {
|
|
10739
10777
|
if (!plan) return null;
|
|
@@ -10807,6 +10845,8 @@ function buildLoftShapeCompilePlan(profiles, heights, options) {
|
|
|
10807
10845
|
heights: heights.map((height) => canonicalNumber(height)),
|
|
10808
10846
|
edgeLength: canonicalNumber(options.edgeLength),
|
|
10809
10847
|
boundsPadding: canonicalNumber(options.boundsPadding),
|
|
10848
|
+
...options.forceField ? { forceField: true } : {},
|
|
10849
|
+
...options.meshing ? { meshing: cloneSdfCompileMeshingSettings(options.meshing) } : {},
|
|
10810
10850
|
edgeLabels: options.edgeLabels ? { ...options.edgeLabels } : void 0
|
|
10811
10851
|
};
|
|
10812
10852
|
}
|
|
@@ -10908,14 +10948,14 @@ function sub$2(a2, b) {
|
|
|
10908
10948
|
function dot$3(a2, b) {
|
|
10909
10949
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
10910
10950
|
}
|
|
10911
|
-
function cross$
|
|
10951
|
+
function cross$3(a2, b) {
|
|
10912
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]];
|
|
10913
10953
|
}
|
|
10914
10954
|
function rotateAroundAxis(v, axis, angleRad) {
|
|
10915
10955
|
const c2 = Math.cos(angleRad);
|
|
10916
10956
|
const s = Math.sin(angleRad);
|
|
10917
10957
|
const term1 = scale$2(v, c2);
|
|
10918
|
-
const term2 = scale$2(cross$
|
|
10958
|
+
const term2 = scale$2(cross$3(axis, v), s);
|
|
10919
10959
|
const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
|
|
10920
10960
|
return add$2(add$2(term1, term2), term3);
|
|
10921
10961
|
}
|
|
@@ -11191,13 +11231,13 @@ function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
|
|
|
11191
11231
|
pts.push(evalPathAt(path, 1));
|
|
11192
11232
|
return pts;
|
|
11193
11233
|
}
|
|
11194
|
-
const EPS$
|
|
11234
|
+
const EPS$1 = 1e-8;
|
|
11195
11235
|
function midpoint$1(start, end) {
|
|
11196
11236
|
return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
|
|
11197
11237
|
}
|
|
11198
11238
|
function normalize$3(v) {
|
|
11199
11239
|
const len = Math.hypot(v[0], v[1], v[2]);
|
|
11200
|
-
if (len <= EPS$
|
|
11240
|
+
if (len <= EPS$1) throw new Error("Edge feature selection requires a non-zero direction vector");
|
|
11201
11241
|
return [v[0] / len, v[1] / len, v[2] / len];
|
|
11202
11242
|
}
|
|
11203
11243
|
function subtract(a2, b) {
|
|
@@ -11276,7 +11316,7 @@ function rigidTransformForEdgeStep(step) {
|
|
|
11276
11316
|
case "mirror": {
|
|
11277
11317
|
const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
|
|
11278
11318
|
const len = Math.hypot(nx0, ny0, nz0);
|
|
11279
|
-
if (len <= EPS$
|
|
11319
|
+
if (len <= EPS$1) return Transform.identity();
|
|
11280
11320
|
const nx = nx0 / len;
|
|
11281
11321
|
const ny = ny0 / len;
|
|
11282
11322
|
const nz = nz0 / len;
|
|
@@ -11573,7 +11613,7 @@ function isRectangleProfile(points) {
|
|
|
11573
11613
|
return [next[0] - point[0], next[1] - point[1]];
|
|
11574
11614
|
});
|
|
11575
11615
|
const lengths = vectors.map(([x2, y2]) => Math.hypot(x2, y2));
|
|
11576
|
-
if (lengths.some((length4) => length4 <= EPS$
|
|
11616
|
+
if (lengths.some((length4) => length4 <= EPS$1)) return false;
|
|
11577
11617
|
const dot01 = vectors[0][0] * vectors[1][0] + vectors[0][1] * vectors[1][1];
|
|
11578
11618
|
const dot12 = vectors[1][0] * vectors[2][0] + vectors[1][1] * vectors[2][1];
|
|
11579
11619
|
const dot23 = vectors[2][0] * vectors[3][0] + vectors[2][1] * vectors[3][1];
|
|
@@ -13512,7 +13552,9 @@ function lowerLoftShellToConcretePlan(plan, thickness, openFaces) {
|
|
|
13512
13552
|
profiles: innerProfiles,
|
|
13513
13553
|
heights: innerHeights,
|
|
13514
13554
|
edgeLength: plan.edgeLength,
|
|
13515
|
-
boundsPadding: plan.boundsPadding
|
|
13555
|
+
boundsPadding: plan.boundsPadding,
|
|
13556
|
+
...plan.forceField ? { forceField: true } : {},
|
|
13557
|
+
...plan.meshing ? { meshing: { ...plan.meshing } } : {}
|
|
13516
13558
|
};
|
|
13517
13559
|
return { ok: true, plan: buildBooleanShapeCompilePlan("difference", [plan, inner]) };
|
|
13518
13560
|
}
|
|
@@ -13650,6 +13692,197 @@ function lowerShellShapeCompilePlanToConcretePlan(plan) {
|
|
|
13650
13692
|
}
|
|
13651
13693
|
return lowerBaseShellPlanToConcretePlan(plan.base, plan.thickness, normalizeShellOpenFaces(plan.openFaces));
|
|
13652
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
|
+
}
|
|
13653
13886
|
const SHAPE_GEOMETRY_CACHE_KEY_VERSION = "shape-geometry-v1";
|
|
13654
13887
|
const SHAPE_BUILD_CACHE_POLICY = "live-structural-lru-v1";
|
|
13655
13888
|
const STRUCTURAL_CACHE_BUDGET_MB = 64;
|
|
@@ -13695,60 +13928,26 @@ function recordEvent(event) {
|
|
|
13695
13928
|
runEvents.push(next);
|
|
13696
13929
|
if (runEvents.length > MAX_RECORDED_EVENTS) runEvents = runEvents.slice(-MAX_RECORDED_EVENTS);
|
|
13697
13930
|
}
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
13705
|
-
|
|
13706
|
-
|
|
13707
|
-
|
|
13708
|
-
|
|
13709
|
-
|
|
13710
|
-
|
|
13711
|
-
|
|
13712
|
-
|
|
13713
|
-
|
|
13714
|
-
for (const [key, item] of entries) {
|
|
13715
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
13716
|
-
const encoded = stableGeometryEncode(item, false);
|
|
13717
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
13718
|
-
}
|
|
13719
|
-
return `{${encodedEntries.join(",")}}`;
|
|
13720
|
-
}
|
|
13721
|
-
function stableCacheOpportunityEncode(value, arrayMember) {
|
|
13722
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
13723
|
-
return arrayMember ? "null" : void 0;
|
|
13724
|
-
}
|
|
13725
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
13726
|
-
return JSON.stringify(value);
|
|
13727
|
-
}
|
|
13728
|
-
if (Array.isArray(value)) {
|
|
13729
|
-
return `[${value.map((item) => stableCacheOpportunityEncode(item, true) ?? "null").join(",")}]`;
|
|
13730
|
-
}
|
|
13731
|
-
const record = value;
|
|
13732
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
13733
|
-
return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
13734
|
-
}
|
|
13735
|
-
let encodedRecord = record;
|
|
13736
|
-
if (record.kind === "transform" && record.base) {
|
|
13737
|
-
const retainedSteps = Array.isArray(record.steps) ? record.steps.filter((step) => step.kind === "scale") : [];
|
|
13738
|
-
if (retainedSteps.length === 0) return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
13739
|
-
encodedRecord = { kind: "transform", base: record.base, steps: retainedSteps };
|
|
13740
|
-
}
|
|
13741
|
-
const entries = Object.entries(encodedRecord).sort(([left], [right]) => left.localeCompare(right));
|
|
13742
|
-
const encodedEntries = [];
|
|
13743
|
-
for (const [key, item] of entries) {
|
|
13744
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
13745
|
-
const encoded = stableCacheOpportunityEncode(item, false);
|
|
13746
|
-
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) };
|
|
13747
13947
|
}
|
|
13748
|
-
|
|
13749
|
-
}
|
|
13948
|
+
});
|
|
13750
13949
|
function shapeGeometryCacheKey(plan) {
|
|
13751
|
-
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${
|
|
13950
|
+
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${geometryPlanHasher(plan)}`;
|
|
13752
13951
|
}
|
|
13753
13952
|
function planComplexityScore(value) {
|
|
13754
13953
|
if (!value || typeof value !== "object") return 0;
|
|
@@ -13807,8 +14006,7 @@ function planComplexityScore(value) {
|
|
|
13807
14006
|
}
|
|
13808
14007
|
}
|
|
13809
14008
|
function estimateCacheRetainedMb(plan) {
|
|
13810
|
-
|
|
13811
|
-
const encodedLength = ((_a3 = stableCacheOpportunityEncode(plan, false)) == null ? void 0 : _a3.length) ?? 0;
|
|
14009
|
+
const encodedLength = estimatePlanEncodedLength(plan);
|
|
13812
14010
|
const serializedComplexityMb = encodedLength / 24e3;
|
|
13813
14011
|
return round2(0.08 + planComplexityScore(plan) * 0.09 + serializedComplexityMb);
|
|
13814
14012
|
}
|
|
@@ -13826,26 +14024,62 @@ function splitCacheablePlacement(plan) {
|
|
|
13826
14024
|
}
|
|
13827
14025
|
return { basePlan: plan, placementSteps: [] };
|
|
13828
14026
|
}
|
|
14027
|
+
const uncacheableReasonMemo = /* @__PURE__ */ new WeakMap();
|
|
13829
14028
|
function findUncacheableReason(value) {
|
|
13830
14029
|
if (value === void 0 || value === null) return null;
|
|
13831
14030
|
if (typeof value === "function" || typeof value === "symbol") return "plan contains runtime-only values";
|
|
13832
14031
|
if (typeof value !== "object") return null;
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
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);
|
|
13838
14075
|
}
|
|
13839
|
-
return null;
|
|
13840
14076
|
}
|
|
13841
|
-
|
|
13842
|
-
|
|
13843
|
-
|
|
13844
|
-
|
|
13845
|
-
const reason = findUncacheableReason(item);
|
|
13846
|
-
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);
|
|
13847
14081
|
}
|
|
13848
|
-
return
|
|
14082
|
+
return reason;
|
|
13849
14083
|
}
|
|
13850
14084
|
function applyPlacementStep(backend, step) {
|
|
13851
14085
|
switch (step.kind) {
|
|
@@ -14522,8 +14756,9 @@ function analyzeNodeUV(node, toLocal) {
|
|
|
14522
14756
|
if (result.majorRadius !== void 0) result.majorRadius *= node.factor;
|
|
14523
14757
|
return result;
|
|
14524
14758
|
}
|
|
14525
|
-
// ── Shell — UV comes from the inner shape ──
|
|
14759
|
+
// ── Shell / offset — UV comes from the inner shape ──
|
|
14526
14760
|
case "sdf:shell":
|
|
14761
|
+
case "sdf:offset":
|
|
14527
14762
|
return analyzeNodeUV(node.child, toLocal);
|
|
14528
14763
|
// ── CSG — take UV from the first (primary) child ──
|
|
14529
14764
|
case "sdf:union":
|
|
@@ -15059,6 +15294,11 @@ function compileSdfNode3(node) {
|
|
|
15059
15294
|
const t = node.thickness * 0.5;
|
|
15060
15295
|
return (x2, y2, z2) => abs(fn(x2, y2, z2)) - t;
|
|
15061
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
|
+
}
|
|
15062
15302
|
case "sdf:displace": {
|
|
15063
15303
|
const fn = compileSdfNode3(node.child);
|
|
15064
15304
|
const constEntries = Object.entries(node.constants ?? {});
|
|
@@ -15552,6 +15792,10 @@ function emitSdfProgramNode(b, node, x2, y2, z2) {
|
|
|
15552
15792
|
const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
15553
15793
|
return b.sub(b.abs(child), b.constant(node.thickness * 0.5));
|
|
15554
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
|
+
}
|
|
15555
15799
|
case "sdf:onion": {
|
|
15556
15800
|
let d2 = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
15557
15801
|
for (let i = 0; i < node.layers; i++) d2 = b.sub(b.abs(d2), b.constant(node.thickness));
|
|
@@ -15700,6 +15944,7 @@ function getUnsupportedSdfProgramReason(node) {
|
|
|
15700
15944
|
case "sdf:bend":
|
|
15701
15945
|
case "sdf:repeat":
|
|
15702
15946
|
case "sdf:shell":
|
|
15947
|
+
case "sdf:offset":
|
|
15703
15948
|
case "sdf:onion":
|
|
15704
15949
|
return getUnsupportedSdfProgramReason(node.child);
|
|
15705
15950
|
default:
|
|
@@ -16141,7 +16386,19 @@ function simplifyMesh(triVerts, vertProperties, targetRatio, maxError) {
|
|
|
16141
16386
|
if (!_simplifier) {
|
|
16142
16387
|
throw new Error("meshoptimizer not initialized — call initMeshoptimizer() first");
|
|
16143
16388
|
}
|
|
16144
|
-
|
|
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;
|
|
16145
16402
|
const [simplified] = _simplifier.simplify(
|
|
16146
16403
|
triVerts,
|
|
16147
16404
|
vertProperties,
|
|
@@ -17118,117 +17375,10 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
17118
17375
|
edgeLength: options.edgeLength
|
|
17119
17376
|
};
|
|
17120
17377
|
}
|
|
17121
|
-
const
|
|
17122
|
-
|
|
17123
|
-
|
|
17124
|
-
|
|
17125
|
-
const dists = [0];
|
|
17126
|
-
for (let i = 0; i < poly.length; i++) {
|
|
17127
|
-
const p1 = poly[i];
|
|
17128
|
-
const p2 = poly[(i + 1) % poly.length];
|
|
17129
|
-
const dx = p2[0] - p1[0];
|
|
17130
|
-
const dy = p2[1] - p1[1];
|
|
17131
|
-
const d2 = Math.sqrt(dx * dx + dy * dy);
|
|
17132
|
-
dists.push(dists[dists.length - 1] + d2);
|
|
17133
|
-
}
|
|
17134
|
-
const totalDist = dists[dists.length - 1];
|
|
17135
|
-
if (totalDist < 1e-12) {
|
|
17136
|
-
return Array.from({ length: targetCount }, () => [poly[0][0], poly[0][1]]);
|
|
17137
|
-
}
|
|
17138
|
-
const out = [];
|
|
17139
|
-
for (let i = 0; i < targetCount; i++) {
|
|
17140
|
-
const targetDist = i / targetCount * totalDist;
|
|
17141
|
-
let low = 0;
|
|
17142
|
-
let high = dists.length - 1;
|
|
17143
|
-
while (low < high) {
|
|
17144
|
-
const mid = low + high >> 1;
|
|
17145
|
-
if (dists[mid] <= targetDist) {
|
|
17146
|
-
low = mid + 1;
|
|
17147
|
-
} else {
|
|
17148
|
-
high = mid;
|
|
17149
|
-
}
|
|
17150
|
-
}
|
|
17151
|
-
const seg = low - 1;
|
|
17152
|
-
const t = (targetDist - dists[seg]) / (dists[seg + 1] - dists[seg]);
|
|
17153
|
-
const p1 = poly[seg % poly.length];
|
|
17154
|
-
const p2 = poly[(seg + 1) % poly.length];
|
|
17155
|
-
out.push([p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t]);
|
|
17156
|
-
}
|
|
17157
|
-
return out;
|
|
17158
|
-
}
|
|
17159
|
-
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid(poly)) {
|
|
17160
|
-
if (poly.length < 3 || targetCount <= 0) return null;
|
|
17161
|
-
if (!isConvexPolygon(poly)) return null;
|
|
17162
|
-
const out = [];
|
|
17163
|
-
for (let index2 = 0; index2 < targetCount; index2 += 1) {
|
|
17164
|
-
const angle = index2 / targetCount * Math.PI * 2;
|
|
17165
|
-
const point = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
17166
|
-
if (!point) return null;
|
|
17167
|
-
out.push(point);
|
|
17168
|
-
}
|
|
17169
|
-
return out;
|
|
17170
|
-
}
|
|
17171
|
-
function rayPolygonIntersection(origin, direction, poly) {
|
|
17172
|
-
let bestT = Infinity;
|
|
17173
|
-
let best = null;
|
|
17174
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
17175
|
-
const a2 = poly[index2];
|
|
17176
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
17177
|
-
const edge = [b[0] - a2[0], b[1] - a2[1]];
|
|
17178
|
-
const denom = cross$3(direction, edge);
|
|
17179
|
-
if (Math.abs(denom) < EPS$1) continue;
|
|
17180
|
-
const delta = [a2[0] - origin[0], a2[1] - origin[1]];
|
|
17181
|
-
const rayT = cross$3(delta, edge) / denom;
|
|
17182
|
-
const edgeT = cross$3(delta, direction) / denom;
|
|
17183
|
-
if (rayT >= -EPS$1 && edgeT >= -EPS$1 && edgeT <= 1 + EPS$1 && rayT < bestT) {
|
|
17184
|
-
bestT = rayT;
|
|
17185
|
-
best = [origin[0] + direction[0] * rayT, origin[1] + direction[1] * rayT];
|
|
17186
|
-
}
|
|
17187
|
-
}
|
|
17188
|
-
return best;
|
|
17189
|
-
}
|
|
17190
|
-
function polygonCentroid(poly) {
|
|
17191
|
-
let area2 = 0;
|
|
17192
|
-
let cx = 0;
|
|
17193
|
-
let cy = 0;
|
|
17194
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
17195
|
-
const a2 = poly[index2];
|
|
17196
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
17197
|
-
const crossValue = cross$3(a2, b);
|
|
17198
|
-
area2 += crossValue;
|
|
17199
|
-
cx += (a2[0] + b[0]) * crossValue;
|
|
17200
|
-
cy += (a2[1] + b[1]) * crossValue;
|
|
17201
|
-
}
|
|
17202
|
-
if (Math.abs(area2) < EPS$1) return averagePoint(poly);
|
|
17203
|
-
return [cx / (3 * area2), cy / (3 * area2)];
|
|
17204
|
-
}
|
|
17205
|
-
function averagePoint(poly) {
|
|
17206
|
-
let x2 = 0;
|
|
17207
|
-
let y2 = 0;
|
|
17208
|
-
for (const point of poly) {
|
|
17209
|
-
x2 += point[0];
|
|
17210
|
-
y2 += point[1];
|
|
17211
|
-
}
|
|
17212
|
-
return [x2 / poly.length, y2 / poly.length];
|
|
17213
|
-
}
|
|
17214
|
-
function isConvexPolygon(poly) {
|
|
17215
|
-
let sign = 0;
|
|
17216
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
17217
|
-
const a2 = poly[index2];
|
|
17218
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
17219
|
-
const c2 = poly[(index2 + 2) % poly.length];
|
|
17220
|
-
const turn = cross$3([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
|
|
17221
|
-
if (Math.abs(turn) < EPS$1) continue;
|
|
17222
|
-
const currentSign = Math.sign(turn);
|
|
17223
|
-
if (sign !== 0 && currentSign !== sign) return false;
|
|
17224
|
-
sign = currentSign;
|
|
17225
|
-
}
|
|
17226
|
-
return sign !== 0;
|
|
17227
|
-
}
|
|
17228
|
-
function cross$3(a2, b) {
|
|
17229
|
-
return a2[0] * b[1] - a2[1] * b[0];
|
|
17230
|
-
}
|
|
17231
|
-
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 = {}) {
|
|
17232
17382
|
if (profiles.length < 2) return null;
|
|
17233
17383
|
const classified = profiles.map((loops) => classifyLoops(loops));
|
|
17234
17384
|
const outerCount = classified[0].outers.length;
|
|
@@ -17243,7 +17393,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
17243
17393
|
const holeGroups = holeCount > 0 ? matchLoopsAcrossProfiles(classified.map((c2) => c2.holes)) : [];
|
|
17244
17394
|
const outerSolids = [];
|
|
17245
17395
|
for (const group of outerGroups) {
|
|
17246
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
17396
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
17247
17397
|
if (!solid) {
|
|
17248
17398
|
for (const s of outerSolids) s.delete();
|
|
17249
17399
|
return null;
|
|
@@ -17260,7 +17410,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
17260
17410
|
if (holeGroups.length > 0) {
|
|
17261
17411
|
const holeSolids = [];
|
|
17262
17412
|
for (const group of holeGroups) {
|
|
17263
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
17413
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
17264
17414
|
if (!solid) {
|
|
17265
17415
|
result.delete();
|
|
17266
17416
|
for (const s of holeSolids) s.delete();
|
|
@@ -17346,68 +17496,527 @@ function signedArea$3(loop) {
|
|
|
17346
17496
|
}
|
|
17347
17497
|
return area * 0.5;
|
|
17348
17498
|
}
|
|
17349
|
-
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) {
|
|
17350
17903
|
const normalizedLoops = loops.map((loop) => {
|
|
17351
17904
|
const area = signedArea$3(loop);
|
|
17352
17905
|
return area < 0 ? [...loop].reverse() : loop;
|
|
17353
17906
|
});
|
|
17354
|
-
|
|
17355
|
-
|
|
17356
|
-
|
|
17357
|
-
|
|
17358
|
-
|
|
17359
|
-
const
|
|
17360
|
-
const
|
|
17361
|
-
const
|
|
17362
|
-
|
|
17363
|
-
|
|
17364
|
-
|
|
17365
|
-
|
|
17366
|
-
const
|
|
17367
|
-
|
|
17368
|
-
|
|
17369
|
-
|
|
17370
|
-
|
|
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
|
+
}
|
|
17371
17941
|
}
|
|
17942
|
+
fwdIdx.push(fwd);
|
|
17943
|
+
bwdIdx.push(bwd);
|
|
17372
17944
|
}
|
|
17373
|
-
|
|
17374
|
-
|
|
17375
|
-
const nextIdx = (i + 1) * N;
|
|
17945
|
+
const triangles = [];
|
|
17946
|
+
for (let r = 0; r < R - 1; r++) {
|
|
17376
17947
|
for (let j = 0; j < N; j++) {
|
|
17377
17948
|
const j1 = (j + 1) % N;
|
|
17378
|
-
const v0 =
|
|
17379
|
-
const
|
|
17380
|
-
const v2 =
|
|
17381
|
-
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];
|
|
17382
17953
|
triangles.push(v0, v3, v2);
|
|
17383
17954
|
triangles.push(v0, v2, v1);
|
|
17384
17955
|
}
|
|
17385
17956
|
}
|
|
17386
|
-
const
|
|
17387
|
-
const
|
|
17388
|
-
|
|
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) {
|
|
17389
17964
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
17390
|
-
triangles.push(v0, v2, v1);
|
|
17965
|
+
triangles.push(bottomBase + v0, bottomBase + v2, bottomBase + v1);
|
|
17391
17966
|
}
|
|
17392
|
-
const
|
|
17393
|
-
const
|
|
17394
|
-
const
|
|
17395
|
-
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) {
|
|
17396
17972
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
17397
|
-
triangles.push(
|
|
17973
|
+
triangles.push(topBase + v0, topBase + v1, topBase + v2);
|
|
17398
17974
|
}
|
|
17399
17975
|
const mesh = new wasm.Mesh({
|
|
17400
|
-
numProp:
|
|
17401
|
-
vertProperties: new Float32Array(
|
|
17976
|
+
numProp: 6,
|
|
17977
|
+
vertProperties: new Float32Array(vertProps),
|
|
17402
17978
|
triVerts: new Uint32Array(triangles)
|
|
17403
17979
|
});
|
|
17404
17980
|
try {
|
|
17981
|
+
mesh.merge();
|
|
17405
17982
|
const manifold = new wasm.Manifold(mesh);
|
|
17406
17983
|
return manifold;
|
|
17407
17984
|
} catch (_e2) {
|
|
17408
17985
|
return null;
|
|
17409
17986
|
}
|
|
17410
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
|
+
}
|
|
17411
18020
|
let _wasm$1 = null;
|
|
17412
18021
|
async function initManifoldWasm() {
|
|
17413
18022
|
if (_wasm$1) return _wasm$1;
|
|
@@ -17513,7 +18122,9 @@ const _ManifoldShapeBackend = class _ManifoldShapeBackend {
|
|
|
17513
18122
|
return this.getLiveManifold("numTri()").numTri();
|
|
17514
18123
|
}
|
|
17515
18124
|
getMesh() {
|
|
17516
|
-
|
|
18125
|
+
const manifold = this.getLiveManifold("getMesh()");
|
|
18126
|
+
const mesh = manifold.numProp() >= 3 ? manifold.getMesh(0) : manifold.getMesh();
|
|
18127
|
+
return mesh;
|
|
17517
18128
|
}
|
|
17518
18129
|
slice(offset) {
|
|
17519
18130
|
return this.getLiveManifold("slice()").slice(offset);
|
|
@@ -17562,6 +18173,43 @@ function requireManifoldShapeBackend(backend, apiName = "requireManifoldShapeBac
|
|
|
17562
18173
|
}
|
|
17563
18174
|
throw new Error(`${apiName} currently requires a Manifold-backed runtime shape.`);
|
|
17564
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
|
+
}
|
|
17565
18213
|
function sweepStitched(profilePolygons, pathPoints, up, wasm) {
|
|
17566
18214
|
if (pathPoints.length < 2) return null;
|
|
17567
18215
|
if (profilePolygons.length === 0) return null;
|
|
@@ -18259,7 +18907,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
|
|
|
18259
18907
|
throw new Error("offsetSolid() collapsed the compatible-loft height span.");
|
|
18260
18908
|
}
|
|
18261
18909
|
const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
|
|
18262
|
-
const stitched = loftStitched(offsetPolygons, heights, wasm);
|
|
18910
|
+
const stitched = loftStitched(offsetPolygons, heights, wasm, { edgeLength: plan.edgeLength });
|
|
18263
18911
|
if (!stitched) {
|
|
18264
18912
|
throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
|
|
18265
18913
|
}
|
|
@@ -18394,12 +19042,12 @@ function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
|
18394
19042
|
disposeWasmObject(crossSection);
|
|
18395
19043
|
}
|
|
18396
19044
|
});
|
|
18397
|
-
if (inputPolygons.length >= 2) {
|
|
18398
|
-
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 });
|
|
18399
19047
|
if (stitched) return stitched;
|
|
18400
19048
|
}
|
|
18401
19049
|
const input = buildLoftLevelSetInput(inputPolygons, plan.heights, { edgeLength: plan.edgeLength, boundsPadding: plan.boundsPadding });
|
|
18402
|
-
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
19050
|
+
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm, plan.meshing);
|
|
18403
19051
|
}
|
|
18404
19052
|
function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
18405
19053
|
const crossSection = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
@@ -18664,7 +19312,7 @@ function lowerFromSlicesToManifold(plan, wasm) {
|
|
|
18664
19312
|
}
|
|
18665
19313
|
});
|
|
18666
19314
|
const heights = sorted.map((s) => s.offset);
|
|
18667
|
-
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm) : null;
|
|
19315
|
+
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm, { edgeLength: plan.edgeLength }) : null;
|
|
18668
19316
|
if (stitched) {
|
|
18669
19317
|
solid = stitched;
|
|
18670
19318
|
} else {
|
|
@@ -19105,17 +19753,21 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
19105
19753
|
if (diagnostics) diagnostics.projectionMs = nowMs() - projectionStart;
|
|
19106
19754
|
const simplificationStart = nowMs();
|
|
19107
19755
|
if (((meshing == null ? void 0 : meshing.simplify) ?? "safe") !== "off" && snMesh.numTris > 100) {
|
|
19108
|
-
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties,
|
|
19756
|
+
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties, vertProps6, edgeLength2, meshing == null ? void 0 : meshing.maxTriangles);
|
|
19109
19757
|
}
|
|
19110
19758
|
if (diagnostics) {
|
|
19111
19759
|
diagnostics.simplificationMs = nowMs() - simplificationStart;
|
|
19112
19760
|
diagnostics.trianglesAfterSimplification = triVerts.length / 3;
|
|
19113
19761
|
}
|
|
19114
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";
|
|
19115
19764
|
throw new Error(
|
|
19116
|
-
`SDF meshing
|
|
19765
|
+
`SDF meshing ${verb} ${triVerts.length / 3} safe triangles, above maxTriangles=${meshing.maxTriangles}. Increase maxTriangles or use a larger edgeLength.`
|
|
19117
19766
|
);
|
|
19118
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
|
+
}
|
|
19119
19771
|
const wasmMesh = new wasm.Mesh({
|
|
19120
19772
|
numProp: 6,
|
|
19121
19773
|
vertProperties: vertProps6,
|
|
@@ -19133,28 +19785,102 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
19133
19785
|
disposeWasmObject(wasmMesh);
|
|
19134
19786
|
}
|
|
19135
19787
|
}
|
|
19136
|
-
function simplifySdfMesh(triVerts, vertProperties,
|
|
19788
|
+
function simplifySdfMesh(triVerts, vertProperties, finalVertProperties, edgeLength2, maxTriangles) {
|
|
19137
19789
|
const maxError = edgeLength2 * 0.15;
|
|
19138
19790
|
const inputTriangles = triVerts.length / 3;
|
|
19139
|
-
const
|
|
19140
|
-
const
|
|
19791
|
+
const ratios = buildSimplificationRatios(inputTriangles, maxTriangles);
|
|
19792
|
+
const vertexCount = finalVertProperties.length / 6;
|
|
19793
|
+
let bestValid = null;
|
|
19141
19794
|
for (const ratio of ratios) {
|
|
19142
|
-
let simplified
|
|
19143
|
-
simplified = filterDegenerateTriangles(simplified);
|
|
19144
|
-
let mesh = null;
|
|
19145
|
-
let manifold = null;
|
|
19795
|
+
let simplified;
|
|
19146
19796
|
try {
|
|
19147
|
-
|
|
19148
|
-
manifold = new wasm.Manifold(mesh);
|
|
19149
|
-
return simplified;
|
|
19797
|
+
simplified = simplifyMesh(triVerts, vertProperties, ratio, maxError);
|
|
19150
19798
|
} catch {
|
|
19151
|
-
|
|
19152
|
-
|
|
19153
|
-
|
|
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
|
+
}
|
|
19154
19809
|
}
|
|
19155
19810
|
}
|
|
19811
|
+
if (bestValid) {
|
|
19812
|
+
return bestValid;
|
|
19813
|
+
}
|
|
19156
19814
|
return triVerts;
|
|
19157
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
|
+
}
|
|
19158
19884
|
function filterDegenerateTriangles(triVerts) {
|
|
19159
19885
|
let writeIdx = 0;
|
|
19160
19886
|
for (let i = 0; i < triVerts.length; i += 3) {
|
|
@@ -20368,11 +21094,12 @@ function profileMayContainInteriorLoopsForOCCT(plan) {
|
|
|
20368
21094
|
return false;
|
|
20369
21095
|
}
|
|
20370
21096
|
}
|
|
21097
|
+
const occtLoweredCache = /* @__PURE__ */ new WeakMap();
|
|
20371
21098
|
function lowerShapeCompilePlanToOCCT(plan, oc) {
|
|
20372
|
-
const cached = plan
|
|
21099
|
+
const cached = occtLoweredCache.get(plan);
|
|
20373
21100
|
if (cached) return cached;
|
|
20374
21101
|
const shape = _lowerShapeCompilePlanToOCCTInner(plan, oc);
|
|
20375
|
-
plan
|
|
21102
|
+
occtLoweredCache.set(plan, shape);
|
|
20376
21103
|
return shape;
|
|
20377
21104
|
}
|
|
20378
21105
|
function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
|
|
@@ -36472,29 +37199,11 @@ function lowerExactSlicedShapeCompilePlanToTruckProfileBackend(plan, offset) {
|
|
|
36472
37199
|
return profilePlan ? lowerProfileCompilePlanToTruckProfileBackend(profilePlan) : null;
|
|
36473
37200
|
}
|
|
36474
37201
|
const SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION = "shape-plan-v1";
|
|
36475
|
-
|
|
36476
|
-
|
|
36477
|
-
|
|
36478
|
-
}
|
|
36479
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
36480
|
-
return JSON.stringify(value);
|
|
36481
|
-
}
|
|
36482
|
-
if (Array.isArray(value)) {
|
|
36483
|
-
return `[${value.map((item) => stableJsonEncode(item, true) ?? "null").join(",")}]`;
|
|
36484
|
-
}
|
|
36485
|
-
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
36486
|
-
const encodedEntries = [];
|
|
36487
|
-
for (const [key, item] of entries) {
|
|
36488
|
-
const encoded = stableJsonEncode(item, false);
|
|
36489
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
36490
|
-
}
|
|
36491
|
-
return `{${encodedEntries.join(",")}}`;
|
|
36492
|
-
}
|
|
36493
|
-
function stableJsonStringify(value) {
|
|
36494
|
-
return stableJsonEncode(value, false) ?? "null";
|
|
36495
|
-
}
|
|
37202
|
+
const exactPlanHasher = createStructuralHasher({
|
|
37203
|
+
skipKey: (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation"
|
|
37204
|
+
});
|
|
36496
37205
|
function shapeCompilePlanCacheKey(plan) {
|
|
36497
|
-
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${
|
|
37206
|
+
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${exactPlanHasher(plan)}`;
|
|
36498
37207
|
}
|
|
36499
37208
|
function formatFaceQuery(query) {
|
|
36500
37209
|
const parts = [];
|
|
@@ -39347,7 +40056,7 @@ class ShapeGroup {
|
|
|
39347
40056
|
};
|
|
39348
40057
|
return this.attachTo(parent, face, opp[face], uvMap[face](u2, v, p2));
|
|
39349
40058
|
}
|
|
39350
|
-
/** 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. */
|
|
39351
40060
|
rotate(axis, angleDeg, options) {
|
|
39352
40061
|
requireRotateAxis(axis, "ShapeGroup.rotate()");
|
|
39353
40062
|
requireFiniteAngle(angleDeg, "ShapeGroup.rotate()");
|
|
@@ -39592,6 +40301,11 @@ class ShapeGroup {
|
|
|
39592
40301
|
* Position this group by matching connectors to a target.
|
|
39593
40302
|
* Connector names support dotted paths into named children: "ChildName.connectorName".
|
|
39594
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
|
+
*
|
|
39595
40309
|
* Overloads:
|
|
39596
40310
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
39597
40311
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -41256,10 +41970,10 @@ function rigidTransformStepsFromMatrix(m2) {
|
|
|
41256
41970
|
return [{ kind: "workplanePlacement", matrix: Array.from(m2) }];
|
|
41257
41971
|
}
|
|
41258
41972
|
async function initKernel() {
|
|
41259
|
-
const [manifoldModule] = await Promise.all([initManifoldWasm(), initMeshoptimizer()
|
|
41973
|
+
const [manifoldModule] = await Promise.all([initManifoldWasm(), initMeshoptimizer()]);
|
|
41260
41974
|
return manifoldModule;
|
|
41261
41975
|
}
|
|
41262
|
-
const DEFAULT_ACTIVE_BACKEND = "
|
|
41976
|
+
const DEFAULT_ACTIVE_BACKEND = "manifold";
|
|
41263
41977
|
let _activeBackend = DEFAULT_ACTIVE_BACKEND;
|
|
41264
41978
|
let _runtimeWarn = (msg) => console.warn(msg);
|
|
41265
41979
|
async function activateBackend(backend) {
|
|
@@ -41286,6 +42000,7 @@ const _shapePlacementRefs = /* @__PURE__ */ new WeakMap();
|
|
|
41286
42000
|
const _shapeExplodeHint = /* @__PURE__ */ new WeakMap();
|
|
41287
42001
|
const _shapeRuntimeBackends = /* @__PURE__ */ new WeakMap();
|
|
41288
42002
|
const _shapeTopology = /* @__PURE__ */ new WeakMap();
|
|
42003
|
+
const _shapeLineageTokens = /* @__PURE__ */ new WeakMap();
|
|
41289
42004
|
const _shapeFaceLabels = /* @__PURE__ */ new WeakMap();
|
|
41290
42005
|
const _shapeReferenceNames = /* @__PURE__ */ new WeakMap();
|
|
41291
42006
|
const _shapeReferenceAliases = /* @__PURE__ */ new WeakMap();
|
|
@@ -41346,6 +42061,10 @@ function copyShapeReferenceMetadata(source, target) {
|
|
|
41346
42061
|
const aliases = cloneReferenceAliases(_shapeReferenceAliases.get(source));
|
|
41347
42062
|
if (aliases && aliases.size > 0) _shapeReferenceAliases.set(target, aliases);
|
|
41348
42063
|
}
|
|
42064
|
+
function copyShapeLineage(source, target) {
|
|
42065
|
+
const token = _shapeLineageTokens.get(source);
|
|
42066
|
+
if (token) _shapeLineageTokens.set(target, token);
|
|
42067
|
+
}
|
|
41349
42068
|
function assertNonEmptyReferenceName(name, apiName) {
|
|
41350
42069
|
const trimmed = name.trim();
|
|
41351
42070
|
if (!trimmed) throw new Error(`${apiName} requires a non-empty reference name.`);
|
|
@@ -41414,50 +42133,25 @@ function setShapeRuntimeBackendInternal(shape, payload) {
|
|
|
41414
42133
|
return shape;
|
|
41415
42134
|
}
|
|
41416
42135
|
function setShapeCompilePlanInternal(shape, plan) {
|
|
41417
|
-
_shapeCompilePlans.set(shape,
|
|
42136
|
+
_shapeCompilePlans.set(shape, deepFreezePlanData(plan));
|
|
41418
42137
|
recordShapeSourceSpanInternal(shape, plan);
|
|
41419
42138
|
return shape;
|
|
41420
42139
|
}
|
|
41421
|
-
function cloneShapeSourceSpanRecords(records) {
|
|
41422
|
-
return (records ?? []).map((record) => ({
|
|
41423
|
-
planCacheKey: record.planCacheKey,
|
|
41424
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41425
|
-
}));
|
|
41426
|
-
}
|
|
41427
42140
|
function upsertShapeSourceSpanRecord(shape, record) {
|
|
41428
|
-
|
|
41429
|
-
if (records
|
|
41430
|
-
|
|
41431
|
-
|
|
41432
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41433
|
-
});
|
|
41434
|
-
_shapeSourceSpans.set(shape, records);
|
|
41435
|
-
}
|
|
41436
|
-
function stableTraceSourcePlanEncode(value, arrayMember) {
|
|
41437
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
41438
|
-
return arrayMember ? "null" : void 0;
|
|
41439
|
-
}
|
|
41440
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
41441
|
-
return JSON.stringify(value);
|
|
42141
|
+
let records = _shapeSourceSpans.get(shape);
|
|
42142
|
+
if (!records) {
|
|
42143
|
+
records = /* @__PURE__ */ new Map();
|
|
42144
|
+
_shapeSourceSpans.set(shape, records);
|
|
41442
42145
|
}
|
|
41443
|
-
if (
|
|
41444
|
-
|
|
41445
|
-
}
|
|
41446
|
-
const record = value;
|
|
41447
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
41448
|
-
return stableTraceSourcePlanEncode(record.base, arrayMember);
|
|
41449
|
-
}
|
|
41450
|
-
const entries = Object.entries(record).sort(([left], [right]) => left.localeCompare(right));
|
|
41451
|
-
const encodedEntries = [];
|
|
41452
|
-
for (const [key, item] of entries) {
|
|
41453
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
41454
|
-
const encoded = stableTraceSourcePlanEncode(item, false);
|
|
41455
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
41456
|
-
}
|
|
41457
|
-
return `{${encodedEntries.join(",")}}`;
|
|
42146
|
+
if (records.has(record.planCacheKey)) return;
|
|
42147
|
+
records.set(record.planCacheKey, Object.freeze({ ...record.sourceSpan }));
|
|
41458
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
|
+
});
|
|
41459
42153
|
function normalizedTraceSourcePlanCacheKey(plan) {
|
|
41460
|
-
return `shape-plan-v1:${
|
|
42154
|
+
return `shape-plan-v1:${traceSourcePlanHasher(plan)}`;
|
|
41461
42155
|
}
|
|
41462
42156
|
function recordShapeSourceSpanInternal(shape, plan) {
|
|
41463
42157
|
if (!hasActiveRuntimeSourceResolver()) return;
|
|
@@ -41477,13 +42171,20 @@ function recordShapeSourceSpanInternal(shape, plan) {
|
|
|
41477
42171
|
}
|
|
41478
42172
|
}
|
|
41479
42173
|
function copyShapeSourceSpans(source, target) {
|
|
41480
|
-
const records =
|
|
41481
|
-
if (records.
|
|
42174
|
+
const records = _shapeSourceSpans.get(source);
|
|
42175
|
+
if (records && records.size > 0) _shapeSourceSpans.set(target, new Map(records));
|
|
41482
42176
|
}
|
|
41483
42177
|
function mergeShapeSourceSpans(sources, target) {
|
|
42178
|
+
let records = _shapeSourceSpans.get(target);
|
|
41484
42179
|
for (const source of sources) {
|
|
41485
|
-
|
|
41486
|
-
|
|
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);
|
|
41487
42188
|
}
|
|
41488
42189
|
}
|
|
41489
42190
|
}
|
|
@@ -41518,7 +42219,7 @@ function getShapeRuntimeBackendInternal(shape) {
|
|
|
41518
42219
|
function getShapeCompilePlanInternal(shape) {
|
|
41519
42220
|
const stored = _shapeCompilePlans.get(shape);
|
|
41520
42221
|
if (!stored) throw new Error("Shape has no compile plan — every Shape must have an explicit plan set via setShapeCompilePlanInternal()");
|
|
41521
|
-
return
|
|
42222
|
+
return stored;
|
|
41522
42223
|
}
|
|
41523
42224
|
function getShapePlacementRefsInternal(shape) {
|
|
41524
42225
|
return clonePlacementReferences(_shapePlacementRefs.get(shape) ?? createPlacementReferences());
|
|
@@ -42210,6 +42911,30 @@ function checkLabelCollisions(operation2, plans) {
|
|
|
42210
42911
|
seen.push(...labels);
|
|
42211
42912
|
}
|
|
42212
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
|
+
}
|
|
42213
42938
|
function withCopiedDimensions(source, out) {
|
|
42214
42939
|
setShapeDimensionsInternal(out, cloneDimensions(getShapeDimensionsInternal(source), true));
|
|
42215
42940
|
setShapeGeometryInfoInternal(out, getShapeGeometryInfoInternal(source));
|
|
@@ -42224,6 +42949,7 @@ function withCopiedDimensions(source, out) {
|
|
|
42224
42949
|
const sourceLabels = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42225
42950
|
if (sourceLabels) _shapeFaceLabels.set(out, sourceLabels);
|
|
42226
42951
|
copyShapeReferenceMetadata(source, out);
|
|
42952
|
+
copyShapeLineage(source, out);
|
|
42227
42953
|
copyShapeSourceSpans(source, out);
|
|
42228
42954
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42229
42955
|
}
|
|
@@ -42253,6 +42979,7 @@ function withTransformedDimensions(source, out, m2) {
|
|
|
42253
42979
|
const sourceLabelsT = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42254
42980
|
if (sourceLabelsT) _shapeFaceLabels.set(out, sourceLabelsT);
|
|
42255
42981
|
copyShapeReferenceMetadata(source, out);
|
|
42982
|
+
copyShapeLineage(source, out);
|
|
42256
42983
|
copyShapeSourceSpans(source, out);
|
|
42257
42984
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42258
42985
|
}
|
|
@@ -42680,6 +43407,7 @@ class Shape {
|
|
|
42680
43407
|
this.colorHex = color;
|
|
42681
43408
|
setShapeRuntimeBackendInternal(this, payload);
|
|
42682
43409
|
setShapeGeometryInfoInternal(this, createGeometryInfo(geometryInfo));
|
|
43410
|
+
_shapeLineageTokens.set(this, {});
|
|
42683
43411
|
}
|
|
42684
43412
|
/** @internal Use .color() instead. */
|
|
42685
43413
|
setColor(value) {
|
|
@@ -42798,6 +43526,12 @@ class Shape {
|
|
|
42798
43526
|
* with `union()` / `difference()` to avoid collisions. Collision detection throws a
|
|
42799
43527
|
* clear error with a fix suggestion.
|
|
42800
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
|
+
*
|
|
42801
43535
|
* For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's
|
|
42802
43536
|
* compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops)
|
|
42803
43537
|
* are resolved via coplanar triangle clustering.
|
|
@@ -43214,7 +43948,7 @@ class Shape {
|
|
|
43214
43948
|
const tbb = s.boundingBox();
|
|
43215
43949
|
return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
|
|
43216
43950
|
}
|
|
43217
|
-
/** 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. */
|
|
43218
43952
|
rotate(axis, angleDeg, options) {
|
|
43219
43953
|
validateRotateAxis(axis, "Shape.rotate()");
|
|
43220
43954
|
validateRotateAngle(angleDeg, "Shape.rotate()");
|
|
@@ -43401,7 +44135,7 @@ class Shape {
|
|
|
43401
44135
|
* Warn if a boolean operation had no geometric effect.
|
|
43402
44136
|
* Compares volumes before and after; if they match within tolerance, the operation was a no-op.
|
|
43403
44137
|
*/
|
|
43404
|
-
static _checkBooleanNoOp(op, base, result) {
|
|
44138
|
+
static _checkBooleanNoOp(op, base, result, tools = []) {
|
|
43405
44139
|
try {
|
|
43406
44140
|
if (op === "intersection") {
|
|
43407
44141
|
if (result.isEmpty()) {
|
|
@@ -43414,8 +44148,15 @@ class Shape {
|
|
|
43414
44148
|
const volAfter = result.volume();
|
|
43415
44149
|
const tol = Math.max(volBefore * 1e-4, 1e-3);
|
|
43416
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(" ");
|
|
43417
44156
|
_runtimeWarn(
|
|
43418
|
-
`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
|
|
43419
44160
|
);
|
|
43420
44161
|
}
|
|
43421
44162
|
}
|
|
@@ -43475,7 +44216,7 @@ class Shape {
|
|
|
43475
44216
|
),
|
|
43476
44217
|
nextPlan
|
|
43477
44218
|
);
|
|
43478
|
-
Shape._checkBooleanNoOp("difference", this, resultShape);
|
|
44219
|
+
Shape._checkBooleanNoOp("difference", this, resultShape, shapes.slice(1));
|
|
43479
44220
|
return resultShape;
|
|
43480
44221
|
}
|
|
43481
44222
|
/** Keep only the overlap with other shapes. Method form of intersection(). */
|
|
@@ -43878,6 +44619,11 @@ class Shape {
|
|
|
43878
44619
|
/**
|
|
43879
44620
|
* Position this shape by matching connectors to a target.
|
|
43880
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
|
+
*
|
|
43881
44627
|
* Overloads:
|
|
43882
44628
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
43883
44629
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -44255,15 +45001,23 @@ function computeGeometryArrays(mesh, options = {}) {
|
|
|
44255
45001
|
normals[o + 7] = vertNormals[i2 * 3 + 1];
|
|
44256
45002
|
normals[o + 8] = vertNormals[i2 * 3 + 2];
|
|
44257
45003
|
} else if (numProp >= 6) {
|
|
44258
|
-
|
|
44259
|
-
|
|
44260
|
-
|
|
44261
|
-
|
|
44262
|
-
|
|
44263
|
-
|
|
44264
|
-
|
|
44265
|
-
|
|
44266
|
-
|
|
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
|
+
}
|
|
44267
45021
|
} else {
|
|
44268
45022
|
normals[o] = fnx;
|
|
44269
45023
|
normals[o + 1] = fny;
|