forgecad 0.9.16 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AdminPage-CXvls4-J.js → AdminPage-DwYHz72L.js} +1 -1
- package/dist/assets/{BenchmarkPage-B27zk8xL.js → BenchmarkPage-a9_f-1US.js} +1 -1
- package/dist/assets/{BlogPage-CMAVvgQL.js → BlogPage-DodHpvmf.js} +1 -1
- package/dist/assets/{DocsPage-knf4I4h7.js → DocsPage-B5LePEuj.js} +8 -858
- package/dist/assets/EditorApp-QXsAISLR.js +16307 -0
- package/dist/assets/{EmbedViewer-D7ZGlFjx.js → EmbedViewer-DdEHGUMU.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-CnevhTE8.js → LandingPageProofDriven-yhhOodbf.js} +1 -1
- package/dist/assets/{LegalPage-BPTUmqeg.js → LegalPage-5RbKRGYK.js} +1 -1
- package/dist/assets/{PricingPage-B0D4goG_.js → PricingPage-E3Rma7aV.js} +1 -1
- package/dist/assets/{SettingsPage-CFF-UgjI.js → SettingsPage-BJZcM97j.js} +1 -1
- package/dist/assets/{app-T0pDcSX4.js → app-DSYrDg0V.js} +733 -205
- package/dist/assets/cli/{render-C5pcIISc.js → render-ZMHR9HkV.js} +19 -46
- package/dist/assets/{constructionHistoryWorker-Ba2Hm58b.js → constructionHistoryWorker-AwMMWSxg.js} +1103 -349
- package/dist/assets/{evalWorker-vkx310U2.js → evalWorker-DbNs7Dkp.js} +3798 -1622
- package/dist/assets/{inspectWorker-BuTJDVX6.js → inspectWorker-CZsCFtQT.js} +1163 -409
- package/dist/assets/{jointPose-B_Cgedn9.js → jointPose-DO6mnXn_.js} +1 -1
- package/dist/assets/{manifold-BWgsjmAM.js → manifold-BGlQBBH9.js} +1 -1
- package/dist/assets/{manifold-rZexZI0G.js → manifold-BU-tJwQh.js} +1 -1
- package/dist/assets/{manifold-D6IFSkhH.js → manifold-fy2MV7K1.js} +2 -2
- package/dist/assets/{reportWorker-0AGij1Ru.js → reportWorker-DO6hcQbh.js} +7155 -2437
- package/dist/assets/{scalar-sampling-budget-J5cuzxT1.js → scalar-sampling-budget-o90NSNmF.js} +3940 -1742
- 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 +63 -241
- package/dist/docs-raw/README.md +6 -0
- package/dist/docs-raw/component-model.md +17 -150
- package/dist/docs-raw/generated/assembly.md +139 -598
- package/dist/docs-raw/generated/concepts.md +245 -3501
- package/dist/docs-raw/generated/core.md +277 -1251
- package/dist/docs-raw/generated/curves.md +387 -1608
- package/dist/docs-raw/generated/legacy.md +162 -0
- package/dist/docs-raw/generated/lib.md +227 -85
- package/dist/docs-raw/generated/output.md +38 -73
- 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/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 +1 -1
- package/dist/llms.txt +1 -2
- package/dist/sitemap.xml +13 -13
- package/dist-cli/{check-compiler-SYQ2PWOB.js → check-compiler-JTVBITCR.js} +1 -1
- package/dist-cli/{check-query-propagation-HIAGV62W.js → check-query-propagation-3FFLSMVN.js} +1 -1
- package/dist-cli/{chunk-SPZE3DUY.js → chunk-OAN5T4XD.js} +4412 -2212
- package/dist-cli/forgecad.js +507 -179
- package/dist-skill/CONTEXT.md +2172 -8377
- package/dist-skill/SKILL.md +15 -15
- package/dist-skill/docs/API/core/concepts.md +27 -157
- package/dist-skill/docs/CLI.md +63 -241
- package/dist-skill/docs/generated/assembly.md +138 -549
- package/dist-skill/docs/generated/core.md +277 -1251
- package/dist-skill/docs/generated/curves.md +387 -1609
- package/dist-skill/docs/generated/lib.md +227 -85
- package/dist-skill/docs/generated/output.md +38 -73
- 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/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
|
@@ -11609,6 +11609,8 @@ function cloneSdfNode(node) {
|
|
|
11609
11609
|
return { kind: "sdf:circularArray", child: cloneSdfNode(node.child), count: node.count, offset: node.offset };
|
|
11610
11610
|
case "sdf:shell":
|
|
11611
11611
|
return { kind: "sdf:shell", child: cloneSdfNode(node.child), thickness: node.thickness };
|
|
11612
|
+
case "sdf:offset":
|
|
11613
|
+
return { kind: "sdf:offset", child: cloneSdfNode(node.child), distance: node.distance };
|
|
11612
11614
|
case "sdf:displace":
|
|
11613
11615
|
return {
|
|
11614
11616
|
kind: "sdf:displace",
|
|
@@ -11693,7 +11695,7 @@ function cloneSdfNode(node) {
|
|
|
11693
11695
|
}
|
|
11694
11696
|
}
|
|
11695
11697
|
const SHEET_METAL_EDGES = ["top", "right", "bottom", "left"];
|
|
11696
|
-
const EPS$
|
|
11698
|
+
const EPS$3 = 1e-9;
|
|
11697
11699
|
function isFinitePositive$1(value) {
|
|
11698
11700
|
return Number.isFinite(value) && value > 0;
|
|
11699
11701
|
}
|
|
@@ -11734,7 +11736,7 @@ function edgeDisplayName(edge) {
|
|
|
11734
11736
|
return `sheetMetal().flange("${edge}", ...)`;
|
|
11735
11737
|
}
|
|
11736
11738
|
function normalizeAngle(angleDeg) {
|
|
11737
|
-
return Math.abs(angleDeg) <= EPS$
|
|
11739
|
+
return Math.abs(angleDeg) <= EPS$3 ? 0 : angleDeg;
|
|
11738
11740
|
}
|
|
11739
11741
|
function validateSheetMetalModel(model) {
|
|
11740
11742
|
if (!isFinitePositive$1(model.panel.width) || !isFinitePositive$1(model.panel.height)) {
|
|
@@ -11746,7 +11748,7 @@ function validateSheetMetalModel(model) {
|
|
|
11746
11748
|
if (!isFiniteNonNegative(model.bendRadius)) {
|
|
11747
11749
|
return "sheetMetal() requires a finite non-negative bendRadius.";
|
|
11748
11750
|
}
|
|
11749
|
-
if (model.bendRadius <= EPS$
|
|
11751
|
+
if (model.bendRadius <= EPS$3) {
|
|
11750
11752
|
return "sheetMetal() v1 requires a positive bendRadius so the bend region stays explicit instead of collapsing into a sharp fold.";
|
|
11751
11753
|
}
|
|
11752
11754
|
if (model.bendAllowance.kind !== "k-factor") {
|
|
@@ -11808,7 +11810,7 @@ function deriveSheetMetalModel(model) {
|
|
|
11808
11810
|
const trimEnd = flanges.has(adjacent.end) ? model.cornerRelief.size : 0;
|
|
11809
11811
|
const fullLength = edge === "top" || edge === "bottom" ? model.panel.width : model.panel.height;
|
|
11810
11812
|
const span = fullLength - trimStart - trimEnd;
|
|
11811
|
-
if (!(span > EPS$
|
|
11813
|
+
if (!(span > EPS$3)) {
|
|
11812
11814
|
throw new Error(
|
|
11813
11815
|
`${edgeDisplayName(edge)} loses all usable span after applying the defended rectangular corner relief size ${model.cornerRelief.size}.`
|
|
11814
11816
|
);
|
|
@@ -11854,7 +11856,7 @@ function transformPlacement(origin, u2, v, normal) {
|
|
|
11854
11856
|
};
|
|
11855
11857
|
}
|
|
11856
11858
|
function translatePlan(plan, x2, y2, z2) {
|
|
11857
|
-
if (Math.abs(x2) <= EPS$
|
|
11859
|
+
if (Math.abs(x2) <= EPS$3 && Math.abs(y2) <= EPS$3 && Math.abs(z2) <= EPS$3) return cloneShapeCompilePlan(plan);
|
|
11858
11860
|
return appendShapeCompileTransform(cloneShapeCompilePlan(plan), {
|
|
11859
11861
|
kind: "translate",
|
|
11860
11862
|
x: x2,
|
|
@@ -12347,7 +12349,7 @@ function requireNonZeroFiniteScale3(value, label) {
|
|
|
12347
12349
|
}
|
|
12348
12350
|
return scale2;
|
|
12349
12351
|
}
|
|
12350
|
-
const EPS$
|
|
12352
|
+
const EPS$2 = 1e-10;
|
|
12351
12353
|
function subVec3(a2, b) {
|
|
12352
12354
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
12353
12355
|
}
|
|
@@ -12373,7 +12375,7 @@ function projectRadial(v, axis) {
|
|
|
12373
12375
|
function signedAngleAroundAxis(from, to, axis) {
|
|
12374
12376
|
const fromLen = lengthVec3$1(from);
|
|
12375
12377
|
const toLen = lengthVec3$1(to);
|
|
12376
|
-
if (fromLen < EPS$
|
|
12378
|
+
if (fromLen < EPS$2 || toLen < EPS$2) return 0;
|
|
12377
12379
|
const fn = scaleVec3(from, 1 / fromLen);
|
|
12378
12380
|
const tn = scaleVec3(to, 1 / toLen);
|
|
12379
12381
|
const sin2 = dotVec3$4(axis, crossVec3$2(fn, tn));
|
|
@@ -12394,19 +12396,19 @@ function solveRotateAroundAngle(axis, pivot, movingPoint, targetPoint, options =
|
|
|
12394
12396
|
const targetDecomp = projectRadial(target, unitAxis);
|
|
12395
12397
|
const movingRadialLen = lengthVec3$1(movingDecomp.radial);
|
|
12396
12398
|
const targetRadialLen = lengthVec3$1(targetDecomp.radial);
|
|
12397
|
-
if (movingRadialLen < EPS$
|
|
12398
|
-
if (mode === "line" && targetRadialLen >= EPS$
|
|
12399
|
+
if (movingRadialLen < EPS$2) {
|
|
12400
|
+
if (mode === "line" && targetRadialLen >= EPS$2) {
|
|
12399
12401
|
throw new Error("rotateAroundTo(...): moving point lies on the rotation axis, so line alignment is impossible");
|
|
12400
12402
|
}
|
|
12401
12403
|
return 0;
|
|
12402
12404
|
}
|
|
12403
12405
|
if (mode === "plane") {
|
|
12404
|
-
if (targetRadialLen < EPS$
|
|
12406
|
+
if (targetRadialLen < EPS$2) {
|
|
12405
12407
|
throw new Error("rotateAroundTo(...): target point lies on the rotation axis, so the target plane is undefined");
|
|
12406
12408
|
}
|
|
12407
12409
|
return signedAngleAroundAxis(movingDecomp.radial, targetDecomp.radial, unitAxis);
|
|
12408
12410
|
}
|
|
12409
|
-
if (targetRadialLen < EPS$
|
|
12411
|
+
if (targetRadialLen < EPS$2) {
|
|
12410
12412
|
throw new Error("rotateAroundTo(...): target line lies on the rotation axis, but the moving point does not");
|
|
12411
12413
|
}
|
|
12412
12414
|
const axialTol = 1e-8 * Math.max(1, Math.abs(movingDecomp.axial), Math.abs(targetDecomp.axial));
|
|
@@ -12443,7 +12445,7 @@ function multiplyMat4(a2, b) {
|
|
|
12443
12445
|
}
|
|
12444
12446
|
function normalizeVec3$3(v) {
|
|
12445
12447
|
const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
12446
|
-
if (len < EPS$
|
|
12448
|
+
if (len < EPS$2) throw new Error("Axis must be non-zero");
|
|
12447
12449
|
return [v[0] / len, v[1] / len, v[2] / len];
|
|
12448
12450
|
}
|
|
12449
12451
|
function transformPoint$1(m2, p2, w2) {
|
|
@@ -12473,7 +12475,7 @@ function invertMat4(m2) {
|
|
|
12473
12475
|
const b10 = a21 * a33 - a23 * a31;
|
|
12474
12476
|
const b11 = a22 * a33 - a23 * a32;
|
|
12475
12477
|
const det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
|
|
12476
|
-
if (Math.abs(det) < EPS$
|
|
12478
|
+
if (Math.abs(det) < EPS$2) throw new Error("Transform matrix is not invertible");
|
|
12477
12479
|
const invDet = 1 / det;
|
|
12478
12480
|
out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * invDet;
|
|
12479
12481
|
out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * invDet;
|
|
@@ -12506,6 +12508,32 @@ class Transform {
|
|
|
12506
12508
|
static from(input) {
|
|
12507
12509
|
return input instanceof Transform ? input : new Transform(requireFiniteMat4(input, "Transform.from() matrix"));
|
|
12508
12510
|
}
|
|
12511
|
+
/**
|
|
12512
|
+
* Compose transforms in chain order: `Transform.compose(a, b, c)` applies
|
|
12513
|
+
* `a`, then `b`, then `c` — the same left-to-right order as
|
|
12514
|
+
* `Transform.from(a).mul(b).mul(c)`.
|
|
12515
|
+
*
|
|
12516
|
+
* Prefer this over manual `.mul()` chains when composing 3+ transforms
|
|
12517
|
+
* (e.g. kinematics: `local -> childBase -> jointMotion -> jointFrame ->
|
|
12518
|
+
* parentWorld`); the variadic form makes the application order explicit and
|
|
12519
|
+
* prevents order mistakes.
|
|
12520
|
+
*
|
|
12521
|
+
* **Example**
|
|
12522
|
+
*
|
|
12523
|
+
* ```ts
|
|
12524
|
+
* const world = Transform.compose(childBase, jointMotion, jointFrame, parentWorld);
|
|
12525
|
+
* ```
|
|
12526
|
+
*
|
|
12527
|
+
* @param steps Transforms (or raw 4x4 matrices) applied left to right.
|
|
12528
|
+
* @returns The composed transform. With no arguments, the identity.
|
|
12529
|
+
*/
|
|
12530
|
+
static compose(...steps) {
|
|
12531
|
+
let acc = Transform.identity();
|
|
12532
|
+
for (const step of steps) {
|
|
12533
|
+
acc = acc.mul(step);
|
|
12534
|
+
}
|
|
12535
|
+
return acc;
|
|
12536
|
+
}
|
|
12509
12537
|
/** Create a translation transform. */
|
|
12510
12538
|
static translation(x2, y2, z2) {
|
|
12511
12539
|
return new Transform([
|
|
@@ -13341,6 +13369,8 @@ function cloneShapeCompilePlan(plan) {
|
|
|
13341
13369
|
heights: plan.heights.map((height) => height),
|
|
13342
13370
|
edgeLength: plan.edgeLength,
|
|
13343
13371
|
boundsPadding: plan.boundsPadding,
|
|
13372
|
+
...plan.forceField ? { forceField: true } : {},
|
|
13373
|
+
...plan.meshing ? { meshing: cloneSdfCompileMeshingSettings(plan.meshing) } : {},
|
|
13344
13374
|
edgeLabels: plan.edgeLabels ? { ...plan.edgeLabels } : void 0,
|
|
13345
13375
|
capLabels: plan.capLabels ? { ...plan.capLabels } : void 0
|
|
13346
13376
|
};
|
|
@@ -13608,7 +13638,6 @@ function cloneShapeCompilePlan(plan) {
|
|
|
13608
13638
|
default:
|
|
13609
13639
|
assertExhaustive(plan);
|
|
13610
13640
|
}
|
|
13611
|
-
if (plan._occtCache) result._occtCache = plan._occtCache;
|
|
13612
13641
|
return result;
|
|
13613
13642
|
}
|
|
13614
13643
|
function appendProfileCompileTransform(plan, step) {
|
|
@@ -13621,22 +13650,31 @@ function appendShapeCompileTransform(plan, step) {
|
|
|
13621
13650
|
if (plan.kind === "transform") {
|
|
13622
13651
|
return {
|
|
13623
13652
|
kind: "transform",
|
|
13624
|
-
base:
|
|
13625
|
-
steps: [...plan.steps
|
|
13653
|
+
base: plan.base,
|
|
13654
|
+
steps: [...plan.steps, cloneShapeTransform(step)]
|
|
13626
13655
|
};
|
|
13627
13656
|
}
|
|
13628
13657
|
return {
|
|
13629
13658
|
kind: "transform",
|
|
13630
|
-
base:
|
|
13659
|
+
base: plan,
|
|
13631
13660
|
steps: [cloneShapeTransform(step)]
|
|
13632
13661
|
};
|
|
13633
13662
|
}
|
|
13634
13663
|
function appendShapeCompileTransforms(plan, steps) {
|
|
13635
|
-
|
|
13636
|
-
|
|
13637
|
-
|
|
13664
|
+
if (!plan) return null;
|
|
13665
|
+
if (steps.length === 0) return plan;
|
|
13666
|
+
if (plan.kind === "transform") {
|
|
13667
|
+
return {
|
|
13668
|
+
kind: "transform",
|
|
13669
|
+
base: plan.base,
|
|
13670
|
+
steps: [...plan.steps, ...steps.map(cloneShapeTransform)]
|
|
13671
|
+
};
|
|
13638
13672
|
}
|
|
13639
|
-
return
|
|
13673
|
+
return {
|
|
13674
|
+
kind: "transform",
|
|
13675
|
+
base: plan,
|
|
13676
|
+
steps: steps.map(cloneShapeTransform)
|
|
13677
|
+
};
|
|
13640
13678
|
}
|
|
13641
13679
|
function wrapShapeCompilePlanWithQueryOwner(plan, owner) {
|
|
13642
13680
|
if (!plan) return null;
|
|
@@ -13710,6 +13748,8 @@ function buildLoftShapeCompilePlan(profiles, heights, options) {
|
|
|
13710
13748
|
heights: heights.map((height) => canonicalNumber(height)),
|
|
13711
13749
|
edgeLength: canonicalNumber(options.edgeLength),
|
|
13712
13750
|
boundsPadding: canonicalNumber(options.boundsPadding),
|
|
13751
|
+
...options.forceField ? { forceField: true } : {},
|
|
13752
|
+
...options.meshing ? { meshing: cloneSdfCompileMeshingSettings(options.meshing) } : {},
|
|
13713
13753
|
edgeLabels: options.edgeLabels ? { ...options.edgeLabels } : void 0
|
|
13714
13754
|
};
|
|
13715
13755
|
}
|
|
@@ -13811,14 +13851,14 @@ function sub$2(a2, b) {
|
|
|
13811
13851
|
function dot$3(a2, b) {
|
|
13812
13852
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
13813
13853
|
}
|
|
13814
|
-
function cross$
|
|
13854
|
+
function cross$3(a2, b) {
|
|
13815
13855
|
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]];
|
|
13816
13856
|
}
|
|
13817
13857
|
function rotateAroundAxis(v, axis, angleRad) {
|
|
13818
13858
|
const c2 = Math.cos(angleRad);
|
|
13819
13859
|
const s = Math.sin(angleRad);
|
|
13820
13860
|
const term1 = scale$2(v, c2);
|
|
13821
|
-
const term2 = scale$2(cross$
|
|
13861
|
+
const term2 = scale$2(cross$3(axis, v), s);
|
|
13822
13862
|
const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
|
|
13823
13863
|
return add$2(add$2(term1, term2), term3);
|
|
13824
13864
|
}
|
|
@@ -14094,13 +14134,13 @@ function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
|
|
|
14094
14134
|
pts.push(evalPathAt(path, 1));
|
|
14095
14135
|
return pts;
|
|
14096
14136
|
}
|
|
14097
|
-
const EPS$
|
|
14137
|
+
const EPS$1 = 1e-8;
|
|
14098
14138
|
function midpoint$1(start, end) {
|
|
14099
14139
|
return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
|
|
14100
14140
|
}
|
|
14101
14141
|
function normalize$3(v) {
|
|
14102
14142
|
const len = Math.hypot(v[0], v[1], v[2]);
|
|
14103
|
-
if (len <= EPS$
|
|
14143
|
+
if (len <= EPS$1) throw new Error("Edge feature selection requires a non-zero direction vector");
|
|
14104
14144
|
return [v[0] / len, v[1] / len, v[2] / len];
|
|
14105
14145
|
}
|
|
14106
14146
|
function subtract(a2, b) {
|
|
@@ -14179,7 +14219,7 @@ function rigidTransformForEdgeStep(step) {
|
|
|
14179
14219
|
case "mirror": {
|
|
14180
14220
|
const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
|
|
14181
14221
|
const len = Math.hypot(nx0, ny0, nz0);
|
|
14182
|
-
if (len <= EPS$
|
|
14222
|
+
if (len <= EPS$1) return Transform.identity();
|
|
14183
14223
|
const nx = nx0 / len;
|
|
14184
14224
|
const ny = ny0 / len;
|
|
14185
14225
|
const nz = nz0 / len;
|
|
@@ -14476,7 +14516,7 @@ function isRectangleProfile(points) {
|
|
|
14476
14516
|
return [next[0] - point[0], next[1] - point[1]];
|
|
14477
14517
|
});
|
|
14478
14518
|
const lengths = vectors.map(([x2, y2]) => Math.hypot(x2, y2));
|
|
14479
|
-
if (lengths.some((length4) => length4 <= EPS$
|
|
14519
|
+
if (lengths.some((length4) => length4 <= EPS$1)) return false;
|
|
14480
14520
|
const dot01 = vectors[0][0] * vectors[1][0] + vectors[0][1] * vectors[1][1];
|
|
14481
14521
|
const dot12 = vectors[1][0] * vectors[2][0] + vectors[1][1] * vectors[2][1];
|
|
14482
14522
|
const dot23 = vectors[2][0] * vectors[3][0] + vectors[2][1] * vectors[3][1];
|
|
@@ -16415,7 +16455,9 @@ function lowerLoftShellToConcretePlan(plan, thickness, openFaces) {
|
|
|
16415
16455
|
profiles: innerProfiles,
|
|
16416
16456
|
heights: innerHeights,
|
|
16417
16457
|
edgeLength: plan.edgeLength,
|
|
16418
|
-
boundsPadding: plan.boundsPadding
|
|
16458
|
+
boundsPadding: plan.boundsPadding,
|
|
16459
|
+
...plan.forceField ? { forceField: true } : {},
|
|
16460
|
+
...plan.meshing ? { meshing: { ...plan.meshing } } : {}
|
|
16419
16461
|
};
|
|
16420
16462
|
return { ok: true, plan: buildBooleanShapeCompilePlan("difference", [plan, inner]) };
|
|
16421
16463
|
}
|
|
@@ -16553,6 +16595,197 @@ function lowerShellShapeCompilePlanToConcretePlan(plan) {
|
|
|
16553
16595
|
}
|
|
16554
16596
|
return lowerBaseShellPlanToConcretePlan(plan.base, plan.thickness, normalizeShellOpenFaces(plan.openFaces));
|
|
16555
16597
|
}
|
|
16598
|
+
function cyrb53(str, seed) {
|
|
16599
|
+
let h1 = 3735928559 ^ seed;
|
|
16600
|
+
let h2 = 1103547991 ^ seed;
|
|
16601
|
+
for (let i = 0; i < str.length; i++) {
|
|
16602
|
+
const ch = str.charCodeAt(i);
|
|
16603
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
16604
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
16605
|
+
}
|
|
16606
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
16607
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
16608
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
16609
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
16610
|
+
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
|
|
16611
|
+
}
|
|
16612
|
+
function hash128(str) {
|
|
16613
|
+
return cyrb53(str, 2654435769).toString(36) + cyrb53(str, 2246822507).toString(36);
|
|
16614
|
+
}
|
|
16615
|
+
function isPlainValue(value) {
|
|
16616
|
+
return value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string";
|
|
16617
|
+
}
|
|
16618
|
+
function createStructuralHasher(normalize2) {
|
|
16619
|
+
const memo = /* @__PURE__ */ new WeakMap();
|
|
16620
|
+
const skipKey = normalize2 == null ? void 0 : normalize2.skipKey;
|
|
16621
|
+
const unwrap = normalize2 == null ? void 0 : normalize2.unwrap;
|
|
16622
|
+
const entriesSource = normalize2 == null ? void 0 : normalize2.entriesSource;
|
|
16623
|
+
return function hashValue(root) {
|
|
16624
|
+
if (isPlainValue(root)) return JSON.stringify(root);
|
|
16625
|
+
if (root === void 0 || typeof root === "function" || typeof root === "symbol") return "null";
|
|
16626
|
+
const inProgress = /* @__PURE__ */ new Set();
|
|
16627
|
+
const stack = [];
|
|
16628
|
+
const attach = (frame, key, token) => {
|
|
16629
|
+
if (frame.isArray) frame.parts.push(token);
|
|
16630
|
+
else frame.parts.push(`${JSON.stringify(key)}:${token}`);
|
|
16631
|
+
};
|
|
16632
|
+
const open = (objIn, key) => {
|
|
16633
|
+
const aliases = [];
|
|
16634
|
+
let current = objIn;
|
|
16635
|
+
while (current !== null && typeof current === "object" && !Array.isArray(current) && unwrap) {
|
|
16636
|
+
const cached2 = memo.get(current);
|
|
16637
|
+
if (cached2 !== void 0) {
|
|
16638
|
+
for (const alias of aliases) memo.set(alias, cached2);
|
|
16639
|
+
return cached2;
|
|
16640
|
+
}
|
|
16641
|
+
const replaced = unwrap(current);
|
|
16642
|
+
if (replaced === void 0 || replaced === current) break;
|
|
16643
|
+
aliases.push(current);
|
|
16644
|
+
if (aliases.includes(replaced)) {
|
|
16645
|
+
throw new Error("planHash: cycle detected through unwrap chain");
|
|
16646
|
+
}
|
|
16647
|
+
current = replaced;
|
|
16648
|
+
}
|
|
16649
|
+
if (isPlainValue(current)) {
|
|
16650
|
+
const token = JSON.stringify(current);
|
|
16651
|
+
for (const alias of aliases) memo.set(alias, token);
|
|
16652
|
+
return token;
|
|
16653
|
+
}
|
|
16654
|
+
if (current === void 0 || typeof current === "function" || typeof current === "symbol") {
|
|
16655
|
+
for (const alias of aliases) memo.set(alias, "null");
|
|
16656
|
+
return "null";
|
|
16657
|
+
}
|
|
16658
|
+
const obj = current;
|
|
16659
|
+
const cached = memo.get(obj);
|
|
16660
|
+
if (cached !== void 0) {
|
|
16661
|
+
for (const alias of aliases) memo.set(alias, cached);
|
|
16662
|
+
return cached;
|
|
16663
|
+
}
|
|
16664
|
+
if (inProgress.has(obj)) {
|
|
16665
|
+
throw new Error(`planHash: cycle detected in plan data (kind=${String(obj.kind)})`);
|
|
16666
|
+
}
|
|
16667
|
+
inProgress.add(obj);
|
|
16668
|
+
aliases.push(obj);
|
|
16669
|
+
let entriesNode = obj;
|
|
16670
|
+
if (!Array.isArray(obj) && entriesSource) {
|
|
16671
|
+
const substitute = entriesSource(obj);
|
|
16672
|
+
if (substitute) entriesNode = substitute;
|
|
16673
|
+
}
|
|
16674
|
+
let pending;
|
|
16675
|
+
if (Array.isArray(entriesNode)) {
|
|
16676
|
+
pending = entriesNode.map((value) => ({ value })).reverse();
|
|
16677
|
+
} else {
|
|
16678
|
+
const entries = Object.entries(entriesNode).sort(([left], [right]) => left.localeCompare(right));
|
|
16679
|
+
pending = [];
|
|
16680
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
16681
|
+
const [entryKey, value] = entries[i];
|
|
16682
|
+
if (skipKey == null ? void 0 : skipKey(entryKey)) continue;
|
|
16683
|
+
pending.push({ value, key: entryKey });
|
|
16684
|
+
}
|
|
16685
|
+
}
|
|
16686
|
+
stack.push({ pending, parts: [], isArray: Array.isArray(entriesNode), aliases, key });
|
|
16687
|
+
return null;
|
|
16688
|
+
};
|
|
16689
|
+
const rootToken = open(root, void 0);
|
|
16690
|
+
if (rootToken !== null) return rootToken;
|
|
16691
|
+
let result = "null";
|
|
16692
|
+
while (stack.length > 0) {
|
|
16693
|
+
const frame = stack[stack.length - 1];
|
|
16694
|
+
const next = frame.pending.pop();
|
|
16695
|
+
if (next) {
|
|
16696
|
+
if (isPlainValue(next.value)) {
|
|
16697
|
+
attach(frame, next.key, JSON.stringify(next.value));
|
|
16698
|
+
continue;
|
|
16699
|
+
}
|
|
16700
|
+
if (next.value === void 0 || typeof next.value === "function" || typeof next.value === "symbol") {
|
|
16701
|
+
if (frame.isArray) frame.parts.push("null");
|
|
16702
|
+
continue;
|
|
16703
|
+
}
|
|
16704
|
+
const token2 = open(next.value, next.key);
|
|
16705
|
+
if (token2 !== null) attach(frame, next.key, token2);
|
|
16706
|
+
continue;
|
|
16707
|
+
}
|
|
16708
|
+
const canonical = frame.isArray ? `[${frame.parts.join(",")}]` : `{${frame.parts.join(",")}}`;
|
|
16709
|
+
const token = "#" + hash128(canonical);
|
|
16710
|
+
for (const alias of frame.aliases) {
|
|
16711
|
+
memo.set(alias, token);
|
|
16712
|
+
inProgress.delete(alias);
|
|
16713
|
+
}
|
|
16714
|
+
stack.pop();
|
|
16715
|
+
if (stack.length === 0) result = token;
|
|
16716
|
+
else attach(stack[stack.length - 1], frame.key, token);
|
|
16717
|
+
}
|
|
16718
|
+
return result;
|
|
16719
|
+
};
|
|
16720
|
+
}
|
|
16721
|
+
const PLAN_BOOKKEEPING_KEY = (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation";
|
|
16722
|
+
const encodedLengthMemo = /* @__PURE__ */ new WeakMap();
|
|
16723
|
+
function estimatePlanEncodedLength(value) {
|
|
16724
|
+
if (value === void 0 || typeof value === "function" || typeof value === "symbol") return 4;
|
|
16725
|
+
if (value === null || typeof value === "boolean") return 5;
|
|
16726
|
+
if (typeof value === "number") return 8;
|
|
16727
|
+
if (typeof value === "string") return value.length + 2;
|
|
16728
|
+
const root = value;
|
|
16729
|
+
const known = encodedLengthMemo.get(root);
|
|
16730
|
+
if (known !== void 0) return known;
|
|
16731
|
+
const childValues = (node) => {
|
|
16732
|
+
if (Array.isArray(node)) return node;
|
|
16733
|
+
const values = [];
|
|
16734
|
+
for (const [key, item] of Object.entries(node)) {
|
|
16735
|
+
if (PLAN_BOOKKEEPING_KEY(key)) continue;
|
|
16736
|
+
values.push(item);
|
|
16737
|
+
}
|
|
16738
|
+
return values;
|
|
16739
|
+
};
|
|
16740
|
+
const order = [];
|
|
16741
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16742
|
+
const discover = [root];
|
|
16743
|
+
while (discover.length > 0) {
|
|
16744
|
+
const node = discover.pop();
|
|
16745
|
+
if (seen.has(node) || encodedLengthMemo.has(node)) continue;
|
|
16746
|
+
seen.add(node);
|
|
16747
|
+
order.push(node);
|
|
16748
|
+
for (const child of childValues(node)) {
|
|
16749
|
+
if (child !== null && typeof child === "object") discover.push(child);
|
|
16750
|
+
}
|
|
16751
|
+
}
|
|
16752
|
+
for (let i = order.length - 1; i >= 0; i--) {
|
|
16753
|
+
const node = order[i];
|
|
16754
|
+
let total = 2;
|
|
16755
|
+
if (Array.isArray(node)) {
|
|
16756
|
+
for (const item of node) {
|
|
16757
|
+
total += 1 + (item !== null && typeof item === "object" ? encodedLengthMemo.get(item) : estimatePlanEncodedLength(item));
|
|
16758
|
+
}
|
|
16759
|
+
} else {
|
|
16760
|
+
for (const [key, item] of Object.entries(node)) {
|
|
16761
|
+
if (PLAN_BOOKKEEPING_KEY(key)) continue;
|
|
16762
|
+
total += key.length + 4 + (item !== null && typeof item === "object" ? encodedLengthMemo.get(item) : estimatePlanEncodedLength(item));
|
|
16763
|
+
}
|
|
16764
|
+
}
|
|
16765
|
+
encodedLengthMemo.set(node, total);
|
|
16766
|
+
}
|
|
16767
|
+
return encodedLengthMemo.get(root);
|
|
16768
|
+
}
|
|
16769
|
+
function deepFreezePlanData(value) {
|
|
16770
|
+
if (value === null || typeof value !== "object") return value;
|
|
16771
|
+
const stack = [value];
|
|
16772
|
+
while (stack.length > 0) {
|
|
16773
|
+
const node = stack.pop();
|
|
16774
|
+
if (Object.isFrozen(node)) continue;
|
|
16775
|
+
Object.freeze(node);
|
|
16776
|
+
if (Array.isArray(node)) {
|
|
16777
|
+
for (const item of node) {
|
|
16778
|
+
if (item !== null && typeof item === "object") stack.push(item);
|
|
16779
|
+
}
|
|
16780
|
+
continue;
|
|
16781
|
+
}
|
|
16782
|
+
for (const [key, item] of Object.entries(node)) {
|
|
16783
|
+
if (PLAN_BOOKKEEPING_KEY(key)) continue;
|
|
16784
|
+
if (item !== null && typeof item === "object") stack.push(item);
|
|
16785
|
+
}
|
|
16786
|
+
}
|
|
16787
|
+
return value;
|
|
16788
|
+
}
|
|
16556
16789
|
const SHAPE_BACKEND_MARKER = Symbol.for("forgecad.shapeBackend");
|
|
16557
16790
|
function isShapeBackend(value) {
|
|
16558
16791
|
return Boolean(value && typeof value === "object" && value[SHAPE_BACKEND_MARKER] === true);
|
|
@@ -16606,60 +16839,26 @@ function recordEvent(event) {
|
|
|
16606
16839
|
runEvents.push(next);
|
|
16607
16840
|
if (runEvents.length > MAX_RECORDED_EVENTS) runEvents = runEvents.slice(-MAX_RECORDED_EVENTS);
|
|
16608
16841
|
}
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
|
|
16625
|
-
for (const [key, item] of entries) {
|
|
16626
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
16627
|
-
const encoded = stableGeometryEncode(item, false);
|
|
16628
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
16629
|
-
}
|
|
16630
|
-
return `{${encodedEntries.join(",")}}`;
|
|
16631
|
-
}
|
|
16632
|
-
function stableCacheOpportunityEncode(value, arrayMember) {
|
|
16633
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
16634
|
-
return arrayMember ? "null" : void 0;
|
|
16635
|
-
}
|
|
16636
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
16637
|
-
return JSON.stringify(value);
|
|
16638
|
-
}
|
|
16639
|
-
if (Array.isArray(value)) {
|
|
16640
|
-
return `[${value.map((item) => stableCacheOpportunityEncode(item, true) ?? "null").join(",")}]`;
|
|
16641
|
-
}
|
|
16642
|
-
const record = value;
|
|
16643
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
16644
|
-
return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
16645
|
-
}
|
|
16646
|
-
let encodedRecord = record;
|
|
16647
|
-
if (record.kind === "transform" && record.base) {
|
|
16648
|
-
const retainedSteps = Array.isArray(record.steps) ? record.steps.filter((step) => step.kind === "scale") : [];
|
|
16649
|
-
if (retainedSteps.length === 0) return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
16650
|
-
encodedRecord = { kind: "transform", base: record.base, steps: retainedSteps };
|
|
16651
|
-
}
|
|
16652
|
-
const entries = Object.entries(encodedRecord).sort(([left], [right]) => left.localeCompare(right));
|
|
16653
|
-
const encodedEntries = [];
|
|
16654
|
-
for (const [key, item] of entries) {
|
|
16655
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
16656
|
-
const encoded = stableCacheOpportunityEncode(item, false);
|
|
16657
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
16842
|
+
const skipBookkeepingKey = (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation";
|
|
16843
|
+
const geometryPlanHasher = createStructuralHasher({
|
|
16844
|
+
skipKey: skipBookkeepingKey,
|
|
16845
|
+
unwrap: (record) => record.kind === "queryOwner" && record.base ? record.base : void 0
|
|
16846
|
+
});
|
|
16847
|
+
const scaleOnlySteps = (record) => Array.isArray(record.steps) ? record.steps.filter((step) => step.kind === "scale") : [];
|
|
16848
|
+
createStructuralHasher({
|
|
16849
|
+
skipKey: skipBookkeepingKey,
|
|
16850
|
+
unwrap: (record) => {
|
|
16851
|
+
if (record.kind === "queryOwner" && record.base) return record.base;
|
|
16852
|
+
if (record.kind === "transform" && record.base && scaleOnlySteps(record).length === 0) return record.base;
|
|
16853
|
+
return void 0;
|
|
16854
|
+
},
|
|
16855
|
+
entriesSource: (record) => {
|
|
16856
|
+
if (record.kind !== "transform" || !record.base) return void 0;
|
|
16857
|
+
return { kind: "transform", base: record.base, steps: scaleOnlySteps(record) };
|
|
16658
16858
|
}
|
|
16659
|
-
|
|
16660
|
-
}
|
|
16859
|
+
});
|
|
16661
16860
|
function shapeGeometryCacheKey(plan) {
|
|
16662
|
-
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${
|
|
16861
|
+
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${geometryPlanHasher(plan)}`;
|
|
16663
16862
|
}
|
|
16664
16863
|
function planComplexityScore(value) {
|
|
16665
16864
|
if (!value || typeof value !== "object") return 0;
|
|
@@ -16718,8 +16917,7 @@ function planComplexityScore(value) {
|
|
|
16718
16917
|
}
|
|
16719
16918
|
}
|
|
16720
16919
|
function estimateCacheRetainedMb(plan) {
|
|
16721
|
-
|
|
16722
|
-
const encodedLength = ((_a3 = stableCacheOpportunityEncode(plan, false)) == null ? void 0 : _a3.length) ?? 0;
|
|
16920
|
+
const encodedLength = estimatePlanEncodedLength(plan);
|
|
16723
16921
|
const serializedComplexityMb = encodedLength / 24e3;
|
|
16724
16922
|
return round2(0.08 + planComplexityScore(plan) * 0.09 + serializedComplexityMb);
|
|
16725
16923
|
}
|
|
@@ -16737,26 +16935,62 @@ function splitCacheablePlacement(plan) {
|
|
|
16737
16935
|
}
|
|
16738
16936
|
return { basePlan: plan, placementSteps: [] };
|
|
16739
16937
|
}
|
|
16938
|
+
const uncacheableReasonMemo = /* @__PURE__ */ new WeakMap();
|
|
16740
16939
|
function findUncacheableReason(value) {
|
|
16741
16940
|
if (value === void 0 || value === null) return null;
|
|
16742
16941
|
if (typeof value === "function" || typeof value === "symbol") return "plan contains runtime-only values";
|
|
16743
16942
|
if (typeof value !== "object") return null;
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
16748
|
-
|
|
16943
|
+
const root = value;
|
|
16944
|
+
const known = uncacheableReasonMemo.get(root);
|
|
16945
|
+
if (known !== void 0) return known;
|
|
16946
|
+
const order = [];
|
|
16947
|
+
const seen = /* @__PURE__ */ new Set();
|
|
16948
|
+
const discover = [root];
|
|
16949
|
+
let reason = null;
|
|
16950
|
+
while (discover.length > 0 && reason === null) {
|
|
16951
|
+
const node = discover.pop();
|
|
16952
|
+
if (seen.has(node)) continue;
|
|
16953
|
+
const cached = uncacheableReasonMemo.get(node);
|
|
16954
|
+
if (cached !== void 0) {
|
|
16955
|
+
if (cached !== null) reason = cached;
|
|
16956
|
+
continue;
|
|
16957
|
+
}
|
|
16958
|
+
seen.add(node);
|
|
16959
|
+
if (ArrayBuffer.isView(node) || node instanceof ArrayBuffer) {
|
|
16960
|
+
reason = "plan contains binary file data";
|
|
16961
|
+
break;
|
|
16962
|
+
}
|
|
16963
|
+
order.push(node);
|
|
16964
|
+
if (Array.isArray(node)) {
|
|
16965
|
+
for (const item of node) {
|
|
16966
|
+
if (typeof item === "function" || typeof item === "symbol") {
|
|
16967
|
+
reason = "plan contains runtime-only values";
|
|
16968
|
+
break;
|
|
16969
|
+
}
|
|
16970
|
+
if (item !== null && typeof item === "object") discover.push(item);
|
|
16971
|
+
}
|
|
16972
|
+
continue;
|
|
16973
|
+
}
|
|
16974
|
+
const record = node;
|
|
16975
|
+
if (record.kind === "importedMesh" || record.kind === "importedStep") {
|
|
16976
|
+
reason = "plan depends on imported file contents";
|
|
16977
|
+
break;
|
|
16978
|
+
}
|
|
16979
|
+
for (const [key, item] of Object.entries(record)) {
|
|
16980
|
+
if (skipBookkeepingKey(key)) continue;
|
|
16981
|
+
if (typeof item === "function" || typeof item === "symbol") {
|
|
16982
|
+
reason = "plan contains runtime-only values";
|
|
16983
|
+
break;
|
|
16984
|
+
}
|
|
16985
|
+
if (item !== null && typeof item === "object") discover.push(item);
|
|
16749
16986
|
}
|
|
16750
|
-
return null;
|
|
16751
16987
|
}
|
|
16752
|
-
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
|
|
16756
|
-
const reason = findUncacheableReason(item);
|
|
16757
|
-
if (reason) return reason;
|
|
16988
|
+
if (reason === null) {
|
|
16989
|
+
for (const node of order) uncacheableReasonMemo.set(node, null);
|
|
16990
|
+
} else {
|
|
16991
|
+
uncacheableReasonMemo.set(root, reason);
|
|
16758
16992
|
}
|
|
16759
|
-
return
|
|
16993
|
+
return reason;
|
|
16760
16994
|
}
|
|
16761
16995
|
function applyPlacementStep(backend, step) {
|
|
16762
16996
|
switch (step.kind) {
|
|
@@ -17433,8 +17667,9 @@ function analyzeNodeUV(node, toLocal) {
|
|
|
17433
17667
|
if (result.majorRadius !== void 0) result.majorRadius *= node.factor;
|
|
17434
17668
|
return result;
|
|
17435
17669
|
}
|
|
17436
|
-
// ── Shell — UV comes from the inner shape ──
|
|
17670
|
+
// ── Shell / offset — UV comes from the inner shape ──
|
|
17437
17671
|
case "sdf:shell":
|
|
17672
|
+
case "sdf:offset":
|
|
17438
17673
|
return analyzeNodeUV(node.child, toLocal);
|
|
17439
17674
|
// ── CSG — take UV from the first (primary) child ──
|
|
17440
17675
|
case "sdf:union":
|
|
@@ -17970,6 +18205,11 @@ function compileSdfNode3(node) {
|
|
|
17970
18205
|
const t = node.thickness * 0.5;
|
|
17971
18206
|
return (x2, y2, z2) => abs(fn(x2, y2, z2)) - t;
|
|
17972
18207
|
}
|
|
18208
|
+
case "sdf:offset": {
|
|
18209
|
+
const fn = compileSdfNode3(node.child);
|
|
18210
|
+
const d2 = node.distance;
|
|
18211
|
+
return (x2, y2, z2) => fn(x2, y2, z2) - d2;
|
|
18212
|
+
}
|
|
17973
18213
|
case "sdf:displace": {
|
|
17974
18214
|
const fn = compileSdfNode3(node.child);
|
|
17975
18215
|
const constEntries = Object.entries(node.constants ?? {});
|
|
@@ -18463,6 +18703,10 @@ function emitSdfProgramNode(b, node, x2, y2, z2) {
|
|
|
18463
18703
|
const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
18464
18704
|
return b.sub(b.abs(child), b.constant(node.thickness * 0.5));
|
|
18465
18705
|
}
|
|
18706
|
+
case "sdf:offset": {
|
|
18707
|
+
const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
18708
|
+
return b.sub(child, b.constant(node.distance));
|
|
18709
|
+
}
|
|
18466
18710
|
case "sdf:onion": {
|
|
18467
18711
|
let d2 = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
18468
18712
|
for (let i = 0; i < node.layers; i++) d2 = b.sub(b.abs(d2), b.constant(node.thickness));
|
|
@@ -18611,6 +18855,7 @@ function getUnsupportedSdfProgramReason(node) {
|
|
|
18611
18855
|
case "sdf:bend":
|
|
18612
18856
|
case "sdf:repeat":
|
|
18613
18857
|
case "sdf:shell":
|
|
18858
|
+
case "sdf:offset":
|
|
18614
18859
|
case "sdf:onion":
|
|
18615
18860
|
return getUnsupportedSdfProgramReason(node.child);
|
|
18616
18861
|
default:
|
|
@@ -18814,7 +19059,19 @@ function simplifyMesh(triVerts, vertProperties, targetRatio, maxError) {
|
|
|
18814
19059
|
if (!_simplifier) {
|
|
18815
19060
|
throw new Error("meshoptimizer not initialized — call initMeshoptimizer() first");
|
|
18816
19061
|
}
|
|
18817
|
-
|
|
19062
|
+
if (triVerts.length === 0 || triVerts.length % 3 !== 0) {
|
|
19063
|
+
throw new Error("Mesh simplification requires triangle indices in groups of 3");
|
|
19064
|
+
}
|
|
19065
|
+
if (!Number.isFinite(targetRatio) || targetRatio <= 0) {
|
|
19066
|
+
throw new Error("Mesh simplification targetRatio must be a positive finite number");
|
|
19067
|
+
}
|
|
19068
|
+
if (!Number.isFinite(maxError) || maxError < 0) {
|
|
19069
|
+
throw new Error("Mesh simplification maxError must be a non-negative finite number");
|
|
19070
|
+
}
|
|
19071
|
+
const inputTriangleCount = triVerts.length / 3;
|
|
19072
|
+
const targetTriangleCount = Math.max(1, Math.min(inputTriangleCount, Math.floor(inputTriangleCount * targetRatio)));
|
|
19073
|
+
if (targetTriangleCount >= inputTriangleCount) return triVerts;
|
|
19074
|
+
const targetIndexCount = targetTriangleCount * 3;
|
|
18818
19075
|
const [simplified] = _simplifier.simplify(
|
|
18819
19076
|
triVerts,
|
|
18820
19077
|
vertProperties,
|
|
@@ -20224,11 +20481,12 @@ function profileMayContainInteriorLoopsForOCCT(plan) {
|
|
|
20224
20481
|
return false;
|
|
20225
20482
|
}
|
|
20226
20483
|
}
|
|
20484
|
+
const occtLoweredCache = /* @__PURE__ */ new WeakMap();
|
|
20227
20485
|
function lowerShapeCompilePlanToOCCT(plan, oc) {
|
|
20228
|
-
const cached = plan
|
|
20486
|
+
const cached = occtLoweredCache.get(plan);
|
|
20229
20487
|
if (cached) return cached;
|
|
20230
20488
|
const shape = _lowerShapeCompilePlanToOCCTInner(plan, oc);
|
|
20231
|
-
plan
|
|
20489
|
+
occtLoweredCache.set(plan, shape);
|
|
20232
20490
|
return shape;
|
|
20233
20491
|
}
|
|
20234
20492
|
function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
|
|
@@ -28766,16 +29024,16 @@ function surfaceNets(sdfFn, bounds, edgeLength2) {
|
|
|
28766
29024
|
numTris: faces.length / 3
|
|
28767
29025
|
};
|
|
28768
29026
|
}
|
|
28769
|
-
const EPS
|
|
29027
|
+
const EPS = 1e-9;
|
|
28770
29028
|
function finitePositive$1(value) {
|
|
28771
|
-
return Number.isFinite(value) && value > EPS
|
|
29029
|
+
return Number.isFinite(value) && value > EPS;
|
|
28772
29030
|
}
|
|
28773
29031
|
function clampNonNegative(value) {
|
|
28774
|
-
return Math.abs(value) <= EPS
|
|
29032
|
+
return Math.abs(value) <= EPS ? 0 : value;
|
|
28775
29033
|
}
|
|
28776
29034
|
function distancePreservingMatrixScale(matrix) {
|
|
28777
29035
|
if (matrix.length !== 16 || matrix.some((value) => !Number.isFinite(value))) return null;
|
|
28778
|
-
if (Math.abs(matrix[3]) > EPS
|
|
29036
|
+
if (Math.abs(matrix[3]) > EPS || Math.abs(matrix[7]) > EPS || Math.abs(matrix[11]) > EPS || Math.abs(matrix[15] - 1) > EPS) {
|
|
28779
29037
|
return null;
|
|
28780
29038
|
}
|
|
28781
29039
|
const col0 = [matrix[0], matrix[1], matrix[2]];
|
|
@@ -28787,8 +29045,8 @@ function distancePreservingMatrixScale(matrix) {
|
|
|
28787
29045
|
const sy = length4(col1);
|
|
28788
29046
|
const sz = length4(col2);
|
|
28789
29047
|
if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
|
|
28790
|
-
if (Math.abs(sx - sy) > EPS
|
|
28791
|
-
if (Math.abs(dot2(col0, col1)) > EPS
|
|
29048
|
+
if (Math.abs(sx - sy) > EPS || Math.abs(sx - sz) > EPS) return null;
|
|
29049
|
+
if (Math.abs(dot2(col0, col1)) > EPS || Math.abs(dot2(col0, col2)) > EPS || Math.abs(dot2(col1, col2)) > EPS) return null;
|
|
28792
29050
|
return sx;
|
|
28793
29051
|
}
|
|
28794
29052
|
function transformStepDistanceScale(step) {
|
|
@@ -28804,7 +29062,7 @@ function transformStepDistanceScale(step) {
|
|
|
28804
29062
|
const sy = Math.abs(step.y);
|
|
28805
29063
|
const sz = Math.abs(step.z);
|
|
28806
29064
|
if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
|
|
28807
|
-
return Math.abs(sx - sy) <= EPS
|
|
29065
|
+
return Math.abs(sx - sy) <= EPS && Math.abs(sx - sz) <= EPS ? sx : null;
|
|
28808
29066
|
}
|
|
28809
29067
|
}
|
|
28810
29068
|
}
|
|
@@ -28835,7 +29093,7 @@ function cloneTransformStep(step) {
|
|
|
28835
29093
|
}
|
|
28836
29094
|
}
|
|
28837
29095
|
function translatedPlan(base, z2) {
|
|
28838
|
-
if (Math.abs(z2) <= EPS
|
|
29096
|
+
if (Math.abs(z2) <= EPS) return base;
|
|
28839
29097
|
return {
|
|
28840
29098
|
kind: "transform",
|
|
28841
29099
|
base,
|
|
@@ -28852,7 +29110,7 @@ function offsetCylinderDimensions(plan, thickness) {
|
|
|
28852
29110
|
const height = zMax - zMin;
|
|
28853
29111
|
const radiusBottom = plan.radius + thickness * (normalScale - slope);
|
|
28854
29112
|
const offsetRadiusTop = radiusTop + thickness * (normalScale + slope);
|
|
28855
|
-
if (!finitePositive$1(height) || radiusBottom < -EPS
|
|
29113
|
+
if (!finitePositive$1(height) || radiusBottom < -EPS || offsetRadiusTop < -EPS) return null;
|
|
28856
29114
|
return {
|
|
28857
29115
|
zMin,
|
|
28858
29116
|
height,
|
|
@@ -28876,7 +29134,7 @@ function transformProfilePoint(point, transform) {
|
|
|
28876
29134
|
const nx = transform.normalX;
|
|
28877
29135
|
const ny = transform.normalY;
|
|
28878
29136
|
const len = Math.hypot(nx, ny);
|
|
28879
|
-
if (len <= EPS
|
|
29137
|
+
if (len <= EPS) return point;
|
|
28880
29138
|
const ux = nx / len;
|
|
28881
29139
|
const uy = ny / len;
|
|
28882
29140
|
const d2 = point[0] * ux + point[1] * uy;
|
|
@@ -28890,7 +29148,7 @@ function transformProfilePointThrough(point, transforms) {
|
|
|
28890
29148
|
return out;
|
|
28891
29149
|
}
|
|
28892
29150
|
function sameScalar$2(a2, b) {
|
|
28893
|
-
return Math.abs(a2 - b) <= EPS
|
|
29151
|
+
return Math.abs(a2 - b) <= EPS;
|
|
28894
29152
|
}
|
|
28895
29153
|
function sameProfilePoint(a2, b) {
|
|
28896
29154
|
return sameScalar$2(a2[0], b[0]) && sameScalar$2(a2[1], b[1]);
|
|
@@ -28944,7 +29202,7 @@ function rectangleFootprintFromProfile(plan) {
|
|
|
28944
29202
|
const [xMin, xMax] = xs;
|
|
28945
29203
|
const [zMin, zMax] = zs;
|
|
28946
29204
|
if (xMin == null || xMax == null || zMin == null || zMax == null) return null;
|
|
28947
|
-
if (xMin < -EPS
|
|
29205
|
+
if (xMin < -EPS || !finitePositive$1(xMax) || !finitePositive$1(zMax - zMin)) return null;
|
|
28948
29206
|
const hasCorner = (x2, z2) => points.some(([px, pz]) => sameScalar$2(px, x2) && sameScalar$2(pz, z2));
|
|
28949
29207
|
if (!hasCorner(xMin, zMin) || !hasCorner(xMax, zMin) || !hasCorner(xMax, zMax) || !hasCorner(xMin, zMax)) return null;
|
|
28950
29208
|
return {
|
|
@@ -28967,7 +29225,7 @@ function circleFootprintFromProfile(plan) {
|
|
|
28967
29225
|
const yScale = Math.hypot(yAxis[0], yAxis[1]);
|
|
28968
29226
|
const dot2 = xAxis[0] * yAxis[0] + xAxis[1] * yAxis[1];
|
|
28969
29227
|
if (!finitePositive$1(xScale) || !finitePositive$1(yScale)) return null;
|
|
28970
|
-
if (Math.abs(xScale - yScale) > EPS
|
|
29228
|
+
if (Math.abs(xScale - yScale) > EPS || Math.abs(dot2) > EPS * xScale * yScale) return null;
|
|
28971
29229
|
return {
|
|
28972
29230
|
center,
|
|
28973
29231
|
radius: radius * xScale,
|
|
@@ -28975,11 +29233,11 @@ function circleFootprintFromProfile(plan) {
|
|
|
28975
29233
|
};
|
|
28976
29234
|
}
|
|
28977
29235
|
function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
|
|
28978
|
-
if (Math.abs(plan.degrees - 360) > EPS
|
|
29236
|
+
if (Math.abs(plan.degrees - 360) > EPS) return null;
|
|
28979
29237
|
const circle = circleFootprintFromProfile(plan.profile);
|
|
28980
|
-
if (!circle || circle.center[0] <= EPS
|
|
29238
|
+
if (!circle || circle.center[0] <= EPS) return null;
|
|
28981
29239
|
const minorRadius = circle.radius + minorRadiusOffset;
|
|
28982
|
-
if (!finitePositive$1(minorRadius) || minorRadius >= circle.center[0] - EPS
|
|
29240
|
+
if (!finitePositive$1(minorRadius) || minorRadius >= circle.center[0] - EPS) return null;
|
|
28983
29241
|
return translatedPlan(
|
|
28984
29242
|
{
|
|
28985
29243
|
kind: "torus",
|
|
@@ -28992,7 +29250,7 @@ function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
|
|
|
28992
29250
|
}
|
|
28993
29251
|
function fullAxisRectRevolveCylinderPlan(plan) {
|
|
28994
29252
|
const rectangle = fullRectRevolveSurfacePlan(plan);
|
|
28995
|
-
if (!rectangle || rectangle.innerRadius > EPS
|
|
29253
|
+
if (!rectangle || rectangle.innerRadius > EPS) return null;
|
|
28996
29254
|
return translatedPlan(
|
|
28997
29255
|
{
|
|
28998
29256
|
kind: "cylinder",
|
|
@@ -29015,20 +29273,20 @@ function rectRevolveSurfacePlan(plan) {
|
|
|
29015
29273
|
};
|
|
29016
29274
|
}
|
|
29017
29275
|
function fullRectRevolveSurfacePlan(plan) {
|
|
29018
|
-
if (Math.abs(plan.degrees - 360) > EPS
|
|
29276
|
+
if (Math.abs(plan.degrees - 360) > EPS) return null;
|
|
29019
29277
|
return rectRevolveSurfacePlan(plan);
|
|
29020
29278
|
}
|
|
29021
29279
|
function offsetFullRectRevolvePlan(plan, thickness) {
|
|
29022
29280
|
const cylinderPlan = fullAxisRectRevolveCylinderPlan(plan);
|
|
29023
29281
|
if (cylinderPlan) return offsetSolidAnalyticPrimitivePlan(cylinderPlan, thickness);
|
|
29024
29282
|
const rectangle = fullRectRevolveSurfacePlan(plan);
|
|
29025
|
-
if (!rectangle || rectangle.innerRadius <= EPS
|
|
29283
|
+
if (!rectangle || rectangle.innerRadius <= EPS) return null;
|
|
29026
29284
|
const innerRadius = rectangle.innerRadius - thickness;
|
|
29027
29285
|
const outerRadius = rectangle.outerRadius + thickness;
|
|
29028
29286
|
const height = rectangle.zMax - rectangle.zMin + 2 * thickness;
|
|
29029
|
-
if (innerRadius < -EPS
|
|
29287
|
+
if (innerRadius < -EPS || !finitePositive$1(outerRadius) || !finitePositive$1(height) || outerRadius <= innerRadius + EPS) return null;
|
|
29030
29288
|
const zCenter = (rectangle.zMin + rectangle.zMax) / 2;
|
|
29031
|
-
if (innerRadius <= EPS
|
|
29289
|
+
if (innerRadius <= EPS) {
|
|
29032
29290
|
return translatedPlan(
|
|
29033
29291
|
{
|
|
29034
29292
|
kind: "cylinder",
|
|
@@ -29107,7 +29365,7 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
|
|
|
29107
29365
|
}
|
|
29108
29366
|
case "torus": {
|
|
29109
29367
|
const minorRadius = base.minorRadius + thickness;
|
|
29110
|
-
if (!finitePositive$1(minorRadius) || minorRadius >= base.majorRadius - EPS
|
|
29368
|
+
if (!finitePositive$1(minorRadius) || minorRadius >= base.majorRadius - EPS) return null;
|
|
29111
29369
|
return {
|
|
29112
29370
|
kind: "torus",
|
|
29113
29371
|
majorRadius: base.majorRadius,
|
|
@@ -29711,7 +29969,7 @@ function sub$1(a2, b) {
|
|
|
29711
29969
|
function scale$1(v, scalar) {
|
|
29712
29970
|
return [v[0] * scalar, v[1] * scalar, v[2] * scalar];
|
|
29713
29971
|
}
|
|
29714
|
-
function cross$
|
|
29972
|
+
function cross$2(a2, b) {
|
|
29715
29973
|
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]];
|
|
29716
29974
|
}
|
|
29717
29975
|
function isFiniteNumber(value) {
|
|
@@ -29947,8 +30205,8 @@ function boundsCorners(bounds) {
|
|
|
29947
30205
|
}
|
|
29948
30206
|
function perpendicularAxes(normal) {
|
|
29949
30207
|
const seed = Math.abs(normal[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0];
|
|
29950
|
-
const uAxis = normalizeVec3$1(cross$
|
|
29951
|
-
const vAxis = normalizeVec3$1(cross$
|
|
30208
|
+
const uAxis = normalizeVec3$1(cross$2(seed, normal));
|
|
30209
|
+
const vAxis = normalizeVec3$1(cross$2(normal, uAxis));
|
|
29952
30210
|
return { uAxis, vAxis };
|
|
29953
30211
|
}
|
|
29954
30212
|
function createBoundedHalfSpace(bounds, normal, originOffset) {
|
|
@@ -30016,7 +30274,7 @@ function faceAxes(face) {
|
|
|
30016
30274
|
for (const vertex of face.vertices.slice(1)) {
|
|
30017
30275
|
const uAxis = normalizeVec3$1([vertex[0] - origin[0], vertex[1] - origin[1], vertex[2] - origin[2]]);
|
|
30018
30276
|
if (Math.hypot(...uAxis) <= 1e-12) continue;
|
|
30019
|
-
const vAxis = normalizeVec3$1(cross$
|
|
30277
|
+
const vAxis = normalizeVec3$1(cross$2(face.normal, uAxis));
|
|
30020
30278
|
if (Math.hypot(...vAxis) <= 1e-12) continue;
|
|
30021
30279
|
return {
|
|
30022
30280
|
uAxis,
|
|
@@ -32577,7 +32835,7 @@ function isStraightMonotonePolyline(points) {
|
|
|
32577
32835
|
for (const point of points) {
|
|
32578
32836
|
const offset = subtract3$1(point, start);
|
|
32579
32837
|
const projection = dot3$3(offset, axis) / axisLengthSq;
|
|
32580
|
-
const lineDistance = vectorLength3$1(cross3$
|
|
32838
|
+
const lineDistance = vectorLength3$1(cross3$5(offset, axis)) / axisLength;
|
|
32581
32839
|
if (lineDistance > 1e-5 || projection < -1e-6 || projection > 1 + 1e-6 || projection + 1e-6 < previousProjection) {
|
|
32582
32840
|
return false;
|
|
32583
32841
|
}
|
|
@@ -32653,7 +32911,7 @@ function isDistancePreservingMatrix$1(matrix) {
|
|
|
32653
32911
|
const col0 = [matrix[0], matrix[1], matrix[2]];
|
|
32654
32912
|
const col1 = [matrix[4], matrix[5], matrix[6]];
|
|
32655
32913
|
const col2 = [matrix[8], matrix[9], matrix[10]];
|
|
32656
|
-
const det = dot3$3(col0, cross3$
|
|
32914
|
+
const det = dot3$3(col0, cross3$5(col1, col2));
|
|
32657
32915
|
return Math.abs(vectorLength3$1(col0) - 1) <= eps && Math.abs(vectorLength3$1(col1) - 1) <= eps && Math.abs(vectorLength3$1(col2) - 1) <= eps && Math.abs(dot3$3(col0, col1)) <= eps && Math.abs(dot3$3(col0, col2)) <= eps && Math.abs(dot3$3(col1, col2)) <= eps && Math.abs(Math.abs(det) - 1) <= eps;
|
|
32658
32916
|
}
|
|
32659
32917
|
function offsetSolidTransformDistanceScale(step) {
|
|
@@ -32898,41 +33156,41 @@ function surfaceGridForFillPlan(plan) {
|
|
|
32898
33156
|
}
|
|
32899
33157
|
return grid;
|
|
32900
33158
|
}
|
|
32901
|
-
function add3(a2, b) {
|
|
33159
|
+
function add3$1(a2, b) {
|
|
32902
33160
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
32903
33161
|
}
|
|
32904
|
-
function scale3(v, s) {
|
|
33162
|
+
function scale3$1(v, s) {
|
|
32905
33163
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
32906
33164
|
}
|
|
32907
33165
|
function analyticYForFrame(axis, xAxis) {
|
|
32908
|
-
return normalizedVector3(cross3$
|
|
33166
|
+
return normalizedVector3(cross3$5(axis, xAxis), "analytic surface yAxis");
|
|
32909
33167
|
}
|
|
32910
33168
|
function radialPoint(xAxis, yAxis, u2, radius) {
|
|
32911
|
-
return add3(scale3(xAxis, Math.cos(u2) * radius), scale3(yAxis, Math.sin(u2) * radius));
|
|
33169
|
+
return add3$1(scale3$1(xAxis, Math.cos(u2) * radius), scale3$1(yAxis, Math.sin(u2) * radius));
|
|
32912
33170
|
}
|
|
32913
33171
|
function analyticSurfacePoint(plan, u2, v) {
|
|
32914
33172
|
switch (plan.kind) {
|
|
32915
33173
|
case "plane":
|
|
32916
|
-
return add3(add3(plan.origin, scale3(plan.uAxis, u2)), scale3(plan.vAxis, v));
|
|
33174
|
+
return add3$1(add3$1(plan.origin, scale3$1(plan.uAxis, u2)), scale3$1(plan.vAxis, v));
|
|
32917
33175
|
case "cylinder": {
|
|
32918
33176
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32919
|
-
return add3(add3(plan.origin, scale3(plan.axis, v)), radialPoint(plan.xAxis, yAxis, u2, plan.radius));
|
|
33177
|
+
return add3$1(add3$1(plan.origin, scale3$1(plan.axis, v)), radialPoint(plan.xAxis, yAxis, u2, plan.radius));
|
|
32920
33178
|
}
|
|
32921
33179
|
case "cone": {
|
|
32922
33180
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32923
33181
|
const t = (v - plan.vMin) / (plan.vMax - plan.vMin);
|
|
32924
33182
|
const radius = plan.radiusBottom + (plan.radiusTop - plan.radiusBottom) * t;
|
|
32925
|
-
return add3(add3(plan.origin, scale3(plan.axis, v)), radialPoint(plan.xAxis, yAxis, u2, radius));
|
|
33183
|
+
return add3$1(add3$1(plan.origin, scale3$1(plan.axis, v)), radialPoint(plan.xAxis, yAxis, u2, radius));
|
|
32926
33184
|
}
|
|
32927
33185
|
case "sphere": {
|
|
32928
33186
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32929
33187
|
const radial = radialPoint(plan.xAxis, yAxis, u2, plan.radius * Math.cos(v));
|
|
32930
|
-
return add3(add3(plan.center, radial), scale3(plan.axis, plan.radius * Math.sin(v)));
|
|
33188
|
+
return add3$1(add3$1(plan.center, radial), scale3$1(plan.axis, plan.radius * Math.sin(v)));
|
|
32931
33189
|
}
|
|
32932
33190
|
case "torus": {
|
|
32933
33191
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32934
33192
|
const radial = radialPoint(plan.xAxis, yAxis, u2, plan.majorRadius + plan.minorRadius * Math.cos(v));
|
|
32935
|
-
return add3(add3(plan.center, radial), scale3(plan.axis, plan.minorRadius * Math.sin(v)));
|
|
33193
|
+
return add3$1(add3$1(plan.center, radial), scale3$1(plan.axis, plan.minorRadius * Math.sin(v)));
|
|
32936
33194
|
}
|
|
32937
33195
|
default:
|
|
32938
33196
|
return assertExhaustive(plan);
|
|
@@ -33050,7 +33308,7 @@ function triangleNormal(vertices, triangle) {
|
|
|
33050
33308
|
const c2 = vertices[triangle[2]];
|
|
33051
33309
|
const ab = [b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]];
|
|
33052
33310
|
const ac = [c2[0] - a2[0], c2[1] - a2[1], c2[2] - a2[2]];
|
|
33053
|
-
const normal = cross3$
|
|
33311
|
+
const normal = cross3$5(ab, ac);
|
|
33054
33312
|
const len = Math.hypot(normal[0], normal[1], normal[2]);
|
|
33055
33313
|
if (len <= 1e-12) return null;
|
|
33056
33314
|
return [normal[0] / len, normal[1] / len, normal[2] / len];
|
|
@@ -33196,7 +33454,7 @@ function lowerSurfaceSolidPlan(plan) {
|
|
|
33196
33454
|
);
|
|
33197
33455
|
}
|
|
33198
33456
|
}
|
|
33199
|
-
function cross3$
|
|
33457
|
+
function cross3$5(a2, b) {
|
|
33200
33458
|
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]];
|
|
33201
33459
|
}
|
|
33202
33460
|
function fromSlicesPlaneFrame(normalInput) {
|
|
@@ -33212,8 +33470,8 @@ function fromSlicesPlaneFrame(normalInput) {
|
|
|
33212
33470
|
return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
|
|
33213
33471
|
}
|
|
33214
33472
|
const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
33215
|
-
const u2 = normalizedVector3(cross3$
|
|
33216
|
-
return { u: u2, v: cross3$
|
|
33473
|
+
const u2 = normalizedVector3(cross3$5(reference, normal), "Shape.fromSlices profile u axis");
|
|
33474
|
+
return { u: u2, v: cross3$5(normal, u2), normal };
|
|
33217
33475
|
}
|
|
33218
33476
|
function fromSlicesLocalToWorldMatrix(normal) {
|
|
33219
33477
|
const frame = fromSlicesPlaneFrame(normal);
|
|
@@ -36632,29 +36890,11 @@ function lowerExactSlicedShapeCompilePlanToTruckProfileBackend(plan, offset) {
|
|
|
36632
36890
|
return profilePlan ? lowerProfileCompilePlanToTruckProfileBackend(profilePlan) : null;
|
|
36633
36891
|
}
|
|
36634
36892
|
const SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION = "shape-plan-v1";
|
|
36635
|
-
|
|
36636
|
-
|
|
36637
|
-
|
|
36638
|
-
}
|
|
36639
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
36640
|
-
return JSON.stringify(value);
|
|
36641
|
-
}
|
|
36642
|
-
if (Array.isArray(value)) {
|
|
36643
|
-
return `[${value.map((item) => stableJsonEncode(item, true) ?? "null").join(",")}]`;
|
|
36644
|
-
}
|
|
36645
|
-
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
36646
|
-
const encodedEntries = [];
|
|
36647
|
-
for (const [key, item] of entries) {
|
|
36648
|
-
const encoded = stableJsonEncode(item, false);
|
|
36649
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
36650
|
-
}
|
|
36651
|
-
return `{${encodedEntries.join(",")}}`;
|
|
36652
|
-
}
|
|
36653
|
-
function stableJsonStringify(value) {
|
|
36654
|
-
return stableJsonEncode(value, false) ?? "null";
|
|
36655
|
-
}
|
|
36893
|
+
const exactPlanHasher = createStructuralHasher({
|
|
36894
|
+
skipKey: (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation"
|
|
36895
|
+
});
|
|
36656
36896
|
function shapeCompilePlanCacheKey(plan) {
|
|
36657
|
-
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${
|
|
36897
|
+
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${exactPlanHasher(plan)}`;
|
|
36658
36898
|
}
|
|
36659
36899
|
function formatFaceQuery(query) {
|
|
36660
36900
|
const parts = [];
|
|
@@ -36984,7 +37224,7 @@ function normalizeFaceSelector(selector) {
|
|
|
36984
37224
|
}
|
|
36985
37225
|
return { compilePlanName: null, query: selector };
|
|
36986
37226
|
}
|
|
36987
|
-
function cross$
|
|
37227
|
+
function cross$1(a2, b) {
|
|
36988
37228
|
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]];
|
|
36989
37229
|
}
|
|
36990
37230
|
function dot$1(a2, b) {
|
|
@@ -36997,8 +37237,8 @@ function normVec3(v) {
|
|
|
36997
37237
|
}
|
|
36998
37238
|
function tangentFrame(normal) {
|
|
36999
37239
|
const ref = Math.abs(normal[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
37000
|
-
const v = normVec3(cross$
|
|
37001
|
-
const u2 = normVec3(cross$
|
|
37240
|
+
const v = normVec3(cross$1(normal, ref));
|
|
37241
|
+
const u2 = normVec3(cross$1(v, normal));
|
|
37002
37242
|
return { u: u2, v };
|
|
37003
37243
|
}
|
|
37004
37244
|
const NORMAL_COS_EPS$1 = 0.9998;
|
|
@@ -37017,7 +37257,7 @@ function clusterMeshFaces(shape) {
|
|
|
37017
37257
|
const v2 = [vertProperties[i2 * numProp], vertProperties[i2 * numProp + 1], vertProperties[i2 * numProp + 2]];
|
|
37018
37258
|
const e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
|
|
37019
37259
|
const e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
|
|
37020
|
-
const rawCross = cross$
|
|
37260
|
+
const rawCross = cross$1(e1, e2);
|
|
37021
37261
|
const normal = normVec3(rawCross);
|
|
37022
37262
|
if (!normal) continue;
|
|
37023
37263
|
const crossLen = Math.sqrt(rawCross[0] * rawCross[0] + rawCross[1] * rawCross[1] + rawCross[2] * rawCross[2]);
|
|
@@ -37427,14 +37667,14 @@ function normalize2d(vec2) {
|
|
|
37427
37667
|
if (len < 1e-12) return [1, 0];
|
|
37428
37668
|
return [vec2[0] / len, vec2[1] / len];
|
|
37429
37669
|
}
|
|
37430
|
-
function cross3$
|
|
37670
|
+
function cross3$4(a2, b) {
|
|
37431
37671
|
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]];
|
|
37432
37672
|
}
|
|
37433
37673
|
function orthonormalBasisFromNormal(normal) {
|
|
37434
37674
|
const n = normalizeAxis(normal);
|
|
37435
37675
|
const seed = Math.abs(n[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0];
|
|
37436
|
-
const u2 = normalizeAxis(cross3$
|
|
37437
|
-
const v = normalizeAxis(cross3$
|
|
37676
|
+
const u2 = normalizeAxis(cross3$4(seed, n));
|
|
37677
|
+
const v = normalizeAxis(cross3$4(n, u2));
|
|
37438
37678
|
return { u: u2, v };
|
|
37439
37679
|
}
|
|
37440
37680
|
function faceFrom2DEdge(name, start, end, zMid, ownerQuery) {
|
|
@@ -38271,7 +38511,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
38271
38511
|
forwardStart3[1] - reverseStart3[1],
|
|
38272
38512
|
forwardStart3[2] - reverseStart3[2]
|
|
38273
38513
|
]);
|
|
38274
|
-
const normal = normalizeAxis(cross3$
|
|
38514
|
+
const normal = normalizeAxis(cross3$4(edgeVec, depthVec));
|
|
38275
38515
|
registerFace(table, {
|
|
38276
38516
|
name: wall.name,
|
|
38277
38517
|
normal,
|
|
@@ -38757,14 +38997,14 @@ function normalize3$1(v) {
|
|
|
38757
38997
|
function dot3$2(a2, b) {
|
|
38758
38998
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
38759
38999
|
}
|
|
38760
|
-
function cross3$
|
|
39000
|
+
function cross3$3(a2, b) {
|
|
38761
39001
|
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]];
|
|
38762
39002
|
}
|
|
38763
39003
|
function perpendicularTo(axis) {
|
|
38764
39004
|
const absX = Math.abs(axis[0]);
|
|
38765
39005
|
const absZ = Math.abs(axis[2]);
|
|
38766
39006
|
const seed = absX < absZ ? [1, 0, 0] : [0, 0, 1];
|
|
38767
|
-
return normalize3$1(cross3$
|
|
39007
|
+
return normalize3$1(cross3$3(axis, seed));
|
|
38768
39008
|
}
|
|
38769
39009
|
function normalizePortInput(input) {
|
|
38770
39010
|
let origin;
|
|
@@ -38945,18 +39185,18 @@ function normalize3(v) {
|
|
|
38945
39185
|
if (l < 1e-10) throw new Error("Cannot normalize zero-length vector");
|
|
38946
39186
|
return [v[0] / l, v[1] / l, v[2] / l];
|
|
38947
39187
|
}
|
|
38948
|
-
function cross3$
|
|
39188
|
+
function cross3$2(a2, b) {
|
|
38949
39189
|
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]];
|
|
38950
39190
|
}
|
|
38951
|
-
function sub3(a2, b) {
|
|
39191
|
+
function sub3$1(a2, b) {
|
|
38952
39192
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
38953
39193
|
}
|
|
38954
39194
|
function negate3(v) {
|
|
38955
39195
|
return [-v[0], -v[1], -v[2]];
|
|
38956
39196
|
}
|
|
38957
39197
|
function alignmentMatrix(childOrigin, childAxis, childUp, parentOrigin, parentAxis, parentUp) {
|
|
38958
|
-
const cRight = normalize3(cross3$
|
|
38959
|
-
const pRight = normalize3(cross3$
|
|
39198
|
+
const cRight = normalize3(cross3$2(childAxis, childUp));
|
|
39199
|
+
const pRight = normalize3(cross3$2(parentAxis, parentUp));
|
|
38960
39200
|
const r00 = pRight[0] * cRight[0] + parentUp[0] * childUp[0] + parentAxis[0] * childAxis[0];
|
|
38961
39201
|
const r01 = pRight[0] * cRight[1] + parentUp[0] * childUp[1] + parentAxis[0] * childAxis[1];
|
|
38962
39202
|
const r02 = pRight[0] * cRight[2] + parentUp[0] * childUp[2] + parentAxis[0] * childAxis[2];
|
|
@@ -38971,7 +39211,7 @@ function alignmentMatrix(childOrigin, childAxis, childUp, parentOrigin, parentAx
|
|
|
38971
39211
|
r10 * childOrigin[0] + r11 * childOrigin[1] + r12 * childOrigin[2],
|
|
38972
39212
|
r20 * childOrigin[0] + r21 * childOrigin[1] + r22 * childOrigin[2]
|
|
38973
39213
|
];
|
|
38974
|
-
const t = sub3(parentOrigin, rc);
|
|
39214
|
+
const t = sub3$1(parentOrigin, rc);
|
|
38975
39215
|
return Transform.from([r00, r10, r20, 0, r01, r11, r21, 0, r02, r12, r22, 0, t[0], t[1], t[2], 1]);
|
|
38976
39216
|
}
|
|
38977
39217
|
function computeSinglePairAlignment(childPort, targetPort) {
|
|
@@ -39010,8 +39250,8 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
|
|
|
39010
39250
|
[0, 0, 0]
|
|
39011
39251
|
];
|
|
39012
39252
|
for (const p2 of pairs) {
|
|
39013
|
-
const s = sub3(p2.childOrigin, srcCentroid);
|
|
39014
|
-
const t2 = sub3(p2.targetOrigin, tgtCentroid);
|
|
39253
|
+
const s = sub3$1(p2.childOrigin, srcCentroid);
|
|
39254
|
+
const t2 = sub3$1(p2.targetOrigin, tgtCentroid);
|
|
39015
39255
|
for (let i = 0; i < 3; i++) {
|
|
39016
39256
|
for (let j = 0; j < 3; j++) {
|
|
39017
39257
|
h[i][j] += s[i] * t2[j];
|
|
@@ -39024,7 +39264,7 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
|
|
|
39024
39264
|
R[1][0] * srcCentroid[0] + R[1][1] * srcCentroid[1] + R[1][2] * srcCentroid[2],
|
|
39025
39265
|
R[2][0] * srcCentroid[0] + R[2][1] * srcCentroid[1] + R[2][2] * srcCentroid[2]
|
|
39026
39266
|
];
|
|
39027
|
-
const t = sub3(tgtCentroid, rSrc);
|
|
39267
|
+
const t = sub3$1(tgtCentroid, rSrc);
|
|
39028
39268
|
const transform = Transform.from([
|
|
39029
39269
|
R[0][0],
|
|
39030
39270
|
R[1][0],
|
|
@@ -39046,7 +39286,7 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
|
|
|
39046
39286
|
const residuals = [];
|
|
39047
39287
|
for (const p2 of pairs) {
|
|
39048
39288
|
const transformed = transform.point(p2.childOrigin);
|
|
39049
|
-
const diff = sub3(transformed, p2.targetOrigin);
|
|
39289
|
+
const diff = sub3$1(transformed, p2.targetOrigin);
|
|
39050
39290
|
residuals.push(len3(diff));
|
|
39051
39291
|
}
|
|
39052
39292
|
const maxResidual = Math.max(...residuals);
|
|
@@ -39247,7 +39487,7 @@ function getConnectorDistance(ports, nameA, nameB) {
|
|
|
39247
39487
|
const b = ports[nameB];
|
|
39248
39488
|
if (!a2) throw new Error(`connectorDistance: unknown connector "${nameA}"`);
|
|
39249
39489
|
if (!b) throw new Error(`connectorDistance: unknown connector "${nameB}"`);
|
|
39250
|
-
const d2 = sub3(a2.origin, b.origin);
|
|
39490
|
+
const d2 = sub3$1(a2.origin, b.origin);
|
|
39251
39491
|
return len3(d2);
|
|
39252
39492
|
}
|
|
39253
39493
|
function getConnectorMeasurements(ports, name) {
|
|
@@ -39360,7 +39600,9 @@ const _ManifoldShapeBackend = class _ManifoldShapeBackend {
|
|
|
39360
39600
|
return this.getLiveManifold("numTri()").numTri();
|
|
39361
39601
|
}
|
|
39362
39602
|
getMesh() {
|
|
39363
|
-
|
|
39603
|
+
const manifold = this.getLiveManifold("getMesh()");
|
|
39604
|
+
const mesh = manifold.numProp() >= 3 ? manifold.getMesh(0) : manifold.getMesh();
|
|
39605
|
+
return mesh;
|
|
39364
39606
|
}
|
|
39365
39607
|
slice(offset) {
|
|
39366
39608
|
return this.getLiveManifold("slice()").slice(offset);
|
|
@@ -39661,7 +39903,7 @@ class ShapeGroup {
|
|
|
39661
39903
|
};
|
|
39662
39904
|
return this.attachTo(parent, face, opp[face], uvMap[face](u2, v, p2));
|
|
39663
39905
|
}
|
|
39664
|
-
/** Rotate the group around an arbitrary axis through the origin. */
|
|
39906
|
+
/** 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. */
|
|
39665
39907
|
rotate(axis, angleDeg, options) {
|
|
39666
39908
|
requireRotateAxis(axis, "ShapeGroup.rotate()");
|
|
39667
39909
|
requireFiniteAngle(angleDeg, "ShapeGroup.rotate()");
|
|
@@ -39906,6 +40148,11 @@ class ShapeGroup {
|
|
|
39906
40148
|
* Position this group by matching connectors to a target.
|
|
39907
40149
|
* Connector names support dotted paths into named children: "ChildName.connectorName".
|
|
39908
40150
|
*
|
|
40151
|
+
* Alignment: with a single connector pair, the group translates and rotates so the connector
|
|
40152
|
+
* origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,
|
|
40153
|
+
* the connector origins define the rigid transform — still author meaningful `axis`/`up` values
|
|
40154
|
+
* so the same connectors remain useful for `connect()`, audits, and future matching.
|
|
40155
|
+
*
|
|
39909
40156
|
* Overloads:
|
|
39910
40157
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
39911
40158
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -41645,7 +41892,7 @@ async function initKernelManifoldOnly() {
|
|
|
41645
41892
|
_activeBackend = "manifold";
|
|
41646
41893
|
return manifoldModule;
|
|
41647
41894
|
}
|
|
41648
|
-
const DEFAULT_ACTIVE_BACKEND = "
|
|
41895
|
+
const DEFAULT_ACTIVE_BACKEND = "manifold";
|
|
41649
41896
|
let _activeBackend = DEFAULT_ACTIVE_BACKEND;
|
|
41650
41897
|
let _runtimeWarn = (msg) => console.warn(msg);
|
|
41651
41898
|
function unwrapShapeLike(value) {
|
|
@@ -41664,6 +41911,7 @@ const _shapePlacementRefs = /* @__PURE__ */ new WeakMap();
|
|
|
41664
41911
|
const _shapeExplodeHint = /* @__PURE__ */ new WeakMap();
|
|
41665
41912
|
const _shapeRuntimeBackends = /* @__PURE__ */ new WeakMap();
|
|
41666
41913
|
const _shapeTopology = /* @__PURE__ */ new WeakMap();
|
|
41914
|
+
const _shapeLineageTokens = /* @__PURE__ */ new WeakMap();
|
|
41667
41915
|
const _shapeFaceLabels = /* @__PURE__ */ new WeakMap();
|
|
41668
41916
|
const _shapeReferenceNames = /* @__PURE__ */ new WeakMap();
|
|
41669
41917
|
const _shapeReferenceAliases = /* @__PURE__ */ new WeakMap();
|
|
@@ -41724,6 +41972,10 @@ function copyShapeReferenceMetadata(source, target) {
|
|
|
41724
41972
|
const aliases = cloneReferenceAliases(_shapeReferenceAliases.get(source));
|
|
41725
41973
|
if (aliases && aliases.size > 0) _shapeReferenceAliases.set(target, aliases);
|
|
41726
41974
|
}
|
|
41975
|
+
function copyShapeLineage(source, target) {
|
|
41976
|
+
const token = _shapeLineageTokens.get(source);
|
|
41977
|
+
if (token) _shapeLineageTokens.set(target, token);
|
|
41978
|
+
}
|
|
41727
41979
|
function assertNonEmptyReferenceName(name, apiName) {
|
|
41728
41980
|
const trimmed = name.trim();
|
|
41729
41981
|
if (!trimmed) throw new Error(`${apiName} requires a non-empty reference name.`);
|
|
@@ -41792,50 +42044,25 @@ function setShapeRuntimeBackendInternal(shape, payload) {
|
|
|
41792
42044
|
return shape;
|
|
41793
42045
|
}
|
|
41794
42046
|
function setShapeCompilePlanInternal(shape, plan) {
|
|
41795
|
-
_shapeCompilePlans.set(shape,
|
|
42047
|
+
_shapeCompilePlans.set(shape, deepFreezePlanData(plan));
|
|
41796
42048
|
recordShapeSourceSpanInternal(shape, plan);
|
|
41797
42049
|
return shape;
|
|
41798
42050
|
}
|
|
41799
|
-
function cloneShapeSourceSpanRecords(records) {
|
|
41800
|
-
return (records ?? []).map((record) => ({
|
|
41801
|
-
planCacheKey: record.planCacheKey,
|
|
41802
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41803
|
-
}));
|
|
41804
|
-
}
|
|
41805
42051
|
function upsertShapeSourceSpanRecord(shape, record) {
|
|
41806
|
-
|
|
41807
|
-
if (records
|
|
41808
|
-
|
|
41809
|
-
|
|
41810
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41811
|
-
});
|
|
41812
|
-
_shapeSourceSpans.set(shape, records);
|
|
41813
|
-
}
|
|
41814
|
-
function stableTraceSourcePlanEncode(value, arrayMember) {
|
|
41815
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
41816
|
-
return arrayMember ? "null" : void 0;
|
|
41817
|
-
}
|
|
41818
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
41819
|
-
return JSON.stringify(value);
|
|
41820
|
-
}
|
|
41821
|
-
if (Array.isArray(value)) {
|
|
41822
|
-
return `[${value.map((item) => stableTraceSourcePlanEncode(item, true) ?? "null").join(",")}]`;
|
|
41823
|
-
}
|
|
41824
|
-
const record = value;
|
|
41825
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
41826
|
-
return stableTraceSourcePlanEncode(record.base, arrayMember);
|
|
41827
|
-
}
|
|
41828
|
-
const entries = Object.entries(record).sort(([left], [right]) => left.localeCompare(right));
|
|
41829
|
-
const encodedEntries = [];
|
|
41830
|
-
for (const [key, item] of entries) {
|
|
41831
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
41832
|
-
const encoded = stableTraceSourcePlanEncode(item, false);
|
|
41833
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
42052
|
+
let records = _shapeSourceSpans.get(shape);
|
|
42053
|
+
if (!records) {
|
|
42054
|
+
records = /* @__PURE__ */ new Map();
|
|
42055
|
+
_shapeSourceSpans.set(shape, records);
|
|
41834
42056
|
}
|
|
41835
|
-
|
|
42057
|
+
if (records.has(record.planCacheKey)) return;
|
|
42058
|
+
records.set(record.planCacheKey, Object.freeze({ ...record.sourceSpan }));
|
|
41836
42059
|
}
|
|
42060
|
+
const traceSourcePlanHasher = createStructuralHasher({
|
|
42061
|
+
skipKey: (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation",
|
|
42062
|
+
unwrap: (record) => record.kind === "queryOwner" && record.base ? record.base : void 0
|
|
42063
|
+
});
|
|
41837
42064
|
function normalizedTraceSourcePlanCacheKey(plan) {
|
|
41838
|
-
return `shape-plan-v1:${
|
|
42065
|
+
return `shape-plan-v1:${traceSourcePlanHasher(plan)}`;
|
|
41839
42066
|
}
|
|
41840
42067
|
function recordShapeSourceSpanInternal(shape, plan) {
|
|
41841
42068
|
if (!hasActiveRuntimeSourceResolver()) return;
|
|
@@ -41855,13 +42082,20 @@ function recordShapeSourceSpanInternal(shape, plan) {
|
|
|
41855
42082
|
}
|
|
41856
42083
|
}
|
|
41857
42084
|
function copyShapeSourceSpans(source, target) {
|
|
41858
|
-
const records =
|
|
41859
|
-
if (records.
|
|
42085
|
+
const records = _shapeSourceSpans.get(source);
|
|
42086
|
+
if (records && records.size > 0) _shapeSourceSpans.set(target, new Map(records));
|
|
41860
42087
|
}
|
|
41861
42088
|
function mergeShapeSourceSpans(sources, target) {
|
|
42089
|
+
let records = _shapeSourceSpans.get(target);
|
|
41862
42090
|
for (const source of sources) {
|
|
41863
|
-
|
|
41864
|
-
|
|
42091
|
+
const sourceRecords = _shapeSourceSpans.get(source);
|
|
42092
|
+
if (!sourceRecords) continue;
|
|
42093
|
+
if (!records) {
|
|
42094
|
+
records = /* @__PURE__ */ new Map();
|
|
42095
|
+
_shapeSourceSpans.set(target, records);
|
|
42096
|
+
}
|
|
42097
|
+
for (const [key, span] of sourceRecords) {
|
|
42098
|
+
if (!records.has(key)) records.set(key, span);
|
|
41865
42099
|
}
|
|
41866
42100
|
}
|
|
41867
42101
|
}
|
|
@@ -41896,7 +42130,7 @@ function getShapeRuntimeBackendInternal(shape) {
|
|
|
41896
42130
|
function getShapeCompilePlanInternal(shape) {
|
|
41897
42131
|
const stored = _shapeCompilePlans.get(shape);
|
|
41898
42132
|
if (!stored) throw new Error("Shape has no compile plan — every Shape must have an explicit plan set via setShapeCompilePlanInternal()");
|
|
41899
|
-
return
|
|
42133
|
+
return stored;
|
|
41900
42134
|
}
|
|
41901
42135
|
function getShapePlacementRefsInternal(shape) {
|
|
41902
42136
|
return clonePlacementReferences(_shapePlacementRefs.get(shape) ?? createPlacementReferences());
|
|
@@ -42588,6 +42822,30 @@ function checkLabelCollisions(operation2, plans) {
|
|
|
42588
42822
|
seen.push(...labels);
|
|
42589
42823
|
}
|
|
42590
42824
|
}
|
|
42825
|
+
function formatDiagnosticNumber(value) {
|
|
42826
|
+
if (!Number.isFinite(value)) return String(value);
|
|
42827
|
+
const rounded = Math.abs(value) < 5e-4 ? 0 : value;
|
|
42828
|
+
return Number(rounded.toFixed(3)).toString();
|
|
42829
|
+
}
|
|
42830
|
+
function formatDiagnosticVec(values) {
|
|
42831
|
+
return `[${values.slice(0, 3).map(formatDiagnosticNumber).join(",")}]`;
|
|
42832
|
+
}
|
|
42833
|
+
function formatShapeBoundsForDiagnostic(shape) {
|
|
42834
|
+
try {
|
|
42835
|
+
const bounds = shape.boundingBox();
|
|
42836
|
+
return `bounds=${formatDiagnosticVec(bounds.min)}..${formatDiagnosticVec(bounds.max)}`;
|
|
42837
|
+
} catch (error) {
|
|
42838
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42839
|
+
return `bounds=unavailable(${message})`;
|
|
42840
|
+
}
|
|
42841
|
+
}
|
|
42842
|
+
function formatShapeOperandForDiagnostic(role, shape) {
|
|
42843
|
+
const name = _shapeReferenceNames.get(shape);
|
|
42844
|
+
return `${role}=${name ? `${name} ` : ""}${formatShapeBoundsForDiagnostic(shape)}`;
|
|
42845
|
+
}
|
|
42846
|
+
function formatSourceSpanForDiagnostic(sourceSpan) {
|
|
42847
|
+
return sourceSpan ? ` source=${sourceSpan.fileName}:${sourceSpan.line}:${sourceSpan.column}` : "";
|
|
42848
|
+
}
|
|
42591
42849
|
function withCopiedDimensions(source, out) {
|
|
42592
42850
|
setShapeDimensionsInternal(out, cloneDimensions(getShapeDimensionsInternal(source), true));
|
|
42593
42851
|
setShapeGeometryInfoInternal(out, getShapeGeometryInfoInternal(source));
|
|
@@ -42602,6 +42860,7 @@ function withCopiedDimensions(source, out) {
|
|
|
42602
42860
|
const sourceLabels = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42603
42861
|
if (sourceLabels) _shapeFaceLabels.set(out, sourceLabels);
|
|
42604
42862
|
copyShapeReferenceMetadata(source, out);
|
|
42863
|
+
copyShapeLineage(source, out);
|
|
42605
42864
|
copyShapeSourceSpans(source, out);
|
|
42606
42865
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42607
42866
|
}
|
|
@@ -42631,6 +42890,7 @@ function withTransformedDimensions(source, out, m2) {
|
|
|
42631
42890
|
const sourceLabelsT = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42632
42891
|
if (sourceLabelsT) _shapeFaceLabels.set(out, sourceLabelsT);
|
|
42633
42892
|
copyShapeReferenceMetadata(source, out);
|
|
42893
|
+
copyShapeLineage(source, out);
|
|
42634
42894
|
copyShapeSourceSpans(source, out);
|
|
42635
42895
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42636
42896
|
}
|
|
@@ -43058,6 +43318,7 @@ class Shape {
|
|
|
43058
43318
|
this.colorHex = color;
|
|
43059
43319
|
setShapeRuntimeBackendInternal(this, payload);
|
|
43060
43320
|
setShapeGeometryInfoInternal(this, createGeometryInfo(geometryInfo));
|
|
43321
|
+
_shapeLineageTokens.set(this, {});
|
|
43061
43322
|
}
|
|
43062
43323
|
/** @internal Use .color() instead. */
|
|
43063
43324
|
setColor(value) {
|
|
@@ -43176,6 +43437,12 @@ class Shape {
|
|
|
43176
43437
|
* with `union()` / `difference()` to avoid collisions. Collision detection throws a
|
|
43177
43438
|
* clear error with a fix suggestion.
|
|
43178
43439
|
*
|
|
43440
|
+
* Boolean survival: `union()` and `intersection()` carry labels from every operand;
|
|
43441
|
+
* `difference()` carries only the base (first) operand's labels — cutter labels are
|
|
43442
|
+
* dropped. A surviving label addresses whatever portion of its face survives the
|
|
43443
|
+
* boolean; cutters may split or erase it, and a lineage shared by multiple union
|
|
43444
|
+
* operands resolves as a face set rather than a single face.
|
|
43445
|
+
*
|
|
43179
43446
|
* For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's
|
|
43180
43447
|
* compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops)
|
|
43181
43448
|
* are resolved via coplanar triangle clustering.
|
|
@@ -43592,7 +43859,7 @@ class Shape {
|
|
|
43592
43859
|
const tbb = s.boundingBox();
|
|
43593
43860
|
return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
|
|
43594
43861
|
}
|
|
43595
|
-
/** Rotate around an arbitrary axis through the origin. */
|
|
43862
|
+
/** 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. */
|
|
43596
43863
|
rotate(axis, angleDeg, options) {
|
|
43597
43864
|
validateRotateAxis(axis, "Shape.rotate()");
|
|
43598
43865
|
validateRotateAngle(angleDeg, "Shape.rotate()");
|
|
@@ -43779,7 +44046,7 @@ class Shape {
|
|
|
43779
44046
|
* Warn if a boolean operation had no geometric effect.
|
|
43780
44047
|
* Compares volumes before and after; if they match within tolerance, the operation was a no-op.
|
|
43781
44048
|
*/
|
|
43782
|
-
static _checkBooleanNoOp(op, base, result) {
|
|
44049
|
+
static _checkBooleanNoOp(op, base, result, tools = []) {
|
|
43783
44050
|
try {
|
|
43784
44051
|
if (op === "intersection") {
|
|
43785
44052
|
if (result.isEmpty()) {
|
|
@@ -43792,8 +44059,15 @@ class Shape {
|
|
|
43792
44059
|
const volAfter = result.volume();
|
|
43793
44060
|
const tol = Math.max(volBefore * 1e-4, 1e-3);
|
|
43794
44061
|
if (Math.abs(volBefore - volAfter) < tol) {
|
|
44062
|
+
const sourceSpan = captureRuntimeSourceSpan();
|
|
44063
|
+
const operandContext = [
|
|
44064
|
+
formatShapeOperandForDiagnostic("base", base),
|
|
44065
|
+
...tools.map((tool, index2) => formatShapeOperandForDiagnostic(`tool[${index2 + 1}]`, tool))
|
|
44066
|
+
].join(" ");
|
|
43795
44067
|
_runtimeWarn(
|
|
43796
|
-
`subtract() had no effect — the tool may not overlap the base shape. Base vol=${volBefore.toFixed(1)}mm³, result vol=${volAfter.toFixed(1)}mm
|
|
44068
|
+
`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}`,
|
|
44069
|
+
"boolean.difference.noEffect",
|
|
44070
|
+
sourceSpan ? { sourceSpan } : void 0
|
|
43797
44071
|
);
|
|
43798
44072
|
}
|
|
43799
44073
|
}
|
|
@@ -43853,7 +44127,7 @@ class Shape {
|
|
|
43853
44127
|
),
|
|
43854
44128
|
nextPlan
|
|
43855
44129
|
);
|
|
43856
|
-
Shape._checkBooleanNoOp("difference", this, resultShape);
|
|
44130
|
+
Shape._checkBooleanNoOp("difference", this, resultShape, shapes.slice(1));
|
|
43857
44131
|
return resultShape;
|
|
43858
44132
|
}
|
|
43859
44133
|
/** Keep only the overlap with other shapes. Method form of intersection(). */
|
|
@@ -44256,6 +44530,11 @@ class Shape {
|
|
|
44256
44530
|
/**
|
|
44257
44531
|
* Position this shape by matching connectors to a target.
|
|
44258
44532
|
*
|
|
44533
|
+
* Alignment: with a single connector pair, the shape translates and rotates so the connector
|
|
44534
|
+
* origins coincide and the axes oppose (plug-in model); `up` pins the roll. With multiple pairs,
|
|
44535
|
+
* the connector origins define the rigid transform — still author meaningful `axis`/`up` values
|
|
44536
|
+
* so the same connectors remain useful for `connect()`, audits, and future matching.
|
|
44537
|
+
*
|
|
44259
44538
|
* Overloads:
|
|
44260
44539
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
44261
44540
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -45325,117 +45604,10 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
45325
45604
|
edgeLength: options.edgeLength
|
|
45326
45605
|
};
|
|
45327
45606
|
}
|
|
45328
|
-
const
|
|
45329
|
-
|
|
45330
|
-
|
|
45331
|
-
|
|
45332
|
-
const dists = [0];
|
|
45333
|
-
for (let i = 0; i < poly.length; i++) {
|
|
45334
|
-
const p1 = poly[i];
|
|
45335
|
-
const p2 = poly[(i + 1) % poly.length];
|
|
45336
|
-
const dx = p2[0] - p1[0];
|
|
45337
|
-
const dy = p2[1] - p1[1];
|
|
45338
|
-
const d2 = Math.sqrt(dx * dx + dy * dy);
|
|
45339
|
-
dists.push(dists[dists.length - 1] + d2);
|
|
45340
|
-
}
|
|
45341
|
-
const totalDist = dists[dists.length - 1];
|
|
45342
|
-
if (totalDist < 1e-12) {
|
|
45343
|
-
return Array.from({ length: targetCount }, () => [poly[0][0], poly[0][1]]);
|
|
45344
|
-
}
|
|
45345
|
-
const out = [];
|
|
45346
|
-
for (let i = 0; i < targetCount; i++) {
|
|
45347
|
-
const targetDist = i / targetCount * totalDist;
|
|
45348
|
-
let low = 0;
|
|
45349
|
-
let high = dists.length - 1;
|
|
45350
|
-
while (low < high) {
|
|
45351
|
-
const mid = low + high >> 1;
|
|
45352
|
-
if (dists[mid] <= targetDist) {
|
|
45353
|
-
low = mid + 1;
|
|
45354
|
-
} else {
|
|
45355
|
-
high = mid;
|
|
45356
|
-
}
|
|
45357
|
-
}
|
|
45358
|
-
const seg = low - 1;
|
|
45359
|
-
const t = (targetDist - dists[seg]) / (dists[seg + 1] - dists[seg]);
|
|
45360
|
-
const p1 = poly[seg % poly.length];
|
|
45361
|
-
const p2 = poly[(seg + 1) % poly.length];
|
|
45362
|
-
out.push([p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t]);
|
|
45363
|
-
}
|
|
45364
|
-
return out;
|
|
45365
|
-
}
|
|
45366
|
-
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid(poly)) {
|
|
45367
|
-
if (poly.length < 3 || targetCount <= 0) return null;
|
|
45368
|
-
if (!isConvexPolygon(poly)) return null;
|
|
45369
|
-
const out = [];
|
|
45370
|
-
for (let index2 = 0; index2 < targetCount; index2 += 1) {
|
|
45371
|
-
const angle = index2 / targetCount * Math.PI * 2;
|
|
45372
|
-
const point = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
45373
|
-
if (!point) return null;
|
|
45374
|
-
out.push(point);
|
|
45375
|
-
}
|
|
45376
|
-
return out;
|
|
45377
|
-
}
|
|
45378
|
-
function rayPolygonIntersection(origin, direction, poly) {
|
|
45379
|
-
let bestT = Infinity;
|
|
45380
|
-
let best = null;
|
|
45381
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
45382
|
-
const a2 = poly[index2];
|
|
45383
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
45384
|
-
const edge = [b[0] - a2[0], b[1] - a2[1]];
|
|
45385
|
-
const denom = cross$1(direction, edge);
|
|
45386
|
-
if (Math.abs(denom) < EPS) continue;
|
|
45387
|
-
const delta = [a2[0] - origin[0], a2[1] - origin[1]];
|
|
45388
|
-
const rayT = cross$1(delta, edge) / denom;
|
|
45389
|
-
const edgeT = cross$1(delta, direction) / denom;
|
|
45390
|
-
if (rayT >= -EPS && edgeT >= -EPS && edgeT <= 1 + EPS && rayT < bestT) {
|
|
45391
|
-
bestT = rayT;
|
|
45392
|
-
best = [origin[0] + direction[0] * rayT, origin[1] + direction[1] * rayT];
|
|
45393
|
-
}
|
|
45394
|
-
}
|
|
45395
|
-
return best;
|
|
45396
|
-
}
|
|
45397
|
-
function polygonCentroid(poly) {
|
|
45398
|
-
let area2 = 0;
|
|
45399
|
-
let cx = 0;
|
|
45400
|
-
let cy = 0;
|
|
45401
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
45402
|
-
const a2 = poly[index2];
|
|
45403
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
45404
|
-
const crossValue = cross$1(a2, b);
|
|
45405
|
-
area2 += crossValue;
|
|
45406
|
-
cx += (a2[0] + b[0]) * crossValue;
|
|
45407
|
-
cy += (a2[1] + b[1]) * crossValue;
|
|
45408
|
-
}
|
|
45409
|
-
if (Math.abs(area2) < EPS) return averagePoint(poly);
|
|
45410
|
-
return [cx / (3 * area2), cy / (3 * area2)];
|
|
45411
|
-
}
|
|
45412
|
-
function averagePoint(poly) {
|
|
45413
|
-
let x2 = 0;
|
|
45414
|
-
let y2 = 0;
|
|
45415
|
-
for (const point of poly) {
|
|
45416
|
-
x2 += point[0];
|
|
45417
|
-
y2 += point[1];
|
|
45418
|
-
}
|
|
45419
|
-
return [x2 / poly.length, y2 / poly.length];
|
|
45420
|
-
}
|
|
45421
|
-
function isConvexPolygon(poly) {
|
|
45422
|
-
let sign = 0;
|
|
45423
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
45424
|
-
const a2 = poly[index2];
|
|
45425
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
45426
|
-
const c2 = poly[(index2 + 2) % poly.length];
|
|
45427
|
-
const turn = cross$1([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
|
|
45428
|
-
if (Math.abs(turn) < EPS) continue;
|
|
45429
|
-
const currentSign = Math.sign(turn);
|
|
45430
|
-
if (sign !== 0 && currentSign !== sign) return false;
|
|
45431
|
-
sign = currentSign;
|
|
45432
|
-
}
|
|
45433
|
-
return sign !== 0;
|
|
45434
|
-
}
|
|
45435
|
-
function cross$1(a2, b) {
|
|
45436
|
-
return a2[0] * b[1] - a2[1] * b[0];
|
|
45437
|
-
}
|
|
45438
|
-
function loftStitched(profiles, heights, wasm) {
|
|
45607
|
+
const CORNER_TURN_DEG = 30;
|
|
45608
|
+
const SPAN_ANGLE_PER_RING_DEG = 3;
|
|
45609
|
+
const MAX_SPAN_SUBDIVISION = 24;
|
|
45610
|
+
function loftStitched(profiles, heights, wasm, options = {}) {
|
|
45439
45611
|
if (profiles.length < 2) return null;
|
|
45440
45612
|
const classified = profiles.map((loops) => classifyLoops(loops));
|
|
45441
45613
|
const outerCount = classified[0].outers.length;
|
|
@@ -45450,7 +45622,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
45450
45622
|
const holeGroups = holeCount > 0 ? matchLoopsAcrossProfiles(classified.map((c2) => c2.holes)) : [];
|
|
45451
45623
|
const outerSolids = [];
|
|
45452
45624
|
for (const group of outerGroups) {
|
|
45453
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
45625
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
45454
45626
|
if (!solid) {
|
|
45455
45627
|
for (const s of outerSolids) s.delete();
|
|
45456
45628
|
return null;
|
|
@@ -45467,7 +45639,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
45467
45639
|
if (holeGroups.length > 0) {
|
|
45468
45640
|
const holeSolids = [];
|
|
45469
45641
|
for (const group of holeGroups) {
|
|
45470
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
45642
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
45471
45643
|
if (!solid) {
|
|
45472
45644
|
result.delete();
|
|
45473
45645
|
for (const s of holeSolids) s.delete();
|
|
@@ -45553,68 +45725,564 @@ function signedArea$1(loop) {
|
|
|
45553
45725
|
}
|
|
45554
45726
|
return area * 0.5;
|
|
45555
45727
|
}
|
|
45556
|
-
function
|
|
45728
|
+
function detectCorners(loop) {
|
|
45729
|
+
const corners = [];
|
|
45730
|
+
const n = loop.length;
|
|
45731
|
+
const threshold = CORNER_TURN_DEG * Math.PI / 180;
|
|
45732
|
+
for (let i = 0; i < n; i++) {
|
|
45733
|
+
const prev = loop[(i - 1 + n) % n];
|
|
45734
|
+
const curr = loop[i];
|
|
45735
|
+
const next = loop[(i + 1) % n];
|
|
45736
|
+
const ax = curr[0] - prev[0];
|
|
45737
|
+
const ay = curr[1] - prev[1];
|
|
45738
|
+
const bx = next[0] - curr[0];
|
|
45739
|
+
const by = next[1] - curr[1];
|
|
45740
|
+
const la = Math.hypot(ax, ay);
|
|
45741
|
+
const lb = Math.hypot(bx, by);
|
|
45742
|
+
if (la < 1e-12 || lb < 1e-12) continue;
|
|
45743
|
+
const dot2 = (ax * bx + ay * by) / (la * lb);
|
|
45744
|
+
const turn = Math.acos(Math.min(1, Math.max(-1, dot2)));
|
|
45745
|
+
if (turn > threshold) corners.push(i);
|
|
45746
|
+
}
|
|
45747
|
+
return corners;
|
|
45748
|
+
}
|
|
45749
|
+
function cumulativeArcLength(loop) {
|
|
45750
|
+
const dists = [0];
|
|
45751
|
+
for (let i = 0; i < loop.length; i++) {
|
|
45752
|
+
const p1 = loop[i];
|
|
45753
|
+
const p2 = loop[(i + 1) % loop.length];
|
|
45754
|
+
dists.push(dists[i] + Math.hypot(p2[0] - p1[0], p2[1] - p1[1]));
|
|
45755
|
+
}
|
|
45756
|
+
return { dists, total: dists[loop.length] };
|
|
45757
|
+
}
|
|
45758
|
+
function pointAtArcLength(loop, dists, total, s) {
|
|
45759
|
+
if (total < 1e-12) return [loop[0][0], loop[0][1]];
|
|
45760
|
+
let target = s % total;
|
|
45761
|
+
if (target < 0) target += total;
|
|
45762
|
+
let low = 0;
|
|
45763
|
+
let high = dists.length - 1;
|
|
45764
|
+
while (low < high) {
|
|
45765
|
+
const mid = low + high >> 1;
|
|
45766
|
+
if (dists[mid] <= target) low = mid + 1;
|
|
45767
|
+
else high = mid;
|
|
45768
|
+
}
|
|
45769
|
+
const seg = low - 1;
|
|
45770
|
+
const segLen = dists[seg + 1] - dists[seg];
|
|
45771
|
+
const t = segLen < 1e-12 ? 0 : (target - dists[seg]) / segLen;
|
|
45772
|
+
const p1 = loop[seg % loop.length];
|
|
45773
|
+
const p2 = loop[(seg + 1) % loop.length];
|
|
45774
|
+
return [p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t];
|
|
45775
|
+
}
|
|
45776
|
+
function refineParams(params, rangeEnd, maxGap) {
|
|
45777
|
+
if (!Number.isFinite(maxGap)) return params;
|
|
45778
|
+
const out = [];
|
|
45779
|
+
for (let i = 0; i < params.length; i++) {
|
|
45780
|
+
const a2 = params[i];
|
|
45781
|
+
const b = i + 1 < params.length ? params[i + 1] : rangeEnd;
|
|
45782
|
+
out.push(a2);
|
|
45783
|
+
const gap = b - a2;
|
|
45784
|
+
if (gap > maxGap) {
|
|
45785
|
+
const pieces = Math.min(64, Math.ceil(gap / maxGap));
|
|
45786
|
+
for (let k2 = 1; k2 < pieces; k2++) out.push(a2 + gap * k2 / pieces);
|
|
45787
|
+
}
|
|
45788
|
+
}
|
|
45789
|
+
return out;
|
|
45790
|
+
}
|
|
45791
|
+
function turnAngle(prev, curr, next) {
|
|
45792
|
+
const ax = curr[0] - prev[0];
|
|
45793
|
+
const ay = curr[1] - prev[1];
|
|
45794
|
+
const bx = next[0] - curr[0];
|
|
45795
|
+
const by = next[1] - curr[1];
|
|
45796
|
+
const la = Math.hypot(ax, ay);
|
|
45797
|
+
const lb = Math.hypot(bx, by);
|
|
45798
|
+
if (la < 1e-12 || lb < 1e-12) return 0;
|
|
45799
|
+
const dot2 = (ax * bx + ay * by) / (la * lb);
|
|
45800
|
+
return Math.acos(Math.min(1, Math.max(-1, dot2)));
|
|
45801
|
+
}
|
|
45802
|
+
function maxTurnPerColumn(rings) {
|
|
45803
|
+
const N = rings[0].length;
|
|
45804
|
+
const out = new Float64Array(N);
|
|
45805
|
+
for (const ring of rings) {
|
|
45806
|
+
for (let j = 0; j < N; j++) {
|
|
45807
|
+
const turn = turnAngle(ring[(j - 1 + N) % N], ring[j], ring[(j + 1) % N]);
|
|
45808
|
+
if (turn > out[j]) out[j] = turn;
|
|
45809
|
+
}
|
|
45810
|
+
}
|
|
45811
|
+
return out;
|
|
45812
|
+
}
|
|
45813
|
+
const CURVE_TURN_EPS = 0.5 * Math.PI / 180;
|
|
45814
|
+
function maxQuadDeviation(rings, heights, colA, colB) {
|
|
45815
|
+
let worst = 0;
|
|
45816
|
+
for (let i = 0; i < rings.length - 1; i++) {
|
|
45817
|
+
const a2 = [rings[i][colA][0], rings[i][colA][1], heights[i]];
|
|
45818
|
+
const b = [rings[i][colB][0], rings[i][colB][1], heights[i]];
|
|
45819
|
+
const c2 = [rings[i + 1][colB][0], rings[i + 1][colB][1], heights[i + 1]];
|
|
45820
|
+
const d2 = [rings[i + 1][colA][0], rings[i + 1][colA][1], heights[i + 1]];
|
|
45821
|
+
const n = cross3$1(sub3(b, a2), sub3(d2, a2));
|
|
45822
|
+
const len = Math.hypot(n[0], n[1], n[2]);
|
|
45823
|
+
if (len < 1e-12) continue;
|
|
45824
|
+
const deviation = Math.abs((n[0] * (c2[0] - a2[0]) + n[1] * (c2[1] - a2[1]) + n[2] * (c2[2] - a2[2])) / len);
|
|
45825
|
+
if (deviation > worst) worst = deviation;
|
|
45826
|
+
}
|
|
45827
|
+
return worst;
|
|
45828
|
+
}
|
|
45829
|
+
function buildCompatibleRings(loops, heights, edgeLength2) {
|
|
45830
|
+
const cornerSets = loops.map(detectCorners);
|
|
45831
|
+
const cornerCount = cornerSets[0].length;
|
|
45832
|
+
const cornersMatch = cornerCount > 0 && cornerSets.every((c2) => c2.length === cornerCount);
|
|
45833
|
+
if (cornersMatch) {
|
|
45834
|
+
const anchored = buildCornerAnchoredRings(loops, heights, cornerSets, edgeLength2);
|
|
45835
|
+
if (anchored) return anchored;
|
|
45836
|
+
}
|
|
45837
|
+
return buildSeamAlignedRings(loops, heights, edgeLength2);
|
|
45838
|
+
}
|
|
45839
|
+
function buildCornerAnchoredRings(loops, heights, cornerSets, edgeLength2) {
|
|
45840
|
+
const cornerCount = cornerSets[0].length;
|
|
45841
|
+
const arcs = loops.map((loop) => cumulativeArcLength(loop));
|
|
45842
|
+
const alignedCorners = [cornerSets[0]];
|
|
45843
|
+
for (let i = 1; i < loops.length; i++) {
|
|
45844
|
+
const prev = alignedCorners[i - 1];
|
|
45845
|
+
const prevLoop = loops[i - 1];
|
|
45846
|
+
const curr = cornerSets[i];
|
|
45847
|
+
const loop = loops[i];
|
|
45848
|
+
let best = curr;
|
|
45849
|
+
let bestCost = Infinity;
|
|
45850
|
+
for (let r = 0; r < cornerCount; r++) {
|
|
45851
|
+
const rotated = curr.map((_2, k2) => curr[(k2 + r) % cornerCount]);
|
|
45852
|
+
let cost = 0;
|
|
45853
|
+
for (let k2 = 0; k2 < cornerCount; k2++) {
|
|
45854
|
+
const a2 = prevLoop[prev[k2]];
|
|
45855
|
+
const b = loop[rotated[k2]];
|
|
45856
|
+
cost += (a2[0] - b[0]) ** 2 + (a2[1] - b[1]) ** 2;
|
|
45857
|
+
}
|
|
45858
|
+
if (cost < bestCost) {
|
|
45859
|
+
bestCost = cost;
|
|
45860
|
+
best = rotated;
|
|
45861
|
+
}
|
|
45862
|
+
}
|
|
45863
|
+
alignedCorners.push(best);
|
|
45864
|
+
}
|
|
45865
|
+
const segParams = [];
|
|
45866
|
+
const segLengths = [];
|
|
45867
|
+
for (let i = 0; i < loops.length; i++) {
|
|
45868
|
+
const loop = loops[i];
|
|
45869
|
+
const { dists, total } = arcs[i];
|
|
45870
|
+
if (total < 1e-9) return null;
|
|
45871
|
+
const corners = alignedCorners[i];
|
|
45872
|
+
const stationSegs = [];
|
|
45873
|
+
const stationLens = [];
|
|
45874
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
45875
|
+
const from = corners[s];
|
|
45876
|
+
const to = corners[(s + 1) % cornerCount];
|
|
45877
|
+
const start = dists[from];
|
|
45878
|
+
let end = dists[to];
|
|
45879
|
+
if (to <= from) end += total;
|
|
45880
|
+
const len = end - start;
|
|
45881
|
+
if (len < 1e-9) return null;
|
|
45882
|
+
const interior = [];
|
|
45883
|
+
const n = loop.length;
|
|
45884
|
+
for (let v = (from + 1) % n; v !== to; v = (v + 1) % n) {
|
|
45885
|
+
let d2 = dists[v];
|
|
45886
|
+
if (d2 < start) d2 += total;
|
|
45887
|
+
const p2 = (d2 - start) / len;
|
|
45888
|
+
if (p2 > 1e-9 && p2 < 1 - 1e-9) interior.push(p2);
|
|
45889
|
+
}
|
|
45890
|
+
stationSegs.push(interior);
|
|
45891
|
+
stationLens.push(len);
|
|
45892
|
+
}
|
|
45893
|
+
segParams.push(stationSegs);
|
|
45894
|
+
segLengths.push(stationLens);
|
|
45895
|
+
}
|
|
45896
|
+
let masterParams = [];
|
|
45897
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
45898
|
+
let bestStation = 0;
|
|
45899
|
+
for (let i = 1; i < loops.length; i++) {
|
|
45900
|
+
if (segParams[i][s].length > segParams[bestStation][s].length) bestStation = i;
|
|
45901
|
+
}
|
|
45902
|
+
masterParams.push([0, ...segParams[bestStation][s]]);
|
|
45903
|
+
}
|
|
45904
|
+
const sampleRings = (paramsBySegment) => {
|
|
45905
|
+
const rings2 = [];
|
|
45906
|
+
for (let i = 0; i < loops.length; i++) {
|
|
45907
|
+
const loop = loops[i];
|
|
45908
|
+
const { dists, total } = arcs[i];
|
|
45909
|
+
const corners = alignedCorners[i];
|
|
45910
|
+
const ring = [];
|
|
45911
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
45912
|
+
const from = corners[s];
|
|
45913
|
+
const to = corners[(s + 1) % cornerCount];
|
|
45914
|
+
const start = dists[from];
|
|
45915
|
+
let end = dists[to];
|
|
45916
|
+
if (to <= from) end += total;
|
|
45917
|
+
const len = end - start;
|
|
45918
|
+
for (const p2 of paramsBySegment[s]) {
|
|
45919
|
+
if (p2 === 0) {
|
|
45920
|
+
ring.push([loop[from][0], loop[from][1]]);
|
|
45921
|
+
} else {
|
|
45922
|
+
ring.push(pointAtArcLength(loop, dists, total, start + p2 * len));
|
|
45923
|
+
}
|
|
45924
|
+
}
|
|
45925
|
+
}
|
|
45926
|
+
rings2.push(ring);
|
|
45927
|
+
}
|
|
45928
|
+
return rings2;
|
|
45929
|
+
};
|
|
45930
|
+
let rings = sampleRings(masterParams);
|
|
45931
|
+
if (edgeLength2 && edgeLength2 > 0) {
|
|
45932
|
+
const ringLength = rings[0].length;
|
|
45933
|
+
const turns = maxTurnPerColumn(rings);
|
|
45934
|
+
const segStart = [];
|
|
45935
|
+
{
|
|
45936
|
+
let col = 0;
|
|
45937
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
45938
|
+
segStart.push(col);
|
|
45939
|
+
col += masterParams[s].length;
|
|
45940
|
+
}
|
|
45941
|
+
}
|
|
45942
|
+
let changed = false;
|
|
45943
|
+
const refined = [];
|
|
45944
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
45945
|
+
const params = masterParams[s];
|
|
45946
|
+
let maxLen = 0;
|
|
45947
|
+
for (let i = 0; i < loops.length; i++) maxLen = Math.max(maxLen, segLengths[i][s]);
|
|
45948
|
+
const out = [];
|
|
45949
|
+
for (let k2 = 0; k2 < params.length; k2++) {
|
|
45950
|
+
const a2 = params[k2];
|
|
45951
|
+
const b = k2 + 1 < params.length ? params[k2 + 1] : 1;
|
|
45952
|
+
out.push(a2);
|
|
45953
|
+
const gapLen = (b - a2) * maxLen;
|
|
45954
|
+
if (gapLen <= edgeLength2) continue;
|
|
45955
|
+
const colA = segStart[s] + k2;
|
|
45956
|
+
const colB = k2 + 1 < params.length ? colA + 1 : segStart[(s + 1) % cornerCount] % ringLength;
|
|
45957
|
+
const curved = k2 > 0 && turns[colA] > CURVE_TURN_EPS || k2 + 1 < params.length && turns[colB] > CURVE_TURN_EPS;
|
|
45958
|
+
const twisted = !curved && maxQuadDeviation(rings, heights, colA, colB) > edgeLength2 * 0.05;
|
|
45959
|
+
if (!curved && !twisted) continue;
|
|
45960
|
+
const pieces = Math.min(64, Math.ceil(gapLen / edgeLength2));
|
|
45961
|
+
for (let p2 = 1; p2 < pieces; p2++) out.push(a2 + (b - a2) * p2 / pieces);
|
|
45962
|
+
changed = true;
|
|
45963
|
+
}
|
|
45964
|
+
refined.push(out);
|
|
45965
|
+
}
|
|
45966
|
+
if (changed) {
|
|
45967
|
+
masterParams = refined;
|
|
45968
|
+
rings = sampleRings(masterParams);
|
|
45969
|
+
}
|
|
45970
|
+
}
|
|
45971
|
+
const cornerColumns = [];
|
|
45972
|
+
{
|
|
45973
|
+
let col = 0;
|
|
45974
|
+
for (let s = 0; s < cornerCount; s++) {
|
|
45975
|
+
cornerColumns.push(col);
|
|
45976
|
+
col += masterParams[s].length;
|
|
45977
|
+
}
|
|
45978
|
+
}
|
|
45979
|
+
return { rings, cornerColumns };
|
|
45980
|
+
}
|
|
45981
|
+
function buildSeamAlignedRings(loops, _heights, edgeLength2) {
|
|
45982
|
+
const arcs = loops.map((loop) => cumulativeArcLength(loop));
|
|
45983
|
+
let bestStation = 0;
|
|
45984
|
+
for (let i = 1; i < loops.length; i++) {
|
|
45985
|
+
if (loops[i].length > loops[bestStation].length) bestStation = i;
|
|
45986
|
+
}
|
|
45987
|
+
const { dists: masterDists, total: masterTotal } = arcs[bestStation];
|
|
45988
|
+
if (masterTotal < 1e-9) return null;
|
|
45989
|
+
let params = loops[bestStation].map((_2, v) => masterDists[v] / masterTotal);
|
|
45990
|
+
if (params.length < 24) {
|
|
45991
|
+
params = refineParams(params, 1, 1 / 24);
|
|
45992
|
+
}
|
|
45993
|
+
const sampleRings = (paramList) => loops.map((loop, i) => {
|
|
45994
|
+
const { dists, total } = arcs[i];
|
|
45995
|
+
if (total < 1e-9) {
|
|
45996
|
+
return paramList.map(() => [loop[0][0], loop[0][1]]);
|
|
45997
|
+
}
|
|
45998
|
+
return paramList.map((p2) => pointAtArcLength(loop, dists, total, p2 * total));
|
|
45999
|
+
});
|
|
46000
|
+
let rings = sampleRings(params);
|
|
46001
|
+
if (edgeLength2 && edgeLength2 > 0) {
|
|
46002
|
+
let maxPerimeter = 0;
|
|
46003
|
+
for (const { total } of arcs) maxPerimeter = Math.max(maxPerimeter, total);
|
|
46004
|
+
const turns = maxTurnPerColumn(rings);
|
|
46005
|
+
const N0 = params.length;
|
|
46006
|
+
const out = [];
|
|
46007
|
+
let changed = false;
|
|
46008
|
+
for (let k2 = 0; k2 < N0; k2++) {
|
|
46009
|
+
const a2 = params[k2];
|
|
46010
|
+
const b = k2 + 1 < N0 ? params[k2 + 1] : 1;
|
|
46011
|
+
out.push(a2);
|
|
46012
|
+
const gapLen = (b - a2) * maxPerimeter;
|
|
46013
|
+
if (gapLen <= edgeLength2) continue;
|
|
46014
|
+
if (turns[k2] <= CURVE_TURN_EPS && turns[(k2 + 1) % N0] <= CURVE_TURN_EPS) continue;
|
|
46015
|
+
const pieces = Math.min(64, Math.ceil(gapLen / edgeLength2));
|
|
46016
|
+
for (let p2 = 1; p2 < pieces; p2++) out.push(a2 + (b - a2) * p2 / pieces);
|
|
46017
|
+
changed = true;
|
|
46018
|
+
}
|
|
46019
|
+
if (changed) {
|
|
46020
|
+
params = out;
|
|
46021
|
+
rings = sampleRings(params);
|
|
46022
|
+
}
|
|
46023
|
+
}
|
|
46024
|
+
const N = params.length;
|
|
46025
|
+
for (let i = 1; i < rings.length; i++) {
|
|
46026
|
+
const prev = rings[i - 1];
|
|
46027
|
+
const curr = rings[i];
|
|
46028
|
+
let bestShift = 0;
|
|
46029
|
+
let bestCost = Infinity;
|
|
46030
|
+
for (let shift = 0; shift < N; shift++) {
|
|
46031
|
+
let cost = 0;
|
|
46032
|
+
for (let j = 0; j < N; j++) {
|
|
46033
|
+
const a2 = prev[j];
|
|
46034
|
+
const b = curr[(j + shift) % N];
|
|
46035
|
+
cost += (a2[0] - b[0]) ** 2 + (a2[1] - b[1]) ** 2;
|
|
46036
|
+
if (cost >= bestCost) break;
|
|
46037
|
+
}
|
|
46038
|
+
if (cost < bestCost) {
|
|
46039
|
+
bestCost = cost;
|
|
46040
|
+
bestShift = shift;
|
|
46041
|
+
}
|
|
46042
|
+
}
|
|
46043
|
+
if (bestShift !== 0) {
|
|
46044
|
+
rings[i] = curr.map((_2, j) => curr[(j + bestShift) % N]);
|
|
46045
|
+
}
|
|
46046
|
+
}
|
|
46047
|
+
return { rings, cornerColumns: [] };
|
|
46048
|
+
}
|
|
46049
|
+
function buildSpanRows(rings, heights) {
|
|
46050
|
+
const R = rings.length;
|
|
46051
|
+
const N = rings[0].length;
|
|
46052
|
+
const stations = rings.map((ring, i) => ring.map(([x2, y2]) => [x2, y2, heights[i]]));
|
|
46053
|
+
const t = [0];
|
|
46054
|
+
for (let i = 0; i < R - 1; i++) {
|
|
46055
|
+
let sum2 = 0;
|
|
46056
|
+
for (let j = 0; j < N; j++) {
|
|
46057
|
+
sum2 += dist3(stations[i][j], stations[i + 1][j]);
|
|
46058
|
+
}
|
|
46059
|
+
const avg = Math.max(sum2 / N, 1e-9);
|
|
46060
|
+
t.push(t[i] + Math.sqrt(avg));
|
|
46061
|
+
}
|
|
46062
|
+
const tangents = [];
|
|
46063
|
+
for (let i = 0; i < R; i++) {
|
|
46064
|
+
const row = [];
|
|
46065
|
+
for (let j = 0; j < N; j++) {
|
|
46066
|
+
row.push(stationTangent(stations, t, i, j));
|
|
46067
|
+
}
|
|
46068
|
+
tangents.push(row);
|
|
46069
|
+
}
|
|
46070
|
+
const rows = [{ points: stations[0], tangents: tangents[0], isStation: true }];
|
|
46071
|
+
for (let i = 0; i < R - 1; i++) {
|
|
46072
|
+
const h = t[i + 1] - t[i];
|
|
46073
|
+
let maxAngle = 0;
|
|
46074
|
+
const stride = Math.max(1, Math.floor(N / 16));
|
|
46075
|
+
for (let j = 0; j < N; j += stride) {
|
|
46076
|
+
maxAngle = Math.max(maxAngle, angleBetween(tangents[i][j], tangents[i + 1][j]));
|
|
46077
|
+
}
|
|
46078
|
+
const k2 = Math.min(MAX_SPAN_SUBDIVISION, Math.max(1, Math.ceil(maxAngle / SPAN_ANGLE_PER_RING_DEG)));
|
|
46079
|
+
for (let s = 1; s < k2; s++) {
|
|
46080
|
+
const u2 = s / k2;
|
|
46081
|
+
const points = [];
|
|
46082
|
+
const rowTangents = [];
|
|
46083
|
+
for (let j = 0; j < N; j++) {
|
|
46084
|
+
const { point, tangent } = hermite(stations[i][j], tangents[i][j], stations[i + 1][j], tangents[i + 1][j], h, u2);
|
|
46085
|
+
points.push(point);
|
|
46086
|
+
rowTangents.push(tangent);
|
|
46087
|
+
}
|
|
46088
|
+
rows.push({ points, tangents: rowTangents, isStation: false });
|
|
46089
|
+
}
|
|
46090
|
+
rows.push({ points: stations[i + 1], tangents: tangents[i + 1], isStation: true });
|
|
46091
|
+
}
|
|
46092
|
+
return rows;
|
|
46093
|
+
}
|
|
46094
|
+
function stationTangent(stations, t, i, j) {
|
|
46095
|
+
const R = stations.length;
|
|
46096
|
+
if (i === 0) {
|
|
46097
|
+
return scale3(sub3(stations[1][j], stations[0][j]), 1 / (t[1] - t[0]));
|
|
46098
|
+
}
|
|
46099
|
+
if (i === R - 1) {
|
|
46100
|
+
return scale3(sub3(stations[R - 1][j], stations[R - 2][j]), 1 / (t[R - 1] - t[R - 2]));
|
|
46101
|
+
}
|
|
46102
|
+
const hPrev = t[i] - t[i - 1];
|
|
46103
|
+
const hNext = t[i + 1] - t[i];
|
|
46104
|
+
const dPrev = scale3(sub3(stations[i][j], stations[i - 1][j]), 1 / hPrev);
|
|
46105
|
+
const dNext = scale3(sub3(stations[i + 1][j], stations[i][j]), 1 / hNext);
|
|
46106
|
+
return scale3(add3(scale3(dPrev, hNext), scale3(dNext, hPrev)), 1 / (hPrev + hNext));
|
|
46107
|
+
}
|
|
46108
|
+
function hermite(p0, m0, p1, m1, h, u2) {
|
|
46109
|
+
const u22 = u2 * u2;
|
|
46110
|
+
const u3 = u22 * u2;
|
|
46111
|
+
const h00 = 2 * u3 - 3 * u22 + 1;
|
|
46112
|
+
const h10 = u3 - 2 * u22 + u2;
|
|
46113
|
+
const h01 = -2 * u3 + 3 * u22;
|
|
46114
|
+
const h11 = u3 - u22;
|
|
46115
|
+
const d00 = 6 * u22 - 6 * u2;
|
|
46116
|
+
const d10 = 3 * u22 - 4 * u2 + 1;
|
|
46117
|
+
const d01 = -6 * u22 + 6 * u2;
|
|
46118
|
+
const d11 = 3 * u22 - 2 * u2;
|
|
46119
|
+
const point = [
|
|
46120
|
+
h00 * p0[0] + h10 * h * m0[0] + h01 * p1[0] + h11 * h * m1[0],
|
|
46121
|
+
h00 * p0[1] + h10 * h * m0[1] + h01 * p1[1] + h11 * h * m1[1],
|
|
46122
|
+
h00 * p0[2] + h10 * h * m0[2] + h01 * p1[2] + h11 * h * m1[2]
|
|
46123
|
+
];
|
|
46124
|
+
const tangent = [
|
|
46125
|
+
d00 * p0[0] / h + d10 * m0[0] + d01 * p1[0] / h + d11 * m1[0],
|
|
46126
|
+
d00 * p0[1] / h + d10 * m0[1] + d01 * p1[1] / h + d11 * m1[1],
|
|
46127
|
+
d00 * p0[2] / h + d10 * m0[2] + d01 * p1[2] / h + d11 * m1[2]
|
|
46128
|
+
];
|
|
46129
|
+
return { point, tangent };
|
|
46130
|
+
}
|
|
46131
|
+
function stitchSingleLoopLoft(loops, heights, wasm, options) {
|
|
45557
46132
|
const normalizedLoops = loops.map((loop) => {
|
|
45558
46133
|
const area = signedArea$1(loop);
|
|
45559
46134
|
return area < 0 ? [...loop].reverse() : loop;
|
|
45560
46135
|
});
|
|
45561
|
-
|
|
45562
|
-
|
|
45563
|
-
|
|
45564
|
-
|
|
45565
|
-
|
|
45566
|
-
const
|
|
45567
|
-
const
|
|
45568
|
-
const
|
|
45569
|
-
|
|
45570
|
-
|
|
45571
|
-
|
|
45572
|
-
|
|
45573
|
-
const
|
|
45574
|
-
|
|
45575
|
-
|
|
45576
|
-
|
|
45577
|
-
|
|
46136
|
+
const compatible = buildCompatibleRings(normalizedLoops, heights, options.edgeLength);
|
|
46137
|
+
if (!compatible) return null;
|
|
46138
|
+
const { rings, cornerColumns } = compatible;
|
|
46139
|
+
const N = rings[0].length;
|
|
46140
|
+
if (N < 3) return null;
|
|
46141
|
+
const rows = buildSpanRows(rings, heights);
|
|
46142
|
+
const R = rows.length;
|
|
46143
|
+
const cornerSet = new Set(cornerColumns);
|
|
46144
|
+
const vertProps = [];
|
|
46145
|
+
let vertCount = 0;
|
|
46146
|
+
const fwdIdx = [];
|
|
46147
|
+
const bwdIdx = [];
|
|
46148
|
+
const pushVert = (p2, n) => {
|
|
46149
|
+
vertProps.push(p2[0], p2[1], p2[2], n[0], n[1], n[2]);
|
|
46150
|
+
return vertCount++;
|
|
46151
|
+
};
|
|
46152
|
+
for (let r = 0; r < R; r++) {
|
|
46153
|
+
const { points, tangents } = rows[r];
|
|
46154
|
+
const fwd = new Array(N);
|
|
46155
|
+
const bwd = new Array(N);
|
|
46156
|
+
for (let j = 0; j < N; j++) {
|
|
46157
|
+
const prev = points[(j - 1 + N) % N];
|
|
46158
|
+
const curr = points[j];
|
|
46159
|
+
const next = points[(j + 1) % N];
|
|
46160
|
+
if (cornerSet.has(j)) {
|
|
46161
|
+
const nFwd = surfaceNormal(sub3(next, curr), tangents[j]);
|
|
46162
|
+
const nBwd = surfaceNormal(sub3(curr, prev), tangents[j]);
|
|
46163
|
+
fwd[j] = pushVert(curr, nFwd);
|
|
46164
|
+
bwd[j] = pushVert(curr, nBwd);
|
|
46165
|
+
} else {
|
|
46166
|
+
const idx = pushVert(curr, surfaceNormal(sub3(next, prev), tangents[j]));
|
|
46167
|
+
fwd[j] = idx;
|
|
46168
|
+
bwd[j] = idx;
|
|
46169
|
+
}
|
|
45578
46170
|
}
|
|
46171
|
+
fwdIdx.push(fwd);
|
|
46172
|
+
bwdIdx.push(bwd);
|
|
45579
46173
|
}
|
|
45580
|
-
|
|
45581
|
-
|
|
45582
|
-
const nextIdx = (i + 1) * N;
|
|
46174
|
+
const triangles = [];
|
|
46175
|
+
for (let r = 0; r < R - 1; r++) {
|
|
45583
46176
|
for (let j = 0; j < N; j++) {
|
|
45584
46177
|
const j1 = (j + 1) % N;
|
|
45585
|
-
const v0 =
|
|
45586
|
-
const
|
|
45587
|
-
const v2 =
|
|
45588
|
-
const
|
|
46178
|
+
const v0 = fwdIdx[r][j];
|
|
46179
|
+
const v3 = bwdIdx[r][j1];
|
|
46180
|
+
const v2 = bwdIdx[r + 1][j1];
|
|
46181
|
+
const v1 = fwdIdx[r + 1][j];
|
|
45589
46182
|
triangles.push(v0, v3, v2);
|
|
45590
46183
|
triangles.push(v0, v2, v1);
|
|
45591
46184
|
}
|
|
45592
46185
|
}
|
|
45593
|
-
const
|
|
45594
|
-
const
|
|
45595
|
-
|
|
46186
|
+
const bottomRing = rows[0].points;
|
|
46187
|
+
const topRing = rows[R - 1].points;
|
|
46188
|
+
const bottom2D = bottomRing.map(([x2, y2]) => [x2, y2]);
|
|
46189
|
+
const bottomTris = wasm.triangulate([bottom2D]);
|
|
46190
|
+
const bottomBase = vertCount;
|
|
46191
|
+
for (const p2 of bottomRing) pushVert(p2, [0, 0, -1]);
|
|
46192
|
+
for (const tri of bottomTris) {
|
|
45596
46193
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
45597
|
-
triangles.push(v0, v2, v1);
|
|
46194
|
+
triangles.push(bottomBase + v0, bottomBase + v2, bottomBase + v1);
|
|
45598
46195
|
}
|
|
45599
|
-
const
|
|
45600
|
-
const
|
|
45601
|
-
const
|
|
45602
|
-
for (const
|
|
46196
|
+
const top2D = topRing.map(([x2, y2]) => [x2, y2]);
|
|
46197
|
+
const topTris = wasm.triangulate([top2D]);
|
|
46198
|
+
const topBase = vertCount;
|
|
46199
|
+
for (const p2 of topRing) pushVert(p2, [0, 0, 1]);
|
|
46200
|
+
for (const tri of topTris) {
|
|
45603
46201
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
45604
|
-
triangles.push(
|
|
46202
|
+
triangles.push(topBase + v0, topBase + v1, topBase + v2);
|
|
45605
46203
|
}
|
|
45606
46204
|
const mesh = new wasm.Mesh({
|
|
45607
|
-
numProp:
|
|
45608
|
-
vertProperties: new Float32Array(
|
|
46205
|
+
numProp: 6,
|
|
46206
|
+
vertProperties: new Float32Array(vertProps),
|
|
45609
46207
|
triVerts: new Uint32Array(triangles)
|
|
45610
46208
|
});
|
|
45611
46209
|
try {
|
|
46210
|
+
mesh.merge();
|
|
45612
46211
|
const manifold = new wasm.Manifold(mesh);
|
|
45613
46212
|
return manifold;
|
|
45614
46213
|
} catch (_e2) {
|
|
45615
46214
|
return null;
|
|
45616
46215
|
}
|
|
45617
46216
|
}
|
|
46217
|
+
function surfaceNormal(chord, span) {
|
|
46218
|
+
const n = cross3$1(chord, span);
|
|
46219
|
+
const len = Math.hypot(n[0], n[1], n[2]);
|
|
46220
|
+
if (len < 1e-12) {
|
|
46221
|
+
const radial = Math.hypot(chord[0], chord[1]);
|
|
46222
|
+
if (radial > 1e-12) return [chord[1] / radial, -chord[0] / radial, 0];
|
|
46223
|
+
return [0, 0, 1];
|
|
46224
|
+
}
|
|
46225
|
+
return [n[0] / len, n[1] / len, n[2] / len];
|
|
46226
|
+
}
|
|
46227
|
+
function sub3(a2, b) {
|
|
46228
|
+
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
46229
|
+
}
|
|
46230
|
+
function add3(a2, b) {
|
|
46231
|
+
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
46232
|
+
}
|
|
46233
|
+
function scale3(a2, s) {
|
|
46234
|
+
return [a2[0] * s, a2[1] * s, a2[2] * s];
|
|
46235
|
+
}
|
|
46236
|
+
function cross3$1(a2, b) {
|
|
46237
|
+
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]];
|
|
46238
|
+
}
|
|
46239
|
+
function dist3(a2, b) {
|
|
46240
|
+
return Math.hypot(a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]);
|
|
46241
|
+
}
|
|
46242
|
+
function angleBetween(a2, b) {
|
|
46243
|
+
const la = Math.hypot(a2[0], a2[1], a2[2]);
|
|
46244
|
+
const lb = Math.hypot(b[0], b[1], b[2]);
|
|
46245
|
+
if (la < 1e-12 || lb < 1e-12) return 0;
|
|
46246
|
+
const dot2 = (a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2]) / (la * lb);
|
|
46247
|
+
return Math.acos(Math.min(1, Math.max(-1, dot2))) * 180 / Math.PI;
|
|
46248
|
+
}
|
|
46249
|
+
function resamplePolygon(poly, targetCount) {
|
|
46250
|
+
if (poly.length < 2) return poly;
|
|
46251
|
+
if (targetCount <= 0) return [];
|
|
46252
|
+
const dists = [0];
|
|
46253
|
+
for (let i = 0; i < poly.length; i++) {
|
|
46254
|
+
const p1 = poly[i];
|
|
46255
|
+
const p2 = poly[(i + 1) % poly.length];
|
|
46256
|
+
const dx = p2[0] - p1[0];
|
|
46257
|
+
const dy = p2[1] - p1[1];
|
|
46258
|
+
const d2 = Math.sqrt(dx * dx + dy * dy);
|
|
46259
|
+
dists.push(dists[dists.length - 1] + d2);
|
|
46260
|
+
}
|
|
46261
|
+
const totalDist = dists[dists.length - 1];
|
|
46262
|
+
if (totalDist < 1e-12) {
|
|
46263
|
+
return Array.from({ length: targetCount }, () => [poly[0][0], poly[0][1]]);
|
|
46264
|
+
}
|
|
46265
|
+
const out = [];
|
|
46266
|
+
for (let i = 0; i < targetCount; i++) {
|
|
46267
|
+
const targetDist = i / targetCount * totalDist;
|
|
46268
|
+
let low = 0;
|
|
46269
|
+
let high = dists.length - 1;
|
|
46270
|
+
while (low < high) {
|
|
46271
|
+
const mid = low + high >> 1;
|
|
46272
|
+
if (dists[mid] <= targetDist) {
|
|
46273
|
+
low = mid + 1;
|
|
46274
|
+
} else {
|
|
46275
|
+
high = mid;
|
|
46276
|
+
}
|
|
46277
|
+
}
|
|
46278
|
+
const seg = low - 1;
|
|
46279
|
+
const t = (targetDist - dists[seg]) / (dists[seg + 1] - dists[seg]);
|
|
46280
|
+
const p1 = poly[seg % poly.length];
|
|
46281
|
+
const p2 = poly[(seg + 1) % poly.length];
|
|
46282
|
+
out.push([p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t]);
|
|
46283
|
+
}
|
|
46284
|
+
return out;
|
|
46285
|
+
}
|
|
45618
46286
|
function sweepStitched(profilePolygons, pathPoints, up, wasm) {
|
|
45619
46287
|
if (pathPoints.length < 2) return null;
|
|
45620
46288
|
if (profilePolygons.length === 0) return null;
|
|
@@ -46312,7 +46980,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
|
|
|
46312
46980
|
throw new Error("offsetSolid() collapsed the compatible-loft height span.");
|
|
46313
46981
|
}
|
|
46314
46982
|
const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
|
|
46315
|
-
const stitched = loftStitched(offsetPolygons, heights, wasm);
|
|
46983
|
+
const stitched = loftStitched(offsetPolygons, heights, wasm, { edgeLength: plan.edgeLength });
|
|
46316
46984
|
if (!stitched) {
|
|
46317
46985
|
throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
|
|
46318
46986
|
}
|
|
@@ -46447,12 +47115,12 @@ function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
|
46447
47115
|
disposeWasmObject(crossSection);
|
|
46448
47116
|
}
|
|
46449
47117
|
});
|
|
46450
|
-
if (inputPolygons.length >= 2) {
|
|
46451
|
-
const stitched = loftStitched(inputPolygons, plan.heights, wasm);
|
|
47118
|
+
if (!plan.forceField && inputPolygons.length >= 2) {
|
|
47119
|
+
const stitched = loftStitched(inputPolygons, plan.heights, wasm, { edgeLength: plan.edgeLength });
|
|
46452
47120
|
if (stitched) return stitched;
|
|
46453
47121
|
}
|
|
46454
47122
|
const input = buildLoftLevelSetInput(inputPolygons, plan.heights, { edgeLength: plan.edgeLength, boundsPadding: plan.boundsPadding });
|
|
46455
|
-
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
47123
|
+
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm, plan.meshing);
|
|
46456
47124
|
}
|
|
46457
47125
|
function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
46458
47126
|
const crossSection = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
@@ -46717,7 +47385,7 @@ function lowerFromSlicesToManifold(plan, wasm) {
|
|
|
46717
47385
|
}
|
|
46718
47386
|
});
|
|
46719
47387
|
const heights = sorted.map((s) => s.offset);
|
|
46720
|
-
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm) : null;
|
|
47388
|
+
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm, { edgeLength: plan.edgeLength }) : null;
|
|
46721
47389
|
if (stitched) {
|
|
46722
47390
|
solid = stitched;
|
|
46723
47391
|
} else {
|
|
@@ -47158,17 +47826,21 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
47158
47826
|
if (diagnostics) diagnostics.projectionMs = nowMs() - projectionStart;
|
|
47159
47827
|
const simplificationStart = nowMs();
|
|
47160
47828
|
if (((meshing == null ? void 0 : meshing.simplify) ?? "safe") !== "off" && snMesh.numTris > 100) {
|
|
47161
|
-
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties,
|
|
47829
|
+
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties, vertProps6, edgeLength2, meshing == null ? void 0 : meshing.maxTriangles);
|
|
47162
47830
|
}
|
|
47163
47831
|
if (diagnostics) {
|
|
47164
47832
|
diagnostics.simplificationMs = nowMs() - simplificationStart;
|
|
47165
47833
|
diagnostics.trianglesAfterSimplification = triVerts.length / 3;
|
|
47166
47834
|
}
|
|
47167
47835
|
if ((meshing == null ? void 0 : meshing.maxTriangles) !== void 0 && triVerts.length / 3 > meshing.maxTriangles) {
|
|
47836
|
+
const verb = (meshing.simplify ?? "safe") === "off" ? "produced" : "could only simplify to";
|
|
47168
47837
|
throw new Error(
|
|
47169
|
-
`SDF meshing
|
|
47838
|
+
`SDF meshing ${verb} ${triVerts.length / 3} safe triangles, above maxTriangles=${meshing.maxTriangles}. Increase maxTriangles or use a larger edgeLength.`
|
|
47170
47839
|
);
|
|
47171
47840
|
}
|
|
47841
|
+
if (!isClosedConsistentlyWoundTriangleMesh(triVerts, vertProps6.length / 6)) {
|
|
47842
|
+
throw new Error("SDF meshing produced an open, non-manifold, or inconsistently wound triangle mesh.");
|
|
47843
|
+
}
|
|
47172
47844
|
const wasmMesh = new wasm.Mesh({
|
|
47173
47845
|
numProp: 6,
|
|
47174
47846
|
vertProperties: vertProps6,
|
|
@@ -47186,28 +47858,102 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
47186
47858
|
disposeWasmObject(wasmMesh);
|
|
47187
47859
|
}
|
|
47188
47860
|
}
|
|
47189
|
-
function simplifySdfMesh(triVerts, vertProperties,
|
|
47861
|
+
function simplifySdfMesh(triVerts, vertProperties, finalVertProperties, edgeLength2, maxTriangles) {
|
|
47190
47862
|
const maxError = edgeLength2 * 0.15;
|
|
47191
47863
|
const inputTriangles = triVerts.length / 3;
|
|
47192
|
-
const
|
|
47193
|
-
const
|
|
47864
|
+
const ratios = buildSimplificationRatios(inputTriangles, maxTriangles);
|
|
47865
|
+
const vertexCount = finalVertProperties.length / 6;
|
|
47866
|
+
let bestValid = null;
|
|
47194
47867
|
for (const ratio of ratios) {
|
|
47195
|
-
let simplified
|
|
47196
|
-
simplified = filterDegenerateTriangles(simplified);
|
|
47197
|
-
let mesh = null;
|
|
47198
|
-
let manifold = null;
|
|
47868
|
+
let simplified;
|
|
47199
47869
|
try {
|
|
47200
|
-
|
|
47201
|
-
manifold = new wasm.Manifold(mesh);
|
|
47202
|
-
return simplified;
|
|
47870
|
+
simplified = simplifyMesh(triVerts, vertProperties, ratio, maxError);
|
|
47203
47871
|
} catch {
|
|
47204
|
-
|
|
47205
|
-
|
|
47206
|
-
|
|
47872
|
+
continue;
|
|
47873
|
+
}
|
|
47874
|
+
simplified = filterDegenerateTriangles(simplified);
|
|
47875
|
+
if (isClosedConsistentlyWoundTriangleMesh(simplified, vertexCount)) {
|
|
47876
|
+
if (maxTriangles === void 0 || simplified.length / 3 <= maxTriangles) {
|
|
47877
|
+
return simplified;
|
|
47878
|
+
}
|
|
47879
|
+
if (!bestValid || simplified.length < bestValid.length) {
|
|
47880
|
+
bestValid = simplified;
|
|
47881
|
+
}
|
|
47207
47882
|
}
|
|
47208
47883
|
}
|
|
47884
|
+
if (bestValid) {
|
|
47885
|
+
return bestValid;
|
|
47886
|
+
}
|
|
47209
47887
|
return triVerts;
|
|
47210
47888
|
}
|
|
47889
|
+
function buildSimplificationRatios(inputTriangles, maxTriangles) {
|
|
47890
|
+
if (maxTriangles === void 0 || maxTriangles >= inputTriangles) {
|
|
47891
|
+
return [0.5, 0.75];
|
|
47892
|
+
}
|
|
47893
|
+
const requestedRatio = Math.max(1 / inputTriangles, Math.min(0.5, maxTriangles / inputTriangles));
|
|
47894
|
+
const candidates = [
|
|
47895
|
+
requestedRatio,
|
|
47896
|
+
0.05,
|
|
47897
|
+
0.04,
|
|
47898
|
+
0.03,
|
|
47899
|
+
0.02,
|
|
47900
|
+
0.015,
|
|
47901
|
+
0.01,
|
|
47902
|
+
5e-3,
|
|
47903
|
+
requestedRatio * 0.85,
|
|
47904
|
+
requestedRatio * 0.7,
|
|
47905
|
+
requestedRatio * 0.55,
|
|
47906
|
+
requestedRatio * 0.4,
|
|
47907
|
+
requestedRatio * 0.25,
|
|
47908
|
+
requestedRatio * 0.1,
|
|
47909
|
+
0.5,
|
|
47910
|
+
0.75
|
|
47911
|
+
];
|
|
47912
|
+
const seen = /* @__PURE__ */ new Set();
|
|
47913
|
+
const ratios = [];
|
|
47914
|
+
for (const ratio of candidates) {
|
|
47915
|
+
const rounded = Number(Math.max(1 / inputTriangles, Math.min(0.75, ratio)).toFixed(6));
|
|
47916
|
+
if (seen.has(rounded)) continue;
|
|
47917
|
+
seen.add(rounded);
|
|
47918
|
+
ratios.push(rounded);
|
|
47919
|
+
}
|
|
47920
|
+
return ratios;
|
|
47921
|
+
}
|
|
47922
|
+
function isClosedConsistentlyWoundTriangleMesh(triVerts, vertexCount) {
|
|
47923
|
+
if (triVerts.length === 0 || triVerts.length % 3 !== 0) return false;
|
|
47924
|
+
const edgeUse = /* @__PURE__ */ new Map();
|
|
47925
|
+
for (let i = 0; i < triVerts.length; i += 3) {
|
|
47926
|
+
const a2 = triVerts[i];
|
|
47927
|
+
const b = triVerts[i + 1];
|
|
47928
|
+
const c2 = triVerts[i + 2];
|
|
47929
|
+
if (!isValidMeshIndex(a2, vertexCount) || !isValidMeshIndex(b, vertexCount) || !isValidMeshIndex(c2, vertexCount)) return false;
|
|
47930
|
+
if (a2 === b || b === c2 || a2 === c2) return false;
|
|
47931
|
+
for (const [from, to] of [
|
|
47932
|
+
[a2, b],
|
|
47933
|
+
[b, c2],
|
|
47934
|
+
[c2, a2]
|
|
47935
|
+
]) {
|
|
47936
|
+
const lo = Math.min(from, to);
|
|
47937
|
+
const hi = Math.max(from, to);
|
|
47938
|
+
const key = `${lo}/${hi}`;
|
|
47939
|
+
const winding = from === lo ? 1 : -1;
|
|
47940
|
+
const entry = edgeUse.get(key);
|
|
47941
|
+
if (entry) {
|
|
47942
|
+
entry.count += 1;
|
|
47943
|
+
entry.winding += winding;
|
|
47944
|
+
} else {
|
|
47945
|
+
edgeUse.set(key, { count: 1, winding });
|
|
47946
|
+
}
|
|
47947
|
+
}
|
|
47948
|
+
}
|
|
47949
|
+
for (const entry of edgeUse.values()) {
|
|
47950
|
+
if (entry.count !== 2 || entry.winding !== 0) return false;
|
|
47951
|
+
}
|
|
47952
|
+
return true;
|
|
47953
|
+
}
|
|
47954
|
+
function isValidMeshIndex(value, vertexCount) {
|
|
47955
|
+
return Number.isInteger(value) && value >= 0 && value < vertexCount;
|
|
47956
|
+
}
|
|
47211
47957
|
function filterDegenerateTriangles(triVerts) {
|
|
47212
47958
|
let writeIdx = 0;
|
|
47213
47959
|
for (let i = 0; i < triVerts.length; i += 3) {
|
|
@@ -49647,15 +50393,23 @@ function computeGeometryArrays(mesh, options = {}) {
|
|
|
49647
50393
|
normals[o + 7] = vertNormals[i2 * 3 + 1];
|
|
49648
50394
|
normals[o + 8] = vertNormals[i2 * 3 + 2];
|
|
49649
50395
|
} else if (numProp >= 6) {
|
|
49650
|
-
|
|
49651
|
-
|
|
49652
|
-
|
|
49653
|
-
|
|
49654
|
-
|
|
49655
|
-
|
|
49656
|
-
|
|
49657
|
-
|
|
49658
|
-
|
|
50396
|
+
const corners = [i0, i1, i2];
|
|
50397
|
+
for (let v = 0; v < 3; v++) {
|
|
50398
|
+
const base = corners[v] * numProp;
|
|
50399
|
+
const nx = vertProperties[base + 3];
|
|
50400
|
+
const ny = vertProperties[base + 4];
|
|
50401
|
+
const nz = vertProperties[base + 5];
|
|
50402
|
+
const oc = o + v * 3;
|
|
50403
|
+
if (nx * nx + ny * ny + nz * nz > 1e-12) {
|
|
50404
|
+
normals[oc] = nx;
|
|
50405
|
+
normals[oc + 1] = ny;
|
|
50406
|
+
normals[oc + 2] = nz;
|
|
50407
|
+
} else {
|
|
50408
|
+
normals[oc] = fnx;
|
|
50409
|
+
normals[oc + 1] = fny;
|
|
50410
|
+
normals[oc + 2] = fnz;
|
|
50411
|
+
}
|
|
50412
|
+
}
|
|
49659
50413
|
} else {
|
|
49660
50414
|
normals[o] = fnx;
|
|
49661
50415
|
normals[o + 1] = fny;
|