forgecad 0.10.0 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{AdminPage-DwYHz72L.js → AdminPage-DcCnj0qo.js} +1 -1
- package/dist/assets/{BenchmarkPage-a9_f-1US.js → BenchmarkPage-BVEpJSVk.js} +1 -1
- package/dist/assets/{BlogPage-DodHpvmf.js → BlogPage-DHaGP50_.js} +1 -1
- package/dist/assets/{DocsPage-B5LePEuj.js → DocsPage-CDoxHkz8.js} +33 -2
- package/dist/assets/{EditorApp-QXsAISLR.js → EditorApp-BJ0Dloyh.js} +174 -35
- package/dist/assets/{EmbedViewer-DdEHGUMU.js → EmbedViewer-CRKZbY0y.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-yhhOodbf.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
- package/dist/assets/{LegalPage-5RbKRGYK.js → LegalPage-B-u6FrVv.js} +1 -1
- package/dist/assets/{PricingPage-E3Rma7aV.js → PricingPage-CzpZ6-Ce.js} +1 -1
- package/dist/assets/{SettingsPage-BJZcM97j.js → SettingsPage-CIZSSAd0.js} +1 -1
- package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
- package/dist/assets/{app-DSYrDg0V.js → app-DaTMg3nH.js} +612 -120
- package/dist/assets/cli/{render-ZMHR9HkV.js → render-DPf4AYJK.js} +38 -16
- package/dist/assets/{evalWorker-DbNs7Dkp.js → evalWorker-CjZZWRWW.js} +1428 -1038
- package/dist/assets/{jointPose-DO6mnXn_.js → jointPose-DzQOViQH.js} +1 -1
- package/dist/assets/{manifold-BGlQBBH9.js → manifold-BYlzU521.js} +1 -1
- package/dist/assets/{manifold-fy2MV7K1.js → manifold-DgXo0T5P.js} +2 -2
- package/dist/assets/{manifold-BU-tJwQh.js → manifold-K1SkarlQ.js} +1 -1
- package/dist/assets/{reportWorker-DO6hcQbh.js → reportWorker-B9nWwSrB.js} +1402 -1012
- package/dist/assets/{scalar-sampling-budget-o90NSNmF.js → scalar-sampling-budget-prBw_s8t.js} +2139 -1749
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/CLI.md +18 -3
- package/dist/docs-raw/generated/assembly.md +70 -5
- package/dist/docs-raw/generated/concepts.md +16 -2
- package/dist/docs-raw/generated/core.md +9 -2
- package/dist/docs-raw/generated/lib.md +1 -1
- package/dist/docs-raw/generated/output.md +14 -43
- package/dist/docs-raw/generated/runtime-names.md +4 -4
- package/dist/docs-raw/guides/simready-quickstart.md +171 -0
- package/dist/docs-raw/simulation-workflow.md +273 -0
- package/dist/index.html +2 -2
- package/dist/sitemap.xml +25 -13
- package/dist-cli/{check-compiler-JTVBITCR.js → check-compiler-II7NLPAB.js} +1 -1
- package/dist-cli/{check-query-propagation-3FFLSMVN.js → check-query-propagation-7462TR3R.js} +1 -1
- package/dist-cli/{chunk-OAN5T4XD.js → chunk-UWTJCGXF.js} +1455 -722
- package/dist-cli/forgecad.js +2994 -529
- package/dist-skill/CONTEXT.md +94 -55
- package/dist-skill/docs/API/core/concepts.md +1 -1
- package/dist-skill/docs/CLI.md +18 -3
- package/dist-skill/docs/generated/assembly.md +66 -5
- package/dist-skill/docs/generated/core.md +9 -2
- package/dist-skill/docs/generated/lib.md +1 -1
- package/dist-skill/docs/generated/output.md +14 -43
- package/dist-skill/docs/generated/runtime-names.md +4 -4
- package/examples/robotics/README.md +46 -0
- package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
- package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
- package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
- package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
- package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
- package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
- package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
- package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
- package/examples/robotics/simready-asset-crate.forge.js +79 -0
- package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
- package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
- package/package.json +1 -1
|
@@ -361,8 +361,8 @@ function normalizeVariadicArgs({ apiName, inputs, minCount, itemName, usage, coe
|
|
|
361
361
|
return flattened.map((value, index) => {
|
|
362
362
|
try {
|
|
363
363
|
return coerce(value);
|
|
364
|
-
} catch (
|
|
365
|
-
const message =
|
|
364
|
+
} catch (error2) {
|
|
365
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
366
366
|
throw new Error(`${apiName} argument ${index + 1}: ${message}`);
|
|
367
367
|
}
|
|
368
368
|
});
|
|
@@ -2442,7 +2442,7 @@ function cloneProfileCompilePlan(plan) {
|
|
|
2442
2442
|
return {
|
|
2443
2443
|
kind: "boolean",
|
|
2444
2444
|
op: plan.op,
|
|
2445
|
-
profiles: plan.profiles.map((
|
|
2445
|
+
profiles: plan.profiles.map((profile2) => cloneProfileCompilePlan(profile2)),
|
|
2446
2446
|
transforms: plan.transforms.map(cloneProfileTransform)
|
|
2447
2447
|
};
|
|
2448
2448
|
case "offset":
|
|
@@ -2580,7 +2580,7 @@ function cloneShapeCompilePlan(plan) {
|
|
|
2580
2580
|
case "loft":
|
|
2581
2581
|
result = {
|
|
2582
2582
|
kind: "loft",
|
|
2583
|
-
profiles: plan.profiles.map((
|
|
2583
|
+
profiles: plan.profiles.map((profile2) => cloneProfileCompilePlan(profile2)),
|
|
2584
2584
|
heights: plan.heights.map((height) => height),
|
|
2585
2585
|
edgeLength: plan.edgeLength,
|
|
2586
2586
|
boundsPadding: plan.boundsPadding,
|
|
@@ -2975,7 +2975,7 @@ function buildBooleanProfileCompilePlan(op, profiles2) {
|
|
|
2975
2975
|
return {
|
|
2976
2976
|
kind: "boolean",
|
|
2977
2977
|
op,
|
|
2978
|
-
profiles: profiles2.map((
|
|
2978
|
+
profiles: profiles2.map((profile2) => cloneProfileCompilePlan(profile2)),
|
|
2979
2979
|
transforms: []
|
|
2980
2980
|
};
|
|
2981
2981
|
}
|
|
@@ -3044,7 +3044,7 @@ function buildChamferShapeCompilePlan(base, edge, size, quadrant) {
|
|
|
3044
3044
|
function buildLoftShapeCompilePlan(profiles2, heights, options) {
|
|
3045
3045
|
return {
|
|
3046
3046
|
kind: "loft",
|
|
3047
|
-
profiles: profiles2.map((
|
|
3047
|
+
profiles: profiles2.map((profile2) => cloneProfileCompilePlan(profile2)),
|
|
3048
3048
|
heights: heights.map((height) => canonicalNumber(height)),
|
|
3049
3049
|
edgeLength: canonicalNumber(options.edgeLength),
|
|
3050
3050
|
boundsPadding: canonicalNumber(options.boundsPadding),
|
|
@@ -3053,10 +3053,10 @@ function buildLoftShapeCompilePlan(profiles2, heights, options) {
|
|
|
3053
3053
|
edgeLabels: options.edgeLabels ? { ...options.edgeLabels } : void 0
|
|
3054
3054
|
};
|
|
3055
3055
|
}
|
|
3056
|
-
function buildSweepShapeCompilePlan(
|
|
3056
|
+
function buildSweepShapeCompilePlan(profile2, path2, options) {
|
|
3057
3057
|
return {
|
|
3058
3058
|
kind: "sweep",
|
|
3059
|
-
profile: cloneProfileCompilePlan(
|
|
3059
|
+
profile: cloneProfileCompilePlan(profile2),
|
|
3060
3060
|
path: cloneSweepPathCompilePlan(path2),
|
|
3061
3061
|
edgeLength: canonicalNumber(options.edgeLength),
|
|
3062
3062
|
boundsPadding: canonicalNumber(options.boundsPadding),
|
|
@@ -4543,48 +4543,48 @@ var DRAFT_TO_LOFT_EPSILON = 1e-9;
|
|
|
4543
4543
|
var DRAFT_TO_LOFT_ZERO_EPSILON = 1e-12;
|
|
4544
4544
|
var DRAFT_TO_LOFT_EDGE_LENGTH = 1;
|
|
4545
4545
|
var DRAFT_TO_LOFT_BOUNDS_PADDING = 1;
|
|
4546
|
-
function offsetProfile(
|
|
4547
|
-
if (Math.abs(delta) <= DRAFT_TO_LOFT_ZERO_EPSILON) return cloneProfileCompilePlan(
|
|
4546
|
+
function offsetProfile(profile2, delta) {
|
|
4547
|
+
if (Math.abs(delta) <= DRAFT_TO_LOFT_ZERO_EPSILON) return cloneProfileCompilePlan(profile2);
|
|
4548
4548
|
return {
|
|
4549
4549
|
kind: "offset",
|
|
4550
|
-
base: cloneProfileCompilePlan(
|
|
4550
|
+
base: cloneProfileCompilePlan(profile2),
|
|
4551
4551
|
delta,
|
|
4552
4552
|
join: "Miter",
|
|
4553
4553
|
transforms: []
|
|
4554
4554
|
};
|
|
4555
4555
|
}
|
|
4556
|
-
function scaleProfile(
|
|
4556
|
+
function scaleProfile(profile2, scale11) {
|
|
4557
4557
|
if (!Number.isFinite(scale11[0]) || !Number.isFinite(scale11[1]) || scale11[0] <= DRAFT_TO_LOFT_EPSILON || scale11[1] <= DRAFT_TO_LOFT_EPSILON) {
|
|
4558
4558
|
return null;
|
|
4559
4559
|
}
|
|
4560
4560
|
if (Math.abs(scale11[0] - 1) <= DRAFT_TO_LOFT_EPSILON && Math.abs(scale11[1] - 1) <= DRAFT_TO_LOFT_EPSILON) {
|
|
4561
|
-
return cloneProfileCompilePlan(
|
|
4561
|
+
return cloneProfileCompilePlan(profile2);
|
|
4562
4562
|
}
|
|
4563
|
-
if (
|
|
4564
|
-
switch (
|
|
4563
|
+
if (profile2.transforms.length > 0) return null;
|
|
4564
|
+
switch (profile2.kind) {
|
|
4565
4565
|
case "rect":
|
|
4566
|
-
return { kind: "rect", width:
|
|
4566
|
+
return { kind: "rect", width: profile2.width * scale11[0], height: profile2.height * scale11[1], transforms: [] };
|
|
4567
4567
|
case "roundedRect":
|
|
4568
4568
|
if (Math.abs(scale11[0] - scale11[1]) > DRAFT_TO_LOFT_EPSILON) return null;
|
|
4569
4569
|
return {
|
|
4570
4570
|
kind: "roundedRect",
|
|
4571
|
-
width:
|
|
4572
|
-
height:
|
|
4573
|
-
radius:
|
|
4571
|
+
width: profile2.width * scale11[0],
|
|
4572
|
+
height: profile2.height * scale11[1],
|
|
4573
|
+
radius: profile2.radius * scale11[0],
|
|
4574
4574
|
transforms: []
|
|
4575
4575
|
};
|
|
4576
4576
|
case "circle":
|
|
4577
4577
|
if (Math.abs(scale11[0] - scale11[1]) > DRAFT_TO_LOFT_EPSILON) return null;
|
|
4578
|
-
return { kind: "circle", radius:
|
|
4578
|
+
return { kind: "circle", radius: profile2.radius * scale11[0], segments: profile2.segments, transforms: [] };
|
|
4579
4579
|
case "polygon":
|
|
4580
|
-
return { kind: "polygon", points:
|
|
4580
|
+
return { kind: "polygon", points: profile2.points.map(([x, y]) => [x * scale11[0], y * scale11[1]]), transforms: [] };
|
|
4581
4581
|
case "pathProfile":
|
|
4582
4582
|
return null;
|
|
4583
4583
|
case "boolean": {
|
|
4584
|
-
if (
|
|
4585
|
-
const profiles2 =
|
|
4584
|
+
if (profile2.transforms.length > 0) return null;
|
|
4585
|
+
const profiles2 = profile2.profiles.map((child) => scaleProfile(child, scale11));
|
|
4586
4586
|
if (profiles2.some((child) => child == null)) return null;
|
|
4587
|
-
return { kind: "boolean", op:
|
|
4587
|
+
return { kind: "boolean", op: profile2.op, profiles: profiles2, transforms: [] };
|
|
4588
4588
|
}
|
|
4589
4589
|
case "offset":
|
|
4590
4590
|
case "project":
|
|
@@ -4597,10 +4597,10 @@ function directVerticalPrismDraftBase(plan) {
|
|
|
4597
4597
|
return directVerticalPrismDraftBase(plan.base);
|
|
4598
4598
|
case "box": {
|
|
4599
4599
|
if (![plan.x, plan.y, plan.z].every(Number.isFinite)) return null;
|
|
4600
|
-
const
|
|
4600
|
+
const profile2 = { kind: "rect", width: Math.abs(plan.x), height: Math.abs(plan.y), transforms: [] };
|
|
4601
4601
|
return {
|
|
4602
|
-
bottomProfile: cloneProfileCompilePlan(
|
|
4603
|
-
topProfile: cloneProfileCompilePlan(
|
|
4602
|
+
bottomProfile: cloneProfileCompilePlan(profile2),
|
|
4603
|
+
topProfile: cloneProfileCompilePlan(profile2),
|
|
4604
4604
|
zMin: Math.min(0, plan.z),
|
|
4605
4605
|
zMax: Math.max(0, plan.z)
|
|
4606
4606
|
};
|
|
@@ -4951,13 +4951,13 @@ function buildHoleShapeCompilePlan(base, placement, hole2, extent) {
|
|
|
4951
4951
|
extent: cloneFeatureCutExtent2(extent)
|
|
4952
4952
|
};
|
|
4953
4953
|
}
|
|
4954
|
-
function buildCutShapeCompilePlan(base, placement,
|
|
4955
|
-
if (!base || !placement || !
|
|
4954
|
+
function buildCutShapeCompilePlan(base, placement, profile2, extent, taper) {
|
|
4955
|
+
if (!base || !placement || !profile2 || !isFinitePositive2(featureCutExtentDepth(extent))) return null;
|
|
4956
4956
|
return {
|
|
4957
4957
|
kind: "cut",
|
|
4958
4958
|
base: cloneShapeCompilePlan(base),
|
|
4959
4959
|
placement: cloneShapeWorkplanePlacement(placement),
|
|
4960
|
-
profile: cloneProfileCompilePlan(
|
|
4960
|
+
profile: cloneProfileCompilePlan(profile2),
|
|
4961
4961
|
extent: cloneFeatureCutExtent2(extent),
|
|
4962
4962
|
taper: taper ? { scale: [taper.scale[0], taper.scale[1]] } : void 0
|
|
4963
4963
|
};
|
|
@@ -6179,12 +6179,12 @@ function parseModel(data) {
|
|
|
6179
6179
|
const objects = [];
|
|
6180
6180
|
for (const match of xml.matchAll(/<object\b([^>]*)>([\s\S]*?)<\/object>/gi)) {
|
|
6181
6181
|
const attrs = parseAttrs(match[1]);
|
|
6182
|
-
const
|
|
6183
|
-
const vertices = [...
|
|
6182
|
+
const body2 = match[2];
|
|
6183
|
+
const vertices = [...body2.matchAll(/<vertex\b([^>]*)\/?>/gi)].map((vertexMatch) => {
|
|
6184
6184
|
const vertex = parseAttrs(vertexMatch[1]);
|
|
6185
6185
|
return [finiteNumber(vertex.x, "vertex x"), finiteNumber(vertex.y, "vertex y"), finiteNumber(vertex.z, "vertex z")];
|
|
6186
6186
|
});
|
|
6187
|
-
const triangles = [...
|
|
6187
|
+
const triangles = [...body2.matchAll(/<triangle\b([^>]*)\/?>/gi)].map((triangleMatch) => {
|
|
6188
6188
|
const triangle = parseAttrs(triangleMatch[1]);
|
|
6189
6189
|
return [
|
|
6190
6190
|
finiteIndex(triangle.v1, "triangle v1"),
|
|
@@ -6204,8 +6204,8 @@ function parseModel(data) {
|
|
|
6204
6204
|
type: cleanName(attrs.type) ?? void 0,
|
|
6205
6205
|
vertices,
|
|
6206
6206
|
triangles,
|
|
6207
|
-
meshCount: [...
|
|
6208
|
-
components: [...
|
|
6207
|
+
meshCount: [...body2.matchAll(/<mesh\b/gi)].length,
|
|
6208
|
+
components: [...body2.matchAll(/<component\b([^>]*)\/?>/gi)].map((componentMatch) => {
|
|
6209
6209
|
const component = parseAttrs(componentMatch[1]);
|
|
6210
6210
|
if (!component.objectid) throw new Error(`3MF object "${attrs.id ?? objects.length + 1}" contains a component without objectid`);
|
|
6211
6211
|
return { objectId: component.objectid, transform: parseTransform(component.transform) };
|
|
@@ -8790,8 +8790,8 @@ function positiveMod(v, period) {
|
|
|
8790
8790
|
function evalStripesPattern(u, v, directionX, directionY, spacing, width, depth) {
|
|
8791
8791
|
const coord = u * directionX + v * directionY;
|
|
8792
8792
|
const d = abs3(coord - Math.round(coord / spacing) * spacing);
|
|
8793
|
-
const
|
|
8794
|
-
return -(
|
|
8793
|
+
const profile2 = max2(0, 1 - d / (width * 0.5));
|
|
8794
|
+
return -(profile2 * profile2) * depth;
|
|
8795
8795
|
}
|
|
8796
8796
|
function evalOverUnderWeavePattern(u, v, spacingX, spacingY, widthX, widthY, depth, underScale) {
|
|
8797
8797
|
const su = u / spacingX;
|
|
@@ -13014,7 +13014,7 @@ function applyProfileCompileTransforms(crossSection, transforms) {
|
|
|
13014
13014
|
return out;
|
|
13015
13015
|
}
|
|
13016
13016
|
function lowerProfileBooleanCompilePlan(plan, wasm) {
|
|
13017
|
-
const profiles2 = plan.profiles.map((
|
|
13017
|
+
const profiles2 = plan.profiles.map((profile2) => lowerProfileCompilePlanToCrossSection(profile2, wasm));
|
|
13018
13018
|
if (profiles2.length === 0) {
|
|
13019
13019
|
throw new Error(`Cannot lower empty profile boolean (${plan.op})`);
|
|
13020
13020
|
}
|
|
@@ -13414,11 +13414,11 @@ function fromSlicesOffsetEquivalentPlanForManifold(plan) {
|
|
|
13414
13414
|
};
|
|
13415
13415
|
return transformZAlignedFromSlicesPlanForManifold(zAligned, group2.normal);
|
|
13416
13416
|
}
|
|
13417
|
-
function offsetProfilePolygonsForManifold(
|
|
13417
|
+
function offsetProfilePolygonsForManifold(profile2, thickness, wasm) {
|
|
13418
13418
|
const crossSection = lowerProfileCompilePlanToCrossSection(
|
|
13419
13419
|
{
|
|
13420
13420
|
kind: "offset",
|
|
13421
|
-
base:
|
|
13421
|
+
base: profile2,
|
|
13422
13422
|
delta: thickness,
|
|
13423
13423
|
join: "Miter",
|
|
13424
13424
|
transforms: []
|
|
@@ -13431,8 +13431,8 @@ function offsetProfilePolygonsForManifold(profile, thickness, wasm) {
|
|
|
13431
13431
|
disposeWasmObject(crossSection);
|
|
13432
13432
|
}
|
|
13433
13433
|
}
|
|
13434
|
-
function profilePolygonsForManifold(
|
|
13435
|
-
const crossSection = lowerProfileCompilePlanToCrossSection(
|
|
13434
|
+
function profilePolygonsForManifold(profile2, wasm) {
|
|
13435
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(profile2, wasm);
|
|
13436
13436
|
try {
|
|
13437
13437
|
return crossSection.toPolygons();
|
|
13438
13438
|
} finally {
|
|
@@ -13475,7 +13475,7 @@ function lowerOffsetLoftCompilePlan(plan, thickness, wasm) {
|
|
|
13475
13475
|
if (heights.some((height) => !Number.isFinite(height)) || heights.some((height, idx) => idx > 0 && height <= heights[idx - 1] + OFFSET_SOLID_EPS)) {
|
|
13476
13476
|
throw new Error("offsetSolid() collapsed the compatible-loft height span.");
|
|
13477
13477
|
}
|
|
13478
|
-
const offsetPolygons = plan.profiles.map((
|
|
13478
|
+
const offsetPolygons = plan.profiles.map((profile2) => offsetProfilePolygonsForManifold(profile2, thickness, wasm));
|
|
13479
13479
|
const stitched = loftStitched(offsetPolygons, heights, wasm, { edgeLength: plan.edgeLength });
|
|
13480
13480
|
if (!stitched) {
|
|
13481
13481
|
throw new Error(`Offset solid requires the OCCT backend. ${OCCT_BACKEND_REQUIRED_HINT}`);
|
|
@@ -13586,7 +13586,7 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
|
|
|
13586
13586
|
if (zMax <= zMin + OFFSET_SOLID_EPS) {
|
|
13587
13587
|
throw new Error("offsetSolid() collapsed the vertical-prism height.");
|
|
13588
13588
|
}
|
|
13589
|
-
const
|
|
13589
|
+
const profile2 = lowerProfileCompilePlanToCrossSection(
|
|
13590
13590
|
{
|
|
13591
13591
|
kind: "offset",
|
|
13592
13592
|
base: base.profile,
|
|
@@ -13597,16 +13597,16 @@ function lowerOffsetSolidCompilePlan(plan, wasm) {
|
|
|
13597
13597
|
wasm
|
|
13598
13598
|
);
|
|
13599
13599
|
try {
|
|
13600
|
-
const solid =
|
|
13600
|
+
const solid = profile2.extrude(zMax - zMin, 0, 0, void 0, false);
|
|
13601
13601
|
if (Math.abs(zMin) <= OFFSET_SOLID_EPS) return solid;
|
|
13602
13602
|
return applyShapeCompileTransforms(solid, [{ kind: "translate", x: 0, y: 0, z: zMin }]);
|
|
13603
13603
|
} finally {
|
|
13604
|
-
disposeWasmObject(
|
|
13604
|
+
disposeWasmObject(profile2);
|
|
13605
13605
|
}
|
|
13606
13606
|
}
|
|
13607
13607
|
function lowerShapeLoftCompilePlan(plan, wasm) {
|
|
13608
|
-
const inputPolygons = plan.profiles.map((
|
|
13609
|
-
const crossSection = lowerProfileCompilePlanToCrossSection(
|
|
13608
|
+
const inputPolygons = plan.profiles.map((profile2) => {
|
|
13609
|
+
const crossSection = lowerProfileCompilePlanToCrossSection(profile2, wasm);
|
|
13610
13610
|
try {
|
|
13611
13611
|
return crossSection.toPolygons();
|
|
13612
13612
|
} finally {
|
|
@@ -14007,9 +14007,9 @@ function lowerShapeFilletCompilePlan(plan, wasm) {
|
|
|
14007
14007
|
const result = applyFilletSelectionToManifold(base, selection.selection, plan.radius, plan.segments, wasm);
|
|
14008
14008
|
if (result !== base) disposeWasmObject(base);
|
|
14009
14009
|
return result;
|
|
14010
|
-
} catch (
|
|
14010
|
+
} catch (error2) {
|
|
14011
14011
|
disposeWasmObject(base);
|
|
14012
|
-
throw
|
|
14012
|
+
throw error2;
|
|
14013
14013
|
}
|
|
14014
14014
|
}
|
|
14015
14015
|
function lowerShapeChamferCompilePlan(plan, wasm) {
|
|
@@ -14025,9 +14025,9 @@ function lowerShapeChamferCompilePlan(plan, wasm) {
|
|
|
14025
14025
|
const result = applyChamferSelectionToManifold(base, selection.selection, plan.size, wasm);
|
|
14026
14026
|
if (result !== base) disposeWasmObject(base);
|
|
14027
14027
|
return result;
|
|
14028
|
-
} catch (
|
|
14028
|
+
} catch (error2) {
|
|
14029
14029
|
disposeWasmObject(base);
|
|
14030
|
-
throw
|
|
14030
|
+
throw error2;
|
|
14031
14031
|
}
|
|
14032
14032
|
}
|
|
14033
14033
|
function edgeSegmentToSelection(segment) {
|
|
@@ -14188,10 +14188,10 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
|
14188
14188
|
}
|
|
14189
14189
|
}
|
|
14190
14190
|
case "extrude": {
|
|
14191
|
-
const
|
|
14191
|
+
const profile2 = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
14192
14192
|
try {
|
|
14193
14193
|
const height = plan.height;
|
|
14194
|
-
const solid =
|
|
14194
|
+
const solid = profile2.extrude(
|
|
14195
14195
|
Math.abs(height),
|
|
14196
14196
|
plan.twistSegments ?? 0,
|
|
14197
14197
|
plan.twist ?? 0,
|
|
@@ -14205,7 +14205,7 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
|
14205
14205
|
disposeWasmObject(solid);
|
|
14206
14206
|
}
|
|
14207
14207
|
} finally {
|
|
14208
|
-
disposeWasmObject(
|
|
14208
|
+
disposeWasmObject(profile2);
|
|
14209
14209
|
}
|
|
14210
14210
|
}
|
|
14211
14211
|
case "sheetMetal":
|
|
@@ -14226,11 +14226,11 @@ function lowerShapeCompilePlanToManifold(plan, wasm) {
|
|
|
14226
14226
|
return lowerShapeCompilePlanToManifold(lowered.plan, wasm);
|
|
14227
14227
|
}
|
|
14228
14228
|
case "revolve": {
|
|
14229
|
-
const
|
|
14229
|
+
const profile2 = lowerProfileCompilePlanToCrossSection(plan.profile, wasm);
|
|
14230
14230
|
try {
|
|
14231
|
-
return
|
|
14231
|
+
return profile2.revolve(plan.segments ?? 0, plan.degrees);
|
|
14232
14232
|
} finally {
|
|
14233
|
-
disposeWasmObject(
|
|
14233
|
+
disposeWasmObject(profile2);
|
|
14234
14234
|
}
|
|
14235
14235
|
}
|
|
14236
14236
|
case "loft":
|
|
@@ -14757,9 +14757,9 @@ var ManifoldProfileBackend = class _ManifoldProfileBackend {
|
|
|
14757
14757
|
function wrapManifoldProfileBackend(cs) {
|
|
14758
14758
|
return new ManifoldProfileBackend(cs);
|
|
14759
14759
|
}
|
|
14760
|
-
function requireManifoldCrossSection(
|
|
14761
|
-
if (
|
|
14762
|
-
return
|
|
14760
|
+
function requireManifoldCrossSection(profile2) {
|
|
14761
|
+
if (profile2 instanceof ManifoldProfileBackend) {
|
|
14762
|
+
return profile2.requireCrossSection();
|
|
14763
14763
|
}
|
|
14764
14764
|
throw new Error("requireManifoldCrossSection(): expected a ManifoldProfileBackend");
|
|
14765
14765
|
}
|
|
@@ -15522,9 +15522,9 @@ var OCCTProfileBackend = class _OCCTProfileBackend {
|
|
|
15522
15522
|
function wrapOCCTProfileBackend(face) {
|
|
15523
15523
|
return new OCCTProfileBackend(face);
|
|
15524
15524
|
}
|
|
15525
|
-
function requireOCCTFace(
|
|
15526
|
-
if (
|
|
15527
|
-
return
|
|
15525
|
+
function requireOCCTFace(profile2) {
|
|
15526
|
+
if (profile2 instanceof OCCTProfileBackend) {
|
|
15527
|
+
return profile2.requireFace();
|
|
15528
15528
|
}
|
|
15529
15529
|
throw new Error("requireOCCTFace(): expected an OCCTProfileBackend");
|
|
15530
15530
|
}
|
|
@@ -17804,9 +17804,9 @@ function expandBounds3(b, amount) {
|
|
|
17804
17804
|
const pad = Math.abs(amount);
|
|
17805
17805
|
return bounds3([b.min[0] - pad, b.min[1] - pad, b.min[2] - pad], [b.max[0] + pad, b.max[1] + pad, b.max[2] + pad]);
|
|
17806
17806
|
}
|
|
17807
|
-
function boundsFromProfileZ(
|
|
17808
|
-
if (!
|
|
17809
|
-
return bounds3([
|
|
17807
|
+
function boundsFromProfileZ(profile2, z0, z1) {
|
|
17808
|
+
if (!profile2) return null;
|
|
17809
|
+
return bounds3([profile2.min[0], profile2.min[1], z0], [profile2.max[0], profile2.max[1], z1]);
|
|
17810
17810
|
}
|
|
17811
17811
|
function transformBounds(b, transform) {
|
|
17812
17812
|
if (!b) return null;
|
|
@@ -17857,14 +17857,14 @@ function transformedBySteps(b, steps) {
|
|
|
17857
17857
|
return out;
|
|
17858
17858
|
}
|
|
17859
17859
|
function extrudeBounds(plan) {
|
|
17860
|
-
const
|
|
17861
|
-
let base = boundsFromProfileZ(
|
|
17862
|
-
if (!base || !
|
|
17860
|
+
const profile2 = boundsFromProfilePlan(plan.profile);
|
|
17861
|
+
let base = boundsFromProfileZ(profile2, 0, plan.height);
|
|
17862
|
+
if (!base || !profile2) return null;
|
|
17863
17863
|
if (plan.scaleTop) {
|
|
17864
17864
|
const top = boundsFromProfileZ(
|
|
17865
17865
|
{
|
|
17866
|
-
min: [
|
|
17867
|
-
max: [
|
|
17866
|
+
min: [profile2.min[0] * plan.scaleTop[0], profile2.min[1] * plan.scaleTop[1]],
|
|
17867
|
+
max: [profile2.max[0] * plan.scaleTop[0], profile2.max[1] * plan.scaleTop[1]]
|
|
17868
17868
|
},
|
|
17869
17869
|
plan.height,
|
|
17870
17870
|
plan.height
|
|
@@ -17873,10 +17873,10 @@ function extrudeBounds(plan) {
|
|
|
17873
17873
|
}
|
|
17874
17874
|
if (plan.twist && Math.abs(plan.twist) > 1e-9 && base) {
|
|
17875
17875
|
const radial = Math.max(
|
|
17876
|
-
Math.hypot(
|
|
17877
|
-
Math.hypot(
|
|
17878
|
-
Math.hypot(
|
|
17879
|
-
Math.hypot(
|
|
17876
|
+
Math.hypot(profile2.min[0], profile2.min[1]),
|
|
17877
|
+
Math.hypot(profile2.min[0], profile2.max[1]),
|
|
17878
|
+
Math.hypot(profile2.max[0], profile2.min[1]),
|
|
17879
|
+
Math.hypot(profile2.max[0], profile2.max[1])
|
|
17880
17880
|
);
|
|
17881
17881
|
base = bounds3([-radial, -radial, base.min[2]], [radial, radial, base.max[2]]);
|
|
17882
17882
|
}
|
|
@@ -17902,13 +17902,13 @@ function pathBounds(path2) {
|
|
|
17902
17902
|
);
|
|
17903
17903
|
}
|
|
17904
17904
|
}
|
|
17905
|
-
function profileRadius(
|
|
17906
|
-
if (!
|
|
17905
|
+
function profileRadius(profile2) {
|
|
17906
|
+
if (!profile2) return 0;
|
|
17907
17907
|
return Math.max(
|
|
17908
|
-
Math.hypot(
|
|
17909
|
-
Math.hypot(
|
|
17910
|
-
Math.hypot(
|
|
17911
|
-
Math.hypot(
|
|
17908
|
+
Math.hypot(profile2.min[0], profile2.min[1]),
|
|
17909
|
+
Math.hypot(profile2.min[0], profile2.max[1]),
|
|
17910
|
+
Math.hypot(profile2.max[0], profile2.min[1]),
|
|
17911
|
+
Math.hypot(profile2.max[0], profile2.max[1])
|
|
17912
17912
|
);
|
|
17913
17913
|
}
|
|
17914
17914
|
function analyticSurfaceBounds(plan) {
|
|
@@ -17957,14 +17957,14 @@ function boundsFromShapeBase(plan) {
|
|
|
17957
17957
|
case "extrude":
|
|
17958
17958
|
return extrudeBounds(plan);
|
|
17959
17959
|
case "revolve": {
|
|
17960
|
-
const
|
|
17961
|
-
if (!
|
|
17962
|
-
const radius = Math.max(Math.abs(
|
|
17963
|
-
return bounds3([-radius, -radius,
|
|
17960
|
+
const profile2 = boundsFromProfilePlan(plan.profile);
|
|
17961
|
+
if (!profile2) return null;
|
|
17962
|
+
const radius = Math.max(Math.abs(profile2.min[0]), Math.abs(profile2.max[0]));
|
|
17963
|
+
return bounds3([-radius, -radius, profile2.min[1]], [radius, radius, profile2.max[1]]);
|
|
17964
17964
|
}
|
|
17965
17965
|
case "loft":
|
|
17966
17966
|
return unionBounds3(
|
|
17967
|
-
plan.profiles.map((
|
|
17967
|
+
plan.profiles.map((profile2, i) => boundsFromProfileZ(boundsFromProfilePlan(profile2), plan.heights[i] ?? 0, plan.heights[i] ?? 0))
|
|
17968
17968
|
);
|
|
17969
17969
|
case "sweep":
|
|
17970
17970
|
return expandBounds3(pathBounds(plan.path), profileRadius(boundsFromProfilePlan(plan.profile)) + plan.boundsPadding);
|
|
@@ -19010,12 +19010,12 @@ function wrapTruckPolygonProfileBackend(polygons) {
|
|
|
19010
19010
|
function wrapTruckProfileRegions(regions) {
|
|
19011
19011
|
return new TruckPolygonProfileBackend(regions, true);
|
|
19012
19012
|
}
|
|
19013
|
-
function requireTruckPolygonProfileBackend(
|
|
19014
|
-
if (
|
|
19013
|
+
function requireTruckPolygonProfileBackend(profile2) {
|
|
19014
|
+
if (profile2 instanceof TruckPolygonProfileBackend) return profile2;
|
|
19015
19015
|
throw new Error("requireTruckPolygonProfileBackend(): expected a TruckPolygonProfileBackend");
|
|
19016
19016
|
}
|
|
19017
|
-
function requireTruckProfileRegions(
|
|
19018
|
-
return requireTruckPolygonProfileBackend(
|
|
19017
|
+
function requireTruckProfileRegions(profile2) {
|
|
19018
|
+
return requireTruckPolygonProfileBackend(profile2).requireRegions();
|
|
19019
19019
|
}
|
|
19020
19020
|
|
|
19021
19021
|
// src/forge/backends/truck/shapeBackend.ts
|
|
@@ -20330,7 +20330,7 @@ function lowerProfileBooleanToRegions(plan) {
|
|
|
20330
20330
|
if (plan.profiles.length === 0) {
|
|
20331
20331
|
throw new Error(`Truck backend cannot lower empty profile boolean "${plan.op}".`);
|
|
20332
20332
|
}
|
|
20333
|
-
const operands = plan.profiles.map((
|
|
20333
|
+
const operands = plan.profiles.map((profile2) => regionsToMultiPolygon2(profilePlanToRegions(profile2)));
|
|
20334
20334
|
const result = (() => {
|
|
20335
20335
|
switch (plan.op) {
|
|
20336
20336
|
case "union":
|
|
@@ -21127,8 +21127,8 @@ function lowerGenericBooleanPlan(plan) {
|
|
|
21127
21127
|
let rawBoolean;
|
|
21128
21128
|
try {
|
|
21129
21129
|
rawBoolean = wrapTruckShapeBackend(getTruckGeometryWasm().geometry_boolean(JSON.stringify(booleanHandles), plan.op));
|
|
21130
|
-
} catch (
|
|
21131
|
-
if (plan.op !== "union") throw
|
|
21130
|
+
} catch (error2) {
|
|
21131
|
+
if (plan.op !== "union") throw error2;
|
|
21132
21132
|
returned = wrapTruckShapeBackend(getTruckGeometryWasm().geometry_union(JSON.stringify(booleanHandles)));
|
|
21133
21133
|
return returned;
|
|
21134
21134
|
}
|
|
@@ -21479,9 +21479,9 @@ function lowerImportedMeshPlan(plan) {
|
|
|
21479
21479
|
})
|
|
21480
21480
|
)
|
|
21481
21481
|
);
|
|
21482
|
-
} catch (
|
|
21482
|
+
} catch (error2) {
|
|
21483
21483
|
throw new Error(
|
|
21484
|
-
`importMesh("${plan.filePath}"): Truck rejected the mesh; it must be closed, manifold, and non-degenerate. Original error: ${
|
|
21484
|
+
`importMesh("${plan.filePath}"): Truck rejected the mesh; it must be closed, manifold, and non-degenerate. Original error: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21485
21485
|
);
|
|
21486
21486
|
}
|
|
21487
21487
|
}
|
|
@@ -21696,8 +21696,8 @@ function lowerSdfPlan(plan) {
|
|
|
21696
21696
|
const shape = lowerExtractedSdfMesh(mesh, cappedEvalFn, true);
|
|
21697
21697
|
if (plan.meshing?.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
|
|
21698
21698
|
return shape;
|
|
21699
|
-
} catch (
|
|
21700
|
-
surfaceNetsError =
|
|
21699
|
+
} catch (error2) {
|
|
21700
|
+
surfaceNetsError = error2;
|
|
21701
21701
|
}
|
|
21702
21702
|
const tetraMesh = marchingTetrahedra(cappedEvalFn, plan.bounds, plan.edgeLength);
|
|
21703
21703
|
assertSdfMeshBudget(tetraMesh, plan);
|
|
@@ -21705,9 +21705,9 @@ function lowerSdfPlan(plan) {
|
|
|
21705
21705
|
const shape = lowerExtractedSdfMesh(tetraMesh, cappedEvalFn, false);
|
|
21706
21706
|
if (plan.meshing?.diagnostics) logSdfMeshingDiagnostics("SDF meshing result", plan.meshing.diagnostics);
|
|
21707
21707
|
return shape;
|
|
21708
|
-
} catch (
|
|
21708
|
+
} catch (error2) {
|
|
21709
21709
|
throw new Error(
|
|
21710
|
-
`Truck backend does not support compile plan "sdf" for this materialized field yet: Surface Nets failed with ${surfaceNetsError instanceof Error ? surfaceNetsError.message : String(surfaceNetsError)}; marching tetrahedra failed with ${
|
|
21710
|
+
`Truck backend does not support compile plan "sdf" for this materialized field yet: Surface Nets failed with ${surfaceNetsError instanceof Error ? surfaceNetsError.message : String(surfaceNetsError)}; marching tetrahedra failed with ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21711
21711
|
);
|
|
21712
21712
|
}
|
|
21713
21713
|
}
|
|
@@ -21863,7 +21863,7 @@ function lowerDraftPlan2(plan) {
|
|
|
21863
21863
|
);
|
|
21864
21864
|
}
|
|
21865
21865
|
function offsetLoftPlan(plan, thickness) {
|
|
21866
|
-
const offsetStations = plan.profiles.map((
|
|
21866
|
+
const offsetStations = plan.profiles.map((profile2) => offsetRegionsWithClipper2(profilePlanToRegions(profile2), thickness, "Miter"));
|
|
21867
21867
|
if (!regionsHaveMatchingTopology(offsetStations)) {
|
|
21868
21868
|
return truckUnsupported("offsetSolid() for lofts whose station offsets change profile topology");
|
|
21869
21869
|
}
|
|
@@ -22083,8 +22083,8 @@ function lowerLoftPlan2(plan) {
|
|
|
22083
22083
|
return wrapTruckShapeBackend(
|
|
22084
22084
|
getTruckGeometryWasm().geometry_create_lofted_regions(
|
|
22085
22085
|
JSON.stringify(
|
|
22086
|
-
plan.profiles.map((
|
|
22087
|
-
regions: profilePlanToRegions(
|
|
22086
|
+
plan.profiles.map((profile2, idx) => ({
|
|
22087
|
+
regions: profilePlanToRegions(profile2),
|
|
22088
22088
|
z: plan.heights[idx]
|
|
22089
22089
|
}))
|
|
22090
22090
|
)
|
|
@@ -22431,9 +22431,9 @@ function lowerThickenedClosedSewMesh(shapes, thickness) {
|
|
|
22431
22431
|
if (!mesh) return null;
|
|
22432
22432
|
try {
|
|
22433
22433
|
return wrapTruckShapeBackend(getTruckGeometryWasm().geometry_create_imported_triangle_mesh(JSON.stringify(mesh)));
|
|
22434
|
-
} catch (
|
|
22434
|
+
} catch (error2) {
|
|
22435
22435
|
throw new Error(
|
|
22436
|
-
`Truck backend could not thicken the closed sewn surface set as a manifold shell: ${
|
|
22436
|
+
`Truck backend could not thicken the closed sewn surface set as a manifold shell: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
22437
22437
|
);
|
|
22438
22438
|
}
|
|
22439
22439
|
}
|
|
@@ -22510,9 +22510,9 @@ function lowerSurfaceSolidPlan2(plan) {
|
|
|
22510
22510
|
if (!mesh) truckUnsupported("Surface.Solid() for this surface source");
|
|
22511
22511
|
try {
|
|
22512
22512
|
return wrapTruckShapeBackend(getTruckGeometryWasm().geometry_create_imported_triangle_mesh(JSON.stringify(mesh)));
|
|
22513
|
-
} catch (
|
|
22513
|
+
} catch (error2) {
|
|
22514
22514
|
throw new Error(
|
|
22515
|
-
`Truck backend could not build Surface.Solid() from the closed surface set: ${
|
|
22515
|
+
`Truck backend could not build Surface.Solid() from the closed surface set: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
22516
22516
|
);
|
|
22517
22517
|
}
|
|
22518
22518
|
}
|
|
@@ -22917,9 +22917,9 @@ function transformedSphereProfile(basePlan, steps, sliceOffset) {
|
|
|
22917
22917
|
const radius = Math.abs(source.radius * scale11);
|
|
22918
22918
|
const sectionRadius = sliceOffset == null ? radius : Math.sqrt(Math.max(0, radius * radius - (sliceOffset - center[2]) * (sliceOffset - center[2])));
|
|
22919
22919
|
if (sectionRadius <= EXACT_PROFILE_EPS) return emptyProfilePlan();
|
|
22920
|
-
const
|
|
22921
|
-
if (!isNearlyZero(center[0]) || !isNearlyZero(center[1]))
|
|
22922
|
-
return
|
|
22920
|
+
const profile2 = circleProfilePlan(sectionRadius, source.segments);
|
|
22921
|
+
if (!isNearlyZero(center[0]) || !isNearlyZero(center[1])) profile2.transforms.push({ kind: "translate", x: center[0], y: center[1] });
|
|
22922
|
+
return profile2;
|
|
22923
22923
|
}
|
|
22924
22924
|
function profileTransformsForShapeTransform(step) {
|
|
22925
22925
|
switch (step.kind) {
|
|
@@ -23823,15 +23823,15 @@ function centeredRectSegmentEnvelopeProjectionProfilePlan(footprints) {
|
|
|
23823
23823
|
for (let idx = 0; idx + 1 < footprints.length; idx += 1) {
|
|
23824
23824
|
const a = footprints[idx];
|
|
23825
23825
|
const b = footprints[idx + 1];
|
|
23826
|
-
const
|
|
23827
|
-
if (!
|
|
23826
|
+
const profile2 = centeredRectPairProjectionProfilePlan({ outer: a.outer, holes: [] }, { outer: b.outer, holes: [] });
|
|
23827
|
+
if (!profile2) return null;
|
|
23828
23828
|
const segmentOuter = selectLargestNestedRectFootprint([a.outer, b.outer]) ?? {
|
|
23829
23829
|
width: Math.max(a.outer.width, b.outer.width),
|
|
23830
23830
|
height: Math.max(a.outer.height, b.outer.height)
|
|
23831
23831
|
};
|
|
23832
23832
|
const holes = centeredRectCommonHoleFootprints([a, b], segmentOuter);
|
|
23833
23833
|
if (!holes) return null;
|
|
23834
|
-
profiles2.push(differenceCenteredRectHoles(
|
|
23834
|
+
profiles2.push(differenceCenteredRectHoles(profile2, holes));
|
|
23835
23835
|
}
|
|
23836
23836
|
if (profiles2.length === 1) return profiles2[0];
|
|
23837
23837
|
return { kind: "boolean", op: "union", profiles: profiles2, transforms: [] };
|
|
@@ -24373,8 +24373,8 @@ function singleRegionWithoutHoles(regions) {
|
|
|
24373
24373
|
}
|
|
24374
24374
|
function nestedSingleRegionLoftProjectionProfilePlan(profiles2) {
|
|
24375
24375
|
if (profiles2.length < 2) return null;
|
|
24376
|
-
const regions = profiles2.map((
|
|
24377
|
-
const lowered = regionsForProfilePlan(
|
|
24376
|
+
const regions = profiles2.map((profile2) => {
|
|
24377
|
+
const lowered = regionsForProfilePlan(profile2);
|
|
24378
24378
|
return lowered ? singleRegionWithoutHoles(lowered) : null;
|
|
24379
24379
|
});
|
|
24380
24380
|
if (regions.some((region) => !region)) return null;
|
|
@@ -24403,8 +24403,8 @@ function loftProjectedProfilePlan(plan) {
|
|
|
24403
24403
|
function variableSweepProjectedProfilePlan(plan) {
|
|
24404
24404
|
const pathInfo = verticalSweepPathInfo(plan.path, plan.up);
|
|
24405
24405
|
if (!pathInfo || !hasDistinctFiniteSectionParameters(plan.sections)) return null;
|
|
24406
|
-
const
|
|
24407
|
-
return
|
|
24406
|
+
const profile2 = compatibleLoftProjectionProfilePlan(plan.sections.map((section) => section.profile));
|
|
24407
|
+
return profile2 ? appendProfileTransforms(profile2, pathInfo.transforms) : null;
|
|
24408
24408
|
}
|
|
24409
24409
|
function normalizeIntervals(intervals) {
|
|
24410
24410
|
const sorted = intervals.map(([a, b]) => [Math.min(a, b), Math.max(a, b)]).filter(([a, b]) => b - a > EXACT_PROFILE_EPS).sort((a, b) => a[0] - b[0]);
|
|
@@ -24451,7 +24451,7 @@ function subtractIntervals(subject, clips) {
|
|
|
24451
24451
|
return out;
|
|
24452
24452
|
}
|
|
24453
24453
|
function profilePlanForRadialIntervals(intervals, segments) {
|
|
24454
|
-
const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => annulusProfilePlan(outer, Math.max(0, inner), segments)).filter((
|
|
24454
|
+
const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => annulusProfilePlan(outer, Math.max(0, inner), segments)).filter((profile2) => !isEmptyProfilePlan(profile2));
|
|
24455
24455
|
if (profiles2.length === 0) return emptyProfilePlan();
|
|
24456
24456
|
if (profiles2.length === 1) return profiles2[0];
|
|
24457
24457
|
return { kind: "boolean", op: "union", profiles: profiles2, transforms: [] };
|
|
@@ -24482,7 +24482,7 @@ function sectorProfilePlan(innerRadius, outerRadius, degrees, segments) {
|
|
|
24482
24482
|
}
|
|
24483
24483
|
function partialRevolutionProfilePlanForRadialIntervals(intervals, degrees, segments) {
|
|
24484
24484
|
if (!Number.isFinite(degrees) || isNearlyZero(degrees)) return null;
|
|
24485
|
-
const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => sectorProfilePlan(inner, outer, degrees, segments)).filter((
|
|
24485
|
+
const profiles2 = normalizeIntervals(intervals).map(([inner, outer]) => sectorProfilePlan(inner, outer, degrees, segments)).filter((profile2) => !isEmptyProfilePlan(profile2));
|
|
24486
24486
|
if (profiles2.length === 0) return emptyProfilePlan();
|
|
24487
24487
|
if (profiles2.length === 1) return profiles2[0];
|
|
24488
24488
|
return { kind: "boolean", op: "union", profiles: profiles2, transforms: [] };
|
|
@@ -24715,11 +24715,11 @@ function expandSimpleFullRevolutionProfileFootprint(plan, distance5) {
|
|
|
24715
24715
|
}
|
|
24716
24716
|
}
|
|
24717
24717
|
function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
|
|
24718
|
-
const translate = (
|
|
24718
|
+
const translate = (profile2, center) => {
|
|
24719
24719
|
if (!isNearlyZero(center[0]) || !isNearlyZero(center[1])) {
|
|
24720
|
-
|
|
24720
|
+
profile2.transforms.push({ kind: "translate", x: center[0], y: center[1] });
|
|
24721
24721
|
}
|
|
24722
|
-
return
|
|
24722
|
+
return profile2;
|
|
24723
24723
|
};
|
|
24724
24724
|
switch (footprint.kind) {
|
|
24725
24725
|
case "circle":
|
|
@@ -24737,12 +24737,12 @@ function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
|
|
|
24737
24737
|
case "roundedRect": {
|
|
24738
24738
|
const { halfWidth, halfHeight, radius, xAxis, yAxis, center } = footprint.footprint;
|
|
24739
24739
|
if (halfWidth <= EXACT_PROFILE_EPS || halfHeight <= EXACT_PROFILE_EPS || radius < -EXACT_PROFILE_EPS) return null;
|
|
24740
|
-
const
|
|
24740
|
+
const profile2 = roundedRectProfilePlan({
|
|
24741
24741
|
width: halfWidth * 2,
|
|
24742
24742
|
height: halfHeight * 2,
|
|
24743
24743
|
radius
|
|
24744
24744
|
});
|
|
24745
|
-
|
|
24745
|
+
profile2.transforms.push(
|
|
24746
24746
|
...profileOrientationTransforms({
|
|
24747
24747
|
m00: xAxis[0],
|
|
24748
24748
|
m01: yAxis[0],
|
|
@@ -24750,7 +24750,7 @@ function profilePlanForSimpleFullRevolutionFootprint(footprint, segments) {
|
|
|
24750
24750
|
m11: yAxis[1]
|
|
24751
24751
|
})
|
|
24752
24752
|
);
|
|
24753
|
-
return translate(
|
|
24753
|
+
return translate(profile2, center);
|
|
24754
24754
|
}
|
|
24755
24755
|
default:
|
|
24756
24756
|
return assertExhaustive(footprint);
|
|
@@ -24854,9 +24854,9 @@ function insetRadialIntervalsForFullRevolutionProfile(plan, distance5) {
|
|
|
24854
24854
|
if (plan.op === "union") {
|
|
24855
24855
|
const baseIntervalGroups = [];
|
|
24856
24856
|
const insetIntervals = [];
|
|
24857
|
-
for (const
|
|
24858
|
-
const baseIntervals = radialProjectionIntervalsForRevolveProfile(
|
|
24859
|
-
const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile(
|
|
24857
|
+
for (const profile2 of profiles2) {
|
|
24858
|
+
const baseIntervals = radialProjectionIntervalsForRevolveProfile(profile2);
|
|
24859
|
+
const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile(profile2, distance5);
|
|
24860
24860
|
if (!baseIntervals || !profileInsetIntervals) return null;
|
|
24861
24861
|
baseIntervalGroups.push(baseIntervals);
|
|
24862
24862
|
insetIntervals.push(...profileInsetIntervals);
|
|
@@ -24905,9 +24905,9 @@ function insetSliceProfileForFullRevolutionProfile(plan, distance5, segments) {
|
|
|
24905
24905
|
if (plan.op === "union") {
|
|
24906
24906
|
const baseIntervalGroups = [];
|
|
24907
24907
|
const sliceProfiles = [];
|
|
24908
|
-
for (const
|
|
24909
|
-
const baseIntervals = radialProjectionIntervalsForRevolveProfile(
|
|
24910
|
-
const sliceProfile = insetSliceProfileForFullRevolutionProfile(
|
|
24908
|
+
for (const profile2 of profiles2) {
|
|
24909
|
+
const baseIntervals = radialProjectionIntervalsForRevolveProfile(profile2);
|
|
24910
|
+
const sliceProfile = insetSliceProfileForFullRevolutionProfile(profile2, distance5, segments);
|
|
24911
24911
|
if (!baseIntervals || !sliceProfile) return null;
|
|
24912
24912
|
baseIntervalGroups.push(baseIntervals);
|
|
24913
24913
|
sliceProfiles.push(sliceProfile);
|
|
@@ -25061,7 +25061,7 @@ function fullRevolutionOffsetSourceContext(plan) {
|
|
|
25061
25061
|
}
|
|
25062
25062
|
}
|
|
25063
25063
|
function booleanProfilesWithParentTransforms(plan) {
|
|
25064
|
-
return plan.transforms.length === 0 ? plan.profiles : plan.profiles.map((
|
|
25064
|
+
return plan.transforms.length === 0 ? plan.profiles : plan.profiles.map((profile2) => appendProfileTransforms(profile2, plan.transforms));
|
|
25065
25065
|
}
|
|
25066
25066
|
function radialProjectionIntervalsForRevolveProfile(plan) {
|
|
25067
25067
|
if (plan.kind === "circle") {
|
|
@@ -25078,8 +25078,8 @@ function radialProjectionIntervalsForRevolveProfile(plan) {
|
|
|
25078
25078
|
const profiles2 = booleanProfilesWithParentTransforms(plan);
|
|
25079
25079
|
if (plan.op === "union") {
|
|
25080
25080
|
const intervals2 = [];
|
|
25081
|
-
for (const
|
|
25082
|
-
const profileIntervals = radialProjectionIntervalsForRevolveProfile(
|
|
25081
|
+
for (const profile2 of profiles2) {
|
|
25082
|
+
const profileIntervals = radialProjectionIntervalsForRevolveProfile(profile2);
|
|
25083
25083
|
if (!profileIntervals) return null;
|
|
25084
25084
|
intervals2.push(...profileIntervals);
|
|
25085
25085
|
}
|
|
@@ -25152,8 +25152,8 @@ function radialSliceIntervalsForRevolveProfile(plan, z) {
|
|
|
25152
25152
|
const profiles2 = booleanProfilesWithParentTransforms(plan);
|
|
25153
25153
|
if (plan.op === "union") {
|
|
25154
25154
|
const intervals2 = [];
|
|
25155
|
-
for (const
|
|
25156
|
-
const profileIntervals = radialSliceIntervalsForRevolveProfile(
|
|
25155
|
+
for (const profile2 of profiles2) {
|
|
25156
|
+
const profileIntervals = radialSliceIntervalsForRevolveProfile(profile2, z);
|
|
25157
25157
|
if (!profileIntervals) return null;
|
|
25158
25158
|
intervals2.push(...profileIntervals);
|
|
25159
25159
|
}
|
|
@@ -25276,9 +25276,9 @@ function offsetSolidFullRevolutionProfilePlan(plan) {
|
|
|
25276
25276
|
const baseProjectionIntervals = radialProjectionIntervalsForRevolveProfile(source.plan.profile);
|
|
25277
25277
|
const projectionIntervals = localThickness > EXACT_PROFILE_EPS ? baseProjectionIntervals ? expandRadialIntervalsForOutwardOffset(baseProjectionIntervals, localThickness) : null : insetRadialIntervalsForFullRevolutionProfile(source.plan.profile, Math.abs(localThickness));
|
|
25278
25278
|
const sliceProfile = localThickness < -EXACT_PROFILE_EPS ? insetSliceProfileForFullRevolutionProfile(source.plan.profile, Math.abs(localThickness), source.plan.segments) : null;
|
|
25279
|
-
const
|
|
25280
|
-
return
|
|
25281
|
-
profile,
|
|
25279
|
+
const profile2 = fullRevolutionOffsetProfilePlan(source.plan.profile, localThickness);
|
|
25280
|
+
return profile2 ? {
|
|
25281
|
+
profile: profile2,
|
|
25282
25282
|
projectionIntervals,
|
|
25283
25283
|
sliceProfile,
|
|
25284
25284
|
segments: source.plan.segments,
|
|
@@ -25336,25 +25336,25 @@ function exactVerticalProjectionProfilePlan(plan) {
|
|
|
25336
25336
|
case "transform": {
|
|
25337
25337
|
const base = exactVerticalProjectionProfilePlan(plan.base);
|
|
25338
25338
|
if (!base) return null;
|
|
25339
|
-
let
|
|
25339
|
+
let profile2 = base.profile;
|
|
25340
25340
|
let span = [base.zMin, base.zMax];
|
|
25341
25341
|
for (const step of plan.steps) {
|
|
25342
25342
|
const projectedTransforms = profileTransformsForShapeTransform(step);
|
|
25343
25343
|
if (!projectedTransforms) return null;
|
|
25344
25344
|
const nextSpan = transformZSpan(span, step);
|
|
25345
25345
|
if (!nextSpan) return null;
|
|
25346
|
-
|
|
25346
|
+
profile2 = appendProfileTransforms(profile2, projectedTransforms);
|
|
25347
25347
|
span = nextSpan;
|
|
25348
25348
|
}
|
|
25349
|
-
return { profile, zMin: span[0], zMax: span[1] };
|
|
25349
|
+
return { profile: profile2, zMin: span[0], zMax: span[1] };
|
|
25350
25350
|
}
|
|
25351
25351
|
default:
|
|
25352
25352
|
return null;
|
|
25353
25353
|
}
|
|
25354
25354
|
}
|
|
25355
25355
|
function zSpanOverlap(profiles2) {
|
|
25356
|
-
const zMin = Math.max(...profiles2.map((
|
|
25357
|
-
const zMax = Math.min(...profiles2.map((
|
|
25356
|
+
const zMin = Math.max(...profiles2.map((profile2) => profile2.zMin));
|
|
25357
|
+
const zMax = Math.min(...profiles2.map((profile2) => profile2.zMax));
|
|
25358
25358
|
return zMax - zMin > EXACT_PROFILE_EPS;
|
|
25359
25359
|
}
|
|
25360
25360
|
function clippedZSpan(subject, clip) {
|
|
@@ -25427,7 +25427,7 @@ function regionsHaveMatchingTopology2(stations) {
|
|
|
25427
25427
|
}
|
|
25428
25428
|
function loftSlicedProfilePlan(plan, offset2) {
|
|
25429
25429
|
if (plan.profiles.length !== plan.heights.length || plan.profiles.length < 2) return null;
|
|
25430
|
-
const stations = plan.profiles.map((
|
|
25430
|
+
const stations = plan.profiles.map((profile2, idx) => ({ profile: profile2, height: plan.heights[idx] })).sort((a, b) => a.height - b.height);
|
|
25431
25431
|
if (stations.some((station) => !Number.isFinite(station.height))) return null;
|
|
25432
25432
|
if (stations.some((station, idx) => idx > 0 && station.height <= stations[idx - 1].height + EXACT_PROFILE_EPS)) return null;
|
|
25433
25433
|
if (!offsetInRange(offset2, stations[0].height, stations[stations.length - 1].height)) return emptyProfilePlan();
|
|
@@ -25454,17 +25454,17 @@ function offsetSolidCompatibleLoftSlicedProfilePlan(plan, offset2) {
|
|
|
25454
25454
|
const base = compatibleLoftProfileReplayBasePlan(plan.base);
|
|
25455
25455
|
if (!base || !Number.isFinite(plan.thickness) || isNearlyZero(plan.thickness)) return null;
|
|
25456
25456
|
const offsetProfiles = base.profiles.map(
|
|
25457
|
-
(
|
|
25457
|
+
(profile2) => ({
|
|
25458
25458
|
kind: "offset",
|
|
25459
|
-
base: cloneProfileCompilePlan(
|
|
25459
|
+
base: cloneProfileCompilePlan(profile2),
|
|
25460
25460
|
delta: plan.thickness,
|
|
25461
25461
|
join: "Miter",
|
|
25462
25462
|
transforms: []
|
|
25463
25463
|
})
|
|
25464
25464
|
);
|
|
25465
25465
|
const offsetStations = [];
|
|
25466
|
-
for (const
|
|
25467
|
-
const regions = regionsForProfilePlan(
|
|
25466
|
+
for (const profile2 of offsetProfiles) {
|
|
25467
|
+
const regions = regionsForProfilePlan(profile2);
|
|
25468
25468
|
if (!regions) return null;
|
|
25469
25469
|
offsetStations.push(regions);
|
|
25470
25470
|
}
|
|
@@ -25502,9 +25502,9 @@ function offsetSolidCompatibleLoftProjectedProfilePlan(plan) {
|
|
|
25502
25502
|
if (!hasDistinctFiniteHeights(base.heights)) return null;
|
|
25503
25503
|
return compatibleLoftProjectionProfilePlan(
|
|
25504
25504
|
base.profiles.map(
|
|
25505
|
-
(
|
|
25505
|
+
(profile2) => ({
|
|
25506
25506
|
kind: "offset",
|
|
25507
|
-
base: cloneProfileCompilePlan(
|
|
25507
|
+
base: cloneProfileCompilePlan(profile2),
|
|
25508
25508
|
delta: plan.thickness,
|
|
25509
25509
|
join: "Miter",
|
|
25510
25510
|
transforms: []
|
|
@@ -25518,7 +25518,7 @@ function offsetSolidVerticalVariableSweepProjectedProfilePlan(plan) {
|
|
|
25518
25518
|
if (base.kind !== "variableSweep" || !Number.isFinite(plan.thickness) || isNearlyZero(plan.thickness)) return null;
|
|
25519
25519
|
const pathInfo = verticalSweepPathInfo(base.path, base.up);
|
|
25520
25520
|
if (!pathInfo || !hasDistinctFiniteSectionParameters(base.sections)) return null;
|
|
25521
|
-
const
|
|
25521
|
+
const profile2 = compatibleLoftProjectionProfilePlan(
|
|
25522
25522
|
base.sections.map(
|
|
25523
25523
|
(section) => ({
|
|
25524
25524
|
kind: "offset",
|
|
@@ -25529,7 +25529,7 @@ function offsetSolidVerticalVariableSweepProjectedProfilePlan(plan) {
|
|
|
25529
25529
|
})
|
|
25530
25530
|
)
|
|
25531
25531
|
);
|
|
25532
|
-
return
|
|
25532
|
+
return profile2 ? appendProfileTransforms(profile2, pathInfo.transforms) : null;
|
|
25533
25533
|
}
|
|
25534
25534
|
function offsetSolidVerticalVariableSweepSlicedProfilePlan(plan, offset2) {
|
|
25535
25535
|
let base = plan.base;
|
|
@@ -25734,27 +25734,27 @@ function exactTruckProjectedProfilePlan(plan) {
|
|
|
25734
25734
|
case "transform": {
|
|
25735
25735
|
const transformedSphere = transformedSphereProfile(plan.base, plan.steps);
|
|
25736
25736
|
if (transformedSphere) return transformedSphere;
|
|
25737
|
-
let
|
|
25738
|
-
if (!
|
|
25737
|
+
let profile2 = exactTruckProjectedProfilePlan(plan.base);
|
|
25738
|
+
if (!profile2) return null;
|
|
25739
25739
|
for (const step of plan.steps) {
|
|
25740
25740
|
const projectedTransforms = profileTransformsForShapeTransform(step);
|
|
25741
25741
|
if (!projectedTransforms) return null;
|
|
25742
|
-
|
|
25742
|
+
profile2 = appendProfileTransforms(profile2, projectedTransforms);
|
|
25743
25743
|
}
|
|
25744
|
-
return
|
|
25744
|
+
return profile2;
|
|
25745
25745
|
}
|
|
25746
25746
|
case "boolean": {
|
|
25747
25747
|
if (plan.op === "union") {
|
|
25748
25748
|
const profiles3 = [];
|
|
25749
25749
|
for (const shape of plan.shapes) {
|
|
25750
|
-
const
|
|
25751
|
-
if (!
|
|
25752
|
-
profiles3.push(
|
|
25750
|
+
const profile2 = exactTruckProjectedProfilePlan(shape);
|
|
25751
|
+
if (!profile2) return null;
|
|
25752
|
+
profiles3.push(profile2);
|
|
25753
25753
|
}
|
|
25754
25754
|
return { kind: "boolean", op: "union", profiles: profiles3, transforms: [] };
|
|
25755
25755
|
}
|
|
25756
25756
|
const verticalProfiles = plan.shapes.map(exactVerticalProjectionProfilePlan);
|
|
25757
|
-
if (verticalProfiles.some((
|
|
25757
|
+
if (verticalProfiles.some((profile2) => !profile2)) return null;
|
|
25758
25758
|
const profiles2 = verticalProfiles;
|
|
25759
25759
|
switch (plan.op) {
|
|
25760
25760
|
case "difference": {
|
|
@@ -25764,7 +25764,7 @@ function exactTruckProjectedProfilePlan(plan) {
|
|
|
25764
25764
|
}
|
|
25765
25765
|
case "intersection":
|
|
25766
25766
|
if (profiles2.length === 0 || !zSpanOverlap(profiles2)) return emptyProfilePlan();
|
|
25767
|
-
return { kind: "boolean", op: "intersection", profiles: profiles2.map((
|
|
25767
|
+
return { kind: "boolean", op: "intersection", profiles: profiles2.map((profile2) => profile2.profile), transforms: [] };
|
|
25768
25768
|
default:
|
|
25769
25769
|
return assertExhaustive(plan.op);
|
|
25770
25770
|
}
|
|
@@ -25859,15 +25859,15 @@ function exactTruckSlicedProfilePlan(plan, offset2) {
|
|
|
25859
25859
|
case "extrude": {
|
|
25860
25860
|
if (isNearlyZero(plan.height) || !offsetInRange(offset2, 0, plan.height)) return emptyProfilePlan();
|
|
25861
25861
|
if (plan.twist != null && !isNearlyZero(plan.twist)) return null;
|
|
25862
|
-
const
|
|
25862
|
+
const profile2 = cloneProfileCompilePlan(plan.profile);
|
|
25863
25863
|
if (plan.scaleTop) {
|
|
25864
25864
|
const t = offset2 / plan.height;
|
|
25865
25865
|
const scaleX = 1 + (plan.scaleTop[0] - 1) * t;
|
|
25866
25866
|
const scaleY = 1 + (plan.scaleTop[1] - 1) * t;
|
|
25867
25867
|
if (isNearlyZero(scaleX) || isNearlyZero(scaleY)) return emptyProfilePlan();
|
|
25868
|
-
|
|
25868
|
+
profile2.transforms.push({ kind: "scale", x: scaleX, y: scaleY });
|
|
25869
25869
|
}
|
|
25870
|
-
return
|
|
25870
|
+
return profile2;
|
|
25871
25871
|
}
|
|
25872
25872
|
case "transform": {
|
|
25873
25873
|
const transformedSphere = transformedSphereProfile(plan.base, plan.steps, offset2);
|
|
@@ -25878,25 +25878,25 @@ function exactTruckSlicedProfilePlan(plan, offset2) {
|
|
|
25878
25878
|
if (nextOffset == null) return null;
|
|
25879
25879
|
sourceOffset = nextOffset;
|
|
25880
25880
|
}
|
|
25881
|
-
let
|
|
25882
|
-
if (!
|
|
25881
|
+
let profile2 = exactTruckSlicedProfilePlan(plan.base, sourceOffset);
|
|
25882
|
+
if (!profile2) return null;
|
|
25883
25883
|
for (const step of plan.steps) {
|
|
25884
25884
|
const transforms = profileTransformsForShapeTransform(step);
|
|
25885
25885
|
if (!transforms) return null;
|
|
25886
|
-
|
|
25886
|
+
profile2 = appendProfileTransforms(profile2, transforms);
|
|
25887
25887
|
}
|
|
25888
|
-
return
|
|
25888
|
+
return profile2;
|
|
25889
25889
|
}
|
|
25890
25890
|
case "boolean": {
|
|
25891
25891
|
const profiles2 = [];
|
|
25892
25892
|
for (const shape of plan.shapes) {
|
|
25893
|
-
const
|
|
25894
|
-
if (!
|
|
25895
|
-
profiles2.push(
|
|
25893
|
+
const profile2 = exactTruckSlicedProfilePlan(shape, offset2);
|
|
25894
|
+
if (!profile2) return null;
|
|
25895
|
+
profiles2.push(profile2);
|
|
25896
25896
|
}
|
|
25897
25897
|
switch (plan.op) {
|
|
25898
25898
|
case "union": {
|
|
25899
|
-
const nonEmpty = profiles2.filter((
|
|
25899
|
+
const nonEmpty = profiles2.filter((profile2) => !isEmptyProfilePlan(profile2));
|
|
25900
25900
|
if (nonEmpty.length === 0) return emptyProfilePlan();
|
|
25901
25901
|
if (nonEmpty.length === 1) return cloneProfileCompilePlan(nonEmpty[0]);
|
|
25902
25902
|
return { kind: "boolean", op: "union", profiles: nonEmpty, transforms: [] };
|
|
@@ -25904,7 +25904,7 @@ function exactTruckSlicedProfilePlan(plan, offset2) {
|
|
|
25904
25904
|
case "difference": {
|
|
25905
25905
|
const [subject, ...clips] = profiles2;
|
|
25906
25906
|
if (!subject || isEmptyProfilePlan(subject)) return emptyProfilePlan();
|
|
25907
|
-
const nonEmptyClips = clips.filter((
|
|
25907
|
+
const nonEmptyClips = clips.filter((profile2) => !isEmptyProfilePlan(profile2));
|
|
25908
25908
|
if (nonEmptyClips.length === 0) return cloneProfileCompilePlan(subject);
|
|
25909
25909
|
return { kind: "boolean", op: "difference", profiles: [subject, ...nonEmptyClips], transforms: [] };
|
|
25910
25910
|
}
|
|
@@ -26527,26 +26527,26 @@ function findOriginOperation(plan) {
|
|
|
26527
26527
|
return { operation: plan.kind };
|
|
26528
26528
|
}
|
|
26529
26529
|
}
|
|
26530
|
-
function summarizeProfile(
|
|
26531
|
-
switch (
|
|
26530
|
+
function summarizeProfile(profile2) {
|
|
26531
|
+
switch (profile2.kind) {
|
|
26532
26532
|
case "rect":
|
|
26533
|
-
return `${
|
|
26533
|
+
return `${profile2.width}\xD7${profile2.height} rect`;
|
|
26534
26534
|
case "roundedRect":
|
|
26535
|
-
return `${
|
|
26535
|
+
return `${profile2.width}\xD7${profile2.height} rounded rect`;
|
|
26536
26536
|
case "circle":
|
|
26537
|
-
return `r=${
|
|
26537
|
+
return `r=${profile2.radius} circle`;
|
|
26538
26538
|
case "polygon":
|
|
26539
|
-
return `polygon (${
|
|
26539
|
+
return `polygon (${profile2.points.length} pts)`;
|
|
26540
26540
|
case "boolean":
|
|
26541
|
-
return `${
|
|
26541
|
+
return `${profile2.op} profile`;
|
|
26542
26542
|
case "offset":
|
|
26543
26543
|
return "offset profile";
|
|
26544
26544
|
case "project":
|
|
26545
26545
|
return "projected profile";
|
|
26546
26546
|
case "pathProfile":
|
|
26547
|
-
return `path profile (${
|
|
26547
|
+
return `path profile (${profile2.edges.length} edges)`;
|
|
26548
26548
|
default:
|
|
26549
|
-
assertExhaustive(
|
|
26549
|
+
assertExhaustive(profile2);
|
|
26550
26550
|
}
|
|
26551
26551
|
}
|
|
26552
26552
|
function collectTimelineEntries(plan, entries) {
|
|
@@ -27200,13 +27200,13 @@ function isRectanglePolygon(points) {
|
|
|
27200
27200
|
const cross13 = vectors[1][0] * vectors[3][1] - vectors[1][1] * vectors[3][0];
|
|
27201
27201
|
return Math.abs(dot01) <= 1e-6 * Math.max(1, lengths[0] * lengths[1]) && Math.abs(dot12) <= 1e-6 * Math.max(1, lengths[1] * lengths[2]) && Math.abs(dot23) <= 1e-6 * Math.max(1, lengths[2] * lengths[3]) && Math.abs(dot30) <= 1e-6 * Math.max(1, lengths[3] * lengths[0]) && Math.abs(cross02) <= 1e-6 * Math.max(1, lengths[0] * lengths[2]) && Math.abs(cross13) <= 1e-6 * Math.max(1, lengths[1] * lengths[3]);
|
|
27202
27202
|
}
|
|
27203
|
-
function rectLikeProfileCorners(
|
|
27204
|
-
const matrix = profileTransformMatrix(
|
|
27205
|
-
if (
|
|
27206
|
-
const minX = -
|
|
27207
|
-
const minY = -
|
|
27208
|
-
const maxX = minX +
|
|
27209
|
-
const maxY = minY +
|
|
27203
|
+
function rectLikeProfileCorners(profile2) {
|
|
27204
|
+
const matrix = profileTransformMatrix(profile2.transforms);
|
|
27205
|
+
if (profile2.kind === "rect" || profile2.kind === "roundedRect") {
|
|
27206
|
+
const minX = -profile2.width / 2;
|
|
27207
|
+
const minY = -profile2.height / 2;
|
|
27208
|
+
const maxX = minX + profile2.width;
|
|
27209
|
+
const maxY = minY + profile2.height;
|
|
27210
27210
|
return [
|
|
27211
27211
|
profilePoint(matrix, minX, minY),
|
|
27212
27212
|
profilePoint(matrix, maxX, minY),
|
|
@@ -27214,8 +27214,8 @@ function rectLikeProfileCorners(profile) {
|
|
|
27214
27214
|
profilePoint(matrix, minX, maxY)
|
|
27215
27215
|
];
|
|
27216
27216
|
}
|
|
27217
|
-
if (!isRectanglePolygon(
|
|
27218
|
-
const transformed =
|
|
27217
|
+
if (!isRectanglePolygon(profile2.points)) return null;
|
|
27218
|
+
const transformed = profile2.points.map(([x, y]) => profilePoint(matrix, x, y));
|
|
27219
27219
|
return [transformed[0], transformed[1], transformed[2], transformed[3]];
|
|
27220
27220
|
}
|
|
27221
27221
|
function buildBoxFaceTable(plan, owner) {
|
|
@@ -27297,8 +27297,8 @@ function buildCylinderFaceTable(plan, owner) {
|
|
|
27297
27297
|
});
|
|
27298
27298
|
return table;
|
|
27299
27299
|
}
|
|
27300
|
-
function buildRectExtrudeFaceTable(
|
|
27301
|
-
const corners2 = rectLikeProfileCorners(
|
|
27300
|
+
function buildRectExtrudeFaceTable(profile2, height, owner) {
|
|
27301
|
+
const corners2 = rectLikeProfileCorners(profile2);
|
|
27302
27302
|
if (!corners2) return emptyFaceTable();
|
|
27303
27303
|
const [bl, br, tr, tl] = corners2;
|
|
27304
27304
|
const center2d = midpoint2d(bl, tr);
|
|
@@ -27331,11 +27331,11 @@ function buildRectExtrudeFaceTable(profile, height, owner) {
|
|
|
27331
27331
|
registerFace(table, faceFrom2DEdge("side-left", tl, bl, (zTop + zBot) / 2, createTrackedFaceQuery("side-left", owner)));
|
|
27332
27332
|
return table;
|
|
27333
27333
|
}
|
|
27334
|
-
function buildCircleExtrudeFaceTable(
|
|
27334
|
+
function buildCircleExtrudeFaceTable(profile2, height, owner) {
|
|
27335
27335
|
const table = emptyFaceTable();
|
|
27336
|
-
const matrix = profileTransformMatrix(
|
|
27336
|
+
const matrix = profileTransformMatrix(profile2.transforms);
|
|
27337
27337
|
const origin = Transform.from(matrix).point([0, 0, 0]);
|
|
27338
|
-
const sidePoint = Transform.from(matrix).point([
|
|
27338
|
+
const sidePoint = Transform.from(matrix).point([profile2.radius, 0, 0]);
|
|
27339
27339
|
const sideNormal = normalizeAxis([sidePoint[0] - origin[0], sidePoint[1] - origin[1], 0]);
|
|
27340
27340
|
const xAxis = normalizeAxis(Transform.from(matrix).vector([1, 0, 0]));
|
|
27341
27341
|
const yAxis = normalizeAxis(Transform.from(matrix).vector([0, 1, 0]));
|
|
@@ -27490,16 +27490,16 @@ function holeCreatedFaceNames(hole2, extent) {
|
|
|
27490
27490
|
if (featureCutExtentReverseSide(extent)?.kind === "blind") names.push("cap");
|
|
27491
27491
|
return names;
|
|
27492
27492
|
}
|
|
27493
|
-
function cutCreatedFaceNames(
|
|
27493
|
+
function cutCreatedFaceNames(profile2, extent, _taper) {
|
|
27494
27494
|
const names = (() => {
|
|
27495
|
-
switch (
|
|
27495
|
+
switch (profile2.kind) {
|
|
27496
27496
|
case "circle":
|
|
27497
27497
|
return ["wall"];
|
|
27498
27498
|
case "rect":
|
|
27499
27499
|
case "roundedRect":
|
|
27500
27500
|
return ["wall-bottom", "wall-right", "wall-top", "wall-left"];
|
|
27501
27501
|
case "polygon":
|
|
27502
|
-
return isRectanglePolygon(
|
|
27502
|
+
return isRectanglePolygon(profile2.points) ? ["wall-bottom", "wall-right", "wall-top", "wall-left"] : [];
|
|
27503
27503
|
default:
|
|
27504
27504
|
return [];
|
|
27505
27505
|
}
|
|
@@ -27518,8 +27518,8 @@ function holeCreatedEdgeNames(hole2, extent) {
|
|
|
27518
27518
|
}
|
|
27519
27519
|
return names;
|
|
27520
27520
|
}
|
|
27521
|
-
function cutCreatedEdgeNames(
|
|
27522
|
-
if (cutCreatedFaceNames(
|
|
27521
|
+
function cutCreatedEdgeNames(profile2, extent, taper) {
|
|
27522
|
+
if (cutCreatedFaceNames(profile2, extent, taper).length === 0) return [];
|
|
27523
27523
|
const names = ["entry-rim", "forward-end-rim"];
|
|
27524
27524
|
if (featureCutExtentReverseSide(extent)) {
|
|
27525
27525
|
names.push("reverse-end-rim");
|
|
@@ -28267,14 +28267,14 @@ function supportedShellCreatedFaceNames(basePlan, openFaces) {
|
|
|
28267
28267
|
function supportedHoleCreatedFaceNames(hole2, extent) {
|
|
28268
28268
|
return holeCreatedFaceNames(hole2, extent);
|
|
28269
28269
|
}
|
|
28270
|
-
function supportedCutCreatedFaceNames(
|
|
28271
|
-
return cutCreatedFaceNames(
|
|
28270
|
+
function supportedCutCreatedFaceNames(profile2, extent, taper) {
|
|
28271
|
+
return cutCreatedFaceNames(profile2, extent, taper);
|
|
28272
28272
|
}
|
|
28273
28273
|
function supportedHoleCreatedEdgeNames(hole2, extent) {
|
|
28274
28274
|
return holeCreatedEdgeNames(hole2, extent);
|
|
28275
28275
|
}
|
|
28276
|
-
function supportedCutCreatedEdgeNames(
|
|
28277
|
-
return cutCreatedEdgeNames(
|
|
28276
|
+
function supportedCutCreatedEdgeNames(profile2, extent, taper) {
|
|
28277
|
+
return cutCreatedEdgeNames(profile2, extent, taper);
|
|
28278
28278
|
}
|
|
28279
28279
|
function preservedShapeFaceQueries(basePlan) {
|
|
28280
28280
|
return listShapeFaceQueries(basePlan);
|
|
@@ -31595,7 +31595,7 @@ function buildHoleTopologyRewritePropagation(owner, base, placement, hole2, exte
|
|
|
31595
31595
|
buildHoleCutEdgePropagation(propagation, owner, base, blocked, "hole");
|
|
31596
31596
|
return propagation;
|
|
31597
31597
|
}
|
|
31598
|
-
function buildCutTopologyRewritePropagation(owner, base, placement,
|
|
31598
|
+
function buildCutTopologyRewritePropagation(owner, base, placement, profile2, extent, taper) {
|
|
31599
31599
|
const propagation = createTopologyRewritePropagation("cut", owner);
|
|
31600
31600
|
const blocked = new Map(blockedShapeFacesForFeature(base, placement.workplane.source, extent).map((entry) => [entry.name, entry.reason]));
|
|
31601
31601
|
for (const entry of preservedShapeFaceQueries(base)) {
|
|
@@ -31630,7 +31630,7 @@ function buildCutTopologyRewritePropagation(owner, base, placement, profile, ext
|
|
|
31630
31630
|
})
|
|
31631
31631
|
);
|
|
31632
31632
|
}
|
|
31633
|
-
const createdNames = supportedCutCreatedFaceNames(
|
|
31633
|
+
const createdNames = supportedCutCreatedFaceNames(profile2, extent);
|
|
31634
31634
|
for (const name of createdNames) {
|
|
31635
31635
|
const query = createCreatedFaceQueryRef(owner, "cut", name);
|
|
31636
31636
|
propagation.createdFaces.push({
|
|
@@ -31644,7 +31644,7 @@ function buildCutTopologyRewritePropagation(owner, base, placement, profile, ext
|
|
|
31644
31644
|
})
|
|
31645
31645
|
);
|
|
31646
31646
|
}
|
|
31647
|
-
for (const name of supportedCutCreatedEdgeNames(
|
|
31647
|
+
for (const name of supportedCutCreatedEdgeNames(profile2, extent, taper)) {
|
|
31648
31648
|
const query = createCreatedEdgeQueryRef(owner, "cut", name);
|
|
31649
31649
|
propagation.createdEdges.push({
|
|
31650
31650
|
query,
|
|
@@ -32103,8 +32103,8 @@ function selectTruckKernelEdges(shape, query) {
|
|
|
32103
32103
|
return JSON.parse(
|
|
32104
32104
|
getTruckGeometryWasm().geometry_select_edges(getTruckShapeBackendHandle(backend), JSON.stringify(query))
|
|
32105
32105
|
);
|
|
32106
|
-
} catch (
|
|
32107
|
-
throw new Error(`selectEdges(): native Truck edge selection failed: ${
|
|
32106
|
+
} catch (error2) {
|
|
32107
|
+
throw new Error(`selectEdges(): native Truck edge selection failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
32108
32108
|
}
|
|
32109
32109
|
}
|
|
32110
32110
|
function selectEdges(shape, query = {}) {
|
|
@@ -33230,11 +33230,11 @@ function extractSdfExpression(source) {
|
|
|
33230
33230
|
const src = source.toString().trim();
|
|
33231
33231
|
const arrowIdx = src.indexOf("=>");
|
|
33232
33232
|
if (arrowIdx !== -1) {
|
|
33233
|
-
const
|
|
33234
|
-
if (
|
|
33235
|
-
return expressionFromFunctionBody(
|
|
33233
|
+
const body2 = src.slice(arrowIdx + 2).trim();
|
|
33234
|
+
if (body2.startsWith("{") && body2.endsWith("}")) {
|
|
33235
|
+
return expressionFromFunctionBody(body2.slice(1, -1));
|
|
33236
33236
|
}
|
|
33237
|
-
return stripReturnKeyword(
|
|
33237
|
+
return stripReturnKeyword(body2);
|
|
33238
33238
|
}
|
|
33239
33239
|
const start = src.indexOf("{");
|
|
33240
33240
|
const end = src.lastIndexOf("}");
|
|
@@ -33319,8 +33319,8 @@ function validateSdfFunctionConstant(value, label) {
|
|
|
33319
33319
|
}
|
|
33320
33320
|
throw new Error(`sdf.fromFunction() ${label} must be a JSON-compatible value.`);
|
|
33321
33321
|
}
|
|
33322
|
-
function expressionFromFunctionBody(
|
|
33323
|
-
const trimmed =
|
|
33322
|
+
function expressionFromFunctionBody(body2) {
|
|
33323
|
+
const trimmed = body2.trim();
|
|
33324
33324
|
const match = trimmed.match(/^return\s+([\s\S]*?);?$/);
|
|
33325
33325
|
if (match) return stripReturnKeyword(match[1].trim());
|
|
33326
33326
|
return `(function() {
|
|
@@ -33422,8 +33422,8 @@ var SurfacePattern = class {
|
|
|
33422
33422
|
body;
|
|
33423
33423
|
/** Named constants injected into the function. */
|
|
33424
33424
|
constants;
|
|
33425
|
-
constructor(
|
|
33426
|
-
this.body =
|
|
33425
|
+
constructor(body2, constants) {
|
|
33426
|
+
this.body = body2;
|
|
33427
33427
|
this.constants = constants;
|
|
33428
33428
|
}
|
|
33429
33429
|
};
|
|
@@ -33432,8 +33432,8 @@ function getTypedSurfacePattern(pattern) {
|
|
|
33432
33432
|
return typedSurfacePatterns.get(pattern);
|
|
33433
33433
|
}
|
|
33434
33434
|
var Pattern2D = class extends SurfacePattern {
|
|
33435
|
-
constructor(
|
|
33436
|
-
super(
|
|
33435
|
+
constructor(body2) {
|
|
33436
|
+
super(body2);
|
|
33437
33437
|
}
|
|
33438
33438
|
/** Add this pattern to one or more patterns or constant height offsets. */
|
|
33439
33439
|
add(...patterns) {
|
|
@@ -33686,10 +33686,10 @@ Fix: use a known preset or pass material props directly.`
|
|
|
33686
33686
|
if (!input || typeof input !== "object") {
|
|
33687
33687
|
throw new Error(`${label}() expects a material preset string or material properties object.`);
|
|
33688
33688
|
}
|
|
33689
|
-
const { color, ...
|
|
33689
|
+
const { color, ...material2 } = input;
|
|
33690
33690
|
return {
|
|
33691
33691
|
...typeof color === "string" ? { color } : {},
|
|
33692
|
-
material: validateMaterialProps(
|
|
33692
|
+
material: validateMaterialProps(material2)
|
|
33693
33693
|
};
|
|
33694
33694
|
}
|
|
33695
33695
|
function requirePositiveFinite4(value, label) {
|
|
@@ -34250,21 +34250,21 @@ var SdfShape = class _SdfShape {
|
|
|
34250
34250
|
* ```
|
|
34251
34251
|
*/
|
|
34252
34252
|
surfaceDisplace(pattern, options) {
|
|
34253
|
-
let
|
|
34253
|
+
let body2;
|
|
34254
34254
|
let constants;
|
|
34255
34255
|
let typedPattern;
|
|
34256
34256
|
if (pattern instanceof SurfacePattern) {
|
|
34257
|
-
|
|
34257
|
+
body2 = pattern.body;
|
|
34258
34258
|
constants = pattern.constants;
|
|
34259
34259
|
typedPattern = getTypedSurfacePattern(pattern);
|
|
34260
34260
|
} else {
|
|
34261
|
-
|
|
34261
|
+
body2 = extractFunctionBody(pattern);
|
|
34262
34262
|
}
|
|
34263
34263
|
return this.withNode({
|
|
34264
34264
|
kind: "sdf:surfaceDisplace",
|
|
34265
34265
|
child: this._node,
|
|
34266
34266
|
...typedPattern ? { pattern: typedPattern } : {},
|
|
34267
|
-
patternBody:
|
|
34267
|
+
patternBody: body2,
|
|
34268
34268
|
constants,
|
|
34269
34269
|
...options?.uv ? { uvMode: options.uv } : {},
|
|
34270
34270
|
...options?.triplanarSharpness !== void 0 ? { triplanarSharpness: options.triplanarSharpness } : {}
|
|
@@ -36161,8 +36161,8 @@ function formatShapeBoundsForDiagnostic(shape) {
|
|
|
36161
36161
|
try {
|
|
36162
36162
|
const bounds = shape.boundingBox();
|
|
36163
36163
|
return `bounds=${formatDiagnosticVec(bounds.min)}..${formatDiagnosticVec(bounds.max)}`;
|
|
36164
|
-
} catch (
|
|
36165
|
-
const message =
|
|
36164
|
+
} catch (error2) {
|
|
36165
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
36166
36166
|
return `bounds=unavailable(${message})`;
|
|
36167
36167
|
}
|
|
36168
36168
|
}
|
|
@@ -36396,10 +36396,10 @@ function parseReferencePath(path2) {
|
|
|
36396
36396
|
if (!trimmed) throw new Error("Shape.ref() requires a non-empty path.");
|
|
36397
36397
|
const atIndex = trimmed.indexOf("@");
|
|
36398
36398
|
if (atIndex < 0) return { body: trimmed };
|
|
36399
|
-
const
|
|
36399
|
+
const body2 = trimmed.slice(0, atIndex).trim();
|
|
36400
36400
|
const selector = trimmed.slice(atIndex + 1).trim();
|
|
36401
|
-
if (!
|
|
36402
|
-
return { body, selector };
|
|
36401
|
+
if (!body2 || !selector) throw new Error(`Shape.ref("${path2}") has an incomplete @ selector.`);
|
|
36402
|
+
return { body: body2, selector };
|
|
36403
36403
|
}
|
|
36404
36404
|
function midpoint32(a, b) {
|
|
36405
36405
|
return [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
|
|
@@ -36477,21 +36477,21 @@ function explainReferenceMissingFace(shape, path2, name) {
|
|
|
36477
36477
|
`Shape.ref("${path2}") could not resolve face "${name}". Available face refs: ${shape.faceNames().join(", ") || "none"}`
|
|
36478
36478
|
);
|
|
36479
36479
|
}
|
|
36480
|
-
function resolveReferenceBody(shape, originalPath,
|
|
36481
|
-
const exactFaces = resolveShapeRefFaces(shape,
|
|
36480
|
+
function resolveReferenceBody(shape, originalPath, body2) {
|
|
36481
|
+
const exactFaces = resolveShapeRefFaces(shape, body2);
|
|
36482
36482
|
if (exactFaces) {
|
|
36483
36483
|
return {
|
|
36484
36484
|
baseKind: "face",
|
|
36485
|
-
rule: exactFaces.length === 1 ? `face(${
|
|
36485
|
+
rule: exactFaces.length === 1 ? `face(${body2})` : `face-set(${body2})`,
|
|
36486
36486
|
faces: exactFaces,
|
|
36487
36487
|
edges: [],
|
|
36488
36488
|
points: []
|
|
36489
36489
|
};
|
|
36490
36490
|
}
|
|
36491
|
-
const slashIndex =
|
|
36492
|
-
if (slashIndex < 0) explainReferenceMissingFace(shape, originalPath,
|
|
36493
|
-
const leftName =
|
|
36494
|
-
const rightName =
|
|
36491
|
+
const slashIndex = body2.indexOf("/");
|
|
36492
|
+
if (slashIndex < 0) explainReferenceMissingFace(shape, originalPath, body2);
|
|
36493
|
+
const leftName = body2.slice(0, slashIndex).trim();
|
|
36494
|
+
const rightName = body2.slice(slashIndex + 1).trim();
|
|
36495
36495
|
if (!leftName || !rightName) throw new Error(`Shape.ref("${originalPath}") has an incomplete face relationship path.`);
|
|
36496
36496
|
const leftFaces = resolveShapeRefFaces(shape, leftName) ?? explainReferenceMissingFace(shape, originalPath, leftName);
|
|
36497
36497
|
const rightFaces = resolveShapeRefFaces(shape, rightName) ?? explainReferenceMissingFace(shape, originalPath, rightName);
|
|
@@ -36591,8 +36591,8 @@ var ShapeRef = class _ShapeRef {
|
|
|
36591
36591
|
resolve() {
|
|
36592
36592
|
try {
|
|
36593
36593
|
return resolveShapeReference(this.shape, this.path);
|
|
36594
|
-
} catch (
|
|
36595
|
-
if (!this.optional) throw
|
|
36594
|
+
} catch (error2) {
|
|
36595
|
+
if (!this.optional) throw error2;
|
|
36596
36596
|
const sourcePath = resolveShapeRefAliases(this.shape, this.path.trim());
|
|
36597
36597
|
const parsed = parseReferencePath(sourcePath);
|
|
36598
36598
|
const missingKind = parsed.selector ? "point-set" : parsed.body.includes("/") ? "edge-set" : "face-set";
|
|
@@ -36603,7 +36603,7 @@ var ShapeRef = class _ShapeRef {
|
|
|
36603
36603
|
kind: missingKind,
|
|
36604
36604
|
status: "deleted",
|
|
36605
36605
|
cardinality: "zero",
|
|
36606
|
-
rule:
|
|
36606
|
+
rule: error2 instanceof Error ? error2.message : "optional reference failed to resolve",
|
|
36607
36607
|
faces: [],
|
|
36608
36608
|
edges: [],
|
|
36609
36609
|
points: []
|
|
@@ -39149,11 +39149,11 @@ var constraintDefs = /* @__PURE__ */ new Map([
|
|
|
39149
39149
|
]);
|
|
39150
39150
|
function buildVariables(bodies) {
|
|
39151
39151
|
const vars = [];
|
|
39152
|
-
for (const [id,
|
|
39153
|
-
if (
|
|
39152
|
+
for (const [id, body2] of bodies) {
|
|
39153
|
+
if (body2.grounded) continue;
|
|
39154
39154
|
for (let c = 0; c < 6; c++) {
|
|
39155
39155
|
const isRotation = c >= 3;
|
|
39156
|
-
const arr = isRotation ?
|
|
39156
|
+
const arr = isRotation ? body2.rotation : body2.position;
|
|
39157
39157
|
const idx = isRotation ? c - 3 : c;
|
|
39158
39158
|
vars.push({
|
|
39159
39159
|
bodyId: id,
|
|
@@ -39162,15 +39162,15 @@ function buildVariables(bodies) {
|
|
|
39162
39162
|
set: (v) => {
|
|
39163
39163
|
arr[idx] = v;
|
|
39164
39164
|
},
|
|
39165
|
-
scale: isRotation ? 1 : computePositionScale(
|
|
39165
|
+
scale: isRotation ? 1 : computePositionScale(body2)
|
|
39166
39166
|
});
|
|
39167
39167
|
}
|
|
39168
39168
|
}
|
|
39169
39169
|
return vars;
|
|
39170
39170
|
}
|
|
39171
|
-
function computePositionScale(
|
|
39171
|
+
function computePositionScale(body2) {
|
|
39172
39172
|
let maxDist = 1;
|
|
39173
|
-
for (const face of
|
|
39173
|
+
for (const face of body2.faces.values()) {
|
|
39174
39174
|
const d = Math.hypot(face.center[0], face.center[1], face.center[2]);
|
|
39175
39175
|
if (d > maxDist) maxDist = d;
|
|
39176
39176
|
}
|
|
@@ -39180,41 +39180,41 @@ function createContext(bodies) {
|
|
|
39180
39180
|
return {
|
|
39181
39181
|
bodies,
|
|
39182
39182
|
toWorld(bodyId, point2) {
|
|
39183
|
-
const
|
|
39184
|
-
if (!
|
|
39185
|
-
return transformPoint2(
|
|
39183
|
+
const body2 = bodies.get(bodyId);
|
|
39184
|
+
if (!body2) throw new Error(`Unknown body: ${bodyId}`);
|
|
39185
|
+
return transformPoint2(body2.rotation, body2.position, point2);
|
|
39186
39186
|
},
|
|
39187
39187
|
toWorldDir(bodyId, dir) {
|
|
39188
|
-
const
|
|
39189
|
-
if (!
|
|
39190
|
-
return normalize36(transformDir(
|
|
39188
|
+
const body2 = bodies.get(bodyId);
|
|
39189
|
+
if (!body2) throw new Error(`Unknown body: ${bodyId}`);
|
|
39190
|
+
return normalize36(transformDir(body2.rotation, dir));
|
|
39191
39191
|
},
|
|
39192
39192
|
worldFace(bodyId, faceName) {
|
|
39193
|
-
const
|
|
39194
|
-
if (!
|
|
39195
|
-
const face =
|
|
39193
|
+
const body2 = bodies.get(bodyId);
|
|
39194
|
+
if (!body2) throw new Error(`Unknown body: ${bodyId}`);
|
|
39195
|
+
const face = body2.faces.get(faceName);
|
|
39196
39196
|
if (!face) throw new Error(`Unknown face "${faceName}" on body "${bodyId}"`);
|
|
39197
39197
|
return {
|
|
39198
|
-
normal: normalize36(transformDir(
|
|
39199
|
-
center: transformPoint2(
|
|
39198
|
+
normal: normalize36(transformDir(body2.rotation, face.normal)),
|
|
39199
|
+
center: transformPoint2(body2.rotation, body2.position, face.center)
|
|
39200
39200
|
};
|
|
39201
39201
|
},
|
|
39202
39202
|
worldAxis(bodyId, axisName) {
|
|
39203
|
-
const
|
|
39204
|
-
if (!
|
|
39205
|
-
const axis =
|
|
39203
|
+
const body2 = bodies.get(bodyId);
|
|
39204
|
+
if (!body2) throw new Error(`Unknown body: ${bodyId}`);
|
|
39205
|
+
const axis = body2.axes.get(axisName);
|
|
39206
39206
|
if (!axis) throw new Error(`Unknown axis "${axisName}" on body "${bodyId}"`);
|
|
39207
39207
|
return {
|
|
39208
|
-
origin: transformPoint2(
|
|
39209
|
-
direction: normalize36(transformDir(
|
|
39208
|
+
origin: transformPoint2(body2.rotation, body2.position, axis.origin),
|
|
39209
|
+
direction: normalize36(transformDir(body2.rotation, axis.direction))
|
|
39210
39210
|
};
|
|
39211
39211
|
},
|
|
39212
39212
|
worldPoint(bodyId, pointName) {
|
|
39213
|
-
const
|
|
39214
|
-
if (!
|
|
39215
|
-
const pt =
|
|
39213
|
+
const body2 = bodies.get(bodyId);
|
|
39214
|
+
if (!body2) throw new Error(`Unknown body: ${bodyId}`);
|
|
39215
|
+
const pt = body2.points.get(pointName);
|
|
39216
39216
|
if (!pt) throw new Error(`Unknown point "${pointName}" on body "${bodyId}"`);
|
|
39217
|
-
return transformPoint2(
|
|
39217
|
+
return transformPoint2(body2.rotation, body2.position, pt.position);
|
|
39218
39218
|
}
|
|
39219
39219
|
};
|
|
39220
39220
|
}
|
|
@@ -39346,8 +39346,8 @@ function solve3D(bodies, constraints, options = {}) {
|
|
|
39346
39346
|
if (def) totalEquations += def.equations;
|
|
39347
39347
|
}
|
|
39348
39348
|
let freeBodies = 0;
|
|
39349
|
-
for (const
|
|
39350
|
-
if (!
|
|
39349
|
+
for (const body2 of bodies.values()) {
|
|
39350
|
+
if (!body2.grounded) freeBodies++;
|
|
39351
39351
|
}
|
|
39352
39352
|
const totalDof = 6 * freeBodies;
|
|
39353
39353
|
const netDof = totalDof - totalEquations;
|
|
@@ -39365,8 +39365,8 @@ function solve3D(bodies, constraints, options = {}) {
|
|
|
39365
39365
|
};
|
|
39366
39366
|
}
|
|
39367
39367
|
let refLength = 1;
|
|
39368
|
-
for (const
|
|
39369
|
-
for (const face of
|
|
39368
|
+
for (const body2 of bodies.values()) {
|
|
39369
|
+
for (const face of body2.faces.values()) {
|
|
39370
39370
|
const d = Math.hypot(face.center[0], face.center[1], face.center[2]);
|
|
39371
39371
|
if (d > refLength) refLength = d;
|
|
39372
39372
|
}
|
|
@@ -39431,10 +39431,10 @@ function classifyStatus(dof, maxError, tolerance) {
|
|
|
39431
39431
|
}
|
|
39432
39432
|
function extractTransforms(bodies) {
|
|
39433
39433
|
const result = /* @__PURE__ */ new Map();
|
|
39434
|
-
for (const [id,
|
|
39434
|
+
for (const [id, body2] of bodies) {
|
|
39435
39435
|
result.set(id, {
|
|
39436
|
-
position: [...
|
|
39437
|
-
rotation: [...
|
|
39436
|
+
position: [...body2.position],
|
|
39437
|
+
rotation: [...body2.rotation]
|
|
39438
39438
|
});
|
|
39439
39439
|
}
|
|
39440
39440
|
return result;
|
|
@@ -40096,6 +40096,201 @@ function jointsView(options = {}) {
|
|
|
40096
40096
|
_collected2 = collectJointsView(options, _collected2);
|
|
40097
40097
|
}
|
|
40098
40098
|
|
|
40099
|
+
// src/forge/sim.ts
|
|
40100
|
+
function requireNonEmptyString(value, label) {
|
|
40101
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
40102
|
+
throw new Error(`${label} must be a non-empty string`);
|
|
40103
|
+
}
|
|
40104
|
+
return value.trim();
|
|
40105
|
+
}
|
|
40106
|
+
function requireFiniteNumber2(value, label) {
|
|
40107
|
+
if (value !== void 0 && !Number.isFinite(value)) {
|
|
40108
|
+
throw new Error(`${label} must be finite`);
|
|
40109
|
+
}
|
|
40110
|
+
}
|
|
40111
|
+
function requirePositiveNumber(value, label) {
|
|
40112
|
+
requireFiniteNumber2(value, label);
|
|
40113
|
+
if (value !== void 0 && value <= 0) {
|
|
40114
|
+
throw new Error(`${label} must be > 0`);
|
|
40115
|
+
}
|
|
40116
|
+
}
|
|
40117
|
+
function requireUnitRange(value, label) {
|
|
40118
|
+
requireFiniteNumber2(value, label);
|
|
40119
|
+
if (value !== void 0 && (value < 0 || value > 1)) {
|
|
40120
|
+
throw new Error(`${label} must be between 0 and 1`);
|
|
40121
|
+
}
|
|
40122
|
+
}
|
|
40123
|
+
function cloneMaterial(material2) {
|
|
40124
|
+
return material2 ? { ...material2 } : void 0;
|
|
40125
|
+
}
|
|
40126
|
+
function cloneSimCollider(collider2) {
|
|
40127
|
+
return collider2 ? { ...collider2 } : void 0;
|
|
40128
|
+
}
|
|
40129
|
+
function cloneSimContact(contact2) {
|
|
40130
|
+
return { ...contact2 };
|
|
40131
|
+
}
|
|
40132
|
+
function cloneSimContacts(contacts) {
|
|
40133
|
+
if (!contacts) return void 0;
|
|
40134
|
+
return Object.fromEntries(Object.entries(contacts).map(([name, contact2]) => [name, cloneSimContact(contact2)]));
|
|
40135
|
+
}
|
|
40136
|
+
function cloneSimBody(body2) {
|
|
40137
|
+
if (!body2) return void 0;
|
|
40138
|
+
return {
|
|
40139
|
+
...body2,
|
|
40140
|
+
material: cloneMaterial(body2.material),
|
|
40141
|
+
collider: cloneSimCollider(body2.collider),
|
|
40142
|
+
contacts: cloneSimContacts(body2.contacts)
|
|
40143
|
+
};
|
|
40144
|
+
}
|
|
40145
|
+
function cloneSimDrive(drive) {
|
|
40146
|
+
return drive ? { ...drive } : void 0;
|
|
40147
|
+
}
|
|
40148
|
+
function cloneSimController(controller) {
|
|
40149
|
+
return {
|
|
40150
|
+
...controller,
|
|
40151
|
+
leftJoints: [...controller.leftJoints],
|
|
40152
|
+
rightJoints: [...controller.rightJoints]
|
|
40153
|
+
};
|
|
40154
|
+
}
|
|
40155
|
+
function cloneSimProfile(profile2) {
|
|
40156
|
+
return { ...profile2 };
|
|
40157
|
+
}
|
|
40158
|
+
function cloneSimAssemblySimulation(simulation) {
|
|
40159
|
+
if (!simulation) return void 0;
|
|
40160
|
+
return {
|
|
40161
|
+
...simulation,
|
|
40162
|
+
profile: cloneSimProfile(simulation.profile),
|
|
40163
|
+
controllers: simulation.controllers?.map((controller) => cloneSimController(controller))
|
|
40164
|
+
};
|
|
40165
|
+
}
|
|
40166
|
+
function simProfileRequiresRootPart(profile2) {
|
|
40167
|
+
return profile2?.name === "Robot-Body-Runnable" || profile2?.name === "Robot-Body-Isaac";
|
|
40168
|
+
}
|
|
40169
|
+
function simProfileAllowsControllers(profile2) {
|
|
40170
|
+
return simProfileRequiresRootPart(profile2);
|
|
40171
|
+
}
|
|
40172
|
+
function material(name, options = {}) {
|
|
40173
|
+
const materialName = requireNonEmptyString(name, "Sim.material(...) name");
|
|
40174
|
+
requirePositiveNumber(options.densityKgM3, `Sim.material("${materialName}") densityKgM3`);
|
|
40175
|
+
requireUnitRange(options.staticFriction, `Sim.material("${materialName}") staticFriction`);
|
|
40176
|
+
requireUnitRange(options.dynamicFriction, `Sim.material("${materialName}") dynamicFriction`);
|
|
40177
|
+
requireUnitRange(options.restitution, `Sim.material("${materialName}") restitution`);
|
|
40178
|
+
return { kind: "material", name: materialName, ...options };
|
|
40179
|
+
}
|
|
40180
|
+
function body(options) {
|
|
40181
|
+
if (!options || typeof options !== "object") {
|
|
40182
|
+
throw new Error("Sim.body(...) expects an options object");
|
|
40183
|
+
}
|
|
40184
|
+
requirePositiveNumber(options.massKg, "Sim.body(...) massKg");
|
|
40185
|
+
requirePositiveNumber(options.densityKgM3, "Sim.body(...) densityKgM3");
|
|
40186
|
+
if (options.massKg === void 0 && options.densityKgM3 === void 0 && options.material?.densityKgM3 === void 0) {
|
|
40187
|
+
throw new Error("Sim.body(...) requires massKg, densityKgM3, or material densityKgM3");
|
|
40188
|
+
}
|
|
40189
|
+
return {
|
|
40190
|
+
kind: "body",
|
|
40191
|
+
massKg: options.massKg,
|
|
40192
|
+
densityKgM3: options.densityKgM3,
|
|
40193
|
+
material: cloneMaterial(options.material),
|
|
40194
|
+
collider: cloneSimCollider(options.collider),
|
|
40195
|
+
contacts: cloneSimContacts(options.contacts)
|
|
40196
|
+
};
|
|
40197
|
+
}
|
|
40198
|
+
function collider(mode, reason) {
|
|
40199
|
+
const trimmedReason = reason?.trim();
|
|
40200
|
+
if (mode === "none" && !trimmedReason) {
|
|
40201
|
+
throw new Error("Sim.collider.none(reason) requires a non-empty reason");
|
|
40202
|
+
}
|
|
40203
|
+
return trimmedReason ? { kind: "collider", mode, reason: trimmedReason } : { kind: "collider", mode };
|
|
40204
|
+
}
|
|
40205
|
+
function contact(kind, connectorName) {
|
|
40206
|
+
return { kind, connectorName: requireNonEmptyString(connectorName, `Sim.contact.${kind}(...) connectorName`) };
|
|
40207
|
+
}
|
|
40208
|
+
function passiveDrive(options = {}) {
|
|
40209
|
+
requireFiniteNumber2(options.damping, "Sim.drive.passive(...) damping");
|
|
40210
|
+
requireFiniteNumber2(options.friction, "Sim.drive.passive(...) friction");
|
|
40211
|
+
return { kind: "passive", ...options };
|
|
40212
|
+
}
|
|
40213
|
+
function velocityDrive(options) {
|
|
40214
|
+
if (!options || typeof options !== "object") {
|
|
40215
|
+
throw new Error("Sim.drive.velocity(...) expects an options object");
|
|
40216
|
+
}
|
|
40217
|
+
requirePositiveNumber(options.maxTorqueNm, "Sim.drive.velocity(...) maxTorqueNm");
|
|
40218
|
+
requirePositiveNumber(options.maxSpeedRpm, "Sim.drive.velocity(...) maxSpeedRpm");
|
|
40219
|
+
requireFiniteNumber2(options.damping, "Sim.drive.velocity(...) damping");
|
|
40220
|
+
requireFiniteNumber2(options.friction, "Sim.drive.velocity(...) friction");
|
|
40221
|
+
return { kind: "velocity", ...options };
|
|
40222
|
+
}
|
|
40223
|
+
function diffDrive(options) {
|
|
40224
|
+
if (!options || typeof options !== "object") {
|
|
40225
|
+
throw new Error("Sim.controller.diffDrive(...) expects an options object");
|
|
40226
|
+
}
|
|
40227
|
+
if (!Array.isArray(options.leftJoints) || options.leftJoints.length === 0) {
|
|
40228
|
+
throw new Error("Sim.controller.diffDrive(...) requires at least one left joint");
|
|
40229
|
+
}
|
|
40230
|
+
if (!Array.isArray(options.rightJoints) || options.rightJoints.length === 0) {
|
|
40231
|
+
throw new Error("Sim.controller.diffDrive(...) requires at least one right joint");
|
|
40232
|
+
}
|
|
40233
|
+
const leftJoints = options.leftJoints.map(
|
|
40234
|
+
(joint2, index) => requireNonEmptyString(joint2, `Sim.controller.diffDrive(...) leftJoints[${index}]`)
|
|
40235
|
+
);
|
|
40236
|
+
const rightJoints = options.rightJoints.map(
|
|
40237
|
+
(joint2, index) => requireNonEmptyString(joint2, `Sim.controller.diffDrive(...) rightJoints[${index}]`)
|
|
40238
|
+
);
|
|
40239
|
+
requirePositiveNumber(options.wheelSeparationMm, "Sim.controller.diffDrive(...) wheelSeparationMm");
|
|
40240
|
+
requirePositiveNumber(options.wheelRadiusMm, "Sim.controller.diffDrive(...) wheelRadiusMm");
|
|
40241
|
+
requireFiniteNumber2(options.maxLinearVelocity, "Sim.controller.diffDrive(...) maxLinearVelocity");
|
|
40242
|
+
requireFiniteNumber2(options.maxAngularVelocity, "Sim.controller.diffDrive(...) maxAngularVelocity");
|
|
40243
|
+
requireFiniteNumber2(options.linearAcceleration, "Sim.controller.diffDrive(...) linearAcceleration");
|
|
40244
|
+
requireFiniteNumber2(options.angularAcceleration, "Sim.controller.diffDrive(...) angularAcceleration");
|
|
40245
|
+
return { ...options, kind: "diffDrive", leftJoints, rightJoints };
|
|
40246
|
+
}
|
|
40247
|
+
var profile = (name) => ({ kind: "profile", name });
|
|
40248
|
+
var Sim = {
|
|
40249
|
+
/** Create a named physical material with density and contact coefficients for simulation export and checks. */
|
|
40250
|
+
material,
|
|
40251
|
+
/** Describe one assembly part as a physical body with mass/density, material, collider intent, and optional contact surfaces. */
|
|
40252
|
+
body,
|
|
40253
|
+
/** Collision-geometry intent constructors for physical parts. */
|
|
40254
|
+
collider: {
|
|
40255
|
+
/** Use a generated collision mesh for the part. This is the default fast rigid-body collider for irregular parts. */
|
|
40256
|
+
convexHull: () => collider("convex"),
|
|
40257
|
+
/** Use the part bounding box as the collision geometry. This is fastest and works well for chassis and simple blocks. */
|
|
40258
|
+
boundingBox: () => collider("box"),
|
|
40259
|
+
/** Use the visual mesh as collision geometry. This is exact but usually slower in physics engines. */
|
|
40260
|
+
visualMesh: () => collider("visual"),
|
|
40261
|
+
/** Disable collision for a part with an explicit reason, such as a sensor-only or decorative object. */
|
|
40262
|
+
none: (reason) => collider("none", reason)
|
|
40263
|
+
},
|
|
40264
|
+
/** Joint-drive intent constructors for passive or powered assembly joints. */
|
|
40265
|
+
drive: {
|
|
40266
|
+
/** Mark a joint as passive while preserving damping and friction metadata for simulation export. */
|
|
40267
|
+
passive: passiveDrive,
|
|
40268
|
+
/** Mark a revolute joint as velocity-driven with torque and speed limits. Speed is authored in rpm and exported as deg/s or rad/s as needed. */
|
|
40269
|
+
velocity: velocityDrive
|
|
40270
|
+
},
|
|
40271
|
+
/** Contact-surface metadata over existing part connectors. */
|
|
40272
|
+
contact: {
|
|
40273
|
+
/** Mark a connector as the wheel tread contact surface for offline checks and downstream simulation metadata. */
|
|
40274
|
+
wheelSurface: (connectorName) => contact("wheelSurface", connectorName),
|
|
40275
|
+
/** Mark a connector as a gripper pad/contact surface for offline checks and downstream grasp-readiness metadata. */
|
|
40276
|
+
gripperSurface: (connectorName) => contact("gripperSurface", connectorName)
|
|
40277
|
+
},
|
|
40278
|
+
/** Named validation/export profile constructors. */
|
|
40279
|
+
profile: {
|
|
40280
|
+
/** SimReady-style profile for a robot body that should be runnable in a physics simulator. */
|
|
40281
|
+
robotBodyRunnable: () => profile("Robot-Body-Runnable"),
|
|
40282
|
+
/** SimReady-style profile for robot bodies targeting Isaac Sim readiness. */
|
|
40283
|
+
robotBodyIsaac: () => profile("Robot-Body-Isaac"),
|
|
40284
|
+
/** SimReady-style profile for robotics assets with PhysX-ready rigid bodies and colliders. */
|
|
40285
|
+
roboticsAssetPhysx: () => profile("Prop-Robotics-Physx")
|
|
40286
|
+
},
|
|
40287
|
+
/** Standard controller metadata constructors for simulator package generation. */
|
|
40288
|
+
controller: {
|
|
40289
|
+
/** Describe a differential-drive controller from left/right wheel joints and wheel dimensions. */
|
|
40290
|
+
diffDrive
|
|
40291
|
+
}
|
|
40292
|
+
};
|
|
40293
|
+
|
|
40099
40294
|
// src/forge/assembly/frames.ts
|
|
40100
40295
|
function requireFiniteVec33(input, label) {
|
|
40101
40296
|
if (!Array.isArray(input) || input.length !== 3) throw new Error(`${label} must be a [x, y, z] tuple`);
|
|
@@ -40839,6 +41034,7 @@ var Assembly = class _Assembly {
|
|
|
40839
41034
|
viewAnimations = [];
|
|
40840
41035
|
_defaultViewAnimation;
|
|
40841
41036
|
_refs = createPlacementReferences();
|
|
41037
|
+
_simulation;
|
|
40842
41038
|
_portsByPart = /* @__PURE__ */ new Map();
|
|
40843
41039
|
_usedPortRefs = /* @__PURE__ */ new Set();
|
|
40844
41040
|
_connectCounter = 0;
|
|
@@ -40848,6 +41044,7 @@ var Assembly = class _Assembly {
|
|
|
40848
41044
|
_fork() {
|
|
40849
41045
|
const next = new _Assembly(this.name);
|
|
40850
41046
|
next._refs = clonePlacementReferences(this._refs);
|
|
41047
|
+
next._simulation = cloneSimAssemblySimulation(this._simulation);
|
|
40851
41048
|
next._connectCounter = this._connectCounter;
|
|
40852
41049
|
next._frameEdgeCounter = this._frameEdgeCounter;
|
|
40853
41050
|
next._linkEdgeCounter = this._linkEdgeCounter;
|
|
@@ -40859,6 +41056,7 @@ var Assembly = class _Assembly {
|
|
|
40859
41056
|
part: record.part,
|
|
40860
41057
|
base: record.base,
|
|
40861
41058
|
metadata: record.metadata ? { ...record.metadata } : void 0,
|
|
41059
|
+
sim: cloneSimBody(record.sim),
|
|
40862
41060
|
mates: record.mates.map((mate) => ({ ...mate })),
|
|
40863
41061
|
bindToFrame: record.bindToFrame
|
|
40864
41062
|
});
|
|
@@ -40879,6 +41077,7 @@ var Assembly = class _Assembly {
|
|
|
40879
41077
|
velocity: joint2.velocity,
|
|
40880
41078
|
damping: joint2.damping,
|
|
40881
41079
|
friction: joint2.friction,
|
|
41080
|
+
sim: joint2.sim ? { drive: cloneSimDrive(joint2.sim.drive) } : void 0,
|
|
40882
41081
|
connectorRefs: joint2.connectorRefs ? { ...joint2.connectorRefs } : void 0
|
|
40883
41082
|
});
|
|
40884
41083
|
}
|
|
@@ -40999,6 +41198,42 @@ var Assembly = class _Assembly {
|
|
|
40999
41198
|
getReferences() {
|
|
41000
41199
|
return clonePlacementReferences(this._refs);
|
|
41001
41200
|
}
|
|
41201
|
+
/**
|
|
41202
|
+
* Attach the root simulation contract for this assembly.
|
|
41203
|
+
*
|
|
41204
|
+
* Use this after adding physical parts and joints. Robot-body profiles require
|
|
41205
|
+
* `rootPart`; asset profiles can describe one-part or multi-part physical assets.
|
|
41206
|
+
* URDF/SDF exporters and `forgecad check simready` read this contract directly,
|
|
41207
|
+
* so model files no longer need a separate `robotExport(...)` side effect.
|
|
41208
|
+
*
|
|
41209
|
+
* @category Assembly
|
|
41210
|
+
*/
|
|
41211
|
+
withSimulation(options) {
|
|
41212
|
+
if (!options || typeof options !== "object") {
|
|
41213
|
+
throw new Error("assembly.withSimulation(...) expects an options object");
|
|
41214
|
+
}
|
|
41215
|
+
if (!options.profile || options.profile.kind !== "profile") {
|
|
41216
|
+
throw new Error("assembly.withSimulation(...) requires a Sim.profile.*() value");
|
|
41217
|
+
}
|
|
41218
|
+
const rootPart = typeof options.rootPart === "string" ? options.rootPart.trim() : "";
|
|
41219
|
+
if (simProfileRequiresRootPart(options.profile) && !rootPart) {
|
|
41220
|
+
throw new Error("assembly.withSimulation(...) rootPart is required for robot-body profiles");
|
|
41221
|
+
}
|
|
41222
|
+
if (rootPart && !this.parts.has(rootPart)) {
|
|
41223
|
+
throw new Error(`assembly.withSimulation(...) rootPart "${rootPart}" does not exist`);
|
|
41224
|
+
}
|
|
41225
|
+
if (options.controllers && options.controllers.length > 0 && !simProfileAllowsControllers(options.profile)) {
|
|
41226
|
+
throw new Error("assembly.withSimulation(...) controllers are only supported for robot-body profiles");
|
|
41227
|
+
}
|
|
41228
|
+
const simulation = {
|
|
41229
|
+
kind: "simulation",
|
|
41230
|
+
profile: cloneSimProfile(options.profile),
|
|
41231
|
+
rootPart: rootPart || void 0,
|
|
41232
|
+
controllers: options.controllers?.map((controller) => ({ ...controller }))
|
|
41233
|
+
};
|
|
41234
|
+
this._simulation = cloneSimAssemblySimulation(simulation);
|
|
41235
|
+
return this;
|
|
41236
|
+
}
|
|
41002
41237
|
withConnectors(partNameOrConnectors, maybeConnectors) {
|
|
41003
41238
|
if (typeof partNameOrConnectors === "string") {
|
|
41004
41239
|
const partName = partNameOrConnectors;
|
|
@@ -41588,6 +41823,7 @@ var Assembly = class _Assembly {
|
|
|
41588
41823
|
part,
|
|
41589
41824
|
base: options.transform ? Transform.from(options.transform) : Transform.identity(),
|
|
41590
41825
|
metadata: options.metadata ? { ...options.metadata } : void 0,
|
|
41826
|
+
sim: cloneSimBody(options.sim),
|
|
41591
41827
|
mates: mates.map((mate, index) => {
|
|
41592
41828
|
const connector = typeof mate.connector === "string" ? mate.connector.trim() : "";
|
|
41593
41829
|
const toLink = typeof mate.toLink === "string" ? mate.toLink.trim() : "";
|
|
@@ -41676,6 +41912,7 @@ var Assembly = class _Assembly {
|
|
|
41676
41912
|
velocity: options.velocity,
|
|
41677
41913
|
damping: options.damping,
|
|
41678
41914
|
friction: options.friction,
|
|
41915
|
+
sim: options.drive ? { drive: cloneSimDrive(options.drive) } : void 0,
|
|
41679
41916
|
connectorRefs: options.connectorRefs ? { ...options.connectorRefs } : void 0
|
|
41680
41917
|
});
|
|
41681
41918
|
if (options.follows) {
|
|
@@ -41766,8 +42003,15 @@ var Assembly = class _Assembly {
|
|
|
41766
42003
|
* mating makes the axes anti-parallel, like a plug meeting a socket (same
|
|
41767
42004
|
* convention as `matchTo()`).
|
|
41768
42005
|
*
|
|
41769
|
-
* **
|
|
41770
|
-
*
|
|
42006
|
+
* **Revolute sign:** a positive joint value follows the right-hand rule about
|
|
42007
|
+
* the **child** connector's placed axis. Because face-to-face mating makes
|
|
42008
|
+
* the axes anti-parallel, that is the *left*-hand rule about the parent
|
|
42009
|
+
* connector's outward axis — if `+30` swings the opposite way you expected,
|
|
42010
|
+
* you predicted from the parent's axis. `forgecad debug assembly` prints each
|
|
42011
|
+
* joint's resolved world axis.
|
|
42012
|
+
*
|
|
42013
|
+
* **Mirrored revolute axes:** because of the right-hand rule, a mirrored
|
|
42014
|
+
* hinge axis (`[1, 0, 0]` vs `[-1, 0, 0]`) rotates oppositely for the
|
|
41771
42015
|
* same `+theta`: negate the mirrored side's value and mirror limits as
|
|
41772
42016
|
* `[min, max] -> [-max, -min]`. Prismatic joints have no handedness flip. Use
|
|
41773
42017
|
* an explicit per-side sign mapping (or side-neutral link controls) for
|
|
@@ -41825,6 +42069,7 @@ var Assembly = class _Assembly {
|
|
|
41825
42069
|
velocity: options.velocity,
|
|
41826
42070
|
damping: options.damping,
|
|
41827
42071
|
friction: options.friction,
|
|
42072
|
+
drive: options.drive,
|
|
41828
42073
|
follows: options.follows,
|
|
41829
42074
|
connectorRefs: {
|
|
41830
42075
|
parent: parentConnectorRef,
|
|
@@ -43291,11 +43536,13 @@ var Assembly = class _Assembly {
|
|
|
43291
43536
|
describe() {
|
|
43292
43537
|
return {
|
|
43293
43538
|
name: this.name,
|
|
43539
|
+
sim: cloneSimAssemblySimulation(this._simulation),
|
|
43294
43540
|
parts: [...this.parts.values()].map((part) => ({
|
|
43295
43541
|
name: part.name,
|
|
43296
43542
|
part: part.part,
|
|
43297
43543
|
base: part.base,
|
|
43298
43544
|
metadata: part.metadata ? { ...part.metadata } : void 0,
|
|
43545
|
+
sim: cloneSimBody(part.sim),
|
|
43299
43546
|
mates: part.mates.map((mate) => ({ ...mate })),
|
|
43300
43547
|
bindToFrame: part.bindToFrame
|
|
43301
43548
|
})),
|
|
@@ -43314,6 +43561,7 @@ var Assembly = class _Assembly {
|
|
|
43314
43561
|
velocity: joint2.velocity,
|
|
43315
43562
|
damping: joint2.damping,
|
|
43316
43563
|
friction: joint2.friction,
|
|
43564
|
+
sim: joint2.sim ? { drive: cloneSimDrive(joint2.sim.drive) } : void 0,
|
|
43317
43565
|
connectorRefs: joint2.connectorRefs ? { ...joint2.connectorRefs } : void 0
|
|
43318
43566
|
})),
|
|
43319
43567
|
jointCouplings: [...this.jointCouplings.values()].map((coupling) => ({
|
|
@@ -43644,6 +43892,7 @@ var ImportedAssembly = class _ImportedAssembly {
|
|
|
43644
43892
|
parent.addPart(`${pfx}${p2.name}`, p2.part, {
|
|
43645
43893
|
transform: p2.base,
|
|
43646
43894
|
metadata: p2.metadata,
|
|
43895
|
+
sim: p2.sim,
|
|
43647
43896
|
mate: p2.mates.map((mate) => ({ connector: mate.connector, toLink: `${pfx}${mate.toLink}` }))
|
|
43648
43897
|
});
|
|
43649
43898
|
}
|
|
@@ -43658,7 +43907,8 @@ var ImportedAssembly = class _ImportedAssembly {
|
|
|
43658
43907
|
effort: j.effort,
|
|
43659
43908
|
velocity: j.velocity,
|
|
43660
43909
|
damping: j.damping,
|
|
43661
|
-
friction: j.friction
|
|
43910
|
+
friction: j.friction,
|
|
43911
|
+
drive: j.sim?.drive
|
|
43662
43912
|
});
|
|
43663
43913
|
}
|
|
43664
43914
|
const allPorts = this._assembly.getAllPorts();
|
|
@@ -44173,9 +44423,9 @@ var GCodeBuilder = class {
|
|
|
44173
44423
|
_maxZ = -Infinity;
|
|
44174
44424
|
// Filament cross-section area (precomputed)
|
|
44175
44425
|
filamentArea;
|
|
44176
|
-
constructor(
|
|
44177
|
-
const preset =
|
|
44178
|
-
this.profile = { ...DEFAULT_PROFILE, ...bambuOverridesFor(preset), ...
|
|
44426
|
+
constructor(profile2) {
|
|
44427
|
+
const preset = profile2?.printer ?? DEFAULT_PROFILE.printer;
|
|
44428
|
+
this.profile = { ...DEFAULT_PROFILE, ...bambuOverridesFor(preset), ...profile2 };
|
|
44179
44429
|
this.currentSpeed = this.profile.printSpeed;
|
|
44180
44430
|
this.filamentArea = Math.PI * (this.profile.filament / 2) ** 2;
|
|
44181
44431
|
}
|
|
@@ -44836,8 +45086,8 @@ function bambuConfigBlock(p2, hotend, bed) {
|
|
|
44836
45086
|
lines.push("; CONFIG_BLOCK_END");
|
|
44837
45087
|
return lines;
|
|
44838
45088
|
}
|
|
44839
|
-
function gcode(
|
|
44840
|
-
return new GCodeBuilder(
|
|
45089
|
+
function gcode(profile2) {
|
|
45090
|
+
return new GCodeBuilder(profile2);
|
|
44841
45091
|
}
|
|
44842
45092
|
|
|
44843
45093
|
// src/forge/sketch/workplane.ts
|
|
@@ -44926,8 +45176,8 @@ function resolvePlanarFaceWorkplane(face) {
|
|
|
44926
45176
|
source
|
|
44927
45177
|
};
|
|
44928
45178
|
}
|
|
44929
|
-
function isGenericMissingFaceError(
|
|
44930
|
-
const message =
|
|
45179
|
+
function isGenericMissingFaceError(error2, face) {
|
|
45180
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
44931
45181
|
return message === `Face "${face}" is not available. Supported faces: none` || message.startsWith(`Face "${face}" is not available. Supported faces: `);
|
|
44932
45182
|
}
|
|
44933
45183
|
function resolveSketchWorkplane(parentOrFace, face) {
|
|
@@ -44951,9 +45201,9 @@ function resolveSketchWorkplane(parentOrFace, face) {
|
|
|
44951
45201
|
workplane.source = { kind: "canonical-face", face, owner: resolveTargetQueryOwner(parentOrFace) };
|
|
44952
45202
|
}
|
|
44953
45203
|
return workplane;
|
|
44954
|
-
} catch (
|
|
44955
|
-
if (!isCanonicalFace(face) || !isGenericMissingFaceError(
|
|
44956
|
-
throw
|
|
45204
|
+
} catch (error2) {
|
|
45205
|
+
if (!isCanonicalFace(face) || !isGenericMissingFaceError(error2, face)) {
|
|
45206
|
+
throw error2;
|
|
44957
45207
|
}
|
|
44958
45208
|
}
|
|
44959
45209
|
}
|
|
@@ -45308,22 +45558,22 @@ function validateHoleExtentCompatibility(hole2, extent) {
|
|
|
45308
45558
|
throw new Error("Shape.hole() thread.depth cannot exceed the primary forward hole depth.");
|
|
45309
45559
|
}
|
|
45310
45560
|
}
|
|
45311
|
-
function resolveCutTaperCompilePlan(
|
|
45561
|
+
function resolveCutTaperCompilePlan(profile2, opts, extent) {
|
|
45312
45562
|
if (opts.taperScale == null) return void 0;
|
|
45313
45563
|
if (extent.kind === "two-sided") {
|
|
45314
45564
|
throw new Error("Shape.cutout() does not yet combine taperScale with reverse two-sided extents.");
|
|
45315
45565
|
}
|
|
45316
|
-
if (!
|
|
45566
|
+
if (!profile2) {
|
|
45317
45567
|
throw new Error("Shape.cutout() requires a compile-covered sketch profile before taperScale can be validated.");
|
|
45318
45568
|
}
|
|
45319
|
-
if (
|
|
45569
|
+
if (profile2.kind !== "circle" && profile2.kind !== "rect" && profile2.kind !== "roundedRect") {
|
|
45320
45570
|
throw new Error("Shape.cutout() taperScale currently supports circle, rect, and roundedRect sketch profiles only.");
|
|
45321
45571
|
}
|
|
45322
45572
|
const scale11 = Array.isArray(opts.taperScale) ? opts.taperScale : [opts.taperScale, opts.taperScale];
|
|
45323
45573
|
if (scale11.length !== 2 || !isFinitePositive3(scale11[0]) || !isFinitePositive3(scale11[1])) {
|
|
45324
45574
|
throw new Error("Shape.cutout() taperScale must be a positive finite number or [x, y] pair.");
|
|
45325
45575
|
}
|
|
45326
|
-
if (
|
|
45576
|
+
if (profile2.kind === "circle" && Math.abs(scale11[0] - scale11[1]) > 1e-6) {
|
|
45327
45577
|
throw new Error("Shape.cutout() circular tapered cuts currently require a uniform taperScale.");
|
|
45328
45578
|
}
|
|
45329
45579
|
return {
|
|
@@ -45418,8 +45668,8 @@ function shapeCutout(target, sketch, opts = {}) {
|
|
|
45418
45668
|
if (!basePlan) {
|
|
45419
45669
|
throw new Error("Shape.cutout() currently requires a compile-covered target shape.");
|
|
45420
45670
|
}
|
|
45421
|
-
const
|
|
45422
|
-
if (!
|
|
45671
|
+
const profile2 = getSketchCompileProfilePlan(sketch);
|
|
45672
|
+
if (!profile2) {
|
|
45423
45673
|
throw new Error("Shape.cutout() requires a compile-covered sketch profile.");
|
|
45424
45674
|
}
|
|
45425
45675
|
const placementModel = getSketchPlacementModel(sketch);
|
|
@@ -45441,7 +45691,7 @@ function shapeCutout(target, sketch, opts = {}) {
|
|
|
45441
45691
|
},
|
|
45442
45692
|
"Shape.cutout()"
|
|
45443
45693
|
);
|
|
45444
|
-
const taper = resolveCutTaperCompilePlan(
|
|
45694
|
+
const taper = resolveCutTaperCompilePlan(profile2, opts, extent);
|
|
45445
45695
|
const plan = createOwnedTopologyRewritePlan2(
|
|
45446
45696
|
buildCutShapeCompilePlan(
|
|
45447
45697
|
basePlan,
|
|
@@ -45449,12 +45699,12 @@ function shapeCutout(target, sketch, opts = {}) {
|
|
|
45449
45699
|
matrix: placementMatrix2,
|
|
45450
45700
|
placement: cloneSketchPlacementModel(placementModel)
|
|
45451
45701
|
},
|
|
45452
|
-
|
|
45702
|
+
profile2,
|
|
45453
45703
|
extent,
|
|
45454
45704
|
taper
|
|
45455
45705
|
),
|
|
45456
45706
|
"cut",
|
|
45457
|
-
(owner) => buildCutTopologyRewritePropagation(owner, basePlan, placementModel,
|
|
45707
|
+
(owner) => buildCutTopologyRewritePropagation(owner, basePlan, placementModel, profile2, extent, taper)
|
|
45458
45708
|
);
|
|
45459
45709
|
return buildFeatureResult(target, plan);
|
|
45460
45710
|
}
|
|
@@ -47570,20 +47820,20 @@ function tSlotProfile(options = {}) {
|
|
|
47570
47820
|
sketchRotateAround(sideSlot, 180, [0, 0]),
|
|
47571
47821
|
sketchRotateAround(sideSlot, 270, [0, 0])
|
|
47572
47822
|
);
|
|
47573
|
-
let
|
|
47574
|
-
if (boss2)
|
|
47575
|
-
|
|
47823
|
+
let profile2 = union2d(shell, webX, webY);
|
|
47824
|
+
if (boss2) profile2 = union2d(profile2, boss2);
|
|
47825
|
+
profile2 = difference2d(profile2, slots);
|
|
47576
47826
|
if (centerBoreDia > 0) {
|
|
47577
|
-
|
|
47827
|
+
profile2 = difference2d(profile2, circle2d(centerBoreDia / 2, segments));
|
|
47578
47828
|
}
|
|
47579
|
-
return
|
|
47829
|
+
return profile2;
|
|
47580
47830
|
}
|
|
47581
47831
|
function tSlotExtrusion(length7, options = {}) {
|
|
47582
47832
|
if (!Number.isFinite(length7) || length7 <= 0) {
|
|
47583
47833
|
throw new Error('tSlotExtrusion: "length" must be > 0');
|
|
47584
47834
|
}
|
|
47585
|
-
const
|
|
47586
|
-
return sketchExtrude(
|
|
47835
|
+
const profile2 = tSlotProfile(options);
|
|
47836
|
+
return sketchExtrude(profile2, length7);
|
|
47587
47837
|
}
|
|
47588
47838
|
|
|
47589
47839
|
// src/forge/lib/profiles-2020.ts
|
|
@@ -47648,26 +47898,26 @@ function profile2020BSlot6Profile(options = {}) {
|
|
|
47648
47898
|
sketchRotateAround(sideSlot, 180, [0, 0]),
|
|
47649
47899
|
sketchRotateAround(sideSlot, 270, [0, 0])
|
|
47650
47900
|
);
|
|
47651
|
-
let
|
|
47901
|
+
let profile2 = difference2d(outer, slots);
|
|
47652
47902
|
const centerBoss = opts.centerBossDia > 0 ? circle2d(opts.centerBossDia / 2, opts.segments) : null;
|
|
47653
47903
|
const webLen = size * 1.15;
|
|
47654
47904
|
const xWeb = rect(webLen, opts.diagonalWebWidth);
|
|
47655
47905
|
const webs = union2d(sketchRotateAround(xWeb, 45, [0, 0]), sketchRotateAround(xWeb, -45, [0, 0]));
|
|
47656
|
-
|
|
47906
|
+
profile2 = union2d(profile2, webs);
|
|
47657
47907
|
if (centerBoss) {
|
|
47658
|
-
|
|
47908
|
+
profile2 = union2d(profile2, centerBoss);
|
|
47659
47909
|
}
|
|
47660
47910
|
if (opts.centerBoreDia > 0) {
|
|
47661
|
-
|
|
47911
|
+
profile2 = difference2d(profile2, circle2d(opts.centerBoreDia / 2, opts.segments));
|
|
47662
47912
|
}
|
|
47663
|
-
return
|
|
47913
|
+
return profile2;
|
|
47664
47914
|
}
|
|
47665
47915
|
function profile2020BSlot6(length7, options = {}) {
|
|
47666
47916
|
if (!Number.isFinite(length7) || length7 <= 0) {
|
|
47667
47917
|
throw new Error('profile2020BSlot6: "length" must be > 0');
|
|
47668
47918
|
}
|
|
47669
|
-
const
|
|
47670
|
-
return sketchExtrude(
|
|
47919
|
+
const profile2 = profile2020BSlot6Profile(options);
|
|
47920
|
+
return sketchExtrude(profile2, length7);
|
|
47671
47921
|
}
|
|
47672
47922
|
|
|
47673
47923
|
// src/forge/assembly/explodeCore.ts
|
|
@@ -48930,7 +49180,7 @@ function loftInternal(profiles2, heights, options = {}) {
|
|
|
48930
49180
|
if (profiles2.length !== heights.length) {
|
|
48931
49181
|
throw new Error("loft requires heights.length === profiles.length");
|
|
48932
49182
|
}
|
|
48933
|
-
const pairs = profiles2.map((
|
|
49183
|
+
const pairs = profiles2.map((profile2, i) => ({ profile: profile2, z: heights[i] })).sort((a, b) => a.z - b.z);
|
|
48934
49184
|
for (let i = 1; i < pairs.length; i++) {
|
|
48935
49185
|
if (Math.abs(pairs[i].z - pairs[i - 1].z) < 1e-8) {
|
|
48936
49186
|
throw new Error("loft requires strictly increasing, unique heights");
|
|
@@ -48981,8 +49231,8 @@ function loftInternal(profiles2, heights, options = {}) {
|
|
|
48981
49231
|
sources: isKernelLoft ? ["loft"] : ["loft", "level-set"]
|
|
48982
49232
|
});
|
|
48983
49233
|
}
|
|
48984
|
-
function extractSweepEdgeLabels(
|
|
48985
|
-
const labels = getSketchEdgeLabels(
|
|
49234
|
+
function extractSweepEdgeLabels(profile2) {
|
|
49235
|
+
const labels = getSketchEdgeLabels(profile2);
|
|
48986
49236
|
if (labels.size === 0) return void 0;
|
|
48987
49237
|
const result = {};
|
|
48988
49238
|
for (const [name, label] of labels) {
|
|
@@ -49088,14 +49338,14 @@ function buildPathPlan(path2) {
|
|
|
49088
49338
|
}
|
|
49089
49339
|
throw new Error("sweep: unsupported path type");
|
|
49090
49340
|
}
|
|
49091
|
-
function sweep(
|
|
49341
|
+
function sweep(profile2, path2, options = {}) {
|
|
49092
49342
|
const requestedPathSamples = Math.max(4, options.samples ?? 48);
|
|
49093
49343
|
const effectivePathSamples = scaleSweepPathSamples(requestedPathSamples);
|
|
49094
49344
|
const { plan: pathPlan, sampleForBounds } = buildPathPlan(path2);
|
|
49095
49345
|
const pathPts = sampleForBounds(effectivePathSamples);
|
|
49096
49346
|
if (pathPts.length < 2) throw new Error("sweep requires a path with at least two points");
|
|
49097
49347
|
const up = options.up ?? [0, 0, 1];
|
|
49098
|
-
const pb =
|
|
49348
|
+
const pb = profile2.bounds();
|
|
49099
49349
|
const pr = Math.max(Math.abs(pb.min[0]), Math.abs(pb.max[0]), Math.abs(pb.min[1]), Math.abs(pb.max[1]));
|
|
49100
49350
|
const roughStats = measurePathPoints(pathPts);
|
|
49101
49351
|
const span = Math.max(
|
|
@@ -49109,8 +49359,8 @@ function sweep(profile, path2, options = {}) {
|
|
|
49109
49359
|
const edgeLength2 = scaleLevelSetEdgeLength(requestedEdgeLength);
|
|
49110
49360
|
const requestedPad = options.boundsPadding ?? Math.max(pr + edgeLength2 * 2, span * 0.04, 2);
|
|
49111
49361
|
const pad = scaleLevelSetBoundsPadding(requestedPad);
|
|
49112
|
-
const edgeLabels = extractSweepEdgeLabels(
|
|
49113
|
-
const plan = buildSweepShapeCompilePlan(getSketchCompileProfilePlan(
|
|
49362
|
+
const edgeLabels = extractSweepEdgeLabels(profile2);
|
|
49363
|
+
const plan = buildSweepShapeCompilePlan(getSketchCompileProfilePlan(profile2), pathPlan, {
|
|
49114
49364
|
edgeLength: edgeLength2,
|
|
49115
49365
|
boundsPadding: pad,
|
|
49116
49366
|
up,
|
|
@@ -49123,7 +49373,7 @@ function sweep(profile, path2, options = {}) {
|
|
|
49123
49373
|
const ownedPlan = createOwnedShapeCompilePlan(plan, "sweep");
|
|
49124
49374
|
const activeBackend = getActiveBackend();
|
|
49125
49375
|
const isKernelSweep = activeBackend === "occt" || activeBackend === "truck";
|
|
49126
|
-
return buildShapeFromCompilePlan(ownedPlan,
|
|
49376
|
+
return buildShapeFromCompilePlan(ownedPlan, profile2.colorHex, {
|
|
49127
49377
|
fidelity: isKernelSweep ? "kernel-native" : "sampled",
|
|
49128
49378
|
sources: isKernelSweep ? ["sweep"] : ["sweep", "level-set"]
|
|
49129
49379
|
});
|
|
@@ -49277,8 +49527,8 @@ var HelixCurve = class extends Curve3D {
|
|
|
49277
49527
|
return Math.hypot(this.radius * Math.PI * 2 * this.turns, this.height);
|
|
49278
49528
|
}
|
|
49279
49529
|
};
|
|
49280
|
-
function profileHalfExtent(
|
|
49281
|
-
const bounds =
|
|
49530
|
+
function profileHalfExtent(profile2) {
|
|
49531
|
+
const bounds = profile2.bounds();
|
|
49282
49532
|
return Math.max(Math.abs(bounds.min[0]), Math.abs(bounds.max[0]), Math.abs(bounds.min[1]), Math.abs(bounds.max[1]));
|
|
49283
49533
|
}
|
|
49284
49534
|
function buildHelixCoil(profileOrOptions, maybeOptions) {
|
|
@@ -49286,12 +49536,12 @@ function buildHelixCoil(profileOrOptions, maybeOptions) {
|
|
|
49286
49536
|
const options = hasCustomProfile ? maybeOptions : profileOrOptions;
|
|
49287
49537
|
if (!options) throw new Error("Helix.coil: options are required.");
|
|
49288
49538
|
const spec2 = normalizeHelixOptions(options, "Helix.coil");
|
|
49289
|
-
const
|
|
49539
|
+
const profile2 = hasCustomProfile ? profileOrOptions : circle2d(
|
|
49290
49540
|
requirePositive(options.wireRadius, "Helix.coil.wireRadius"),
|
|
49291
49541
|
requireIntegerAtLeast2(options.profileSegments ?? 24, "Helix.coil.profileSegments", 8)
|
|
49292
49542
|
);
|
|
49293
|
-
if (
|
|
49294
|
-
const halfExtent = profileHalfExtent(
|
|
49543
|
+
if (profile2.isEmpty()) throw new Error("Helix.coil: profile must not be empty.");
|
|
49544
|
+
const halfExtent = profileHalfExtent(profile2);
|
|
49295
49545
|
if (spec2.radius <= halfExtent) {
|
|
49296
49546
|
throw new Error(`Helix.coil: radius must be greater than the profile half-extent (${halfExtent}).`);
|
|
49297
49547
|
}
|
|
@@ -49305,7 +49555,7 @@ function buildHelixCoil(profileOrOptions, maybeOptions) {
|
|
|
49305
49555
|
...options,
|
|
49306
49556
|
samplesPerTurn: divisionsPerTurn
|
|
49307
49557
|
});
|
|
49308
|
-
return sweep(
|
|
49558
|
+
return sweep(profile2, guide, { samples, up: [0, 0, -spec2.direction] });
|
|
49309
49559
|
}
|
|
49310
49560
|
var Helix = {
|
|
49311
49561
|
path(options) {
|
|
@@ -50027,8 +50277,8 @@ function pipeRoute(points, radius, options) {
|
|
|
50027
50277
|
}
|
|
50028
50278
|
const route = Curve.Route.fromPolyline(simplifyStraightPipeRoutePoints(points), { cornerRadius: bendR });
|
|
50029
50279
|
const outer = circle2d(radius, segs);
|
|
50030
|
-
const
|
|
50031
|
-
return sweep(
|
|
50280
|
+
const profile2 = wall != null && wall > 0 ? difference2d(outer, circle2d(radius - wall, segs)) : outer;
|
|
50281
|
+
return sweep(profile2, route, { samples: segs });
|
|
50032
50282
|
}
|
|
50033
50283
|
function simplifyStraightPipeRoutePoints(points) {
|
|
50034
50284
|
const simplified = [points[0]];
|
|
@@ -50076,9 +50326,9 @@ function elbow(pipeRadius, bendRadius, angle, options) {
|
|
|
50076
50326
|
const center = scale7(inward, bendRadius);
|
|
50077
50327
|
const endRadial = rotateDirection(scale7(inward, -bendRadius), turnAxis, angleRad);
|
|
50078
50328
|
const end = addVec(center, endRadial);
|
|
50079
|
-
const
|
|
50329
|
+
const profile2 = profileForPipeRadius(pipeRadius, wall, Math.floor(segments));
|
|
50080
50330
|
const pathSamples = Math.max(6, Math.ceil(segments * (angleRad * 180 / Math.PI) / 360) + 1);
|
|
50081
|
-
return sweep(
|
|
50331
|
+
return sweep(profile2, Curve.Arc({ start: [0, 0, 0], end, tangent: from }), { samples: pathSamples, up: turnAxis });
|
|
50082
50332
|
}
|
|
50083
50333
|
|
|
50084
50334
|
// src/forge/lib/belts.ts
|
|
@@ -50427,14 +50677,14 @@ function readGearMeta(shape) {
|
|
|
50427
50677
|
const meta = shape[GEAR_META_KEY];
|
|
50428
50678
|
return meta ?? null;
|
|
50429
50679
|
}
|
|
50430
|
-
function remapErrorPrefix(
|
|
50431
|
-
if (
|
|
50432
|
-
if (
|
|
50433
|
-
throw new Error(`${targetPrefix}:${
|
|
50680
|
+
function remapErrorPrefix(error2, sourcePrefix, targetPrefix) {
|
|
50681
|
+
if (error2 instanceof Error) {
|
|
50682
|
+
if (error2.message.startsWith(`${sourcePrefix}:`)) {
|
|
50683
|
+
throw new Error(`${targetPrefix}:${error2.message.slice(sourcePrefix.length + 1)}`);
|
|
50434
50684
|
}
|
|
50435
|
-
throw
|
|
50685
|
+
throw error2;
|
|
50436
50686
|
}
|
|
50437
|
-
throw
|
|
50687
|
+
throw error2;
|
|
50438
50688
|
}
|
|
50439
50689
|
|
|
50440
50690
|
// src/forge/lib/gears/spur.ts
|
|
@@ -50561,11 +50811,11 @@ function buildSpurGearProfile(meta, segmentsPerTooth) {
|
|
|
50561
50811
|
function spurGear(options) {
|
|
50562
50812
|
const normalized = normalizeSpurGearOptions(options);
|
|
50563
50813
|
const meta = buildSpurGearMeta(normalized);
|
|
50564
|
-
let
|
|
50814
|
+
let profile2 = buildSpurGearProfile(meta, normalized.segmentsPerTooth);
|
|
50565
50815
|
if (normalized.boreDiameter > 0) {
|
|
50566
|
-
|
|
50816
|
+
profile2 = difference2d(profile2, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
|
|
50567
50817
|
}
|
|
50568
|
-
const shape = sketchExtrude(
|
|
50818
|
+
const shape = sketchExtrude(profile2, normalized.faceWidth);
|
|
50569
50819
|
const shapeWithConnectors = shape.withConnectors({
|
|
50570
50820
|
bore: connectorFactory(
|
|
50571
50821
|
"gear-bore",
|
|
@@ -50614,8 +50864,8 @@ function gearBodyDisk(options) {
|
|
|
50614
50864
|
const bore = requireOptionalBore("gearBodyDisk", options.boreDiameter, options.outerRadius * 2);
|
|
50615
50865
|
const segments = resolveSegments(options.segments);
|
|
50616
50866
|
const outer = circle2d(options.outerRadius, segments);
|
|
50617
|
-
const
|
|
50618
|
-
return sketchExtrude(
|
|
50867
|
+
const profile2 = bore > 0 ? difference2d(outer, circle2d(bore * 0.5, segments)) : outer;
|
|
50868
|
+
return sketchExtrude(profile2, options.faceWidth);
|
|
50619
50869
|
}
|
|
50620
50870
|
function gearBodyDiskWithHub(options) {
|
|
50621
50871
|
requirePositive2("gearBodyDiskWithHub", "hubDiameter", options.hubDiameter);
|
|
@@ -50656,15 +50906,15 @@ function gearBodySpoked(options) {
|
|
|
50656
50906
|
for (let i = 0; i < options.spokeCount; i++) {
|
|
50657
50907
|
spokes.push(sketchRotateAround(spoke, 360 / options.spokeCount * i, [0, 0]));
|
|
50658
50908
|
}
|
|
50659
|
-
const
|
|
50660
|
-
return sketchExtrude(
|
|
50909
|
+
const profile2 = bore > 0 ? difference2d(union2d(rim, hub, ...spokes), circle2d(bore * 0.5, segments)) : union2d(rim, hub, ...spokes);
|
|
50910
|
+
return sketchExtrude(profile2, options.faceWidth);
|
|
50661
50911
|
}
|
|
50662
|
-
function gearBodyFromProfile(
|
|
50663
|
-
if (!(
|
|
50912
|
+
function gearBodyFromProfile(profile2, options) {
|
|
50913
|
+
if (!(profile2 instanceof Sketch)) throw new Error('gearBodyFromProfile: "profile" must be a Sketch');
|
|
50664
50914
|
requirePositive2("gearBodyFromProfile", "faceWidth", options.faceWidth);
|
|
50665
50915
|
const bore = options.boreDiameter ?? 0;
|
|
50666
50916
|
if (!Number.isFinite(bore) || bore < 0) throw new Error('gearBodyFromProfile: "boreDiameter" must be >= 0');
|
|
50667
|
-
return cutBore(sketchExtrude(
|
|
50917
|
+
return cutBore(sketchExtrude(profile2, options.faceWidth), bore);
|
|
50668
50918
|
}
|
|
50669
50919
|
|
|
50670
50920
|
// src/forge/lib/gears/drive-wheel-geometry.ts
|
|
@@ -50702,16 +50952,16 @@ function buildSpurTeethRegion(options, name, faceWidth) {
|
|
|
50702
50952
|
let normalized;
|
|
50703
50953
|
try {
|
|
50704
50954
|
normalized = normalizeSpurGearOptions({ ...options, teeth: teethOnFullCircle, faceWidth, boreDiameter: 0 });
|
|
50705
|
-
} catch (
|
|
50706
|
-
remapErrorPrefix(
|
|
50955
|
+
} catch (error2) {
|
|
50956
|
+
remapErrorPrefix(error2, "spurGear", scope);
|
|
50707
50957
|
}
|
|
50708
50958
|
const gearMeta = buildSpurGearMeta(normalized);
|
|
50709
50959
|
const pitchStepDeg = 360 / teethOnFullCircle;
|
|
50710
50960
|
const fromAngleDeg = firstTooth * pitchStepDeg - pitchStepDeg * 0.5;
|
|
50711
50961
|
const toAngleDeg = (firstTooth + toothCount - 1) * pitchStepDeg + pitchStepDeg * 0.5;
|
|
50712
|
-
const
|
|
50962
|
+
const profile2 = buildSpurToothRegionProfile(gearMeta, firstTooth, toothCount, normalized.segmentsPerTooth);
|
|
50713
50963
|
return {
|
|
50714
|
-
shape: sketchExtrude(
|
|
50964
|
+
shape: sketchExtrude(profile2, faceWidth),
|
|
50715
50965
|
gearMeta,
|
|
50716
50966
|
meta: {
|
|
50717
50967
|
name,
|
|
@@ -50858,8 +51108,8 @@ var DriveWheelBuilder = class {
|
|
|
50858
51108
|
if (firstGearRegion && this.boreDiameter * 0.5 >= firstGearRegion.rootRadius - EPSILON4) {
|
|
50859
51109
|
throw new Error("driveWheel: bore is too large for the first spur-tooth region");
|
|
50860
51110
|
}
|
|
50861
|
-
const
|
|
50862
|
-
let combined =
|
|
51111
|
+
const body2 = this.body?.clone() ?? gearBodyDisk({ outerRadius: firstGearRegion?.rootRadius ?? this.defaultBodyRadius(), faceWidth });
|
|
51112
|
+
let combined = body2;
|
|
50863
51113
|
for (const region of this.regions) combined = combined.add(region.shape);
|
|
50864
51114
|
combined = cutBore2(combined, this.boreDiameter);
|
|
50865
51115
|
const withConnectors = combined.withConnectors({
|
|
@@ -50873,7 +51123,7 @@ var DriveWheelBuilder = class {
|
|
|
50873
51123
|
kind: "driveWheel",
|
|
50874
51124
|
faceWidth,
|
|
50875
51125
|
boreDiameter: this.boreDiameter,
|
|
50876
|
-
regions: this.regionMetadata(
|
|
51126
|
+
regions: this.regionMetadata(body2, faceWidth)
|
|
50877
51127
|
});
|
|
50878
51128
|
}
|
|
50879
51129
|
measurements(faceWidth) {
|
|
@@ -50890,9 +51140,9 @@ var DriveWheelBuilder = class {
|
|
|
50890
51140
|
} : {}
|
|
50891
51141
|
};
|
|
50892
51142
|
}
|
|
50893
|
-
regionMetadata(
|
|
51143
|
+
regionMetadata(body2, faceWidth) {
|
|
50894
51144
|
return [
|
|
50895
|
-
{ name: "body", kind: "body", outerRadius: bodyOuterRadius(
|
|
51145
|
+
{ name: "body", kind: "body", outerRadius: bodyOuterRadius(body2), faceWidth },
|
|
50896
51146
|
...this.regions.map((region) => ({ ...region.meta }))
|
|
50897
51147
|
];
|
|
50898
51148
|
}
|
|
@@ -50990,8 +51240,8 @@ function normalizeFaceGearOptions(options) {
|
|
|
50990
51240
|
let normalizedSpur;
|
|
50991
51241
|
try {
|
|
50992
51242
|
normalizedSpur = normalizeSpurGearOptions(options);
|
|
50993
|
-
} catch (
|
|
50994
|
-
remapErrorPrefix(
|
|
51243
|
+
} catch (error2) {
|
|
51244
|
+
remapErrorPrefix(error2, "spurGear", "faceGear");
|
|
50995
51245
|
}
|
|
50996
51246
|
const side = options.side ?? "top";
|
|
50997
51247
|
if (side !== "top" && side !== "bottom") {
|
|
@@ -51036,8 +51286,8 @@ function faceGear(options) {
|
|
|
51036
51286
|
let spurMeta;
|
|
51037
51287
|
try {
|
|
51038
51288
|
spurMeta = buildSpurGearMeta(normalized);
|
|
51039
|
-
} catch (
|
|
51040
|
-
remapErrorPrefix(
|
|
51289
|
+
} catch (error2) {
|
|
51290
|
+
remapErrorPrefix(error2, "spurGear", "faceGear");
|
|
51041
51291
|
}
|
|
51042
51292
|
const zBands = resolveFaceGearZBands(normalized);
|
|
51043
51293
|
const meta = {
|
|
@@ -51051,8 +51301,8 @@ function faceGear(options) {
|
|
|
51051
51301
|
};
|
|
51052
51302
|
const segments = Math.max(48, normalized.teeth * normalized.segmentsPerTooth);
|
|
51053
51303
|
const rootDisk = cylinder2(normalized.faceWidth, meta.rootRadius, void 0, segments);
|
|
51054
|
-
const
|
|
51055
|
-
const toothBandRaw = difference2d(
|
|
51304
|
+
const profile2 = buildSpurGearProfile(spurMeta, normalized.segmentsPerTooth);
|
|
51305
|
+
const toothBandRaw = difference2d(profile2, circle2d(meta.rootRadius, Math.max(48, normalized.teeth * 2)));
|
|
51056
51306
|
const simplifiedCross = toothBandRaw.cross.simplify(1e-6);
|
|
51057
51307
|
const toothBandProfile = setSketchCompileProfilePlan(
|
|
51058
51308
|
new Sketch(simplifiedCross, toothBandRaw.colorHex),
|
|
@@ -51095,8 +51345,8 @@ function faceGear(options) {
|
|
|
51095
51345
|
function sideGear(options) {
|
|
51096
51346
|
try {
|
|
51097
51347
|
return faceGear(options);
|
|
51098
|
-
} catch (
|
|
51099
|
-
remapErrorPrefix(
|
|
51348
|
+
} catch (error2) {
|
|
51349
|
+
remapErrorPrefix(error2, "faceGear", "sideGear");
|
|
51100
51350
|
}
|
|
51101
51351
|
}
|
|
51102
51352
|
|
|
@@ -51207,8 +51457,8 @@ function ringGear(options) {
|
|
|
51207
51457
|
for (let i = 0; i < normalized.teeth; i++) {
|
|
51208
51458
|
spaces.push(sketchRotateAround(toothSpace, 360 / normalized.teeth * i, [0, 0]));
|
|
51209
51459
|
}
|
|
51210
|
-
const
|
|
51211
|
-
const shape = sketchExtrude(
|
|
51460
|
+
const profile2 = difference2d(ringBlank, union2d(...spaces));
|
|
51461
|
+
const shape = sketchExtrude(profile2, normalized.faceWidth);
|
|
51212
51462
|
const shapeWithConnectors = shape.withConnectors({
|
|
51213
51463
|
bore: connectorFactory(
|
|
51214
51464
|
"ring-bore",
|
|
@@ -51281,8 +51531,8 @@ function rackGear(options) {
|
|
|
51281
51531
|
const span = (options.teeth - 1) * pitch + halfRoot * 2;
|
|
51282
51532
|
const length7 = span + module * 2;
|
|
51283
51533
|
const base = sketchTranslate(rect(length7, baseHeight), 0, -dedendum - baseHeight * 0.5);
|
|
51284
|
-
const
|
|
51285
|
-
const shape = sketchExtrude(
|
|
51534
|
+
const profile2 = union2d(base, ...teethSketches);
|
|
51535
|
+
const shape = sketchExtrude(profile2, options.faceWidth);
|
|
51286
51536
|
const meta = {
|
|
51287
51537
|
kind: "rack",
|
|
51288
51538
|
module,
|
|
@@ -51390,11 +51640,11 @@ function normalizeBevelGearOptions(options) {
|
|
|
51390
51640
|
function bevelGear(options) {
|
|
51391
51641
|
const normalized = normalizeBevelGearOptions(options);
|
|
51392
51642
|
const meta = buildSpurGearMeta(normalized);
|
|
51393
|
-
let
|
|
51643
|
+
let profile2 = buildSpurGearProfile(meta, normalized.segmentsPerTooth);
|
|
51394
51644
|
if (normalized.boreDiameter > 0) {
|
|
51395
|
-
|
|
51645
|
+
profile2 = difference2d(profile2, circle2d(normalized.boreDiameter * 0.5, Math.max(48, normalized.teeth * 2)));
|
|
51396
51646
|
}
|
|
51397
|
-
const shape = sketchExtrude(
|
|
51647
|
+
const shape = sketchExtrude(profile2, normalized.faceWidth, {
|
|
51398
51648
|
scaleTop: normalized.topScale
|
|
51399
51649
|
});
|
|
51400
51650
|
const apexZ = normalized.module * normalized.teeth * 0.5 / Math.tan(normalized.pitchAngleRad);
|
|
@@ -51886,8 +52136,8 @@ function sideGearPair(options) {
|
|
|
51886
52136
|
place: options.place,
|
|
51887
52137
|
phaseDeg: options.phaseDeg
|
|
51888
52138
|
});
|
|
51889
|
-
} catch (
|
|
51890
|
-
remapErrorPrefix(
|
|
52139
|
+
} catch (error2) {
|
|
52140
|
+
remapErrorPrefix(error2, "faceGearPair", "sideGearPair");
|
|
51891
52141
|
}
|
|
51892
52142
|
const diagnostics = pair.diagnostics.map((d) => ({
|
|
51893
52143
|
...d,
|
|
@@ -52235,7 +52485,7 @@ var partLibrary = {
|
|
|
52235
52485
|
gearBodyFromProfile: softDeprecated(
|
|
52236
52486
|
"lib.gearBodyFromProfile",
|
|
52237
52487
|
"lib.gearBodies.fromProfile(profile, options)",
|
|
52238
|
-
(
|
|
52488
|
+
(profile2, options) => gearBodyFromProfile(profile2, options)
|
|
52239
52489
|
)
|
|
52240
52490
|
};
|
|
52241
52491
|
|
|
@@ -65834,23 +66084,23 @@ function lowerShapeCompilePlanToCadQueryResultAtPath(plan, path2) {
|
|
|
65834
66084
|
}
|
|
65835
66085
|
return compilerSuccess({ kind: "torus", majorRadius: plan.majorRadius, minorRadius: plan.minorRadius });
|
|
65836
66086
|
case "extrude": {
|
|
65837
|
-
const
|
|
65838
|
-
if (!
|
|
66087
|
+
const profile2 = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
|
|
66088
|
+
if (!profile2.ok) return compilerFailure(...profile2.diagnostics);
|
|
65839
66089
|
return compilerSuccess(
|
|
65840
66090
|
{
|
|
65841
66091
|
kind: "extrude",
|
|
65842
|
-
profile:
|
|
66092
|
+
profile: profile2.value,
|
|
65843
66093
|
height: plan.height,
|
|
65844
66094
|
scaleTop: plan.scaleTop ? [plan.scaleTop[0], plan.scaleTop[1]] : void 0
|
|
65845
66095
|
},
|
|
65846
|
-
|
|
66096
|
+
profile2.diagnostics
|
|
65847
66097
|
);
|
|
65848
66098
|
}
|
|
65849
66099
|
case "sheetMetal": {
|
|
65850
66100
|
try {
|
|
65851
66101
|
return lowerShapeCompilePlanToCadQueryResultAtPath(lowerSheetMetalBasePlan(plan.model, plan.output), path2);
|
|
65852
|
-
} catch (
|
|
65853
|
-
return compilerFailure(unsupportedSheetMetalDiagnostic(path2,
|
|
66102
|
+
} catch (error2) {
|
|
66103
|
+
return compilerFailure(unsupportedSheetMetalDiagnostic(path2, error2 instanceof Error ? error2.message : String(error2)));
|
|
65854
66104
|
}
|
|
65855
66105
|
}
|
|
65856
66106
|
case "shell": {
|
|
@@ -65872,15 +66122,15 @@ function lowerShapeCompilePlanToCadQueryResultAtPath(plan, path2) {
|
|
|
65872
66122
|
if (plan.segments != null && plan.segments > 0) {
|
|
65873
66123
|
return compilerFailure(segmentedShapeDiagnostic("revolve", path2));
|
|
65874
66124
|
}
|
|
65875
|
-
const
|
|
65876
|
-
if (!
|
|
66125
|
+
const profile2 = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
|
|
66126
|
+
if (!profile2.ok) return compilerFailure(...profile2.diagnostics);
|
|
65877
66127
|
return compilerSuccess(
|
|
65878
66128
|
{
|
|
65879
66129
|
kind: "revolve",
|
|
65880
|
-
profile:
|
|
66130
|
+
profile: profile2.value,
|
|
65881
66131
|
degrees: plan.degrees
|
|
65882
66132
|
},
|
|
65883
|
-
|
|
66133
|
+
profile2.diagnostics
|
|
65884
66134
|
);
|
|
65885
66135
|
}
|
|
65886
66136
|
case "loft": {
|
|
@@ -65910,18 +66160,18 @@ function lowerShapeCompilePlanToCadQueryResultAtPath(plan, path2) {
|
|
|
65910
66160
|
);
|
|
65911
66161
|
}
|
|
65912
66162
|
case "sweep": {
|
|
65913
|
-
const
|
|
65914
|
-
if (!
|
|
66163
|
+
const profile2 = lowerProfileCompilePlanToCadQueryResultAtPath(plan.profile, `${path2}.profile`);
|
|
66164
|
+
if (!profile2.ok) return compilerFailure(...profile2.diagnostics);
|
|
65915
66165
|
return compilerSuccess(
|
|
65916
66166
|
{
|
|
65917
66167
|
kind: "sweep",
|
|
65918
|
-
profile:
|
|
66168
|
+
profile: profile2.value,
|
|
65919
66169
|
path: lowerSweepPathToCadQuery(plan.path),
|
|
65920
66170
|
edgeLength: plan.edgeLength,
|
|
65921
66171
|
boundsPadding: plan.boundsPadding,
|
|
65922
66172
|
up: [plan.up[0], plan.up[1], plan.up[2]]
|
|
65923
66173
|
},
|
|
65924
|
-
|
|
66174
|
+
profile2.diagnostics
|
|
65925
66175
|
);
|
|
65926
66176
|
}
|
|
65927
66177
|
case "variableSweep": {
|
|
@@ -66856,8 +67106,8 @@ var ProductSkinBuilder = class {
|
|
|
66856
67106
|
return { side, u, v };
|
|
66857
67107
|
}
|
|
66858
67108
|
/** Apply a product material preset to the lowered skin. */
|
|
66859
|
-
material(
|
|
66860
|
-
this.materialValue =
|
|
67109
|
+
material(material2) {
|
|
67110
|
+
this.materialValue = material2;
|
|
66861
67111
|
return this;
|
|
66862
67112
|
}
|
|
66863
67113
|
/** Apply a simple color override to the lowered skin. */
|
|
@@ -66926,8 +67176,8 @@ var ProductPanelBuilder = class {
|
|
|
66926
67176
|
return this;
|
|
66927
67177
|
}
|
|
66928
67178
|
/** Use a custom 2D panel profile. */
|
|
66929
|
-
profile(
|
|
66930
|
-
this.profileValue =
|
|
67179
|
+
profile(profile2) {
|
|
67180
|
+
this.profileValue = profile2;
|
|
66931
67181
|
return this;
|
|
66932
67182
|
}
|
|
66933
67183
|
/** Set panel extrusion thickness. */
|
|
@@ -66936,8 +67186,8 @@ var ProductPanelBuilder = class {
|
|
|
66936
67186
|
return this;
|
|
66937
67187
|
}
|
|
66938
67188
|
/** Apply a product material preset to the panel. */
|
|
66939
|
-
material(
|
|
66940
|
-
this.materialValue =
|
|
67189
|
+
material(material2) {
|
|
67190
|
+
this.materialValue = material2;
|
|
66941
67191
|
return this;
|
|
66942
67192
|
}
|
|
66943
67193
|
/** Apply a simple color override to the panel. */
|
|
@@ -66991,8 +67241,8 @@ var ProductSpoutBuilder = class {
|
|
|
66991
67241
|
return this;
|
|
66992
67242
|
}
|
|
66993
67243
|
/** Apply a product material preset to the spout. */
|
|
66994
|
-
material(
|
|
66995
|
-
this.materialValue =
|
|
67244
|
+
material(material2) {
|
|
67245
|
+
this.materialValue = material2;
|
|
66996
67246
|
return this;
|
|
66997
67247
|
}
|
|
66998
67248
|
/** Apply a simple color override to the spout. */
|
|
@@ -67062,18 +67312,18 @@ var ProductHandleBuilder = class {
|
|
|
67062
67312
|
return this;
|
|
67063
67313
|
}
|
|
67064
67314
|
/** Set the grip cross-section profile. */
|
|
67065
|
-
grip(
|
|
67066
|
-
this.gripProfile =
|
|
67315
|
+
grip(profile2) {
|
|
67316
|
+
this.gripProfile = profile2;
|
|
67067
67317
|
return this;
|
|
67068
67318
|
}
|
|
67069
67319
|
/** Apply a product material preset to the grip. */
|
|
67070
|
-
material(
|
|
67071
|
-
this.materialValue =
|
|
67320
|
+
material(material2) {
|
|
67321
|
+
this.materialValue = material2;
|
|
67072
67322
|
return this;
|
|
67073
67323
|
}
|
|
67074
67324
|
/** Apply a product material preset to handle landing pads. */
|
|
67075
|
-
padMaterial(
|
|
67076
|
-
this.padMaterialValue =
|
|
67325
|
+
padMaterial(material2) {
|
|
67326
|
+
this.padMaterialValue = material2;
|
|
67077
67327
|
return this;
|
|
67078
67328
|
}
|
|
67079
67329
|
/** Set the sampled loft target edge length for the grip. */
|
|
@@ -67248,8 +67498,8 @@ var ProductRibbonBuilder = class {
|
|
|
67248
67498
|
return this;
|
|
67249
67499
|
}
|
|
67250
67500
|
/** Apply a product material preset. */
|
|
67251
|
-
material(
|
|
67252
|
-
this.materialValue =
|
|
67501
|
+
material(material2) {
|
|
67502
|
+
this.materialValue = material2;
|
|
67253
67503
|
return this;
|
|
67254
67504
|
}
|
|
67255
67505
|
/** Apply a simple color override. */
|
|
@@ -67563,8 +67813,8 @@ var Product = {
|
|
|
67563
67813
|
return detail.transform(frame.matrix);
|
|
67564
67814
|
},
|
|
67565
67815
|
/** Small blended landing volume for manual structural bridges and connection proofs. */
|
|
67566
|
-
landing(name, radius = 10,
|
|
67567
|
-
return applyMaterial(sphere2(radius, 48).scale([1.25, 0.75, 0.38]).as(name),
|
|
67816
|
+
landing(name, radius = 10, material2) {
|
|
67817
|
+
return applyMaterial(sphere2(radius, 48).scale([1.25, 0.75, 0.38]).as(name), material2);
|
|
67568
67818
|
}
|
|
67569
67819
|
};
|
|
67570
67820
|
|
|
@@ -68260,19 +68510,19 @@ function ease(t, easing) {
|
|
|
68260
68510
|
function validateWidth(width, label) {
|
|
68261
68511
|
return requirePositive6(width, label);
|
|
68262
68512
|
}
|
|
68263
|
-
function widthAt(
|
|
68264
|
-
if (typeof
|
|
68265
|
-
if (typeof
|
|
68266
|
-
if ("from" in
|
|
68267
|
-
return validateWidth(
|
|
68268
|
-
const stations = [...
|
|
68513
|
+
function widthAt(profile2, t) {
|
|
68514
|
+
if (typeof profile2 === "number") return profile2;
|
|
68515
|
+
if (typeof profile2 === "function") return validateWidth(profile2(t), "SurfaceBand width function result");
|
|
68516
|
+
if ("from" in profile2)
|
|
68517
|
+
return validateWidth(profile2.from + (profile2.to - profile2.from) * ease(t, profile2.easing), "SurfaceBand eased width");
|
|
68518
|
+
const stations = [...profile2.stations].sort((a, b) => a.t - b.t);
|
|
68269
68519
|
if (stations.length === 0) throw new Error("SurfaceBand width profile requires at least one station");
|
|
68270
68520
|
if (t <= stations[0].t) return stations[0].width;
|
|
68271
68521
|
for (let index = 0; index < stations.length - 1; index += 1) {
|
|
68272
68522
|
const a = stations[index];
|
|
68273
68523
|
const b = stations[index + 1];
|
|
68274
68524
|
if (t > b.t) continue;
|
|
68275
|
-
const localT = ease((t - a.t) / Math.max(1e-8, b.t - a.t),
|
|
68525
|
+
const localT = ease((t - a.t) / Math.max(1e-8, b.t - a.t), profile2.easing);
|
|
68276
68526
|
return a.width + (b.width - a.width) * localT;
|
|
68277
68527
|
}
|
|
68278
68528
|
return stations[stations.length - 1].width;
|
|
@@ -70287,8 +70537,8 @@ function debugCarrierFootprintPoint(path2, frame, across) {
|
|
|
70287
70537
|
return add9(frame.point, scale10(frame.tangentAcross, across));
|
|
70288
70538
|
}
|
|
70289
70539
|
var SurfaceJoinBuilder = class {
|
|
70290
|
-
constructor(
|
|
70291
|
-
this.body =
|
|
70540
|
+
constructor(body2, join2) {
|
|
70541
|
+
this.body = body2;
|
|
70292
70542
|
this.join = join2;
|
|
70293
70543
|
}
|
|
70294
70544
|
/** Select named anchors on the source and target members before lowering this join. */
|
|
@@ -70308,8 +70558,8 @@ var SurfaceJoinBuilder = class {
|
|
|
70308
70558
|
}
|
|
70309
70559
|
};
|
|
70310
70560
|
var SurfaceMemberBuilder = class {
|
|
70311
|
-
constructor(
|
|
70312
|
-
this.body =
|
|
70561
|
+
constructor(body2, record) {
|
|
70562
|
+
this.body = body2;
|
|
70313
70563
|
this.record = record;
|
|
70314
70564
|
}
|
|
70315
70565
|
plate() {
|
|
@@ -72885,54 +73135,123 @@ function assertFinite(value, label) {
|
|
|
72885
73135
|
throw new Error(`${label} must be finite`);
|
|
72886
73136
|
}
|
|
72887
73137
|
}
|
|
73138
|
+
function metadataNumber(value) {
|
|
73139
|
+
return typeof value === "number" ? value : void 0;
|
|
73140
|
+
}
|
|
73141
|
+
function metadataCollision(value) {
|
|
73142
|
+
return value === "none" || value === "visual" || value === "box" || value === "convex" ? value : void 0;
|
|
73143
|
+
}
|
|
73144
|
+
function colliderCollision(collider2) {
|
|
73145
|
+
if (!collider2) return void 0;
|
|
73146
|
+
if (collider2.mode === "box") return "box";
|
|
73147
|
+
if (collider2.mode === "visual") return "visual";
|
|
73148
|
+
return collider2.mode;
|
|
73149
|
+
}
|
|
73150
|
+
function velocityDriveEffort(drive) {
|
|
73151
|
+
return drive?.kind === "velocity" ? drive.maxTorqueNm : void 0;
|
|
73152
|
+
}
|
|
73153
|
+
function velocityDriveVelocityDegS(drive) {
|
|
73154
|
+
return drive?.kind === "velocity" ? drive.maxSpeedRpm * 6 : void 0;
|
|
73155
|
+
}
|
|
73156
|
+
function driveDamping(drive) {
|
|
73157
|
+
return drive?.damping;
|
|
73158
|
+
}
|
|
73159
|
+
function driveFriction(drive) {
|
|
73160
|
+
return drive?.friction;
|
|
73161
|
+
}
|
|
72888
73162
|
function jointByName(assembly2) {
|
|
72889
73163
|
return new Map(assembly2.joints.map((joint2) => [joint2.name, joint2]));
|
|
72890
73164
|
}
|
|
73165
|
+
function diffDriveFromControllers(controllers) {
|
|
73166
|
+
if (!controllers) return void 0;
|
|
73167
|
+
const diffDrives = controllers.filter((controller) => controller.kind === "diffDrive");
|
|
73168
|
+
if (diffDrives.length > 1) {
|
|
73169
|
+
throw new Error("assembly.withSimulation(...) currently supports one Sim.controller.diffDrive(...) controller");
|
|
73170
|
+
}
|
|
73171
|
+
const diffDrive2 = diffDrives[0];
|
|
73172
|
+
return diffDrive2 ? {
|
|
73173
|
+
leftJoints: [...diffDrive2.leftJoints],
|
|
73174
|
+
rightJoints: [...diffDrive2.rightJoints],
|
|
73175
|
+
wheelSeparationMm: diffDrive2.wheelSeparationMm,
|
|
73176
|
+
wheelRadiusMm: diffDrive2.wheelRadiusMm,
|
|
73177
|
+
topic: diffDrive2.topic,
|
|
73178
|
+
odomTopic: diffDrive2.odomTopic,
|
|
73179
|
+
tfTopic: diffDrive2.tfTopic,
|
|
73180
|
+
frameId: diffDrive2.frameId,
|
|
73181
|
+
odomFrameId: diffDrive2.odomFrameId,
|
|
73182
|
+
maxLinearVelocity: diffDrive2.maxLinearVelocity,
|
|
73183
|
+
maxAngularVelocity: diffDrive2.maxAngularVelocity,
|
|
73184
|
+
linearAcceleration: diffDrive2.linearAcceleration,
|
|
73185
|
+
angularAcceleration: diffDrive2.angularAcceleration
|
|
73186
|
+
} : void 0;
|
|
73187
|
+
}
|
|
72891
73188
|
function resetRobotExport() {
|
|
72892
73189
|
_collectedRobotExport = null;
|
|
72893
73190
|
}
|
|
72894
73191
|
function getCollectedRobotExport() {
|
|
72895
73192
|
return _collectedRobotExport;
|
|
72896
73193
|
}
|
|
72897
|
-
function
|
|
72898
|
-
|
|
72899
|
-
throw new Error("robotExport(...) expects an options object");
|
|
72900
|
-
}
|
|
72901
|
-
if (!options.assembly || typeof options.assembly.describe !== "function") {
|
|
72902
|
-
throw new Error("robotExport(...) requires an assembly");
|
|
72903
|
-
}
|
|
72904
|
-
const assembly2 = options.assembly.describe();
|
|
73194
|
+
function collectSimulationModel(assemblyInput, options = {}) {
|
|
73195
|
+
const assembly2 = typeof assemblyInput.describe === "function" ? assemblyInput.describe() : assemblyInput;
|
|
72905
73196
|
const partNames = new Set(assembly2.parts.map((part) => part.name));
|
|
72906
73197
|
const joints = jointByName(assembly2);
|
|
72907
73198
|
const links = cloneLinkOptions(options.links);
|
|
73199
|
+
for (const part of assembly2.parts) {
|
|
73200
|
+
const fromSim = part.sim;
|
|
73201
|
+
const fromMaterialDensity = fromSim?.material?.densityKgM3;
|
|
73202
|
+
const current = links[part.name] ?? {};
|
|
73203
|
+
const next = {
|
|
73204
|
+
massKg: current.massKg ?? fromSim?.massKg ?? metadataNumber(part.metadata?.massKg),
|
|
73205
|
+
densityKgM3: current.densityKgM3 ?? fromSim?.densityKgM3 ?? fromMaterialDensity ?? metadataNumber(part.metadata?.densityKgM3),
|
|
73206
|
+
collision: current.collision ?? colliderCollision(fromSim?.collider) ?? metadataCollision(part.metadata?.collision)
|
|
73207
|
+
};
|
|
73208
|
+
if (next.massKg !== void 0 || next.densityKgM3 !== void 0 || next.collision !== void 0) {
|
|
73209
|
+
links[part.name] = next;
|
|
73210
|
+
}
|
|
73211
|
+
}
|
|
72908
73212
|
for (const [partName, link] of Object.entries(links)) {
|
|
72909
|
-
if (!partNames.has(partName)) throw new Error(`
|
|
72910
|
-
assertFinite(link.massKg, `
|
|
72911
|
-
assertFinite(link.densityKgM3, `
|
|
73213
|
+
if (!partNames.has(partName)) throw new Error(`simulation model references unknown link "${partName}"`);
|
|
73214
|
+
assertFinite(link.massKg, `simulation model link "${partName}" massKg`);
|
|
73215
|
+
assertFinite(link.densityKgM3, `simulation model link "${partName}" densityKgM3`);
|
|
72912
73216
|
}
|
|
72913
73217
|
const jointOpts = cloneJointOptions(options.joints);
|
|
73218
|
+
for (const joint2 of assembly2.joints) {
|
|
73219
|
+
const drive = joint2.sim?.drive;
|
|
73220
|
+
const current = jointOpts[joint2.name] ?? {};
|
|
73221
|
+
const next = {
|
|
73222
|
+
effort: current.effort ?? velocityDriveEffort(drive) ?? joint2.effort,
|
|
73223
|
+
velocity: current.velocity ?? velocityDriveVelocityDegS(drive) ?? joint2.velocity,
|
|
73224
|
+
damping: current.damping ?? driveDamping(drive) ?? joint2.damping,
|
|
73225
|
+
friction: current.friction ?? driveFriction(drive) ?? joint2.friction
|
|
73226
|
+
};
|
|
73227
|
+
if (next.effort !== void 0 || next.velocity !== void 0 || next.damping !== void 0 || next.friction !== void 0) {
|
|
73228
|
+
jointOpts[joint2.name] = next;
|
|
73229
|
+
}
|
|
73230
|
+
}
|
|
72914
73231
|
for (const [jointName, joint2] of Object.entries(jointOpts)) {
|
|
72915
|
-
if (!joints.has(jointName)) throw new Error(`
|
|
72916
|
-
assertFinite(joint2.effort, `
|
|
72917
|
-
assertFinite(joint2.velocity, `
|
|
72918
|
-
assertFinite(joint2.damping, `
|
|
72919
|
-
assertFinite(joint2.friction, `
|
|
72920
|
-
}
|
|
72921
|
-
const
|
|
72922
|
-
|
|
72923
|
-
|
|
72924
|
-
|
|
72925
|
-
|
|
72926
|
-
|
|
72927
|
-
|
|
72928
|
-
|
|
72929
|
-
|
|
72930
|
-
|
|
72931
|
-
|
|
73232
|
+
if (!joints.has(jointName)) throw new Error(`simulation model references unknown joint "${jointName}"`);
|
|
73233
|
+
assertFinite(joint2.effort, `simulation model joint "${jointName}" effort`);
|
|
73234
|
+
assertFinite(joint2.velocity, `simulation model joint "${jointName}" velocity`);
|
|
73235
|
+
assertFinite(joint2.damping, `simulation model joint "${jointName}" damping`);
|
|
73236
|
+
assertFinite(joint2.friction, `simulation model joint "${jointName}" friction`);
|
|
73237
|
+
}
|
|
73238
|
+
const simulation = cloneSimAssemblySimulation(assembly2.sim) ?? null;
|
|
73239
|
+
const simulationDiffDrive = diffDriveFromControllers(simulation?.controllers);
|
|
73240
|
+
const diffDrive2 = cloneDiffDrive(options.plugins?.diffDrive) ?? simulationDiffDrive;
|
|
73241
|
+
if (diffDrive2) {
|
|
73242
|
+
if (diffDrive2.leftJoints.length === 0 || diffDrive2.rightJoints.length === 0) {
|
|
73243
|
+
throw new Error("simulation model diffDrive requires at least one left joint and one right joint");
|
|
73244
|
+
}
|
|
73245
|
+
assertFinite(diffDrive2.wheelSeparationMm, "simulation model diffDrive wheelSeparationMm");
|
|
73246
|
+
assertFinite(diffDrive2.wheelRadiusMm, "simulation model diffDrive wheelRadiusMm");
|
|
73247
|
+
if (diffDrive2.wheelSeparationMm <= 0 || diffDrive2.wheelRadiusMm <= 0) {
|
|
73248
|
+
throw new Error("simulation model diffDrive wheel separation and radius must be > 0");
|
|
73249
|
+
}
|
|
73250
|
+
[...diffDrive2.leftJoints, ...diffDrive2.rightJoints].forEach((jointName) => {
|
|
72932
73251
|
const joint2 = joints.get(jointName);
|
|
72933
|
-
if (!joint2) throw new Error(`
|
|
73252
|
+
if (!joint2) throw new Error(`simulation model diffDrive references unknown joint "${jointName}"`);
|
|
72934
73253
|
if (joint2.type !== "revolute") {
|
|
72935
|
-
throw new Error(`
|
|
73254
|
+
throw new Error(`simulation model diffDrive joint "${jointName}" must be revolute`);
|
|
72936
73255
|
}
|
|
72937
73256
|
});
|
|
72938
73257
|
}
|
|
@@ -72940,19 +73259,21 @@ function robotExport(options) {
|
|
|
72940
73259
|
if (jointStatePublisher?.joints) {
|
|
72941
73260
|
jointStatePublisher.joints.forEach((jointName) => {
|
|
72942
73261
|
if (!joints.has(jointName)) {
|
|
72943
|
-
throw new Error(`
|
|
73262
|
+
throw new Error(`simulation model jointStatePublisher references unknown joint "${jointName}"`);
|
|
72944
73263
|
}
|
|
72945
73264
|
});
|
|
72946
73265
|
}
|
|
72947
73266
|
const world = cloneWorld(options.world);
|
|
72948
73267
|
if (world?.spawnPose) {
|
|
72949
|
-
world.spawnPose.forEach((value, index) => assertFinite(value, `
|
|
73268
|
+
world.spawnPose.forEach((value, index) => assertFinite(value, `simulation model world spawnPose[${index}]`));
|
|
72950
73269
|
}
|
|
72951
|
-
assertFinite(world?.keyboardTeleop?.linearStep, "
|
|
72952
|
-
assertFinite(world?.keyboardTeleop?.angularStep, "
|
|
72953
|
-
|
|
72954
|
-
modelName: (options.modelName ?? assembly2.name ?? "ForgeCAD
|
|
73270
|
+
assertFinite(world?.keyboardTeleop?.linearStep, "simulation model world keyboardTeleop.linearStep");
|
|
73271
|
+
assertFinite(world?.keyboardTeleop?.angularStep, "simulation model world keyboardTeleop.angularStep");
|
|
73272
|
+
return {
|
|
73273
|
+
modelName: (options.modelName ?? assembly2.name ?? "ForgeCAD Simulation").trim() || "ForgeCAD Simulation",
|
|
72955
73274
|
assembly: assembly2,
|
|
73275
|
+
simulation,
|
|
73276
|
+
source: options.source ?? "assembly",
|
|
72956
73277
|
state: { ...options.state ?? {} },
|
|
72957
73278
|
static: options.static ?? false,
|
|
72958
73279
|
selfCollide: options.selfCollide ?? false,
|
|
@@ -72960,14 +73281,358 @@ function robotExport(options) {
|
|
|
72960
73281
|
links,
|
|
72961
73282
|
joints: jointOpts,
|
|
72962
73283
|
plugins: {
|
|
72963
|
-
diffDrive,
|
|
73284
|
+
diffDrive: diffDrive2,
|
|
72964
73285
|
jointStatePublisher
|
|
72965
73286
|
},
|
|
72966
73287
|
world
|
|
72967
73288
|
};
|
|
73289
|
+
}
|
|
73290
|
+
function robotExport(options) {
|
|
73291
|
+
if (!options || typeof options !== "object") {
|
|
73292
|
+
throw new Error("robotExport(...) expects an options object");
|
|
73293
|
+
}
|
|
73294
|
+
if (!options.assembly || typeof options.assembly.describe !== "function") {
|
|
73295
|
+
throw new Error("robotExport(...) requires an assembly");
|
|
73296
|
+
}
|
|
73297
|
+
_collectedRobotExport = collectSimulationModel(options.assembly, { ...options, source: "robotExport" });
|
|
72968
73298
|
return _collectedRobotExport;
|
|
72969
73299
|
}
|
|
72970
73300
|
|
|
73301
|
+
// src/forge/export/simreadyValidation.ts
|
|
73302
|
+
function error(findings, code, message, path2) {
|
|
73303
|
+
findings.push({ level: "error", code, message, path: path2 });
|
|
73304
|
+
}
|
|
73305
|
+
function warn(findings, code, message, path2) {
|
|
73306
|
+
findings.push({ level: "warning", code, message, path: path2 });
|
|
73307
|
+
}
|
|
73308
|
+
function isFiniteNumber3(value) {
|
|
73309
|
+
return value === void 0 || Number.isFinite(value);
|
|
73310
|
+
}
|
|
73311
|
+
function requireFinite9(findings, value, label, path2) {
|
|
73312
|
+
if (!isFiniteNumber3(value)) error(findings, "SIM.NUMBER.FINITE", `${label} must be finite.`, path2);
|
|
73313
|
+
}
|
|
73314
|
+
function requirePositive7(findings, value, label, path2) {
|
|
73315
|
+
requireFinite9(findings, value, label, path2);
|
|
73316
|
+
if (value !== void 0 && value <= 0) error(findings, "SIM.NUMBER.POSITIVE", `${label} must be > 0.`, path2);
|
|
73317
|
+
}
|
|
73318
|
+
function requireNonNegative(findings, value, label, path2) {
|
|
73319
|
+
requireFinite9(findings, value, label, path2);
|
|
73320
|
+
if (value !== void 0 && value < 0) error(findings, "SIM.NUMBER.NON_NEGATIVE", `${label} must be >= 0.`, path2);
|
|
73321
|
+
}
|
|
73322
|
+
function partConnectorNames(part) {
|
|
73323
|
+
if (part.part instanceof Shape) return new Set(Object.keys(getShapePorts(part.part)));
|
|
73324
|
+
if (part.part instanceof ShapeGroup) return new Set(Object.keys(getShapeGroupPorts(part.part)));
|
|
73325
|
+
return /* @__PURE__ */ new Set();
|
|
73326
|
+
}
|
|
73327
|
+
function profileRequiresExplicitCollider(model) {
|
|
73328
|
+
return model.simulation !== null;
|
|
73329
|
+
}
|
|
73330
|
+
function validatePart(findings, model, part) {
|
|
73331
|
+
const link = model.links[part.name];
|
|
73332
|
+
const hasLegacyPhysicalData = link?.massKg !== void 0 || link?.densityKgM3 !== void 0;
|
|
73333
|
+
const path2 = `parts.${part.name}`;
|
|
73334
|
+
if (!part.sim && !hasLegacyPhysicalData) {
|
|
73335
|
+
error(
|
|
73336
|
+
findings,
|
|
73337
|
+
"SIM.BODY.MISSING",
|
|
73338
|
+
`Part "${part.name}" needs Sim.body(...) metadata or legacy massKg/densityKgM3 export metadata.`,
|
|
73339
|
+
path2
|
|
73340
|
+
);
|
|
73341
|
+
return;
|
|
73342
|
+
}
|
|
73343
|
+
requirePositive7(findings, link?.massKg, `Part "${part.name}" massKg`, `${path2}.massKg`);
|
|
73344
|
+
requirePositive7(findings, link?.densityKgM3, `Part "${part.name}" densityKgM3`, `${path2}.densityKgM3`);
|
|
73345
|
+
if (link?.massKg === void 0 && link?.densityKgM3 === void 0) {
|
|
73346
|
+
error(findings, "SIM.BODY.MASS_MISSING", `Part "${part.name}" needs massKg or densityKgM3.`, path2);
|
|
73347
|
+
}
|
|
73348
|
+
if (profileRequiresExplicitCollider(model) && !part.sim?.collider && !link?.collision) {
|
|
73349
|
+
error(findings, "SIM.COLLIDER.MISSING", `Part "${part.name}" needs an explicit Sim.collider.*(...) selection.`, `${path2}.collider`);
|
|
73350
|
+
}
|
|
73351
|
+
if (part.sim?.collider?.mode === "none" && !part.sim.collider.reason?.trim()) {
|
|
73352
|
+
error(findings, "SIM.COLLIDER.NONE_REASON", `Part "${part.name}" uses Sim.collider.none(...) without a reason.`, `${path2}.collider`);
|
|
73353
|
+
}
|
|
73354
|
+
const material2 = part.sim?.material;
|
|
73355
|
+
if (material2) {
|
|
73356
|
+
requirePositive7(findings, material2.densityKgM3, `Material "${material2.name}" densityKgM3`, `${path2}.material.densityKgM3`);
|
|
73357
|
+
requireNonNegative(findings, material2.staticFriction, `Material "${material2.name}" staticFriction`, `${path2}.material.staticFriction`);
|
|
73358
|
+
requireNonNegative(
|
|
73359
|
+
findings,
|
|
73360
|
+
material2.dynamicFriction,
|
|
73361
|
+
`Material "${material2.name}" dynamicFriction`,
|
|
73362
|
+
`${path2}.material.dynamicFriction`
|
|
73363
|
+
);
|
|
73364
|
+
requireNonNegative(findings, material2.restitution, `Material "${material2.name}" restitution`, `${path2}.material.restitution`);
|
|
73365
|
+
}
|
|
73366
|
+
const connectorNames = partConnectorNames(part);
|
|
73367
|
+
for (const [name, contact2] of Object.entries(part.sim?.contacts ?? {})) {
|
|
73368
|
+
if (!connectorNames.has(contact2.connectorName)) {
|
|
73369
|
+
error(
|
|
73370
|
+
findings,
|
|
73371
|
+
"SIM.CONTACT.CONNECTOR_MISSING",
|
|
73372
|
+
`Contact "${name}" on part "${part.name}" references missing connector "${contact2.connectorName}".`,
|
|
73373
|
+
`${path2}.contacts.${name}`
|
|
73374
|
+
);
|
|
73375
|
+
}
|
|
73376
|
+
}
|
|
73377
|
+
}
|
|
73378
|
+
function validateJoint(findings, model, joint2) {
|
|
73379
|
+
const opts = model.joints[joint2.name];
|
|
73380
|
+
const path2 = `joints.${joint2.name}`;
|
|
73381
|
+
requirePositive7(findings, opts?.effort, `Joint "${joint2.name}" effort`, `${path2}.effort`);
|
|
73382
|
+
requirePositive7(findings, opts?.velocity, `Joint "${joint2.name}" velocity`, `${path2}.velocity`);
|
|
73383
|
+
requireNonNegative(findings, opts?.damping, `Joint "${joint2.name}" damping`, `${path2}.damping`);
|
|
73384
|
+
requireNonNegative(findings, opts?.friction, `Joint "${joint2.name}" friction`, `${path2}.friction`);
|
|
73385
|
+
const drive = joint2.sim?.drive;
|
|
73386
|
+
if (drive?.kind === "velocity") {
|
|
73387
|
+
requirePositive7(findings, drive.maxTorqueNm, `Joint "${joint2.name}" velocity drive maxTorqueNm`, `${path2}.drive.maxTorqueNm`);
|
|
73388
|
+
requirePositive7(findings, drive.maxSpeedRpm, `Joint "${joint2.name}" velocity drive maxSpeedRpm`, `${path2}.drive.maxSpeedRpm`);
|
|
73389
|
+
}
|
|
73390
|
+
if (drive) {
|
|
73391
|
+
requireNonNegative(findings, drive.damping, `Joint "${joint2.name}" drive damping`, `${path2}.drive.damping`);
|
|
73392
|
+
requireNonNegative(findings, drive.friction, `Joint "${joint2.name}" drive friction`, `${path2}.drive.friction`);
|
|
73393
|
+
}
|
|
73394
|
+
}
|
|
73395
|
+
function validateDiffDriveController(findings, model) {
|
|
73396
|
+
const diffDrive2 = model.plugins.diffDrive;
|
|
73397
|
+
if (!diffDrive2) return;
|
|
73398
|
+
const jointsByName = new Map(model.assembly.joints.map((joint2) => [joint2.name, joint2]));
|
|
73399
|
+
if (diffDrive2.leftJoints.length === 0 || diffDrive2.rightJoints.length === 0) {
|
|
73400
|
+
error(
|
|
73401
|
+
findings,
|
|
73402
|
+
"SIM.CONTROLLER.DIFF_DRIVE_EMPTY",
|
|
73403
|
+
"Diff-drive controller needs at least one left and one right joint.",
|
|
73404
|
+
"controllers.diffDrive"
|
|
73405
|
+
);
|
|
73406
|
+
}
|
|
73407
|
+
requirePositive7(findings, diffDrive2.wheelRadiusMm, "Diff-drive wheelRadiusMm", "controllers.diffDrive.wheelRadiusMm");
|
|
73408
|
+
requirePositive7(findings, diffDrive2.wheelSeparationMm, "Diff-drive wheelSeparationMm", "controllers.diffDrive.wheelSeparationMm");
|
|
73409
|
+
for (const jointName of [...diffDrive2.leftJoints, ...diffDrive2.rightJoints]) {
|
|
73410
|
+
const joint2 = jointsByName.get(jointName);
|
|
73411
|
+
if (!joint2) {
|
|
73412
|
+
error(
|
|
73413
|
+
findings,
|
|
73414
|
+
"SIM.CONTROLLER.UNKNOWN_JOINT",
|
|
73415
|
+
`Diff-drive controller references unknown joint "${jointName}".`,
|
|
73416
|
+
"controllers.diffDrive"
|
|
73417
|
+
);
|
|
73418
|
+
} else if (joint2.type !== "revolute") {
|
|
73419
|
+
error(
|
|
73420
|
+
findings,
|
|
73421
|
+
"SIM.CONTROLLER.JOINT_TYPE",
|
|
73422
|
+
`Diff-drive joint "${jointName}" must be revolute, got ${joint2.type}.`,
|
|
73423
|
+
`joints.${jointName}`
|
|
73424
|
+
);
|
|
73425
|
+
}
|
|
73426
|
+
}
|
|
73427
|
+
}
|
|
73428
|
+
function validateRobotGraph(findings, model) {
|
|
73429
|
+
if (!simProfileRequiresRootPart(model.simulation?.profile)) return;
|
|
73430
|
+
const rootPart = model.simulation?.rootPart;
|
|
73431
|
+
if (!rootPart) return;
|
|
73432
|
+
const partNames = new Set(model.assembly.parts.map((part) => part.name));
|
|
73433
|
+
if (!partNames.has(rootPart)) {
|
|
73434
|
+
error(findings, "SIM.ROBOT.ROOT_PART", `Robot rootPart "${rootPart}" does not exist.`, "sim.rootPart");
|
|
73435
|
+
return;
|
|
73436
|
+
}
|
|
73437
|
+
const graph = /* @__PURE__ */ new Map();
|
|
73438
|
+
for (const part of partNames) graph.set(part, /* @__PURE__ */ new Set());
|
|
73439
|
+
for (const joint2 of model.assembly.joints) {
|
|
73440
|
+
graph.get(joint2.parent)?.add(joint2.child);
|
|
73441
|
+
graph.get(joint2.child)?.add(joint2.parent);
|
|
73442
|
+
}
|
|
73443
|
+
const visited = /* @__PURE__ */ new Set();
|
|
73444
|
+
const queue = [rootPart];
|
|
73445
|
+
while (queue.length > 0) {
|
|
73446
|
+
const current = queue.shift();
|
|
73447
|
+
if (visited.has(current)) continue;
|
|
73448
|
+
visited.add(current);
|
|
73449
|
+
for (const next of graph.get(current) ?? []) {
|
|
73450
|
+
if (!visited.has(next)) queue.push(next);
|
|
73451
|
+
}
|
|
73452
|
+
}
|
|
73453
|
+
const disconnected = [...partNames].filter((partName) => !visited.has(partName));
|
|
73454
|
+
if (disconnected.length > 0) {
|
|
73455
|
+
error(
|
|
73456
|
+
findings,
|
|
73457
|
+
"SIM.ROBOT.DISCONNECTED_GRAPH",
|
|
73458
|
+
`Robot joint graph is disconnected from rootPart "${rootPart}": ${disconnected.join(", ")}.`,
|
|
73459
|
+
"sim.graph"
|
|
73460
|
+
);
|
|
73461
|
+
}
|
|
73462
|
+
}
|
|
73463
|
+
function validateWorld(findings, model) {
|
|
73464
|
+
const world = model.world;
|
|
73465
|
+
if (!world) return;
|
|
73466
|
+
world.spawnPose?.forEach((value, index) => requireFinite9(findings, value, `world spawnPose[${index}]`, `world.spawnPose.${index}`));
|
|
73467
|
+
requirePositive7(findings, world.keyboardTeleop?.linearStep, "world keyboardTeleop.linearStep", "world.keyboardTeleop.linearStep");
|
|
73468
|
+
requirePositive7(findings, world.keyboardTeleop?.angularStep, "world keyboardTeleop.angularStep", "world.keyboardTeleop.angularStep");
|
|
73469
|
+
}
|
|
73470
|
+
function validateSimulationModel(model) {
|
|
73471
|
+
const findings = [];
|
|
73472
|
+
const simulation = model.simulation;
|
|
73473
|
+
if (!simulation && model.source !== "robotExport") {
|
|
73474
|
+
error(findings, "SIM.SIMULATION.MISSING", "Model must return assembly(...).withSimulation(...).", "sim");
|
|
73475
|
+
}
|
|
73476
|
+
if (!simulation && model.source === "robotExport") {
|
|
73477
|
+
warn(
|
|
73478
|
+
findings,
|
|
73479
|
+
"SIM.SIMULATION.LEGACY_ROBOT_EXPORT",
|
|
73480
|
+
"Legacy robotExport(...) metadata is accepted for compatibility; prefer assembly.withSimulation(...).",
|
|
73481
|
+
"sim"
|
|
73482
|
+
);
|
|
73483
|
+
}
|
|
73484
|
+
if (simulation && !simulation.profile) {
|
|
73485
|
+
error(findings, "SIM.PROFILE.MISSING", "Simulation contract needs a Sim.profile.*() value.", "sim.profile");
|
|
73486
|
+
}
|
|
73487
|
+
if (simProfileRequiresRootPart(simulation?.profile) && !simulation?.rootPart) {
|
|
73488
|
+
error(findings, "SIM.ROBOT.ROOT_PART_MISSING", "Robot-body simulation profiles require rootPart.", "sim.rootPart");
|
|
73489
|
+
}
|
|
73490
|
+
if (simulation?.controllers && simulation.controllers.length > 0 && !simProfileAllowsControllers(simulation.profile)) {
|
|
73491
|
+
error(
|
|
73492
|
+
findings,
|
|
73493
|
+
"SIM.CONTROLLER.PROFILE_UNSUPPORTED",
|
|
73494
|
+
"Controllers are only supported for robot-body simulation profiles.",
|
|
73495
|
+
"sim.controllers"
|
|
73496
|
+
);
|
|
73497
|
+
}
|
|
73498
|
+
model.assembly.parts.forEach((part) => validatePart(findings, model, part));
|
|
73499
|
+
model.assembly.joints.forEach((joint2) => validateJoint(findings, model, joint2));
|
|
73500
|
+
validateDiffDriveController(findings, model);
|
|
73501
|
+
validateRobotGraph(findings, model);
|
|
73502
|
+
validateWorld(findings, model);
|
|
73503
|
+
const errors = findings.filter((finding) => finding.level === "error");
|
|
73504
|
+
const warnings = findings.filter((finding) => finding.level === "warning");
|
|
73505
|
+
return {
|
|
73506
|
+
ok: errors.length === 0,
|
|
73507
|
+
modelName: model.modelName,
|
|
73508
|
+
profile: simulation?.profile.name ?? null,
|
|
73509
|
+
contract: simulation?.kind ?? null,
|
|
73510
|
+
errors,
|
|
73511
|
+
warnings
|
|
73512
|
+
};
|
|
73513
|
+
}
|
|
73514
|
+
|
|
73515
|
+
// src/forge/export/simreadyManifest.ts
|
|
73516
|
+
function materialManifest(material2) {
|
|
73517
|
+
if (!material2) return void 0;
|
|
73518
|
+
return {
|
|
73519
|
+
name: material2.name,
|
|
73520
|
+
densityKgM3: material2.densityKgM3,
|
|
73521
|
+
staticFriction: material2.staticFriction,
|
|
73522
|
+
dynamicFriction: material2.dynamicFriction,
|
|
73523
|
+
restitution: material2.restitution
|
|
73524
|
+
};
|
|
73525
|
+
}
|
|
73526
|
+
function bodyPhysicalSource(sim, link) {
|
|
73527
|
+
if (sim) return "sim";
|
|
73528
|
+
if (link?.massKg !== void 0 || link?.densityKgM3 !== void 0 || link?.collision !== void 0) return "legacy-export";
|
|
73529
|
+
return "missing";
|
|
73530
|
+
}
|
|
73531
|
+
function bodyManifest(model) {
|
|
73532
|
+
return model.assembly.parts.map((part) => {
|
|
73533
|
+
const link = model.links[part.name];
|
|
73534
|
+
const contacts = Object.entries(part.sim?.contacts ?? {}).map(([name, contact2]) => ({
|
|
73535
|
+
name,
|
|
73536
|
+
kind: contact2.kind,
|
|
73537
|
+
connectorName: contact2.connectorName
|
|
73538
|
+
}));
|
|
73539
|
+
return {
|
|
73540
|
+
name: part.name,
|
|
73541
|
+
physicalSource: bodyPhysicalSource(part.sim, link),
|
|
73542
|
+
massKg: link?.massKg,
|
|
73543
|
+
densityKgM3: link?.densityKgM3,
|
|
73544
|
+
material: materialManifest(part.sim?.material),
|
|
73545
|
+
collider: part.sim?.collider ? { mode: part.sim.collider.mode, reason: part.sim.collider.reason } : void 0,
|
|
73546
|
+
contacts
|
|
73547
|
+
};
|
|
73548
|
+
});
|
|
73549
|
+
}
|
|
73550
|
+
function jointManifest(model) {
|
|
73551
|
+
return model.assembly.joints.map((joint2) => {
|
|
73552
|
+
const exportDynamics = model.joints[joint2.name];
|
|
73553
|
+
return {
|
|
73554
|
+
name: joint2.name,
|
|
73555
|
+
type: joint2.type,
|
|
73556
|
+
parent: joint2.parent,
|
|
73557
|
+
child: joint2.child,
|
|
73558
|
+
limits: joint2.min !== void 0 || joint2.max !== void 0 ? {
|
|
73559
|
+
min: joint2.min,
|
|
73560
|
+
max: joint2.max,
|
|
73561
|
+
unit: joint2.unit
|
|
73562
|
+
} : void 0,
|
|
73563
|
+
drive: joint2.sim?.drive,
|
|
73564
|
+
exportDynamics
|
|
73565
|
+
};
|
|
73566
|
+
});
|
|
73567
|
+
}
|
|
73568
|
+
function diffDriveManifest(diffDrive2) {
|
|
73569
|
+
if (!diffDrive2) return [];
|
|
73570
|
+
return [
|
|
73571
|
+
{
|
|
73572
|
+
kind: "diffDrive",
|
|
73573
|
+
leftJoints: [...diffDrive2.leftJoints],
|
|
73574
|
+
rightJoints: [...diffDrive2.rightJoints],
|
|
73575
|
+
wheelSeparationMm: diffDrive2.wheelSeparationMm,
|
|
73576
|
+
wheelRadiusMm: diffDrive2.wheelRadiusMm
|
|
73577
|
+
}
|
|
73578
|
+
];
|
|
73579
|
+
}
|
|
73580
|
+
function externalStages(model) {
|
|
73581
|
+
const stages = ["convert-to-usd", "validate-usd-minimum"];
|
|
73582
|
+
if (model.simulation) stages.push("simready-conform-profile");
|
|
73583
|
+
stages.push("omni-asset-validate", "omni-asset-validate-geometry", "omni-asset-validate-physics", "simready-validate");
|
|
73584
|
+
return stages;
|
|
73585
|
+
}
|
|
73586
|
+
function buildSimReadyManifest(model) {
|
|
73587
|
+
const validation = validateSimulationModel(model);
|
|
73588
|
+
const bodies = bodyManifest(model);
|
|
73589
|
+
const joints = jointManifest(model);
|
|
73590
|
+
const controllers = diffDriveManifest(model.plugins.diffDrive);
|
|
73591
|
+
const contactSurfaceCount = bodies.reduce((sum, body2) => sum + body2.contacts.length, 0);
|
|
73592
|
+
const profile2 = model.simulation?.profile;
|
|
73593
|
+
return {
|
|
73594
|
+
format: "forgecad-simready-manifest",
|
|
73595
|
+
version: 1,
|
|
73596
|
+
modelName: model.modelName,
|
|
73597
|
+
sourceModelName: model.assembly.name,
|
|
73598
|
+
source: model.source,
|
|
73599
|
+
profile: profile2?.name ?? null,
|
|
73600
|
+
profileVersion: "1.0.0",
|
|
73601
|
+
units: {
|
|
73602
|
+
sourceLength: "millimeter",
|
|
73603
|
+
exportLength: "meter",
|
|
73604
|
+
sourceToMeters: 1e-3
|
|
73605
|
+
},
|
|
73606
|
+
contract: model.simulation ? { kind: model.simulation.kind, rootPart: model.simulation.rootPart } : null,
|
|
73607
|
+
capabilities: {
|
|
73608
|
+
bodyCount: bodies.length,
|
|
73609
|
+
jointCount: joints.length,
|
|
73610
|
+
contactSurfaceCount,
|
|
73611
|
+
drivenJointCount: joints.filter((joint2) => joint2.drive || joint2.exportDynamics).length,
|
|
73612
|
+
controllerCount: controllers.length,
|
|
73613
|
+
hasRobotProfile: simProfileRequiresRootPart(profile2),
|
|
73614
|
+
hasAssetProfile: profile2?.name === "Prop-Robotics-Physx"
|
|
73615
|
+
},
|
|
73616
|
+
bodies,
|
|
73617
|
+
joints,
|
|
73618
|
+
controllers,
|
|
73619
|
+
validation: {
|
|
73620
|
+
ok: validation.ok,
|
|
73621
|
+
errors: validation.errors,
|
|
73622
|
+
warnings: validation.warnings
|
|
73623
|
+
},
|
|
73624
|
+
externalHandoff: {
|
|
73625
|
+
usdAuthoring: "not-included",
|
|
73626
|
+
nvidiaValidation: "optional-external",
|
|
73627
|
+
suggestedStages: externalStages(model)
|
|
73628
|
+
}
|
|
73629
|
+
};
|
|
73630
|
+
}
|
|
73631
|
+
function simReadyManifestJson(manifest) {
|
|
73632
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
73633
|
+
`;
|
|
73634
|
+
}
|
|
73635
|
+
|
|
72971
73636
|
// src/forge/sketch/entities.ts
|
|
72972
73637
|
var Point2D = class _Point2D {
|
|
72973
73638
|
constructor(x, y) {
|
|
@@ -73516,7 +74181,7 @@ function requireVec34(v, label) {
|
|
|
73516
74181
|
}
|
|
73517
74182
|
return v;
|
|
73518
74183
|
}
|
|
73519
|
-
function
|
|
74184
|
+
function requireFiniteNumber3(n, label) {
|
|
73520
74185
|
if (typeof n !== "number" || !Number.isFinite(n)) {
|
|
73521
74186
|
throw new Error(`${label} must be a finite number, got ${JSON.stringify(n)}`);
|
|
73522
74187
|
}
|
|
@@ -73535,7 +74200,7 @@ function midpoint4(a, b) {
|
|
|
73535
74200
|
function lerp4(a, b, t) {
|
|
73536
74201
|
requireVec34(a, "a");
|
|
73537
74202
|
requireVec34(b, "b");
|
|
73538
|
-
|
|
74203
|
+
requireFiniteNumber3(t, "t");
|
|
73539
74204
|
return [a[0] + (b[0] - a[0]) * t, a[1] + (b[1] - a[1]) * t, a[2] + (b[2] - a[2]) * t];
|
|
73540
74205
|
}
|
|
73541
74206
|
function direction(a, b) {
|
|
@@ -73553,7 +74218,7 @@ function direction(a, b) {
|
|
|
73553
74218
|
function offset(point2, dir, amount) {
|
|
73554
74219
|
requireVec34(point2, "point");
|
|
73555
74220
|
requireVec34(dir, "dir");
|
|
73556
|
-
|
|
74221
|
+
requireFiniteNumber3(amount, "amount");
|
|
73557
74222
|
return [point2[0] + dir[0] * amount, point2[1] + dir[1] * amount, point2[2] + dir[2] * amount];
|
|
73558
74223
|
}
|
|
73559
74224
|
var Points2 = {
|
|
@@ -73661,14 +74326,14 @@ var WoodBoard = class _WoodBoard {
|
|
|
73661
74326
|
};
|
|
73662
74327
|
|
|
73663
74328
|
// src/forge/wood/joints.ts
|
|
73664
|
-
function
|
|
74329
|
+
function requireFinite10(value, name) {
|
|
73665
74330
|
if (!Number.isFinite(value)) {
|
|
73666
74331
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
73667
74332
|
}
|
|
73668
74333
|
return value;
|
|
73669
74334
|
}
|
|
73670
|
-
function
|
|
73671
|
-
|
|
74335
|
+
function requirePositive8(value, name) {
|
|
74336
|
+
requireFinite10(value, name);
|
|
73672
74337
|
if (value <= 0) {
|
|
73673
74338
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
73674
74339
|
}
|
|
@@ -73694,7 +74359,7 @@ function dado(host, guest, opts) {
|
|
|
73694
74359
|
const fit = opts.fit ?? "snug";
|
|
73695
74360
|
const clearance = clearanceForFit(fit);
|
|
73696
74361
|
const channelWidth = guest.thickness + clearance;
|
|
73697
|
-
const channelDepth = opts.depth != null ?
|
|
74362
|
+
const channelDepth = opts.depth != null ? requirePositive8(opts.depth, "depth") : host.thickness / 3;
|
|
73698
74363
|
if (opts.fromBottom != null && opts.fromTop != null) {
|
|
73699
74364
|
throw new Error("dado: specify fromBottom or fromTop, not both");
|
|
73700
74365
|
}
|
|
@@ -73703,15 +74368,15 @@ function dado(host, guest, opts) {
|
|
|
73703
74368
|
}
|
|
73704
74369
|
let fromBottom;
|
|
73705
74370
|
if (opts.fromBottom != null) {
|
|
73706
|
-
fromBottom =
|
|
74371
|
+
fromBottom = requireFinite10(opts.fromBottom, "fromBottom");
|
|
73707
74372
|
} else {
|
|
73708
74373
|
fromBottom = host.height - opts.fromTop - channelWidth;
|
|
73709
|
-
|
|
74374
|
+
requireFinite10(fromBottom, "computed fromBottom");
|
|
73710
74375
|
}
|
|
73711
74376
|
let dadoLength = host.width;
|
|
73712
74377
|
let xOffset = 0;
|
|
73713
74378
|
if (opts.stopped != null) {
|
|
73714
|
-
const stoppedDist =
|
|
74379
|
+
const stoppedDist = requirePositive8(opts.stopped, "stopped");
|
|
73715
74380
|
dadoLength = host.width - stoppedDist;
|
|
73716
74381
|
xOffset = -stoppedDist / 2;
|
|
73717
74382
|
}
|
|
@@ -73723,8 +74388,8 @@ function dado(host, guest, opts) {
|
|
|
73723
74388
|
function rabbet(board, opts) {
|
|
73724
74389
|
if (!board) throw new Error("rabbet: board is required");
|
|
73725
74390
|
if (!opts) throw new Error("rabbet: options are required");
|
|
73726
|
-
const width =
|
|
73727
|
-
const depth =
|
|
74391
|
+
const width = requirePositive8(opts.width, "width");
|
|
74392
|
+
const depth = requirePositive8(opts.depth, "depth");
|
|
73728
74393
|
const edge = opts.edge;
|
|
73729
74394
|
const bw = board.width;
|
|
73730
74395
|
const bh = board.height;
|
|
@@ -73764,10 +74429,10 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
73764
74429
|
const style = o.style ?? "blind";
|
|
73765
74430
|
const fit = o.fit ?? "snug";
|
|
73766
74431
|
const clearance = clearanceForFit(fit);
|
|
73767
|
-
const cornerRadius = o.cornerRadius != null ?
|
|
73768
|
-
const tenonThickness = o.tenonThickness != null ?
|
|
73769
|
-
const tenonWidth = o.tenonWidth != null ?
|
|
73770
|
-
const tenonLength = o.tenonLength != null ?
|
|
74432
|
+
const cornerRadius = o.cornerRadius != null ? requireFinite10(o.cornerRadius, "cornerRadius") : 0;
|
|
74433
|
+
const tenonThickness = o.tenonThickness != null ? requirePositive8(o.tenonThickness, "tenonThickness") : tenonBoard.thickness / 3;
|
|
74434
|
+
const tenonWidth = o.tenonWidth != null ? requirePositive8(o.tenonWidth, "tenonWidth") : Math.min(tenonBoard.height * 0.6, mortiseBoard.height * 0.8);
|
|
74435
|
+
const tenonLength = o.tenonLength != null ? requirePositive8(o.tenonLength, "tenonLength") : style === "through" ? mortiseBoard.thickness : mortiseBoard.thickness * 2 / 3;
|
|
73771
74436
|
const mortiseW = tenonThickness + clearance;
|
|
73772
74437
|
const mortiseH = tenonWidth + clearance;
|
|
73773
74438
|
const mortiseDepth = style === "through" ? mortiseBoard.thickness + 1 : tenonLength;
|
|
@@ -73777,10 +74442,10 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
73777
74442
|
throw new Error("mortiseAndTenon: specify position.fromTop or position.fromBottom, not both");
|
|
73778
74443
|
}
|
|
73779
74444
|
if (o.position.fromTop != null) {
|
|
73780
|
-
|
|
74445
|
+
requireFinite10(o.position.fromTop, "position.fromTop");
|
|
73781
74446
|
mortiseCenterY = mortiseBoard.height / 2 - o.position.fromTop - mortiseH / 2;
|
|
73782
74447
|
} else if (o.position.fromBottom != null) {
|
|
73783
|
-
|
|
74448
|
+
requireFinite10(o.position.fromBottom, "position.fromBottom");
|
|
73784
74449
|
mortiseCenterY = -mortiseBoard.height / 2 + o.position.fromBottom + mortiseH / 2;
|
|
73785
74450
|
}
|
|
73786
74451
|
}
|
|
@@ -73788,8 +74453,8 @@ function mortiseAndTenon(mortiseBoard, tenonBoard, opts) {
|
|
|
73788
74453
|
const mortiseZBase = style === "through" ? -0.5 : mortiseBoard.thickness - mortiseDepth;
|
|
73789
74454
|
if (cornerRadius > 0) {
|
|
73790
74455
|
const r = Math.min(cornerRadius, mortiseW / 2, mortiseH / 2);
|
|
73791
|
-
const
|
|
73792
|
-
mortiseCutter =
|
|
74456
|
+
const profile2 = roundedRect(mortiseW, mortiseH, r);
|
|
74457
|
+
mortiseCutter = profile2.extrude(mortiseDepth).translate(0, mortiseCenterY, mortiseZBase);
|
|
73793
74458
|
} else {
|
|
73794
74459
|
mortiseCutter = box2(mortiseW, mortiseH, mortiseDepth).translate(0, mortiseCenterY, mortiseZBase);
|
|
73795
74460
|
}
|
|
@@ -73881,19 +74546,19 @@ function isOrbitKeyframe(kf) {
|
|
|
73881
74546
|
function isCartesianKeyframe(kf) {
|
|
73882
74547
|
return "position" in kf;
|
|
73883
74548
|
}
|
|
73884
|
-
function
|
|
74549
|
+
function requireFinite11(value, label) {
|
|
73885
74550
|
if (!Number.isFinite(value)) {
|
|
73886
74551
|
throw new Error(`cameraTrajectory(): ${label} must be a finite number, got ${value}`);
|
|
73887
74552
|
}
|
|
73888
74553
|
}
|
|
73889
74554
|
function validateOrbitKeyframe(kf, index) {
|
|
73890
|
-
|
|
73891
|
-
|
|
73892
|
-
|
|
73893
|
-
|
|
74555
|
+
requireFinite11(kf.at, `keyframes[${index}].at`);
|
|
74556
|
+
requireFinite11(kf.orbit.angle, `keyframes[${index}].orbit.angle`);
|
|
74557
|
+
requireFinite11(kf.orbit.pitch, `keyframes[${index}].orbit.pitch`);
|
|
74558
|
+
requireFinite11(kf.orbit.distance, `keyframes[${index}].orbit.distance`);
|
|
73894
74559
|
}
|
|
73895
74560
|
function validateCartesianKeyframe(kf, index) {
|
|
73896
|
-
|
|
74561
|
+
requireFinite11(kf.at, `keyframes[${index}].at`);
|
|
73897
74562
|
if (!Array.isArray(kf.position) || kf.position.length !== 3) {
|
|
73898
74563
|
throw new Error(`cameraTrajectory(): keyframes[${index}].position must be a 3-element array`);
|
|
73899
74564
|
}
|
|
@@ -73901,8 +74566,8 @@ function validateCartesianKeyframe(kf, index) {
|
|
|
73901
74566
|
throw new Error(`cameraTrajectory(): keyframes[${index}].target must be a 3-element array`);
|
|
73902
74567
|
}
|
|
73903
74568
|
for (let i = 0; i < 3; i++) {
|
|
73904
|
-
|
|
73905
|
-
|
|
74569
|
+
requireFinite11(kf.position[i], `keyframes[${index}].position[${i}]`);
|
|
74570
|
+
requireFinite11(kf.target[i], `keyframes[${index}].target[${i}]`);
|
|
73906
74571
|
}
|
|
73907
74572
|
}
|
|
73908
74573
|
function validateKeyframeOrder(keyframes) {
|
|
@@ -74157,21 +74822,28 @@ function consumeBroadEdgeFeatureBudget(edgeCount) {
|
|
|
74157
74822
|
broadEdgeFeatureBudget = Math.max(0, remaining - edgeCount);
|
|
74158
74823
|
return true;
|
|
74159
74824
|
}
|
|
74825
|
+
var BROAD_EDGE_FEATURE_REMEDY = "Pass an explicit edge selector (e.g. selectEdges(shape) or { convex: true }), use high quality/export, or raise the budget with FORGECAD_BROAD_EDGE_FEATURE_BUDGET / FORGECAD_ALLOW_BROAD_EDGE_FEATURES=1.";
|
|
74826
|
+
function skipOrRejectBroadEdgeFeature(operation, reason) {
|
|
74827
|
+
if (getForgeQualityPreset() === "live") {
|
|
74828
|
+
emitRuntimeWarning(
|
|
74829
|
+
`${operation}() without an edge selector ${reason} Skipped this edge finish for live-preview responsiveness. ${BROAD_EDGE_FEATURE_REMEDY}`
|
|
74830
|
+
);
|
|
74831
|
+
return true;
|
|
74832
|
+
}
|
|
74833
|
+
throw new Error(`${operation}() without an edge selector ${reason} ${BROAD_EDGE_FEATURE_REMEDY}`);
|
|
74834
|
+
}
|
|
74160
74835
|
function shouldSkipBroadEdgeFeature(operation, edgeCount) {
|
|
74161
74836
|
if (consumeBroadEdgeFeatureBudget(edgeCount)) return false;
|
|
74162
74837
|
const remaining = Math.max(0, remainingBroadEdgeFeatureBudget());
|
|
74163
|
-
|
|
74164
|
-
|
|
74838
|
+
return skipOrRejectBroadEdgeFeature(
|
|
74839
|
+
operation,
|
|
74840
|
+
`matched ${edgeCount} edge(s), exceeding the remaining broad edge-feature budget (${remaining}).`
|
|
74165
74841
|
);
|
|
74166
|
-
return true;
|
|
74167
74842
|
}
|
|
74168
74843
|
function shouldSkipExhaustedBroadEdgeFeature(operation) {
|
|
74169
74844
|
const remaining = remainingBroadEdgeFeatureBudget();
|
|
74170
74845
|
if (!Number.isFinite(remaining) || remaining > 0) return false;
|
|
74171
|
-
|
|
74172
|
-
`${operation}() without an edge selector was skipped because the broad edge-feature budget is exhausted. Skipped this cosmetic edge finish for responsiveness. Pass an explicit edge selector, use high quality/export, or set FORGECAD_BROAD_EDGE_FEATURE_BUDGET to opt into more broad edge finishing.`
|
|
74173
|
-
);
|
|
74174
|
-
return true;
|
|
74846
|
+
return skipOrRejectBroadEdgeFeature(operation, "cannot run because the broad edge-feature budget is exhausted.");
|
|
74175
74847
|
}
|
|
74176
74848
|
function estimateSelectorlessEdgeCount(plan) {
|
|
74177
74849
|
if (!plan) return null;
|
|
@@ -74190,10 +74862,10 @@ function shouldSkipUnestimatedBroadEdgeFeature(operation, target) {
|
|
|
74190
74862
|
if (!Number.isFinite(remaining)) return false;
|
|
74191
74863
|
const estimatedEdges = estimateSelectorlessEdgeCount(getShapeCompilePlan(target));
|
|
74192
74864
|
if (estimatedEdges !== null && estimatedEdges <= remaining) return false;
|
|
74193
|
-
|
|
74194
|
-
|
|
74865
|
+
return skipOrRejectBroadEdgeFeature(
|
|
74866
|
+
operation,
|
|
74867
|
+
`targets a shape too complex for the remaining broad edge-feature budget (${Math.max(0, remaining)}).`
|
|
74195
74868
|
);
|
|
74196
|
-
return true;
|
|
74197
74869
|
}
|
|
74198
74870
|
function edgesToTargets(edges) {
|
|
74199
74871
|
return edges.map((e) => {
|
|
@@ -74439,7 +75111,7 @@ function applyMatrixToPlacement(placement, matrix) {
|
|
|
74439
75111
|
function applyShapeTransformToPlacement(placement, step) {
|
|
74440
75112
|
return applyMatrixToPlacement(placement, shapeTransformStepMatrix2(step));
|
|
74441
75113
|
}
|
|
74442
|
-
function mapProfileToPlane(
|
|
75114
|
+
function mapProfileToPlane(profile2, sourcePlacement, targetPlane) {
|
|
74443
75115
|
const sourceTransform = Transform.from(sourcePlacement.matrix);
|
|
74444
75116
|
const sourceOrigin = sourceTransform.point([0, 0, 0]);
|
|
74445
75117
|
const sourceU = sourceTransform.vector([1, 0, 0]);
|
|
@@ -74470,7 +75142,7 @@ function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
|
|
|
74470
75142
|
if (!nearlyEqual(Math.abs(det), 1, 1e-5)) {
|
|
74471
75143
|
return { reason: "projection replay requires a rigid or mirrored in-plane basis." };
|
|
74472
75144
|
}
|
|
74473
|
-
let next = cloneProfileCompilePlan(
|
|
75145
|
+
let next = cloneProfileCompilePlan(profile2);
|
|
74474
75146
|
if (!nearlyEqual(sx, 1) || !nearlyEqual(sy, 1)) {
|
|
74475
75147
|
next = appendProfileCompileTransform(next, { kind: "scale", x: sx, y: sy });
|
|
74476
75148
|
}
|
|
@@ -74494,8 +75166,8 @@ function mapProfileToPlane(profile, sourcePlacement, targetPlane) {
|
|
|
74494
75166
|
}
|
|
74495
75167
|
return { profile: next };
|
|
74496
75168
|
}
|
|
74497
|
-
function buildReplayProfile(
|
|
74498
|
-
return mapProfileToPlane(
|
|
75169
|
+
function buildReplayProfile(profile2, sourcePlacement, targetPlane) {
|
|
75170
|
+
return mapProfileToPlane(profile2, sourcePlacement, targetPlane);
|
|
74499
75171
|
}
|
|
74500
75172
|
function circleProjectionProfile(radius, segments) {
|
|
74501
75173
|
return {
|
|
@@ -74550,14 +75222,14 @@ function targetPlanePlacement(targetPlane) {
|
|
|
74550
75222
|
};
|
|
74551
75223
|
}
|
|
74552
75224
|
function sphereProjectionProfileAtCenter(radius, segments, center, targetPlane) {
|
|
74553
|
-
const
|
|
75225
|
+
const profile2 = circleProjectionProfile(Math.abs(radius), segments);
|
|
74554
75226
|
const delta = sub11(center, targetPlane.origin);
|
|
74555
75227
|
const tx = dot11(delta, targetPlane.u);
|
|
74556
75228
|
const ty = dot11(delta, targetPlane.v);
|
|
74557
75229
|
if (!nearlyEqual(tx, 0) || !nearlyEqual(ty, 0)) {
|
|
74558
|
-
|
|
75230
|
+
profile2.transforms.push({ kind: "translate", x: tx, y: ty });
|
|
74559
75231
|
}
|
|
74560
|
-
return
|
|
75232
|
+
return profile2;
|
|
74561
75233
|
}
|
|
74562
75234
|
function pointDistance2d(a, b) {
|
|
74563
75235
|
return Math.hypot(a[0] - b[0], a[1] - b[1]);
|
|
@@ -74705,7 +75377,7 @@ function transformedTorusSideProjectionReplayProfile(sourceShape, targetPlane) {
|
|
|
74705
75377
|
const radiusDirection = normalize9(cross11(targetPlane.normal, axis));
|
|
74706
75378
|
const radiusDirectionU = dot11(radiusDirection, targetPlane.u);
|
|
74707
75379
|
const radiusDirectionV = dot11(radiusDirection, targetPlane.v);
|
|
74708
|
-
const
|
|
75380
|
+
const profile2 = {
|
|
74709
75381
|
kind: "roundedRect",
|
|
74710
75382
|
width: 2 * (majorRadius + minorRadius),
|
|
74711
75383
|
height: 2 * minorRadius,
|
|
@@ -74713,15 +75385,15 @@ function transformedTorusSideProjectionReplayProfile(sourceShape, targetPlane) {
|
|
|
74713
75385
|
transforms: []
|
|
74714
75386
|
};
|
|
74715
75387
|
const angle = Math.atan2(radiusDirectionV, radiusDirectionU) * 180 / Math.PI;
|
|
74716
|
-
if (!nearlyEqual(angle, 0))
|
|
75388
|
+
if (!nearlyEqual(angle, 0)) profile2.transforms.push({ kind: "rotate", degrees: angle });
|
|
74717
75389
|
const center = input.transform.point([0, 0, 0]);
|
|
74718
75390
|
const delta = sub11(center, targetPlane.origin);
|
|
74719
75391
|
const tx = dot11(delta, targetPlane.u);
|
|
74720
75392
|
const ty = dot11(delta, targetPlane.v);
|
|
74721
75393
|
if (!nearlyEqual(tx, 0) || !nearlyEqual(ty, 0)) {
|
|
74722
|
-
|
|
75394
|
+
profile2.transforms.push({ kind: "translate", x: tx, y: ty });
|
|
74723
75395
|
}
|
|
74724
|
-
return { profile, placement: targetPlanePlacement(targetPlane) };
|
|
75396
|
+
return { profile: profile2, placement: targetPlanePlacement(targetPlane) };
|
|
74725
75397
|
}
|
|
74726
75398
|
function transformedExtrudeProjectionInput(sourceShape) {
|
|
74727
75399
|
const source = unwrapQueryOwnerPlan3(sourceShape);
|
|
@@ -74844,7 +75516,7 @@ function intervalGroupsAreStrictlySeparated2(groups) {
|
|
|
74844
75516
|
return true;
|
|
74845
75517
|
}
|
|
74846
75518
|
function radialIntervalsProjectionProfile(intervals, segments) {
|
|
74847
|
-
const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => annulusProjectionProfile(outer, inner, segments)).filter((
|
|
75519
|
+
const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => annulusProjectionProfile(outer, inner, segments)).filter((profile2) => profile2 != null);
|
|
74848
75520
|
if (profiles2.length === 0) return null;
|
|
74849
75521
|
if (profiles2.length === 1) return profiles2[0];
|
|
74850
75522
|
return buildBooleanProfileCompilePlan("union", profiles2);
|
|
@@ -74875,7 +75547,7 @@ function radialSectorProjectionProfile(innerRadius, outerRadius, degrees, segmen
|
|
|
74875
75547
|
}
|
|
74876
75548
|
function partialRevolveProjectionProfile(intervals, degrees, segments) {
|
|
74877
75549
|
if (!Number.isFinite(degrees) || nearlyEqual(degrees, 0)) return null;
|
|
74878
|
-
const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => radialSectorProjectionProfile(inner, outer, degrees, segments)).filter((
|
|
75550
|
+
const profiles2 = normalizeIntervals2(intervals).map(([inner, outer]) => radialSectorProjectionProfile(inner, outer, degrees, segments)).filter((profile2) => profile2 != null);
|
|
74879
75551
|
if (profiles2.length === 0) return null;
|
|
74880
75552
|
if (profiles2.length === 1) return profiles2[0];
|
|
74881
75553
|
return buildBooleanProfileCompilePlan("union", profiles2);
|
|
@@ -75039,8 +75711,8 @@ function profileOuterProjectionLoops(plan) {
|
|
|
75039
75711
|
if (plan.profiles.length === 0) return null;
|
|
75040
75712
|
if (plan.op === "union") {
|
|
75041
75713
|
loops = [];
|
|
75042
|
-
for (const
|
|
75043
|
-
const profileLoops = profileOuterProjectionLoops(
|
|
75714
|
+
for (const profile2 of plan.profiles) {
|
|
75715
|
+
const profileLoops = profileOuterProjectionLoops(profile2);
|
|
75044
75716
|
if (!profileLoops) return null;
|
|
75045
75717
|
loops.push(...profileLoops);
|
|
75046
75718
|
}
|
|
@@ -75070,10 +75742,10 @@ function profileOuterProjectionLoops(plan) {
|
|
|
75070
75742
|
}
|
|
75071
75743
|
return transformProfileLoops(loops, plan.transforms);
|
|
75072
75744
|
}
|
|
75073
|
-
function profileStrictlyContainsCircle(
|
|
75074
|
-
switch (
|
|
75745
|
+
function profileStrictlyContainsCircle(profile2, circle2) {
|
|
75746
|
+
switch (profile2.kind) {
|
|
75075
75747
|
case "circle": {
|
|
75076
|
-
const container = circleProfileFootprint(
|
|
75748
|
+
const container = circleProfileFootprint(profile2);
|
|
75077
75749
|
if (!container) return false;
|
|
75078
75750
|
const distance5 = Math.hypot(circle2.center[0] - container.center[0], circle2.center[1] - container.center[1]);
|
|
75079
75751
|
return distance5 + circle2.radius < container.radius - EPS10;
|
|
@@ -75081,11 +75753,11 @@ function profileStrictlyContainsCircle(profile, circle2) {
|
|
|
75081
75753
|
case "rect":
|
|
75082
75754
|
case "polygon":
|
|
75083
75755
|
case "boolean": {
|
|
75084
|
-
const loops = profileOuterProjectionLoops(
|
|
75756
|
+
const loops = profileOuterProjectionLoops(profile2);
|
|
75085
75757
|
return loops ? loops.some((loop) => loopStrictlyContainsCircle2d(loop, circle2)) : false;
|
|
75086
75758
|
}
|
|
75087
75759
|
case "roundedRect": {
|
|
75088
|
-
const container = roundedRectProfileFootprint(
|
|
75760
|
+
const container = roundedRectProfileFootprint(profile2);
|
|
75089
75761
|
return container ? roundedRectStrictlyContainsCircle2d(container, circle2) : false;
|
|
75090
75762
|
}
|
|
75091
75763
|
case "offset":
|
|
@@ -75093,25 +75765,25 @@ function profileStrictlyContainsCircle(profile, circle2) {
|
|
|
75093
75765
|
case "pathProfile":
|
|
75094
75766
|
return false;
|
|
75095
75767
|
default:
|
|
75096
|
-
return assertExhaustive(
|
|
75768
|
+
return assertExhaustive(profile2);
|
|
75097
75769
|
}
|
|
75098
75770
|
}
|
|
75099
|
-
function profileStrictlyContainsRoundedRect(
|
|
75100
|
-
switch (
|
|
75771
|
+
function profileStrictlyContainsRoundedRect(profile2, roundedRect2) {
|
|
75772
|
+
switch (profile2.kind) {
|
|
75101
75773
|
case "circle": {
|
|
75102
|
-
const container = circleProfileFootprint(
|
|
75774
|
+
const container = circleProfileFootprint(profile2);
|
|
75103
75775
|
return container ? circleStrictlyContainsRoundedRect2d(container, roundedRect2) : false;
|
|
75104
75776
|
}
|
|
75105
75777
|
case "rect": {
|
|
75106
|
-
const container = rectProfileFootprint(
|
|
75778
|
+
const container = rectProfileFootprint(profile2);
|
|
75107
75779
|
return container ? rectStrictlyContainsRoundedRect2d(container, roundedRect2) : false;
|
|
75108
75780
|
}
|
|
75109
75781
|
case "roundedRect": {
|
|
75110
|
-
const container = roundedRectProfileFootprint(
|
|
75782
|
+
const container = roundedRectProfileFootprint(profile2);
|
|
75111
75783
|
return container ? roundedRectStrictlyContainsRoundedRect2d(container, roundedRect2) : false;
|
|
75112
75784
|
}
|
|
75113
75785
|
case "polygon": {
|
|
75114
|
-
const loops = profileOuterProjectionLoops(
|
|
75786
|
+
const loops = profileOuterProjectionLoops(profile2);
|
|
75115
75787
|
return loops ? loops.some((loop) => loopStrictlyContainsRoundedRect2d(loop, roundedRect2)) : false;
|
|
75116
75788
|
}
|
|
75117
75789
|
case "boolean":
|
|
@@ -75120,23 +75792,23 @@ function profileStrictlyContainsRoundedRect(profile, roundedRect2) {
|
|
|
75120
75792
|
case "pathProfile":
|
|
75121
75793
|
return false;
|
|
75122
75794
|
default:
|
|
75123
|
-
return assertExhaustive(
|
|
75795
|
+
return assertExhaustive(profile2);
|
|
75124
75796
|
}
|
|
75125
75797
|
}
|
|
75126
|
-
function profileStrictlyContainsLoop(
|
|
75127
|
-
switch (
|
|
75798
|
+
function profileStrictlyContainsLoop(profile2, loop) {
|
|
75799
|
+
switch (profile2.kind) {
|
|
75128
75800
|
case "circle": {
|
|
75129
|
-
const circle2 = circleProfileFootprint(
|
|
75801
|
+
const circle2 = circleProfileFootprint(profile2);
|
|
75130
75802
|
return circle2 ? circleStrictlyContainsLoop2d(circle2, loop) : false;
|
|
75131
75803
|
}
|
|
75132
75804
|
case "rect":
|
|
75133
75805
|
case "polygon":
|
|
75134
75806
|
case "boolean": {
|
|
75135
|
-
const loops = profileOuterProjectionLoops(
|
|
75807
|
+
const loops = profileOuterProjectionLoops(profile2);
|
|
75136
75808
|
return loops ? loops.some((subjectLoop) => loopStrictlyContainsLoop2d(subjectLoop, loop)) : false;
|
|
75137
75809
|
}
|
|
75138
75810
|
case "roundedRect": {
|
|
75139
|
-
const container = roundedRectProfileFootprint(
|
|
75811
|
+
const container = roundedRectProfileFootprint(profile2);
|
|
75140
75812
|
return container ? roundedRectStrictlyContainsLoop2d(container, loop) : false;
|
|
75141
75813
|
}
|
|
75142
75814
|
case "offset":
|
|
@@ -75144,7 +75816,7 @@ function profileStrictlyContainsLoop(profile, loop) {
|
|
|
75144
75816
|
case "pathProfile":
|
|
75145
75817
|
return false;
|
|
75146
75818
|
default:
|
|
75147
|
-
return assertExhaustive(
|
|
75819
|
+
return assertExhaustive(profile2);
|
|
75148
75820
|
}
|
|
75149
75821
|
}
|
|
75150
75822
|
function profileStrictlyContainsProfile(container, subject) {
|
|
@@ -75152,7 +75824,7 @@ function profileStrictlyContainsProfile(container, subject) {
|
|
|
75152
75824
|
const profiles2 = booleanProfilesWithParentTransforms2(subject);
|
|
75153
75825
|
if (!profiles2 || profiles2.length === 0) return false;
|
|
75154
75826
|
if (subject.op === "union") {
|
|
75155
|
-
return profiles2.every((
|
|
75827
|
+
return profiles2.every((profile2) => profileStrictlyContainsProfile(container, profile2));
|
|
75156
75828
|
}
|
|
75157
75829
|
if (subject.op === "intersection") {
|
|
75158
75830
|
const contained = containedIntersectionProfile2(profiles2);
|
|
@@ -75272,7 +75944,7 @@ function profileProjectionIntervalAlongDirection(plan, direction2) {
|
|
|
75272
75944
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
75273
75945
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
75274
75946
|
if (plan.op === "union") {
|
|
75275
|
-
const intervals = profiles2.map((
|
|
75947
|
+
const intervals = profiles2.map((profile2) => profileProjectionIntervalAlongDirection(profile2, direction2));
|
|
75276
75948
|
if (intervals.some((interval) => !interval)) return null;
|
|
75277
75949
|
const merged = normalizeIntervals2(intervals);
|
|
75278
75950
|
return merged.length === 1 ? merged[0] : null;
|
|
@@ -75313,7 +75985,7 @@ function profileSupportsDefendedMiterOffsetSideInterval(plan, delta) {
|
|
|
75313
75985
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
75314
75986
|
if (!profiles2 || profiles2.length === 0) return false;
|
|
75315
75987
|
if (plan.op === "union") {
|
|
75316
|
-
return delta >= -EPS10 && profiles2.every((
|
|
75988
|
+
return delta >= -EPS10 && profiles2.every((profile2) => profileSupportsDefendedMiterOffsetSideInterval(profile2, delta));
|
|
75317
75989
|
}
|
|
75318
75990
|
if (plan.op === "intersection") {
|
|
75319
75991
|
const contained = containedIntersectionProfile2(profiles2);
|
|
@@ -75422,7 +76094,7 @@ function miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(plan, d
|
|
|
75422
76094
|
return miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(subject, direction2, delta);
|
|
75423
76095
|
}
|
|
75424
76096
|
if (plan.op !== "union" || delta < -EPS10) return null;
|
|
75425
|
-
const intervals = profiles2.map((
|
|
76097
|
+
const intervals = profiles2.map((profile2) => miterOffsetConvexPolygonProfileProjectionIntervalAlongDirection(profile2, direction2, delta));
|
|
75426
76098
|
if (intervals.some((interval) => !interval)) return null;
|
|
75427
76099
|
const merged = normalizeIntervals2(intervals);
|
|
75428
76100
|
return merged.length === 1 ? merged[0] : null;
|
|
@@ -75462,11 +76134,11 @@ function miterOffsetConvexPolygonProfileForLocalDelta(plan, localDelta) {
|
|
|
75462
76134
|
if (strictlyConvexPolygonOrientation(offsetPoints) !== orientation3) return null;
|
|
75463
76135
|
return { kind: "polygon", points: offsetPoints, transforms: [] };
|
|
75464
76136
|
}
|
|
75465
|
-
function translatedProfileIfNeeded(
|
|
76137
|
+
function translatedProfileIfNeeded(profile2, center) {
|
|
75466
76138
|
if (!nearlyEqual(center[0], 0) || !nearlyEqual(center[1], 0)) {
|
|
75467
|
-
|
|
76139
|
+
profile2.transforms.push({ kind: "translate", x: center[0], y: center[1] });
|
|
75468
76140
|
}
|
|
75469
|
-
return
|
|
76141
|
+
return profile2;
|
|
75470
76142
|
}
|
|
75471
76143
|
function insetProfileForContainment(plan, distance5) {
|
|
75472
76144
|
if (!Number.isFinite(distance5) || distance5 < -EPS10) return null;
|
|
@@ -75498,7 +76170,7 @@ function insetProfileForContainment(plan, distance5) {
|
|
|
75498
76170
|
if (!roundedRect2 || roundedRect2.halfWidth <= distance5 + EPS10 || roundedRect2.halfHeight <= distance5 + EPS10 || roundedRect2.radius <= distance5 + EPS10) {
|
|
75499
76171
|
return null;
|
|
75500
76172
|
}
|
|
75501
|
-
const
|
|
76173
|
+
const profile2 = {
|
|
75502
76174
|
kind: "roundedRect",
|
|
75503
76175
|
width: 2 * (roundedRect2.halfWidth - distance5),
|
|
75504
76176
|
height: 2 * (roundedRect2.halfHeight - distance5),
|
|
@@ -75506,8 +76178,8 @@ function insetProfileForContainment(plan, distance5) {
|
|
|
75506
76178
|
transforms: []
|
|
75507
76179
|
};
|
|
75508
76180
|
const angle = Math.atan2(roundedRect2.xAxis[1], roundedRect2.xAxis[0]) * 180 / Math.PI;
|
|
75509
|
-
if (!nearlyEqual(angle, 0))
|
|
75510
|
-
return translatedProfileIfNeeded(
|
|
76181
|
+
if (!nearlyEqual(angle, 0)) profile2.transforms.push({ kind: "rotate", degrees: angle });
|
|
76182
|
+
return translatedProfileIfNeeded(profile2, roundedRect2.center);
|
|
75511
76183
|
}
|
|
75512
76184
|
if (plan.kind === "polygon") {
|
|
75513
76185
|
return miterOffsetConvexPolygonProfileForLocalDelta(plan, -distance5);
|
|
@@ -75520,8 +76192,8 @@ function expandedProfileForContainment(plan, distance5) {
|
|
|
75520
76192
|
if (plan.kind === "boolean" && plan.op === "union") {
|
|
75521
76193
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
75522
76194
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
75523
|
-
const expandedProfiles = profiles2.map((
|
|
75524
|
-
return expandedProfiles.some((
|
|
76195
|
+
const expandedProfiles = profiles2.map((profile2) => expandedProfileForContainment(profile2, distance5));
|
|
76196
|
+
return expandedProfiles.some((profile2) => !profile2) ? null : buildBooleanProfileCompilePlan("union", expandedProfiles);
|
|
75525
76197
|
}
|
|
75526
76198
|
if (plan.kind === "boolean" && plan.op === "intersection") {
|
|
75527
76199
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
@@ -75555,7 +76227,7 @@ function expandedProfileForContainment(plan, distance5) {
|
|
|
75555
76227
|
if (plan.kind === "roundedRect") {
|
|
75556
76228
|
const roundedRect2 = roundedRectProfileFootprint(plan);
|
|
75557
76229
|
if (!roundedRect2) return null;
|
|
75558
|
-
const
|
|
76230
|
+
const profile2 = {
|
|
75559
76231
|
kind: "roundedRect",
|
|
75560
76232
|
width: 2 * (roundedRect2.halfWidth + distance5),
|
|
75561
76233
|
height: 2 * (roundedRect2.halfHeight + distance5),
|
|
@@ -75563,8 +76235,8 @@ function expandedProfileForContainment(plan, distance5) {
|
|
|
75563
76235
|
transforms: []
|
|
75564
76236
|
};
|
|
75565
76237
|
const angle = Math.atan2(roundedRect2.xAxis[1], roundedRect2.xAxis[0]) * 180 / Math.PI;
|
|
75566
|
-
if (!nearlyEqual(angle, 0))
|
|
75567
|
-
return translatedProfileIfNeeded(
|
|
76238
|
+
if (!nearlyEqual(angle, 0)) profile2.transforms.push({ kind: "rotate", degrees: angle });
|
|
76239
|
+
return translatedProfileIfNeeded(profile2, roundedRect2.center);
|
|
75568
76240
|
}
|
|
75569
76241
|
if (plan.kind === "polygon") {
|
|
75570
76242
|
return miterOffsetConvexPolygonProfileForLocalDelta(plan, distance5);
|
|
@@ -75577,7 +76249,7 @@ function booleanOffsetProfileProjectionIntervalAlongDirection(plan, direction2,
|
|
|
75577
76249
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
75578
76250
|
if (plan.op === "union") {
|
|
75579
76251
|
if (delta < -EPS10) return null;
|
|
75580
|
-
const intervals = profiles2.map((
|
|
76252
|
+
const intervals = profiles2.map((profile2) => childInterval(profile2, direction2, delta));
|
|
75581
76253
|
if (intervals.some((interval) => !interval)) return null;
|
|
75582
76254
|
const merged = normalizeIntervals2(intervals);
|
|
75583
76255
|
return merged.length === 1 ? merged[0] : null;
|
|
@@ -75655,7 +76327,7 @@ function circularOffsetProfileProjectionIntervalAlongDirection(plan, direction2,
|
|
|
75655
76327
|
if (plan.kind === "boolean" && plan.op === "union" && delta >= -EPS10) {
|
|
75656
76328
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
75657
76329
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
75658
|
-
const intervals = profiles2.map((
|
|
76330
|
+
const intervals = profiles2.map((profile2) => circularOffsetProfileProjectionIntervalAlongDirection(profile2, direction2, delta));
|
|
75659
76331
|
if (intervals.some((interval2) => !interval2)) return null;
|
|
75660
76332
|
const merged = normalizeIntervals2(intervals);
|
|
75661
76333
|
return merged.length === 1 ? merged[0] : null;
|
|
@@ -75672,7 +76344,7 @@ function miterOffsetProfileProjectionIntervalAlongCardinalDirection(plan, direct
|
|
|
75672
76344
|
if (plan.kind === "boolean" && plan.op === "union" && delta >= -EPS10) {
|
|
75673
76345
|
const profiles2 = booleanProfilesWithParentTransforms2(plan);
|
|
75674
76346
|
if (!profiles2 || profiles2.length === 0) return null;
|
|
75675
|
-
const intervals = profiles2.map((
|
|
76347
|
+
const intervals = profiles2.map((profile2) => miterOffsetProfileProjectionIntervalAlongCardinalDirection(profile2, direction2, delta));
|
|
75676
76348
|
if (intervals.some((interval2) => !interval2)) return null;
|
|
75677
76349
|
const merged = normalizeIntervals2(intervals);
|
|
75678
76350
|
return merged.length === 1 ? merged[0] : null;
|
|
@@ -75708,8 +76380,8 @@ function radialProjectionIntervalsForLoops(loops) {
|
|
|
75708
76380
|
function booleanProfilesWithParentTransforms2(plan) {
|
|
75709
76381
|
if (plan.transforms.length === 0) return plan.profiles;
|
|
75710
76382
|
const profiles2 = [];
|
|
75711
|
-
for (const
|
|
75712
|
-
const transformed = appendProfileCompileTransforms(
|
|
76383
|
+
for (const profile2 of plan.profiles) {
|
|
76384
|
+
const transformed = appendProfileCompileTransforms(profile2, plan.transforms);
|
|
75713
76385
|
if (!transformed) return null;
|
|
75714
76386
|
profiles2.push(transformed);
|
|
75715
76387
|
}
|
|
@@ -75732,8 +76404,8 @@ function radialProjectionIntervalsForRevolveProfile2(plan) {
|
|
|
75732
76404
|
if (!profiles2) return null;
|
|
75733
76405
|
if (plan.op === "union") {
|
|
75734
76406
|
const intervals = [];
|
|
75735
|
-
for (const
|
|
75736
|
-
const profileIntervals = radialProjectionIntervalsForRevolveProfile2(
|
|
76407
|
+
for (const profile2 of profiles2) {
|
|
76408
|
+
const profileIntervals = radialProjectionIntervalsForRevolveProfile2(profile2);
|
|
75737
76409
|
if (!profileIntervals) return null;
|
|
75738
76410
|
intervals.push(...profileIntervals);
|
|
75739
76411
|
}
|
|
@@ -75963,9 +76635,9 @@ function insetRadialIntervalsForFullRevolutionProfile2(plan, distance5) {
|
|
|
75963
76635
|
if (plan.op === "union") {
|
|
75964
76636
|
const baseIntervalGroups = [];
|
|
75965
76637
|
const insetIntervals = [];
|
|
75966
|
-
for (const
|
|
75967
|
-
const baseIntervals = radialProjectionIntervalsForRevolveProfile2(
|
|
75968
|
-
const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile2(
|
|
76638
|
+
for (const profile2 of profiles2) {
|
|
76639
|
+
const baseIntervals = radialProjectionIntervalsForRevolveProfile2(profile2);
|
|
76640
|
+
const profileInsetIntervals = insetRadialIntervalsForFullRevolutionProfile2(profile2, distance5);
|
|
75969
76641
|
if (!baseIntervals || !profileInsetIntervals) return null;
|
|
75970
76642
|
baseIntervalGroups.push(baseIntervals);
|
|
75971
76643
|
insetIntervals.push(...profileInsetIntervals);
|
|
@@ -76010,11 +76682,11 @@ function revolveProjectionReplayContext(plan) {
|
|
|
76010
76682
|
reason: "projection replay currently supports revolve sources only when the radial profile has finite non-negative rect/polygon/circle/rounded-rectangle outer footprints and contained difference holes."
|
|
76011
76683
|
};
|
|
76012
76684
|
}
|
|
76013
|
-
const
|
|
76014
|
-
if (!
|
|
76685
|
+
const profile2 = isFullRevolution2(plan.degrees) ? radialIntervalsProjectionProfile(intervals, plan.segments) : partialRevolveProjectionProfile(intervals, plan.degrees, plan.segments);
|
|
76686
|
+
if (!profile2) {
|
|
76015
76687
|
return { ok: false, reason: "projection replay could not derive a non-empty revolve projection profile." };
|
|
76016
76688
|
}
|
|
76017
|
-
return { ok: true, context: defaultProjectionContext(
|
|
76689
|
+
return { ok: true, context: defaultProjectionContext(profile2) };
|
|
76018
76690
|
}
|
|
76019
76691
|
function pointOnSegment2d2(point2, a, b) {
|
|
76020
76692
|
const cross12 = (point2[0] - a[0]) * (b[1] - a[1]) - (point2[1] - a[1]) * (b[0] - a[0]);
|
|
@@ -76282,8 +76954,8 @@ function nestedPolygonProjectionProfile(profiles2) {
|
|
|
76282
76954
|
const container = checked.find((candidate) => checked.every((subject) => loopContainsLoop2d(candidate.points, subject.points)));
|
|
76283
76955
|
return container ? cloneProfileCompilePlan(container) : null;
|
|
76284
76956
|
}
|
|
76285
|
-
function appendProfileCompileTransforms(
|
|
76286
|
-
let next = cloneProfileCompilePlan(
|
|
76957
|
+
function appendProfileCompileTransforms(profile2, transforms) {
|
|
76958
|
+
let next = cloneProfileCompilePlan(profile2);
|
|
76287
76959
|
for (const transform of transforms) {
|
|
76288
76960
|
next = appendProfileCompileTransform(next, transform);
|
|
76289
76961
|
}
|
|
@@ -76346,23 +77018,23 @@ function loftProjectionReplayContext(plan) {
|
|
|
76346
77018
|
if (plan.heights.some((height) => !Number.isFinite(height))) {
|
|
76347
77019
|
return { ok: false, reason: "projection replay requires finite loft heights." };
|
|
76348
77020
|
}
|
|
76349
|
-
const
|
|
76350
|
-
if (!
|
|
77021
|
+
const profile2 = nestedPolygonProjectionProfile(plan.profiles);
|
|
77022
|
+
if (!profile2) {
|
|
76351
77023
|
return {
|
|
76352
77024
|
ok: false,
|
|
76353
77025
|
reason: "projection replay currently supports loft sources only when every section has a no-transform polygon footprint and one section contains all others."
|
|
76354
77026
|
};
|
|
76355
77027
|
}
|
|
76356
|
-
return { ok: true, context: defaultProjectionContext(
|
|
77028
|
+
return { ok: true, context: defaultProjectionContext(profile2) };
|
|
76357
77029
|
}
|
|
76358
77030
|
function sweepProjectionReplayContext(plan) {
|
|
76359
77031
|
const transforms = verticalSweepFrameProfileTransforms(plan.path, plan.up);
|
|
76360
77032
|
if (!transforms) {
|
|
76361
77033
|
return { ok: false, reason: "projection replay currently supports sweep sources only for monotone vertical polyline paths." };
|
|
76362
77034
|
}
|
|
76363
|
-
const
|
|
76364
|
-
if (!
|
|
76365
|
-
return { ok: true, context: defaultProjectionContext(
|
|
77035
|
+
const profile2 = appendProfileCompileTransforms(plan.profile, transforms);
|
|
77036
|
+
if (!profile2) return { ok: false, reason: "projection replay could not map the sweep profile into its vertical path frame." };
|
|
77037
|
+
return { ok: true, context: defaultProjectionContext(profile2) };
|
|
76366
77038
|
}
|
|
76367
77039
|
function variableSweepProjectionReplayContext(plan) {
|
|
76368
77040
|
const transforms = verticalSweepFrameProfileTransforms(plan.path, plan.up);
|
|
@@ -76376,14 +77048,14 @@ function variableSweepProjectionReplayContext(plan) {
|
|
|
76376
77048
|
if (sortedT.some((t, idx) => idx > 0 && t <= sortedT[idx - 1] + EPS10)) {
|
|
76377
77049
|
return { ok: false, reason: "projection replay requires distinct variable-sweep section parameters." };
|
|
76378
77050
|
}
|
|
76379
|
-
const
|
|
76380
|
-
if (!
|
|
77051
|
+
const profile2 = nestedPolygonProjectionProfile(plan.sections.map((section) => section.profile));
|
|
77052
|
+
if (!profile2) {
|
|
76381
77053
|
return {
|
|
76382
77054
|
ok: false,
|
|
76383
77055
|
reason: "projection replay currently supports variable sweep sources only when every section has a no-transform polygon footprint and one section contains all others."
|
|
76384
77056
|
};
|
|
76385
77057
|
}
|
|
76386
|
-
const transformed = appendProfileCompileTransforms(
|
|
77058
|
+
const transformed = appendProfileCompileTransforms(profile2, transforms);
|
|
76387
77059
|
if (!transformed)
|
|
76388
77060
|
return { ok: false, reason: "projection replay could not map the variable-sweep profile into its vertical path frame." };
|
|
76389
77061
|
return { ok: true, context: defaultProjectionContext(transformed) };
|
|
@@ -76520,8 +77192,8 @@ function transformedSphereProjectionReplayProfile(sourceShape, targetPlane) {
|
|
|
76520
77192
|
return analyticPrimitive ? transformedSphereProjectionReplayProfile(analyticPrimitive, targetPlane) : null;
|
|
76521
77193
|
}
|
|
76522
77194
|
if (source.kind === "sphere") {
|
|
76523
|
-
const
|
|
76524
|
-
return { profile:
|
|
77195
|
+
const profile3 = sphereProjectionProfileAtCenter(source.radius, source.segments, [0, 0, 0], targetPlane);
|
|
77196
|
+
return { profile: profile3, placement: targetPlanePlacement(targetPlane) };
|
|
76525
77197
|
}
|
|
76526
77198
|
if (source.kind !== "transform") return null;
|
|
76527
77199
|
const base = unwrapQueryOwnerPlan3(source.base);
|
|
@@ -76535,8 +77207,8 @@ function transformedSphereProjectionReplayProfile(sourceShape, targetPlane) {
|
|
|
76535
77207
|
distanceScale *= next.scale;
|
|
76536
77208
|
}
|
|
76537
77209
|
const center = transform.point([0, 0, 0]);
|
|
76538
|
-
const
|
|
76539
|
-
return { profile, placement: targetPlanePlacement(targetPlane) };
|
|
77210
|
+
const profile2 = sphereProjectionProfileAtCenter(base.radius * distanceScale, base.segments, center, targetPlane);
|
|
77211
|
+
return { profile: profile2, placement: targetPlanePlacement(targetPlane) };
|
|
76540
77212
|
}
|
|
76541
77213
|
function targetPlaneSpecificProjectionReplayContext(sourceShape, targetPlane) {
|
|
76542
77214
|
const boxReplay = transformedBoxProjectionReplayProfile(sourceShape, targetPlane);
|
|
@@ -76599,9 +77271,9 @@ function targetPlaneSpecificProjectionReplayContext(sourceShape, targetPlane) {
|
|
|
76599
77271
|
profiles2.push(replay.profile);
|
|
76600
77272
|
}
|
|
76601
77273
|
if (!usedTargetPlaneSpecificReplay) return null;
|
|
76602
|
-
const
|
|
76603
|
-
return
|
|
76604
|
-
profile,
|
|
77274
|
+
const profile2 = buildBooleanProfileCompilePlan("union", profiles2);
|
|
77275
|
+
return profile2 ? {
|
|
77276
|
+
profile: profile2,
|
|
76605
77277
|
placement: targetPlanePlacement(targetPlane),
|
|
76606
77278
|
usedTargetPlaneSpecificReplay
|
|
76607
77279
|
} : null;
|
|
@@ -76719,8 +77391,8 @@ function offsetSolidFromSlicesLoftProjectionReplayContext(plan) {
|
|
|
76719
77391
|
if (zMax <= zMin + EPS10) {
|
|
76720
77392
|
return { ok: false, reason: "projection replay cannot derive a collapsed offsetSolid(Shape.fromSlices(...)) vertical span." };
|
|
76721
77393
|
}
|
|
76722
|
-
const
|
|
76723
|
-
if (!
|
|
77394
|
+
const profile2 = nestedPolygonProjectionProfile(sorted.map((slice) => slice.profile));
|
|
77395
|
+
if (!profile2) {
|
|
76724
77396
|
return {
|
|
76725
77397
|
ok: false,
|
|
76726
77398
|
reason: "projection replay currently supports offsetSolid(Shape.fromSlices(...)) lofts only when every slice has a no-transform polygon footprint and one contains all others."
|
|
@@ -76729,7 +77401,7 @@ function offsetSolidFromSlicesLoftProjectionReplayContext(plan) {
|
|
|
76729
77401
|
return {
|
|
76730
77402
|
ok: true,
|
|
76731
77403
|
context: {
|
|
76732
|
-
profile: buildOffsetProfileCompilePlan(
|
|
77404
|
+
profile: buildOffsetProfileCompilePlan(profile2, plan.thickness, "Miter"),
|
|
76733
77405
|
placement
|
|
76734
77406
|
}
|
|
76735
77407
|
};
|
|
@@ -76809,8 +77481,8 @@ function offsetSolidFullRevolutionProjectionReplayContext(plan) {
|
|
|
76809
77481
|
reason: "projection replay could not derive non-empty radial intervals for the offset revolved solid."
|
|
76810
77482
|
};
|
|
76811
77483
|
}
|
|
76812
|
-
const
|
|
76813
|
-
return
|
|
77484
|
+
const profile2 = radialIntervalsProjectionProfile(offsetIntervals, source.context.plan.segments);
|
|
77485
|
+
return profile2 ? { ok: true, context: { profile: profile2, placement: cloneShapeWorkplanePlacement(source.context.placement) } } : { ok: false, reason: "projection replay could not build a profile for the offset revolved solid." };
|
|
76814
77486
|
}
|
|
76815
77487
|
function offsetSolidProjectionReplayContext(plan) {
|
|
76816
77488
|
if (!Number.isFinite(plan.thickness) || nearlyEqual(plan.thickness, 0)) {
|
|
@@ -76845,9 +77517,9 @@ function offsetSolidProjectionReplayContext(plan) {
|
|
|
76845
77517
|
function holeProjectionRadius(plan) {
|
|
76846
77518
|
return Math.max(plan.hole.radius, plan.hole.counterbore?.radius ?? 0, plan.hole.countersink?.radius ?? 0);
|
|
76847
77519
|
}
|
|
76848
|
-
function defaultProjectionContext(
|
|
77520
|
+
function defaultProjectionContext(profile2) {
|
|
76849
77521
|
return {
|
|
76850
|
-
profile: cloneProfileCompilePlan(
|
|
77522
|
+
profile: cloneProfileCompilePlan(profile2),
|
|
76851
77523
|
placement: cloneShapeWorkplanePlacement(DEFAULT_PROJECTION_PLACEMENT)
|
|
76852
77524
|
};
|
|
76853
77525
|
}
|
|
@@ -76874,14 +77546,14 @@ function projectThroughHoleIntoContext(base, plan) {
|
|
|
76874
77546
|
reason: `projection replay can only absorb hole rewrites when the hole axis stays parallel to the projected source basis. ${mapped.reason ?? ""}`.trim()
|
|
76875
77547
|
};
|
|
76876
77548
|
}
|
|
76877
|
-
const
|
|
76878
|
-
if (!
|
|
77549
|
+
const profile2 = buildBooleanProfileCompilePlan("difference", [base.profile, mapped.profile]);
|
|
77550
|
+
if (!profile2) {
|
|
76879
77551
|
return { ok: false, reason: "projection replay could not subtract the hole silhouette from the projected source profile." };
|
|
76880
77552
|
}
|
|
76881
77553
|
return {
|
|
76882
77554
|
ok: true,
|
|
76883
77555
|
context: {
|
|
76884
|
-
profile,
|
|
77556
|
+
profile: profile2,
|
|
76885
77557
|
placement: cloneShapeWorkplanePlacement(base.placement)
|
|
76886
77558
|
}
|
|
76887
77559
|
};
|
|
@@ -76894,14 +77566,14 @@ function projectThroughCutIntoContext(base, plan) {
|
|
|
76894
77566
|
reason: `projection replay can only absorb cut rewrites when the cut workplane stays parallel to the projected source basis. ${mapped.reason ?? ""}`.trim()
|
|
76895
77567
|
};
|
|
76896
77568
|
}
|
|
76897
|
-
const
|
|
76898
|
-
if (!
|
|
77569
|
+
const profile2 = buildBooleanProfileCompilePlan("difference", [base.profile, mapped.profile]);
|
|
77570
|
+
if (!profile2) {
|
|
76899
77571
|
return { ok: false, reason: "projection replay could not subtract the cut silhouette from the projected source profile." };
|
|
76900
77572
|
}
|
|
76901
77573
|
return {
|
|
76902
77574
|
ok: true,
|
|
76903
77575
|
context: {
|
|
76904
|
-
profile,
|
|
77576
|
+
profile: profile2,
|
|
76905
77577
|
placement: cloneShapeWorkplanePlacement(base.placement)
|
|
76906
77578
|
}
|
|
76907
77579
|
};
|
|
@@ -77028,14 +77700,14 @@ function buildProjectionReplayContext(plan) {
|
|
|
77028
77700
|
}
|
|
77029
77701
|
unionProfiles.push(reoriented.context.profile);
|
|
77030
77702
|
}
|
|
77031
|
-
const
|
|
77032
|
-
if (!
|
|
77703
|
+
const profile2 = buildBooleanProfileCompilePlan("union", unionProfiles);
|
|
77704
|
+
if (!profile2) {
|
|
77033
77705
|
return { ok: false, reason: "projection replay could not combine the projected union operands into one 2D profile." };
|
|
77034
77706
|
}
|
|
77035
77707
|
return {
|
|
77036
77708
|
ok: true,
|
|
77037
77709
|
context: {
|
|
77038
|
-
profile,
|
|
77710
|
+
profile: profile2,
|
|
77039
77711
|
placement: targetPlacement
|
|
77040
77712
|
}
|
|
77041
77713
|
};
|
|
@@ -77046,8 +77718,8 @@ function buildProjectionReplayContext(plan) {
|
|
|
77046
77718
|
context: defaultProjectionContext(circleProjectionProfile(plan.radius, plan.segments))
|
|
77047
77719
|
};
|
|
77048
77720
|
case "torus": {
|
|
77049
|
-
const
|
|
77050
|
-
return
|
|
77721
|
+
const profile2 = annulusProjectionProfile(plan.majorRadius + plan.minorRadius, plan.majorRadius - plan.minorRadius, plan.segments);
|
|
77722
|
+
return profile2 ? { ok: true, context: defaultProjectionContext(profile2) } : { ok: false, reason: "projection replay could not derive a non-empty torus annulus profile." };
|
|
77051
77723
|
}
|
|
77052
77724
|
case "sheetMetal":
|
|
77053
77725
|
return {
|
|
@@ -79514,8 +80186,8 @@ var ConstrainedSketchBuilder = class _ConstrainedSketchBuilder {
|
|
|
79514
80186
|
if (err2 >= 0 && err2 <= DEFAULT_TOLERANCE3 * 100) {
|
|
79515
80187
|
this.syncPointsFromSession();
|
|
79516
80188
|
}
|
|
79517
|
-
} catch (
|
|
79518
|
-
if (this.strict) throw
|
|
80189
|
+
} catch (error2) {
|
|
80190
|
+
if (this.strict) throw error2;
|
|
79519
80191
|
}
|
|
79520
80192
|
return;
|
|
79521
80193
|
}
|
|
@@ -79541,8 +80213,8 @@ var ConstrainedSketchBuilder = class _ConstrainedSketchBuilder {
|
|
|
79541
80213
|
if (Number.isFinite(maxError) && maxError <= DEFAULT_TOLERANCE3 * 100) {
|
|
79542
80214
|
this.syncFromDefinition(working);
|
|
79543
80215
|
}
|
|
79544
|
-
} catch (
|
|
79545
|
-
if (this.strict) throw
|
|
80216
|
+
} catch (error2) {
|
|
80217
|
+
if (this.strict) throw error2;
|
|
79546
80218
|
}
|
|
79547
80219
|
}
|
|
79548
80220
|
/** Serialize a constraint for the session API (matches Rust serde format). */
|
|
@@ -82962,8 +83634,8 @@ function applyAxisRail(bounds, axis, minRail, maxRail, center) {
|
|
|
82962
83634
|
bounds[maxKey] = center + width / 2;
|
|
82963
83635
|
}
|
|
82964
83636
|
}
|
|
82965
|
-
function fitProfileToBounds(
|
|
82966
|
-
const source = sketchBounds(
|
|
83637
|
+
function fitProfileToBounds(profile2, target) {
|
|
83638
|
+
const source = sketchBounds(profile2);
|
|
82967
83639
|
const sourceWidth = source.maxX - source.minX;
|
|
82968
83640
|
const sourceDepth = source.maxY - source.minY;
|
|
82969
83641
|
if (sourceWidth < LOFT_GUIDE_EPS || sourceDepth < LOFT_GUIDE_EPS) {
|
|
@@ -82971,10 +83643,10 @@ function fitProfileToBounds(profile, target) {
|
|
|
82971
83643
|
}
|
|
82972
83644
|
const sourceCenter = [(source.minX + source.maxX) / 2, (source.minY + source.maxY) / 2];
|
|
82973
83645
|
const targetCenter = [(target.minX + target.maxX) / 2, (target.minY + target.maxY) / 2];
|
|
82974
|
-
return
|
|
83646
|
+
return profile2.scaleAround(sourceCenter, [(target.maxX - target.minX) / sourceWidth, (target.maxY - target.minY) / sourceDepth]).translate(targetCenter[0] - sourceCenter[0], targetCenter[1] - sourceCenter[1]);
|
|
82975
83647
|
}
|
|
82976
|
-
function sketchBounds(
|
|
82977
|
-
const bounds =
|
|
83648
|
+
function sketchBounds(profile2) {
|
|
83649
|
+
const bounds = profile2.bounds();
|
|
82978
83650
|
return { minX: bounds.min[0], maxX: bounds.max[0], minY: bounds.min[1], maxY: bounds.max[1] };
|
|
82979
83651
|
}
|
|
82980
83652
|
function lerpBounds(a, b, t) {
|
|
@@ -83029,9 +83701,9 @@ function fieldLoftMeshingOptions(options) {
|
|
|
83029
83701
|
}
|
|
83030
83702
|
var Loft = {
|
|
83031
83703
|
/** Create a loft station from a 2D profile and an axis position. */
|
|
83032
|
-
station(
|
|
83704
|
+
station(profile2, position) {
|
|
83033
83705
|
if (!Number.isFinite(position)) throw new Error("Loft.station position must be finite");
|
|
83034
|
-
return { profile, position };
|
|
83706
|
+
return { profile: profile2, position };
|
|
83035
83707
|
},
|
|
83036
83708
|
/**
|
|
83037
83709
|
* Loft by interpolating signed-distance fields instead of matching vertices.
|
|
@@ -85670,11 +86342,11 @@ function parseSvgGeometry(svgText, options) {
|
|
|
85670
86342
|
continue;
|
|
85671
86343
|
}
|
|
85672
86344
|
const selfClosing = rawTag.endsWith("/>");
|
|
85673
|
-
const
|
|
85674
|
-
if (!
|
|
85675
|
-
const space =
|
|
85676
|
-
const tag = (space < 0 ?
|
|
85677
|
-
const attrText = space < 0 ? "" :
|
|
86345
|
+
const body2 = rawTag.slice(1, rawTag.length - (selfClosing ? 2 : 1)).trim();
|
|
86346
|
+
if (!body2) continue;
|
|
86347
|
+
const space = body2.search(/\s/);
|
|
86348
|
+
const tag = (space < 0 ? body2 : body2.slice(0, space)).toLowerCase();
|
|
86349
|
+
const attrText = space < 0 ? "" : body2.slice(space + 1);
|
|
85678
86350
|
const attrs = parseAttributes(attrText);
|
|
85679
86351
|
const parent = stack[stack.length - 1].ctx;
|
|
85680
86352
|
const mergedStyle = mergeStyle(parent.style, attrs);
|
|
@@ -85913,31 +86585,31 @@ function extractFaceProfile(rawShape, face) {
|
|
|
85913
86585
|
function shapePocket(rawShape, face, depth, opts) {
|
|
85914
86586
|
const faceRef = rawShape.face(face);
|
|
85915
86587
|
const frame = resolvePlaneFrame({ face: faceRef });
|
|
85916
|
-
let
|
|
86588
|
+
let profile2 = extractFaceProfile(rawShape, faceRef);
|
|
85917
86589
|
if (opts?.inset) {
|
|
85918
|
-
|
|
86590
|
+
profile2 = sketchOffset(profile2, -opts.inset, opts.join ?? "Round");
|
|
85919
86591
|
} else if (opts?.scale != null && opts.scale !== 1) {
|
|
85920
|
-
|
|
86592
|
+
profile2 = profile2.scale(opts.scale);
|
|
85921
86593
|
}
|
|
85922
86594
|
const worldToPlane = planeFrameToWorldToPlaneMatrix(frame);
|
|
85923
86595
|
const planeToWorld = Transform.from(worldToPlane).inverse();
|
|
85924
86596
|
const inwardShift = Transform.translation(0, 0, -depth);
|
|
85925
86597
|
const toolTransform = Transform.compose(inwardShift, planeToWorld).toArray();
|
|
85926
|
-
const tool =
|
|
86598
|
+
const tool = profile2.extrude(depth).transform(toolTransform);
|
|
85927
86599
|
return rawShape.subtract(tool);
|
|
85928
86600
|
}
|
|
85929
86601
|
function shapeBoss(rawShape, face, height, opts) {
|
|
85930
86602
|
const faceRef = rawShape.face(face);
|
|
85931
86603
|
const frame = resolvePlaneFrame({ face: faceRef });
|
|
85932
|
-
let
|
|
86604
|
+
let profile2 = extractFaceProfile(rawShape, faceRef);
|
|
85933
86605
|
if (opts?.inset) {
|
|
85934
|
-
|
|
86606
|
+
profile2 = sketchOffset(profile2, -opts.inset, opts.join ?? "Round");
|
|
85935
86607
|
} else if (opts?.scale != null && opts.scale !== 1) {
|
|
85936
|
-
|
|
86608
|
+
profile2 = profile2.scale(opts.scale);
|
|
85937
86609
|
}
|
|
85938
86610
|
const worldToPlane = planeFrameToWorldToPlaneMatrix(frame);
|
|
85939
86611
|
const planeToWorld = Transform.from(worldToPlane).inverse();
|
|
85940
|
-
const tool =
|
|
86612
|
+
const tool = profile2.extrude(height).transform(planeToWorld.toArray());
|
|
85941
86613
|
return rawShape.add(tool);
|
|
85942
86614
|
}
|
|
85943
86615
|
Shape.prototype.pocket = function pocket(face, depth, opts) {
|
|
@@ -86318,13 +86990,62 @@ function sheetMetal(options) {
|
|
|
86318
86990
|
return new SheetMetalPart(model);
|
|
86319
86991
|
}
|
|
86320
86992
|
|
|
86993
|
+
// src/forge/import/sourceFrame.ts
|
|
86994
|
+
var SOURCE_FRAME_UP_AXES = ["+X", "-X", "+Y", "-Y", "+Z", "-Z"];
|
|
86995
|
+
function isRecord(value) {
|
|
86996
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
86997
|
+
}
|
|
86998
|
+
function sourceFrameErrorContext(importKind, fileName) {
|
|
86999
|
+
return `${importKind}("${fileName}")`;
|
|
87000
|
+
}
|
|
87001
|
+
function requireSourceFrameUp(importKind, fileName, sourceFrame) {
|
|
87002
|
+
if (sourceFrame === void 0) return null;
|
|
87003
|
+
const context = sourceFrameErrorContext(importKind, fileName);
|
|
87004
|
+
if (!isRecord(sourceFrame)) throw new Error(`${context}: sourceFrame must be an object with an "up" axis`);
|
|
87005
|
+
const keys = Object.keys(sourceFrame);
|
|
87006
|
+
if (keys.some((key) => key !== "up")) {
|
|
87007
|
+
throw new Error(
|
|
87008
|
+
`${context}: sourceFrame only supports "up"; remove unsupported keys: ${keys.filter((key) => key !== "up").join(", ")}`
|
|
87009
|
+
);
|
|
87010
|
+
}
|
|
87011
|
+
if (!Object.prototype.hasOwnProperty.call(sourceFrame, "up")) throw new Error(`${context}: sourceFrame.up is required`);
|
|
87012
|
+
const up = sourceFrame.up;
|
|
87013
|
+
if (typeof up !== "string" || !SOURCE_FRAME_UP_AXES.includes(up)) {
|
|
87014
|
+
throw new Error(`${context}: sourceFrame.up must be one of ${SOURCE_FRAME_UP_AXES.join(", ")}, got ${JSON.stringify(up)}`);
|
|
87015
|
+
}
|
|
87016
|
+
return up;
|
|
87017
|
+
}
|
|
87018
|
+
function sourceFrameTransformStep(importKind, fileName, sourceFrame) {
|
|
87019
|
+
const up = requireSourceFrameUp(importKind, fileName, sourceFrame);
|
|
87020
|
+
switch (up) {
|
|
87021
|
+
case null:
|
|
87022
|
+
case "+Z":
|
|
87023
|
+
return null;
|
|
87024
|
+
case "-Z":
|
|
87025
|
+
return { kind: "rotateAround", axisX: 1, axisY: 0, axisZ: 0, degrees: 180, pivotX: 0, pivotY: 0, pivotZ: 0 };
|
|
87026
|
+
case "+Y":
|
|
87027
|
+
return { kind: "rotateAround", axisX: 1, axisY: 0, axisZ: 0, degrees: 90, pivotX: 0, pivotY: 0, pivotZ: 0 };
|
|
87028
|
+
case "-Y":
|
|
87029
|
+
return { kind: "rotateAround", axisX: 1, axisY: 0, axisZ: 0, degrees: -90, pivotX: 0, pivotY: 0, pivotZ: 0 };
|
|
87030
|
+
case "+X":
|
|
87031
|
+
return { kind: "rotateAround", axisX: 0, axisY: 1, axisZ: 0, degrees: -90, pivotX: 0, pivotY: 0, pivotZ: 0 };
|
|
87032
|
+
case "-X":
|
|
87033
|
+
return { kind: "rotateAround", axisX: 0, axisY: 1, axisZ: 0, degrees: 90, pivotX: 0, pivotY: 0, pivotZ: 0 };
|
|
87034
|
+
}
|
|
87035
|
+
}
|
|
87036
|
+
function applySourceFrameToShapePlan(plan, importKind, fileName, sourceFrame) {
|
|
87037
|
+
const step = sourceFrameTransformStep(importKind, fileName, sourceFrame);
|
|
87038
|
+
return step ? appendShapeCompileTransform(plan, step) : plan;
|
|
87039
|
+
}
|
|
87040
|
+
|
|
86321
87041
|
// src/forge/import/importStep.ts
|
|
86322
|
-
function importStepFromBuffer(fileData, displayName = "import.step") {
|
|
86323
|
-
|
|
86324
|
-
|
|
86325
|
-
|
|
86326
|
-
|
|
86327
|
-
|
|
87042
|
+
function importStepFromBuffer(fileData, displayName = "import.step", options = {}) {
|
|
87043
|
+
let plan = { kind: "importedStep", filePath: displayName, fileData };
|
|
87044
|
+
plan = applySourceFrameToShapePlan(plan, "Import.step", displayName, options.sourceFrame);
|
|
87045
|
+
return buildShapeFromCompilePlan(createOwnedShapeCompilePlan(plan, "importStep"), void 0, {
|
|
87046
|
+
fidelity: "exact",
|
|
87047
|
+
sources: ["imported"]
|
|
87048
|
+
});
|
|
86328
87049
|
}
|
|
86329
87050
|
|
|
86330
87051
|
// src/forge/export/sheetStock.ts
|
|
@@ -86337,9 +87058,9 @@ function resetSheetStock() {
|
|
|
86337
87058
|
function getCollectedSheetStock() {
|
|
86338
87059
|
return collectedSheetStock;
|
|
86339
87060
|
}
|
|
86340
|
-
function normalizeMaterial(
|
|
86341
|
-
if (typeof
|
|
86342
|
-
const value =
|
|
87061
|
+
function normalizeMaterial(material2) {
|
|
87062
|
+
if (typeof material2 !== "string") return "sheet stock";
|
|
87063
|
+
const value = material2.trim();
|
|
86343
87064
|
return value.length > 0 ? value : "sheet stock";
|
|
86344
87065
|
}
|
|
86345
87066
|
function sheetStock(width, height, description, opts) {
|
|
@@ -86368,14 +87089,14 @@ function sheetStock(width, height, description, opts) {
|
|
|
86368
87089
|
}
|
|
86369
87090
|
|
|
86370
87091
|
// src/forge/laserKit/joints.ts
|
|
86371
|
-
function
|
|
87092
|
+
function requireFinite12(value, name) {
|
|
86372
87093
|
if (!Number.isFinite(value)) {
|
|
86373
87094
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
86374
87095
|
}
|
|
86375
87096
|
return value;
|
|
86376
87097
|
}
|
|
86377
|
-
function
|
|
86378
|
-
|
|
87098
|
+
function requirePositive9(value, name) {
|
|
87099
|
+
requireFinite12(value, name);
|
|
86379
87100
|
if (value <= 0) {
|
|
86380
87101
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
86381
87102
|
}
|
|
@@ -86388,10 +87109,10 @@ function requireOdd(value, name) {
|
|
|
86388
87109
|
return value;
|
|
86389
87110
|
}
|
|
86390
87111
|
function fingerJointProfile(length7, thickness, options = {}) {
|
|
86391
|
-
|
|
86392
|
-
|
|
86393
|
-
const clearance =
|
|
86394
|
-
const kerf =
|
|
87112
|
+
requirePositive9(length7, "length");
|
|
87113
|
+
requirePositive9(thickness, "thickness");
|
|
87114
|
+
const clearance = requireFinite12(options.clearance ?? 0, "clearance");
|
|
87115
|
+
const kerf = requireFinite12(options.kerf ?? 0, "kerf");
|
|
86395
87116
|
const endStyle = options.endStyle ?? "full";
|
|
86396
87117
|
let fingers;
|
|
86397
87118
|
if (options.fingers != null) {
|
|
@@ -86400,7 +87121,7 @@ function fingerJointProfile(length7, thickness, options = {}) {
|
|
|
86400
87121
|
fingers = Math.max(3, Math.round(length7 / (2 * thickness)) | 1);
|
|
86401
87122
|
if (fingers % 2 === 0) fingers += 1;
|
|
86402
87123
|
}
|
|
86403
|
-
const fingerWidth = options.fingerWidth != null ?
|
|
87124
|
+
const fingerWidth = options.fingerWidth != null ? requirePositive9(options.fingerWidth, "fingerWidth") : length7 / (2 * fingers - 1);
|
|
86404
87125
|
const halfKerf = kerf / 2;
|
|
86405
87126
|
const evenFingers = [];
|
|
86406
87127
|
const oddFingers = [];
|
|
@@ -86452,13 +87173,13 @@ function fingerJointProfile(length7, thickness, options = {}) {
|
|
|
86452
87173
|
return { tabProfile, matingProfile, slotProfile };
|
|
86453
87174
|
}
|
|
86454
87175
|
function tabSlotProfile(length7, thickness, options = {}) {
|
|
86455
|
-
|
|
86456
|
-
|
|
86457
|
-
const clearance =
|
|
86458
|
-
const kerf =
|
|
86459
|
-
const inset =
|
|
86460
|
-
const tabWidth =
|
|
86461
|
-
const tabCountRaw = options.tabCount != null ?
|
|
87176
|
+
requirePositive9(length7, "length");
|
|
87177
|
+
requirePositive9(thickness, "thickness");
|
|
87178
|
+
const clearance = requireFinite12(options.clearance ?? 0, "clearance");
|
|
87179
|
+
const kerf = requireFinite12(options.kerf ?? 0, "kerf");
|
|
87180
|
+
const inset = requirePositive9(options.inset ?? thickness, "inset");
|
|
87181
|
+
const tabWidth = requirePositive9(options.tabWidth ?? 2 * thickness, "tabWidth");
|
|
87182
|
+
const tabCountRaw = options.tabCount != null ? requireFinite12(options.tabCount, "tabCount") : length7 / (4 * thickness);
|
|
86462
87183
|
const tabCount = Math.max(1, Math.round(tabCountRaw));
|
|
86463
87184
|
const halfKerf = kerf / 2;
|
|
86464
87185
|
const usableLength = length7 - 2 * inset;
|
|
@@ -86508,20 +87229,20 @@ function kerfCompensateSlots(sketch, kerf) {
|
|
|
86508
87229
|
}
|
|
86509
87230
|
function kerfCompensatePart(baseProfile, joints, kerf) {
|
|
86510
87231
|
requireFiniteKerf(kerf);
|
|
86511
|
-
let
|
|
87232
|
+
let profile2 = baseProfile;
|
|
86512
87233
|
if (joints.additions) {
|
|
86513
87234
|
for (const tab of joints.additions) {
|
|
86514
87235
|
const compensated = kerfCompensateTabs(tab, kerf);
|
|
86515
|
-
|
|
87236
|
+
profile2 = profile2.add(compensated);
|
|
86516
87237
|
}
|
|
86517
87238
|
}
|
|
86518
87239
|
if (joints.subtractions) {
|
|
86519
87240
|
for (const slot2 of joints.subtractions) {
|
|
86520
87241
|
const compensated = kerfCompensateSlots(slot2, kerf);
|
|
86521
|
-
|
|
87242
|
+
profile2 = profile2.subtract(compensated);
|
|
86522
87243
|
}
|
|
86523
87244
|
}
|
|
86524
|
-
return kerfCompensateOutline(
|
|
87245
|
+
return kerfCompensateOutline(profile2, kerf);
|
|
86525
87246
|
}
|
|
86526
87247
|
var COMMON_KERFS_DATA = [
|
|
86527
87248
|
// Plywood
|
|
@@ -86541,22 +87262,22 @@ var COMMON_KERFS_DATA = [
|
|
|
86541
87262
|
{ material: "cardboard", thickness: 3, kerf: 0.15, laserType: "CO2-40W" }
|
|
86542
87263
|
];
|
|
86543
87264
|
var COMMON_KERFS = COMMON_KERFS_DATA;
|
|
86544
|
-
function lookupKerf(
|
|
87265
|
+
function lookupKerf(material2, thickness, laserType) {
|
|
86545
87266
|
const entry = COMMON_KERFS_DATA.find(
|
|
86546
|
-
(e) => e.material ===
|
|
87267
|
+
(e) => e.material === material2 && e.thickness === thickness && (laserType === void 0 || e.laserType === laserType)
|
|
86547
87268
|
);
|
|
86548
87269
|
return entry?.kerf;
|
|
86549
87270
|
}
|
|
86550
87271
|
|
|
86551
87272
|
// src/forge/laserKit/flatPart.ts
|
|
86552
|
-
function
|
|
87273
|
+
function requireFinite13(value, name) {
|
|
86553
87274
|
if (!Number.isFinite(value)) {
|
|
86554
87275
|
throw new Error(`${name} must be a finite number, got ${value}`);
|
|
86555
87276
|
}
|
|
86556
87277
|
return value;
|
|
86557
87278
|
}
|
|
86558
|
-
function
|
|
86559
|
-
|
|
87279
|
+
function requirePositive10(value, name) {
|
|
87280
|
+
requireFinite13(value, name);
|
|
86560
87281
|
if (value <= 0) {
|
|
86561
87282
|
throw new Error(`${name} must be positive, got ${value}`);
|
|
86562
87283
|
}
|
|
@@ -86580,7 +87301,7 @@ var FlatPart = class {
|
|
|
86580
87301
|
_partNumber = 0;
|
|
86581
87302
|
constructor(name, baseProfile, thickness, edges, options) {
|
|
86582
87303
|
requireNonEmpty(name, "name");
|
|
86583
|
-
|
|
87304
|
+
requirePositive10(thickness, "thickness");
|
|
86584
87305
|
if (baseProfile.area() <= 0) {
|
|
86585
87306
|
throw new Error(`FlatPart "${name}" baseProfile must have positive area`);
|
|
86586
87307
|
}
|
|
@@ -86650,14 +87371,14 @@ var FlatPart = class {
|
|
|
86650
87371
|
return this.profile(kerf).extrude(this.thickness);
|
|
86651
87372
|
}
|
|
86652
87373
|
};
|
|
86653
|
-
function positionAlongEdge(
|
|
87374
|
+
function positionAlongEdge(profile2, edge, inward = false) {
|
|
86654
87375
|
const nx = inward ? -edge.normal[0] : edge.normal[0];
|
|
86655
87376
|
const ny = inward ? -edge.normal[1] : edge.normal[1];
|
|
86656
87377
|
const angle = Math.atan2(-nx, ny) * (180 / Math.PI);
|
|
86657
87378
|
const edx = (edge.end[0] - edge.start[0]) / edge.length;
|
|
86658
87379
|
const edy = (edge.end[1] - edge.start[1]) / edge.length;
|
|
86659
87380
|
const dot12 = ny * edx + -nx * edy;
|
|
86660
|
-
const rotated =
|
|
87381
|
+
const rotated = profile2.rotateAround(angle, [0, 0]);
|
|
86661
87382
|
if (dot12 > 0) {
|
|
86662
87383
|
return rotated.translate(edge.start[0], edge.start[1]);
|
|
86663
87384
|
} else {
|
|
@@ -86665,20 +87386,20 @@ function positionAlongEdge(profile, edge, inward = false) {
|
|
|
86665
87386
|
}
|
|
86666
87387
|
}
|
|
86667
87388
|
function flatPanel(name, width, height, thickness, options) {
|
|
86668
|
-
|
|
86669
|
-
|
|
86670
|
-
|
|
86671
|
-
const
|
|
87389
|
+
requirePositive10(width, "width");
|
|
87390
|
+
requirePositive10(height, "height");
|
|
87391
|
+
requirePositive10(thickness, "thickness");
|
|
87392
|
+
const profile2 = rect(width, height);
|
|
86672
87393
|
const edges = /* @__PURE__ */ new Map([
|
|
86673
87394
|
["bottom", { name: "bottom", start: [0, 0], end: [width, 0], length: width, normal: [0, -1] }],
|
|
86674
87395
|
["right", { name: "right", start: [width, 0], end: [width, height], length: height, normal: [1, 0] }],
|
|
86675
87396
|
["top", { name: "top", start: [width, height], end: [0, height], length: width, normal: [0, 1] }],
|
|
86676
87397
|
["left", { name: "left", start: [0, height], end: [0, 0], length: height, normal: [-1, 0] }]
|
|
86677
87398
|
]);
|
|
86678
|
-
return new FlatPart(name,
|
|
87399
|
+
return new FlatPart(name, profile2, thickness, edges, options);
|
|
86679
87400
|
}
|
|
86680
|
-
function flatPart(name,
|
|
86681
|
-
|
|
87401
|
+
function flatPart(name, profile2, thickness, edges, options) {
|
|
87402
|
+
requirePositive10(thickness, "thickness");
|
|
86682
87403
|
const edgeMap = /* @__PURE__ */ new Map();
|
|
86683
87404
|
if (edges) {
|
|
86684
87405
|
for (const [eName, e] of Object.entries(edges)) {
|
|
@@ -86693,7 +87414,7 @@ function flatPart(name, profile, thickness, edges, options) {
|
|
|
86693
87414
|
edgeMap.set(eName, { name: eName, start: e.start, end: e.end, length: len2, normal: [nx, ny] });
|
|
86694
87415
|
}
|
|
86695
87416
|
}
|
|
86696
|
-
return new FlatPart(name,
|
|
87417
|
+
return new FlatPart(name, profile2, thickness, edgeMap, options);
|
|
86697
87418
|
}
|
|
86698
87419
|
function fingerJoint(partA, edgeNameA, partB, edgeNameB, options) {
|
|
86699
87420
|
const edgeA = partA.edge(edgeNameA);
|
|
@@ -86793,10 +87514,10 @@ function groupByMaterial(pieces) {
|
|
|
86793
87514
|
}
|
|
86794
87515
|
return groups;
|
|
86795
87516
|
}
|
|
86796
|
-
function createEmptySheet(sheetIndex,
|
|
87517
|
+
function createEmptySheet(sheetIndex, material2, sheetWidth, sheetHeight) {
|
|
86797
87518
|
return {
|
|
86798
87519
|
sheetIndex,
|
|
86799
|
-
material,
|
|
87520
|
+
material: material2,
|
|
86800
87521
|
pieces: [],
|
|
86801
87522
|
sheetWidth,
|
|
86802
87523
|
sheetHeight,
|
|
@@ -87160,7 +87881,7 @@ function packSingleSheetExact(pieces, sheetW, sheetH, kerf) {
|
|
|
87160
87881
|
usedArea: bestUsedArea
|
|
87161
87882
|
};
|
|
87162
87883
|
}
|
|
87163
|
-
function packMaterialGroupExact(pieces, sheetW, sheetH,
|
|
87884
|
+
function packMaterialGroupExact(pieces, sheetW, sheetH, material2, kerf) {
|
|
87164
87885
|
const sheets = [];
|
|
87165
87886
|
let remainingPieces = [...pieces];
|
|
87166
87887
|
while (remainingPieces.length > 0) {
|
|
@@ -87176,7 +87897,7 @@ function packMaterialGroupExact(pieces, sheetW, sheetH, material, kerf) {
|
|
|
87176
87897
|
continue;
|
|
87177
87898
|
}
|
|
87178
87899
|
sheets.push({
|
|
87179
|
-
...createEmptySheet(sheets.length,
|
|
87900
|
+
...createEmptySheet(sheets.length, material2, sheetW, sheetH),
|
|
87180
87901
|
pieces: searchResult.placedPieces.map((placedPiece) => ({ ...placedPiece.packed })),
|
|
87181
87902
|
usedArea: searchResult.usedArea
|
|
87182
87903
|
});
|
|
@@ -87185,10 +87906,10 @@ function packMaterialGroupExact(pieces, sheetW, sheetH, material, kerf) {
|
|
|
87185
87906
|
}
|
|
87186
87907
|
return sheets;
|
|
87187
87908
|
}
|
|
87188
|
-
function packMaterialGroup(pieces, sheetW, sheetH,
|
|
87909
|
+
function packMaterialGroup(pieces, sheetW, sheetH, material2, kerf) {
|
|
87189
87910
|
const sorted = [...pieces].sort((a, b) => b.width * b.height - a.width * a.height);
|
|
87190
87911
|
if (sorted.length <= EXACT_SHEET_SEARCH_MAX_PIECES) {
|
|
87191
|
-
const sheets = packMaterialGroupExact(sorted, sheetW, sheetH,
|
|
87912
|
+
const sheets = packMaterialGroupExact(sorted, sheetW, sheetH, material2, kerf);
|
|
87192
87913
|
for (const sheet of sheets) {
|
|
87193
87914
|
sheet.cuts = computeSheetCutSequence(sheet);
|
|
87194
87915
|
}
|
|
@@ -87304,8 +88025,8 @@ function computeCuttingLayout(entries, sheetWidth, sheetHeight, kerf = 0) {
|
|
|
87304
88025
|
const expanded = expandPieces(entries);
|
|
87305
88026
|
const groups = groupByMaterial(expanded);
|
|
87306
88027
|
const allSheets = [];
|
|
87307
|
-
for (const [
|
|
87308
|
-
const sheets = packMaterialGroup(pieces, sheetWidth, sheetHeight,
|
|
88028
|
+
for (const [material2, pieces] of groups) {
|
|
88029
|
+
const sheets = packMaterialGroup(pieces, sheetWidth, sheetHeight, material2, kerf);
|
|
87309
88030
|
allSheets.push(...sheets);
|
|
87310
88031
|
}
|
|
87311
88032
|
allSheets.forEach((s, i) => {
|
|
@@ -87639,13 +88360,13 @@ function renderSummaryPage(layout, sheetWidth, sheetHeight) {
|
|
|
87639
88360
|
arr.push(s);
|
|
87640
88361
|
}
|
|
87641
88362
|
cmd.push(commandSetFill([0.14, 0.14, 0.16]));
|
|
87642
|
-
for (const [
|
|
88363
|
+
for (const [material2, sheets] of byMaterial) {
|
|
87643
88364
|
const usedArea = sheets.reduce((s, sh) => s + sh.usedArea, 0);
|
|
87644
88365
|
const totalArea = sheets.length * sheetWidth * sheetHeight;
|
|
87645
88366
|
const waste = totalArea > 0 ? (totalArea - usedArea) / totalArea * 100 : 0;
|
|
87646
88367
|
const matCuts = sheets.reduce((s, sh) => s + sh.cuts.length, 0);
|
|
87647
88368
|
const matCutLen = sheets.reduce((s, sh) => s + sh.cuts.reduce((cs, c) => cs + c.lengthMm, 0), 0);
|
|
87648
|
-
cmd.push(commandText(
|
|
88369
|
+
cmd.push(commandText(material2, colMaterial + 4, tableY, 9));
|
|
87649
88370
|
cmd.push(commandText(String(sheets.length), colSheets + 4, tableY, 9));
|
|
87650
88371
|
cmd.push(commandText(`${matCuts} (${formatNumber2(matCutLen)} mm)`, colCuts + 4, tableY, 9));
|
|
87651
88372
|
cmd.push(commandText(formatNumber2(usedArea), colArea + 4, tableY, 9));
|
|
@@ -88225,8 +88946,8 @@ var LaserKit = class {
|
|
|
88225
88946
|
const pieces = [];
|
|
88226
88947
|
let idCounter = 0;
|
|
88227
88948
|
for (const { part, qty } of this._parts) {
|
|
88228
|
-
const
|
|
88229
|
-
const bounds = getSketchBounds(
|
|
88949
|
+
const profile2 = part.profile(this.kerf);
|
|
88950
|
+
const bounds = getSketchBounds(profile2);
|
|
88230
88951
|
const w = bounds.maxX - bounds.minX;
|
|
88231
88952
|
const h = bounds.maxY - bounds.minY;
|
|
88232
88953
|
idCounter++;
|
|
@@ -88244,8 +88965,8 @@ var LaserKit = class {
|
|
|
88244
88965
|
/** Bill of materials listing every part with dimensions. */
|
|
88245
88966
|
bom() {
|
|
88246
88967
|
return this._parts.map(({ part, qty }) => {
|
|
88247
|
-
const
|
|
88248
|
-
const bounds = getSketchBounds(
|
|
88968
|
+
const profile2 = part.profile(this.kerf);
|
|
88969
|
+
const bounds = getSketchBounds(profile2);
|
|
88249
88970
|
return {
|
|
88250
88971
|
partNumber: part.partNumber,
|
|
88251
88972
|
name: part.name,
|
|
@@ -88260,8 +88981,8 @@ var LaserKit = class {
|
|
|
88260
88981
|
partSvgs() {
|
|
88261
88982
|
const result = /* @__PURE__ */ new Map();
|
|
88262
88983
|
for (const { part } of this._parts) {
|
|
88263
|
-
const
|
|
88264
|
-
const svg = sketchToSvg(
|
|
88984
|
+
const profile2 = part.profile(this.kerf);
|
|
88985
|
+
const svg = sketchToSvg(profile2, { strokeWidth: 0.1, padding: 1 });
|
|
88265
88986
|
result.set(part.name, svg);
|
|
88266
88987
|
}
|
|
88267
88988
|
return result;
|
|
@@ -88275,13 +88996,13 @@ var LaserKit = class {
|
|
|
88275
88996
|
let maxCellH = 0;
|
|
88276
88997
|
const partData = [];
|
|
88277
88998
|
for (const { part, qty } of this._parts) {
|
|
88278
|
-
const
|
|
88279
|
-
const bounds = getSketchBounds(
|
|
88999
|
+
const profile2 = part.profile(this.kerf);
|
|
89000
|
+
const bounds = getSketchBounds(profile2);
|
|
88280
89001
|
const w = bounds.maxX - bounds.minX;
|
|
88281
89002
|
const h = bounds.maxY - bounds.minY;
|
|
88282
89003
|
maxCellW = Math.max(maxCellW, w);
|
|
88283
89004
|
maxCellH = Math.max(maxCellH, h);
|
|
88284
|
-
const polys =
|
|
89005
|
+
const polys = profile2.toPolygons();
|
|
88285
89006
|
const pathParts = [];
|
|
88286
89007
|
for (const loop of polys) {
|
|
88287
89008
|
if (loop.length < 2) continue;
|
|
@@ -88407,7 +89128,7 @@ var Laser = {
|
|
|
88407
89128
|
* @returns A new FlatPart with the given named edges.
|
|
88408
89129
|
* @category Laser Cutting
|
|
88409
89130
|
*/
|
|
88410
|
-
part: (name,
|
|
89131
|
+
part: (name, profile2, thickness, edges, options) => flatPart(name, profile2, thickness, edges, options),
|
|
88411
89132
|
/**
|
|
88412
89133
|
* Connect two parts with finger joints along the named edges.
|
|
88413
89134
|
*
|
|
@@ -88503,7 +89224,7 @@ var Laser = {
|
|
|
88503
89224
|
* @returns Full kerf width in mm, or `undefined`.
|
|
88504
89225
|
* @category Laser Cutting
|
|
88505
89226
|
*/
|
|
88506
|
-
lookupKerf: (
|
|
89227
|
+
lookupKerf: (material2, thickness, laserType) => lookupKerf(material2, thickness, laserType),
|
|
88507
89228
|
/**
|
|
88508
89229
|
* Common full-kerf values by material, thickness, and laser type.
|
|
88509
89230
|
*
|
|
@@ -89980,9 +90701,9 @@ var LOG_MAX_DEPTH = 4;
|
|
|
89980
90701
|
var LOG_MAX_ARRAY_ITEMS = 24;
|
|
89981
90702
|
var LOG_MAX_OBJECT_KEYS = 32;
|
|
89982
90703
|
var LOG_NUMBER_PRECISION = 4;
|
|
89983
|
-
function formatLogError(
|
|
89984
|
-
if (
|
|
89985
|
-
return String(
|
|
90704
|
+
function formatLogError(error2) {
|
|
90705
|
+
if (error2 instanceof Error) return `${error2.name}: ${error2.message}`;
|
|
90706
|
+
return String(error2);
|
|
89986
90707
|
}
|
|
89987
90708
|
function roundLogNumber(value) {
|
|
89988
90709
|
return Number(value.toFixed(LOG_NUMBER_PRECISION));
|
|
@@ -90008,28 +90729,28 @@ function summarizeShapeForLog(shape) {
|
|
|
90008
90729
|
if (min2.length === max4.length && min2.length > 0) {
|
|
90009
90730
|
summary.size = min2.map((value, index) => roundLogNumber(max4[index] - value));
|
|
90010
90731
|
}
|
|
90011
|
-
} catch (
|
|
90012
|
-
summary.bboxError = formatLogError(
|
|
90732
|
+
} catch (error2) {
|
|
90733
|
+
summary.bboxError = formatLogError(error2);
|
|
90013
90734
|
}
|
|
90014
90735
|
try {
|
|
90015
90736
|
summary.volume = roundLogNumber(shape.volume());
|
|
90016
|
-
} catch (
|
|
90017
|
-
summary.volumeError = formatLogError(
|
|
90737
|
+
} catch (error2) {
|
|
90738
|
+
summary.volumeError = formatLogError(error2);
|
|
90018
90739
|
}
|
|
90019
90740
|
try {
|
|
90020
90741
|
summary.surfaceArea = roundLogNumber(shape.surfaceArea());
|
|
90021
|
-
} catch (
|
|
90022
|
-
summary.surfaceAreaError = formatLogError(
|
|
90742
|
+
} catch (error2) {
|
|
90743
|
+
summary.surfaceAreaError = formatLogError(error2);
|
|
90023
90744
|
}
|
|
90024
90745
|
try {
|
|
90025
90746
|
summary.triangles = shape.numTri();
|
|
90026
|
-
} catch (
|
|
90027
|
-
summary.triangleError = formatLogError(
|
|
90747
|
+
} catch (error2) {
|
|
90748
|
+
summary.triangleError = formatLogError(error2);
|
|
90028
90749
|
}
|
|
90029
90750
|
try {
|
|
90030
90751
|
summary.isEmpty = shape.isEmpty();
|
|
90031
|
-
} catch (
|
|
90032
|
-
summary.isEmptyError = formatLogError(
|
|
90752
|
+
} catch (error2) {
|
|
90753
|
+
summary.isEmptyError = formatLogError(error2);
|
|
90033
90754
|
}
|
|
90034
90755
|
return summary;
|
|
90035
90756
|
}
|
|
@@ -90104,8 +90825,8 @@ function formatLogArg(value) {
|
|
|
90104
90825
|
const inspected = inspectForLog(value);
|
|
90105
90826
|
if (typeof inspected === "string") return inspected;
|
|
90106
90827
|
return JSON.stringify(inspected, null, 2);
|
|
90107
|
-
} catch (
|
|
90108
|
-
return `[Log serialization failed: ${formatLogError(
|
|
90828
|
+
} catch (error2) {
|
|
90829
|
+
return `[Log serialization failed: ${formatLogError(error2)}]`;
|
|
90109
90830
|
}
|
|
90110
90831
|
}
|
|
90111
90832
|
function makeSandboxConsole(collectedLogs, mirror) {
|
|
@@ -91401,6 +92122,9 @@ function attachAssemblySource(runResult, result) {
|
|
|
91401
92122
|
}
|
|
91402
92123
|
return runResult;
|
|
91403
92124
|
}
|
|
92125
|
+
function getRunResultAssemblySource(runResult) {
|
|
92126
|
+
return runResult[RUN_RESULT_ASSEMBLY_SOURCE] ?? null;
|
|
92127
|
+
}
|
|
91404
92128
|
var FORGE_RUNTIME_MODULE_SPECIFIERS = /* @__PURE__ */ new Set(["forgecad", "@forge/runtime", "@forgecad/runtime"]);
|
|
91405
92129
|
var MAX_PERSISTENT_COMPILED_FILES = 256;
|
|
91406
92130
|
var persistentCompiledFiles = /* @__PURE__ */ new Map();
|
|
@@ -91523,12 +92247,12 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91523
92247
|
verts: result.numVert()
|
|
91524
92248
|
});
|
|
91525
92249
|
return result;
|
|
91526
|
-
} catch (
|
|
92250
|
+
} catch (error2) {
|
|
91527
92251
|
logImportTrace(_collectedLogs, fileName, scope, options, "Import.svgSketch", resolvedPath, "error", {
|
|
91528
92252
|
requested: name,
|
|
91529
|
-
error: formatLogError(
|
|
92253
|
+
error: formatLogError(error2)
|
|
91530
92254
|
});
|
|
91531
|
-
throw
|
|
92255
|
+
throw error2;
|
|
91532
92256
|
}
|
|
91533
92257
|
};
|
|
91534
92258
|
const importDxfSketch = (name, optionsArg) => {
|
|
@@ -91550,12 +92274,12 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91550
92274
|
verts: result.numVert()
|
|
91551
92275
|
});
|
|
91552
92276
|
return result;
|
|
91553
|
-
} catch (
|
|
92277
|
+
} catch (error2) {
|
|
91554
92278
|
logImportTrace(_collectedLogs, fileName, scope, options, "Import.dxfSketch", resolvedPath, "error", {
|
|
91555
92279
|
requested: name,
|
|
91556
|
-
error: formatLogError(
|
|
92280
|
+
error: formatLogError(error2)
|
|
91557
92281
|
});
|
|
91558
|
-
throw
|
|
92282
|
+
throw error2;
|
|
91559
92283
|
}
|
|
91560
92284
|
};
|
|
91561
92285
|
const importMesh = (name, meshOptions) => {
|
|
@@ -91569,6 +92293,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91569
92293
|
const ext = resolvedPath.split(".").pop() ?? "";
|
|
91570
92294
|
throw new Error(`Import.mesh("${name}"): unsupported format ".${ext}". Supported: .stl, .obj, .3mf`);
|
|
91571
92295
|
}
|
|
92296
|
+
const sourceFrameStep = sourceFrameTransformStep("Import.mesh", name, meshOptions?.sourceFrame);
|
|
91572
92297
|
if (!options.readBinaryFile) {
|
|
91573
92298
|
throw new Error(
|
|
91574
92299
|
`Import.mesh("${name}"): binary file reading is not available in this environment. Provide a readBinaryFile callback in RunScriptOptions.`
|
|
@@ -91603,6 +92328,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91603
92328
|
...object ? { object } : {},
|
|
91604
92329
|
fileData
|
|
91605
92330
|
};
|
|
92331
|
+
if (sourceFrameStep) plan = appendShapeCompileTransform(plan, sourceFrameStep);
|
|
91606
92332
|
if (meshOptions?.scale != null && meshOptions.scale !== 1) {
|
|
91607
92333
|
const s = meshOptions.scale;
|
|
91608
92334
|
if (!Number.isFinite(s) || s <= 0) {
|
|
@@ -91626,7 +92352,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91626
92352
|
}
|
|
91627
92353
|
return centerImportedMesh(buildImportedMeshShape());
|
|
91628
92354
|
};
|
|
91629
|
-
const importStep = (name) => {
|
|
92355
|
+
const importStep = (name, stepOptions) => {
|
|
91630
92356
|
if (typeof name !== "string" || name.trim().length === 0) {
|
|
91631
92357
|
throw new Error("Import.step() requires a non-empty file path string");
|
|
91632
92358
|
}
|
|
@@ -91636,13 +92362,14 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91636
92362
|
if (ext !== "step" && ext !== "stp") {
|
|
91637
92363
|
throw new Error(`Import.step("${name}"): unsupported extension ".${ext}". Expected .step or .stp`);
|
|
91638
92364
|
}
|
|
92365
|
+
sourceFrameTransformStep("Import.step", name, stepOptions?.sourceFrame);
|
|
91639
92366
|
if (!options.readBinaryFile) {
|
|
91640
92367
|
throw new Error(
|
|
91641
92368
|
`Import.step("${name}"): binary file reading is not available in this environment. Provide a readBinaryFile callback in RunScriptOptions.`
|
|
91642
92369
|
);
|
|
91643
92370
|
}
|
|
91644
92371
|
const fileData = options.readBinaryFile(resolvedPath);
|
|
91645
|
-
return importStepFromBuffer(fileData, name);
|
|
92372
|
+
return importStepFromBuffer(fileData, name, stepOptions);
|
|
91646
92373
|
};
|
|
91647
92374
|
const Import = {
|
|
91648
92375
|
dxfSketch: importDxfSketch,
|
|
@@ -91867,6 +92594,7 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91867
92594
|
bom,
|
|
91868
92595
|
sheetStock,
|
|
91869
92596
|
robotExport,
|
|
92597
|
+
Sim,
|
|
91870
92598
|
group,
|
|
91871
92599
|
ShapeGroup,
|
|
91872
92600
|
console: sandboxConsole,
|
|
@@ -91983,13 +92711,13 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
91983
92711
|
cached: false
|
|
91984
92712
|
});
|
|
91985
92713
|
return finalExports;
|
|
91986
|
-
} catch (
|
|
92714
|
+
} catch (error2) {
|
|
91987
92715
|
options.moduleCache.delete(cacheKey3);
|
|
91988
92716
|
logImportTrace(_collectedLogs, fileName, scope, options, "require", resolvedPath, "error", {
|
|
91989
92717
|
requested: normalizedRequested,
|
|
91990
|
-
error: formatLogError(
|
|
92718
|
+
error: formatLogError(error2)
|
|
91991
92719
|
});
|
|
91992
|
-
throw
|
|
92720
|
+
throw error2;
|
|
91993
92721
|
} finally {
|
|
91994
92722
|
restoreJointsView(savedJointsView);
|
|
91995
92723
|
}
|
|
@@ -92022,13 +92750,13 @@ function executeFile(code, fileName, allFiles, visited, scope = {}, options, exe
|
|
|
92022
92750
|
cached: false
|
|
92023
92751
|
});
|
|
92024
92752
|
return finalExports;
|
|
92025
|
-
} catch (
|
|
92753
|
+
} catch (error2) {
|
|
92026
92754
|
options.moduleCache.delete(cacheKey2);
|
|
92027
92755
|
logImportTrace(_collectedLogs, fileName, scope, options, "require", resolvedPath, "error", {
|
|
92028
92756
|
requested: normalizedRequested,
|
|
92029
|
-
error: formatLogError(
|
|
92757
|
+
error: formatLogError(error2)
|
|
92030
92758
|
});
|
|
92031
|
-
throw
|
|
92759
|
+
throw error2;
|
|
92032
92760
|
}
|
|
92033
92761
|
};
|
|
92034
92762
|
const bindingNames = Object.keys(runtimeBindings).filter((name) => name !== "showLabels");
|
|
@@ -92552,8 +93280,8 @@ function runDirectCliMain(metaUrl, sourceEntryPath, run) {
|
|
|
92552
93280
|
Promise.resolve().then(() => {
|
|
92553
93281
|
setActiveBackend(CLI_DEFAULT_BACKEND);
|
|
92554
93282
|
return run();
|
|
92555
|
-
}).catch((
|
|
92556
|
-
const message =
|
|
93283
|
+
}).catch((error2) => {
|
|
93284
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
92557
93285
|
console.error(message);
|
|
92558
93286
|
process.exit(1);
|
|
92559
93287
|
});
|
|
@@ -92766,23 +93494,23 @@ function collectFilesRecursive(dir, root) {
|
|
|
92766
93494
|
return result;
|
|
92767
93495
|
}
|
|
92768
93496
|
var MANIFEST_FILE = "forgecad.json";
|
|
92769
|
-
function errorMessage(
|
|
92770
|
-
return
|
|
93497
|
+
function errorMessage(error2) {
|
|
93498
|
+
return error2 instanceof Error ? error2.message : String(error2);
|
|
92771
93499
|
}
|
|
92772
|
-
function errorPath(
|
|
92773
|
-
if (!
|
|
92774
|
-
const path2 =
|
|
93500
|
+
function errorPath(error2) {
|
|
93501
|
+
if (!error2 || typeof error2 !== "object" || !("path" in error2)) return null;
|
|
93502
|
+
const path2 = error2.path;
|
|
92775
93503
|
return typeof path2 === "string" && path2.length > 0 ? path2 : null;
|
|
92776
93504
|
}
|
|
92777
|
-
function formatProjectCollectionError(scriptPath, projectRoot,
|
|
92778
|
-
const failedPath = errorPath(
|
|
93505
|
+
function formatProjectCollectionError(scriptPath, projectRoot, error2) {
|
|
93506
|
+
const failedPath = errorPath(error2);
|
|
92779
93507
|
const rootNote = resolve2(projectRoot) === resolve2(homedir()) ? "The inferred project root is your home directory. Move the model into a dedicated folder or put forgecad.json in the intended project folder." : "ForgeCAD scans the project root for .forge.js, .js, .svg, and .dxf files so local require/import paths work.";
|
|
92780
93508
|
return [
|
|
92781
93509
|
"ForgeCAD could not collect local project files.",
|
|
92782
93510
|
` Input file: ${resolve2(scriptPath)}`,
|
|
92783
93511
|
` Project root used for import discovery: ${projectRoot}`,
|
|
92784
93512
|
failedPath ? ` Failed path: ${failedPath}` : null,
|
|
92785
|
-
` Filesystem error: ${errorMessage(
|
|
93513
|
+
` Filesystem error: ${errorMessage(error2)}`,
|
|
92786
93514
|
"",
|
|
92787
93515
|
rootNote,
|
|
92788
93516
|
"If this root is too broad, run the command from a dedicated ForgeCAD folder or initialize the folder with `forgecad project init`."
|
|
@@ -92807,8 +93535,8 @@ function collectProjectFiles(scriptPath) {
|
|
|
92807
93535
|
let allFiles;
|
|
92808
93536
|
try {
|
|
92809
93537
|
allFiles = collectFilesRecursive(root, root);
|
|
92810
|
-
} catch (
|
|
92811
|
-
throw Object.assign(new Error(formatProjectCollectionError(absScript, root,
|
|
93538
|
+
} catch (error2) {
|
|
93539
|
+
throw Object.assign(new Error(formatProjectCollectionError(absScript, root, error2)), { cause: error2 });
|
|
92812
93540
|
}
|
|
92813
93541
|
const fileName = relative(root, absScript);
|
|
92814
93542
|
const readBinaryFile = (relativePath) => {
|
|
@@ -92924,10 +93652,10 @@ function inspectShapeObject(object, compiled) {
|
|
|
92924
93652
|
summary: summarizeShapeRuntime(lowerShapeCompilePlanToShapeBackend(report.compilePlan, getManifoldWasm())),
|
|
92925
93653
|
error: null
|
|
92926
93654
|
};
|
|
92927
|
-
} catch (
|
|
93655
|
+
} catch (error2) {
|
|
92928
93656
|
return {
|
|
92929
93657
|
summary: null,
|
|
92930
|
-
error:
|
|
93658
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
92931
93659
|
};
|
|
92932
93660
|
}
|
|
92933
93661
|
})();
|
|
@@ -92965,10 +93693,10 @@ function inspectSketchObject(object, compiled) {
|
|
|
92965
93693
|
summary: summarizeCrossSectionRuntime(lowerProfileCompilePlanToCrossSection(compilePlan, getManifoldWasm())),
|
|
92966
93694
|
error: null
|
|
92967
93695
|
};
|
|
92968
|
-
} catch (
|
|
93696
|
+
} catch (error2) {
|
|
92969
93697
|
return {
|
|
92970
93698
|
summary: null,
|
|
92971
|
-
error:
|
|
93699
|
+
error: error2 instanceof Error ? error2.message : String(error2)
|
|
92972
93700
|
};
|
|
92973
93701
|
}
|
|
92974
93702
|
})();
|
|
@@ -93102,6 +93830,10 @@ export {
|
|
|
93102
93830
|
mapDimensionsToOwnerIds,
|
|
93103
93831
|
generateReportPdf,
|
|
93104
93832
|
getCollectedRobotExport,
|
|
93833
|
+
collectSimulationModel,
|
|
93834
|
+
validateSimulationModel,
|
|
93835
|
+
buildSimReadyManifest,
|
|
93836
|
+
simReadyManifestJson,
|
|
93105
93837
|
Rectangle2D,
|
|
93106
93838
|
filletTrackedEdge,
|
|
93107
93839
|
chamferTrackedEdge,
|
|
@@ -93137,6 +93869,7 @@ export {
|
|
|
93137
93869
|
getDesignTraceInputs,
|
|
93138
93870
|
buildDesignTraceNeighborhood,
|
|
93139
93871
|
formatDesignTraceAnchor,
|
|
93872
|
+
getRunResultAssemblySource,
|
|
93140
93873
|
runScript,
|
|
93141
93874
|
generateCuttingLayoutDxf,
|
|
93142
93875
|
init,
|