forgecad 0.10.3 → 0.10.4

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.
Files changed (99) hide show
  1. package/dist/assets/{AdminPage-CK7ObBz3.js → AdminPage-B3L3W1Uo.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-Ds7Z2doN.js → BenchmarkPage-DXKVXMrJ.js} +2 -2
  3. package/dist/assets/{BlogPage-DlPbpt6A.js → BlogPage-B7BWxOCg.js} +1 -1
  4. package/dist/assets/{DocsPage-vZb3b3Y0.js → DocsPage-BPGGwht1.js} +28 -43
  5. package/dist/assets/{EditorApp-HLoKfe15.js → EditorApp-BWUGCdD5.js} +49 -16
  6. package/dist/assets/{EmbedViewer--KnqBKrJ.js → EmbedViewer-DygByZS2.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-C_LssmnA.js → LandingPageProofDriven-BoVE7JGY.js} +54 -36
  8. package/dist/assets/{LegalPage-DGsyo4n1.js → LegalPage-Din8wv8d.js} +2 -2
  9. package/dist/assets/{PricingPage-BOE27B-R.js → PricingPage-C2PMzmDc.js} +2 -2
  10. package/dist/assets/{SettingsPage-f47cnk39.js → SettingsPage-BlJDCRe8.js} +1 -1
  11. package/dist/assets/{app-D6ccu2Xx.js → app-BsRYSfxY.js} +238 -3714
  12. package/dist/assets/{backendInit-DbTkQN9J.js → backendInit-6C0DLgH0.js} +5972 -1566
  13. package/dist/assets/cli/{render-BsngirjC.js → render-XXol_ET7.js} +724 -112
  14. package/dist/assets/{constructionHistoryWorker-PCwXrTDB.js → constructionHistoryWorker-cTHWRJEi.js} +528 -252
  15. package/dist/assets/{evalWorker-CS63PfZu.js → evalWorker-BssDYW9u.js} +1453 -902
  16. package/dist/assets/{inspectWorker-Y4cOzNyA.js → inspectWorker-ymhBV4Ll.js} +2635 -1024
  17. package/dist/assets/{jointPose-AMvCywzS.js → jointPose-B0blBj9A.js} +1 -1
  18. package/dist/assets/{landing-proof-driven-ORyigZ6p.css → landing-proof-driven-Cpf-MIbI.css} +73 -13
  19. package/dist/assets/{manifold-Crd_F2qx.js → manifold-B_7QXpGB.js} +1 -1
  20. package/dist/assets/{manifold-k2kRcc85.js → manifold-CNShmpEJ.js} +1 -1
  21. package/dist/assets/{manifold-CBry38ly.js → manifold-CYlIm-M6.js} +2 -2
  22. package/dist/assets/{reportWorker-CWvn0CEv.js → reportWorker-Cb5eyM7D.js} +1407 -892
  23. package/dist/cli/render.html +1 -1
  24. package/dist/docs/index.html +2 -2
  25. package/dist/docs-raw/AI/usage.md +17 -15
  26. package/dist/docs-raw/component-model.md +2 -2
  27. package/dist/docs-raw/generated/concepts.md +5 -1
  28. package/dist/docs-raw/generated/core.md +26 -0
  29. package/dist/docs-raw/generated/runtime-names.md +1 -1
  30. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  31. package/dist/docs-raw/simulation-workflow.md +1 -1
  32. package/dist/docs-raw/skills/{forgecad-make-a-model.md → forgecad-build-model.md} +18 -8
  33. package/dist/docs-raw/skills/{forgecad-spec-by-walking-through-it.md → forgecad-design-spec.md} +6 -6
  34. package/dist/docs-raw/skills/{forgecad-model-grader.md → forgecad-grade-model.md} +8 -6
  35. package/{dist-skill/website/skills/forgecad-visual-spec.md → dist/docs-raw/skills/forgecad-image-prompt.md} +7 -7
  36. package/dist/docs-raw/skills/{forgecad-render-inspect.md → forgecad-inspect-model.md} +6 -6
  37. package/{dist-skill/website/skills/forgecad-project.md → dist/docs-raw/skills/forgecad-project-sync.md} +5 -5
  38. package/dist/docs-raw/skills/{forgecad-3d-reconstruction.md → forgecad-reconstruct-cad-file.md} +7 -7
  39. package/dist/docs-raw/skills/{forgecad-image-replicator.md → forgecad-reconstruct-from-images.md} +12 -12
  40. package/dist/docs-raw/skills/{forgecad-mujoco-verify.md → forgecad-verify-mujoco.md} +6 -6
  41. package/dist/docs-raw/skills/index.md +9 -12
  42. package/dist/index.html +9 -9
  43. package/dist/llms.txt +7 -7
  44. package/dist/sitemap.xml +16 -16
  45. package/dist-cli/{check-compiler-HPF2T2FS.js → check-compiler-4RPB6SB5.js} +1 -1
  46. package/dist-cli/{check-query-propagation-HYSLTXAB.js → check-query-propagation-KN3DFQTX.js} +1 -1
  47. package/dist-cli/{chunk-WLUKAW3H.js → chunk-UHBRMYA6.js} +28802 -28152
  48. package/dist-cli/forgecad.js +660 -9
  49. package/dist-skill/CONTEXT.md +27 -1
  50. package/dist-skill/docs/generated/core.md +26 -0
  51. package/dist-skill/docs/generated/runtime-names.md +1 -1
  52. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  53. package/dist-skill/library/README.md +9 -12
  54. package/dist-skill/library/{forgecad-make-a-model → forgecad-build-model}/SKILL.md +16 -6
  55. package/dist-skill/library/{forgecad-spec-by-walking-through-it → forgecad-design-spec}/SKILL.md +4 -4
  56. package/dist-skill/library/{forgecad-spec-by-walking-through-it → forgecad-design-spec}/references/master-prompt.md +1 -1
  57. package/dist-skill/library/{forgecad-model-grader → forgecad-grade-model}/SKILL.md +6 -4
  58. package/dist-skill/library/forgecad-grade-model/agents/openai.yaml +4 -0
  59. package/dist-skill/library/{forgecad-visual-spec → forgecad-image-prompt}/SKILL.md +5 -5
  60. package/dist-skill/library/forgecad-image-prompt/agents/openai.yaml +4 -0
  61. package/dist-skill/library/{forgecad-render-inspect → forgecad-inspect-model}/SKILL.md +4 -4
  62. package/dist-skill/library/{forgecad-project → forgecad-project-sync}/SKILL.md +3 -3
  63. package/dist-skill/library/{forgecad-3d-reconstruction → forgecad-reconstruct-cad-file}/SKILL.md +5 -5
  64. package/dist-skill/library/forgecad-reconstruct-cad-file/agents/openai.yaml +4 -0
  65. package/dist-skill/library/{forgecad-image-replicator → forgecad-reconstruct-from-images}/SKILL.md +10 -10
  66. package/dist-skill/library/forgecad-reconstruct-from-images/agents/openai.yaml +4 -0
  67. package/dist-skill/library/{forgecad-mujoco-verify → forgecad-verify-mujoco}/SKILL.md +4 -4
  68. package/dist-skill/website/skills/{forgecad-make-a-model.md → forgecad-build-model.md} +18 -8
  69. package/dist-skill/website/skills/{forgecad-spec-by-walking-through-it.md → forgecad-design-spec.md} +6 -6
  70. package/dist-skill/website/skills/{forgecad-model-grader.md → forgecad-grade-model.md} +8 -6
  71. package/{dist/docs-raw/skills/forgecad-visual-spec.md → dist-skill/website/skills/forgecad-image-prompt.md} +7 -7
  72. package/dist-skill/website/skills/{forgecad-render-inspect.md → forgecad-inspect-model.md} +6 -6
  73. package/{dist/docs-raw/skills/forgecad-project.md → dist-skill/website/skills/forgecad-project-sync.md} +5 -5
  74. package/dist-skill/website/skills/{forgecad-3d-reconstruction.md → forgecad-reconstruct-cad-file.md} +7 -7
  75. package/dist-skill/website/skills/{forgecad-image-replicator.md → forgecad-reconstruct-from-images.md} +12 -12
  76. package/dist-skill/website/skills/{forgecad-mujoco-verify.md → forgecad-verify-mujoco.md} +6 -6
  77. package/dist-skill/website/skills/index.md +9 -12
  78. package/examples/api/texture-projection.forge.js +75 -0
  79. package/examples/assets/uv-grid.png +0 -0
  80. package/package.json +1 -1
  81. package/dist/docs-raw/skills/forgecad-blockout-model.md +0 -49
  82. package/dist/docs-raw/skills/forgecad-component-model.md +0 -53
  83. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +0 -60
  84. package/dist-skill/library/forgecad-3d-reconstruction/agents/openai.yaml +0 -4
  85. package/dist-skill/library/forgecad-blockout-model/SKILL.md +0 -42
  86. package/dist-skill/library/forgecad-component-model/SKILL.md +0 -46
  87. package/dist-skill/library/forgecad-image-replicator/agents/openai.yaml +0 -4
  88. package/dist-skill/library/forgecad-model-grader/agents/openai.yaml +0 -4
  89. package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +0 -48
  90. package/dist-skill/library/forgecad-reconstruction-benchmark/agents/openai.yaml +0 -4
  91. package/dist-skill/library/forgecad-visual-spec/agents/openai.yaml +0 -4
  92. package/dist-skill/website/skills/forgecad-blockout-model.md +0 -49
  93. package/dist-skill/website/skills/forgecad-component-model.md +0 -53
  94. package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +0 -60
  95. /package/dist/assets/{landing-proof-driven-DiGqdtWa.js → landing-proof-driven-BxZZh5r5.js} +0 -0
  96. /package/dist-skill/library/{forgecad-spec-by-walking-through-it → forgecad-design-spec}/references/default-profiles.md +0 -0
  97. /package/dist-skill/library/{forgecad-render-inspect → forgecad-inspect-model}/summarize_manifest.py +0 -0
  98. /package/dist-skill/library/{forgecad-image-replicator → forgecad-reconstruct-from-images}/scripts/compare_images.py +0 -0
  99. /package/dist-skill/library/{forgecad-mujoco-verify → forgecad-verify-mujoco}/scripts/mujoco_verify.py +0 -0
