forgecad 0.9.7 → 0.9.9

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 (47) hide show
  1. package/README.md +1 -0
  2. package/dist/assets/{AdminPage-DX0mpSZT.js → AdminPage-CNEvQM7c.js} +1 -1
  3. package/dist/assets/{BlogPage-CI_P0_Pf.js → BlogPage-Cc41PP4d.js} +1 -1
  4. package/dist/assets/{DocsPage-DLhIIZyJ.js → DocsPage-9U1hGjrg.js} +2 -2
  5. package/dist/assets/{EditorApp-DfFT2Dn8.css → EditorApp-D11wL4Qn.css} +51 -0
  6. package/dist/assets/{EditorApp-BujZvuwX.js → EditorApp-DnddQvBt.js} +151 -9
  7. package/dist/assets/{EmbedViewer-0S0qXKog.js → EmbedViewer-B2lhWTcd.js} +2 -2
  8. package/dist/assets/{LandingPageProofDriven-O_yMtAri.js → LandingPageProofDriven-CFet-l3o.js} +1 -1
  9. package/dist/assets/{PricingPage-DGkX3Ahr.js → PricingPage-CPm8mQx3.js} +1 -1
  10. package/dist/assets/{SettingsPage-DBsqTB_y.js → SettingsPage-BarZonVZ.js} +1 -1
  11. package/dist/assets/__vite-browser-external-Dhvy_jtL.js +4 -0
  12. package/dist/assets/{app-BE2nD6Yz.js → app-BjSEB4sY.js} +1006 -526
  13. package/dist/assets/cli/{render-iP9qh475.js → render-CXVrHY8q.js} +647 -481
  14. package/dist/assets/constructionHistoryWorker-z9_LGiRd.js +42984 -0
  15. package/dist/assets/{evalWorker-Ds5U4xtN.js → evalWorker-CtO7GsJR.js} +42 -9
  16. package/dist/assets/{inspectWorker-Dll4eVyD.js → inspectWorker-BZ2CkQZr.js} +785 -111
  17. package/dist/assets/{manifold-DjYsd7A_.js → manifold-BRdhoQy5.js} +2 -2
  18. package/dist/assets/{manifold-sJ-axdXM.js → manifold-BVi4_OeB.js} +1 -1
  19. package/dist/assets/manifold-Cp_dCC7i.js +3018 -0
  20. package/dist/assets/{manifold-Bk26ViCr.js → manifold-DAzn2Fsa.js} +1 -1
  21. package/dist/assets/{renderSceneState-Bngp5MrQ.js → renderSceneState-CWO8rHlt.js} +1 -1
  22. package/dist/assets/{reportWorker-CU8RZ4O0.js → reportWorker-Bz9tGiHb.js} +42 -9
  23. package/dist/assets/{sectionPlaneMath-BdTjyVfs.js → scalar-sampling-budget-Bmewod18.js} +1339 -215
  24. package/dist/cli/render.html +1 -1
  25. package/dist/docs/index.html +1 -1
  26. package/dist/docs-raw/CLI.md +10 -10
  27. package/dist/docs-raw/coding-best-practices.md +1 -1
  28. package/dist/docs-raw/guides/inspection-bundles.md +77 -19
  29. package/dist/docs-raw/guides/skill-maintenance.md +1 -1
  30. package/dist/docs-raw/runbook.md +2 -2
  31. package/dist/docs-raw/skills/forgecad-make-a-model.md +11 -0
  32. package/dist/docs-raw/skills/forgecad-render-inspect.md +12 -6
  33. package/dist/docs-raw/skills/index.md +1 -1
  34. package/dist/index.html +1 -1
  35. package/dist/sitemap.xml +6 -6
  36. package/dist-cli/forgecad.js +596 -354
  37. package/dist-cli/forgecad.js.map +1 -1
  38. package/dist-skill/CONTEXT.md +77 -19
  39. package/dist-skill/docs/CLI.md +10 -10
  40. package/dist-skill/docs/guides/inspection-bundles.md +77 -19
  41. package/dist-skill/docs-dev/CLI.md +10 -10
  42. package/dist-skill/docs-dev/coding-best-practices.md +1 -1
  43. package/dist-skill/docs-dev/guides/inspection-bundles.md +77 -19
  44. package/dist-skill/docs-dev/guides/skill-maintenance.md +1 -1
  45. package/dist-skill/library/forgecad-make-a-model/SKILL.md +11 -0
  46. package/dist-skill/library/forgecad-render-inspect/SKILL.md +12 -6
  47. package/package.json +6 -3
@@ -1,10 +1,10 @@
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]);
1
+ const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/EditorApp-D11wL4Qn.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
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";
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 geometryWithVisibleVertexColors, bb as getRenderStylePreset, bc as AdditiveBlending, bd as ZEBRA_STRIPE_SOFTNESS, be as ZEBRA_STRIPE_SCALE, bf as ZEBRA_LIGHT_COLOR, bg as ZEBRA_DARK_COLOR, bh as ZEBRA_ACCENT_COLOR, bi as ZEBRA_STRIPE_FRAGMENT_SHADER, bj as ZEBRA_STRIPE_VERTEX_SHADER, bk as SURFACE_FIELD_FRAGMENT_SHADER, bl as SURFACE_FIELD_VERTEX_SHADER, bm as CatmullRomCurve3, bn as TubeGeometry, bo as ROUGHNESS_COLORS, bp as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bq as DEFAULT_THICKNESS_INSPECTION_OPTIONS, br as THICKNESS_COLORS, bs as MeshStandardMaterial, bt as compileSdfNode3, bu as buildSdfRaymarchFragmentShader, bv as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bw as Shape, bx as ShapeGeometry, by as ShaderLib, bz as CylinderGeometry, bA as parseViewportCameraState, bB as createResolvedExplodeConfig, bC as explodeBoundsCenter, bD as explodeMergeBounds, bE as resolveExplodeDirective, bF as computeExplodeMotion, bG as getSketchWorldMatrix, bH as explodeAdd, bI as hasExplodeOverride, bJ as resolveExplodeLocalFanDirection, bK as explodeMul, bL as explodeLeafFanStage, bM as normalizeCutPlane, bN as toClippingPlane, bO as findJointAnimationClip, bP as resolveJointAnimation, bQ as resolveJointViewValues, bR as getShapePorts, bS as getShapeUsedPorts, bT as DEFAULT_VIEW_CONFIG, bU as getKernelFaceNameForTriangle, bV as resolveScalarSceneSampleBudget, bW as initKernel, bX as initSolverWasm } from "./scalar-sampling-budget-Bmewod18.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];
@@ -14909,24 +14909,24 @@ const formatAnimationSpeed = (speed) => {
14909
14909
  const safeSpeed = clampAnimationSpeed(speed);
14910
14910
  return safeSpeed < 0.1 ? safeSpeed.toFixed(3) : safeSpeed.toFixed(2);
14911
14911
  };
