forgecad 0.9.15 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AdminPage-CDyGUinA.js → AdminPage-DwYHz72L.js} +1 -1
- package/dist/assets/{BenchmarkPage-DfPMY_-d.js → BenchmarkPage-a9_f-1US.js} +1 -1
- package/dist/assets/{BlogPage-kF0fkdJT.js → BlogPage-DodHpvmf.js} +1 -1
- package/dist/assets/{DocsPage-B954L3YN.js → DocsPage-B5LePEuj.js} +8 -858
- package/dist/assets/{EditorApp-CuDLxKqL.css → EditorApp-BpjZgzk0.css} +148 -0
- package/dist/assets/EditorApp-QXsAISLR.js +16307 -0
- package/dist/assets/{EmbedViewer-C77B-TrF.js → EmbedViewer-DdEHGUMU.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-Cr6fXMDj.js → LandingPageProofDriven-yhhOodbf.js} +2 -2
- package/dist/assets/{LegalPage-Dzklqmmg.js → LegalPage-5RbKRGYK.js} +1 -1
- package/dist/assets/{PricingPage-zWXkvlwl.js → PricingPage-E3Rma7aV.js} +1 -1
- package/dist/assets/{SettingsPage-Bz0of4KQ.js → SettingsPage-BJZcM97j.js} +1 -1
- package/dist/assets/{app-D3kDkggg.js → app-DSYrDg0V.js} +1846 -352
- package/dist/assets/cli/{render-DSY3mMQa.js → render-ZMHR9HkV.js} +161 -70
- package/dist/assets/{constructionHistoryWorker-gpDo-uH2.js → constructionHistoryWorker-AwMMWSxg.js} +1104 -349
- package/dist/assets/{evalWorker-CU0Ke6DP.js → evalWorker-DbNs7Dkp.js} +5155 -3772
- package/dist/assets/{inspectWorker-COyp8XXA.js → inspectWorker-CZsCFtQT.js} +1415 -439
- package/dist/assets/{targets-B9sGB5nB.js → jointPose-DO6mnXn_.js} +71 -3
- package/dist/assets/{manifold-DNkrUWpA.js → manifold-BGlQBBH9.js} +1 -1
- package/dist/assets/{manifold-BRI5prcH.js → manifold-BU-tJwQh.js} +1 -1
- package/dist/assets/{manifold-C-3h2M7p.js → manifold-fy2MV7K1.js} +2 -2
- package/dist/assets/{reportWorker-CdBz5bNg.js → reportWorker-DO6hcQbh.js} +8474 -4549
- package/dist/assets/{scalar-sampling-budget-wJF98aY9.js → scalar-sampling-budget-o90NSNmF.js} +5347 -3906
- package/dist/assets/{scanProxyWorker-B-9VbLIs.js → scanProxyWorker-2GtDLk-R.js} +19 -6
- package/dist/assets/{javascript-1kQXfVaz.js → typescript-DBQ6RN5l.js} +874 -22
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +3 -3
- package/dist/docs-raw/AI/usage.md +3 -1
- package/dist/docs-raw/CLI.md +65 -239
- package/dist/docs-raw/README.md +6 -0
- package/dist/docs-raw/component-model.md +17 -150
- package/dist/docs-raw/generated/assembly.md +159 -520
- package/dist/docs-raw/generated/concepts.md +245 -3491
- package/dist/docs-raw/generated/core.md +277 -1251
- package/dist/docs-raw/generated/curves.md +387 -1608
- package/dist/docs-raw/generated/legacy.md +162 -0
- package/dist/docs-raw/generated/lib.md +238 -112
- package/dist/docs-raw/generated/output.md +51 -76
- package/dist/docs-raw/generated/runtime-names.md +30 -22
- package/dist/docs-raw/generated/sdf.md +68 -284
- package/dist/docs-raw/generated/sheet-metal.md +68 -335
- package/dist/docs-raw/generated/sketch.md +240 -1161
- package/dist/docs-raw/generated/viewport.md +75 -316
- package/dist/docs-raw/generated/wood.md +21 -49
- package/dist/docs-raw/guides/coordinate-system.md +4 -42
- package/dist/docs-raw/guides/inspection-bundles.md +44 -442
- package/dist/docs-raw/guides/joint-design.md +18 -79
- package/dist/docs-raw/guides/positioning.md +21 -143
- package/dist/docs-raw/guides/scene-presentation.md +89 -0
- package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +25 -111
- package/dist/docs-raw/skills/forgecad-blockout-model.md +20 -117
- package/dist/docs-raw/skills/forgecad-component-model.md +23 -107
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +47 -155
- package/dist/docs-raw/skills/forgecad-image-replicator.md +26 -143
- package/dist/docs-raw/skills/forgecad-lld.md +19 -113
- package/dist/docs-raw/skills/forgecad-make-a-model.md +113 -532
- package/dist/docs-raw/skills/forgecad-model-grader.md +38 -108
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +24 -211
- package/dist/docs-raw/skills/forgecad-project.md +13 -129
- package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +42 -134
- package/dist/docs-raw/skills/forgecad-render-inspect.md +27 -174
- package/dist/docs-raw/skills/forgecad-visual-spec.md +32 -112
- package/dist/docs-raw/skills/forgecad.md +19 -18
- package/dist/docs-raw/skills/index.md +2 -0
- package/dist/docs-raw/welcome.md +4 -2
- package/dist/index.html +1 -1
- package/dist/llms.txt +1 -2
- package/dist/sitemap.xml +13 -13
- package/dist-cli/{check-compiler-SDX5QIXI.js → check-compiler-JTVBITCR.js} +1 -1
- package/dist-cli/{check-query-propagation-EAYEFT77.js → check-query-propagation-3FFLSMVN.js} +1 -1
- package/dist-cli/{chunk-N4O47JLF.js → chunk-OAN5T4XD.js} +5722 -4287
- package/dist-cli/forgecad.js +2195 -656
- package/dist-skill/CONTEXT.md +1778 -7912
- package/dist-skill/SKILL.md +15 -15
- package/dist-skill/docs/API/core/concepts.md +27 -157
- package/dist-skill/docs/CLI.md +65 -239
- package/dist-skill/docs/generated/assembly.md +160 -493
- package/dist-skill/docs/generated/core.md +277 -1251
- package/dist-skill/docs/generated/curves.md +387 -1609
- package/dist-skill/docs/generated/lib.md +238 -112
- package/dist-skill/docs/generated/output.md +51 -76
- package/dist-skill/docs/generated/runtime-names.md +16 -22
- package/dist-skill/docs/generated/sdf.md +68 -284
- package/dist-skill/docs/generated/sheet-metal.md +68 -335
- package/dist-skill/docs/generated/sketch.md +240 -1160
- package/dist-skill/docs/generated/viewport.md +75 -223
- package/dist-skill/docs/generated/wood.md +21 -49
- package/dist-skill/docs/guides/coordinate-system.md +4 -42
- package/dist-skill/docs/guides/inspection-bundles.md +44 -442
- package/dist-skill/docs/guides/joint-design.md +18 -79
- package/dist-skill/docs/guides/positioning.md +21 -143
- package/dist-skill/docs/guides/scene-presentation.md +89 -0
- package/dist-skill/docs/guides/surface-members.md +26 -0
- package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +23 -111
- package/dist-skill/library/forgecad-blockout-model/SKILL.md +18 -117
- package/dist-skill/library/forgecad-component-model/SKILL.md +21 -107
- package/dist-skill/library/forgecad-high-level-spec/SKILL.md +45 -155
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +24 -143
- package/dist-skill/library/forgecad-lld/SKILL.md +17 -113
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +111 -532
- package/dist-skill/library/forgecad-model-grader/SKILL.md +36 -108
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +35 -224
- package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +43 -271
- package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +30 -99
- package/dist-skill/library/forgecad-project/SKILL.md +13 -131
- package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +29 -123
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +25 -174
- package/dist-skill/library/forgecad-visual-spec/SKILL.md +30 -111
- package/dist-skill/website/skills/forgecad-3d-reconstruction.md +58 -0
- package/dist-skill/website/skills/forgecad-blockout-model.md +49 -0
- package/dist-skill/website/skills/forgecad-component-model.md +53 -0
- package/dist-skill/website/skills/forgecad-high-level-spec.md +101 -0
- package/dist-skill/website/skills/forgecad-image-replicator.md +63 -0
- package/dist-skill/website/skills/forgecad-lld.md +41 -0
- package/dist-skill/website/skills/forgecad-make-a-model.md +186 -0
- package/dist-skill/website/skills/forgecad-model-grader.md +82 -0
- package/dist-skill/website/skills/forgecad-prepare-prompt.md +63 -0
- package/dist-skill/website/skills/forgecad-project.md +26 -0
- package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +60 -0
- package/dist-skill/website/skills/forgecad-render-inspect.md +80 -0
- package/dist-skill/website/skills/forgecad-visual-spec.md +71 -0
- package/dist-skill/website/skills/forgecad.md +122 -0
- package/dist-skill/website/skills/index.md +26 -0
- package/examples/api/comparison-imported-sphere-candidate.forge.js +1 -1
- package/examples/api/conformal-product-ribbon.forge.js +1 -1
- package/examples/api/exact-sheet-shell-assembly.forge.js +1 -1
- package/examples/api/extrude-options.forge.js +4 -2
- package/examples/api/field-loft-drive-tip.forge.js +40 -0
- package/examples/api/guided-loft-olive-oil-bottle.forge.js +1 -1
- package/examples/api/helix-basics.forge.js +2 -2
- package/examples/api/highlight-debug.forge.js +10 -10
- package/examples/api/mesh-import-slats.forge.js +1 -1
- package/examples/api/real-product-curves.forge.js +1 -1
- package/examples/api/route3d-elbow.forge.js +3 -0
- package/examples/api/sculpt-box-circle-booleans.forge.js +1 -1
- package/examples/api/sdf-shapes.forge.js +2 -5
- package/examples/api/sketch-rounding-strategies.forge.js +6 -6
- package/examples/api/surface-member-bottle-cage.forge.js +3 -3
- package/examples/api/surface-member-conformal-product-ribbon.forge.js +3 -3
- package/examples/api/surface-member-razor-inlay.forge.js +1 -1
- package/examples/api/variable-sweep-test.forge.js +4 -2
- package/examples/mechanical/airplane-propeller.forge.js +74 -39
- package/examples/nurbs-surface.forge.js +1 -1
- package/examples/products/iphone.forge.js +1 -1
- package/package.json +4 -1
- package/dist/assets/EditorApp-Beb-IZ0y.js +0 -14014
- package/dist/docs-raw/guides/geometry-conventions.md +0 -52
- package/dist/docs-raw/guides/modeling-recipes.md +0 -78
- package/dist-skill/docs/guides/geometry-conventions.md +0 -52
- package/dist-skill/docs/guides/modeling-recipes.md +0 -78
- package/dist-skill/library/forgecad-visual-spec/references/prompt-template.md +0 -79
- package/examples/api/bolted-service-cover.forge.js +0 -17
- package/examples/api/cable-gland-anchor.forge.js +0 -14
- package/examples/api/captured-cartridge-guide.forge.js +0 -14
- package/examples/api/captured-linear-slide.forge.js +0 -13
- package/examples/api/clevis-pin-joint.forge.js +0 -13
- package/examples/api/datum-enclosure.forge.js +0 -16
- package/examples/api/hose-barb-port.forge.js +0 -14
- package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
- package/examples/api/living-hinge-cover.forge.js +0 -14
- package/examples/api/pcb-terminal-block.forge.js +0 -22
- package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
- package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
- package/examples/api/routed-tube-clip.forge.js +0 -15
- package/examples/api/seated-bearing-stack.forge.js +0 -30
- package/examples/api/snap-latch-cover.forge.js +0 -14
- package/examples/api/thumb-screw-clamp.forge.js +0 -15
|
@@ -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([
|
|
@@ -12589,6 +12617,7 @@ class Transform {
|
|
|
12589
12617
|
return this.rotateAxis([0, 0, 1], angleDeg, pivot);
|
|
12590
12618
|
}
|
|
12591
12619
|
/** Scale after the current transform. */
|
|
12620
|
+
// biome-ignore lint/suspicious/useAdjacentOverloadSignatures: Static Transform.scale() and chainable instance scale() intentionally share the CAD API name.
|
|
12592
12621
|
scale(v) {
|
|
12593
12622
|
return this.mul(Transform.scale(v));
|
|
12594
12623
|
}
|
|
@@ -13340,6 +13369,8 @@ function cloneShapeCompilePlan(plan) {
|
|
|
13340
13369
|
heights: plan.heights.map((height) => height),
|
|
13341
13370
|
edgeLength: plan.edgeLength,
|
|
13342
13371
|
boundsPadding: plan.boundsPadding,
|
|
13372
|
+
...plan.forceField ? { forceField: true } : {},
|
|
13373
|
+
...plan.meshing ? { meshing: cloneSdfCompileMeshingSettings(plan.meshing) } : {},
|
|
13343
13374
|
edgeLabels: plan.edgeLabels ? { ...plan.edgeLabels } : void 0,
|
|
13344
13375
|
capLabels: plan.capLabels ? { ...plan.capLabels } : void 0
|
|
13345
13376
|
};
|
|
@@ -13607,7 +13638,6 @@ function cloneShapeCompilePlan(plan) {
|
|
|
13607
13638
|
default:
|
|
13608
13639
|
assertExhaustive(plan);
|
|
13609
13640
|
}
|
|
13610
|
-
if (plan._occtCache) result._occtCache = plan._occtCache;
|
|
13611
13641
|
return result;
|
|
13612
13642
|
}
|
|
13613
13643
|
function appendProfileCompileTransform(plan, step) {
|
|
@@ -13620,22 +13650,31 @@ function appendShapeCompileTransform(plan, step) {
|
|
|
13620
13650
|
if (plan.kind === "transform") {
|
|
13621
13651
|
return {
|
|
13622
13652
|
kind: "transform",
|
|
13623
|
-
base:
|
|
13624
|
-
steps: [...plan.steps
|
|
13653
|
+
base: plan.base,
|
|
13654
|
+
steps: [...plan.steps, cloneShapeTransform(step)]
|
|
13625
13655
|
};
|
|
13626
13656
|
}
|
|
13627
13657
|
return {
|
|
13628
13658
|
kind: "transform",
|
|
13629
|
-
base:
|
|
13659
|
+
base: plan,
|
|
13630
13660
|
steps: [cloneShapeTransform(step)]
|
|
13631
13661
|
};
|
|
13632
13662
|
}
|
|
13633
13663
|
function appendShapeCompileTransforms(plan, steps) {
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
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
|
+
};
|
|
13637
13672
|
}
|
|
13638
|
-
return
|
|
13673
|
+
return {
|
|
13674
|
+
kind: "transform",
|
|
13675
|
+
base: plan,
|
|
13676
|
+
steps: steps.map(cloneShapeTransform)
|
|
13677
|
+
};
|
|
13639
13678
|
}
|
|
13640
13679
|
function wrapShapeCompilePlanWithQueryOwner(plan, owner) {
|
|
13641
13680
|
if (!plan) return null;
|
|
@@ -13709,6 +13748,8 @@ function buildLoftShapeCompilePlan(profiles, heights, options) {
|
|
|
13709
13748
|
heights: heights.map((height) => canonicalNumber(height)),
|
|
13710
13749
|
edgeLength: canonicalNumber(options.edgeLength),
|
|
13711
13750
|
boundsPadding: canonicalNumber(options.boundsPadding),
|
|
13751
|
+
...options.forceField ? { forceField: true } : {},
|
|
13752
|
+
...options.meshing ? { meshing: cloneSdfCompileMeshingSettings(options.meshing) } : {},
|
|
13712
13753
|
edgeLabels: options.edgeLabels ? { ...options.edgeLabels } : void 0
|
|
13713
13754
|
};
|
|
13714
13755
|
}
|
|
@@ -13810,14 +13851,14 @@ function sub$2(a2, b) {
|
|
|
13810
13851
|
function dot$3(a2, b) {
|
|
13811
13852
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
13812
13853
|
}
|
|
13813
|
-
function cross$
|
|
13854
|
+
function cross$3(a2, b) {
|
|
13814
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]];
|
|
13815
13856
|
}
|
|
13816
13857
|
function rotateAroundAxis(v, axis, angleRad) {
|
|
13817
13858
|
const c2 = Math.cos(angleRad);
|
|
13818
13859
|
const s = Math.sin(angleRad);
|
|
13819
13860
|
const term1 = scale$2(v, c2);
|
|
13820
|
-
const term2 = scale$2(cross$
|
|
13861
|
+
const term2 = scale$2(cross$3(axis, v), s);
|
|
13821
13862
|
const term3 = scale$2(axis, dot$3(axis, v) * (1 - c2));
|
|
13822
13863
|
return add$2(add$2(term1, term2), term3);
|
|
13823
13864
|
}
|
|
@@ -14093,13 +14134,13 @@ function sweepPathToPolylineAdaptive(path, baseSamples = 48) {
|
|
|
14093
14134
|
pts.push(evalPathAt(path, 1));
|
|
14094
14135
|
return pts;
|
|
14095
14136
|
}
|
|
14096
|
-
const EPS$
|
|
14137
|
+
const EPS$1 = 1e-8;
|
|
14097
14138
|
function midpoint$1(start, end) {
|
|
14098
14139
|
return [(start[0] + end[0]) * 0.5, (start[1] + end[1]) * 0.5, (start[2] + end[2]) * 0.5];
|
|
14099
14140
|
}
|
|
14100
14141
|
function normalize$3(v) {
|
|
14101
14142
|
const len = Math.hypot(v[0], v[1], v[2]);
|
|
14102
|
-
if (len <= EPS$
|
|
14143
|
+
if (len <= EPS$1) throw new Error("Edge feature selection requires a non-zero direction vector");
|
|
14103
14144
|
return [v[0] / len, v[1] / len, v[2] / len];
|
|
14104
14145
|
}
|
|
14105
14146
|
function subtract(a2, b) {
|
|
@@ -14178,7 +14219,7 @@ function rigidTransformForEdgeStep(step) {
|
|
|
14178
14219
|
case "mirror": {
|
|
14179
14220
|
const [nx0, ny0, nz0] = [step.normalX, step.normalY, step.normalZ];
|
|
14180
14221
|
const len = Math.hypot(nx0, ny0, nz0);
|
|
14181
|
-
if (len <= EPS$
|
|
14222
|
+
if (len <= EPS$1) return Transform.identity();
|
|
14182
14223
|
const nx = nx0 / len;
|
|
14183
14224
|
const ny = ny0 / len;
|
|
14184
14225
|
const nz = nz0 / len;
|
|
@@ -14475,7 +14516,7 @@ function isRectangleProfile(points) {
|
|
|
14475
14516
|
return [next[0] - point[0], next[1] - point[1]];
|
|
14476
14517
|
});
|
|
14477
14518
|
const lengths = vectors.map(([x2, y2]) => Math.hypot(x2, y2));
|
|
14478
|
-
if (lengths.some((length4) => length4 <= EPS$
|
|
14519
|
+
if (lengths.some((length4) => length4 <= EPS$1)) return false;
|
|
14479
14520
|
const dot01 = vectors[0][0] * vectors[1][0] + vectors[0][1] * vectors[1][1];
|
|
14480
14521
|
const dot12 = vectors[1][0] * vectors[2][0] + vectors[1][1] * vectors[2][1];
|
|
14481
14522
|
const dot23 = vectors[2][0] * vectors[3][0] + vectors[2][1] * vectors[3][1];
|
|
@@ -16414,7 +16455,9 @@ function lowerLoftShellToConcretePlan(plan, thickness, openFaces) {
|
|
|
16414
16455
|
profiles: innerProfiles,
|
|
16415
16456
|
heights: innerHeights,
|
|
16416
16457
|
edgeLength: plan.edgeLength,
|
|
16417
|
-
boundsPadding: plan.boundsPadding
|
|
16458
|
+
boundsPadding: plan.boundsPadding,
|
|
16459
|
+
...plan.forceField ? { forceField: true } : {},
|
|
16460
|
+
...plan.meshing ? { meshing: { ...plan.meshing } } : {}
|
|
16418
16461
|
};
|
|
16419
16462
|
return { ok: true, plan: buildBooleanShapeCompilePlan("difference", [plan, inner]) };
|
|
16420
16463
|
}
|
|
@@ -16552,6 +16595,197 @@ function lowerShellShapeCompilePlanToConcretePlan(plan) {
|
|
|
16552
16595
|
}
|
|
16553
16596
|
return lowerBaseShellPlanToConcretePlan(plan.base, plan.thickness, normalizeShellOpenFaces(plan.openFaces));
|
|
16554
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
|
+
}
|
|
16555
16789
|
const SHAPE_BACKEND_MARKER = Symbol.for("forgecad.shapeBackend");
|
|
16556
16790
|
function isShapeBackend(value) {
|
|
16557
16791
|
return Boolean(value && typeof value === "object" && value[SHAPE_BACKEND_MARKER] === true);
|
|
@@ -16605,60 +16839,26 @@ function recordEvent(event) {
|
|
|
16605
16839
|
runEvents.push(next);
|
|
16606
16840
|
if (runEvents.length > MAX_RECORDED_EVENTS) runEvents = runEvents.slice(-MAX_RECORDED_EVENTS);
|
|
16607
16841
|
}
|
|
16608
|
-
|
|
16609
|
-
|
|
16610
|
-
|
|
16611
|
-
|
|
16612
|
-
|
|
16613
|
-
|
|
16614
|
-
|
|
16615
|
-
|
|
16616
|
-
|
|
16617
|
-
|
|
16618
|
-
|
|
16619
|
-
|
|
16620
|
-
|
|
16621
|
-
|
|
16622
|
-
|
|
16623
|
-
|
|
16624
|
-
for (const [key, item] of entries) {
|
|
16625
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
16626
|
-
const encoded = stableGeometryEncode(item, false);
|
|
16627
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
16628
|
-
}
|
|
16629
|
-
return `{${encodedEntries.join(",")}}`;
|
|
16630
|
-
}
|
|
16631
|
-
function stableCacheOpportunityEncode(value, arrayMember) {
|
|
16632
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
16633
|
-
return arrayMember ? "null" : void 0;
|
|
16634
|
-
}
|
|
16635
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
16636
|
-
return JSON.stringify(value);
|
|
16637
|
-
}
|
|
16638
|
-
if (Array.isArray(value)) {
|
|
16639
|
-
return `[${value.map((item) => stableCacheOpportunityEncode(item, true) ?? "null").join(",")}]`;
|
|
16640
|
-
}
|
|
16641
|
-
const record = value;
|
|
16642
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
16643
|
-
return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
16644
|
-
}
|
|
16645
|
-
let encodedRecord = record;
|
|
16646
|
-
if (record.kind === "transform" && record.base) {
|
|
16647
|
-
const retainedSteps = Array.isArray(record.steps) ? record.steps.filter((step) => step.kind === "scale") : [];
|
|
16648
|
-
if (retainedSteps.length === 0) return stableCacheOpportunityEncode(record.base, arrayMember);
|
|
16649
|
-
encodedRecord = { kind: "transform", base: record.base, steps: retainedSteps };
|
|
16650
|
-
}
|
|
16651
|
-
const entries = Object.entries(encodedRecord).sort(([left], [right]) => left.localeCompare(right));
|
|
16652
|
-
const encodedEntries = [];
|
|
16653
|
-
for (const [key, item] of entries) {
|
|
16654
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
16655
|
-
const encoded = stableCacheOpportunityEncode(item, false);
|
|
16656
|
-
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) };
|
|
16657
16858
|
}
|
|
16658
|
-
|
|
16659
|
-
}
|
|
16859
|
+
});
|
|
16660
16860
|
function shapeGeometryCacheKey(plan) {
|
|
16661
|
-
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${
|
|
16861
|
+
return `${SHAPE_GEOMETRY_CACHE_KEY_VERSION}:${geometryPlanHasher(plan)}`;
|
|
16662
16862
|
}
|
|
16663
16863
|
function planComplexityScore(value) {
|
|
16664
16864
|
if (!value || typeof value !== "object") return 0;
|
|
@@ -16717,8 +16917,7 @@ function planComplexityScore(value) {
|
|
|
16717
16917
|
}
|
|
16718
16918
|
}
|
|
16719
16919
|
function estimateCacheRetainedMb(plan) {
|
|
16720
|
-
|
|
16721
|
-
const encodedLength = ((_a3 = stableCacheOpportunityEncode(plan, false)) == null ? void 0 : _a3.length) ?? 0;
|
|
16920
|
+
const encodedLength = estimatePlanEncodedLength(plan);
|
|
16722
16921
|
const serializedComplexityMb = encodedLength / 24e3;
|
|
16723
16922
|
return round2(0.08 + planComplexityScore(plan) * 0.09 + serializedComplexityMb);
|
|
16724
16923
|
}
|
|
@@ -16736,26 +16935,62 @@ function splitCacheablePlacement(plan) {
|
|
|
16736
16935
|
}
|
|
16737
16936
|
return { basePlan: plan, placementSteps: [] };
|
|
16738
16937
|
}
|
|
16938
|
+
const uncacheableReasonMemo = /* @__PURE__ */ new WeakMap();
|
|
16739
16939
|
function findUncacheableReason(value) {
|
|
16740
16940
|
if (value === void 0 || value === null) return null;
|
|
16741
16941
|
if (typeof value === "function" || typeof value === "symbol") return "plan contains runtime-only values";
|
|
16742
16942
|
if (typeof value !== "object") return null;
|
|
16743
|
-
|
|
16744
|
-
|
|
16745
|
-
|
|
16746
|
-
|
|
16747
|
-
|
|
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);
|
|
16748
16986
|
}
|
|
16749
|
-
return null;
|
|
16750
16987
|
}
|
|
16751
|
-
|
|
16752
|
-
|
|
16753
|
-
|
|
16754
|
-
|
|
16755
|
-
const reason = findUncacheableReason(item);
|
|
16756
|
-
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);
|
|
16757
16992
|
}
|
|
16758
|
-
return
|
|
16993
|
+
return reason;
|
|
16759
16994
|
}
|
|
16760
16995
|
function applyPlacementStep(backend, step) {
|
|
16761
16996
|
switch (step.kind) {
|
|
@@ -17432,8 +17667,9 @@ function analyzeNodeUV(node, toLocal) {
|
|
|
17432
17667
|
if (result.majorRadius !== void 0) result.majorRadius *= node.factor;
|
|
17433
17668
|
return result;
|
|
17434
17669
|
}
|
|
17435
|
-
// ── Shell — UV comes from the inner shape ──
|
|
17670
|
+
// ── Shell / offset — UV comes from the inner shape ──
|
|
17436
17671
|
case "sdf:shell":
|
|
17672
|
+
case "sdf:offset":
|
|
17437
17673
|
return analyzeNodeUV(node.child, toLocal);
|
|
17438
17674
|
// ── CSG — take UV from the first (primary) child ──
|
|
17439
17675
|
case "sdf:union":
|
|
@@ -17969,6 +18205,11 @@ function compileSdfNode3(node) {
|
|
|
17969
18205
|
const t = node.thickness * 0.5;
|
|
17970
18206
|
return (x2, y2, z2) => abs(fn(x2, y2, z2)) - t;
|
|
17971
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
|
+
}
|
|
17972
18213
|
case "sdf:displace": {
|
|
17973
18214
|
const fn = compileSdfNode3(node.child);
|
|
17974
18215
|
const constEntries = Object.entries(node.constants ?? {});
|
|
@@ -18462,6 +18703,10 @@ function emitSdfProgramNode(b, node, x2, y2, z2) {
|
|
|
18462
18703
|
const child = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
18463
18704
|
return b.sub(b.abs(child), b.constant(node.thickness * 0.5));
|
|
18464
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
|
+
}
|
|
18465
18710
|
case "sdf:onion": {
|
|
18466
18711
|
let d2 = emitSdfProgramNode(b, node.child, x2, y2, z2);
|
|
18467
18712
|
for (let i = 0; i < node.layers; i++) d2 = b.sub(b.abs(d2), b.constant(node.thickness));
|
|
@@ -18610,6 +18855,7 @@ function getUnsupportedSdfProgramReason(node) {
|
|
|
18610
18855
|
case "sdf:bend":
|
|
18611
18856
|
case "sdf:repeat":
|
|
18612
18857
|
case "sdf:shell":
|
|
18858
|
+
case "sdf:offset":
|
|
18613
18859
|
case "sdf:onion":
|
|
18614
18860
|
return getUnsupportedSdfProgramReason(node.child);
|
|
18615
18861
|
default:
|
|
@@ -18813,7 +19059,19 @@ function simplifyMesh(triVerts, vertProperties, targetRatio, maxError) {
|
|
|
18813
19059
|
if (!_simplifier) {
|
|
18814
19060
|
throw new Error("meshoptimizer not initialized — call initMeshoptimizer() first");
|
|
18815
19061
|
}
|
|
18816
|
-
|
|
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;
|
|
18817
19075
|
const [simplified] = _simplifier.simplify(
|
|
18818
19076
|
triVerts,
|
|
18819
19077
|
vertProperties,
|
|
@@ -20223,11 +20481,12 @@ function profileMayContainInteriorLoopsForOCCT(plan) {
|
|
|
20223
20481
|
return false;
|
|
20224
20482
|
}
|
|
20225
20483
|
}
|
|
20484
|
+
const occtLoweredCache = /* @__PURE__ */ new WeakMap();
|
|
20226
20485
|
function lowerShapeCompilePlanToOCCT(plan, oc) {
|
|
20227
|
-
const cached = plan
|
|
20486
|
+
const cached = occtLoweredCache.get(plan);
|
|
20228
20487
|
if (cached) return cached;
|
|
20229
20488
|
const shape = _lowerShapeCompilePlanToOCCTInner(plan, oc);
|
|
20230
|
-
plan
|
|
20489
|
+
occtLoweredCache.set(plan, shape);
|
|
20231
20490
|
return shape;
|
|
20232
20491
|
}
|
|
20233
20492
|
function _lowerShapeCompilePlanToOCCTInner(plan, oc) {
|
|
@@ -28765,16 +29024,16 @@ function surfaceNets(sdfFn, bounds, edgeLength2) {
|
|
|
28765
29024
|
numTris: faces.length / 3
|
|
28766
29025
|
};
|
|
28767
29026
|
}
|
|
28768
|
-
const EPS
|
|
29027
|
+
const EPS = 1e-9;
|
|
28769
29028
|
function finitePositive$1(value) {
|
|
28770
|
-
return Number.isFinite(value) && value > EPS
|
|
29029
|
+
return Number.isFinite(value) && value > EPS;
|
|
28771
29030
|
}
|
|
28772
29031
|
function clampNonNegative(value) {
|
|
28773
|
-
return Math.abs(value) <= EPS
|
|
29032
|
+
return Math.abs(value) <= EPS ? 0 : value;
|
|
28774
29033
|
}
|
|
28775
29034
|
function distancePreservingMatrixScale(matrix) {
|
|
28776
29035
|
if (matrix.length !== 16 || matrix.some((value) => !Number.isFinite(value))) return null;
|
|
28777
|
-
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) {
|
|
28778
29037
|
return null;
|
|
28779
29038
|
}
|
|
28780
29039
|
const col0 = [matrix[0], matrix[1], matrix[2]];
|
|
@@ -28786,8 +29045,8 @@ function distancePreservingMatrixScale(matrix) {
|
|
|
28786
29045
|
const sy = length4(col1);
|
|
28787
29046
|
const sz = length4(col2);
|
|
28788
29047
|
if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
|
|
28789
|
-
if (Math.abs(sx - sy) > EPS
|
|
28790
|
-
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;
|
|
28791
29050
|
return sx;
|
|
28792
29051
|
}
|
|
28793
29052
|
function transformStepDistanceScale(step) {
|
|
@@ -28803,7 +29062,7 @@ function transformStepDistanceScale(step) {
|
|
|
28803
29062
|
const sy = Math.abs(step.y);
|
|
28804
29063
|
const sz = Math.abs(step.z);
|
|
28805
29064
|
if (!finitePositive$1(sx) || !finitePositive$1(sy) || !finitePositive$1(sz)) return null;
|
|
28806
|
-
return Math.abs(sx - sy) <= EPS
|
|
29065
|
+
return Math.abs(sx - sy) <= EPS && Math.abs(sx - sz) <= EPS ? sx : null;
|
|
28807
29066
|
}
|
|
28808
29067
|
}
|
|
28809
29068
|
}
|
|
@@ -28834,7 +29093,7 @@ function cloneTransformStep(step) {
|
|
|
28834
29093
|
}
|
|
28835
29094
|
}
|
|
28836
29095
|
function translatedPlan(base, z2) {
|
|
28837
|
-
if (Math.abs(z2) <= EPS
|
|
29096
|
+
if (Math.abs(z2) <= EPS) return base;
|
|
28838
29097
|
return {
|
|
28839
29098
|
kind: "transform",
|
|
28840
29099
|
base,
|
|
@@ -28851,7 +29110,7 @@ function offsetCylinderDimensions(plan, thickness) {
|
|
|
28851
29110
|
const height = zMax - zMin;
|
|
28852
29111
|
const radiusBottom = plan.radius + thickness * (normalScale - slope);
|
|
28853
29112
|
const offsetRadiusTop = radiusTop + thickness * (normalScale + slope);
|
|
28854
|
-
if (!finitePositive$1(height) || radiusBottom < -EPS
|
|
29113
|
+
if (!finitePositive$1(height) || radiusBottom < -EPS || offsetRadiusTop < -EPS) return null;
|
|
28855
29114
|
return {
|
|
28856
29115
|
zMin,
|
|
28857
29116
|
height,
|
|
@@ -28875,7 +29134,7 @@ function transformProfilePoint(point, transform) {
|
|
|
28875
29134
|
const nx = transform.normalX;
|
|
28876
29135
|
const ny = transform.normalY;
|
|
28877
29136
|
const len = Math.hypot(nx, ny);
|
|
28878
|
-
if (len <= EPS
|
|
29137
|
+
if (len <= EPS) return point;
|
|
28879
29138
|
const ux = nx / len;
|
|
28880
29139
|
const uy = ny / len;
|
|
28881
29140
|
const d2 = point[0] * ux + point[1] * uy;
|
|
@@ -28889,7 +29148,7 @@ function transformProfilePointThrough(point, transforms) {
|
|
|
28889
29148
|
return out;
|
|
28890
29149
|
}
|
|
28891
29150
|
function sameScalar$2(a2, b) {
|
|
28892
|
-
return Math.abs(a2 - b) <= EPS
|
|
29151
|
+
return Math.abs(a2 - b) <= EPS;
|
|
28893
29152
|
}
|
|
28894
29153
|
function sameProfilePoint(a2, b) {
|
|
28895
29154
|
return sameScalar$2(a2[0], b[0]) && sameScalar$2(a2[1], b[1]);
|
|
@@ -28943,7 +29202,7 @@ function rectangleFootprintFromProfile(plan) {
|
|
|
28943
29202
|
const [xMin, xMax] = xs;
|
|
28944
29203
|
const [zMin, zMax] = zs;
|
|
28945
29204
|
if (xMin == null || xMax == null || zMin == null || zMax == null) return null;
|
|
28946
|
-
if (xMin < -EPS
|
|
29205
|
+
if (xMin < -EPS || !finitePositive$1(xMax) || !finitePositive$1(zMax - zMin)) return null;
|
|
28947
29206
|
const hasCorner = (x2, z2) => points.some(([px, pz]) => sameScalar$2(px, x2) && sameScalar$2(pz, z2));
|
|
28948
29207
|
if (!hasCorner(xMin, zMin) || !hasCorner(xMax, zMin) || !hasCorner(xMax, zMax) || !hasCorner(xMin, zMax)) return null;
|
|
28949
29208
|
return {
|
|
@@ -28966,7 +29225,7 @@ function circleFootprintFromProfile(plan) {
|
|
|
28966
29225
|
const yScale = Math.hypot(yAxis[0], yAxis[1]);
|
|
28967
29226
|
const dot2 = xAxis[0] * yAxis[0] + xAxis[1] * yAxis[1];
|
|
28968
29227
|
if (!finitePositive$1(xScale) || !finitePositive$1(yScale)) return null;
|
|
28969
|
-
if (Math.abs(xScale - yScale) > EPS
|
|
29228
|
+
if (Math.abs(xScale - yScale) > EPS || Math.abs(dot2) > EPS * xScale * yScale) return null;
|
|
28970
29229
|
return {
|
|
28971
29230
|
center,
|
|
28972
29231
|
radius: radius * xScale,
|
|
@@ -28974,11 +29233,11 @@ function circleFootprintFromProfile(plan) {
|
|
|
28974
29233
|
};
|
|
28975
29234
|
}
|
|
28976
29235
|
function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
|
|
28977
|
-
if (Math.abs(plan.degrees - 360) > EPS
|
|
29236
|
+
if (Math.abs(plan.degrees - 360) > EPS) return null;
|
|
28978
29237
|
const circle = circleFootprintFromProfile(plan.profile);
|
|
28979
|
-
if (!circle || circle.center[0] <= EPS
|
|
29238
|
+
if (!circle || circle.center[0] <= EPS) return null;
|
|
28980
29239
|
const minorRadius = circle.radius + minorRadiusOffset;
|
|
28981
|
-
if (!finitePositive$1(minorRadius) || minorRadius >= circle.center[0] - EPS
|
|
29240
|
+
if (!finitePositive$1(minorRadius) || minorRadius >= circle.center[0] - EPS) return null;
|
|
28982
29241
|
return translatedPlan(
|
|
28983
29242
|
{
|
|
28984
29243
|
kind: "torus",
|
|
@@ -28991,7 +29250,7 @@ function fullCircleRevolveTorusPlan(plan, minorRadiusOffset = 0) {
|
|
|
28991
29250
|
}
|
|
28992
29251
|
function fullAxisRectRevolveCylinderPlan(plan) {
|
|
28993
29252
|
const rectangle = fullRectRevolveSurfacePlan(plan);
|
|
28994
|
-
if (!rectangle || rectangle.innerRadius > EPS
|
|
29253
|
+
if (!rectangle || rectangle.innerRadius > EPS) return null;
|
|
28995
29254
|
return translatedPlan(
|
|
28996
29255
|
{
|
|
28997
29256
|
kind: "cylinder",
|
|
@@ -29014,20 +29273,20 @@ function rectRevolveSurfacePlan(plan) {
|
|
|
29014
29273
|
};
|
|
29015
29274
|
}
|
|
29016
29275
|
function fullRectRevolveSurfacePlan(plan) {
|
|
29017
|
-
if (Math.abs(plan.degrees - 360) > EPS
|
|
29276
|
+
if (Math.abs(plan.degrees - 360) > EPS) return null;
|
|
29018
29277
|
return rectRevolveSurfacePlan(plan);
|
|
29019
29278
|
}
|
|
29020
29279
|
function offsetFullRectRevolvePlan(plan, thickness) {
|
|
29021
29280
|
const cylinderPlan = fullAxisRectRevolveCylinderPlan(plan);
|
|
29022
29281
|
if (cylinderPlan) return offsetSolidAnalyticPrimitivePlan(cylinderPlan, thickness);
|
|
29023
29282
|
const rectangle = fullRectRevolveSurfacePlan(plan);
|
|
29024
|
-
if (!rectangle || rectangle.innerRadius <= EPS
|
|
29283
|
+
if (!rectangle || rectangle.innerRadius <= EPS) return null;
|
|
29025
29284
|
const innerRadius = rectangle.innerRadius - thickness;
|
|
29026
29285
|
const outerRadius = rectangle.outerRadius + thickness;
|
|
29027
29286
|
const height = rectangle.zMax - rectangle.zMin + 2 * thickness;
|
|
29028
|
-
if (innerRadius < -EPS
|
|
29287
|
+
if (innerRadius < -EPS || !finitePositive$1(outerRadius) || !finitePositive$1(height) || outerRadius <= innerRadius + EPS) return null;
|
|
29029
29288
|
const zCenter = (rectangle.zMin + rectangle.zMax) / 2;
|
|
29030
|
-
if (innerRadius <= EPS
|
|
29289
|
+
if (innerRadius <= EPS) {
|
|
29031
29290
|
return translatedPlan(
|
|
29032
29291
|
{
|
|
29033
29292
|
kind: "cylinder",
|
|
@@ -29106,7 +29365,7 @@ function offsetSolidAnalyticPrimitivePlan(base, thickness) {
|
|
|
29106
29365
|
}
|
|
29107
29366
|
case "torus": {
|
|
29108
29367
|
const minorRadius = base.minorRadius + thickness;
|
|
29109
|
-
if (!finitePositive$1(minorRadius) || minorRadius >= base.majorRadius - EPS
|
|
29368
|
+
if (!finitePositive$1(minorRadius) || minorRadius >= base.majorRadius - EPS) return null;
|
|
29110
29369
|
return {
|
|
29111
29370
|
kind: "torus",
|
|
29112
29371
|
majorRadius: base.majorRadius,
|
|
@@ -29710,7 +29969,7 @@ function sub$1(a2, b) {
|
|
|
29710
29969
|
function scale$1(v, scalar) {
|
|
29711
29970
|
return [v[0] * scalar, v[1] * scalar, v[2] * scalar];
|
|
29712
29971
|
}
|
|
29713
|
-
function cross$
|
|
29972
|
+
function cross$2(a2, b) {
|
|
29714
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]];
|
|
29715
29974
|
}
|
|
29716
29975
|
function isFiniteNumber(value) {
|
|
@@ -29946,8 +30205,8 @@ function boundsCorners(bounds) {
|
|
|
29946
30205
|
}
|
|
29947
30206
|
function perpendicularAxes(normal) {
|
|
29948
30207
|
const seed = Math.abs(normal[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0];
|
|
29949
|
-
const uAxis = normalizeVec3$1(cross$
|
|
29950
|
-
const vAxis = normalizeVec3$1(cross$
|
|
30208
|
+
const uAxis = normalizeVec3$1(cross$2(seed, normal));
|
|
30209
|
+
const vAxis = normalizeVec3$1(cross$2(normal, uAxis));
|
|
29951
30210
|
return { uAxis, vAxis };
|
|
29952
30211
|
}
|
|
29953
30212
|
function createBoundedHalfSpace(bounds, normal, originOffset) {
|
|
@@ -30015,7 +30274,7 @@ function faceAxes(face) {
|
|
|
30015
30274
|
for (const vertex of face.vertices.slice(1)) {
|
|
30016
30275
|
const uAxis = normalizeVec3$1([vertex[0] - origin[0], vertex[1] - origin[1], vertex[2] - origin[2]]);
|
|
30017
30276
|
if (Math.hypot(...uAxis) <= 1e-12) continue;
|
|
30018
|
-
const vAxis = normalizeVec3$1(cross$
|
|
30277
|
+
const vAxis = normalizeVec3$1(cross$2(face.normal, uAxis));
|
|
30019
30278
|
if (Math.hypot(...vAxis) <= 1e-12) continue;
|
|
30020
30279
|
return {
|
|
30021
30280
|
uAxis,
|
|
@@ -30024,18 +30283,18 @@ function faceAxes(face) {
|
|
|
30024
30283
|
}
|
|
30025
30284
|
return {};
|
|
30026
30285
|
}
|
|
30027
|
-
function edgeKey$
|
|
30286
|
+
function edgeKey$2(start, end) {
|
|
30028
30287
|
const encode = (p2) => p2.map((value) => value.toFixed(9)).join(",");
|
|
30029
30288
|
const a2 = encode(start);
|
|
30030
30289
|
const b = encode(end);
|
|
30031
30290
|
return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
|
|
30032
30291
|
}
|
|
30033
30292
|
function faceEdgeIndex(face, start, end) {
|
|
30034
|
-
const target = edgeKey$
|
|
30293
|
+
const target = edgeKey$2(start, end);
|
|
30035
30294
|
for (let i = 0; i < face.vertices.length; i++) {
|
|
30036
30295
|
const faceStart = face.vertices[i];
|
|
30037
30296
|
const faceEnd = face.vertices[(i + 1) % face.vertices.length];
|
|
30038
|
-
if (faceStart && faceEnd && edgeKey$
|
|
30297
|
+
if (faceStart && faceEnd && edgeKey$2(faceStart, faceEnd) === target) return i;
|
|
30039
30298
|
}
|
|
30040
30299
|
return null;
|
|
30041
30300
|
}
|
|
@@ -30236,7 +30495,7 @@ function topologyPayloadToTopology(payload) {
|
|
|
30236
30495
|
const start = explicitVertices[explicitEdge.vertices[0]];
|
|
30237
30496
|
const end = explicitVertices[explicitEdge.vertices[1]];
|
|
30238
30497
|
if (!isVec3(start) || !isVec3(end)) continue;
|
|
30239
|
-
const key = edgeKey$
|
|
30498
|
+
const key = edgeKey$2(start, end);
|
|
30240
30499
|
if (seenEdges.has(key)) continue;
|
|
30241
30500
|
seenEdges.set(key, edges.size);
|
|
30242
30501
|
const display = explicitEdgeDisplayName(payload, explicitEdge, explicitEdgeIndex, start, end);
|
|
@@ -30256,7 +30515,7 @@ function topologyPayloadToTopology(payload) {
|
|
|
30256
30515
|
const start = face.vertices[i];
|
|
30257
30516
|
const end = face.vertices[(i + 1) % face.vertices.length];
|
|
30258
30517
|
if (!start || !end) continue;
|
|
30259
|
-
const key = edgeKey$
|
|
30518
|
+
const key = edgeKey$2(start, end);
|
|
30260
30519
|
if (seenEdges.has(key)) continue;
|
|
30261
30520
|
seenEdges.set(key, edges.size);
|
|
30262
30521
|
edges.set(`${face.id}:edge-${i}`, {
|
|
@@ -32576,7 +32835,7 @@ function isStraightMonotonePolyline(points) {
|
|
|
32576
32835
|
for (const point of points) {
|
|
32577
32836
|
const offset = subtract3$1(point, start);
|
|
32578
32837
|
const projection = dot3$3(offset, axis) / axisLengthSq;
|
|
32579
|
-
const lineDistance = vectorLength3$1(cross3$
|
|
32838
|
+
const lineDistance = vectorLength3$1(cross3$5(offset, axis)) / axisLength;
|
|
32580
32839
|
if (lineDistance > 1e-5 || projection < -1e-6 || projection > 1 + 1e-6 || projection + 1e-6 < previousProjection) {
|
|
32581
32840
|
return false;
|
|
32582
32841
|
}
|
|
@@ -32652,7 +32911,7 @@ function isDistancePreservingMatrix$1(matrix) {
|
|
|
32652
32911
|
const col0 = [matrix[0], matrix[1], matrix[2]];
|
|
32653
32912
|
const col1 = [matrix[4], matrix[5], matrix[6]];
|
|
32654
32913
|
const col2 = [matrix[8], matrix[9], matrix[10]];
|
|
32655
|
-
const det = dot3$3(col0, cross3$
|
|
32914
|
+
const det = dot3$3(col0, cross3$5(col1, col2));
|
|
32656
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;
|
|
32657
32916
|
}
|
|
32658
32917
|
function offsetSolidTransformDistanceScale(step) {
|
|
@@ -32897,41 +33156,41 @@ function surfaceGridForFillPlan(plan) {
|
|
|
32897
33156
|
}
|
|
32898
33157
|
return grid;
|
|
32899
33158
|
}
|
|
32900
|
-
function add3(a2, b) {
|
|
33159
|
+
function add3$1(a2, b) {
|
|
32901
33160
|
return [a2[0] + b[0], a2[1] + b[1], a2[2] + b[2]];
|
|
32902
33161
|
}
|
|
32903
|
-
function scale3(v, s) {
|
|
33162
|
+
function scale3$1(v, s) {
|
|
32904
33163
|
return [v[0] * s, v[1] * s, v[2] * s];
|
|
32905
33164
|
}
|
|
32906
33165
|
function analyticYForFrame(axis, xAxis) {
|
|
32907
|
-
return normalizedVector3(cross3$
|
|
33166
|
+
return normalizedVector3(cross3$5(axis, xAxis), "analytic surface yAxis");
|
|
32908
33167
|
}
|
|
32909
33168
|
function radialPoint(xAxis, yAxis, u2, radius) {
|
|
32910
|
-
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));
|
|
32911
33170
|
}
|
|
32912
33171
|
function analyticSurfacePoint(plan, u2, v) {
|
|
32913
33172
|
switch (plan.kind) {
|
|
32914
33173
|
case "plane":
|
|
32915
|
-
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));
|
|
32916
33175
|
case "cylinder": {
|
|
32917
33176
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32918
|
-
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));
|
|
32919
33178
|
}
|
|
32920
33179
|
case "cone": {
|
|
32921
33180
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32922
33181
|
const t = (v - plan.vMin) / (plan.vMax - plan.vMin);
|
|
32923
33182
|
const radius = plan.radiusBottom + (plan.radiusTop - plan.radiusBottom) * t;
|
|
32924
|
-
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));
|
|
32925
33184
|
}
|
|
32926
33185
|
case "sphere": {
|
|
32927
33186
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32928
33187
|
const radial = radialPoint(plan.xAxis, yAxis, u2, plan.radius * Math.cos(v));
|
|
32929
|
-
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)));
|
|
32930
33189
|
}
|
|
32931
33190
|
case "torus": {
|
|
32932
33191
|
const yAxis = analyticYForFrame(plan.axis, plan.xAxis);
|
|
32933
33192
|
const radial = radialPoint(plan.xAxis, yAxis, u2, plan.majorRadius + plan.minorRadius * Math.cos(v));
|
|
32934
|
-
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)));
|
|
32935
33194
|
}
|
|
32936
33195
|
default:
|
|
32937
33196
|
return assertExhaustive(plan);
|
|
@@ -33049,7 +33308,7 @@ function triangleNormal(vertices, triangle) {
|
|
|
33049
33308
|
const c2 = vertices[triangle[2]];
|
|
33050
33309
|
const ab = [b[0] - a2[0], b[1] - a2[1], b[2] - a2[2]];
|
|
33051
33310
|
const ac = [c2[0] - a2[0], c2[1] - a2[1], c2[2] - a2[2]];
|
|
33052
|
-
const normal = cross3$
|
|
33311
|
+
const normal = cross3$5(ab, ac);
|
|
33053
33312
|
const len = Math.hypot(normal[0], normal[1], normal[2]);
|
|
33054
33313
|
if (len <= 1e-12) return null;
|
|
33055
33314
|
return [normal[0] / len, normal[1] / len, normal[2] / len];
|
|
@@ -33195,7 +33454,7 @@ function lowerSurfaceSolidPlan(plan) {
|
|
|
33195
33454
|
);
|
|
33196
33455
|
}
|
|
33197
33456
|
}
|
|
33198
|
-
function cross3$
|
|
33457
|
+
function cross3$5(a2, b) {
|
|
33199
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]];
|
|
33200
33459
|
}
|
|
33201
33460
|
function fromSlicesPlaneFrame(normalInput) {
|
|
@@ -33211,8 +33470,8 @@ function fromSlicesPlaneFrame(normalInput) {
|
|
|
33211
33470
|
return { u: [0, 1, 0], v: [0, 0, nx > 0 ? 1 : -1], normal };
|
|
33212
33471
|
}
|
|
33213
33472
|
const reference = Math.abs(nx) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
33214
|
-
const u2 = normalizedVector3(cross3$
|
|
33215
|
-
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 };
|
|
33216
33475
|
}
|
|
33217
33476
|
function fromSlicesLocalToWorldMatrix(normal) {
|
|
33218
33477
|
const frame = fromSlicesPlaneFrame(normal);
|
|
@@ -36631,29 +36890,11 @@ function lowerExactSlicedShapeCompilePlanToTruckProfileBackend(plan, offset) {
|
|
|
36631
36890
|
return profilePlan ? lowerProfileCompilePlanToTruckProfileBackend(profilePlan) : null;
|
|
36632
36891
|
}
|
|
36633
36892
|
const SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION = "shape-plan-v1";
|
|
36634
|
-
|
|
36635
|
-
|
|
36636
|
-
|
|
36637
|
-
}
|
|
36638
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
36639
|
-
return JSON.stringify(value);
|
|
36640
|
-
}
|
|
36641
|
-
if (Array.isArray(value)) {
|
|
36642
|
-
return `[${value.map((item) => stableJsonEncode(item, true) ?? "null").join(",")}]`;
|
|
36643
|
-
}
|
|
36644
|
-
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
36645
|
-
const encodedEntries = [];
|
|
36646
|
-
for (const [key, item] of entries) {
|
|
36647
|
-
const encoded = stableJsonEncode(item, false);
|
|
36648
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
36649
|
-
}
|
|
36650
|
-
return `{${encodedEntries.join(",")}}`;
|
|
36651
|
-
}
|
|
36652
|
-
function stableJsonStringify(value) {
|
|
36653
|
-
return stableJsonEncode(value, false) ?? "null";
|
|
36654
|
-
}
|
|
36893
|
+
const exactPlanHasher = createStructuralHasher({
|
|
36894
|
+
skipKey: (key) => key.startsWith("_") || key === "owner" || key === "queryPropagation"
|
|
36895
|
+
});
|
|
36655
36896
|
function shapeCompilePlanCacheKey(plan) {
|
|
36656
|
-
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${
|
|
36897
|
+
return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${exactPlanHasher(plan)}`;
|
|
36657
36898
|
}
|
|
36658
36899
|
function formatFaceQuery(query) {
|
|
36659
36900
|
const parts = [];
|
|
@@ -36983,7 +37224,7 @@ function normalizeFaceSelector(selector) {
|
|
|
36983
37224
|
}
|
|
36984
37225
|
return { compilePlanName: null, query: selector };
|
|
36985
37226
|
}
|
|
36986
|
-
function cross$
|
|
37227
|
+
function cross$1(a2, b) {
|
|
36987
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]];
|
|
36988
37229
|
}
|
|
36989
37230
|
function dot$1(a2, b) {
|
|
@@ -36996,8 +37237,8 @@ function normVec3(v) {
|
|
|
36996
37237
|
}
|
|
36997
37238
|
function tangentFrame(normal) {
|
|
36998
37239
|
const ref = Math.abs(normal[0]) < 0.9 ? [1, 0, 0] : [0, 1, 0];
|
|
36999
|
-
const v = normVec3(cross$
|
|
37000
|
-
const u2 = normVec3(cross$
|
|
37240
|
+
const v = normVec3(cross$1(normal, ref));
|
|
37241
|
+
const u2 = normVec3(cross$1(v, normal));
|
|
37001
37242
|
return { u: u2, v };
|
|
37002
37243
|
}
|
|
37003
37244
|
const NORMAL_COS_EPS$1 = 0.9998;
|
|
@@ -37016,7 +37257,7 @@ function clusterMeshFaces(shape) {
|
|
|
37016
37257
|
const v2 = [vertProperties[i2 * numProp], vertProperties[i2 * numProp + 1], vertProperties[i2 * numProp + 2]];
|
|
37017
37258
|
const e1 = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
|
|
37018
37259
|
const e2 = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
|
|
37019
|
-
const rawCross = cross$
|
|
37260
|
+
const rawCross = cross$1(e1, e2);
|
|
37020
37261
|
const normal = normVec3(rawCross);
|
|
37021
37262
|
if (!normal) continue;
|
|
37022
37263
|
const crossLen = Math.sqrt(rawCross[0] * rawCross[0] + rawCross[1] * rawCross[1] + rawCross[2] * rawCross[2]);
|
|
@@ -37426,14 +37667,14 @@ function normalize2d(vec2) {
|
|
|
37426
37667
|
if (len < 1e-12) return [1, 0];
|
|
37427
37668
|
return [vec2[0] / len, vec2[1] / len];
|
|
37428
37669
|
}
|
|
37429
|
-
function cross3$
|
|
37670
|
+
function cross3$4(a2, b) {
|
|
37430
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]];
|
|
37431
37672
|
}
|
|
37432
37673
|
function orthonormalBasisFromNormal(normal) {
|
|
37433
37674
|
const n = normalizeAxis(normal);
|
|
37434
37675
|
const seed = Math.abs(n[2]) < 0.9 ? [0, 0, 1] : [0, 1, 0];
|
|
37435
|
-
const u2 = normalizeAxis(cross3$
|
|
37436
|
-
const v = normalizeAxis(cross3$
|
|
37676
|
+
const u2 = normalizeAxis(cross3$4(seed, n));
|
|
37677
|
+
const v = normalizeAxis(cross3$4(n, u2));
|
|
37437
37678
|
return { u: u2, v };
|
|
37438
37679
|
}
|
|
37439
37680
|
function faceFrom2DEdge(name, start, end, zMid, ownerQuery) {
|
|
@@ -38270,7 +38511,7 @@ function resolveShapeFaceTableInternal(plan, owner) {
|
|
|
38270
38511
|
forwardStart3[1] - reverseStart3[1],
|
|
38271
38512
|
forwardStart3[2] - reverseStart3[2]
|
|
38272
38513
|
]);
|
|
38273
|
-
const normal = normalizeAxis(cross3$
|
|
38514
|
+
const normal = normalizeAxis(cross3$4(edgeVec, depthVec));
|
|
38274
38515
|
registerFace(table, {
|
|
38275
38516
|
name: wall.name,
|
|
38276
38517
|
normal,
|
|
@@ -38756,14 +38997,14 @@ function normalize3$1(v) {
|
|
|
38756
38997
|
function dot3$2(a2, b) {
|
|
38757
38998
|
return a2[0] * b[0] + a2[1] * b[1] + a2[2] * b[2];
|
|
38758
38999
|
}
|
|
38759
|
-
function cross3$
|
|
39000
|
+
function cross3$3(a2, b) {
|
|
38760
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]];
|
|
38761
39002
|
}
|
|
38762
39003
|
function perpendicularTo(axis) {
|
|
38763
39004
|
const absX = Math.abs(axis[0]);
|
|
38764
39005
|
const absZ = Math.abs(axis[2]);
|
|
38765
39006
|
const seed = absX < absZ ? [1, 0, 0] : [0, 0, 1];
|
|
38766
|
-
return normalize3$1(cross3$
|
|
39007
|
+
return normalize3$1(cross3$3(axis, seed));
|
|
38767
39008
|
}
|
|
38768
39009
|
function normalizePortInput(input) {
|
|
38769
39010
|
let origin;
|
|
@@ -38944,18 +39185,18 @@ function normalize3(v) {
|
|
|
38944
39185
|
if (l < 1e-10) throw new Error("Cannot normalize zero-length vector");
|
|
38945
39186
|
return [v[0] / l, v[1] / l, v[2] / l];
|
|
38946
39187
|
}
|
|
38947
|
-
function cross3$
|
|
39188
|
+
function cross3$2(a2, b) {
|
|
38948
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]];
|
|
38949
39190
|
}
|
|
38950
|
-
function sub3(a2, b) {
|
|
39191
|
+
function sub3$1(a2, b) {
|
|
38951
39192
|
return [a2[0] - b[0], a2[1] - b[1], a2[2] - b[2]];
|
|
38952
39193
|
}
|
|
38953
39194
|
function negate3(v) {
|
|
38954
39195
|
return [-v[0], -v[1], -v[2]];
|
|
38955
39196
|
}
|
|
38956
39197
|
function alignmentMatrix(childOrigin, childAxis, childUp, parentOrigin, parentAxis, parentUp) {
|
|
38957
|
-
const cRight = normalize3(cross3$
|
|
38958
|
-
const pRight = normalize3(cross3$
|
|
39198
|
+
const cRight = normalize3(cross3$2(childAxis, childUp));
|
|
39199
|
+
const pRight = normalize3(cross3$2(parentAxis, parentUp));
|
|
38959
39200
|
const r00 = pRight[0] * cRight[0] + parentUp[0] * childUp[0] + parentAxis[0] * childAxis[0];
|
|
38960
39201
|
const r01 = pRight[0] * cRight[1] + parentUp[0] * childUp[1] + parentAxis[0] * childAxis[1];
|
|
38961
39202
|
const r02 = pRight[0] * cRight[2] + parentUp[0] * childUp[2] + parentAxis[0] * childAxis[2];
|
|
@@ -38970,7 +39211,7 @@ function alignmentMatrix(childOrigin, childAxis, childUp, parentOrigin, parentAx
|
|
|
38970
39211
|
r10 * childOrigin[0] + r11 * childOrigin[1] + r12 * childOrigin[2],
|
|
38971
39212
|
r20 * childOrigin[0] + r21 * childOrigin[1] + r22 * childOrigin[2]
|
|
38972
39213
|
];
|
|
38973
|
-
const t = sub3(parentOrigin, rc);
|
|
39214
|
+
const t = sub3$1(parentOrigin, rc);
|
|
38974
39215
|
return Transform.from([r00, r10, r20, 0, r01, r11, r21, 0, r02, r12, r22, 0, t[0], t[1], t[2], 1]);
|
|
38975
39216
|
}
|
|
38976
39217
|
function computeSinglePairAlignment(childPort, targetPort) {
|
|
@@ -39009,8 +39250,8 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
|
|
|
39009
39250
|
[0, 0, 0]
|
|
39010
39251
|
];
|
|
39011
39252
|
for (const p2 of pairs) {
|
|
39012
|
-
const s = sub3(p2.childOrigin, srcCentroid);
|
|
39013
|
-
const t2 = sub3(p2.targetOrigin, tgtCentroid);
|
|
39253
|
+
const s = sub3$1(p2.childOrigin, srcCentroid);
|
|
39254
|
+
const t2 = sub3$1(p2.targetOrigin, tgtCentroid);
|
|
39014
39255
|
for (let i = 0; i < 3; i++) {
|
|
39015
39256
|
for (let j = 0; j < 3; j++) {
|
|
39016
39257
|
h[i][j] += s[i] * t2[j];
|
|
@@ -39023,7 +39264,7 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
|
|
|
39023
39264
|
R[1][0] * srcCentroid[0] + R[1][1] * srcCentroid[1] + R[1][2] * srcCentroid[2],
|
|
39024
39265
|
R[2][0] * srcCentroid[0] + R[2][1] * srcCentroid[1] + R[2][2] * srcCentroid[2]
|
|
39025
39266
|
];
|
|
39026
|
-
const t = sub3(tgtCentroid, rSrc);
|
|
39267
|
+
const t = sub3$1(tgtCentroid, rSrc);
|
|
39027
39268
|
const transform = Transform.from([
|
|
39028
39269
|
R[0][0],
|
|
39029
39270
|
R[1][0],
|
|
@@ -39045,7 +39286,7 @@ function computeMultiPairAlignment(pairs, childPorts, targetPorts, tolerance = 0
|
|
|
39045
39286
|
const residuals = [];
|
|
39046
39287
|
for (const p2 of pairs) {
|
|
39047
39288
|
const transformed = transform.point(p2.childOrigin);
|
|
39048
|
-
const diff = sub3(transformed, p2.targetOrigin);
|
|
39289
|
+
const diff = sub3$1(transformed, p2.targetOrigin);
|
|
39049
39290
|
residuals.push(len3(diff));
|
|
39050
39291
|
}
|
|
39051
39292
|
const maxResidual = Math.max(...residuals);
|
|
@@ -39246,7 +39487,7 @@ function getConnectorDistance(ports, nameA, nameB) {
|
|
|
39246
39487
|
const b = ports[nameB];
|
|
39247
39488
|
if (!a2) throw new Error(`connectorDistance: unknown connector "${nameA}"`);
|
|
39248
39489
|
if (!b) throw new Error(`connectorDistance: unknown connector "${nameB}"`);
|
|
39249
|
-
const d2 = sub3(a2.origin, b.origin);
|
|
39490
|
+
const d2 = sub3$1(a2.origin, b.origin);
|
|
39250
39491
|
return len3(d2);
|
|
39251
39492
|
}
|
|
39252
39493
|
function getConnectorMeasurements(ports, name) {
|
|
@@ -39359,7 +39600,9 @@ const _ManifoldShapeBackend = class _ManifoldShapeBackend {
|
|
|
39359
39600
|
return this.getLiveManifold("numTri()").numTri();
|
|
39360
39601
|
}
|
|
39361
39602
|
getMesh() {
|
|
39362
|
-
|
|
39603
|
+
const manifold = this.getLiveManifold("getMesh()");
|
|
39604
|
+
const mesh = manifold.numProp() >= 3 ? manifold.getMesh(0) : manifold.getMesh();
|
|
39605
|
+
return mesh;
|
|
39363
39606
|
}
|
|
39364
39607
|
slice(offset) {
|
|
39365
39608
|
return this.getLiveManifold("slice()").slice(offset);
|
|
@@ -39660,7 +39903,7 @@ class ShapeGroup {
|
|
|
39660
39903
|
};
|
|
39661
39904
|
return this.attachTo(parent, face, opp[face], uvMap[face](u2, v, p2));
|
|
39662
39905
|
}
|
|
39663
|
-
/** 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. */
|
|
39664
39907
|
rotate(axis, angleDeg, options) {
|
|
39665
39908
|
requireRotateAxis(axis, "ShapeGroup.rotate()");
|
|
39666
39909
|
requireFiniteAngle(angleDeg, "ShapeGroup.rotate()");
|
|
@@ -39905,6 +40148,11 @@ class ShapeGroup {
|
|
|
39905
40148
|
* Position this group by matching connectors to a target.
|
|
39906
40149
|
* Connector names support dotted paths into named children: "ChildName.connectorName".
|
|
39907
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
|
+
*
|
|
39908
40156
|
* Overloads:
|
|
39909
40157
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
39910
40158
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -41644,7 +41892,7 @@ async function initKernelManifoldOnly() {
|
|
|
41644
41892
|
_activeBackend = "manifold";
|
|
41645
41893
|
return manifoldModule;
|
|
41646
41894
|
}
|
|
41647
|
-
const DEFAULT_ACTIVE_BACKEND = "
|
|
41895
|
+
const DEFAULT_ACTIVE_BACKEND = "manifold";
|
|
41648
41896
|
let _activeBackend = DEFAULT_ACTIVE_BACKEND;
|
|
41649
41897
|
let _runtimeWarn = (msg) => console.warn(msg);
|
|
41650
41898
|
function unwrapShapeLike(value) {
|
|
@@ -41663,6 +41911,7 @@ const _shapePlacementRefs = /* @__PURE__ */ new WeakMap();
|
|
|
41663
41911
|
const _shapeExplodeHint = /* @__PURE__ */ new WeakMap();
|
|
41664
41912
|
const _shapeRuntimeBackends = /* @__PURE__ */ new WeakMap();
|
|
41665
41913
|
const _shapeTopology = /* @__PURE__ */ new WeakMap();
|
|
41914
|
+
const _shapeLineageTokens = /* @__PURE__ */ new WeakMap();
|
|
41666
41915
|
const _shapeFaceLabels = /* @__PURE__ */ new WeakMap();
|
|
41667
41916
|
const _shapeReferenceNames = /* @__PURE__ */ new WeakMap();
|
|
41668
41917
|
const _shapeReferenceAliases = /* @__PURE__ */ new WeakMap();
|
|
@@ -41723,6 +41972,10 @@ function copyShapeReferenceMetadata(source, target) {
|
|
|
41723
41972
|
const aliases = cloneReferenceAliases(_shapeReferenceAliases.get(source));
|
|
41724
41973
|
if (aliases && aliases.size > 0) _shapeReferenceAliases.set(target, aliases);
|
|
41725
41974
|
}
|
|
41975
|
+
function copyShapeLineage(source, target) {
|
|
41976
|
+
const token = _shapeLineageTokens.get(source);
|
|
41977
|
+
if (token) _shapeLineageTokens.set(target, token);
|
|
41978
|
+
}
|
|
41726
41979
|
function assertNonEmptyReferenceName(name, apiName) {
|
|
41727
41980
|
const trimmed = name.trim();
|
|
41728
41981
|
if (!trimmed) throw new Error(`${apiName} requires a non-empty reference name.`);
|
|
@@ -41791,50 +42044,25 @@ function setShapeRuntimeBackendInternal(shape, payload) {
|
|
|
41791
42044
|
return shape;
|
|
41792
42045
|
}
|
|
41793
42046
|
function setShapeCompilePlanInternal(shape, plan) {
|
|
41794
|
-
_shapeCompilePlans.set(shape,
|
|
42047
|
+
_shapeCompilePlans.set(shape, deepFreezePlanData(plan));
|
|
41795
42048
|
recordShapeSourceSpanInternal(shape, plan);
|
|
41796
42049
|
return shape;
|
|
41797
42050
|
}
|
|
41798
|
-
function cloneShapeSourceSpanRecords(records) {
|
|
41799
|
-
return (records ?? []).map((record) => ({
|
|
41800
|
-
planCacheKey: record.planCacheKey,
|
|
41801
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41802
|
-
}));
|
|
41803
|
-
}
|
|
41804
42051
|
function upsertShapeSourceSpanRecord(shape, record) {
|
|
41805
|
-
|
|
41806
|
-
if (records
|
|
41807
|
-
|
|
41808
|
-
|
|
41809
|
-
sourceSpan: { ...record.sourceSpan }
|
|
41810
|
-
});
|
|
41811
|
-
_shapeSourceSpans.set(shape, records);
|
|
41812
|
-
}
|
|
41813
|
-
function stableTraceSourcePlanEncode(value, arrayMember) {
|
|
41814
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
|
|
41815
|
-
return arrayMember ? "null" : void 0;
|
|
41816
|
-
}
|
|
41817
|
-
if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
|
|
41818
|
-
return JSON.stringify(value);
|
|
42052
|
+
let records = _shapeSourceSpans.get(shape);
|
|
42053
|
+
if (!records) {
|
|
42054
|
+
records = /* @__PURE__ */ new Map();
|
|
42055
|
+
_shapeSourceSpans.set(shape, records);
|
|
41819
42056
|
}
|
|
41820
|
-
if (
|
|
41821
|
-
|
|
41822
|
-
}
|
|
41823
|
-
const record = value;
|
|
41824
|
-
if (record.kind === "queryOwner" && record.base) {
|
|
41825
|
-
return stableTraceSourcePlanEncode(record.base, arrayMember);
|
|
41826
|
-
}
|
|
41827
|
-
const entries = Object.entries(record).sort(([left], [right]) => left.localeCompare(right));
|
|
41828
|
-
const encodedEntries = [];
|
|
41829
|
-
for (const [key, item] of entries) {
|
|
41830
|
-
if (key.startsWith("_") || key === "owner" || key === "queryPropagation") continue;
|
|
41831
|
-
const encoded = stableTraceSourcePlanEncode(item, false);
|
|
41832
|
-
if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
|
|
41833
|
-
}
|
|
41834
|
-
return `{${encodedEntries.join(",")}}`;
|
|
42057
|
+
if (records.has(record.planCacheKey)) return;
|
|
42058
|
+
records.set(record.planCacheKey, Object.freeze({ ...record.sourceSpan }));
|
|
41835
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
|
+
});
|
|
41836
42064
|
function normalizedTraceSourcePlanCacheKey(plan) {
|
|
41837
|
-
return `shape-plan-v1:${
|
|
42065
|
+
return `shape-plan-v1:${traceSourcePlanHasher(plan)}`;
|
|
41838
42066
|
}
|
|
41839
42067
|
function recordShapeSourceSpanInternal(shape, plan) {
|
|
41840
42068
|
if (!hasActiveRuntimeSourceResolver()) return;
|
|
@@ -41854,13 +42082,20 @@ function recordShapeSourceSpanInternal(shape, plan) {
|
|
|
41854
42082
|
}
|
|
41855
42083
|
}
|
|
41856
42084
|
function copyShapeSourceSpans(source, target) {
|
|
41857
|
-
const records =
|
|
41858
|
-
if (records.
|
|
42085
|
+
const records = _shapeSourceSpans.get(source);
|
|
42086
|
+
if (records && records.size > 0) _shapeSourceSpans.set(target, new Map(records));
|
|
41859
42087
|
}
|
|
41860
42088
|
function mergeShapeSourceSpans(sources, target) {
|
|
42089
|
+
let records = _shapeSourceSpans.get(target);
|
|
41861
42090
|
for (const source of sources) {
|
|
41862
|
-
|
|
41863
|
-
|
|
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);
|
|
41864
42099
|
}
|
|
41865
42100
|
}
|
|
41866
42101
|
}
|
|
@@ -41895,7 +42130,7 @@ function getShapeRuntimeBackendInternal(shape) {
|
|
|
41895
42130
|
function getShapeCompilePlanInternal(shape) {
|
|
41896
42131
|
const stored = _shapeCompilePlans.get(shape);
|
|
41897
42132
|
if (!stored) throw new Error("Shape has no compile plan — every Shape must have an explicit plan set via setShapeCompilePlanInternal()");
|
|
41898
|
-
return
|
|
42133
|
+
return stored;
|
|
41899
42134
|
}
|
|
41900
42135
|
function getShapePlacementRefsInternal(shape) {
|
|
41901
42136
|
return clonePlacementReferences(_shapePlacementRefs.get(shape) ?? createPlacementReferences());
|
|
@@ -42587,6 +42822,30 @@ function checkLabelCollisions(operation2, plans) {
|
|
|
42587
42822
|
seen.push(...labels);
|
|
42588
42823
|
}
|
|
42589
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
|
+
}
|
|
42590
42849
|
function withCopiedDimensions(source, out) {
|
|
42591
42850
|
setShapeDimensionsInternal(out, cloneDimensions(getShapeDimensionsInternal(source), true));
|
|
42592
42851
|
setShapeGeometryInfoInternal(out, getShapeGeometryInfoInternal(source));
|
|
@@ -42601,6 +42860,7 @@ function withCopiedDimensions(source, out) {
|
|
|
42601
42860
|
const sourceLabels = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42602
42861
|
if (sourceLabels) _shapeFaceLabels.set(out, sourceLabels);
|
|
42603
42862
|
copyShapeReferenceMetadata(source, out);
|
|
42863
|
+
copyShapeLineage(source, out);
|
|
42604
42864
|
copyShapeSourceSpans(source, out);
|
|
42605
42865
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42606
42866
|
}
|
|
@@ -42630,6 +42890,7 @@ function withTransformedDimensions(source, out, m2) {
|
|
|
42630
42890
|
const sourceLabelsT = cloneFaceLabelMap(_shapeFaceLabels.get(source));
|
|
42631
42891
|
if (sourceLabelsT) _shapeFaceLabels.set(out, sourceLabelsT);
|
|
42632
42892
|
copyShapeReferenceMetadata(source, out);
|
|
42893
|
+
copyShapeLineage(source, out);
|
|
42633
42894
|
copyShapeSourceSpans(source, out);
|
|
42634
42895
|
return setShapeCompilePlanInternal(out, getShapeCompilePlanInternal(source));
|
|
42635
42896
|
}
|
|
@@ -43057,6 +43318,7 @@ class Shape {
|
|
|
43057
43318
|
this.colorHex = color;
|
|
43058
43319
|
setShapeRuntimeBackendInternal(this, payload);
|
|
43059
43320
|
setShapeGeometryInfoInternal(this, createGeometryInfo(geometryInfo));
|
|
43321
|
+
_shapeLineageTokens.set(this, {});
|
|
43060
43322
|
}
|
|
43061
43323
|
/** @internal Use .color() instead. */
|
|
43062
43324
|
setColor(value) {
|
|
@@ -43175,6 +43437,12 @@ class Shape {
|
|
|
43175
43437
|
* with `union()` / `difference()` to avoid collisions. Collision detection throws a
|
|
43176
43438
|
* clear error with a fix suggestion.
|
|
43177
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
|
+
*
|
|
43178
43446
|
* For compile-covered shapes (extrude, loft, etc.) the lookup resolves via the shape's
|
|
43179
43447
|
* compile plan. As a fallback, planar-faced mesh shapes (e.g. results of boolean ops)
|
|
43180
43448
|
* are resolved via coplanar triangle clustering.
|
|
@@ -43591,7 +43859,7 @@ class Shape {
|
|
|
43591
43859
|
const tbb = s.boundingBox();
|
|
43592
43860
|
return this.moveTo(tbb.min[0] + localX, tbb.min[1] + localY, tbb.min[2] + localZ);
|
|
43593
43861
|
}
|
|
43594
|
-
/** 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. */
|
|
43595
43863
|
rotate(axis, angleDeg, options) {
|
|
43596
43864
|
validateRotateAxis(axis, "Shape.rotate()");
|
|
43597
43865
|
validateRotateAngle(angleDeg, "Shape.rotate()");
|
|
@@ -43778,7 +44046,7 @@ class Shape {
|
|
|
43778
44046
|
* Warn if a boolean operation had no geometric effect.
|
|
43779
44047
|
* Compares volumes before and after; if they match within tolerance, the operation was a no-op.
|
|
43780
44048
|
*/
|
|
43781
|
-
static _checkBooleanNoOp(op, base, result) {
|
|
44049
|
+
static _checkBooleanNoOp(op, base, result, tools = []) {
|
|
43782
44050
|
try {
|
|
43783
44051
|
if (op === "intersection") {
|
|
43784
44052
|
if (result.isEmpty()) {
|
|
@@ -43791,8 +44059,15 @@ class Shape {
|
|
|
43791
44059
|
const volAfter = result.volume();
|
|
43792
44060
|
const tol = Math.max(volBefore * 1e-4, 1e-3);
|
|
43793
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(" ");
|
|
43794
44067
|
_runtimeWarn(
|
|
43795
|
-
`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
|
|
43796
44071
|
);
|
|
43797
44072
|
}
|
|
43798
44073
|
}
|
|
@@ -43852,7 +44127,7 @@ class Shape {
|
|
|
43852
44127
|
),
|
|
43853
44128
|
nextPlan
|
|
43854
44129
|
);
|
|
43855
|
-
Shape._checkBooleanNoOp("difference", this, resultShape);
|
|
44130
|
+
Shape._checkBooleanNoOp("difference", this, resultShape, shapes.slice(1));
|
|
43856
44131
|
return resultShape;
|
|
43857
44132
|
}
|
|
43858
44133
|
/** Keep only the overlap with other shapes. Method form of intersection(). */
|
|
@@ -44255,6 +44530,11 @@ class Shape {
|
|
|
44255
44530
|
/**
|
|
44256
44531
|
* Position this shape by matching connectors to a target.
|
|
44257
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
|
+
*
|
|
44258
44538
|
* Overloads:
|
|
44259
44539
|
* - Single pair: `matchTo(target, selfConn, targetConn, options?)`
|
|
44260
44540
|
* - Dictionary (same target): `matchTo(target, { selfConn: targetConn, ... }, options?)`
|
|
@@ -45324,117 +45604,10 @@ function buildSweepLevelSetInput(profilePolygons, pathInput, options) {
|
|
|
45324
45604
|
edgeLength: options.edgeLength
|
|
45325
45605
|
};
|
|
45326
45606
|
}
|
|
45327
|
-
const
|
|
45328
|
-
|
|
45329
|
-
|
|
45330
|
-
|
|
45331
|
-
const dists = [0];
|
|
45332
|
-
for (let i = 0; i < poly.length; i++) {
|
|
45333
|
-
const p1 = poly[i];
|
|
45334
|
-
const p2 = poly[(i + 1) % poly.length];
|
|
45335
|
-
const dx = p2[0] - p1[0];
|
|
45336
|
-
const dy = p2[1] - p1[1];
|
|
45337
|
-
const d2 = Math.sqrt(dx * dx + dy * dy);
|
|
45338
|
-
dists.push(dists[dists.length - 1] + d2);
|
|
45339
|
-
}
|
|
45340
|
-
const totalDist = dists[dists.length - 1];
|
|
45341
|
-
if (totalDist < 1e-12) {
|
|
45342
|
-
return Array.from({ length: targetCount }, () => [poly[0][0], poly[0][1]]);
|
|
45343
|
-
}
|
|
45344
|
-
const out = [];
|
|
45345
|
-
for (let i = 0; i < targetCount; i++) {
|
|
45346
|
-
const targetDist = i / targetCount * totalDist;
|
|
45347
|
-
let low = 0;
|
|
45348
|
-
let high = dists.length - 1;
|
|
45349
|
-
while (low < high) {
|
|
45350
|
-
const mid = low + high >> 1;
|
|
45351
|
-
if (dists[mid] <= targetDist) {
|
|
45352
|
-
low = mid + 1;
|
|
45353
|
-
} else {
|
|
45354
|
-
high = mid;
|
|
45355
|
-
}
|
|
45356
|
-
}
|
|
45357
|
-
const seg = low - 1;
|
|
45358
|
-
const t = (targetDist - dists[seg]) / (dists[seg + 1] - dists[seg]);
|
|
45359
|
-
const p1 = poly[seg % poly.length];
|
|
45360
|
-
const p2 = poly[(seg + 1) % poly.length];
|
|
45361
|
-
out.push([p1[0] + (p2[0] - p1[0]) * t, p1[1] + (p2[1] - p1[1]) * t]);
|
|
45362
|
-
}
|
|
45363
|
-
return out;
|
|
45364
|
-
}
|
|
45365
|
-
function resamplePolygonByAngle(poly, targetCount, center = polygonCentroid(poly)) {
|
|
45366
|
-
if (poly.length < 3 || targetCount <= 0) return null;
|
|
45367
|
-
if (!isConvexPolygon(poly)) return null;
|
|
45368
|
-
const out = [];
|
|
45369
|
-
for (let index2 = 0; index2 < targetCount; index2 += 1) {
|
|
45370
|
-
const angle = index2 / targetCount * Math.PI * 2;
|
|
45371
|
-
const point = rayPolygonIntersection(center, [Math.cos(angle), Math.sin(angle)], poly);
|
|
45372
|
-
if (!point) return null;
|
|
45373
|
-
out.push(point);
|
|
45374
|
-
}
|
|
45375
|
-
return out;
|
|
45376
|
-
}
|
|
45377
|
-
function rayPolygonIntersection(origin, direction, poly) {
|
|
45378
|
-
let bestT = Infinity;
|
|
45379
|
-
let best = null;
|
|
45380
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
45381
|
-
const a2 = poly[index2];
|
|
45382
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
45383
|
-
const edge = [b[0] - a2[0], b[1] - a2[1]];
|
|
45384
|
-
const denom = cross$1(direction, edge);
|
|
45385
|
-
if (Math.abs(denom) < EPS) continue;
|
|
45386
|
-
const delta = [a2[0] - origin[0], a2[1] - origin[1]];
|
|
45387
|
-
const rayT = cross$1(delta, edge) / denom;
|
|
45388
|
-
const edgeT = cross$1(delta, direction) / denom;
|
|
45389
|
-
if (rayT >= -EPS && edgeT >= -EPS && edgeT <= 1 + EPS && rayT < bestT) {
|
|
45390
|
-
bestT = rayT;
|
|
45391
|
-
best = [origin[0] + direction[0] * rayT, origin[1] + direction[1] * rayT];
|
|
45392
|
-
}
|
|
45393
|
-
}
|
|
45394
|
-
return best;
|
|
45395
|
-
}
|
|
45396
|
-
function polygonCentroid(poly) {
|
|
45397
|
-
let area2 = 0;
|
|
45398
|
-
let cx = 0;
|
|
45399
|
-
let cy = 0;
|
|
45400
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
45401
|
-
const a2 = poly[index2];
|
|
45402
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
45403
|
-
const crossValue = cross$1(a2, b);
|
|
45404
|
-
area2 += crossValue;
|
|
45405
|
-
cx += (a2[0] + b[0]) * crossValue;
|
|
45406
|
-
cy += (a2[1] + b[1]) * crossValue;
|
|
45407
|
-
}
|
|
45408
|
-
if (Math.abs(area2) < EPS) return averagePoint(poly);
|
|
45409
|
-
return [cx / (3 * area2), cy / (3 * area2)];
|
|
45410
|
-
}
|
|
45411
|
-
function averagePoint(poly) {
|
|
45412
|
-
let x2 = 0;
|
|
45413
|
-
let y2 = 0;
|
|
45414
|
-
for (const point of poly) {
|
|
45415
|
-
x2 += point[0];
|
|
45416
|
-
y2 += point[1];
|
|
45417
|
-
}
|
|
45418
|
-
return [x2 / poly.length, y2 / poly.length];
|
|
45419
|
-
}
|
|
45420
|
-
function isConvexPolygon(poly) {
|
|
45421
|
-
let sign = 0;
|
|
45422
|
-
for (let index2 = 0; index2 < poly.length; index2 += 1) {
|
|
45423
|
-
const a2 = poly[index2];
|
|
45424
|
-
const b = poly[(index2 + 1) % poly.length];
|
|
45425
|
-
const c2 = poly[(index2 + 2) % poly.length];
|
|
45426
|
-
const turn = cross$1([b[0] - a2[0], b[1] - a2[1]], [c2[0] - b[0], c2[1] - b[1]]);
|
|
45427
|
-
if (Math.abs(turn) < EPS) continue;
|
|
45428
|
-
const currentSign = Math.sign(turn);
|
|
45429
|
-
if (sign !== 0 && currentSign !== sign) return false;
|
|
45430
|
-
sign = currentSign;
|
|
45431
|
-
}
|
|
45432
|
-
return sign !== 0;
|
|
45433
|
-
}
|
|
45434
|
-
function cross$1(a2, b) {
|
|
45435
|
-
return a2[0] * b[1] - a2[1] * b[0];
|
|
45436
|
-
}
|
|
45437
|
-
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 = {}) {
|
|
45438
45611
|
if (profiles.length < 2) return null;
|
|
45439
45612
|
const classified = profiles.map((loops) => classifyLoops(loops));
|
|
45440
45613
|
const outerCount = classified[0].outers.length;
|
|
@@ -45449,7 +45622,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
45449
45622
|
const holeGroups = holeCount > 0 ? matchLoopsAcrossProfiles(classified.map((c2) => c2.holes)) : [];
|
|
45450
45623
|
const outerSolids = [];
|
|
45451
45624
|
for (const group of outerGroups) {
|
|
45452
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
45625
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
45453
45626
|
if (!solid) {
|
|
45454
45627
|
for (const s of outerSolids) s.delete();
|
|
45455
45628
|
return null;
|
|
@@ -45466,7 +45639,7 @@ function loftStitched(profiles, heights, wasm) {
|
|
|
45466
45639
|
if (holeGroups.length > 0) {
|
|
45467
45640
|
const holeSolids = [];
|
|
45468
45641
|
for (const group of holeGroups) {
|
|
45469
|
-
const solid = stitchSingleLoopLoft(group, heights, wasm);
|
|
45642
|
+
const solid = stitchSingleLoopLoft(group, heights, wasm, options);
|
|
45470
45643
|
if (!solid) {
|
|
45471
45644
|
result.delete();
|
|
45472
45645
|
for (const s of holeSolids) s.delete();
|
|
@@ -45552,68 +45725,564 @@ function signedArea$1(loop) {
|
|
|
45552
45725
|
}
|
|
45553
45726
|
return area * 0.5;
|
|
45554
45727
|
}
|
|
45555
|
-
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) {
|
|
45556
46132
|
const normalizedLoops = loops.map((loop) => {
|
|
45557
46133
|
const area = signedArea$1(loop);
|
|
45558
46134
|
return area < 0 ? [...loop].reverse() : loop;
|
|
45559
46135
|
});
|
|
45560
|
-
|
|
45561
|
-
|
|
45562
|
-
|
|
45563
|
-
|
|
45564
|
-
|
|
45565
|
-
const
|
|
45566
|
-
const
|
|
45567
|
-
const
|
|
45568
|
-
|
|
45569
|
-
|
|
45570
|
-
|
|
45571
|
-
|
|
45572
|
-
const
|
|
45573
|
-
|
|
45574
|
-
|
|
45575
|
-
|
|
45576
|
-
|
|
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
|
+
}
|
|
45577
46170
|
}
|
|
46171
|
+
fwdIdx.push(fwd);
|
|
46172
|
+
bwdIdx.push(bwd);
|
|
45578
46173
|
}
|
|
45579
|
-
|
|
45580
|
-
|
|
45581
|
-
const nextIdx = (i + 1) * N;
|
|
46174
|
+
const triangles = [];
|
|
46175
|
+
for (let r = 0; r < R - 1; r++) {
|
|
45582
46176
|
for (let j = 0; j < N; j++) {
|
|
45583
46177
|
const j1 = (j + 1) % N;
|
|
45584
|
-
const v0 =
|
|
45585
|
-
const
|
|
45586
|
-
const v2 =
|
|
45587
|
-
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];
|
|
45588
46182
|
triangles.push(v0, v3, v2);
|
|
45589
46183
|
triangles.push(v0, v2, v1);
|
|
45590
46184
|
}
|
|
45591
46185
|
}
|
|
45592
|
-
const
|
|
45593
|
-
const
|
|
45594
|
-
|
|
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) {
|
|
45595
46193
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
45596
|
-
triangles.push(v0, v2, v1);
|
|
46194
|
+
triangles.push(bottomBase + v0, bottomBase + v2, bottomBase + v1);
|
|
45597
46195
|
}
|
|
45598
|
-
const
|
|
45599
|
-
const
|
|
45600
|
-
const
|
|
45601
|
-
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) {
|
|
45602
46201
|
const [v0, v1, v2] = Array.isArray(tri) ? tri : [tri[0], tri[1], tri[2]];
|
|
45603
|
-
triangles.push(
|
|
46202
|
+
triangles.push(topBase + v0, topBase + v1, topBase + v2);
|
|
45604
46203
|
}
|
|
45605
46204
|
const mesh = new wasm.Mesh({
|
|
45606
|
-
numProp:
|
|
45607
|
-
vertProperties: new Float32Array(
|
|
46205
|
+
numProp: 6,
|
|
46206
|
+
vertProperties: new Float32Array(vertProps),
|
|
45608
46207
|
triVerts: new Uint32Array(triangles)
|
|
45609
46208
|
});
|
|
45610
46209
|
try {
|
|
46210
|
+
mesh.merge();
|
|
45611
46211
|
const manifold = new wasm.Manifold(mesh);
|
|
45612
46212
|
return manifold;
|
|
45613
46213
|
} catch (_e2) {
|
|
45614
46214
|
return null;
|
|
45615
46215
|
}
|
|
45616
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
|
+
}
|
|
45617
46286
|
function sweepStitched(profilePolygons, pathPoints, up, wasm) {
|
|
45618
46287
|
if (pathPoints.length < 2) return null;
|
|
45619
46288
|
if (profilePolygons.length === 0) return null;
|
|
@@ -46311,7 +46980,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
|
|
|
46311
46980
|
throw new Error("offsetSolid() collapsed the compatible-loft height span.");
|
|
46312
46981
|
}
|
|
46313
46982
|
const offsetPolygons = plan.profiles.map((profile) => offsetProfilePolygonsForManifold(profile, thickness, wasm));
|
|
46314
|
-
const stitched = loftStitched(offsetPolygons, heights, wasm);
|
|
46983
|
+
const stitched = loftStitched(offsetPolygons, heights, wasm, { edgeLength: plan.edgeLength });
|
|
46315
46984
|
if (!stitched) {
|
|
46316
46985
|
throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
|
|
46317
46986
|
}
|
|
@@ -46446,12 +47115,12 @@ function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
|
46446
47115
|
disposeWasmObject(crossSection);
|
|
46447
47116
|
}
|
|
46448
47117
|
});
|
|
46449
|
-
if (inputPolygons.length >= 2) {
|
|
46450
|
-
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 });
|
|
46451
47120
|
if (stitched) return stitched;
|
|
46452
47121
|
}
|
|
46453
47122
|
const input = buildLoftLevelSetInput(inputPolygons, plan.heights, { edgeLength: plan.edgeLength, boundsPadding: plan.boundsPadding });
|
|
46454
|
-
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm);
|
|
47123
|
+
return lowerSdfToManifold(levelSetFieldToStandardSdf3(input.sdf), input.bounds, input.edgeLength, wasm, plan.meshing);
|
|
46455
47124
|
}
|
|
46456
47125
|
function lowerShapeSweepCompilePlan(plan, wasm) {
|
|
46457
47126
|
const crossSection = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
@@ -46716,7 +47385,7 @@ function lowerFromSlicesToManifold(plan, wasm) {
|
|
|
46716
47385
|
}
|
|
46717
47386
|
});
|
|
46718
47387
|
const heights = sorted.map((s) => s.offset);
|
|
46719
|
-
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm) : null;
|
|
47388
|
+
const stitched = polygons.length >= 2 ? loftStitched(polygons, heights, wasm, { edgeLength: plan.edgeLength }) : null;
|
|
46720
47389
|
if (stitched) {
|
|
46721
47390
|
solid = stitched;
|
|
46722
47391
|
} else {
|
|
@@ -47157,17 +47826,21 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
47157
47826
|
if (diagnostics) diagnostics.projectionMs = nowMs() - projectionStart;
|
|
47158
47827
|
const simplificationStart = nowMs();
|
|
47159
47828
|
if (((meshing == null ? void 0 : meshing.simplify) ?? "safe") !== "off" && snMesh.numTris > 100) {
|
|
47160
|
-
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties,
|
|
47829
|
+
triVerts = simplifySdfMesh(triVerts, snMesh.vertProperties, vertProps6, edgeLength2, meshing == null ? void 0 : meshing.maxTriangles);
|
|
47161
47830
|
}
|
|
47162
47831
|
if (diagnostics) {
|
|
47163
47832
|
diagnostics.simplificationMs = nowMs() - simplificationStart;
|
|
47164
47833
|
diagnostics.trianglesAfterSimplification = triVerts.length / 3;
|
|
47165
47834
|
}
|
|
47166
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";
|
|
47167
47837
|
throw new Error(
|
|
47168
|
-
`SDF meshing
|
|
47838
|
+
`SDF meshing ${verb} ${triVerts.length / 3} safe triangles, above maxTriangles=${meshing.maxTriangles}. Increase maxTriangles or use a larger edgeLength.`
|
|
47169
47839
|
);
|
|
47170
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
|
+
}
|
|
47171
47844
|
const wasmMesh = new wasm.Mesh({
|
|
47172
47845
|
numProp: 6,
|
|
47173
47846
|
vertProperties: vertProps6,
|
|
@@ -47185,28 +47858,102 @@ function lowerSdfToManifold(evalFn, bounds, edgeLength2, wasm, meshing, evaluato
|
|
|
47185
47858
|
disposeWasmObject(wasmMesh);
|
|
47186
47859
|
}
|
|
47187
47860
|
}
|
|
47188
|
-
function simplifySdfMesh(triVerts, vertProperties,
|
|
47861
|
+
function simplifySdfMesh(triVerts, vertProperties, finalVertProperties, edgeLength2, maxTriangles) {
|
|
47189
47862
|
const maxError = edgeLength2 * 0.15;
|
|
47190
47863
|
const inputTriangles = triVerts.length / 3;
|
|
47191
|
-
const
|
|
47192
|
-
const
|
|
47864
|
+
const ratios = buildSimplificationRatios(inputTriangles, maxTriangles);
|
|
47865
|
+
const vertexCount = finalVertProperties.length / 6;
|
|
47866
|
+
let bestValid = null;
|
|
47193
47867
|
for (const ratio of ratios) {
|
|
47194
|
-
let simplified
|
|
47195
|
-
simplified = filterDegenerateTriangles(simplified);
|
|
47196
|
-
let mesh = null;
|
|
47197
|
-
let manifold = null;
|
|
47868
|
+
let simplified;
|
|
47198
47869
|
try {
|
|
47199
|
-
|
|
47200
|
-
manifold = new wasm.Manifold(mesh);
|
|
47201
|
-
return simplified;
|
|
47870
|
+
simplified = simplifyMesh(triVerts, vertProperties, ratio, maxError);
|
|
47202
47871
|
} catch {
|
|
47203
|
-
|
|
47204
|
-
|
|
47205
|
-
|
|
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
|
+
}
|
|
47206
47882
|
}
|
|
47207
47883
|
}
|
|
47884
|
+
if (bestValid) {
|
|
47885
|
+
return bestValid;
|
|
47886
|
+
}
|
|
47208
47887
|
return triVerts;
|
|
47209
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
|
+
}
|
|
47210
47957
|
function filterDegenerateTriangles(triVerts) {
|
|
47211
47958
|
let writeIdx = 0;
|
|
47212
47959
|
for (let i = 0; i < triVerts.length; i += 3) {
|
|
@@ -48788,6 +49535,92 @@ function percentile(sorted, q) {
|
|
|
48788
49535
|
const index2 = MathUtils.clamp(Math.floor(sorted.length * q), 0, sorted.length - 1);
|
|
48789
49536
|
return Number(sorted[index2].toFixed(2));
|
|
48790
49537
|
}
|
|
49538
|
+
class DisjointSet {
|
|
49539
|
+
constructor(size) {
|
|
49540
|
+
__publicField(this, "parent");
|
|
49541
|
+
__publicField(this, "rank");
|
|
49542
|
+
this.parent = Array.from({ length: size }, (_2, index2) => index2);
|
|
49543
|
+
this.rank = Array.from({ length: size }, () => 0);
|
|
49544
|
+
}
|
|
49545
|
+
find(index2) {
|
|
49546
|
+
const parent = this.parent[index2];
|
|
49547
|
+
if (parent === index2) return index2;
|
|
49548
|
+
const root = this.find(parent);
|
|
49549
|
+
this.parent[index2] = root;
|
|
49550
|
+
return root;
|
|
49551
|
+
}
|
|
49552
|
+
union(a2, b) {
|
|
49553
|
+
const rootA = this.find(a2);
|
|
49554
|
+
const rootB = this.find(b);
|
|
49555
|
+
if (rootA === rootB) return;
|
|
49556
|
+
if (this.rank[rootA] < this.rank[rootB]) {
|
|
49557
|
+
this.parent[rootA] = rootB;
|
|
49558
|
+
} else if (this.rank[rootA] > this.rank[rootB]) {
|
|
49559
|
+
this.parent[rootB] = rootA;
|
|
49560
|
+
} else {
|
|
49561
|
+
this.parent[rootB] = rootA;
|
|
49562
|
+
this.rank[rootA] += 1;
|
|
49563
|
+
}
|
|
49564
|
+
}
|
|
49565
|
+
}
|
|
49566
|
+
function connectedCoplanarSurfacePatches(triangles) {
|
|
49567
|
+
const snap = surfacePatchSnap(triangles);
|
|
49568
|
+
const planeKeys = triangles.map((triangle) => planeKey(triangle, snap));
|
|
49569
|
+
const edgeOwners = /* @__PURE__ */ new Map();
|
|
49570
|
+
const sets = new DisjointSet(triangles.length);
|
|
49571
|
+
triangles.forEach((triangle, index2) => {
|
|
49572
|
+
for (const key of triangleEdgeKeys(triangle, snap)) {
|
|
49573
|
+
const owners = edgeOwners.get(key);
|
|
49574
|
+
if (owners) {
|
|
49575
|
+
for (const owner of owners) {
|
|
49576
|
+
if (planeKeys[owner] === planeKeys[index2]) sets.union(owner, index2);
|
|
49577
|
+
}
|
|
49578
|
+
owners.push(index2);
|
|
49579
|
+
} else {
|
|
49580
|
+
edgeOwners.set(key, [index2]);
|
|
49581
|
+
}
|
|
49582
|
+
}
|
|
49583
|
+
});
|
|
49584
|
+
const patchByRoot = /* @__PURE__ */ new Map();
|
|
49585
|
+
triangles.forEach((triangle, index2) => {
|
|
49586
|
+
const root = sets.find(index2);
|
|
49587
|
+
const patch = patchByRoot.get(root) ?? { triangleIndexes: [], area: 0 };
|
|
49588
|
+
patch.triangleIndexes.push(index2);
|
|
49589
|
+
patch.area += triangle.area;
|
|
49590
|
+
patchByRoot.set(root, patch);
|
|
49591
|
+
});
|
|
49592
|
+
return [...patchByRoot.values()];
|
|
49593
|
+
}
|
|
49594
|
+
function surfacePatchSnap(triangles) {
|
|
49595
|
+
const bounds = new Box3();
|
|
49596
|
+
for (const triangle of triangles) {
|
|
49597
|
+
bounds.expandByPoint(triangle.a);
|
|
49598
|
+
bounds.expandByPoint(triangle.b);
|
|
49599
|
+
bounds.expandByPoint(triangle.c);
|
|
49600
|
+
}
|
|
49601
|
+
const size = bounds.getSize(new Vector3());
|
|
49602
|
+
return Math.max(1e-6, size.length() * 1e-8);
|
|
49603
|
+
}
|
|
49604
|
+
function planeKey(triangle, snap) {
|
|
49605
|
+
const normalSnap = 1e-6;
|
|
49606
|
+
const distance = triangle.normal.dot(triangle.a);
|
|
49607
|
+
return [
|
|
49608
|
+
Math.round(triangle.normal.x / normalSnap),
|
|
49609
|
+
Math.round(triangle.normal.y / normalSnap),
|
|
49610
|
+
Math.round(triangle.normal.z / normalSnap),
|
|
49611
|
+
Math.round(distance / snap)
|
|
49612
|
+
].join(",");
|
|
49613
|
+
}
|
|
49614
|
+
function triangleEdgeKeys(triangle, snap) {
|
|
49615
|
+
const vertices = [vertexKey$2(triangle.a, snap), vertexKey$2(triangle.b, snap), vertexKey$2(triangle.c, snap)];
|
|
49616
|
+
return [edgeKey$1(vertices[0], vertices[1]), edgeKey$1(vertices[1], vertices[2]), edgeKey$1(vertices[2], vertices[0])];
|
|
49617
|
+
}
|
|
49618
|
+
function vertexKey$2(point, snap) {
|
|
49619
|
+
return `${Math.round(point.x / snap)},${Math.round(point.y / snap)},${Math.round(point.z / snap)}`;
|
|
49620
|
+
}
|
|
49621
|
+
function edgeKey$1(a2, b) {
|
|
49622
|
+
return a2 < b ? `${a2}|${b}` : `${b}|${a2}`;
|
|
49623
|
+
}
|
|
48791
49624
|
const MIN_TRIANGLE_AREA = 1e-12;
|
|
48792
49625
|
const R2_ALPHA = 0.7548776662466927;
|
|
48793
49626
|
const R2_BETA = 0.5698402909980532;
|
|
@@ -48840,7 +49673,7 @@ function allocateAreaSampleCounts(triangles, maxSamples) {
|
|
|
48840
49673
|
return counts;
|
|
48841
49674
|
}
|
|
48842
49675
|
function sampleSurfaceTriangles(triangles, maxSamples) {
|
|
48843
|
-
const counts =
|
|
49676
|
+
const counts = allocateSurfacePatchSampleCounts(triangles, maxSamples);
|
|
48844
49677
|
const samples = [];
|
|
48845
49678
|
const position = new Vector3();
|
|
48846
49679
|
let sampleIndex = 0;
|
|
@@ -48865,6 +49698,35 @@ function sampleSurfaceTriangles(triangles, maxSamples) {
|
|
|
48865
49698
|
});
|
|
48866
49699
|
return samples;
|
|
48867
49700
|
}
|
|
49701
|
+
function allocateSurfacePatchSampleCounts(triangles, maxSamples) {
|
|
49702
|
+
const counts = new Array(triangles.length).fill(0);
|
|
49703
|
+
if (triangles.length === 0) return counts;
|
|
49704
|
+
const patches = connectedCoplanarSurfacePatches(triangles);
|
|
49705
|
+
const patchCounts = allocateAreaSampleCounts(
|
|
49706
|
+
patches.map((patch, index2) => {
|
|
49707
|
+
var _a3, _b3, _c2, _d2;
|
|
49708
|
+
return {
|
|
49709
|
+
index: index2,
|
|
49710
|
+
a: ((_a3 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _a3.a) ?? new Vector3(),
|
|
49711
|
+
b: ((_b3 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _b3.b) ?? new Vector3(),
|
|
49712
|
+
c: ((_c2 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _c2.c) ?? new Vector3(),
|
|
49713
|
+
normal: ((_d2 = triangles[patch.triangleIndexes[0]]) == null ? void 0 : _d2.normal) ?? new Vector3(0, 0, 1),
|
|
49714
|
+
area: patch.area
|
|
49715
|
+
};
|
|
49716
|
+
}),
|
|
49717
|
+
maxSamples
|
|
49718
|
+
);
|
|
49719
|
+
patches.forEach((patch, patchIndex) => {
|
|
49720
|
+
const patchBudget = patchCounts[patchIndex] ?? 0;
|
|
49721
|
+
if (patchBudget <= 0) return;
|
|
49722
|
+
const patchTriangles = patch.triangleIndexes.map((index2) => triangles[index2]);
|
|
49723
|
+
const localCounts = allocateAreaSampleCounts(patchTriangles, patchBudget);
|
|
49724
|
+
patch.triangleIndexes.forEach((triangleIndex, localIndex) => {
|
|
49725
|
+
counts[triangleIndex] += localCounts[localIndex] ?? 0;
|
|
49726
|
+
});
|
|
49727
|
+
});
|
|
49728
|
+
return counts;
|
|
49729
|
+
}
|
|
48868
49730
|
function totalSurfaceArea(triangles) {
|
|
48869
49731
|
return triangles.reduce((sum2, triangle) => sum2 + triangle.area, 0);
|
|
48870
49732
|
}
|
|
@@ -49045,16 +49907,23 @@ const DEFAULT_THICKNESS_INSPECTION_OPTIONS = {
|
|
|
49045
49907
|
minThickness: 1.2,
|
|
49046
49908
|
warnThickness: 2,
|
|
49047
49909
|
maxThickness: 6,
|
|
49910
|
+
colorMinThickness: 0,
|
|
49911
|
+
colorMaxThickness: 6,
|
|
49048
49912
|
maxSamplesPerObject: 5e3,
|
|
49049
49913
|
contactTolerance: DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance
|
|
49050
49914
|
};
|
|
49051
49915
|
const THICKNESS_COLORS = {
|
|
49052
49916
|
critical: [255, 28, 28],
|
|
49053
|
-
warning: [255, 150, 0],
|
|
49054
49917
|
ok: [60, 220, 90],
|
|
49055
49918
|
thick: [70, 145, 255],
|
|
49056
49919
|
unknown: [90, 90, 90]
|
|
49057
49920
|
};
|
|
49921
|
+
const THICKNESS_GRADIENT_COLORS = [
|
|
49922
|
+
THICKNESS_COLORS.critical,
|
|
49923
|
+
[255, 222, 0],
|
|
49924
|
+
THICKNESS_COLORS.ok,
|
|
49925
|
+
THICKNESS_COLORS.thick
|
|
49926
|
+
];
|
|
49058
49927
|
function finitePositive(value, fallback, label) {
|
|
49059
49928
|
if (value === void 0) return fallback;
|
|
49060
49929
|
if (!Number.isFinite(value) || value <= 0) {
|
|
@@ -49073,6 +49942,16 @@ function resolveThicknessInspectionOptions(raw = {}) {
|
|
|
49073
49942
|
const minThickness = finitePositive(raw.minThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.minThickness, "minThickness");
|
|
49074
49943
|
const warnThickness = finitePositive(raw.warnThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.warnThickness, "warnThickness");
|
|
49075
49944
|
const maxThickness = finitePositive(raw.maxThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxThickness, "maxThickness");
|
|
49945
|
+
const colorMinThickness = finiteNonNegative(
|
|
49946
|
+
raw.colorMinThickness,
|
|
49947
|
+
DEFAULT_THICKNESS_INSPECTION_OPTIONS.colorMinThickness,
|
|
49948
|
+
"colorMinThickness"
|
|
49949
|
+
);
|
|
49950
|
+
const colorMaxThickness = finitePositive(
|
|
49951
|
+
raw.colorMaxThickness,
|
|
49952
|
+
DEFAULT_THICKNESS_INSPECTION_OPTIONS.colorMaxThickness,
|
|
49953
|
+
"colorMaxThickness"
|
|
49954
|
+
);
|
|
49076
49955
|
const maxSamplesPerObject = finitePositive(
|
|
49077
49956
|
raw.maxSamplesPerObject,
|
|
49078
49957
|
DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxSamplesPerObject,
|
|
@@ -49089,10 +49968,15 @@ function resolveThicknessInspectionOptions(raw = {}) {
|
|
|
49089
49968
|
if (warnThickness > maxThickness) {
|
|
49090
49969
|
throw new Error("warnThickness must be less than or equal to maxThickness.");
|
|
49091
49970
|
}
|
|
49971
|
+
if (colorMinThickness >= colorMaxThickness) {
|
|
49972
|
+
throw new Error("colorMinThickness must be less than colorMaxThickness.");
|
|
49973
|
+
}
|
|
49092
49974
|
return {
|
|
49093
49975
|
minThickness,
|
|
49094
49976
|
warnThickness,
|
|
49095
49977
|
maxThickness,
|
|
49978
|
+
colorMinThickness,
|
|
49979
|
+
colorMaxThickness,
|
|
49096
49980
|
maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject)),
|
|
49097
49981
|
contactTolerance
|
|
49098
49982
|
};
|
|
@@ -49103,6 +49987,16 @@ function lerp$1(a2, b, t) {
|
|
|
49103
49987
|
function lerpColor(a2, b, t) {
|
|
49104
49988
|
return [Math.round(lerp$1(a2[0], b[0], t)), Math.round(lerp$1(a2[1], b[1], t)), Math.round(lerp$1(a2[2], b[2], t))];
|
|
49105
49989
|
}
|
|
49990
|
+
function gradientColor(stops, t) {
|
|
49991
|
+
if (stops.length === 0) return THICKNESS_COLORS.unknown;
|
|
49992
|
+
if (stops.length === 1) return stops[0] ?? THICKNESS_COLORS.unknown;
|
|
49993
|
+
const clamped = Math.max(0, Math.min(1, t));
|
|
49994
|
+
const scaled = clamped * (stops.length - 1);
|
|
49995
|
+
const leftIndex = Math.min(stops.length - 2, Math.floor(scaled));
|
|
49996
|
+
const left = stops[leftIndex] ?? THICKNESS_COLORS.unknown;
|
|
49997
|
+
const right = stops[leftIndex + 1] ?? left;
|
|
49998
|
+
return lerpColor(left, right, scaled - leftIndex);
|
|
49999
|
+
}
|
|
49106
50000
|
function thicknessClass(thickness, options) {
|
|
49107
50001
|
if (thickness == null || !Number.isFinite(thickness) || thickness <= 0) return "unknown";
|
|
49108
50002
|
if (thickness <= options.minThickness) return "critical";
|
|
@@ -49111,18 +50005,9 @@ function thicknessClass(thickness, options) {
|
|
|
49111
50005
|
return "thick";
|
|
49112
50006
|
}
|
|
49113
50007
|
function thicknessColor(thickness, options) {
|
|
49114
|
-
|
|
49115
|
-
|
|
49116
|
-
|
|
49117
|
-
if (cls === "warning") {
|
|
49118
|
-
const span = Math.max(1e-9, options.warnThickness - options.minThickness);
|
|
49119
|
-
return lerpColor(THICKNESS_COLORS.critical, THICKNESS_COLORS.warning, ((thickness ?? 0) - options.minThickness) / span);
|
|
49120
|
-
}
|
|
49121
|
-
if (cls === "ok") {
|
|
49122
|
-
const span = Math.max(1e-9, options.maxThickness - options.warnThickness);
|
|
49123
|
-
return lerpColor(THICKNESS_COLORS.ok, THICKNESS_COLORS.thick, ((thickness ?? 0) - options.warnThickness) / span);
|
|
49124
|
-
}
|
|
49125
|
-
return THICKNESS_COLORS.thick;
|
|
50008
|
+
if (thickness == null || !Number.isFinite(thickness) || thickness <= 0) return THICKNESS_COLORS.unknown;
|
|
50009
|
+
const span = Math.max(1e-9, options.colorMaxThickness - options.colorMinThickness);
|
|
50010
|
+
return gradientColor(THICKNESS_GRADIENT_COLORS, (thickness - options.colorMinThickness) / span);
|
|
49126
50011
|
}
|
|
49127
50012
|
function cloneGeometryForFaceColors(geometry) {
|
|
49128
50013
|
return geometry.index ? geometry.toNonIndexed() : geometry.clone();
|
|
@@ -49508,15 +50393,23 @@ function computeGeometryArrays(mesh, options = {}) {
|
|
|
49508
50393
|
normals[o + 7] = vertNormals[i2 * 3 + 1];
|
|
49509
50394
|
normals[o + 8] = vertNormals[i2 * 3 + 2];
|
|
49510
50395
|
} else if (numProp >= 6) {
|
|
49511
|
-
|
|
49512
|
-
|
|
49513
|
-
|
|
49514
|
-
|
|
49515
|
-
|
|
49516
|
-
|
|
49517
|
-
|
|
49518
|
-
|
|
49519
|
-
|
|
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
|
+
}
|
|
49520
50413
|
} else {
|
|
49521
50414
|
normals[o] = fnx;
|
|
49522
50415
|
normals[o + 1] = fny;
|
|
@@ -49986,7 +50879,7 @@ for (let radius = 0; radius <= MAX_SEARCH_RADIUS; radius += 1) {
|
|
|
49986
50879
|
function gridKey(x2, y2, z2) {
|
|
49987
50880
|
return `${x2},${y2},${z2}`;
|
|
49988
50881
|
}
|
|
49989
|
-
function
|
|
50882
|
+
function buildInspectHeatmapFieldBounds(geometry) {
|
|
49990
50883
|
const position = geometry.getAttribute("position");
|
|
49991
50884
|
if (!position || position.count === 0) return null;
|
|
49992
50885
|
const bounds = new Box3().setFromBufferAttribute(position);
|
|
@@ -49994,7 +50887,11 @@ function buildGeometryBounds(geometry) {
|
|
|
49994
50887
|
const size = bounds.getSize(new Vector3());
|
|
49995
50888
|
const pad = Math.max(size.x, size.y, size.z, 1) * 1e-5;
|
|
49996
50889
|
bounds.expandByScalar(pad);
|
|
49997
|
-
|
|
50890
|
+
const boundsSize = bounds.getSize(new Vector3());
|
|
50891
|
+
return {
|
|
50892
|
+
boundsMin: [bounds.min.x, bounds.min.y, bounds.min.z],
|
|
50893
|
+
boundsSize: [boundsSize.x, boundsSize.y, boundsSize.z]
|
|
50894
|
+
};
|
|
49998
50895
|
}
|
|
49999
50896
|
function buildSampleGrid(pointCloud) {
|
|
50000
50897
|
const sampleCount = Math.floor(pointCloud.positions.length / 3);
|
|
@@ -50078,11 +50975,15 @@ function blendedColorAt(point, pointCloud, sampleGrid) {
|
|
|
50078
50975
|
return [r / weightSum, g2 / weightSum, b / weightSum];
|
|
50079
50976
|
}
|
|
50080
50977
|
function buildInspectHeatmapFieldData(geometry, pointCloud) {
|
|
50081
|
-
const bounds =
|
|
50978
|
+
const bounds = buildInspectHeatmapFieldBounds(geometry);
|
|
50979
|
+
return bounds ? buildInspectHeatmapFieldDataFromBounds(bounds, pointCloud) : null;
|
|
50980
|
+
}
|
|
50981
|
+
function buildInspectHeatmapFieldDataFromBounds(bounds, pointCloud) {
|
|
50082
50982
|
const sampleGrid = buildSampleGrid(pointCloud);
|
|
50083
50983
|
if (!bounds || !sampleGrid) return null;
|
|
50084
50984
|
const gridSize = fieldGridSizeForSampleCount(Math.floor(pointCloud.positions.length / 3));
|
|
50085
|
-
const
|
|
50985
|
+
const boundsMin = new Vector3(...bounds.boundsMin);
|
|
50986
|
+
const boundsSize = new Vector3(...bounds.boundsSize);
|
|
50086
50987
|
const data = new Uint8Array(gridSize * gridSize * gridSize * 4);
|
|
50087
50988
|
const point = new Vector3();
|
|
50088
50989
|
let dataOffset = 0;
|
|
@@ -50090,9 +50991,9 @@ function buildInspectHeatmapFieldData(geometry, pointCloud) {
|
|
|
50090
50991
|
for (let z2 = 0; z2 < gridSize; z2 += 1) {
|
|
50091
50992
|
for (let x2 = 0; x2 < gridSize; x2 += 1) {
|
|
50092
50993
|
point.set(
|
|
50093
|
-
|
|
50094
|
-
|
|
50095
|
-
|
|
50994
|
+
boundsMin.x + boundsSize.x * x2 / (gridSize - 1),
|
|
50995
|
+
boundsMin.y + boundsSize.y * y2 / (gridSize - 1),
|
|
50996
|
+
boundsMin.z + boundsSize.z * z2 / (gridSize - 1)
|
|
50096
50997
|
);
|
|
50097
50998
|
const [r, g2, b] = blendedColorAt(point, pointCloud, sampleGrid);
|
|
50098
50999
|
data[dataOffset] = Math.max(0, Math.min(255, Math.round(r)));
|
|
@@ -50105,13 +51006,14 @@ function buildInspectHeatmapFieldData(geometry, pointCloud) {
|
|
|
50105
51006
|
}
|
|
50106
51007
|
return {
|
|
50107
51008
|
data,
|
|
50108
|
-
boundsMin:
|
|
51009
|
+
boundsMin: bounds.boundsMin,
|
|
50109
51010
|
boundsSize: [boundsSize.x, boundsSize.y, boundsSize.z],
|
|
50110
51011
|
gridSize
|
|
50111
51012
|
};
|
|
50112
51013
|
}
|
|
50113
51014
|
const workerScope = self;
|
|
50114
51015
|
let manifoldReadyPromise = null;
|
|
51016
|
+
let cachedThicknessColorizeAnalysis = null;
|
|
50115
51017
|
function ensureManifoldReady() {
|
|
50116
51018
|
if (!manifoldReadyPromise) manifoldReadyPromise = initKernelManifoldOnly();
|
|
50117
51019
|
return manifoldReadyPromise;
|
|
@@ -50157,6 +51059,7 @@ function geometryFromPositions(positions) {
|
|
|
50157
51059
|
function pointBuffers(samples) {
|
|
50158
51060
|
const positions = new Float32Array(samples.length * 3);
|
|
50159
51061
|
const colors = new Float32Array(samples.length * 3);
|
|
51062
|
+
const values = new Float32Array(samples.length);
|
|
50160
51063
|
samples.forEach((sample, index2) => {
|
|
50161
51064
|
const base = index2 * 3;
|
|
50162
51065
|
positions[base] = sample.position[0] + sample.normal[0] * 0.025;
|
|
@@ -50165,8 +51068,9 @@ function pointBuffers(samples) {
|
|
|
50165
51068
|
colors[base] = sample.color[0] / 255;
|
|
50166
51069
|
colors[base + 1] = sample.color[1] / 255;
|
|
50167
51070
|
colors[base + 2] = sample.color[2] / 255;
|
|
51071
|
+
values[index2] = sample.value ?? Number.NaN;
|
|
50168
51072
|
});
|
|
50169
|
-
return { positions, colors };
|
|
51073
|
+
return { positions, colors, values };
|
|
50170
51074
|
}
|
|
50171
51075
|
function rgbFloatsForHex(hex) {
|
|
50172
51076
|
const color = new Color(hex);
|
|
@@ -50191,6 +51095,7 @@ function analyzeScalarChannel(request) {
|
|
|
50191
51095
|
const pointObjects = [];
|
|
50192
51096
|
const heatmapFieldObjects = [];
|
|
50193
51097
|
const warnings = [];
|
|
51098
|
+
const thicknessCacheObjects = [];
|
|
50194
51099
|
for (const object of request.objects) {
|
|
50195
51100
|
if (!object.positions || object.positions.length < 9) continue;
|
|
50196
51101
|
const geometry = geometryFromPositions(object.positions);
|
|
@@ -50198,13 +51103,22 @@ function analyzeScalarChannel(request) {
|
|
|
50198
51103
|
const analysis = request.channel === "thickness" ? analyzeThicknessGeometry(geometry, request.thickness) : analyzeRoughnessGeometry(geometry, request.roughness);
|
|
50199
51104
|
analysis.warnings.forEach((warning) => warnings.push(`${object.name}: ${warning}`));
|
|
50200
51105
|
const buffers = pointBuffers(analysis.pointSamples);
|
|
50201
|
-
const
|
|
51106
|
+
const heatmapBounds = buildInspectHeatmapFieldBounds(analysis.geometry);
|
|
51107
|
+
const field = heatmapBounds ? buildInspectHeatmapFieldDataFromBounds(heatmapBounds, buffers) : buildInspectHeatmapFieldData(analysis.geometry, buffers);
|
|
50202
51108
|
if (field) {
|
|
50203
51109
|
heatmapFieldObjects.push({
|
|
50204
51110
|
objectId: object.id,
|
|
50205
51111
|
...field
|
|
50206
51112
|
});
|
|
50207
51113
|
}
|
|
51114
|
+
if (request.channel === "thickness" && buffers.values) {
|
|
51115
|
+
thicknessCacheObjects.push({
|
|
51116
|
+
objectId: object.id,
|
|
51117
|
+
positions: new Float32Array(buffers.positions),
|
|
51118
|
+
values: new Float32Array(buffers.values),
|
|
51119
|
+
heatmapBounds
|
|
51120
|
+
});
|
|
51121
|
+
}
|
|
50208
51122
|
pointObjects.push({
|
|
50209
51123
|
objectId: object.id,
|
|
50210
51124
|
sampleCount: analysis.pointSamples.length,
|
|
@@ -50215,7 +51129,9 @@ function analyzeScalarChannel(request) {
|
|
|
50215
51129
|
geometry.dispose();
|
|
50216
51130
|
}
|
|
50217
51131
|
}
|
|
51132
|
+
cachedThicknessColorizeAnalysis = request.channel === "thickness" ? { analysisId: request.reqId, objects: thicknessCacheObjects } : null;
|
|
50218
51133
|
return {
|
|
51134
|
+
analysisId: request.reqId,
|
|
50219
51135
|
channel: request.channel,
|
|
50220
51136
|
objectColors: {},
|
|
50221
51137
|
pointObjects,
|
|
@@ -50225,6 +51141,47 @@ function analyzeScalarChannel(request) {
|
|
|
50225
51141
|
warnings
|
|
50226
51142
|
};
|
|
50227
51143
|
}
|
|
51144
|
+
function thicknessColorsForValues(values, colorMinThickness, colorMaxThickness) {
|
|
51145
|
+
const options = resolveThicknessInspectionOptions({ colorMinThickness, colorMaxThickness });
|
|
51146
|
+
const colors = new Float32Array(values.length * 3);
|
|
51147
|
+
for (let index2 = 0; index2 < values.length; index2 += 1) {
|
|
51148
|
+
const color = thicknessColor(values[index2], options);
|
|
51149
|
+
const offset = index2 * 3;
|
|
51150
|
+
colors[offset] = color[0] / 255;
|
|
51151
|
+
colors[offset + 1] = color[1] / 255;
|
|
51152
|
+
colors[offset + 2] = color[2] / 255;
|
|
51153
|
+
}
|
|
51154
|
+
return colors;
|
|
51155
|
+
}
|
|
51156
|
+
function colorizeThicknessAnalysis(request) {
|
|
51157
|
+
const cached = cachedThicknessColorizeAnalysis;
|
|
51158
|
+
if (!cached || cached.analysisId !== request.analysisId) {
|
|
51159
|
+
throw new Error("Thickness colorize cache is no longer available.");
|
|
51160
|
+
}
|
|
51161
|
+
const pointObjects = [];
|
|
51162
|
+
const heatmapFieldObjects = [];
|
|
51163
|
+
for (const object of cached.objects) {
|
|
51164
|
+
const colors = thicknessColorsForValues(object.values, request.colorMinThickness, request.colorMaxThickness);
|
|
51165
|
+
pointObjects.push({ objectId: object.objectId, colors });
|
|
51166
|
+
if (object.heatmapBounds) {
|
|
51167
|
+
const field = buildInspectHeatmapFieldDataFromBounds(object.heatmapBounds, {
|
|
51168
|
+
positions: object.positions,
|
|
51169
|
+
colors
|
|
51170
|
+
});
|
|
51171
|
+
if (field) {
|
|
51172
|
+
heatmapFieldObjects.push({
|
|
51173
|
+
objectId: object.objectId,
|
|
51174
|
+
...field
|
|
51175
|
+
});
|
|
51176
|
+
}
|
|
51177
|
+
}
|
|
51178
|
+
}
|
|
51179
|
+
return {
|
|
51180
|
+
analysisId: cached.analysisId,
|
|
51181
|
+
pointObjects,
|
|
51182
|
+
heatmapFieldObjects
|
|
51183
|
+
};
|
|
51184
|
+
}
|
|
50228
51185
|
function analyzeConnectivityChannel(request) {
|
|
50229
51186
|
const bodyInput = buildMeshBodyConnectivityInput(
|
|
50230
51187
|
request.objects.map((object) => ({
|
|
@@ -50391,7 +51348,11 @@ function analyzeCollisionChannel(request) {
|
|
|
50391
51348
|
}
|
|
50392
51349
|
function transferFor(result) {
|
|
50393
51350
|
return [
|
|
50394
|
-
...result.pointObjects.flatMap((object) => [
|
|
51351
|
+
...result.pointObjects.flatMap((object) => [
|
|
51352
|
+
object.positions.buffer,
|
|
51353
|
+
object.colors.buffer,
|
|
51354
|
+
...object.values ? [object.values.buffer] : []
|
|
51355
|
+
]),
|
|
50395
51356
|
...result.meshColorObjects.map((object) => object.colors.buffer),
|
|
50396
51357
|
...result.heatmapFieldObjects.map((object) => object.data.buffer),
|
|
50397
51358
|
...result.collisionGeometryObjects.flatMap((object) => [object.positions.buffer, object.normals.buffer, object.edgePositions.buffer])
|
|
@@ -50399,6 +51360,21 @@ function transferFor(result) {
|
|
|
50399
51360
|
}
|
|
50400
51361
|
async function handleRequest(request) {
|
|
50401
51362
|
try {
|
|
51363
|
+
if (request.type === "colorize-thickness") {
|
|
51364
|
+
const result2 = colorizeThicknessAnalysis(request.payload);
|
|
51365
|
+
const response2 = {
|
|
51366
|
+
type: "inspect-colorize-success",
|
|
51367
|
+
payload: {
|
|
51368
|
+
reqId: request.payload.reqId,
|
|
51369
|
+
result: result2
|
|
51370
|
+
}
|
|
51371
|
+
};
|
|
51372
|
+
workerScope.postMessage(response2, [
|
|
51373
|
+
...result2.pointObjects.map((object) => object.colors.buffer),
|
|
51374
|
+
...result2.heatmapFieldObjects.map((object) => object.data.buffer)
|
|
51375
|
+
]);
|
|
51376
|
+
return;
|
|
51377
|
+
}
|
|
50402
51378
|
if (request.payload.channel === "collisions") await ensureManifoldReady();
|
|
50403
51379
|
const result = request.payload.channel === "thickness" || request.payload.channel === "roughness" ? analyzeScalarChannel(request.payload) : request.payload.channel === "collisions" ? analyzeCollisionChannel(request.payload) : analyzeConnectivityChannel(request.payload);
|
|
50404
51380
|
const response = {
|