forgecad 0.9.6 → 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 (76) hide show
  1. package/dist/assets/{AdminPage-Da6hhpJx.js → AdminPage-DX0mpSZT.js} +1 -1
  2. package/dist/assets/{BlogPage-Bl_sKeWb.js → BlogPage-CI_P0_Pf.js} +1 -1
  3. package/dist/assets/{DocsPage-Blz3Tp4j.js → DocsPage-DLhIIZyJ.js} +3 -3
  4. package/dist/assets/{EditorApp-CuiPbtn5.js → EditorApp-BujZvuwX.js} +140 -20
  5. package/dist/assets/{EditorApp-DS0AIUrZ.css → EditorApp-DfFT2Dn8.css} +1 -0
  6. package/dist/assets/{EmbedViewer-BFG6-Ufm.js → EmbedViewer-0S0qXKog.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-DB9fQd5P.js → LandingPageProofDriven-O_yMtAri.js} +1 -1
  8. package/dist/assets/{PricingPage-BMxYT_F0.js → PricingPage-DGkX3Ahr.js} +1 -1
  9. package/dist/assets/{SettingsPage-VVQNrCAg.js → SettingsPage-DBsqTB_y.js} +82 -22
  10. package/dist/assets/{app-Dl9ymBWC.js → app-BE2nD6Yz.js} +1056 -258
  11. package/dist/assets/cli/{render-CFtwKCCY.js → render-iP9qh475.js} +1533 -207
  12. package/dist/assets/{evalWorker-CRvbzTXm.js → evalWorker-Ds5U4xtN.js} +2178 -30
  13. package/dist/assets/inspectWorker-Dll4eVyD.js +12620 -0
  14. package/dist/assets/{manifold-DpBXFS2K.js → manifold-Bk26ViCr.js} +1 -1
  15. package/dist/assets/{manifold-DzZ4VRPs.js → manifold-DjYsd7A_.js} +2 -2
  16. package/dist/assets/{manifold-B9QSr-qP.js → manifold-sJ-axdXM.js} +1 -1
  17. package/dist/assets/{renderSceneState-BuAXF2jh.js → renderSceneState-Bngp5MrQ.js} +1 -1
  18. package/dist/assets/{reportWorker-BNWEnRg1.js → reportWorker-CU8RZ4O0.js} +2161 -30
  19. package/dist/assets/{distance-BEC2RjJi.js → sectionPlaneMath-BdTjyVfs.js} +2539 -1187
  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 +5 -0
  25. package/dist/docs-raw/coding.md +1 -1
  26. package/dist/docs-raw/generated/concepts.md +59 -2
  27. package/dist/docs-raw/generated/core.md +206 -1
  28. package/dist/docs-raw/generated/lib.md +17 -1
  29. package/dist/docs-raw/generated/viewport.md +1 -1
  30. package/dist/docs-raw/guides/inspection-bundles.md +36 -13
  31. package/dist/docs-raw/platform/auth.md +2 -0
  32. package/dist/docs-raw/platform/google-oauth-setup.md +4 -0
  33. package/dist/docs-raw/skills/forgecad-make-a-model.md +87 -8
  34. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +14 -6
  35. package/dist/docs-raw/skills/forgecad-render-inspect.md +1 -1
  36. package/dist/docs-raw/skills/index.md +2 -2
  37. package/dist/index.html +1 -1
  38. package/dist/sitemap.xml +6 -6
  39. package/dist-cli/forgecad.js +7975 -4528
  40. package/dist-cli/forgecad.js.map +1 -1
  41. package/dist-skill/CONTEXT.md +260 -16
  42. package/dist-skill/docs/CLI.md +82 -53
  43. package/dist-skill/docs/generated/core.md +206 -1
  44. package/dist-skill/docs/generated/lib.md +17 -1
  45. package/dist-skill/docs/generated/viewport.md +1 -1
  46. package/dist-skill/docs/guides/inspection-bundles.md +36 -13
  47. package/dist-skill/docs-dev/CLI.md +82 -53
  48. package/dist-skill/docs-dev/coding.md +1 -1
  49. package/dist-skill/docs-dev/generated/core.md +206 -1
  50. package/dist-skill/docs-dev/generated/lib.md +17 -1
  51. package/dist-skill/docs-dev/generated/viewport.md +1 -1
  52. package/dist-skill/docs-dev/guides/inspection-bundles.md +36 -13
  53. package/dist-skill/library/forgecad-make-a-model/SKILL.md +87 -8
  54. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +14 -6
  55. package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +5 -3
  56. package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +7 -5
  57. package/dist-skill/library/forgecad-render-inspect/SKILL.md +1 -1
  58. package/examples/api/bolted-service-cover.forge.js +17 -0
  59. package/examples/api/cable-gland-anchor.forge.js +14 -0
  60. package/examples/api/captured-cartridge-guide.forge.js +14 -0
  61. package/examples/api/captured-linear-slide.forge.js +13 -0
  62. package/examples/api/clevis-pin-joint.forge.js +13 -0
  63. package/examples/api/datum-enclosure.forge.js +16 -0
  64. package/examples/api/hose-barb-port.forge.js +14 -0
  65. package/examples/api/intentional-overlap-overmold.forge.js +16 -0
  66. package/examples/api/knuckled-hinge-assembly.forge.js +15 -0
  67. package/examples/api/living-hinge-cover.forge.js +14 -0
  68. package/examples/api/pcb-terminal-block.forge.js +22 -0
  69. package/examples/api/pinned-lever-pivot-stack.forge.js +14 -0
  70. package/examples/api/retained-shaft-knob-stack.forge.js +15 -0
  71. package/examples/api/routed-tube-clip.forge.js +15 -0
  72. package/examples/api/seated-bearing-stack.forge.js +30 -0
  73. package/examples/api/snap-latch-cover.forge.js +14 -0
  74. package/examples/api/static-assembly-connectors.forge.js +14 -16
  75. package/examples/api/thumb-screw-clamp.forge.js +15 -0
  76. package/package.json +1 -1
