forgecad 0.10.2 → 0.10.3
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/README.md +7 -6
- package/dist/assets/{AdminPage-CHY6ZN-p.js → AdminPage-CK7ObBz3.js} +1 -1
- package/dist/assets/{BenchmarkPage-BcRT5iGN.js → BenchmarkPage-Ds7Z2doN.js} +1 -1
- package/dist/assets/{BlogPage-BssBbnb-.js → BlogPage-DlPbpt6A.js} +1 -1
- package/dist/assets/{DocsPage-DsvdiRNK.js → DocsPage-vZb3b3Y0.js} +9 -14
- package/dist/assets/{EditorApp-BpjZgzk0.css → EditorApp-C5f24ZN9.css} +8 -0
- package/dist/assets/{EditorApp-Bfd3jbtC.js → EditorApp-HLoKfe15.js} +141 -12
- package/dist/assets/{EmbedViewer-D5t8WamV.js → EmbedViewer--KnqBKrJ.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-DbN7o-Be.js → LandingPageProofDriven-C_LssmnA.js} +1 -1
- package/dist/assets/{LegalPage-DNGrrY0p.js → LegalPage-DGsyo4n1.js} +1 -1
- package/dist/assets/{PricingPage-Nczr3pRz.js → PricingPage-BOE27B-R.js} +1 -1
- package/dist/assets/{SettingsPage-DZlyu4d4.js → SettingsPage-f47cnk39.js} +1 -1
- package/dist/assets/{app-C9ct2hRD.js → app-D6ccu2Xx.js} +6854 -7373
- package/dist/assets/{backendInit-ymjonyQp.js → backendInit-DbTkQN9J.js} +2557 -809
- package/dist/assets/cli/{render-B_0lQwKU.js → render-BsngirjC.js} +114 -9
- package/dist/assets/{constructionHistoryWorker-CZ42Dksy.js → constructionHistoryWorker-PCwXrTDB.js} +175 -36
- package/dist/assets/{evalWorker-C2pm8LHP.js → evalWorker-CS63PfZu.js} +1125 -447
- package/dist/assets/{forgecad_geometry-BlMtqluF.js → forgecad_geometry-CZ_IfuvA.js} +1 -9
- package/dist/assets/{forgecad_geometry_bg-BllP_WiL.wasm → forgecad_geometry_bg-C3rQHfwg.wasm} +0 -0
- package/dist/assets/{inspectWorker-D5T5VbfK.js → inspectWorker-Y4cOzNyA.js} +4345 -373
- package/dist/assets/{jointPose-4r8ed8_5.js → jointPose-AMvCywzS.js} +1 -1
- package/dist/assets/{manifold-C4r6B-XY.js → manifold-CBry38ly.js} +2 -2
- package/dist/assets/{manifold-5PP1eGLN.js → manifold-Crd_F2qx.js} +1 -1
- package/dist/assets/{manifold-DjBkyIc8.js → manifold-k2kRcc85.js} +1 -1
- package/dist/assets/{reportWorker-CwenM7wB.js → reportWorker-CWvn0CEv.js} +1095 -400
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/AI/usage.md +2 -4
- package/dist/docs-raw/CLI.md +9 -7
- package/dist/docs-raw/README.md +1 -1
- package/dist/docs-raw/component-model.md +1 -1
- package/dist/docs-raw/generated/assembly.md +1 -1
- package/dist/docs-raw/generated/concepts.md +5 -3
- package/dist/docs-raw/generated/core.md +70 -1
- package/dist/docs-raw/generated/curves.md +8 -1
- package/dist/docs-raw/generated/output.md +0 -64
- package/dist/docs-raw/generated/runtime-names.md +6 -6
- package/dist/docs-raw/generated/viewport.md +3 -12
- package/dist/docs-raw/guides/inspection-bundles.md +1 -1
- package/dist/docs-raw/simulation-workflow.md +58 -0
- package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
- package/dist/docs-raw/skills/forgecad-image-replicator.md +2 -2
- package/dist/docs-raw/skills/forgecad-mujoco-verify.md +78 -0
- package/dist/docs-raw/skills/forgecad-spec-by-walking-through-it.md +145 -0
- package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
- package/dist/docs-raw/skills/forgecad.md +24 -24
- package/dist/docs-raw/skills/index.md +2 -3
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +15 -15
- package/dist-cli/{check-compiler-SP7FAL7R.js → check-compiler-HPF2T2FS.js} +1 -1
- package/dist-cli/{check-query-propagation-BRLSHP22.js → check-query-propagation-HYSLTXAB.js} +1 -1
- package/dist-cli/{chunk-RQQ42YCP.js → chunk-WLUKAW3H.js} +1025 -158
- package/dist-cli/forgecad.js +2621 -232
- package/dist-cli/{forgecad_geometry-7TVSNVUB.js → forgecad_geometry-2IMYCUWW.js} +0 -8
- package/dist-cli/forgecad_geometry_bg.wasm +0 -0
- package/dist-skill/CONTEXT.md +85 -73
- package/dist-skill/SKILL.md +1 -1
- package/dist-skill/docs/CLI.md +9 -7
- package/dist-skill/docs/generated/assembly.md +1 -1
- package/dist-skill/docs/generated/core.md +70 -1
- package/dist-skill/docs/generated/curves.md +8 -1
- package/dist-skill/docs/generated/output.md +0 -64
- package/dist-skill/docs/generated/runtime-names.md +6 -6
- package/dist-skill/docs/generated/viewport.md +3 -12
- package/dist-skill/docs/guides/inspection-bundles.md +1 -1
- package/dist-skill/library/README.md +2 -3
- package/dist-skill/library/forgecad-blockout-model/SKILL.md +1 -1
- package/dist-skill/library/forgecad-image-replicator/SKILL.md +2 -2
- package/dist-skill/library/forgecad-mujoco-verify/SKILL.md +66 -0
- package/dist-skill/library/forgecad-mujoco-verify/scripts/mujoco_verify.py +385 -0
- package/dist-skill/library/forgecad-spec-by-walking-through-it/SKILL.md +132 -0
- package/dist-skill/library/forgecad-visual-spec/SKILL.md +1 -1
- package/dist-skill/website/skills/forgecad-blockout-model.md +1 -1
- package/dist-skill/website/skills/forgecad-image-replicator.md +2 -2
- package/dist-skill/website/skills/forgecad-mujoco-verify.md +78 -0
- package/dist-skill/website/skills/forgecad-spec-by-walking-through-it.md +145 -0
- package/dist-skill/website/skills/forgecad-visual-spec.md +1 -1
- package/dist-skill/website/skills/forgecad.md +24 -24
- package/dist-skill/website/skills/index.md +2 -3
- package/examples/analysis/clearance-fit.forge.js +31 -0
- package/examples/analysis/lever-arm-actuator.forge.js +43 -0
- package/examples/analysis/tipping-tripod.forge.js +35 -0
- package/examples/products/sportscar.forge.js +77 -0
- package/package.json +1 -3
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +0 -101
- package/dist/docs-raw/skills/forgecad-lld.md +0 -41
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +0 -63
- package/dist-skill/library/forgecad-high-level-spec/SKILL.md +0 -94
- package/dist-skill/library/forgecad-lld/SKILL.md +0 -34
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +0 -50
- package/dist-skill/website/skills/forgecad-high-level-spec.md +0 -101
- package/dist-skill/website/skills/forgecad-lld.md +0 -41
- package/dist-skill/website/skills/forgecad-prepare-prompt.md +0 -63
- /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/default-profiles.md +0 -0
- /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/master-prompt.md +0 -0
|
@@ -10475,7 +10475,7 @@ async function initTruckGeometryWasm() {
|
|
|
10475
10475
|
if (_initPromise) return _initPromise;
|
|
10476
10476
|
_initPromise = (async () => {
|
|
10477
10477
|
try {
|
|
10478
|
-
const geometryModule = await import("./forgecad_geometry-
|
|
10478
|
+
const geometryModule = await import("./forgecad_geometry-2IMYCUWW.js");
|
|
10479
10479
|
const isNode = isNodeRuntime();
|
|
10480
10480
|
if (isNode) {
|
|
10481
10481
|
const { readFileSync: readFileSync3, existsSync: existsSync3 } = await import("fs");
|
|
@@ -11427,23 +11427,62 @@ var NurbsSurface = class {
|
|
|
11427
11427
|
return [wx / wSum, wy / wSum, wz / wSum];
|
|
11428
11428
|
}
|
|
11429
11429
|
/**
|
|
11430
|
-
* Evaluate the surface normal at (u, v)
|
|
11430
|
+
* Evaluate the surface unit normal at (u, v) from analytic first derivatives.
|
|
11431
|
+
*
|
|
11432
|
+
* Uses Algorithm A2.3 basis-function derivatives with the rational quotient
|
|
11433
|
+
* rule, so the normal is exact (no finite-difference epsilon, no error near
|
|
11434
|
+
* the boundary). Constant chain-rule factors from the parameter remap scale Su
|
|
11435
|
+
* and Sv positively and cancel under normalization, so they are omitted.
|
|
11431
11436
|
*/
|
|
11432
11437
|
normalAt(u, v) {
|
|
11433
|
-
const
|
|
11434
|
-
const
|
|
11435
|
-
const
|
|
11436
|
-
const
|
|
11437
|
-
const pv = this.pointAt(u, v1), pmv = this.pointAt(u, v0);
|
|
11438
|
-
const du = [pu[0] - pmu[0], pu[1] - pmu[1], pu[2] - pmu[2]];
|
|
11439
|
-
const dv = [pv[0] - pmv[0], pv[1] - pmv[1], pv[2] - pmv[2]];
|
|
11440
|
-
const nx = du[1] * dv[2] - du[2] * dv[1];
|
|
11441
|
-
const ny = du[2] * dv[0] - du[0] * dv[2];
|
|
11442
|
-
const nz = du[0] * dv[1] - du[1] * dv[0];
|
|
11438
|
+
const { Su, Sv } = this.derivativesAt(u, v);
|
|
11439
|
+
const nx = Su[1] * Sv[2] - Su[2] * Sv[1];
|
|
11440
|
+
const ny = Su[2] * Sv[0] - Su[0] * Sv[2];
|
|
11441
|
+
const nz = Su[0] * Sv[1] - Su[1] * Sv[0];
|
|
11443
11442
|
const len2 = Math.sqrt(nx * nx + ny * ny + nz * nz);
|
|
11444
11443
|
if (len2 < 1e-12) return [0, 0, 1];
|
|
11445
11444
|
return [nx / len2, ny / len2, nz / len2];
|
|
11446
11445
|
}
|
|
11446
|
+
/** Analytic first partial derivatives S_u, S_v (rational quotient rule). */
|
|
11447
|
+
derivativesAt(u, v) {
|
|
11448
|
+
const uu = this.remapU(Math.max(0, Math.min(1, u)));
|
|
11449
|
+
const vv = this.remapV(Math.max(0, Math.min(1, v)));
|
|
11450
|
+
const spanU = findSpan(this.nU, this.degreeU, uu, this.knotsU);
|
|
11451
|
+
const spanV = findSpan(this.nV, this.degreeV, vv, this.knotsV);
|
|
11452
|
+
const dU = basisFunsDeriv(spanU, uu, this.degreeU, this.knotsU, 1);
|
|
11453
|
+
const dV = basisFunsDeriv(spanV, vv, this.degreeV, this.knotsV, 1);
|
|
11454
|
+
const A = [0, 0, 0];
|
|
11455
|
+
const Au = [0, 0, 0];
|
|
11456
|
+
const Av = [0, 0, 0];
|
|
11457
|
+
let w = 0;
|
|
11458
|
+
let wu = 0;
|
|
11459
|
+
let wv = 0;
|
|
11460
|
+
for (let i = 0; i <= this.degreeU; i++) {
|
|
11461
|
+
const rowIdx = spanU - this.degreeU + i;
|
|
11462
|
+
for (let j = 0; j <= this.degreeV; j++) {
|
|
11463
|
+
const colIdx = spanV - this.degreeV + j;
|
|
11464
|
+
const weight = this.weightsGrid[rowIdx][colIdx];
|
|
11465
|
+
const pt = this.controlGrid[rowIdx][colIdx];
|
|
11466
|
+
const n00 = dU[0][i] * dV[0][j] * weight;
|
|
11467
|
+
const n10 = dU[1][i] * dV[0][j] * weight;
|
|
11468
|
+
const n01 = dU[0][i] * dV[1][j] * weight;
|
|
11469
|
+
w += n00;
|
|
11470
|
+
wu += n10;
|
|
11471
|
+
wv += n01;
|
|
11472
|
+
for (let c = 0; c < 3; c++) {
|
|
11473
|
+
A[c] += n00 * pt[c];
|
|
11474
|
+
Au[c] += n10 * pt[c];
|
|
11475
|
+
Av[c] += n01 * pt[c];
|
|
11476
|
+
}
|
|
11477
|
+
}
|
|
11478
|
+
}
|
|
11479
|
+
if (w === 0) return { S: [0, 0, 0], Su: [0, 0, 0], Sv: [0, 0, 0] };
|
|
11480
|
+
const invW = 1 / w;
|
|
11481
|
+
const S = [A[0] * invW, A[1] * invW, A[2] * invW];
|
|
11482
|
+
const Su = [(Au[0] - wu * S[0]) * invW, (Au[1] - wu * S[1]) * invW, (Au[2] - wu * S[2]) * invW];
|
|
11483
|
+
const Sv = [(Av[0] - wv * S[0]) * invW, (Av[1] - wv * S[1]) * invW, (Av[2] - wv * S[2]) * invW];
|
|
11484
|
+
return { S, Su, Sv };
|
|
11485
|
+
}
|
|
11447
11486
|
/**
|
|
11448
11487
|
* Tessellate the surface into a triangle mesh.
|
|
11449
11488
|
* Returns positions, normals, and triangle indices.
|
|
@@ -13889,6 +13928,20 @@ function fromSlicesSingleSliceHalfExtentForManifold(plan) {
|
|
|
13889
13928
|
}
|
|
13890
13929
|
return Math.max(1, radius + maxOffsetMagnitude + plan.boundsPadding + plan.edgeLength * 3);
|
|
13891
13930
|
}
|
|
13931
|
+
var BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG = 60;
|
|
13932
|
+
function promoteBooleanOperandNormals(shapes) {
|
|
13933
|
+
let maxExtra = 0;
|
|
13934
|
+
for (const shape of shapes) maxExtra = Math.max(maxExtra, shape.numProp());
|
|
13935
|
+
if (maxExtra < 3) return { operands: shapes, created: [] };
|
|
13936
|
+
const created = [];
|
|
13937
|
+
const operands = shapes.map((shape) => {
|
|
13938
|
+
if (shape.numProp() >= 3) return shape;
|
|
13939
|
+
const promoted = shape.calculateNormals(0, BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG);
|
|
13940
|
+
created.push(promoted);
|
|
13941
|
+
return promoted;
|
|
13942
|
+
});
|
|
13943
|
+
return { operands, created };
|
|
13944
|
+
}
|
|
13892
13945
|
function lowerShapeBooleanCompilePlan(plan, wasm) {
|
|
13893
13946
|
const shapes = plan.shapes.map((shape) => lowerShapeCompilePlanToManifold(shape, wasm));
|
|
13894
13947
|
if (shapes.length === 0) {
|
|
@@ -13897,16 +13950,18 @@ function lowerShapeBooleanCompilePlan(plan, wasm) {
|
|
|
13897
13950
|
if (shapes.length === 1) {
|
|
13898
13951
|
return shapes[0];
|
|
13899
13952
|
}
|
|
13953
|
+
const { operands, created } = promoteBooleanOperandNormals(shapes);
|
|
13900
13954
|
try {
|
|
13901
13955
|
switch (plan.op) {
|
|
13902
13956
|
case "union":
|
|
13903
|
-
return wasm.Manifold.union(
|
|
13957
|
+
return wasm.Manifold.union(operands);
|
|
13904
13958
|
case "difference":
|
|
13905
|
-
return wasm.Manifold.difference(
|
|
13959
|
+
return wasm.Manifold.difference(operands);
|
|
13906
13960
|
case "intersection":
|
|
13907
|
-
return wasm.Manifold.intersection(
|
|
13961
|
+
return wasm.Manifold.intersection(operands);
|
|
13908
13962
|
}
|
|
13909
13963
|
} finally {
|
|
13964
|
+
disposeWasmObjects(created);
|
|
13910
13965
|
disposeWasmObjects(shapes);
|
|
13911
13966
|
}
|
|
13912
13967
|
}
|
|
@@ -15258,12 +15313,16 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
|
|
|
15258
15313
|
const { positions, normals, indices } = surface.tessellate(res, res);
|
|
15259
15314
|
const thickness = plan.thickness;
|
|
15260
15315
|
const numVerts = positions.length;
|
|
15261
|
-
const
|
|
15262
|
-
for (const [x, y, z] of positions) allPositions.push(x, y, z);
|
|
15316
|
+
const vertProps = [];
|
|
15263
15317
|
for (let i = 0; i < numVerts; i++) {
|
|
15264
15318
|
const [x, y, z] = positions[i];
|
|
15265
15319
|
const [nx, ny, nz] = normals[i];
|
|
15266
|
-
|
|
15320
|
+
vertProps.push(x, y, z, nx, ny, nz);
|
|
15321
|
+
}
|
|
15322
|
+
for (let i = 0; i < numVerts; i++) {
|
|
15323
|
+
const [x, y, z] = positions[i];
|
|
15324
|
+
const [nx, ny, nz] = normals[i];
|
|
15325
|
+
vertProps.push(x - nx * thickness, y - ny * thickness, z - nz * thickness, -nx, -ny, -nz);
|
|
15267
15326
|
}
|
|
15268
15327
|
const allIndices = [];
|
|
15269
15328
|
for (const idx of indices) allIndices.push(idx);
|
|
@@ -15288,11 +15347,12 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
|
|
|
15288
15347
|
allIndices.push(c, c + numVerts, d, d, c + numVerts, d + numVerts);
|
|
15289
15348
|
}
|
|
15290
15349
|
const mesh = new wasm.Mesh({
|
|
15291
|
-
numProp:
|
|
15292
|
-
vertProperties: new Float32Array(
|
|
15350
|
+
numProp: 6,
|
|
15351
|
+
vertProperties: new Float32Array(vertProps),
|
|
15293
15352
|
triVerts: new Uint32Array(allIndices)
|
|
15294
15353
|
});
|
|
15295
15354
|
try {
|
|
15355
|
+
mesh.merge();
|
|
15296
15356
|
return new wasm.Manifold(mesh);
|
|
15297
15357
|
} finally {
|
|
15298
15358
|
disposeWasmObject(mesh);
|
|
@@ -37311,6 +37371,7 @@ var TruckShapeBackend = class _TruckShapeBackend {
|
|
|
37311
37371
|
const payload = JSON.parse(getTruckGeometryWasm().geometry_mesh(this.getLiveHandle("getMesh()")));
|
|
37312
37372
|
const numTri = payload.triangles.length / 3;
|
|
37313
37373
|
const numVert = payload.positions.length / 3;
|
|
37374
|
+
const cornerNormals = payload.normals && payload.normals.length === numTri * 9 ? new Float32Array(payload.normals) : void 0;
|
|
37314
37375
|
this.resource.mesh = {
|
|
37315
37376
|
numProp: 3,
|
|
37316
37377
|
numTri,
|
|
@@ -37324,7 +37385,8 @@ var TruckShapeBackend = class _TruckShapeBackend {
|
|
|
37324
37385
|
runTransform: new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]),
|
|
37325
37386
|
faceID: new Int32Array(payload.face_ids),
|
|
37326
37387
|
faceIdNames: payload.face_id_names ?? [],
|
|
37327
|
-
halfedgeTangent: new Float32Array(0)
|
|
37388
|
+
halfedgeTangent: new Float32Array(0),
|
|
37389
|
+
cornerNormals
|
|
37328
37390
|
};
|
|
37329
37391
|
return this.resource.mesh;
|
|
37330
37392
|
}
|
|
@@ -38540,6 +38602,16 @@ function boundsInteriorOverlap2(a, b) {
|
|
|
38540
38602
|
const tolerance = 1e-8;
|
|
38541
38603
|
return Math.min(a.max[0], b.max[0]) - Math.max(a.min[0], b.min[0]) > tolerance && Math.min(a.max[1], b.max[1]) - Math.max(a.min[1], b.min[1]) > tolerance && Math.min(a.max[2], b.max[2]) - Math.max(a.min[2], b.min[2]) > tolerance;
|
|
38542
38604
|
}
|
|
38605
|
+
function boundsFaceOrInteriorOverlap(a, b) {
|
|
38606
|
+
const tolerance = 1e-8;
|
|
38607
|
+
const overlaps = [
|
|
38608
|
+
Math.min(a.max[0], b.max[0]) - Math.max(a.min[0], b.min[0]),
|
|
38609
|
+
Math.min(a.max[1], b.max[1]) - Math.max(a.min[1], b.min[1]),
|
|
38610
|
+
Math.min(a.max[2], b.max[2]) - Math.max(a.min[2], b.min[2])
|
|
38611
|
+
];
|
|
38612
|
+
if (overlaps.some((overlap) => overlap < -tolerance)) return false;
|
|
38613
|
+
return overlaps.filter((overlap) => overlap <= tolerance).length <= 1;
|
|
38614
|
+
}
|
|
38543
38615
|
function boundsFromPoints3(points) {
|
|
38544
38616
|
const first = points[0];
|
|
38545
38617
|
if (!first) return null;
|
|
@@ -38622,16 +38694,16 @@ function shapePlanBounds(plan) {
|
|
|
38622
38694
|
return null;
|
|
38623
38695
|
}
|
|
38624
38696
|
}
|
|
38625
|
-
function
|
|
38697
|
+
function hasPairwiseFaceOrInteriorBoundsOverlap(shapes) {
|
|
38626
38698
|
const bounds = shapes.map((shape) => shape.boundingBox());
|
|
38627
38699
|
for (let i = 0; i < bounds.length; i++) {
|
|
38628
38700
|
for (let j = i + 1; j < bounds.length; j++) {
|
|
38629
|
-
if (
|
|
38701
|
+
if (boundsFaceOrInteriorOverlap(bounds[i], bounds[j])) return true;
|
|
38630
38702
|
}
|
|
38631
38703
|
}
|
|
38632
38704
|
return false;
|
|
38633
38705
|
}
|
|
38634
|
-
function
|
|
38706
|
+
function faceOrInteriorOverlapComponents(shapes) {
|
|
38635
38707
|
const bounds = shapes.map((shape) => shape.boundingBox());
|
|
38636
38708
|
const visited = /* @__PURE__ */ new Set();
|
|
38637
38709
|
const components = [];
|
|
@@ -38645,7 +38717,7 @@ function interiorOverlapComponents(shapes) {
|
|
|
38645
38717
|
component.push(current);
|
|
38646
38718
|
for (let next = 0; next < shapes.length; next++) {
|
|
38647
38719
|
if (visited.has(next)) continue;
|
|
38648
|
-
if (
|
|
38720
|
+
if (boundsFaceOrInteriorOverlap(bounds[current], bounds[next])) {
|
|
38649
38721
|
visited.add(next);
|
|
38650
38722
|
queue.push(next);
|
|
38651
38723
|
}
|
|
@@ -38690,10 +38762,10 @@ function lowerGenericBooleanPlan(plan) {
|
|
|
38690
38762
|
let returned = null;
|
|
38691
38763
|
try {
|
|
38692
38764
|
if (plan.op === "union") {
|
|
38693
|
-
if (!
|
|
38765
|
+
if (!hasPairwiseFaceOrInteriorBoundsOverlap(shapes)) {
|
|
38694
38766
|
return null;
|
|
38695
38767
|
}
|
|
38696
|
-
const components =
|
|
38768
|
+
const components = faceOrInteriorOverlapComponents(shapes);
|
|
38697
38769
|
if (components.length > 1) {
|
|
38698
38770
|
returned = lowerClusteredUnion(shapes, components);
|
|
38699
38771
|
return returned;
|
|
@@ -39219,9 +39291,13 @@ function shapeHasClosedNativeTopology(shape) {
|
|
|
39219
39291
|
function normalizeTruckShapeForBooleanInput(shape) {
|
|
39220
39292
|
if (shapeHasClosedNativeTopology(shape)) return shape;
|
|
39221
39293
|
if (!meshHasRawBoundaryEdges(shape.getMesh())) return shape;
|
|
39222
|
-
|
|
39223
|
-
|
|
39224
|
-
|
|
39294
|
+
try {
|
|
39295
|
+
const normalized = normalizeFacetedTruckShape(shape);
|
|
39296
|
+
disposeShapeBackend(shape);
|
|
39297
|
+
return normalized;
|
|
39298
|
+
} catch {
|
|
39299
|
+
return shape;
|
|
39300
|
+
}
|
|
39225
39301
|
}
|
|
39226
39302
|
function lowerSdfPlan(plan) {
|
|
39227
39303
|
if (getUnsupportedSdfProgramReason(plan.tree) === void 0) {
|
|
@@ -41105,7 +41181,7 @@ function circleProfilePlan(radius, segments) {
|
|
|
41105
41181
|
}
|
|
41106
41182
|
function annulusProfilePlan(outerRadius, innerRadius, segments) {
|
|
41107
41183
|
const outer = Math.abs(outerRadius);
|
|
41108
|
-
const inner = Math.
|
|
41184
|
+
const inner = Math.max(0, innerRadius);
|
|
41109
41185
|
if (outer <= EXACT_PROFILE_EPS) return emptyProfilePlan();
|
|
41110
41186
|
if (inner <= EXACT_PROFILE_EPS) return circleProfilePlan(outer, segments);
|
|
41111
41187
|
return {
|
|
@@ -52512,8 +52588,7 @@ function sculptLook(preset = "gallery") {
|
|
|
52512
52588
|
{ type: "directional", position: [90, -110, 150], color: "#ffffff", intensity: 1.4 },
|
|
52513
52589
|
{ type: "directional", position: [-120, 70, 80], color: "#dcecff", intensity: 0.65 },
|
|
52514
52590
|
{ type: "hemisphere", skyColor: "#d9edff", groundColor: "#f0e6d4", intensity: 0.45 }
|
|
52515
|
-
]
|
|
52516
|
-
postProcessing: { toneMappingExposure: 1.12, vignette: { darkness: 0.16, offset: 0.6 } }
|
|
52591
|
+
]
|
|
52517
52592
|
};
|
|
52518
52593
|
case "candy-shop":
|
|
52519
52594
|
return {
|
|
@@ -52524,8 +52599,7 @@ function sculptLook(preset = "gallery") {
|
|
|
52524
52599
|
{ type: "point", position: [70, -60, 90], color: "#ff8ac8", intensity: 2.2, distance: 280, decay: 1.2 },
|
|
52525
52600
|
{ type: "point", position: [-85, 80, 70], color: "#70e1ff", intensity: 1.8, distance: 260, decay: 1.4 },
|
|
52526
52601
|
{ type: "directional", position: [30, -80, 140], color: "#fff6dd", intensity: 0.9 }
|
|
52527
|
-
]
|
|
52528
|
-
postProcessing: { toneMappingExposure: 1.25, bloom: { intensity: 0.35, threshold: 0.78, radius: 0.45 } }
|
|
52602
|
+
]
|
|
52529
52603
|
};
|
|
52530
52604
|
case "midnight":
|
|
52531
52605
|
return {
|
|
@@ -52537,8 +52611,7 @@ function sculptLook(preset = "gallery") {
|
|
|
52537
52611
|
{ type: "point", position: [-90, 70, 50], color: "#ff7ac8", intensity: 1.4, distance: 300, decay: 1.3 },
|
|
52538
52612
|
{ type: "directional", position: [30, -30, 160], color: "#d7e9ff", intensity: 0.65 }
|
|
52539
52613
|
],
|
|
52540
|
-
fog: { color: "#060814", near: 180, far: 520 }
|
|
52541
|
-
postProcessing: { toneMappingExposure: 1.35, bloom: { intensity: 0.65, threshold: 0.65, radius: 0.6 } }
|
|
52614
|
+
fog: { color: "#060814", near: 180, far: 520 }
|
|
52542
52615
|
};
|
|
52543
52616
|
case "workbench":
|
|
52544
52617
|
return {
|
|
@@ -52549,8 +52622,7 @@ function sculptLook(preset = "gallery") {
|
|
|
52549
52622
|
{ type: "directional", position: [80, -100, 130], color: "#fff5df", intensity: 1.25 },
|
|
52550
52623
|
{ type: "hemisphere", skyColor: "#eef6ff", groundColor: "#d8d0c0", intensity: 0.35 }
|
|
52551
52624
|
],
|
|
52552
|
-
ground: { visible: true, color: "#d8d4ca", offset: 1, receiveShadow: true }
|
|
52553
|
-
postProcessing: { toneMappingExposure: 1.05 }
|
|
52625
|
+
ground: { visible: true, color: "#d8d4ca", offset: 1, receiveShadow: true }
|
|
52554
52626
|
};
|
|
52555
52627
|
default:
|
|
52556
52628
|
return {
|
|
@@ -52561,8 +52633,7 @@ function sculptLook(preset = "gallery") {
|
|
|
52561
52633
|
{ type: "directional", position: [110, -130, 150], color: "#ffffff", intensity: 1.5 },
|
|
52562
52634
|
{ type: "directional", position: [-90, 80, 90], color: "#b8d8ff", intensity: 0.55 },
|
|
52563
52635
|
{ type: "hemisphere", skyColor: "#ddefff", groundColor: "#e8e2d8", intensity: 0.45 }
|
|
52564
|
-
]
|
|
52565
|
-
postProcessing: { toneMappingExposure: 1.18, vignette: { darkness: 0.18, offset: 0.55 } }
|
|
52636
|
+
]
|
|
52566
52637
|
};
|
|
52567
52638
|
}
|
|
52568
52639
|
}
|
|
@@ -54395,7 +54466,7 @@ var Shape2 = class _Shape {
|
|
|
54395
54466
|
*
|
|
54396
54467
|
* Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves
|
|
54397
54468
|
* under light (metalness, roughness, clearcoat) and can add emissive glow independent of
|
|
54398
|
-
* lighting.
|
|
54469
|
+
* lighting.
|
|
54399
54470
|
*
|
|
54400
54471
|
* **Example**
|
|
54401
54472
|
*
|
|
@@ -59063,8 +59134,8 @@ var Assembly = class _Assembly {
|
|
|
59063
59134
|
*
|
|
59064
59135
|
* Use this after adding physical parts and joints. Robot-body profiles require
|
|
59065
59136
|
* `rootPart`; asset profiles can describe one-part or multi-part physical assets.
|
|
59066
|
-
* URDF/SDF exporters and `forgecad check simready` read this
|
|
59067
|
-
*
|
|
59137
|
+
* URDF/SDF/MJCF/USD exporters and `forgecad check simready` read this
|
|
59138
|
+
* contract directly from the returned assembly.
|
|
59068
59139
|
*
|
|
59069
59140
|
* @category Assembly
|
|
59070
59141
|
*/
|
|
@@ -70553,7 +70624,8 @@ var FrozenShape = class extends Shape2 {
|
|
|
70553
70624
|
var EDGE_THRESHOLD_DOT2 = Math.cos(Math.PI / 180);
|
|
70554
70625
|
var SMOOTH_THRESHOLD_DOT = Math.cos(30 * Math.PI / 180);
|
|
70555
70626
|
function computeGeometryArrays(mesh, options = {}) {
|
|
70556
|
-
const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals } = mesh;
|
|
70627
|
+
const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals, cornerNormals } = mesh;
|
|
70628
|
+
const useCornerNormals = !!cornerNormals && cornerNormals.length === triCount * 9;
|
|
70557
70629
|
const positions = new Float32Array(triCount * 9);
|
|
70558
70630
|
const normals = new Float32Array(triCount * 9);
|
|
70559
70631
|
const faceNx = new Float32Array(triCount);
|
|
@@ -70585,7 +70657,9 @@ function computeGeometryArrays(mesh, options = {}) {
|
|
|
70585
70657
|
positions[o + 6] = cx;
|
|
70586
70658
|
positions[o + 7] = cy;
|
|
70587
70659
|
positions[o + 8] = cz;
|
|
70588
|
-
if (
|
|
70660
|
+
if (useCornerNormals) {
|
|
70661
|
+
for (let k = 0; k < 9; k++) normals[o + k] = cornerNormals[o + k];
|
|
70662
|
+
} else if (vertNormals) {
|
|
70589
70663
|
normals[o] = vertNormals[i0 * 3];
|
|
70590
70664
|
normals[o + 1] = vertNormals[i0 * 3 + 1];
|
|
70591
70665
|
normals[o + 2] = vertNormals[i0 * 3 + 2];
|
|
@@ -70628,7 +70702,7 @@ function computeGeometryArrays(mesh, options = {}) {
|
|
|
70628
70702
|
faceNy[t] = fny;
|
|
70629
70703
|
faceNz[t] = fnz;
|
|
70630
70704
|
}
|
|
70631
|
-
if (!vertNormals && numProp < 6 && triCount > 0) {
|
|
70705
|
+
if (!useCornerNormals && !vertNormals && numProp < 6 && triCount > 0) {
|
|
70632
70706
|
computeAutoSmoothNormals(
|
|
70633
70707
|
triVerts,
|
|
70634
70708
|
vertProperties,
|
|
@@ -70817,7 +70891,8 @@ function shapeToGeometryFallback(shape) {
|
|
|
70817
70891
|
vertProperties: mesh.vertProperties,
|
|
70818
70892
|
mergeFromVert: mesh.mergeFromVert,
|
|
70819
70893
|
mergeToVert: mesh.mergeToVert,
|
|
70820
|
-
vertNormals
|
|
70894
|
+
vertNormals,
|
|
70895
|
+
cornerNormals: mesh.cornerNormals && mesh.cornerNormals.length === mesh.numTri * 9 ? mesh.cornerNormals : void 0
|
|
70821
70896
|
});
|
|
70822
70897
|
const solid = new BufferGeometry();
|
|
70823
70898
|
solid.setAttribute("position", new BufferAttribute(positions, 3));
|
|
@@ -72464,31 +72539,6 @@ function validateFog(fog, label) {
|
|
|
72464
72539
|
if (fog.density !== void 0) out.density = requireFinite7(fog.density, `${label}.density`);
|
|
72465
72540
|
return out;
|
|
72466
72541
|
}
|
|
72467
|
-
function validatePostProcessing(pp, label) {
|
|
72468
|
-
const out = {};
|
|
72469
|
-
if (pp.bloom !== void 0) {
|
|
72470
|
-
if (!pp.bloom || typeof pp.bloom !== "object") throw new Error(`${label}.bloom must be an object`);
|
|
72471
|
-
out.bloom = {};
|
|
72472
|
-
if (pp.bloom.intensity !== void 0) out.bloom.intensity = requireFinite7(pp.bloom.intensity, `${label}.bloom.intensity`);
|
|
72473
|
-
if (pp.bloom.threshold !== void 0) out.bloom.threshold = requireFinite7(pp.bloom.threshold, `${label}.bloom.threshold`);
|
|
72474
|
-
if (pp.bloom.radius !== void 0) out.bloom.radius = requireFinite7(pp.bloom.radius, `${label}.bloom.radius`);
|
|
72475
|
-
}
|
|
72476
|
-
if (pp.vignette !== void 0) {
|
|
72477
|
-
if (!pp.vignette || typeof pp.vignette !== "object") throw new Error(`${label}.vignette must be an object`);
|
|
72478
|
-
out.vignette = {};
|
|
72479
|
-
if (pp.vignette.darkness !== void 0) out.vignette.darkness = requireFinite7(pp.vignette.darkness, `${label}.vignette.darkness`);
|
|
72480
|
-
if (pp.vignette.offset !== void 0) out.vignette.offset = requireFinite7(pp.vignette.offset, `${label}.vignette.offset`);
|
|
72481
|
-
}
|
|
72482
|
-
if (pp.grain !== void 0) {
|
|
72483
|
-
if (!pp.grain || typeof pp.grain !== "object") throw new Error(`${label}.grain must be an object`);
|
|
72484
|
-
out.grain = {};
|
|
72485
|
-
if (pp.grain.intensity !== void 0) out.grain.intensity = requireFinite7(pp.grain.intensity, `${label}.grain.intensity`);
|
|
72486
|
-
}
|
|
72487
|
-
if (pp.toneMappingExposure !== void 0) {
|
|
72488
|
-
out.toneMappingExposure = requireFinite7(pp.toneMappingExposure, `${label}.toneMappingExposure`);
|
|
72489
|
-
}
|
|
72490
|
-
return out;
|
|
72491
|
-
}
|
|
72492
72542
|
function validateGround(ground, label) {
|
|
72493
72543
|
const out = {};
|
|
72494
72544
|
if (ground.visible !== void 0) {
|
|
@@ -72570,7 +72620,6 @@ function scene(options) {
|
|
|
72570
72620
|
lights: null,
|
|
72571
72621
|
environment: null,
|
|
72572
72622
|
fog: null,
|
|
72573
|
-
postProcessing: null,
|
|
72574
72623
|
ground: null,
|
|
72575
72624
|
capture: null
|
|
72576
72625
|
};
|
|
@@ -72610,12 +72659,9 @@ function scene(options) {
|
|
|
72610
72659
|
}
|
|
72611
72660
|
current.fog = validateFog(options.fog, "scene.fog");
|
|
72612
72661
|
}
|
|
72613
|
-
|
|
72614
|
-
|
|
72615
|
-
|
|
72616
|
-
}
|
|
72617
|
-
const validated = validatePostProcessing(options.postProcessing, "scene.postProcessing");
|
|
72618
|
-
current.postProcessing = current.postProcessing ? { ...current.postProcessing, ...validated } : validated;
|
|
72662
|
+
const disabledPostProcessing = options.postProcessing;
|
|
72663
|
+
if (disabledPostProcessing !== void 0) {
|
|
72664
|
+
console.warn("scene.postProcessing is disabled for now while the browser post-processing path is being rebuilt.");
|
|
72619
72665
|
}
|
|
72620
72666
|
if (options.ground !== void 0) {
|
|
72621
72667
|
if (!options.ground || typeof options.ground !== "object") {
|
|
@@ -73518,8 +73564,7 @@ function scenePreset(name) {
|
|
|
73518
73564
|
{ type: "hemisphere", skyColor: "#ffffff", groundColor: "#d5d9de", intensity: 0.75 },
|
|
73519
73565
|
{ type: "directional", position: [90, -120, 180], color: "#ffffff", intensity: 1.8, castShadow: true },
|
|
73520
73566
|
{ type: "directional", position: [-140, 100, 80], color: "#dfe9ff", intensity: 0.55 }
|
|
73521
|
-
]
|
|
73522
|
-
postProcessing: { toneMappingExposure: 1.08 }
|
|
73567
|
+
]
|
|
73523
73568
|
});
|
|
73524
73569
|
return;
|
|
73525
73570
|
}
|
|
@@ -73765,8 +73810,8 @@ function shapeBoundsCenter(shape) {
|
|
|
73765
73810
|
];
|
|
73766
73811
|
}
|
|
73767
73812
|
var ProductSurfaceRef = class _ProductSurfaceRef {
|
|
73768
|
-
constructor(
|
|
73769
|
-
this.skin =
|
|
73813
|
+
constructor(skin2, query, name) {
|
|
73814
|
+
this.skin = skin2;
|
|
73770
73815
|
this.query = query;
|
|
73771
73816
|
this.name = name;
|
|
73772
73817
|
}
|
|
@@ -74283,8 +74328,8 @@ var ProductHandleBuilder = class {
|
|
|
74283
74328
|
}
|
|
74284
74329
|
};
|
|
74285
74330
|
var ProductSurfaceBuilder = class {
|
|
74286
|
-
constructor(
|
|
74287
|
-
this.skin =
|
|
74331
|
+
constructor(skin2, side) {
|
|
74332
|
+
this.skin = skin2;
|
|
74288
74333
|
this.side = side;
|
|
74289
74334
|
}
|
|
74290
74335
|
/** Create a ref on this skin side. */
|
|
@@ -74352,9 +74397,9 @@ var ProductRibbonBuilder = class {
|
|
|
74352
74397
|
* ProductSkin.frame(), so the ribbon bends along the selected side as station width/depth changes.
|
|
74353
74398
|
* All query path points must stay on one side; split side transitions into separate ribbons.
|
|
74354
74399
|
*/
|
|
74355
|
-
on(
|
|
74400
|
+
on(skin2, points, options = {}) {
|
|
74356
74401
|
if (points.length < 2) throw new Error("Product.ribbon().on(skin, points) requires at least two path points");
|
|
74357
|
-
this.skinValue =
|
|
74402
|
+
this.skinValue = skin2;
|
|
74358
74403
|
this.queryPath = resolvePathQueries(points);
|
|
74359
74404
|
this.refPath = [];
|
|
74360
74405
|
return this.applyOptions(options);
|
|
@@ -74477,7 +74522,7 @@ var ProductRibbonBuilder = class {
|
|
|
74477
74522
|
const localT = scaled - segment;
|
|
74478
74523
|
return interpolateQuery(this.queryPath[segment], this.queryPath[segment + 1], localT);
|
|
74479
74524
|
}
|
|
74480
|
-
buildSkinGrid(
|
|
74525
|
+
buildSkinGrid(skin2, path2) {
|
|
74481
74526
|
if (path2.length < 2) throw new Error("Product.ribbon().on(skin, points) must be called before .build()");
|
|
74482
74527
|
const side = normalizedSide(path2[0].side);
|
|
74483
74528
|
if (side === "front" || side === "rear") {
|
|
@@ -74496,7 +74541,7 @@ var ProductRibbonBuilder = class {
|
|
|
74496
74541
|
for (let i = 0; i < this.samplesValue; i += 1) {
|
|
74497
74542
|
const along = this.samplesValue === 1 ? 0 : i / (this.samplesValue - 1);
|
|
74498
74543
|
const center = this.samplePathQuery(along);
|
|
74499
|
-
const station =
|
|
74544
|
+
const station = skin2.stationAt(center.v ?? 0.5);
|
|
74500
74545
|
const span = sideSpan(side, station.width, station.depth);
|
|
74501
74546
|
for (let j = 0; j < this.widthSamplesValue; j += 1) {
|
|
74502
74547
|
const across = this.widthSamplesValue === 1 ? 0 : j / (this.widthSamplesValue - 1) - 0.5;
|
|
@@ -74513,13 +74558,13 @@ var ProductRibbonBuilder = class {
|
|
|
74513
74558
|
u,
|
|
74514
74559
|
offset: (center.offset ?? 0) + this.offsetValue + this.thicknessValue
|
|
74515
74560
|
};
|
|
74516
|
-
rows[j].push(
|
|
74561
|
+
rows[j].push(skin2.frame(query).point);
|
|
74517
74562
|
}
|
|
74518
74563
|
}
|
|
74519
74564
|
return {
|
|
74520
74565
|
grid: rows,
|
|
74521
74566
|
diagnostics: this.makeDiagnostics({
|
|
74522
|
-
skin:
|
|
74567
|
+
skin: skin2.name,
|
|
74523
74568
|
side,
|
|
74524
74569
|
pathPointCount: path2.length,
|
|
74525
74570
|
clampedUCount,
|
|
@@ -74676,16 +74721,16 @@ var Product = {
|
|
|
74676
74721
|
return scaleProfileTo(sketch, width, depth);
|
|
74677
74722
|
},
|
|
74678
74723
|
/** Create an ad-hoc ProductSurfaceRef from a skin and side/u/v query. */
|
|
74679
|
-
ref(
|
|
74680
|
-
return new ProductSurfaceRef(
|
|
74724
|
+
ref(skin2, query) {
|
|
74725
|
+
return new ProductSurfaceRef(skin2, query);
|
|
74681
74726
|
},
|
|
74682
74727
|
/**
|
|
74683
74728
|
* Create a fluent surface helper for refs and conformal features on one side of a skin.
|
|
74684
74729
|
*
|
|
74685
74730
|
* Equivalent to skin.surface(side), useful when writing in Product.* namespace style.
|
|
74686
74731
|
*/
|
|
74687
|
-
surface(
|
|
74688
|
-
return
|
|
74732
|
+
surface(skin2, side) {
|
|
74733
|
+
return skin2.surface(side);
|
|
74689
74734
|
},
|
|
74690
74735
|
/** Start a panel feature builder. */
|
|
74691
74736
|
panel(name) {
|
|
@@ -75166,8 +75211,8 @@ function coordinateOnSide(coordinate, side, label) {
|
|
|
75166
75211
|
return { ...coordinate, kind: "productSkin", side };
|
|
75167
75212
|
}
|
|
75168
75213
|
var ProductSkinCarrier = class _ProductSkinCarrier {
|
|
75169
|
-
constructor(
|
|
75170
|
-
this.skin =
|
|
75214
|
+
constructor(skin2, name = skin2.name, sideValue2, offsetValue = 0) {
|
|
75215
|
+
this.skin = skin2;
|
|
75171
75216
|
this.name = name;
|
|
75172
75217
|
this.sideValue = sideValue2;
|
|
75173
75218
|
this.offsetValue = offsetValue;
|
|
@@ -78108,8 +78153,8 @@ var Carrier = {
|
|
|
78108
78153
|
return new PlaneCarrier(name);
|
|
78109
78154
|
},
|
|
78110
78155
|
/** Adapt an existing ProductSkin into the general surface-member carrier protocol. */
|
|
78111
|
-
productSkin(
|
|
78112
|
-
return new ProductSkinCarrier(
|
|
78156
|
+
productSkin(skin2) {
|
|
78157
|
+
return new ProductSkinCarrier(skin2);
|
|
78113
78158
|
},
|
|
78114
78159
|
/** Reserved stub for future parameterized mesh carriers; arbitrary mesh parameterization is not implemented yet. */
|
|
78115
78160
|
mesh(name) {
|
|
@@ -78829,9 +78874,9 @@ function pickDimensionOffsetBasis(dirModel, frame) {
|
|
|
78829
78874
|
];
|
|
78830
78875
|
const candidates = [];
|
|
78831
78876
|
const pushCandidate = (candidate) => {
|
|
78832
|
-
const
|
|
78833
|
-
if (
|
|
78834
|
-
const dir3 = [candidate[0] /
|
|
78877
|
+
const len35 = Math.hypot(candidate[0], candidate[1], candidate[2]);
|
|
78878
|
+
if (len35 < 1e-8) return;
|
|
78879
|
+
const dir3 = [candidate[0] / len35, candidate[1] / len35, candidate[2] / len35];
|
|
78835
78880
|
const proj = projectVectorToView(dir3, frame);
|
|
78836
78881
|
const projLen = Math.hypot(proj[0], proj[1]);
|
|
78837
78882
|
if (projLen < 1e-6) return;
|
|
@@ -80010,8 +80055,7 @@ function generateReportPdf(result, options = {}) {
|
|
|
80010
80055
|
};
|
|
80011
80056
|
}
|
|
80012
80057
|
|
|
80013
|
-
// src/forge/export/
|
|
80014
|
-
var _collectedRobotExport = null;
|
|
80058
|
+
// src/forge/export/simulationModel.ts
|
|
80015
80059
|
function cloneLinkOptions(input) {
|
|
80016
80060
|
if (!input) return {};
|
|
80017
80061
|
return Object.fromEntries(Object.entries(input).map(([name, opts]) => [name, { ...opts }]));
|
|
@@ -80098,12 +80142,6 @@ function diffDriveFromControllers(controllers) {
|
|
|
80098
80142
|
angularAcceleration: diffDrive2.angularAcceleration
|
|
80099
80143
|
} : void 0;
|
|
80100
80144
|
}
|
|
80101
|
-
function resetRobotExport() {
|
|
80102
|
-
_collectedRobotExport = null;
|
|
80103
|
-
}
|
|
80104
|
-
function getCollectedRobotExport() {
|
|
80105
|
-
return _collectedRobotExport;
|
|
80106
|
-
}
|
|
80107
80145
|
function collectSimulationModel(assemblyInput, options = {}) {
|
|
80108
80146
|
const assembly2 = typeof assemblyInput.describe === "function" ? assemblyInput.describe() : assemblyInput;
|
|
80109
80147
|
const partNames = new Set(assembly2.parts.map((part) => part.name));
|
|
@@ -80186,7 +80224,6 @@ function collectSimulationModel(assemblyInput, options = {}) {
|
|
|
80186
80224
|
modelName: (options.modelName ?? assembly2.name ?? "ForgeCAD Simulation").trim() || "ForgeCAD Simulation",
|
|
80187
80225
|
assembly: assembly2,
|
|
80188
80226
|
simulation,
|
|
80189
|
-
source: options.source ?? "assembly",
|
|
80190
80227
|
state: { ...options.state ?? {} },
|
|
80191
80228
|
static: options.static ?? false,
|
|
80192
80229
|
selfCollide: options.selfCollide ?? false,
|
|
@@ -80200,24 +80237,11 @@ function collectSimulationModel(assemblyInput, options = {}) {
|
|
|
80200
80237
|
world
|
|
80201
80238
|
};
|
|
80202
80239
|
}
|
|
80203
|
-
function robotExport(options) {
|
|
80204
|
-
if (!options || typeof options !== "object") {
|
|
80205
|
-
throw new Error("robotExport(...) expects an options object");
|
|
80206
|
-
}
|
|
80207
|
-
if (!options.assembly || typeof options.assembly.describe !== "function") {
|
|
80208
|
-
throw new Error("robotExport(...) requires an assembly");
|
|
80209
|
-
}
|
|
80210
|
-
_collectedRobotExport = collectSimulationModel(options.assembly, { ...options, source: "robotExport" });
|
|
80211
|
-
return _collectedRobotExport;
|
|
80212
|
-
}
|
|
80213
80240
|
|
|
80214
80241
|
// src/forge/export/simreadyValidation.ts
|
|
80215
80242
|
function error(findings, code, message, path2) {
|
|
80216
80243
|
findings.push({ level: "error", code, message, path: path2 });
|
|
80217
80244
|
}
|
|
80218
|
-
function warn(findings, code, message, path2) {
|
|
80219
|
-
findings.push({ level: "warning", code, message, path: path2 });
|
|
80220
|
-
}
|
|
80221
80245
|
function isFiniteNumber3(value) {
|
|
80222
80246
|
return value === void 0 || Number.isFinite(value);
|
|
80223
80247
|
}
|
|
@@ -80242,13 +80266,13 @@ function profileRequiresExplicitCollider(model) {
|
|
|
80242
80266
|
}
|
|
80243
80267
|
function validatePart(findings, model, part) {
|
|
80244
80268
|
const link = model.links[part.name];
|
|
80245
|
-
const
|
|
80269
|
+
const hasPhysicalData = link?.massKg !== void 0 || link?.densityKgM3 !== void 0;
|
|
80246
80270
|
const path2 = `parts.${part.name}`;
|
|
80247
|
-
if (!part.sim && !
|
|
80271
|
+
if (!part.sim && !hasPhysicalData) {
|
|
80248
80272
|
error(
|
|
80249
80273
|
findings,
|
|
80250
80274
|
"SIM.BODY.MISSING",
|
|
80251
|
-
`Part "${part.name}" needs Sim.body(...) metadata
|
|
80275
|
+
`Part "${part.name}" needs Sim.body(...) metadata with massKg or densityKgM3.`,
|
|
80252
80276
|
path2
|
|
80253
80277
|
);
|
|
80254
80278
|
return;
|
|
@@ -80383,17 +80407,9 @@ function validateWorld(findings, model) {
|
|
|
80383
80407
|
function validateSimulationModel(model) {
|
|
80384
80408
|
const findings = [];
|
|
80385
80409
|
const simulation = model.simulation;
|
|
80386
|
-
if (!simulation
|
|
80410
|
+
if (!simulation) {
|
|
80387
80411
|
error(findings, "SIM.SIMULATION.MISSING", "Model must return assembly(...).withSimulation(...).", "sim");
|
|
80388
80412
|
}
|
|
80389
|
-
if (!simulation && model.source === "robotExport") {
|
|
80390
|
-
warn(
|
|
80391
|
-
findings,
|
|
80392
|
-
"SIM.SIMULATION.LEGACY_ROBOT_EXPORT",
|
|
80393
|
-
"Legacy robotExport(...) metadata is accepted for compatibility; prefer assembly.withSimulation(...).",
|
|
80394
|
-
"sim"
|
|
80395
|
-
);
|
|
80396
|
-
}
|
|
80397
80413
|
if (simulation && !simulation.profile) {
|
|
80398
80414
|
error(findings, "SIM.PROFILE.MISSING", "Simulation contract needs a Sim.profile.*() value.", "sim.profile");
|
|
80399
80415
|
}
|
|
@@ -80438,7 +80454,7 @@ function materialManifest(material2) {
|
|
|
80438
80454
|
}
|
|
80439
80455
|
function bodyPhysicalSource(sim, link) {
|
|
80440
80456
|
if (sim) return "sim";
|
|
80441
|
-
if (link?.massKg !== void 0 || link?.densityKgM3 !== void 0 || link?.collision !== void 0) return "
|
|
80457
|
+
if (link?.massKg !== void 0 || link?.densityKgM3 !== void 0 || link?.collision !== void 0) return "metadata";
|
|
80442
80458
|
return "missing";
|
|
80443
80459
|
}
|
|
80444
80460
|
function bodyManifest(model) {
|
|
@@ -80508,7 +80524,7 @@ function buildSimReadyManifest(model) {
|
|
|
80508
80524
|
version: 1,
|
|
80509
80525
|
modelName: model.modelName,
|
|
80510
80526
|
sourceModelName: model.assembly.name,
|
|
80511
|
-
source:
|
|
80527
|
+
source: "assembly",
|
|
80512
80528
|
profile: profile2?.name ?? null,
|
|
80513
80529
|
profileVersion: "1.0.0",
|
|
80514
80530
|
units: {
|
|
@@ -90816,6 +90832,849 @@ Sketch.prototype.onFace = function(parentOrFace, faceOrOpts, maybeOpts = {}) {
|
|
|
90816
90832
|
return sketchOnFace(this, parentOrFace, faceOrOpts, maybeOpts);
|
|
90817
90833
|
};
|
|
90818
90834
|
|
|
90835
|
+
// src/forge/sketch/nurbsSurfaceFit.ts
|
|
90836
|
+
var KNOT_EPS = 1e-10;
|
|
90837
|
+
var KNOT_MERGE_EPS = 1e-7;
|
|
90838
|
+
function sameKnot2(a, b) {
|
|
90839
|
+
return Math.abs(a - b) <= KNOT_EPS;
|
|
90840
|
+
}
|
|
90841
|
+
function knotMultiplicity2(knots, u) {
|
|
90842
|
+
let count = 0;
|
|
90843
|
+
for (const k of knots) if (sameKnot2(k, u)) count++;
|
|
90844
|
+
return count;
|
|
90845
|
+
}
|
|
90846
|
+
function computeParamsCentripetal(points, alpha = 0.5) {
|
|
90847
|
+
const n = points.length;
|
|
90848
|
+
const params = new Array(n).fill(0);
|
|
90849
|
+
const seg = new Array(n).fill(0);
|
|
90850
|
+
let total = 0;
|
|
90851
|
+
for (let k = 1; k < n; k++) {
|
|
90852
|
+
const dx = points[k][0] - points[k - 1][0];
|
|
90853
|
+
const dy = points[k][1] - points[k - 1][1];
|
|
90854
|
+
const dz = points[k][2] - points[k - 1][2];
|
|
90855
|
+
seg[k] = Math.hypot(dx, dy, dz) ** alpha;
|
|
90856
|
+
total += seg[k];
|
|
90857
|
+
}
|
|
90858
|
+
if (total === 0) {
|
|
90859
|
+
for (let k = 0; k < n; k++) params[k] = n > 1 ? k / (n - 1) : 0;
|
|
90860
|
+
return params;
|
|
90861
|
+
}
|
|
90862
|
+
let acc = 0;
|
|
90863
|
+
for (let k = 1; k < n - 1; k++) {
|
|
90864
|
+
acc += seg[k];
|
|
90865
|
+
params[k] = acc / total;
|
|
90866
|
+
}
|
|
90867
|
+
params[n - 1] = 1;
|
|
90868
|
+
return params;
|
|
90869
|
+
}
|
|
90870
|
+
function knotsFromParamsAveraging(params, degree) {
|
|
90871
|
+
const n = params.length - 1;
|
|
90872
|
+
const m = n + degree + 1;
|
|
90873
|
+
const knots = new Array(m + 1).fill(0);
|
|
90874
|
+
for (let i = m - degree; i <= m; i++) knots[i] = 1;
|
|
90875
|
+
for (let j = 1; j <= n - degree; j++) {
|
|
90876
|
+
let s = 0;
|
|
90877
|
+
for (let i = j; i <= j + degree - 1; i++) s += params[i];
|
|
90878
|
+
knots[j + degree] = s / degree;
|
|
90879
|
+
}
|
|
90880
|
+
return knots;
|
|
90881
|
+
}
|
|
90882
|
+
function solveLinear(A, B) {
|
|
90883
|
+
const n = A.length;
|
|
90884
|
+
const k = B[0].length;
|
|
90885
|
+
const M = A.map((row, i) => [...row, ...B[i]]);
|
|
90886
|
+
for (let col = 0; col < n; col++) {
|
|
90887
|
+
let piv = col;
|
|
90888
|
+
for (let r = col + 1; r < n; r++) if (Math.abs(M[r][col]) > Math.abs(M[piv][col])) piv = r;
|
|
90889
|
+
if (Math.abs(M[piv][col]) < 1e-14) throw new Error(`Singular interpolation matrix at column ${col}.`);
|
|
90890
|
+
[M[col], M[piv]] = [M[piv], M[col]];
|
|
90891
|
+
const inv = 1 / M[col][col];
|
|
90892
|
+
for (let j = col; j < n + k; j++) M[col][j] *= inv;
|
|
90893
|
+
for (let r = 0; r < n; r++) {
|
|
90894
|
+
if (r === col) continue;
|
|
90895
|
+
const f4 = M[r][col];
|
|
90896
|
+
if (f4 === 0) continue;
|
|
90897
|
+
for (let j = col; j < n + k; j++) M[r][j] -= f4 * M[col][j];
|
|
90898
|
+
}
|
|
90899
|
+
}
|
|
90900
|
+
return Array.from({ length: n }, (_, i) => M[i].slice(n));
|
|
90901
|
+
}
|
|
90902
|
+
function globalCurveInterp(points, degree, paramsIn) {
|
|
90903
|
+
const last = points.length - 1;
|
|
90904
|
+
if (last < degree) throw new Error(`Curve interpolation needs at least ${degree + 1} points, got ${points.length}.`);
|
|
90905
|
+
const params = paramsIn ? paramsIn.slice() : computeParamsCentripetal(points, 0.5);
|
|
90906
|
+
const knots = knotsFromParamsAveraging(params, degree);
|
|
90907
|
+
const count = last + 1;
|
|
90908
|
+
const N = Array.from({ length: count }, () => new Array(count).fill(0));
|
|
90909
|
+
for (let i = 0; i <= last; i++) {
|
|
90910
|
+
const span = findSpan(count, degree, params[i], knots);
|
|
90911
|
+
const basis = basisFuns(span, params[i], degree, knots);
|
|
90912
|
+
for (let j = 0; j <= degree; j++) N[i][span - degree + j] = basis[j];
|
|
90913
|
+
}
|
|
90914
|
+
const Q = points.map((p2) => [p2[0], p2[1], p2[2]]);
|
|
90915
|
+
const cps = solveLinear(N, Q);
|
|
90916
|
+
return { cps, knots, degree };
|
|
90917
|
+
}
|
|
90918
|
+
function insertKnotCurve(cps, knots, degree, u) {
|
|
90919
|
+
const n = cps.length;
|
|
90920
|
+
const span = findSpan(n, degree, u, knots);
|
|
90921
|
+
const mult = knotMultiplicity2(knots, u);
|
|
90922
|
+
if (mult >= degree) return { cps: cps.slice(), knots: knots.slice() };
|
|
90923
|
+
const newCps = new Array(n + 1);
|
|
90924
|
+
const newKnots = new Array(knots.length + 1);
|
|
90925
|
+
for (let i = 0; i <= span; i++) newKnots[i] = knots[i];
|
|
90926
|
+
newKnots[span + 1] = u;
|
|
90927
|
+
for (let i = span + 1; i < knots.length; i++) newKnots[i + 1] = knots[i];
|
|
90928
|
+
for (let i = 0; i <= span - degree; i++) newCps[i] = [...cps[i]];
|
|
90929
|
+
for (let i = span - mult; i < n; i++) newCps[i + 1] = [...cps[i]];
|
|
90930
|
+
for (let i = span - degree + 1; i <= span - mult; i++) {
|
|
90931
|
+
const denom = knots[i + degree] - knots[i];
|
|
90932
|
+
const alpha = denom === 0 ? 0 : (u - knots[i]) / denom;
|
|
90933
|
+
const a = cps[i - 1];
|
|
90934
|
+
const b = cps[i];
|
|
90935
|
+
newCps[i] = [(1 - alpha) * a[0] + alpha * b[0], (1 - alpha) * a[1] + alpha * b[1], (1 - alpha) * a[2] + alpha * b[2]];
|
|
90936
|
+
}
|
|
90937
|
+
return { cps: newCps, knots: newKnots };
|
|
90938
|
+
}
|
|
90939
|
+
function interiorKnotBuckets(kv, mergeEps = KNOT_MERGE_EPS) {
|
|
90940
|
+
const buckets = [];
|
|
90941
|
+
for (const k of kv) {
|
|
90942
|
+
if (k <= KNOT_EPS || k >= 1 - KNOT_EPS) continue;
|
|
90943
|
+
const b = buckets.find((x) => Math.abs(x.value - k) <= mergeEps);
|
|
90944
|
+
if (b) b.mult++;
|
|
90945
|
+
else buckets.push({ value: k, mult: 1 });
|
|
90946
|
+
}
|
|
90947
|
+
return buckets;
|
|
90948
|
+
}
|
|
90949
|
+
function mergeKnotVectors(knotVectors, degree, mergeEps = KNOT_MERGE_EPS) {
|
|
90950
|
+
const merged = [];
|
|
90951
|
+
for (const kv of knotVectors) {
|
|
90952
|
+
for (const { value, mult } of interiorKnotBuckets(kv, mergeEps)) {
|
|
90953
|
+
const b = merged.find((x) => Math.abs(x.value - value) <= mergeEps);
|
|
90954
|
+
if (b) b.mult = Math.max(b.mult, mult);
|
|
90955
|
+
else merged.push({ value, mult });
|
|
90956
|
+
}
|
|
90957
|
+
}
|
|
90958
|
+
merged.sort((a, b) => a.value - b.value);
|
|
90959
|
+
const out = [];
|
|
90960
|
+
for (let i = 0; i <= degree; i++) out.push(0);
|
|
90961
|
+
for (const { value, mult } of merged) for (let i = 0; i < mult; i++) out.push(value);
|
|
90962
|
+
for (let i = 0; i <= degree; i++) out.push(1);
|
|
90963
|
+
return out;
|
|
90964
|
+
}
|
|
90965
|
+
function refineKnotsToTarget(cps, knots, degree, targetKnots, mergeEps = KNOT_MERGE_EPS) {
|
|
90966
|
+
let curCps = cps.map((p2) => [...p2]);
|
|
90967
|
+
let curKnots = knots.slice();
|
|
90968
|
+
for (const { value, mult } of interiorKnotBuckets(targetKnots, mergeEps)) {
|
|
90969
|
+
const curMult = () => curKnots.reduce((c, k) => c + (Math.abs(k - value) <= mergeEps ? 1 : 0), 0);
|
|
90970
|
+
let guard = 0;
|
|
90971
|
+
while (curMult() < mult) {
|
|
90972
|
+
const r = insertKnotCurve(curCps, curKnots, degree, value);
|
|
90973
|
+
if (r.cps.length === curCps.length) break;
|
|
90974
|
+
curCps = r.cps;
|
|
90975
|
+
curKnots = r.knots;
|
|
90976
|
+
if (++guard > degree + 2) break;
|
|
90977
|
+
}
|
|
90978
|
+
}
|
|
90979
|
+
return { cps: curCps, knots: curKnots };
|
|
90980
|
+
}
|
|
90981
|
+
function binomial(a, b) {
|
|
90982
|
+
if (b < 0 || b > a) return 0;
|
|
90983
|
+
let r = 1;
|
|
90984
|
+
for (let i = 0; i < b; i++) r = r * (a - i) / (i + 1);
|
|
90985
|
+
return r;
|
|
90986
|
+
}
|
|
90987
|
+
function bezierElevate(bez, p2, t) {
|
|
90988
|
+
const m = p2 + t;
|
|
90989
|
+
const out = [];
|
|
90990
|
+
for (let i = 0; i <= m; i++) {
|
|
90991
|
+
const acc = [0, 0, 0];
|
|
90992
|
+
const lo = Math.max(0, i - t);
|
|
90993
|
+
const hi = Math.min(p2, i);
|
|
90994
|
+
for (let j = lo; j <= hi; j++) {
|
|
90995
|
+
const w = binomial(p2, j) * binomial(t, i - j) / binomial(m, i);
|
|
90996
|
+
acc[0] += w * bez[j][0];
|
|
90997
|
+
acc[1] += w * bez[j][1];
|
|
90998
|
+
acc[2] += w * bez[j][2];
|
|
90999
|
+
}
|
|
91000
|
+
out.push(acc);
|
|
91001
|
+
}
|
|
91002
|
+
return out;
|
|
91003
|
+
}
|
|
91004
|
+
function elevateCurveDegree(cps, knots, degree, targetDegree) {
|
|
91005
|
+
if (targetDegree === degree) return { cps: cps.map((p2) => [...p2]), knots: knots.slice(), degree };
|
|
91006
|
+
if (targetDegree < degree) throw new Error("Degree reduction is not supported.");
|
|
91007
|
+
const t = targetDegree - degree;
|
|
91008
|
+
let curCps = cps.map((p2) => [...p2]);
|
|
91009
|
+
let curKnots = knots.slice();
|
|
91010
|
+
const breaks = interiorKnotBuckets(curKnots).map((b) => b.value);
|
|
91011
|
+
for (const v of breaks) {
|
|
91012
|
+
let guard = 0;
|
|
91013
|
+
while (knotMultiplicity2(curKnots, v) < degree) {
|
|
91014
|
+
const r = insertKnotCurve(curCps, curKnots, degree, v);
|
|
91015
|
+
curCps = r.cps;
|
|
91016
|
+
curKnots = r.knots;
|
|
91017
|
+
if (++guard > degree + 2) break;
|
|
91018
|
+
}
|
|
91019
|
+
}
|
|
91020
|
+
const segVals = [0, ...breaks.slice().sort((a, b) => a - b), 1];
|
|
91021
|
+
const nSeg = segVals.length - 1;
|
|
91022
|
+
const newCps = [];
|
|
91023
|
+
for (let s = 0; s < nSeg; s++) {
|
|
91024
|
+
const bez = curCps.slice(s * degree, s * degree + degree + 1);
|
|
91025
|
+
const elevated = bezierElevate(bez, degree, t);
|
|
91026
|
+
if (s === 0) newCps.push(...elevated);
|
|
91027
|
+
else newCps.push(...elevated.slice(1));
|
|
91028
|
+
}
|
|
91029
|
+
const newKnots = [];
|
|
91030
|
+
for (let i = 0; i <= targetDegree; i++) newKnots.push(0);
|
|
91031
|
+
for (let s = 1; s < nSeg; s++) for (let i = 0; i < targetDegree; i++) newKnots.push(segVals[s]);
|
|
91032
|
+
for (let i = 0; i <= targetDegree; i++) newKnots.push(1);
|
|
91033
|
+
if (newKnots.length !== newCps.length + targetDegree + 1) {
|
|
91034
|
+
throw new Error(
|
|
91035
|
+
`Degree elevation produced inconsistent knots (${newKnots.length}) for ${newCps.length} control points at degree ${targetDegree}.`
|
|
91036
|
+
);
|
|
91037
|
+
}
|
|
91038
|
+
return { cps: newCps, knots: newKnots, degree: targetDegree };
|
|
91039
|
+
}
|
|
91040
|
+
function unifyCurves(curves) {
|
|
91041
|
+
const targetDeg = Math.max(...curves.map((c) => c.degree));
|
|
91042
|
+
const elevated = curves.map((c) => elevateCurveDegree(c.cps, c.knots, c.degree, targetDeg));
|
|
91043
|
+
const common2 = mergeKnotVectors(
|
|
91044
|
+
elevated.map((c) => c.knots),
|
|
91045
|
+
targetDeg
|
|
91046
|
+
);
|
|
91047
|
+
const refined = elevated.map((c) => refineKnotsToTarget(c.cps, c.knots, targetDeg, common2));
|
|
91048
|
+
const ncp = common2.length - targetDeg - 1;
|
|
91049
|
+
for (const r of refined) {
|
|
91050
|
+
if (r.cps.length !== ncp) throw new Error(`Curve unification produced ${r.cps.length} control points, expected ${ncp}.`);
|
|
91051
|
+
}
|
|
91052
|
+
return { degree: targetDeg, knots: common2, curves: refined.map((r) => r.cps) };
|
|
91053
|
+
}
|
|
91054
|
+
function transpose(g) {
|
|
91055
|
+
const out = [];
|
|
91056
|
+
for (let j = 0; j < g[0].length; j++) {
|
|
91057
|
+
const row = [];
|
|
91058
|
+
for (let i = 0; i < g.length; i++) row.push(g[i][j]);
|
|
91059
|
+
out.push(row);
|
|
91060
|
+
}
|
|
91061
|
+
return out;
|
|
91062
|
+
}
|
|
91063
|
+
function skin(curveCps, spanParams, spanDegree) {
|
|
91064
|
+
const ncp = curveCps[0].length;
|
|
91065
|
+
const cols = [];
|
|
91066
|
+
for (let j = 0; j < ncp; j++) {
|
|
91067
|
+
const pts = curveCps.map((row) => row[j]);
|
|
91068
|
+
cols.push(globalCurveInterp(pts, spanDegree, spanParams).cps);
|
|
91069
|
+
}
|
|
91070
|
+
const spanCp = cols[0].length;
|
|
91071
|
+
const grid = [];
|
|
91072
|
+
for (let s = 0; s < spanCp; s++) {
|
|
91073
|
+
const row = [];
|
|
91074
|
+
for (let j = 0; j < ncp; j++) row.push(cols[j][s]);
|
|
91075
|
+
grid.push(row);
|
|
91076
|
+
}
|
|
91077
|
+
const spanKnots = knotsFromParamsAveraging(spanParams, spanDegree);
|
|
91078
|
+
return { grid, spanKnots };
|
|
91079
|
+
}
|
|
91080
|
+
function tensorInterp(gridPts, uParams, vParams, degreeU, degreeV) {
|
|
91081
|
+
const rowInterp = gridPts.map((rowPts) => globalCurveInterp(rowPts, degreeV, vParams));
|
|
91082
|
+
const knotsV = rowInterp[0].knots;
|
|
91083
|
+
const ncpV = rowInterp[0].cps.length;
|
|
91084
|
+
const cols = [];
|
|
91085
|
+
for (let j = 0; j < ncpV; j++) {
|
|
91086
|
+
const pts = rowInterp.map((ri) => ri.cps[j]);
|
|
91087
|
+
cols.push(globalCurveInterp(pts, degreeU, uParams));
|
|
91088
|
+
}
|
|
91089
|
+
const knotsU = cols[0].knots;
|
|
91090
|
+
const cpU = cols[0].cps.length;
|
|
91091
|
+
const grid = [];
|
|
91092
|
+
for (let i = 0; i < cpU; i++) {
|
|
91093
|
+
const row = [];
|
|
91094
|
+
for (let j = 0; j < ncpV; j++) row.push(cols[j].cps[i]);
|
|
91095
|
+
grid.push(row);
|
|
91096
|
+
}
|
|
91097
|
+
return { grid, knotsU, knotsV };
|
|
91098
|
+
}
|
|
91099
|
+
function refineSurface(surf, targetKnotsU, targetKnotsV) {
|
|
91100
|
+
const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
|
|
91101
|
+
const rows = grid.map((row) => refineKnotsToTarget(row, knotsV, degreeV, targetKnotsV).cps);
|
|
91102
|
+
const ncpV = rows[0].length;
|
|
91103
|
+
const cols = [];
|
|
91104
|
+
for (let j = 0; j < ncpV; j++) {
|
|
91105
|
+
const colPts = rows.map((r) => r[j]);
|
|
91106
|
+
cols.push(refineKnotsToTarget(colPts, knotsU, degreeU, targetKnotsU).cps);
|
|
91107
|
+
}
|
|
91108
|
+
const cpU = cols[0].length;
|
|
91109
|
+
const out = [];
|
|
91110
|
+
for (let i = 0; i < cpU; i++) {
|
|
91111
|
+
const row = [];
|
|
91112
|
+
for (let j = 0; j < ncpV; j++) row.push(cols[j][i]);
|
|
91113
|
+
out.push(row);
|
|
91114
|
+
}
|
|
91115
|
+
return { grid: out, knotsU: targetKnotsU, knotsV: targetKnotsV, degreeU, degreeV };
|
|
91116
|
+
}
|
|
91117
|
+
function averageParams(polylines, alpha = 0.5) {
|
|
91118
|
+
const count = polylines[0].length;
|
|
91119
|
+
const acc = new Array(count).fill(0);
|
|
91120
|
+
for (const pl of polylines) {
|
|
91121
|
+
const p2 = computeParamsCentripetal(pl, alpha);
|
|
91122
|
+
for (let i = 0; i < count; i++) acc[i] += p2[i];
|
|
91123
|
+
}
|
|
91124
|
+
return acc.map((s) => s / polylines.length);
|
|
91125
|
+
}
|
|
91126
|
+
function gordonGridParams(gridPts) {
|
|
91127
|
+
const rows = gridPts;
|
|
91128
|
+
const cols = gridPts[0].map((_, l) => gridPts.map((r) => r[l]));
|
|
91129
|
+
return { vParams: averageParams(rows, 0.5), uParams: averageParams(cols, 0.5) };
|
|
91130
|
+
}
|
|
91131
|
+
function buildGordonFromGrid(gridPts, degreeU = 3, degreeV = 3) {
|
|
91132
|
+
const m = gridPts.length - 1;
|
|
91133
|
+
const n = gridPts[0].length - 1;
|
|
91134
|
+
if (m < 1 || n < 1) throw new Error("A Gordon surface needs at least a 2x2 curve network.");
|
|
91135
|
+
if (m < degreeU) degreeU = m;
|
|
91136
|
+
if (n < degreeV) degreeV = n;
|
|
91137
|
+
const rows = gridPts;
|
|
91138
|
+
const cols = gridPts[0].map((_, l) => gridPts.map((r) => r[l]));
|
|
91139
|
+
const { uParams, vParams } = gordonGridParams(gridPts);
|
|
91140
|
+
const uCurves = rows.map((rowPts) => globalCurveInterp(rowPts, degreeV, vParams));
|
|
91141
|
+
const vCurves = cols.map((colPts) => globalCurveInterp(colPts, degreeU, uParams));
|
|
91142
|
+
const uFam = unifyCurves(uCurves);
|
|
91143
|
+
const vFam = unifyCurves(vCurves);
|
|
91144
|
+
const Lu = skin(uFam.curves, uParams, degreeU);
|
|
91145
|
+
const LvRaw = skin(vFam.curves, vParams, degreeV);
|
|
91146
|
+
const Lv = { grid: transpose(LvRaw.grid), knotsU: vFam.knots, knotsV: LvRaw.spanKnots };
|
|
91147
|
+
const T = tensorInterp(gridPts, uParams, vParams, degreeU, degreeV);
|
|
91148
|
+
const knotsU = mergeKnotVectors([Lu.spanKnots, Lv.knotsU, T.knotsU], degreeU);
|
|
91149
|
+
const knotsV = mergeKnotVectors([uFam.knots, Lv.knotsV, T.knotsV], degreeV);
|
|
91150
|
+
const LuS = refineSurface({ grid: Lu.grid, knotsU: Lu.spanKnots, knotsV: uFam.knots, degreeU, degreeV }, knotsU, knotsV);
|
|
91151
|
+
const LvS = refineSurface({ grid: Lv.grid, knotsU: Lv.knotsU, knotsV: Lv.knotsV, degreeU, degreeV }, knotsU, knotsV);
|
|
91152
|
+
const TS = refineSurface({ grid: T.grid, knotsU: T.knotsU, knotsV: T.knotsV, degreeU, degreeV }, knotsU, knotsV);
|
|
91153
|
+
const nuc = LuS.grid.length;
|
|
91154
|
+
const nvc = LuS.grid[0].length;
|
|
91155
|
+
if (LvS.grid.length !== nuc || TS.grid.length !== nuc || LvS.grid[0].length !== nvc || TS.grid[0].length !== nvc) {
|
|
91156
|
+
throw new Error(
|
|
91157
|
+
`Gordon blend grid mismatch: Lu ${nuc}x${nvc}, Lv ${LvS.grid.length}x${LvS.grid[0].length}, T ${TS.grid.length}x${TS.grid[0].length}.`
|
|
91158
|
+
);
|
|
91159
|
+
}
|
|
91160
|
+
const grid = [];
|
|
91161
|
+
for (let i = 0; i < nuc; i++) {
|
|
91162
|
+
const row = [];
|
|
91163
|
+
for (let j = 0; j < nvc; j++) {
|
|
91164
|
+
const a = LuS.grid[i][j];
|
|
91165
|
+
const b = LvS.grid[i][j];
|
|
91166
|
+
const c = TS.grid[i][j];
|
|
91167
|
+
row.push([a[0] + b[0] - c[0], a[1] + b[1] - c[1], a[2] + b[2] - c[2]]);
|
|
91168
|
+
}
|
|
91169
|
+
grid.push(row);
|
|
91170
|
+
}
|
|
91171
|
+
return { grid, knotsU, knotsV, degreeU, degreeV };
|
|
91172
|
+
}
|
|
91173
|
+
function evalSurface(surf, u, v) {
|
|
91174
|
+
const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
|
|
91175
|
+
const nu = grid.length;
|
|
91176
|
+
const nv = grid[0].length;
|
|
91177
|
+
const su = findSpan(nu, degreeU, u, knotsU);
|
|
91178
|
+
const sv = findSpan(nv, degreeV, v, knotsV);
|
|
91179
|
+
const Nu = basisFuns(su, u, degreeU, knotsU);
|
|
91180
|
+
const Nv = basisFuns(sv, v, degreeV, knotsV);
|
|
91181
|
+
const out = [0, 0, 0];
|
|
91182
|
+
for (let i = 0; i <= degreeU; i++) {
|
|
91183
|
+
const ui = su - degreeU + i;
|
|
91184
|
+
for (let j = 0; j <= degreeV; j++) {
|
|
91185
|
+
const vj = sv - degreeV + j;
|
|
91186
|
+
const w = Nu[i] * Nv[j];
|
|
91187
|
+
const p2 = grid[ui][vj];
|
|
91188
|
+
out[0] += w * p2[0];
|
|
91189
|
+
out[1] += w * p2[1];
|
|
91190
|
+
out[2] += w * p2[2];
|
|
91191
|
+
}
|
|
91192
|
+
}
|
|
91193
|
+
return out;
|
|
91194
|
+
}
|
|
91195
|
+
function evalSurfaceJet(surf, u, v) {
|
|
91196
|
+
const { grid, knotsU, knotsV, degreeU, degreeV } = surf;
|
|
91197
|
+
const nu = grid.length;
|
|
91198
|
+
const nv = grid[0].length;
|
|
91199
|
+
const su = findSpan(nu, degreeU, u, knotsU);
|
|
91200
|
+
const sv = findSpan(nv, degreeV, v, knotsV);
|
|
91201
|
+
const dU = basisFunsDeriv(su, u, degreeU, knotsU, 2);
|
|
91202
|
+
const dV = basisFunsDeriv(sv, v, degreeV, knotsV, 2);
|
|
91203
|
+
const S = [0, 0, 0];
|
|
91204
|
+
const Su = [0, 0, 0];
|
|
91205
|
+
const Sv = [0, 0, 0];
|
|
91206
|
+
const Suu = [0, 0, 0];
|
|
91207
|
+
const Suv = [0, 0, 0];
|
|
91208
|
+
const Svv = [0, 0, 0];
|
|
91209
|
+
for (let i = 0; i <= degreeU; i++) {
|
|
91210
|
+
const ui = su - degreeU + i;
|
|
91211
|
+
for (let j = 0; j <= degreeV; j++) {
|
|
91212
|
+
const vj = sv - degreeV + j;
|
|
91213
|
+
const p2 = grid[ui][vj];
|
|
91214
|
+
const w00 = dU[0][i] * dV[0][j];
|
|
91215
|
+
const w10 = dU[1][i] * dV[0][j];
|
|
91216
|
+
const w01 = dU[0][i] * dV[1][j];
|
|
91217
|
+
const w20 = dU[2][i] * dV[0][j];
|
|
91218
|
+
const w11 = dU[1][i] * dV[1][j];
|
|
91219
|
+
const w02 = dU[0][i] * dV[2][j];
|
|
91220
|
+
for (let c = 0; c < 3; c++) {
|
|
91221
|
+
S[c] += w00 * p2[c];
|
|
91222
|
+
Su[c] += w10 * p2[c];
|
|
91223
|
+
Sv[c] += w01 * p2[c];
|
|
91224
|
+
Suu[c] += w20 * p2[c];
|
|
91225
|
+
Suv[c] += w11 * p2[c];
|
|
91226
|
+
Svv[c] += w02 * p2[c];
|
|
91227
|
+
}
|
|
91228
|
+
}
|
|
91229
|
+
}
|
|
91230
|
+
let nx = Su[1] * Sv[2] - Su[2] * Sv[1];
|
|
91231
|
+
let ny = Su[2] * Sv[0] - Su[0] * Sv[2];
|
|
91232
|
+
let nz = Su[0] * Sv[1] - Su[1] * Sv[0];
|
|
91233
|
+
const len2 = Math.hypot(nx, ny, nz) || 1;
|
|
91234
|
+
nx /= len2;
|
|
91235
|
+
ny /= len2;
|
|
91236
|
+
nz /= len2;
|
|
91237
|
+
return { S, Su, Sv, Suu, Suv, Svv, normal: [nx, ny, nz] };
|
|
91238
|
+
}
|
|
91239
|
+
function surfaceCurvature(jet) {
|
|
91240
|
+
const { Su, Sv, Suu, Suv, Svv, normal: normal2 } = jet;
|
|
91241
|
+
const E = Su[0] * Su[0] + Su[1] * Su[1] + Su[2] * Su[2];
|
|
91242
|
+
const F = Su[0] * Sv[0] + Su[1] * Sv[1] + Su[2] * Sv[2];
|
|
91243
|
+
const G = Sv[0] * Sv[0] + Sv[1] * Sv[1] + Sv[2] * Sv[2];
|
|
91244
|
+
const e = Suu[0] * normal2[0] + Suu[1] * normal2[1] + Suu[2] * normal2[2];
|
|
91245
|
+
const f4 = Suv[0] * normal2[0] + Suv[1] * normal2[1] + Suv[2] * normal2[2];
|
|
91246
|
+
const g = Svv[0] * normal2[0] + Svv[1] * normal2[1] + Svv[2] * normal2[2];
|
|
91247
|
+
const denom = E * G - F * F;
|
|
91248
|
+
if (Math.abs(denom) < 1e-20) return { k1: 0, k2: 0, K: 0, H: 0 };
|
|
91249
|
+
const K = (e * g - f4 * f4) / denom;
|
|
91250
|
+
const H = (e * G - 2 * f4 * F + g * E) / (2 * denom);
|
|
91251
|
+
const disc = Math.max(0, H * H - K);
|
|
91252
|
+
const root = Math.sqrt(disc);
|
|
91253
|
+
return { k1: H + root, k2: H - root, K, H };
|
|
91254
|
+
}
|
|
91255
|
+
|
|
91256
|
+
// src/forge/surfacing/curveNet.ts
|
|
91257
|
+
function isVec34(v) {
|
|
91258
|
+
return Array.isArray(v) && v.length === 3 && typeof v[0] === "number" && typeof v[1] === "number" && typeof v[2] === "number";
|
|
91259
|
+
}
|
|
91260
|
+
function requireFiniteVec(p2, label) {
|
|
91261
|
+
for (let i = 0; i < 3; i++)
|
|
91262
|
+
if (!Number.isFinite(p2[i])) throw new Error(`Surface.Net: ${label} component ${i} must be finite, got ${p2[i]}`);
|
|
91263
|
+
return [p2[0], p2[1], p2[2]];
|
|
91264
|
+
}
|
|
91265
|
+
function toSampler(input, label) {
|
|
91266
|
+
if (input instanceof NurbsCurve3D) {
|
|
91267
|
+
return (t) => input.pointAt(t);
|
|
91268
|
+
}
|
|
91269
|
+
if (Array.isArray(input) && input.length > 0 && isVec34(input[0])) {
|
|
91270
|
+
const pts = input.map((p2, i) => requireFiniteVec(p2, `${label}[${i}]`));
|
|
91271
|
+
if (pts.length < 2) throw new Error(`Surface.Net: ${label} needs at least 2 points.`);
|
|
91272
|
+
if (pts.length === 2) {
|
|
91273
|
+
const [a, b] = pts;
|
|
91274
|
+
return (t) => [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
|
|
91275
|
+
}
|
|
91276
|
+
const curve = globalCurveInterp(pts, Math.min(3, pts.length - 1));
|
|
91277
|
+
const nc = new NurbsCurve3D(curve.cps, { degree: curve.degree, knots: curve.knots });
|
|
91278
|
+
return (t) => nc.pointAt(t);
|
|
91279
|
+
}
|
|
91280
|
+
throw new Error(`Surface.Net: ${label} must be a Curve.Fit/Curve.Nurbs value or an array of [x,y,z] points.`);
|
|
91281
|
+
}
|
|
91282
|
+
function sampleCurve(sampler, count) {
|
|
91283
|
+
const out = [];
|
|
91284
|
+
for (let i = 0; i < count; i++) out.push(sampler(count > 1 ? i / (count - 1) : 0));
|
|
91285
|
+
return out;
|
|
91286
|
+
}
|
|
91287
|
+
var DEFAULT_THICKEN_RESOLUTION = 48;
|
|
91288
|
+
var Sheet = class {
|
|
91289
|
+
constructor(surface) {
|
|
91290
|
+
this.surface = surface;
|
|
91291
|
+
}
|
|
91292
|
+
/** Edge naming follows parameter direction (documented): front=v0, rear=v1, left=u0, right=u1. */
|
|
91293
|
+
get frontEdge() {
|
|
91294
|
+
return { sheet: this, fixed: "v", value: 0 };
|
|
91295
|
+
}
|
|
91296
|
+
get rearEdge() {
|
|
91297
|
+
return { sheet: this, fixed: "v", value: 1 };
|
|
91298
|
+
}
|
|
91299
|
+
get leftEdge() {
|
|
91300
|
+
return { sheet: this, fixed: "u", value: 0 };
|
|
91301
|
+
}
|
|
91302
|
+
get rightEdge() {
|
|
91303
|
+
return { sheet: this, fixed: "u", value: 1 };
|
|
91304
|
+
}
|
|
91305
|
+
pointAt(u, v) {
|
|
91306
|
+
return evalSurface(this.surface, clamp013(u), clamp013(v));
|
|
91307
|
+
}
|
|
91308
|
+
normalAt(u, v) {
|
|
91309
|
+
return evalSurfaceJet(this.surface, clamp013(u), clamp013(v)).normal;
|
|
91310
|
+
}
|
|
91311
|
+
curvatureAt(u, v) {
|
|
91312
|
+
return surfaceCurvature(evalSurfaceJet(this.surface, clamp013(u), clamp013(v)));
|
|
91313
|
+
}
|
|
91314
|
+
/** Largest principal curvature magnitude over a sampling grid (for offset safety). */
|
|
91315
|
+
maxAbsPrincipalCurvature(samples = 9) {
|
|
91316
|
+
let maxK = 0;
|
|
91317
|
+
for (let i = 0; i <= samples; i++) {
|
|
91318
|
+
for (let j = 0; j <= samples; j++) {
|
|
91319
|
+
const c = this.curvatureAt(i / samples, j / samples);
|
|
91320
|
+
maxK = Math.max(maxK, Math.abs(c.k1), Math.abs(c.k2));
|
|
91321
|
+
}
|
|
91322
|
+
}
|
|
91323
|
+
return maxK;
|
|
91324
|
+
}
|
|
91325
|
+
/**
|
|
91326
|
+
* Offset the sheet along its analytic normals into a watertight solid shell of
|
|
91327
|
+
* the given wall thickness. Throws if the wall would self-intersect on a
|
|
91328
|
+
* concave region (no silent degenerate solid).
|
|
91329
|
+
*/
|
|
91330
|
+
thicken(wall, options = {}) {
|
|
91331
|
+
if (!Number.isFinite(wall) || wall <= 0) throw new Error(`Sheet.thicken: wall must be a positive finite number, got ${wall}`);
|
|
91332
|
+
const maxK = this.maxAbsPrincipalCurvature();
|
|
91333
|
+
if (wall * maxK >= 1) {
|
|
91334
|
+
throw new Error(
|
|
91335
|
+
`Sheet.thicken: wall ${wall} exceeds the minimum radius of curvature (1/${maxK.toFixed(4)} = ${(1 / maxK).toFixed(3)}). The offset surface would self-intersect. Reduce the wall thickness or relax the curvature.`
|
|
91336
|
+
);
|
|
91337
|
+
}
|
|
91338
|
+
const resolution = options.resolution ?? DEFAULT_THICKEN_RESOLUTION;
|
|
91339
|
+
return nurbsSurface(this.surface.grid, {
|
|
91340
|
+
degreeU: this.surface.degreeU,
|
|
91341
|
+
degreeV: this.surface.degreeV,
|
|
91342
|
+
knotsU: this.surface.knotsU,
|
|
91343
|
+
knotsV: this.surface.knotsV,
|
|
91344
|
+
thickness: wall,
|
|
91345
|
+
resolution
|
|
91346
|
+
});
|
|
91347
|
+
}
|
|
91348
|
+
/** Per-edge continuity match against a neighbor (returns a NEW Sheet). */
|
|
91349
|
+
matchEdge(edge) {
|
|
91350
|
+
if (edge.sheet !== this) throw new Error("Sheet.matchEdge: the edge must belong to this sheet.");
|
|
91351
|
+
return new MatchEdgeBuilder(this, edge);
|
|
91352
|
+
}
|
|
91353
|
+
};
|
|
91354
|
+
function clamp013(t) {
|
|
91355
|
+
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
91356
|
+
}
|
|
91357
|
+
var CurveNetBuilder = class {
|
|
91358
|
+
lengthwiseCurves = [];
|
|
91359
|
+
crosswiseCurves = [];
|
|
91360
|
+
railCurves = [];
|
|
91361
|
+
cageGrid = null;
|
|
91362
|
+
degU;
|
|
91363
|
+
degV;
|
|
91364
|
+
built = null;
|
|
91365
|
+
lengthwise(...curves) {
|
|
91366
|
+
this.lengthwiseCurves = curves;
|
|
91367
|
+
this.built = null;
|
|
91368
|
+
return this;
|
|
91369
|
+
}
|
|
91370
|
+
crosswise(...curves) {
|
|
91371
|
+
this.crosswiseCurves = curves;
|
|
91372
|
+
this.built = null;
|
|
91373
|
+
return this;
|
|
91374
|
+
}
|
|
91375
|
+
alongRails(railA, railB) {
|
|
91376
|
+
this.railCurves = [railA, railB];
|
|
91377
|
+
this.built = null;
|
|
91378
|
+
return this;
|
|
91379
|
+
}
|
|
91380
|
+
sections(...curves) {
|
|
91381
|
+
this.crosswiseCurves = curves;
|
|
91382
|
+
this.built = null;
|
|
91383
|
+
return this;
|
|
91384
|
+
}
|
|
91385
|
+
cage(grid) {
|
|
91386
|
+
if (!Array.isArray(grid) || grid.length < 2 || !Array.isArray(grid[0]) || grid[0].length < 2) {
|
|
91387
|
+
throw new Error("Surface.Net().cage: grid must be at least a 2x2 array of [x,y,z] points.");
|
|
91388
|
+
}
|
|
91389
|
+
const cols = grid[0].length;
|
|
91390
|
+
this.cageGrid = grid.map((row, k) => {
|
|
91391
|
+
if (row.length !== cols) throw new Error(`Surface.Net().cage: row ${k} has ${row.length} points, expected ${cols}.`);
|
|
91392
|
+
return row.map((p2, l) => requireFiniteVec(p2, `cage[${k}][${l}]`));
|
|
91393
|
+
});
|
|
91394
|
+
this.built = null;
|
|
91395
|
+
return this;
|
|
91396
|
+
}
|
|
91397
|
+
degree(u, v) {
|
|
91398
|
+
if (!Number.isInteger(u) || u < 1) throw new Error(`Surface.Net().degree: degree must be a positive integer, got ${u}`);
|
|
91399
|
+
this.degU = u;
|
|
91400
|
+
this.degV = v === void 0 ? u : v;
|
|
91401
|
+
if (v !== void 0 && (!Number.isInteger(v) || v < 1))
|
|
91402
|
+
throw new Error(`Surface.Net().degree: degree must be a positive integer, got ${v}`);
|
|
91403
|
+
this.built = null;
|
|
91404
|
+
return this;
|
|
91405
|
+
}
|
|
91406
|
+
/** Build (once) and return the Sheet. */
|
|
91407
|
+
toSheet() {
|
|
91408
|
+
if (this.built) return this.built;
|
|
91409
|
+
const grid = this.buildGrid();
|
|
91410
|
+
const m = grid.length - 1;
|
|
91411
|
+
const n = grid[0].length - 1;
|
|
91412
|
+
const degreeU = Math.max(1, Math.min(this.degU ?? 3, m));
|
|
91413
|
+
const degreeV = Math.max(1, Math.min(this.degV ?? 3, n));
|
|
91414
|
+
const surface = buildGordonFromGrid(grid, degreeU, degreeV);
|
|
91415
|
+
this.built = new Sheet(surface);
|
|
91416
|
+
return this.built;
|
|
91417
|
+
}
|
|
91418
|
+
buildGrid() {
|
|
91419
|
+
if (this.cageGrid) return this.cageGrid;
|
|
91420
|
+
const lengthwise = this.railCurves.length > 0 ? this.railCurves : this.lengthwiseCurves;
|
|
91421
|
+
const crosswise = this.crosswiseCurves;
|
|
91422
|
+
const SAMPLES = 17;
|
|
91423
|
+
if (lengthwise.length >= 2) {
|
|
91424
|
+
const samplers = lengthwise.map((c, l) => toSampler(c, `lengthwise[${l}]`));
|
|
91425
|
+
const grid = [];
|
|
91426
|
+
for (let i = 0; i < SAMPLES; i++) {
|
|
91427
|
+
const u = i / (SAMPLES - 1);
|
|
91428
|
+
grid.push(samplers.map((s) => s(u)));
|
|
91429
|
+
}
|
|
91430
|
+
return grid;
|
|
91431
|
+
}
|
|
91432
|
+
if (crosswise.length >= 2) {
|
|
91433
|
+
const samplers = crosswise.map((c, k) => toSampler(c, `crosswise[${k}]`));
|
|
91434
|
+
const grid = [];
|
|
91435
|
+
for (const s of samplers) grid.push(sampleCurve(s, SAMPLES));
|
|
91436
|
+
return grid;
|
|
91437
|
+
}
|
|
91438
|
+
throw new Error(
|
|
91439
|
+
"Surface.Net: provide a .cage(grid), or a family of at least 2 curves via .lengthwise(...) / .crosswise(...) / .alongRails(a,b).sections(...). Intersecting two independent hand-drawn families is not available yet \u2014 supply the denser family or a cage."
|
|
91440
|
+
);
|
|
91441
|
+
}
|
|
91442
|
+
// ── Sheet delegation (build on first access) ──────────────────────────────
|
|
91443
|
+
get frontEdge() {
|
|
91444
|
+
return this.toSheet().frontEdge;
|
|
91445
|
+
}
|
|
91446
|
+
get rearEdge() {
|
|
91447
|
+
return this.toSheet().rearEdge;
|
|
91448
|
+
}
|
|
91449
|
+
get leftEdge() {
|
|
91450
|
+
return this.toSheet().leftEdge;
|
|
91451
|
+
}
|
|
91452
|
+
get rightEdge() {
|
|
91453
|
+
return this.toSheet().rightEdge;
|
|
91454
|
+
}
|
|
91455
|
+
get surface() {
|
|
91456
|
+
return this.toSheet().surface;
|
|
91457
|
+
}
|
|
91458
|
+
pointAt(u, v) {
|
|
91459
|
+
return this.toSheet().pointAt(u, v);
|
|
91460
|
+
}
|
|
91461
|
+
normalAt(u, v) {
|
|
91462
|
+
return this.toSheet().normalAt(u, v);
|
|
91463
|
+
}
|
|
91464
|
+
curvatureAt(u, v) {
|
|
91465
|
+
return this.toSheet().curvatureAt(u, v);
|
|
91466
|
+
}
|
|
91467
|
+
thicken(wall, options) {
|
|
91468
|
+
return this.toSheet().thicken(wall, options);
|
|
91469
|
+
}
|
|
91470
|
+
matchEdge(edge) {
|
|
91471
|
+
return this.toSheet().matchEdge(edge);
|
|
91472
|
+
}
|
|
91473
|
+
};
|
|
91474
|
+
function createCurveNet() {
|
|
91475
|
+
return new CurveNetBuilder();
|
|
91476
|
+
}
|
|
91477
|
+
var MatchEdgeBuilder = class {
|
|
91478
|
+
constructor(sheet, edge) {
|
|
91479
|
+
this.sheet = sheet;
|
|
91480
|
+
this.edge = edge;
|
|
91481
|
+
}
|
|
91482
|
+
toG0(neighbor) {
|
|
91483
|
+
return applyEdgeMatch(this.sheet, this.edge, neighbor, 0);
|
|
91484
|
+
}
|
|
91485
|
+
toG1(neighbor) {
|
|
91486
|
+
return applyEdgeMatch(this.sheet, this.edge, neighbor, 1);
|
|
91487
|
+
}
|
|
91488
|
+
toG2(neighbor) {
|
|
91489
|
+
return applyEdgeMatch(this.sheet, this.edge, neighbor, 2);
|
|
91490
|
+
}
|
|
91491
|
+
};
|
|
91492
|
+
function boundaryRows(surf, fixed, value, depth) {
|
|
91493
|
+
const rows = [];
|
|
91494
|
+
if (fixed === "u") {
|
|
91495
|
+
const nU = surf.grid.length;
|
|
91496
|
+
for (let d = 0; d <= depth; d++) {
|
|
91497
|
+
const i = value === 0 ? d : nU - 1 - d;
|
|
91498
|
+
rows.push(surf.grid[i].map((p2) => [p2[0], p2[1], p2[2]]));
|
|
91499
|
+
}
|
|
91500
|
+
} else {
|
|
91501
|
+
const nV = surf.grid[0].length;
|
|
91502
|
+
for (let d = 0; d <= depth; d++) {
|
|
91503
|
+
const j = value === 0 ? d : nV - 1 - d;
|
|
91504
|
+
rows.push(surf.grid.map((p2) => [p2[j][0], p2[j][1], p2[j][2]]));
|
|
91505
|
+
}
|
|
91506
|
+
}
|
|
91507
|
+
return rows;
|
|
91508
|
+
}
|
|
91509
|
+
function setBoundaryRow(surf, fixed, value, depth, row) {
|
|
91510
|
+
if (fixed === "u") {
|
|
91511
|
+
const nU = surf.grid.length;
|
|
91512
|
+
const i = value === 0 ? depth : nU - 1 - depth;
|
|
91513
|
+
for (let l = 0; l < surf.grid[i].length; l++) surf.grid[i][l] = [row[l][0], row[l][1], row[l][2]];
|
|
91514
|
+
} else {
|
|
91515
|
+
const nV = surf.grid[0].length;
|
|
91516
|
+
const j = value === 0 ? depth : nV - 1 - depth;
|
|
91517
|
+
for (let k = 0; k < surf.grid.length; k++) surf.grid[k][j] = [row[k][0], row[k][1], row[k][2]];
|
|
91518
|
+
}
|
|
91519
|
+
}
|
|
91520
|
+
function cloneSurface(surf) {
|
|
91521
|
+
return {
|
|
91522
|
+
grid: surf.grid.map((row) => row.map((p2) => [p2[0], p2[1], p2[2]])),
|
|
91523
|
+
knotsU: [...surf.knotsU],
|
|
91524
|
+
knotsV: [...surf.knotsV],
|
|
91525
|
+
degreeU: surf.degreeU,
|
|
91526
|
+
degreeV: surf.degreeV
|
|
91527
|
+
};
|
|
91528
|
+
}
|
|
91529
|
+
function applyEdgeMatch(sheet, edge, neighbor, order) {
|
|
91530
|
+
const result = cloneSurface(sheet.surface);
|
|
91531
|
+
const my = boundaryRows(result, edge.fixed, edge.value, order);
|
|
91532
|
+
const their = boundaryRows(neighbor.sheet.surface, neighbor.fixed, neighbor.value, order);
|
|
91533
|
+
if (my[0].length !== their[0].length) {
|
|
91534
|
+
throw new Error(
|
|
91535
|
+
`Sheet.matchEdge: edge control-point counts differ (${my[0].length} vs ${their[0].length}). Both sheets must share section sampling along the matched edge for continuity.`
|
|
91536
|
+
);
|
|
91537
|
+
}
|
|
91538
|
+
const len2 = my[0].length;
|
|
91539
|
+
const b0 = their[0].map((p2) => [p2[0], p2[1], p2[2]]);
|
|
91540
|
+
setBoundaryRow(result, edge.fixed, edge.value, 0, b0);
|
|
91541
|
+
if (order === 0) return new Sheet(result);
|
|
91542
|
+
const b1 = [];
|
|
91543
|
+
for (let i = 0; i < len2; i++) {
|
|
91544
|
+
const p0 = their[0][i];
|
|
91545
|
+
const p1 = their[1][i];
|
|
91546
|
+
b1.push([2 * p0[0] - p1[0], 2 * p0[1] - p1[1], 2 * p0[2] - p1[2]]);
|
|
91547
|
+
}
|
|
91548
|
+
setBoundaryRow(result, edge.fixed, edge.value, 1, b1);
|
|
91549
|
+
if (order === 1) return new Sheet(result);
|
|
91550
|
+
const b22 = [];
|
|
91551
|
+
for (let i = 0; i < len2; i++) {
|
|
91552
|
+
const p0 = their[0][i];
|
|
91553
|
+
const p1 = their[1][i];
|
|
91554
|
+
const p2 = their[2][i];
|
|
91555
|
+
b22.push([3 * p0[0] - 3 * p1[0] + p2[0], 3 * p0[1] - 3 * p1[1] + p2[1], 3 * p0[2] - 3 * p1[2] + p2[2]]);
|
|
91556
|
+
}
|
|
91557
|
+
setBoundaryRow(result, edge.fixed, edge.value, 2, b22);
|
|
91558
|
+
return new Sheet(result);
|
|
91559
|
+
}
|
|
91560
|
+
function edgeJet(edge, t) {
|
|
91561
|
+
const surf = edge.sheet.surface;
|
|
91562
|
+
if (edge.fixed === "u") {
|
|
91563
|
+
const j2 = evalSurfaceJet(surf, edge.value, clamp013(t));
|
|
91564
|
+
return { point: j2.S, cross: j2.Su, cross2: j2.Suu };
|
|
91565
|
+
}
|
|
91566
|
+
const j = evalSurfaceJet(surf, clamp013(t), edge.value);
|
|
91567
|
+
return { point: j.S, cross: j.Sv, cross2: j.Svv };
|
|
91568
|
+
}
|
|
91569
|
+
function sub39(a, b) {
|
|
91570
|
+
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
91571
|
+
}
|
|
91572
|
+
function len34(a) {
|
|
91573
|
+
return Math.hypot(a[0], a[1], a[2]);
|
|
91574
|
+
}
|
|
91575
|
+
function unit3(a) {
|
|
91576
|
+
const l = len34(a) || 1;
|
|
91577
|
+
return [a[0] / l, a[1] / l, a[2] / l];
|
|
91578
|
+
}
|
|
91579
|
+
function edgeMatchReport(edgeA, edgeB, samples = 24) {
|
|
91580
|
+
let maxPositionGap = 0;
|
|
91581
|
+
let maxTangentAngleDeg = 0;
|
|
91582
|
+
let maxCurvatureRelErr = 0;
|
|
91583
|
+
for (let i = 0; i <= samples; i++) {
|
|
91584
|
+
const t = i / samples;
|
|
91585
|
+
const a = edgeJet(edgeA, t);
|
|
91586
|
+
const b = edgeJet(edgeB, t);
|
|
91587
|
+
maxPositionGap = Math.max(maxPositionGap, len34(sub39(a.point, b.point)));
|
|
91588
|
+
const ua = unit3(a.cross);
|
|
91589
|
+
const ub = unit3(b.cross);
|
|
91590
|
+
const dot12 = Math.max(-1, Math.min(1, ua[0] * ub[0] + ua[1] * ub[1] + ua[2] * ub[2]));
|
|
91591
|
+
const angle = Math.acos(Math.abs(dot12)) * 180 / Math.PI;
|
|
91592
|
+
maxTangentAngleDeg = Math.max(maxTangentAngleDeg, angle);
|
|
91593
|
+
const la = len34(a.cross) ** 2 || 1;
|
|
91594
|
+
const lb = len34(b.cross) ** 2 || 1;
|
|
91595
|
+
const ka = (a.cross2[0] * ua[0] + a.cross2[1] * ua[1] + a.cross2[2] * ua[2]) / la;
|
|
91596
|
+
const kb = (b.cross2[0] * ub[0] + b.cross2[1] * ub[1] + b.cross2[2] * ub[2]) / lb;
|
|
91597
|
+
maxCurvatureRelErr = Math.max(maxCurvatureRelErr, Math.abs(ka - kb) / (Math.abs(ka) + Math.abs(kb) + 1e-9));
|
|
91598
|
+
}
|
|
91599
|
+
return { maxPositionGap, maxTangentAngleDeg, maxCurvatureRelErr };
|
|
91600
|
+
}
|
|
91601
|
+
function bridgeBetween(edgeA, edgeB) {
|
|
91602
|
+
return new BridgeBuilder(edgeA, edgeB);
|
|
91603
|
+
}
|
|
91604
|
+
var BridgeBuilder = class {
|
|
91605
|
+
constructor(edgeA, edgeB) {
|
|
91606
|
+
this.edgeA = edgeA;
|
|
91607
|
+
this.edgeB = edgeB;
|
|
91608
|
+
}
|
|
91609
|
+
bulgeA = 0.5;
|
|
91610
|
+
bulgeB = 0.5;
|
|
91611
|
+
/** Tune the influence of each side (Rhino-style bulge). */
|
|
91612
|
+
bulge(a, b) {
|
|
91613
|
+
if (!Number.isFinite(a) || !Number.isFinite(b)) throw new Error("Surfaces.bridge.bulge: both factors must be finite.");
|
|
91614
|
+
this.bulgeA = a;
|
|
91615
|
+
this.bulgeB = b;
|
|
91616
|
+
return this;
|
|
91617
|
+
}
|
|
91618
|
+
g0() {
|
|
91619
|
+
return this.build(0);
|
|
91620
|
+
}
|
|
91621
|
+
g1() {
|
|
91622
|
+
return this.build(1);
|
|
91623
|
+
}
|
|
91624
|
+
g2() {
|
|
91625
|
+
return this.build(2);
|
|
91626
|
+
}
|
|
91627
|
+
build(order) {
|
|
91628
|
+
const SAMPLES = 21;
|
|
91629
|
+
const CROSS = 11;
|
|
91630
|
+
const cage = [];
|
|
91631
|
+
for (let i = 0; i < SAMPLES; i++) {
|
|
91632
|
+
const t = i / (SAMPLES - 1);
|
|
91633
|
+
const a = edgeJet(this.edgeA, t);
|
|
91634
|
+
const b = edgeJet(this.edgeB, t);
|
|
91635
|
+
const chord = len34(sub39(b.point, a.point));
|
|
91636
|
+
const toward = unit3(sub39(b.point, a.point));
|
|
91637
|
+
const tA = orientToward(unit3(a.cross), toward);
|
|
91638
|
+
const tB = orientToward(unit3(b.cross), [-toward[0], -toward[1], -toward[2]]);
|
|
91639
|
+
const poles = bridgePoles(a.point, b.point, tA, tB, chord, this.bulgeA, this.bulgeB, order);
|
|
91640
|
+
const row = [];
|
|
91641
|
+
for (let c = 0; c < CROSS; c++) row.push(deCasteljau(poles, c / (CROSS - 1)));
|
|
91642
|
+
cage.push(row);
|
|
91643
|
+
}
|
|
91644
|
+
return new CurveNetBuilder().cage(cage).degree(Math.min(3, SAMPLES - 1), Math.min(2 * order + 1 || 1, CROSS - 1)).toSheet();
|
|
91645
|
+
}
|
|
91646
|
+
};
|
|
91647
|
+
function orientToward(v, toward) {
|
|
91648
|
+
const dot12 = v[0] * toward[0] + v[1] * toward[1] + v[2] * toward[2];
|
|
91649
|
+
return dot12 < 0 ? [-v[0], -v[1], -v[2]] : v;
|
|
91650
|
+
}
|
|
91651
|
+
function bridgePoles(a, b, tA, tB, chord, bulgeA, bulgeB, order) {
|
|
91652
|
+
if (order === 0) return [a, b];
|
|
91653
|
+
const dA = chord * bulgeA / (order === 1 ? 3 : 5);
|
|
91654
|
+
const dB = chord * bulgeB / (order === 1 ? 3 : 5);
|
|
91655
|
+
const a1 = [a[0] + tA[0] * dA, a[1] + tA[1] * dA, a[2] + tA[2] * dA];
|
|
91656
|
+
const b1 = [b[0] + tB[0] * dB, b[1] + tB[1] * dB, b[2] + tB[2] * dB];
|
|
91657
|
+
if (order === 1) return [a, a1, b1, b];
|
|
91658
|
+
const a2 = [a1[0] + tA[0] * dA, a1[1] + tA[1] * dA, a1[2] + tA[2] * dA];
|
|
91659
|
+
const b22 = [b1[0] + tB[0] * dB, b1[1] + tB[1] * dB, b1[2] + tB[2] * dB];
|
|
91660
|
+
return [a, a1, a2, b22, b1, b];
|
|
91661
|
+
}
|
|
91662
|
+
function deCasteljau(poles, s) {
|
|
91663
|
+
let pts = poles.map((p2) => [p2[0], p2[1], p2[2]]);
|
|
91664
|
+
while (pts.length > 1) {
|
|
91665
|
+
const next = [];
|
|
91666
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
91667
|
+
next.push([
|
|
91668
|
+
pts[i][0] + (pts[i + 1][0] - pts[i][0]) * s,
|
|
91669
|
+
pts[i][1] + (pts[i + 1][1] - pts[i][1]) * s,
|
|
91670
|
+
pts[i][2] + (pts[i + 1][2] - pts[i][2]) * s
|
|
91671
|
+
]);
|
|
91672
|
+
}
|
|
91673
|
+
pts = next;
|
|
91674
|
+
}
|
|
91675
|
+
return pts[0];
|
|
91676
|
+
}
|
|
91677
|
+
|
|
90819
91678
|
// src/forge/surfacing.ts
|
|
90820
91679
|
var CORNER_Y_ALPHA_ISSUE_URL = "https://github.com/KoStard/forgecad-private/issues/162";
|
|
90821
91680
|
function isVec3Array(value) {
|
|
@@ -92204,7 +93063,16 @@ var Surface = {
|
|
|
92204
93063
|
"Surface.MatchEdge",
|
|
92205
93064
|
"Surface.Match()",
|
|
92206
93065
|
(shape, options) => Surface.Match(shape, options)
|
|
92207
|
-
)
|
|
93066
|
+
),
|
|
93067
|
+
/**
|
|
93068
|
+
* Begin a curve-network (Gordon) surface — the class-A keystone. Chain
|
|
93069
|
+
* `.lengthwise(...)/.crosswise(...)` (or `.alongRails(a,b).sections(...)`, or
|
|
93070
|
+
* `.cage(grid)`), then `.thicken(wall)` to get a solid Shape. Returns a fluent
|
|
93071
|
+
* `Sheet` builder with analytic point/normal/curvature queries and named edges.
|
|
93072
|
+
*/
|
|
93073
|
+
Net() {
|
|
93074
|
+
return createCurveNet();
|
|
93075
|
+
}
|
|
92208
93076
|
};
|
|
92209
93077
|
var Blend = {
|
|
92210
93078
|
Edge(options) {
|
|
@@ -92224,6 +93092,14 @@ var Blend = {
|
|
|
92224
93092
|
Surface(options) {
|
|
92225
93093
|
return Surface.Fill(options);
|
|
92226
93094
|
},
|
|
93095
|
+
/**
|
|
93096
|
+
* Build a transition strip between two `Surface.Net` sheet edges. Chain
|
|
93097
|
+
* `.bulge(a, b)` then `.g0()/.g1()/.g2()` for the continuity order. Returns a
|
|
93098
|
+
* `Sheet`; verify the seam with `Analysis.EdgeMatch`.
|
|
93099
|
+
*/
|
|
93100
|
+
Bridge(edgeA, edgeB) {
|
|
93101
|
+
return bridgeBetween(edgeA, edgeB);
|
|
93102
|
+
},
|
|
92227
93103
|
/**
|
|
92228
93104
|
* @alpha
|
|
92229
93105
|
* Current implementation uses continuity-controlled edge fillets on solid edges.
|
|
@@ -92258,6 +93134,14 @@ var Analysis = {
|
|
|
92258
93134
|
SurfaceContinuity(shape, options = {}) {
|
|
92259
93135
|
return evaluateEdgeContinuityReport(shape, options, "Analysis.SurfaceContinuity()");
|
|
92260
93136
|
},
|
|
93137
|
+
/**
|
|
93138
|
+
* Measure G0/G1/G2 agreement between two `Surface.Net` sheet edges: worst
|
|
93139
|
+
* position gap, cross-boundary tangent angle (0 = G1), and normal-curvature
|
|
93140
|
+
* mismatch (0 = G2). The reflection/fairness check for matched panel seams.
|
|
93141
|
+
*/
|
|
93142
|
+
EdgeMatch(edgeA, edgeB, options = {}) {
|
|
93143
|
+
return edgeMatchReport(edgeA, edgeB, options.samples);
|
|
93144
|
+
},
|
|
92261
93145
|
CurvatureComb(input, options = {}) {
|
|
92262
93146
|
if (input instanceof NurbsCurve3D) {
|
|
92263
93147
|
const count = Math.max(8, options.samples ?? 32);
|
|
@@ -96222,22 +97106,10 @@ function roundNum(n, digits = 4) {
|
|
|
96222
97106
|
return Number.isFinite(n) ? n.toFixed(digits).replace(/\.?0+$/, "") : String(n);
|
|
96223
97107
|
}
|
|
96224
97108
|
var COLLISION_OVERLAP_VOLUME_TOLERANCE = 1e-6;
|
|
96225
|
-
function meshDerivedManifoldBackend(shape) {
|
|
96226
|
-
const mesh = getShapeRuntimeBackend(shape).getMesh();
|
|
96227
|
-
return reconstructBackendFromMesh({
|
|
96228
|
-
numProp: mesh.numProp,
|
|
96229
|
-
triVerts: mesh.triVerts,
|
|
96230
|
-
vertProperties: mesh.vertProperties,
|
|
96231
|
-
mergeFromVert: mesh.mergeFromVert ?? new Uint32Array(),
|
|
96232
|
-
mergeToVert: mesh.mergeToVert ?? new Uint32Array()
|
|
96233
|
-
});
|
|
96234
|
-
}
|
|
96235
97109
|
function backendForMinGap(shape) {
|
|
96236
97110
|
const backend = getShapeRuntimeBackend(shape);
|
|
96237
97111
|
if (isManifoldCapableBackend(backend)) return { kind: "manifold", backend, method: "exact", dispose: false };
|
|
96238
|
-
|
|
96239
|
-
return { kind: "mesh", backend, method: "mesh-derived", dispose: false };
|
|
96240
|
-
return { kind: "manifold", backend: meshDerivedManifoldBackend(shape), method: "mesh-derived", dispose: true };
|
|
97112
|
+
return { kind: "mesh", backend, method: "mesh-derived", dispose: false };
|
|
96241
97113
|
}
|
|
96242
97114
|
function meshHasPointInsideSdf(source, target) {
|
|
96243
97115
|
const mesh = source.getMesh();
|
|
@@ -97528,7 +98400,6 @@ function resetExecutionSession(logs) {
|
|
|
97528
98400
|
resetHighlights();
|
|
97529
98401
|
resetBom();
|
|
97530
98402
|
resetSheetStock();
|
|
97531
|
-
resetRobotExport();
|
|
97532
98403
|
resetCutPlanes();
|
|
97533
98404
|
resetRenderLabels();
|
|
97534
98405
|
resetCameraTrajectory();
|
|
@@ -97611,7 +98482,6 @@ function collectSuccessfulExecutionSnapshot(args) {
|
|
|
97611
98482
|
jointsView: getCollectedJointsView(),
|
|
97612
98483
|
viewConfig: getCollectedViewConfig(),
|
|
97613
98484
|
sceneConfig: getCollectedScene(),
|
|
97614
|
-
robotExport: getCollectedRobotExport(),
|
|
97615
98485
|
quality: args.quality,
|
|
97616
98486
|
logs: args.logs.slice(),
|
|
97617
98487
|
verifications: getCollectedVerifications(),
|
|
@@ -97636,7 +98506,6 @@ function collectFailedExecutionSnapshot(args) {
|
|
|
97636
98506
|
jointsView: getCollectedJointsView(),
|
|
97637
98507
|
viewConfig: getCollectedViewConfig(),
|
|
97638
98508
|
sceneConfig: getCollectedScene(),
|
|
97639
|
-
robotExport: getCollectedRobotExport(),
|
|
97640
98509
|
quality: args.quality,
|
|
97641
98510
|
logs: args.logs.slice(),
|
|
97642
98511
|
verifications: getCollectedVerifications(),
|
|
@@ -99609,7 +100478,6 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
99609
100478
|
sketchToDxf,
|
|
99610
100479
|
bom,
|
|
99611
100480
|
sheetStock,
|
|
99612
|
-
robotExport,
|
|
99613
100481
|
Sim,
|
|
99614
100482
|
group,
|
|
99615
100483
|
ShapeGroup,
|
|
@@ -100849,7 +101717,6 @@ export {
|
|
|
100849
101717
|
buildCompiledSceneReport,
|
|
100850
101718
|
mapDimensionsToOwnerIds,
|
|
100851
101719
|
generateReportPdf,
|
|
100852
|
-
getCollectedRobotExport,
|
|
100853
101720
|
collectSimulationModel,
|
|
100854
101721
|
validateSimulationModel,
|
|
100855
101722
|
buildSimReadyManifest,
|