forgecad 0.9.15 → 0.9.16

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 (70) hide show
  1. package/dist/assets/{AdminPage-CDyGUinA.js → AdminPage-CXvls4-J.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-DfPMY_-d.js → BenchmarkPage-B27zk8xL.js} +1 -1
  3. package/dist/assets/{BlogPage-kF0fkdJT.js → BlogPage-CMAVvgQL.js} +1 -1
  4. package/dist/assets/{DocsPage-B954L3YN.js → DocsPage-knf4I4h7.js} +1 -1
  5. package/dist/assets/EditorApp-BHMQlJ-D.js +14686 -0
  6. package/dist/assets/{EditorApp-CuDLxKqL.css → EditorApp-BpjZgzk0.css} +148 -0
  7. package/dist/assets/{EmbedViewer-C77B-TrF.js → EmbedViewer-D7ZGlFjx.js} +2 -2
  8. package/dist/assets/{LandingPageProofDriven-Cr6fXMDj.js → LandingPageProofDriven-CnevhTE8.js} +2 -2
  9. package/dist/assets/{LegalPage-Dzklqmmg.js → LegalPage-BPTUmqeg.js} +1 -1
  10. package/dist/assets/{PricingPage-zWXkvlwl.js → PricingPage-B0D4goG_.js} +1 -1
  11. package/dist/assets/{SettingsPage-Bz0of4KQ.js → SettingsPage-CFF-UgjI.js} +1 -1
  12. package/dist/assets/{app-D3kDkggg.js → app-T0pDcSX4.js} +1184 -218
  13. package/dist/assets/cli/{render-DSY3mMQa.js → render-C5pcIISc.js} +144 -26
  14. package/dist/assets/{constructionHistoryWorker-gpDo-uH2.js → constructionHistoryWorker-Ba2Hm58b.js} +1 -0
  15. package/dist/assets/{evalWorker-CU0Ke6DP.js → evalWorker-vkx310U2.js} +1380 -2173
  16. package/dist/assets/{inspectWorker-COyp8XXA.js → inspectWorker-BuTJDVX6.js} +252 -30
  17. package/dist/assets/{targets-B9sGB5nB.js → jointPose-B_Cgedn9.js} +71 -3
  18. package/dist/assets/{manifold-DNkrUWpA.js → manifold-BWgsjmAM.js} +1 -1
  19. package/dist/assets/{manifold-C-3h2M7p.js → manifold-D6IFSkhH.js} +2 -2
  20. package/dist/assets/{manifold-BRI5prcH.js → manifold-rZexZI0G.js} +1 -1
  21. package/dist/assets/{reportWorker-CdBz5bNg.js → reportWorker-0AGij1Ru.js} +1373 -2166
  22. package/dist/assets/{scalar-sampling-budget-wJF98aY9.js → scalar-sampling-budget-J5cuzxT1.js} +1494 -2251
  23. package/dist/assets/{scanProxyWorker-B-9VbLIs.js → scanProxyWorker-Vl4Wxa1y.js} +18 -5
  24. package/dist/cli/render.html +1 -1
  25. package/dist/docs/index.html +1 -1
  26. package/dist/docs-raw/AI/usage.md +2 -0
  27. package/dist/docs-raw/CLI.md +4 -0
  28. package/dist/docs-raw/generated/assembly.md +104 -6
  29. package/dist/docs-raw/generated/concepts.md +14 -4
  30. package/dist/docs-raw/generated/lib.md +2 -18
  31. package/dist/docs-raw/generated/output.md +14 -4
  32. package/dist/docs-raw/generated/runtime-names.md +27 -19
  33. package/dist/docs-raw/skills/forgecad-make-a-model.md +39 -38
  34. package/dist/docs-raw/skills/forgecad-project.md +2 -0
  35. package/dist/docs-raw/welcome.md +2 -0
  36. package/dist/index.html +1 -1
  37. package/dist/sitemap.xml +13 -13
  38. package/dist-cli/{check-compiler-SDX5QIXI.js → check-compiler-SYQ2PWOB.js} +1 -1
  39. package/dist-cli/{check-query-propagation-EAYEFT77.js → check-query-propagation-HIAGV62W.js} +1 -1
  40. package/dist-cli/{chunk-N4O47JLF.js → chunk-SPZE3DUY.js} +1591 -2356
  41. package/dist-cli/forgecad.js +1698 -487
  42. package/dist-skill/CONTEXT.md +117 -46
  43. package/dist-skill/docs/CLI.md +4 -0
  44. package/dist-skill/docs/generated/assembly.md +83 -5
  45. package/dist-skill/docs/generated/lib.md +2 -18
  46. package/dist-skill/docs/generated/output.md +14 -4
  47. package/dist-skill/docs/generated/runtime-names.md +18 -19
  48. package/dist-skill/library/forgecad-make-a-model/SKILL.md +39 -38
  49. package/dist-skill/library/forgecad-project/SKILL.md +2 -0
  50. package/examples/api/helix-basics.forge.js +2 -2
  51. package/examples/api/route3d-elbow.forge.js +3 -0
  52. package/examples/api/variable-sweep-test.forge.js +3 -1
  53. package/package.json +4 -1
  54. package/dist/assets/EditorApp-Beb-IZ0y.js +0 -14014
  55. package/examples/api/bolted-service-cover.forge.js +0 -17
  56. package/examples/api/cable-gland-anchor.forge.js +0 -14
  57. package/examples/api/captured-cartridge-guide.forge.js +0 -14
  58. package/examples/api/captured-linear-slide.forge.js +0 -13
  59. package/examples/api/clevis-pin-joint.forge.js +0 -13
  60. package/examples/api/datum-enclosure.forge.js +0 -16
  61. package/examples/api/hose-barb-port.forge.js +0 -14
  62. package/examples/api/knuckled-hinge-assembly.forge.js +0 -15
  63. package/examples/api/living-hinge-cover.forge.js +0 -14
  64. package/examples/api/pcb-terminal-block.forge.js +0 -22
  65. package/examples/api/pinned-lever-pivot-stack.forge.js +0 -14
  66. package/examples/api/retained-shaft-knob-stack.forge.js +0 -15
  67. package/examples/api/routed-tube-clip.forge.js +0 -15
  68. package/examples/api/seated-bearing-stack.forge.js +0 -30
  69. package/examples/api/snap-latch-cover.forge.js +0 -14
  70. package/examples/api/thumb-screw-clamp.forge.js +0 -15
@@ -1,10 +1,10 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/EditorApp-CuDLxKqL.css","assets/landing-proof-driven-ORyigZ6p.css","assets/BenchmarkPage-BAbsyMaF.css","assets/PricingPage-BPF6HKyO.css","assets/LegalPage-BRlScr9A.css"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/EditorApp-BpjZgzk0.css","assets/landing-proof-driven-ORyigZ6p.css","assets/BenchmarkPage-BAbsyMaF.css","assets/PricingPage-BPF6HKyO.css","assets/LegalPage-BRlScr9A.css"])))=>i.map(i=>d[i]);
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  var _a2;
6
6
  import { c as create, j as jsxRuntimeExports, r as reactExports, a as createWithEqualityFn, R as React, T as Tb, s as schedulerExports, b as clientExports, d as reactDomExports, u as useParams, e as useSearchParams, f as useNavigate, L as Link, g as useLocation, B as BrowserRouter, h as Routes, i as Route, N as Navigate } from "./vendor-react-6j1Kke-Y.js";
7
- import { _ as __vitePreload, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, S as Scene, a as PCFSoftShadowMap, V as VSMShadowMap, b as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, c as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, d as Layers, e as Color, f as RGBAFormat, U as UnsignedByteType, g as Vector3, h as Vector2, i as Clock, T as THREE, D as DoubleSide, j as REVISION, M as Mesh, I as IcosahedronGeometry, k as ShaderMaterial, l as Spherical, Q as Quaternion, m as MOUSE, n as TOUCH, o as Ray, p as Plane, q as DataTextureLoader, H as HalfFloatType, F as FloatType, r as DataUtils, t as LinearFilter, u as RedFormat, v as InstancedBufferGeometry, w as Float32BufferAttribute, x as InstancedInterleavedBuffer, y as InterleavedBufferAttribute, E as WireframeGeometry, G as Box3, J as Sphere, K as UniformsUtils, X as UniformsLib, Y as Vector4, Z as Line3, $ as Matrix4, a0 as MathUtils, a1 as Uniform, a2 as WebGLRenderTarget, a3 as DepthTexture, a4 as BackSide, a5 as ClampToEdgeWrapping, a6 as PlaneGeometry, a7 as UVMapping, a8 as DataTexture, a9 as Texture, aa as MeshBasicMaterial, ab as IntType, ac as ShortType, ad as ByteType, ae as UnsignedIntType, af as Loader, ag as LoadingManager, ah as LinearMipMapLinearFilter, ai as FileLoader, aj as NoBlending, ak as CubeReflectionMapping, al as EquirectangularReflectionMapping, am as CubeTextureLoader, an as WebGLCubeRenderTarget, ao as ConstraintSketch, ap as setSketchPlacement3D, aq as Sketch, ar as PROFILE_BACKEND_MARKER, as as FrozenShape, at as setShapeCompilePlan, au as hasAnyPorts, av as setShapePortsInternal, aw as markShapePortsUsed, ax as setParamOverrides, ay as resolveForgeRenderStyle, az as DEFAULT_ACTIVE_BACKEND, aA as isConstraintSketch, aB as updateConstraintValue, aC as linearizeMultiObjectSteps, aD as getShapeCompilePlan, aE as SCAN_PROXY_GRANULARITY_DEFAULT, aF as publishSolverWasmRunDebug, aG as resolveForgeQualityPreset, aH as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aI as findJointAnimationClip, aJ as resolveJointAnimation, aK as resolveJointViewValues, aL as resolveImportPath, aM as resolveScanProxyGranularity, aN as BufferGeometry, aO as LineBasicMaterial, aP as Line$1, aQ as LineDashedMaterial, aR as DepthStencilFormat, aS as UnsignedInt248Type, aT as MeshNormalMaterial, aU as NearestFilter, aV as BasicDepthPacking, aW as EventDispatcher$1, aX as NoColorSpace, aY as FrontSide, aZ as Material, a_ as AlwaysDepth, a$ as BufferAttribute, b0 as CanvasTexture, b1 as Object3D, b2 as FogExp2, b3 as Fog, b4 as AmbientLight, b5 as HemisphereLight, b6 as SpotLight, b7 as PointLight, b8 as DirectionalLight, b9 as heatPointsForSide, ba as COMPARISON_COLORS, bb as comparisonHeatDepthTest, bc as comparisonHeatEdgeOpacity, bd as comparisonHeatPatchOpacity, be as shapeToGeometry, bf as buildComparisonHeatPatchGeometry, bg as EdgesGeometry, bh as buildShapeFromCompilePlan, bi as buildVisibleHistoryStacks, bj as sketchToSvg, bk as sketchToDxf, bl as runScript, bm as MeshPhysicalMaterial, bn as LineSegments, bo as findDesignTraceNodeForConstructionStep, bp as formatDesignTraceAnchor, bq as waitForAnimationFrame, br as selectBuildLedgerNodes, bs as worldAuthorPlaneToLocal, bt as scanProxySourceBytes, bu as disposeScanProxyGeometry, bv as scanProxyGeometryFromPayload, bw as AdditiveBlending, bx as geometryWithVisibleVertexColors, by as getRenderStylePreset, bz as SCAN_RENDER_COLORS, bA as NormalBlending, bB as ZEBRA_STRIPE_SOFTNESS, bC as ZEBRA_STRIPE_SCALE, bD as ZEBRA_LIGHT_COLOR, bE as ZEBRA_DARK_COLOR, bF as ZEBRA_ACCENT_COLOR, bG as ZEBRA_STRIPE_FRAGMENT_SHADER, bH as ZEBRA_STRIPE_VERTEX_SHADER, bI as SCAN_PROXY_LAYER_STYLES, bJ as SURFACE_FIELD_FRAGMENT_SHADER, bK as SURFACE_FIELD_VERTEX_SHADER, bL as WORLD_UP, bM as CatmullRomCurve3, bN as TubeGeometry, bO as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bP as ROUGHNESS_COLORS, bQ as DEFAULT_THICKNESS_INSPECTION_OPTIONS, bR as THICKNESS_COLORS, bS as PERFORMANCE_SAMPLE_INTERVAL_SEC, bT as formatPerformanceCount, bU as NON_TEXT_INPUT_TYPES, bV as MeshStandardMaterial, bW as compileSdfNode3, bX as buildSdfRaymarchFragmentShader, bY as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bZ as Shape, b_ as ShapeGeometry, b$ as ShaderLib, c0 as CylinderGeometry, c1 as VIEWPORT_CAMERA_STORAGE_KEY, c2 as parseViewportCameraState, c3 as createResolvedExplodeConfig, c4 as explodeBoundsCenter, c5 as explodeMergeBounds, c6 as resolveExplodeDirective, c7 as computeExplodeMotion, c8 as getSketchWorldMatrix, c9 as explodeAdd, ca as hasExplodeOverride, cb as resolveExplodeLocalFanDirection, cc as explodeMul, cd as explodeLeafFanStage, ce as normalizeCutPlane, cf as toClippingPlane, cg as isObjectExcludedFromCutPlane, ch as getShapePorts, ci as getShapeUsedPorts, cj as DEFAULT_VIEW_CONFIG, ck as SECTION_EXPLORER_PLANE_NAME, cl as ZERO_OFFSET, cm as scanProxyGridForBounds, cn as IDENTITY_MATRIX$2, co as OBJECT_CONTEXT_MENU_MARGIN, cp as OBJECT_CONTEXT_MENU_WIDTH, cq as OBJECT_CONTEXT_MENU_HEIGHT, cr as getKernelFaceNameForTriangle, cs as triangleSoupFromMeshes, ct as compareTriangleSoups, cu as buildGeometryComparisonPointCloud, cv as aabbOverlaps, cw as aabbOverlapVolume, cx as DEFAULT_COLLISION_INSPECTION_OPTIONS, cy as resolveScalarSceneSampleBudget, cz as FOCUS_MODE_DIM_OPACITY, cA as comparisonCandidateContextOpacity, cB as initKernel, cC as initSolverWasm } from "./scalar-sampling-budget-wJF98aY9.js";
7
+ import { _ as __vitePreload, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, S as Scene, a as PCFSoftShadowMap, V as VSMShadowMap, b as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, c as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, d as Layers, e as Color, f as RGBAFormat, U as UnsignedByteType, g as Vector3, h as Vector2, i as Clock, T as THREE, D as DoubleSide, j as REVISION, M as Mesh, I as IcosahedronGeometry, k as ShaderMaterial, l as Spherical, Q as Quaternion, m as MOUSE, n as TOUCH, o as Ray, p as Plane, q as DataTextureLoader, H as HalfFloatType, F as FloatType, r as DataUtils, t as LinearFilter, u as RedFormat, v as InstancedBufferGeometry, w as Float32BufferAttribute, x as InstancedInterleavedBuffer, y as InterleavedBufferAttribute, E as WireframeGeometry, G as Box3, J as Sphere, K as UniformsUtils, X as UniformsLib, Y as Vector4, Z as Line3, $ as Matrix4, a0 as MathUtils, a1 as Uniform, a2 as WebGLRenderTarget, a3 as DepthTexture, a4 as BackSide, a5 as ClampToEdgeWrapping, a6 as PlaneGeometry, a7 as UVMapping, a8 as DataTexture, a9 as Texture, aa as MeshBasicMaterial, ab as IntType, ac as ShortType, ad as ByteType, ae as UnsignedIntType, af as Loader, ag as LoadingManager, ah as LinearMipMapLinearFilter, ai as FileLoader, aj as NoBlending, ak as CubeReflectionMapping, al as EquirectangularReflectionMapping, am as CubeTextureLoader, an as WebGLCubeRenderTarget, ao as ConstraintSketch, ap as setSketchPlacement3D, aq as Sketch, ar as PROFILE_BACKEND_MARKER, as as FrozenShape, at as setShapeCompilePlan, au as hasAnyPorts, av as setShapePortsInternal, aw as markShapePortsUsed, ax as setParamOverrides, ay as resolveForgeRenderStyle, az as DEFAULT_ACTIVE_BACKEND, aA as isConstraintSketch, aB as updateConstraintValue, aC as linearizeMultiObjectSteps, aD as getShapeCompilePlan, aE as SCAN_PROXY_GRANULARITY_DEFAULT, aF as publishSolverWasmRunDebug, aG as resolveForgeQualityPreset, aH as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aI as findJointAnimationClip, aJ as resolveJointAnimation, aK as resolveJointViewValues, aL as resolveImportPath, aM as resolveScanProxyGranularity, aN as BufferGeometry, aO as LineBasicMaterial, aP as Line$1, aQ as LineDashedMaterial, aR as DepthStencilFormat, aS as UnsignedInt248Type, aT as MeshNormalMaterial, aU as NearestFilter, aV as BasicDepthPacking, aW as EventDispatcher$1, aX as NoColorSpace, aY as FrontSide, aZ as Material, a_ as AlwaysDepth, a$ as BufferAttribute, b0 as CanvasTexture, b1 as Object3D, b2 as FogExp2, b3 as Fog, b4 as AmbientLight, b5 as HemisphereLight, b6 as SpotLight, b7 as PointLight, b8 as DirectionalLight, b9 as heatPointsForSide, ba as COMPARISON_COLORS, bb as comparisonHeatDepthTest, bc as comparisonHeatEdgeOpacity, bd as comparisonHeatPatchOpacity, be as shapeToGeometry, bf as buildComparisonHeatPatchGeometry, bg as EdgesGeometry, bh as buildShapeFromCompilePlan, bi as buildVisibleHistoryStacks, bj as sketchToSvg, bk as sketchToDxf, bl as runScript, bm as MeshPhysicalMaterial, bn as LineSegments, bo as findDesignTraceNodeForConstructionStep, bp as formatDesignTraceAnchor, bq as waitForAnimationFrame, br as selectBuildLedgerNodes, bs as worldAuthorPlaneToLocal, bt as scanProxySourceBytes, bu as disposeScanProxyGeometry, bv as scanProxyGeometryFromPayload, bw as AdditiveBlending, bx as geometryWithVisibleVertexColors, by as getRenderStylePreset, bz as SCAN_RENDER_COLORS, bA as NormalBlending, bB as ZEBRA_STRIPE_SOFTNESS, bC as ZEBRA_STRIPE_SCALE, bD as ZEBRA_LIGHT_COLOR, bE as ZEBRA_DARK_COLOR, bF as ZEBRA_ACCENT_COLOR, bG as ZEBRA_STRIPE_FRAGMENT_SHADER, bH as ZEBRA_STRIPE_VERTEX_SHADER, bI as SCAN_PROXY_LAYER_STYLES, bJ as SURFACE_FIELD_FRAGMENT_SHADER, bK as SURFACE_FIELD_VERTEX_SHADER, bL as WORLD_UP, bM as CatmullRomCurve3, bN as TubeGeometry, bO as THICKNESS_GRADIENT_COLORS, bP as ROUGHNESS_COLORS, bQ as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bR as PERFORMANCE_SAMPLE_INTERVAL_SEC, bS as formatPerformanceCount, bT as NON_TEXT_INPUT_TYPES, bU as MeshStandardMaterial, bV as compileSdfNode3, bW as buildSdfRaymarchFragmentShader, bX as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bY as Shape, bZ as ShapeGeometry, b_ as ShaderLib, b$ as CylinderGeometry, c0 as VIEWPORT_CAMERA_STORAGE_KEY, c1 as parseViewportCameraState, c2 as createResolvedExplodeConfig, c3 as explodeBoundsCenter, c4 as explodeMergeBounds, c5 as resolveExplodeDirective, c6 as computeExplodeMotion, c7 as getSketchWorldMatrix, c8 as explodeAdd, c9 as hasExplodeOverride, ca as resolveExplodeLocalFanDirection, cb as explodeMul, cc as explodeLeafFanStage, cd as normalizeCutPlane, ce as toClippingPlane, cf as isObjectExcludedFromCutPlane, cg as getShapePorts, ch as getShapeUsedPorts, ci as DEFAULT_VIEW_CONFIG, cj as SECTION_EXPLORER_PLANE_NAME, ck as ZERO_OFFSET, cl as scanProxyGridForBounds, cm as IDENTITY_MATRIX$2, cn as OBJECT_CONTEXT_MENU_MARGIN, co as OBJECT_CONTEXT_MENU_WIDTH, cp as OBJECT_CONTEXT_MENU_HEIGHT, cq as getKernelFaceNameForTriangle, cr as triangleSoupFromMeshes, cs as compareTriangleSoups, ct as buildGeometryComparisonPointCloud, cu as aabbOverlaps, cv as aabbOverlapVolume, cw as DEFAULT_COLLISION_INSPECTION_OPTIONS, cx as resolveScalarSceneSampleBudget, cy as FOCUS_MODE_DIM_OPACITY, cz as comparisonCandidateContextOpacity, cA as Matrix3, cB as initKernel, cC as initSolverWasm } from "./scalar-sampling-budget-J5cuzxT1.js";
8
8
  function getCsrfToken() {
9
9
  const match = document.cookie.match(/(?:^|;\s*)fc-csrf-token=([^;]+)/);
10
10
  return match == null ? void 0 : match[1];
@@ -39,7 +39,15 @@ async function doFetch(url2, init) {
39
39
  credentials: "include",
40
40
  headers: { ...headers, ...init == null ? void 0 : init.headers }
41
41
  });