@@ -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 analyzeCollisionIntersections, b3 as shapeToGeometry, b4 as buildShapeFromCompilePlan, b5 as sketchToSvg, b6 as sketchToDxf, b7 as runScript, b8 as MeshPhysicalMaterial, b9 as LineSegments, ba as analyzeThicknessGeometry, bb as analyzeRoughnessGeometry, bc as getRenderStylePreset, bd as AdditiveBlending, be as CatmullRomCurve3, bf as TubeGeometry, bg as MeshStandardMaterial, bh as compileSdfNode3, bi as buildSdfRaymarchFragmentShader, bj as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bk as Shape, bl as ShapeGeometry, bm as ShaderLib, bn as CylinderGeometry, bo as parseViewportCameraState, bp as createResolvedExplodeConfig, bq as explodeBoundsCenter, br as explodeMergeBounds, bs as resolveExplodeDirective, bt as computeExplodeMotion, bu as getSketchWorldMatrix, bv as explodeAdd, bw as hasExplodeOverride, bx as resolveExplodeLocalFanDirection, by as explodeMul, bz as explodeLeafFanStage, bA as normalizeCutPlane, bB as toClippingPlane, bC as findJointAnimationClip, bD as resolveJointAnimation, bE as resolveJointViewValues, bF as getShapePorts, bG as getShapeUsedPorts, bH as DEFAULT_VIEW_CONFIG, bI as getKernelFaceNameForTriangle, bJ as analyzePhysicalConnectivity, bK as analyzeDistanceInspection, bL as initKernel, bM as initSolverWasm } from "./distance-BEC2RjJi.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
  }