@@ -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, a as Scene, bw as BoxGeometry, c1 as MeshStandardMaterial, a5 as BackSide, b2 as PointLight, M as Mesh, af as MeshBasicMaterial, cF as localAabbPlaneRelation, i as Vector2, cG as ShapeUtils, cH as analyzePhysicalConnectivity, h as Vector3, a0 as Matrix4, cI as Frustum, J as Box3, a1 as MathUtils, cJ as meshContactDataFor, cK as AabbSpatialIndex, cL as detectPhysicalContact, cM as resolveThicknessInspectionOptions, R as Raycaster, cN as thicknessColor, cO as thicknessClass, a6 as BufferAttribute, cE as initBackendForEvaluation, f as Color, b5 as COMPARISON_COLORS, aE as resolveForgeRenderStyle, bG as getRenderStylePreset, aD as setParamOverrides, bk as runScript, cp as scanProxyGridForBounds, cP as Group, b9 as shapeToGeometry, bl as MeshPhysicalMaterial, bD as AdditiveBlending, bJ as scanMaterialShellColor, cQ as createScanProxyGeometry, aU as LineBasicMaterial, bI as NormalBlending, bm as LineSegments, P as PerspectiveCamera, cm as DEFAULT_VIEW_CONFIG, bs as worldAuthorPlaneToLocal, cR as resolveSectionHatchMetrics, cv as buildGeometryComparisonPointCloud, ct as triangleSoupFromMeshes, O as OrthographicCamera, l as ShaderMaterial, bP as ZEBRA_STRIPE_FRAGMENT_SHADER, bQ as ZEBRA_STRIPE_VERTEX_SHADER, bK as ZEBRA_STRIPE_SOFTNESS, bL as ZEBRA_STRIPE_SCALE, bM as ZEBRA_LIGHT_COLOR, bN as ZEBRA_DARK_COLOR, bO as ZEBRA_ACCENT_COLOR, bE as geometryWithVisibleVertexColors, cS as intersectWithPlane, cT as setActiveBackend, W as WebGLRenderer, A as ACESFilmicToneMapping, d as SRGBColorSpace, cU as parseCameraCliSpec, cV as PMREMGenerator, aX as CanvasTexture, aY as Object3D, aZ as FogExp2, a_ as Fog, a$ as AmbientLight, b3 as DirectionalLight, b0 as HemisphereLight, aO as findJointAnimationClip, q as Plane, bT as SURFACE_FIELD_FRAGMENT_SHADER, bU as SURFACE_FIELD_VERTEX_SHADER, bS as scanMaterialLayerStyles, bR as SCAN_PROXY_LAYER_STYLES, aP as resolveJointAnimation, aQ as resolveJointViewValues, aT as BufferGeometry, cW as DEFAULT_ROUGHNESS_COLOR_SCALE, cX as PointsMaterial, cY as Points, b4 as heatPointsForSide, cZ as analyzeCollisionIntersections, c_ as serializeCollisionFinding, c$ as summarizeThicknessSamples, d0 as THICKNESS_GRADIENT_COLORS, d1 as THICKNESS_COLORS, d2 as DEFAULT_THICKNESS_COLOR_SCALE, b1 as SpotLight, c5 as CylinderGeometry, d3 as TorusGeometry, bW as CatmullRomCurve3, bX as TubeGeometry, cz as resolveScalarSceneSampleBudget, d4 as sampleColorScale, ba as buildComparisonHeatPatchGeometry, bb as EdgesGeometry, d5 as SphereGeometry, d6 as ConeGeometry, b6 as comparisonHeatDepthTest, b7 as comparisonHeatEdgeOpacity, b8 as comparisonHeatPatchOpacity, cC as comparisonCandidateContextOpacity, d7 as DEFAULT_COMPARISON_CANDIDATE_OPACITY } from "../backendInit-DbTkQN9J.js";
5
- import { m as mergeViewportRenderSceneStates, v as validateJointOverrides, b as buildBaseJointValues, p as parseRenderSceneCliSpec, g as getSceneObjectTreePath } from "../jointPose-AMvCywzS.js";
4
+ import { D as DoubleSide, a as Scene, bH as BoxGeometry, cm as MeshStandardMaterial, a5 as BackSide, bd as PointLight, M as Mesh, ab as MeshBasicMaterial, c$ as localAabbPlaneRelation, i as Vector2, d0 as ShapeUtils, d1 as analyzePhysicalConnectivity, h as Vector3, a0 as Matrix4, d2 as Frustum, J as Box3, a1 as MathUtils, d3 as meshContactDataFor, d4 as AabbSpatialIndex, d5 as detectPhysicalContact, d6 as resolveThicknessInspectionOptions, R as Raycaster, d7 as thicknessColor, d8 as thicknessClass, bf as BufferAttribute, bQ as MeshBVH, b_ as acceleratedRaycast, d9 as requireFiniteNumber, da as requireIntegerAtLeast, db as requirePositiveFiniteNumber, c_ as initBackendForEvaluation, f as Color, bh as COMPARISON_COLORS, aB as resolveForgeRenderStyle, bX as getRenderStylePreset, az as setParamOverrides, bw as runScript, cK as scanProxyGridForBounds, dc as Group, bl as shapeToGeometry, bx as MeshPhysicalMaterial, bO as AdditiveBlending, c1 as scanMaterialShellColor, bV as descriptorToThreeTexture, bW as applyProjectedTexture, dd as createScanProxyGeometry, b3 as LineBasicMaterial, bZ as NormalBlending, by as LineSegments, P as PerspectiveCamera, cH as DEFAULT_VIEW_CONFIG, bD as worldAuthorPlaneToLocal, de as resolveSectionHatchMetrics, cQ as buildGeometryComparisonPointCloud, cO as triangleSoupFromMeshes, O as OrthographicCamera, l as ShaderMaterial, c7 as ZEBRA_STRIPE_FRAGMENT_SHADER, c8 as ZEBRA_STRIPE_VERTEX_SHADER, c2 as ZEBRA_STRIPE_SOFTNESS, c3 as ZEBRA_STRIPE_SCALE, c4 as ZEBRA_LIGHT_COLOR, c5 as ZEBRA_DARK_COLOR, c6 as ZEBRA_ACCENT_COLOR, bP as geometryWithVisibleVertexColors, df as intersectWithPlane, dg as setActiveBackend, W as WebGLRenderer, A as ACESFilmicToneMapping, d as SRGBColorSpace, dh as parseCameraCliSpec, di as PMREMGenerator, b6 as CanvasTexture, b7 as Object3D, b8 as FogExp2, b9 as Fog, ba as AmbientLight, be as DirectionalLight, bb as HemisphereLight, aW as findJointAnimationClip, q as Plane, cb as SURFACE_FIELD_FRAGMENT_SHADER, cc as SURFACE_FIELD_VERTEX_SHADER, ca as scanMaterialLayerStyles, c9 as SCAN_PROXY_LAYER_STYLES, aX as resolveJointAnimation, aY as resolveJointViewValues, b2 as BufferGeometry, dj as DEFAULT_ROUGHNESS_COLOR_SCALE, bR as makeColorScaleTexture, bS as colorScaleLUT, bT as makeInspectScalarUniforms, b$ as INSPECT_SCALAR_FRAGMENT_SHADER, c0 as INSPECT_SCALAR_VERTEX_SHADER, bg as heatPointsForSide, dk as analyzeCollisionIntersections, dl as serializeCollisionFinding, dm as summarizeThicknessSamples, dn as THICKNESS_COLORS, dp as DEFAULT_THICKNESS_COLOR_SCALE, bc as SpotLight, cq as CylinderGeometry, dq as TorusGeometry, ce as CatmullRomCurve3, cf as TubeGeometry, cU as resolveScalarSceneSampleBudget, dr as DEFAULT_INSPECT_ISOLINE_SPACING, ch as DEFAULT_COLORMAP, bm as buildComparisonHeatPatchGeometry, bn as EdgesGeometry, ds as SphereGeometry, dt as ConeGeometry, bi as comparisonHeatDepthTest, bj as comparisonHeatEdgeOpacity, bk as comparisonHeatPatchOpacity, cY as comparisonCandidateContextOpacity, du as DEFAULT_COMPARISON_CANDIDATE_OPACITY } from "../backendInit-6C0DLgH0.js";
5
+ import { m as mergeViewportRenderSceneStates, v as validateJointOverrides, b as buildBaseJointValues, p as parseRenderSceneCliSpec, g as getSceneObjectTreePath } from "../jointPose-B0blBj9A.js";
6
6
  const CAD_MATERIAL_PROPS = {
7
7
  color: 6003669,
8
8
  metalness: 0.05,
@@ -1324,6 +1324,13 @@ function clampUnit(value) {
1324
1324
  function cloneGeometryForFaceColors(geometry) {
1325
1325
  return geometry.index ? geometry.toNonIndexed() : geometry.clone();
1326
1326
  }
1327
+ function makeThicknessRaycastTarget(sourceGeometry, rayMaterial, jumpable) {
1328
+ const geometry = sourceGeometry.clone();
1329
+ geometry.boundsTree = new MeshBVH(geometry);
1330
+ const mesh = new Mesh(geometry, rayMaterial);
1331
+ mesh.raycast = acceleratedRaycast;
1332
+ return { mesh, jumpable, geometry };
1333
+ }
1327
1334
  function geometryMaxDimension(geometry) {
1328
1335
  geometry.computeBoundingBox();
1329
1336
  const box = geometry.boundingBox;
@@ -1399,69 +1406,454 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}, context = {})
1399
1406
  const warnings = [];
1400
1407
  const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
1401
1408
  const rayTargets = [
1402
- { mesh: new Mesh(geometry, rayMaterial), jumpable: false },
1403
- ...connectedGeometries.map((connectedGeometry) => ({
1404
- mesh: new Mesh(connectedGeometry, rayMaterial),
1405
- jumpable: true
1406
- }))
1409
+ makeThicknessRaycastTarget(geometry, rayMaterial, false),
1410
+ ...connectedGeometries.map((connectedGeometry) => makeThicknessRaycastTarget(connectedGeometry, rayMaterial, true))
1407
1411
  ];
1408
- const rayTargetMeshes = rayTargets.map((target) => target.mesh);
1409
- const jumpableMeshes = new Set(rayTargets.filter((target) => target.jumpable).map((target) => target.mesh));
1410
- const raycaster = new Raycaster();
1411
- if (surfaceTriangles.length === 0) {
1412
- warnings.push("No non-degenerate triangle surface was available for thickness sampling.");
1413
- } else if (surfaceSamples.length < surfaceTriangles.length) {
1414
- warnings.push(
1415
- `Area sampling budget ${surfaceSamples.length} covers ${surfaceTriangles.length} surface triangles; increase --thickness-samples for denser analysis.`
1416
- );
1412
+ try {
1413
+ const rayTargetMeshes = rayTargets.map((target) => target.mesh);
1414
+ const jumpableMeshes = new Set(rayTargets.filter((target) => target.jumpable).map((target) => target.mesh));
1415
+ const raycaster = new Raycaster();
1416
+ if (surfaceTriangles.length === 0) {
1417
+ warnings.push("No non-degenerate triangle surface was available for thickness sampling.");
1418
+ } else if (surfaceSamples.length < surfaceTriangles.length) {
1419
+ warnings.push(
1420
+ `Area sampling budget ${surfaceSamples.length} covers ${surfaceTriangles.length} surface triangles; increase --thickness-samples for denser analysis.`
1421
+ );
1422
+ }
1423
+ const sampledTriangleIndexes = /* @__PURE__ */ new Set();
1424
+ for (const sample of surfaceSamples) {
1425
+ sampledTriangleIndexes.add(sample.triangle.index);
1426
+ const thickness = triangleThickness(
1427
+ raycaster,
1428
+ rayTargetMeshes,
1429
+ jumpableMeshes,
1430
+ sample.position,
1431
+ sample.normal,
1432
+ epsilon,
1433
+ far,
1434
+ options.contactTolerance
1435
+ );
1436
+ samples.push({ thickness, area: sample.area });
1437
+ const previous = triangleThicknessValues[sample.triangle.index];
1438
+ if (previous === void 0 || previous == null || thickness != null && thickness < previous) {
1439
+ triangleThicknessValues[sample.triangle.index] = thickness;
1440
+ }
1441
+ pointSamples.push({
1442
+ position: [sample.position.x, sample.position.y, sample.position.z],
1443
+ normal: [sample.normal.x, sample.normal.y, sample.normal.z],
1444
+ value: thickness,
1445
+ className: thicknessClass(thickness, options),
1446
+ color: thicknessColor(thickness, options),
1447
+ area: sample.area
1448
+ });
1449
+ }
1450
+ for (let tri = 0; tri < triangleCount; tri += 1) {
1451
+ const color = thicknessColor(triangleThicknessValues[tri], options);
1452
+ const offset = tri * 3;
1453
+ for (let vertex = 0; vertex < 3; vertex += 1) {
1454
+ const colorOffset = (offset + vertex) * 3;
1455
+ colors[colorOffset] = color[0] / 255;
1456
+ colors[colorOffset + 1] = color[1] / 255;
1457
+ colors[colorOffset + 2] = color[2] / 255;
1458
+ }
1459
+ }
1460
+ geometry.setAttribute("color", new BufferAttribute(colors, 3));
1461
+ return {
1462
+ geometry,
1463
+ samples,
1464
+ pointSamples,
1465
+ triangleCount,
1466
+ sampledTriangleCount: sampledTriangleIndexes.size,
1467
+ sampleStride: Math.max(1, Math.ceil(Math.max(1, surfaceTriangles.length) / Math.max(1, sampledTriangleIndexes.size))),
1468
+ warnings
1469
+ };
1470
+ } finally {
1471
+ rayTargets.forEach((target) => target.geometry.dispose());
1472
+ rayMaterial.dispose();
1417
1473
  }
1418
- const sampledTriangleIndexes = /* @__PURE__ */ new Set();
1419
- for (const sample of surfaceSamples) {
1420
- sampledTriangleIndexes.add(sample.triangle.index);
1421
- const thickness = triangleThickness(
1422
- raycaster,
1423
- rayTargetMeshes,
1424
- jumpableMeshes,
1425
- sample.position,
1426
- sample.normal,
1427
- epsilon,
1428
- far,
1429
- options.contactTolerance
1474
+ }
1475
+ const DEFAULT_VERTEX_CAP = 2e6;
1476
+ const DEFAULT_TARGET_EDGE_SPACING_FACTOR = 2;
1477
+ const DEFAULT_K = 8;
1478
+ const DEFAULT_GATE_DOT = 0.3;
1479
+ const SCATTER_CELL_SPACING_FACTOR = 1.5;
1480
+ const SCATTER_RADIUS_SPACING_FACTOR = 3;
1481
+ const IDW_DISTANCE_FLOOR = 1e-9;
1482
+ const MAX_SUBDIVISION_PASSES = 24;
1483
+ function subVec(a, b) {
1484
+ return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
1485
+ }
1486
+ function crossVec(a, b) {
1487
+ return [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]];
1488
+ }
1489
+ function lenVec(a) {
1490
+ return Math.hypot(a[0], a[1], a[2]);
1491
+ }
1492
+ function getVert(positions, i) {
1493
+ const o = i * 3;
1494
+ return [positions[o], positions[o + 1], positions[o + 2]];
1495
+ }
1496
+ function triArea(positions, a, b, c) {
1497
+ const va = getVert(positions, a);
1498
+ return 0.5 * lenVec(crossVec(subVec(getVert(positions, b), va), subVec(getVert(positions, c), va)));
1499
+ }
1500
+ function triNormal(positions, a, b, c) {
1501
+ const va = getVert(positions, a);
1502
+ const n = crossVec(subVec(getVert(positions, b), va), subVec(getVert(positions, c), va));
1503
+ const l = lenVec(n) || 1;
1504
+ return [n[0] / l, n[1] / l, n[2] / l];
1505
+ }
1506
+ function edgeLen(positions, a, b) {
1507
+ return lenVec(subVec(getVert(positions, a), getVert(positions, b)));
1508
+ }
1509
+ function maxEdge(positions, a, b, c) {
1510
+ return Math.max(edgeLen(positions, a, b), edgeLen(positions, b, c), edgeLen(positions, c, a));
1511
+ }
1512
+ function weld(positions) {
1513
+ if (positions.length % 9 !== 0) {
1514
+ throw new Error(
1515
+ `weld: positions length must be a multiple of 9 (9 floats per triangle), got ${positions.length}`
1430
1516
  );
1431
- samples.push({ thickness, area: sample.area });
1432
- const previous = triangleThicknessValues[sample.triangle.index];
1433
- if (previous === void 0 || previous == null || thickness != null && thickness < previous) {
1434
- triangleThicknessValues[sample.triangle.index] = thickness;
1517
+ }
1518
+ positions.length / 3;
1519
+ let minX = Infinity;
1520
+ let minY = Infinity;
1521
+ let minZ = Infinity;
1522
+ let maxX = -Infinity;
1523
+ let maxY = -Infinity;
1524
+ let maxZ = -Infinity;
1525
+ for (let i = 0; i < positions.length; i += 3) {
1526
+ const x = positions[i];
1527
+ const y = positions[i + 1];
1528
+ const z = positions[i + 2];
1529
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) {
1530
+ throw new Error(`weld: non-finite vertex position at float index ${i}`);
1531
+ }
1532
+ if (x < minX) minX = x;
1533
+ if (y < minY) minY = y;
1534
+ if (z < minZ) minZ = z;
1535
+ if (x > maxX) maxX = x;
1536
+ if (y > maxY) maxY = y;
1537
+ if (z > maxZ) maxZ = z;
1538
+ }
1539
+ const diagonal = Math.hypot(maxX - minX, maxY - minY, maxZ - minZ);
1540
+ const tolerance = Math.max(diagonal * 1e-5, Number.EPSILON);
1541
+ const remap = /* @__PURE__ */ new Map();
1542
+ const out = [];
1543
+ const tris = [];
1544
+ const inv = 1 / tolerance;
1545
+ const quantKey = (i) => {
1546
+ const qx = Math.round(positions[i] * inv);
1547
+ const qy = Math.round(positions[i + 1] * inv);
1548
+ const qz = Math.round(positions[i + 2] * inv);
1549
+ return `${qx},${qy},${qz}`;
1550
+ };
1551
+ for (let tri = 0; tri < positions.length; tri += 9) {
1552
+ const idx = [];
1553
+ for (let corner = 0; corner < 3; corner += 1) {
1554
+ const o = tri + corner * 3;
1555
+ const key = quantKey(o);
1556
+ let vi = remap.get(key);
1557
+ if (vi === void 0) {
1558
+ vi = out.length / 3;
1559
+ out.push(positions[o], positions[o + 1], positions[o + 2]);
1560
+ remap.set(key, vi);
1561
+ }
1562
+ idx.push(vi);
1563
+ }
1564
+ if (idx[0] !== idx[1] && idx[1] !== idx[2] && idx[2] !== idx[0]) {
1565
+ tris.push(idx[0], idx[1], idx[2]);
1435
1566
  }
1436
- pointSamples.push({
1437
- position: [sample.position.x, sample.position.y, sample.position.z],
1438
- normal: [sample.normal.x, sample.normal.y, sample.normal.z],
1439
- value: thickness,
1440
- className: thicknessClass(thickness, options),
1441
- color: thicknessColor(thickness, options),
1442
- area: sample.area
1443
- });
1444
1567
  }
1445
- for (let tri = 0; tri < triangleCount; tri += 1) {
1446
- const color = thicknessColor(triangleThicknessValues[tri], options);
1447
- const offset = tri * 3;
1448
- for (let vertex = 0; vertex < 3; vertex += 1) {
1449
- const colorOffset = (offset + vertex) * 3;
1450
- colors[colorOffset] = color[0] / 255;
1451
- colors[colorOffset + 1] = color[1] / 255;
1452
- colors[colorOffset + 2] = color[2] / 255;
1568
+ const distinctVertexCount = out.length / 3;
1569
+ const degenerate = distinctVertexCount < 3 || tris.length === 0;
1570
+ return { positions: out, tris, degenerate };
1571
+ }
1572
+ function adaptiveSubdivide(mesh, targetEdge, vertexCap) {
1573
+ requirePositiveFiniteNumber(targetEdge, "adaptiveSubdivide targetEdge");
1574
+ requireIntegerAtLeast(vertexCap, "adaptiveSubdivide vertexCap", 3);
1575
+ const positions = mesh.positions.slice();
1576
+ let tris = mesh.tris.slice();
1577
+ let capped = false;
1578
+ const keyOf = (a, b) => a < b ? `${a}_${b}` : `${b}_${a}`;
1579
+ for (let pass = 0; pass < MAX_SUBDIVISION_PASSES; pass += 1) {
1580
+ const next = [];
1581
+ const midCache = /* @__PURE__ */ new Map();
1582
+ let changed = false;
1583
+ const getMid = (a, b) => {
1584
+ const key = keyOf(a, b);
1585
+ let mi = midCache.get(key);
1586
+ if (mi === void 0) {
1587
+ mi = positions.length / 3;
1588
+ const ao = a * 3;
1589
+ const bo = b * 3;
1590
+ positions.push(
1591
+ (positions[ao] + positions[bo]) / 2,
1592
+ (positions[ao + 1] + positions[bo + 1]) / 2,
1593
+ (positions[ao + 2] + positions[bo + 2]) / 2
1594
+ );
1595
+ midCache.set(key, mi);
1596
+ }
1597
+ return mi;
1598
+ };
1599
+ for (let t = 0; t < tris.length; t += 3) {
1600
+ const a = tris[t];
1601
+ const b = tris[t + 1];
1602
+ const c = tris[t + 2];
1603
+ const vertexCount = positions.length / 3;
1604
+ if (maxEdge(positions, a, b, c) > targetEdge && vertexCount < vertexCap) {
1605
+ const ab = getMid(a, b);
1606
+ const bc = getMid(b, c);
1607
+ const ca = getMid(c, a);
1608
+ next.push(a, ab, ca, ab, b, bc, ca, bc, c, ab, bc, ca);
1609
+ changed = true;
1610
+ } else {
1611
+ if (positions.length / 3 >= vertexCap) capped = true;
1612
+ next.push(a, b, c);
1613
+ }
1614
+ }
1615
+ tris = next;
1616
+ if (!changed) break;
1617
+ }
1618
+ return { positions, tris, capped };
1619
+ }
1620
+ function vertexNormals(mesh) {
1621
+ const vertexCount = mesh.positions.length / 3;
1622
+ const acc = new Float32Array(vertexCount * 3);
1623
+ for (let t = 0; t < mesh.tris.length; t += 3) {
1624
+ const a = mesh.tris[t];
1625
+ const b = mesh.tris[t + 1];
1626
+ const c = mesh.tris[t + 2];
1627
+ const n = triNormal(mesh.positions, a, b, c);
1628
+ const area = triArea(mesh.positions, a, b, c);
1629
+ const nx = n[0] * area;
1630
+ const ny = n[1] * area;
1631
+ const nz = n[2] * area;
1632
+ for (const vi of [a, b, c]) {
1633
+ acc[vi * 3] += nx;
1634
+ acc[vi * 3 + 1] += ny;
1635
+ acc[vi * 3 + 2] += nz;
1453
1636
  }
1454
1637
  }
1455
- geometry.setAttribute("color", new BufferAttribute(colors, 3));
1456
- rayMaterial.dispose();
1638
+ for (let i = 0; i < vertexCount; i += 1) {
1639
+ const o = i * 3;
1640
+ const l = Math.hypot(acc[o], acc[o + 1], acc[o + 2]) || 1;
1641
+ acc[o] /= l;
1642
+ acc[o + 1] /= l;
1643
+ acc[o + 2] /= l;
1644
+ }
1645
+ return acc;
1646
+ }
1647
+ function buildSampleGrid(positions, cell) {
1648
+ const grid = /* @__PURE__ */ new Map();
1649
+ const sampleCount = positions.length / 3;
1650
+ for (let i = 0; i < sampleCount; i += 1) {
1651
+ const o = i * 3;
1652
+ const key = `${Math.floor(positions[o] / cell)},${Math.floor(positions[o + 1] / cell)},${Math.floor(positions[o + 2] / cell)}`;
1653
+ let arr = grid.get(key);
1654
+ if (!arr) {
1655
+ arr = [];
1656
+ grid.set(key, arr);
1657
+ }
1658
+ arr.push(i);
1659
+ }
1660
+ return { grid, cell };
1661
+ }
1662
+ function gatherSamples(sampleGrid, p, rings, out) {
1663
+ out.length = 0;
1664
+ const { cell, grid } = sampleGrid;
1665
+ const bx = Math.floor(p[0] / cell);
1666
+ const by = Math.floor(p[1] / cell);
1667
+ const bz = Math.floor(p[2] / cell);
1668
+ for (let dx = -rings; dx <= rings; dx += 1) {
1669
+ for (let dy = -rings; dy <= rings; dy += 1) {
1670
+ for (let dz = -rings; dz <= rings; dz += 1) {
1671
+ const arr = grid.get(`${bx + dx},${by + dy},${bz + dz}`);
1672
+ if (arr) for (const i of arr) out.push(i);
1673
+ }
1674
+ }
1675
+ }
1676
+ }
1677
+ function scatterToVertices(positions, vnormals, samples, spacing, k, gateDot) {
1678
+ const cell = spacing * SCATTER_CELL_SPACING_FACTOR;
1679
+ const sampleGrid = buildSampleGrid(samples.positions, cell);
1680
+ const radius = spacing * SCATTER_RADIUS_SPACING_FACTOR;
1681
+ const radius2 = radius * radius;
1682
+ const vertexCount = positions.length / 3;
1683
+ const values = new Float32Array(vertexCount);
1684
+ let holeCount = 0;
1685
+ const candidates = [];
1686
+ const scratchP = [0, 0, 0];
1687
+ const bestD2 = new Float64Array(k);
1688
+ const bestVal = new Float64Array(k);
1689
+ for (let vi = 0; vi < vertexCount; vi += 1) {
1690
+ const pi = vi * 3;
1691
+ const px = positions[pi];
1692
+ const py = positions[pi + 1];
1693
+ const pz = positions[pi + 2];
1694
+ const nx = vnormals[pi];
1695
+ const ny = vnormals[pi + 1];
1696
+ const nz = vnormals[pi + 2];
1697
+ scratchP[0] = px;
1698
+ scratchP[1] = py;
1699
+ scratchP[2] = pz;
1700
+ gatherSamples(sampleGrid, scratchP, 3, candidates);
1701
+ let count = 0;
1702
+ let worst = 0;
1703
+ let worstD2 = -Infinity;
1704
+ for (let c = 0; c < candidates.length; c += 1) {
1705
+ const si = candidates[c];
1706
+ const so = si * 3;
1707
+ const dx = samples.positions[so] - px;
1708
+ const dy = samples.positions[so + 1] - py;
1709
+ const dz = samples.positions[so + 2] - pz;
1710
+ const d2 = dx * dx + dy * dy + dz * dz;
1711
+ if (d2 > radius2) continue;
1712
+ if (samples.normals[so] * nx + samples.normals[so + 1] * ny + samples.normals[so + 2] * nz < gateDot) continue;
1713
+ const val = samples.values[si];
1714
+ if (count < k) {
1715
+ bestD2[count] = d2;
1716
+ bestVal[count] = val;
1717
+ if (d2 > worstD2) {
1718
+ worstD2 = d2;
1719
+ worst = count;
1720
+ }
1721
+ count += 1;
1722
+ } else if (d2 < worstD2) {
1723
+ bestD2[worst] = d2;
1724
+ bestVal[worst] = val;
1725
+ worstD2 = -Infinity;
1726
+ for (let t = 0; t < k; t += 1) {
1727
+ if (bestD2[t] > worstD2) {
1728
+ worstD2 = bestD2[t];
1729
+ worst = t;
1730
+ }
1731
+ }
1732
+ }
1733
+ }
1734
+ if (count === 0) {
1735
+ holeCount += 1;
1736
+ values[vi] = NaN;
1737
+ continue;
1738
+ }
1739
+ let w = 0;
1740
+ let acc = 0;
1741
+ for (let t = 0; t < count; t += 1) {
1742
+ const ww = 1 / Math.max(bestD2[t], IDW_DISTANCE_FLOOR);
1743
+ w += ww;
1744
+ acc += ww * bestVal[t];
1745
+ }
1746
+ values[vi] = acc / w;
1747
+ }
1748
+ return { values, holeCount };
1749
+ }
1750
+ function backfillHoles(values, mesh) {
1751
+ const vertexCount = values.length;
1752
+ const adjacency = Array.from({ length: vertexCount }, () => []);
1753
+ for (let t = 0; t < mesh.tris.length; t += 3) {
1754
+ const a = mesh.tris[t];
1755
+ const b = mesh.tris[t + 1];
1756
+ const c = mesh.tris[t + 2];
1757
+ adjacency[a].push(b, c);
1758
+ adjacency[b].push(a, c);
1759
+ adjacency[c].push(a, b);
1760
+ }
1761
+ let frontier = [];
1762
+ for (let i = 0; i < vertexCount; i += 1) {
1763
+ if (Number.isFinite(values[i])) frontier.push(i);
1764
+ }
1765
+ if (frontier.length === 0) {
1766
+ values.fill(0);
1767
+ return;
1768
+ }
1769
+ while (frontier.length > 0) {
1770
+ const nextFrontier = [];
1771
+ for (const v of frontier) {
1772
+ const val = values[v];
1773
+ for (const n of adjacency[v]) {
1774
+ if (!Number.isFinite(values[n])) {
1775
+ values[n] = val;
1776
+ nextFrontier.push(n);
1777
+ }
1778
+ }
1779
+ }
1780
+ frontier = nextFrontier;
1781
+ }
1782
+ }
1783
+ function reconstructSurfaceScalarField(trianglePositions, samples, options = {}) {
1784
+ if (!(trianglePositions instanceof Float32Array)) {
1785
+ throw new Error("reconstructSurfaceScalarField: trianglePositions must be a Float32Array");
1786
+ }
1787
+ if (trianglePositions.length === 0 || trianglePositions.length % 9 !== 0) {
1788
+ throw new Error(
1789
+ `reconstructSurfaceScalarField: trianglePositions length must be a positive multiple of 9, got ${trianglePositions.length}`
1790
+ );
1791
+ }
1792
+ if (!(samples.positions instanceof Float32Array) || !(samples.values instanceof Float32Array) || !(samples.normals instanceof Float32Array)) {
1793
+ throw new Error("reconstructSurfaceScalarField: samples positions/values/normals must be Float32Arrays");
1794
+ }
1795
+ const sampleCount = samples.values.length;
1796
+ if (sampleCount === 0) {
1797
+ throw new Error("reconstructSurfaceScalarField: samples must be non-empty");
1798
+ }
1799
+ if (samples.positions.length !== sampleCount * 3) {
1800
+ throw new Error(
1801
+ `reconstructSurfaceScalarField: samples.positions length ${samples.positions.length} must equal values*3 (${sampleCount * 3})`
1802
+ );
1803
+ }
1804
+ if (samples.normals.length !== sampleCount * 3) {
1805
+ throw new Error(
1806
+ `reconstructSurfaceScalarField: samples.normals length ${samples.normals.length} must equal values*3 (${sampleCount * 3})`
1807
+ );
1808
+ }
1809
+ for (let i = 0; i < sampleCount; i += 1) {
1810
+ requireFiniteNumber(samples.values[i], `samples.values[${i}]`);
1811
+ }
1812
+ const vertexCap = options.vertexCap === void 0 ? DEFAULT_VERTEX_CAP : requireIntegerAtLeast(options.vertexCap, "vertexCap", 3);
1813
+ const targetEdgeSpacingFactor = options.targetEdgeSpacingFactor === void 0 ? DEFAULT_TARGET_EDGE_SPACING_FACTOR : requirePositiveFiniteNumber(options.targetEdgeSpacingFactor, "targetEdgeSpacingFactor");
1814
+ const k = options.k === void 0 ? DEFAULT_K : requireIntegerAtLeast(options.k, "k", 1);
1815
+ const gateDot = options.gateDot === void 0 ? DEFAULT_GATE_DOT : requireFiniteNumber(options.gateDot, "gateDot");
1816
+ const welded = weld(trianglePositions);
1817
+ let surfaceArea = 0;
1818
+ for (let t = 0; t < welded.tris.length; t += 3) {
1819
+ surfaceArea += triArea(welded.positions, welded.tris[t], welded.tris[t + 1], welded.tris[t + 2]);
1820
+ }
1821
+ const sampleSpacing = surfaceArea > 0 ? Math.sqrt(surfaceArea / sampleCount) : 0;
1822
+ let subdivided;
1823
+ if (welded.degenerate || !(sampleSpacing > 0)) {
1824
+ subdivided = { positions: welded.positions, tris: welded.tris, capped: false };
1825
+ } else {
1826
+ const targetEdge = targetEdgeSpacingFactor * sampleSpacing;
1827
+ subdivided = adaptiveSubdivide(welded, targetEdge, vertexCap);
1828
+ }
1829
+ const normals = vertexNormals(subdivided);
1830
+ const effectiveSpacing = sampleSpacing > 0 ? sampleSpacing : 1;
1831
+ const scatter = scatterToVertices(subdivided.positions, normals, samples, effectiveSpacing, k, gateDot);
1832
+ let valueMin = Infinity;
1833
+ let valueMax = -Infinity;
1834
+ for (let i = 0; i < scatter.values.length; i += 1) {
1835
+ const v = scatter.values[i];
1836
+ if (!Number.isFinite(v)) continue;
1837
+ if (v < valueMin) valueMin = v;
1838
+ if (v > valueMax) valueMax = v;
1839
+ }
1840
+ if (!Number.isFinite(valueMin) || !Number.isFinite(valueMax)) {
1841
+ valueMin = 0;
1842
+ valueMax = 0;
1843
+ }
1844
+ backfillHoles(scatter.values, subdivided);
1845
+ const vertexCount = subdivided.positions.length / 3;
1457
1846
  return {
1458
- geometry,
1459
- samples,
1460
- pointSamples,
1461
- triangleCount,
1462
- sampledTriangleCount: sampledTriangleIndexes.size,
1463
- sampleStride: Math.max(1, Math.ceil(Math.max(1, surfaceTriangles.length) / Math.max(1, sampledTriangleIndexes.size))),
1464
- warnings
1847
+ positions: Float32Array.from(subdivided.positions),
1848
+ normals,
1849
+ index: Uint32Array.from(subdivided.tris),
1850
+ values: scatter.values,
1851
+ valueMin,
1852
+ valueMax,
1853
+ capped: subdivided.capped,
1854
+ degenerate: welded.degenerate,
1855
+ holeCount: scatter.holeCount,
1856
+ vertexCount
1465
1857
  };
1466
1858
  }
1467
1859
  const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
@@ -2001,6 +2393,16 @@ function createCaptureDebugLogger(enabled) {
2001
2393
  console.info(`[forge-capture:debug] +${sinceStart}ms Δ${delta}ms ${phase}${detailText}`);
2002
2394
  };
2003
2395
  }
2396
+ function setInspectProfileEnabled(enabled) {
2397
+ window.__forgeInspectProfile = enabled === true;
2398
+ }
2399
+ function inspectProfileEnabled() {
2400
+ return window.__forgeInspectProfile === true;
2401
+ }
2402
+ function inspectProfileLog(phase, detail) {
2403
+ if (!inspectProfileEnabled()) return;
2404
+ console.info(`[forge-inspect-profile] ${phase} ${JSON.stringify(detail)}`);
2405
+ }
2004
2406
  class EmptyInspectionShape {
2005
2407
  intersect() {
2006
2408
  return {
@@ -3675,28 +4077,100 @@ function bboxFromGeometry(geometry) {
3675
4077
  max: bbox ? [bbox.max.x, bbox.max.y, bbox.max.z] : [0, 0, 0]
3676
4078
  };
3677
4079
  }
3678
- function pointBuffersFromSamples(samples, colorScale, offset = 0.025) {
3679
- const positions = new Float32Array(samples.length * 3);
3680
- const colors = new Float32Array(samples.length * 3);
3681
- samples.forEach((sample, index) => {
4080
+ const SCALAR_SURFACE_VERTEX_CAP = 2e6;
4081
+ const SCALAR_SURFACE_TARGET_EDGE_SPACING_FACTOR = 8;
4082
+ function scalarSamplesFromPointSamples(samples) {
4083
+ const finite = samples.filter((sample) => sample.value != null && Number.isFinite(sample.value));
4084
+ const positions = new Float32Array(finite.length * 3);
4085
+ const values = new Float32Array(finite.length);
4086
+ const normals = new Float32Array(finite.length * 3);
4087
+ finite.forEach((sample, index) => {
3682
4088
  const base = index * 3;
3683
- positions[base] = sample.position[0] + sample.normal[0] * offset;
3684
- positions[base + 1] = sample.position[1] + sample.normal[1] * offset;
3685
- positions[base + 2] = sample.position[2] + sample.normal[2] * offset;
3686
- if (colorScale && sample.value != null && Number.isFinite(sample.value)) {
3687
- const [r, g, b] = sampleColorScale(colorScale, sample.value);
3688
- colors[base] = r / 255;
3689
- colors[base + 1] = g / 255;
3690
- colors[base + 2] = b / 255;
3691
- } else {
3692
- colors[base] = sample.color[0] / 255;
3693
- colors[base + 1] = sample.color[1] / 255;
3694
- colors[base + 2] = sample.color[2] / 255;
4089
+ positions[base] = sample.position[0];
4090
+ positions[base + 1] = sample.position[1];
4091
+ positions[base + 2] = sample.position[2];
4092
+ normals[base] = sample.normal[0];
4093
+ normals[base + 1] = sample.normal[1];
4094
+ normals[base + 2] = sample.normal[2];
4095
+ values[index] = sample.value;
4096
+ });
4097
+ return { positions, values, normals };
4098
+ }
4099
+ const DEFAULT_SCALAR_FIELD_PARAMS = {
4100
+ quantizeBands: 0,
4101
+ isoEnabled: false,
4102
+ isoSpacing: 0,
4103
+ isoLineWidthPx: 1.5,
4104
+ criticalEnabled: false,
4105
+ criticalThreshold: 0,
4106
+ shadingEnabled: true
4107
+ };
4108
+ function mergeScalarFieldParams(override) {
4109
+ if (!override) return DEFAULT_SCALAR_FIELD_PARAMS;
4110
+ const merged = { ...DEFAULT_SCALAR_FIELD_PARAMS };
4111
+ if (override.quantizeBands !== void 0) {
4112
+ const bands = override.quantizeBands;
4113
+ if (!Number.isInteger(bands) || bands < 0) {
4114
+ throw new RangeError(`Heatmap band count must be a non-negative integer, got ${bands}.`);
3695
4115
  }
4116
+ merged.quantizeBands = bands;
4117
+ }
4118
+ if (override.isoEnabled !== void 0) {
4119
+ merged.isoEnabled = override.isoEnabled;
4120
+ }
4121
+ if (merged.isoEnabled && merged.isoSpacing <= 0) {
4122
+ merged.isoSpacing = DEFAULT_INSPECT_ISOLINE_SPACING;
4123
+ }
4124
+ if (override.isoLineWidthPx !== void 0) {
4125
+ const width = override.isoLineWidthPx;
4126
+ if (!Number.isFinite(width) || width <= 0) {
4127
+ throw new RangeError(`Heatmap isoline width must be a finite positive number of pixels, got ${width}.`);
4128
+ }
4129
+ merged.isoLineWidthPx = width;
4130
+ }
4131
+ return merged;
4132
+ }
4133
+ function buildScalarSurfaceOverlay(renderable, trianglePositions, samples, domain, fieldParams) {
4134
+ const started = performance.now();
4135
+ const scalarSamples = scalarSamplesFromPointSamples(samples);
4136
+ if (scalarSamples.values.length === 0) return null;
4137
+ const reconstructStarted = performance.now();
4138
+ const field = reconstructSurfaceScalarField(trianglePositions, scalarSamples, {
4139
+ vertexCap: SCALAR_SURFACE_VERTEX_CAP,
4140
+ targetEdgeSpacingFactor: SCALAR_SURFACE_TARGET_EDGE_SPACING_FACTOR
4141
+ });
4142
+ const reconstructMs = performance.now() - reconstructStarted;
4143
+ inspectProfileLog("scalar-overlay", {
4144
+ object: renderable.name,
4145
+ samples: scalarSamples.values.length,
4146
+ inputTriangles: trianglePositions.length / 9,
4147
+ vertices: field.vertexCount,
4148
+ capped: field.capped,
4149
+ holes: field.holeCount,
4150
+ reconstructMs: Number(reconstructMs.toFixed(1)),
4151
+ totalMs: Number((performance.now() - started).toFixed(1))
3696
4152
  });
3697
- return { positions, colors };
4153
+ const colorScale = domain ? { colormap: DEFAULT_COLORMAP, domainMin: domain.min, domainMax: domain.max } : {
4154
+ colormap: DEFAULT_COLORMAP,
4155
+ domainMin: field.valueMin,
4156
+ domainMax: field.valueMax > field.valueMin ? field.valueMax : field.valueMin + 1
4157
+ };
4158
+ return {
4159
+ overlay: {
4160
+ renderable,
4161
+ positions: field.positions,
4162
+ normals: field.normals,
4163
+ index: field.index,
4164
+ aValue: field.values,
4165
+ colorScale,
4166
+ fieldParams
4167
+ },
4168
+ capped: field.capped,
4169
+ holeCount: field.holeCount
4170
+ };
3698
4171
  }
3699
- function renderScalarPointOverlays(session, overlays) {
4172
+ function renderScalarSurfaceOverlays(session, overlays) {
4173
+ const started = performance.now();
3700
4174
  const r = getRenderer(session.size, session.pixelRatio);
3701
4175
  const overlayById = new Map(overlays.map((overlay) => [overlay.renderable.id, overlay]));
3702
4176
  const replacements = session.renderables.map((renderable) => {
@@ -3713,49 +4187,77 @@ function renderScalarPointOverlays(session, overlays) {
3713
4187
  ghostMaterial.toneMapped = false;
3714
4188
  renderable.solid.material = ghostMaterial;
3715
4189
  const overlay = overlayById.get(renderable.id);
3716
- if (!overlay || overlay.positions.length === 0)
3717
- return { renderable, previousMaterial, ghostMaterial, points: null, geometry: null, material: null };
4190
+ if (!overlay || overlay.positions.length === 0) {
4191
+ return { renderable, previousMaterial, ghostMaterial, mesh: null, geometry: null, material: null, lut: null };
4192
+ }
3718
4193
  const geometry = new BufferGeometry();
3719
4194
  geometry.setAttribute("position", new BufferAttribute(overlay.positions, 3));
3720
- geometry.setAttribute("color", new BufferAttribute(overlay.colors, 3));
3721
- const material = new PointsMaterial({
3722
- size: 3,
3723
- sizeAttenuation: false,
3724
- vertexColors: true,
3725
- depthTest: true,
3726
- depthWrite: false,
3727
- clippingPlanes: renderable.solidMaterial.clippingPlanes ?? void 0
4195
+ geometry.setAttribute("normal", new BufferAttribute(overlay.normals, 3));
4196
+ geometry.setAttribute("aValue", new BufferAttribute(overlay.aValue, 1));
4197
+ geometry.setIndex(new BufferAttribute(overlay.index, 1));
4198
+ const lut = makeColorScaleTexture(colorScaleLUT(overlay.colorScale.colormap));
4199
+ trackTextureDecode(lut);
4200
+ const uniforms = makeInspectScalarUniforms({
4201
+ colorScale: lut,
4202
+ domainMin: overlay.colorScale.domainMin,
4203
+ domainMax: overlay.colorScale.domainMax,
4204
+ quantizeBands: overlay.fieldParams.quantizeBands,
4205
+ isoEnabled: overlay.fieldParams.isoEnabled,
4206
+ isoSpacing: overlay.fieldParams.isoSpacing,
4207
+ isoLineWidthPx: overlay.fieldParams.isoLineWidthPx,
4208
+ criticalEnabled: overlay.fieldParams.criticalEnabled,
4209
+ criticalThreshold: overlay.fieldParams.criticalThreshold,
4210
+ shadingEnabled: overlay.fieldParams.shadingEnabled
4211
+ });
4212
+ const clippingPlanes = renderable.solidMaterial.clippingPlanes ?? void 0;
4213
+ const material = new ShaderMaterial({
4214
+ vertexShader: INSPECT_SCALAR_VERTEX_SHADER,
4215
+ fragmentShader: INSPECT_SCALAR_FRAGMENT_SHADER,
4216
+ uniforms,
4217
+ side: DoubleSide,
4218
+ clippingPlanes,
4219
+ // Engage the shader's `#include <clipping_planes_*>` chunks only when section
4220
+ // planes are actually present (NUM_CLIPPING_PLANES define is gated on this),
4221
+ // so heatmap surfaces honor section cuts the same as the ghosted solid does.
4222
+ clipping: Boolean(clippingPlanes && clippingPlanes.length > 0)
3728
4223
  });
3729
4224
  material.toneMapped = false;
3730
- const points = new Points(geometry, material);
3731
- points.renderOrder = 5;
3732
- points.raycast = () => null;
3733
- renderable.root.add(points);
3734
- return { renderable, previousMaterial, ghostMaterial, points, geometry, material };
4225
+ const mesh = new Mesh(geometry, material);
4226
+ mesh.renderOrder = 5;
4227
+ mesh.raycast = () => null;
4228
+ renderable.root.add(mesh);
4229
+ return { renderable, previousMaterial, ghostMaterial, mesh, geometry, material, lut };
3735
4230
  });
3736
4231
  try {
3737
- return withSolidOnlyVisibility(
4232
+ const png = withSolidOnlyVisibility(
3738
4233
  session,
3739
4234
  () => withTemporarySceneBackground(session, new Color(0), () => {
3740
4235
  r.render(session.scene, session.camera);
3741
4236
  return captureRenderedPng(session.size);
3742
4237
  })
3743
4238
  );
4239
+ inspectProfileLog("scalar-render", {
4240
+ overlays: overlays.length,
4241
+ totalMs: Number((performance.now() - started).toFixed(1))
4242
+ });
4243
+ return png;
3744
4244
  } finally {
3745
- replacements.forEach(({ renderable, previousMaterial, ghostMaterial, points, geometry, material }) => {
3746
- if (points) renderable.root.remove(points);
4245
+ replacements.forEach(({ renderable, previousMaterial, ghostMaterial, mesh, geometry, material, lut }) => {
4246
+ if (mesh) renderable.root.remove(mesh);
3747
4247
  renderable.solid.material = previousMaterial;
3748
4248
  ghostMaterial.dispose();
3749
4249
  geometry == null ? void 0 : geometry.dispose();
3750
4250
  material == null ? void 0 : material.dispose();
4251
+ lut == null ? void 0 : lut.dispose();
3751
4252
  });
3752
4253
  }
3753
4254
  }
3754
- function getSessionThicknessInspection(session, rawOptions) {
4255
+ function getSessionThicknessInspection(session, rawOptions, fieldOverride) {
3755
4256
  var _a;
3756
4257
  const resolvedOptions = resolveThicknessInspectionOptions(rawOptions);
3757
4258
  const { options, sampleBudget } = withSceneSampleBudget(session, resolvedOptions, (rawOptions == null ? void 0 : rawOptions.maxSamplesPerObject) !== void 0);
3758
- const optionsKey = inspectionOptionsKey({ options, sampleBudget });
4259
+ const fieldParams = mergeScalarFieldParams(fieldOverride);
4260
+ const optionsKey = inspectionOptionsKey({ options, sampleBudget, fieldParams });
3759
4261
  if (((_a = session.thicknessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.thicknessInspection;
3760
4262
  const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
3761
4263
  const warnings = [];
@@ -3769,11 +4271,24 @@ function getSessionThicknessInspection(session, rawOptions) {
3769
4271
  const cloudObjects = [];
3770
4272
  const points = [];
3771
4273
  const overlays = [];
4274
+ const connectivityStarted = performance.now();
3772
4275
  const raycastConnectivity = buildThicknessRaycastConnectivityContext(session);
4276
+ inspectProfileLog("thickness-connectivity", {
4277
+ ms: Number((performance.now() - connectivityStarted).toFixed(1)),
4278
+ neighborSets: raycastConnectivity.neighborIdsByObjectId.size
4279
+ });
3773
4280
  session.renderables.forEach((renderable, index) => {
4281
+ var _a2;
3774
4282
  const sourceObject = byId.get(renderable.id);
3775
- const analysis = analyzeThicknessGeometry(renderable.solid.geometry, options, {
3776
- connectedGeometries: connectedThicknessGeometriesFor(raycastConnectivity, renderable)
4283
+ const connectedGeometries = connectedThicknessGeometriesFor(raycastConnectivity, renderable);
4284
+ const analysisStarted = performance.now();
4285
+ const analysis = analyzeThicknessGeometry(renderable.solid.geometry, options, { connectedGeometries });
4286
+ inspectProfileLog("thickness-analysis", {
4287
+ object: renderable.name,
4288
+ samples: analysis.pointSamples.length,
4289
+ triangles: analysis.triangleCount,
4290
+ connectedGeometries: connectedGeometries.length,
4291
+ ms: Number((performance.now() - analysisStarted).toFixed(1))
3777
4292
  });
3778
4293
  const bbox = bboxFromGeometry(analysis.geometry);
3779
4294
  const summary = summarizeThicknessSamples(analysis.samples, options);
@@ -3812,7 +4327,27 @@ function getSessionThicknessInspection(session, rawOptions) {
3812
4327
  ...sample
3813
4328
  });
3814
4329
  });
3815
- overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples, thicknessColorScale) });
4330
+ const trianglePositions = (_a2 = analysis.geometry.getAttribute("position")) == null ? void 0 : _a2.array;
4331
+ if (trianglePositions instanceof Float32Array) {
4332
+ const built = buildScalarSurfaceOverlay(
4333
+ renderable,
4334
+ trianglePositions,
4335
+ analysis.pointSamples,
4336
+ { min: thicknessColorScale.domainMin, max: thicknessColorScale.domainMax },
4337
+ fieldParams
4338
+ );
4339
+ if (built) {
4340
+ overlays.push(built.overlay);
4341
+ if (built.capped) {
4342
+ warnings.push(
4343
+ `${renderable.name}: scalar surface hit the ${SCALAR_SURFACE_VERTEX_CAP.toLocaleString()} vertex cap; raise the sample budget or reduce object size for full resolution.`
4344
+ );
4345
+ }
4346
+ if (built.holeCount > 0) {
4347
+ warnings.push(`${renderable.name}: ${built.holeCount} surface vertices had no in-gate sample (filled by nearest neighbor).`);
4348
+ }
4349
+ }
4350
+ }
3816
4351
  analysis.geometry.dispose();
3817
4352
  });
3818
4353
  const state = {
@@ -3835,9 +4370,6 @@ function getSessionThicknessInspection(session, rawOptions) {
3835
4370
  objects,
3836
4371
  warnings,
3837
4372
  style: {
3838
- // gradientColors is the legacy 'thickness-classic' rainbow, kept for
3839
- // back-compat. colorScale is the truth the viewport/CLI now render with.
3840
- gradientColors: THICKNESS_GRADIENT_COLORS.map((color) => [...color]),
3841
4373
  colorScale: thicknessColorScale,
3842
4374
  colorMinThickness: options.colorMinThickness,
3843
4375
  colorMaxThickness: options.colorMaxThickness,
@@ -3848,21 +4380,22 @@ function getSessionThicknessInspection(session, rawOptions) {
3848
4380
  session.thicknessInspection = state;
3849
4381
  return state;
3850
4382
  }
3851
- function renderCurrentThickness(session, rawOptions) {
3852
- const state = getSessionThicknessInspection(session, rawOptions);
3853
- return { png: renderScalarPointOverlays(session, state.overlays), report: state.report, pointCloud: state.pointCloud };
4383
+ function renderCurrentThickness(session, rawOptions, fieldOverride) {
4384
+ const state = getSessionThicknessInspection(session, rawOptions, fieldOverride);
4385
+ return { png: renderScalarSurfaceOverlays(session, state.overlays), report: state.report, pointCloud: state.pointCloud };
3854
4386
  }
3855
4387
  const ROUGHNESS_SMOOTH_OPACITY = 0.16;
3856
4388
  const ROUGHNESS_HARSH_OPACITY = 1;
3857
- function renderCurrentRoughness(session, rawOptions) {
3858
- const state = getSessionRoughnessInspection(session, rawOptions);
3859
- return { png: renderScalarPointOverlays(session, state.overlays), report: state.report, pointCloud: state.pointCloud };
4389
+ function renderCurrentRoughness(session, rawOptions, fieldOverride) {
4390
+ const state = getSessionRoughnessInspection(session, rawOptions, fieldOverride);
4391
+ return { png: renderScalarSurfaceOverlays(session, state.overlays), report: state.report, pointCloud: state.pointCloud };
3860
4392
  }
3861
- function getSessionRoughnessInspection(session, rawOptions) {
4393
+ function getSessionRoughnessInspection(session, rawOptions, fieldOverride) {
3862
4394
  var _a;
3863
4395
  const resolvedOptions = resolveRoughnessInspectionOptions(rawOptions);
3864
4396
  const { options, sampleBudget } = withSceneSampleBudget(session, resolvedOptions, (rawOptions == null ? void 0 : rawOptions.maxSamplesPerObject) !== void 0);
3865
- const optionsKey = inspectionOptionsKey({ options, sampleBudget });
4397
+ const fieldParams = mergeScalarFieldParams(fieldOverride);
4398
+ const optionsKey = inspectionOptionsKey({ options, sampleBudget, fieldParams });
3866
4399
  if (((_a = session.roughnessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.roughnessInspection;
3867
4400
  const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
3868
4401
  const warnings = [];
@@ -3886,6 +4419,7 @@ function getSessionRoughnessInspection(session, rawOptions) {
3886
4419
  });
3887
4420
  const roughnessColorScale = Number.isFinite(roughnessMin) && roughnessMax > roughnessMin ? { colormap: DEFAULT_ROUGHNESS_COLOR_SCALE.colormap, domainMin: roughnessMin, domainMax: roughnessMax } : DEFAULT_ROUGHNESS_COLOR_SCALE;
3888
4421
  session.renderables.forEach((renderable, index) => {
4422
+ var _a2;
3889
4423
  const analysis = analysesByIndex[index];
3890
4424
  const bbox = bboxFromGeometry(analysis.geometry);
3891
4425
  const sourceObject = byId.get(renderable.id);
@@ -3921,7 +4455,21 @@ function getSessionRoughnessInspection(session, rawOptions) {
3921
4455
  ...sample
3922
4456
  });
3923
4457
  });
3924
- overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples, roughnessColorScale) });
4458
+ const trianglePositions = (_a2 = analysis.geometry.getAttribute("position")) == null ? void 0 : _a2.array;
4459
+ if (trianglePositions instanceof Float32Array) {
4460
+ const built = buildScalarSurfaceOverlay(renderable, trianglePositions, analysis.pointSamples, null, fieldParams);
4461
+ if (built) {
4462
+ overlays.push(built.overlay);
4463
+ if (built.capped) {
4464
+ warnings.push(
4465
+ `${renderable.name}: scalar surface hit the ${SCALAR_SURFACE_VERTEX_CAP.toLocaleString()} vertex cap; raise the sample budget or reduce object size for full resolution.`
4466
+ );
4467
+ }
4468
+ if (built.holeCount > 0) {
4469
+ warnings.push(`${renderable.name}: ${built.holeCount} surface vertices had no in-gate sample (filled by nearest neighbor).`);
4470
+ }
4471
+ }
4472
+ }
3925
4473
  analysis.geometry.dispose();
3926
4474
  });
3927
4475
  const state = {
@@ -5073,6 +5621,60 @@ Available renderable objects: ${available}`;
5073
5621
  }
5074
5622
  return "No visible renderable objects found.";
5075
5623
  }
5624
+ const PENDING_TEXTURE_DECODES = /* @__PURE__ */ new Set();
5625
+ function trackTextureDecode(texture) {
5626
+ PENDING_TEXTURE_DECODES.add(textureDecoded(texture));
5627
+ }
5628
+ function textureDecoded(texture) {
5629
+ return new Promise((resolve) => {
5630
+ var _a;
5631
+ const finish = () => {
5632
+ texture.needsUpdate = true;
5633
+ resolve();
5634
+ };
5635
+ const forgeDecoded = (_a = texture.userData) == null ? void 0 : _a.forgeDecoded;
5636
+ if (forgeDecoded) {
5637
+ forgeDecoded.then(
5638
+ () => finish(),
5639
+ () => finish()
5640
+ );
5641
+ return;
5642
+ }
5643
+ const image = texture.image;
5644
+ if (image && typeof ImageBitmap !== "undefined" && image instanceof ImageBitmap) {
5645
+ finish();
5646
+ return;
5647
+ }
5648
+ if (image && typeof image.decode === "function") {
5649
+ image.decode().then(finish, finish);
5650
+ return;
5651
+ }
5652
+ if (image && (image.naturalWidth > 0 || image.complete && image.width > 0)) {
5653
+ finish();
5654
+ return;
5655
+ }
5656
+ if (image && "onload" in image) {
5657
+ const prevLoad = image.onload;
5658
+ const prevError = image.onerror;
5659
+ image.onload = (ev) => {
5660
+ if (typeof prevLoad === "function") prevLoad.call(image, ev);
5661
+ finish();
5662
+ };
5663
+ image.onerror = (ev) => {
5664
+ if (typeof prevError === "function") prevError.call(image, ev);
5665
+ finish();
5666
+ };
5667
+ return;
5668
+ }
5669
+ finish();
5670
+ });
5671
+ }
5672
+ async function awaitPendingTextureDecodes() {
5673
+ if (PENDING_TEXTURE_DECODES.size === 0) return;
5674
+ const pending = Array.from(PENDING_TEXTURE_DECODES);
5675
+ PENDING_TEXTURE_DECODES.clear();
5676
+ await Promise.all(pending);
5677
+ }
5076
5678
  function createSession(code, opts) {
5077
5679
  var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
5078
5680
  const size = (opts == null ? void 0 : opts.size) ?? 1024;
@@ -5426,6 +6028,12 @@ Fix one:
5426
6028
  depthWrite: materialOpacity >= 0.99 && materialTransmission <= 0,
5427
6029
  clippingPlanes: applicableCutPlanes
5428
6030
  });
6031
+ const textureDescriptor = mp == null ? void 0 : mp.texture;
6032
+ if (textureDescriptor && solidMaterial instanceof MeshPhysicalMaterial) {
6033
+ const projectedTexture = descriptorToThreeTexture(textureDescriptor.image, { colorSpace: "srgb" });
6034
+ applyProjectedTexture(solidMaterial, textureDescriptor.projection, projectedTexture);
6035
+ trackTextureDecode(projectedTexture);
6036
+ }
5429
6037
  solid = new Mesh(geo.solid, solidMaterial);
5430
6038
  if (isScanRenderStyle) {
5431
6039
  const scanProxyGeometry = createScanProxyGeometry(geo.solid, { grid: scanProxyGrid ?? void 0 });
@@ -5618,6 +6226,7 @@ async function emitInspectProgress(opts, event) {
5618
6226
  }
5619
6227
  window.__forgeRender = async (code, opts) => {
5620
6228
  var _a, _b, _c;
6229
+ setInspectProfileEnabled(opts == null ? void 0 : opts.debug);
5621
6230
  const requestedCameraTokens = (opts == null ? void 0 : opts.cameras) ?? (opts == null ? void 0 : opts.angles);
5622
6231
  const hasDirectionalCameraTokens = Array.isArray(requestedCameraTokens) && requestedCameraTokens.length > 0;
5623
6232
  if ((opts == null ? void 0 : opts.viewName) && hasDirectionalCameraTokens) {
@@ -5635,6 +6244,7 @@ window.__forgeRender = async (code, opts) => {
5635
6244
  const built = createSession(code, {
5636
6245
  size: (opts == null ? void 0 : opts.size) || 1024,
5637
6246
  pixelRatio: (opts == null ? void 0 : opts.pixelRatio) || 1,
6247
+ debug: opts == null ? void 0 : opts.debug,
5638
6248
  quality: opts == null ? void 0 : opts.quality,
5639
6249
  allFiles: opts == null ? void 0 : opts.allFiles,
5640
6250
  binaryFiles: opts == null ? void 0 : opts.binaryFiles,
@@ -5697,6 +6307,7 @@ window.__forgeRender = async (code, opts) => {
5697
6307
  return { ok: false, error: error instanceof Error ? error.message : String(error) };
5698
6308
  }
5699
6309
  }
6310
+ await awaitPendingTextureDecodes();
5700
6311
  await emitInspectProgress(opts, { type: "session-done", objectCount: session.objects.length });
5701
6312
  const renderMode = (opts == null ? void 0 : opts.renderMode) === "wireframe" ? "wireframe" : "solid";
5702
6313
  const edgePreset = (opts == null ? void 0 : opts.edges) ?? "off";
@@ -5822,7 +6433,7 @@ window.__forgeRender = async (code, opts) => {
5822
6433
  }
5823
6434
  if (requestedChannels.has("roughness")) {
5824
6435
  await markChannelViewStart("roughness", label);
5825
- const roughness = renderCurrentRoughness(session, opts == null ? void 0 : opts.roughness);
6436
+ const roughness = renderCurrentRoughness(session, opts == null ? void 0 : opts.roughness, opts == null ? void 0 : opts.scalarFieldParams);
5826
6437
  roughnessRenders[label] = roughness.png;
5827
6438
  roughnessReport = roughness.report;
5828
6439
  roughnessPointCloud = roughness.pointCloud;
@@ -5877,7 +6488,7 @@ window.__forgeRender = async (code, opts) => {
5877
6488
  }
5878
6489
  if (requestedChannels.has("thickness")) {
5879
6490
  await markChannelViewStart("thickness", label);
5880
- const thickness = renderCurrentThickness(session, opts == null ? void 0 : opts.thickness);
6491
+ const thickness = renderCurrentThickness(session, opts == null ? void 0 : opts.thickness, opts == null ? void 0 : opts.scalarFieldParams);
5881
6492
  thicknessRenders[label] = thickness.png;
5882
6493
  thicknessReport = thickness.report;
5883
6494
  thicknessPointCloud = thickness.pointCloud;
@@ -6022,6 +6633,7 @@ window.__forgeCaptureInit = async (code, opts) => {
6022
6633
  if (!built.ok) {
6023
6634
  return built;
6024
6635
  }
6636
+ await awaitPendingTextureDecodes();
6025
6637
  captureSession = built.session;
6026
6638
  return {
6027
6639
  ok: true,