forgecad 0.9.5 → 0.9.7

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 (86) hide show
  1. package/dist/assets/{AdminPage-uTtcSXtn.js → AdminPage-DX0mpSZT.js} +1 -1
  2. package/dist/assets/{BlogPage-DYJMjWx3.js → BlogPage-CI_P0_Pf.js} +1 -1
  3. package/dist/assets/{DocsPage-C58f0K5v.js → DocsPage-DLhIIZyJ.js} +3 -3
  4. package/dist/assets/EditorApp-BujZvuwX.js +12874 -0
  5. package/dist/assets/{EditorApp-DS0AIUrZ.css → EditorApp-DfFT2Dn8.css} +1 -0
  6. package/dist/assets/{EmbedViewer-CMXWA2LX.js → EmbedViewer-0S0qXKog.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-CAu2OZFn.js → LandingPageProofDriven-O_yMtAri.js} +1 -1
  8. package/dist/assets/{PricingPage-BIgW7m3X.js → PricingPage-DGkX3Ahr.js} +1 -1
  9. package/dist/assets/{SettingsPage-N1l1tMXO.js → SettingsPage-DBsqTB_y.js} +82 -22
  10. package/dist/assets/{app-CFy7g5WP.js → app-BE2nD6Yz.js} +1246 -191
  11. package/dist/assets/cli/{render-BrVVdj_T.js → render-iP9qh475.js} +841 -586
  12. package/dist/assets/{evalWorker-c_SB9gg3.js → evalWorker-Ds5U4xtN.js} +2732 -112
  13. package/dist/assets/inspectWorker-Dll4eVyD.js +12620 -0
  14. package/dist/assets/{manifold-Dp6pvFr6.js → manifold-Bk26ViCr.js} +1 -1
  15. package/dist/assets/{manifold-CRoBhJKH.js → manifold-DjYsd7A_.js} +2 -2
  16. package/dist/assets/{manifold-Cjk7WhRs.js → manifold-sJ-axdXM.js} +1 -1
  17. package/dist/assets/{renderSceneState-3DfsSASX.js → renderSceneState-Bngp5MrQ.js} +1 -1
  18. package/dist/assets/{reportWorker-BLkuIoS8.js → reportWorker-CU8RZ4O0.js} +2715 -112
  19. package/dist/assets/{sectionPlaneMath-CykEnkvQ.js → sectionPlaneMath-BdTjyVfs.js} +3213 -252
  20. package/dist/cli/render.html +1 -1
  21. package/dist/docs/index.html +1 -1
  22. package/dist/docs-raw/AI/usage.md +7 -2
  23. package/dist/docs-raw/CLI.md +82 -53
  24. package/dist/docs-raw/beta-operations.md +9 -0
  25. package/dist/docs-raw/coding.md +1 -1
  26. package/dist/docs-raw/deployment.md +38 -23
  27. package/dist/docs-raw/generated/concepts.md +141 -7
  28. package/dist/docs-raw/generated/core.md +206 -1
  29. package/dist/docs-raw/generated/curves.md +97 -5
  30. package/dist/docs-raw/generated/lib.md +17 -1
  31. package/dist/docs-raw/generated/sketch.md +9 -1
  32. package/dist/docs-raw/generated/viewport.md +1 -1
  33. package/dist/docs-raw/guides/inspection-bundles.md +45 -16
  34. package/dist/docs-raw/platform/auth.md +2 -0
  35. package/dist/docs-raw/platform/google-oauth-setup.md +4 -0
  36. package/dist/docs-raw/runbook.md +3 -3
  37. package/dist/docs-raw/skills/forgecad-make-a-model.md +87 -8
  38. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +14 -6
  39. package/dist/docs-raw/skills/forgecad-render-inspect.md +1 -1
  40. package/dist/docs-raw/skills/index.md +2 -2
  41. package/dist/index.html +1 -1
  42. package/dist/sitemap.xml +6 -6
  43. package/dist-cli/forgecad.js +8725 -4747
  44. package/dist-cli/forgecad.js.map +1 -1
  45. package/dist-skill/CONTEXT.md +375 -25
  46. package/dist-skill/docs/CLI.md +82 -53
  47. package/dist-skill/docs/generated/core.md +206 -1
  48. package/dist-skill/docs/generated/curves.md +97 -5
  49. package/dist-skill/docs/generated/lib.md +17 -1
  50. package/dist-skill/docs/generated/sketch.md +9 -1
  51. package/dist-skill/docs/generated/viewport.md +1 -1
  52. package/dist-skill/docs/guides/inspection-bundles.md +45 -16
  53. package/dist-skill/docs-dev/CLI.md +82 -53
  54. package/dist-skill/docs-dev/coding.md +1 -1
  55. package/dist-skill/docs-dev/generated/core.md +206 -1
  56. package/dist-skill/docs-dev/generated/curves.md +97 -5
  57. package/dist-skill/docs-dev/generated/lib.md +17 -1
  58. package/dist-skill/docs-dev/generated/sketch.md +9 -1
  59. package/dist-skill/docs-dev/generated/viewport.md +1 -1
  60. package/dist-skill/docs-dev/guides/inspection-bundles.md +45 -16
  61. package/dist-skill/library/forgecad-make-a-model/SKILL.md +87 -8
  62. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +14 -6
  63. package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +5 -3
  64. package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +7 -5
  65. package/dist-skill/library/forgecad-render-inspect/SKILL.md +1 -1
  66. package/examples/api/bolted-service-cover.forge.js +17 -0
  67. package/examples/api/cable-gland-anchor.forge.js +14 -0
  68. package/examples/api/captured-cartridge-guide.forge.js +14 -0
  69. package/examples/api/captured-linear-slide.forge.js +13 -0
  70. package/examples/api/clevis-pin-joint.forge.js +13 -0
  71. package/examples/api/datum-enclosure.forge.js +16 -0
  72. package/examples/api/guided-loft-olive-oil-bottle.forge.js +135 -0
  73. package/examples/api/hose-barb-port.forge.js +14 -0
  74. package/examples/api/intentional-overlap-overmold.forge.js +16 -0
  75. package/examples/api/knuckled-hinge-assembly.forge.js +15 -0
  76. package/examples/api/living-hinge-cover.forge.js +14 -0
  77. package/examples/api/pcb-terminal-block.forge.js +22 -0
  78. package/examples/api/pinned-lever-pivot-stack.forge.js +14 -0
  79. package/examples/api/retained-shaft-knob-stack.forge.js +15 -0
  80. package/examples/api/routed-tube-clip.forge.js +15 -0
  81. package/examples/api/seated-bearing-stack.forge.js +30 -0
  82. package/examples/api/snap-latch-cover.forge.js +14 -0
  83. package/examples/api/static-assembly-connectors.forge.js +14 -16
  84. package/examples/api/thumb-screw-clamp.forge.js +15 -0
  85. package/package.json +20 -2
  86. package/dist/assets/EditorApp-DNH1TEz1.js +0 -12729
@@ -1,10 +1,10 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/EditorApp-DS0AIUrZ.css","assets/landing-proof-driven-B7_RpP79.css","assets/PricingPage-BMedqFef.css"])))=>i.map(i=>d[i]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/EditorApp-DfFT2Dn8.css","assets/landing-proof-driven-B7_RpP79.css","assets/PricingPage-BMedqFef.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
- import { c as create, r as reactExports, j as jsxRuntimeExports, 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, B as BrowserRouter, g as Routes, h as Route, N as Navigate } from "./vendor-react-Da3A2QmU.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 DEFAULT_ACTIVE_BACKEND, az as isConstraintSketch, aA as updateConstraintValue, aB as getShapeCompilePlan, aC as resolveForgeRenderStyle, aD as publishSolverWasmRunDebug, aE as resolveForgeQualityPreset, aF as resolveImportPath, aG as BufferGeometry, aH as LineBasicMaterial, aI as Line$1, aJ as LineDashedMaterial, aK as DepthStencilFormat, aL as UnsignedInt248Type, aM as MeshNormalMaterial, aN as NearestFilter, aO as BasicDepthPacking, aP as EventDispatcher$1, aQ as NoColorSpace, aR as FrontSide, aS as Material, aT as AlwaysDepth, aU as BufferAttribute, aV as CanvasTexture, aW as Object3D, aX as FogExp2, aY as Fog, aZ as AmbientLight, a_ as HemisphereLight, a$ as SpotLight, b0 as PointLight, b1 as DirectionalLight, b2 as buildShapeFromCompilePlan, b3 as shapeToGeometry, b4 as sketchToSvg, b5 as sketchToDxf, b6 as runScript, b7 as MeshPhysicalMaterial, b8 as LineSegments, b9 as getRenderStylePreset, ba as AdditiveBlending, bb as CatmullRomCurve3, bc as TubeGeometry, bd as MeshStandardMaterial, be as compileSdfNode3, bf as buildSdfRaymarchFragmentShader, bg as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bh as Shape, bi as ShapeGeometry, bj as ShaderLib, bk as CylinderGeometry, bl as parseViewportCameraState, bm as createResolvedExplodeConfig, bn as explodeBoundsCenter, bo as explodeMergeBounds, bp as resolveExplodeDirective, bq as computeExplodeMotion, br as getSketchWorldMatrix, bs as explodeAdd, bt as hasExplodeOverride, bu as resolveExplodeLocalFanDirection, bv as explodeMul, bw as explodeLeafFanStage, bx as normalizeCutPlane, by as toClippingPlane, bz as findJointAnimationClip, bA as resolveJointAnimation, bB as resolveJointViewValues, bC as getShapePorts, bD as getShapeUsedPorts, bE as DEFAULT_VIEW_CONFIG, bF as getKernelFaceNameForTriangle, bG as initKernel, bH as initSolverWasm } from "./sectionPlaneMath-CykEnkvQ.js";
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, B as BrowserRouter, g as Routes, h as Route, N as Navigate } from "./vendor-react-Da3A2QmU.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 DEFAULT_ACTIVE_BACKEND, az as isConstraintSketch, aA as updateConstraintValue, aB as getShapeCompilePlan, aC as resolveForgeRenderStyle, aD as publishSolverWasmRunDebug, aE as resolveForgeQualityPreset, aF as resolveImportPath, aG as BufferGeometry, aH as LineBasicMaterial, aI as Line$1, aJ as LineDashedMaterial, aK as DepthStencilFormat, aL as UnsignedInt248Type, aM as MeshNormalMaterial, aN as NearestFilter, aO as BasicDepthPacking, aP as EventDispatcher$1, aQ as NoColorSpace, aR as FrontSide, aS as Material, aT as AlwaysDepth, aU as BufferAttribute, aV as CanvasTexture, aW as Object3D, aX as FogExp2, aY as Fog, aZ as AmbientLight, a_ as HemisphereLight, a$ as SpotLight, b0 as PointLight, b1 as DirectionalLight, b2 as analyzeCollisionIntersections, b3 as shapeToGeometry, b4 as buildShapeFromCompilePlan, b5 as sketchToSvg, b6 as sketchToDxf, b7 as runScript, b8 as MeshPhysicalMaterial, b9 as LineSegments, ba as getRenderStylePreset, bb as AdditiveBlending, bc as CatmullRomCurve3, bd as TubeGeometry, be as MeshStandardMaterial, bf as compileSdfNode3, bg as buildSdfRaymarchFragmentShader, bh as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bi as Shape, bj as ShapeGeometry, bk as ShaderLib, bl as CylinderGeometry, bm as parseViewportCameraState, bn as createResolvedExplodeConfig, bo as explodeBoundsCenter, bp as explodeMergeBounds, bq as resolveExplodeDirective, br as computeExplodeMotion, bs as getSketchWorldMatrix, bt as explodeAdd, bu as hasExplodeOverride, bv as resolveExplodeLocalFanDirection, bw as explodeMul, bx as explodeLeafFanStage, by as normalizeCutPlane, bz as toClippingPlane, bA as findJointAnimationClip, bB as resolveJointAnimation, bC as resolveJointViewValues, bD as getShapePorts, bE as getShapeUsedPorts, bF as DEFAULT_VIEW_CONFIG, bG as getKernelFaceNameForTriangle, bH as initKernel, bI as initSolverWasm } from "./sectionPlaneMath-BdTjyVfs.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];
@@ -71,6 +71,9 @@ const authApi = {
71
71
  logout() {
72
72
  return authFetch("/api/auth/logout", { method: "POST" });
73
73
  },
74
+ deleteAccount() {
75
+ return authFetch("/api/users/me", { method: "DELETE" });
76
+ },
74
77
  providers() {
75
78
  return authFetch("/api/auth/providers");
76
79
  },
@@ -516,6 +519,17 @@ function applyAuthenticatedUser(set, user) {
516
519
  set({ user, loading: false, error: null, backendAvailable: true });
517
520
  startRefreshTimer();
518
521
  }
522
+ function clearAuthenticatedState(set) {
523
+ stopRefreshTimer();
524
+ clearUserCache();
525
+ setCurrentUserId(void 0);
526
+ set({ user: void 0, loading: false, error: null });
527
+ useProjectStore.setState({ projects: [], activeProjectId: null, error: null });
528
+ try {
529
+ localStorage.removeItem("fc-active-project-id");
530
+ } catch {
531
+ }
532
+ }
519
533
  const useAuthStore = create((set) => ({
520
534
  user: null,
521
535
  loading: true,
@@ -569,21 +583,58 @@ const useAuthStore = create((set) => ({
569
583
  }
570
584
  },
571
585
  async logout() {
572
- stopRefreshTimer();
573
586
  try {
574
587
  await authApi.logout();
575
588
  } finally {
576
- clearUserCache();
577
- setCurrentUserId(void 0);
578
- set({ user: void 0, loading: false, error: null });
579
- useProjectStore.setState({ projects: [], activeProjectId: null, error: null });
580
- try {
581
- localStorage.removeItem("fc-active-project-id");
582
- } catch {
583
- }
589
+ clearAuthenticatedState(set);
590
+ }
591
+ },
592
+ async deleteAccount() {
593
+ set({ loading: true, error: null });
594
+ try {
595
+ await authApi.deleteAccount();
596
+ clearAuthenticatedState(set);
597
+ } catch (err) {
598
+ set({ loading: false, error: err.message || "Account deletion failed" });
599
+ throw err;
584
600
  }
585
601
  }
586
602
  }));
603
+ const BRAND_MARK_SRC = "/brand/logo-mark-128.png";
604
+ const BRAND_MARK_SRC_SET = [
605
+ "/brand/logo-mark-64.png 64w",
606
+ "/brand/logo-mark-128.png 128w",
607
+ "/brand/logo-mark-256.png 256w",
608
+ "/brand/logo-mark.png 512w"
609
+ ].join(", ");
610
+ function BrandMark({
611
+ size = 24,
612
+ alt = "ForgeCAD logo",
613
+ decorative = false,
614
+ style
615
+ }) {
616
+ const logicalSize = Math.max(1, Math.ceil(size));
617
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
618
+ "img",
619
+ {
620
+ src: BRAND_MARK_SRC,
621
+ srcSet: BRAND_MARK_SRC_SET,
622
+ sizes: `${logicalSize}px`,
623
+ alt: decorative ? "" : alt,
624
+ "aria-hidden": decorative ? "true" : void 0,
625
+ width: size,
626
+ height: size,
627
+ style: {
628
+ width: size,
629
+ height: size,
630
+ display: "block",
631
+ flex: "0 0 auto",
632
+ objectFit: "contain",
633
+ ...style
634
+ }
635
+ }
636
+ );
637
+ }
587
638
  let nextId = 0;
588
639
  let toasts = [];
589
640
  const listeners$1 = /* @__PURE__ */ new Set();
@@ -779,41 +830,6 @@ const styles = {
779
830
  opacity: 0.65
780
831
  }
781
832
  };