@@ -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-CRvbzTXm.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 {
@@ -17370,9 +17445,13 @@ const VIEW_INSPECT_CHANNELS = /* @__PURE__ */ new Set([
17370
17445
  "thickness",
17371
17446
  "roughness"
17372
17447
  ]);
17448
+ const INSPECT_DISPLAY_MODES = /* @__PURE__ */ new Set(["heatmap", "points", "both"]);
17373
17449
  function resolveViewInspectChannel(value) {
17374
17450
  return typeof value === "string" && VIEW_INSPECT_CHANNELS.has(value) ? value : "none";
17375
17451
  }
17452
+ function resolveInspectDisplayMode(value) {
17453
+ return typeof value === "string" && INSPECT_DISPLAY_MODES.has(value) ? value : "heatmap";
17454
+ }
17376
17455
  const initialViewPreferences = readViewPreferences();
17377
17456
  const initialPreviewFile = resolvePreviewFile(initialActive, INITIAL_FILES);
17378
17457
  const initialObjectSettingsByFile = (() => {
@@ -18137,6 +18216,18 @@ Switch to LOCAL mode or wait for the server to recover.`,
18137
18216
  writeViewPreferences({ inspectChannel: next });
18138
18217
  set({ inspectChannel: next });
18139
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
+ },
18140
18231
  projectionMode: initialViewPreferences.projectionMode ?? "perspective",
18141
18232
  setProjectionMode: (mode) => {
18142
18233
  writeViewPreferences({ projectionMode: mode });
@@ -26747,10 +26838,6 @@ const MASK_PALETTE = [
26747
26838
  [0, 0, 128],
26748
26839
  [128, 128, 128]
26749
26840
  ];
26750
- const DISTANCE_NEAR_COLOR = [66, 220, 120];
26751
- const DISTANCE_MID_COLOR = [255, 214, 0];
26752
- const DISTANCE_FAR_COLOR = [255, 68, 16];
26753
- const DISTANCE_UNKNOWN_COLOR = [128, 128, 128];
26754
26841
  function rgbToHex(color2) {
26755
26842
  return `#${color2.map((value) => value.toString(16).padStart(2, "0")).join("")}`;
26756
26843
  }
@@ -26763,19 +26850,8 @@ function maskColorForIndex(index) {
26763
26850
  function collisionColorForIndex(index) {
26764
26851
  return rgbToHex(COLLISION_PALETTE[(index - 1) % COLLISION_PALETTE.length]);
26765
26852
  }
26766
- function lerp$1(a2, b2, t2) {
26767
- return a2 + (b2 - a2) * Math.max(0, Math.min(1, t2));
26768
- }
26769
- function lerpRgb(a2, b2, t2) {
26770
- return [Math.round(lerp$1(a2[0], b2[0], t2)), Math.round(lerp$1(a2[1], b2[1], t2)), Math.round(lerp$1(a2[2], b2[2], t2))];
26771
- }
26772
- function distanceColorForRootDistance(distance, maxDistance) {
26773
- if (!Number.isFinite(distance)) return rgbToHex(DISTANCE_UNKNOWN_COLOR);
26774
- if (!Number.isFinite(maxDistance) || maxDistance <= 1e-9) return rgbToHex(DISTANCE_NEAR_COLOR);
26775
- const t2 = Math.max(0, Math.min(1, distance / maxDistance));
26776
- if (t2 <= 0.5) return rgbToHex(lerpRgb(DISTANCE_NEAR_COLOR, DISTANCE_MID_COLOR, t2 * 2));
26777
- return rgbToHex(lerpRgb(DISTANCE_MID_COLOR, DISTANCE_FAR_COLOR, (t2 - 0.5) * 2));
26778
- }
26853
+ const MAX_VIEWPORT_COLLISION_CANDIDATES = 200;
26854
+ const MAX_VIEWPORT_COLLISION_MS = 1e3;
26779
26855
  function matrixToCollisionMat4(matrix) {
26780
26856
  if (!matrix) return void 0;
26781
26857
  const e2 = matrix.elements;
@@ -26811,7 +26887,11 @@ function CollisionInspectionOverlay({
26811
26887
  objectMatrices
26812
26888
  }) {
26813
26889
  const collisionGeometries = reactExports.useMemo(() => {
26814
- const report = analyzeCollisionIntersections(buildCollisionEntries(objects, objectSettings, objectMatrices));
26890
+ const report = analyzeCollisionIntersections(buildCollisionEntries(objects, objectSettings, objectMatrices), {
26891
+ maxCandidatePairs: MAX_VIEWPORT_COLLISION_CANDIDATES,
26892
+ maxElapsedMs: MAX_VIEWPORT_COLLISION_MS,
26893
+ includeBBoxCandidates: false
26894
+ });
26815
26895
  return report.collisions.flatMap((collision) => {
26816
26896
  try {
26817
26897
  const geometry = shapeToGeometry(collision.shape);
@@ -26864,7 +26944,8 @@ function ConstructionGhostOverlay({ matrix }) {
26864
26944
  const MAX_CACHE_SIZE = 40;
26865
26945
  const geometryCache = /* @__PURE__ */ new Map();
26866
26946
  function getCachedGeometry(step) {
26867
- const cached = geometryCache.get(step.index);
26947
+ const cacheKey = constructionStepGeometryCacheKey(step);
26948
+ const cached = geometryCache.get(cacheKey);
26868
26949
  if (cached) return cached;
26869
26950
  try {
26870
26951
  const shape = buildShapeFromCompilePlan(step.cumulativePlan);
@@ -26880,7 +26961,7 @@ function getCachedGeometry(step) {
26880
26961
  geometryCache.delete(firstKey);
26881
26962
  }
26882
26963
  }
26883
- geometryCache.set(step.index, entry);
26964
+ geometryCache.set(cacheKey, entry);
26884
26965
  return entry;
26885
26966
  } catch {
26886
26967
  return null;
@@ -29066,7 +29147,7 @@ function generateReportInWorker(options) {
29066
29147
  return new Promise((resolve2, reject) => {
29067
29148
  const worker = new Worker(new URL(
29068
29149
  /* @vite-ignore */
29069
- "/assets/reportWorker-BNWEnRg1.js",
29150
+ "/assets/reportWorker-CU8RZ4O0.js",
29070
29151
  import.meta.url
29071
29152
  ), { type: "module" });
29072
29153
  const cleanup = () => {
@@ -32270,16 +32351,44 @@ const PHASE_CONFIG = {
32270
32351
  evaluating: { color: "#4a9eff", label: "Evaluating model" },
32271
32352
  serializing: { color: "#7c4dff", label: "Preparing display" },
32272
32353
  exporting: { color: "#4caf50", label: "Exporting geometry" },
32354
+ inspecting: { color: "#14b8a6", label: "Generating inspect view" },
32273
32355
  idle: { color: "#888", label: "" }
32274
32356
  };
32275
32357
  const PHASE_ORDER = ["kernel-init", "evaluating", "serializing"];
32276
- 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
+ }) {
32277
32383
  const [frame2, setFrame] = reactExports.useState(0);
32278
32384
  const [elapsed, setElapsed] = reactExports.useState(0);
32385
+ const [previewStepIndex, setPreviewStepIndex] = reactExports.useState(0);
32279
32386
  const startRef = reactExports.useRef(Date.now());
32387
+ const stepCount = constructionSteps.length;
32280
32388
  reactExports.useEffect(() => {
32281
32389
  startRef.current = Date.now();
32282
32390
  setElapsed(0);
32391
+ setPreviewStepIndex(0);
32283
32392
  }, [phase]);
32284
32393
  reactExports.useEffect(() => {
32285
32394
  const spinnerInterval = setInterval(() => setFrame((f2) => (f2 + 1) % BRAILLE_FRAMES.length), 80);
@@ -32289,9 +32398,17 @@ function EvaluationIndicator({ phase }) {
32289
32398
  clearInterval(timerInterval);
32290
32399
  };
32291
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]);
32292
32407
  const config = PHASE_CONFIG[phase] ?? PHASE_CONFIG["evaluating"];
32293
32408
  const elapsedSec = (elapsed / 1e3).toFixed(1);
32294
- 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);
32295
32412
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32296
32413
  "div",
32297
32414
  {
@@ -32299,53 +32416,368 @@ function EvaluationIndicator({ phase }) {
32299
32416
  position: "absolute",
32300
32417
  bottom: 16,
32301
32418
  right: 16,
32419
+ width: previewStep ? "min(360px, calc(100vw - 32px))" : void 0,
32302
32420
  background: "var(--fc-bgPanel)",
32303
32421
  border: "1px solid var(--fc-border)",
32304
32422
  borderRadius: 8,
32305
- padding: "8px 14px",
32423
+ padding: previewStep ? "10px 12px" : "8px 14px",
32306
32424
  pointerEvents: "none",
32307
- display: "flex",
32308
- alignItems: "center",
32309
- gap: 10,
32425
+ display: "grid",
32426
+ gap: previewStep ? 8 : 0,
32310
32427
  fontSize: 12,
32311
32428
  animation: "fc-fadein 0.2s ease-out",
32312
32429
  boxShadow: "0 4px 16px rgba(0,0,0,0.25)"
32313
32430
  },
32314
32431
  children: [
32315
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: config.color, fontSize: 16, fontWeight: 700, width: 16, textAlign: "center" }, children: BRAILLE_FRAMES[frame2] }),
32316
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--fc-text)", fontWeight: 500 }, children: config.label }),
32317
- /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { display: "flex", gap: 4, alignItems: "center", marginLeft: 2 }, children: PHASE_ORDER.map((p2, i) => {
32318
- var _a3;
32319
- 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(
32320
32435
  "span",
32321
32436
  {
32322
32437
  style: {
32323
- width: 6,
32324
- height: 6,
32325
- borderRadius: "50%",
32326
- background: i <= phaseIdx ? ((_a3 = PHASE_CONFIG[p2]) == null ? void 0 : _a3.color) ?? "var(--fc-border)" : "var(--fc-border)",
32327
- transition: "background 0.3s ease",
32328
- 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"
32329
32484
  }
32330
- },
32331
- p2
32332
- );
32333
- }) }),
32334
- /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-textDim)", fontVariantNumeric: "tabular-nums", fontSize: 11 }, children: [
32335
- elapsedSec,
32336
- "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
+ ) })
32337
32551
  ] })
32338
32552
  ]
32339
32553
  }
32340
32554
  );