42
- const body = await res.json();
42
+ const text = await res.text();
43
+ let body = {};
44
+ if (text.trim().length > 0) {
45
+ try {
46
+ body = JSON.parse(text);
47
+ } catch {
48
+ body = { error: text };
49
+ }
50
+ }
43
51
  return { res, body };
44
52
  }
45
53
  async function authFetch(url2, init) {
@@ -14721,6 +14729,7 @@ const MOUSE_BUTTONS_DRAW = {
14721
14729
  MIDDLE: MOUSE.PAN,
14722
14730
  RIGHT: MOUSE.PAN
14723
14731
  };
14732
+ const MOUSE_BUTTONS_VOXEL = MOUSE_BUTTONS_3D;
14724
14733
  const dark = {
14725
14734
  bg: "#0d1117",
14726
14735
  bgPanel: "#161b22",
@@ -15825,7 +15834,7 @@ const CRASH_COOLDOWN_MS = 2e3;
15825
15834
  class EvalWorkerClient {
15826
15835
  constructor(workerFactory = () => new Worker(new URL(
15827
15836
  /* @vite-ignore */
15828
- "/assets/evalWorker-CU0Ke6DP.js",
15837
+ "/assets/evalWorker-vkx310U2.js",
15829
15838
  import.meta.url
15830
15839
  ), { type: "module" })) {
15831
15840
  __publicField(this, "worker", null);
@@ -17208,6 +17217,13 @@ const DEFAULT_INSPECT_POINT_SAMPLE_COUNT = 2e3;
17208
17217
  const DEFAULT_COMPARISON_INSPECT_MODE = "difference-only";
17209
17218
  const DEFAULT_COMPARISON_CANDIDATE_OPACITY = 0.42;
17210
17219
  const DEFAULT_COMPARISON_REFERENCE_OPACITY = 0.2;
17220
+ const THICKNESS_COLOR_RANGE_MIN = 0;
17221
+ const THICKNESS_COLOR_RANGE_MAX = 1e3;
17222
+ const THICKNESS_COLOR_RANGE_MIN_SPAN = 1e-3;
17223
+ const DEFAULT_THICKNESS_COLOR_RANGE = {
17224
+ min: 0,
17225
+ max: 6
17226
+ };
17211
17227
  const DEFAULT_MANUAL_SCENE_SETTINGS = {
17212
17228
  enabled: false,
17213
17229
  backgroundColor: "#f6f7f8",
@@ -17235,6 +17251,31 @@ const resolveClampedNumber = (value, fallback, min, max2) => {
17235
17251
  return Math.max(min, Math.min(max2, numeric));
17236
17252
  };
17237
17253
  const resolveComparisonOpacity = (value, fallback) => resolveClampedNumber(value, fallback, 0, 1);
17254
+ const resolveThicknessColorRange = (value) => {
17255
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
17256
+ return DEFAULT_THICKNESS_COLOR_RANGE;
17257
+ }
17258
+ const raw = value;
17259
+ const min = resolveClampedNumber(
17260
+ raw.min,
17261
+ DEFAULT_THICKNESS_COLOR_RANGE.min,
17262
+ THICKNESS_COLOR_RANGE_MIN,
17263
+ THICKNESS_COLOR_RANGE_MAX
17264
+ );
17265
+ const max2 = resolveClampedNumber(
17266
+ raw.max,
17267
+ DEFAULT_THICKNESS_COLOR_RANGE.max,
17268
+ THICKNESS_COLOR_RANGE_MIN,
17269
+ THICKNESS_COLOR_RANGE_MAX
17270
+ );
17271
+ if (max2 - min >= THICKNESS_COLOR_RANGE_MIN_SPAN) return { min, max: max2 };
17272
+ const defaultSpan = DEFAULT_THICKNESS_COLOR_RANGE.max - DEFAULT_THICKNESS_COLOR_RANGE.min;
17273
+ const expandedMax = Math.min(THICKNESS_COLOR_RANGE_MAX, min + defaultSpan);
17274
+ if (expandedMax - min >= THICKNESS_COLOR_RANGE_MIN_SPAN) return { min, max: expandedMax };
17275
+ const expandedMin = Math.max(THICKNESS_COLOR_RANGE_MIN, max2 - defaultSpan);
17276
+ if (max2 - expandedMin >= THICKNESS_COLOR_RANGE_MIN_SPAN) return { min: expandedMin, max: max2 };
17277
+ return DEFAULT_THICKNESS_COLOR_RANGE;
17278
+ };
17238
17279
  const resolveManualSceneSettings = (value) => {
17239
17280
  if (!value || typeof value !== "object" || Array.isArray(value)) {
17240
17281
  return DEFAULT_MANUAL_SCENE_SETTINGS;
@@ -17411,8 +17452,17 @@ function resolveInitialViewInspectChannel(value) {
17411
17452
  function resolveInspectDisplayMode(value) {
17412
17453
  return typeof value === "string" && INSPECT_DISPLAY_MODES.has(value) ? value : "heatmap";
17413
17454
  }
17455
+ function isScalarInspectChannel(channel) {
17456
+ return channel === "thickness" || channel === "roughness";
17457
+ }
17458
+ function shouldUseScanRenderStyle(channel, displayMode) {
17459
+ return isScalarInspectChannel(channel) && displayMode === "scan";
17460
+ }
17414
17461
  const initialViewPreferences = readViewPreferences();
17415
- const initialRenderStyle = resolveForgeRenderStyle(initialViewPreferences.renderStyle);
17462
+ const initialInspectChannel = resolveInitialViewInspectChannel(initialViewPreferences.inspectChannel);
17463
+ const initialInspectDisplayMode = resolveInspectDisplayMode(initialViewPreferences.inspectDisplayMode);
17464
+ const initialStoredRenderStyle = resolveForgeRenderStyle(initialViewPreferences.renderStyle);
17465
+ const initialRenderStyle = shouldUseScanRenderStyle(initialInspectChannel, initialInspectDisplayMode) ? "scan" : initialStoredRenderStyle;
17416
17466
  const resolveFiniteNumber = (value, fallback) => {
17417
17467
  const numeric = typeof value === "number" ? value : Number(value);
17418
17468
  return Number.isFinite(numeric) ? numeric : fallback;
@@ -18254,6 +18304,19 @@ const useForgeStore = create((set, get) => ({
18254
18304
  });
18255
18305
  if (isAssemblyDrivenResult(state2.lastValidResult)) void get().updateAssemblyPose();
18256
18306
  },
18307
+ resetJointValues: () => {
18308
+ const state2 = get();
18309
+ const hasPoseState = Object.keys(state2.jointValues).length > 0 || state2.jointAnimationClip !== null || state2.jointAnimationProgress !== 0 || state2.jointAnimationPlaying;
18310
+ if (!hasPoseState) return;
18311
+ const assemblyDriven = isAssemblyDrivenResult(state2.lastValidResult);
18312
+ set({
18313
+ jointValues: {},
18314
+ jointAnimationClip: null,
18315
+ jointAnimationProgress: 0,
18316
+ jointAnimationPlaying: false
18317
+ });
18318
+ if (assemblyDriven) void get().updateAssemblyPose();
18319
+ },
18257
18320
  setJointAnimationClip: (name) => {
18258
18321
  const assemblyDriven = isAssemblyDrivenResult(get().lastValidResult);
18259
18322
  set((state2) => {
@@ -18347,17 +18410,31 @@ const useForgeStore = create((set, get) => ({
18347
18410
  writeViewPreferences({ manualScene: DEFAULT_MANUAL_SCENE_SETTINGS });
18348
18411
  set({ manualScene: DEFAULT_MANUAL_SCENE_SETTINGS });
18349
18412
  },
18350
- inspectChannel: resolveInitialViewInspectChannel(initialViewPreferences.inspectChannel),
18413
+ inspectChannel: initialInspectChannel,
18351
18414
  setInspectChannel: (channel) => {
18352
18415
  const next = resolveViewInspectChannel(channel);
18353
- writeViewPreferences({ inspectChannel: next === "collisions" ? "none" : next });
18354
- set({ inspectChannel: next });
18416
+ set((state2) => {
18417
+ const renderStyle = shouldUseScanRenderStyle(next, state2.inspectDisplayMode) ? "scan" : state2.renderStyle;
18418
+ const scanGranularity = resolveViewportScanGranularity(state2.scanGranularity, renderStyle);
18419
+ writeViewPreferences({
18420
+ inspectChannel: next === "collisions" ? "none" : next,
18421
+ ...renderStyle !== state2.renderStyle ? { renderStyle, scanGranularity } : {}
18422
+ });
18423
+ return { inspectChannel: next, renderStyle, scanGranularity };
18424
+ });
18355
18425
  },
18356
- inspectDisplayMode: resolveInspectDisplayMode(initialViewPreferences.inspectDisplayMode),
18426
+ inspectDisplayMode: initialInspectDisplayMode,
18357
18427
  setInspectDisplayMode: (mode) => {
18358
18428
  const next = resolveInspectDisplayMode(mode);
18359
- writeViewPreferences({ inspectDisplayMode: next });
18360
- set({ inspectDisplayMode: next });
18429
+ set((state2) => {
18430
+ const renderStyle = shouldUseScanRenderStyle(state2.inspectChannel, next) ? "scan" : state2.renderStyle;
18431
+ const scanGranularity = resolveViewportScanGranularity(state2.scanGranularity, renderStyle);
18432
+ writeViewPreferences({
18433
+ inspectDisplayMode: next,
18434
+ ...renderStyle !== state2.renderStyle ? { renderStyle, scanGranularity } : {}
18435
+ });
18436
+ return { inspectDisplayMode: next, renderStyle, scanGranularity };
18437
+ });
18361
18438
  },
18362
18439
  scanGranularity: resolveViewportScanGranularity(
18363
18440
  initialViewPreferences.scanGranularity ?? SCAN_PROXY_GRANULARITY_DEFAULT,
@@ -18376,6 +18453,14 @@ const useForgeStore = create((set, get) => ({
18376
18453
  writeViewPreferences({ inspectPointSampleCount: next });
18377
18454
  set({ inspectPointSampleCount: next });
18378
18455
  },
18456
+ thicknessColorRange: resolveThicknessColorRange(initialViewPreferences.thicknessColorRange),
18457
+ setThicknessColorRange: (range) => {
18458
+ set((state2) => {
18459
+ const next = resolveThicknessColorRange({ ...state2.thicknessColorRange, ...range });
18460
+ writeViewPreferences({ thicknessColorRange: next });
18461
+ return { thicknessColorRange: next };
18462
+ });
18463
+ },
18379
18464
  comparisonInspectMode: resolveComparisonInspectMode(initialViewPreferences.comparisonInspectMode),
18380
18465
  setComparisonInspectMode: (mode) => {
18381
18466
  const next = resolveComparisonInspectMode(mode);
@@ -18763,7 +18848,7 @@ const useForgeStore = create((set, get) => ({
18763
18848
  if (!next) {
18764
18849
  return { measureMode: false, measureSelections: [], measurements: [] };
18765
18850
  }
18766
- return { measureMode: true };
18851
+ return { measureMode: true, voxelIntentMode: false };
18767
18852
  });
18768
18853
  },
18769
18854
  measureSelections: [],
@@ -18797,6 +18882,73 @@ const useForgeStore = create((set, get) => ({
18797
18882
  writeViewPreferences({ measureSnapPx: value });
18798
18883
  set({ measureSnapPx: value });
18799
18884
  },
18885
+ voxelIntentMode: false,
18886
+ toggleVoxelIntentMode: () => set((s) => {
18887
+ const next = !s.voxelIntentMode;
18888
+ if (!next) return { voxelIntentMode: false };
18889
+ return {
18890
+ voxelIntentMode: true,
18891
+ measureMode: false,
18892
+ measureSelections: [],
18893
+ measurements: [],
18894
+ constructionGhost: null
18895
+ };
18896
+ }),
18897
+ setVoxelIntentMode: (enabled) => set(
18898
+ () => enabled ? {
18899
+ voxelIntentMode: true,
18900
+ measureMode: false,
18901
+ measureSelections: [],
18902
+ measurements: [],
18903
+ constructionGhost: null
18904
+ } : { voxelIntentMode: false }
18905
+ ),
18906
+ voxelIntentTool: "red",
18907
+ setVoxelIntentTool: (tool) => set({ voxelIntentTool: tool }),
18908
+ voxelIntentPlacement: "surface",
18909
+ setVoxelIntentPlacement: (placement) => set({ voxelIntentPlacement: placement }),
18910
+ voxelIntentCellSize: 10,
18911
+ setVoxelIntentCellSize: (cellSize) => set((s) => {
18912
+ if (Object.keys(s.voxelIntentBlocks).length > 0) return {};
18913
+ const nextCellSize = Math.max(1, Math.min(200, Number.isFinite(cellSize) ? Math.round(cellSize) : 10));
18914
+ return { voxelIntentCellSize: nextCellSize };
18915
+ }),
18916
+ voxelIntentCellSizePreviewVisible: false,
18917
+ setVoxelIntentCellSizePreviewVisible: (visible) => set({ voxelIntentCellSizePreviewVisible: visible }),
18918
+ voxelIntentGhostOpacity: 0.24,
18919
+ setVoxelIntentGhostOpacity: (opacity) => set({ voxelIntentGhostOpacity: Math.max(0.05, Math.min(0.75, opacity)) }),
18920
+ voxelIntentContextFile: null,
18921
+ setVoxelIntentContextFile: (filePath) => set((s) => {
18922
+ if (s.voxelIntentContextFile === filePath) return {};
18923
+ return {
18924
+ voxelIntentContextFile: filePath,
18925
+ voxelIntentBlocks: filePath ? s.voxelIntentBlocksByFile[filePath] ?? {} : {}
18926
+ };
18927
+ }),
18928
+ voxelIntentBlocksByFile: {},
18929
+ voxelIntentBlocks: {},
18930
+ setVoxelIntentBlock: (cell, block) => set((s) => {
18931
+ const key = `${cell.x},${cell.y},${cell.z}`;
18932
+ const voxelIntentBlocks = { ...s.voxelIntentBlocks, [key]: block };
18933
+ return {
18934
+ voxelIntentBlocks,
18935
+ voxelIntentBlocksByFile: s.voxelIntentContextFile ? { ...s.voxelIntentBlocksByFile, [s.voxelIntentContextFile]: voxelIntentBlocks } : s.voxelIntentBlocksByFile
18936
+ };
18937
+ }),
18938
+ eraseVoxelIntentBlock: (cell) => set((s) => {
18939
+ const key = `${cell.x},${cell.y},${cell.z}`;
18940
+ if (!(key in s.voxelIntentBlocks)) return {};
18941
+ const next = { ...s.voxelIntentBlocks };
18942
+ delete next[key];
18943
+ return {
18944
+ voxelIntentBlocks: next,
18945
+ voxelIntentBlocksByFile: s.voxelIntentContextFile ? { ...s.voxelIntentBlocksByFile, [s.voxelIntentContextFile]: next } : s.voxelIntentBlocksByFile
18946
+ };
18947
+ }),
18948
+ clearVoxelIntentBlocks: () => set((s) => ({
18949
+ voxelIntentBlocks: {},
18950
+ voxelIntentBlocksByFile: s.voxelIntentContextFile ? { ...s.voxelIntentBlocksByFile, [s.voxelIntentContextFile]: {} } : s.voxelIntentBlocksByFile
18951
+ })),
18800
18952
  dimensionsVisible: initialViewPreferences.dimensionsVisible ?? true,
18801
18953
  toggleDimensions: () => set((s) => {
18802
18954
  const nextDimensionsVisible = !s.dimensionsVisible;
@@ -26998,12 +27150,15 @@ const COMPARISON_REFERENCE_COLOR = [32, 199, 255];
26998
27150
  const COMPARISON_CONTEXT_COLOR = [174, 184, 194];
26999
27151
  const SCAN_CONTEXT_COLOR = [19, 201, 255];
27000
27152
  const SCAN_CONTEXT_EDGE_COLOR = [145, 231, 255];
27001
- const SCAN_RISK_MID_COLOR = [255, 193, 77];
27002
- const SCAN_RISK_HOT_COLOR = [255, 138, 31];
27003
27153
  const FLOATING_HIGHLIGHT_COLOR = [255, 68, 16];
27004
27154
  const FLOATING_CONTEXT_COLOR = [38, 49, 58];
27005
27155
  const RIG_SHADOW_COLOR = [6, 10, 18];
27006
- const RIG_LINK_COLOR = [20, 220, 255];
27156
+ const RIG_LINK_COLOR = [142, 106, 255];
27157
+ const RIG_KINEMATIC_LINK_COLOR = [255, 226, 106];
27158
+ const RIG_KINEMATIC_EDGE_COLOR = [20, 220, 255];
27159
+ const RIG_FIXED_LINK_COLOR = [240, 253, 255];
27160
+ const RIG_DERIVED_LINK_COLOR = [255, 76, 196];
27161
+ const RIG_CONTROL_LINK_COLOR = [255, 122, 26];
27007
27162
  const RIG_PART_LINK_COLOR = [174, 255, 92];
27008
27163
  const RIG_HIDDEN_LINK_COLOR = [116, 136, 148];
27009
27164
  const COLLISION_PALETTE = [
@@ -27280,7 +27435,7 @@ function ConstructionGhostOverlay({ matrix }) {
27280
27435
  class ConstructionHistoryWorkerClient {
27281
27436
  constructor(workerFactory = () => new Worker(new URL(
27282
27437
  /* @vite-ignore */
27283
- "/assets/constructionHistoryWorker-gpDo-uH2.js",
27438
+ "/assets/constructionHistoryWorker-Ba2Hm58b.js",
27284
27439
  import.meta.url
27285
27440
  ), { type: "module" })) {
27286
27441
  __publicField(this, "worker", null);
@@ -29664,7 +29819,7 @@ function generateReportInWorker(options) {
29664
29819
  return new Promise((resolve2, reject) => {
29665
29820
  const worker = new Worker(new URL(
29666
29821
  /* @vite-ignore */
29667
- "/assets/reportWorker-CdBz5bNg.js",
29822
+ "/assets/reportWorker-0AGij1Ru.js",
29668
29823
  import.meta.url
29669
29824
  ), { type: "module" });
29670
29825
  const cleanup = () => {
@@ -33521,7 +33676,7 @@ function SectionCapPreview({
33521
33676
  class ScanProxyWorkerClient {
33522
33677
  constructor(workerFactory = () => new Worker(new URL(
33523
33678
  /* @vite-ignore */
33524
- "/assets/scanProxyWorker-B-9VbLIs.js",
33679
+ "/assets/scanProxyWorker-Vl4Wxa1y.js",
33525
33680
  import.meta.url
33526
33681
  ), { type: "module" })) {
33527
33682
  __publicField(this, "worker", null);
@@ -33897,35 +34052,6 @@ function MatrixGlyphVolume({
33897
34052
  )
33898
34053
  ] });
33899
34054
  }
33900
- const SCAN_COLD_RGB = normalizedRgb(SCAN_CONTEXT_EDGE_COLOR);
33901
- const SCAN_MID_RGB = normalizedRgb(SCAN_RISK_MID_COLOR);
33902
- const SCAN_HOT_RGB = normalizedRgb(SCAN_RISK_HOT_COLOR);
33903
- function normalizedRgb([r2, g2, b2]) {
33904
- return [r2 / 255, g2 / 255, b2 / 255];
33905
- }
33906
- function clamp01$1(value) {
33907
- return Math.max(0, Math.min(1, value));
33908
- }
33909
- function mix(a2, b2, t2) {
33910
- return a2 + (b2 - a2) * clamp01$1(t2);
33911
- }
33912
- function scanRiskFromColor(r2, g2, b2) {
33913
- return clamp01$1(r2 * 1.08 + g2 * 0.48 - b2 * 0.62 - 0.38);
33914
- }
33915
- function scanColorBuffer(source) {
33916
- const out = new Float32Array(source.length);
33917
- for (let index = 0; index < source.length; index += 3) {
33918
- const risk = scanRiskFromColor(source[index] ?? 0, source[index + 1] ?? 0, source[index + 2] ?? 0);
33919
- const lowBand = risk < 0.55;
33920
- const t2 = lowBand ? risk / 0.55 : (risk - 0.55) / 0.45;
33921
- const from = lowBand ? SCAN_COLD_RGB : SCAN_MID_RGB;
33922
- const to = lowBand ? SCAN_MID_RGB : SCAN_HOT_RGB;
33923
- out[index] = mix(from[0], to[0], t2);
33924
- out[index + 1] = mix(from[1], to[1], t2);
33925
- out[index + 2] = mix(from[2], to[2], t2);
33926
- }
33927
- return out;
33928
- }
33929
34055
  function SurfaceFieldMaterial({
33930
34056
  field,
33931
34057
  color: color2,
@@ -33985,17 +34111,104 @@ function ZebraInspectionMaterial({ clippingPlanes }) {
33985
34111
  }
33986
34112
  const EMPTY_CLIPPING_PLANES$1 = [];
33987
34113
  const EMPTY_SECTION_PLANES$1 = [];
34114
+ function scanCellKey(ix, iy, iz) {
34115
+ return `${ix}:${iy}:${iz}`;
34116
+ }
34117
+ function buildDirectScanCellColors(pointCloud, grid, matrix) {
34118
+ const accumulators = /* @__PURE__ */ new Map();
34119
+ const point = new Vector3();
34120
+ const [originX, originY, originZ] = grid.origin;
34121
+ const sampleCount = Math.floor(pointCloud.positions.length / 3);
34122
+ for (let sample = 0; sample < sampleCount; sample += 1) {
34123
+ const offset = sample * 3;
34124
+ point.set(pointCloud.positions[offset], pointCloud.positions[offset + 1], pointCloud.positions[offset + 2]).applyMatrix4(matrix);
34125
+ const ix = Math.floor((point.x - originX) / grid.cellSize);
34126
+ const iy = Math.floor((point.y - originY) / grid.cellSize);
34127
+ const iz = Math.floor((point.z - originZ) / grid.cellSize);
34128
+ const key = scanCellKey(ix, iy, iz);
34129
+ const accumulator = accumulators.get(key) ?? { ix, iy, iz, r: 0, g: 0, b: 0, count: 0 };
34130
+ accumulator.r += pointCloud.colors[offset] ?? 0.35;
34131
+ accumulator.g += pointCloud.colors[offset + 1] ?? 0.35;
34132
+ accumulator.b += pointCloud.colors[offset + 2] ?? 0.35;
34133
+ accumulator.count += 1;
34134
+ accumulators.set(key, accumulator);
34135
+ }
34136
+ const directColors = /* @__PURE__ */ new Map();
34137
+ for (const [key, accumulator] of accumulators) {
34138
+ directColors.set(key, {
34139
+ ix: accumulator.ix,
34140
+ iy: accumulator.iy,
34141
+ iz: accumulator.iz,
34142
+ color: [accumulator.r / accumulator.count, accumulator.g / accumulator.count, accumulator.b / accumulator.count]
34143
+ });
34144
+ }
34145
+ return directColors;
34146
+ }
34147
+ function nearestScanCellColor(ix, iy, iz, coloredCells) {
34148
+ let best = coloredCells[0];
34149
+ let bestDistance = Number.POSITIVE_INFINITY;
34150
+ for (const candidate of coloredCells) {
34151
+ const distance = (candidate.ix - ix) ** 2 + (candidate.iy - iy) ** 2 + (candidate.iz - iz) ** 2;
34152
+ if (distance >= bestDistance) continue;
34153
+ best = candidate;
34154
+ bestDistance = distance;
34155
+ }
34156
+ return (best == null ? void 0 : best.color) ?? [0.35, 0.35, 0.35];
34157
+ }
34158
+ function createScanAnalysisColorGeometry(geometry, pointCloud, grid, matrix) {
34159
+ var _a3;
34160
+ if (!geometry) return null;
34161
+ const position = geometry.getAttribute("position");
34162
+ const scanCell = geometry.getAttribute("scanCell");
34163
+ if (!position || !scanCell || position.count !== scanCell.count) return null;
34164
+ const directColors = buildDirectScanCellColors(pointCloud, grid, matrix);
34165
+ const coloredCells = [...directColors.values()];
34166
+ if (coloredCells.length === 0) return null;
34167
+ const resolvedColors = /* @__PURE__ */ new Map();
34168
+ const colors = new Float32Array(position.count * 3);
34169
+ for (let vertex = 0; vertex < position.count; vertex += 1) {
34170
+ const ix = Math.round(scanCell.getX(vertex));
34171
+ const iy = Math.round(scanCell.getY(vertex));
34172
+ const iz = Math.round(scanCell.getZ(vertex));
34173
+ const key = scanCellKey(ix, iy, iz);
34174
+ let color2 = resolvedColors.get(key);
34175
+ if (!color2) {
34176
+ color2 = ((_a3 = directColors.get(key)) == null ? void 0 : _a3.color) ?? nearestScanCellColor(ix, iy, iz, coloredCells);
34177
+ resolvedColors.set(key, color2);
34178
+ }
34179
+ const offset = vertex * 3;
34180
+ colors[offset] = color2[0];
34181
+ colors[offset + 1] = color2[1];
34182
+ colors[offset + 2] = color2[2];
34183
+ }
34184
+ const coloredGeometry = geometry.clone();
34185
+ coloredGeometry.setAttribute("color", new BufferAttribute(colors, 3));
34186
+ return coloredGeometry;
34187
+ }
33988
34188
  function ScanProxyLayer({
33989
34189
  geometry,
33990
34190
  color: color2,
33991
34191
  fillOpacity,
33992
34192
  wireOpacity,
33993
34193
  clippingPlanes,
33994
- additiveWire
34194
+ additiveWire,
34195
+ analysisGeometry
33995
34196
  }) {
33996
- if (!geometry) return null;
34197
+ const fillGeometry = analysisGeometry ?? geometry;
34198
+ if (!geometry || !fillGeometry) return null;
33997
34199
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
33998
- /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34200
+ /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: fillGeometry, raycast: () => null, children: analysisGeometry ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34201
+ "meshBasicMaterial",
34202
+ {
34203
+ vertexColors: true,
34204
+ transparent: true,
34205
+ opacity: fillOpacity,
34206
+ side: DoubleSide,
34207
+ depthWrite: false,
34208
+ toneMapped: false,
34209
+ clippingPlanes
34210
+ }
34211
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
33999
34212
  "meshBasicMaterial",
34000
34213
  {
34001
34214
  color: color2,
@@ -34029,20 +34242,29 @@ function objectColorScanLayerStyles(color2) {
34029
34242
  function ScanProxyVolume({
34030
34243
  proxy,
34031
34244
  clippingPlanes,
34032
- objectColor
34245
+ objectColor,
34246
+ analysisGeometries
34033
34247
  }) {
34034
34248
  const layers = objectColor ? objectColorScanLayerStyles(objectColor) : SCAN_PROXY_LAYER_STYLES;
34035
34249
  const additiveWire = objectColor === void 0;
34036
- return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { userData: { scanProxyTelemetry: proxy.telemetry }, children: layers.map((layer) => /* @__PURE__ */ jsxRuntimeExports.jsx(
34037
- ScanProxyLayer,
34038
- {
34039
- geometry: proxy.geometries[layer.material],
34040
- clippingPlanes,
34041
- additiveWire,
34042
- ...layer
34043
- },
34044
- layer.material
34045
- )) });
34250
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { userData: { scanProxyTelemetry: proxy.telemetry }, children: layers.map((layer) => {
34251
+ const analysisGeometry = (analysisGeometries == null ? void 0 : analysisGeometries[layer.material]) ?? null;
34252
+ const fillOpacity = analysisGeometry ? Math.max(layer.fillOpacity, 0.78) : layer.fillOpacity;
34253
+ const wireOpacity = analysisGeometry ? Math.max(layer.wireOpacity, 0.34) : layer.wireOpacity;
34254
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
34255
+ ScanProxyLayer,
34256
+ {
34257
+ geometry: proxy.geometries[layer.material],
34258
+ analysisGeometry,
34259
+ clippingPlanes,
34260
+ additiveWire,
34261
+ ...layer,
34262
+ fillOpacity,
34263
+ wireOpacity
34264
+ },
34265
+ layer.material
34266
+ );
34267
+ }) });
34046
34268
  }
34047
34269
  function ForgeObject({
34048
34270
  obj,
@@ -34100,11 +34322,10 @@ function ForgeObject({
34100
34322
  const inspectPointGeo = reactExports.useMemo(() => {
34101
34323
  if (!inspectPointCloud) return null;
34102
34324
  const geometry = new BufferGeometry();
34103
- const colors = isScalarScan ? scanColorBuffer(inspectPointCloud.colors) : inspectPointCloud.colors;
34104
34325
  geometry.setAttribute("position", new BufferAttribute(inspectPointCloud.positions, 3));
34105
- geometry.setAttribute("color", new BufferAttribute(colors, 3));
34326
+ geometry.setAttribute("color", new BufferAttribute(inspectPointCloud.colors, 3));
34106
34327
  return geometry;
34107
- }, [inspectPointCloud, isScalarScan]);
34328
+ }, [inspectPointCloud]);
34108
34329
  const inspectMeshColorGeo = reactExports.useMemo(() => {
34109
34330
  if (!solidGeo || !inspectMeshColors) return null;
34110
34331
  const position = solidGeo.getAttribute("position");
@@ -34123,6 +34344,11 @@ function ForgeObject({
34123
34344
  );
34124
34345
  const scanProxyState = useScanProxyWorkerGeometry(solidGeo, wantsScanProxy, scanProxyGrid, matrix);
34125
34346
  const scanProxy = scanProxyState.proxy;
34347
+ const scanAnalysisGeometries = reactExports.useMemo(() => {
34348
+ if (!isScalarScan || !scanProxy || !inspectPointCloud) return null;
34349
+ const shell = createScanAnalysisColorGeometry(scanProxy.geometries.shell, inspectPointCloud, scanProxy.grid, matrix);
34350
+ return shell ? { shell } : null;
34351
+ }, [inspectPointCloud, isScalarScan, matrix, scanProxy]);
34126
34352
  reactExports.useEffect(() => {
34127
34353
  return () => {
34128
34354
  solidGeo == null ? void 0 : solidGeo.dispose();
@@ -34144,16 +34370,13 @@ function ForgeObject({
34144
34370
  inspectHeatmapField == null ? void 0 : inspectHeatmapField.texture.dispose();
34145
34371
  };
34146
34372
  }, [inspectHeatmapField]);
34373
+ reactExports.useEffect(() => {
34374
+ return () => {
34375
+ Object.values(scanAnalysisGeometries ?? {}).forEach((geometry) => geometry == null ? void 0 : geometry.dispose());
34376
+ };
34377
+ }, [scanAnalysisGeometries]);
34147
34378
  if (!solidGeo || !settings.visible) return null;
34148
34379
  const effectiveRenderMode = isInteracting && renderMode === "overlay" ? "solid" : renderMode;
34149
- const isInspecting = inspectChannel !== "none";
34150
- const showInspectHeatmap = Boolean(
34151
- isScalarInspect && !isScalarScan && inspectHeatmapField && (inspectDisplayMode === "heatmap" || inspectDisplayMode === "both")
34152
- );
34153
- const showScalarContextMesh = Boolean(isScalarInspect && (isScalarScan || !showInspectHeatmap));
34154
- const showInspectPoints = Boolean(
34155
- isScalarInspect && inspectPointGeo && (inspectDisplayMode === "points" || inspectDisplayMode === "both" || isScalarScan)
34156
- );
34157
34380
  const renderStylePreset = getRenderStylePreset(renderStyle);
34158
34381
  const materialDefaults = renderStylePreset.material;
34159
34382
  const surfaceField = renderStylePreset.surfaceField;
@@ -34166,6 +34389,7 @@ function ForgeObject({
34166
34389
  const materialOpacity = Math.min(meshOpacity, styleOpacity);
34167
34390
  const transmission = authoredMaterialTransmission ?? transparentDefaults.transmission;
34168
34391
  const isTransparent = materialOpacity < 1 || transmission > 0;
34392
+ const isInspecting = inspectChannel !== "none";
34169
34393
  const showSolid = isInspecting || effectiveRenderMode !== "wireframe";
34170
34394
  const showEdges = !isInspecting && effectiveRenderMode === "overlay" && renderStyle !== "scan" && renderStyle !== "matrix";
34171
34395
  const showWire = !isInspecting && effectiveRenderMode === "wireframe";
@@ -34177,7 +34401,16 @@ function ForgeObject({
34177
34401
  const showFloatingObject = inspectChannel !== "floating" || hasInspectMeshColors || inspectColor !== "#000000";
34178
34402
  const showScanRenderStyle = showSolid && inspectChannel === "none" && renderStyle === "scan";
34179
34403
  const showMatrixRenderStyle = showSolid && inspectChannel === "none" && renderStyle === "matrix";
34180
- const showScalarScanProxy = showSolid && isScalarScan && scanProxy !== null;
34404
+ const showScalarScanProxy = showSolid && isScalarScan && scanProxy !== null && (scanAnalysisGeometries == null ? void 0 : scanAnalysisGeometries.shell) != null;
34405
+ const showInspectHeatmap = Boolean(
34406
+ isScalarInspect && inspectHeatmapField && (!isScalarScan && (inspectDisplayMode === "heatmap" || inspectDisplayMode === "both") || isScalarScan && !showScalarScanProxy)
34407
+ );
34408
+ const showScalarContextMesh = Boolean(
34409
+ isScalarInspect && (!isScalarScan && !showInspectHeatmap || isScalarScan && !showScalarScanProxy && !showInspectHeatmap)
34410
+ );
34411
+ const showInspectPoints = Boolean(
34412
+ isScalarInspect && inspectPointGeo && (inspectDisplayMode === "points" || inspectDisplayMode === "both")
34413
+ );
34181
34414
  const showScanFallbackMesh = showScanRenderStyle && scanProxy === null;
34182
34415
  const showMatrixFallbackMesh = showMatrixRenderStyle && scanProxy === null;
34183
34416
  const showHiddenPickMesh = showScanRenderStyle || showMatrixRenderStyle || showScalarScanProxy;
@@ -34256,7 +34489,7 @@ function ForgeObject({
34256
34489
  clippingPlanes: effectiveClippingPlanes
34257
34490
  }
34258
34491
  ) }),
34259
- showScalarScanProxy && /* @__PURE__ */ jsxRuntimeExports.jsx(ScanProxyVolume, { proxy: scanProxy, clippingPlanes: effectiveClippingPlanes }),
34492
+ showScalarScanProxy && scanProxy && scanAnalysisGeometries && /* @__PURE__ */ jsxRuntimeExports.jsx(ScanProxyVolume, { proxy: scanProxy, clippingPlanes: effectiveClippingPlanes, analysisGeometries: scanAnalysisGeometries }),
34260
34493
  showSolid && showScalarContextMesh && !showScalarScanProxy && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34261
34494
  "meshBasicMaterial",
34262
34495
  {
@@ -34295,7 +34528,8 @@ function ForgeObject({
34295
34528
  side: DoubleSide,
34296
34529
  toneMapped: false,
34297
34530
  clippingPlanes: effectiveClippingPlanes
34298
- }
34531
+ },
34532
+ inspectHeatmapField.texture.uuid
34299
34533
  ) }),
34300
34534
  showInspectPoints && inspectPointGeo && /* @__PURE__ */ jsxRuntimeExports.jsx("points", { geometry: inspectPointGeo, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34301
34535
  "pointsMaterial",
@@ -34782,9 +35016,12 @@ function colorToHex(color2) {
34782
35016
  return rgbToHex(color2);
34783
35017
  }
34784
35018
  function fmt(value) {
34785
- return Number.isInteger(value) ? String(value) : value.toFixed(1);
35019
+ if (!Number.isFinite(value)) return "0";
35020
+ if (Number.isInteger(value)) return String(value);
35021
+ const fixed = Math.abs(value) < 10 ? value.toFixed(2) : value.toFixed(1);
35022
+ return fixed.replace(/\.?0+$/, "");
34786
35023
  }
34787
- function inspectionLegendDefinitionFor(channel, displayMode) {
35024
+ function inspectionLegendDefinitionFor(channel, displayMode, legendOptions = {}) {
34788
35025
  switch (channel) {
34789
35026
  case "mask":
34790
35027
  return {
@@ -34810,8 +35047,11 @@ function inspectionLegendDefinitionFor(channel, displayMode) {
34810
35047
  case "rig":
34811
35048
  return {
34812
35049
  title: "Rig Skeleton",
34813
- summary: "Geometry is ghosted while joint axes, rotation arcs, and kinematic links stay bright.",
35050
+ summary: "Geometry is ghosted while joint axes, link pins, and rigid edges stay bright.",
34814
35051
  swatches: [
35052
+ { label: "Link pin", detail: "kinematic node", color: rgbToHex(RIG_KINEMATIC_LINK_COLOR) },
35053
+ { label: "Fixed link", detail: "anchored node", color: rgbToHex(RIG_FIXED_LINK_COLOR) },
35054
+ { label: "Rigid edge", detail: "distance member", color: rgbToHex(RIG_KINEMATIC_EDGE_COLOR) },
34815
35055
  { label: "Joint link", detail: "parent-child chain", color: rgbToHex(RIG_LINK_COLOR) },
34816
35056
  { label: "Part link", detail: "joint to moving body", color: rgbToHex(RIG_PART_LINK_COLOR) },
34817
35057
  { label: "Hidden joint", detail: "fixed follower", color: rgbToHex(RIG_HIDDEN_LINK_COLOR) },
@@ -34869,33 +35109,25 @@ function inspectionLegendDefinitionFor(channel, displayMode) {
34869
35109
  ]
34870
35110
  };
34871
35111
  case "thickness": {
34872
- const options = DEFAULT_THICKNESS_INSPECTION_OPTIONS;
35112
+ const colorRange = legendOptions.thicknessColorRange ?? DEFAULT_THICKNESS_COLOR_RANGE;
35113
+ const midpoint = colorRange.min + (colorRange.max - colorRange.min) / 2;
35114
+ const ramp = {
35115
+ colors: THICKNESS_GRADIENT_COLORS.map(colorToHex),
35116
+ leftLabel: `${fmt(colorRange.min)} mm`,
35117
+ centerLabel: `${fmt(midpoint)} mm`,
35118
+ rightLabel: `${fmt(colorRange.max)} mm`
35119
+ };
34873
35120
  if (displayMode === "scan") {
34874
35121
  return {
34875
35122
  title: "Thickness Scan",
34876
- summary: "Cyan is the context shell. Warm samples mark thin or risky material from the same thickness analysis.",
34877
- swatches: [
34878
- { label: "Shell", detail: "context", color: colorToHex(SCAN_CONTEXT_COLOR) },
34879
- { label: "Trace", detail: "surface edge", color: colorToHex(SCAN_CONTEXT_EDGE_COLOR) },
34880
- { label: "Warn", detail: `<= ${fmt(options.warnThickness)} mm`, color: colorToHex(SCAN_RISK_MID_COLOR) },
34881
- { label: "Critical", detail: `<= ${fmt(options.minThickness)} mm`, color: colorToHex(SCAN_RISK_HOT_COLOR) }
34882
- ]
35123
+ summary: "Each scan box uses the same continuous wall-thickness color ramp as the heatmap.",
35124
+ ramp
34883
35125
  };
34884
35126
  }
34885
35127
  return {
34886
35128
  title: "Wall Thickness",
34887
- summary: "Red/orange is thin material. Green/blue has more printable or machinable thickness.",
34888
- ramp: {
34889
- colors: [
34890
- colorToHex(THICKNESS_COLORS.critical),
34891
- colorToHex(THICKNESS_COLORS.warning),
34892
- colorToHex(THICKNESS_COLORS.ok),
34893
- colorToHex(THICKNESS_COLORS.thick)
34894
- ],
34895
- leftLabel: `<= ${fmt(options.minThickness)} mm`,
34896
- centerLabel: `${fmt(options.warnThickness)} mm warn`,
34897
- rightLabel: `>= ${fmt(options.maxThickness)} mm`
34898
- }
35129
+ summary: "Color maps continuously from thinner to thicker material across the selected range.",
35130
+ ramp
34899
35131
  };
34900
35132
  }
34901
35133
  case "roughness": {
@@ -34903,12 +35135,20 @@ function inspectionLegendDefinitionFor(channel, displayMode) {
34903
35135
  if (displayMode === "scan") {
34904
35136
  return {
34905
35137
  title: "Roughness Scan",
34906
- summary: "Cyan is the context shell. Warm samples mark sharper or harsher mesh transitions from the same roughness analysis.",
35138
+ summary: "Each scan box is colored from the same roughness analysis field as the heatmap.",
34907
35139
  swatches: [
34908
- { label: "Shell", detail: "context", color: colorToHex(SCAN_CONTEXT_COLOR) },
34909
- { label: "Trace", detail: "surface edge", color: colorToHex(SCAN_CONTEXT_EDGE_COLOR) },
34910
- { label: "Sharp", detail: `>= ${fmt(options.sharpAngleDeg)} deg`, color: colorToHex(SCAN_RISK_MID_COLOR) },
34911
- { label: "Harsh", detail: `>= ${fmt(options.harshAngleDeg)} deg`, color: colorToHex(SCAN_RISK_HOT_COLOR) }
35140
+ { label: "Smooth", detail: `< ${fmt(options.smoothAngleDeg)} deg`, color: colorToHex(ROUGHNESS_COLORS.smooth) },
35141
+ {
35142
+ label: "Moderate",
35143
+ detail: `${fmt(options.smoothAngleDeg)}-${fmt(options.sharpAngleDeg)} deg`,
35144
+ color: colorToHex(ROUGHNESS_COLORS.moderate)
35145
+ },
35146
+ {
35147
+ label: "Sharp",
35148
+ detail: `${fmt(options.sharpAngleDeg)}-${fmt(options.harshAngleDeg)} deg`,
35149
+ color: colorToHex(ROUGHNESS_COLORS.sharp)
35150
+ },
35151
+ { label: "Harsh", detail: `>= ${fmt(options.harshAngleDeg)} deg`, color: colorToHex(ROUGHNESS_COLORS.harsh) }
34912
35152
  ]
34913
35153
  };
34914
35154
  }
@@ -34968,6 +35208,78 @@ const swatchGridStyle = {
34968
35208
  gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
34969
35209
  gap: 6
34970
35210
  };
35211
+ const rangeControlsStyle = {
35212
+ display: "grid",
35213
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
35214
+ gap: 7,
35215
+ marginTop: 7
35216
+ };
35217
+ const rangeLabelStyle = {
35218
+ display: "grid",
35219
+ gap: 3,
35220
+ minWidth: 0,
35221
+ color: "var(--fc-textDim)",
35222
+ fontSize: 10
35223
+ };
35224
+ const rangeInputStyle = {
35225
+ width: "100%",
35226
+ minWidth: 0,
35227
+ boxSizing: "border-box",
35228
+ height: 24,
35229
+ borderRadius: 6,
35230
+ border: "1px solid var(--fc-border)",
35231
+ background: "var(--fc-bg)",
35232
+ color: "var(--fc-text)",
35233
+ font: "inherit",
35234
+ fontSize: 11,
35235
+ padding: "2px 6px",
35236
+ outline: "none"
35237
+ };
35238
+ const dualSliderStyle = {
35239
+ position: "relative",
35240
+ height: 28,
35241
+ marginTop: 9,
35242
+ touchAction: "none"
35243
+ };
35244
+ const sliderTrackStyle = {
35245
+ position: "absolute",
35246
+ left: 0,
35247
+ right: 0,
35248
+ top: 12,
35249
+ height: 5,
35250
+ borderRadius: 999,
35251
+ background: "color-mix(in srgb, var(--fc-border) 76%, transparent)"
35252
+ };
35253
+ const sliderActiveStyle = {
35254
+ position: "absolute",
35255
+ top: 12,
35256
+ height: 5,
35257
+ borderRadius: 999,
35258
+ background: "linear-gradient(90deg, #ff1c1c, #ffde00, #3cdc5a, #4691ff)"
35259
+ };
35260
+ const sliderThumbStyle = {
35261
+ position: "absolute",
35262
+ top: 5,
35263
+ width: 18,
35264
+ height: 18,
35265
+ borderRadius: "50%",
35266
+ border: "2px solid var(--fc-bgPanel)",
35267
+ background: "var(--fc-text)",
35268
+ boxShadow: "0 1px 5px rgba(0, 0, 0, 0.3)",
35269
+ padding: 0,
35270
+ cursor: "grab",
35271
+ touchAction: "none"
35272
+ };
35273
+ const sliderScaleStyle = {
35274
+ display: "grid",
35275
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
35276
+ gap: 6,
35277
+ marginTop: 1,
35278
+ color: "var(--fc-textDim)",
35279
+ fontSize: 9
35280
+ };
35281
+ const SLIDER_STEP = 0.01;
35282
+ const MIN_RANGE_SPAN = 1e-3;
34971
35283
  function Ramp({ compact, ramp }) {
34972
35284
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { minWidth: 0 }, children: [
34973
35285
  /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -35024,15 +35336,272 @@ function Swatch({ item }) {
35024
35336
  ] })
35025
35337
  ] });
35026
35338
  }
35339
+ function parseRangeInput(value, fallback) {
35340
+ const parsed = Number(value);
35341
+ return Number.isFinite(parsed) ? parsed : fallback;
35342
+ }
35343
+ function rangeDraftValue(value) {
35344
+ return Number.isFinite(value) ? String(value) : "";
35345
+ }
35346
+ function rangeKey(range) {
35347
+ return `${range.min}:${range.max}`;
35348
+ }
35349
+ function clamp(value, min, max2) {
35350
+ return Math.max(min, Math.min(max2, value));
35351
+ }
35352
+ function snapSliderValue(value) {
35353
+ return Number((Math.round(value / SLIDER_STEP) * SLIDER_STEP).toFixed(2));
35354
+ }
35355
+ function formatSliderTick(value) {
35356
+ if (!Number.isFinite(value)) return "0";
35357
+ return Number.isInteger(value) ? String(value) : value.toFixed(1).replace(/\.0$/, "");
35358
+ }
35359
+ function sliderMaxFor(range) {
35360
+ const target = Math.max(6, range.max * 1.25, range.min + 1);
35361
+ if (target <= 10) return Math.ceil(target);
35362
+ if (target <= 50) return Math.ceil(target / 5) * 5;
35363
+ if (target <= 100) return Math.ceil(target / 10) * 10;
35364
+ return Math.ceil(target / 50) * 50;
35365
+ }
35366
+ function DualRangeSlider({
35367
+ range,
35368
+ sliderMax,
35369
+ onDraftChange,
35370
+ onCommit
35371
+ }) {
35372
+ const trackRef = reactExports.useRef(null);
35373
+ const minPercent = clamp(range.min, 0, sliderMax) / sliderMax * 100;
35374
+ const maxPercent = clamp(range.max, 0, sliderMax) / sliderMax * 100;
35375
+ const valueFromPointer = (event) => {
35376
+ var _a3;
35377
+ const rect = (_a3 = trackRef.current) == null ? void 0 : _a3.getBoundingClientRect();
35378
+ if (!rect || rect.width <= 0) return null;
35379
+ const ratio = clamp((event.clientX - rect.left) / rect.width, 0, 1);
35380
+ return snapSliderValue(ratio * sliderMax);
35381
+ };
35382
+ const updateThumb = (thumb, value) => {
35383
+ if (thumb === "min") {
35384
+ onDraftChange({ min: clamp(value, 0, range.max - MIN_RANGE_SPAN), max: range.max });
35385
+ } else {
35386
+ onDraftChange({ min: range.min, max: Math.max(range.min + MIN_RANGE_SPAN, clamp(value, MIN_RANGE_SPAN, sliderMax)) });
35387
+ }
35388
+ };
35389
+ const handlePointerMove = (thumb, event) => {
35390
+ if ((event.buttons & 1) !== 1) return;
35391
+ const value = valueFromPointer(event);
35392
+ if (value == null) return;
35393
+ updateThumb(thumb, value);
35394
+ };
35395
+ const handlePointerDown = (thumb, event) => {
35396
+ event.currentTarget.setPointerCapture(event.pointerId);
35397
+ const value = valueFromPointer(event);
35398
+ if (value != null) updateThumb(thumb, value);
35399
+ };
35400
+ const handleKeyDown = (thumb, event) => {
35401
+ let next = null;
35402
+ if (event.key === "ArrowLeft" || event.key === "ArrowDown") next = range[thumb] - SLIDER_STEP;
35403
+ if (event.key === "ArrowRight" || event.key === "ArrowUp") next = range[thumb] + SLIDER_STEP;
35404
+ if (event.key === "Home") next = thumb === "min" ? 0 : range.min + MIN_RANGE_SPAN;
35405
+ if (event.key === "End") next = thumb === "min" ? range.max - MIN_RANGE_SPAN : sliderMax;
35406
+ if (event.key === "Enter") {
35407
+ event.preventDefault();
35408
+ event.currentTarget.blur();
35409
+ return;
35410
+ }
35411
+ if (next == null) return;
35412
+ event.preventDefault();
35413
+ updateThumb(thumb, snapSliderValue(next));
35414
+ };
35415
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
35416
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: trackRef, style: dualSliderStyle, children: [
35417
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: sliderTrackStyle }),
35418
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35419
+ "div",
35420
+ {
35421
+ style: {
35422
+ ...sliderActiveStyle,
35423
+ left: `${minPercent}%`,
35424
+ width: `${Math.max(0, maxPercent - minPercent)}%`
35425
+ }
35426
+ }
35427
+ ),
35428
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35429
+ "button",
35430
+ {
35431
+ type: "button",
35432
+ role: "slider",
35433
+ "aria-label": "Minimum thickness color",
35434
+ "aria-valuemin": 0,
35435
+ "aria-valuemax": range.max,
35436
+ "aria-valuenow": range.min,
35437
+ onPointerDown: (event) => handlePointerDown("min", event),
35438
+ onPointerMove: (event) => handlePointerMove("min", event),
35439
+ onPointerUp: onCommit,
35440
+ onLostPointerCapture: onCommit,
35441
+ onBlur: onCommit,
35442
+ onKeyDown: (event) => handleKeyDown("min", event),
35443
+ style: {
35444
+ ...sliderThumbStyle,
35445
+ left: `${minPercent}%`,
35446
+ transform: "translateX(-50%)",
35447
+ zIndex: range.min >= range.max - SLIDER_STEP ? 3 : 2
35448
+ }
35449
+ }
35450
+ ),
35451
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35452
+ "button",
35453
+ {
35454
+ type: "button",
35455
+ role: "slider",
35456
+ "aria-label": "Maximum thickness color",
35457
+ "aria-valuemin": range.min,
35458
+ "aria-valuemax": sliderMax,
35459
+ "aria-valuenow": range.max,
35460
+ onPointerDown: (event) => handlePointerDown("max", event),
35461
+ onPointerMove: (event) => handlePointerMove("max", event),
35462
+ onPointerUp: onCommit,
35463
+ onLostPointerCapture: onCommit,
35464
+ onBlur: onCommit,
35465
+ onKeyDown: (event) => handleKeyDown("max", event),
35466
+ style: {
35467
+ ...sliderThumbStyle,
35468
+ left: `${maxPercent}%`,
35469
+ transform: "translateX(-50%)",
35470
+ zIndex: 2,
35471
+ background: "var(--fc-accent, #4f8cff)"
35472
+ }
35473
+ }
35474
+ )
35475
+ ] }),
35476
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: sliderScaleStyle, children: [
35477
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "0" }),
35478
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { textAlign: "center" }, children: formatSliderTick(sliderMax / 2) }),
35479
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { textAlign: "right" }, children: formatSliderTick(sliderMax) })
35480
+ ] })
35481
+ ] });
35482
+ }
35483
+ function ThicknessRangeControls({
35484
+ compact,
35485
+ range,
35486
+ onChange
35487
+ }) {
35488
+ const [minDraft, setMinDraft] = reactExports.useState(() => rangeDraftValue(range.min));
35489
+ const [maxDraft, setMaxDraft] = reactExports.useState(() => rangeDraftValue(range.max));
35490
+ const [sliderDraft, setSliderDraft] = reactExports.useState(range);
35491
+ const [sliderMax, setSliderMax] = reactExports.useState(() => sliderMaxFor(range));
35492
+ const sliderDraftRef = reactExports.useRef(range);
35493
+ const committedSliderKeyRef = reactExports.useRef(rangeKey(range));
35494
+ const span = Math.max(1, range.max - range.min);
35495
+ reactExports.useEffect(() => {
35496
+ setMinDraft(rangeDraftValue(range.min));
35497
+ setMaxDraft(rangeDraftValue(range.max));
35498
+ setSliderDraft(range);
35499
+ sliderDraftRef.current = range;
35500
+ committedSliderKeyRef.current = rangeKey(range);
35501
+ }, [range.max, range.min]);
35502
+ const updateSliderDraft = (next) => {
35503
+ sliderDraftRef.current = next;
35504
+ setSliderDraft(next);
35505
+ setMinDraft(rangeDraftValue(next.min));
35506
+ setMaxDraft(rangeDraftValue(next.max));
35507
+ };
35508
+ const commitSliderDraft = () => {
35509
+ const next = sliderDraftRef.current;
35510
+ const nextKey = rangeKey(next);
35511
+ if (nextKey === committedSliderKeyRef.current) return;
35512
+ committedSliderKeyRef.current = nextKey;
35513
+ onChange(next);
35514
+ };
35515
+ const commitMin = () => {
35516
+ const value = minDraft.trim();
35517
+ if (value === "") {
35518
+ setMinDraft(rangeDraftValue(range.min));
35519
+ return;
35520
+ }
35521
+ const min = Math.max(0, parseRangeInput(value, range.min));
35522
+ const max2 = range.max <= min ? min + span : range.max;
35523
+ const next = { min, max: max2 };
35524
+ setSliderMax(sliderMaxFor(next));
35525
+ onChange(next);
35526
+ };
35527
+ const commitMax = () => {
35528
+ const value = maxDraft.trim();
35529
+ if (value === "") {
35530
+ setMaxDraft(rangeDraftValue(range.max));
35531
+ return;
35532
+ }
35533
+ const max2 = Math.max(1e-3, parseRangeInput(value, range.max));
35534
+ const min = range.min >= max2 ? Math.max(0, max2 - span) : range.min;
35535
+ const next = { min, max: max2 };
35536
+ setSliderMax(sliderMaxFor(next));
35537
+ onChange(next);
35538
+ };
35539
+ const handleKeyDown = (event, reset) => {
35540
+ if (event.key === "Enter") {
35541
+ event.preventDefault();
35542
+ event.currentTarget.blur();
35543
+ } else if (event.key === "Escape") {
35544
+ event.preventDefault();
35545
+ reset();
35546
+ event.currentTarget.blur();
35547
+ }
35548
+ };
35549
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs(
35550
+ "div",
35551
+ {
35552
+ style: {
35553
+ ...rangeControlsStyle,
35554
+ gridTemplateColumns: compact ? "minmax(0, 1fr)" : rangeControlsStyle.gridTemplateColumns
35555
+ },
35556
+ children: [
35557
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { gridColumn: "1 / -1" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(DualRangeSlider, { range: sliderDraft, sliderMax, onDraftChange: updateSliderDraft, onCommit: commitSliderDraft }) }),
35558
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { style: rangeLabelStyle, children: [
35559
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Min" }),
35560
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35561
+ "input",
35562
+ {
35563
+ type: "number",
35564
+ min: 0,
35565
+ step: 0.1,
35566
+ value: minDraft,
35567
+ onChange: (event) => setMinDraft(event.currentTarget.value),
35568
+ onBlur: commitMin,
35569
+ onKeyDown: (event) => handleKeyDown(event, () => setMinDraft(rangeDraftValue(range.min))),
35570
+ style: rangeInputStyle
35571
+ }
35572
+ )
35573
+ ] }),
35574
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("label", { style: rangeLabelStyle, children: [
35575
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: "Max" }),
35576
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
35577
+ "input",
35578
+ {
35579
+ type: "number",
35580
+ min: 1e-3,
35581
+ step: 0.1,
35582
+ value: maxDraft,
35583
+ onChange: (event) => setMaxDraft(event.currentTarget.value),
35584
+ onBlur: commitMax,
35585
+ onKeyDown: (event) => handleKeyDown(event, () => setMaxDraft(rangeDraftValue(range.max))),
35586
+ style: rangeInputStyle
35587
+ }
35588
+ )
35589
+ ] })
35590
+ ]
35591
+ }
35592
+ );
35593
+ }
35027
35594
  function InspectionLegend({
35028
35595
  channel,
35029
35596
  displayMode,
35030
35597
  warnings,
35031
- details
35598
+ details,
35599
+ thicknessColorRange,
35600
+ onThicknessColorRangeChange
35032
35601
  }) {
35033
35602
  const panelRef = reactExports.useRef(null);
35034
35603
  const [compact, setCompact] = reactExports.useState(false);
35035
- const definition = inspectionLegendDefinitionFor(channel, displayMode);
35604
+ const definition = inspectionLegendDefinitionFor(channel, displayMode, { thicknessColorRange });
35036
35605
  reactExports.useEffect(() => {
35037
35606
  var _a3;
35038
35607
  const parent = (_a3 = panelRef.current) == null ? void 0 : _a3.parentElement;
@@ -35045,7 +35614,12 @@ function InspectionLegend({
35045
35614
  }, [channel]);
35046
35615
  if (!definition) return null;
35047
35616
  const warning = warnings[0];
35048
- const effectivePanelStyle = compact ? { ...panelStyle, padding: "7px 8px" } : panelStyle;
35617
+ const showThicknessControls = channel === "thickness" && thicknessColorRange !== void 0 && onThicknessColorRangeChange !== void 0;
35618
+ const effectivePanelStyle = {
35619
+ ...panelStyle,
35620
+ ...compact ? { padding: "7px 8px" } : {},
35621
+ ...showThicknessControls ? { pointerEvents: "auto" } : {}
35622
+ };
35049
35623
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: panelRef, className: "fc-viewport-floating-panel fc-inspection-legend", style: effectivePanelStyle, children: [
35050
35624
  /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: titleStyle, children: [
35051
35625
  /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: definition.title }),
@@ -35053,6 +35627,7 @@ function InspectionLegend({
35053
35627
  ] }),
35054
35628
  !compact && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: summaryStyle, children: definition.summary }),
35055
35629
  definition.ramp && /* @__PURE__ */ jsxRuntimeExports.jsx(Ramp, { compact, ramp: definition.ramp }),
35630
+ showThicknessControls && /* @__PURE__ */ jsxRuntimeExports.jsx(ThicknessRangeControls, { compact, range: thicknessColorRange, onChange: onThicknessColorRangeChange }),
35056
35631
  details && /* @__PURE__ */ jsxRuntimeExports.jsx(
35057
35632
  "div",
35058
35633
  {
@@ -36672,6 +37247,12 @@ function computeSceneObjectBounds(obj, objectMatrices) {
36672
37247
  }
36673
37248
  return isFiniteBox3(out) ? out : null;
36674
37249
  }
37250
+ if (obj.curve3d) {
37251
+ const b2 = obj.curve3d.bounds;
37252
+ const out = new Box3();
37253
+ if (!expandBoundsByTransformedAabb(out, b2.min, b2.max, matrix)) return null;
37254
+ return isFiniteBox3(out) ? out : null;
37255
+ }
36675
37256
  if (obj.toolpath) {
36676
37257
  const b2 = obj.toolpath.bounds;
36677
37258
  const out = new Box3();
@@ -40322,8 +40903,8 @@ const computeExplodeTreeOffsets = (root, explodeAmount, explodeConfig) => {
40322
40903
  };
40323
40904
  const EMPTY_RIG_INSPECTION_OVERLAY_STATE = {
40324
40905
  joints: [],
40325
- links: [],
40326
- points: [],
40906
+ edges: [],
40907
+ kinematicLinks: [],
40327
40908
  bounds: null
40328
40909
  };
40329
40910
  function isFiniteVector(value) {
@@ -40388,18 +40969,20 @@ function buildRigInspectionOverlayState(args) {
40388
40969
  joints.forEach((joint) => {
40389
40970
  byChild.set(joint.joint.child, joint);
40390
40971
  });
40391
- const links = [];
40392
- const points = [];
40972
+ const edges = [];
40973
+ const kinematicLinks = [];
40393
40974
  if (args.assemblyKinematics) {
40394
- const positions = new Map(args.assemblyKinematics.links.map((link) => [link.name, new Vector3(...link.position)]));
40975
+ const derivedNames = new Set(args.assemblyKinematics.derivedLinks.map((link) => link.name));
40976
+ const controlledNames = new Set(args.assemblyKinematics.angles.filter((angle) => angle.control).map((angle) => angle.c));
40395
40977
  const connectedLinkNames = /* @__PURE__ */ new Set();
40978
+ const positions = new Map(args.assemblyKinematics.links.map((link) => [link.name, new Vector3(...link.position)]));
40396
40979
  for (const edge of args.assemblyKinematics.edges) {
40397
40980
  const start = positions.get(edge.a);
40398
40981
  const end = positions.get(edge.b);
40399
40982
  if (!start || !end || start.distanceToSquared(end) <= 1e-8) continue;
40400
40983
  connectedLinkNames.add(edge.a);
40401
40984
  connectedLinkNames.add(edge.b);
40402
- links.push({
40985
+ edges.push({
40403
40986
  id: `kinematic:${edge.name}`,
40404
40987
  kind: "kinematic",
40405
40988
  start,
@@ -40407,22 +40990,36 @@ function buildRigInspectionOverlayState(args) {
40407
40990
  hidden: false
40408
40991
  });
40409
40992
  }
40993
+ for (const edge of args.assemblyKinematics.frameEdges ?? []) {
40994
+ const start = new Vector3(...edge.start);
40995
+ const end = new Vector3(...edge.end);
40996
+ if (!isFiniteVector(start) || !isFiniteVector(end) || start.distanceToSquared(end) <= 1e-8) continue;
40997
+ edges.push({
40998
+ id: `frame-edge:${edge.name}`,
40999
+ kind: "kinematic",
41000
+ start,
41001
+ end,
41002
+ hidden: false
41003
+ });
41004
+ }
40410
41005
  for (const link of args.assemblyKinematics.links) {
40411
41006
  const position = positions.get(link.name);
40412
41007
  if (!position || !isFiniteVector(position)) continue;
40413
- points.push({
41008
+ kinematicLinks.push({
40414
41009
  id: `kinematic-link:${link.name}`,
40415
41010
  name: link.name,
41011
+ kind: derivedNames.has(link.name) ? "derived" : link.fixed ? "fixed" : link.gaugeFixed ? "gauge" : "moving",
40416
41012
  position,
40417
- fixed: link.fixed === true,
40418
- connected: connectedLinkNames.has(link.name)
41013
+ controlled: controlledNames.has(link.name),
41014
+ connected: connectedLinkNames.has(link.name),
41015
+ hidden: false
40419
41016
  });
40420
41017
  }
40421
41018
  }
40422
41019
  for (const joint of joints) {
40423
41020
  const parentJoint = joint.joint.parent ? byChild.get(joint.joint.parent) : null;
40424
41021
  if (parentJoint && parentJoint.pivotWorld.distanceToSquared(joint.pivotWorld) > 1e-8) {
40425
- links.push({
41022
+ edges.push({
40426
41023
  id: `${joint.joint.name}:hierarchy`,
40427
41024
  kind: "hierarchy",
40428
41025
  start: parentJoint.pivotWorld,
@@ -40432,7 +41029,7 @@ function buildRigInspectionOverlayState(args) {
40432
41029
  }
40433
41030
  const center = childCenter(args, joint.joint.child);
40434
41031
  if (center && center.distanceToSquared(joint.pivotWorld) > 1e-8) {
40435
- links.push({
41032
+ edges.push({
40436
41033
  id: `${joint.joint.name}:part`,
40437
41034
  kind: "part",
40438
41035
  start: joint.pivotWorld,
@@ -40443,12 +41040,12 @@ function buildRigInspectionOverlayState(args) {
40443
41040
  }
40444
41041
  const bounds = new Box3();
40445
41042
  let hasBounds = false;
40446
- for (const point of points) {
40447
- if (expandFinitePoint(bounds, point.position)) hasBounds = true;
41043
+ for (const link of kinematicLinks) {
41044
+ if (expandFinitePoint(bounds, link.position)) hasBounds = true;
40448
41045
  }
40449
- for (const link of links) {
40450
- if (expandFinitePoint(bounds, link.start)) hasBounds = true;
40451
- if (expandFinitePoint(bounds, link.end)) hasBounds = true;
41046
+ for (const edge of edges) {
41047
+ if (expandFinitePoint(bounds, edge.start)) hasBounds = true;
41048
+ if (expandFinitePoint(bounds, edge.end)) hasBounds = true;
40452
41049
  }
40453
41050
  for (const joint of joints) {
40454
41051
  if (expandFinitePoint(bounds, joint.pivotWorld)) hasBounds = true;
@@ -40459,7 +41056,7 @@ function buildRigInspectionOverlayState(args) {
40459
41056
  if (hasBounds) {
40460
41057
  bounds.expandByScalar(Math.max(2, args.axisLength * 0.12));
40461
41058
  }
40462
- return { joints, links, points, bounds: hasBounds ? bounds : null };
41059
+ return { joints, edges, kinematicLinks, bounds: hasBounds ? bounds : null };
40463
41060
  }
40464
41061
  const EMPTY_PLANE_RECORD = Object.freeze({});
40465
41062
  const EMPTY_CLIPPING_RECORD = Object.freeze({});
@@ -40641,6 +41238,8 @@ function useViewportState() {
40641
41238
  const inspectDisplayMode = useForgeStore((s) => s.inspectDisplayMode);
40642
41239
  const scanGranularity = useForgeStore((s) => s.scanGranularity);
40643
41240
  const inspectPointSampleCount = useForgeStore((s) => s.inspectPointSampleCount);
41241
+ const thicknessColorRange = useForgeStore((s) => s.thicknessColorRange);
41242
+ const setThicknessColorRange = useForgeStore((s) => s.setThicknessColorRange);
40644
41243
  const comparisonInspectMode = useForgeStore((s) => s.comparisonInspectMode);
40645
41244
  const comparisonCandidateOpacity = useForgeStore((s) => s.comparisonCandidateOpacity);
40646
41245
  const comparisonReferenceOpacity = useForgeStore((s) => s.comparisonReferenceOpacity);
@@ -41032,6 +41631,8 @@ function useViewportState() {
41032
41631
  inspectChannel,
41033
41632
  inspectDisplayMode,
41034
41633
  inspectPointSampleCount,
41634
+ thicknessColorRange,
41635
+ setThicknessColorRange,
41035
41636
  comparisonInspectMode,
41036
41637
  comparisonCandidateOpacity,
41037
41638
  comparisonReferenceOpacity,
@@ -42088,12 +42689,12 @@ const HoverTooltipLayer = reactExports.forwardRef(function HoverTooltipLayer2({
42088
42689
  top: 0,
42089
42690
  display: "none",
42090
42691
  zIndex: 15,
42091
- background: "var(--fc-floating-panel-bg, #111111d9)",
42692
+ background: "var(--fc-floating-panel-bg, var(--fc-bgPanel, #111111d9))",
42092
42693
  color: "var(--fc-text, #f2f2f2)",
42093
42694
  padding: "3px 7px",
42094
42695
  borderRadius: "var(--fc-floating-panel-radius, 4px)",
42095
- border: "1px solid var(--fc-floating-panel-border, #2a2a2a)",
42096
- boxShadow: "var(--fc-floating-panel-shadow, none)",
42696
+ border: "1px solid var(--fc-floating-panel-border, var(--fc-border, #2a2a2a))",
42697
+ boxShadow: "var(--fc-floating-panel-shadow, 0 8px 20px rgba(0, 0, 0, 0.18))",
42097
42698
  fontFamily: "var(--fc-hud-font, inherit)",
42098
42699
  fontSize: 11,
42099
42700
  fontWeight: 600,
@@ -42424,10 +43025,12 @@ function RenderLabelsOverlay({ labels }) {
42424
43025
  if (labels.length === 0) return null;
42425
43026
  return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { renderOrder: 12, children: labels.map((label) => /* @__PURE__ */ jsxRuntimeExports.jsx(RenderLabelItem, { label }, label.id)) });
42426
43027
  }
42427
- function RigLink({ link, radius }) {
42428
- const segment = reactExports.useMemo(() => resolveSegmentMeshTransform(link.start, link.end), [link.end, link.start]);
43028
+ function RigEdge({ edge, radius }) {
43029
+ const segment = reactExports.useMemo(() => resolveSegmentMeshTransform(edge.start, edge.end), [edge.end, edge.start]);
42429
43030
  if (!segment) return null;
42430
- const color2 = rgbToHex(link.hidden ? RIG_HIDDEN_LINK_COLOR : link.kind === "part" ? RIG_PART_LINK_COLOR : RIG_LINK_COLOR);
43031
+ const color2 = rgbToHex(
43032
+ edge.hidden ? RIG_HIDDEN_LINK_COLOR : edge.kind === "part" ? RIG_PART_LINK_COLOR : edge.kind === "kinematic" ? RIG_KINEMATIC_EDGE_COLOR : RIG_LINK_COLOR
43033
+ );
42431
43034
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
42432
43035
  "mesh",
42433
43036
  {
@@ -42437,16 +43040,35 @@ function RigLink({ link, radius }) {
42437
43040
  userData: { measureHelper: true },
42438
43041
  children: [
42439
43042
  /* @__PURE__ */ jsxRuntimeExports.jsx("cylinderGeometry", { args: [radius, radius, segment.length, 16] }),
42440
- /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: color2, depthTest: false, transparent: true, opacity: link.hidden ? 0.72 : 0.96, toneMapped: false })
43043
+ /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: color2, depthTest: false, transparent: true, opacity: edge.hidden ? 0.72 : 0.96, toneMapped: false })
42441
43044
  ]
42442
43045
  }
42443
43046
  );
42444
43047
  }
42445
- function RigLinkPoint({ point, radius }) {
42446
- const color2 = rgbToHex(point.connected ? RIG_LINK_COLOR : RIG_PART_LINK_COLOR);
42447
- return /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { position: [point.position.x, point.position.y, point.position.z], renderOrder: 94, userData: { measureHelper: true }, children: [
42448
- /* @__PURE__ */ jsxRuntimeExports.jsx("sphereGeometry", { args: [radius, 20, 12] }),
42449
- /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: color2, depthTest: false, transparent: true, opacity: point.fixed ? 1 : 0.9, toneMapped: false })
43048
+ function colorForKinematicLink(link) {
43049
+ if (link.kind === "fixed" || link.kind === "gauge") return rgbToHex(RIG_FIXED_LINK_COLOR);
43050
+ if (link.kind === "derived") return rgbToHex(RIG_DERIVED_LINK_COLOR);
43051
+ return rgbToHex(link.connected ? RIG_KINEMATIC_LINK_COLOR : RIG_PART_LINK_COLOR);
43052
+ }
43053
+ function RigKinematicLinkPin({ link, radius }) {
43054
+ const color2 = colorForKinematicLink(link);
43055
+ const position = [link.position.x, link.position.y, link.position.z];
43056
+ const markerRadius = link.kind === "derived" || !link.connected ? radius * 0.72 : radius;
43057
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("group", { position, renderOrder: 94, userData: { measureHelper: true }, children: [
43058
+ link.kind === "fixed" || link.kind === "gauge" ? /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { renderOrder: 94, userData: { measureHelper: true }, children: [
43059
+ /* @__PURE__ */ jsxRuntimeExports.jsx("boxGeometry", { args: [radius * 1.75, radius * 1.75, radius * 1.05] }),
43060
+ /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: color2, depthTest: false, transparent: true, opacity: 0.98, toneMapped: false })
43061
+ ] }) : link.kind === "derived" || !link.connected ? /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { renderOrder: 94, userData: { measureHelper: true }, children: [
43062
+ /* @__PURE__ */ jsxRuntimeExports.jsx("torusGeometry", { args: [markerRadius * 0.82, markerRadius * 0.18, 8, 24] }),
43063
+ /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: color2, depthTest: false, transparent: true, opacity: 0.98, toneMapped: false })
43064
+ ] }) : /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { renderOrder: 94, userData: { measureHelper: true }, children: [
43065
+ /* @__PURE__ */ jsxRuntimeExports.jsx("sphereGeometry", { args: [markerRadius, 18, 18] }),
43066
+ /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: color2, depthTest: false, transparent: true, opacity: 0.98, toneMapped: false })
43067
+ ] }),
43068
+ link.controlled && /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { renderOrder: 95, userData: { measureHelper: true }, children: [
43069
+ /* @__PURE__ */ jsxRuntimeExports.jsx("torusGeometry", { args: [radius * 1.32, radius * 0.12, 8, 28] }),
43070
+ /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: rgbToHex(RIG_CONTROL_LINK_COLOR), depthTest: false, transparent: true, opacity: 0.98, toneMapped: false })
43071
+ ] })
42450
43072
  ] });
42451
43073
  }
42452
43074
  function RigInspectionOverlay({ state: state2, config }) {
@@ -42462,16 +43084,21 @@ function RigInspectionOverlay({ state: state2, config }) {
42462
43084
  [config, hiddenColor]
42463
43085
  );
42464
43086
  const maxAxisLength = reactExports.useMemo(() => state2.joints.reduce((max2, joint) => Math.max(max2, joint.axisLength), 0), [state2.joints]);
42465
- const linkRadius = MathUtils.clamp(
42466
- maxAxisLength * config.axisLineRadiusScale * 1.5,
42467
- config.axisLineRadiusMin * 1.5,
42468
- config.axisLineRadiusMax * 2.25
43087
+ const scaleBasis = Math.max(maxAxisLength, config.axisLengthMin);
43088
+ const edgeRadius = MathUtils.clamp(
43089
+ scaleBasis * config.axisLineRadiusScale * 1.15,
43090
+ config.axisLineRadiusMin * 1.15,
43091
+ config.axisLineRadiusMax * 1.75
42469
43092
  );
42470
- const pointRadius = Math.max(linkRadius * 2.15, config.axisLineRadiusMin * 2.4);
42471
- if (state2.joints.length === 0 && state2.links.length === 0 && state2.points.length === 0) return null;
43093
+ const linkPinRadius = MathUtils.clamp(
43094
+ scaleBasis * config.axisDotRadiusScale * 3.2,
43095
+ config.axisDotRadiusMin * 2.2,
43096
+ config.axisLineRadiusMax * 3.2
43097
+ );
43098
+ if (state2.joints.length === 0 && state2.edges.length === 0 && state2.kinematicLinks.length === 0) return null;
42472
43099
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("group", { children: [
42473
- state2.links.map((link) => /* @__PURE__ */ jsxRuntimeExports.jsx(RigLink, { link, radius: linkRadius }, link.id)),
42474
- state2.points.map((point) => /* @__PURE__ */ jsxRuntimeExports.jsx(RigLinkPoint, { point, radius: pointRadius }, point.id)),
43100
+ state2.edges.map((edge) => /* @__PURE__ */ jsxRuntimeExports.jsx(RigEdge, { edge, radius: edgeRadius }, edge.id)),
43101
+ state2.kinematicLinks.map((link) => /* @__PURE__ */ jsxRuntimeExports.jsx(RigKinematicLinkPin, { link, radius: linkPinRadius }, link.id)),
42475
43102
  state2.joints.map((joint) => /* @__PURE__ */ jsxRuntimeExports.jsx(HoveredJointOverlay, { state: joint, config: joint.hidden ? hiddenConfig : config }, joint.joint.name))
42476
43103
  ] });
42477
43104
  }
@@ -42647,12 +43274,12 @@ function useGeometryComparison(args) {
42647
43274
  class InspectWorkerClient {
42648
43275
  constructor(workerFactory = () => new Worker(new URL(
42649
43276
  /* @vite-ignore */
42650
- "/assets/inspectWorker-COyp8XXA.js",
43277
+ "/assets/inspectWorker-BuTJDVX6.js",
42651
43278
  import.meta.url
42652
43279
  ), { type: "module" })) {
42653
43280
  __publicField(this, "worker", null);
42654
43281
  __publicField(this, "reqId", 0);
42655
- __publicField(this, "pending", null);
43282
+ __publicField(this, "pending", /* @__PURE__ */ new Map());
42656
43283
  this.workerFactory = workerFactory;
42657
43284
  }
42658
43285
  getWorker() {
@@ -42660,9 +43287,9 @@ class InspectWorkerClient {
42660
43287
  this.worker = this.workerFactory();
42661
43288
  this.worker.onmessage = (event) => {
42662
43289
  const data = event.data;
42663
- const pending = this.pending;
43290
+ const pending = this.pending.get(data.payload.reqId);
42664
43291
  if (!pending) return;
42665
- this.pending = null;
43292
+ this.pending.delete(data.payload.reqId);
42666
43293
  if (data.type === "inspect-error") {
42667
43294
  pending.reject(new Error(data.payload.message));
42668
43295
  } else {
@@ -42670,22 +43297,28 @@ class InspectWorkerClient {
42670
43297
  }
42671
43298
  };
42672
43299
  this.worker.onerror = (event) => {
42673
- var _a3, _b2;
43300
+ var _a3;
42674
43301
  const error = new Error(event.message || "Inspect worker failed unexpectedly.");
42675
- (_a3 = this.pending) == null ? void 0 : _a3.reject(error);
42676
- this.pending = null;
42677
- (_b2 = this.worker) == null ? void 0 : _b2.terminate();
43302
+ this.pending.forEach((pending) => pending.reject(error));
43303
+ this.pending.clear();
43304
+ (_a3 = this.worker) == null ? void 0 : _a3.terminate();
42678
43305
  this.worker = null;
42679
43306
  };
42680
43307
  return this.worker;
42681
43308
  }
42682
43309
  replaceActiveWorker() {
42683
- var _a3, _b2;
42684
- (_a3 = this.pending) == null ? void 0 : _a3.reject(new Error("cancelled"));
42685
- this.pending = null;
42686
- (_b2 = this.worker) == null ? void 0 : _b2.terminate();
43310
+ var _a3;
43311
+ this.pending.forEach((pending) => pending.reject(new Error("cancelled")));
43312
+ this.pending.clear();
43313
+ (_a3 = this.worker) == null ? void 0 : _a3.terminate();
42687
43314
  this.worker = null;
42688
43315
  }
43316
+ postRequest(request, transfers = []) {
43317
+ return new Promise((resolve2, reject) => {
43318
+ this.pending.set(request.payload.reqId, { resolve: resolve2, reject });
43319
+ this.getWorker().postMessage(request, transfers);
43320
+ });
43321
+ }
42689
43322
  analyze(payload) {
42690
43323
  if (this.pending) this.replaceActiveWorker();
42691
43324
  const reqId = ++this.reqId;
@@ -42705,10 +43338,18 @@ class InspectWorkerClient {
42705
43338
  object.collisionShape.mergeToVert.buffer
42706
43339
  ] : []
42707
43340
  ]);
42708
- return new Promise((resolve2, reject) => {
42709
- this.pending = { resolve: resolve2, reject };
42710
- this.getWorker().postMessage(request, transfers);
42711
- });
43341
+ return this.postRequest(request, transfers);
43342
+ }
43343
+ colorizeThickness(payload) {
43344
+ const reqId = ++this.reqId;
43345
+ const request = {
43346
+ type: "colorize-thickness",
43347
+ payload: {
43348
+ reqId,
43349
+ ...payload
43350
+ }
43351
+ };
43352
+ return this.postRequest(request);
42712
43353
  }
42713
43354
  dispose() {
42714
43355
  this.replaceActiveWorker();
@@ -43039,6 +43680,7 @@ function analyzePayloadFor(channel, objects, inspectPointSampleCount, groundZ, w
43039
43680
  }
43040
43681
  function resultToState(channel, result) {
43041
43682
  return {
43683
+ analysisId: result.analysisId,
43042
43684
  status: "ready",
43043
43685
  channel,
43044
43686
  objectColors: result.objectColors,
@@ -43061,7 +43703,8 @@ function resultToState(channel, result) {
43061
43703
  {
43062
43704
  sampleCount: object.sampleCount,
43063
43705
  positions: object.positions,
43064
- colors: object.colors
43706
+ colors: object.colors,
43707
+ values: object.values
43065
43708
  }
43066
43709
  ])
43067
43710
  ),
@@ -43069,8 +43712,40 @@ function resultToState(channel, result) {
43069
43712
  error: null
43070
43713
  };
43071
43714
  }
43715
+ function applyThicknessColorizeResult(state2, result) {
43716
+ if (state2.channel !== "thickness" || state2.status !== "ready") return state2;
43717
+ if (state2.analysisId !== result.analysisId) return state2;
43718
+ const colorsByObjectId = new Map(result.pointObjects.map((object) => [object.objectId, object.colors]));
43719
+ const pointClouds = Object.fromEntries(
43720
+ Object.entries(state2.pointClouds).map(([objectId, pointCloud]) => [
43721
+ objectId,
43722
+ {
43723
+ ...pointCloud,
43724
+ colors: colorsByObjectId.get(objectId) ?? pointCloud.colors
43725
+ }
43726
+ ])
43727
+ );
43728
+ return {
43729
+ ...state2,
43730
+ pointClouds,
43731
+ heatmapFields: Object.fromEntries(
43732
+ result.heatmapFieldObjects.map((object) => [
43733
+ object.objectId,
43734
+ {
43735
+ data: object.data,
43736
+ boundsMin: object.boundsMin,
43737
+ boundsSize: object.boundsSize,
43738
+ gridSize: object.gridSize
43739
+ }
43740
+ ])
43741
+ )
43742
+ };
43743
+ }
43744
+ function thicknessColorRangeKey(range) {
43745
+ return `${range.min}:${range.max}`;
43746
+ }
43072
43747
  function useInspectWorkerAnalysis(args) {
43073
- const [state2, setState] = reactExports.useState({
43748
+ const [rawState, setRawState] = reactExports.useState({
43074
43749
  status: "idle",
43075
43750
  channel: "none",
43076
43751
  objectColors: {},
@@ -43081,10 +43756,11 @@ function useInspectWorkerAnalysis(args) {
43081
43756
  warnings: [],
43082
43757
  error: null
43083
43758
  });
43759
+ const [colorizedState, setColorizedState] = reactExports.useState(null);
43084
43760
  reactExports.useEffect(() => {
43085
43761
  if (!WORKER_CHANNELS.has(args.inspectChannel)) {
43086
43762
  inspectWorkerClient.dispose();
43087
- setState({
43763
+ setRawState({
43088
43764
  status: "idle",
43089
43765
  channel: args.inspectChannel,
43090
43766
  objectColors: {},
@@ -43099,7 +43775,7 @@ function useInspectWorkerAnalysis(args) {
43099
43775
  }
43100
43776
  let cancelled = false;
43101
43777
  const channel = args.inspectChannel;
43102
- setState({
43778
+ setRawState({
43103
43779
  status: "loading",
43104
43780
  channel: args.inspectChannel,
43105
43781
  objectColors: {},
@@ -43133,12 +43809,13 @@ function useInspectWorkerAnalysis(args) {
43133
43809
  )
43134
43810
  );
43135
43811
  if (cancelled) return;
43136
- setState(resultToState(args.inspectChannel, result));
43812
+ setRawState(resultToState(args.inspectChannel, result));
43813
+ setColorizedState(null);
43137
43814
  } catch (error) {
43138
43815
  if (cancelled || error instanceof InspectBuildCancelledError || error instanceof Error && error.message === "cancelled") {
43139
43816
  return;
43140
43817
  }
43141
- setState({
43818
+ setRawState({
43142
43819
  status: "error",
43143
43820
  channel: args.inspectChannel,
43144
43821
  objectColors: {},
@@ -43164,7 +43841,179 @@ function useInspectWorkerAnalysis(args) {
43164
43841
  args.objectMatrices,
43165
43842
  args.groundZ
43166
43843
  ]);
43167
- return state2;
43844
+ reactExports.useEffect(() => {
43845
+ if (rawState.status !== "ready" || rawState.channel !== "thickness" || rawState.analysisId === void 0) {
43846
+ setColorizedState(null);
43847
+ return;
43848
+ }
43849
+ let cancelled = false;
43850
+ const analysisId = rawState.analysisId;
43851
+ const rangeKey22 = thicknessColorRangeKey(args.thicknessColorRange);
43852
+ void inspectWorkerClient.colorizeThickness({
43853
+ analysisId,
43854
+ colorMinThickness: args.thicknessColorRange.min,
43855
+ colorMaxThickness: args.thicknessColorRange.max
43856
+ }).then((result) => {
43857
+ if (cancelled) return;
43858
+ setColorizedState({
43859
+ analysisId,
43860
+ rangeKey: rangeKey22,
43861
+ state: applyThicknessColorizeResult(rawState, result)
43862
+ });
43863
+ }).catch((error) => {
43864
+ if (cancelled || error instanceof Error && error.message === "cancelled") return;
43865
+ setColorizedState(null);
43866
+ });
43867
+ return () => {
43868
+ cancelled = true;
43869
+ };
43870
+ }, [args.thicknessColorRange, rawState]);
43871
+ const rangeKey2 = thicknessColorRangeKey(args.thicknessColorRange);
43872
+ if (rawState.status === "ready" && rawState.channel === "thickness" && rawState.analysisId !== void 0 && (colorizedState == null ? void 0 : colorizedState.analysisId) === rawState.analysisId && colorizedState.rangeKey === rangeKey2) {
43873
+ return colorizedState.state;
43874
+ }
43875
+ return rawState;
43876
+ }
43877
+ function voxelCellKey(cell) {
43878
+ return `${cell.x},${cell.y},${cell.z}`;
43879
+ }
43880
+ function resolveVoxelIntentCellSize(cellSize) {
43881
+ return Math.max(1, Number.isFinite(cellSize) ? cellSize : 10);
43882
+ }
43883
+ function voxelCellFromPoint(point, cellSize) {
43884
+ return {
43885
+ x: Math.floor(point.x / cellSize),
43886
+ y: Math.floor(point.y / cellSize),
43887
+ z: Math.floor(point.z / cellSize)
43888
+ };
43889
+ }
43890
+ function dominantAxis(normal) {
43891
+ const ax = Math.abs(normal.x);
43892
+ const ay = Math.abs(normal.y);
43893
+ const az = Math.abs(normal.z);
43894
+ if (ax >= ay && ax >= az) return "x";
43895
+ if (ay >= ax && ay >= az) return "y";
43896
+ return "z";
43897
+ }
43898
+ function anchoredCellIndex(value, cellSize, direction) {
43899
+ const scaled = value / cellSize;
43900
+ const base = Math.floor(scaled);
43901
+ const onBoundary = Math.abs(scaled - Math.round(scaled)) < 1e-7;
43902
+ return onBoundary && direction < 0 ? base - 1 : base;
43903
+ }
43904
+ function voxelCellFromSurfaceAnchor(point, normal, cellSize, side) {
43905
+ const cell = voxelCellFromPoint(point, cellSize);
43906
+ const axis = dominantAxis(normal);
43907
+ const normalValue = normal[axis];
43908
+ const outsideDirection = normalValue < 0 ? -1 : 1;
43909
+ const direction = side === "outside" ? outsideDirection : -outsideDirection;
43910
+ cell[axis] = anchoredCellIndex(point[axis], cellSize, direction);
43911
+ return cell;
43912
+ }
43913
+ function parseVoxelCellKey(key) {
43914
+ const [x, y, z] = key.split(",").map(Number);
43915
+ return { x, y, z };
43916
+ }
43917
+ function addVoxelCells(a2, b2) {
43918
+ return { x: a2.x + b2.x, y: a2.y + b2.y, z: a2.z + b2.z };
43919
+ }
43920
+ function voxelCellCenter(cell, cellSize) {
43921
+ return [(cell.x + 0.5) * cellSize, (cell.y + 0.5) * cellSize, (cell.z + 0.5) * cellSize];
43922
+ }
43923
+ const VOXEL_INTENT_TOOL_COLORS = {
43924
+ red: "#ef4444",
43925
+ green: "#22c55e",
43926
+ blue: "#3b82f6",
43927
+ yellow: "#facc15",
43928
+ white: "#f8fafc",
43929
+ erase: "#a855f7"
43930
+ };
43931
+ const VOXEL_INTENT_TOOL_LABELS = {
43932
+ red: { label: "Red", detail: "mark a red thing to explain in chat" },
43933
+ green: { label: "Green", detail: "mark a green thing to explain in chat" },
43934
+ blue: { label: "Blue", detail: "mark a blue thing to explain in chat" },
43935
+ yellow: { label: "Yellow", detail: "mark a yellow thing to explain in chat" },
43936
+ white: { label: "White", detail: "mark a neutral thing to explain in chat" },
43937
+ erase: { label: "Eraser", detail: "delete the first marker block clicked" }
43938
+ };
43939
+ const VOXEL_INTENT_TOOL_ORDER = ["red", "green", "blue", "yellow", "white", "erase"];
43940
+ const VOXEL_INTENT_PLACEMENT_LABELS = {
43941
+ surface: { label: "Surface", detail: "place outside the clicked face" },
43942
+ inside: { label: "Inside", detail: "place just under the first clicked surface" },
43943
+ through: { label: "Through", detail: "place between the first and second surface hits" },
43944
+ grid: { label: "Grid", detail: "place from the click point in the global grid" }
43945
+ };
43946
+ const VOXEL_INTENT_PLACEMENT_ORDER = ["surface", "inside", "through", "grid"];
43947
+ function directionFromFace(event) {
43948
+ var _a3;
43949
+ const normal = (_a3 = event.face) == null ? void 0 : _a3.normal;
43950
+ if (!normal) return { x: 0, y: 0, z: 1 };
43951
+ const values = [
43952
+ { axis: "x", value: normal.x },
43953
+ { axis: "y", value: normal.y },
43954
+ { axis: "z", value: normal.z }
43955
+ ];
43956
+ values.sort((a2, b2) => Math.abs(b2.value) - Math.abs(a2.value));
43957
+ const strongest = values[0];
43958
+ return {
43959
+ x: strongest.axis === "x" ? Math.sign(strongest.value) || 1 : 0,
43960
+ y: strongest.axis === "y" ? Math.sign(strongest.value) || 1 : 0,
43961
+ z: strongest.axis === "z" ? Math.sign(strongest.value) || 1 : 0
43962
+ };
43963
+ }
43964
+ function VoxelIntentCellMesh({
43965
+ cell,
43966
+ color: color2,
43967
+ cellSize,
43968
+ opacity,
43969
+ onClick
43970
+ }) {
43971
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { position: voxelCellCenter(cell, cellSize), onClick, renderOrder: 60, userData: { voxelIntentCell: cell }, children: [
43972
+ /* @__PURE__ */ jsxRuntimeExports.jsx("boxGeometry", { args: [cellSize * 0.96, cellSize * 0.96, cellSize * 0.96] }),
43973
+ /* @__PURE__ */ jsxRuntimeExports.jsx("meshStandardMaterial", { color: color2, roughness: 0.62, metalness: 0.04, transparent: true, opacity, depthWrite: opacity > 0.55 })
43974
+ ] });
43975
+ }
43976
+ function VoxelIntentOverlay() {
43977
+ const tool = useForgeStore((s) => s.voxelIntentTool);
43978
+ const placement = useForgeStore((s) => s.voxelIntentPlacement);
43979
+ const cellSize = useForgeStore((s) => s.voxelIntentCellSize);
43980
+ const blocks = useForgeStore((s) => s.voxelIntentBlocks);
43981
+ const cellSizePreviewVisible = useForgeStore((s) => s.voxelIntentCellSizePreviewVisible);
43982
+ const setBlock = useForgeStore((s) => s.setVoxelIntentBlock);
43983
+ const eraseBlock = useForgeStore((s) => s.eraseVoxelIntentBlock);
43984
+ const resolvedCellSize = resolveVoxelIntentCellSize(cellSize);
43985
+ const blockEntries = reactExports.useMemo(() => Object.entries(blocks).map(([key, block]) => ({ cell: parseVoxelCellKey(key), block })), [blocks]);
43986
+ const occupied = (cell) => voxelCellKey(cell) in blocks;
43987
+ const placeAdjacentTo = (blockCell, event) => {
43988
+ event.stopPropagation();
43989
+ if (tool === "erase") {
43990
+ const clickedCell = event.object.userData.voxelIntentCell ?? blockCell;
43991
+ eraseBlock(clickedCell);
43992
+ return;
43993
+ }
43994
+ const faceCandidate = addVoxelCells(blockCell, directionFromFace(event));
43995
+ if (!occupied(faceCandidate)) {
43996
+ setBlock(faceCandidate, { groupId: tool, placement });
43997
+ return;
43998
+ }
43999
+ };
44000
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("group", { children: [
44001
+ cellSizePreviewVisible && /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { position: voxelCellCenter({ x: 0, y: 0, z: 0 }, resolvedCellSize), renderOrder: 55, raycast: () => null, children: [
44002
+ /* @__PURE__ */ jsxRuntimeExports.jsx("boxGeometry", { args: [resolvedCellSize, resolvedCellSize, resolvedCellSize] }),
44003
+ /* @__PURE__ */ jsxRuntimeExports.jsx("meshStandardMaterial", { color: "#22d3ee", emissive: "#0891b2", emissiveIntensity: 0.42, roughness: 0.28, metalness: 0.02 })
44004
+ ] }),
44005
+ blockEntries.map(({ cell, block }) => /* @__PURE__ */ jsxRuntimeExports.jsx(
44006
+ VoxelIntentCellMesh,
44007
+ {
44008
+ cell,
44009
+ color: VOXEL_INTENT_TOOL_COLORS[block.groupId] ?? VOXEL_INTENT_TOOL_COLORS.white,
44010
+ cellSize: resolvedCellSize,
44011
+ opacity: 0.92,
44012
+ onClick: (event) => placeAdjacentTo(cell, event)
44013
+ },
44014
+ `block:${voxelCellKey(cell)}`
44015
+ ))
44016
+ ] });
43168
44017
  }
43169
44018
  function buildManualSceneConfig(base, manualScene, renderStylePreset) {
43170
44019
  var _a3, _b2, _c;
@@ -43480,6 +44329,15 @@ function Viewport() {
43480
44329
  const buildLedgerEvents = useForgeStore((s) => s.buildLedgerEvents);
43481
44330
  const measureSelections = useForgeStore((s) => s.measureSelections);
43482
44331
  const meshPreviewFile = useForgeStore((s) => s.meshPreviewFile);
44332
+ const voxelIntentMode = useForgeStore((s) => s.voxelIntentMode);
44333
+ const voxelIntentTool = useForgeStore((s) => s.voxelIntentTool);
44334
+ const voxelIntentPlacement = useForgeStore((s) => s.voxelIntentPlacement);
44335
+ const voxelIntentCellSizeValue = useForgeStore((s) => s.voxelIntentCellSize);
44336
+ const voxelIntentGhostOpacity = useForgeStore((s) => s.voxelIntentGhostOpacity);
44337
+ const voxelIntentBlocks = useForgeStore((s) => s.voxelIntentBlocks);
44338
+ const setVoxelIntentBlock = useForgeStore((s) => s.setVoxelIntentBlock);
44339
+ const eraseVoxelIntentBlock = useForgeStore((s) => s.eraseVoxelIntentBlock);
44340
+ const setVoxelIntentContextFile = useForgeStore((s) => s.setVoxelIntentContextFile);
43483
44341
  const isSvgActive = !!activeFile && activeFile.toLowerCase().endsWith(".svg");
43484
44342
  const state2 = useViewportState();
43485
44343
  const {
@@ -43494,6 +44352,8 @@ function Viewport() {
43494
44352
  inspectChannel,
43495
44353
  inspectDisplayMode,
43496
44354
  inspectPointSampleCount,
44355
+ thicknessColorRange,
44356
+ setThicknessColorRange,
43497
44357
  comparisonInspectMode,
43498
44358
  comparisonCandidateOpacity,
43499
44359
  projectionMode,
@@ -43566,6 +44426,7 @@ function Viewport() {
43566
44426
  previewFile,
43567
44427
  knownFileNames
43568
44428
  } = state2;
44429
+ const voxelContextFile = previewFile ?? ((activeFile == null ? void 0 : activeFile.toLowerCase().endsWith(".forge.js")) ? activeFile : null);
43569
44430
  const historyMode = useForgeStore((s) => s.historyMode);
43570
44431
  const historyObjectIds = useForgeStore((s) => s.historyObjectIds);
43571
44432
  useHistoryPlaybackLoop();
@@ -43632,6 +44493,7 @@ function Viewport() {
43632
44493
  const inspectAnalysis = useInspectWorkerAnalysis({
43633
44494
  inspectChannel: comparisonInspectActive ? "none" : inspectChannel,
43634
44495
  inspectPointSampleCount,
44496
+ thicknessColorRange,
43635
44497
  sceneVersion,
43636
44498
  objects,
43637
44499
  objectSettings,
@@ -43656,7 +44518,7 @@ function Viewport() {
43656
44518
  const handlers = useViewportHandlers({
43657
44519
  containerRef,
43658
44520
  contextMenuRef,
43659
- measureMode,
44521
+ measureMode: measureMode || voxelIntentMode,
43660
44522
  isViewportInteracting,
43661
44523
  objectPickSyncEnabled,
43662
44524
  hoveredObjectId,
@@ -43701,6 +44563,100 @@ function Viewport() {
43701
44563
  handlePerformanceInfoChange,
43702
44564
  handleViewPersistenceResolved: handleViewPersistenceResolvedFromHandlers
43703
44565
  } = handlers;
44566
+ reactExports.useEffect(() => {
44567
+ setVoxelIntentContextFile(voxelContextFile);
44568
+ }, [setVoxelIntentContextFile, voxelContextFile]);
44569
+ const voxelCellKey2 = (cell) => `${cell.x},${cell.y},${cell.z}`;
44570
+ const addVoxelCells2 = (a2, b2) => ({ x: a2.x + b2.x, y: a2.y + b2.y, z: a2.z + b2.z });
44571
+ const voxelNormalFromBoxHit = (point, bounds) => {
44572
+ const candidates = [
44573
+ { normal: { x: -1, y: 0, z: 0 }, distance: Math.abs(point.x - bounds.min.x) },
44574
+ { normal: { x: 1, y: 0, z: 0 }, distance: Math.abs(point.x - bounds.max.x) },
44575
+ { normal: { x: 0, y: -1, z: 0 }, distance: Math.abs(point.y - bounds.min.y) },
44576
+ { normal: { x: 0, y: 1, z: 0 }, distance: Math.abs(point.y - bounds.max.y) },
44577
+ { normal: { x: 0, y: 0, z: -1 }, distance: Math.abs(point.z - bounds.min.z) },
44578
+ { normal: { x: 0, y: 0, z: 1 }, distance: Math.abs(point.z - bounds.max.z) }
44579
+ ];
44580
+ candidates.sort((a2, b2) => a2.distance - b2.distance);
44581
+ return candidates[0].normal;
44582
+ };
44583
+ const voxelBlockHitsOnRay = (ray, cellSize) => {
44584
+ const hits = [];
44585
+ const halfExtent = cellSize * 0.55;
44586
+ const hitPoint = new Vector3();
44587
+ for (const key of Object.keys(voxelIntentBlocks)) {
44588
+ const [x, y, z] = key.split(",").map(Number);
44589
+ const center = new Vector3((x + 0.5) * cellSize, (y + 0.5) * cellSize, (z + 0.5) * cellSize);
44590
+ const bounds = new Box3(
44591
+ new Vector3(center.x - halfExtent, center.y - halfExtent, center.z - halfExtent),
44592
+ new Vector3(center.x + halfExtent, center.y + halfExtent, center.z + halfExtent)
44593
+ );
44594
+ const intersection = ray.intersectBox(bounds, hitPoint);
44595
+ if (!intersection) continue;
44596
+ const alongRay = intersection.clone().sub(ray.origin).dot(ray.direction);
44597
+ hits.push({ cell: { x, y, z }, normal: voxelNormalFromBoxHit(intersection, bounds), alongRay });
44598
+ }
44599
+ hits.sort((a2, b2) => a2.alongRay - b2.alongRay);
44600
+ return hits;
44601
+ };
44602
+ const rayExitPointForObjectClick = (event, cellSize) => {
44603
+ var _a4;
44604
+ const hitObject = event.object;
44605
+ hitObject.updateWorldMatrix(true, false);
44606
+ const raycaster = new Raycaster(event.ray.origin.clone(), event.ray.direction.clone(), 0, 1e5);
44607
+ const hits = raycaster.intersectObject(hitObject, false).sort((a2, b2) => a2.distance - b2.distance);
44608
+ const minGap = Math.max(1e-3, cellSize * 0.08);
44609
+ const distinctHits = [];
44610
+ for (const hit of hits) {
44611
+ if (hit.distance < event.distance - minGap) continue;
44612
+ const previousHit = distinctHits[distinctHits.length - 1];
44613
+ if (!previousHit || Math.abs(hit.distance - previousHit.distance) > minGap) distinctHits.push(hit);
44614
+ }
44615
+ return ((_a4 = distinctHits[1]) == null ? void 0 : _a4.point) ?? null;
44616
+ };
44617
+ const handleVoxelObjectSurfaceClick = (event, matrix) => {
44618
+ var _a4;
44619
+ event.stopPropagation();
44620
+ if ((event.delta ?? 0) > 4) return;
44621
+ const cellSize = resolveVoxelIntentCellSize(voxelIntentCellSizeValue);
44622
+ const markerHits = voxelBlockHitsOnRay(event.ray, cellSize);
44623
+ if (voxelIntentTool === "erase") {
44624
+ const markerHit2 = markerHits[0];
44625
+ if (markerHit2) eraseVoxelIntentBlock(markerHit2.cell);
44626
+ return;
44627
+ }
44628
+ const markerHit = markerHits[0];
44629
+ if (markerHit) {
44630
+ const candidate = addVoxelCells2(markerHit.cell, markerHit.normal);
44631
+ if (!(voxelCellKey2(candidate) in voxelIntentBlocks)) {
44632
+ setVoxelIntentBlock(candidate, { groupId: voxelIntentTool, placement: voxelIntentPlacement });
44633
+ }
44634
+ return;
44635
+ }
44636
+ const worldNormal = ((_a4 = event.face) == null ? void 0 : _a4.normal) ? event.face.normal.clone().applyNormalMatrix(new Matrix3().getNormalMatrix(matrix)).normalize() : new Vector3(0, 0, 1);
44637
+ if (worldNormal.lengthSq() === 0) worldNormal.set(0, 0, 1);
44638
+ if (voxelIntentPlacement === "through") {
44639
+ const exitPoint = rayExitPointForObjectClick(event, cellSize);
44640
+ const cutPoint = exitPoint ? event.point.clone().lerp(exitPoint, 0.5) : event.point.clone().sub(worldNormal.clone().multiplyScalar(cellSize * 0.5));
44641
+ setVoxelIntentBlock(voxelCellFromPoint(cutPoint, cellSize), { groupId: voxelIntentTool, placement: voxelIntentPlacement });
44642
+ return;
44643
+ }
44644
+ if (voxelIntentPlacement === "inside") {
44645
+ setVoxelIntentBlock(voxelCellFromSurfaceAnchor(event.point, worldNormal, cellSize, "inside"), {
44646
+ groupId: voxelIntentTool,
44647
+ placement: voxelIntentPlacement
44648
+ });
44649
+ return;
44650
+ }
44651
+ if (voxelIntentPlacement === "grid") {
44652
+ setVoxelIntentBlock(voxelCellFromPoint(event.point, cellSize), { groupId: voxelIntentTool, placement: voxelIntentPlacement });
44653
+ return;
44654
+ }
44655
+ setVoxelIntentBlock(voxelCellFromSurfaceAnchor(event.point, worldNormal, cellSize, "outside"), {
44656
+ groupId: voxelIntentTool,
44657
+ placement: voxelIntentPlacement
44658
+ });
44659
+ };
43704
44660
  const overlayEntries = [];
43705
44661
  if (showPerformanceInfo) {
43706
44662
  overlayEntries.push({
@@ -43755,6 +44711,8 @@ function Viewport() {
43755
44711
  channel: inspectChannel,
43756
44712
  displayMode: inspectDisplayMode,
43757
44713
  warnings: inspectWarnings,
44714
+ thicknessColorRange,
44715
+ onThicknessColorRangeChange: setThicknessColorRange,
43758
44716
  details: comparisonInspectActive ? /* @__PURE__ */ jsxRuntimeExports.jsx(ComparisonLegendDetails, { metrics: comparisonAnalysis.metrics, mode: comparisonInspectMode }) : void 0
43759
44717
  }
43760
44718
  )
@@ -43834,7 +44792,7 @@ function Viewport() {
43834
44792
  {
43835
44793
  style: {
43836
44794
  background: renderStyle === "classic" ? t2.viewportBg : renderStylePreset.background,
43837
- cursor: measureMode ? "crosshair" : "default"
44795
+ cursor: measureMode || voxelIntentMode ? "crosshair" : "default"
43838
44796
  },
43839
44797
  dpr: canvasDpr,
43840
44798
  gl: {
@@ -43921,14 +44879,16 @@ function Viewport() {
43921
44879
  const settings = objectSettings[obj.id] ?? { visible: true, opacity: 1, color: "#5b9bd5" };
43922
44880
  const isDimmedByFocus = focusedObjectIdSet.size > 0 && !focusedObjectIdSet.has(obj.id);
43923
44881
  const isDimmedByGhost = constructionGhost !== null && obj.id !== constructionGhost.objectId;
44882
+ const isDimmedByVoxel = voxelIntentMode && !!obj.shape;
43924
44883
  const isHiddenByHistory = historyMode && historyObjectIds.includes(obj.id);
43925
44884
  if (isHiddenByHistory) return null;
43926
44885
  const effectiveSettings = isDimmedByFocus || isDimmedByGhost ? { ...settings, opacity: Math.min(settings.opacity, FOCUS_MODE_DIM_OPACITY) } : settings;
44886
+ const voxelSettings = isDimmedByVoxel ? { ...effectiveSettings, opacity: Math.min(effectiveSettings.opacity, voxelIntentGhostOpacity) } : effectiveSettings;
43927
44887
  const comparisonSettings = comparisonInspectActive && obj.shape ? {
43928
- ...effectiveSettings,
43929
- opacity: Math.min(effectiveSettings.opacity, comparisonCandidateContextOpacity(comparisonCandidateOpacity)),
44888
+ ...voxelSettings,
44889
+ opacity: Math.min(voxelSettings.opacity, comparisonCandidateContextOpacity(comparisonCandidateOpacity)),
43930
44890
  color: "#b9c3cc"
43931
- } : effectiveSettings;
44891
+ } : voxelSettings;
43932
44892
  const isHovered = hoveredObjectId === obj.id;
43933
44893
  const matrix = objectMatrices[obj.id] ?? new Matrix4();
43934
44894
  const objectClippingPlanes = objectClippingPlanesById[obj.id] ?? EMPTY_CLIPPING_PLANES;
@@ -43959,7 +44919,7 @@ function Viewport() {
43959
44919
  onPointerEnter: (event) => updateHoverLabel(obj, event),
43960
44920
  onPointerMove: (event) => updateHoverLabel(obj, event),
43961
44921
  onPointerLeave: (event) => clearHoverLabel(obj, event),
43962
- onClick: (event) => handleObjectClick(obj, event),
44922
+ onClick: (event) => voxelIntentMode ? handleVoxelObjectSurfaceClick(event, matrix) : handleObjectClick(obj, event),
43963
44923
  onDoubleClick: (event) => handleObjectDoubleClick(obj, event),
43964
44924
  onContextMenu: (event) => handleObjectContextMenu(obj, event)
43965
44925
  },
@@ -44032,6 +44992,7 @@ function Viewport() {
44032
44992
  }
44033
44993
  return null;
44034
44994
  }),
44995
+ voxelIntentMode && /* @__PURE__ */ jsxRuntimeExports.jsx(VoxelIntentOverlay, {}),
44035
44996
  inspectChannel === "collisions" && /* @__PURE__ */ jsxRuntimeExports.jsx(CollisionInspectionOverlay, { geometries: inspectAnalysis.collisionGeometries }),
44036
44997
  rigInspectActive && /* @__PURE__ */ jsxRuntimeExports.jsx(RigInspectionOverlay, { state: rigInspectionOverlay, config: jointOverlayConfig }),
44037
44998
  comparisonInspectActive && comparisonAnalysis.referenceResult && /* @__PURE__ */ jsxRuntimeExports.jsx(
@@ -44086,7 +45047,7 @@ function Viewport() {
44086
45047
  }
44087
45048
  ),
44088
45049
  /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomSampler, { onZoomChange: setZoomMmPerPx }),
44089
- gridEnabled && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
45050
+ gridEnabled && !isSketchOnly && !voxelIntentMode && /* @__PURE__ */ jsxRuntimeExports.jsx(
44090
45051
  Grid,
44091
45052
  {
44092
45053
  args: [500, 500],
@@ -44111,7 +45072,7 @@ function Viewport() {
44111
45072
  onPointerEnter: (obj, event) => updateHoverLabel(obj, event),
44112
45073
  onPointerMove: (obj, event) => updateHoverLabel(obj, event),
44113
45074
  onPointerLeave: (obj, event) => clearHoverLabel(obj, event),
44114
- onClick: (obj, event) => handleObjectClick(obj, event),
45075
+ onClick: (obj, event) => voxelIntentMode ? handleVoxelObjectSurfaceClick(event, objectMatrices[obj.id] ?? new Matrix4()) : handleObjectClick(obj, event),
44115
45076
  onDoubleClick: (obj, event) => handleObjectDoubleClick(obj, event),
44116
45077
  onContextMenu: (obj, event) => handleObjectContextMenu(obj, event)
44117
45078
  }
@@ -44146,7 +45107,7 @@ function Viewport() {
44146
45107
  minPolarAngle: 0,
44147
45108
  maxPolarAngle: Math.PI,
44148
45109
  enableRotate: !isSketchOnly,
44149
- mouseButtons: drawModeActive ? MOUSE_BUTTONS_DRAW : isSketchOnly ? MOUSE_BUTTONS_SKETCH : MOUSE_BUTTONS_3D,
45110
+ mouseButtons: voxelIntentMode ? MOUSE_BUTTONS_VOXEL : drawModeActive ? MOUSE_BUTTONS_DRAW : isSketchOnly ? MOUSE_BUTTONS_SKETCH : MOUSE_BUTTONS_3D,
44150
45111
  touches: isSketchOnly ? TOUCH_GESTURES_SKETCH : TOUCH_GESTURES_3D
44151
45112
  }
44152
45113
  ),
@@ -44255,7 +45216,7 @@ function Viewport() {
44255
45216
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportOverlayHost, { entries: overlayEntries }),
44256
45217
  viewportDisabledMessage && /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportDisabledOverlay, { title: viewportDisabledMessage.title, body: viewportDisabledMessage.body }),
44257
45218
  drawFlagEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(DrawToolbar, {}),
44258
- /* @__PURE__ */ jsxRuntimeExports.jsx(HoverTooltipLayer, { ref: hoverTooltipRef, enabled: objectPickSyncEnabled && !measureMode }),
45219
+ /* @__PURE__ */ jsxRuntimeExports.jsx(HoverTooltipLayer, { ref: hoverTooltipRef, enabled: objectPickSyncEnabled && !measureMode && !voxelIntentMode }),
44259
45220
  viewportPortalHost && objectContextMenu && reactDomExports.createPortal(
44260
45221
  /* @__PURE__ */ jsxRuntimeExports.jsxs(
44261
45222
  "div",
@@ -44729,7 +45690,7 @@ function Viewport() {
44729
45690
  }
44730
45691
  );
44731
45692
  }
44732
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-Beb-IZ0y.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
45693
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BHMQlJ-D.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
44733
45694
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
44734
45695
  function storePendingShareCopy(shareId) {
44735
45696
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -44995,17 +45956,17 @@ function SeoMetadata() {
44995
45956
  }, [location.pathname]);
44996
45957
  return null;
44997
45958
  }
44998
- reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-Cr6fXMDj.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
44999
- const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-B954L3YN.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
45000
- reactExports.lazy(() => __vitePreload(() => import("./BlogPage-kF0fkdJT.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
45001
- reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-DfPMY_-d.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
45002
- reactExports.lazy(() => __vitePreload(() => import("./AdminPage-CDyGUinA.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
45959
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-CnevhTE8.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
45960
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-knf4I4h7.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
45961
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-CMAVvgQL.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
45962
+ reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-B27zk8xL.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
45963
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-CXvls4-J.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
45003
45964
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
45004
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-Bz0of4KQ.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
45005
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-zWXkvlwl.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
45006
- reactExports.lazy(() => __vitePreload(() => import("./LegalPage-Dzklqmmg.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
45007
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-Beb-IZ0y.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
45008
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-C77B-TrF.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
45965
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-CFF-UgjI.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
45966
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-B0D4goG_.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
45967
+ reactExports.lazy(() => __vitePreload(() => import("./LegalPage-BPTUmqeg.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
45968
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BHMQlJ-D.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
45969
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-D7ZGlFjx.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
45009
45970
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
45010
45971
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg|dxf)$/i;
45011
45972
  function firstMeaningfulLine(text) {
@@ -45225,7 +46186,7 @@ function App() {
45225
46186
  applyTheme(localStorage.getItem("fc-theme") || "dark");
45226
46187
  clientExports.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsxRuntimeExports.jsx(App, {}));
45227
46188
  export {
45228
- ViewController as $,
46189
+ VOXEL_INTENT_PLACEMENT_LABELS as $,
45229
46190
  AuthApiError as A,
45230
46191
  BrandMark as B,
45231
46192
  hasExternalFiles as C,
@@ -45249,37 +46210,42 @@ export {
45249
46210
  useJointAnimationValues as U,
45250
46211
  INSPECT_POINT_SAMPLE_COUNT_MAX as V,
45251
46212
  INSPECT_POINT_SAMPLE_COUNT_MIN as W,
45252
- expandBoundsByTransformedAabb as X,
45253
- Canvas as Y,
45254
- PerspectiveCamera as Z,
45255
- ControlsInteractionBridge as _,
46213
+ VOXEL_INTENT_TOOL_ORDER as X,
46214
+ VOXEL_INTENT_TOOL_LABELS as Y,
46215
+ VOXEL_INTENT_TOOL_COLORS as Z,
46216
+ VOXEL_INTENT_PLACEMENT_ORDER as _,
45256
46217
  applyTheme as a,
45257
- SceneConfigurator as a0,
45258
- LocalEnvironment as a1,
45259
- ForgeObject as a2,
45260
- RenderLabelsOverlay as a3,
45261
- Grid as a4,
45262
- OrbitControls2 as a5,
45263
- TOUCH_GESTURES_3D as a6,
45264
- MOUSE_BUTTONS_3D as a7,
45265
- ModelJourneyBar as a8,
45266
- useJointAnimationLoop as a9,
45267
- computeJointNodeMatrices as aa,
45268
- computeObjectJointMatrices as ab,
45269
- readLastActiveFileForUser as ac,
45270
- ToastContainer as ad,
45271
- isMobile as ae,
45272
- useFeatureFlag as af,
45273
- decodeSharedHash as ag,
45274
- decodeSharedBundle as ah,
45275
- getExternalUrl as ai,
45276
- getGistId as aj,
45277
- Viewport as ak,
45278
- shouldBlockBrowserShortcut as al,
45279
- useDrawStore as am,
45280
- storePendingShareCopy as an,
45281
- buildShareUrl as ao,
45282
- share as ap,
46218
+ expandBoundsByTransformedAabb as a0,
46219
+ Canvas as a1,
46220
+ PerspectiveCamera as a2,
46221
+ ControlsInteractionBridge as a3,
46222
+ ViewController as a4,
46223
+ SceneConfigurator as a5,
46224
+ LocalEnvironment as a6,
46225
+ ForgeObject as a7,
46226
+ RenderLabelsOverlay as a8,
46227
+ Grid as a9,
46228
+ OrbitControls2 as aa,
46229
+ TOUCH_GESTURES_3D as ab,
46230
+ MOUSE_BUTTONS_3D as ac,
46231
+ ModelJourneyBar as ad,
46232
+ useJointAnimationLoop as ae,
46233
+ computeJointNodeMatrices as af,
46234
+ computeObjectJointMatrices as ag,
46235
+ readLastActiveFileForUser as ah,
46236
+ ToastContainer as ai,
46237
+ isMobile as aj,
46238
+ useFeatureFlag as ak,
46239
+ decodeSharedHash as al,
46240
+ decodeSharedBundle as am,
46241
+ getExternalUrl as an,
46242
+ getGistId as ao,
46243
+ Viewport as ap,
46244
+ shouldBlockBrowserShortcut as aq,
46245
+ useDrawStore as ar,
46246
+ storePendingShareCopy as as,
46247
+ buildShareUrl as at,
46248
+ share as au,
45283
46249
  authFetch as b,
45284
46250
  authApi as c,
45285
46251
  showToast as d,