782
- const BRAND_MARK_SRC = "/brand/logo-mark-128.png";
783
- const BRAND_MARK_SRC_SET = [
784
- "/brand/logo-mark-64.png 64w",
785
- "/brand/logo-mark-128.png 128w",
786
- "/brand/logo-mark-256.png 256w",
787
- "/brand/logo-mark.png 512w"
788
- ].join(", ");
789
- function BrandMark({
790
- size = 24,
791
- alt = "ForgeCAD logo",
792
- decorative = false,
793
- style
794
- }) {
795
- const logicalSize = Math.max(1, Math.ceil(size));
796
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
797
- "img",
798
- {
799
- src: BRAND_MARK_SRC,
800
- srcSet: BRAND_MARK_SRC_SET,
801
- sizes: `${logicalSize}px`,
802
- alt: decorative ? "" : alt,
803
- "aria-hidden": decorative ? "true" : void 0,
804
- width: size,
805
- height: size,
806
- style: {
807
- width: size,
808
- height: size,
809
- display: "block",
810
- flex: "0 0 auto",
811
- objectFit: "contain",
812
- ...style
813
- }
814
- }
815
- );
816
- }
817
833
  function getQueryParam(name) {
818
834
  return new URLSearchParams(window.location.search).get(name);
819
835
  }
@@ -856,7 +872,7 @@ function toRGB555(colorHex) {
856
872
  if (!rgb) return 0;
857
873
  return 32768 | rgb.r >> 3 << 10 | rgb.g >> 3 << 5 | rgb.b >> 3;
858
874
  }