32341
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
+ }
32342
32771
  function ForgeObject({
32343
32772
  obj,
32344
32773
  settings,
32345
32774
  renderStyle,
32346
32775
  renderMode,
32347
32776
  inspectChannel = "none",
32777
+ inspectDisplayMode = "heatmap",
32348
32778
  inspectColor,
32779
+ inspectMeshColors,
32780
+ inspectPointCloud,
32349
32781
  isInteracting,
32350
32782
  matrix,
32351
32783
  isHovered,
@@ -32382,16 +32814,26 @@ function ForgeObject({
32382
32814
  };
32383
32815
  }
32384
32816
  }, [obj.shape]);
32385
- const inspectionGeo = reactExports.useMemo(() => {
32386
- if (!solidGeo) return null;
32387
- try {
32388
- if (inspectChannel === "thickness") return analyzeThicknessGeometry(solidGeo).geometry;
32389
- if (inspectChannel === "roughness") return analyzeRoughnessGeometry(solidGeo).geometry;
32390
- return null;
32391
- } catch {
32392
- return null;
32393
- }
32394
- }, [inspectChannel, solidGeo]);
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]);
32395
32837
  reactExports.useEffect(() => {
32396
32838
  return () => {
32397
32839
  solidGeo == null ? void 0 : solidGeo.dispose();
@@ -32400,12 +32842,28 @@ function ForgeObject({
32400
32842
  }, [edgesGeo, solidGeo]);
32401
32843
  reactExports.useEffect(() => {
32402
32844
  return () => {
32403
- inspectionGeo == null ? void 0 : inspectionGeo.dispose();
32845
+ inspectPointGeo == null ? void 0 : inspectPointGeo.dispose();
32846
+ };
32847
+ }, [inspectPointGeo]);
32848
+ reactExports.useEffect(() => {
32849
+ return () => {
32850
+ inspectMeshColorGeo == null ? void 0 : inspectMeshColorGeo.dispose();
32404
32851
  };
32405
- }, [inspectionGeo]);
32852
+ }, [inspectMeshColorGeo]);
32853
+ reactExports.useEffect(() => {
32854
+ return () => {
32855
+ inspectHeatmapField == null ? void 0 : inspectHeatmapField.texture.dispose();
32856
+ };
32857
+ }, [inspectHeatmapField]);
32406
32858
  if (!solidGeo || !settings.visible) return null;
32407
32859
  const effectiveRenderMode = isInteracting && renderMode === "overlay" ? "solid" : renderMode;
32408
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
+ );
32409
32867
  const renderStylePreset = getRenderStylePreset(renderStyle);
