forgecad 0.9.7 → 0.9.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/assets/{AdminPage-DX0mpSZT.js → AdminPage-CXaVLMiV.js} +1 -1
- package/dist/assets/{BlogPage-CI_P0_Pf.js → BlogPage-Crpr3JjH.js} +1 -1
- package/dist/assets/{DocsPage-DLhIIZyJ.js → DocsPage-CNBKuitP.js} +2 -2
- package/dist/assets/{EditorApp-DfFT2Dn8.css → EditorApp-D11wL4Qn.css} +51 -0
- package/dist/assets/{EditorApp-BujZvuwX.js → EditorApp-DVMnXOmO.js} +151 -9
- package/dist/assets/{EmbedViewer-0S0qXKog.js → EmbedViewer-KXFLSnpo.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-O_yMtAri.js → LandingPageProofDriven-2q2sn7aW.js} +1 -1
- package/dist/assets/{PricingPage-DGkX3Ahr.js → PricingPage-CVvgdv0i.js} +1 -1
- package/dist/assets/{SettingsPage-DBsqTB_y.js → SettingsPage-BVj1FtEv.js} +1 -1
- package/dist/assets/__vite-browser-external-Dhvy_jtL.js +4 -0
- package/dist/assets/{app-BE2nD6Yz.js → app-Dn4EwHhN.js} +707 -458
- package/dist/assets/cli/{render-iP9qh475.js → render-BI3gLMXz.js} +1011 -145
- package/dist/assets/constructionHistoryWorker-z9_LGiRd.js +42984 -0
- package/dist/assets/{evalWorker-Ds5U4xtN.js → evalWorker-CtO7GsJR.js} +42 -9
- package/dist/assets/{inspectWorker-Dll4eVyD.js → inspectWorker-BZ2CkQZr.js} +785 -111
- package/dist/assets/{manifold-sJ-axdXM.js → manifold-BVi4_OeB.js} +1 -1
- package/dist/assets/{manifold-DjYsd7A_.js → manifold-C6-sZYQN.js} +2 -2
- package/dist/assets/manifold-Cp_dCC7i.js +3018 -0
- package/dist/assets/{manifold-Bk26ViCr.js → manifold-DAzn2Fsa.js} +1 -1
- package/dist/assets/{renderSceneState-Bngp5MrQ.js → renderSceneState-BIvOkPK3.js} +1 -1
- package/dist/assets/{reportWorker-CU8RZ4O0.js → reportWorker-Bz9tGiHb.js} +42 -9
- package/dist/assets/{sectionPlaneMath-BdTjyVfs.js → scalar-sampling-budget-iBAeF8RM.js} +483 -71
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +1 -1
- package/dist/docs-raw/CLI.md +10 -10
- package/dist/docs-raw/coding-best-practices.md +1 -1
- package/dist/docs-raw/guides/inspection-bundles.md +77 -19
- package/dist/docs-raw/guides/skill-maintenance.md +1 -1
- package/dist/docs-raw/runbook.md +2 -2
- package/dist/docs-raw/skills/forgecad-make-a-model.md +11 -0
- package/dist/docs-raw/skills/forgecad-render-inspect.md +12 -6
- package/dist/docs-raw/skills/index.md +1 -1
- package/dist/index.html +1 -1
- package/dist/sitemap.xml +6 -6
- package/dist-cli/forgecad.js +596 -354
- package/dist-cli/forgecad.js.map +1 -1
- package/dist-skill/CONTEXT.md +77 -19
- package/dist-skill/docs/CLI.md +10 -10
- package/dist-skill/docs/guides/inspection-bundles.md +77 -19
- package/dist-skill/docs-dev/CLI.md +10 -10
- package/dist-skill/docs-dev/coding-best-practices.md +1 -1
- package/dist-skill/docs-dev/guides/inspection-bundles.md +77 -19
- package/dist-skill/docs-dev/guides/skill-maintenance.md +1 -1
- package/dist-skill/library/forgecad-make-a-model/SKILL.md +11 -0
- package/dist-skill/library/forgecad-render-inspect/SKILL.md +12 -6
- package/package.json +6 -3
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
-
import { D as DoubleSide,
|
|
5
|
-
import { m as mergeViewportRenderSceneStates, p as parseRenderSceneCliSpec } from "../renderSceneState-
|
|
4
|
+
import { D as DoubleSide, bT as initSolverWasm, bS as initKernel, S as Scene, bU as BoxGeometry, bo as MeshStandardMaterial, a4 as BackSide, b0 as PointLight, M as Mesh, aa as MeshBasicMaterial, bV as localAabbPlaneRelation, h as Vector2, bW as ShapeUtils, bX as aabbGaps, bY as aabbInteriorOverlaps, bZ as aabbOverlapVolume, b_ as AabbSpatialIndex, g as Vector3, R as Raycaster, aU as BufferAttribute, a0 as MathUtils, G as Box3, e as Color, aC as resolveForgeRenderStyle, bb as getRenderStylePreset, ax as setParamOverrides, b7 as runScript, b$ as Group, b3 as shapeToGeometry, b8 as MeshPhysicalMaterial, bc as AdditiveBlending, aH as LineBasicMaterial, b9 as LineSegments, aG as BufferGeometry, P as PerspectiveCamera, k as ShaderMaterial, bi as ZEBRA_STRIPE_FRAGMENT_SHADER, bj as ZEBRA_STRIPE_VERTEX_SHADER, bd as ZEBRA_STRIPE_SOFTNESS, be as ZEBRA_STRIPE_SCALE, bf as ZEBRA_LIGHT_COLOR, bg as ZEBRA_DARK_COLOR, bh as ZEBRA_ACCENT_COLOR, ba as geometryWithVisibleVertexColors, c0 as intersectWithPlane, c1 as setActiveBackend, W as WebGLRenderer, A as ACESFilmicToneMapping, c as SRGBColorSpace, c2 as parseCameraCliSpec, c3 as PMREMGenerator, aV as CanvasTexture, aW as Object3D, aX as FogExp2, aY as Fog, aZ as AmbientLight, b1 as DirectionalLight, a_ as HemisphereLight, bK as findJointAnimationClip, p as Plane, bk as SURFACE_FIELD_FRAGMENT_SHADER, bl as SURFACE_FIELD_VERTEX_SHADER, Y as Vector4, $ as Matrix4, br as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bq as buildSdfRaymarchFragmentShader, O as OrthographicCamera, bL as resolveJointAnimation, bM as resolveJointViewValues, c4 as PointsMaterial, c5 as Points, b2 as analyzeCollisionIntersections, c6 as serializeCollisionFinding, c7 as worldAuthorPlaneToLocal, a$ as SpotLight, bR as resolveScalarSceneSampleBudget } from "../scalar-sampling-budget-iBAeF8RM.js";
|
|
5
|
+
import { m as mergeViewportRenderSceneStates, p as parseRenderSceneCliSpec } from "../renderSceneState-BIvOkPK3.js";
|
|
6
6
|
const CAD_MATERIAL_PROPS = {
|
|
7
7
|
color: 6003669,
|
|
8
8
|
metalness: 0.05,
|
|
@@ -457,13 +457,333 @@ function computeMeshSectionCap(mesh, planeInput) {
|
|
|
457
457
|
warnings: stitched.warnings.length > 0 ? stitched.warnings : void 0
|
|
458
458
|
};
|
|
459
459
|
}
|
|
460
|
+
const AXIS_NAMES = ["x", "y", "z"];
|
|
461
|
+
function nearestBoundaryGap(a, b, axis) {
|
|
462
|
+
return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
|
|
463
|
+
}
|
|
464
|
+
function hasPositiveGap(gaps) {
|
|
465
|
+
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
466
|
+
}
|
|
467
|
+
function contactFromBBoxes(a, b, tolerance) {
|
|
468
|
+
const gaps = aabbGaps(a, b);
|
|
469
|
+
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
470
|
+
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
471
|
+
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
472
|
+
if (separatedAxes.length > 0) {
|
|
473
|
+
const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
|
|
474
|
+
return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
|
|
475
|
+
}
|
|
476
|
+
const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
|
|
477
|
+
axis,
|
|
478
|
+
axisName,
|
|
479
|
+
gap: nearestBoundaryGap(a, b, axis)
|
|
480
|
+
})).filter((entry) => entry.gap <= tolerance);
|
|
481
|
+
if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
|
|
482
|
+
const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
|
|
483
|
+
return { touching: true, gap: nearest.gap, axis: nearest.axisName };
|
|
484
|
+
}
|
|
485
|
+
function isFiniteTriangle(positions, offset) {
|
|
486
|
+
for (let index = 0; index < 9; index += 1) {
|
|
487
|
+
if (!Number.isFinite(positions[offset + index])) return false;
|
|
488
|
+
}
|
|
489
|
+
return true;
|
|
490
|
+
}
|
|
491
|
+
function triangleRef(positions, offset) {
|
|
492
|
+
if (!isFiniteTriangle(positions, offset)) return null;
|
|
493
|
+
const ax = positions[offset];
|
|
494
|
+
const ay = positions[offset + 1];
|
|
495
|
+
const az = positions[offset + 2];
|
|
496
|
+
const bx = positions[offset + 3];
|
|
497
|
+
const by = positions[offset + 4];
|
|
498
|
+
const bz = positions[offset + 5];
|
|
499
|
+
const cx = positions[offset + 6];
|
|
500
|
+
const cy = positions[offset + 7];
|
|
501
|
+
const cz = positions[offset + 8];
|
|
502
|
+
return {
|
|
503
|
+
offset,
|
|
504
|
+
min: [Math.min(ax, bx, cx), Math.min(ay, by, cy), Math.min(az, bz, cz)],
|
|
505
|
+
max: [Math.max(ax, bx, cx), Math.max(ay, by, cy), Math.max(az, bz, cz)]
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function meshContactDataFor(entry) {
|
|
509
|
+
const positions = entry.positions;
|
|
510
|
+
if (!positions || positions.length < 9) return null;
|
|
511
|
+
const triangleCount = Math.floor(positions.length / 9);
|
|
512
|
+
const triangles = [];
|
|
513
|
+
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
514
|
+
const triangle = triangleRef(positions, triangleIndex * 9);
|
|
515
|
+
if (triangle) triangles.push(triangle);
|
|
516
|
+
}
|
|
517
|
+
if (triangles.length === 0) return null;
|
|
518
|
+
return {
|
|
519
|
+
positions,
|
|
520
|
+
vertexCount: triangleCount * 3,
|
|
521
|
+
triangles
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
function distSq(px, py, pz, qx, qy, qz) {
|
|
525
|
+
const dx = px - qx;
|
|
526
|
+
const dy = py - qy;
|
|
527
|
+
const dz = pz - qz;
|
|
528
|
+
return dx * dx + dy * dy + dz * dz;
|
|
529
|
+
}
|
|
530
|
+
function pointSegmentDistanceSq(point, start, end) {
|
|
531
|
+
const dx = end[0] - start[0];
|
|
532
|
+
const dy = end[1] - start[1];
|
|
533
|
+
const dz = end[2] - start[2];
|
|
534
|
+
const lengthSq = dx * dx + dy * dy + dz * dz;
|
|
535
|
+
if (lengthSq <= 1e-20) return distSq(point[0], point[1], point[2], start[0], start[1], start[2]);
|
|
536
|
+
const t = Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dy + (point[2] - start[2]) * dz) / lengthSq));
|
|
537
|
+
return distSq(point[0], point[1], point[2], start[0] + t * dx, start[1] + t * dy, start[2] + t * dz);
|
|
538
|
+
}
|
|
539
|
+
function pointTriangleDistanceSq(px, py, pz, ax, ay, az, bx, by, bz, cx, cy, cz) {
|
|
540
|
+
const abx = bx - ax;
|
|
541
|
+
const aby = by - ay;
|
|
542
|
+
const abz = bz - az;
|
|
543
|
+
const acx = cx - ax;
|
|
544
|
+
const acy = cy - ay;
|
|
545
|
+
const acz = cz - az;
|
|
546
|
+
const apx = px - ax;
|
|
547
|
+
const apy = py - ay;
|
|
548
|
+
const apz = pz - az;
|
|
549
|
+
const d1 = abx * apx + aby * apy + abz * apz;
|
|
550
|
+
const d2 = acx * apx + acy * apy + acz * apz;
|
|
551
|
+
if (d1 <= 0 && d2 <= 0) return distSq(px, py, pz, ax, ay, az);
|
|
552
|
+
const bpx = px - bx;
|
|
553
|
+
const bpy = py - by;
|
|
554
|
+
const bpz = pz - bz;
|
|
555
|
+
const d3 = abx * bpx + aby * bpy + abz * bpz;
|
|
556
|
+
const d4 = acx * bpx + acy * bpy + acz * bpz;
|
|
557
|
+
if (d3 >= 0 && d4 <= d3) return distSq(px, py, pz, bx, by, bz);
|
|
558
|
+
const vc = d1 * d4 - d3 * d2;
|
|
559
|
+
if (vc <= 0 && d1 >= 0 && d3 <= 0) {
|
|
560
|
+
const v2 = d1 / (d1 - d3);
|
|
561
|
+
return distSq(px, py, pz, ax + v2 * abx, ay + v2 * aby, az + v2 * abz);
|
|
562
|
+
}
|
|
563
|
+
const cpx = px - cx;
|
|
564
|
+
const cpy = py - cy;
|
|
565
|
+
const cpz = pz - cz;
|
|
566
|
+
const d5 = abx * cpx + aby * cpy + abz * cpz;
|
|
567
|
+
const d6 = acx * cpx + acy * cpy + acz * cpz;
|
|
568
|
+
if (d6 >= 0 && d5 <= d6) return distSq(px, py, pz, cx, cy, cz);
|
|
569
|
+
const vb = d5 * d2 - d1 * d6;
|
|
570
|
+
if (vb <= 0 && d2 >= 0 && d6 <= 0) {
|
|
571
|
+
const w2 = d2 / (d2 - d6);
|
|
572
|
+
return distSq(px, py, pz, ax + w2 * acx, ay + w2 * acy, az + w2 * acz);
|
|
573
|
+
}
|
|
574
|
+
const va = d3 * d6 - d5 * d4;
|
|
575
|
+
if (va <= 0 && d4 - d3 >= 0 && d5 - d6 >= 0) {
|
|
576
|
+
const bcx = cx - bx;
|
|
577
|
+
const bcy = cy - by;
|
|
578
|
+
const bcz = cz - bz;
|
|
579
|
+
const w2 = (d4 - d3) / (d4 - d3 + d5 - d6);
|
|
580
|
+
return distSq(px, py, pz, bx + w2 * bcx, by + w2 * bcy, bz + w2 * bcz);
|
|
581
|
+
}
|
|
582
|
+
const barycentricTotal = va + vb + vc;
|
|
583
|
+
if (!Number.isFinite(barycentricTotal) || Math.abs(barycentricTotal) < 1e-20) {
|
|
584
|
+
return Math.min(distSq(px, py, pz, ax, ay, az), distSq(px, py, pz, bx, by, bz), distSq(px, py, pz, cx, cy, cz));
|
|
585
|
+
}
|
|
586
|
+
const denom = 1 / barycentricTotal;
|
|
587
|
+
const v = vb * denom;
|
|
588
|
+
const w = vc * denom;
|
|
589
|
+
return distSq(px, py, pz, ax + abx * v + acx * w, ay + aby * v + acy * w, az + abz * v + acz * w);
|
|
590
|
+
}
|
|
591
|
+
function segmentSegmentDistanceSq(a0, a1, b0, b1) {
|
|
592
|
+
const ux = a1[0] - a0[0];
|
|
593
|
+
const uy = a1[1] - a0[1];
|
|
594
|
+
const uz = a1[2] - a0[2];
|
|
595
|
+
const vx = b1[0] - b0[0];
|
|
596
|
+
const vy = b1[1] - b0[1];
|
|
597
|
+
const vz = b1[2] - b0[2];
|
|
598
|
+
const wx = a0[0] - b0[0];
|
|
599
|
+
const wy = a0[1] - b0[1];
|
|
600
|
+
const wz = a0[2] - b0[2];
|
|
601
|
+
const a = ux * ux + uy * uy + uz * uz;
|
|
602
|
+
const b = ux * vx + uy * vy + uz * vz;
|
|
603
|
+
const c = vx * vx + vy * vy + vz * vz;
|
|
604
|
+
const d = ux * wx + uy * wy + uz * wz;
|
|
605
|
+
const e = vx * wx + vy * wy + vz * wz;
|
|
606
|
+
const denom = a * c - b * b;
|
|
607
|
+
let sNumerator = denom;
|
|
608
|
+
let sDenominator = denom;
|
|
609
|
+
let tNumerator = denom;
|
|
610
|
+
let tDenominator = denom;
|
|
611
|
+
if (a <= 1e-20) return pointSegmentDistanceSq(a0, b0, b1);
|
|
612
|
+
if (c <= 1e-20) return pointSegmentDistanceSq(b0, a0, a1);
|
|
613
|
+
if (denom < 1e-20) {
|
|
614
|
+
sNumerator = 0;
|
|
615
|
+
sDenominator = 1;
|
|
616
|
+
tNumerator = e;
|
|
617
|
+
tDenominator = c;
|
|
618
|
+
} else {
|
|
619
|
+
sNumerator = b * e - c * d;
|
|
620
|
+
tNumerator = a * e - b * d;
|
|
621
|
+
if (sNumerator < 0) {
|
|
622
|
+
sNumerator = 0;
|
|
623
|
+
tNumerator = e;
|
|
624
|
+
tDenominator = c;
|
|
625
|
+
} else if (sNumerator > sDenominator) {
|
|
626
|
+
sNumerator = sDenominator;
|
|
627
|
+
tNumerator = e + b;
|
|
628
|
+
tDenominator = c;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (tNumerator < 0) {
|
|
632
|
+
tNumerator = 0;
|
|
633
|
+
if (-d < 0) {
|
|
634
|
+
sNumerator = 0;
|
|
635
|
+
} else if (-d > a) {
|
|
636
|
+
sNumerator = sDenominator;
|
|
637
|
+
} else {
|
|
638
|
+
sNumerator = -d;
|
|
639
|
+
sDenominator = a;
|
|
640
|
+
}
|
|
641
|
+
} else if (tNumerator > tDenominator) {
|
|
642
|
+
tNumerator = tDenominator;
|
|
643
|
+
if (-d + b < 0) {
|
|
644
|
+
sNumerator = 0;
|
|
645
|
+
} else if (-d + b > a) {
|
|
646
|
+
sNumerator = sDenominator;
|
|
647
|
+
} else {
|
|
648
|
+
sNumerator = -d + b;
|
|
649
|
+
sDenominator = a;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
const s = Math.abs(sNumerator) < 1e-20 ? 0 : sNumerator / sDenominator;
|
|
653
|
+
const t = Math.abs(tNumerator) < 1e-20 ? 0 : tNumerator / tDenominator;
|
|
654
|
+
const dx = wx + s * ux - t * vx;
|
|
655
|
+
const dy = wy + s * uy - t * vy;
|
|
656
|
+
const dz = wz + s * uz - t * vz;
|
|
657
|
+
return dx * dx + dy * dy + dz * dz;
|
|
658
|
+
}
|
|
659
|
+
function trianglePoint(positions, triangle, corner) {
|
|
660
|
+
const offset = triangle.offset + corner * 3;
|
|
661
|
+
return [positions[offset], positions[offset + 1], positions[offset + 2]];
|
|
662
|
+
}
|
|
663
|
+
function bboxDistanceSq(a, b) {
|
|
664
|
+
let total = 0;
|
|
665
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
666
|
+
const gap = Math.max(0, a.min[axis] - b.max[axis], b.min[axis] - a.max[axis]);
|
|
667
|
+
total += gap * gap;
|
|
668
|
+
}
|
|
669
|
+
return total;
|
|
670
|
+
}
|
|
671
|
+
function triangleDistanceSq(aPositions, a, bPositions, b) {
|
|
672
|
+
const a0 = trianglePoint(aPositions, a, 0);
|
|
673
|
+
const a1 = trianglePoint(aPositions, a, 1);
|
|
674
|
+
const a2 = trianglePoint(aPositions, a, 2);
|
|
675
|
+
const b0 = trianglePoint(bPositions, b, 0);
|
|
676
|
+
const b1 = trianglePoint(bPositions, b, 1);
|
|
677
|
+
const b2 = trianglePoint(bPositions, b, 2);
|
|
678
|
+
return Math.min(
|
|
679
|
+
pointTriangleDistanceSq(a0[0], a0[1], a0[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
|
|
680
|
+
pointTriangleDistanceSq(a1[0], a1[1], a1[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
|
|
681
|
+
pointTriangleDistanceSq(a2[0], a2[1], a2[2], b0[0], b0[1], b0[2], b1[0], b1[1], b1[2], b2[0], b2[1], b2[2]),
|
|
682
|
+
pointTriangleDistanceSq(b0[0], b0[1], b0[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
683
|
+
pointTriangleDistanceSq(b1[0], b1[1], b1[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
684
|
+
pointTriangleDistanceSq(b2[0], b2[1], b2[2], a0[0], a0[1], a0[2], a1[0], a1[1], a1[2], a2[0], a2[1], a2[2]),
|
|
685
|
+
segmentSegmentDistanceSq(a0, a1, b0, b1),
|
|
686
|
+
segmentSegmentDistanceSq(a0, a1, b1, b2),
|
|
687
|
+
segmentSegmentDistanceSq(a0, a1, b2, b0),
|
|
688
|
+
segmentSegmentDistanceSq(a1, a2, b0, b1),
|
|
689
|
+
segmentSegmentDistanceSq(a1, a2, b1, b2),
|
|
690
|
+
segmentSegmentDistanceSq(a1, a2, b2, b0),
|
|
691
|
+
segmentSegmentDistanceSq(a2, a0, b0, b1),
|
|
692
|
+
segmentSegmentDistanceSq(a2, a0, b1, b2),
|
|
693
|
+
segmentSegmentDistanceSq(a2, a0, b2, b0)
|
|
694
|
+
);
|
|
695
|
+
}
|
|
696
|
+
function meshEntriesContactDistance(a, b, tolerance) {
|
|
697
|
+
const toleranceSq = tolerance * tolerance;
|
|
698
|
+
let bestDistanceSq = Infinity;
|
|
699
|
+
for (const aTriangle of a.triangles) {
|
|
700
|
+
for (const bTriangle of b.triangles) {
|
|
701
|
+
if (bboxDistanceSq(aTriangle, bTriangle) > toleranceSq) continue;
|
|
702
|
+
const distanceSq = triangleDistanceSq(a.positions, aTriangle, b.positions, bTriangle);
|
|
703
|
+
if (distanceSq > toleranceSq) continue;
|
|
704
|
+
if (distanceSq <= 1e-20) return 0;
|
|
705
|
+
bestDistanceSq = Math.min(bestDistanceSq, distanceSq);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return Number.isFinite(bestDistanceSq) ? Math.sqrt(bestDistanceSq) : null;
|
|
709
|
+
}
|
|
710
|
+
function intersectionVolume(a, b) {
|
|
711
|
+
try {
|
|
712
|
+
const hit = a.shape.intersect(b.shape);
|
|
713
|
+
if (hit.isEmpty()) return { volume: 0 };
|
|
714
|
+
const volume = hit.volume();
|
|
715
|
+
return { volume: Number.isFinite(volume) ? volume : 0 };
|
|
716
|
+
} catch (err) {
|
|
717
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
718
|
+
return { volume: null, warning: `Could not boolean-test ${a.name} against ${b.name}: ${message}` };
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
function detectPhysicalContact(a, b, options, meshCache = {}) {
|
|
722
|
+
const gaps = aabbGaps(a, b);
|
|
723
|
+
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
724
|
+
if (largestGap > options.contactTolerance) return { contact: null };
|
|
725
|
+
const sourceMesh = meshCache.sourceMesh !== void 0 ? meshCache.sourceMesh : meshContactDataFor(a);
|
|
726
|
+
const targetMesh = meshCache.targetMesh !== void 0 ? meshCache.targetMesh : meshContactDataFor(b);
|
|
727
|
+
if (sourceMesh && targetMesh) {
|
|
728
|
+
const distance = meshEntriesContactDistance(sourceMesh, targetMesh, options.contactTolerance);
|
|
729
|
+
if (distance != null) {
|
|
730
|
+
return {
|
|
731
|
+
contact: {
|
|
732
|
+
kind: "touching",
|
|
733
|
+
method: "mesh-surface-distance",
|
|
734
|
+
gap: distance
|
|
735
|
+
}
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
const bboxOverlaps = !hasPositiveGap(gaps) && aabbInteriorOverlaps(a, b);
|
|
740
|
+
if (options.exactGeometry && bboxOverlaps) {
|
|
741
|
+
if (aabbOverlapVolume(a, b) <= options.minOverlapVolume) return { contact: null };
|
|
742
|
+
const overlap = intersectionVolume(a, b);
|
|
743
|
+
if (overlap.volume != null && overlap.volume > options.minOverlapVolume) {
|
|
744
|
+
return {
|
|
745
|
+
contact: {
|
|
746
|
+
kind: "overlap",
|
|
747
|
+
method: "boolean-intersection",
|
|
748
|
+
gap: 0,
|
|
749
|
+
overlapVolume: overlap.volume
|
|
750
|
+
},
|
|
751
|
+
warning: overlap.warning
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
return { contact: null, warning: overlap.warning };
|
|
755
|
+
}
|
|
756
|
+
if (bboxOverlaps && options.mergeOverlappingBBoxes) {
|
|
757
|
+
return {
|
|
758
|
+
contact: {
|
|
759
|
+
kind: "overlap",
|
|
760
|
+
method: "bbox-overlap",
|
|
761
|
+
gap: 0,
|
|
762
|
+
overlapVolume: aabbOverlapVolume(a, b)
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
if (options.mergeTouchingBBoxes) {
|
|
767
|
+
const contact = contactFromBBoxes(a, b, options.contactTolerance);
|
|
768
|
+
if (contact.touching) {
|
|
769
|
+
return {
|
|
770
|
+
contact: {
|
|
771
|
+
kind: "touching",
|
|
772
|
+
method: "bbox-contact",
|
|
773
|
+
gap: contact.gap,
|
|
774
|
+
axis: contact.axis
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return { contact: null };
|
|
780
|
+
}
|
|
460
781
|
const DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS = {
|
|
461
782
|
contactTolerance: 0.05,
|
|
462
783
|
minOverlapVolume: 0.1,
|
|
463
784
|
exactGeometry: true
|
|
464
785
|
};
|
|
465
|
-
|
|
466
|
-
let UnionFind$1 = class UnionFind {
|
|
786
|
+
let UnionFind$2 = class UnionFind {
|
|
467
787
|
constructor(size) {
|
|
468
788
|
__publicField(this, "parent");
|
|
469
789
|
__publicField(this, "rank");
|
|
@@ -493,27 +813,21 @@ let UnionFind$1 = class UnionFind {
|
|
|
493
813
|
this.rank[rootA] += 1;
|
|
494
814
|
}
|
|
495
815
|
};
|
|
496
|
-
function cloneVec3(value) {
|
|
816
|
+
function cloneVec3$1(value) {
|
|
497
817
|
return [value[0], value[1], value[2]];
|
|
498
818
|
}
|
|
499
|
-
function emptyBBox$
|
|
819
|
+
function emptyBBox$2() {
|
|
500
820
|
return {
|
|
501
821
|
min: [Infinity, Infinity, Infinity],
|
|
502
822
|
max: [-Infinity, -Infinity, -Infinity]
|
|
503
823
|
};
|
|
504
824
|
}
|
|
505
|
-
function expandBBox$
|
|
825
|
+
function expandBBox$2(target, min, max) {
|
|
506
826
|
for (let axis = 0; axis < 3; axis += 1) {
|
|
507
827
|
target.min[axis] = Math.min(target.min[axis], min[axis]);
|
|
508
828
|
target.max[axis] = Math.max(target.max[axis], max[axis]);
|
|
509
829
|
}
|
|
510
830
|
}
|
|
511
|
-
function nearestBoundaryGap(a, b, axis) {
|
|
512
|
-
return Math.min(Math.abs(a.max[axis] - b.min[axis]), Math.abs(b.max[axis] - a.min[axis]));
|
|
513
|
-
}
|
|
514
|
-
function hasPositiveGap(gaps) {
|
|
515
|
-
return gaps[0] > 0 || gaps[1] > 0 || gaps[2] > 0;
|
|
516
|
-
}
|
|
517
831
|
function collectCandidatePairs(entries, tolerance) {
|
|
518
832
|
if (entries.length < 2) return [];
|
|
519
833
|
const index = new AabbSpatialIndex(entries);
|
|
@@ -525,36 +839,7 @@ function collectCandidatePairs(entries, tolerance) {
|
|
|
525
839
|
pairs.sort((a, b) => a.sourceIndex - b.sourceIndex || a.targetIndex - b.targetIndex);
|
|
526
840
|
return pairs;
|
|
527
841
|
}
|
|
528
|
-
function
|
|
529
|
-
const gaps = aabbGaps(a, b);
|
|
530
|
-
const largestGap = Math.max(gaps[0], gaps[1], gaps[2]);
|
|
531
|
-
if (largestGap > tolerance) return { touching: false, gap: largestGap };
|
|
532
|
-
const separatedAxes = gaps.map((gap, axis) => ({ gap, axis })).filter((entry) => entry.gap > 0);
|
|
533
|
-
if (separatedAxes.length > 0) {
|
|
534
|
-
const nearest2 = separatedAxes.reduce((best, entry) => entry.gap > best.gap ? entry : best, separatedAxes[0]);
|
|
535
|
-
return { touching: true, gap: nearest2.gap, axis: AXIS_NAMES[nearest2.axis] };
|
|
536
|
-
}
|
|
537
|
-
const boundaryAxes = AXIS_NAMES.map((axisName, axis) => ({
|
|
538
|
-
axis,
|
|
539
|
-
axisName,
|
|
540
|
-
gap: nearestBoundaryGap(a, b, axis)
|
|
541
|
-
})).filter((entry) => entry.gap <= tolerance);
|
|
542
|
-
if (boundaryAxes.length === 0) return { touching: false, gap: 0 };
|
|
543
|
-
const nearest = boundaryAxes.reduce((best, entry) => entry.gap < best.gap ? entry : best, boundaryAxes[0]);
|
|
544
|
-
return { touching: true, gap: nearest.gap, axis: nearest.axisName };
|
|
545
|
-
}
|
|
546
|
-
function intersectionVolume(a, b) {
|
|
547
|
-
try {
|
|
548
|
-
const hit = a.shape.intersect(b.shape);
|
|
549
|
-
if (hit.isEmpty()) return { volume: 0 };
|
|
550
|
-
const volume = hit.volume();
|
|
551
|
-
return { volume: Number.isFinite(volume) ? volume : 0 };
|
|
552
|
-
} catch (err) {
|
|
553
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
554
|
-
return { volume: null, warning: `Could not boolean-test ${a.name} against ${b.name}: ${message}` };
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
function bodyCountForEntry(entry) {
|
|
842
|
+
function bodyCountForEntry$1(entry) {
|
|
558
843
|
if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
|
|
559
844
|
return Math.max(0, Math.round(entry.bodyCount));
|
|
560
845
|
}
|
|
@@ -584,53 +869,23 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
584
869
|
};
|
|
585
870
|
const warnings = [];
|
|
586
871
|
const edges = [];
|
|
587
|
-
const unionFind = new UnionFind$
|
|
872
|
+
const unionFind = new UnionFind$2(entries.length);
|
|
873
|
+
const meshDataByIndex = /* @__PURE__ */ new Map();
|
|
874
|
+
entries.forEach((entry, index) => {
|
|
875
|
+
const meshData = meshContactDataFor(entry);
|
|
876
|
+
if (meshData) meshDataByIndex.set(index, meshData);
|
|
877
|
+
});
|
|
588
878
|
for (const pair of collectCandidatePairs(entries, options.contactTolerance)) {
|
|
589
879
|
const i = pair.sourceIndex;
|
|
590
880
|
const j = pair.targetIndex;
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
edges.push(
|
|
600
|
-
makeEdge(entries, i, j, {
|
|
601
|
-
kind: "overlap",
|
|
602
|
-
method: "boolean-intersection",
|
|
603
|
-
gap: 0,
|
|
604
|
-
overlapVolume: overlap.volume
|
|
605
|
-
})
|
|
606
|
-
);
|
|
607
|
-
continue;
|
|
608
|
-
}
|
|
609
|
-
continue;
|
|
610
|
-
}
|
|
611
|
-
if (bboxOverlaps && options.mergeOverlappingBBoxes) {
|
|
612
|
-
unionFind.union(i, j);
|
|
613
|
-
edges.push(
|
|
614
|
-
makeEdge(entries, i, j, {
|
|
615
|
-
kind: "overlap",
|
|
616
|
-
method: "bbox-overlap",
|
|
617
|
-
gap: 0,
|
|
618
|
-
overlapVolume: aabbOverlapVolume(a, b)
|
|
619
|
-
})
|
|
620
|
-
);
|
|
621
|
-
} else {
|
|
622
|
-
const contact = contactFromBBoxes(a, b, options.contactTolerance);
|
|
623
|
-
if (!contact.touching || !options.mergeTouchingBBoxes) continue;
|
|
624
|
-
unionFind.union(i, j);
|
|
625
|
-
edges.push(
|
|
626
|
-
makeEdge(entries, i, j, {
|
|
627
|
-
kind: "touching",
|
|
628
|
-
method: "bbox-contact",
|
|
629
|
-
gap: contact.gap,
|
|
630
|
-
axis: contact.axis
|
|
631
|
-
})
|
|
632
|
-
);
|
|
633
|
-
}
|
|
881
|
+
const detection = detectPhysicalContact(entries[i], entries[j], options, {
|
|
882
|
+
sourceMesh: meshDataByIndex.get(i) ?? null,
|
|
883
|
+
targetMesh: meshDataByIndex.get(j) ?? null
|
|
884
|
+
});
|
|
885
|
+
if (detection.warning) warnings.push(detection.warning);
|
|
886
|
+
if (!detection.contact) continue;
|
|
887
|
+
unionFind.union(i, j);
|
|
888
|
+
edges.push(makeEdge(entries, i, j, detection.contact));
|
|
634
889
|
}
|
|
635
890
|
const objects = entries.map((entry, index) => ({
|
|
636
891
|
index,
|
|
@@ -639,10 +894,10 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
639
894
|
groupName: entry.groupName,
|
|
640
895
|
treePath: entry.treePath,
|
|
641
896
|
mock: entry.mock === true,
|
|
642
|
-
bodyCount: bodyCountForEntry(entry),
|
|
897
|
+
bodyCount: bodyCountForEntry$1(entry),
|
|
643
898
|
bbox: {
|
|
644
|
-
min: cloneVec3(entry.min),
|
|
645
|
-
max: cloneVec3(entry.max)
|
|
899
|
+
min: cloneVec3$1(entry.min),
|
|
900
|
+
max: cloneVec3$1(entry.max)
|
|
646
901
|
},
|
|
647
902
|
componentIndex: 0
|
|
648
903
|
}));
|
|
@@ -659,7 +914,7 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
659
914
|
objectNames: [],
|
|
660
915
|
objectCount: 0,
|
|
661
916
|
bodyCount: 0,
|
|
662
|
-
bbox: emptyBBox$
|
|
917
|
+
bbox: emptyBBox$2()
|
|
663
918
|
};
|
|
664
919
|
componentByRoot.set(root, component);
|
|
665
920
|
rootToComponentIndex.set(root, component.index);
|
|
@@ -671,11 +926,11 @@ function analyzePhysicalConnectivity(entries, rawOptions = {}) {
|
|
|
671
926
|
component.objectNames.push(object.name);
|
|
672
927
|
component.objectCount += 1;
|
|
673
928
|
component.bodyCount += object.bodyCount;
|
|
674
|
-
expandBBox$
|
|
929
|
+
expandBBox$2(component.bbox, object.bbox.min, object.bbox.max);
|
|
675
930
|
}
|
|
676
931
|
const components = [...componentByRoot.values()];
|
|
677
932
|
return {
|
|
678
|
-
method: options.exactGeometry ? "
|
|
933
|
+
method: options.exactGeometry ? "mesh-contact-plus-boolean-overlap" : "bbox-neighborhood",
|
|
679
934
|
options,
|
|
680
935
|
objectCount: objects.length,
|
|
681
936
|
componentCount: components.length,
|
|
@@ -876,6 +1131,185 @@ function analyzeDistanceInspection(entries, rawOptions = {}) {
|
|
|
876
1131
|
warnings: [...connectivity.warnings]
|
|
877
1132
|
};
|
|
878
1133
|
}
|
|
1134
|
+
const DEFAULT_CONTACT_TOLERANCE = 0.05;
|
|
1135
|
+
const DEFAULT_BED_TOLERANCE = 0.05;
|
|
1136
|
+
const DEFAULT_GROUND_Z = 0;
|
|
1137
|
+
const MIN_SHAPE_OVERLAP_VOLUME = 1e-9;
|
|
1138
|
+
let UnionFind$1 = class UnionFind2 {
|
|
1139
|
+
constructor(size) {
|
|
1140
|
+
__publicField(this, "parent");
|
|
1141
|
+
__publicField(this, "rank");
|
|
1142
|
+
this.parent = Array.from({ length: size }, (_, index) => index);
|
|
1143
|
+
this.rank = Array.from({ length: size }, () => 0);
|
|
1144
|
+
}
|
|
1145
|
+
find(value) {
|
|
1146
|
+
const parent = this.parent[value];
|
|
1147
|
+
if (parent === value) return value;
|
|
1148
|
+
const root = this.find(parent);
|
|
1149
|
+
this.parent[value] = root;
|
|
1150
|
+
return root;
|
|
1151
|
+
}
|
|
1152
|
+
union(a, b) {
|
|
1153
|
+
const rootA = this.find(a);
|
|
1154
|
+
const rootB = this.find(b);
|
|
1155
|
+
if (rootA === rootB) return;
|
|
1156
|
+
if (this.rank[rootA] < this.rank[rootB]) {
|
|
1157
|
+
this.parent[rootA] = rootB;
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1160
|
+
if (this.rank[rootA] > this.rank[rootB]) {
|
|
1161
|
+
this.parent[rootB] = rootA;
|
|
1162
|
+
return;
|
|
1163
|
+
}
|
|
1164
|
+
this.parent[rootB] = rootA;
|
|
1165
|
+
this.rank[rootA] += 1;
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
function cloneVec3(value) {
|
|
1169
|
+
return [value[0], value[1], value[2]];
|
|
1170
|
+
}
|
|
1171
|
+
function emptyBBox$1() {
|
|
1172
|
+
return {
|
|
1173
|
+
min: [Infinity, Infinity, Infinity],
|
|
1174
|
+
max: [-Infinity, -Infinity, -Infinity]
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
function expandBBox$1(target, min, max) {
|
|
1178
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
1179
|
+
target.min[axis] = Math.min(target.min[axis], min[axis]);
|
|
1180
|
+
target.max[axis] = Math.max(target.max[axis], max[axis]);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
function bodyCountForEntry(entry) {
|
|
1184
|
+
if (typeof entry.bodyCount === "number" && Number.isFinite(entry.bodyCount)) {
|
|
1185
|
+
return Math.max(0, Math.round(entry.bodyCount));
|
|
1186
|
+
}
|
|
1187
|
+
return 1;
|
|
1188
|
+
}
|
|
1189
|
+
function resolveNonNegative(value, fallback) {
|
|
1190
|
+
return Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
1191
|
+
}
|
|
1192
|
+
function resolveFinite(value, fallback) {
|
|
1193
|
+
return Number.isFinite(value) ? value : fallback;
|
|
1194
|
+
}
|
|
1195
|
+
function analyzeFloatingInspection(entries, rawOptions = {}) {
|
|
1196
|
+
const options = {
|
|
1197
|
+
contactTolerance: resolveNonNegative(rawOptions.contactTolerance, DEFAULT_CONTACT_TOLERANCE),
|
|
1198
|
+
bedTolerance: resolveNonNegative(rawOptions.bedTolerance, DEFAULT_BED_TOLERANCE),
|
|
1199
|
+
groundZ: resolveFinite(rawOptions.groundZ, DEFAULT_GROUND_Z)
|
|
1200
|
+
};
|
|
1201
|
+
const unionFind = new UnionFind$1(entries.length);
|
|
1202
|
+
const contacts = [];
|
|
1203
|
+
const warnings = [];
|
|
1204
|
+
const meshDataByIndex = /* @__PURE__ */ new Map();
|
|
1205
|
+
entries.forEach((entry, index2) => {
|
|
1206
|
+
const meshData = meshContactDataFor(entry);
|
|
1207
|
+
if (meshData) meshDataByIndex.set(index2, meshData);
|
|
1208
|
+
});
|
|
1209
|
+
const candidateTolerance = options.contactTolerance > 0 ? options.contactTolerance : Number.EPSILON;
|
|
1210
|
+
const index = new AabbSpatialIndex(entries);
|
|
1211
|
+
const contactOptions = {
|
|
1212
|
+
contactTolerance: options.contactTolerance,
|
|
1213
|
+
minOverlapVolume: MIN_SHAPE_OVERLAP_VOLUME,
|
|
1214
|
+
exactGeometry: true,
|
|
1215
|
+
mergeOverlappingBBoxes: false,
|
|
1216
|
+
mergeTouchingBBoxes: false
|
|
1217
|
+
};
|
|
1218
|
+
for (const pair of index.overlapPairs({ padding: candidateTolerance }).pairs) {
|
|
1219
|
+
const source = entries[pair.sourceIndex];
|
|
1220
|
+
const target = entries[pair.targetIndex];
|
|
1221
|
+
const detection = detectPhysicalContact(source, target, contactOptions, {
|
|
1222
|
+
sourceMesh: meshDataByIndex.get(pair.sourceIndex) ?? null,
|
|
1223
|
+
targetMesh: meshDataByIndex.get(pair.targetIndex) ?? null
|
|
1224
|
+
});
|
|
1225
|
+
if (detection.warning) warnings.push(detection.warning);
|
|
1226
|
+
if (!detection.contact) continue;
|
|
1227
|
+
unionFind.union(pair.sourceIndex, pair.targetIndex);
|
|
1228
|
+
contacts.push({
|
|
1229
|
+
sourceIndex: pair.sourceIndex,
|
|
1230
|
+
targetIndex: pair.targetIndex,
|
|
1231
|
+
sourceId: source.id,
|
|
1232
|
+
targetId: target.id,
|
|
1233
|
+
sourceName: source.name,
|
|
1234
|
+
targetName: target.name,
|
|
1235
|
+
gap: detection.contact.gap
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
const objects = entries.map((entry, entryIndex) => ({
|
|
1239
|
+
index: entryIndex,
|
|
1240
|
+
id: entry.id,
|
|
1241
|
+
name: entry.name,
|
|
1242
|
+
groupName: entry.groupName,
|
|
1243
|
+
treePath: entry.treePath,
|
|
1244
|
+
mock: entry.mock === true,
|
|
1245
|
+
bodyCount: bodyCountForEntry(entry),
|
|
1246
|
+
bbox: {
|
|
1247
|
+
min: cloneVec3(entry.min),
|
|
1248
|
+
max: cloneVec3(entry.max)
|
|
1249
|
+
},
|
|
1250
|
+
componentIndex: 0,
|
|
1251
|
+
isRootComponent: false,
|
|
1252
|
+
isBedSupported: entry.min[2] <= options.groundZ + options.bedTolerance,
|
|
1253
|
+
isFloating: false
|
|
1254
|
+
}));
|
|
1255
|
+
const componentByRoot = /* @__PURE__ */ new Map();
|
|
1256
|
+
const rootToComponentIndex = /* @__PURE__ */ new Map();
|
|
1257
|
+
for (let objectIndex = 0; objectIndex < objects.length; objectIndex += 1) {
|
|
1258
|
+
const root = unionFind.find(objectIndex);
|
|
1259
|
+
let component = componentByRoot.get(root);
|
|
1260
|
+
if (!component) {
|
|
1261
|
+
component = {
|
|
1262
|
+
index: componentByRoot.size + 1,
|
|
1263
|
+
objectIndexes: [],
|
|
1264
|
+
objectIds: [],
|
|
1265
|
+
objectNames: [],
|
|
1266
|
+
objectCount: 0,
|
|
1267
|
+
bodyCount: 0,
|
|
1268
|
+
bbox: emptyBBox$1(),
|
|
1269
|
+
isRoot: false,
|
|
1270
|
+
isBedSupported: false,
|
|
1271
|
+
isFloating: false
|
|
1272
|
+
};
|
|
1273
|
+
componentByRoot.set(root, component);
|
|
1274
|
+
rootToComponentIndex.set(root, component.index);
|
|
1275
|
+
}
|
|
1276
|
+
const object = objects[objectIndex];
|
|
1277
|
+
object.componentIndex = rootToComponentIndex.get(root) ?? component.index;
|
|
1278
|
+
component.objectIndexes.push(object.index);
|
|
1279
|
+
component.objectIds.push(object.id);
|
|
1280
|
+
component.objectNames.push(object.name);
|
|
1281
|
+
component.objectCount += 1;
|
|
1282
|
+
component.bodyCount += object.bodyCount;
|
|
1283
|
+
component.isBedSupported || (component.isBedSupported = object.isBedSupported);
|
|
1284
|
+
expandBBox$1(component.bbox, object.bbox.min, object.bbox.max);
|
|
1285
|
+
}
|
|
1286
|
+
const components = [...componentByRoot.values()];
|
|
1287
|
+
for (const component of components) {
|
|
1288
|
+
component.isRoot = false;
|
|
1289
|
+
component.isFloating = !component.isBedSupported;
|
|
1290
|
+
}
|
|
1291
|
+
const componentByIndex = new Map(components.map((component) => [component.index, component]));
|
|
1292
|
+
for (const object of objects) {
|
|
1293
|
+
const component = componentByIndex.get(object.componentIndex);
|
|
1294
|
+
object.isRootComponent = false;
|
|
1295
|
+
object.isBedSupported = (component == null ? void 0 : component.isBedSupported) ?? object.isBedSupported;
|
|
1296
|
+
object.isFloating = (component == null ? void 0 : component.isFloating) ?? false;
|
|
1297
|
+
}
|
|
1298
|
+
return {
|
|
1299
|
+
method: "mesh-contact-ground-reachability",
|
|
1300
|
+
options,
|
|
1301
|
+
rootComponentIndex: null,
|
|
1302
|
+
objectCount: objects.length,
|
|
1303
|
+
componentCount: components.length,
|
|
1304
|
+
floatingComponentCount: components.filter((component) => component.isFloating).length,
|
|
1305
|
+
floatingObjectCount: objects.filter((object) => object.isFloating).length,
|
|
1306
|
+
floatingBodyCount: components.filter((component) => component.isFloating).reduce((total, component) => total + component.bodyCount, 0),
|
|
1307
|
+
contacts,
|
|
1308
|
+
objects,
|
|
1309
|
+
components,
|
|
1310
|
+
warnings
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
879
1313
|
const CAMERA_TOKEN_DIRECTIONS = {
|
|
880
1314
|
front: [0, -1, 0.2],
|
|
881
1315
|
back: [0, 1, 0.2],
|
|
@@ -908,6 +1342,7 @@ function parseCameraToken(token) {
|
|
|
908
1342
|
`Unknown camera "${token}". Use a preset (front, back, side, right, top, iso) or azimuth:elevation in degrees (e.g. 45:30).`
|
|
909
1343
|
);
|
|
910
1344
|
}
|
|
1345
|
+
const CLI_DEFAULT_BACKEND = "manifold";
|
|
911
1346
|
function formatAvailableViews(views) {
|
|
912
1347
|
const names = Object.keys(views ?? {}).sort();
|
|
913
1348
|
if (names.length === 0) {
|
|
@@ -936,14 +1371,36 @@ function resolveNamedSceneViewCamera(sceneConfig, viewName) {
|
|
|
936
1371
|
}
|
|
937
1372
|
return sceneViewCameraToViewportCameraState(view.camera);
|
|
938
1373
|
}
|
|
939
|
-
function
|
|
1374
|
+
function paletteForTheme(theme) {
|
|
1375
|
+
if (theme === "cad-section") {
|
|
1376
|
+
return {
|
|
1377
|
+
background: "#f7f7f4",
|
|
1378
|
+
fill: "#e6e8eb",
|
|
1379
|
+
stroke: "#20262e",
|
|
1380
|
+
hatchStroke: "#a8afb7",
|
|
1381
|
+
strokeThin: 0.22,
|
|
1382
|
+
strokeBold: 0.55,
|
|
1383
|
+
useHatch: true
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
return {
|
|
1387
|
+
background: "#2a2a2a",
|
|
1388
|
+
fill: "#4488cc",
|
|
1389
|
+
stroke: "#224466",
|
|
1390
|
+
hatchStroke: "#224466",
|
|
1391
|
+
strokeThin: 0.3,
|
|
1392
|
+
strokeBold: 0.8,
|
|
1393
|
+
useHatch: false
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function edgesStrokeAttrs(preset, palette) {
|
|
940
1397
|
switch (preset) {
|
|
941
1398
|
case "off":
|
|
942
1399
|
return 'stroke="none"';
|
|
943
1400
|
case "bold":
|
|
944
|
-
return
|
|
1401
|
+
return `stroke="${palette.stroke}" stroke-width="${palette.strokeBold}"`;
|
|
945
1402
|
default:
|
|
946
|
-
return
|
|
1403
|
+
return `stroke="${palette.stroke}" stroke-width="${palette.strokeThin}"`;
|
|
947
1404
|
}
|
|
948
1405
|
}
|
|
949
1406
|
function combineBounds(left, right) {
|
|
@@ -958,6 +1415,19 @@ function escapeAttribute(value) {
|
|
|
958
1415
|
function polygonPath(poly) {
|
|
959
1416
|
return `${poly.map((point, index) => `${index === 0 ? "M" : "L"}${point[0].toFixed(3)},${(-point[1]).toFixed(3)}`).join(" ")} Z`;
|
|
960
1417
|
}
|
|
1418
|
+
function compoundPolygonPath(polygons) {
|
|
1419
|
+
return polygons.map(polygonPath).join(" ");
|
|
1420
|
+
}
|
|
1421
|
+
function hatchDefs(palette) {
|
|
1422
|
+
if (!palette.useHatch) return "";
|
|
1423
|
+
return ` <defs>
|
|
1424
|
+
<pattern id="forge-section-hatch" patternUnits="userSpaceOnUse" width="4" height="4" patternTransform="rotate(45)">
|
|
1425
|
+
<rect width="4" height="4" fill="${palette.fill}"/>
|
|
1426
|
+
<line x1="0" y1="0" x2="0" y2="4" stroke="${palette.hatchStroke}" stroke-width="0.28"/>
|
|
1427
|
+
</pattern>
|
|
1428
|
+
</defs>
|
|
1429
|
+
`;
|
|
1430
|
+
}
|
|
961
1431
|
function prepareEntry(entry) {
|
|
962
1432
|
const polygons = entry.sketch.toPolygons();
|
|
963
1433
|
if (polygons.length === 0) {
|
|
@@ -979,7 +1449,9 @@ function buildSketchSvgDocument(entries, options = {}) {
|
|
|
979
1449
|
throw new Error("Sketch SVG export requires at least one sketch payload.");
|
|
980
1450
|
}
|
|
981
1451
|
const edges = options.edges ?? "thin";
|
|
982
|
-
const
|
|
1452
|
+
const palette = paletteForTheme(options.theme ?? "debug");
|
|
1453
|
+
const strokeAttrs = edgesStrokeAttrs(edges, palette);
|
|
1454
|
+
const fill = palette.useHatch ? "url(#forge-section-hatch)" : palette.fill;
|
|
983
1455
|
const prepared = entries.map(prepareEntry);
|
|
984
1456
|
const combinedBounds = prepared.reduce((acc, entry) => combineBounds(acc, entry.bounds), prepared[0].bounds);
|
|
985
1457
|
const margin = 2;
|
|
@@ -992,17 +1464,15 @@ function buildSketchSvgDocument(entries, options = {}) {
|
|
|
992
1464
|
let pathCount = 0;
|
|
993
1465
|
const body = prepared.map((entry) => {
|
|
994
1466
|
const label = entry.name ? ` data-name="${escapeAttribute(entry.name)}"` : "";
|
|
995
|
-
|
|
996
|
-
pathCount += 1;
|
|
997
|
-
return ` <path d="${polygonPath(poly)}" fill="#4488cc" ${strokeAttrs}/>`;
|
|
998
|
-
}).join("\n");
|
|
1467
|
+
pathCount += entry.polygons.length;
|
|
999
1468
|
return ` <g${label}>
|
|
1000
|
-
${
|
|
1469
|
+
<path d="${compoundPolygonPath(entry.polygons)}" fill="${fill}" fill-rule="evenodd" ${strokeAttrs}/>
|
|
1001
1470
|
</g>`;
|
|
1002
1471
|
}).join("\n");
|
|
1003
1472
|
return {
|
|
1004
1473
|
svg: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${minX.toFixed(1)} ${(-maxY).toFixed(1)} ${width.toFixed(1)} ${height.toFixed(1)}" width="${Math.max(width * 4, 400)}" height="${Math.max(height * 4, 400)}">
|
|
1005
|
-
<rect x="${minX.toFixed(1)}" y="${(-maxY).toFixed(1)}" width="${width.toFixed(1)}" height="${height.toFixed(1)}" fill="
|
|
1474
|
+
<rect x="${minX.toFixed(1)}" y="${(-maxY).toFixed(1)}" width="${width.toFixed(1)}" height="${height.toFixed(1)}" fill="${palette.background}"/>
|
|
1475
|
+
${hatchDefs(palette)}
|
|
1006
1476
|
${body}
|
|
1007
1477
|
</svg>`,
|
|
1008
1478
|
width,
|
|
@@ -1015,7 +1485,8 @@ const DEFAULT_THICKNESS_INSPECTION_OPTIONS = {
|
|
|
1015
1485
|
minThickness: 1.2,
|
|
1016
1486
|
warnThickness: 2,
|
|
1017
1487
|
maxThickness: 6,
|
|
1018
|
-
maxSamplesPerObject: 5e3
|
|
1488
|
+
maxSamplesPerObject: 5e3,
|
|
1489
|
+
contactTolerance: DEFAULT_PHYSICAL_CONNECTIVITY_OPTIONS.contactTolerance
|
|
1019
1490
|
};
|
|
1020
1491
|
const THICKNESS_COLORS = {
|
|
1021
1492
|
critical: [255, 28, 28],
|
|
@@ -1031,6 +1502,13 @@ function finitePositive(value, fallback, label) {
|
|
|
1031
1502
|
}
|
|
1032
1503
|
return value;
|
|
1033
1504
|
}
|
|
1505
|
+
function finiteNonNegative(value, fallback, label) {
|
|
1506
|
+
if (value === void 0) return fallback;
|
|
1507
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1508
|
+
throw new Error(`${label} must be a non-negative finite number.`);
|
|
1509
|
+
}
|
|
1510
|
+
return value;
|
|
1511
|
+
}
|
|
1034
1512
|
function resolveThicknessInspectionOptions(raw = {}) {
|
|
1035
1513
|
const minThickness = finitePositive(raw.minThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.minThickness, "minThickness");
|
|
1036
1514
|
const warnThickness = finitePositive(raw.warnThickness, DEFAULT_THICKNESS_INSPECTION_OPTIONS.warnThickness, "warnThickness");
|
|
@@ -1040,6 +1518,11 @@ function resolveThicknessInspectionOptions(raw = {}) {
|
|
|
1040
1518
|
DEFAULT_THICKNESS_INSPECTION_OPTIONS.maxSamplesPerObject,
|
|
1041
1519
|
"maxSamplesPerObject"
|
|
1042
1520
|
);
|
|
1521
|
+
const contactTolerance = finiteNonNegative(
|
|
1522
|
+
raw.contactTolerance,
|
|
1523
|
+
DEFAULT_THICKNESS_INSPECTION_OPTIONS.contactTolerance,
|
|
1524
|
+
"contactTolerance"
|
|
1525
|
+
);
|
|
1043
1526
|
if (minThickness > warnThickness) {
|
|
1044
1527
|
throw new Error("minThickness must be less than or equal to warnThickness.");
|
|
1045
1528
|
}
|
|
@@ -1050,7 +1533,8 @@ function resolveThicknessInspectionOptions(raw = {}) {
|
|
|
1050
1533
|
minThickness,
|
|
1051
1534
|
warnThickness,
|
|
1052
1535
|
maxThickness,
|
|
1053
|
-
maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject))
|
|
1536
|
+
maxSamplesPerObject: Math.max(1, Math.floor(maxSamplesPerObject)),
|
|
1537
|
+
contactTolerance
|
|
1054
1538
|
};
|
|
1055
1539
|
}
|
|
1056
1540
|
function lerp(a, b, t) {
|
|
@@ -1249,22 +1733,45 @@ function geometryMaxDimension(geometry) {
|
|
|
1249
1733
|
box.getSize(size);
|
|
1250
1734
|
return Math.max(1, size.x, size.y, size.z);
|
|
1251
1735
|
}
|
|
1252
|
-
function firstOppositeSurfaceDistance(raycaster,
|
|
1736
|
+
function firstOppositeSurfaceDistance(raycaster, rayTargetMeshes, jumpableMeshes, point, direction, epsilon, far, contactTolerance) {
|
|
1253
1737
|
const origin = point.clone().addScaledVector(direction, epsilon);
|
|
1254
1738
|
raycaster.set(origin, direction);
|
|
1255
1739
|
raycaster.near = epsilon;
|
|
1256
1740
|
raycaster.far = far;
|
|
1257
|
-
const
|
|
1258
|
-
|
|
1741
|
+
const hits = raycaster.intersectObjects(rayTargetMeshes, false);
|
|
1742
|
+
for (const hit of hits) {
|
|
1743
|
+
if (hit.distance <= epsilon) continue;
|
|
1744
|
+
if (hit.distance <= contactTolerance && jumpableMeshes.has(hit.object)) continue;
|
|
1745
|
+
return hit.distance + epsilon;
|
|
1746
|
+
}
|
|
1747
|
+
return null;
|
|
1259
1748
|
}
|
|
1260
|
-
function triangleThickness(raycaster,
|
|
1261
|
-
const forward = firstOppositeSurfaceDistance(
|
|
1262
|
-
|
|
1749
|
+
function triangleThickness(raycaster, rayTargetMeshes, jumpableMeshes, centroid, normal, epsilon, far, contactTolerance) {
|
|
1750
|
+
const forward = firstOppositeSurfaceDistance(
|
|
1751
|
+
raycaster,
|
|
1752
|
+
rayTargetMeshes,
|
|
1753
|
+
jumpableMeshes,
|
|
1754
|
+
centroid,
|
|
1755
|
+
normal,
|
|
1756
|
+
epsilon,
|
|
1757
|
+
far,
|
|
1758
|
+
contactTolerance
|
|
1759
|
+
);
|
|
1760
|
+
const backward = firstOppositeSurfaceDistance(
|
|
1761
|
+
raycaster,
|
|
1762
|
+
rayTargetMeshes,
|
|
1763
|
+
jumpableMeshes,
|
|
1764
|
+
centroid,
|
|
1765
|
+
normal.clone().negate(),
|
|
1766
|
+
epsilon,
|
|
1767
|
+
far,
|
|
1768
|
+
contactTolerance
|
|
1769
|
+
);
|
|
1263
1770
|
if (forward == null) return backward;
|
|
1264
1771
|
if (backward == null) return forward;
|
|
1265
1772
|
return Math.min(forward, backward);
|
|
1266
1773
|
}
|
|
1267
|
-
function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
1774
|
+
function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}, context = {}) {
|
|
1268
1775
|
const options = resolveThicknessInspectionOptions(rawOptions);
|
|
1269
1776
|
const geometry = cloneGeometryForFaceColors(sourceGeometry);
|
|
1270
1777
|
const position = geometry.getAttribute("position");
|
|
@@ -1282,7 +1789,8 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
1282
1789
|
const triangleCount = Math.floor(position.count / 3);
|
|
1283
1790
|
const surfaceTriangles = readSurfaceTriangles(position);
|
|
1284
1791
|
const surfaceSamples = sampleSurfaceTriangles(surfaceTriangles, options.maxSamplesPerObject);
|
|
1285
|
-
const
|
|
1792
|
+
const connectedGeometries = context.connectedGeometries ?? [];
|
|
1793
|
+
const maxDim = Math.max(geometryMaxDimension(geometry), ...connectedGeometries.map(geometryMaxDimension));
|
|
1286
1794
|
const epsilon = Math.max(1e-4, maxDim * 1e-6);
|
|
1287
1795
|
const far = Math.max(maxDim * 4, options.maxThickness * 4, 1);
|
|
1288
1796
|
const colors = new Float32Array(position.count * 3);
|
|
@@ -1291,7 +1799,15 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
1291
1799
|
const pointSamples = [];
|
|
1292
1800
|
const warnings = [];
|
|
1293
1801
|
const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
|
|
1294
|
-
const
|
|
1802
|
+
const rayTargets = [
|
|
1803
|
+
{ mesh: new Mesh(geometry, rayMaterial), jumpable: false },
|
|
1804
|
+
...connectedGeometries.map((connectedGeometry) => ({
|
|
1805
|
+
mesh: new Mesh(connectedGeometry, rayMaterial),
|
|
1806
|
+
jumpable: true
|
|
1807
|
+
}))
|
|
1808
|
+
];
|
|
1809
|
+
const rayTargetMeshes = rayTargets.map((target) => target.mesh);
|
|
1810
|
+
const jumpableMeshes = new Set(rayTargets.filter((target) => target.jumpable).map((target) => target.mesh));
|
|
1295
1811
|
const raycaster = new Raycaster();
|
|
1296
1812
|
if (surfaceTriangles.length === 0) {
|
|
1297
1813
|
warnings.push("No non-degenerate triangle surface was available for thickness sampling.");
|
|
@@ -1303,7 +1819,16 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}) {
|
|
|
1303
1819
|
const sampledTriangleIndexes = /* @__PURE__ */ new Set();
|
|
1304
1820
|
for (const sample of surfaceSamples) {
|
|
1305
1821
|
sampledTriangleIndexes.add(sample.triangle.index);
|
|
1306
|
-
const thickness = triangleThickness(
|
|
1822
|
+
const thickness = triangleThickness(
|
|
1823
|
+
raycaster,
|
|
1824
|
+
rayTargetMeshes,
|
|
1825
|
+
jumpableMeshes,
|
|
1826
|
+
sample.position,
|
|
1827
|
+
sample.normal,
|
|
1828
|
+
epsilon,
|
|
1829
|
+
far,
|
|
1830
|
+
options.contactTolerance
|
|
1831
|
+
);
|
|
1307
1832
|
samples.push({ thickness, area: sample.area });
|
|
1308
1833
|
const previous = triangleThicknessValues[sample.triangle.index];
|
|
1309
1834
|
if (previous === void 0 || previous == null || thickness != null && thickness < previous) {
|
|
@@ -1637,7 +2162,7 @@ function pointSegmentDistance(point, start, end) {
|
|
|
1637
2162
|
return point.distanceTo(segment.multiplyScalar(t).add(start));
|
|
1638
2163
|
}
|
|
1639
2164
|
const DEFAULT_VERTEX_TOLERANCE = 1e-5;
|
|
1640
|
-
class
|
|
2165
|
+
class UnionFind3 {
|
|
1641
2166
|
constructor(size) {
|
|
1642
2167
|
__publicField(this, "parent");
|
|
1643
2168
|
__publicField(this, "rank");
|
|
@@ -1696,7 +2221,7 @@ function analyzeMeshConnectedComponents(positions, vertexTolerance = DEFAULT_VER
|
|
|
1696
2221
|
const vertexComponentIndexes = new Int32Array(vertexCount);
|
|
1697
2222
|
if (triangleCount === 0) return { components: [], vertexComponentIndexes };
|
|
1698
2223
|
const tolerance = Number.isFinite(vertexTolerance) && vertexTolerance > 0 ? vertexTolerance : DEFAULT_VERTEX_TOLERANCE;
|
|
1699
|
-
const unionFind = new
|
|
2224
|
+
const unionFind = new UnionFind3(triangleCount);
|
|
1700
2225
|
const vertexOwnerByKey = /* @__PURE__ */ new Map();
|
|
1701
2226
|
for (let triangleIndex = 0; triangleIndex < triangleCount; triangleIndex += 1) {
|
|
1702
2227
|
for (let corner = 0; corner < 3; corner += 1) {
|
|
@@ -1747,12 +2272,25 @@ function objectConnectivityEntry(object) {
|
|
|
1747
2272
|
shape: object.shape,
|
|
1748
2273
|
min: object.min,
|
|
1749
2274
|
max: object.max,
|
|
2275
|
+
positions: object.positions,
|
|
1750
2276
|
groupName: object.groupName,
|
|
1751
2277
|
treePath: object.treePath,
|
|
1752
2278
|
mock: object.mock,
|
|
1753
2279
|
bodyCount: 1
|
|
1754
2280
|
};
|
|
1755
2281
|
}
|
|
2282
|
+
function componentPositions(sourcePositions, component) {
|
|
2283
|
+
const out = new Float32Array(component.triangleIndexes.length * 9);
|
|
2284
|
+
let outOffset = 0;
|
|
2285
|
+
for (const triangleIndex of component.triangleIndexes) {
|
|
2286
|
+
const sourceOffset = triangleIndex * 9;
|
|
2287
|
+
for (let offset = 0; offset < 9; offset += 1) {
|
|
2288
|
+
out[outOffset + offset] = sourcePositions[sourceOffset + offset];
|
|
2289
|
+
}
|
|
2290
|
+
outOffset += 9;
|
|
2291
|
+
}
|
|
2292
|
+
return out;
|
|
2293
|
+
}
|
|
1756
2294
|
function buildMeshBodyConnectivityInput(objects, options) {
|
|
1757
2295
|
const entries = [];
|
|
1758
2296
|
const bodyIdsByObjectId = /* @__PURE__ */ new Map();
|
|
@@ -1776,6 +2314,7 @@ function buildMeshBodyConnectivityInput(objects, options) {
|
|
|
1776
2314
|
shape: options.bodyShape(object, component),
|
|
1777
2315
|
min: component.bbox.min,
|
|
1778
2316
|
max: component.bbox.max,
|
|
2317
|
+
positions: componentPositions(object.positions, component),
|
|
1779
2318
|
groupName: object.groupName,
|
|
1780
2319
|
treePath: object.treePath,
|
|
1781
2320
|
mock: object.mock,
|
|
@@ -1854,6 +2393,9 @@ class EmptyInspectionShape {
|
|
|
1854
2393
|
const COLLISION_SOURCE_OPACITY = 0.22;
|
|
1855
2394
|
const COLLISION_SOURCE_COLOR = [180, 200, 220];
|
|
1856
2395
|
const COLLISION_HIGHLIGHT_COLOR = [255, 68, 16];
|
|
2396
|
+
const FLOATING_HIGHLIGHT_COLOR = [255, 68, 16];
|
|
2397
|
+
const FLOATING_CONTEXT_COLOR = [38, 49, 58];
|
|
2398
|
+
const FLOATING_HIDDEN_COLOR = [0, 0, 0];
|
|
1857
2399
|
const COLLISION_PALETTE = [
|
|
1858
2400
|
COLLISION_HIGHLIGHT_COLOR,
|
|
1859
2401
|
[0, 204, 255],
|
|
@@ -1894,18 +2436,13 @@ const MASK_PALETTE = [
|
|
|
1894
2436
|
[0, 0, 128],
|
|
1895
2437
|
[128, 128, 128]
|
|
1896
2438
|
];
|
|
1897
|
-
function
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
return new Float32Array(position.array);
|
|
1903
|
-
} finally {
|
|
1904
|
-
geometry.solid.dispose();
|
|
1905
|
-
geometry.edges.dispose();
|
|
1906
|
-
}
|
|
2439
|
+
function cloneGeometryPositions(geometry) {
|
|
2440
|
+
if (!geometry) return void 0;
|
|
2441
|
+
const position = geometry.getAttribute("position");
|
|
2442
|
+
if (!position) return void 0;
|
|
2443
|
+
return new Float32Array(position.array);
|
|
1907
2444
|
}
|
|
1908
|
-
function meshConnectivitySource(entry) {
|
|
2445
|
+
function meshConnectivitySource(entry, positions) {
|
|
1909
2446
|
const bb = entry.shape.boundingBox();
|
|
1910
2447
|
return {
|
|
1911
2448
|
id: entry.source.id,
|
|
@@ -1916,7 +2453,7 @@ function meshConnectivitySource(entry) {
|
|
|
1916
2453
|
groupName: entry.source.groupName,
|
|
1917
2454
|
treePath: entry.source.treePath,
|
|
1918
2455
|
mock: entry.source.mock,
|
|
1919
|
-
positions
|
|
2456
|
+
positions
|
|
1920
2457
|
};
|
|
1921
2458
|
}
|
|
1922
2459
|
function summarizeSceneGeometry(entries) {
|
|
@@ -2268,6 +2805,37 @@ function renderCurrentNormals(session) {
|
|
|
2268
2805
|
normalMaterial.dispose();
|
|
2269
2806
|
}
|
|
2270
2807
|
}
|
|
2808
|
+
function renderCurrentZebra(session) {
|
|
2809
|
+
const r = getRenderer(session.size, session.pixelRatio);
|
|
2810
|
+
const zebraMaterial = new ShaderMaterial({
|
|
2811
|
+
uniforms: {
|
|
2812
|
+
uAccentColor: { value: new Color(ZEBRA_ACCENT_COLOR) },
|
|
2813
|
+
uDarkColor: { value: new Color(ZEBRA_DARK_COLOR) },
|
|
2814
|
+
uLightColor: { value: new Color(ZEBRA_LIGHT_COLOR) },
|
|
2815
|
+
uStripeScale: { value: ZEBRA_STRIPE_SCALE },
|
|
2816
|
+
uStripeSoftness: { value: ZEBRA_STRIPE_SOFTNESS }
|
|
2817
|
+
},
|
|
2818
|
+
vertexShader: ZEBRA_STRIPE_VERTEX_SHADER,
|
|
2819
|
+
fragmentShader: ZEBRA_STRIPE_FRAGMENT_SHADER,
|
|
2820
|
+
side: DoubleSide
|
|
2821
|
+
});
|
|
2822
|
+
zebraMaterial.toneMapped = false;
|
|
2823
|
+
const prevOverride = session.scene.overrideMaterial;
|
|
2824
|
+
session.scene.overrideMaterial = zebraMaterial;
|
|
2825
|
+
try {
|
|
2826
|
+
return withSolidOnlyVisibility(
|
|
2827
|
+
session,
|
|
2828
|
+
() => withTemporarySceneBackground(session, new Color(0), () => {
|
|
2829
|
+
updateSdfRaymarchUniforms(session);
|
|
2830
|
+
r.render(session.scene, session.camera);
|
|
2831
|
+
return captureRenderedPng(session.size);
|
|
2832
|
+
})
|
|
2833
|
+
);
|
|
2834
|
+
} finally {
|
|
2835
|
+
session.scene.overrideMaterial = prevOverride;
|
|
2836
|
+
zebraMaterial.dispose();
|
|
2837
|
+
}
|
|
2838
|
+
}
|
|
2271
2839
|
function maskColorForIndex(index) {
|
|
2272
2840
|
return MASK_PALETTE[(index - 1) % MASK_PALETTE.length];
|
|
2273
2841
|
}
|
|
@@ -2371,6 +2939,47 @@ function analyzeSessionConnectivity(session) {
|
|
|
2371
2939
|
}
|
|
2372
2940
|
return session.physicalConnectivity;
|
|
2373
2941
|
}
|
|
2942
|
+
function addContactNeighbor(target, sourceId, targetId) {
|
|
2943
|
+
let neighbors = target.get(sourceId);
|
|
2944
|
+
if (!neighbors) {
|
|
2945
|
+
neighbors = /* @__PURE__ */ new Set();
|
|
2946
|
+
target.set(sourceId, neighbors);
|
|
2947
|
+
}
|
|
2948
|
+
neighbors.add(targetId);
|
|
2949
|
+
}
|
|
2950
|
+
function baseObjectIdForConnectivityEntry(entryId, renderableIds) {
|
|
2951
|
+
if (renderableIds.has(entryId)) return entryId;
|
|
2952
|
+
const bodySuffixIndex = entryId.lastIndexOf(":body:");
|
|
2953
|
+
if (bodySuffixIndex <= 0) return null;
|
|
2954
|
+
const baseId = entryId.slice(0, bodySuffixIndex);
|
|
2955
|
+
return renderableIds.has(baseId) ? baseId : null;
|
|
2956
|
+
}
|
|
2957
|
+
function buildThicknessRaycastConnectivityContext(session) {
|
|
2958
|
+
const report = analyzeSessionConnectivity(session);
|
|
2959
|
+
const renderableById = new Map(
|
|
2960
|
+
session.renderables.filter((renderable) => !renderable.sdfRaymarch).map((renderable) => [renderable.id, renderable])
|
|
2961
|
+
);
|
|
2962
|
+
const renderableIds = new Set(renderableById.keys());
|
|
2963
|
+
const neighborIdsByObjectId = /* @__PURE__ */ new Map();
|
|
2964
|
+
for (const edge of report.edges) {
|
|
2965
|
+
const sourceId = baseObjectIdForConnectivityEntry(edge.sourceId, renderableIds);
|
|
2966
|
+
const targetId = baseObjectIdForConnectivityEntry(edge.targetId, renderableIds);
|
|
2967
|
+
if (!sourceId || !targetId || sourceId === targetId) continue;
|
|
2968
|
+
addContactNeighbor(neighborIdsByObjectId, sourceId, targetId);
|
|
2969
|
+
addContactNeighbor(neighborIdsByObjectId, targetId, sourceId);
|
|
2970
|
+
}
|
|
2971
|
+
return { neighborIdsByObjectId, renderableById };
|
|
2972
|
+
}
|
|
2973
|
+
function connectedThicknessGeometriesFor(context, source) {
|
|
2974
|
+
const neighborIds = context.neighborIdsByObjectId.get(source.id);
|
|
2975
|
+
if (!neighborIds) return [];
|
|
2976
|
+
const geometries = [];
|
|
2977
|
+
for (const neighborId of neighborIds) {
|
|
2978
|
+
const renderable = context.renderableById.get(neighborId);
|
|
2979
|
+
if (renderable) geometries.push(renderable.solid.geometry);
|
|
2980
|
+
}
|
|
2981
|
+
return geometries;
|
|
2982
|
+
}
|
|
2374
2983
|
function decorateConnectivityReport(report) {
|
|
2375
2984
|
const components = report.components.map((component) => {
|
|
2376
2985
|
const color = maskColorForIndex(component.index);
|
|
@@ -2539,6 +3148,151 @@ function renderCurrentDistance(session) {
|
|
|
2539
3148
|
});
|
|
2540
3149
|
}
|
|
2541
3150
|
}
|
|
3151
|
+
function analyzeSessionFloating(session) {
|
|
3152
|
+
if (!session.floatingReport) {
|
|
3153
|
+
session.floatingReport = analyzeFloatingInspection(session.connectivityEntries, { groundZ: session.floatingGroundZ });
|
|
3154
|
+
}
|
|
3155
|
+
return session.floatingReport;
|
|
3156
|
+
}
|
|
3157
|
+
function decorateFloatingReport(report) {
|
|
3158
|
+
const components = report.components.map((component) => {
|
|
3159
|
+
const color = component.isFloating ? FLOATING_HIGHLIGHT_COLOR : FLOATING_HIDDEN_COLOR;
|
|
3160
|
+
return {
|
|
3161
|
+
...component,
|
|
3162
|
+
color,
|
|
3163
|
+
hex: colorHex(color)
|
|
3164
|
+
};
|
|
3165
|
+
});
|
|
3166
|
+
const objectByComponentIndex = new Map(components.map((component) => [component.index, component]));
|
|
3167
|
+
const objects = report.objects.map((object) => {
|
|
3168
|
+
var _a;
|
|
3169
|
+
const color = ((_a = objectByComponentIndex.get(object.componentIndex)) == null ? void 0 : _a.color) ?? FLOATING_HIDDEN_COLOR;
|
|
3170
|
+
return {
|
|
3171
|
+
...object,
|
|
3172
|
+
color,
|
|
3173
|
+
hex: colorHex(color)
|
|
3174
|
+
};
|
|
3175
|
+
});
|
|
3176
|
+
return {
|
|
3177
|
+
method: report.method,
|
|
3178
|
+
options: report.options,
|
|
3179
|
+
rootComponentIndex: report.rootComponentIndex,
|
|
3180
|
+
objectCount: report.objectCount,
|
|
3181
|
+
componentCount: report.componentCount,
|
|
3182
|
+
floatingComponentCount: report.floatingComponentCount,
|
|
3183
|
+
floatingObjectCount: report.floatingObjectCount,
|
|
3184
|
+
floatingBodyCount: report.floatingBodyCount,
|
|
3185
|
+
objects,
|
|
3186
|
+
components,
|
|
3187
|
+
contacts: report.contacts,
|
|
3188
|
+
warnings: report.warnings,
|
|
3189
|
+
style: {
|
|
3190
|
+
highlightColor: FLOATING_HIGHLIGHT_COLOR,
|
|
3191
|
+
highlightHex: colorHex(FLOATING_HIGHLIGHT_COLOR),
|
|
3192
|
+
contextColor: FLOATING_CONTEXT_COLOR,
|
|
3193
|
+
contextHex: colorHex(FLOATING_CONTEXT_COLOR),
|
|
3194
|
+
hiddenColor: FLOATING_HIDDEN_COLOR,
|
|
3195
|
+
hiddenHex: colorHex(FLOATING_HIDDEN_COLOR)
|
|
3196
|
+
}
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
function renderCurrentFloating(session) {
|
|
3200
|
+
const r = getRenderer(session.size, session.pixelRatio);
|
|
3201
|
+
const report = decorateFloatingReport(analyzeSessionFloating(session));
|
|
3202
|
+
const byId = new Map(report.objects.map((object) => [object.id, object]));
|
|
3203
|
+
const vertexColorsById = session.connectivityBodyInput ? meshVertexColorBuffersFor(session.connectivityBodyInput, (entryId) => {
|
|
3204
|
+
var _a;
|
|
3205
|
+
return rgbFloats(((_a = byId.get(entryId)) == null ? void 0 : _a.color) ?? FLOATING_HIDDEN_COLOR);
|
|
3206
|
+
}) : /* @__PURE__ */ new Map();
|
|
3207
|
+
const replacements = session.renderables.map((renderable) => {
|
|
3208
|
+
var _a;
|
|
3209
|
+
const object = byId.get(renderable.id);
|
|
3210
|
+
const colors = vertexColorsById.get(renderable.id);
|
|
3211
|
+
const vertexGeometry = colors ? geometryWithVisibleVertexColors(renderable.solid.geometry, colors) : null;
|
|
3212
|
+
const previousMaterial = renderable.solid.material;
|
|
3213
|
+
const previousGeometry = renderable.solid.geometry;
|
|
3214
|
+
const previousVisible = renderable.solid.visible;
|
|
3215
|
+
const previousWireVisible = renderable.wire.visible;
|
|
3216
|
+
const previousShellVisible = (_a = renderable.shell) == null ? void 0 : _a.visible;
|
|
3217
|
+
const contextMaterial = new MeshBasicMaterial({
|
|
3218
|
+
color: colorHex(FLOATING_CONTEXT_COLOR),
|
|
3219
|
+
transparent: true,
|
|
3220
|
+
opacity: 0.18,
|
|
3221
|
+
side: DoubleSide,
|
|
3222
|
+
depthWrite: false,
|
|
3223
|
+
clippingPlanes: renderable.solidMaterial.clippingPlanes ?? null
|
|
3224
|
+
});
|
|
3225
|
+
contextMaterial.toneMapped = false;
|
|
3226
|
+
const highlightMaterial = new MeshBasicMaterial({
|
|
3227
|
+
color: vertexGeometry ? 16777215 : colorHex(FLOATING_HIGHLIGHT_COLOR),
|
|
3228
|
+
vertexColors: Boolean(vertexGeometry),
|
|
3229
|
+
side: DoubleSide,
|
|
3230
|
+
depthWrite: false,
|
|
3231
|
+
clippingPlanes: renderable.solidMaterial.clippingPlanes ?? null,
|
|
3232
|
+
polygonOffset: true,
|
|
3233
|
+
polygonOffsetFactor: -1,
|
|
3234
|
+
polygonOffsetUnits: -1
|
|
3235
|
+
});
|
|
3236
|
+
highlightMaterial.toneMapped = false;
|
|
3237
|
+
renderable.solid.material = contextMaterial;
|
|
3238
|
+
renderable.solid.geometry = previousGeometry;
|
|
3239
|
+
renderable.solid.visible = true;
|
|
3240
|
+
const highlightGeometry = vertexGeometry ?? ((object == null ? void 0 : object.isFloating) === true ? previousGeometry : null);
|
|
3241
|
+
const highlightMesh = highlightGeometry ? new Mesh(highlightGeometry, highlightMaterial) : null;
|
|
3242
|
+
if (highlightMesh) {
|
|
3243
|
+
highlightMesh.renderOrder = 5;
|
|
3244
|
+
highlightMesh.raycast = () => null;
|
|
3245
|
+
renderable.root.add(highlightMesh);
|
|
3246
|
+
}
|
|
3247
|
+
renderable.wire.visible = false;
|
|
3248
|
+
if (renderable.shell) renderable.shell.visible = false;
|
|
3249
|
+
return {
|
|
3250
|
+
renderable,
|
|
3251
|
+
previousMaterial,
|
|
3252
|
+
previousGeometry,
|
|
3253
|
+
previousVisible,
|
|
3254
|
+
previousWireVisible,
|
|
3255
|
+
previousShellVisible,
|
|
3256
|
+
vertexGeometry,
|
|
3257
|
+
contextMaterial,
|
|
3258
|
+
highlightMaterial,
|
|
3259
|
+
highlightMesh
|
|
3260
|
+
};
|
|
3261
|
+
});
|
|
3262
|
+
try {
|
|
3263
|
+
const png = withTemporarySceneBackground(session, new Color(0), () => {
|
|
3264
|
+
updateSdfRaymarchUniforms(session);
|
|
3265
|
+
r.render(session.scene, session.camera);
|
|
3266
|
+
return captureRenderedPng(session.size);
|
|
3267
|
+
});
|
|
3268
|
+
return { png, report };
|
|
3269
|
+
} finally {
|
|
3270
|
+
replacements.forEach(
|
|
3271
|
+
({
|
|
3272
|
+
renderable,
|
|
3273
|
+
previousMaterial,
|
|
3274
|
+
previousGeometry,
|
|
3275
|
+
previousVisible,
|
|
3276
|
+
previousWireVisible,
|
|
3277
|
+
previousShellVisible,
|
|
3278
|
+
vertexGeometry,
|
|
3279
|
+
contextMaterial,
|
|
3280
|
+
highlightMaterial,
|
|
3281
|
+
highlightMesh
|
|
3282
|
+
}) => {
|
|
3283
|
+
if (highlightMesh) renderable.root.remove(highlightMesh);
|
|
3284
|
+
renderable.solid.material = previousMaterial;
|
|
3285
|
+
renderable.solid.geometry = previousGeometry;
|
|
3286
|
+
renderable.solid.visible = previousVisible;
|
|
3287
|
+
renderable.wire.visible = previousWireVisible;
|
|
3288
|
+
if (renderable.shell && previousShellVisible !== void 0) renderable.shell.visible = previousShellVisible;
|
|
3289
|
+
vertexGeometry == null ? void 0 : vertexGeometry.dispose();
|
|
3290
|
+
contextMaterial.dispose();
|
|
3291
|
+
highlightMaterial.dispose();
|
|
3292
|
+
}
|
|
3293
|
+
);
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
2542
3296
|
function analyzeSessionCollisions(session) {
|
|
2543
3297
|
if (!session.collisionReport) {
|
|
2544
3298
|
session.collisionReport = analyzeCollisionIntersections(session.collisionEntries);
|
|
@@ -2549,7 +3303,15 @@ function decorateCollisionReport(report) {
|
|
|
2549
3303
|
return {
|
|
2550
3304
|
method: report.method,
|
|
2551
3305
|
options: report.options,
|
|
3306
|
+
broadphase: report.broadphase,
|
|
2552
3307
|
objectCount: report.objectCount,
|
|
3308
|
+
candidatePairCount: report.candidatePairCount,
|
|
3309
|
+
bboxVolumePrunedPairCount: report.bboxVolumePrunedPairCount,
|
|
3310
|
+
testedPairCount: report.testedPairCount,
|
|
3311
|
+
skippedPairCount: report.skippedPairCount,
|
|
3312
|
+
pairLimitSkippedPairCount: report.pairLimitSkippedPairCount,
|
|
3313
|
+
timeBudgetSkippedPairCount: report.timeBudgetSkippedPairCount,
|
|
3314
|
+
exactCheckMs: report.exactCheckMs,
|
|
2553
3315
|
collisionCount: report.collisionCount,
|
|
2554
3316
|
objects: report.objects,
|
|
2555
3317
|
collisions: report.collisions.map((finding) => {
|
|
@@ -2637,6 +3399,26 @@ function renderCurrentCollisions(session) {
|
|
|
2637
3399
|
function inspectionOptionsKey(value) {
|
|
2638
3400
|
return JSON.stringify(value ?? {});
|
|
2639
3401
|
}
|
|
3402
|
+
function scalarInspectableObjectCount(session) {
|
|
3403
|
+
return session.renderables.filter((renderable) => !renderable.sdfRaymarch).length;
|
|
3404
|
+
}
|
|
3405
|
+
function withSceneSampleBudget(session, options, explicitMaxSamplesPerObject) {
|
|
3406
|
+
const sampleBudget = resolveScalarSceneSampleBudget({
|
|
3407
|
+
objectCount: scalarInspectableObjectCount(session),
|
|
3408
|
+
maxSamplesPerObject: options.maxSamplesPerObject,
|
|
3409
|
+
explicitMaxSamplesPerObject
|
|
3410
|
+
});
|
|
3411
|
+
return {
|
|
3412
|
+
options: { ...options, maxSamplesPerObject: sampleBudget.effectiveMaxSamplesPerObject },
|
|
3413
|
+
sampleBudget
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
function maybePushSceneSampleBudgetWarning(warnings, channel, sampleBudget) {
|
|
3417
|
+
if (!sampleBudget.capped) return;
|
|
3418
|
+
warnings.push(
|
|
3419
|
+
`${channel} inspection scene budget lowered maxSamplesPerObject from ${sampleBudget.requestedMaxSamplesPerObject} to ${sampleBudget.effectiveMaxSamplesPerObject} across ${sampleBudget.objectCount} mesh object(s); pass the sample-count flag to override.`
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
2640
3422
|
function bboxFromGeometry(geometry) {
|
|
2641
3423
|
geometry.computeBoundingBox();
|
|
2642
3424
|
const bbox = geometry.boundingBox;
|
|
@@ -2717,22 +3499,27 @@ function renderScalarPointOverlays(session, overlays) {
|
|
|
2717
3499
|
}
|
|
2718
3500
|
function getSessionThicknessInspection(session, rawOptions) {
|
|
2719
3501
|
var _a;
|
|
2720
|
-
const
|
|
2721
|
-
const
|
|
3502
|
+
const resolvedOptions = resolveThicknessInspectionOptions(rawOptions);
|
|
3503
|
+
const { options, sampleBudget } = withSceneSampleBudget(session, resolvedOptions, (rawOptions == null ? void 0 : rawOptions.maxSamplesPerObject) !== void 0);
|
|
3504
|
+
const optionsKey = inspectionOptionsKey({ options, sampleBudget });
|
|
2722
3505
|
if (((_a = session.thicknessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.thicknessInspection;
|
|
2723
3506
|
const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
|
|
2724
3507
|
const warnings = [];
|
|
3508
|
+
maybePushSceneSampleBudgetWarning(warnings, "Thickness", sampleBudget);
|
|
2725
3509
|
const objects = [];
|
|
2726
3510
|
const cloudObjects = [];
|
|
2727
3511
|
const points = [];
|
|
2728
3512
|
const overlays = [];
|
|
3513
|
+
const raycastConnectivity = buildThicknessRaycastConnectivityContext(session);
|
|
2729
3514
|
session.renderables.forEach((renderable, index) => {
|
|
2730
3515
|
const sourceObject = byId.get(renderable.id);
|
|
2731
3516
|
if (renderable.sdfRaymarch) {
|
|
2732
3517
|
warnings.push(`${renderable.name}: SDF raymarch objects are not included in mesh thickness inspection.`);
|
|
2733
3518
|
return;
|
|
2734
3519
|
}
|
|
2735
|
-
const analysis = analyzeThicknessGeometry(renderable.solid.geometry, options
|
|
3520
|
+
const analysis = analyzeThicknessGeometry(renderable.solid.geometry, options, {
|
|
3521
|
+
connectedGeometries: connectedThicknessGeometriesFor(raycastConnectivity, renderable)
|
|
3522
|
+
});
|
|
2736
3523
|
const bbox = bboxFromGeometry(analysis.geometry);
|
|
2737
3524
|
const summary = summarizeThicknessSamples(analysis.samples, options);
|
|
2738
3525
|
if (analysis.warnings.length > 0) {
|
|
@@ -2786,8 +3573,9 @@ function getSessionThicknessInspection(session, rawOptions) {
|
|
|
2786
3573
|
points
|
|
2787
3574
|
},
|
|
2788
3575
|
report: {
|
|
2789
|
-
method: "mesh-normal-raycast",
|
|
3576
|
+
method: "mesh-normal-raycast-contact-aware",
|
|
2790
3577
|
options,
|
|
3578
|
+
sampleBudget,
|
|
2791
3579
|
objectCount: objects.length,
|
|
2792
3580
|
objects,
|
|
2793
3581
|
warnings,
|
|
@@ -2815,11 +3603,13 @@ function renderCurrentRoughness(session, rawOptions) {
|
|
|
2815
3603
|
}
|
|
2816
3604
|
function getSessionRoughnessInspection(session, rawOptions) {
|
|
2817
3605
|
var _a;
|
|
2818
|
-
const
|
|
2819
|
-
const
|
|
3606
|
+
const resolvedOptions = resolveRoughnessInspectionOptions(rawOptions);
|
|
3607
|
+
const { options, sampleBudget } = withSceneSampleBudget(session, resolvedOptions, (rawOptions == null ? void 0 : rawOptions.maxSamplesPerObject) !== void 0);
|
|
3608
|
+
const optionsKey = inspectionOptionsKey({ options, sampleBudget });
|
|
2820
3609
|
if (((_a = session.roughnessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.roughnessInspection;
|
|
2821
3610
|
const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
|
|
2822
3611
|
const warnings = [];
|
|
3612
|
+
maybePushSceneSampleBudgetWarning(warnings, "Roughness", sampleBudget);
|
|
2823
3613
|
const objects = [];
|
|
2824
3614
|
const cloudObjects = [];
|
|
2825
3615
|
const points = [];
|
|
@@ -2882,6 +3672,7 @@ function getSessionRoughnessInspection(session, rawOptions) {
|
|
|
2882
3672
|
report: {
|
|
2883
3673
|
method: "mesh-dihedral-angle",
|
|
2884
3674
|
options,
|
|
3675
|
+
sampleBudget,
|
|
2885
3676
|
objectCount: objects.length,
|
|
2886
3677
|
objects,
|
|
2887
3678
|
warnings,
|
|
@@ -2900,14 +3691,14 @@ function getSessionRoughnessInspection(session, rawOptions) {
|
|
|
2900
3691
|
}
|
|
2901
3692
|
function emptySectionSvg() {
|
|
2902
3693
|
return {
|
|
2903
|
-
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 2 2" width="400" height="400"><rect x="-1" y="-1" width="2" height="2" fill="#
|
|
3694
|
+
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-1 -1 2 2" width="400" height="400"><rect x="-1" y="-1" width="2" height="2" fill="#f7f7f4"/></svg>',
|
|
2904
3695
|
width: 0,
|
|
2905
3696
|
height: 0,
|
|
2906
3697
|
area: 0,
|
|
2907
3698
|
pathCount: 0
|
|
2908
3699
|
};
|
|
2909
3700
|
}
|
|
2910
|
-
async function svgToPngDataUrl(svg, size, background = "#
|
|
3701
|
+
async function svgToPngDataUrl(svg, size, background = "#f7f7f4") {
|
|
2911
3702
|
if (!exportCtx) {
|
|
2912
3703
|
throw new Error("Could not create export canvas context.");
|
|
2913
3704
|
}
|
|
@@ -2965,7 +3756,7 @@ async function renderSectionAtlas(session, opts) {
|
|
|
2965
3756
|
sectionSketches.push({ name: entry.name, sketch });
|
|
2966
3757
|
}
|
|
2967
3758
|
}
|
|
2968
|
-
const svgDocument = sectionSketches.length > 0 ? buildSketchSvgDocument(sectionSketches, { edges: "thin" }) : emptySectionSvg();
|
|
3759
|
+
const svgDocument = sectionSketches.length > 0 ? buildSketchSvgDocument(sectionSketches, { edges: "thin", theme: "cad-section" }) : emptySectionSvg();
|
|
2969
3760
|
slices.push({
|
|
2970
3761
|
index,
|
|
2971
3762
|
offset,
|
|
@@ -3015,6 +3806,37 @@ function addRenderStyleLights(scene, style) {
|
|
|
3015
3806
|
new HemisphereLight(new Color(lights.hemisphereSky), new Color(lights.hemisphereGround), lights.hemisphereIntensity)
|
|
3016
3807
|
);
|
|
3017
3808
|
}
|
|
3809
|
+
function fieldKindUniform(kind) {
|
|
3810
|
+
return kind === "hybrid" ? 1 : 0;
|
|
3811
|
+
}
|
|
3812
|
+
function createSurfaceFieldMaterial({
|
|
3813
|
+
field,
|
|
3814
|
+
objectColor,
|
|
3815
|
+
clippingPlanes,
|
|
3816
|
+
fieldScale
|
|
3817
|
+
}) {
|
|
3818
|
+
return new ShaderMaterial({
|
|
3819
|
+
vertexShader: SURFACE_FIELD_VERTEX_SHADER,
|
|
3820
|
+
fragmentShader: SURFACE_FIELD_FRAGMENT_SHADER,
|
|
3821
|
+
uniforms: {
|
|
3822
|
+
uAccentColor: { value: new Color(field.accentColor) },
|
|
3823
|
+
uBaseColor: { value: new Color(field.baseColor) },
|
|
3824
|
+
uDarkColor: { value: new Color(field.darkColor) },
|
|
3825
|
+
uFieldKind: { value: fieldKindUniform(field.kind) },
|
|
3826
|
+
uFieldScale: { value: fieldScale },
|
|
3827
|
+
uGlow: { value: field.glow },
|
|
3828
|
+
uLineColor: { value: new Color(field.lineColor) },
|
|
3829
|
+
uLineWidth: { value: field.lineWidth },
|
|
3830
|
+
uNodeColor: { value: new Color(field.nodeColor) },
|
|
3831
|
+
uObjectColor: { value: objectColor },
|
|
3832
|
+
uObjectColorMix: { value: field.objectColorMix },
|
|
3833
|
+
uSpacing: { value: field.spacing }
|
|
3834
|
+
},
|
|
3835
|
+
side: DoubleSide,
|
|
3836
|
+
toneMapped: false,
|
|
3837
|
+
clippingPlanes
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3018
3840
|
function createSceneLight(def) {
|
|
3019
3841
|
const color = def.color ? new Color(def.color) : new Color(16777215);
|
|
3020
3842
|
const intensity = def.intensity ?? 1;
|
|
@@ -3703,7 +4525,7 @@ function isFocusVisible(obj, focus, hide) {
|
|
|
3703
4525
|
return true;
|
|
3704
4526
|
}
|
|
3705
4527
|
function createSession(code, opts) {
|
|
3706
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
|
|
4528
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o;
|
|
3707
4529
|
const size = (opts == null ? void 0 : opts.size) ?? 1024;
|
|
3708
4530
|
const pixelRatio = (opts == null ? void 0 : opts.pixelRatio) ?? 1;
|
|
3709
4531
|
const debug = createCaptureDebugLogger(opts == null ? void 0 : opts.debug);
|
|
@@ -3785,8 +4607,6 @@ function createSession(code, opts) {
|
|
|
3785
4607
|
name: entry.source.name,
|
|
3786
4608
|
shape: entry.shape
|
|
3787
4609
|
}));
|
|
3788
|
-
const connectivityBodyInput = (opts == null ? void 0 : opts.includeConnectivity) ? buildMeshBodyConnectivityInput(shapeVisibleObjs.map(meshConnectivitySource), { bodyShape: () => new EmptyInspectionShape() }) : null;
|
|
3789
|
-
const connectivityEntries = (connectivityBodyInput == null ? void 0 : connectivityBodyInput.entries) ?? [];
|
|
3790
4610
|
const collisionEntries = (opts == null ? void 0 : opts.includeCollisions) ? shapeVisibleObjs.map((entry) => {
|
|
3791
4611
|
const bb2 = entry.shape.boundingBox();
|
|
3792
4612
|
return {
|
|
@@ -3843,6 +4663,7 @@ function createSession(code, opts) {
|
|
|
3843
4663
|
const bsize = new Vector3();
|
|
3844
4664
|
bb.getSize(bsize);
|
|
3845
4665
|
const maxDim = Math.max(1, bsize.x, bsize.y, bsize.z);
|
|
4666
|
+
const surfaceFieldScale = maxDim / 5;
|
|
3846
4667
|
const fov = 45;
|
|
3847
4668
|
const distance = maxDim / (2 * Math.tan(fov * Math.PI / 360)) * 1.6;
|
|
3848
4669
|
const cameraFov = ((_c = requestedSceneState == null ? void 0 : requestedSceneState.camera) == null ? void 0 : _c.fov) ?? ((_d = sceneConfig == null ? void 0 : sceneConfig.camera) == null ? void 0 : _d.fov) ?? fov;
|
|
@@ -3949,15 +4770,17 @@ function createSession(code, opts) {
|
|
|
3949
4770
|
edgeSegments: Math.floor((((_m = geo.edges.getAttribute("position")) == null ? void 0 : _m.count) ?? 0) / 2)
|
|
3950
4771
|
});
|
|
3951
4772
|
const materialDefaults = renderStylePreset.material;
|
|
4773
|
+
const surfaceField = renderStylePreset.surfaceField;
|
|
3952
4774
|
const authoredMaterialOpacity = mp == null ? void 0 : mp.opacity;
|
|
3953
4775
|
const authoredMaterialTransmission = mp == null ? void 0 : mp.transmission;
|
|
3954
4776
|
const hasAuthoredTransparency = authoredMaterialOpacity !== void 0 && authoredMaterialOpacity < 0.99 || authoredMaterialTransmission !== void 0 && authoredMaterialTransmission > 0;
|
|
3955
4777
|
const transparentDefaults = hasAuthoredTransparency ? materialDefaults.authoredTransparent : materialDefaults;
|
|
3956
4778
|
const materialOpacity = Math.min(obj.opacity, authoredMaterialOpacity ?? materialDefaults.opacity);
|
|
3957
4779
|
const materialTransmission = authoredMaterialTransmission ?? transparentDefaults.transmission;
|
|
4780
|
+
const objectColor = parseColor(obj.color, CAD_MATERIAL_PROPS.color);
|
|
3958
4781
|
const solidMaterialProps = {
|
|
3959
4782
|
...CAD_MATERIAL_PROPS,
|
|
3960
|
-
color:
|
|
4783
|
+
color: objectColor,
|
|
3961
4784
|
metalness: (mp == null ? void 0 : mp.metalness) ?? materialDefaults.metalness,
|
|
3962
4785
|
roughness: (mp == null ? void 0 : mp.roughness) ?? transparentDefaults.roughness,
|
|
3963
4786
|
clearcoat: (mp == null ? void 0 : mp.clearcoat) ?? transparentDefaults.clearcoat,
|
|
@@ -3973,7 +4796,12 @@ function createSession(code, opts) {
|
|
|
3973
4796
|
...(mp == null ? void 0 : mp.emissiveIntensity) !== void 0 && { emissiveIntensity: mp.emissiveIntensity },
|
|
3974
4797
|
...(mp == null ? void 0 : mp.wireframe) && { wireframe: true }
|
|
3975
4798
|
};
|
|
3976
|
-
solidMaterial =
|
|
4799
|
+
solidMaterial = surfaceField.enabled ? createSurfaceFieldMaterial({
|
|
4800
|
+
field: surfaceField,
|
|
4801
|
+
objectColor,
|
|
4802
|
+
clippingPlanes: applicableCutPlanes,
|
|
4803
|
+
fieldScale: surfaceFieldScale
|
|
4804
|
+
}) : new MeshPhysicalMaterial({
|
|
3977
4805
|
...solidMaterialProps,
|
|
3978
4806
|
transparent: materialOpacity < 1 || materialTransmission > 0,
|
|
3979
4807
|
opacity: materialOpacity,
|
|
@@ -4051,6 +4879,19 @@ function createSession(code, opts) {
|
|
|
4051
4879
|
...sdfRaymarch ? { sdfRaymarch } : {}
|
|
4052
4880
|
});
|
|
4053
4881
|
}
|
|
4882
|
+
const renderableById = new Map(renderables.map((renderable) => [renderable.id, renderable]));
|
|
4883
|
+
const connectivityBodyInput = (opts == null ? void 0 : opts.includeConnectivity) ? buildMeshBodyConnectivityInput(
|
|
4884
|
+
shapeVisibleObjs.map(
|
|
4885
|
+
(entry) => {
|
|
4886
|
+
var _a2;
|
|
4887
|
+
return meshConnectivitySource(entry, cloneGeometryPositions((_a2 = renderableById.get(entry.source.id)) == null ? void 0 : _a2.solid.geometry));
|
|
4888
|
+
}
|
|
4889
|
+
),
|
|
4890
|
+
{ bodyShape: () => new EmptyInspectionShape() }
|
|
4891
|
+
) : null;
|
|
4892
|
+
const connectivityEntries = (connectivityBodyInput == null ? void 0 : connectivityBodyInput.entries) ?? [];
|
|
4893
|
+
const groundOffset = Number.isFinite((_n = sceneConfig == null ? void 0 : sceneConfig.ground) == null ? void 0 : _n.offset) ? sceneConfig.ground.offset : 0;
|
|
4894
|
+
const floatingGroundZ = bbox.min[2] - groundOffset;
|
|
4054
4895
|
let sceneConfigCameraState = null;
|
|
4055
4896
|
if ((sceneConfig == null ? void 0 : sceneConfig.camera) && !(requestedSceneState == null ? void 0 : requestedSceneState.camera)) {
|
|
4056
4897
|
const cam = sceneConfig.camera;
|
|
@@ -4068,7 +4909,7 @@ function createSession(code, opts) {
|
|
|
4068
4909
|
}
|
|
4069
4910
|
const cameraSpec = sceneConfigCameraState && (opts == null ? void 0 : opts.capture) === "section-sweep" ? fitCameraStateToBounds(sceneConfigCameraState, bbox, cameraFov) : (requestedSceneState == null ? void 0 : requestedSceneState.camera) ?? sceneConfigCameraState;
|
|
4070
4911
|
const cameraRig = buildCameraRig(center, distance, maxDim, cameraSpec, cameraFov);
|
|
4071
|
-
const explicitCameraFov = (cameraSpec == null ? void 0 : cameraSpec.fov) ?? ((
|
|
4912
|
+
const explicitCameraFov = (cameraSpec == null ? void 0 : cameraSpec.fov) ?? ((_o = sceneConfig == null ? void 0 : sceneConfig.camera) == null ? void 0 : _o.fov);
|
|
4072
4913
|
if (explicitCameraFov && cameraRig.camera instanceof PerspectiveCamera) {
|
|
4073
4914
|
cameraRig.camera.fov = explicitCameraFov;
|
|
4074
4915
|
cameraRig.camera.updateProjectionMatrix();
|
|
@@ -4090,8 +4931,10 @@ function createSession(code, opts) {
|
|
|
4090
4931
|
sectionShapes,
|
|
4091
4932
|
connectivityEntries,
|
|
4092
4933
|
connectivityBodyInput,
|
|
4934
|
+
floatingGroundZ,
|
|
4093
4935
|
physicalConnectivity: null,
|
|
4094
4936
|
distanceReport: null,
|
|
4937
|
+
floatingReport: null,
|
|
4095
4938
|
collisionEntries,
|
|
4096
4939
|
collisionReport: null,
|
|
4097
4940
|
thicknessInspection: null,
|
|
@@ -4121,6 +4964,7 @@ function createSession(code, opts) {
|
|
|
4121
4964
|
}
|
|
4122
4965
|
async function setup() {
|
|
4123
4966
|
await init();
|
|
4967
|
+
setActiveBackend(CLI_DEFAULT_BACKEND);
|
|
4124
4968
|
window.__forgeCaptureCapabilities = CAPTURE_RUNTIME_CAPABILITIES;
|
|
4125
4969
|
window.__forgeReady = true;
|
|
4126
4970
|
}
|
|
@@ -4161,7 +5005,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4161
5005
|
hide: opts == null ? void 0 : opts.hide,
|
|
4162
5006
|
paramOverrides: opts == null ? void 0 : opts.paramOverrides,
|
|
4163
5007
|
renderStyle: opts == null ? void 0 : opts.renderStyle,
|
|
4164
|
-
includeConnectivity: requestedChannels.has("connectivity") || requestedChannels.has("distance"),
|
|
5008
|
+
includeConnectivity: requestedChannels.has("connectivity") || requestedChannels.has("floating") || requestedChannels.has("distance") || requestedChannels.has("thickness"),
|
|
4165
5009
|
includeCollisions: requestedChannels.has("collisions"),
|
|
4166
5010
|
capture: "orbit"
|
|
4167
5011
|
});
|
|
@@ -4188,14 +5032,17 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4188
5032
|
const renders = {};
|
|
4189
5033
|
const depthRenders = {};
|
|
4190
5034
|
const normalRenders = {};
|
|
5035
|
+
const zebraRenders = {};
|
|
4191
5036
|
const maskRenders = {};
|
|
4192
5037
|
const connectivityRenders = {};
|
|
5038
|
+
const floatingRenders = {};
|
|
4193
5039
|
const distanceRenders = {};
|
|
4194
5040
|
const collisionRenders = {};
|
|
4195
5041
|
const thicknessRenders = {};
|
|
4196
5042
|
const roughnessRenders = {};
|
|
4197
5043
|
let maskObjects = [];
|
|
4198
5044
|
let connectivityReport = null;
|
|
5045
|
+
let floatingReport = null;
|
|
4199
5046
|
let distanceReport = null;
|
|
4200
5047
|
let collisionReport = null;
|
|
4201
5048
|
let thicknessReport = null;
|
|
@@ -4242,6 +5089,11 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4242
5089
|
normalRenders[label] = renderCurrentNormals(session);
|
|
4243
5090
|
await markChannelViewDone("normals", label);
|
|
4244
5091
|
}
|
|
5092
|
+
if (requestedChannels.has("zebra")) {
|
|
5093
|
+
await markChannelViewStart("zebra", label);
|
|
5094
|
+
zebraRenders[label] = renderCurrentZebra(session);
|
|
5095
|
+
await markChannelViewDone("zebra", label);
|
|
5096
|
+
}
|
|
4245
5097
|
if (requestedChannels.has("roughness")) {
|
|
4246
5098
|
await markChannelViewStart("roughness", label);
|
|
4247
5099
|
const roughness = renderCurrentRoughness(session, opts == null ? void 0 : opts.roughness);
|
|
@@ -4267,6 +5119,13 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4267
5119
|
connectivityReport = connectivity.report;
|
|
4268
5120
|
await markChannelViewDone("connectivity", label);
|
|
4269
5121
|
}
|
|
5122
|
+
if (requestedChannels.has("floating")) {
|
|
5123
|
+
await markChannelViewStart("floating", label);
|
|
5124
|
+
const floating = renderCurrentFloating(session);
|
|
5125
|
+
floatingRenders[label] = floating.png;
|
|
5126
|
+
floatingReport = floating.report;
|
|
5127
|
+
await markChannelViewDone("floating", label);
|
|
5128
|
+
}
|
|
4270
5129
|
if (requestedChannels.has("distance")) {
|
|
4271
5130
|
await markChannelViewStart("distance", label);
|
|
4272
5131
|
const distance = renderCurrentDistance(session);
|
|
@@ -4311,6 +5170,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4311
5170
|
renders,
|
|
4312
5171
|
depth: depthRenders,
|
|
4313
5172
|
normals: normalRenders,
|
|
5173
|
+
zebra: zebraRenders,
|
|
4314
5174
|
roughness: roughnessReport ? {
|
|
4315
5175
|
...roughnessReport,
|
|
4316
5176
|
pointCloud: roughnessPointCloud,
|
|
@@ -4328,6 +5188,12 @@ window.__forgeRender = async (code, opts) => {
|
|
|
4328
5188
|
} : {
|
|
4329
5189
|
views: connectivityRenders
|
|
4330
5190
|
},
|
|
5191
|
+
floating: floatingReport ? {
|
|
5192
|
+
...floatingReport,
|
|
5193
|
+
views: floatingRenders
|
|
5194
|
+
} : {
|
|
5195
|
+
views: floatingRenders
|
|
5196
|
+
},
|
|
4331
5197
|
distance: distanceReport ? {
|
|
4332
5198
|
...distanceReport,
|
|
4333
5199
|
views: distanceRenders
|