859
- function rgbToHex(r2, g2, b2) {
875
+ function rgbToHex$1(r2, g2, b2) {
860
876
  const toHex = (v) => v.toString(16).padStart(2, "0").toUpperCase();
861
877
  return `#${toHex(r2)}${toHex(g2)}${toHex(b2)}`;
862
878
  }
@@ -871,7 +887,7 @@ function buildPure3mfBuffer(objects, options = {}) {
871
887
  if (obj.color) {
872
888
  const rgb = parseHexColor(obj.color);
873
889
  if (rgb) {
874
- const hex = rgbToHex(rgb.r, rgb.g, rgb.b);
890
+ const hex = rgbToHex$1(rgb.r, rgb.g, rgb.b);
875
891
  if (!colorMap.has(hex)) {
876
892
  colorMap.set(hex, colors.length);
877
893
  colors.push({ hex, objectIndices: [] });
@@ -908,7 +924,7 @@ function buildPure3mfBuffer(objects, options = {}) {
908
924
  if (obj.color && hasColors) {
909
925
  const rgb = parseHexColor(obj.color);
910
926
  if (rgb) {
911
- const hex = rgbToHex(rgb.r, rgb.g, rgb.b);
927
+ const hex = rgbToHex$1(rgb.r, rgb.g, rgb.b);
912
928
  const colorIdx = colorMap.get(hex);
913
929
  if (colorIdx !== void 0) {
914
930
  pidAttr = ` pid="${colorgroupId}" pindex="${colorIdx}"`;
@@ -14920,7 +14936,7 @@ function planLabel(plan) {
14920
14936
  case "boolean":
14921
14937
  return plan.op === "union" ? "Union" : plan.op === "difference" ? "Subtract" : "Intersect";
14922
14938
  case "transform":
14923
- return planLabel(plan.base);
14939
+ return formatTransformLabel(plan.steps);
14924
14940
  case "queryOwner":
14925
14941
  return planLabel(plan.base);
14926
14942
  case "fillet":
@@ -14969,12 +14985,53 @@ function planLabel(plan) {
14969
14985
  return `STEP Import (${plan.filePath})`;
14970
14986
  }
14971
14987
  }
14988
+ function formatTransformLabel(steps) {
14989
+ if (steps.length === 0) return "Transform";
14990
+ const kinds = [...new Set(steps.map((step) => step.kind))];
14991
+ return `Transform (${kinds.join(", ")})`;
14992
+ }
14972
14993
  function resolvePlan(plan) {
14973
14994
  return plan.kind === "queryOwner" ? resolvePlan(plan.base) : plan;
14974
14995
  }
14975
14996
  function wrapWithTransforms(plan, transformStacks) {
14976
14997
  return transformStacks.reduceRight((inner, steps) => ({ kind: "transform", base: inner, steps }), plan);
14977
14998
  }
14999
+ const SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION = "shape-plan-v1";
15000
+ function stableJsonEncode(value, arrayMember) {
15001
+ if (value === void 0 || typeof value === "function" || typeof value === "symbol") {
15002
+ return arrayMember ? "null" : void 0;
15003
+ }
15004
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "string") {
15005
+ return JSON.stringify(value);
15006
+ }
15007
+ if (Array.isArray(value)) {
15008
+ return `[${value.map((item) => stableJsonEncode(item, true) ?? "null").join(",")}]`;
15009
+ }
15010
+ const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
15011
+ const encodedEntries = [];
15012
+ for (const [key, item] of entries) {
15013
+ const encoded = stableJsonEncode(item, false);
15014
+ if (encoded !== void 0) encodedEntries.push(`${JSON.stringify(key)}:${encoded}`);
15015
+ }
15016
+ return `{${encodedEntries.join(",")}}`;
15017
+ }
15018
+ function stableJsonStringify(value) {
15019
+ return stableJsonEncode(value, false) ?? "null";
15020
+ }
15021
+ function shapeCompilePlanCacheKey(plan) {
15022
+ return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${stableJsonStringify(plan)}`;
15023
+ }
15024
+ function constructionStepGeometryCacheKey(step) {
15025
+ return step.cumulativePlanCacheKey;
15026
+ }
15027
+ function makeConstructionStep(step) {
15028
+ const planCacheKey = shapeCompilePlanCacheKey(step.plan);
15029
+ return {
15030
+ ...step,
15031
+ planCacheKey,
15032
+ cumulativePlanCacheKey: step.cumulativePlan === step.plan ? planCacheKey : shapeCompilePlanCacheKey(step.cumulativePlan)
15033
+ };
15034
+ }
14978
15035
  function linearizeConstructionSteps(rootPlan, objectId) {
14979
15036
  const steps = [];
14980
15037
  const ancestorTransforms = [];
@@ -15004,16 +15061,18 @@ function linearizeConstructionSteps(rootPlan, objectId) {
15004
15061
  shapes: childCumulatives
15005
15062
  };
15006
15063
  const worldPlan = wrapWithTransforms(cumulativePlan, ancestorTransforms);
15007
- steps.push({
15008
- index: steps.length,
15009
- label: planLabel(resolved),
15010
- role: "operation",
15011
- displayAsTool,
15012
- stackPopCount: childCumulatives.length,
15013
- plan: worldPlan,
15014
- cumulativePlan: worldPlan,
15015
- objectId
15016
- });
15064
+ steps.push(
15065
+ makeConstructionStep({
15066
+ index: steps.length,
15067
+ label: planLabel(resolved),
15068
+ role: "operation",
15069
+ displayAsTool,
15070
+ stackPopCount: childCumulatives.length,
15071
+ plan: worldPlan,
15072
+ cumulativePlan: worldPlan,
15073
+ objectId
15074
+ })
15075
+ );
15017
15076
  return cumulativePlan;
15018
15077
  }
15019
15078
  // ── Modifications: visit base, then add modification step ──
@@ -15031,16 +15090,18 @@ function linearizeConstructionSteps(rootPlan, objectId) {
15031
15090
  const baseCumulative = visit(resolved.base, options);
15032
15091
  const modPlan = { ...resolved, base: baseCumulative };
15033
15092
  const worldPlan = wrapWithTransforms(modPlan, ancestorTransforms);
15034
- steps.push({
15035
- index: steps.length,
15036
- label: planLabel(resolved),
15037
- role: "modification",
15038
- displayAsTool,
15039
- stackPopCount: 1,
15040
- plan: worldPlan,
15041
- cumulativePlan: worldPlan,
15042
- objectId
15043
- });
15093
+ steps.push(
15094
+ makeConstructionStep({
15095
+ index: steps.length,
15096
+ label: planLabel(resolved),
15097
+ role: "modification",
15098
+ displayAsTool,
15099
+ stackPopCount: 1,
15100
+ plan: worldPlan,
15101
+ cumulativePlan: worldPlan,
15102
+ objectId
15103
+ })
15104
+ );
15044
15105
  return modPlan;
15045
15106
  }
15046
15107
  case "surfaceExtend":
@@ -15048,16 +15109,18 @@ function linearizeConstructionSteps(rootPlan, objectId) {
15048
15109
  const baseCumulative = visit(resolved.base, options);
15049
15110
  const modPlan = { ...resolved, base: baseCumulative };
15050
15111
  const worldPlan = wrapWithTransforms(modPlan, ancestorTransforms);
15051
- steps.push({
15052
- index: steps.length,
15053
- label: planLabel(resolved),
15054
- role: "modification",
15055
- displayAsTool,
15056
- stackPopCount: 1,
15057
- plan: worldPlan,
15058
- cumulativePlan: worldPlan,
15059
- objectId
15060
- });
15112
+ steps.push(
15113
+ makeConstructionStep({
15114
+ index: steps.length,
15115
+ label: planLabel(resolved),
15116
+ role: "modification",
15117
+ displayAsTool,
15118
+ stackPopCount: 1,
15119
+ plan: worldPlan,
15120
+ cumulativePlan: worldPlan,
15121
+ objectId
15122
+ })
15123
+ );
15061
15124
  return modPlan;
15062
15125
  }
15063
15126
  case "surfaceSew": {
@@ -15071,31 +15134,35 @@ function linearizeConstructionSteps(rootPlan, objectId) {
15071
15134
  tolerance: resolved.tolerance
15072
15135
  };
15073
15136
  const worldPlan = wrapWithTransforms(cumulativePlan, ancestorTransforms);
15074
- steps.push({
15075
- index: steps.length,
15076
- label: planLabel(resolved),
15077
- role: "operation",
15078
- displayAsTool,
15079
- stackPopCount: childCumulatives.length,
15080
- plan: worldPlan,
15081
- cumulativePlan: worldPlan,
15082
- objectId
15083
- });
15137
+ steps.push(
15138
+ makeConstructionStep({
15139
+ index: steps.length,
15140
+ label: planLabel(resolved),
15141
+ role: "operation",
15142
+ displayAsTool,
15143
+ stackPopCount: childCumulatives.length,
15144
+ plan: worldPlan,
15145
+ cumulativePlan: worldPlan,
15146
+ objectId
15147
+ })
15148
+ );
15084
15149
  return cumulativePlan;
15085
15150
  }
15086
15151
  // ── Leaf primitives: add directly as a step ──
15087
15152
  default: {
15088
15153
  const worldPlan = wrapWithTransforms(resolved, ancestorTransforms);
15089
- steps.push({
15090
- index: steps.length,
15091
- label: planLabel(resolved),
15092
- role: "primitive",
15093
- displayAsTool,
15094
- stackPopCount: 0,
15095
- plan: worldPlan,
15096
- cumulativePlan: worldPlan,
15097
- objectId
15098
- });
15154
+ steps.push(
15155
+ makeConstructionStep({
15156
+ index: steps.length,
15157
+ label: planLabel(resolved),
15158
+ role: "primitive",
15159
+ displayAsTool,
15160
+ stackPopCount: 0,
15161
+ plan: worldPlan,
15162
+ cumulativePlan: worldPlan,
15163
+ objectId
15164
+ })
15165
+ );
15099
15166
  return resolved;
15100
15167
  }
15101
15168
  }
@@ -15788,7 +15855,7 @@ const CRASH_COOLDOWN_MS = 2e3;
15788
15855
  class EvalWorkerClient {
15789
15856
  constructor(workerFactory = () => new Worker(new URL(
15790
15857
  /* @vite-ignore */
15791
- "/assets/evalWorker-c_SB9gg3.js",
15858
+ "/assets/evalWorker-Ds5U4xtN.js",
15792
15859
  import.meta.url
15793
15860
  ), { type: "module" })) {
15794
15861
  __publicField(this, "worker", null);
@@ -17253,6 +17320,14 @@ function buildRunState(previewFile, runResult, state2) {
17253
17320
  };
17254
17321
  }
17255
17322
  const VIEW_PREFERENCES_KEY = "fc-view-preferences-v1";
17323
+ const INSPECT_POINT_SAMPLE_COUNT_MIN = 100;
17324
+ const INSPECT_POINT_SAMPLE_COUNT_MAX = 1e4;
17325
+ const DEFAULT_INSPECT_POINT_SAMPLE_COUNT = 2e3;
17326
+ const resolveInspectPointSampleCount = (value) => {
17327
+ const numeric = typeof value === "number" ? value : Number(value);
17328
+ if (!Number.isFinite(numeric)) return DEFAULT_INSPECT_POINT_SAMPLE_COUNT;
17329
+ return Math.max(INSPECT_POINT_SAMPLE_COUNT_MIN, Math.min(INSPECT_POINT_SAMPLE_COUNT_MAX, Math.round(numeric)));
17330
+ };
17256
17331
  const readViewPreferences = () => {
17257
17332
  if (typeof window === "undefined") return {};
17258
17333
  try {
@@ -17361,6 +17436,22 @@ const initialActive = (() => {
17361
17436
  return fallback;
17362
17437
  })();
17363
17438
  const INITIAL_SAVED = {};
17439
+ const VIEW_INSPECT_CHANNELS = /* @__PURE__ */ new Set([
17440
+ "none",
17441
+ "mask",
17442
+ "connectivity",
17443
+ "distance",
17444
+ "collisions",
17445
+ "thickness",
17446
+ "roughness"
17447
+ ]);
17448
+ const INSPECT_DISPLAY_MODES = /* @__PURE__ */ new Set(["heatmap", "points", "both"]);
17449
+ function resolveViewInspectChannel(value) {
17450
+ return typeof value === "string" && VIEW_INSPECT_CHANNELS.has(value) ? value : "none";
17451
+ }
17452
+ function resolveInspectDisplayMode(value) {
17453
+ return typeof value === "string" && INSPECT_DISPLAY_MODES.has(value) ? value : "heatmap";
17454
+ }
17364
17455
  const initialViewPreferences = readViewPreferences();
17365
17456
  const initialPreviewFile = resolvePreviewFile(initialActive, INITIAL_FILES);
17366
17457
  const initialObjectSettingsByFile = (() => {
@@ -18119,6 +18210,24 @@ Switch to LOCAL mode or wait for the server to recover.`,
18119
18210
  writeViewPreferences({ renderMode: mode });
18120
18211
  set({ renderMode: mode });
18121
18212
  },
18213
+ inspectChannel: resolveViewInspectChannel(initialViewPreferences.inspectChannel),
18214
+ setInspectChannel: (channel) => {
18215
+ const next = resolveViewInspectChannel(channel);
18216
+ writeViewPreferences({ inspectChannel: next });
18217
+ set({ inspectChannel: next });
18218
+ },
18219
+ inspectDisplayMode: resolveInspectDisplayMode(initialViewPreferences.inspectDisplayMode),
18220
+ setInspectDisplayMode: (mode) => {
18221
+ const next = resolveInspectDisplayMode(mode);
18222
+ writeViewPreferences({ inspectDisplayMode: next });
18223
+ set({ inspectDisplayMode: next });
18224
+ },
18225
+ inspectPointSampleCount: resolveInspectPointSampleCount(initialViewPreferences.inspectPointSampleCount),
18226
+ setInspectPointSampleCount: (count) => {
18227
+ const next = resolveInspectPointSampleCount(count);
18228
+ writeViewPreferences({ inspectPointSampleCount: next });
18229
+ set({ inspectPointSampleCount: next });
18230
+ },
18122
18231
  projectionMode: initialViewPreferences.projectionMode ?? "perspective",
18123
18232
  setProjectionMode: (mode) => {
18124
18233
  writeViewPreferences({ projectionMode: mode });
@@ -26690,6 +26799,129 @@ function ClippingManager({ active }) {
26690
26799
  }, [gl, active]);
26691
26800
  return null;
26692
26801
  }
26802
+ const COLLISION_SOURCE_OPACITY = 0.22;
26803
+ const COLLISION_SOURCE_COLOR = [180, 200, 220];
26804
+ const COLLISION_HIGHLIGHT_COLOR = [255, 68, 16];
26805
+ const COLLISION_PALETTE = [
26806
+ COLLISION_HIGHLIGHT_COLOR,
26807
+ [0, 204, 255],
26808
+ [255, 214, 0],
26809
+ [66, 220, 120],
26810
+ [255, 76, 196],
26811
+ [142, 106, 255],
26812
+ [255, 145, 48],
26813
+ [86, 232, 202],
26814
+ [210, 245, 60],
26815
+ [255, 120, 120],
26816
+ [80, 150, 255],
26817
+ [190, 110, 60]
26818
+ ];
26819
+ const MASK_PALETTE = [
26820
+ [230, 25, 75],
26821
+ [60, 180, 75],
26822
+ [255, 225, 25],
26823
+ [0, 130, 200],
26824
+ [245, 130, 48],
26825
+ [145, 30, 180],
26826
+ [70, 240, 240],
26827
+ [240, 50, 230],
26828
+ [210, 245, 60],
26829
+ [250, 190, 190],
26830
+ [0, 128, 128],
26831
+ [230, 190, 255],
26832
+ [170, 110, 40],
26833
+ [255, 250, 200],
26834
+ [128, 0, 0],
26835
+ [170, 255, 195],
26836
+ [128, 128, 0],
26837
+ [255, 215, 180],
26838
+ [0, 0, 128],
26839
+ [128, 128, 128]
26840
+ ];
26841
+ function rgbToHex(color2) {
26842
+ return `#${color2.map((value) => value.toString(16).padStart(2, "0")).join("")}`;
26843
+ }
26844
+ function maskRgbForIndex(index) {
26845
+ return MASK_PALETTE[index % MASK_PALETTE.length];
26846
+ }
26847
+ function maskColorForIndex(index) {
26848
+ return rgbToHex(maskRgbForIndex(index));
26849
+ }
26850
+ function collisionColorForIndex(index) {
26851
+ return rgbToHex(COLLISION_PALETTE[(index - 1) % COLLISION_PALETTE.length]);
26852
+ }
26853
+ const MAX_VIEWPORT_COLLISION_CANDIDATES = 200;
26854
+ const MAX_VIEWPORT_COLLISION_MS = 1e3;
26855
+ function matrixToCollisionMat4(matrix) {
26856
+ if (!matrix) return void 0;
26857
+ const e2 = matrix.elements;
26858
+ return [e2[0], e2[1], e2[2], e2[3], e2[4], e2[5], e2[6], e2[7], e2[8], e2[9], e2[10], e2[11], e2[12], e2[13], e2[14], e2[15]];
26859
+ }
26860
+ function buildCollisionEntries(objects, objectSettings, objectMatrices) {
26861
+ const entries = [];
26862
+ objects.forEach((obj) => {
26863
+ var _a3;
26864
+ if (!obj.shape) return;
26865
+ if (((_a3 = objectSettings[obj.id]) == null ? void 0 : _a3.visible) === false) return;
26866
+ try {
26867
+ const bb = obj.shape.boundingBox();
26868
+ entries.push({
26869
+ id: obj.id,
26870
+ name: obj.name,
26871
+ shape: obj.shape,
26872
+ min: [bb.min[0], bb.min[1], bb.min[2]],
26873
+ max: [bb.max[0], bb.max[1], bb.max[2]],
26874
+ transform: matrixToCollisionMat4(objectMatrices[obj.id]),
26875
+ groupName: obj.groupName,
26876
+ treePath: obj.treePath,
26877
+ mock: obj.mock
26878
+ });
26879
+ } catch {
26880
+ }
26881
+ });
26882
+ return entries;
26883
+ }
26884
+ function CollisionInspectionOverlay({
26885
+ objects,
26886
+ objectSettings,
26887
+ objectMatrices
26888
+ }) {
26889
+ const collisionGeometries = reactExports.useMemo(() => {
26890
+ const report = analyzeCollisionIntersections(buildCollisionEntries(objects, objectSettings, objectMatrices), {
26891
+ maxCandidatePairs: MAX_VIEWPORT_COLLISION_CANDIDATES,
26892
+ maxElapsedMs: MAX_VIEWPORT_COLLISION_MS,
26893
+ includeBBoxCandidates: false
26894
+ });
26895
+ return report.collisions.flatMap((collision) => {
26896
+ try {
26897
+ const geometry = shapeToGeometry(collision.shape);
26898
+ return [
26899
+ {
26900
+ id: collision.id,
26901
+ color: collisionColorForIndex(collision.index),
26902
+ solid: geometry.solid,
26903
+ edges: geometry.edges
26904
+ }
26905
+ ];
26906
+ } catch {
26907
+ return [];
26908
+ }
26909
+ });
26910
+ }, [objectMatrices, objectSettings, objects]);
26911
+ reactExports.useEffect(() => {
26912
+ return () => {
26913
+ collisionGeometries.forEach((geometry) => {
26914
+ var _a3;
26915
+ geometry.solid.dispose();
26916
+ (_a3 = geometry.edges) == null ? void 0 : _a3.dispose();
26917
+ });
26918
+ };
26919
+ }, [collisionGeometries]);
26920
+ return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { children: collisionGeometries.map((geometry) => /* @__PURE__ */ jsxRuntimeExports.jsxs("group", { children: [
26921
+ /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: geometry.solid, renderOrder: 20, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { color: geometry.color, depthTest: true, depthWrite: true, toneMapped: false }) }),
26922
+ geometry.edges && /* @__PURE__ */ jsxRuntimeExports.jsx("lineSegments", { geometry: geometry.edges, renderOrder: 21, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx("lineBasicMaterial", { color: "#ffffff", transparent: true, opacity: 0.65, depthTest: false, toneMapped: false }) })
26923
+ ] }, geometry.id)) });
26924
+ }
26693
26925
  function ConstructionGhostOverlay({ matrix }) {
26694
26926
  const ghost = useForgeStore((s) => s.constructionGhost);
26695
26927
  const { solidGeo, edgesGeo } = reactExports.useMemo(() => {
@@ -26712,7 +26944,8 @@ function ConstructionGhostOverlay({ matrix }) {
26712
26944
  const MAX_CACHE_SIZE = 40;
26713
26945
  const geometryCache = /* @__PURE__ */ new Map();
26714
26946
  function getCachedGeometry(step) {
26715
- const cached = geometryCache.get(step.index);
26947
+ const cacheKey = constructionStepGeometryCacheKey(step);
26948
+ const cached = geometryCache.get(cacheKey);
26716
26949
  if (cached) return cached;
26717
26950
  try {
26718
26951
  const shape = buildShapeFromCompilePlan(step.cumulativePlan);
@@ -26728,7 +26961,7 @@ function getCachedGeometry(step) {
26728
26961
  geometryCache.delete(firstKey);
26729
26962
  }
26730
26963
  }
26731
- geometryCache.set(step.index, entry);
26964
+ geometryCache.set(cacheKey, entry);
26732
26965
  return entry;
26733
26966
  } catch {
26734
26967
  return null;
@@ -28914,7 +29147,7 @@ function generateReportInWorker(options) {
28914
29147
  return new Promise((resolve2, reject) => {
28915
29148
  const worker = new Worker(new URL(
28916
29149
  /* @vite-ignore */
28917
- "/assets/reportWorker-BLkuIoS8.js",
29150
+ "/assets/reportWorker-CU8RZ4O0.js",
28918
29151
  import.meta.url
28919
29152
  ), { type: "module" });
28920
29153
  const cleanup = () => {
@@ -32118,16 +32351,44 @@ const PHASE_CONFIG = {
32118
32351
  evaluating: { color: "#4a9eff", label: "Evaluating model" },
32119
32352
  serializing: { color: "#7c4dff", label: "Preparing display" },
32120
32353
  exporting: { color: "#4caf50", label: "Exporting geometry" },
32354
+ inspecting: { color: "#14b8a6", label: "Generating inspect view" },
32121
32355
  idle: { color: "#888", label: "" }
32122
32356
  };
32123
32357
  const PHASE_ORDER = ["kernel-init", "evaluating", "serializing"];
32124
- function EvaluationIndicator({ phase }) {
32358
+ function formatEvaluationBackendLabel(activeBackend, computeTarget) {
32359
+ const backend = activeBackend === "occt" ? "OCCT" : activeBackend === "manifold" ? "Manifold" : activeBackend === "truck" ? "Truck" : "kernel";
32360
+ return computeTarget === "server" ? `Server ${backend}` : `Local ${backend}`;
32361
+ }
32362
+ function constructionStepRoleSymbol(role) {
32363
+ if (role === "primitive") return "+";
32364
+ if (role === "operation") return "->";
32365
+ return "~";
32366
+ }
32367
+ function constructionStepPreview(constructionSteps, previewStepIndex) {
32368
+ const stepCount = constructionSteps.length;
32369
+ const displayStepIndex = stepCount > 0 ? (previewStepIndex % stepCount + stepCount) % stepCount : 0;
32370
+ return {
32371
+ displayStepIndex,
32372
+ previewStep: stepCount > 0 ? constructionSteps[displayStepIndex] : null,
32373
+ progress: stepCount > 1 ? (displayStepIndex + 1) / stepCount : 1
32374
+ };
32375
+ }
32376
+ function EvaluationIndicator({
32377
+ phase,
32378
+ label,
32379
+ constructionSteps = [],
32380
+ activeBackend,
32381
+ computeTarget
32382
+ }) {
32125
32383
  const [frame2, setFrame] = reactExports.useState(0);
32126
32384
  const [elapsed, setElapsed] = reactExports.useState(0);
32385
+ const [previewStepIndex, setPreviewStepIndex] = reactExports.useState(0);
32127
32386
  const startRef = reactExports.useRef(Date.now());
32387
+ const stepCount = constructionSteps.length;
32128
32388
  reactExports.useEffect(() => {
32129
32389
  startRef.current = Date.now();
32130
32390
  setElapsed(0);
32391
+ setPreviewStepIndex(0);
32131
32392
  }, [phase]);
32132
32393
  reactExports.useEffect(() => {
32133
32394
  const spinnerInterval = setInterval(() => setFrame((f2) => (f2 + 1) % BRAILLE_FRAMES.length), 80);
@@ -32137,9 +32398,17 @@ function EvaluationIndicator({ phase }) {
32137
32398
  clearInterval(timerInterval);
32138
32399
  };
32139
32400
  }, []);
32401
+ reactExports.useEffect(() => {
32402
+ setPreviewStepIndex(0);
32403
+ if (stepCount <= 1) return;
32404
+ const interval = setInterval(() => setPreviewStepIndex((i) => (i + 1) % stepCount), 700);
32405
+ return () => clearInterval(interval);
32406
+ }, [stepCount]);
32140
32407
  const config = PHASE_CONFIG[phase] ?? PHASE_CONFIG["evaluating"];
32141
32408
  const elapsedSec = (elapsed / 1e3).toFixed(1);
32142
- const phaseIdx = PHASE_ORDER.indexOf(phase);
32409
+ const phaseOrder = PHASE_ORDER.includes(phase) ? PHASE_ORDER : [phase];
32410
+ const phaseIdx = phaseOrder.indexOf(phase);
32411
+ const { displayStepIndex, previewStep, progress } = constructionStepPreview(constructionSteps, previewStepIndex);
32143
32412
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32144
32413
  "div",
32145
32414
  {
@@ -32147,51 +32416,368 @@ function EvaluationIndicator({ phase }) {
32147
32416
  position: "absolute",
32148
32417
  bottom: 16,
32149
32418
  right: 16,
32419
+ width: previewStep ? "min(360px, calc(100vw - 32px))" : void 0,
32150
32420
  background: "var(--fc-bgPanel)",
32151
32421
  border: "1px solid var(--fc-border)",
32152
32422
  borderRadius: 8,
32153
- padding: "8px 14px",
32423
+ padding: previewStep ? "10px 12px" : "8px 14px",
32154
32424
  pointerEvents: "none",
32155
- display: "flex",
32156
- alignItems: "center",
32157
- gap: 10,
32425
+ display: "grid",
32426
+ gap: previewStep ? 8 : 0,
32158
32427
  fontSize: 12,
32159
32428
  animation: "fc-fadein 0.2s ease-out",
32160
32429
  boxShadow: "0 4px 16px rgba(0,0,0,0.25)"
32161
32430
  },
32162
32431
  children: [
32163
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: config.color, fontSize: 16, fontWeight: 700, width: 16, textAlign: "center" }, children: BRAILLE_FRAMES[frame2] }),
32164
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--fc-text)", fontWeight: 500 }, children: config.label }),
32165
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { display: "flex", gap: 4, alignItems: "center", marginLeft: 2 }, children: PHASE_ORDER.map((p2, i) => {
32166
- var _a3;
32167
- return /* @__PURE__ */ jsxRuntimeExports.jsx(
32432
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, minWidth: 0 }, children: [
32433
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: config.color, fontSize: 16, fontWeight: 700, width: 16, textAlign: "center", flex: "0 0 auto" }, children: BRAILLE_FRAMES[frame2] }),
32434
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32168
32435
  "span",
32169
32436
  {
32170
32437
  style: {
32171
- width: 6,
32172
- height: 6,
32173
- borderRadius: "50%",
32174
- background: i <= phaseIdx ? ((_a3 = PHASE_CONFIG[p2]) == null ? void 0 : _a3.color) ?? "var(--fc-border)" : "var(--fc-border)",
32175
- transition: "background 0.3s ease",
32176
- animation: i === phaseIdx ? "fc-pulse 1.2s ease-in-out infinite" : void 0
32438
+ color: "var(--fc-text)",
32439
+ fontWeight: 500,
32440
+ minWidth: 0,
32441
+ overflow: "hidden",
32442
+ textOverflow: "ellipsis",
32443
+ whiteSpace: "nowrap"
32444
+ },
32445
+ children: label ?? config.label
32446
+ }
32447
+ ),
32448
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { display: "flex", gap: 4, alignItems: "center", marginLeft: "auto", flex: "0 0 auto" }, children: phaseOrder.map((p2, i) => {
32449
+ var _a3;
32450
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
32451
+ "span",
32452
+ {
32453
+ style: {
32454
+ width: 6,
32455
+ height: 6,
32456
+ borderRadius: "50%",
32457
+ background: i <= phaseIdx ? ((_a3 = PHASE_CONFIG[p2]) == null ? void 0 : _a3.color) ?? "var(--fc-border)" : "var(--fc-border)",
32458
+ transition: "background 0.3s ease",
32459
+ animation: i === phaseIdx ? "fc-pulse 1.2s ease-in-out infinite" : void 0
32460
+ }
32461
+ },
32462
+ p2
32463
+ );
32464
+ }) }),
32465
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-textDim)", fontVariantNumeric: "tabular-nums", fontSize: 11, flex: "0 0 auto" }, children: [
32466
+ elapsedSec,
32467
+ "s"
32468
+ ] })
32469
+ ] }),
32470
+ previewStep && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "grid", gap: 6, minWidth: 0 }, children: [
32471
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 }, children: [
32472
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32473
+ "span",
32474
+ {
32475
+ style: {
32476
+ color: "var(--fc-textDim)",
32477
+ fontSize: 10,
32478
+ textTransform: "uppercase",
32479
+ fontWeight: 700,
32480
+ letterSpacing: 0,
32481
+ flex: "0 0 auto"
32482
+ },
32483
+ children: "Cached build path"
32177
32484
  }
32178
- },
32179
- p2
32180
- );
32181
- }) }),
32182
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-textDim)", fontVariantNumeric: "tabular-nums", fontSize: 11 }, children: [
32183
- elapsedSec,
32184
- "s"
32485
+ ),
32486
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32487
+ "span",
32488
+ {
32489
+ style: {
32490
+ color: "var(--fc-textMuted)",
32491
+ fontSize: 11,
32492
+ minWidth: 0,
32493
+ overflow: "hidden",
32494
+ textOverflow: "ellipsis",
32495
+ whiteSpace: "nowrap"
32496
+ },
32497
+ children: formatEvaluationBackendLabel(activeBackend, computeTarget)
32498
+ }
32499
+ )
32500
+ ] }),
32501
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 8, minWidth: 0 }, children: [
32502
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
32503
+ "span",
32504
+ {
32505
+ style: {
32506
+ color: config.color,
32507
+ fontVariantNumeric: "tabular-nums",
32508
+ fontSize: 11,
32509
+ fontWeight: 700,
32510
+ flex: "0 0 auto",
32511
+ width: 46
32512
+ },
32513
+ children: [
32514
+ displayStepIndex + 1,
32515
+ "/",
32516
+ stepCount
32517
+ ]
32518
+ }
32519
+ ),
32520
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
32521
+ "span",
32522
+ {
32523
+ style: {
32524
+ color: "var(--fc-text)",
32525
+ minWidth: 0,
32526
+ overflow: "hidden",
32527
+ textOverflow: "ellipsis",
32528
+ whiteSpace: "nowrap"
32529
+ },
32530
+ children: [
32531
+ constructionStepRoleSymbol(previewStep.role),
32532
+ " ",
32533
+ previewStep.label
32534
+ ]
32535
+ }
32536
+ ),
32537
+ previewStep.displayAsTool && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: config.color, fontSize: 11, flex: "0 0 auto" }, children: "tool" })
32538
+ ] }),
32539
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { height: 3, borderRadius: 999, background: "var(--fc-borderLight)", overflow: "hidden" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32540
+ "div",
32541
+ {
32542
+ style: {
32543
+ width: `${Math.round(progress * 100)}%`,
32544
+ height: "100%",
32545
+ borderRadius: 999,
32546
+ background: config.color,
32547
+ transition: "width 0.2s ease-out"
32548
+ }
32549
+ }
32550
+ ) })
32185
32551
  ] })