32410
32868
  const materialDefaults = renderStylePreset.material;
32411
32869
  const authoredMaterialOpacity = (_a3 = obj.materialProps) == null ? void 0 : _a3.opacity;
@@ -32421,6 +32879,8 @@ function ForgeObject({
32421
32879
  const showEdges = !isInspecting && effectiveRenderMode === "overlay";
32422
32880
  const showWire = !isInspecting && effectiveRenderMode === "wireframe";
32423
32881
  const effectiveClippingPlanes = clippingPlanes ?? [];
32882
+ const inspectSolidGeo = inspectMeshColorGeo ?? solidGeo;
32883
+ const hasInspectMeshColors = inspectMeshColorGeo !== null;
32424
32884
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32425
32885
  "group",
32426
32886
  {
@@ -32433,16 +32893,56 @@ function ForgeObject({
32433
32893
  onDoubleClick,
32434
32894
  onContextMenu,
32435
32895
  children: [
32436
- showSolid && (inspectChannel === "mask" || inspectChannel === "connectivity" || inspectChannel === "distance") && /* @__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(
32437
32907
  "meshBasicMaterial",
32438
32908
  {
32439
- color: inspectColor ?? settings.color,
32909
+ color: "#26313a",
32910
+ transparent: true,
32911
+ opacity: 0.24,
32440
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,
32441
32942
  toneMapped: false,
32442
32943
  clippingPlanes: effectiveClippingPlanes
32443
32944
  }
32444
32945
  ) }),
32445
- showSolid && (inspectChannel === "thickness" || inspectChannel === "roughness") && inspectionGeo && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: inspectionGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx("meshBasicMaterial", { vertexColors: true, side: DoubleSide, toneMapped: false, clippingPlanes: effectiveClippingPlanes }) }),
32446
32946
  showSolid && inspectChannel === "collisions" && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32447
32947
  "meshPhysicalMaterial",
32448
32948
  {
@@ -38049,6 +38549,8 @@ function useViewportState() {
38049
38549
  const renderMode = useForgeStore((s) => s.renderMode);
38050
38550
  const renderStyle = useForgeStore((s) => s.renderStyle);
38051
38551
  const inspectChannel = useForgeStore((s) => s.inspectChannel);
38552
+ const inspectDisplayMode = useForgeStore((s) => s.inspectDisplayMode);
38553
+ const inspectPointSampleCount = useForgeStore((s) => s.inspectPointSampleCount);
38052
38554
  const projectionMode = useForgeStore((s) => s.projectionMode);
38053
38555
  const gridEnabled = useForgeStore((s) => s.gridEnabled);
38054
38556
  const gridSize = useForgeStore((s) => s.gridSize);
@@ -38376,6 +38878,8 @@ function useViewportState() {
38376
38878
  renderMode,
38377
38879
  renderStyle,
38378
38880
  inspectChannel,
38881
+ inspectDisplayMode,
38882
+ inspectPointSampleCount,
38379
38883
  projectionMode,
38380
38884
  gridEnabled,
38381
38885
  gridSize,
@@ -39705,58 +40209,6 @@ function ModelJourneyBar({
39705
40209
  }
39706
40210
  );
39707
40211
  }
39708
- function transformedShapeBounds(obj, matrix) {
39709
- if (!obj.shape) return null;
39710
- try {
39711
- const bb = obj.shape.boundingBox();
39712
- const bounds = new Box3();
39713
- if (!expandBoundsByTransformedAabb(bounds, bb.min, bb.max, matrix)) return null;
39714
- return isFiniteBox3(bounds) ? bounds : null;
39715
- } catch {
39716
- return null;
39717
- }
39718
- }
39719
- function bodyCountForShape(obj) {
39720
- var _a3, _b2;
39721
- try {
39722
- const bodyCount = (_b2 = (_a3 = obj.shape) == null ? void 0 : _a3.numBodies) == null ? void 0 : _b2.call(_a3);
39723
- return typeof bodyCount === "number" && Number.isFinite(bodyCount) ? Math.max(0, Math.round(bodyCount)) : 1;
39724
- } catch {
39725
- return 1;
39726
- }
39727
- }
39728
- function buildPhysicalConnectivityEntries(objects, objectSettings, objectMatrices) {
39729
- const entries = [];
39730
- objects.forEach((obj) => {
39731
- var _a3;
39732
- if (!obj.shape) return;
39733
- if (((_a3 = objectSettings[obj.id]) == null ? void 0 : _a3.visible) === false) return;
39734
- const bounds = transformedShapeBounds(obj, objectMatrices[obj.id] ?? new Matrix4());
39735
- if (!bounds) return;
39736
- entries.push({
39737
- id: obj.id,
39738
- name: obj.name,
39739
- shape: obj.shape,
39740
- min: [bounds.min.x, bounds.min.y, bounds.min.z],
39741
- max: [bounds.max.x, bounds.max.y, bounds.max.z],
39742
- groupName: obj.groupName,
39743
- treePath: obj.treePath,
39744
- mock: obj.mock,
39745
- bodyCount: bodyCountForShape(obj)
39746
- });
39747
- });
39748
- return entries;
39749
- }
39750
- function connectivityColorByObjectId(entries) {
39751
- const report = analyzePhysicalConnectivity(entries);
39752
- return Object.fromEntries(report.objects.map((object) => [object.id, maskColorForIndex(object.componentIndex - 1)]));
39753
- }
39754
- function distanceColorByObjectId(entries) {
39755
- const report = analyzeDistanceInspection(entries);
39756
- return Object.fromEntries(
39757
- report.objects.map((object) => [object.id, distanceColorForRootDistance(object.rootDistance, report.maxRootDistance)])
39758
- );
39759
- }
39760
40212
  const anchorTransform = {
39761
40213
  center: "translate(-50%, -50%)",
39762
40214
  top: "translate(-50%, 0)",
@@ -39804,6 +40256,276 @@ function RenderLabelsOverlay({ labels }) {
39804
40256
  if (labels.length === 0) return null;
39805
40257
  return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { renderOrder: 12, children: labels.map((label) => /* @__PURE__ */ jsxRuntimeExports.jsx(RenderLabelItem, { label }, label.id)) });
39806
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
+ }
39807
40529
  function panelNumber(value) {
39808
40530
  if (!Number.isFinite(value)) return String(value);
39809
40531
  if (Number.isInteger(value)) return String(value);
@@ -39829,6 +40551,20 @@ function edgeCurveLabel(edge) {
39829
40551
  return "segment";
39830
40552
  }
39831
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
+ }
39832
40568
  function edgeBelongsToFace(edge, faceName) {
39833
40569
  var _a3;
39834
40570
  return edge.faceName === faceName || ((_a3 = edge.curve) == null ? void 0 : _a3.faceName) === faceName || edge.name.startsWith(`${faceName}:`);
@@ -39916,6 +40652,8 @@ function RenderStyleExposure({ exposure }) {
39916
40652
  function Viewport() {
39917
40653
  var _a3, _b2, _c;
39918
40654
  const activeFile = useForgeStore((s) => s.activeFile);
40655
+ const activeBackend = useForgeStore((s) => s.activeBackend);
40656
+ const computeTarget = useForgeStore((s) => s.computeTarget);
39919
40657
  const isSvgActive = !!activeFile && activeFile.toLowerCase().endsWith(".svg");
39920
40658
  const state2 = useViewportState();
39921
40659
  const {
@@ -39925,6 +40663,8 @@ function Viewport() {
39925
40663
  renderMode,
39926
40664
  renderStyle,
39927
40665
  inspectChannel,
40666
+ inspectDisplayMode,
40667
+ inspectPointSampleCount,
39928
40668
  projectionMode,
39929
40669
  gridEnabled,
39930
40670
  gridSize,
@@ -40025,11 +40765,27 @@ function Viewport() {
40025
40765
  }
40026
40766
  return next;
40027
40767
  }, [constructionGhost, focusedObjectIdSet, objectSettings, objects]);
40028
- const physicalInspectColorByObjectId = reactExports.useMemo(() => {
40029
- if (inspectChannel !== "connectivity" && inspectChannel !== "distance") return {};
40030
- const entries = buildPhysicalConnectivityEntries(objects, objectSettings, objectMatrices);
40031
- return inspectChannel === "connectivity" ? connectivityColorByObjectId(entries) : distanceColorByObjectId(entries);
40032
- }, [inspectChannel, objectMatrices, 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]);
40033
40789
  const handlers = useViewportHandlers({
40034
40790
  containerRef,
40035
40791
  contextMenuRef,
@@ -40195,7 +40951,10 @@ function Viewport() {
40195
40951
  renderStyle,
40196
40952
  renderMode,
40197
40953
  inspectChannel,
40198
- inspectColor: physicalInspectColorByObjectId[obj.id] ?? maskColorForIndex(objIndex),
40954
+ inspectDisplayMode,
40955
+ inspectColor: inspectAnalysis.objectColors[obj.id] ?? maskColorForIndex(objIndex),
40956
+ inspectMeshColors: inspectAnalysis.meshColors[obj.id],
40957
+ inspectPointCloud: inspectAnalysis.pointClouds[obj.id],
40199
40958
  isInteracting: isViewportInteracting,
40200
40959
  matrix,
40201
40960
  isHovered,
@@ -40452,7 +41211,7 @@ function Viewport() {
40452
41211
  }
40453
41212
  ),
40454
41213
  /* @__PURE__ */ jsxRuntimeExports.jsx(PerformanceInfoPanel, { enabled: showPerformanceInfo, stats: performanceInfo }),
40455
- !(isEvaluating || evaluationPhase === "exporting") && /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIndicatorPanel, { mmPerPx: zoomMmPerPx }),
41214
+ !viewportBusyPhase && /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIndicatorPanel, { mmPerPx: zoomMmPerPx }),
40456
41215
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
40457
41216
  SketchRulersOverlay,
40458
41217
  {
@@ -40483,7 +41242,44 @@ function Viewport() {
40483
41242
  }
40484
41243
  ),
40485
41244
  measureMode && /* @__PURE__ */ jsxRuntimeExports.jsx(MeasureInfoPanel, {}),
40486
- (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
+ ),
40487
41283
  /* @__PURE__ */ jsxRuntimeExports.jsx(HoverTooltipLayer, { ref: hoverTooltipRef, enabled: objectPickSyncEnabled && !measureMode }),
40488
41284
  objectContextMenu && /* @__PURE__ */ jsxRuntimeExports.jsxs(
40489
41285
  "div",
@@ -40941,7 +41737,7 @@ function Viewport() {
40941
41737
  }
40942
41738
  );
40943
41739
  }
40944
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-CuiPbtn5.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 })));
40945
41741
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
40946
41742
  function storePendingShareCopy(shareId) {
40947
41743
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -41128,15 +41924,15 @@ const PublishedModelPage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Objec
41128
41924
  consumePendingShareCopy,
41129
41925
  storePendingShareCopy
41130
41926
  }, Symbol.toStringTag, { value: "Module" }));
