forgecad 0.10.2 → 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.
- package/README.md +7 -6
- package/dist/assets/{AdminPage-CHY6ZN-p.js → AdminPage-B3L3W1Uo.js} +1 -1
- package/dist/assets/{BenchmarkPage-BcRT5iGN.js → BenchmarkPage-DXKVXMrJ.js} +2 -2
- package/dist/assets/{BlogPage-BssBbnb-.js → BlogPage-B7BWxOCg.js} +1 -1
- package/dist/assets/{DocsPage-DsvdiRNK.js → DocsPage-BPGGwht1.js} +28 -48
- package/dist/assets/{EditorApp-Bfd3jbtC.js → EditorApp-BWUGCdD5.js} +183 -21
- package/dist/assets/{EditorApp-BpjZgzk0.css → EditorApp-C5f24ZN9.css} +8 -0
- package/dist/assets/{EmbedViewer-D5t8WamV.js → EmbedViewer-DygByZS2.js} +2 -2
- package/dist/assets/{LandingPageProofDriven-DbN7o-Be.js → LandingPageProofDriven-BoVE7JGY.js} +54 -36
- package/dist/assets/{LegalPage-DNGrrY0p.js → LegalPage-Din8wv8d.js} +2 -2
- package/dist/assets/{PricingPage-Nczr3pRz.js → PricingPage-C2PMzmDc.js} +2 -2
- package/dist/assets/{SettingsPage-DZlyu4d4.js → SettingsPage-BlJDCRe8.js} +1 -1
- package/dist/assets/{app-C9ct2hRD.js → app-BsRYSfxY.js} +2264 -6259
- package/dist/assets/{backendInit-ymjonyQp.js → backendInit-6C0DLgH0.js} +8290 -2136
- package/dist/assets/cli/{render-B_0lQwKU.js → render-XXol_ET7.js} +822 -105
- package/dist/assets/{constructionHistoryWorker-CZ42Dksy.js → constructionHistoryWorker-cTHWRJEi.js} +699 -284
- package/dist/assets/{evalWorker-C2pm8LHP.js → evalWorker-BssDYW9u.js} +2559 -1330
- package/dist/assets/{forgecad_geometry-BlMtqluF.js → forgecad_geometry-CZ_IfuvA.js} +1 -9
- package/dist/assets/{forgecad_geometry_bg-BllP_WiL.wasm → forgecad_geometry_bg-C3rQHfwg.wasm} +0 -0
- package/dist/assets/{inspectWorker-D5T5VbfK.js → inspectWorker-ymhBV4Ll.js} +6254 -671
- package/dist/assets/{jointPose-4r8ed8_5.js → jointPose-B0blBj9A.js} +1 -1
- package/dist/assets/{landing-proof-driven-ORyigZ6p.css → landing-proof-driven-Cpf-MIbI.css} +73 -13
- package/dist/assets/{manifold-5PP1eGLN.js → manifold-B_7QXpGB.js} +1 -1
- package/dist/assets/{manifold-DjBkyIc8.js → manifold-CNShmpEJ.js} +1 -1
- package/dist/assets/{manifold-C4r6B-XY.js → manifold-CYlIm-M6.js} +2 -2
- package/dist/assets/{reportWorker-CwenM7wB.js → reportWorker-Cb5eyM7D.js} +2485 -1275
- package/dist/cli/render.html +1 -1
- package/dist/docs/index.html +2 -2
- package/dist/docs-raw/AI/usage.md +17 -17
- package/dist/docs-raw/CLI.md +9 -7
- package/dist/docs-raw/README.md +1 -1
- package/dist/docs-raw/component-model.md +2 -2
- package/dist/docs-raw/generated/assembly.md +1 -1
- package/dist/docs-raw/generated/concepts.md +10 -4
- package/dist/docs-raw/generated/core.md +96 -1
- package/dist/docs-raw/generated/curves.md +8 -1
- package/dist/docs-raw/generated/output.md +0 -64
- package/dist/docs-raw/generated/runtime-names.md +6 -6
- package/dist/docs-raw/generated/viewport.md +3 -12
- package/dist/docs-raw/guides/inspection-bundles.md +1 -1
- package/dist/docs-raw/simulation-workflow.md +58 -0
- package/{dist-skill/website/skills/forgecad-make-a-model.md → dist/docs-raw/skills/forgecad-build-model.md} +18 -8
- package/dist/docs-raw/skills/forgecad-design-spec.md +145 -0
- package/dist/docs-raw/skills/{forgecad-model-grader.md → forgecad-grade-model.md} +8 -6
- package/{dist-skill/website/skills/forgecad-visual-spec.md → dist/docs-raw/skills/forgecad-image-prompt.md} +7 -7
- package/dist/docs-raw/skills/{forgecad-render-inspect.md → forgecad-inspect-model.md} +6 -6
- package/{dist-skill/website/skills/forgecad-project.md → dist/docs-raw/skills/forgecad-project-sync.md} +5 -5
- package/{dist-skill/website/skills/forgecad-3d-reconstruction.md → dist/docs-raw/skills/forgecad-reconstruct-cad-file.md} +7 -7
- package/dist/docs-raw/skills/{forgecad-image-replicator.md → forgecad-reconstruct-from-images.md} +12 -12
- package/dist/docs-raw/skills/forgecad-verify-mujoco.md +78 -0
- package/dist/docs-raw/skills/forgecad.md +24 -24
- package/dist/docs-raw/skills/index.md +9 -13
- package/dist/index.html +9 -9
- package/dist/llms.txt +7 -7
- package/dist/sitemap.xml +16 -16
- package/dist-cli/{check-compiler-SP7FAL7R.js → check-compiler-4RPB6SB5.js} +1 -1
- package/dist-cli/{check-query-propagation-BRLSHP22.js → check-query-propagation-KN3DFQTX.js} +1 -1
- package/dist-cli/{chunk-RQQ42YCP.js → chunk-UHBRMYA6.js} +30770 -29253
- package/dist-cli/forgecad.js +3277 -237
- package/dist-cli/{forgecad_geometry-7TVSNVUB.js → forgecad_geometry-2IMYCUWW.js} +0 -8
- package/dist-cli/forgecad_geometry_bg.wasm +0 -0
- package/dist-skill/CONTEXT.md +111 -73
- package/dist-skill/SKILL.md +1 -1
- package/dist-skill/docs/CLI.md +9 -7
- package/dist-skill/docs/generated/assembly.md +1 -1
- package/dist-skill/docs/generated/core.md +96 -1
- package/dist-skill/docs/generated/curves.md +8 -1
- package/dist-skill/docs/generated/output.md +0 -64
- package/dist-skill/docs/generated/runtime-names.md +6 -6
- package/dist-skill/docs/generated/viewport.md +3 -12
- package/dist-skill/docs/guides/inspection-bundles.md +1 -1
- package/dist-skill/library/README.md +9 -13
- package/dist-skill/library/{forgecad-make-a-model → forgecad-build-model}/SKILL.md +16 -6
- package/dist-skill/library/forgecad-design-spec/SKILL.md +132 -0
- package/dist-skill/library/{forgecad-prepare-prompt → forgecad-design-spec}/references/master-prompt.md +1 -1
- package/dist-skill/library/{forgecad-model-grader → forgecad-grade-model}/SKILL.md +6 -4
- package/dist-skill/library/forgecad-grade-model/agents/openai.yaml +4 -0
- package/dist-skill/library/{forgecad-visual-spec → forgecad-image-prompt}/SKILL.md +5 -5
- package/dist-skill/library/forgecad-image-prompt/agents/openai.yaml +4 -0
- package/dist-skill/library/{forgecad-render-inspect → forgecad-inspect-model}/SKILL.md +4 -4
- package/dist-skill/library/{forgecad-project → forgecad-project-sync}/SKILL.md +3 -3
- package/dist-skill/library/{forgecad-3d-reconstruction → forgecad-reconstruct-cad-file}/SKILL.md +5 -5
- package/dist-skill/library/forgecad-reconstruct-cad-file/agents/openai.yaml +4 -0
- package/dist-skill/library/{forgecad-image-replicator → forgecad-reconstruct-from-images}/SKILL.md +10 -10
- package/dist-skill/library/forgecad-reconstruct-from-images/agents/openai.yaml +4 -0
- package/dist-skill/library/forgecad-verify-mujoco/SKILL.md +66 -0
- package/dist-skill/library/forgecad-verify-mujoco/scripts/mujoco_verify.py +385 -0
- package/{dist/docs-raw/skills/forgecad-make-a-model.md → dist-skill/website/skills/forgecad-build-model.md} +18 -8
- package/dist-skill/website/skills/forgecad-design-spec.md +145 -0
- package/dist-skill/website/skills/{forgecad-model-grader.md → forgecad-grade-model.md} +8 -6
- package/{dist/docs-raw/skills/forgecad-visual-spec.md → dist-skill/website/skills/forgecad-image-prompt.md} +7 -7
- package/dist-skill/website/skills/{forgecad-render-inspect.md → forgecad-inspect-model.md} +6 -6
- package/{dist/docs-raw/skills/forgecad-project.md → dist-skill/website/skills/forgecad-project-sync.md} +5 -5
- package/{dist/docs-raw/skills/forgecad-3d-reconstruction.md → dist-skill/website/skills/forgecad-reconstruct-cad-file.md} +7 -7
- package/dist-skill/website/skills/{forgecad-image-replicator.md → forgecad-reconstruct-from-images.md} +12 -12
- package/dist-skill/website/skills/forgecad-verify-mujoco.md +78 -0
- package/dist-skill/website/skills/forgecad.md +24 -24
- package/dist-skill/website/skills/index.md +9 -13
- package/examples/analysis/clearance-fit.forge.js +31 -0
- package/examples/analysis/lever-arm-actuator.forge.js +43 -0
- package/examples/analysis/tipping-tripod.forge.js +35 -0
- package/examples/api/texture-projection.forge.js +75 -0
- package/examples/assets/uv-grid.png +0 -0
- package/examples/products/sportscar.forge.js +77 -0
- package/package.json +1 -3
- package/dist/docs-raw/skills/forgecad-blockout-model.md +0 -49
- package/dist/docs-raw/skills/forgecad-component-model.md +0 -53
- package/dist/docs-raw/skills/forgecad-high-level-spec.md +0 -101
- package/dist/docs-raw/skills/forgecad-lld.md +0 -41
- package/dist/docs-raw/skills/forgecad-prepare-prompt.md +0 -63
- package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +0 -60
- package/dist-skill/library/forgecad-3d-reconstruction/agents/openai.yaml +0 -4
- package/dist-skill/library/forgecad-blockout-model/SKILL.md +0 -42
- package/dist-skill/library/forgecad-component-model/SKILL.md +0 -46
- package/dist-skill/library/forgecad-high-level-spec/SKILL.md +0 -94
- package/dist-skill/library/forgecad-image-replicator/agents/openai.yaml +0 -4
- package/dist-skill/library/forgecad-lld/SKILL.md +0 -34
- package/dist-skill/library/forgecad-model-grader/agents/openai.yaml +0 -4
- package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +0 -50
- package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +0 -48
- package/dist-skill/library/forgecad-reconstruction-benchmark/agents/openai.yaml +0 -4
- package/dist-skill/library/forgecad-visual-spec/agents/openai.yaml +0 -4
- package/dist-skill/website/skills/forgecad-blockout-model.md +0 -49
- package/dist-skill/website/skills/forgecad-component-model.md +0 -53
- package/dist-skill/website/skills/forgecad-high-level-spec.md +0 -101
- package/dist-skill/website/skills/forgecad-lld.md +0 -41
- package/dist-skill/website/skills/forgecad-prepare-prompt.md +0 -63
- package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +0 -60
- /package/dist/assets/{landing-proof-driven-DiGqdtWa.js → landing-proof-driven-BxZZh5r5.js} +0 -0
- /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-design-spec}/references/default-profiles.md +0 -0
- /package/dist-skill/library/{forgecad-render-inspect → forgecad-inspect-model}/summarize_manifest.py +0 -0
- /package/dist-skill/library/{forgecad-image-replicator → forgecad-reconstruct-from-images}/scripts/compare_images.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,
|
|
5
|
-
import { m as mergeViewportRenderSceneStates, v as validateJointOverrides, b as buildBaseJointValues, p as parseRenderSceneCliSpec, g as getSceneObjectTreePath } from "../jointPose-
|
|
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,71 +1406,529 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}, context = {})
|
|
|
1399
1406
|
const warnings = [];
|
|
1400
1407
|
const rayMaterial = new MeshBasicMaterial({ side: DoubleSide });
|
|
1401
1408
|
const rayTargets = [
|
|
1402
|
-
|
|
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
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
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
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
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
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
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
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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
|
-
|
|
1456
|
-
|
|
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
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
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
|
}
|
|
1859
|
+
const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
|
|
1860
|
+
smoothAngleDeg: 5,
|
|
1861
|
+
sharpAngleDeg: 30,
|
|
1862
|
+
harshAngleDeg: 90,
|
|
1863
|
+
maxSamplesPerObject: 5e3
|
|
1864
|
+
};
|
|
1865
|
+
const ROUGHNESS_COLORS = {
|
|
1866
|
+
smooth: [62, 72, 84],
|
|
1867
|
+
moderate: [255, 214, 0],
|
|
1868
|
+
sharp: [255, 124, 34],
|
|
1869
|
+
harsh: [255, 42, 96]
|
|
1870
|
+
};
|
|
1871
|
+
function resolveRoughnessInspectionOptions(raw = {}) {
|
|
1872
|
+
const options = {
|
|
1873
|
+
smoothAngleDeg: raw.smoothAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.smoothAngleDeg,
|
|
1874
|
+
sharpAngleDeg: raw.sharpAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.sharpAngleDeg,
|
|
1875
|
+
harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg,
|
|
1876
|
+
maxSamplesPerObject: raw.maxSamplesPerObject ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.maxSamplesPerObject
|
|
1877
|
+
};
|
|
1878
|
+
if (!Number.isFinite(options.smoothAngleDeg) || options.smoothAngleDeg < 0) {
|
|
1879
|
+
throw new Error(`smoothAngleDeg must be a finite non-negative angle (got ${options.smoothAngleDeg}).`);
|
|
1880
|
+
}
|
|
1881
|
+
if (!Number.isFinite(options.sharpAngleDeg) || options.sharpAngleDeg <= options.smoothAngleDeg) {
|
|
1882
|
+
throw new Error(`sharpAngleDeg must be greater than smoothAngleDeg (got ${options.sharpAngleDeg}).`);
|
|
1883
|
+
}
|
|
1884
|
+
if (!Number.isFinite(options.harshAngleDeg) || options.harshAngleDeg <= options.sharpAngleDeg || options.harshAngleDeg > 180) {
|
|
1885
|
+
throw new Error(`harshAngleDeg must be greater than sharpAngleDeg and <= 180 (got ${options.harshAngleDeg}).`);
|
|
1886
|
+
}
|
|
1887
|
+
if (!Number.isFinite(options.maxSamplesPerObject) || options.maxSamplesPerObject <= 0) {
|
|
1888
|
+
throw new Error(`maxSamplesPerObject must be a positive finite number (got ${options.maxSamplesPerObject}).`);
|
|
1889
|
+
}
|
|
1890
|
+
return {
|
|
1891
|
+
...options,
|
|
1892
|
+
maxSamplesPerObject: Math.max(1, Math.floor(options.maxSamplesPerObject))
|
|
1893
|
+
};
|
|
1894
|
+
}
|
|
1895
|
+
function roughnessClassForAngle(angleDeg, options) {
|
|
1896
|
+
if (angleDeg >= options.harshAngleDeg) return "harsh";
|
|
1897
|
+
if (angleDeg >= options.sharpAngleDeg) return "sharp";
|
|
1898
|
+
if (angleDeg >= options.smoothAngleDeg) return "moderate";
|
|
1899
|
+
return "smooth";
|
|
1900
|
+
}
|
|
1901
|
+
function roughnessScoreForAngle(angleDeg, options) {
|
|
1902
|
+
if (angleDeg < options.sharpAngleDeg) return 0;
|
|
1903
|
+
if (angleDeg < options.harshAngleDeg) {
|
|
1904
|
+
return MathUtils.lerp(0.48, 0.82, (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg));
|
|
1905
|
+
}
|
|
1906
|
+
return 1;
|
|
1907
|
+
}
|
|
1908
|
+
function roughnessColorForAngle(angleDeg, options) {
|
|
1909
|
+
const cls = roughnessClassForAngle(angleDeg, options);
|
|
1910
|
+
if (cls === "smooth" || cls === "harsh") return ROUGHNESS_COLORS[cls];
|
|
1911
|
+
if (cls === "moderate") {
|
|
1912
|
+
return lerpRgb(
|
|
1913
|
+
ROUGHNESS_COLORS.moderate,
|
|
1914
|
+
ROUGHNESS_COLORS.sharp,
|
|
1915
|
+
(angleDeg - options.smoothAngleDeg) / (options.sharpAngleDeg - options.smoothAngleDeg)
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
return lerpRgb(
|
|
1919
|
+
ROUGHNESS_COLORS.sharp,
|
|
1920
|
+
ROUGHNESS_COLORS.harsh,
|
|
1921
|
+
(angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg)
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
function lerpRgb(a, b, t) {
|
|
1925
|
+
const clamped = MathUtils.clamp(t, 0, 1);
|
|
1926
|
+
return [
|
|
1927
|
+
Math.round(MathUtils.lerp(a[0], b[0], clamped)),
|
|
1928
|
+
Math.round(MathUtils.lerp(a[1], b[1], clamped)),
|
|
1929
|
+
Math.round(MathUtils.lerp(a[2], b[2], clamped))
|
|
1930
|
+
];
|
|
1931
|
+
}
|
|
1467
1932
|
function emptyRoughnessSummary() {
|
|
1468
1933
|
return {
|
|
1469
1934
|
triangleCount: 0,
|
|
@@ -1928,6 +2393,16 @@ function createCaptureDebugLogger(enabled) {
|
|
|
1928
2393
|
console.info(`[forge-capture:debug] +${sinceStart}ms Δ${delta}ms ${phase}${detailText}`);
|
|
1929
2394
|
};
|
|
1930
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
|
+
}
|
|
1931
2406
|
class EmptyInspectionShape {
|
|
1932
2407
|
intersect() {
|
|
1933
2408
|
return {
|
|
@@ -3602,21 +4077,100 @@ function bboxFromGeometry(geometry) {
|
|
|
3602
4077
|
max: bbox ? [bbox.max.x, bbox.max.y, bbox.max.z] : [0, 0, 0]
|
|
3603
4078
|
};
|
|
3604
4079
|
}
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
samples.
|
|
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) => {
|
|
3609
4088
|
const base = index * 3;
|
|
3610
|
-
positions[base] = sample.position[0]
|
|
3611
|
-
positions[base + 1] = sample.position[1]
|
|
3612
|
-
positions[base + 2] = sample.position[2]
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
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}.`);
|
|
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
|
|
3616
4141
|
});
|
|
3617
|
-
|
|
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))
|
|
4152
|
+
});
|
|
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
|
+
};
|
|
3618
4171
|
}
|
|
3619
|
-
function
|
|
4172
|
+
function renderScalarSurfaceOverlays(session, overlays) {
|
|
4173
|
+
const started = performance.now();
|
|
3620
4174
|
const r = getRenderer(session.size, session.pixelRatio);
|
|
3621
4175
|
const overlayById = new Map(overlays.map((overlay) => [overlay.renderable.id, overlay]));
|
|
3622
4176
|
const replacements = session.renderables.map((renderable) => {
|
|
@@ -3633,62 +4187,108 @@ function renderScalarPointOverlays(session, overlays) {
|
|
|
3633
4187
|
ghostMaterial.toneMapped = false;
|
|
3634
4188
|
renderable.solid.material = ghostMaterial;
|
|
3635
4189
|
const overlay = overlayById.get(renderable.id);
|
|
3636
|
-
if (!overlay || overlay.positions.length === 0)
|
|
3637
|
-
return { renderable, previousMaterial, ghostMaterial,
|
|
4190
|
+
if (!overlay || overlay.positions.length === 0) {
|
|
4191
|
+
return { renderable, previousMaterial, ghostMaterial, mesh: null, geometry: null, material: null, lut: null };
|
|
4192
|
+
}
|
|
3638
4193
|
const geometry = new BufferGeometry();
|
|
3639
4194
|
geometry.setAttribute("position", new BufferAttribute(overlay.positions, 3));
|
|
3640
|
-
geometry.setAttribute("
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
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)
|
|
3648
4223
|
});
|
|
3649
4224
|
material.toneMapped = false;
|
|
3650
|
-
const
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
renderable.root.add(
|
|
3654
|
-
return { renderable, previousMaterial, ghostMaterial,
|
|
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 };
|
|
3655
4230
|
});
|
|
3656
4231
|
try {
|
|
3657
|
-
|
|
4232
|
+
const png = withSolidOnlyVisibility(
|
|
3658
4233
|
session,
|
|
3659
4234
|
() => withTemporarySceneBackground(session, new Color(0), () => {
|
|
3660
4235
|
r.render(session.scene, session.camera);
|
|
3661
4236
|
return captureRenderedPng(session.size);
|
|
3662
4237
|
})
|
|
3663
4238
|
);
|
|
4239
|
+
inspectProfileLog("scalar-render", {
|
|
4240
|
+
overlays: overlays.length,
|
|
4241
|
+
totalMs: Number((performance.now() - started).toFixed(1))
|
|
4242
|
+
});
|
|
4243
|
+
return png;
|
|
3664
4244
|
} finally {
|
|
3665
|
-
replacements.forEach(({ renderable, previousMaterial, ghostMaterial,
|
|
3666
|
-
if (
|
|
4245
|
+
replacements.forEach(({ renderable, previousMaterial, ghostMaterial, mesh, geometry, material, lut }) => {
|
|
4246
|
+
if (mesh) renderable.root.remove(mesh);
|
|
3667
4247
|
renderable.solid.material = previousMaterial;
|
|
3668
4248
|
ghostMaterial.dispose();
|
|
3669
4249
|
geometry == null ? void 0 : geometry.dispose();
|
|
3670
4250
|
material == null ? void 0 : material.dispose();
|
|
4251
|
+
lut == null ? void 0 : lut.dispose();
|
|
3671
4252
|
});
|
|
3672
4253
|
}
|
|
3673
4254
|
}
|
|
3674
|
-
function getSessionThicknessInspection(session, rawOptions) {
|
|
4255
|
+
function getSessionThicknessInspection(session, rawOptions, fieldOverride) {
|
|
3675
4256
|
var _a;
|
|
3676
4257
|
const resolvedOptions = resolveThicknessInspectionOptions(rawOptions);
|
|
3677
4258
|
const { options, sampleBudget } = withSceneSampleBudget(session, resolvedOptions, (rawOptions == null ? void 0 : rawOptions.maxSamplesPerObject) !== void 0);
|
|
3678
|
-
const
|
|
4259
|
+
const fieldParams = mergeScalarFieldParams(fieldOverride);
|
|
4260
|
+
const optionsKey = inspectionOptionsKey({ options, sampleBudget, fieldParams });
|
|
3679
4261
|
if (((_a = session.thicknessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.thicknessInspection;
|
|
3680
4262
|
const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
|
|
3681
4263
|
const warnings = [];
|
|
3682
4264
|
maybePushSceneSampleBudgetWarning(warnings, "Thickness", sampleBudget);
|
|
4265
|
+
const thicknessColorScale = {
|
|
4266
|
+
colormap: DEFAULT_THICKNESS_COLOR_SCALE.colormap,
|
|
4267
|
+
domainMin: options.colorMinThickness,
|
|
4268
|
+
domainMax: options.colorMaxThickness
|
|
4269
|
+
};
|
|
3683
4270
|
const objects = [];
|
|
3684
4271
|
const cloudObjects = [];
|
|
3685
4272
|
const points = [];
|
|
3686
4273
|
const overlays = [];
|
|
4274
|
+
const connectivityStarted = performance.now();
|
|
3687
4275
|
const raycastConnectivity = buildThicknessRaycastConnectivityContext(session);
|
|
4276
|
+
inspectProfileLog("thickness-connectivity", {
|
|
4277
|
+
ms: Number((performance.now() - connectivityStarted).toFixed(1)),
|
|
4278
|
+
neighborSets: raycastConnectivity.neighborIdsByObjectId.size
|
|
4279
|
+
});
|
|
3688
4280
|
session.renderables.forEach((renderable, index) => {
|
|
4281
|
+
var _a2;
|
|
3689
4282
|
const sourceObject = byId.get(renderable.id);
|
|
3690
|
-
const
|
|
3691
|
-
|
|
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))
|
|
3692
4292
|
});
|
|
3693
4293
|
const bbox = bboxFromGeometry(analysis.geometry);
|
|
3694
4294
|
const summary = summarizeThicknessSamples(analysis.samples, options);
|
|
@@ -3727,7 +4327,27 @@ function getSessionThicknessInspection(session, rawOptions) {
|
|
|
3727
4327
|
...sample
|
|
3728
4328
|
});
|
|
3729
4329
|
});
|
|
3730
|
-
|
|
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
|
+
}
|
|
3731
4351
|
analysis.geometry.dispose();
|
|
3732
4352
|
});
|
|
3733
4353
|
const state = {
|
|
@@ -3750,7 +4370,7 @@ function getSessionThicknessInspection(session, rawOptions) {
|
|
|
3750
4370
|
objects,
|
|
3751
4371
|
warnings,
|
|
3752
4372
|
style: {
|
|
3753
|
-
|
|
4373
|
+
colorScale: thicknessColorScale,
|
|
3754
4374
|
colorMinThickness: options.colorMinThickness,
|
|
3755
4375
|
colorMaxThickness: options.colorMaxThickness,
|
|
3756
4376
|
unknownColor: THICKNESS_COLORS.unknown
|
|
@@ -3760,21 +4380,22 @@ function getSessionThicknessInspection(session, rawOptions) {
|
|
|
3760
4380
|
session.thicknessInspection = state;
|
|
3761
4381
|
return state;
|
|
3762
4382
|
}
|
|
3763
|
-
function renderCurrentThickness(session, rawOptions) {
|
|
3764
|
-
const state = getSessionThicknessInspection(session, rawOptions);
|
|
3765
|
-
return { png:
|
|
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 };
|
|
3766
4386
|
}
|
|
3767
4387
|
const ROUGHNESS_SMOOTH_OPACITY = 0.16;
|
|
3768
4388
|
const ROUGHNESS_HARSH_OPACITY = 1;
|
|
3769
|
-
function renderCurrentRoughness(session, rawOptions) {
|
|
3770
|
-
const state = getSessionRoughnessInspection(session, rawOptions);
|
|
3771
|
-
return { png:
|
|
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 };
|
|
3772
4392
|
}
|
|
3773
|
-
function getSessionRoughnessInspection(session, rawOptions) {
|
|
4393
|
+
function getSessionRoughnessInspection(session, rawOptions, fieldOverride) {
|
|
3774
4394
|
var _a;
|
|
3775
4395
|
const resolvedOptions = resolveRoughnessInspectionOptions(rawOptions);
|
|
3776
4396
|
const { options, sampleBudget } = withSceneSampleBudget(session, resolvedOptions, (rawOptions == null ? void 0 : rawOptions.maxSamplesPerObject) !== void 0);
|
|
3777
|
-
const
|
|
4397
|
+
const fieldParams = mergeScalarFieldParams(fieldOverride);
|
|
4398
|
+
const optionsKey = inspectionOptionsKey({ options, sampleBudget, fieldParams });
|
|
3778
4399
|
if (((_a = session.roughnessInspection) == null ? void 0 : _a.optionsKey) === optionsKey) return session.roughnessInspection;
|
|
3779
4400
|
const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
|
|
3780
4401
|
const warnings = [];
|
|
@@ -3783,8 +4404,23 @@ function getSessionRoughnessInspection(session, rawOptions) {
|
|
|
3783
4404
|
const cloudObjects = [];
|
|
3784
4405
|
const points = [];
|
|
3785
4406
|
const overlays = [];
|
|
3786
|
-
|
|
4407
|
+
let roughnessMin = Infinity;
|
|
4408
|
+
let roughnessMax = -Infinity;
|
|
4409
|
+
const analysesByIndex = [];
|
|
4410
|
+
session.renderables.forEach((renderable) => {
|
|
3787
4411
|
const analysis = analyzeRoughnessGeometry(renderable.solid.geometry, options);
|
|
4412
|
+
analysesByIndex.push(analysis);
|
|
4413
|
+
for (const sample of analysis.pointSamples) {
|
|
4414
|
+
if (sample.value != null && Number.isFinite(sample.value)) {
|
|
4415
|
+
if (sample.value < roughnessMin) roughnessMin = sample.value;
|
|
4416
|
+
if (sample.value > roughnessMax) roughnessMax = sample.value;
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
});
|
|
4420
|
+
const roughnessColorScale = Number.isFinite(roughnessMin) && roughnessMax > roughnessMin ? { colormap: DEFAULT_ROUGHNESS_COLOR_SCALE.colormap, domainMin: roughnessMin, domainMax: roughnessMax } : DEFAULT_ROUGHNESS_COLOR_SCALE;
|
|
4421
|
+
session.renderables.forEach((renderable, index) => {
|
|
4422
|
+
var _a2;
|
|
4423
|
+
const analysis = analysesByIndex[index];
|
|
3788
4424
|
const bbox = bboxFromGeometry(analysis.geometry);
|
|
3789
4425
|
const sourceObject = byId.get(renderable.id);
|
|
3790
4426
|
if (analysis.warnings.length > 0) {
|
|
@@ -3819,7 +4455,21 @@ function getSessionRoughnessInspection(session, rawOptions) {
|
|
|
3819
4455
|
...sample
|
|
3820
4456
|
});
|
|
3821
4457
|
});
|
|
3822
|
-
|
|
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
|
+
}
|
|
3823
4473
|
analysis.geometry.dispose();
|
|
3824
4474
|
});
|
|
3825
4475
|
const state = {
|
|
@@ -3842,6 +4492,9 @@ function getSessionRoughnessInspection(session, rawOptions) {
|
|
|
3842
4492
|
objects,
|
|
3843
4493
|
warnings,
|
|
3844
4494
|
style: {
|
|
4495
|
+
// Legacy class colors kept for back-compat; colorScale is the truth the
|
|
4496
|
+
// viewport/CLI now render with (continuous viridis over the data domain).
|
|
4497
|
+
colorScale: roughnessColorScale,
|
|
3845
4498
|
smoothColor: ROUGHNESS_COLORS.smooth,
|
|
3846
4499
|
moderateColor: ROUGHNESS_COLORS.moderate,
|
|
3847
4500
|
sharpColor: ROUGHNESS_COLORS.sharp,
|
|
@@ -4968,6 +5621,60 @@ Available renderable objects: ${available}`;
|
|
|
4968
5621
|
}
|
|
4969
5622
|
return "No visible renderable objects found.";
|
|
4970
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
|
+
}
|
|
4971
5678
|
function createSession(code, opts) {
|
|
4972
5679
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u;
|
|
4973
5680
|
const size = (opts == null ? void 0 : opts.size) ?? 1024;
|
|
@@ -5321,6 +6028,12 @@ Fix one:
|
|
|
5321
6028
|
depthWrite: materialOpacity >= 0.99 && materialTransmission <= 0,
|
|
5322
6029
|
clippingPlanes: applicableCutPlanes
|
|
5323
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
|
+
}
|
|
5324
6037
|
solid = new Mesh(geo.solid, solidMaterial);
|
|
5325
6038
|
if (isScanRenderStyle) {
|
|
5326
6039
|
const scanProxyGeometry = createScanProxyGeometry(geo.solid, { grid: scanProxyGrid ?? void 0 });
|
|
@@ -5513,6 +6226,7 @@ async function emitInspectProgress(opts, event) {
|
|
|
5513
6226
|
}
|
|
5514
6227
|
window.__forgeRender = async (code, opts) => {
|
|
5515
6228
|
var _a, _b, _c;
|
|
6229
|
+
setInspectProfileEnabled(opts == null ? void 0 : opts.debug);
|
|
5516
6230
|
const requestedCameraTokens = (opts == null ? void 0 : opts.cameras) ?? (opts == null ? void 0 : opts.angles);
|
|
5517
6231
|
const hasDirectionalCameraTokens = Array.isArray(requestedCameraTokens) && requestedCameraTokens.length > 0;
|
|
5518
6232
|
if ((opts == null ? void 0 : opts.viewName) && hasDirectionalCameraTokens) {
|
|
@@ -5530,6 +6244,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
5530
6244
|
const built = createSession(code, {
|
|
5531
6245
|
size: (opts == null ? void 0 : opts.size) || 1024,
|
|
5532
6246
|
pixelRatio: (opts == null ? void 0 : opts.pixelRatio) || 1,
|
|
6247
|
+
debug: opts == null ? void 0 : opts.debug,
|
|
5533
6248
|
quality: opts == null ? void 0 : opts.quality,
|
|
5534
6249
|
allFiles: opts == null ? void 0 : opts.allFiles,
|
|
5535
6250
|
binaryFiles: opts == null ? void 0 : opts.binaryFiles,
|
|
@@ -5592,6 +6307,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
5592
6307
|
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
5593
6308
|
}
|
|
5594
6309
|
}
|
|
6310
|
+
await awaitPendingTextureDecodes();
|
|
5595
6311
|
await emitInspectProgress(opts, { type: "session-done", objectCount: session.objects.length });
|
|
5596
6312
|
const renderMode = (opts == null ? void 0 : opts.renderMode) === "wireframe" ? "wireframe" : "solid";
|
|
5597
6313
|
const edgePreset = (opts == null ? void 0 : opts.edges) ?? "off";
|
|
@@ -5717,7 +6433,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
5717
6433
|
}
|
|
5718
6434
|
if (requestedChannels.has("roughness")) {
|
|
5719
6435
|
await markChannelViewStart("roughness", label);
|
|
5720
|
-
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);
|
|
5721
6437
|
roughnessRenders[label] = roughness.png;
|
|
5722
6438
|
roughnessReport = roughness.report;
|
|
5723
6439
|
roughnessPointCloud = roughness.pointCloud;
|
|
@@ -5772,7 +6488,7 @@ window.__forgeRender = async (code, opts) => {
|
|
|
5772
6488
|
}
|
|
5773
6489
|
if (requestedChannels.has("thickness")) {
|
|
5774
6490
|
await markChannelViewStart("thickness", label);
|
|
5775
|
-
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);
|
|
5776
6492
|
thicknessRenders[label] = thickness.png;
|
|
5777
6493
|
thicknessReport = thickness.report;
|
|
5778
6494
|
thicknessPointCloud = thickness.pointCloud;
|
|
@@ -5917,6 +6633,7 @@ window.__forgeCaptureInit = async (code, opts) => {
|
|
|
5917
6633
|
if (!built.ok) {
|
|
5918
6634
|
return built;
|
|
5919
6635
|
}
|
|
6636
|
+
await awaitPendingTextureDecodes();
|
|
5920
6637
|
captureSession = built.session;
|
|
5921
6638
|
return {
|
|
5922
6639
|
ok: true,
|