32186
32552
  ]
32187
32553
  }
32188
32554
  );
32189
32555
  }
32556
+ const MIN_FIELD_GRID_SIZE = 18;
32557
+ const MAX_FIELD_GRID_SIZE = 32;
32558
+ const MAX_BLEND_SAMPLES = 12;
32559
+ const MAX_SEARCH_RADIUS = 5;
32560
+ const SAMPLE_RING_OFFSETS = [];
32561
+ for (let radius = 0; radius <= MAX_SEARCH_RADIUS; radius += 1) {
32562
+ const ring = [];
32563
+ for (let x = -radius; x <= radius; x += 1) {
32564
+ for (let y = -radius; y <= radius; y += 1) {
32565
+ for (let z = -radius; z <= radius; z += 1) {
32566
+ if (Math.max(Math.abs(x), Math.abs(y), Math.abs(z)) === radius) ring.push([x, y, z]);
32567
+ }
32568
+ }
32569
+ }
32570
+ SAMPLE_RING_OFFSETS.push(ring);
32571
+ }
32572
+ const INSPECT_HEATMAP_VERTEX_SHADER = `
32573
+ varying vec3 vLocalPosition;
32574
+ varying vec3 vViewDirection;
32575
+ varying vec3 vViewNormal;
32576
+ #include <clipping_planes_pars_vertex>
32577
+
32578
+ void main() {
32579
+ vLocalPosition = position;
32580
+ vViewNormal = normalize(normalMatrix * normal);
32581
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
32582
+ vViewDirection = normalize(-mvPosition.xyz);
32583
+ gl_Position = projectionMatrix * mvPosition;
32584
+ #include <clipping_planes_vertex>
32585
+ }
32586
+ `;
32587
+ const INSPECT_HEATMAP_FRAGMENT_SHADER = `
32588
+ uniform sampler2D uField;
32589
+ uniform float uGridSize;
32590
+ uniform vec3 uBoundsMin;
32591
+ uniform vec3 uBoundsSize;
32592
+ varying vec3 vLocalPosition;
32593
+ varying vec3 vViewDirection;
32594
+ varying vec3 vViewNormal;
32595
+ #include <clipping_planes_pars_fragment>
32596
+
32597
+ vec2 fieldUv(vec3 index) {
32598
+ vec2 atlasSize = vec2(uGridSize * uGridSize, uGridSize);
32599
+ float atlasX = index.x + index.z * uGridSize;
32600
+ return (vec2(atlasX, index.y) + 0.5) / atlasSize;
32601
+ }
32602
+
32603
+ vec3 fetchField(vec3 index) {
32604
+ vec3 bounded = clamp(index, vec3(0.0), vec3(uGridSize - 1.0));
32605
+ return texture2D(uField, fieldUv(bounded)).rgb;
32606
+ }
32607
+
32608
+ vec3 sampleField(vec3 position) {
32609
+ vec3 normalized = clamp((position - uBoundsMin) / max(uBoundsSize, vec3(0.000001)), vec3(0.0), vec3(1.0));
32610
+ vec3 coord = normalized * (uGridSize - 1.0);
32611
+ vec3 base = floor(coord);
32612
+ vec3 f = fract(coord);
32613
+
32614
+ vec3 c000 = fetchField(base + vec3(0.0, 0.0, 0.0));
32615
+ vec3 c100 = fetchField(base + vec3(1.0, 0.0, 0.0));
32616
+ vec3 c010 = fetchField(base + vec3(0.0, 1.0, 0.0));
32617
+ vec3 c110 = fetchField(base + vec3(1.0, 1.0, 0.0));
32618
+ vec3 c001 = fetchField(base + vec3(0.0, 0.0, 1.0));
32619
+ vec3 c101 = fetchField(base + vec3(1.0, 0.0, 1.0));
32620
+ vec3 c011 = fetchField(base + vec3(0.0, 1.0, 1.0));
32621
+ vec3 c111 = fetchField(base + vec3(1.0, 1.0, 1.0));
32622
+
32623
+ vec3 c00 = mix(c000, c100, f.x);
32624
+ vec3 c10 = mix(c010, c110, f.x);
32625
+ vec3 c01 = mix(c001, c101, f.x);
32626
+ vec3 c11 = mix(c011, c111, f.x);
32627
+ return mix(mix(c00, c10, f.y), mix(c01, c11, f.y), f.z);
32628
+ }
32629
+
32630
+ void main() {
32631
+ #include <clipping_planes_fragment>
32632
+ vec3 heat = sampleField(vLocalPosition);
32633
+ float rim = pow(1.0 - clamp(dot(normalize(vViewNormal), normalize(vViewDirection)), 0.0, 1.0), 2.0);
32634
+ vec3 color = heat * (0.88 + rim * 0.2) + vec3(0.025, 0.04, 0.055) * rim;
32635
+ gl_FragColor = vec4(color, 1.0);
32636
+ }
32637
+ `;
32638
+ function gridKey(x, y, z) {
32639
+ return `${x},${y},${z}`;
32640
+ }
32641
+ function buildGeometryBounds(geometry) {
32642
+ const position = geometry.getAttribute("position");
32643
+ if (!position || position.count === 0) return null;
32644
+ const bounds = new Box3().setFromBufferAttribute(position);
32645
+ if (bounds.isEmpty()) return null;
32646
+ const size = bounds.getSize(new Vector3());
32647
+ const pad = Math.max(size.x, size.y, size.z, 1) * 1e-5;
32648
+ bounds.expandByScalar(pad);
32649
+ return bounds;
32650
+ }
32651
+ function buildSampleGrid(pointCloud) {
32652
+ const sampleCount = Math.floor(pointCloud.positions.length / 3);
32653
+ if (sampleCount <= 0) return null;
32654
+ const bounds = new Box3();
32655
+ const point = new Vector3();
32656
+ for (let sample = 0; sample < sampleCount; sample += 1) {
32657
+ const offset = sample * 3;
32658
+ bounds.expandByPoint(point.set(pointCloud.positions[offset], pointCloud.positions[offset + 1], pointCloud.positions[offset + 2]));
32659
+ }
32660
+ const size = bounds.getSize(new Vector3());
32661
+ const maxDim = Math.max(size.x, size.y, size.z);
32662
+ const cellSize = maxDim > 0 ? Math.max(maxDim / Math.cbrt(sampleCount), 1e-6) : 1;
32663
+ const cells = /* @__PURE__ */ new Map();
32664
+ for (let sample = 0; sample < sampleCount; sample += 1) {
32665
+ const offset = sample * 3;
32666
+ const x = Math.floor((pointCloud.positions[offset] - bounds.min.x) / cellSize);
32667
+ const y = Math.floor((pointCloud.positions[offset + 1] - bounds.min.y) / cellSize);
32668
+ const z = Math.floor((pointCloud.positions[offset + 2] - bounds.min.z) / cellSize);
32669
+ const key = gridKey(x, y, z);
32670
+ const entries = cells.get(key);
32671
+ if (entries) {
32672
+ entries.push(sample);
32673
+ } else {
32674
+ cells.set(key, [sample]);
32675
+ }
32676
+ }
32677
+ return { origin: bounds.min, cellSize, cells };
32678
+ }
32679
+ function fieldGridSizeForSampleCount(sampleCount) {
32680
+ if (sampleCount <= 0) return MIN_FIELD_GRID_SIZE;
32681
+ const scaled = Math.ceil(Math.cbrt(sampleCount) * 2.6);
32682
+ return Math.max(MIN_FIELD_GRID_SIZE, Math.min(MAX_FIELD_GRID_SIZE, scaled));
32683
+ }
32684
+ function distanceSquaredToSample(pointCloud, sample, point) {
32685
+ const offset = sample * 3;
32686
+ return (pointCloud.positions[offset] - point.x) ** 2 + (pointCloud.positions[offset + 1] - point.y) ** 2 + (pointCloud.positions[offset + 2] - point.z) ** 2;
32687
+ }
32688
+ function nearestSamples(point, pointCloud, sampleGrid) {
32689
+ const baseX = Math.floor((point.x - sampleGrid.origin.x) / sampleGrid.cellSize);
32690
+ const baseY = Math.floor((point.y - sampleGrid.origin.y) / sampleGrid.cellSize);
32691
+ const baseZ = Math.floor((point.z - sampleGrid.origin.z) / sampleGrid.cellSize);
32692
+ const found = [];
32693
+ for (const ring of SAMPLE_RING_OFFSETS) {
32694
+ for (const [dx, dy, dz] of ring) {
32695
+ const entries = sampleGrid.cells.get(gridKey(baseX + dx, baseY + dy, baseZ + dz));
32696
+ if (!entries) continue;
32697
+ for (const sample of entries) {
32698
+ found.push({ sample, distSq: distanceSquaredToSample(pointCloud, sample, point) });
32699
+ }
32700
+ }
32701
+ if (found.length >= MAX_BLEND_SAMPLES) break;
32702
+ }
32703
+ if (found.length === 0) {
32704
+ const sampleCount = Math.floor(pointCloud.positions.length / 3);
32705
+ for (let sample = 0; sample < sampleCount; sample += 1) {
32706
+ found.push({ sample, distSq: distanceSquaredToSample(pointCloud, sample, point) });
32707
+ }
32708
+ }
32709
+ found.sort((a2, b2) => a2.distSq - b2.distSq);
32710
+ return found.slice(0, MAX_BLEND_SAMPLES);
32711
+ }
32712
+ function blendedColorAt(point, pointCloud, sampleGrid) {
32713
+ const samples = nearestSamples(point, pointCloud, sampleGrid);
32714
+ if (samples.length === 0) return [90, 90, 90];
32715
+ const sigma = Math.max(sampleGrid.cellSize * 1.65, 1e-6);
32716
+ const sigmaSq = sigma * sigma;
32717
+ let weightSum = 0;
32718
+ let r2 = 0;
32719
+ let g2 = 0;
32720
+ let b2 = 0;
32721
+ for (const { sample, distSq } of samples) {
32722
+ const colorOffset = sample * 3;
32723
+ const weight = Math.exp(-distSq / (2 * sigmaSq)) + 1e-4 / Math.max(distSq, 1e-8);
32724
+ weightSum += weight;
32725
+ r2 += (pointCloud.colors[colorOffset] ?? 0.35) * 255 * weight;
32726
+ g2 += (pointCloud.colors[colorOffset + 1] ?? 0.35) * 255 * weight;
32727
+ b2 += (pointCloud.colors[colorOffset + 2] ?? 0.35) * 255 * weight;
32728
+ }
32729
+ if (weightSum <= 0) return [90, 90, 90];
32730
+ return [r2 / weightSum, g2 / weightSum, b2 / weightSum];
32731
+ }
32732
+ function buildInspectHeatmapField(geometry, pointCloud) {
32733
+ const bounds = buildGeometryBounds(geometry);
32734
+ const sampleGrid = buildSampleGrid(pointCloud);
32735
+ if (!bounds || !sampleGrid) return null;
32736
+ const gridSize = fieldGridSizeForSampleCount(Math.floor(pointCloud.positions.length / 3));
32737
+ const boundsSize = bounds.getSize(new Vector3());
32738
+ const data = new Uint8Array(gridSize * gridSize * gridSize * 4);
32739
+ const point = new Vector3();
32740
+ let dataOffset = 0;
32741
+ for (let y = 0; y < gridSize; y += 1) {
32742
+ for (let z = 0; z < gridSize; z += 1) {
32743
+ for (let x = 0; x < gridSize; x += 1) {
32744
+ point.set(
32745
+ bounds.min.x + boundsSize.x * x / (gridSize - 1),
32746
+ bounds.min.y + boundsSize.y * y / (gridSize - 1),
32747
+ bounds.min.z + boundsSize.z * z / (gridSize - 1)
32748
+ );
32749
+ const [r2, g2, b2] = blendedColorAt(point, pointCloud, sampleGrid);
32750
+ data[dataOffset] = Math.max(0, Math.min(255, Math.round(r2)));
32751
+ data[dataOffset + 1] = Math.max(0, Math.min(255, Math.round(g2)));
32752
+ data[dataOffset + 2] = Math.max(0, Math.min(255, Math.round(b2)));
32753
+ data[dataOffset + 3] = 255;
32754
+ dataOffset += 4;
32755
+ }
32756
+ }
32757
+ }
32758
+ const texture = new DataTexture(data, gridSize * gridSize, gridSize, RGBAFormat, UnsignedByteType);
32759
+ texture.minFilter = NearestFilter;
32760
+ texture.magFilter = NearestFilter;
32761
+ texture.generateMipmaps = false;
32762
+ texture.unpackAlignment = 1;
32763
+ texture.needsUpdate = true;
32764
+ return {
32765
+ texture,
32766
+ boundsMin: bounds.min,
32767
+ boundsSize,
32768
+ gridSize
32769
+ };
32770
+ }
32190
32771
  function ForgeObject({
32191
32772
  obj,
32192
32773
  settings,
32193
32774
  renderStyle,
32194
32775
  renderMode,
32776
+ inspectChannel = "none",
32777
+ inspectDisplayMode = "heatmap",
32778
+ inspectColor,
32779
+ inspectMeshColors,
32780
+ inspectPointCloud,
32195
32781
  isInteracting,
32196
32782
  matrix,
32197
32783
  isHovered,
@@ -32228,14 +32814,56 @@ function ForgeObject({
32228
32814
  };
32229
32815
  }
32230
32816
  }, [obj.shape]);