41131
- reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-DB9fQd5P.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
41132
- const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-Blz3Tp4j.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
41133
- reactExports.lazy(() => __vitePreload(() => import("./BlogPage-Bl_sKeWb.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
41134
- reactExports.lazy(() => __vitePreload(() => import("./AdminPage-Da6hhpJx.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 })));
41135
41931
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
41136
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-VVQNrCAg.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
41137
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-BMxYT_F0.js"), true ? __vite__mapDeps([2,1]) : void 0).then((m2) => ({ default: m2.PricingPage })));
41138
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-CuiPbtn5.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
41139
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-BFG6-Ufm.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 })));
41140
41936
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
41141
41937
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg)$/i;
41142
41938
  function firstMeaningfulLine(text) {
@@ -41353,7 +42149,7 @@ function App() {
41353
42149
  applyTheme(localStorage.getItem("fc-theme") || "dark");
41354
42150
  clientExports.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsxRuntimeExports.jsx(App, {}));
41355
42151
  export {
41356
- LocalStudioEnvironment as $,
42152
+ ViewController as $,
41357
42153
  AuthApiError as A,
41358
42154
  BrandMark as B,
41359
42155
  exportExactFromStore as C,
@@ -41375,36 +42171,38 @@ export {
41375
42171
  resolveJointRange as S,
41376
42172
  useJointsConfig as T,
41377
42173
  useJointAnimationValues as U,
41378
- expandBoundsByTransformedAabb as V,
41379
- Canvas as W,
41380
- PerspectiveCamera as X,
41381
- ControlsInteractionBridge as Y,
41382
- ViewController as Z,
41383
- 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 _,
41384
42180
  applyTheme as a,
41385
- RenderLabelsOverlay as a0,
41386
- Grid as a1,
41387
- OrbitControls2 as a2,
41388
- TOUCH_GESTURES_3D as a3,
41389
- MOUSE_BUTTONS_3D as a4,
41390
- ModelJourneyBar as a5,
41391
- FOCUS_MODE_DIM_OPACITY as a6,
41392
- useJointAnimationLoop as a7,
41393
- computeJointNodeMatrices as a8,
41394
- computeObjectJointMatrices as a9,
41395
- readLastActiveFileForUser as aa,
41396
- ToastContainer as ab,
41397
- isMobile as ac,
41398
- useFeatureFlag as ad,
41399
- decodeSharedHash as ae,
41400
- decodeSharedBundle as af,
41401
- getExternalUrl as ag,
41402
- getGistId as ah,
41403
- Viewport as ai,
41404
- shouldBlockBrowserShortcut as aj,
41405
- useDrawStore as ak,
41406
- storePendingShareCopy as al,
41407
- 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,
41408
42206
  authFetch as b,
41409
42207
  authApi as c,
41410
42208
  showToast as d,