14912
- const fmt$1 = (n) => (Math.round(n * 1e3) / 1e3).toString();
14912
+ const fmt$2 = (n) => (Math.round(n * 1e3) / 1e3).toString();
14913
14913
  function planLabel(plan) {
14914
14914
  var _a3;
14915
14915
  switch (plan.kind) {
14916
14916
  case "box":
14917
- return `Box ${fmt$1(plan.x)} × ${fmt$1(plan.y)} × ${fmt$1(plan.z)}`;
14917
+ return `Box ${fmt$2(plan.x)} × ${fmt$2(plan.y)} × ${fmt$2(plan.z)}`;
14918
14918
  case "cylinder": {
14919
- const rLabel = plan.radiusTop !== void 0 && plan.radiusTop !== plan.radius ? `r=${fmt$1(plan.radius)}→${fmt$1(plan.radiusTop)}` : `r=${fmt$1(plan.radius)}`;
14920
- return `Cylinder ${rLabel} h=${fmt$1(plan.height)}`;
14919
+ const rLabel = plan.radiusTop !== void 0 && plan.radiusTop !== plan.radius ? `r=${fmt$2(plan.radius)}→${fmt$2(plan.radiusTop)}` : `r=${fmt$2(plan.radius)}`;
14920
+ return `Cylinder ${rLabel} h=${fmt$2(plan.height)}`;
14921
14921
  }
14922
14922
  case "sphere":
14923
- return `Sphere r=${fmt$1(plan.radius)}`;
14923
+ return `Sphere r=${fmt$2(plan.radius)}`;
14924
14924
  case "torus":
14925
- return `Torus R=${fmt$1(plan.majorRadius)} r=${fmt$1(plan.minorRadius)}`;
14925
+ return `Torus R=${fmt$2(plan.majorRadius)} r=${fmt$2(plan.minorRadius)}`;
14926
14926
  case "extrude":
14927
- return `Extrude h=${fmt$1(plan.height)}`;
14927
+ return `Extrude h=${fmt$2(plan.height)}`;
14928
14928
  case "revolve":
14929
- return `Revolve ${fmt$1(plan.degrees)}°`;
14929
+ return `Revolve ${fmt$2(plan.degrees)}°`;
14930
14930
  case "loft":
14931
14931
  return `Loft (${plan.profiles.length} profiles)`;
14932
14932
  case "sweep":
@@ -14940,17 +14940,17 @@ function planLabel(plan) {
14940
14940
  case "queryOwner":
14941
14941
  return planLabel(plan.base);
14942
14942
  case "fillet":
14943
- return `Fillet r=${fmt$1(plan.radius)}`;
14943
+ return `Fillet r=${fmt$2(plan.radius)}`;
14944
14944
  case "filletEdges":
14945
- return `Fillet r=${fmt$1(plan.radius)}`;
14945
+ return `Fillet r=${fmt$2(plan.radius)}`;
14946
14946
  case "cornerYBlend":
14947
- return `Y-corner blend r=${fmt$1(plan.radius)}`;
14947
+ return `Y-corner blend r=${fmt$2(plan.radius)}`;
14948
14948
  case "chamfer":
14949
- return `Chamfer ${fmt$1(plan.size)}`;
14949
+ return `Chamfer ${fmt$2(plan.size)}`;
14950
14950
  case "chamferEdges":
14951
- return `Chamfer ${fmt$1(plan.size)}`;
14951
+ return `Chamfer ${fmt$2(plan.size)}`;
14952
14952
  case "shell":
14953
- return `Shell t=${fmt$1(plan.thickness)}`;
14953
+ return `Shell t=${fmt$2(plan.thickness)}`;
14954
14954
  case "hole":
14955
14955
  return "Hole";
14956
14956
  case "cut":
@@ -14960,9 +14960,9 @@ function planLabel(plan) {
14960
14960
  case "sheetMetal":
14961
14961
  return "Sheet Metal";
14962
14962
  case "draft":
14963
- return `Draft ${fmt$1(plan.angleDeg)}°`;
14963
+ return `Draft ${fmt$2(plan.angleDeg)}°`;
14964
14964
  case "offsetSolid":
14965
- return `Offset ${fmt$1(plan.thickness)}`;
14965
+ return `Offset ${fmt$2(plan.thickness)}`;
14966
14966
  case "importedMesh":
14967
14967
  return `Import ${plan.filePath.split("/").pop() ?? "mesh"}`;
14968
14968
  case "sdf":
@@ -14978,9 +14978,9 @@ function planLabel(plan) {
14978
14978
  case "surfaceSew":
14979
14979
  return `Surface Sew (${plan.shapes.length} sheets)`;
14980
14980
  case "surfaceExtend":
14981
- return `Surface Extend ${fmt$1(plan.length)}`;
14981
+ return `Surface Extend ${fmt$2(plan.length)}`;
14982
14982
  case "surfaceThicken":
14983
- return `Thicken ${fmt$1(plan.thickness)}`;
14983
+ return `Thicken ${fmt$2(plan.thickness)}`;
14984
14984
  case "importedStep":
14985
14985
  return `STEP Import (${plan.filePath})`;
14986
14986
  }
@@ -15021,9 +15021,6 @@ function stableJsonStringify(value) {
15021
15021
  function shapeCompilePlanCacheKey(plan) {
15022
15022
  return `${SHAPE_COMPILE_PLAN_CACHE_KEY_VERSION}:${stableJsonStringify(plan)}`;
15023
15023
  }
15024
- function constructionStepGeometryCacheKey(step) {
15025
- return step.cumulativePlanCacheKey;
15026
- }
15027
15024
  function makeConstructionStep(step) {
15028
15025
  const planCacheKey = shapeCompilePlanCacheKey(step.plan);
15029
15026
  return {
@@ -15849,13 +15846,11 @@ function triggerDownload(blob, filename) {
15849
15846
  URL.revokeObjectURL(url2);
15850
15847
  }
15851
15848
  const INIT_TIMEOUT_MS = 12e4;
15852
- const RUN_TIMEOUT_MS = 3e4;
15853
- const EXPORT_TIMEOUT_MS = 3e5;
15854
15849
  const CRASH_COOLDOWN_MS = 2e3;
15855
15850
  class EvalWorkerClient {
15856
15851
  constructor(workerFactory = () => new Worker(new URL(
15857
15852
  /* @vite-ignore */
15858
- "/assets/evalWorker-Ds5U4xtN.js",
15853
+ "/assets/evalWorker-CtO7GsJR.js",
15859
15854
  import.meta.url
15860
15855
  ), { type: "module" })) {
15861
15856
  __publicField(this, "worker", null);
@@ -15867,8 +15862,7 @@ class EvalWorkerClient {
15867
15862
  __publicField(this, "pendingFaceInfo", /* @__PURE__ */ new Map());
15868
15863
  __publicField(this, "pendingExportExact", /* @__PURE__ */ new Map());
15869
15864
  __publicField(this, "pendingExportMesh", /* @__PURE__ */ new Map());
15870
- __publicField(this, "runTimeoutId", null);
15871
- __publicField(this, "exportTimeoutId", null);
15865
+ __publicField(this, "initTimeoutId", null);
15872
15866
  /** Timestamp of the last worker crash — used to enforce a cooldown before respawning. */
15873
15867
  __publicField(this, "lastCrashTime", 0);
15874
15868
  __publicField(this, "activeRunSeq", null);
@@ -15894,16 +15888,12 @@ class EvalWorkerClient {
15894
15888
  const { data } = event;
15895
15889
  if (data.type === "progress") {
15896
15890
  if (data.payload.phase === "export-evaluating" || data.payload.phase === "export-writing") {
15897
- this.resetExportTimeout();
15898
15891
  return;
15899
15892
  }
15900
15893
  if (data.payload.seq !== this.activeRunSeq) return;
15901
15894
  (_a3 = this.onProgress) == null ? void 0 : _a3.call(this, data.payload.phase);
15902
15895
  if (data.payload.phase === "evaluating") {
15903
- this.clearRunTimeout();
15904
- this.runTimeoutId = setTimeout(() => {
15905
- this.killWorker(`Script execution timed out after ${RUN_TIMEOUT_MS / 1e3}s`);
15906
- }, RUN_TIMEOUT_MS);
15896
+ this.clearInitTimeout();
15907
15897
  }
15908
15898
  return;
15909
15899
  }
@@ -15915,7 +15905,7 @@ class EvalWorkerClient {
15915
15905
  if (data.type === "run-success" || data.type === "run-error") {
15916
15906
  const req = this.pendingRuns.get(data.payload.seq);
15917
15907
  if (!req) return;
15918
- this.clearRunTimeout();
15908
+ this.clearInitTimeout();
15919
15909
  this.clearActiveRun(data.payload.seq);
15920
15910
  this.pendingRuns.delete(data.payload.seq);
15921
15911
  if (data.type === "run-success") {
@@ -15942,7 +15932,6 @@ class EvalWorkerClient {
15942
15932
  return;
15943
15933
  }
15944
15934
  if (data.type === "export-exact-success" || data.type === "export-exact-error") {
15945
- this.clearExportTimeout();
15946
15935
  const req = this.pendingExportExact.get(data.payload.reqId);
15947
15936
  if (!req) return;
15948
15937
  this.pendingExportExact.delete(data.payload.reqId);
@@ -15954,7 +15943,6 @@ class EvalWorkerClient {
15954
15943
  return;
15955
15944
  }
15956
15945
  if (data.type === "export-mesh-success" || data.type === "export-mesh-error") {
15957
- this.clearExportTimeout();
15958
15946
  const req = this.pendingExportMesh.get(data.payload.reqId);
15959
15947
  if (!req) return;
15960
15948
  this.pendingExportMesh.delete(data.payload.reqId);
@@ -15966,8 +15954,7 @@ class EvalWorkerClient {
15966
15954
  }
15967
15955
  };
15968
15956
  this.worker.onerror = (event) => {
15969
- this.clearRunTimeout();
15970
- this.clearExportTimeout();
15957
+ this.clearInitTimeout();
15971
15958
  this.clearActiveRun();
15972
15959
  this.lastCrashTime = Date.now();
15973
15960
  const err = new Error(event.message || "Eval worker crashed");
@@ -15985,28 +15972,12 @@ class EvalWorkerClient {
15985
15972
  };
15986
15973
  return this.worker;
15987
15974
  }
15988
- clearRunTimeout() {
15989
- if (this.runTimeoutId !== null) {
15990
- clearTimeout(this.runTimeoutId);
15991
- this.runTimeoutId = null;
15992
- }
15993
- }
15994
- clearExportTimeout() {
15995
- if (this.exportTimeoutId !== null) {
15996
- clearTimeout(this.exportTimeoutId);
15997
- this.exportTimeoutId = null;
15975
+ clearInitTimeout() {
15976
+ if (this.initTimeoutId !== null) {
15977
+ clearTimeout(this.initTimeoutId);
15978
+ this.initTimeoutId = null;
15998
15979
  }
15999
15980
  }
16000
- resetExportTimeout() {
16001
- this.clearExportTimeout();
16002
- this.exportTimeoutId = setTimeout(() => {
16003
- const err = new Error(`Export timed out after ${EXPORT_TIMEOUT_MS / 1e3}s`);
16004
- for (const req of this.pendingExportExact.values()) req.reject(err);
16005
- for (const req of this.pendingExportMesh.values()) req.reject(err);
16006
- this.pendingExportExact.clear();
16007
- this.pendingExportMesh.clear();
16008
- }, EXPORT_TIMEOUT_MS);
16009
- }
16010
15981
  clearActiveRun(seq) {
16011
15982
  if (seq !== void 0 && this.activeRunSeq !== seq) return;
16012
15983
  this.activeRunSeq = null;
@@ -16020,8 +15991,7 @@ class EvalWorkerClient {
16020
15991
  */
16021
15992
  replaceWorkerForSupersedingRun() {
16022
15993
  console.info("[evalWorkerClient] Superseded eval run — replacing worker");
16023
- this.clearRunTimeout();
16024
- this.clearExportTimeout();
15994
+ this.clearInitTimeout();
16025
15995
  this.clearActiveRun();
16026
15996
  this.workerWasmHeap = null;
16027
15997
  if (this.worker) {
@@ -16045,8 +16015,7 @@ class EvalWorkerClient {
16045
16015
  */
16046
16016
  killWorker(reason, failureMessage = reason) {
16047
16017
  console.warn(`[evalWorkerClient] ${reason} — terminating worker`);
16048
- this.clearRunTimeout();
16049
- this.clearExportTimeout();
16018
+ this.clearInitTimeout();
16050
16019
  this.clearActiveRun();
16051
16020
  this.lastCrashTime = Date.now();
16052
16021
  this.workerWasmHeap = null;
@@ -16068,7 +16037,7 @@ class EvalWorkerClient {
16068
16037
  /** Post a run request to the worker and arm the init timeout. */
16069
16038
  startRun(seq, options) {
16070
16039
  this.activeRunSeq = seq;
16071
- this.runTimeoutId = setTimeout(() => {
16040
+ this.initTimeoutId = setTimeout(() => {
16072
16041
  this.killWorker(`Kernel initialization timed out after ${INIT_TIMEOUT_MS / 1e3}s`);
16073
16042
  }, INIT_TIMEOUT_MS);
16074
16043
  const request = { type: "run", payload: { seq, ...options } };
@@ -16080,7 +16049,7 @@ class EvalWorkerClient {
16080
16049
  * block the newer request. Only the latest run's result is resolved.
16081
16050
  *
16082
16051
  * A generous init timeout (120s) covers WASM loading. Once the worker signals
16083
- * 'evaluating', a shorter run timeout (30s) takes over.
16052
+ * 'evaluating', the run is allowed to continue until it completes or is superseded.
16084
16053
  */
16085
16054
  run(options) {
16086
16055
  if (this.shouldReplaceWorkerForSupersedingRun()) {
@@ -16088,7 +16057,7 @@ class EvalWorkerClient {
16088
16057
  } else {
16089
16058
  for (const req of this.pendingRuns.values()) req.reject(new Error("cancelled"));
16090
16059
  this.pendingRuns.clear();
16091
- this.clearRunTimeout();
16060
+ this.clearInitTimeout();
16092
16061
  }
16093
16062
  const seq = ++this.seq;
16094
16063
  const msSinceCrash = Date.now() - this.lastCrashTime;
@@ -16122,7 +16091,6 @@ class EvalWorkerClient {
16122
16091
  /**
16123
16092
  * Export exact geometry (STEP/BREP) using live OCCT shapes in the worker.
16124
16093
  * Includes script context so the worker can re-evaluate if needed.
16125
- * Uses a generous 5-minute timeout that resets on each progress phase.
16126
16094
  */
16127
16095
  exportExact(format, scriptContext) {
16128
16096
  const reqId = ++this.exportExactReqId;
@@ -16134,7 +16102,6 @@ class EvalWorkerClient {
16134
16102
  },
16135
16103
  reject
16136
16104
  });
16137
- this.resetExportTimeout();
16138
16105
  const request = {
16139
16106
  type: "export-exact",
16140
16107
  payload: { reqId, format, ...scriptContext }
@@ -16156,7 +16123,6 @@ class EvalWorkerClient {
16156
16123
  },
16157
16124
  reject
16158
16125
  });
16159
- this.resetExportTimeout();
16160
16126
  const request = {
16161
16127
  type: "export-mesh",
16162
16128
  payload: { reqId, format, ...scriptContext }
@@ -16170,8 +16136,7 @@ class EvalWorkerClient {
16170
16136
  this.worker.postMessage({ type: "set-project", payload: { projectId } });
16171
16137
  }
16172
16138
  terminate() {
16173
- this.clearRunTimeout();
16174
- this.clearExportTimeout();
16139
+ this.clearInitTimeout();
16175
16140
  this.clearActiveRun();
16176
16141
  this.workerWasmHeap = null;
16177
16142
  this.lastWorkerFailure = null;
@@ -17323,11 +17288,48 @@ const VIEW_PREFERENCES_KEY = "fc-view-preferences-v1";
17323
17288
  const INSPECT_POINT_SAMPLE_COUNT_MIN = 100;
17324
17289
  const INSPECT_POINT_SAMPLE_COUNT_MAX = 1e4;
17325
17290
  const DEFAULT_INSPECT_POINT_SAMPLE_COUNT = 2e3;
17291
+ const DEFAULT_MANUAL_SCENE_SETTINGS = {
17292
+ enabled: false,
17293
+ backgroundColor: "#f6f7f8",
17294
+ groundVisible: true,
17295
+ groundColor: "#eef0f2",
17296
+ ambientIntensity: 0.3,
17297
+ keyIntensity: 1.45,
17298
+ fillIntensity: 0.55,
17299
+ rimIntensity: 0.2,
17300
+ environmentIntensity: 0.9
17301
+ };
17326
17302
  const resolveInspectPointSampleCount = (value) => {
17327
17303
  const numeric = typeof value === "number" ? value : Number(value);
17328
17304
  if (!Number.isFinite(numeric)) return DEFAULT_INSPECT_POINT_SAMPLE_COUNT;
17329
17305
  return Math.max(INSPECT_POINT_SAMPLE_COUNT_MIN, Math.min(INSPECT_POINT_SAMPLE_COUNT_MAX, Math.round(numeric)));
17330
17306
  };
17307
+ const HEX_COLOR_RE = /^#[0-9a-f]{6}$/i;
17308
+ const resolveHexColor = (value, fallback) => {
17309
+ return typeof value === "string" && HEX_COLOR_RE.test(value) ? value : fallback;
17310
+ };
17311
+ const resolveClampedNumber = (value, fallback, min, max2) => {
17312
+ const numeric = typeof value === "number" ? value : Number(value);
17313
+ if (!Number.isFinite(numeric)) return fallback;
17314
+ return Math.max(min, Math.min(max2, numeric));
17315
+ };
17316
+ const resolveManualSceneSettings = (value) => {
17317
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
17318
+ return DEFAULT_MANUAL_SCENE_SETTINGS;
17319
+ }
17320
+ const raw = value;
17321
+ return {
17322
+ enabled: typeof raw.enabled === "boolean" ? raw.enabled : DEFAULT_MANUAL_SCENE_SETTINGS.enabled,
17323
+ backgroundColor: resolveHexColor(raw.backgroundColor, DEFAULT_MANUAL_SCENE_SETTINGS.backgroundColor),
17324
+ groundVisible: typeof raw.groundVisible === "boolean" ? raw.groundVisible : DEFAULT_MANUAL_SCENE_SETTINGS.groundVisible,
17325
+ groundColor: resolveHexColor(raw.groundColor, DEFAULT_MANUAL_SCENE_SETTINGS.groundColor),
17326
+ ambientIntensity: resolveClampedNumber(raw.ambientIntensity, DEFAULT_MANUAL_SCENE_SETTINGS.ambientIntensity, 0, 3),
17327
+ keyIntensity: resolveClampedNumber(raw.keyIntensity, DEFAULT_MANUAL_SCENE_SETTINGS.keyIntensity, 0, 5),
17328
+ fillIntensity: resolveClampedNumber(raw.fillIntensity, DEFAULT_MANUAL_SCENE_SETTINGS.fillIntensity, 0, 3),
17329
+ rimIntensity: resolveClampedNumber(raw.rimIntensity, DEFAULT_MANUAL_SCENE_SETTINGS.rimIntensity, 0, 3),
17330
+ environmentIntensity: resolveClampedNumber(raw.environmentIntensity, DEFAULT_MANUAL_SCENE_SETTINGS.environmentIntensity, 0, 3)
17331
+ };
17332
+ };
17331
17333
  const readViewPreferences = () => {
17332
17334
  if (typeof window === "undefined") return {};
17333
17335
  try {
@@ -17439,7 +17441,10 @@ const INITIAL_SAVED = {};
17439
17441
  const VIEW_INSPECT_CHANNELS = /* @__PURE__ */ new Set([
17440
17442
  "none",
17441
17443
  "mask",
17444
+ "normals",
17445
+ "zebra",
17442
17446
  "connectivity",
17447
+ "floating",
17443
17448
  "distance",
17444
17449
  "collisions",
17445
17450
  "thickness",
@@ -18210,6 +18215,16 @@ Switch to LOCAL mode or wait for the server to recover.`,
18210
18215
  writeViewPreferences({ renderMode: mode });
18211
18216
  set({ renderMode: mode });
18212
18217
  },
18218
+ manualScene: resolveManualSceneSettings(initialViewPreferences.manualScene),
18219
+ setManualScene: (patch) => set((state2) => {
18220
+ const next = resolveManualSceneSettings({ ...state2.manualScene, ...patch });
18221
+ writeViewPreferences({ manualScene: next });
18222
+ return { manualScene: next };
18223
+ }),
18224
+ resetManualScene: () => {
18225
+ writeViewPreferences({ manualScene: DEFAULT_MANUAL_SCENE_SETTINGS });
18226
+ set({ manualScene: DEFAULT_MANUAL_SCENE_SETTINGS });
18227
+ },
18213
18228
  inspectChannel: resolveViewInspectChannel(initialViewPreferences.inspectChannel),
18214
18229
  setInspectChannel: (channel) => {
18215
18230
  const next = resolveViewInspectChannel(channel);
@@ -18234,11 +18249,16 @@ Switch to LOCAL mode or wait for the server to recover.`,
18234
18249
  set({ projectionMode: mode });
18235
18250
  },
18236
18251
  gridEnabled: initialViewPreferences.gridEnabled ?? true,
18252
+ axesVisible: initialViewPreferences.axesVisible ?? true,
18237
18253
  gridSize: initialViewPreferences.gridSize ?? 10,
18238
18254
  setGridEnabled: (enabled) => {
18239
18255
  writeViewPreferences({ gridEnabled: enabled });
18240
18256
  set({ gridEnabled: enabled });
18241
18257
  },
18258
+ setAxesVisible: (visible) => {
18259
+ writeViewPreferences({ axesVisible: visible });
18260
+ set({ axesVisible: visible });
18261
+ },
18242
18262
  setGridSize: (size) => {
18243
18263
  writeViewPreferences({ gridSize: size });
18244
18264
  set({ gridSize: size });
@@ -19021,7 +19041,7 @@ useAuthStore.subscribe((state2) => {
19021
19041
  function roundCoord(v) {
19022
19042
  return Math.round(v * 10) / 10;
19023
19043
  }
19024
- function fmt(v) {
19044
+ function fmt$1(v) {
19025
19045
  const r2 = roundCoord(v);
19026
19046
  return Number.isInteger(r2) ? String(r2) : r2.toFixed(1);
19027
19047
  }
@@ -19040,13 +19060,13 @@ function generateSketchCode(session) {
19040
19060
  return lines.join("\n");
19041
19061
  }
19042
19062
  function pointStatement(varName, x, y) {
19043
- return `const ${varName} = sk.point(${fmt(x)}, ${fmt(y)});`;
19063
+ return `const ${varName} = sk.point(${fmt$1(x)}, ${fmt$1(y)});`;
19044
19064
  }
19045
19065
  function lineStatement(varName, startVar, endVar) {
19046
19066
  return `const ${varName} = sk.line(${startVar}, ${endVar});`;
19047
19067
  }
19048
19068
  function circleStatement(varName, centerVar, radius) {
19049
- return `const ${varName} = sk.circle(${centerVar}, ${fmt(radius)});`;
19069
+ return `const ${varName} = sk.circle(${centerVar}, ${fmt$1(radius)});`;
19050
19070
  }
19051
19071
  function arcStatement(varName, centerVar, startVar, endVar) {
19052
19072
  return `const ${varName} = sk.arcByCenter(${centerVar}, ${startVar}, ${endVar});`;
@@ -19056,7 +19076,7 @@ const CONSTRAINT_API_MAP = {
19056
19076
  };
19057
19077
  function constraintStatement(type, ...args) {
19058
19078
  const apiName = CONSTRAINT_API_MAP[type] ?? type;
19059
- const fmtArgs = args.map((a2) => typeof a2 === "number" ? fmt(a2) : a2).join(", ");
19079
+ const fmtArgs = args.map((a2) => typeof a2 === "number" ? fmt$1(a2) : a2).join(", ");
19060
19080
  return `sk.${apiName}(${fmtArgs});`;
19061
19081
  }
19062
19082
  function isConstraintTool(tool) {
@@ -26737,12 +26757,12 @@ function SceneEffects({ config }) {
26737
26757
  /* @__PURE__ */ jsxRuntimeExports.jsx(dt$1, { children: effects })
26738
26758
  ] });
26739
26759
  }
26740
- function ScenePostProcessing({ config }) {
26760
+ function ScenePostProcessing({ config, interactivePreview }) {
26741
26761
  const pp = config.postProcessing;
26742
26762
  if (!pp) return null;
26743
26763
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
26744
26764
  pp.toneMappingExposure !== void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneExposure, { exposure: pp.toneMappingExposure }),
26745
- /* @__PURE__ */ jsxRuntimeExports.jsx(SceneEffects, { config })
26765
+ !interactivePreview && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneEffects, { config })
26746
26766
  ] });
26747
26767
  }
26748
26768
  function SceneEnvironment({ config }) {
@@ -26757,9 +26777,9 @@ function SceneEnvironment({ config }) {
26757
26777
  }
26758
26778
  );
26759
26779
  }
26760
- function SceneGround({ config, modelBoundsMinZ }) {
26780
+ function SceneGround({ config, modelBoundsMinZ, hidden }) {
26761
26781
  const ground = config.ground;
26762
- if (!ground || ground.visible === false) return null;
26782
+ if (hidden || !ground || ground.visible === false) return null;
26763
26783
  const offset = ground.offset ?? 0;
26764
26784
  const height = (modelBoundsMinZ ?? 0) - offset;
26765
26785
  return /* @__PURE__ */ jsxRuntimeExports.jsxs("mesh", { position: [0, 0, height], receiveShadow: ground.receiveShadow ?? false, children: [
@@ -26769,8 +26789,10 @@ function SceneGround({ config, modelBoundsMinZ }) {
26769
26789
  }
26770
26790
  function SceneConfigurator({
26771
26791
  config,
26792
+ interactivePreview = false,
26772
26793
  controlsRef,
26773
26794
  modelBoundsMinZ,
26795
+ hideGround = false,
26774
26796
  onDefaultLightsOverridden,
26775
26797
  onDefaultEnvironmentOverridden
26776
26798
  }) {
@@ -26785,11 +26807,11 @@ function SceneConfigurator({
26785
26807
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
26786
26808
  /* @__PURE__ */ jsxRuntimeExports.jsx(SceneBackground, { config }),
26787
26809
  /* @__PURE__ */ jsxRuntimeExports.jsx(SceneCamera, { config, controlsRef }),
26788
- config.lights && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneLights, { lights: config.lights }),
26810
+ !interactivePreview && config.lights && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneLights, { lights: config.lights }),
26789
26811
  config.fog && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneFog, { fog: config.fog }),
26790
- config.environment && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneEnvironment, { config }),
26791
- config.ground && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneGround, { config, modelBoundsMinZ }),
26792
- /* @__PURE__ */ jsxRuntimeExports.jsx(ScenePostProcessing, { config })
26812
+ !interactivePreview && config.environment && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneEnvironment, { config }),
26813
+ config.ground && /* @__PURE__ */ jsxRuntimeExports.jsx(SceneGround, { config, modelBoundsMinZ, hidden: hideGround }),
26814
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ScenePostProcessing, { config, interactivePreview })
26793
26815
  ] });
26794
26816
  }
26795
26817
  function ClippingManager({ active }) {
@@ -26799,9 +26821,45 @@ function ClippingManager({ active }) {
26799
26821
  }, [gl, active]);
26800
26822
  return null;
26801
26823
  }
26824
+ function matrixToCollisionMat4(matrix) {
26825
+ if (!matrix) return void 0;
26826
+ const e2 = matrix.elements;
26827
+ 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]];
26828
+ }
26829
+ function buildViewportCollisionEntries(objects, objectSettings, objectMatrices) {
26830
+ const entries = [];
26831
+ objects.forEach((obj) => {
26832
+ var _a3;
26833
+ if (!obj.shape) return;
26834
+ if (((_a3 = objectSettings[obj.id]) == null ? void 0 : _a3.visible) === false) return;
26835
+ try {
26836
+ const bb = obj.shape.boundingBox();
26837
+ entries.push({
26838
+ id: obj.id,
26839
+ name: obj.name,
26840
+ shape: obj.shape,
26841
+ min: [bb.min[0], bb.min[1], bb.min[2]],
26842
+ max: [bb.max[0], bb.max[1], bb.max[2]],
26843
+ transform: matrixToCollisionMat4(objectMatrices[obj.id]),
26844
+ groupName: obj.groupName,
26845
+ treePath: obj.treePath,
26846
+ mock: obj.mock
26847
+ });
26848
+ } catch {
26849
+ }
26850
+ });
26851
+ return entries;
26852
+ }
26853
+ function analyzeViewportCollisions(objects, objectSettings, objectMatrices) {
26854
+ return analyzeCollisionIntersections(buildViewportCollisionEntries(objects, objectSettings, objectMatrices), {
26855
+ includeBBoxCandidates: false
26856
+ });
26857
+ }
26802
26858
  const COLLISION_SOURCE_OPACITY = 0.22;
26803
26859
  const COLLISION_SOURCE_COLOR = [180, 200, 220];
26804
26860
  const COLLISION_HIGHLIGHT_COLOR = [255, 68, 16];
26861
+ const FLOATING_HIGHLIGHT_COLOR = [255, 68, 16];
26862
+ const FLOATING_CONTEXT_COLOR = [38, 49, 58];
26805
26863
  const COLLISION_PALETTE = [
26806
26864
  COLLISION_HIGHLIGHT_COLOR,
26807
26865
  [0, 204, 255],
@@ -26838,6 +26896,10 @@ const MASK_PALETTE = [
26838
26896
  [0, 0, 128],
26839
26897
  [128, 128, 128]
26840
26898
  ];
26899
+ const DISTANCE_NEAR_COLOR = [66, 220, 120];
26900
+ const DISTANCE_MID_COLOR = [255, 214, 0];
26901
+ const DISTANCE_FAR_COLOR = [255, 68, 16];
26902
+ const DISTANCE_UNKNOWN_COLOR = [128, 128, 128];
26841
26903
  function rgbToHex(color2) {
26842
26904
  return `#${color2.map((value) => value.toString(16).padStart(2, "0")).join("")}`;
26843
26905
  }
@@ -26850,48 +26912,13 @@ function maskColorForIndex(index) {
26850
26912
  function collisionColorForIndex(index) {
26851
26913
  return rgbToHex(COLLISION_PALETTE[(index - 1) % COLLISION_PALETTE.length]);
26852
26914
  }
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
26915
  function CollisionInspectionOverlay({
26885
26916
  objects,
26886
26917
  objectSettings,
26887
26918
  objectMatrices
26888
26919
  }) {
26889
26920
  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
- });
26921
+ const report = analyzeViewportCollisions(objects, objectSettings, objectMatrices);
26895
26922
  return report.collisions.flatMap((collision) => {
26896
26923
  try {
26897
26924
  const geometry = shapeToGeometry(collision.shape);
@@ -26941,53 +26968,108 @@ function ConstructionGhostOverlay({ matrix }) {
26941
26968
  /* @__PURE__ */ jsxRuntimeExports.jsx("lineSegments", { geometry: edgesGeo, renderOrder: 3, children: /* @__PURE__ */ jsxRuntimeExports.jsx("lineBasicMaterial", { color: "#4a9eff", transparent: true, opacity: 0.55, depthTest: false, depthWrite: false }) })
26942
26969
  ] });
26943
26970
  }
26944
- const MAX_CACHE_SIZE = 40;
26945
- const geometryCache = /* @__PURE__ */ new Map();
26946
- function getCachedGeometry(step) {
26947
- const cacheKey = constructionStepGeometryCacheKey(step);
26948
- const cached = geometryCache.get(cacheKey);
26949
- if (cached) return cached;
26950
- try {
26951
- const shape = buildShapeFromCompilePlan(step.cumulativePlan);
26952
- const { solid, edges, hasSmoothNormals } = shapeToGeometry(shape);
26953
- if (!solid || !edges) return null;
26954
- const entry = { solid, edges, hasSmoothNormals };
26955
- if (geometryCache.size >= MAX_CACHE_SIZE) {
26956
- const firstKey = geometryCache.keys().next().value;
26957
- if (firstKey !== void 0) {
26958
- const old = geometryCache.get(firstKey);
26959
- old == null ? void 0 : old.solid.dispose();
26960
- old == null ? void 0 : old.edges.dispose();
26961
- geometryCache.delete(firstKey);
26962
- }
26963
- }
26964
- geometryCache.set(cacheKey, entry);
26965
- return entry;
26966
- } catch {
26967
- return null;
26971
+ class ConstructionHistoryWorkerClient {
26972
+ constructor(workerFactory = () => new Worker(new URL(
26973
+ /* @vite-ignore */
26974
+ "/assets/constructionHistoryWorker-z9_LGiRd.js",
26975
+ import.meta.url
26976
+ ), { type: "module" })) {
26977
+ __publicField(this, "worker", null);
26978
+ __publicField(this, "reqId", 0);
26979
+ __publicField(this, "pending", null);
26980
+ this.workerFactory = workerFactory;
26968
26981
  }
26982
+ getWorker() {
26983
+ if (this.worker) return this.worker;
26984
+ this.worker = this.workerFactory();
26985
+ this.worker.onmessage = (event) => {
26986
+ const data = event.data;
26987
+ const pending = this.pending;
26988
+ if (!pending) return;
26989
+ this.pending = null;
26990
+ if (data.type === "history-geometry-error") {
26991
+ pending.reject(new Error(data.payload.message));
26992
+ } else {
26993
+ pending.resolve(data.payload.result);
26994
+ }
26995
+ };
26996
+ this.worker.onerror = (event) => {
26997
+ var _a3, _b2;
26998
+ const error = new Error(event.message || "Construction history worker failed unexpectedly.");
26999
+ (_a3 = this.pending) == null ? void 0 : _a3.reject(error);
27000
+ this.pending = null;
27001
+ (_b2 = this.worker) == null ? void 0 : _b2.terminate();
27002
+ this.worker = null;
27003
+ };
27004
+ return this.worker;
27005
+ }
27006
+ replaceActiveWorker() {
27007
+ var _a3, _b2;
27008
+ (_a3 = this.pending) == null ? void 0 : _a3.reject(new Error("cancelled"));
27009
+ this.pending = null;
27010
+ (_b2 = this.worker) == null ? void 0 : _b2.terminate();
27011
+ this.worker = null;
27012
+ }
27013
+ build(payload) {
27014
+ if (this.pending) this.replaceActiveWorker();
27015
+ const reqId = ++this.reqId;
27016
+ const request = {
27017
+ type: "build",
27018
+ payload: {
27019
+ reqId,
27020
+ ...payload
27021
+ }
27022
+ };
27023
+ return new Promise((resolve2, reject) => {
27024
+ this.pending = { resolve: resolve2, reject };
27025
+ this.getWorker().postMessage(request);
27026
+ });
27027
+ }
27028
+ dispose() {
27029
+ this.replaceActiveWorker();
27030
+ }
27031
+ }
27032
+ const constructionHistoryWorkerClient = new ConstructionHistoryWorkerClient();
27033
+ function historyGeometryRequestKey(activeBackend, items) {
27034
+ return `${activeBackend}:${items.map(({ objectId, step }) => `${objectId}:${step.index}:${step.cumulativePlanCacheKey}`).join("|")}`;
26969
27035
  }
26970
- function clearHistoryGeometryCache() {
26971
- for (const entry of geometryCache.values()) {
26972
- entry.solid.dispose();
26973
- entry.edges.dispose();
27036
+ function geometryItemKey(objectId, stepIndex) {
27037
+ return `${objectId}:${stepIndex}`;
27038
+ }
27039
+ function bufferGeometryFromWorkerGeometry(geometry) {
27040
+ const solid = new BufferGeometry();
27041
+ solid.setAttribute("position", new BufferAttribute(geometry.positions, 3));
27042
+ if (geometry.normals.length > 0) solid.setAttribute("normal", new BufferAttribute(geometry.normals, 3));
27043
+ if (geometry.triangleFaceIds.length > 0) {
27044
+ solid.userData.forgeTriangleFaceIds = geometry.triangleFaceIds;
27045
+ solid.userData.forgeFaceIdNames = geometry.faceIdNames;
26974
27046
  }
26975
- geometryCache.clear();
27047
+ const edges = new BufferGeometry();
27048
+ edges.setAttribute("position", new BufferAttribute(geometry.edgePositions, 3));
27049
+ return { solid, edges, hasSmoothNormals: geometry.hasSmoothNormals };
27050
+ }
27051
+ function disposeBufferedHistoryGeometry(geometry) {
27052
+ geometry.solid.dispose();
27053
+ geometry.edges.dispose();
26976
27054
  }
26977
27055
  function ConstructionHistoryOverlay({
27056
+ activeBackend,
26978
27057
  objectMatrices,
26979
- objectSettings
27058
+ objectSettings,
27059
+ onGeometryStatusChange
26980
27060
  }) {
26981
27061
  const historyMode = useForgeStore((s) => s.historyMode);
26982
27062
  const historySteps = useForgeStore((s) => s.historySteps);
26983
27063
  const historyCurrentStep = useForgeStore((s) => s.historyCurrentStep);
26984
27064
  const theme = useForgeStore((s) => themes[s.theme]);
26985
27065
  const [fadeOpacity, setFadeOpacity] = reactExports.useState(1);
27066
+ const [objectGeos, setObjectGeos] = reactExports.useState([]);
26986
27067
  const fadeRef = reactExports.useRef(0);
27068
+ const objectGeosRef = reactExports.useRef([]);
26987
27069
  const prevStepRef = reactExports.useRef(-1);
26988
27070
  reactExports.useEffect(() => {
26989
- return () => clearHistoryGeometryCache();
26990
- }, []);
27071
+ return () => onGeometryStatusChange == null ? void 0 : onGeometryStatusChange({ status: "idle", stepCount: 0, error: null });
27072
+ }, [onGeometryStatusChange]);
26991
27073
  reactExports.useEffect(() => {
26992
27074
  if (prevStepRef.current !== historyCurrentStep) {
26993
27075
  prevStepRef.current = historyCurrentStep;
@@ -27008,16 +27090,86 @@ function ConstructionHistoryOverlay({
27008
27090
  }, [historyCurrentStep]);
27009
27091
  const visibleStacks = reactExports.useMemo(() => buildVisibleHistoryStacks(historySteps, historyCurrentStep), [historySteps, historyCurrentStep]);
27010
27092
  const currentStep = historySteps[historyCurrentStep] ?? null;
27011
- const objectGeos = reactExports.useMemo(() => {
27012
- const result = [];
27093
+ const visibleHistoryItems = reactExports.useMemo(() => {
27094
+ const items = [];
27013
27095
  for (const [objectId, stack] of visibleStacks) {
27014
- for (const step of stack) {
27015
- const geo = getCachedGeometry(step);
27016
- if (geo) result.push({ objectId, geo, step });
27017
- }
27096
+ for (const step of stack) items.push({ objectId, step });
27018
27097
  }
27019
- return result;
27098
+ return items;
27020
27099
  }, [visibleStacks]);
27100
+ const requestKey = reactExports.useMemo(
27101
+ () => historyMode && visibleHistoryItems.length > 0 ? historyGeometryRequestKey(activeBackend, visibleHistoryItems) : "idle",
27102
+ [activeBackend, historyMode, visibleHistoryItems]
27103
+ );
27104
+ const [geometryState, setGeometryState] = reactExports.useState({
27105
+ status: "idle",
27106
+ requestKey: "idle",
27107
+ geometries: [],
27108
+ error: null
27109
+ });
27110
+ reactExports.useEffect(() => {
27111
+ onGeometryStatusChange == null ? void 0 : onGeometryStatusChange({
27112
+ status: historyMode ? geometryState.status : "idle",
27113
+ stepCount: visibleHistoryItems.length,
27114
+ error: historyMode ? geometryState.error : null
27115
+ });
27116
+ }, [geometryState.error, geometryState.status, historyMode, onGeometryStatusChange, visibleHistoryItems.length]);
27117
+ reactExports.useEffect(() => {
27118
+ if (!historyMode || visibleHistoryItems.length === 0) {
27119
+ constructionHistoryWorkerClient.dispose();
27120
+ setGeometryState({ status: "idle", requestKey, geometries: [], error: null });
27121
+ return;
27122
+ }
27123
+ let cancelled = false;
27124
+ setGeometryState({ status: "loading", requestKey, geometries: [], error: null });
27125
+ void constructionHistoryWorkerClient.build({
27126
+ activeBackend,
27127
+ items: visibleHistoryItems.map(({ objectId, step }) => ({
27128
+ objectId,
27129
+ stepIndex: step.index,
27130
+ cumulativePlan: step.cumulativePlan
27131
+ }))
27132
+ }).then((result) => {
27133
+ if (cancelled) return;
27134
+ setGeometryState({ status: "ready", requestKey, geometries: result.geometries, error: null });
27135
+ }).catch((error) => {
27136
+ if (cancelled || error instanceof Error && error.message === "cancelled") return;
27137
+ setGeometryState({
27138
+ status: "error",
27139
+ requestKey,
27140
+ geometries: [],
27141
+ error: error instanceof Error ? error.message : String(error)
27142
+ });
27143
+ });
27144
+ return () => {
27145
+ cancelled = true;
27146
+ constructionHistoryWorkerClient.dispose();
27147
+ };
27148
+ }, [activeBackend, historyMode, requestKey, visibleHistoryItems]);
27149
+ reactExports.useEffect(() => {
27150
+ const nextGeos = [];
27151
+ if (historyMode && geometryState.status === "ready" && geometryState.requestKey === requestKey) {
27152
+ const itemByKey = new Map(visibleHistoryItems.map((item) => [geometryItemKey(item.objectId, item.step.index), item]));
27153
+ for (const geometry of geometryState.geometries) {
27154
+ const item = itemByKey.get(geometryItemKey(geometry.objectId, geometry.stepIndex));
27155
+ if (!item) continue;
27156
+ nextGeos.push({
27157
+ objectId: item.objectId,
27158
+ step: item.step,
27159
+ geo: bufferGeometryFromWorkerGeometry(geometry)
27160
+ });
27161
+ }
27162
+ }
27163
+ for (const { geo } of objectGeosRef.current) disposeBufferedHistoryGeometry(geo);
27164
+ objectGeosRef.current = nextGeos;
27165
+ setObjectGeos(nextGeos);
27166
+ }, [geometryState, historyMode, requestKey, visibleHistoryItems]);
27167
+ reactExports.useEffect(() => {
27168
+ return () => {
27169
+ for (const { geo } of objectGeosRef.current) disposeBufferedHistoryGeometry(geo);
27170
+ objectGeosRef.current = [];
27171
+ };
27172
+ }, []);
27021
27173
  if (!historyMode || objectGeos.length === 0) return null;
27022
27174
  return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: objectGeos.map(({ objectId, geo, step }) => {
27023
27175
  var _a3;
@@ -29147,7 +29299,7 @@ function generateReportInWorker(options) {
29147
29299
  return new Promise((resolve2, reject) => {
29148
29300
  const worker = new Worker(new URL(
29149
29301
  /* @vite-ignore */
29150
- "/assets/reportWorker-CU8RZ4O0.js",
29302
+ "/assets/reportWorker-Bz9tGiHb.js",
29151
29303
  import.meta.url
29152
29304
  ), { type: "module" });
29153
29305
  const cleanup = () => {
@@ -32352,6 +32504,7 @@ const PHASE_CONFIG = {
32352
32504
  serializing: { color: "#7c4dff", label: "Preparing display" },
32353
32505
  exporting: { color: "#4caf50", label: "Exporting geometry" },
32354
32506
  inspecting: { color: "#14b8a6", label: "Generating inspect view" },
32507
+ history: { color: "#c084fc", label: "Building timeline geometry" },
32355
32508
  idle: { color: "#888", label: "" }
32356
32509
  };
32357
32510
  const PHASE_ORDER = ["kernel-init", "evaluating", "serializing"];
@@ -32359,36 +32512,18 @@ function formatEvaluationBackendLabel(activeBackend, computeTarget) {
32359
32512
  const backend = activeBackend === "occt" ? "OCCT" : activeBackend === "manifold" ? "Manifold" : activeBackend === "truck" ? "Truck" : "kernel";
32360
32513
  return computeTarget === "server" ? `Server ${backend}` : `Local ${backend}`;
32361
32514
  }
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
32515
  function EvaluationIndicator({
32377
32516
  phase,
32378
32517
  label,
32379
- constructionSteps = [],
32380
32518
  activeBackend,
32381
32519
  computeTarget
32382
32520
  }) {
32383
32521
  const [frame2, setFrame] = reactExports.useState(0);
32384
32522
  const [elapsed, setElapsed] = reactExports.useState(0);
32385
- const [previewStepIndex, setPreviewStepIndex] = reactExports.useState(0);
32386
32523
  const startRef = reactExports.useRef(Date.now());
32387
- const stepCount = constructionSteps.length;
32388
32524
  reactExports.useEffect(() => {
32389
32525
  startRef.current = Date.now();
32390
32526
  setElapsed(0);
32391
- setPreviewStepIndex(0);
32392
32527
  }, [phase]);
32393
32528
  reactExports.useEffect(() => {
32394
32529
  const spinnerInterval = setInterval(() => setFrame((f2) => (f2 + 1) % BRAILLE_FRAMES.length), 80);
@@ -32398,166 +32533,85 @@ function EvaluationIndicator({
32398
32533
  clearInterval(timerInterval);
32399
32534
  };
32400
32535
  }, []);
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]);
32407
32536
  const config = PHASE_CONFIG[phase] ?? PHASE_CONFIG["evaluating"];
32408
32537
  const elapsedSec = (elapsed / 1e3).toFixed(1);
32409
32538
  const phaseOrder = PHASE_ORDER.includes(phase) ? PHASE_ORDER : [phase];
32410
32539
  const phaseIdx = phaseOrder.indexOf(phase);
32411
- const { displayStepIndex, previewStep, progress } = constructionStepPreview(constructionSteps, previewStepIndex);
32412
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32540
+ const backendLabel = formatEvaluationBackendLabel(activeBackend, computeTarget);
32541
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
32413
32542
  "div",
32414
32543
  {
32415
32544
  style: {
32416
32545
  position: "absolute",
32417
32546
  bottom: 16,
32418
32547
  right: 16,
32419
- width: previewStep ? "min(360px, calc(100vw - 32px))" : void 0,
32420
32548
  background: "var(--fc-bgPanel)",
32421
32549
  border: "1px solid var(--fc-border)",
32422
32550
  borderRadius: 8,
32423
- padding: previewStep ? "10px 12px" : "8px 14px",
32551
+ padding: "8px 14px",
32424
32552
  pointerEvents: "none",
32425
32553
  display: "grid",
32426
- gap: previewStep ? 8 : 0,
32554
+ gap: 0,
32427
32555
  fontSize: 12,
32428
32556
  animation: "fc-fadein 0.2s ease-out",
32429
32557
  boxShadow: "0 4px 16px rgba(0,0,0,0.25)"
32430
32558
  },
32431
- children: [
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(
32559
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 10, minWidth: 0 }, children: [
32560
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: config.color, fontSize: 16, fontWeight: 700, width: 16, textAlign: "center", flex: "0 0 auto" }, children: BRAILLE_FRAMES[frame2] }),
32561
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32562
+ "span",
32563
+ {
32564
+ style: {
32565
+ color: "var(--fc-text)",
32566
+ fontWeight: 500,
32567
+ minWidth: 0,
32568
+ overflow: "hidden",
32569
+ textOverflow: "ellipsis",
32570
+ whiteSpace: "nowrap"
32571
+ },
32572
+ children: label ?? config.label
32573
+ }
32574
+ ),
32575
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
32576
+ "span",
32577
+ {
32578
+ style: {
32579
+ color: "var(--fc-textMuted)",
32580
+ fontSize: 11,
32581
+ minWidth: 0,
32582
+ overflow: "hidden",
32583
+ textOverflow: "ellipsis",
32584
+ whiteSpace: "nowrap"
32585
+ },
32586
+ children: backendLabel
32587
+ }
32588
+ ),
32589
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { display: "flex", gap: 4, alignItems: "center", marginLeft: "auto", flex: "0 0 auto" }, children: phaseOrder.map((p2, i) => {
32590
+ var _a3;
32591
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
32435
32592
  "span",
32436
32593
  {
32437
32594
  style: {
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"
32484
- }
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"
32595
+ width: 6,
32596
+ height: 6,
32597
+ borderRadius: "50%",
32598
+ background: i <= phaseIdx ? ((_a3 = PHASE_CONFIG[p2]) == null ? void 0 : _a3.color) ?? "var(--fc-border)" : "var(--fc-border)",
32599
+ transition: "background 0.3s ease",
32600
+ animation: i === phaseIdx ? "fc-pulse 1.2s ease-in-out infinite" : void 0
32548
32601
  }
32549
- }
32550
- ) })
32602
+ },
32603
+ p2
32604
+ );
32605
+ }) }),
32606
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-textDim)", fontVariantNumeric: "tabular-nums", fontSize: 11, flex: "0 0 auto" }, children: [
32607
+ elapsedSec,
32608
+ "s"
32551
32609
  ] })
32552
- ]
32610
+ ] })
32553
32611
  }
32554
32612
  );
32555
32613
  }
32556
- const MIN_FIELD_GRID_SIZE = 18;
32557
- const MAX_FIELD_GRID_SIZE = 32;
32558
- const MAX_BLEND_SAMPLES = 12;
32559
32614
  const MAX_SEARCH_RADIUS = 5;
32560
- const SAMPLE_RING_OFFSETS = [];
32561
32615
  for (let radius = 0; radius <= MAX_SEARCH_RADIUS; radius += 1) {
32562
32616
  const ring = [];
32563
32617
  for (let x = -radius; x <= radius; x += 1) {
@@ -32567,7 +32621,6 @@ for (let radius = 0; radius <= MAX_SEARCH_RADIUS; radius += 1) {
32567
32621
  }
32568
32622
  }
32569
32623
  }
32570
- SAMPLE_RING_OFFSETS.push(ring);
32571
32624
  }
32572
32625
  const INSPECT_HEATMAP_VERTEX_SHADER = `
32573
32626
  varying vec3 vLocalPosition;
@@ -32635,127 +32688,14 @@ const INSPECT_HEATMAP_FRAGMENT_SHADER = `
32635
32688
  gl_FragColor = vec4(color, 1.0);
32636
32689
  }
32637
32690
  `;
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);
32691
+ function createInspectHeatmapField(field) {
32692
+ const texture = new DataTexture(
32693
+ field.data,
32694
+ field.gridSize * field.gridSize,
32695
+ field.gridSize,
32696
+ RGBAFormat,
32697
+ UnsignedByteType
32698
+ );
32759
32699
  texture.minFilter = NearestFilter;
32760
32700
  texture.magFilter = NearestFilter;
32761
32701
  texture.generateMipmaps = false;
@@ -32763,11 +32703,72 @@ function buildInspectHeatmapField(geometry, pointCloud) {
32763
32703
  texture.needsUpdate = true;
32764
32704
  return {
32765
32705
  texture,
32766
- boundsMin: bounds.min,
32767
- boundsSize,
32768
- gridSize
32706
+ boundsMin: new Vector3(...field.boundsMin),
32707
+ boundsSize: new Vector3(...field.boundsSize),
32708
+ gridSize: field.gridSize
32769
32709
  };
32770
32710
  }
32711
+ function fieldKindUniform(kind) {
32712
+ return kind === "hybrid" ? 1 : 0;
32713
+ }
32714
+ function SurfaceFieldMaterial({
32715
+ field,
32716
+ color: color2,
32717
+ clippingPlanes,
32718
+ fieldScale
32719
+ }) {
32720
+ const uniforms = reactExports.useMemo(
32721
+ () => ({
32722
+ uAccentColor: { value: new Color(field.accentColor) },
32723
+ uBaseColor: { value: new Color(field.baseColor) },
32724
+ uDarkColor: { value: new Color(field.darkColor) },
32725
+ uFieldKind: { value: fieldKindUniform(field.kind) },
32726
+ uFieldScale: { value: fieldScale },
32727
+ uGlow: { value: field.glow },
32728
+ uLineColor: { value: new Color(field.lineColor) },
32729
+ uLineWidth: { value: field.lineWidth },
32730
+ uNodeColor: { value: new Color(field.nodeColor) },
32731
+ uObjectColor: { value: new Color(color2) },
32732
+ uObjectColorMix: { value: field.objectColorMix },
32733
+ uSpacing: { value: field.spacing }
32734
+ }),
32735
+ [color2, field, fieldScale]
32736
+ );
32737
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
32738
+ "shaderMaterial",
32739
+ {
32740
+ vertexShader: SURFACE_FIELD_VERTEX_SHADER,
32741
+ fragmentShader: SURFACE_FIELD_FRAGMENT_SHADER,
32742
+ uniforms,
32743
+ side: DoubleSide,
32744
+ toneMapped: false,
32745
+ clippingPlanes
32746
+ }
32747
+ );
32748
+ }
32749
+ function ZebraInspectionMaterial({ clippingPlanes }) {
32750
+ const uniforms = reactExports.useMemo(
32751
+ () => ({
32752
+ uAccentColor: { value: new Color(ZEBRA_ACCENT_COLOR) },
32753
+ uDarkColor: { value: new Color(ZEBRA_DARK_COLOR) },
32754
+ uLightColor: { value: new Color(ZEBRA_LIGHT_COLOR) },
32755
+ uStripeScale: { value: ZEBRA_STRIPE_SCALE },
32756
+ uStripeSoftness: { value: ZEBRA_STRIPE_SOFTNESS }
32757
+ }),
32758
+ []
32759
+ );
32760
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
32761
+ "shaderMaterial",
32762
+ {
32763
+ vertexShader: ZEBRA_STRIPE_VERTEX_SHADER,
32764
+ fragmentShader: ZEBRA_STRIPE_FRAGMENT_SHADER,
32765
+ uniforms,
32766
+ side: DoubleSide,
32767
+ toneMapped: false,
32768
+ clippingPlanes
32769
+ }
32770
+ );
32771
+ }
32771
32772
  function ForgeObject({
32772
32773
  obj,
32773
32774
  settings,
@@ -32778,8 +32779,10 @@ function ForgeObject({
32778
32779
  inspectColor,
32779
32780
  inspectMeshColors,
32780
32781
  inspectPointCloud,
32782
+ inspectHeatmapFieldData,
32781
32783
  isInteracting,
32782
32784
  matrix,
32785
+ surfaceFieldScale,
32783
32786
  isHovered,
32784
32787
  clippingPlanes,
32785
32788
  debugHighlightColor,
@@ -32825,15 +32828,16 @@ function ForgeObject({
32825
32828
  if (!solidGeo || !inspectMeshColors) return null;
32826
32829
  const position = solidGeo.getAttribute("position");
32827
32830
  if (!position || inspectMeshColors.length !== position.count * 3) return null;
32831
+ if (inspectChannel === "floating") return geometryWithVisibleVertexColors(solidGeo, inspectMeshColors);
32828
32832
  const geometry = solidGeo.clone();
32829
32833
  geometry.setAttribute("color", new BufferAttribute(inspectMeshColors, 3));
32830
32834
  return geometry;
32831
- }, [inspectMeshColors, solidGeo]);
32835
+ }, [inspectChannel, inspectMeshColors, solidGeo]);
32832
32836
  const isScalarInspect = inspectChannel === "thickness" || inspectChannel === "roughness";
32833
32837
  const inspectHeatmapField = reactExports.useMemo(() => {
32834
- if (!isScalarInspect || !solidGeo || !inspectPointCloud) return null;
32835
- return buildInspectHeatmapField(solidGeo, inspectPointCloud);
32836
- }, [inspectPointCloud, isScalarInspect, solidGeo]);
32838
+ if (!isScalarInspect || !inspectHeatmapFieldData) return null;
32839
+ return createInspectHeatmapField(inspectHeatmapFieldData);
32840
+ }, [inspectHeatmapFieldData, isScalarInspect]);
32837
32841
  reactExports.useEffect(() => {
32838
32842
  return () => {
32839
32843
  solidGeo == null ? void 0 : solidGeo.dispose();
@@ -32861,11 +32865,13 @@ function ForgeObject({
32861
32865
  const showInspectHeatmap = Boolean(
32862
32866
  isScalarInspect && inspectHeatmapField && (inspectDisplayMode === "heatmap" || inspectDisplayMode === "both")
32863
32867
  );
32868
+ const showScalarContextMesh = Boolean(isScalarInspect && !showInspectHeatmap);
32864
32869
  const showInspectPoints = Boolean(
32865
32870
  isScalarInspect && inspectPointGeo && (inspectDisplayMode === "points" || inspectDisplayMode === "both")
32866
32871
  );
32867
32872
  const renderStylePreset = getRenderStylePreset(renderStyle);
32868
32873
  const materialDefaults = renderStylePreset.material;
32874
+ const surfaceField = renderStylePreset.surfaceField;
32869
32875
  const authoredMaterialOpacity = (_a3 = obj.materialProps) == null ? void 0 : _a3.opacity;
32870
32876
  const authoredMaterialTransmission = (_b2 = obj.materialProps) == null ? void 0 : _b2.transmission;
32871
32877
  const hasAuthoredTransparency = authoredMaterialOpacity !== void 0 && authoredMaterialOpacity < 0.99 || authoredMaterialTransmission !== void 0 && authoredMaterialTransmission > 0;
@@ -32881,6 +32887,8 @@ function ForgeObject({
32881
32887
  const effectiveClippingPlanes = clippingPlanes ?? [];
32882
32888
  const inspectSolidGeo = inspectMeshColorGeo ?? solidGeo;
32883
32889
  const hasInspectMeshColors = inspectMeshColorGeo !== null;
32890
+ const showFloatingContext = showSolid && inspectChannel === "floating";
32891
+ const showFloatingObject = inspectChannel !== "floating" || hasInspectMeshColors || inspectColor !== "#000000";
32884
32892
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
32885
32893
  "group",
32886
32894
  {
@@ -32893,17 +32901,42 @@ function ForgeObject({
32893
32901
  onDoubleClick,
32894
32902
  onContextMenu,
32895
32903
  children: [
32896
- showSolid && (inspectChannel === "mask" || inspectChannel === "connectivity" || inspectChannel === "distance") && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: inspectSolidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32904
+ showFloatingContext && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, renderOrder: 1, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32905
+ "meshBasicMaterial",
32906
+ {
32907
+ color: rgbToHex(FLOATING_CONTEXT_COLOR),
32908
+ transparent: true,
32909
+ opacity: 0.18,
32910
+ side: DoubleSide,
32911
+ depthWrite: false,
32912
+ toneMapped: false,
32913
+ clippingPlanes: effectiveClippingPlanes
32914
+ }
32915
+ ) }),
32916
+ showSolid && (inspectChannel === "mask" || inspectChannel === "connectivity" || inspectChannel === "floating" || inspectChannel === "distance") && showFloatingObject && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: inspectSolidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32897
32917
  "meshBasicMaterial",
32898
32918
  {
32899
32919
  color: hasInspectMeshColors ? "#ffffff" : inspectColor ?? settings.color,
32900
32920
  vertexColors: hasInspectMeshColors,
32901
32921
  side: DoubleSide,
32902
32922
  toneMapped: false,
32923
+ clippingPlanes: effectiveClippingPlanes,
32924
+ polygonOffset: inspectChannel === "floating",
32925
+ polygonOffsetFactor: -1,
32926
+ polygonOffsetUnits: -1
32927
+ }
32928
+ ) }),
32929
+ showSolid && inspectChannel === "normals" && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32930
+ "meshNormalMaterial",
32931
+ {
32932
+ flatShading: !hasSmoothNormals,
32933
+ side: DoubleSide,
32934
+ toneMapped: false,
32903
32935
  clippingPlanes: effectiveClippingPlanes
32904
32936
  }
32905
32937
  ) }),
32906
- showSolid && isScalarInspect && !showInspectHeatmap && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32938
+ showSolid && inspectChannel === "zebra" && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ZebraInspectionMaterial, { clippingPlanes: effectiveClippingPlanes }) }),
32939
+ showSolid && showScalarContextMesh && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, renderOrder: 3, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32907
32940
  "meshBasicMaterial",
32908
32941
  {
32909
32942
  color: "#26313a",
@@ -32931,7 +32964,7 @@ function ForgeObject({
32931
32964
  clippingPlanes: effectiveClippingPlanes
32932
32965
  }
32933
32966
  ) }),
32934
- showSolid && showInspectPoints && inspectPointGeo && /* @__PURE__ */ jsxRuntimeExports.jsx("points", { geometry: inspectPointGeo, raycast: () => null, renderOrder: 5, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32967
+ showInspectPoints && inspectPointGeo && /* @__PURE__ */ jsxRuntimeExports.jsx("points", { geometry: inspectPointGeo, raycast: () => null, renderOrder: 5, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32935
32968
  "pointsMaterial",
32936
32969
  {
32937
32970
  size: 3,
@@ -32956,7 +32989,15 @@ function ForgeObject({
32956
32989
  clippingPlanes: effectiveClippingPlanes
32957
32990
  }
32958
32991
  ) }),
32959
- showSolid && inspectChannel === "none" && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
32992
+ showSolid && inspectChannel === "none" && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, userData: { forgeMesh: true }, children: surfaceField.enabled ? /* @__PURE__ */ jsxRuntimeExports.jsx(
32993
+ SurfaceFieldMaterial,
32994
+ {
32995
+ field: surfaceField,
32996
+ color: settings.color,
32997
+ clippingPlanes: effectiveClippingPlanes,
32998
+ fieldScale: surfaceFieldScale
32999
+ }
33000
+ ) : /* @__PURE__ */ jsxRuntimeExports.jsx(
32960
33001
  "meshPhysicalMaterial",
32961
33002
  {
32962
33003
  color: settings.color,
@@ -33315,6 +33356,263 @@ function HoveredJointOverlay({ state: state2, config }) {
33315
33356
  ] })
33316
33357
  ] });
33317
33358
  }
33359
+ function colorToHex(color2) {
33360
+ return rgbToHex(color2);
33361
+ }
33362
+ function fmt(value) {
33363
+ return Number.isInteger(value) ? String(value) : value.toFixed(1);
33364
+ }
33365
+ function inspectionLegendDefinitionFor(channel) {
33366
+ switch (channel) {
33367
+ case "mask":
33368
+ return {
33369
+ title: "Mask Colors",
33370
+ summary: "Each color identifies a different top-level object for picking or image analysis.",
33371
+ swatches: [
33372
+ { label: "Object A", detail: "unique id", color: rgbToHex(maskRgbForIndex(0)) },
33373
+ { label: "Object B", detail: "unique id", color: rgbToHex(maskRgbForIndex(1)) },
33374
+ { label: "Object C", detail: "unique id", color: rgbToHex(maskRgbForIndex(2)) },
33375
+ { label: "Object D", detail: "unique id", color: rgbToHex(maskRgbForIndex(3)) }
33376
+ ]
33377
+ };
33378
+ case "normals":
33379
+ return {
33380
+ title: "Normal Colors",
33381
+ summary: "Color comes from surface direction in view space, so seams and flipped normals become visible."
33382
+ };
33383
+ case "zebra":
33384
+ return {
33385
+ title: "Zebra Stripes",
33386
+ summary: "Continuous stripes imply smooth surface flow; kinks or broken stripes reveal continuity changes."
33387
+ };
33388
+ case "connectivity":
33389
+ return {
33390
+ title: "Connected Bodies",
33391
+ summary: "Each color is one physically connected component. Different colors mean disconnected islands.",
33392
+ swatches: [
33393
+ { label: "Component 1", detail: "connected", color: rgbToHex(maskRgbForIndex(0)) },
33394
+ { label: "Component 2", detail: "separate", color: rgbToHex(maskRgbForIndex(1)) },
33395
+ { label: "Component 3", detail: "separate", color: rgbToHex(maskRgbForIndex(2)) }
33396
+ ]
33397
+ };
33398
+ case "floating":
33399
+ return {
33400
+ title: "Floating Bodies",
33401
+ summary: "Highlights bodies that are disconnected from grounded material above the build plane.",
33402
+ swatches: [
33403
+ { label: "Floating", detail: "unsupported", color: rgbToHex(FLOATING_HIGHLIGHT_COLOR) },
33404
+ { label: "Context", detail: "other geometry", color: rgbToHex(FLOATING_CONTEXT_COLOR) }
33405
+ ]
33406
+ };
33407
+ case "distance":
33408
+ return {
33409
+ title: "Root Distance",
33410
+ summary: "Shows how far each disconnected component is from the rooted component graph.",
33411
+ ramp: {
33412
+ colors: [rgbToHex(DISTANCE_NEAR_COLOR), rgbToHex(DISTANCE_MID_COLOR), rgbToHex(DISTANCE_FAR_COLOR)],
33413
+ leftLabel: "Near root",
33414
+ centerLabel: "Mid",
33415
+ rightLabel: "Far"
33416
+ },
33417
+ swatches: [{ label: "Unknown", detail: "not resolved", color: rgbToHex(DISTANCE_UNKNOWN_COLOR) }]
33418
+ };
33419
+ case "collisions":
33420
+ return {
33421
+ title: "Collision Overlaps",
33422
+ summary: "Bright solids are actual overlap volumes; source bodies are ghosted behind them.",
33423
+ swatches: [
33424
+ { label: "Overlap", detail: "intersecting volume", color: rgbToHex(COLLISION_HIGHLIGHT_COLOR) },
33425
+ { label: "Ghost body", detail: "source geometry", color: rgbToHex(COLLISION_SOURCE_COLOR) }
33426
+ ]
33427
+ };
33428
+ case "thickness": {
33429
+ const options = DEFAULT_THICKNESS_INSPECTION_OPTIONS;
33430
+ return {
33431
+ title: "Wall Thickness",
33432
+ summary: "Red/orange is thin material. Green/blue has more printable or machinable thickness.",
33433
+ ramp: {
33434
+ colors: [
33435
+ colorToHex(THICKNESS_COLORS.critical),
33436
+ colorToHex(THICKNESS_COLORS.warning),
33437
+ colorToHex(THICKNESS_COLORS.ok),
33438
+ colorToHex(THICKNESS_COLORS.thick)
33439
+ ],
33440
+ leftLabel: `<= ${fmt(options.minThickness)} mm`,
33441
+ centerLabel: `${fmt(options.warnThickness)} mm warn`,
33442
+ rightLabel: `>= ${fmt(options.maxThickness)} mm`
33443
+ }
33444
+ };
33445
+ }
33446
+ case "roughness": {
33447
+ const options = DEFAULT_ROUGHNESS_INSPECTION_OPTIONS;
33448
+ return {
33449
+ title: "Surface Roughness",
33450
+ summary: "Color follows local angle change. Dark is smooth; warmer colors mean sharper or harsher mesh transitions.",
33451
+ swatches: [
33452
+ { label: "Smooth", detail: `< ${fmt(options.smoothAngleDeg)} deg`, color: colorToHex(ROUGHNESS_COLORS.smooth) },
33453
+ {
33454
+ label: "Moderate",
33455
+ detail: `${fmt(options.smoothAngleDeg)}-${fmt(options.sharpAngleDeg)} deg`,
33456
+ color: colorToHex(ROUGHNESS_COLORS.moderate)
33457
+ },
33458
+ {
33459
+ label: "Sharp",
33460
+ detail: `${fmt(options.sharpAngleDeg)}-${fmt(options.harshAngleDeg)} deg`,
33461
+ color: colorToHex(ROUGHNESS_COLORS.sharp)
33462
+ },
33463
+ { label: "Harsh", detail: `>= ${fmt(options.harshAngleDeg)} deg`, color: colorToHex(ROUGHNESS_COLORS.harsh) }
33464
+ ]
33465
+ };
33466
+ }
33467
+ default:
33468
+ return null;
33469
+ }
33470
+ }
33471
+ const panelStyle = {
33472
+ position: "absolute",
33473
+ left: 12,
33474
+ bottom: 12,
33475
+ width: "min(300px, calc(100% - 24px))",
33476
+ padding: "9px 10px",
33477
+ borderRadius: 8,
33478
+ border: "1px solid color-mix(in srgb, var(--fc-border) 86%, transparent)",
33479
+ background: "color-mix(in srgb, var(--fc-bgPanel) 92%, transparent)",
33480
+ boxShadow: "0 10px 26px rgba(0, 0, 0, 0.22)",
33481
+ color: "var(--fc-text)",
33482
+ fontSize: 11,
33483
+ lineHeight: 1.35,
33484
+ pointerEvents: "none",
33485
+ zIndex: 9
33486
+ };
33487
+ const titleStyle = {
33488
+ display: "flex",
33489
+ alignItems: "center",
33490
+ justifyContent: "space-between",
33491
+ gap: 8,
33492
+ marginBottom: 5,
33493
+ color: "var(--fc-text)",
33494
+ fontSize: 11,
33495
+ fontWeight: 700,
33496
+ letterSpacing: 0
33497
+ };
33498
+ const summaryStyle = {
33499
+ marginBottom: 7,
33500
+ color: "var(--fc-textDim)",
33501
+ overflowWrap: "anywhere"
33502
+ };
33503
+ const swatchGridStyle = {
33504
+ display: "grid",
33505
+ gridTemplateColumns: "repeat(2, minmax(0, 1fr))",
33506
+ gap: 6
33507
+ };
33508
+ function Ramp({ compact, ramp }) {
33509
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { minWidth: 0 }, children: [
33510
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33511
+ "div",
33512
+ {
33513
+ style: {
33514
+ height: 9,
33515
+ borderRadius: 999,
33516
+ border: "1px solid rgba(255, 255, 255, 0.24)",
33517
+ background: `linear-gradient(90deg, ${ramp.colors.join(", ")})`
33518
+ }
33519
+ }
33520
+ ),
33521
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
33522
+ "div",
33523
+ {
33524
+ style: {
33525
+ display: "grid",
33526
+ gridTemplateColumns: ramp.centerLabel ? "repeat(3, minmax(0, 1fr))" : "repeat(2, minmax(0, 1fr))",
33527
+ gap: 6,
33528
+ marginTop: 4,
33529
+ color: "var(--fc-textDim)",
33530
+ fontSize: compact ? 9 : 10
33531
+ },
33532
+ children: [
33533
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: ramp.leftLabel }),
33534
+ ramp.centerLabel && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { textAlign: "center" }, children: ramp.centerLabel }),
33535
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { textAlign: "right" }, children: ramp.rightLabel })
33536
+ ]
33537
+ }
33538
+ )
33539
+ ] });
33540
+ }
33541
+ function Swatch({ item }) {
33542
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "grid", gridTemplateColumns: "12px minmax(0, 1fr)", alignItems: "center", gap: 6, minWidth: 0 }, children: [
33543
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
33544
+ "span",
33545
+ {
33546
+ style: {
33547
+ width: 12,
33548
+ height: 12,
33549
+ borderRadius: 3,
33550
+ border: "1px solid rgba(255, 255, 255, 0.28)",
33551
+ background: item.color
33552
+ }
33553
+ }
33554
+ ),
33555
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: [
33556
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--fc-text)" }, children: item.label }),
33557
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-textDim)" }, children: [
33558
+ " - ",
33559
+ item.detail
33560
+ ] })
33561
+ ] })
33562
+ ] });
33563
+ }
33564
+ function InspectionLegend({ channel, warnings }) {
33565
+ const panelRef = reactExports.useRef(null);
33566
+ const [compact, setCompact] = reactExports.useState(false);
33567
+ const definition = inspectionLegendDefinitionFor(channel);
33568
+ reactExports.useEffect(() => {
33569
+ var _a3;
33570
+ const parent = (_a3 = panelRef.current) == null ? void 0 : _a3.parentElement;
33571
+ if (!parent || typeof ResizeObserver === "undefined") return;
33572
+ const updateCompact = () => setCompact(parent.clientWidth < 260);
33573
+ updateCompact();
33574
+ const observer = new ResizeObserver(updateCompact);
33575
+ observer.observe(parent);
33576
+ return () => observer.disconnect();
33577
+ }, [channel]);
33578
+ if (!definition) return null;
33579
+ const warning = warnings[0];
33580
+ const effectivePanelStyle = compact ? { ...panelStyle, padding: "7px 8px" } : panelStyle;
33581
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { ref: panelRef, className: "fc-inspection-legend", style: effectivePanelStyle, children: [
33582
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: titleStyle, children: [
33583
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { children: definition.title }),
33584
+ warning && /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--fc-warning)", fontSize: 10 }, children: "Warning" })
33585
+ ] }),
33586
+ !compact && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: summaryStyle, children: definition.summary }),
33587
+ definition.ramp && /* @__PURE__ */ jsxRuntimeExports.jsx(Ramp, { compact, ramp: definition.ramp }),
33588
+ definition.swatches && /* @__PURE__ */ jsxRuntimeExports.jsx(
33589
+ "div",
33590
+ {
33591
+ style: {
33592
+ ...swatchGridStyle,
33593
+ gridTemplateColumns: compact ? "minmax(0, 1fr)" : swatchGridStyle.gridTemplateColumns,
33594
+ marginTop: definition.ramp ? 8 : 0
33595
+ },
33596
+ children: definition.swatches.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx(Swatch, { item }, `${item.label}:${item.detail}`))
33597
+ }
33598
+ ),
33599
+ warning && /* @__PURE__ */ jsxRuntimeExports.jsx(
33600
+ "div",
33601
+ {
33602
+ style: {
33603
+ marginTop: 7,
33604
+ paddingTop: 7,
33605
+ borderTop: "1px solid color-mix(in srgb, var(--fc-border) 72%, transparent)",
33606
+ color: "var(--fc-warning)",
33607
+ overflow: "hidden",
33608
+ textOverflow: "ellipsis",
33609
+ whiteSpace: "nowrap"
33610
+ },
33611
+ children: warning
33612
+ }
33613
+ )
33614
+ ] });
33615
+ }
33318
33616
  function LabeledAxes({ size = 50 }) {
33319
33617
  const axesRef = reactExports.useRef(null);
33320
33618
  reactExports.useEffect(() => {
@@ -38548,11 +38846,13 @@ function useViewportState() {
38548
38846
  const files = useForgeStore((s) => s.files);
38549
38847
  const renderMode = useForgeStore((s) => s.renderMode);
38550
38848
  const renderStyle = useForgeStore((s) => s.renderStyle);
38849
+ const manualScene = useForgeStore((s) => s.manualScene);
38551
38850
  const inspectChannel = useForgeStore((s) => s.inspectChannel);
38552
38851
  const inspectDisplayMode = useForgeStore((s) => s.inspectDisplayMode);
38553
38852
  const inspectPointSampleCount = useForgeStore((s) => s.inspectPointSampleCount);
38554
38853
  const projectionMode = useForgeStore((s) => s.projectionMode);
38555
38854
  const gridEnabled = useForgeStore((s) => s.gridEnabled);
38855
+ const axesVisible = useForgeStore((s) => s.axesVisible);
38556
38856
  const gridSize = useForgeStore((s) => s.gridSize);
38557
38857
  const showPerformanceInfo = useForgeStore((s) => s.showPerformanceInfo);
38558
38858
  const objectSettings = useForgeStore((s) => s.objectSettings);
@@ -38713,6 +39013,26 @@ function useViewportState() {
38713
39013
  });
38714
39014
  return hasBounds ? bounds.min.z : null;
38715
39015
  }, [objects, objectMatrices]);
39016
+ const surfaceFieldScale = reactExports.useMemo(() => {
39017
+ const bounds = new Box3();
39018
+ let hasBounds = false;
39019
+ objects.forEach((obj) => {
39020
+ var _a3;
39021
+ if (((_a3 = objectSettings[obj.id]) == null ? void 0 : _a3.visible) === false) return;
39022
+ const matrix = objectMatrices[obj.id] ?? new Matrix4();
39023
+ if (obj.shape) {
39024
+ try {
39025
+ const bb = obj.shape.boundingBox();
39026
+ if (expandBoundsByTransformedAabb(bounds, bb.min, bb.max, matrix)) hasBounds = true;
39027
+ } catch {
39028
+ }
39029
+ }
39030
+ });
39031
+ if (!hasBounds) return 1;
39032
+ const size = bounds.getSize(new Vector3());
39033
+ const maxDim = Math.max(size.x, size.y, size.z);
39034
+ return Number.isFinite(maxDim) && maxDim > 0 ? maxDim / 5 : 1;
39035
+ }, [objectMatrices, objects, objectSettings]);
38716
39036
  useJointAnimationLoop(activeJointAnimation);
38717
39037
  const anySectionActive = activeCutPlaneDefs.length > 0;
38718
39038
  const sectionGuideBoundsKey = (sectionPlaneGuidesEnabled || sectionExplorerEnabled) && anySectionActive ? objectMatrices : null;
@@ -38877,11 +39197,13 @@ function useViewportState() {
38877
39197
  files,
38878
39198
  renderMode,
38879
39199
  renderStyle,
39200
+ manualScene,
38880
39201
  inspectChannel,
38881
39202
  inspectDisplayMode,
38882
39203
  inspectPointSampleCount,
38883
39204
  projectionMode,
38884
39205
  gridEnabled,
39206
+ axesVisible,
38885
39207
  gridSize,
38886
39208
  showPerformanceInfo,
38887
39209
  objectSettings,
@@ -38954,6 +39276,7 @@ function useViewportState() {
38954
39276
  jointsConfig,
38955
39277
  sceneConfig,
38956
39278
  modelBoundsMinZ,
39279
+ surfaceFieldScale,
38957
39280
  focusedObjectIdSet,
38958
39281
  visibleSceneObjectCount,
38959
39282
  visibleModelTriangles,
@@ -40259,7 +40582,7 @@ function RenderLabelsOverlay({ labels }) {
40259
40582
  class InspectWorkerClient {
40260
40583
  constructor(workerFactory = () => new Worker(new URL(
40261
40584
  /* @vite-ignore */
40262
- "/assets/inspectWorker-Dll4eVyD.js",
40585
+ "/assets/inspectWorker-BZ2CkQZr.js",
40263
40586
  import.meta.url
40264
40587
  ), { type: "module" })) {
40265
40588
  __publicField(this, "worker", null);
@@ -40319,10 +40642,11 @@ class InspectWorkerClient {
40319
40642
  }
40320
40643
  }
40321
40644
  const inspectWorkerClient = new InspectWorkerClient();
40322
- const WORKER_CHANNELS = /* @__PURE__ */ new Set(["thickness", "roughness", "connectivity", "distance"]);
40645
+ const WORKER_CHANNELS = /* @__PURE__ */ new Set(["thickness", "roughness", "connectivity", "floating", "distance"]);
40323
40646
  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;
40647
+ const MESH_COMPONENT_WORKER_CHANNELS = /* @__PURE__ */ new Set(["connectivity", "floating", "distance"]);
40648
+ const IDENTITY_MATRIX_ELEMENTS = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
40649
+ const INSPECT_BUILD_YIELD_INTERVAL_MS = 8;
40326
40650
  class InspectBuildCancelledError extends Error {
40327
40651
  constructor() {
40328
40652
  super("cancelled");
@@ -40334,23 +40658,34 @@ function isScalarWorkerChannel(channel) {
40334
40658
  function needsMeshComponents(channel) {
40335
40659
  return MESH_COMPONENT_WORKER_CHANNELS.has(channel);
40336
40660
  }
40661
+ function shouldIncludeInspectPositions(channel, obj) {
40662
+ var _a3;
40663
+ if (isScalarWorkerChannel(channel)) return true;
40664
+ if (!needsMeshComponents(channel)) return false;
40665
+ if (channel === "floating") return true;
40666
+ const sources = (_a3 = obj.geometryInfo) == null ? void 0 : _a3.sources;
40667
+ return !((sources == null ? void 0 : sources.length) === 1 && sources[0] === "primitive");
40668
+ }
40337
40669
  function viewportScalarSamplesPerObject(objectCount, inspectPointSampleCount) {
40338
40670
  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));
40671
+ return resolveScalarSceneSampleBudget({
40672
+ objectCount,
40673
+ maxSamplesPerObject: perObjectCap,
40674
+ minSamplesPerObject: INSPECT_POINT_SAMPLE_COUNT_MIN
40675
+ }).effectiveMaxSamplesPerObject;
40343
40676
  }
40344
40677
  function yieldToBrowser() {
40345
40678
  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
- });
40679
+ return new Promise((resolve2) => window.setTimeout(resolve2, 0));
40680
+ }
40681
+ function createBuildYield(isCancelled) {
40682
+ let lastYield = performance.now();
40683
+ return async () => {
40684
+ if (performance.now() - lastYield < INSPECT_BUILD_YIELD_INTERVAL_MS) return;
40685
+ await yieldToBrowser();
40686
+ if (isCancelled()) throw new InspectBuildCancelledError();
40687
+ lastYield = performance.now();
40688
+ };
40354
40689
  }
40355
40690
  function transformedBounds(obj, matrix) {
40356
40691
  if (!obj.shape) return null;
@@ -40367,6 +40702,31 @@ function transformedBounds(obj, matrix) {
40367
40702
  return null;
40368
40703
  }
40369
40704
  }
40705
+ function matrixIsIdentity(matrix) {
40706
+ if (!matrix) return true;
40707
+ const elements = matrix.elements;
40708
+ return IDENTITY_MATRIX_ELEMENTS.every((value, index) => elements[index] === value);
40709
+ }
40710
+ function clonePositionsWithOptionalTransform(source, matrix) {
40711
+ const positions = new Float32Array(source);
40712
+ if (matrixIsIdentity(matrix)) return positions;
40713
+ const e2 = matrix.elements;
40714
+ for (let index = 0; index < positions.length; index += 3) {
40715
+ const x = positions[index];
40716
+ const y = positions[index + 1];
40717
+ const z = positions[index + 2];
40718
+ const w = e2[3] * x + e2[7] * y + e2[11] * z + e2[15];
40719
+ positions[index] = e2[0] * x + e2[4] * y + e2[8] * z + e2[12];
40720
+ positions[index + 1] = e2[1] * x + e2[5] * y + e2[9] * z + e2[13];
40721
+ positions[index + 2] = e2[2] * x + e2[6] * y + e2[10] * z + e2[14];
40722
+ if (w !== 1 && w !== 0) {
40723
+ positions[index] /= w;
40724
+ positions[index + 1] /= w;
40725
+ positions[index + 2] /= w;
40726
+ }
40727
+ }
40728
+ return positions;
40729
+ }
40370
40730
  function clonePositions(obj, matrix) {
40371
40731
  var _a3;
40372
40732
  if (!obj.shape) return void 0;
@@ -40374,17 +40734,7 @@ function clonePositions(obj, matrix) {
40374
40734
  try {
40375
40735
  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
40736
  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;
40737
+ return clonePositionsWithOptionalTransform(source, matrix);
40388
40738
  } finally {
40389
40739
  geometry == null ? void 0 : geometry.solid.dispose();
40390
40740
  geometry == null ? void 0 : geometry.edges.dispose();
@@ -40392,18 +40742,17 @@ function clonePositions(obj, matrix) {
40392
40742
  }
40393
40743
  async function buildWorkerObjects(args) {
40394
40744
  var _a3;
40395
- const needsMesh = isScalarWorkerChannel(args.inspectChannel) || needsMeshComponents(args.inspectChannel);
40396
40745
  const out = [];
40746
+ const maybeYield = createBuildYield(args.isCancelled);
40397
40747
  for (const obj of args.objects) {
40398
40748
  if (args.isCancelled()) throw new InspectBuildCancelledError();
40399
40749
  if (!obj.shape) continue;
40400
40750
  if (((_a3 = args.objectSettings[obj.id]) == null ? void 0 : _a3.visible) === false) continue;
40401
- await yieldToBrowser();
40402
- if (args.isCancelled()) throw new InspectBuildCancelledError();
40403
40751
  const matrix = args.objectMatrices[obj.id] ?? new Matrix4();
40404
40752
  const bbox = transformedBounds(obj, matrix);
40405
40753
  if (!bbox) continue;
40406
40754
  if (args.isCancelled()) throw new InspectBuildCancelledError();
40755
+ const positions = shouldIncludeInspectPositions(args.inspectChannel, obj) ? clonePositions(obj, needsMeshComponents(args.inspectChannel) ? matrix : void 0) : void 0;
40407
40756
  out.push({
40408
40757
  id: obj.id,
40409
40758
  name: obj.name,
@@ -40411,13 +40760,13 @@ async function buildWorkerObjects(args) {
40411
40760
  treePath: obj.treePath,
40412
40761
  mock: obj.mock,
40413
40762
  bbox,
40414
- ...needsMesh ? { positions: clonePositions(obj, needsMeshComponents(args.inspectChannel) ? matrix : void 0) } : {}
40763
+ ...positions ? { positions } : {}
40415
40764
  });
40416
- await yieldToBrowser();
40765
+ await maybeYield();
40417
40766
  }
40418
40767
  return out;
40419
40768
  }
40420
- function analyzePayloadFor(channel, objects, inspectPointSampleCount) {
40769
+ function analyzePayloadFor(channel, objects, inspectPointSampleCount, groundZ) {
40421
40770
  const maxSamplesPerObject = viewportScalarSamplesPerObject(objects.length, inspectPointSampleCount);
40422
40771
  if (channel === "thickness") {
40423
40772
  return {
@@ -40437,7 +40786,7 @@ function analyzePayloadFor(channel, objects, inspectPointSampleCount) {
40437
40786
  }
40438
40787
  };
40439
40788
  }
40440
- return { channel, objects };
40789
+ return { channel, objects, groundZ };
40441
40790
  }
40442
40791
  function resultToState(channel, result) {
40443
40792
  return {
@@ -40445,6 +40794,17 @@ function resultToState(channel, result) {
40445
40794
  channel,
40446
40795
  objectColors: result.objectColors,
40447
40796
  meshColors: Object.fromEntries(result.meshColorObjects.map((object) => [object.objectId, object.colors])),
40797
+ heatmapFields: Object.fromEntries(
40798
+ result.heatmapFieldObjects.map((object) => [
40799
+ object.objectId,
40800
+ {
40801
+ data: object.data,
40802
+ boundsMin: object.boundsMin,
40803
+ boundsSize: object.boundsSize,
40804
+ gridSize: object.gridSize
40805
+ }
40806
+ ])
40807
+ ),
40448
40808
  pointClouds: Object.fromEntries(
40449
40809
  result.pointObjects.map((object) => [
40450
40810
  object.objectId,
@@ -40466,6 +40826,7 @@ function useInspectWorkerAnalysis(args) {
40466
40826
  objectColors: {},
40467
40827
  meshColors: {},
40468
40828
  pointClouds: {},
40829
+ heatmapFields: {},
40469
40830
  warnings: [],
40470
40831
  error: null
40471
40832
  });
@@ -40478,6 +40839,7 @@ function useInspectWorkerAnalysis(args) {
40478
40839
  objectColors: {},
40479
40840
  meshColors: {},
40480
40841
  pointClouds: {},
40842
+ heatmapFields: {},
40481
40843
  warnings: [],
40482
40844
  error: null
40483
40845
  });
@@ -40491,6 +40853,7 @@ function useInspectWorkerAnalysis(args) {
40491
40853
  objectColors: {},
40492
40854
  meshColors: {},
40493
40855
  pointClouds: {},
40856
+ heatmapFields: {},
40494
40857
  warnings: [],
40495
40858
  error: null
40496
40859
  });
@@ -40501,7 +40864,8 @@ function useInspectWorkerAnalysis(args) {
40501
40864
  isCancelled: () => cancelled
40502
40865
  });
40503
40866
  if (cancelled) return;
40504
- const result = await inspectWorkerClient.analyze(analyzePayloadFor(channel, objects, args.inspectPointSampleCount));
40867
+ const groundZ = typeof args.groundZ === "number" && Number.isFinite(args.groundZ) ? args.groundZ : 0;
40868
+ const result = await inspectWorkerClient.analyze(analyzePayloadFor(channel, objects, args.inspectPointSampleCount, groundZ));
40505
40869
  if (cancelled) return;
40506
40870
  setState(resultToState(args.inspectChannel, result));
40507
40871
  } catch (error) {
@@ -40514,6 +40878,7 @@ function useInspectWorkerAnalysis(args) {
40514
40878
  objectColors: {},
40515
40879
  meshColors: {},
40516
40880
  pointClouds: {},
40881
+ heatmapFields: {},
40517
40882
  warnings: [],
40518
40883
  error: error instanceof Error ? error.message : String(error)
40519
40884
  });
@@ -40523,9 +40888,72 @@ function useInspectWorkerAnalysis(args) {
40523
40888
  cancelled = true;
40524
40889
  inspectWorkerClient.dispose();
40525
40890
  };
40526
- }, [args.inspectChannel, args.inspectPointSampleCount, args.sceneVersion, args.objects, args.objectSettings, args.objectMatrices]);
40891
+ }, [
40892
+ args.inspectChannel,
40893
+ args.inspectPointSampleCount,
40894
+ args.sceneVersion,
40895
+ args.objects,
40896
+ args.objectSettings,
40897
+ args.objectMatrices,
40898
+ args.groundZ
40899
+ ]);
40527
40900
  return state2;
40528
40901
  }
40902
+ function buildManualSceneConfig(base, manualScene, renderStylePreset) {
40903
+ var _a3, _b2, _c;
40904
+ const lights = [
40905
+ { type: "ambient", color: "#ffffff", intensity: manualScene.ambientIntensity },
40906
+ {
40907
+ type: "directional",
40908
+ position: renderStylePreset.lights.keyPosition,
40909
+ color: renderStylePreset.lights.keyColor,
40910
+ intensity: manualScene.keyIntensity,
40911
+ castShadow: true
40912
+ },
40913
+ {
40914
+ type: "directional",
40915
+ position: renderStylePreset.lights.fillPosition,
40916
+ color: renderStylePreset.lights.fillColor,
40917
+ intensity: manualScene.fillIntensity
40918
+ }
40919
+ ];
40920
+ if (manualScene.rimIntensity > 0) {
40921
+ lights.push({
40922
+ type: "directional",
40923
+ position: renderStylePreset.lights.rimPosition,
40924
+ color: renderStylePreset.lights.rimColor,
40925
+ intensity: manualScene.rimIntensity
40926
+ });
40927
+ }
40928
+ lights.push({
40929
+ type: "hemisphere",
40930
+ skyColor: renderStylePreset.lights.hemisphereSky,
40931
+ groundColor: manualScene.groundColor,
40932
+ intensity: renderStylePreset.lights.hemisphereIntensity
40933
+ });
40934
+ return {
40935
+ background: manualScene.backgroundColor,
40936
+ camera: (base == null ? void 0 : base.camera) ?? null,
40937
+ views: (base == null ? void 0 : base.views) ?? null,
40938
+ journeys: (base == null ? void 0 : base.journeys) ?? null,
40939
+ lights,
40940
+ environment: {
40941
+ ...(base == null ? void 0 : base.environment) ?? {},
40942
+ preset: ((_a3 = base == null ? void 0 : base.environment) == null ? void 0 : _a3.preset) ?? "studio",
40943
+ intensity: manualScene.environmentIntensity,
40944
+ background: ((_b2 = base == null ? void 0 : base.environment) == null ? void 0 : _b2.background) ?? false
40945
+ },
40946
+ fog: (base == null ? void 0 : base.fog) ?? null,
40947
+ postProcessing: (base == null ? void 0 : base.postProcessing) ?? null,
40948
+ ground: {
40949
+ ...(base == null ? void 0 : base.ground) ?? {},
40950
+ visible: manualScene.groundVisible,
40951
+ color: manualScene.groundColor,
40952
+ receiveShadow: ((_c = base == null ? void 0 : base.ground) == null ? void 0 : _c.receiveShadow) ?? true
40953
+ },
40954
+ capture: (base == null ? void 0 : base.capture) ?? null
40955
+ };
40956
+ }
40529
40957
  function panelNumber(value) {
40530
40958
  if (!Number.isFinite(value)) return String(value);
40531
40959
  if (Number.isInteger(value)) return String(value);
@@ -40555,8 +40983,14 @@ function inspectChannelLabel(channel) {
40555
40983
  switch (channel) {
40556
40984
  case "connectivity":
40557
40985
  return "Connect";
40986
+ case "floating":
40987
+ return "Floating";
40558
40988
  case "distance":
40559
40989
  return "Distance";
40990
+ case "normals":
40991
+ return "Normals";
40992
+ case "zebra":
40993
+ return "Zebra";
40560
40994
  case "thickness":
40561
40995
  return "Thickness";
40562
40996
  case "roughness":
@@ -40650,7 +41084,7 @@ function RenderStyleExposure({ exposure }) {
40650
41084
  return null;
40651
41085
  }
40652
41086
  function Viewport() {
40653
- var _a3, _b2, _c;
41087
+ var _a3, _b2, _c, _d;
40654
41088
  const activeFile = useForgeStore((s) => s.activeFile);
40655
41089
  const activeBackend = useForgeStore((s) => s.activeBackend);
40656
41090
  const computeTarget = useForgeStore((s) => s.computeTarget);
@@ -40662,11 +41096,13 @@ function Viewport() {
40662
41096
  evaluationPhase,
40663
41097
  renderMode,
40664
41098
  renderStyle,
41099
+ manualScene,
40665
41100
  inspectChannel,
40666
41101
  inspectDisplayMode,
40667
41102
  inspectPointSampleCount,
40668
41103
  projectionMode,
40669
41104
  gridEnabled,
41105
+ axesVisible,
40670
41106
  gridSize,
40671
41107
  showPerformanceInfo,
40672
41108
  objectSettings,
@@ -40714,6 +41150,7 @@ function Viewport() {
40714
41150
  isSketchOnly,
40715
41151
  sceneConfig,
40716
41152
  modelBoundsMinZ,
41153
+ surfaceFieldScale,
40717
41154
  focusedObjectIdSet,
40718
41155
  visibleSceneObjectCount,
40719
41156
  visibleModelTriangles,
@@ -40734,6 +41171,11 @@ function Viewport() {
40734
41171
  const historyObjectIds = useForgeStore((s) => s.historyObjectIds);
40735
41172
  useHistoryPlaybackLoop();
40736
41173
  const [viewPersistenceResolved, setViewPersistenceResolved] = reactExports.useState(false);
41174
+ const [historyGeometryStatus, setHistoryGeometryStatus] = reactExports.useState({
41175
+ status: "idle",
41176
+ stepCount: 0,
41177
+ error: null
41178
+ });
40737
41179
  const [isViewportInteracting, setIsViewportInteracting] = reactExports.useState(false);
40738
41180
  const [zoomMmPerPx, setZoomMmPerPx] = reactExports.useState(null);
40739
41181
  const hoverTooltipRef = reactExports.useRef(null);
@@ -40748,6 +41190,12 @@ function Viewport() {
40748
41190
  const contextMenuRef = reactExports.useRef(null);
40749
41191
  const t2 = themes[themeName];
40750
41192
  const renderStylePreset = reactExports.useMemo(() => getRenderStylePreset(renderStyle), [renderStyle]);
41193
+ const effectiveSceneConfig = reactExports.useMemo(
41194
+ () => manualScene.enabled ? buildManualSceneConfig(sceneConfig, manualScene, renderStylePreset) : sceneConfig,
41195
+ [manualScene, renderStylePreset, sceneConfig]
41196
+ );
41197
+ const inspectGroundOffset = typeof ((_a3 = effectiveSceneConfig == null ? void 0 : effectiveSceneConfig.ground) == null ? void 0 : _a3.offset) === "number" && Number.isFinite(effectiveSceneConfig.ground.offset) ? effectiveSceneConfig.ground.offset : 0;
41198
+ const inspectGroundZ = (modelBoundsMinZ ?? 0) - inspectGroundOffset;
40751
41199
  const inspectChannelActive = inspectChannel !== "none";
40752
41200
  const hasVisibleSdfObjects = objects.some((obj) => {
40753
41201
  var _a4;
@@ -40771,21 +41219,13 @@ function Viewport() {
40771
41219
  sceneVersion,
40772
41220
  objects,
40773
41221
  objectSettings,
40774
- objectMatrices
41222
+ objectMatrices,
41223
+ groundZ: inspectGroundZ
40775
41224
  });
40776
41225
  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]);
41226
+ const historyGeometryLoading = historyMode && historyGeometryStatus.status === "loading";
41227
+ const viewportBusyPhase = isEvaluating || evaluationPhase === "exporting" ? evaluationPhase : historyGeometryLoading ? "history" : inspectLoading ? "inspecting" : null;
41228
+ const viewportBusyLabel = historyGeometryLoading ? "Building timeline geometry" : inspectLoading ? `Generating ${inspectChannelLabel(inspectChannel)} inspect view` : void 0;
40789
41229
  const handlers = useViewportHandlers({
40790
41230
  containerRef,
40791
41231
  contextMenuRef,
@@ -40849,7 +41289,7 @@ function Viewport() {
40849
41289
  Canvas,
40850
41290
  {
40851
41291
  style: {
40852
- background: inspectChannelActive ? "#000000" : renderStyle === "classic" ? t2.viewportBg : renderStylePreset.background,
41292
+ background: renderStyle === "classic" ? t2.viewportBg : renderStylePreset.background,
40853
41293
  cursor: measureMode ? "crosshair" : "default"
40854
41294
  },
40855
41295
  dpr: canvasDpr,
@@ -40866,19 +41306,21 @@ function Viewport() {
40866
41306
  children: [
40867
41307
  /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Suspense, { fallback: null, children: [
40868
41308
  projectionMode === "orthographic" ? /* @__PURE__ */ jsxRuntimeExports.jsx(OrthographicCamera, { makeDefault: true, position: [120, 80, 120], zoom: 2, near: -5e4, far: 5e4, up: [0, 0, 1] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PerspectiveCamera, { makeDefault: true, position: [120, 80, 120], fov: 45, near: 0.1, far: 1e5, up: [0, 0, 1] }),
40869
- ((_a3 = sceneConfig == null ? void 0 : sceneConfig.postProcessing) == null ? void 0 : _a3.toneMappingExposure) === void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(RenderStyleExposure, { exposure: renderStylePreset.toneMappingExposure }),
40870
- sceneConfig && /* @__PURE__ */ jsxRuntimeExports.jsx(
41309
+ ((_b2 = effectiveSceneConfig == null ? void 0 : effectiveSceneConfig.postProcessing) == null ? void 0 : _b2.toneMappingExposure) === void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(RenderStyleExposure, { exposure: renderStylePreset.toneMappingExposure }),
41310
+ effectiveSceneConfig && /* @__PURE__ */ jsxRuntimeExports.jsx(
40871
41311
  SceneConfigurator,
40872
41312
  {
40873
- config: sceneConfig,
41313
+ config: effectiveSceneConfig,
41314
+ interactivePreview: isViewportInteracting,
40874
41315
  controlsRef,
40875
41316
  modelBoundsMinZ,
41317
+ hideGround: inspectChannelActive,
40876
41318
  onDefaultLightsOverridden: handleDefaultLightsOverridden,
40877
41319
  onDefaultEnvironmentOverridden: handleDefaultEnvironmentOverridden
40878
41320
  }
40879
41321
  ),
40880
- !defaultEnvironmentOverridden && /* @__PURE__ */ jsxRuntimeExports.jsx(LocalEnvironment, { preset: "studio", intensity: renderStylePreset.environmentIntensity }),
40881
- !defaultLightsOverridden && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
41322
+ !isViewportInteracting && !defaultEnvironmentOverridden && /* @__PURE__ */ jsxRuntimeExports.jsx(LocalEnvironment, { preset: "studio", intensity: renderStylePreset.environmentIntensity }),
41323
+ (isViewportInteracting || !defaultLightsOverridden) && /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
40882
41324
  /* @__PURE__ */ jsxRuntimeExports.jsx("ambientLight", { intensity: renderStylePreset.lights.ambientIntensity }),
40883
41325
  /* @__PURE__ */ jsxRuntimeExports.jsx(
40884
41326
  "directionalLight",
@@ -40917,8 +41359,8 @@ function Viewport() {
40917
41359
  )
40918
41360
  ] }),
40919
41361
  /* @__PURE__ */ jsxRuntimeExports.jsx(ClippingManager, { active: hasAnyObjectCutPlanes }),
40920
- !inspectChannelActive && sectionExplorerEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(SectionExplorerGizmo, { size: sectionGuideSize }),
40921
- !inspectChannelActive && sectionPlaneGuidesEnabled && activeCutPlaneDefs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
41362
+ sectionExplorerEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(SectionExplorerGizmo, { size: sectionGuideSize }),
41363
+ sectionPlaneGuidesEnabled && activeCutPlaneDefs.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
40922
41364
  SectionPlaneGuides,
40923
41365
  {
40924
41366
  cutPlanes: scriptCutPlaneDefs,
@@ -40952,11 +41394,13 @@ function Viewport() {
40952
41394
  renderMode,
40953
41395
  inspectChannel,
40954
41396
  inspectDisplayMode,
40955
- inspectColor: inspectAnalysis.objectColors[obj.id] ?? maskColorForIndex(objIndex),
41397
+ inspectColor: inspectChannel === "floating" ? inspectAnalysis.objectColors[obj.id] ?? "#000000" : inspectAnalysis.objectColors[obj.id] ?? maskColorForIndex(objIndex),
40956
41398
  inspectMeshColors: inspectAnalysis.meshColors[obj.id],
40957
41399
  inspectPointCloud: inspectAnalysis.pointClouds[obj.id],
41400
+ inspectHeatmapFieldData: inspectAnalysis.heatmapFields[obj.id],
40958
41401
  isInteracting: isViewportInteracting,
40959
41402
  matrix,
41403
+ surfaceFieldScale,
40960
41404
  isHovered,
40961
41405
  clippingPlanes: objectClippingPlanes,
40962
41406
  debugHighlightColor: shapeHl == null ? void 0 : shapeHl.color,
@@ -41020,7 +41464,7 @@ function Viewport() {
41020
41464
  return null;
41021
41465
  }),
41022
41466
  inspectChannel === "collisions" && /* @__PURE__ */ jsxRuntimeExports.jsx(CollisionInspectionOverlay, { objects, objectSettings, objectMatrices }),
41023
- !inspectChannelActive && /* @__PURE__ */ jsxRuntimeExports.jsx(
41467
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
41024
41468
  SectionCaps,
41025
41469
  {
41026
41470
  sceneVersion,
@@ -41032,17 +41476,25 @@ function Viewport() {
41032
41476
  isInteracting: isViewportInteracting
41033
41477
  }
41034
41478
  ),
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) => {
41479
+ constructionGhost && /* @__PURE__ */ jsxRuntimeExports.jsx(ConstructionGhostOverlay, { matrix: constructionGhostMatrix }),
41480
+ historyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(
41481
+ ConstructionHistoryOverlay,
41482
+ {
41483
+ activeBackend,
41484
+ objectMatrices,
41485
+ objectSettings,
41486
+ onGeometryStatusChange: setHistoryGeometryStatus
41487
+ }
41488
+ ),
41489
+ hoveredJointOverlay && /* @__PURE__ */ jsxRuntimeExports.jsx(HoveredJointOverlay, { state: hoveredJointOverlay, config: jointOverlayConfig }),
41490
+ dimensionsVisible && dimensions.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx(DimensionAnnotation, { def: d, lengthUnit }, d.id)),
41491
+ /* @__PURE__ */ jsxRuntimeExports.jsx(RenderLabelsOverlay, { labels: renderLabels }),
41492
+ attachmentsVisible !== "none" && attachmentPoints.map((ap) => {
41041
41493
  const matrix = objectMatrices[ap.objectId];
41042
41494
  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}`);
41043
41495
  }),
41044
41496
  /* @__PURE__ */ jsxRuntimeExports.jsx(MeasureTool, {}),
41045
- !inspectChannelActive && debugHighlights3D.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(DebugHighlightsOverlay, { highlights: debugHighlights3D }),
41497
+ debugHighlights3D.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(DebugHighlightsOverlay, { highlights: debugHighlights3D }),
41046
41498
  drawFlagEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(DrawCanvas, {}),
41047
41499
  /* @__PURE__ */ jsxRuntimeExports.jsx(
41048
41500
  PerformanceInfoSampler,
@@ -41055,7 +41507,7 @@ function Viewport() {
41055
41507
  }
41056
41508
  ),
41057
41509
  /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomSampler, { onZoomChange: setZoomMmPerPx }),
41058
- !inspectChannelActive && gridEnabled && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41510
+ gridEnabled && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41059
41511
  Grid,
41060
41512
  {
41061
41513
  args: [500, 500],
@@ -41070,7 +41522,7 @@ function Viewport() {
41070
41522
  infiniteGrid: true
41071
41523
  }
41072
41524
  ),
41073
- !inspectChannelActive && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41525
+ !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41074
41526
  SdfRaymarchLayer,
41075
41527
  {
41076
41528
  objects,
@@ -41085,11 +41537,11 @@ function Viewport() {
41085
41537
  onContextMenu: (obj, event) => handleObjectContextMenu(obj, event)
41086
41538
  }
41087
41539
  ),
41088
- !inspectChannelActive && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(LabeledAxes, {}),
41089
- !inspectChannelActive && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchAxes, {}),
41540
+ axesVisible && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(LabeledAxes, {}),
41541
+ axesVisible && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchAxes, {}),
41090
41542
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(CursorTracker, { onMove: (x, y) => setCursorPos({ x, y }) }),
41091
41543
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchRulersUpdater, { hCanvasRef: hRulerRef, vCanvasRef: vRulerRef }),
41092
- !inspectChannelActive && gridEnabled && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41544
+ gridEnabled && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41093
41545
  Grid,
41094
41546
  {
41095
41547
  args: [500, 500],
@@ -41202,7 +41654,7 @@ function Viewport() {
41202
41654
  /* @__PURE__ */ jsxRuntimeExports.jsx(
41203
41655
  ModelJourneyBar,
41204
41656
  {
41205
- journeys: sceneConfig == null ? void 0 : sceneConfig.journeys,
41657
+ journeys: effectiveSceneConfig == null ? void 0 : effectiveSceneConfig.journeys,
41206
41658
  isViewportInteracting,
41207
41659
  requestViewCommand,
41208
41660
  selectObject,
@@ -41211,14 +41663,15 @@ function Viewport() {
41211
41663
  }
41212
41664
  ),
41213
41665
  /* @__PURE__ */ jsxRuntimeExports.jsx(PerformanceInfoPanel, { enabled: showPerformanceInfo, stats: performanceInfo }),
41666
+ !viewportBusyPhase && /* @__PURE__ */ jsxRuntimeExports.jsx(InspectionLegend, { channel: inspectChannel, warnings: inspectAnalysis.warnings }),
41214
41667
  !viewportBusyPhase && /* @__PURE__ */ jsxRuntimeExports.jsx(ZoomIndicatorPanel, { mmPerPx: zoomMmPerPx }),
41215
41668
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
41216
41669
  SketchRulersOverlay,
41217
41670
  {
41218
41671
  hCanvasRef: hRulerRef,
41219
41672
  vCanvasRef: vRulerRef,
41220
- viewportWidth: ((_b2 = containerRef.current) == null ? void 0 : _b2.clientWidth) ?? 800,
41221
- viewportHeight: ((_c = containerRef.current) == null ? void 0 : _c.clientHeight) ?? 600
41673
+ viewportWidth: ((_c = containerRef.current) == null ? void 0 : _c.clientWidth) ?? 800,
41674
+ viewportHeight: ((_d = containerRef.current) == null ? void 0 : _d.clientHeight) ?? 600
41222
41675
  }
41223
41676
  ),
41224
41677
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(CursorCoordinatesPanel, { x: cursorPos.x, y: cursorPos.y }),
@@ -41247,7 +41700,6 @@ function Viewport() {
41247
41700
  {
41248
41701
  phase: viewportBusyPhase,
41249
41702
  label: viewportBusyLabel,
41250
- constructionSteps: rebuildHistorySteps,
41251
41703
  activeBackend,
41252
41704
  computeTarget
41253
41705
  }
@@ -41280,6 +41732,34 @@ function Viewport() {
41280
41732
  ]
41281
41733
  }
41282
41734
  ),
41735
+ historyMode && historyGeometryStatus.status === "error" && historyGeometryStatus.error && /* @__PURE__ */ jsxRuntimeExports.jsxs(
41736
+ "div",
41737
+ {
41738
+ style: {
41739
+ position: "absolute",
41740
+ top: 12,
41741
+ left: "50%",
41742
+ transform: "translateX(-50%)",
41743
+ background: "rgba(127, 29, 29, 0.92)",
41744
+ color: "#fee2e2",
41745
+ border: "1px solid rgba(254, 202, 202, 0.4)",
41746
+ borderRadius: 6,
41747
+ padding: "7px 12px",
41748
+ fontSize: 12,
41749
+ fontWeight: 600,
41750
+ pointerEvents: "none",
41751
+ zIndex: 12,
41752
+ maxWidth: "min(520px, calc(100% - 32px))",
41753
+ whiteSpace: "nowrap",
41754
+ overflow: "hidden",
41755
+ textOverflow: "ellipsis"
41756
+ },
41757
+ children: [
41758
+ "Timeline failed: ",
41759
+ historyGeometryStatus.error
41760
+ ]
41761
+ }
41762
+ ),
41283
41763
  /* @__PURE__ */ jsxRuntimeExports.jsx(HoverTooltipLayer, { ref: hoverTooltipRef, enabled: objectPickSyncEnabled && !measureMode }),
41284
41764
  objectContextMenu && /* @__PURE__ */ jsxRuntimeExports.jsxs(
41285
41765
  "div",
@@ -41737,7 +42217,7 @@ function Viewport() {
41737
42217
  }
41738
42218
  );
41739
42219
  }
41740
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BujZvuwX.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
42220
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-DnddQvBt.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
41741
42221
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
41742
42222
  function storePendingShareCopy(shareId) {
41743
42223
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -41924,15 +42404,15 @@ const PublishedModelPage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Objec
41924
42404
  consumePendingShareCopy,
41925
42405
  storePendingShareCopy
41926
42406
  }, Symbol.toStringTag, { value: "Module" }));
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 })));
42407
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-CFet-l3o.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
42408
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-9U1hGjrg.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
42409
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-Cc41PP4d.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
42410
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-CNEvQM7c.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
41931
42411
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
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 })));
42412
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-BarZonVZ.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
42413
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-CPm8mQx3.js"), true ? __vite__mapDeps([2,1]) : void 0).then((m2) => ({ default: m2.PricingPage })));
42414
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-DnddQvBt.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
42415
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-B2lhWTcd.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
41936
42416
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
41937
42417
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg)$/i;
41938
42418
  function firstMeaningfulLine(text) {