32817
+ const inspectPointGeo = reactExports.useMemo(() => {
32818
+ if (!inspectPointCloud) return null;
32819
+ const geometry = new BufferGeometry();
32820
+ geometry.setAttribute("position", new BufferAttribute(inspectPointCloud.positions, 3));
32821
+ geometry.setAttribute("color", new BufferAttribute(inspectPointCloud.colors, 3));
32822
+ return geometry;
32823
+ }, [inspectPointCloud]);
32824
+ const inspectMeshColorGeo = reactExports.useMemo(() => {
32825
+ if (!solidGeo || !inspectMeshColors) return null;
32826
+ const position = solidGeo.getAttribute("position");
32827
+ if (!position || inspectMeshColors.length !== position.count * 3) return null;
32828
+ const geometry = solidGeo.clone();
32829
+ geometry.setAttribute("color", new BufferAttribute(inspectMeshColors, 3));
32830
+ return geometry;
32831
+ }, [inspectMeshColors, solidGeo]);
32832
+ const isScalarInspect = inspectChannel === "thickness" || inspectChannel === "roughness";
32833
+ const inspectHeatmapField = reactExports.useMemo(() => {
32834
+ if (!isScalarInspect || !solidGeo || !inspectPointCloud) return null;
32835
+ return buildInspectHeatmapField(solidGeo, inspectPointCloud);
32836
+ }, [inspectPointCloud, isScalarInspect, solidGeo]);
32231
32837
  reactExports.useEffect(() => {
32232
32838
  return () => {
32233
32839
  solidGeo == null ? void 0 : solidGeo.dispose();
32234
32840
  edgesGeo == null ? void 0 : edgesGeo.dispose();
32235
32841
  };
32236
32842
  }, [edgesGeo, solidGeo]);
32843
+ reactExports.useEffect(() => {
32844
+ return () => {
32845
+ inspectPointGeo == null ? void 0 : inspectPointGeo.dispose();
32846
+ };
32847
+ }, [inspectPointGeo]);
32848
+ reactExports.useEffect(() => {
32849
+ return () => {
32850
+ inspectMeshColorGeo == null ? void 0 : inspectMeshColorGeo.dispose();
32851
+ };
32852
+ }, [inspectMeshColorGeo]);
32853
+ reactExports.useEffect(() => {
32854
+ return () => {
32855
+ inspectHeatmapField == null ? void 0 : inspectHeatmapField.texture.dispose();
32856
+ };
32857
+ }, [inspectHeatmapField]);
32237
32858
  if (!solidGeo || !settings.visible) return null;
32238
32859
  const effectiveRenderMode = isInteracting && renderMode === "overlay" ? "solid" : renderMode;
32860
+ const isInspecting = inspectChannel !== "none";
32861
+ const showInspectHeatmap = Boolean(
32862
+ isScalarInspect && inspectHeatmapField && (inspectDisplayMode === "heatmap" || inspectDisplayMode === "both")
32863
+ );
32864
+ const showInspectPoints = Boolean(
32865
+ isScalarInspect && inspectPointGeo && (inspectDisplayMode === "points" || inspectDisplayMode === "both")
32866
+ );
32239
32867
  const renderStylePreset = getRenderStylePreset(renderStyle);
32240
32868
  const materialDefaults = renderStylePreset.material;
32241
32869
  const authoredMaterialOpacity = (_a3 = obj.materialProps) == null ? void 0 : _a3.opacity;
@@ -32247,10 +32875,12 @@ function ForgeObject({
32247
32875
  const materialOpacity = Math.min(meshOpacity, styleOpacity);
32248
32876
  const transmission = authoredMaterialTransmission ?? transparentDefaults.transmission;
32249
32877
  const isTransparent = materialOpacity < 1 || transmission > 0;
32250
- const showSolid = effectiveRenderMode !== "wireframe";
32251
- const showEdges = effectiveRenderMode === "overlay";
32252
- const showWire = effectiveRenderMode === "wireframe";
32878
+ const showSolid = isInspecting || effectiveRenderMode !== "wireframe";
32879
+ const showEdges = !isInspecting && effectiveRenderMode === "overlay";
32880
+ const showWire = !isInspecting && effectiveRenderMode === "wireframe";
32253
32881
  const effectiveClippingPlanes = clippingPlanes ?? [];
32882
+ const inspectSolidGeo = inspectMeshColorGeo ?? solidGeo;
32883
+ const hasInspectMeshColors = inspectMeshColorGeo !== null;
32254
32884
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32255
32885
  "group",
32256
32886
  {
@@ -32263,7 +32893,70 @@ function ForgeObject({
32263
32893
  onDoubleClick,
32264
32894
  onContextMenu,
32265
32895
  children: [
32266
- showSolid && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32896
+ showSolid && (inspectChannel === "mask" || inspectChannel === "connectivity" || inspectChannel === "distance") && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: inspectSolidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32897
+ "meshBasicMaterial",
32898
+ {
32899
+ color: hasInspectMeshColors ? "#ffffff" : inspectColor ?? settings.color,
32900
+ vertexColors: hasInspectMeshColors,
32901
+ side: DoubleSide,
32902
+ toneMapped: false,
32903
+ clippingPlanes: effectiveClippingPlanes
32904
+ }
32905
+ ) }),
32906
+ showSolid && isScalarInspect && !showInspectHeatmap && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32907
+ "meshBasicMaterial",
32908
+ {
32909
+ color: "#26313a",
32910
+ transparent: true,
32911
+ opacity: 0.24,
32912
+ side: DoubleSide,
32913
+ depthWrite: true,
32914
+ toneMapped: false,
32915
+ clippingPlanes: effectiveClippingPlanes
32916
+ }
32917
+ ) }),
32918
+ showSolid && showInspectHeatmap && inspectHeatmapField && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, renderOrder: 4, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32919
+ "shaderMaterial",
32920
+ {
32921
+ vertexShader: INSPECT_HEATMAP_VERTEX_SHADER,
32922
+ fragmentShader: INSPECT_HEATMAP_FRAGMENT_SHADER,
32923
+ uniforms: {
32924
+ uField: { value: inspectHeatmapField.texture },
32925
+ uGridSize: { value: inspectHeatmapField.gridSize },
32926
+ uBoundsMin: { value: inspectHeatmapField.boundsMin },
32927
+ uBoundsSize: { value: inspectHeatmapField.boundsSize }
32928
+ },
32929
+ side: DoubleSide,
32930
+ toneMapped: false,
32931
+ clippingPlanes: effectiveClippingPlanes
32932
+ }
32933
+ ) }),
32934
+ showSolid && showInspectPoints && inspectPointGeo && /* @__PURE__ */ jsxRuntimeExports.jsx("points", { geometry: inspectPointGeo, raycast: () => null, renderOrder: 5, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32935
+ "pointsMaterial",
32936
+ {
32937
+ size: 3,
32938
+ sizeAttenuation: false,
32939
+ vertexColors: true,
32940
+ depthTest: true,
32941
+ depthWrite: false,
32942
+ toneMapped: false,
32943
+ clippingPlanes: effectiveClippingPlanes
32944
+ }
32945
+ ) }),
32946
+ showSolid && inspectChannel === "collisions" && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32947
+ "meshPhysicalMaterial",
32948
+ {
32949
+ color: rgbToHex(COLLISION_SOURCE_COLOR),
32950
+ roughness: 0.75,
32951
+ metalness: 0,
32952
+ side: DoubleSide,
32953
+ transparent: true,
32954
+ opacity: COLLISION_SOURCE_OPACITY,
32955
+ depthWrite: false,
32956
+ clippingPlanes: effectiveClippingPlanes
32957
+ }
32958
+ ) }),
32959
+ showSolid && inspectChannel === "none" && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32267
32960
  "meshPhysicalMaterial",
32268
32961
  {
32269
32962
  color: settings.color,
@@ -32288,7 +32981,7 @@ function ForgeObject({
32288
32981
  clippingPlanes: effectiveClippingPlanes
32289
32982
  }
32290
32983
  ) }),
32291
- showSolid && hasAuthoredTransparency && renderStylePreset.glassShell.enabled && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, scale: renderStylePreset.glassShell.scale, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32984
+ showSolid && inspectChannel === "none" && hasAuthoredTransparency && renderStylePreset.glassShell.enabled && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, scale: renderStylePreset.glassShell.scale, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32292
32985
  "meshBasicMaterial",
32293
32986
  {
32294
32987
  color: renderStylePreset.glassShell.color,
@@ -37855,6 +38548,9 @@ function useViewportState() {
37855
38548
  const files = useForgeStore((s) => s.files);
37856
38549
  const renderMode = useForgeStore((s) => s.renderMode);
37857
38550
  const renderStyle = useForgeStore((s) => s.renderStyle);
38551
+ const inspectChannel = useForgeStore((s) => s.inspectChannel);
38552
+ const inspectDisplayMode = useForgeStore((s) => s.inspectDisplayMode);
38553
+ const inspectPointSampleCount = useForgeStore((s) => s.inspectPointSampleCount);
37858
38554
  const projectionMode = useForgeStore((s) => s.projectionMode);
37859
38555
  const gridEnabled = useForgeStore((s) => s.gridEnabled);
37860
38556
  const gridSize = useForgeStore((s) => s.gridSize);
@@ -38181,6 +38877,9 @@ function useViewportState() {
38181
38877
  files,
38182
38878
  renderMode,
38183
38879
  renderStyle,
38880
+ inspectChannel,
38881
+ inspectDisplayMode,
38882
+ inspectPointSampleCount,
38184
38883
  projectionMode,
38185
38884
  gridEnabled,
38186
38885
  gridSize,
@@ -39557,6 +40256,276 @@ function RenderLabelsOverlay({ labels }) {
39557
40256
  if (labels.length === 0) return null;
39558
40257
  return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { renderOrder: 12, children: labels.map((label) => /* @__PURE__ */ jsxRuntimeExports.jsx(RenderLabelItem, { label }, label.id)) });
39559
40258
  }
40259
+ class InspectWorkerClient {
40260
+ constructor(workerFactory = () => new Worker(new URL(
40261
+ /* @vite-ignore */
40262
+ "/assets/inspectWorker-Dll4eVyD.js",
40263
+ import.meta.url
40264
+ ), { type: "module" })) {
40265
+ __publicField(this, "worker", null);
40266
+ __publicField(this, "reqId", 0);
40267
+ __publicField(this, "pending", null);
40268
+ this.workerFactory = workerFactory;
40269
+ }
40270
+ getWorker() {
40271
+ if (this.worker) return this.worker;
40272
+ this.worker = this.workerFactory();
40273
+ this.worker.onmessage = (event) => {
40274
+ const data = event.data;
40275
+ const pending = this.pending;
40276
+ if (!pending) return;
40277
+ this.pending = null;
40278
+ if (data.type === "inspect-error") {
40279
+ pending.reject(new Error(data.payload.message));
40280
+ } else {
40281
+ pending.resolve(data.payload.result);
40282
+ }
40283
+ };
40284
+ this.worker.onerror = (event) => {
40285
+ var _a3, _b2;
40286
+ const error = new Error(event.message || "Inspect worker failed unexpectedly.");
40287
+ (_a3 = this.pending) == null ? void 0 : _a3.reject(error);
40288
+ this.pending = null;
40289
+ (_b2 = this.worker) == null ? void 0 : _b2.terminate();
40290
+ this.worker = null;
40291
+ };
40292
+ return this.worker;
40293
+ }
40294
+ replaceActiveWorker() {
40295
+ var _a3, _b2;
40296
+ (_a3 = this.pending) == null ? void 0 : _a3.reject(new Error("cancelled"));
40297
+ this.pending = null;
40298
+ (_b2 = this.worker) == null ? void 0 : _b2.terminate();
40299
+ this.worker = null;
40300
+ }
40301
+ analyze(payload) {
40302
+ if (this.pending) this.replaceActiveWorker();
40303
+ const reqId = ++this.reqId;
40304
+ const request = {
40305
+ type: "analyze",
40306
+ payload: {
40307
+ reqId,
40308
+ ...payload
40309
+ }
40310
+ };
40311
+ const transfers = payload.objects.flatMap((object) => object.positions ? [object.positions.buffer] : []);
40312
+ return new Promise((resolve2, reject) => {
40313
+ this.pending = { resolve: resolve2, reject };
40314
+ this.getWorker().postMessage(request, transfers);
40315
+ });
40316
+ }
40317
+ dispose() {
40318
+ this.replaceActiveWorker();
40319
+ }
40320
+ }
40321
+ const inspectWorkerClient = new InspectWorkerClient();
40322
+ const WORKER_CHANNELS = /* @__PURE__ */ new Set(["thickness", "roughness", "connectivity", "distance"]);
40323
+ const SCALAR_WORKER_CHANNELS = /* @__PURE__ */ new Set(["thickness", "roughness"]);
40324
+ const MESH_COMPONENT_WORKER_CHANNELS = /* @__PURE__ */ new Set(["connectivity", "distance"]);
40325
+ const VIEWPORT_SCALAR_SCENE_BUDGET_OBJECTS = 15;
40326
+ class InspectBuildCancelledError extends Error {
40327
+ constructor() {
40328
+ super("cancelled");
40329
+ }
40330
+ }
40331
+ function isScalarWorkerChannel(channel) {
40332
+ return SCALAR_WORKER_CHANNELS.has(channel);
40333
+ }
40334
+ function needsMeshComponents(channel) {
40335
+ return MESH_COMPONENT_WORKER_CHANNELS.has(channel);
40336
+ }
40337
+ function viewportScalarSamplesPerObject(objectCount, inspectPointSampleCount) {
40338
+ const perObjectCap = resolveInspectPointSampleCount(inspectPointSampleCount);
40339
+ if (objectCount <= 0) return perObjectCap;
40340
+ const sceneBudget = perObjectCap * VIEWPORT_SCALAR_SCENE_BUDGET_OBJECTS;
40341
+ const budgeted = Math.floor(sceneBudget / objectCount);
40342
+ return Math.max(INSPECT_POINT_SAMPLE_COUNT_MIN, Math.min(perObjectCap, budgeted));
40343
+ }
40344
+ function yieldToBrowser() {
40345
+ if (typeof window === "undefined") return Promise.resolve();
40346
+ return new Promise((resolve2) => {
40347
+ const maybeIdleWindow = window;
40348
+ if (typeof maybeIdleWindow.requestIdleCallback === "function") {
40349
+ maybeIdleWindow.requestIdleCallback(() => resolve2(), { timeout: 50 });
40350
+ return;
40351
+ }
40352
+ window.setTimeout(resolve2, 0);
40353
+ });
40354
+ }
40355
+ function transformedBounds(obj, matrix) {
40356
+ if (!obj.shape) return null;
40357
+ try {
40358
+ const bbox = obj.shape.boundingBox();
40359
+ const bounds = new Box3();
40360
+ if (!expandBoundsByTransformedAabb(bounds, bbox.min, bbox.max, matrix)) return null;
40361
+ if (!isFiniteBox3(bounds)) return null;
40362
+ return {
40363
+ min: [bounds.min.x, bounds.min.y, bounds.min.z],
40364
+ max: [bounds.max.x, bounds.max.y, bounds.max.z]
40365
+ };
40366
+ } catch {
40367
+ return null;
40368
+ }
40369
+ }
40370
+ function clonePositions(obj, matrix) {
40371
+ var _a3;
40372
+ if (!obj.shape) return void 0;
40373
+ const geometry = obj.shape instanceof FrozenShape ? null : shapeToGeometry(obj.shape);
40374
+ try {
40375
+ const source = obj.shape instanceof FrozenShape ? obj.shape.getPrecomputedGeometry().positions : (_a3 = geometry == null ? void 0 : geometry.solid.getAttribute("position")) == null ? void 0 : _a3.array;
40376
+ if (!source) return void 0;
40377
+ const positions = new Float32Array(source);
40378
+ if (matrix) {
40379
+ const point = new Vector3();
40380
+ for (let index = 0; index < positions.length; index += 3) {
40381
+ point.set(positions[index], positions[index + 1], positions[index + 2]).applyMatrix4(matrix);
40382
+ positions[index] = point.x;
40383
+ positions[index + 1] = point.y;
40384
+ positions[index + 2] = point.z;
40385
+ }
40386
+ }
40387
+ return positions;
40388
+ } finally {
40389
+ geometry == null ? void 0 : geometry.solid.dispose();
40390
+ geometry == null ? void 0 : geometry.edges.dispose();
40391
+ }
40392
+ }
40393
+ async function buildWorkerObjects(args) {
40394
+ var _a3;
40395
+ const needsMesh = isScalarWorkerChannel(args.inspectChannel) || needsMeshComponents(args.inspectChannel);
40396
+ const out = [];
40397
+ for (const obj of args.objects) {
40398
+ if (args.isCancelled()) throw new InspectBuildCancelledError();
40399
+ if (!obj.shape) continue;
40400
+ if (((_a3 = args.objectSettings[obj.id]) == null ? void 0 : _a3.visible) === false) continue;
40401
+ await yieldToBrowser();
40402
+ if (args.isCancelled()) throw new InspectBuildCancelledError();
40403
+ const matrix = args.objectMatrices[obj.id] ?? new Matrix4();
40404
+ const bbox = transformedBounds(obj, matrix);
40405
+ if (!bbox) continue;
40406
+ if (args.isCancelled()) throw new InspectBuildCancelledError();
40407
+ out.push({
40408
+ id: obj.id,
40409
+ name: obj.name,
40410
+ groupName: obj.groupName,
40411
+ treePath: obj.treePath,
40412
+ mock: obj.mock,
40413
+ bbox,
40414
+ ...needsMesh ? { positions: clonePositions(obj, needsMeshComponents(args.inspectChannel) ? matrix : void 0) } : {}
40415
+ });
40416
+ await yieldToBrowser();
40417
+ }
40418
+ return out;
40419
+ }
40420
+ function analyzePayloadFor(channel, objects, inspectPointSampleCount) {
40421
+ const maxSamplesPerObject = viewportScalarSamplesPerObject(objects.length, inspectPointSampleCount);
40422
+ if (channel === "thickness") {
40423
+ return {
40424
+ channel,
40425
+ objects,
40426
+ thickness: {
40427
+ maxSamplesPerObject
40428
+ }
40429
+ };
40430
+ }
40431
+ if (channel === "roughness") {
40432
+ return {
40433
+ channel,
40434
+ objects,
40435
+ roughness: {
40436
+ maxSamplesPerObject
40437
+ }
40438
+ };
40439
+ }
40440
+ return { channel, objects };
40441
+ }
40442
+ function resultToState(channel, result) {
40443
+ return {
40444
+ status: "ready",
40445
+ channel,
40446
+ objectColors: result.objectColors,
40447
+ meshColors: Object.fromEntries(result.meshColorObjects.map((object) => [object.objectId, object.colors])),
40448
+ pointClouds: Object.fromEntries(
40449
+ result.pointObjects.map((object) => [
40450
+ object.objectId,
40451
+ {
40452
+ sampleCount: object.sampleCount,
40453
+ positions: object.positions,
40454
+ colors: object.colors
40455
+ }
40456
+ ])
40457
+ ),
40458
+ warnings: result.warnings,
40459
+ error: null
40460
+ };
40461
+ }
40462
+ function useInspectWorkerAnalysis(args) {
40463
+ const [state2, setState] = reactExports.useState({
40464
+ status: "idle",
40465
+ channel: "none",
40466
+ objectColors: {},
40467
+ meshColors: {},
40468
+ pointClouds: {},
40469
+ warnings: [],
40470
+ error: null
40471
+ });
40472
+ reactExports.useEffect(() => {
40473
+ if (!WORKER_CHANNELS.has(args.inspectChannel)) {
40474
+ inspectWorkerClient.dispose();
40475
+ setState({
40476
+ status: "idle",
40477
+ channel: args.inspectChannel,
40478
+ objectColors: {},
40479
+ meshColors: {},
40480
+ pointClouds: {},
40481
+ warnings: [],
40482
+ error: null
40483
+ });
40484
+ return;
40485
+ }
40486
+ let cancelled = false;
40487
+ const channel = args.inspectChannel;
40488
+ setState({
40489
+ status: "loading",
40490
+ channel: args.inspectChannel,
40491
+ objectColors: {},
40492
+ meshColors: {},
40493
+ pointClouds: {},
40494
+ warnings: [],
40495
+ error: null
40496
+ });
40497
+ void (async () => {
40498
+ try {
40499
+ const objects = await buildWorkerObjects({
40500
+ ...args,
40501
+ isCancelled: () => cancelled
40502
+ });
40503
+ if (cancelled) return;
40504
+ const result = await inspectWorkerClient.analyze(analyzePayloadFor(channel, objects, args.inspectPointSampleCount));
40505
+ if (cancelled) return;
40506
+ setState(resultToState(args.inspectChannel, result));
40507
+ } catch (error) {
40508
+ if (cancelled || error instanceof InspectBuildCancelledError || error instanceof Error && error.message === "cancelled") {
40509
+ return;
40510
+ }
40511
+ setState({
40512
+ status: "error",
40513
+ channel: args.inspectChannel,
40514
+ objectColors: {},
40515
+ meshColors: {},
40516
+ pointClouds: {},
40517
+ warnings: [],
40518
+ error: error instanceof Error ? error.message : String(error)
40519
+ });
40520
+ }
40521
+ })();
40522
+ return () => {
40523
+ cancelled = true;
40524
+ inspectWorkerClient.dispose();
40525
+ };
40526
+ }, [args.inspectChannel, args.inspectPointSampleCount, args.sceneVersion, args.objects, args.objectSettings, args.objectMatrices]);
40527
+ return state2;
40528
+ }
39560
40529
  function panelNumber(value) {
39561
40530
  if (!Number.isFinite(value)) return String(value);
39562
40531
  if (Number.isInteger(value)) return String(value);
@@ -39582,6 +40551,20 @@ function edgeCurveLabel(edge) {
39582
40551
  return "segment";
39583
40552
  }
39584
40553
  }
40554
+ function inspectChannelLabel(channel) {
40555
+ switch (channel) {
40556
+ case "connectivity":
40557
+ return "Connect";
40558
+ case "distance":
40559
+ return "Distance";
40560
+ case "thickness":
40561
+ return "Thickness";
40562
+ case "roughness":
40563
+ return "Roughness";
40564
+ default:
40565
+ return "Inspect";
40566
+ }
40567
+ }
39585
40568
  function edgeBelongsToFace(edge, faceName) {
39586
40569
  var _a3;
39587
40570
  return edge.faceName === faceName || ((_a3 = edge.curve) == null ? void 0 : _a3.faceName) === faceName || edge.name.startsWith(`${faceName}:`);
@@ -39669,6 +40652,8 @@ function RenderStyleExposure({ exposure }) {
39669
40652
  function Viewport() {
39670
40653
  var _a3, _b2, _c;
39671
40654
  const activeFile = useForgeStore((s) => s.activeFile);
40655
+ const activeBackend = useForgeStore((s) => s.activeBackend);
40656
+ const computeTarget = useForgeStore((s) => s.computeTarget);
39672
40657
  const isSvgActive = !!activeFile && activeFile.toLowerCase().endsWith(".svg");
39673
40658
  const state2 = useViewportState();
39674
40659
  const {
@@ -39677,6 +40662,9 @@ function Viewport() {
39677
40662
  evaluationPhase,
39678
40663
  renderMode,
39679
40664
  renderStyle,
40665
+ inspectChannel,
40666
+ inspectDisplayMode,
40667
+ inspectPointSampleCount,
39680
40668
  projectionMode,
39681
40669
  gridEnabled,
39682
40670
  gridSize,
@@ -39760,6 +40748,7 @@ function Viewport() {
39760
40748
  const contextMenuRef = reactExports.useRef(null);
39761
40749
  const t2 = themes[themeName];
39762
40750
  const renderStylePreset = reactExports.useMemo(() => getRenderStylePreset(renderStyle), [renderStyle]);
40751
+ const inspectChannelActive = inspectChannel !== "none";
39763
40752
  const hasVisibleSdfObjects = objects.some((obj) => {
39764
40753
  var _a4;
39765
40754
  return obj.sdf && (((_a4 = objectSettings[obj.id]) == null ? void 0 : _a4.visible) ?? true);
@@ -39776,6 +40765,27 @@ function Viewport() {
39776
40765
  }
39777
40766
  return next;
39778
40767
  }, [constructionGhost, focusedObjectIdSet, objectSettings, objects]);
40768
+ const inspectAnalysis = useInspectWorkerAnalysis({
40769
+ inspectChannel,
40770
+ inspectPointSampleCount,
40771
+ sceneVersion,
40772
+ objects,
40773
+ objectSettings,
40774
+ objectMatrices
40775
+ });
40776
+ const inspectLoading = inspectChannelActive && inspectAnalysis.status === "loading";
40777
+ const viewportBusyPhase = isEvaluating || evaluationPhase === "exporting" ? evaluationPhase : inspectLoading ? "inspecting" : null;
40778
+ const viewportBusyLabel = inspectLoading ? `Generating ${inspectChannelLabel(inspectChannel)} inspect view` : void 0;
40779
+ const rebuildHistorySteps = reactExports.useMemo(() => {
40780
+ if (!isEvaluating || historyMode) return [];
40781
+ const historyObjects = [];
40782
+ for (const obj of objects) {
40783
+ if (!obj.shape) continue;
40784
+ const plan = getShapeCompilePlan(obj.shape);
40785
+ if (plan) historyObjects.push({ id: obj.id, plan });
40786
+ }
40787
+ return historyObjects.length > 0 ? linearizeMultiObjectSteps(historyObjects) : [];
40788
+ }, [historyMode, isEvaluating, objects]);
39779
40789
  const handlers = useViewportHandlers({
39780
40790
  containerRef,
39781
40791
  contextMenuRef,
@@ -39839,7 +40849,7 @@ function Viewport() {
39839
40849
  Canvas,
39840
40850
  {
39841
40851
  style: {
39842
- background: renderStyle === "classic" ? t2.viewportBg : renderStylePreset.background,
40852
+ background: inspectChannelActive ? "#000000" : renderStyle === "classic" ? t2.viewportBg : renderStylePreset.background,
39843
40853
  cursor: measureMode ? "crosshair" : "default"
39844
40854
  },
39845
40855
  dpr: canvasDpr,
@@ -39907,8 +40917,8 @@ function Viewport() {
39907
40917
  )
39908
40918
  ] }),
39909
40919
  /* @__PURE__ */ jsxRuntimeExports.jsx(ClippingManager, { active: hasAnyObjectCutPlanes }),
39910
- sectionExplorerEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(SectionExplorerGizmo, { size: sectionGuideSize }),
39911
- sectionPlaneGuidesEnabled && activeCutPlaneDefs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
40920
+ !inspectChannelActive && sectionExplorerEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(SectionExplorerGizmo, { size: sectionGuideSize }),
40921
+ !inspectChannelActive && sectionPlaneGuidesEnabled && activeCutPlaneDefs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
39912
40922
  SectionPlaneGuides,
39913
40923
  {
39914
40924
  cutPlanes: scriptCutPlaneDefs,
@@ -39940,6 +40950,11 @@ function Viewport() {
39940
40950
  settings: effectiveSettings,
39941
40951
  renderStyle,
39942
40952
  renderMode,
40953
+ inspectChannel,
40954
+ inspectDisplayMode,
40955
+ inspectColor: inspectAnalysis.objectColors[obj.id] ?? maskColorForIndex(objIndex),
40956
+ inspectMeshColors: inspectAnalysis.meshColors[obj.id],
40957
+ inspectPointCloud: inspectAnalysis.pointClouds[obj.id],
39943
40958
  isInteracting: isViewportInteracting,
39944
40959
  matrix,
39945
40960
  isHovered,
@@ -40004,7 +41019,8 @@ function Viewport() {
40004
41019
  }
40005
41020
  return null;
40006
41021
  }),
40007
- /* @__PURE__ */ jsxRuntimeExports.jsx(
41022
+ inspectChannel === "collisions" && /* @__PURE__ */ jsxRuntimeExports.jsx(CollisionInspectionOverlay, { objects, objectSettings, objectMatrices }),
41023
+ !inspectChannelActive && /* @__PURE__ */ jsxRuntimeExports.jsx(
40008
41024
  SectionCaps,
40009
41025
  {
40010
41026
  sceneVersion,
@@ -40016,17 +41032,17 @@ function Viewport() {
40016
41032
  isInteracting: isViewportInteracting
40017
41033
  }
40018
41034
  ),
40019
- constructionGhost && /* @__PURE__ */ jsxRuntimeExports.jsx(ConstructionGhostOverlay, { matrix: constructionGhostMatrix }),
40020
- historyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(ConstructionHistoryOverlay, { objectMatrices, objectSettings }),
40021
- hoveredJointOverlay && /* @__PURE__ */ jsxRuntimeExports.jsx(HoveredJointOverlay, { state: hoveredJointOverlay, config: jointOverlayConfig }),
40022
- dimensionsVisible && dimensions.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx(DimensionAnnotation, { def: d, lengthUnit }, d.id)),
40023
- /* @__PURE__ */ jsxRuntimeExports.jsx(RenderLabelsOverlay, { labels: renderLabels }),
40024
- attachmentsVisible !== "none" && attachmentPoints.map((ap) => {
41035
+ !inspectChannelActive && constructionGhost && /* @__PURE__ */ jsxRuntimeExports.jsx(ConstructionGhostOverlay, { matrix: constructionGhostMatrix }),
41036
+ !inspectChannelActive && historyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(ConstructionHistoryOverlay, { objectMatrices, objectSettings }),
41037
+ !inspectChannelActive && hoveredJointOverlay && /* @__PURE__ */ jsxRuntimeExports.jsx(HoveredJointOverlay, { state: hoveredJointOverlay, config: jointOverlayConfig }),
41038
+ !inspectChannelActive && dimensionsVisible && dimensions.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx(DimensionAnnotation, { def: d, lengthUnit }, d.id)),
41039
+ !inspectChannelActive && /* @__PURE__ */ jsxRuntimeExports.jsx(RenderLabelsOverlay, { labels: renderLabels }),
41040
+ !inspectChannelActive && attachmentsVisible !== "none" && attachmentPoints.map((ap) => {
40025
41041
  const matrix = objectMatrices[ap.objectId];
40026
41042
  return matrix ? /* @__PURE__ */ jsxRuntimeExports.jsx("group", { matrixAutoUpdate: false, matrix, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ConnectorAttachmentAnnotation, { def: ap }) }, `${ap.objectId}:${ap.name}`) : /* @__PURE__ */ jsxRuntimeExports.jsx(ConnectorAttachmentAnnotation, { def: ap }, `${ap.objectId}:${ap.name}`);
40027
41043
  }),
40028
41044
  /* @__PURE__ */ jsxRuntimeExports.jsx(MeasureTool, {}),
40029
- debugHighlights3D.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(DebugHighlightsOverlay, { highlights: debugHighlights3D }),
41045
+ !inspectChannelActive && debugHighlights3D.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(DebugHighlightsOverlay, { highlights: debugHighlights3D }),
40030
41046
  drawFlagEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(DrawCanvas, {}),
40031
41047
  /* @__PURE__ */ jsxRuntimeExports.jsx(
40032
41048
  PerformanceInfoSampler,
@@ -40039,7 +41055,7 @@ function Viewport() {
40039
41055
  }
40040
41056
  ),
40041
41057
  /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomSampler, { onZoomChange: setZoomMmPerPx }),
40042
- gridEnabled && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41058
+ !inspectChannelActive && gridEnabled && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
40043
41059
  Grid,
40044
41060
  {
40045
41061
  args: [500, 500],
@@ -40054,7 +41070,7 @@ function Viewport() {
40054
41070
  infiniteGrid: true
40055
41071
  }
40056
41072
  ),
40057
- !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41073
+ !inspectChannelActive && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
40058
41074
  SdfRaymarchLayer,
40059
41075
  {
40060
41076
  objects,
@@ -40069,11 +41085,11 @@ function Viewport() {
40069
41085
  onContextMenu: (obj, event) => handleObjectContextMenu(obj, event)
40070
41086
  }
40071
41087
  ),
40072
- !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(LabeledAxes, {}),
40073
- isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchAxes, {}),
41088
+ !inspectChannelActive && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(LabeledAxes, {}),
41089
+ !inspectChannelActive && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchAxes, {}),
40074
41090
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(CursorTracker, { onMove: (x, y) => setCursorPos({ x, y }) }),
40075
41091
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchRulersUpdater, { hCanvasRef: hRulerRef, vCanvasRef: vRulerRef }),
40076
- gridEnabled && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41092
+ !inspectChannelActive && gridEnabled && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
40077
41093
  Grid,
40078
41094
  {
40079
41095
  args: [500, 500],
@@ -40195,7 +41211,7 @@ function Viewport() {
40195
41211
  }
40196
41212
  ),
40197
41213
  /* @__PURE__ */ jsxRuntimeExports.jsx(PerformanceInfoPanel, { enabled: showPerformanceInfo, stats: performanceInfo }),
40198
- !(isEvaluating || evaluationPhase === "exporting") && /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIndicatorPanel, { mmPerPx: zoomMmPerPx }),
41214
+ !viewportBusyPhase && /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIndicatorPanel, { mmPerPx: zoomMmPerPx }),
40199
41215
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
40200
41216
  SketchRulersOverlay,
40201
41217
  {
@@ -40226,7 +41242,44 @@ function Viewport() {
40226
41242
  }
40227
41243
  ),
40228
41244
  measureMode && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasureInfoPanel, {}),
40229
- (isEvaluating || evaluationPhase === "exporting") && /* @__PURE__ */ jsxRuntimeExports.jsx(EvaluationIndicator, { phase: evaluationPhase }),
41245
+ viewportBusyPhase && /* @__PURE__ */ jsxRuntimeExports.jsx(
41246
+ EvaluationIndicator,
41247
+ {
41248
+ phase: viewportBusyPhase,
41249
+ label: viewportBusyLabel,
41250
+ constructionSteps: rebuildHistorySteps,
41251
+ activeBackend,
41252
+ computeTarget
41253
+ }
41254
+ ),
41255
+ inspectChannelActive && inspectAnalysis.status === "error" && inspectAnalysis.error && /* @__PURE__ */ jsxRuntimeExports.jsxs(
41256
+ "div",
41257
+ {
41258
+ style: {
41259
+ position: "absolute",
41260
+ top: 12,
41261
+ left: "50%",
41262
+ transform: "translateX(-50%)",
41263
+ background: "rgba(127, 29, 29, 0.92)",
41264
+ color: "#fee2e2",
41265
+ border: "1px solid rgba(254, 202, 202, 0.4)",
41266
+ borderRadius: 6,
41267
+ padding: "7px 12px",
41268
+ fontSize: 12,
41269
+ fontWeight: 600,
41270
+ pointerEvents: "none",
41271
+ zIndex: 12,
41272
+ maxWidth: "min(520px, calc(100% - 32px))",
41273
+ whiteSpace: "nowrap",
41274
+ overflow: "hidden",
41275
+ textOverflow: "ellipsis"
41276
+ },
41277
+ children: [
41278
+ "Inspect failed: ",
41279
+ inspectAnalysis.error
41280
+ ]
41281
+ }
41282
+ ),
40230
41283
  /* @__PURE__ */ jsxRuntimeExports.jsx(HoverTooltipLayer, { ref: hoverTooltipRef, enabled: objectPickSyncEnabled && !measureMode }),
40231
41284
  objectContextMenu && /* @__PURE__ */ jsxRuntimeExports.jsxs(
40232
41285
  "div",
@@ -40684,7 +41737,7 @@ function Viewport() {
40684
41737
  }
40685
41738
  );
40686
41739
  }
40687
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-DNH1TEz1.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
41740
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BujZvuwX.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
40688
41741
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
40689
41742
  function storePendingShareCopy(shareId) {
40690
41743
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -40871,15 +41924,15 @@ const PublishedModelPage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Objec
40871
41924
  consumePendingShareCopy,
40872
41925
  storePendingShareCopy
40873
41926
  }, Symbol.toStringTag, { value: "Module" }));
40874
- reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-CAu2OZFn.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
40875
- const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-C58f0K5v.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
40876
- reactExports.lazy(() => __vitePreload(() => import("./BlogPage-DYJMjWx3.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
40877
- reactExports.lazy(() => __vitePreload(() => import("./AdminPage-uTtcSXtn.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
41927
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-O_yMtAri.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
41928
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-DLhIIZyJ.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
41929
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-CI_P0_Pf.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
41930
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-DX0mpSZT.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
40878
41931
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
40879
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-N1l1tMXO.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
40880
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-BIgW7m3X.js"), true ? __vite__mapDeps([2,1]) : void 0).then((m2) => ({ default: m2.PricingPage })));
40881
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-DNH1TEz1.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
40882
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-CMXWA2LX.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
41932
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-DBsqTB_y.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
41933
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-DGkX3Ahr.js"), true ? __vite__mapDeps([2,1]) : void 0).then((m2) => ({ default: m2.PricingPage })));
41934
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BujZvuwX.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
41935
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-0S0qXKog.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
40883
41936
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
40884
41937
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg)$/i;
40885
41938
  function firstMeaningfulLine(text) {
@@ -41096,7 +42149,7 @@ function App() {
41096
42149
  applyTheme(localStorage.getItem("fc-theme") || "dark");
41097
42150
  clientExports.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsxRuntimeExports.jsx(App, {}));
41098
42151
  export {
41099
- LocalStudioEnvironment as $,
42152
+ ViewController as $,
41100
42153
  AuthApiError as A,
41101
42154
  BrandMark as B,
41102
42155
  exportExactFromStore as C,
@@ -41118,36 +42171,38 @@ export {
41118
42171
  resolveJointRange as S,
41119
42172
  useJointsConfig as T,
41120
42173
  useJointAnimationValues as U,
41121
- expandBoundsByTransformedAabb as V,
41122
- Canvas as W,
41123
- PerspectiveCamera as X,
41124
- ControlsInteractionBridge as Y,
41125
- ViewController as Z,
41126
- SceneConfigurator as _,
42174
+ INSPECT_POINT_SAMPLE_COUNT_MAX as V,
42175
+ INSPECT_POINT_SAMPLE_COUNT_MIN as W,
42176
+ expandBoundsByTransformedAabb as X,
42177
+ Canvas as Y,
42178
+ PerspectiveCamera as Z,
42179
+ ControlsInteractionBridge as _,
41127
42180
  applyTheme as a,
41128
- RenderLabelsOverlay as a0,
41129
- Grid as a1,
41130
- OrbitControls2 as a2,
41131
- TOUCH_GESTURES_3D as a3,
41132
- MOUSE_BUTTONS_3D as a4,
41133
- ModelJourneyBar as a5,
41134
- FOCUS_MODE_DIM_OPACITY as a6,
41135
- useJointAnimationLoop as a7,
41136
- computeJointNodeMatrices as a8,
41137
- computeObjectJointMatrices as a9,
41138
- readLastActiveFileForUser as aa,
41139
- ToastContainer as ab,
41140
- isMobile as ac,
41141
- useFeatureFlag as ad,
41142
- decodeSharedHash as ae,
41143
- decodeSharedBundle as af,
41144
- getExternalUrl as ag,
41145
- getGistId as ah,
41146
- Viewport as ai,
41147
- shouldBlockBrowserShortcut as aj,
41148
- useDrawStore as ak,
41149
- storePendingShareCopy as al,
41150
- share as am,
42181
+ SceneConfigurator as a0,
42182
+ LocalStudioEnvironment as a1,
42183
+ RenderLabelsOverlay as a2,
42184
+ Grid as a3,
42185
+ OrbitControls2 as a4,
42186
+ TOUCH_GESTURES_3D as a5,
42187
+ MOUSE_BUTTONS_3D as a6,
42188
+ ModelJourneyBar as a7,
42189
+ FOCUS_MODE_DIM_OPACITY as a8,
42190
+ useJointAnimationLoop as a9,
42191
+ computeJointNodeMatrices as aa,
42192
+ computeObjectJointMatrices as ab,
42193
+ readLastActiveFileForUser as ac,
42194
+ ToastContainer as ad,
42195
+ isMobile as ae,
42196
+ useFeatureFlag as af,
42197
+ decodeSharedHash as ag,
42198
+ decodeSharedBundle as ah,
42199
+ getExternalUrl as ai,
42200
+ getGistId as aj,
42201
+ Viewport as ak,
42202
+ shouldBlockBrowserShortcut as al,
42203
+ useDrawStore as am,
42204
+ storePendingShareCopy as an,
42205
+ share as ao,
41151
42206
  authFetch as b,
41152
42207
  authApi as c,
41153
42208
  showToast as d,