forgecad 0.10.0 → 0.10.2

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 (75) hide show
  1. package/dist/assets/{AdminPage-DwYHz72L.js → AdminPage-CHY6ZN-p.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-a9_f-1US.js → BenchmarkPage-BcRT5iGN.js} +1 -1
  3. package/dist/assets/{BlogPage-DodHpvmf.js → BlogPage-BssBbnb-.js} +1 -1
  4. package/dist/assets/{DocsPage-B5LePEuj.js → DocsPage-DsvdiRNK.js} +33 -2
  5. package/dist/assets/{EditorApp-QXsAISLR.js → EditorApp-Bfd3jbtC.js} +185 -44
  6. package/dist/assets/{EmbedViewer-DdEHGUMU.js → EmbedViewer-D5t8WamV.js} +3 -3
  7. package/dist/assets/{LandingPageProofDriven-yhhOodbf.js → LandingPageProofDriven-DbN7o-Be.js} +1 -1
  8. package/dist/assets/{LegalPage-5RbKRGYK.js → LegalPage-DNGrrY0p.js} +1 -1
  9. package/dist/assets/{PricingPage-E3Rma7aV.js → PricingPage-Nczr3pRz.js} +1 -1
  10. package/dist/assets/{SettingsPage-BJZcM97j.js → SettingsPage-DZlyu4d4.js} +1 -1
  11. package/dist/assets/{app-DSYrDg0V.js → app-C9ct2hRD.js} +1752 -474
  12. package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
  13. package/dist/assets/{scalar-sampling-budget-o90NSNmF.js → backendInit-ymjonyQp.js} +85756 -78750
  14. package/dist/assets/cli/{render-ZMHR9HkV.js → render-B_0lQwKU.js} +71 -193
  15. package/dist/assets/{constructionHistoryWorker-AwMMWSxg.js → constructionHistoryWorker-CZ42Dksy.js} +8058 -1225
  16. package/dist/assets/{evalWorker-DbNs7Dkp.js → evalWorker-C2pm8LHP.js} +23037 -15821
  17. package/dist/assets/{forgecad_geometry-Dgceylq9.js → forgecad_geometry-BlMtqluF.js} +120 -1
  18. package/dist/assets/{forgecad_geometry_bg-dD4RNQF1.wasm → forgecad_geometry_bg-BllP_WiL.wasm} +0 -0
  19. package/dist/assets/{inspectWorker-CZsCFtQT.js → inspectWorker-D5T5VbfK.js} +31375 -32603
  20. package/dist/assets/{jointPose-DO6mnXn_.js → jointPose-4r8ed8_5.js} +1 -1
  21. package/dist/assets/{manifold-BU-tJwQh.js → manifold-5PP1eGLN.js} +1 -1
  22. package/dist/assets/{manifold-fy2MV7K1.js → manifold-C4r6B-XY.js} +2 -2
  23. package/dist/assets/{manifold-BGlQBBH9.js → manifold-DjBkyIc8.js} +1 -1
  24. package/dist/assets/{reportWorker-DO6hcQbh.js → reportWorker-CwenM7wB.js} +46620 -44936
  25. package/dist/cli/render.html +1 -1
  26. package/dist/docs/index.html +2 -2
  27. package/dist/docs-raw/CLI.md +43 -16
  28. package/dist/docs-raw/generated/assembly.md +71 -6
  29. package/dist/docs-raw/generated/concepts.md +17 -3
  30. package/dist/docs-raw/generated/core.md +10 -3
  31. package/dist/docs-raw/generated/output.md +14 -43
  32. package/dist/docs-raw/generated/runtime-names.md +4 -4
  33. package/dist/docs-raw/generated/sdf.md +2 -2
  34. package/dist/docs-raw/guides/simready-quickstart.md +173 -0
  35. package/dist/docs-raw/simulation-workflow.md +273 -0
  36. package/dist/index.html +2 -2
  37. package/dist/sitemap.xml +25 -13
  38. package/dist-cli/{check-compiler-JTVBITCR.js → check-compiler-SP7FAL7R.js} +1 -1
  39. package/dist-cli/{check-query-propagation-3FFLSMVN.js → check-query-propagation-BRLSHP22.js} +1 -1
  40. package/dist-cli/{chunk-OAN5T4XD.js → chunk-RQQ42YCP.js} +51209 -43456
  41. package/dist-cli/forgecad.js +5783 -1691
  42. package/dist-cli/{forgecad_geometry-QOQIIP53.js → forgecad_geometry-7TVSNVUB.js} +119 -0
  43. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  44. package/dist-skill/CONTEXT.md +107 -68
  45. package/dist-skill/docs/API/core/concepts.md +2 -2
  46. package/dist-skill/docs/CLI.md +43 -16
  47. package/dist-skill/docs/generated/assembly.md +67 -6
  48. package/dist-skill/docs/generated/core.md +10 -3
  49. package/dist-skill/docs/generated/output.md +14 -43
  50. package/dist-skill/docs/generated/runtime-names.md +4 -4
  51. package/dist-skill/docs/generated/sdf.md +2 -2
  52. package/examples/api/gyroid-voronoi-blend.forge.js +1 -1
  53. package/examples/api/organic-noise-sculpture.forge.js +1 -1
  54. package/examples/api/sdf-circular-array-knurling.forge.js +1 -1
  55. package/examples/api/{sdf-custom-raymarch.forge.js → sdf-custom-field-mesh-preview.forge.js} +3 -4
  56. package/examples/api/sdf-materialize-tree.forge.js +2 -2
  57. package/examples/api/sdf-plain-return.forge.js +3 -2
  58. package/examples/api/sdf-shapes.forge.js +2 -2
  59. package/examples/api/sdf-surface-basket-weave.forge.js +2 -2
  60. package/examples/generative/twisted-lattice-tower.forge.js +1 -1
  61. package/examples/generative/voronoi-lampshade.forge.js +1 -1
  62. package/examples/robotics/README.md +46 -0
  63. package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
  64. package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
  65. package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
  66. package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
  67. package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
  68. package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
  69. package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
  70. package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
  71. package/examples/robotics/simready-asset-crate.forge.js +79 -0
  72. package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
  73. package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
  74. package/package.json +2 -2
  75. package/dist/assets/manifold-CzYf_iub.js +0 -3023
@@ -4,7 +4,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  var _a2;
6
6
  import { c as create, j as jsxRuntimeExports, r as reactExports, a as createWithEqualityFn, R as React, T as Tb, s as schedulerExports, b as clientExports, d as reactDomExports, u as useParams, e as useSearchParams, f as useNavigate, L as Link, g as useLocation, B as BrowserRouter, h as Routes, i as Route, N as Navigate } from "./vendor-react-6j1Kke-Y.js";
7
- import { _ as __vitePreload, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, S as Scene, a as PCFSoftShadowMap, V as VSMShadowMap, b as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, c as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, d as Layers, e as Color, f as RGBAFormat, U as UnsignedByteType, g as Vector3, h as Vector2, i as Clock, T as THREE, D as DoubleSide, j as REVISION, M as Mesh, I as IcosahedronGeometry, k as ShaderMaterial, l as Spherical, Q as Quaternion, m as MOUSE, n as TOUCH, o as Ray, p as Plane, q as DataTextureLoader, H as HalfFloatType, F as FloatType, r as DataUtils, t as LinearFilter, u as RedFormat, v as InstancedBufferGeometry, w as Float32BufferAttribute, x as InstancedInterleavedBuffer, y as InterleavedBufferAttribute, E as WireframeGeometry, G as Box3, J as Sphere, K as UniformsUtils, X as UniformsLib, Y as Vector4, Z as Line3, $ as Matrix4, a0 as MathUtils, a1 as Uniform, a2 as WebGLRenderTarget, a3 as DepthTexture, a4 as BackSide, a5 as ClampToEdgeWrapping, a6 as PlaneGeometry, a7 as UVMapping, a8 as DataTexture, a9 as Texture, aa as MeshBasicMaterial, ab as IntType, ac as ShortType, ad as ByteType, ae as UnsignedIntType, af as Loader, ag as LoadingManager, ah as LinearMipMapLinearFilter, ai as FileLoader, aj as NoBlending, ak as CubeReflectionMapping, al as EquirectangularReflectionMapping, am as CubeTextureLoader, an as WebGLCubeRenderTarget, ao as ConstraintSketch, ap as setSketchPlacement3D, aq as Sketch, ar as PROFILE_BACKEND_MARKER, as as FrozenShape, at as setShapeCompilePlan, au as hasAnyPorts, av as setShapePortsInternal, aw as markShapePortsUsed, ax as setParamOverrides, ay as resolveForgeRenderStyle, az as DEFAULT_ACTIVE_BACKEND, aA as isConstraintSketch, aB as updateConstraintValue, aC as linearizeMultiObjectSteps, aD as getShapeCompilePlan, aE as SCAN_PROXY_GRANULARITY_DEFAULT, aF as publishSolverWasmRunDebug, aG as resolveForgeQualityPreset, aH as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aI as findJointAnimationClip, aJ as resolveJointAnimation, aK as resolveJointViewValues, aL as resolveImportPath, aM as resolveScanProxyGranularity, aN as BufferGeometry, aO as LineBasicMaterial, aP as Line$1, aQ as LineDashedMaterial, aR as DepthStencilFormat, aS as UnsignedInt248Type, aT as MeshNormalMaterial, aU as NearestFilter, aV as BasicDepthPacking, aW as EventDispatcher$1, aX as NoColorSpace, aY as FrontSide, aZ as Material, a_ as AlwaysDepth, a$ as BufferAttribute, b0 as CanvasTexture, b1 as Object3D, b2 as FogExp2, b3 as Fog, b4 as AmbientLight, b5 as HemisphereLight, b6 as SpotLight, b7 as PointLight, b8 as DirectionalLight, b9 as heatPointsForSide, ba as COMPARISON_COLORS, bb as comparisonHeatDepthTest, bc as comparisonHeatEdgeOpacity, bd as comparisonHeatPatchOpacity, be as shapeToGeometry, bf as buildComparisonHeatPatchGeometry, bg as EdgesGeometry, bh as buildShapeFromCompilePlan, bi as buildVisibleHistoryStacks, bj as sketchToSvg, bk as sketchToDxf, bl as runScript, bm as MeshPhysicalMaterial, bn as LineSegments, bo as findDesignTraceNodeForConstructionStep, bp as formatDesignTraceAnchor, bq as waitForAnimationFrame, br as selectBuildLedgerNodes, bs as worldAuthorPlaneToLocal, bt as scanProxySourceBytes, bu as disposeScanProxyGeometry, bv as scanProxyGeometryFromPayload, bw as AdditiveBlending, bx as geometryWithVisibleVertexColors, by as getRenderStylePreset, bz as SCAN_RENDER_COLORS, bA as NormalBlending, bB as scanMaterialShellColor, bC as ZEBRA_STRIPE_SOFTNESS, bD as ZEBRA_STRIPE_SCALE, bE as ZEBRA_LIGHT_COLOR, bF as ZEBRA_DARK_COLOR, bG as ZEBRA_ACCENT_COLOR, bH as ZEBRA_STRIPE_FRAGMENT_SHADER, bI as ZEBRA_STRIPE_VERTEX_SHADER, bJ as SCAN_PROXY_LAYER_STYLES, bK as scanMaterialLayerStyles, bL as SURFACE_FIELD_FRAGMENT_SHADER, bM as SURFACE_FIELD_VERTEX_SHADER, bN as WORLD_UP, bO as CatmullRomCurve3, bP as TubeGeometry, bQ as THICKNESS_GRADIENT_COLORS, bR as ROUGHNESS_COLORS, bS as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bT as PERFORMANCE_SAMPLE_INTERVAL_SEC, bU as formatPerformanceCount, bV as NON_TEXT_INPUT_TYPES, bW as MeshStandardMaterial, bX as compileSdfNode3, bY as buildSdfRaymarchFragmentShader, bZ as SDF_RAYMARCH_PROXY_VERTEX_SHADER, b_ as Shape, b$ as ShapeGeometry, c0 as ShaderLib, c1 as CylinderGeometry, c2 as VIEWPORT_CAMERA_STORAGE_KEY, c3 as parseViewportCameraState, c4 as createResolvedExplodeConfig, c5 as explodeBoundsCenter, c6 as explodeMergeBounds, c7 as resolveExplodeDirective, c8 as computeExplodeMotion, c9 as getSketchWorldMatrix, ca as explodeAdd, cb as hasExplodeOverride, cc as resolveExplodeLocalFanDirection, cd as explodeMul, ce as explodeLeafFanStage, cf as normalizeCutPlane, cg as toClippingPlane, ch as isObjectExcludedFromCutPlane, ci as getShapePorts, cj as getShapeUsedPorts, ck as DEFAULT_VIEW_CONFIG, cl as SECTION_EXPLORER_PLANE_NAME, cm as ZERO_OFFSET, cn as scanProxyGridForBounds, co as IDENTITY_MATRIX$2, cp as OBJECT_CONTEXT_MENU_MARGIN, cq as OBJECT_CONTEXT_MENU_WIDTH, cr as OBJECT_CONTEXT_MENU_HEIGHT, cs as getKernelFaceNameForTriangle, ct as triangleSoupFromMeshes, cu as compareTriangleSoups, cv as buildGeometryComparisonPointCloud, cw as aabbOverlaps, cx as aabbOverlapVolume, cy as DEFAULT_COLLISION_INSPECTION_OPTIONS, cz as resolveScalarSceneSampleBudget, cA as FOCUS_MODE_DIM_OPACITY, cB as comparisonCandidateContextOpacity, cC as Matrix3, cD as initKernel, cE as initSolverWasm } from "./scalar-sampling-budget-o90NSNmF.js";
7
+ import { _ as __vitePreload, S as SDF_PROGRAM_OP, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, a as Scene, b as PCFSoftShadowMap, V as VSMShadowMap, c as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, d as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, e as Layers, f as Color, g as RGBAFormat, U as UnsignedByteType, h as Vector3, i as Vector2, j as Clock, T as THREE, D as DoubleSide, k as REVISION, M as Mesh, I as IcosahedronGeometry, l as ShaderMaterial, m as Spherical, Q as Quaternion, n as MOUSE, o as TOUCH, p as Ray, q as Plane, r as DataTextureLoader, H as HalfFloatType, F as FloatType, t as DataUtils, u as LinearFilter, v as RedFormat, w as InstancedBufferGeometry, x as Float32BufferAttribute, y as InstancedInterleavedBuffer, E as InterleavedBufferAttribute, G as WireframeGeometry, J as Box3, K as Sphere, X as UniformsUtils, Y as UniformsLib, Z as Vector4, $ as Line3, a0 as Matrix4, a1 as MathUtils, a2 as Uniform, a3 as WebGLRenderTarget, a4 as DepthTexture, a5 as BackSide, a6 as ClampToEdgeWrapping, a7 as PlaneGeometry, a8 as UVMapping, a9 as DataTexture, aa as Texture, ab as MeshBasicMaterial, ac as IntType, ad as ShortType, ae as ByteType, af as UnsignedIntType, ag as Loader, ah as LoadingManager, ai as LinearMipMapLinearFilter, aj as FileLoader, ak as NoBlending, al as CubeReflectionMapping, am as EquirectangularReflectionMapping, an as CubeTextureLoader, ao as WebGLCubeRenderTarget, ap as ConstraintSketch, aq as setSketchPlacement3D, ar as Sketch, as as PROFILE_BACKEND_MARKER, at as FrozenShape, au as setShapeCompilePlan, av as hasAnyPorts, aw as setShapePortsInternal, ax as markShapePortsUsed, ay as setParamOverrides, az as resolveForgeRenderStyle, aA as DEFAULT_ACTIVE_BACKEND, aB as isConstraintSketch, aC as updateConstraintValue, aD as linearizeMultiObjectSteps, aE as getShapeCompilePlan, aF as SCAN_PROXY_GRANULARITY_DEFAULT, aG as publishSolverWasmRunDebug, aH as resolveForgeQualityPreset, aI as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aJ as findJointAnimationClip, aK as resolveJointAnimation, aL as resolveJointViewValues, aM as resolveImportPath, aN as resolveScanProxyGranularity, aO as BufferGeometry, aP as LineBasicMaterial, aQ as Line$1, aR as LineDashedMaterial, aS as DepthStencilFormat, aT as UnsignedInt248Type, aU as MeshNormalMaterial, aV as NearestFilter, aW as BasicDepthPacking, aX as EventDispatcher$1, aY as NoColorSpace, aZ as FrontSide, a_ as Material, a$ as AlwaysDepth, b0 as BufferAttribute, b1 as CanvasTexture, b2 as Object3D, b3 as FogExp2, b4 as Fog, b5 as AmbientLight, b6 as HemisphereLight, b7 as SpotLight, b8 as PointLight, b9 as DirectionalLight, ba as heatPointsForSide, bb as COMPARISON_COLORS, bc as comparisonHeatDepthTest, bd as comparisonHeatEdgeOpacity, be as comparisonHeatPatchOpacity, bf as shapeToGeometry, bg as buildComparisonHeatPatchGeometry, bh as EdgesGeometry, bi as buildShapeFromCompilePlan, bj as buildVisibleHistoryStacks, bk as sketchToSvg, bl as sketchToDxf, bm as runScript, bn as MeshPhysicalMaterial, bo as LineSegments, bp as findDesignTraceNodeForConstructionStep, bq as formatDesignTraceAnchor, br as waitForAnimationFrame, bs as selectBuildLedgerNodes, bt as worldAuthorPlaneToLocal, bu as compileSdfProgramEvaluator3, bv as SDF_LINEAR_OUTPUT_COLOR_GLSL, bw as GLSL3, bx as BoxGeometry, by as Data3DTexture, bz as buildSdfRaymarchFragmentShader, bA as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bB as scanProxySourceBytes, bC as disposeScanProxyGeometry, bD as scanProxyGeometryFromPayload, bE as AdditiveBlending, bF as geometryWithVisibleVertexColors, bG as getRenderStylePreset, bH as SCAN_RENDER_COLORS, bI as NormalBlending, bJ as scanMaterialShellColor, bK as ZEBRA_STRIPE_SOFTNESS, bL as ZEBRA_STRIPE_SCALE, bM as ZEBRA_LIGHT_COLOR, bN as ZEBRA_DARK_COLOR, bO as ZEBRA_ACCENT_COLOR, bP as ZEBRA_STRIPE_FRAGMENT_SHADER, bQ as ZEBRA_STRIPE_VERTEX_SHADER, bR as SCAN_PROXY_LAYER_STYLES, bS as scanMaterialLayerStyles, bT as SURFACE_FIELD_FRAGMENT_SHADER, bU as SURFACE_FIELD_VERTEX_SHADER, bV as WORLD_UP, bW as CatmullRomCurve3, bX as TubeGeometry, bY as THICKNESS_GRADIENT_COLORS, bZ as ROUGHNESS_COLORS, b_ as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, b$ as PERFORMANCE_SAMPLE_INTERVAL_SEC, c0 as formatPerformanceCount, c1 as NON_TEXT_INPUT_TYPES, c2 as MeshStandardMaterial, c3 as Shape, c4 as ShapeGeometry, c5 as ShaderLib, c6 as CylinderGeometry, c7 as VIEWPORT_CAMERA_STORAGE_KEY, c8 as parseViewportCameraState, c9 as createResolvedExplodeConfig, ca as explodeBoundsCenter, cb as explodeMergeBounds, cc as resolveExplodeDirective, cd as computeExplodeMotion, ce as getSketchWorldMatrix, cf as explodeAdd, cg as hasExplodeOverride, ch as resolveExplodeLocalFanDirection, ci as explodeMul, cj as explodeLeafFanStage, ck as normalizeCutPlane, cl as toClippingPlane, cm as isObjectExcludedFromCutPlane, cn as getShapePorts, co as getShapeUsedPorts, cp as DEFAULT_VIEW_CONFIG, cq as SECTION_EXPLORER_PLANE_NAME, cr as ZERO_OFFSET, cs as scanProxyGridForBounds, ct as IDENTITY_MATRIX$2, cu as OBJECT_CONTEXT_MENU_MARGIN, cv as OBJECT_CONTEXT_MENU_WIDTH, cw as OBJECT_CONTEXT_MENU_HEIGHT, cx as getKernelFaceNameForTriangle, cy as triangleSoupFromMeshes, cz as compareTriangleSoups, cA as buildGeometryComparisonPointCloud, cB as aabbOverlaps, cC as aabbOverlapVolume, cD as DEFAULT_COLLISION_INSPECTION_OPTIONS, cE as resolveScalarSceneSampleBudget, cF as FOCUS_MODE_DIM_OPACITY, cG as comparisonCandidateContextOpacity, cH as Matrix3, cI as initBackendForEvaluation } from "./backendInit-ymjonyQp.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];
@@ -880,6 +880,136 @@ function detectMobile() {
880
880
  return hasTouch && isNarrow;
881
881
  }
882
882
  const isMobile = detectMobile();
883
+ function buildSdfProgramWgslFunction(program, options = {}) {
884
+ const functionName = requireWgslIdentifier(options.functionName ?? "sdfProgram", "functionName");
885
+ const lines = [`fn ${functionName}(p: vec3f) -> f32 {`, " var v0: f32 = p.x;", " var v1: f32 = p.y;", " var v2: f32 = p.z;"];
886
+ const { opcodes, argA, argB, argC, constants, output } = program;
887
+ for (let index = 0; index < opcodes.length; index += 1) {
888
+ const slot = `v${index + 3}`;
889
+ const a2 = `v${argA[index]}`;
890
+ const b2 = `v${argB[index]}`;
891
+ switch (opcodes[index]) {
892
+ case SDF_PROGRAM_OP.Const:
893
+ lines.push(` var ${slot}: f32 = ${wgslNumber(constants[argC[index]] ?? 0)};`);
894
+ break;
895
+ case SDF_PROGRAM_OP.Neg:
896
+ lines.push(` var ${slot}: f32 = -${a2};`);
897
+ break;
898
+ case SDF_PROGRAM_OP.Abs:
899
+ lines.push(` var ${slot}: f32 = abs(${a2});`);
900
+ break;
901
+ case SDF_PROGRAM_OP.Sqrt:
902
+ lines.push(` var ${slot}: f32 = sqrt(${a2});`);
903
+ break;
904
+ case SDF_PROGRAM_OP.Sin:
905
+ lines.push(` var ${slot}: f32 = sin(${a2});`);
906
+ break;
907
+ case SDF_PROGRAM_OP.Cos:
908
+ lines.push(` var ${slot}: f32 = cos(${a2});`);
909
+ break;
910
+ case SDF_PROGRAM_OP.Round:
911
+ lines.push(` var ${slot}: f32 = round(${a2});`);
912
+ break;
913
+ case SDF_PROGRAM_OP.Add:
914
+ lines.push(` var ${slot}: f32 = ${a2} + ${b2};`);
915
+ break;
916
+ case SDF_PROGRAM_OP.Sub:
917
+ lines.push(` var ${slot}: f32 = ${a2} - ${b2};`);
918
+ break;
919
+ case SDF_PROGRAM_OP.Mul:
920
+ lines.push(` var ${slot}: f32 = ${a2} * ${b2};`);
921
+ break;
922
+ case SDF_PROGRAM_OP.Div:
923
+ lines.push(` var ${slot}: f32 = ${a2} / ${b2};`);
924
+ break;
925
+ case SDF_PROGRAM_OP.Min:
926
+ lines.push(` var ${slot}: f32 = min(${a2}, ${b2});`);
927
+ break;
928
+ case SDF_PROGRAM_OP.Max:
929
+ lines.push(` var ${slot}: f32 = max(${a2}, ${b2});`);
930
+ break;
931
+ case SDF_PROGRAM_OP.Atan2:
932
+ lines.push(` var ${slot}: f32 = atan2(${a2}, ${b2});`);
933
+ break;
934
+ case SDF_PROGRAM_OP.LessThan:
935
+ lines.push(` var ${slot}: f32 = select(0.0, 1.0, ${a2} < ${b2});`);
936
+ break;
937
+ case SDF_PROGRAM_OP.Mod:
938
+ lines.push(` var ${slot}: f32 = ${a2} % ${b2};`);
939
+ break;
940
+ default:
941
+ throw new Error(`Unknown SdfProgram opcode ${opcodes[index]} at instruction ${index}.`);
942
+ }
943
+ }
944
+ lines.push(` return v${output};`);
945
+ lines.push("}");
946
+ return lines.join("\n");
947
+ }
948
+ function buildSdfProgramBrickComputeWgsl(program, options = {}) {
949
+ const functionName = requireWgslIdentifier(options.functionName ?? "sdfProgram", "functionName");
950
+ const entryPoint = requireWgslIdentifier(options.entryPoint ?? "sampleSdfBrick", "entryPoint");
951
+ const [workgroupX, workgroupY, workgroupZ] = requireWorkgroupSize(options.workgroupSize ?? [4, 4, 4]);
952
+ const bindingGroup = requireBindingIndex(options.bindingGroup ?? 0, "bindingGroup");
953
+ const paramsBinding = requireBindingIndex(options.paramsBinding ?? 0, "paramsBinding");
954
+ const distancesBinding = requireBindingIndex(options.distancesBinding ?? 1, "distancesBinding");
955
+ return [
956
+ buildSdfProgramWgslFunction(program, { functionName }),
957
+ "",
958
+ "struct SdfBrickParams {",
959
+ " origin: vec4f,",
960
+ " step: vec4f,",
961
+ " dims: vec4u,",
962
+ "};",
963
+ "",
964
+ `@group(${bindingGroup}) @binding(${paramsBinding}) var<uniform> brick: SdfBrickParams;`,
965
+ `@group(${bindingGroup}) @binding(${distancesBinding}) var<storage, read_write> distances: array<f32>;`,
966
+ "",
967
+ `@compute @workgroup_size(${workgroupX}, ${workgroupY}, ${workgroupZ})`,
968
+ `fn ${entryPoint}(@builtin(global_invocation_id) gid: vec3u) {`,
969
+ " let dims = brick.dims.xyz;",
970
+ " if (any(gid >= dims)) {",
971
+ " return;",
972
+ " }",
973
+ " let index = gid.x + dims.x * (gid.y + dims.y * gid.z);",
974
+ " let voxel = vec3f(f32(gid.x), f32(gid.y), f32(gid.z));",
975
+ " let p = brick.origin.xyz + voxel * brick.step.xyz;",
976
+ ` distances[index] = ${functionName}(p);`,
977
+ "}"
978
+ ].join("\n");
979
+ }
980
+ function wgslNumber(value) {
981
+ if (!Number.isFinite(value)) return "0.0";
982
+ const text = Number(value.toPrecision(9)).toString();
983
+ return text.includes(".") || text.includes("e") ? text : `${text}.0`;
984
+ }
985
+ function requireWgslIdentifier(value, name) {
986
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
987
+ throw new Error(`${name} must be a valid WGSL identifier.`);
988
+ }
989
+ return value;
990
+ }
991
+ function requireWorkgroupSize(value) {
992
+ const [x, y, z] = value;
993
+ for (const [name, size] of [
994
+ ["x", x],
995
+ ["y", y],
996
+ ["z", z]
997
+ ]) {
998
+ if (!Number.isInteger(size) || size < 1 || size > 256) {
999
+ throw new Error(`workgroupSize.${name} must be an integer from 1 to 256.`);
1000
+ }
1001
+ }
1002
+ if (x * y * z > 256) {
1003
+ throw new Error("workgroupSize product must be at most 256 for portable WebGPU compute.");
1004
+ }
1005
+ return [x, y, z];
1006
+ }
1007
+ function requireBindingIndex(value, name) {
1008
+ if (!Number.isInteger(value) || value < 0) {
1009
+ throw new Error(`${name} must be a non-negative integer.`);
1010
+ }
1011
+ return value;
1012
+ }
883
1013
  function escapeXml(value) {
884
1014
  return value.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g, " ").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
885
1015
  }
@@ -15157,8 +15287,12 @@ const runResultDeserializer = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Obje
15157
15287
  __proto__: null,
15158
15288
  deserializeRunResult
15159
15289
  }, Symbol.toStringTag, { value: "Module" }));
15290
+ function compilePlanNeedsNativeShapeLowering(plan) {
15291
+ if (plan.kind === "queryOwner") return compilePlanNeedsNativeShapeLowering(plan.base);
15292
+ return plan.kind !== "sdf";
15293
+ }
15160
15294
  function serializedSceneObjectNeedsNativeShapeLowering(object) {
15161
- return object.compilePlan !== null && object.compilePlan !== void 0;
15295
+ return object.compilePlan !== null && object.compilePlan !== void 0 && compilePlanNeedsNativeShapeLowering(object.compilePlan);
15162
15296
  }
15163
15297
  function serializedRunResultNeedsNativeOcctLowering(result) {
15164
15298
  return result.objects.some(serializedSceneObjectNeedsNativeShapeLowering);
@@ -15753,7 +15887,7 @@ function computeServerSnapshot$1(state2, serverFiles, serverFolders, sharedModel
15753
15887
  const isMeshHash = startupHashFile && MESH_EXTS.some((ext) => startupHashFile.toLowerCase().endsWith(ext));
15754
15888
  const isBinaryHash = startupHashFile && BINARY_IMPORT_EXTS.some((ext) => startupHashFile.toLowerCase().endsWith(ext));
15755
15889
  const meshPreviewFile = isMeshHash && startupHashFile && nextFiles[startupHashFile] !== void 0 ? startupHashFile : null;
15756
- const newActiveFile = sharedBundle2 ? sharedBundle2.entry : sharedModel2 ? sharedModel2.filename : startupHashFile && !isBinaryHash && nextFiles[startupHashFile] !== void 0 ? startupHashFile : initialFile && nextFiles[initialFile] !== void 0 ? initialFile : activeFile && nextFiles[activeFile] ? activeFile : findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0];
15890
+ const newActiveFile = sharedBundle2 ? sharedBundle2.entry : sharedModel2 ? sharedModel2.filename : startupHashFile && !isBinaryHash && nextFiles[startupHashFile] !== void 0 ? startupHashFile : initialFile && nextFiles[initialFile] !== void 0 ? initialFile : activeFile && nextFiles[activeFile] !== void 0 ? activeFile : findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] || "";
15757
15891
  const nextDirty = Object.keys(nextFiles).some((p2) => nextSaved[p2] !== nextFiles[p2]);
15758
15892
  const nextObjectSettingsByFile = Object.fromEntries(Object.entries(objectSettingsByFile).filter(([f2]) => f2 in nextFiles));
15759
15893
  return {
@@ -15798,7 +15932,7 @@ function computeServerFileDelete$1(state2, filename) {
15798
15932
  const newFolders = /* @__PURE__ */ new Set();
15799
15933
  Object.keys(nextFiles).forEach((p2) => collectParentPaths(p2).forEach((f2) => newFolders.add(f2)));
15800
15934
  const availableFiles = Object.keys(nextFiles);
15801
- const newActiveFile = activeFile === filename ? findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] : activeFile;
15935
+ const newActiveFile = activeFile === filename ? findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] || "" : activeFile;
15802
15936
  const nextObjectSettingsByFile = Object.fromEntries(Object.entries(objectSettingsByFile).filter(([f2]) => f2 in nextFiles));
15803
15937
  return {
15804
15938
  files: nextFiles,
@@ -15841,7 +15975,7 @@ const CRASH_COOLDOWN_MS = 2e3;
15841
15975
  class EvalWorkerClient {
15842
15976
  constructor(workerFactory = () => new Worker(new URL(
15843
15977
  /* @vite-ignore */
15844
- "/assets/evalWorker-DbNs7Dkp.js",
15978
+ "/assets/evalWorker-C2pm8LHP.js",
15845
15979
  import.meta.url
15846
15980
  ), { type: "module" })) {
15847
15981
  __publicField(this, "worker", null);
@@ -16332,6 +16466,7 @@ function projectTextFileTemplate(path) {
16332
16466
  return "";
16333
16467
  }
16334
16468
  function monacoLanguageForProjectFile(path) {
16469
+ if (!path) return "plaintext";
16335
16470
  const lower = path.toLowerCase();
16336
16471
  if (lower.endsWith(".md")) return "markdown";
16337
16472
  if (lower.endsWith(".json")) return "json";
@@ -16344,6 +16479,7 @@ function monacoLanguageForProjectFile(path) {
16344
16479
  return "plaintext";
16345
16480
  }
16346
16481
  function highlightLanguageForProjectFile(path) {
16482
+ if (!path) return null;
16347
16483
  const lower = path.toLowerCase();
16348
16484
  if (lower.endsWith(".md")) return "markdown";
16349
16485
  if (lower.endsWith(".json")) return "json";
@@ -17064,7 +17200,12 @@ const INITIAL_FOLDERS = collectInitialFolders(INITIAL_FILES);
17064
17200
  const getActiveFileFromHash = () => {
17065
17201
  const hash = window.location.hash.slice(1);
17066
17202
  if (hash.startsWith("code/") || hash.startsWith("bundle/")) return null;
17067
- return hash || null;
17203
+ if (!hash) return null;
17204
+ try {
17205
+ return decodeURIComponent(hash);
17206
+ } catch {
17207
+ return hash;
17208
+ }
17068
17209
  };
17069
17210
  const STARTUP_HASH_FILE = getActiveFileFromHash();
17070
17211
  const sharedModel = decodeSharedHash(window.location.hash);
@@ -17320,6 +17461,8 @@ function formatComputeBackendLabel(backend) {
17320
17461
  return "Local OCCT";
17321
17462
  case "truck":
17322
17463
  return "Local Truck";
17464
+ case "sdf":
17465
+ return "Local SDF";
17323
17466
  case "server-occt":
17324
17467
  return "Server OCCT";
17325
17468
  }
@@ -17523,7 +17666,7 @@ const initialActive = (() => {
17523
17666
  }
17524
17667
  const names = Object.keys(INITIAL_FILES);
17525
17668
  const fallback = findPreferredEntryFile(names) || names.find((n) => n.endsWith(".js")) || names[0];
17526
- return fallback;
17669
+ return fallback || "";
17527
17670
  })();
17528
17671
  const INITIAL_SAVED = {};
17529
17672
  const MAX_RECENT_FILES = 200;
@@ -17606,6 +17749,19 @@ function isScalarInspectChannel(channel) {
17606
17749
  function shouldUseScanRenderStyle(channel, displayMode) {
17607
17750
  return isScalarInspectChannel(channel) && displayMode === "scan";
17608
17751
  }
17752
+ function recordViewCommandDebug(command) {
17753
+ if (typeof window === "undefined") return;
17754
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
17755
+ const entry = {
17756
+ id: command.id,
17757
+ type: command.type,
17758
+ view: command.view,
17759
+ targetId: command.targetId,
17760
+ camera: command.camera
17761
+ };
17762
+ window.__forgecadViewCommandDebug = [...window.__forgecadViewCommandDebug ?? [], entry].slice(-50);
17763
+ console.debug("[forgecad view-command]", entry);
17764
+ }
17609
17765
  const initialViewPreferences = readViewPreferences();
17610
17766
  const initialInspectChannel = resolveInitialViewInspectChannel(initialViewPreferences.inspectChannel);
17611
17767
  const initialInspectDisplayMode = resolveInspectDisplayMode(initialViewPreferences.inspectDisplayMode);
@@ -17620,6 +17776,28 @@ function resolveViewportScanGranularity(granularity, renderStyle) {
17620
17776
  return renderStyle === "matrix" ? Math.min(next, SCAN_PROXY_MATRIX_GRANULARITY_MAX) : next;
17621
17777
  }
17622
17778
  const initialPreviewFile = resolvePreviewFile(initialActive, INITIAL_FILES);
17779
+ function openCodeEditorPatch() {
17780
+ writeViewPreferences({ codeEditorOpen: true });
17781
+ return { codeEditorOpen: true };
17782
+ }
17783
+ function activeFileHasViewportPreview(activeFile, files, meshPreviewFile) {
17784
+ if (meshPreviewFile) return true;
17785
+ if (!activeFile || !(activeFile in files)) return false;
17786
+ if (resolvePreviewFile(activeFile, files)) return true;
17787
+ return activeFile.toLowerCase().endsWith(".svg");
17788
+ }
17789
+ function codeEditorPatchForActiveFile(activeFile, files, meshPreviewFile) {
17790
+ if (!activeFile || !(activeFile in files)) return {};
17791
+ return activeFileHasViewportPreview(activeFile, files, meshPreviewFile) ? {} : openCodeEditorPatch();
17792
+ }
17793
+ function runResultHasViewportContent(result) {
17794
+ var _a3, _b2, _c, _d;
17795
+ return result.objects.length > 0 || !!result.assemblyKinematics || (((_a3 = result.jointsView) == null ? void 0 : _a3.joints.length) ?? 0) > 0 && ((_b2 = result.jointsView) == null ? void 0 : _b2.enabled) !== false || (((_c = result.renderLabels) == null ? void 0 : _c.length) ?? 0) > 0 || (((_d = result.debugHighlights3D) == null ? void 0 : _d.length) ?? 0) > 0;
17796
+ }
17797
+ function codeEditorPatchForRunResult(result, meshPreviewFile) {
17798
+ if (meshPreviewFile || runResultHasViewportContent(result)) return {};
17799
+ return openCodeEditorPatch();
17800
+ }
17623
17801
  const initialObjectSettingsByFile = (() => {
17624
17802
  const viewPreferences = initialViewPreferences;
17625
17803
  if (viewPreferences.objectSettingsByFile && typeof viewPreferences.objectSettingsByFile === "object") {
@@ -17799,6 +17977,7 @@ const useForgeStore = create((set, get) => ({
17799
17977
  const restored = targetPreviewFile && nextByFile[targetPreviewFile] ? nextByFile[targetPreviewFile] : {};
17800
17978
  set({
17801
17979
  activeFile: name,
17980
+ ...codeEditorPatchForActiveFile(name, files, null),
17802
17981
  recentFiles: touchAndPersistRecentFile(get().recentFiles, files, name),
17803
17982
  meshPreviewFile: null,
17804
17983
  // Clear mesh preview when switching files
@@ -17849,11 +18028,13 @@ const useForgeStore = create((set, get) => ({
17849
18028
  if (get().files[normalized] !== void 0) return;
17850
18029
  if (get().folders.includes(normalized)) return;
17851
18030
  const template = projectTextFileTemplate(normalized);
18031
+ const nextFilesForEditor = { ...get().files, [normalized]: template };
17852
18032
  const newFolders = Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort();
17853
18033
  set((s) => ({
17854
18034
  files: { ...s.files, [normalized]: template },
17855
18035
  savedFiles: { ...s.savedFiles, [normalized]: template },
17856
18036
  activeFile: normalized,
18037
+ ...codeEditorPatchForActiveFile(normalized, nextFilesForEditor, null),
17857
18038
  recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: template }, normalized),
17858
18039
  paramOverrides: {},
17859
18040
  jointValues: {},
@@ -17892,6 +18073,7 @@ const useForgeStore = create((set, get) => ({
17892
18073
  files: remaining,
17893
18074
  savedFiles: remainingSaved,
17894
18075
  activeFile: newActive,
18076
+ ...codeEditorPatchForActiveFile(newActive, remaining, null),
17895
18077
  recentFiles: touchAndPersistRecentFile(get().recentFiles, remaining, newActive),
17896
18078
  objectSettingsByFile: nextObjectSettingsByFile,
17897
18079
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -17922,10 +18104,12 @@ const useForgeStore = create((set, get) => ({
17922
18104
  const nextObjectSettingsByFile = remapObjectSettingsByFile(objectSettingsByFile, oldName, normalized);
17923
18105
  const nextParamSnapshotsByFile = remapParamSnapshotsByFile(paramSnapshotsByFile, oldName, normalized);
17924
18106
  writeViewPreferences({ objectSettingsByFile: nextObjectSettingsByFile, paramSnapshotsByFile: nextParamSnapshotsByFile });
18107
+ const nextActive = oldName === activeFile ? normalized : activeFile;
17925
18108
  set({
17926
18109
  files: remaining,
17927
18110
  savedFiles: remainingSaved,
17928
- activeFile: oldName === activeFile ? normalized : activeFile,
18111
+ activeFile: nextActive,
18112
+ ...codeEditorPatchForActiveFile(nextActive, remaining, null),
17929
18113
  recentFiles: remapAndPersistRecentFiles(get().recentFiles, remaining, oldName, normalized),
17930
18114
  objectSettingsByFile: nextObjectSettingsByFile,
17931
18115
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -17983,6 +18167,7 @@ const useForgeStore = create((set, get) => ({
17983
18167
  savedFiles: updatedSaved,
17984
18168
  folders: updatedFolders,
17985
18169
  activeFile: nextActive,
18170
+ ...codeEditorPatchForActiveFile(nextActive, updatedFiles, null),
17986
18171
  recentFiles: remapAndPersistRecentFiles(get().recentFiles, updatedFiles, normalizedOld, normalizedNew),
17987
18172
  objectSettingsByFile: nextObjectSettingsByFile,
17988
18173
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -18041,6 +18226,7 @@ const useForgeStore = create((set, get) => ({
18041
18226
  savedFiles: remainingSaved,
18042
18227
  folders: remainingFolders,
18043
18228
  activeFile: newActive,
18229
+ ...codeEditorPatchForActiveFile(newActive, remainingFiles, null),
18044
18230
  recentFiles: touchAndPersistRecentFile(get().recentFiles, remainingFiles, newActive),
18045
18231
  objectSettingsByFile: nextObjectSettingsByFile,
18046
18232
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -18068,7 +18254,7 @@ const useForgeStore = create((set, get) => ({
18068
18254
  }
18069
18255
  },
18070
18256
  dirty: false,
18071
- filesLoading: true,
18257
+ filesLoading: false,
18072
18258
  fileHandle: null,
18073
18259
  setFileHandle: (fileHandle) => set({ fileHandle }),
18074
18260
  result: null,
@@ -18195,7 +18381,14 @@ const useForgeStore = create((set, get) => ({
18195
18381
  if (cached) {
18196
18382
  const applied = buildRunState(previewFile, cached, get());
18197
18383
  rememberAcceptedAssemblyPoseState(cached, readAssemblyPoseControlState(applied.nextState));
18198
- set({ ...applied.nextState, lastValidResult: cached, isEvaluating: false, evaluationPhase: "idle", buildLedgerEvents: [] });
18384
+ set({
18385
+ ...applied.nextState,
18386
+ ...codeEditorPatchForRunResult(cached, meshPreviewFile),
18387
+ lastValidResult: cached,
18388
+ isEvaluating: false,
18389
+ evaluationPhase: "idle",
18390
+ buildLedgerEvents: []
18391
+ });
18199
18392
  writeViewPreferences({ objectSettingsByFile: applied.nextObjectSettingsByFile, cutPlaneEnabled: applied.nextCutPlaneEnabled });
18200
18393
  return;
18201
18394
  }
@@ -18247,10 +18440,12 @@ const useForgeStore = create((set, get) => ({
18247
18440
  const message = serverError instanceof Error ? serverError.message : String(serverError);
18248
18441
  if (execId !== currentExecutionId) return;
18249
18442
  rememberAcceptedAssemblyPoseState(null, EMPTY_ASSEMBLY_POSE_CONTROL_STATE);
18443
+ const failedResult = errorRunResult(`Server compute failed: ${message}`, performance.now() - tDispatch);
18250
18444
  set({
18251
- result: errorRunResult(`Server compute failed: ${message}`, performance.now() - tDispatch),
18445
+ result: failedResult,
18252
18446
  consoleLogs: [],
18253
18447
  previewFile,
18448
+ ...codeEditorPatchForRunResult(failedResult, meshPreviewFile),
18254
18449
  isEvaluating: false,
18255
18450
  evaluationPhase: "idle",
18256
18451
  buildLedgerEvents: []
@@ -18281,12 +18476,25 @@ const useForgeStore = create((set, get) => ({
18281
18476
  );
18282
18477
  if (runResult.error) {
18283
18478
  rememberAcceptedAssemblyPoseState(null, EMPTY_ASSEMBLY_POSE_CONTROL_STATE);
18284
- set({ result: runResult, consoleLogs: runResult.logs, previewFile, isEvaluating: false, evaluationPhase: "idle" });
18479
+ set({
18480
+ result: runResult,
18481
+ consoleLogs: runResult.logs,
18482
+ previewFile,
18483
+ ...codeEditorPatchForRunResult(runResult, meshPreviewFile),
18484
+ isEvaluating: false,
18485
+ evaluationPhase: "idle"
18486
+ });
18285
18487
  } else {
18286
18488
  storeCache(previewFile, code, files, paramOverrides, assemblyState, runQuality, executionBackend, runResult, serialized);
18287
18489
  const applied = buildRunState(previewFile, runResult, get());
18288
18490
  rememberAcceptedAssemblyPoseState(runResult, readAssemblyPoseControlState(applied.nextState));
18289
- set({ ...applied.nextState, lastValidResult: runResult, isEvaluating: false, evaluationPhase: "idle" });
18491
+ set({
18492
+ ...applied.nextState,
18493
+ ...codeEditorPatchForRunResult(runResult, meshPreviewFile),
18494
+ lastValidResult: runResult,
18495
+ isEvaluating: false,
18496
+ evaluationPhase: "idle"
18497
+ });
18290
18498
  writeViewPreferences({ objectSettingsByFile: applied.nextObjectSettingsByFile, cutPlaneEnabled: applied.nextCutPlaneEnabled });
18291
18499
  }
18292
18500
  } catch (error) {
@@ -18295,7 +18503,14 @@ const useForgeStore = create((set, get) => ({
18295
18503
  const message = error instanceof Error ? error.message : String(error);
18296
18504
  const errResult = createErrorRunResult(message, runQuality);
18297
18505
  rememberAcceptedAssemblyPoseState(null, EMPTY_ASSEMBLY_POSE_CONTROL_STATE);
18298
- set({ result: errResult, consoleLogs: errResult.logs, previewFile, isEvaluating: false, evaluationPhase: "idle" });
18506
+ set({
18507
+ result: errResult,
18508
+ consoleLogs: errResult.logs,
18509
+ previewFile,
18510
+ ...codeEditorPatchForRunResult(errResult, meshPreviewFile),
18511
+ isEvaluating: false,
18512
+ evaluationPhase: "idle"
18513
+ });
18299
18514
  }
18300
18515
  },
18301
18516
  updateAssemblyPose: async () => {
@@ -18657,6 +18872,7 @@ const useForgeStore = create((set, get) => ({
18657
18872
  },
18658
18873
  gridEnabled: initialViewPreferences.gridEnabled ?? true,
18659
18874
  axesVisible: initialViewPreferences.axesVisible ?? true,
18875
+ viewCubeVisible: initialViewPreferences.viewCubeVisible ?? true,
18660
18876
  gridSize: initialViewPreferences.gridSize ?? 10,
18661
18877
  setGridEnabled: (enabled) => {
18662
18878
  writeViewPreferences({ gridEnabled: enabled });
@@ -18666,6 +18882,10 @@ const useForgeStore = create((set, get) => ({
18666
18882
  writeViewPreferences({ axesVisible: visible });
18667
18883
  set({ axesVisible: visible });
18668
18884
  },
18885
+ setViewCubeVisible: (visible) => {
18886
+ writeViewPreferences({ viewCubeVisible: visible });
18887
+ set({ viewCubeVisible: visible });
18888
+ },
18669
18889
  setGridSize: (size) => {
18670
18890
  writeViewPreferences({ gridSize: size });
18671
18891
  set({ gridSize: size });
@@ -19002,7 +19222,11 @@ const useForgeStore = create((set, get) => ({
19002
19222
  set({ objectPickSyncEnabled: enabled });
19003
19223
  },
19004
19224
  viewCommand: null,
19005
- requestViewCommand: (command) => set({ viewCommand: { ...command, id: Date.now() } }),
19225
+ requestViewCommand: (command) => {
19226
+ const viewCommand = { ...command, id: Date.now() };
19227
+ recordViewCommandDebug(viewCommand);
19228
+ set({ viewCommand });
19229
+ },
19006
19230
  clearViewCommand: () => set({ viewCommand: null }),
19007
19231
  viewportCameraState: null,
19008
19232
  setViewportCameraState: (state2) => set({ viewportCameraState: state2 }),
@@ -19275,10 +19499,12 @@ const useForgeStore = create((set, get) => ({
19275
19499
  loadFromText: (text, name) => {
19276
19500
  const normalized = normalizePath(name);
19277
19501
  const newFolders = Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort();
19502
+ const nextFilesForEditor = { ...get().files, [normalized]: text };
19278
19503
  set((s) => ({
19279
19504
  files: { ...s.files, [normalized]: text },
19280
19505
  savedFiles: { ...s.savedFiles, [normalized]: text },
19281
19506
  activeFile: normalized,
19507
+ ...codeEditorPatchForActiveFile(normalized, nextFilesForEditor, null),
19282
19508
  recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: text }, normalized),
19283
19509
  fileHandle: null,
19284
19510
  dirty: false,
@@ -19338,6 +19564,7 @@ const useForgeStore = create((set, get) => ({
19338
19564
  return {
19339
19565
  files: nextFiles,
19340
19566
  activeFile: nextActive,
19567
+ ...codeEditorPatchForActiveFile(nextActive, nextFiles, nextMeshPreview),
19341
19568
  recentFiles: touchAndPersistRecentFile(s.recentFiles, nextFiles, nextActive),
19342
19569
  fileHandle: null,
19343
19570
  dirty: true,
@@ -19396,6 +19623,12 @@ const useForgeStore = create((set, get) => ({
19396
19623
  writeViewPreferences({ fileExplorerOpen: nextFileExplorerOpen });
19397
19624
  return { fileExplorerOpen: nextFileExplorerOpen };
19398
19625
  }),
19626
+ codeEditorOpen: initialViewPreferences.codeEditorOpen ?? true,
19627
+ toggleCodeEditor: () => set((s) => {
19628
+ const nextCodeEditorOpen = !s.codeEditorOpen;
19629
+ writeViewPreferences({ codeEditorOpen: nextCodeEditorOpen });
19630
+ return { codeEditorOpen: nextCodeEditorOpen };
19631
+ }),
19399
19632
  viewPanelOpen: initialViewPreferences.viewPanelOpen ?? true,
19400
19633
  toggleViewPanel: () => set((s) => {
19401
19634
  const nextViewPanelOpen = !s.viewPanelOpen;
@@ -19423,6 +19656,7 @@ const useForgeStore = create((set, get) => ({
19423
19656
  set({
19424
19657
  ...nextState,
19425
19658
  activeFile: newActiveFile,
19659
+ ...codeEditorPatchForActiveFile(newActiveFile, nextFiles, nextState.meshPreviewFile),
19426
19660
  recentFiles: touchAndPersistRecentFile(baseRecentFiles, nextFiles, newActiveFile),
19427
19661
  filesLoading: false
19428
19662
  });
@@ -19459,6 +19693,7 @@ const useForgeStore = create((set, get) => ({
19459
19693
  set({
19460
19694
  ...nextState,
19461
19695
  activeFile: newActiveFile,
19696
+ ...codeEditorPatchForActiveFile(newActiveFile, nextState.files, null),
19462
19697
  recentFiles: touchAndPersistRecentFile(state2.recentFiles, nextState.files, newActiveFile)
19463
19698
  });
19464
19699
  if (newActiveFile && newActiveFile !== prevActiveFile) {
@@ -19500,10 +19735,13 @@ const useForgeStore = create((set, get) => ({
19500
19735
  openAISkill: () => set({ aiSkillOpen: true }),
19501
19736
  closeAISkill: () => set({ aiSkillOpen: false }),
19502
19737
  editorNavigate: null,
19503
- requestEditorNavigate: (line) => set((s) => {
19504
- var _a3;
19505
- return { editorNavigate: { line, id: (((_a3 = s.editorNavigate) == null ? void 0 : _a3.id) ?? 0) + 1 } };
19506
- }),
19738
+ requestEditorNavigate: (line) => {
19739
+ writeViewPreferences({ codeEditorOpen: true });
19740
+ set((s) => {
19741
+ var _a3;
19742
+ return { codeEditorOpen: true, editorNavigate: { line, id: (((_a3 = s.editorNavigate) == null ? void 0 : _a3.id) ?? 0) + 1 } };
19743
+ });
19744
+ },
19507
19745
  clearEditorNavigate: () => set({ editorNavigate: null }),
19508
19746
  disableRunCache: initialViewPreferences.disableRunCache ?? false,
19509
19747
  setDisableRunCache: (disabled) => {
@@ -27622,7 +27860,7 @@ function ConstructionGhostOverlay({ matrix }) {
27622
27860
  class ConstructionHistoryWorkerClient {
27623
27861
  constructor(workerFactory = () => new Worker(new URL(
27624
27862
  /* @vite-ignore */
27625
- "/assets/constructionHistoryWorker-AwMMWSxg.js",
27863
+ "/assets/constructionHistoryWorker-CZ42Dksy.js",
27626
27864
  import.meta.url
27627
27865
  ), { type: "module" })) {
27628
27866
  __publicField(this, "worker", null);
@@ -30134,7 +30372,7 @@ function generateReportInWorker(options) {
30134
30372
  return new Promise((resolve2, reject) => {
30135
30373
  const worker = new Worker(new URL(
30136
30374
  /* @vite-ignore */
30137
- "/assets/reportWorker-DO6hcQbh.js",
30375
+ "/assets/reportWorker-CwenM7wB.js",
30138
30376
  import.meta.url
30139
30377
  ), { type: "module" });
30140
30378
  const cleanup = () => {
@@ -30339,7 +30577,7 @@ const SURFACE_PALETTE = ["#4488cc", "#44cc88", "#cc8844", "#cc44aa", "#88cc44",
30339
30577
  function toPage(tx, x, y) {
30340
30578
  return [x * tx.scale + tx.offsetX, y * tx.scale + tx.offsetY];
30341
30579
  }
30342
- function computeBounds$1(meta) {
30580
+ function computeBounds(meta) {
30343
30581
  const bounds = { min: [Infinity, Infinity], max: [-Infinity, -Infinity] };
30344
30582
  function expand(x, y) {
30345
30583
  bounds.min[0] = Math.min(bounds.min[0], x);
@@ -30405,7 +30643,7 @@ function computeBounds$1(meta) {
30405
30643
  return bounds;
30406
30644
  }
30407
30645
  function computeTransform(meta, ptsPerMm, marginPts) {
30408
- const bounds = computeBounds$1(meta);
30646
+ const bounds = computeBounds(meta);
30409
30647
  const sketchW = bounds.max[0] - bounds.min[0];
30410
30648
  const sketchH = bounds.max[1] - bounds.min[1];
30411
30649
  return {
@@ -30701,7 +30939,7 @@ function generateSketchPdf(meta, options) {
30701
30939
  const tx = computeTransform(meta, POINTS_PER_MM, MARGIN);
30702
30940
  tx.pageWidth = Math.max(tx.pageWidth, 600);
30703
30941
  tx.pageHeight = Math.max(tx.pageHeight, 400);
30704
- const bounds = computeBounds$1(meta);
30942
+ const bounds = computeBounds(meta);
30705
30943
  const extent = Math.max(bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], 1);
30706
30944
  const baseExtent = 100;
30707
30945
  const autoFontScale = Math.max(1, extent / baseExtent);
@@ -30881,9 +31119,14 @@ async function exportMeshFromStore(format, preferredStem, options = {}) {
30881
31119
  downloadExportArtifact(await buildMeshExportArtifact(format, stem, runResult, objectSettings));
30882
31120
  }
30883
31121
  async function exportExactFromStore(format, preferredStem) {
30884
- const { result, activeFile, files, paramOverrides, runQuality } = useForgeStore.getState();
31122
+ const { result, activeFile, files, paramOverrides, runQuality, activeBackend } = useForgeStore.getState();
30885
31123
  const assemblyState = resolveAssemblyStateForExport();
30886
31124
  requireSuccessfulRunResult(result);
31125
+ if (activeBackend === "sdf") {
31126
+ throw new Error(
31127
+ "STEP/BREP exact export is not available from the SDF backend. Use 3MF, OBJ, or STL mesh export, or switch to OCCT for exact B-rep export."
31128
+ );
31129
+ }
30887
31130
  const stem = sanitizeExportStem(preferredStem ?? deriveExportStem(activeFile));
30888
31131
  const code = files[activeFile];
30889
31132
  if (!code) throw new Error(`Active file "${activeFile}" is missing.`);
@@ -30895,7 +31138,8 @@ async function exportExactFromStore(format, preferredStem) {
30895
31138
  quality: runQuality,
30896
31139
  paramOverrides,
30897
31140
  assemblyState,
30898
- isNotebook: false
31141
+ isNotebook: false,
31142
+ activeBackend
30899
31143
  });
30900
31144
  triggerDownload(blob, `${stem}.${format}`);
30901
31145
  } finally {
@@ -33586,7 +33830,7 @@ const PHASE_CONFIG = {
33586
33830
  };
33587
33831
  const PHASE_ORDER = ["kernel-init", "evaluating", "serializing"];
33588
33832
  function formatEvaluationBackendLabel(activeBackend, computeTarget) {
33589
- const backend = activeBackend === "occt" ? "OCCT" : activeBackend === "manifold" ? "Manifold" : activeBackend === "truck" ? "Truck" : "kernel";
33833
+ const backend = activeBackend === "occt" ? "OCCT" : activeBackend === "manifold" ? "Manifold" : activeBackend === "truck" ? "Truck" : activeBackend === "sdf" ? "SDF" : "kernel";
33590
33834
  return computeTarget === "server" ? "Server OCCT" : `Local ${backend}`;
33591
33835
  }
33592
33836
  function EvaluationIndicator({
@@ -34180,6 +34424,712 @@ function SectionCapPreview({
34180
34424
  if (previewPlanes.length === 0) return null;
34181
34425
  return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: previewPlanes.map((plane) => /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: plane.geometry, renderOrder: plane.renderOrder, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PreviewCapMaterial, { color: color2, opacity, plane }) }, plane.planeId)) });
34182
34426
  }
34427
+ const DEFAULT_TARGET_LONGEST_AXIS_SAMPLES = 96;
34428
+ const DEFAULT_MAX_VOXELS = 18e4;
34429
+ const DEFAULT_MIN_AXIS_SAMPLES = 8;
34430
+ const BRICK_PROGRAM_OPCODE_THRESHOLD = 768;
34431
+ function compiledSdfProgramFromSceneData(program) {
34432
+ return {
34433
+ opcodes: Uint8Array.from(program.opcodes),
34434
+ argA: Int32Array.from(program.argA),
34435
+ argB: Int32Array.from(program.argB),
34436
+ argC: Int32Array.from(program.argC),
34437
+ constants: Float64Array.from(program.constants),
34438
+ output: program.output,
34439
+ slotCount: program.slotCount
34440
+ };
34441
+ }
34442
+ function shouldUseSdfDistanceBrick(data, opcodeThreshold = BRICK_PROGRAM_OPCODE_THRESHOLD) {
34443
+ return Boolean((data == null ? void 0 : data.program) && data.program.opcodes.length >= opcodeThreshold && finitePositiveBounds(data.bounds));
34444
+ }
34445
+ function chooseSdfDistanceBrickResolution(bounds, options = {}) {
34446
+ const targetLongestAxisSamples = clampInteger(options.targetLongestAxisSamples ?? DEFAULT_TARGET_LONGEST_AXIS_SAMPLES, 4, 256);
34447
+ const maxVoxels = clampInteger(options.maxVoxels ?? DEFAULT_MAX_VOXELS, 512, 16e6);
34448
+ const minAxisSamples = clampInteger(options.minAxisSamples ?? DEFAULT_MIN_AXIS_SAMPLES, 2, 64);
34449
+ const size = boundsSize(bounds);
34450
+ const longest = Math.max(...size);
34451
+ if (!Number.isFinite(longest) || longest <= 0) {
34452
+ return { dims: [minAxisSamples, minAxisSamples, minAxisSamples], cellSize: 1, voxelCount: minAxisSamples ** 3 };
34453
+ }
34454
+ let samples = targetLongestAxisSamples;
34455
+ while (samples >= minAxisSamples) {
34456
+ const cellSize = longest / Math.max(1, samples - 1);
34457
+ const dims = size.map((axis) => Math.max(minAxisSamples, Math.ceil(axis / cellSize) + 1));
34458
+ const voxelCount = dims[0] * dims[1] * dims[2];
34459
+ if (voxelCount <= maxVoxels || samples === minAxisSamples) return { dims, cellSize, voxelCount };
34460
+ samples = Math.max(minAxisSamples, Math.floor(samples * 0.9));
34461
+ }
34462
+ return {
34463
+ dims: [minAxisSamples, minAxisSamples, minAxisSamples],
34464
+ cellSize: longest / Math.max(1, minAxisSamples - 1),
34465
+ voxelCount: minAxisSamples ** 3
34466
+ };
34467
+ }
34468
+ function buildSdfDistanceBrick(data, options = {}) {
34469
+ const startedAt = performance.now();
34470
+ const resolution = chooseSdfDistanceBrickResolution(data.bounds, options);
34471
+ const program = compiledSdfProgramFromSceneData(data.program);
34472
+ const evalSdf = compileSdfProgramEvaluator3(program);
34473
+ const distances = new Float32Array(resolution.voxelCount);
34474
+ const min = [...data.bounds.min];
34475
+ const max2 = [...data.bounds.max];
34476
+ const size = boundsSize(data.bounds);
34477
+ const [nx, ny, nz] = resolution.dims;
34478
+ let offset = 0;
34479
+ for (let z = 0; z < nz; z += 1) {
34480
+ const pz = coordinateAt(min[2], size[2], z, nz);
34481
+ for (let y = 0; y < ny; y += 1) {
34482
+ const py = coordinateAt(min[1], size[1], y, ny);
34483
+ for (let x = 0; x < nx; x += 1) {
34484
+ const px = coordinateAt(min[0], size[0], x, nx);
34485
+ const d = evalSdf(px, py, pz);
34486
+ distances[offset] = Number.isFinite(d) ? d : 1e9;
34487
+ offset += 1;
34488
+ }
34489
+ }
34490
+ }
34491
+ return {
34492
+ ...resolution,
34493
+ min,
34494
+ max: max2,
34495
+ distances,
34496
+ buildMs: performance.now() - startedAt,
34497
+ buildSource: "cpu"
34498
+ };
34499
+ }
34500
+ function finitePositiveBounds(bounds) {
34501
+ const size = boundsSize(bounds);
34502
+ return [...bounds.min, ...bounds.max, ...size].every(Number.isFinite) && size.every((axis) => axis > 0);
34503
+ }
34504
+ function boundsSize(bounds) {
34505
+ return [bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], bounds.max[2] - bounds.min[2]];
34506
+ }
34507
+ function coordinateAt(min, size, index, count) {
34508
+ if (count <= 1) return min;
34509
+ return min + size * (index / (count - 1));
34510
+ }
34511
+ function clampInteger(value, min, max2) {
34512
+ if (!Number.isFinite(value)) return min;
34513
+ return Math.max(min, Math.min(max2, Math.round(value)));
34514
+ }
34515
+ const DEFAULT_WORKGROUP_SIZE = [4, 4, 4];
34516
+ const BRICK_PARAMS_BYTE_LENGTH = 48;
34517
+ async function buildSdfDistanceBrickWebGpu(data, options = {}) {
34518
+ var _a3, _b2, _c, _d;
34519
+ const gpu = options.gpu ?? currentBrowserGpu();
34520
+ if (!gpu) return null;
34521
+ const adapter = await gpu.requestAdapter();
34522
+ if (!adapter) return null;
34523
+ const startedAt = performance.now();
34524
+ const device = await adapter.requestDevice();
34525
+ const buffers = [];
34526
+ try {
34527
+ const resolution = chooseSdfDistanceBrickResolution(data.bounds, options);
34528
+ const min = [...data.bounds.min];
34529
+ const max2 = [...data.bounds.max];
34530
+ const step = brickStep(data.bounds, resolution.dims);
34531
+ const byteLength2 = resolution.voxelCount * Float32Array.BYTES_PER_ELEMENT;
34532
+ const workgroupSize = options.workgroupSize ?? DEFAULT_WORKGROUP_SIZE;
34533
+ const entryPoint = "sampleSdfBrick";
34534
+ const module = device.createShaderModule({
34535
+ label: "ForgeCAD SDF distance brick compute shader",
34536
+ code: buildSdfProgramBrickComputeWgsl(compiledSdfProgramFromSceneData(data.program), { entryPoint, workgroupSize })
34537
+ });
34538
+ const pipeline = device.createComputePipeline({
34539
+ label: "ForgeCAD SDF distance brick compute pipeline",
34540
+ layout: "auto",
34541
+ compute: { module, entryPoint }
34542
+ });
34543
+ const usage = webGpuUsage();
34544
+ const paramsBuffer = device.createBuffer({
34545
+ label: "ForgeCAD SDF distance brick params",
34546
+ size: BRICK_PARAMS_BYTE_LENGTH,
34547
+ usage: usage.UNIFORM | usage.COPY_DST
34548
+ });
34549
+ const distancesBuffer = device.createBuffer({
34550
+ label: "ForgeCAD SDF distance brick distances",
34551
+ size: byteLength2,
34552
+ usage: usage.STORAGE | usage.COPY_SRC
34553
+ });
34554
+ const readBuffer = device.createBuffer({
34555
+ label: "ForgeCAD SDF distance brick readback",
34556
+ size: byteLength2,
34557
+ usage: usage.MAP_READ | usage.COPY_DST
34558
+ });
34559
+ buffers.push(paramsBuffer, distancesBuffer, readBuffer);
34560
+ device.queue.writeBuffer(paramsBuffer, 0, packSdfDistanceBrickWebGpuParams(min, step, resolution.dims));
34561
+ const bindGroup = device.createBindGroup({
34562
+ label: "ForgeCAD SDF distance brick bind group",
34563
+ layout: pipeline.getBindGroupLayout(0),
34564
+ entries: [
34565
+ { binding: 0, resource: { buffer: paramsBuffer } },
34566
+ { binding: 1, resource: { buffer: distancesBuffer } }
34567
+ ]
34568
+ });
34569
+ const encoder2 = device.createCommandEncoder({ label: "ForgeCAD SDF distance brick command encoder" });
34570
+ const pass = encoder2.beginComputePass({ label: "ForgeCAD SDF distance brick compute pass" });
34571
+ pass.setPipeline(pipeline);
34572
+ pass.setBindGroup(0, bindGroup);
34573
+ pass.dispatchWorkgroups(
34574
+ Math.ceil(resolution.dims[0] / workgroupSize[0]),
34575
+ Math.ceil(resolution.dims[1] / workgroupSize[1]),
34576
+ Math.ceil(resolution.dims[2] / workgroupSize[2])
34577
+ );
34578
+ pass.end();
34579
+ encoder2.copyBufferToBuffer(distancesBuffer, 0, readBuffer, 0, byteLength2);
34580
+ device.queue.submit([encoder2.finish()]);
34581
+ await ((_b2 = (_a3 = device.queue).onSubmittedWorkDone) == null ? void 0 : _b2.call(_a3));
34582
+ await readBuffer.mapAsync(webGpuMapModeRead());
34583
+ const mapped = readBuffer.getMappedRange();
34584
+ const distances = new Float32Array(mapped.slice(0, byteLength2));
34585
+ readBuffer.unmap();
34586
+ return {
34587
+ ...resolution,
34588
+ min,
34589
+ max: max2,
34590
+ distances,
34591
+ buildMs: performance.now() - startedAt,
34592
+ buildSource: "webgpu"
34593
+ };
34594
+ } finally {
34595
+ for (const buffer of buffers) (_c = buffer.destroy) == null ? void 0 : _c.call(buffer);
34596
+ (_d = device.destroy) == null ? void 0 : _d.call(device);
34597
+ }
34598
+ }
34599
+ function isSdfDistanceBrickWebGpuEnabled() {
34600
+ if (typeof window === "undefined") return false;
34601
+ const params = new URLSearchParams(window.location.search);
34602
+ return params.has("sdf-webgpu-bricks") || window.localStorage.getItem("fc-sdf-webgpu-bricks") === "1";
34603
+ }
34604
+ function packSdfDistanceBrickWebGpuParams(origin, step, dims) {
34605
+ const buffer = new ArrayBuffer(BRICK_PARAMS_BYTE_LENGTH);
34606
+ const floats = new Float32Array(buffer, 0, 8);
34607
+ floats[0] = origin[0];
34608
+ floats[1] = origin[1];
34609
+ floats[2] = origin[2];
34610
+ floats[3] = 0;
34611
+ floats[4] = step[0];
34612
+ floats[5] = step[1];
34613
+ floats[6] = step[2];
34614
+ floats[7] = 0;
34615
+ const uints = new Uint32Array(buffer, 32, 4);
34616
+ uints[0] = dims[0];
34617
+ uints[1] = dims[1];
34618
+ uints[2] = dims[2];
34619
+ uints[3] = 0;
34620
+ return buffer;
34621
+ }
34622
+ function brickStep(bounds, dims) {
34623
+ return [
34624
+ axisStep(bounds.min[0], bounds.max[0], dims[0]),
34625
+ axisStep(bounds.min[1], bounds.max[1], dims[1]),
34626
+ axisStep(bounds.min[2], bounds.max[2], dims[2])
34627
+ ];
34628
+ }
34629
+ function axisStep(min, max2, count) {
34630
+ if (count <= 1) return 0;
34631
+ return (max2 - min) / (count - 1);
34632
+ }
34633
+ function currentBrowserGpu() {
34634
+ const nav = globalThis.navigator;
34635
+ return (nav == null ? void 0 : nav.gpu) ?? null;
34636
+ }
34637
+ function webGpuUsage() {
34638
+ return globalThis.GPUBufferUsage ?? {
34639
+ MAP_READ: 1,
34640
+ COPY_SRC: 4,
34641
+ COPY_DST: 8,
34642
+ UNIFORM: 64,
34643
+ STORAGE: 128
34644
+ };
34645
+ }
34646
+ function webGpuMapModeRead() {
34647
+ var _a3;
34648
+ return ((_a3 = globalThis.GPUMapMode) == null ? void 0 : _a3.READ) ?? 1;
34649
+ }
34650
+ const MAX_CLIP_PLANES$1 = 8;
34651
+ const SDF_BRICK_VERTEX_SHADER = `precision highp float;
34652
+
34653
+ out vec3 vLocalPosition;
34654
+
34655
+ void main() {
34656
+ vLocalPosition = position;
34657
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
34658
+ }
34659
+ `;
34660
+ const SDF_BRICK_FRAGMENT_SHADER = `precision highp float;
34661
+ precision highp sampler3D;
34662
+
34663
+ in vec3 vLocalPosition;
34664
+ out vec4 outColor;
34665
+
34666
+ ${SDF_LINEAR_OUTPUT_COLOR_GLSL}
34667
+
34668
+ uniform sampler3D uDistanceTexture;
34669
+ uniform mat4 uCameraMatrixWorld;
34670
+ uniform mat4 uLocalToWorld;
34671
+ uniform mat4 uWorldToLocal;
34672
+ uniform mat4 uViewProjectionMatrix;
34673
+ uniform vec3 uBrickMin;
34674
+ uniform vec3 uBrickMax;
34675
+ uniform vec3 uColor;
34676
+ uniform vec3 uHoverColor;
34677
+ uniform float uAlpha;
34678
+ uniform float uHoverIntensity;
34679
+ uniform float uRayStep;
34680
+ uniform float uIsoEps;
34681
+ uniform float uNormalStep;
34682
+ uniform float uIsOrthographic;
34683
+ uniform int uClipPlaneCount;
34684
+ uniform vec4 uClipPlanes[8];
34685
+
34686
+ const int MAX_STEPS = 420;
34687
+ const int REFINE_STEPS = 7;
34688
+
34689
+ bool intersectAabb(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax, out float tNear, out float tFar) {
34690
+ vec3 inv = 1.0 / rd;
34691
+ vec3 t0 = (bmin - ro) * inv;
34692
+ vec3 t1 = (bmax - ro) * inv;
34693
+ vec3 tmin = min(t0, t1);
34694
+ vec3 tmax = max(t0, t1);
34695
+ tNear = max(max(tmin.x, tmin.y), tmin.z);
34696
+ tFar = min(min(tmax.x, tmax.y), tmax.z);
34697
+ return tFar >= max(tNear, 0.0);
34698
+ }
34699
+
34700
+ float brickSdf(vec3 p) {
34701
+ vec3 uv = clamp((p - uBrickMin) / max(uBrickMax - uBrickMin, vec3(1e-6)), vec3(0.0), vec3(1.0));
34702
+ return texture(uDistanceTexture, uv).r;
34703
+ }
34704
+
34705
+ float clippedBrickSdf(vec3 p) {
34706
+ float d = brickSdf(p);
34707
+ for (int i = 0; i < 8; i++) {
34708
+ if (i >= uClipPlaneCount) break;
34709
+ vec4 plane = uClipPlanes[i];
34710
+ d = max(d, dot(plane.xyz, p) + plane.w);
34711
+ }
34712
+ return d;
34713
+ }
34714
+
34715
+ bool crossedSurface(float a, float b) {
34716
+ return (a < 0.0 && b > 0.0) || (a > 0.0 && b < 0.0);
34717
+ }
34718
+
34719
+ float refineSurface(vec3 ro, vec3 rd, float lo, float hi) {
34720
+ float dLo = clippedBrickSdf(ro + rd * lo);
34721
+ for (int i = 0; i < REFINE_STEPS; i++) {
34722
+ float mid = 0.5 * (lo + hi);
34723
+ float dMid = clippedBrickSdf(ro + rd * mid);
34724
+ if (abs(dMid) < uIsoEps) return mid;
34725
+ if (crossedSurface(dLo, dMid)) {
34726
+ hi = mid;
34727
+ } else {
34728
+ lo = mid;
34729
+ dLo = dMid;
34730
+ }
34731
+ }
34732
+ return 0.5 * (lo + hi);
34733
+ }
34734
+
34735
+ vec3 estimateNormal(vec3 p) {
34736
+ vec2 e = vec2(uNormalStep, 0.0);
34737
+ return normalize(vec3(
34738
+ brickSdf(p + e.xyy) - brickSdf(p - e.xyy),
34739
+ brickSdf(p + e.yxy) - brickSdf(p - e.yxy),
34740
+ brickSdf(p + e.yyx) - brickSdf(p - e.yyx)
34741
+ ));
34742
+ }
34743
+
34744
+ void writeFragmentDepth(vec3 pLocal) {
34745
+ vec4 world = uLocalToWorld * vec4(pLocal, 1.0);
34746
+ vec4 clip = uViewProjectionMatrix * world;
34747
+ float ndcDepth = clip.z / clip.w;
34748
+ gl_FragDepth = ndcDepth * 0.5 + 0.5;
34749
+ }
34750
+
34751
+ void main() {
34752
+ float sceneDiag = max(length(uBrickMax - uBrickMin), 1.0);
34753
+ vec3 roWorld = uCameraMatrixWorld[3].xyz;
34754
+ vec3 ro = (uWorldToLocal * vec4(roWorld, 1.0)).xyz;
34755
+ vec3 rd = normalize(vLocalPosition - ro);
34756
+ if (uIsOrthographic > 0.5) {
34757
+ vec3 rdWorld = normalize((uCameraMatrixWorld * vec4(0.0, 0.0, -1.0, 0.0)).xyz);
34758
+ rd = normalize((uWorldToLocal * vec4(rdWorld, 0.0)).xyz);
34759
+ ro = vLocalPosition - rd * sceneDiag * 2.0;
34760
+ }
34761
+
34762
+ float tNear = 0.0;
34763
+ float tFar = 0.0;
34764
+ if (!intersectAabb(ro, rd, uBrickMin, uBrickMax, tNear, tFar)) discard;
34765
+
34766
+ float t = max(tNear, 0.0);
34767
+ float prevT = t;
34768
+ float prevD = clippedBrickSdf(ro + rd * t);
34769
+ bool hit = prevD <= 0.0 && abs(prevD) < uIsoEps;
34770
+
34771
+ for (int i = 0; i < MAX_STEPS; i++) {
34772
+ if (hit) break;
34773
+ t += clamp(abs(prevD) * 0.8, uRayStep, uRayStep * 4.0);
34774
+ if (t > tFar) break;
34775
+ float d = clippedBrickSdf(ro + rd * t);
34776
+ float absD = abs(d);
34777
+ if (d <= 0.0 && absD < uIsoEps) {
34778
+ hit = true;
34779
+ break;
34780
+ }
34781
+ if (crossedSurface(prevD, d)) {
34782
+ t = refineSurface(ro, rd, prevT, t);
34783
+ hit = true;
34784
+ break;
34785
+ }
34786
+ prevT = t;
34787
+ prevD = d;
34788
+ }
34789
+
34790
+ if (!hit) discard;
34791
+
34792
+ vec3 p = ro + rd * t;
34793
+ writeFragmentDepth(p);
34794
+ vec3 pWorld = (uLocalToWorld * vec4(p, 1.0)).xyz;
34795
+ vec3 normal = normalize(mat3(transpose(uWorldToLocal)) * estimateNormal(p));
34796
+ vec3 viewDir = normalize(roWorld - pWorld);
34797
+ vec3 keyDir = normalize(vec3(0.45, 0.62, 0.64));
34798
+ vec3 fillDir = normalize(vec3(-0.68, -0.18, 0.42));
34799
+ float diffuse = max(dot(normal, keyDir), 0.0) * 0.72 + max(dot(normal, fillDir), 0.0) * 0.22 + 0.16;
34800
+ float fresnel = pow(1.0 - max(dot(normal, viewDir), 0.0), 3.0);
34801
+ vec3 base = mix(uColor, uHoverColor, uHoverIntensity);
34802
+ vec3 shaded = base * diffuse + base * fresnel * 0.18;
34803
+ outColor = forgeLinearToOutputColor(shaded, uAlpha);
34804
+ }
34805
+ `;
34806
+ function colorToRgb$1(color2, fallback) {
34807
+ const resolved = new Color(color2 ?? fallback);
34808
+ return [resolved.r, resolved.g, resolved.b];
34809
+ }
34810
+ function createProxyGeometry$1(data) {
34811
+ const { min, max: max2 } = data.bounds;
34812
+ const size = [max2[0] - min[0], max2[1] - min[1], max2[2] - min[2]];
34813
+ if (!size.every((value) => Number.isFinite(value) && value > 0)) return null;
34814
+ const geometry = new BoxGeometry(size[0], size[1], size[2]);
34815
+ geometry.translate((min[0] + max2[0]) / 2, (min[1] + max2[1]) / 2, (min[2] + max2[2]) / 2);
34816
+ return geometry;
34817
+ }
34818
+ function createClipPlaneUniforms$1() {
34819
+ return Array.from({ length: MAX_CLIP_PLANES$1 }, () => new Vector4(0, 0, 0, 0));
34820
+ }
34821
+ function createDistanceTexture(brick) {
34822
+ const texture = new Data3DTexture(brick.distances, brick.dims[0], brick.dims[1], brick.dims[2]);
34823
+ texture.format = RedFormat;
34824
+ texture.type = FloatType;
34825
+ texture.minFilter = LinearFilter;
34826
+ texture.magFilter = LinearFilter;
34827
+ texture.wrapS = ClampToEdgeWrapping;
34828
+ texture.wrapT = ClampToEdgeWrapping;
34829
+ texture.wrapR = ClampToEdgeWrapping;
34830
+ texture.unpackAlignment = 1;
34831
+ texture.needsUpdate = true;
34832
+ return texture;
34833
+ }
34834
+ async function buildViewportDistanceBrick(sdf) {
34835
+ if (!shouldUseSdfDistanceBrick(sdf)) return null;
34836
+ if (isSdfDistanceBrickWebGpuEnabled()) {
34837
+ const webGpuBrick = await buildSdfDistanceBrickWebGpu(sdf);
34838
+ if (webGpuBrick) return webGpuBrick;
34839
+ }
34840
+ return buildSdfDistanceBrick(sdf);
34841
+ }
34842
+ function SdfDistanceBrickObject({
34843
+ sdf,
34844
+ settings,
34845
+ matrix,
34846
+ materialProps,
34847
+ isHovered,
34848
+ clippingPlanes = [],
34849
+ onPointerEnter,
34850
+ onPointerMove,
34851
+ onPointerLeave,
34852
+ onClick,
34853
+ onDoubleClick,
34854
+ onContextMenu
34855
+ }) {
34856
+ const meshRef = reactExports.useRef(null);
34857
+ const materialRef = reactExports.useRef(null);
34858
+ const { camera } = useThree();
34859
+ const viewProjection = reactExports.useMemo(() => new Matrix4(), []);
34860
+ const worldToLocal = reactExports.useMemo(() => new Matrix4(), []);
34861
+ const localClipPlane = reactExports.useMemo(() => new Plane(), []);
34862
+ const [brick, setBrick] = reactExports.useState(null);
34863
+ const [brickError, setBrickError] = reactExports.useState(null);
34864
+ reactExports.useEffect(() => {
34865
+ let cancelled = false;
34866
+ setBrick(null);
34867
+ setBrickError(null);
34868
+ buildViewportDistanceBrick(sdf).then((nextBrick) => {
34869
+ if (!cancelled) setBrick(nextBrick);
34870
+ }).catch((error) => {
34871
+ const nextError = error instanceof Error ? error : new Error(String(error));
34872
+ console.error("ForgeCAD SDF distance brick build failed.", nextError);
34873
+ if (!cancelled) setBrickError(nextError);
34874
+ });
34875
+ return () => {
34876
+ cancelled = true;
34877
+ };
34878
+ }, [sdf]);
34879
+ const geometry = reactExports.useMemo(() => createProxyGeometry$1(sdf), [sdf]);
34880
+ const texture = reactExports.useMemo(() => brick ? createDistanceTexture(brick) : null, [brick]);
34881
+ const opacity = Math.min(settings.opacity, (materialProps == null ? void 0 : materialProps.opacity) ?? 1);
34882
+ const color2 = reactExports.useMemo(() => colorToRgb$1(settings.color ?? sdf.colorHex, "#5b9bd5"), [settings.color, sdf.colorHex]);
34883
+ const uniforms = reactExports.useMemo(
34884
+ () => ({
34885
+ uDistanceTexture: { value: texture },
34886
+ uCameraMatrixWorld: { value: new Matrix4() },
34887
+ uLocalToWorld: { value: new Matrix4() },
34888
+ uWorldToLocal: { value: new Matrix4() },
34889
+ uViewProjectionMatrix: { value: new Matrix4() },
34890
+ uBrickMin: { value: new Vector3((brick == null ? void 0 : brick.min[0]) ?? 0, (brick == null ? void 0 : brick.min[1]) ?? 0, (brick == null ? void 0 : brick.min[2]) ?? 0) },
34891
+ uBrickMax: { value: new Vector3((brick == null ? void 0 : brick.max[0]) ?? 1, (brick == null ? void 0 : brick.max[1]) ?? 1, (brick == null ? void 0 : brick.max[2]) ?? 1) },
34892
+ uColor: { value: new Vector3(...color2) },
34893
+ uHoverColor: { value: new Color(settings.color) },
34894
+ uAlpha: { value: opacity },
34895
+ uHoverIntensity: { value: 0 },
34896
+ uRayStep: { value: Math.max(0.05, ((brick == null ? void 0 : brick.cellSize) ?? 1) * 0.65) },
34897
+ uIsoEps: { value: Math.max(0.05, ((brick == null ? void 0 : brick.cellSize) ?? 1) * 0.55) },
34898
+ uNormalStep: { value: Math.max(0.05, (brick == null ? void 0 : brick.cellSize) ?? 1) },
34899
+ uIsOrthographic: { value: 0 },
34900
+ uClipPlaneCount: { value: 0 },
34901
+ uClipPlanes: { value: createClipPlaneUniforms$1() }
34902
+ }),
34903
+ [brick, color2, opacity, settings.color, texture]
34904
+ );
34905
+ useFrame(() => {
34906
+ const material = materialRef.current;
34907
+ const mesh = meshRef.current;
34908
+ if (!material || !mesh) return;
34909
+ mesh.updateMatrixWorld();
34910
+ camera.updateMatrixWorld();
34911
+ viewProjection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
34912
+ worldToLocal.copy(mesh.matrixWorld).invert();
34913
+ material.uniforms.uCameraMatrixWorld.value.copy(camera.matrixWorld);
34914
+ material.uniforms.uLocalToWorld.value.copy(mesh.matrixWorld);
34915
+ material.uniforms.uWorldToLocal.value.copy(worldToLocal);
34916
+ material.uniforms.uViewProjectionMatrix.value.copy(viewProjection);
34917
+ material.uniforms.uIsOrthographic.value = camera.isOrthographicCamera ? 1 : 0;
34918
+ material.uniforms.uHoverColor.value.set(settings.color);
34919
+ material.uniforms.uHoverIntensity.value = isHovered ? 0.3 : 0;
34920
+ material.uniforms.uClipPlaneCount.value = Math.min(clippingPlanes.length, MAX_CLIP_PLANES$1);
34921
+ const clipUniforms = material.uniforms.uClipPlanes.value;
34922
+ for (let i = 0; i < MAX_CLIP_PLANES$1; i += 1) {
34923
+ const source = clippingPlanes[i];
34924
+ if (!source) {
34925
+ clipUniforms[i].set(0, 0, 0, 0);
34926
+ continue;
34927
+ }
34928
+ localClipPlane.copy(source).applyMatrix4(worldToLocal);
34929
+ clipUniforms[i].set(localClipPlane.normal.x, localClipPlane.normal.y, localClipPlane.normal.z, localClipPlane.constant);
34930
+ }
34931
+ });
34932
+ reactExports.useEffect(() => {
34933
+ return () => {
34934
+ geometry == null ? void 0 : geometry.dispose();
34935
+ };
34936
+ }, [geometry]);
34937
+ reactExports.useEffect(() => {
34938
+ return () => {
34939
+ texture == null ? void 0 : texture.dispose();
34940
+ };
34941
+ }, [texture]);
34942
+ if (brickError || !brick || !geometry || !texture) return null;
34943
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
34944
+ "group",
34945
+ {
34946
+ matrixAutoUpdate: false,
34947
+ matrix,
34948
+ onPointerEnter,
34949
+ onPointerMove,
34950
+ onPointerLeave,
34951
+ onClick,
34952
+ onDoubleClick,
34953
+ onContextMenu,
34954
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34955
+ "mesh",
34956
+ {
34957
+ ref: meshRef,
34958
+ geometry,
34959
+ userData: {
34960
+ forgeMesh: true,
34961
+ forgeSdfDirect: true,
34962
+ forgeSdfRenderer: "distance-brick",
34963
+ forgeSdfDistanceBrick: true,
34964
+ forgeSdfDistanceBrickBuildSource: brick.buildSource,
34965
+ forgeSdfDistanceBrickBuildMs: brick.buildMs,
34966
+ forgeSdfDistanceBrickVoxels: brick.voxelCount
34967
+ },
34968
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34969
+ "shaderMaterial",
34970
+ {
34971
+ ref: materialRef,
34972
+ glslVersion: GLSL3,
34973
+ vertexShader: SDF_BRICK_VERTEX_SHADER,
34974
+ fragmentShader: SDF_BRICK_FRAGMENT_SHADER,
34975
+ uniforms,
34976
+ side: DoubleSide,
34977
+ transparent: opacity < 1 || ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) > 0,
34978
+ depthWrite: opacity >= 1 && ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) <= 0,
34979
+ toneMapped: false
34980
+ }
34981
+ )
34982
+ }
34983
+ )
34984
+ }
34985
+ );
34986
+ }
34987
+ const MAX_CLIP_PLANES = 8;
34988
+ function colorToRgb(color2, fallback) {
34989
+ const resolved = new Color(color2 ?? fallback);
34990
+ return [resolved.r, resolved.g, resolved.b];
34991
+ }
34992
+ function sdfLeaf(data, settings, materialProps) {
34993
+ const opacity = Math.min(settings.opacity, (materialProps == null ? void 0 : materialProps.opacity) ?? 1);
34994
+ return {
34995
+ node: data.node,
34996
+ ...data.program ? { program: data.program } : {},
34997
+ color: colorToRgb(settings.color ?? data.colorHex, "#5b9bd5"),
34998
+ alpha: opacity,
34999
+ emissive: colorToRgb(materialProps == null ? void 0 : materialProps.emissive, "#000000"),
35000
+ emissiveIntensity: (materialProps == null ? void 0 : materialProps.emissiveIntensity) ?? 0,
35001
+ metalness: (materialProps == null ? void 0 : materialProps.metalness) ?? 0.05,
35002
+ roughness: (materialProps == null ? void 0 : materialProps.roughness) ?? 0.35,
35003
+ clearcoat: (materialProps == null ? void 0 : materialProps.clearcoat) ?? 0.1,
35004
+ clearcoatRoughness: (materialProps == null ? void 0 : materialProps.clearcoatRoughness) ?? 0.4,
35005
+ transmission: (materialProps == null ? void 0 : materialProps.transmission) ?? 0,
35006
+ reflectivity: (materialProps == null ? void 0 : materialProps.reflectivity) ?? 0.5
35007
+ };
35008
+ }
35009
+ function canRenderSdfDirectly(data) {
35010
+ return Boolean(data && data.preview.mode === "raymarch");
35011
+ }
35012
+ function createProxyGeometry(data) {
35013
+ const { min, max: max2 } = data.bounds;
35014
+ const size = [max2[0] - min[0], max2[1] - min[1], max2[2] - min[2]];
35015
+ if (!size.every((value) => Number.isFinite(value) && value > 0)) return null;
35016
+ const geometry = new BoxGeometry(size[0], size[1], size[2]);
35017
+ geometry.translate((min[0] + max2[0]) / 2, (min[1] + max2[1]) / 2, (min[2] + max2[2]) / 2);
35018
+ return geometry;
35019
+ }
35020
+ function createClipPlaneUniforms() {
35021
+ return Array.from({ length: MAX_CLIP_PLANES }, () => new Vector4(0, 0, 0, 0));
35022
+ }
35023
+ function SdfDirectObject(props) {
35024
+ const gl = useThree((state2) => state2.gl);
35025
+ if (shouldUseSdfDistanceBrick(props.sdf) && gl.capabilities.isWebGL2) {
35026
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(SdfDistanceBrickObject, { ...props });
35027
+ }
35028
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(SdfRaymarchObject, { ...props });
35029
+ }
35030
+ function SdfRaymarchObject({
35031
+ sdf,
35032
+ settings,
35033
+ matrix,
35034
+ materialProps,
35035
+ isHovered,
35036
+ clippingPlanes = [],
35037
+ onPointerEnter,
35038
+ onPointerMove,
35039
+ onPointerLeave,
35040
+ onClick,
35041
+ onDoubleClick,
35042
+ onContextMenu
35043
+ }) {
35044
+ const meshRef = reactExports.useRef(null);
35045
+ const materialRef = reactExports.useRef(null);
35046
+ const { camera } = useThree();
35047
+ const viewProjection = reactExports.useMemo(() => new Matrix4(), []);
35048
+ const worldToLocal = reactExports.useMemo(() => new Matrix4(), []);
35049
+ const localClipPlane = reactExports.useMemo(() => new Plane(), []);
35050
+ const geometry = reactExports.useMemo(() => createProxyGeometry(sdf), [sdf]);
35051
+ const fragmentShader2 = reactExports.useMemo(
35052
+ () => buildSdfRaymarchFragmentShader([sdfLeaf(sdf, settings, materialProps)]),
35053
+ [materialProps, settings, sdf]
35054
+ );
35055
+ const uniforms = reactExports.useMemo(
35056
+ () => ({
35057
+ uCameraMatrixWorld: { value: new Matrix4() },
35058
+ uLocalToWorld: { value: new Matrix4() },
35059
+ uWorldToLocal: { value: new Matrix4() },
35060
+ uViewProjectionMatrix: { value: new Matrix4() },
35061
+ uSceneMin: { value: new Vector3(sdf.bounds.min[0], sdf.bounds.min[1], sdf.bounds.min[2]) },
35062
+ uSceneMax: { value: new Vector3(sdf.bounds.max[0], sdf.bounds.max[1], sdf.bounds.max[2]) },
35063
+ uClipPlaneCount: { value: 0 },
35064
+ uClipPlanes: { value: createClipPlaneUniforms() },
35065
+ uHoverLeafIndex: { value: -1 },
35066
+ uHoverColor: { value: new Color(settings.color) },
35067
+ uHoverIntensity: { value: 0 },
35068
+ uIsOrthographic: { value: 0 }
35069
+ }),
35070
+ [sdf.bounds.max, sdf.bounds.min, settings.color]
35071
+ );
35072
+ useFrame(() => {
35073
+ const material = materialRef.current;
35074
+ const mesh = meshRef.current;
35075
+ if (!material || !mesh) return;
35076
+ mesh.updateMatrixWorld();
35077
+ camera.updateMatrixWorld();
35078
+ viewProjection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
35079
+ worldToLocal.copy(mesh.matrixWorld).invert();
35080
+ material.uniforms.uCameraMatrixWorld.value.copy(camera.matrixWorld);
35081
+ material.uniforms.uLocalToWorld.value.copy(mesh.matrixWorld);
35082
+ material.uniforms.uWorldToLocal.value.copy(worldToLocal);
35083
+ material.uniforms.uViewProjectionMatrix.value.copy(viewProjection);
35084
+ material.uniforms.uIsOrthographic.value = camera.isOrthographicCamera ? 1 : 0;
35085
+ material.uniforms.uHoverLeafIndex.value = isHovered ? 0 : -1;
35086
+ material.uniforms.uHoverColor.value.set(settings.color);
35087
+ material.uniforms.uHoverIntensity.value = isHovered ? 0.3 : 0;
35088
+ material.uniforms.uClipPlaneCount.value = Math.min(clippingPlanes.length, MAX_CLIP_PLANES);
35089
+ const clipUniforms = material.uniforms.uClipPlanes.value;
35090
+ for (let i = 0; i < MAX_CLIP_PLANES; i += 1) {
35091
+ const source = clippingPlanes[i];
35092
+ if (!source) {
35093
+ clipUniforms[i].set(0, 0, 0, 0);
35094
+ continue;
35095
+ }
35096
+ localClipPlane.copy(source).applyMatrix4(worldToLocal);
35097
+ clipUniforms[i].set(localClipPlane.normal.x, localClipPlane.normal.y, localClipPlane.normal.z, localClipPlane.constant);
35098
+ }
35099
+ });
35100
+ reactExports.useEffect(() => {
35101
+ return () => geometry == null ? void 0 : geometry.dispose();
35102
+ }, [geometry]);
35103
+ if (!geometry) return null;
35104
+ const opacity = Math.min(settings.opacity, (materialProps == null ? void 0 : materialProps.opacity) ?? 1);
35105
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
35106
+ "group",
35107
+ {
35108
+ matrixAutoUpdate: false,
35109
+ matrix,
35110
+ onPointerEnter,
35111
+ onPointerMove,
35112
+ onPointerLeave,
35113
+ onClick,
35114
+ onDoubleClick,
35115
+ onContextMenu,
35116
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { ref: meshRef, geometry, userData: { forgeMesh: true, forgeSdfDirect: true, forgeSdfRenderer: "raymarch" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35117
+ "shaderMaterial",
35118
+ {
35119
+ ref: materialRef,
35120
+ vertexShader: SDF_RAYMARCH_PROXY_VERTEX_SHADER,
35121
+ fragmentShader: fragmentShader2,
35122
+ uniforms,
35123
+ side: DoubleSide,
35124
+ transparent: opacity < 1 || ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) > 0,
35125
+ depthWrite: opacity >= 1 && ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) <= 0,
35126
+ toneMapped: false,
35127
+ extensions: { fragDepth: true }
35128
+ }
35129
+ ) })
35130
+ }
35131
+ );
35132
+ }
34183
35133
  class ScanProxyWorkerClient {
34184
35134
  constructor(workerFactory = () => new Worker(new URL(
34185
35135
  /* @vite-ignore */
@@ -34815,7 +35765,17 @@ function ForgeObject({
34815
35765
  onContextMenu
34816
35766
  }) {
34817
35767
  var _a3, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
35768
+ const wantsDirectSdf = Boolean(
35769
+ canRenderSdfDirectly(obj.sdf) && inspectChannel === "none" && renderStyle !== "scan" && renderStyle !== "matrix" && renderMode !== "wireframe" && !debugHighlightColor
35770
+ );
34818
35771
  const { solidGeo, edgesGeo, hasSmoothNormals } = reactExports.useMemo(() => {
35772
+ if (wantsDirectSdf) {
35773
+ return {
35774
+ solidGeo: null,
35775
+ edgesGeo: null,
35776
+ hasSmoothNormals: false
35777
+ };
35778
+ }
34819
35779
  if (!obj.shape) {
34820
35780
  return {
34821
35781
  solidGeo: null,
@@ -34837,7 +35797,7 @@ function ForgeObject({
34837
35797
  hasSmoothNormals: false
34838
35798
  };
34839
35799
  }
34840
- }, [obj.shape]);
35800
+ }, [obj.shape, wantsDirectSdf]);
34841
35801
  const isScalarInspect = inspectChannel === "thickness" || inspectChannel === "roughness";
34842
35802
  const isScalarScan = isScalarInspect && inspectDisplayMode === "scan";
34843
35803
  const inspectPointGeo = reactExports.useMemo(() => {
@@ -34896,7 +35856,7 @@ function ForgeObject({
34896
35856
  Object.values(scanAnalysisGeometries ?? {}).forEach((geometry) => geometry == null ? void 0 : geometry.dispose());
34897
35857
  };
34898
35858
  }, [scanAnalysisGeometries]);
34899
- if (!solidGeo || !settings.visible) return null;
35859
+ if (!settings.visible) return null;
34900
35860
  const effectiveRenderMode = isInteracting && renderMode === "overlay" ? "solid" : renderMode;
34901
35861
  const renderStylePreset = getRenderStylePreset(renderStyle);
34902
35862
  const materialDefaults = renderStylePreset.material;
@@ -34916,7 +35876,6 @@ function ForgeObject({
34916
35876
  const showWire = !isInspecting && effectiveRenderMode === "wireframe";
34917
35877
  const effectiveClippingPlanes = clippingPlanes ?? EMPTY_CLIPPING_PLANES$1;
34918
35878
  const effectiveSectionPlanes = sectionPlanes ?? EMPTY_SECTION_PLANES$1;
34919
- const inspectSolidGeo = inspectMeshColorGeo ?? solidGeo;
34920
35879
  const hasInspectMeshColors = inspectMeshColorGeo !== null;
34921
35880
  const showFloatingContext = showSolid && inspectChannel === "floating";
34922
35881
  const showFloatingObject = inspectChannel !== "floating" || hasInspectMeshColors || inspectColor !== "#000000";
@@ -34938,6 +35897,27 @@ function ForgeObject({
34938
35897
  const showSectionCapPreview = Boolean(
34939
35898
  isInteracting && showSolid && effectiveRenderMode !== "wireframe" && effectiveSectionPlanes.length > 0
34940
35899
  );
35900
+ if (wantsDirectSdf && obj.sdf) {
35901
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
35902
+ SdfDirectObject,
35903
+ {
35904
+ sdf: obj.sdf,
35905
+ settings,
35906
+ matrix,
35907
+ materialProps: obj.materialProps,
35908
+ isHovered,
35909
+ clippingPlanes: effectiveClippingPlanes,
35910
+ onPointerEnter,
35911
+ onPointerMove,
35912
+ onPointerLeave,
35913
+ onClick,
35914
+ onDoubleClick,
35915
+ onContextMenu
35916
+ }
35917
+ );
35918
+ }
35919
+ if (!solidGeo) return null;
35920
+ const inspectSolidGeo = inspectMeshColorGeo ?? solidGeo;
34941
35921
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
34942
35922
  "group",
34943
35923
  {
@@ -37083,6 +38063,7 @@ function MeasureInfoPanel() {
37083
38063
  }
37084
38064
  return null;
37085
38065
  }
38066
+ const MAX_VIEWPORT_PERFORMANCE_SAMPLES = 240;
37086
38067
  const getProgramCount = (gl) => {
37087
38068
  const info = gl.info;
37088
38069
  return Array.isArray(info.programs) ? info.programs.length : 0;
@@ -37123,6 +38104,26 @@ function collectScanProxyPerformanceInfo(scene) {
37123
38104
  performanceReplacementCount
37124
38105
  } : null;
37125
38106
  }
38107
+ function collectSdfDistanceBrickPerformanceInfo(scene) {
38108
+ let count = 0;
38109
+ let voxels = 0;
38110
+ let buildMs = 0;
38111
+ let cpuCount = 0;
38112
+ let webGpuCount = 0;
38113
+ scene.traverse((object) => {
38114
+ const userData = object.userData;
38115
+ if (!userData.forgeSdfDistanceBrick) return;
38116
+ count += 1;
38117
+ voxels += userData.forgeSdfDistanceBrickVoxels ?? 0;
38118
+ buildMs += userData.forgeSdfDistanceBrickBuildMs ?? 0;
38119
+ if (userData.forgeSdfDistanceBrickBuildSource === "webgpu") {
38120
+ webGpuCount += 1;
38121
+ } else {
38122
+ cpuCount += 1;
38123
+ }
38124
+ });
38125
+ return count > 0 ? { count, voxels, buildMs, cpuCount, webGpuCount } : null;
38126
+ }
37126
38127
  const TREND_WINDOW = 20;
37127
38128
  class TrendTracker {
37128
38129
  constructor(size) {
@@ -37174,6 +38175,88 @@ const formatWorkerStatus = (message) => {
37174
38175
  if (/timed out/i.test(message)) return "restarted after timeout";
37175
38176
  return "restarted after worker error";
37176
38177
  };
38178
+ function summarizeViewportPerformanceSamples(samples) {
38179
+ var _a3, _b2, _c, _d;
38180
+ if (samples.length === 0) {
38181
+ return {
38182
+ samples: 0,
38183
+ latestFps: null,
38184
+ averageFps: null,
38185
+ minFps: null,
38186
+ p10Fps: null,
38187
+ averageFrameTimeMs: null,
38188
+ latestDrawCalls: null,
38189
+ latestRenderTriangles: null,
38190
+ latestModelTriangles: null,
38191
+ latestProgramCount: null,
38192
+ latestSdfDistanceBrickCount: null,
38193
+ latestSdfDistanceBrickVoxels: null,
38194
+ latestSdfDistanceBrickCpuCount: null,
38195
+ latestSdfDistanceBrickWebGpuCount: null
38196
+ };
38197
+ }
38198
+ const latest = samples[samples.length - 1];
38199
+ const fpsValues = samples.map((sample) => sample.fps).sort((a2, b2) => a2 - b2);
38200
+ const fpsTotal = samples.reduce((sum, sample) => sum + sample.fps, 0);
38201
+ const frameTimeTotal = samples.reduce((sum, sample) => sum + sample.frameTimeMs, 0);
38202
+ const p10Index = Math.floor((fpsValues.length - 1) * 0.1);
38203
+ return {
38204
+ samples: samples.length,
38205
+ latestFps: latest.fps,
38206
+ averageFps: fpsTotal / samples.length,
38207
+ minFps: fpsValues[0],
38208
+ p10Fps: fpsValues[p10Index],
38209
+ averageFrameTimeMs: frameTimeTotal / samples.length,
38210
+ latestDrawCalls: latest.drawCalls,
38211
+ latestRenderTriangles: latest.renderTriangles,
38212
+ latestModelTriangles: latest.modelTriangles,
38213
+ latestProgramCount: latest.programCount,
38214
+ latestSdfDistanceBrickCount: ((_a3 = latest.sdfDistanceBricks) == null ? void 0 : _a3.count) ?? 0,
38215
+ latestSdfDistanceBrickVoxels: ((_b2 = latest.sdfDistanceBricks) == null ? void 0 : _b2.voxels) ?? 0,
38216
+ latestSdfDistanceBrickCpuCount: ((_c = latest.sdfDistanceBricks) == null ? void 0 : _c.cpuCount) ?? 0,
38217
+ latestSdfDistanceBrickWebGpuCount: ((_d = latest.sdfDistanceBricks) == null ? void 0 : _d.webGpuCount) ?? 0
38218
+ };
38219
+ }
38220
+ function getViewportPerformanceProbe() {
38221
+ if (typeof window === "undefined") return null;
38222
+ if (!window.__forgeViewportPerformance) {
38223
+ window.__forgeViewportPerformance = {
38224
+ latest: null,
38225
+ samples: [],
38226
+ reset() {
38227
+ this.latest = null;
38228
+ this.samples = [];
38229
+ },
38230
+ summary(sampleLimit) {
38231
+ const selected = typeof sampleLimit === "number" && Number.isFinite(sampleLimit) && sampleLimit > 0 ? this.samples.slice(-Math.round(sampleLimit)) : this.samples;
38232
+ return summarizeViewportPerformanceSamples(selected);
38233
+ }
38234
+ };
38235
+ }
38236
+ return window.__forgeViewportPerformance;
38237
+ }
38238
+ function publishViewportPerformanceSample(stats) {
38239
+ const probe = getViewportPerformanceProbe();
38240
+ if (!probe) return;
38241
+ if (!stats) {
38242
+ probe.latest = null;
38243
+ return;
38244
+ }
38245
+ const sample = {
38246
+ ...stats,
38247
+ sampledAtMs: performance.now()
38248
+ };
38249
+ probe.latest = sample;
38250
+ probe.samples.push(sample);
38251
+ if (probe.samples.length > MAX_VIEWPORT_PERFORMANCE_SAMPLES) {
38252
+ probe.samples.splice(0, probe.samples.length - MAX_VIEWPORT_PERFORMANCE_SAMPLES);
38253
+ }
38254
+ }
38255
+ function isViewportPerformanceProbeEnabled() {
38256
+ if (typeof window === "undefined") return false;
38257
+ const params = new URLSearchParams(window.location.search);
38258
+ return params.has("viewport-fps") || params.has("viewportPerf") || window.localStorage.getItem("fc-viewport-performance-probe") === "1";
38259
+ }
37177
38260
  function PerformanceInfoSampler({
37178
38261
  enabled,
37179
38262
  modelTriangles,
@@ -37198,7 +38281,10 @@ function PerformanceInfoSampler({
37198
38281
  sinceEmitSec: 0,
37199
38282
  reactRenderCountAtLastEmit: reactRenderCountRef.current
37200
38283
  };
37201
- if (!enabled) onStatsChange(null);
38284
+ if (!enabled) {
38285
+ publishViewportPerformanceSample(null);
38286
+ onStatsChange(null);
38287
+ }
37202
38288
  }, [enabled, modelTriangles, onStatsChange, reactRenderCountRef, sceneObjects]);
37203
38289
  useFrame((_state, delta) => {
37204
38290
  var _a3, _b2, _c, _d;
@@ -37213,7 +38299,7 @@ function PerformanceInfoSampler({
37213
38299
  const reactRendersDelta = reactRenderCountRef.current - sample.reactRenderCountAtLastEmit;
37214
38300
  const mem = performance.memory;
37215
38301
  const cacheStats = getRunResultCacheStats();
37216
- onStatsChange({
38302
+ const stats = {
37217
38303
  fps: frameCount / Math.max(sample.elapsedSec, 1e-6),
37218
38304
  frameTimeMs: sample.frameTimeMsTotal / frameCount,
37219
38305
  sceneObjects,
@@ -37236,8 +38322,11 @@ function PerformanceInfoSampler({
37236
38322
  wasmHeapTruckMB: toMB(((_d = evalWorkerClient.workerWasmHeap) == null ? void 0 : _d.truckBytes) ?? null),
37237
38323
  workerFailure: evalWorkerClient.lastWorkerFailure,
37238
38324
  reactRendersPerSec: reactRendersDelta / Math.max(sample.sinceEmitSec, 1e-6),
37239
- scanProxy: collectScanProxyPerformanceInfo(scene)
37240
- });
38325
+ scanProxy: collectScanProxyPerformanceInfo(scene),
38326
+ sdfDistanceBricks: collectSdfDistanceBrickPerformanceInfo(scene)
38327
+ };
38328
+ publishViewportPerformanceSample(stats);
38329
+ onStatsChange(stats);
37241
38330
  sample.frames = 0;
37242
38331
  sample.elapsedSec = 0;
37243
38332
  sample.frameTimeMsTotal = 0;
@@ -37764,6 +38853,13 @@ function computeSceneObjectBounds(obj, objectMatrices) {
37764
38853
  }
37765
38854
  return isFiniteBox3(out) ? out : null;
37766
38855
  }
38856
+ if (obj.sdf) {
38857
+ const out = new Box3();
38858
+ if (!expandBoundsByTransformedAabb(out, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) {
38859
+ return null;
38860
+ }
38861
+ return isFiniteBox3(out) ? out : null;
38862
+ }
37767
38863
  if (obj.curve3d) {
37768
38864
  const b2 = obj.curve3d.bounds;
37769
38865
  const out = new Box3();
@@ -37776,12 +38872,6 @@ function computeSceneObjectBounds(obj, objectMatrices) {
37776
38872
  if (!expandBoundsByTransformedAabb(out, b2.min, b2.max, matrix)) return null;
37777
38873
  return isFiniteBox3(out) ? out : null;
37778
38874
  }
37779
- if (obj.sdf) {
37780
- const b2 = obj.sdf.bounds;
37781
- const out = new Box3();
37782
- if (!expandBoundsByTransformedAabb(out, b2.min, b2.max, matrix)) return null;
37783
- return isFiniteBox3(out) ? out : null;
37784
- }
37785
38875
  return null;
37786
38876
  }
37787
38877
  function computeSceneBounds(objects, objectMatrices) {
@@ -38221,290 +39311,6 @@ function SectionCaps({
38221
39311
  );
38222
39312
  }) });
38223
39313
  }
38224
- const DEFAULT_COLOR = "#5b9bd5";
38225
- const BLACK = [0, 0, 0];
38226
- const MAX_SDF_CLIP_PLANES = 8;
38227
- const PICK_MAX_STEPS = 220;
38228
- const PICK_MIN_STEP = 0.012;
38229
- const PICK_HIT_EPS = 0.05;
38230
- function colorToRgb(value, fallback = DEFAULT_COLOR) {
38231
- const color2 = new Color(value || fallback);
38232
- return [color2.r, color2.g, color2.b];
38233
- }
38234
- function clamp01(value) {
38235
- return Math.min(1, Math.max(0, value));
38236
- }
38237
- function computeBounds(leaves) {
38238
- if (leaves.length === 0) return null;
38239
- const min = [Infinity, Infinity, Infinity];
38240
- const max2 = [-Infinity, -Infinity, -Infinity];
38241
- for (const leaf of leaves) {
38242
- for (let i = 0; i < 3; i++) {
38243
- min[i] = Math.min(min[i], leaf.bounds.min[i]);
38244
- max2[i] = Math.max(max2[i], leaf.bounds.max[i]);
38245
- }
38246
- }
38247
- if (!min.every(Number.isFinite) || !max2.every(Number.isFinite)) return null;
38248
- const diagonal = Math.hypot(max2[0] - min[0], max2[1] - min[1], max2[2] - min[2]);
38249
- const pad = Math.max(1, diagonal * 0.04);
38250
- return {
38251
- min: [min[0] - pad, min[1] - pad, min[2] - pad],
38252
- max: [max2[0] + pad, max2[1] + pad, max2[2] + pad]
38253
- };
38254
- }
38255
- function setClipPlaneUniforms(target, clippingPlanes) {
38256
- const count = Math.min(clippingPlanes.length, MAX_SDF_CLIP_PLANES);
38257
- for (let i = 0; i < MAX_SDF_CLIP_PLANES; i++) {
38258
- const plane = clippingPlanes[i];
38259
- target[i].set((plane == null ? void 0 : plane.normal.x) ?? 0, (plane == null ? void 0 : plane.normal.y) ?? 0, (plane == null ? void 0 : plane.normal.z) ?? 0, (plane == null ? void 0 : plane.constant) ?? 0);
38260
- }
38261
- return count;
38262
- }
38263
- function clipPlaneDistance(point, clippingPlanes) {
38264
- let distance = -Infinity;
38265
- const count = Math.min(clippingPlanes.length, MAX_SDF_CLIP_PLANES);
38266
- for (let i = 0; i < count; i++) {
38267
- const plane = clippingPlanes[i];
38268
- distance = Math.max(distance, -plane.distanceToPoint(point));
38269
- }
38270
- return distance;
38271
- }
38272
- function clippedDistance(fn, point, clippingPlanes) {
38273
- return Math.max(fn(point.x, point.y, point.z), clipPlaneDistance(point, clippingPlanes));
38274
- }
38275
- function intersectRayBoxRange(ray, box2) {
38276
- let tNear = -Infinity;
38277
- let tFar = Infinity;
38278
- const origin = ray.origin;
38279
- const direction = ray.direction;
38280
- for (const axis of ["x", "y", "z"]) {
38281
- const o2 = origin[axis];
38282
- const d = direction[axis];
38283
- const min = box2.min[axis];
38284
- const max2 = box2.max[axis];
38285
- if (Math.abs(d) < 1e-10) {
38286
- if (o2 < min || o2 > max2) return null;
38287
- continue;
38288
- }
38289
- const inv = 1 / d;
38290
- let t02 = (min - o2) * inv;
38291
- let t1 = (max2 - o2) * inv;
38292
- if (t02 > t1) [t02, t1] = [t1, t02];
38293
- tNear = Math.max(tNear, t02);
38294
- tFar = Math.min(tFar, t1);
38295
- if (tFar < tNear) return null;
38296
- }
38297
- if (tFar < 0) return null;
38298
- return { near: Math.max(0, tNear), far: tFar };
38299
- }
38300
- function raymarchHit(ray, box2, diagonal, sdfFn, clippingPlanes) {
38301
- const range = intersectRayBoxRange(ray, box2);
38302
- if (!range) return null;
38303
- const maxStep = Math.max(PICK_MIN_STEP, diagonal / 180);
38304
- const point = new Vector3();
38305
- let t2 = range.near;
38306
- let prevT = t2;
38307
- let prevDist = Number.POSITIVE_INFINITY;
38308
- let bestT = t2;
38309
- let bestAbsDist = Number.POSITIVE_INFINITY;
38310
- for (let i = 0; i < PICK_MAX_STEPS; i++) {
38311
- point.copy(ray.origin).addScaledVector(ray.direction, t2);
38312
- const dist = clippedDistance(sdfFn, point, clippingPlanes);
38313
- const absDist = Math.abs(dist);
38314
- if (absDist < bestAbsDist) {
38315
- bestAbsDist = absDist;
38316
- bestT = t2;
38317
- }
38318
- if (absDist < PICK_HIT_EPS) return point;
38319
- if (Number.isFinite(prevDist) && (prevDist < 0 && dist > 0 || prevDist > 0 && dist < 0)) {
38320
- let lo = prevT;
38321
- let hi = t2;
38322
- const midPoint = point.clone();
38323
- let dLo = prevDist;
38324
- for (let step = 0; step < 8; step++) {
38325
- const mid = (lo + hi) * 0.5;
38326
- midPoint.copy(ray.origin).addScaledVector(ray.direction, mid);
38327
- const dMid = clippedDistance(sdfFn, midPoint, clippingPlanes);
38328
- if (Math.abs(dMid) < PICK_HIT_EPS) return midPoint;
38329
- if (dLo < 0 && dMid > 0 || dLo > 0 && dMid < 0) {
38330
- hi = mid;
38331
- } else {
38332
- lo = mid;
38333
- dLo = dMid;
38334
- }
38335
- }
38336
- return midPoint;
38337
- }
38338
- prevT = t2;
38339
- prevDist = dist;
38340
- t2 += MathUtils.clamp(absDist * 0.5, PICK_MIN_STEP, maxStep);
38341
- if (t2 > range.far) break;
38342
- }
38343
- return bestAbsDist < PICK_HIT_EPS * 2 ? ray.origin.clone().addScaledVector(ray.direction, bestT) : null;
38344
- }
38345
- function SdfRaymarchObject({
38346
- obj,
38347
- settings,
38348
- clippingPlanes,
38349
- isHovered,
38350
- onPointerEnter,
38351
- onPointerMove,
38352
- onPointerLeave,
38353
- onClick,
38354
- onDoubleClick,
38355
- onContextMenu
38356
- }) {
38357
- const meshRef = reactExports.useRef(null);
38358
- const sdf = obj.sdf;
38359
- const bounds = reactExports.useMemo(() => sdf ? computeBounds([sdf]) : null, [sdf]);
38360
- const boundsBox = reactExports.useMemo(
38361
- () => bounds ? new Box3(new Vector3(...bounds.min), new Vector3(...bounds.max)) : null,
38362
- [bounds]
38363
- );
38364
- const boundsDiagonal = reactExports.useMemo(() => boundsBox ? boundsBox.getSize(new Vector3()).length() : 0, [boundsBox]);
38365
- const sdfFn = reactExports.useMemo(() => sdf ? compileSdfNode3(sdf.node) : null, [sdf]);
38366
- const color2 = reactExports.useMemo(() => colorToRgb((settings == null ? void 0 : settings.color) ?? (sdf == null ? void 0 : sdf.colorHex)), [sdf == null ? void 0 : sdf.colorHex, settings == null ? void 0 : settings.color]);
38367
- const leaf = reactExports.useMemo(() => {
38368
- var _a3, _b2, _c, _d, _e, _f, _g, _h, _i;
38369
- if (!sdf) return null;
38370
- const materialAlpha = ((_a3 = sdf.materialProps) == null ? void 0 : _a3.opacity) ?? 1;
38371
- return {
38372
- node: sdf.node,
38373
- color: color2,
38374
- alpha: clamp01(Math.min((settings == null ? void 0 : settings.opacity) ?? 1, materialAlpha)),
38375
- emissive: ((_b2 = sdf.materialProps) == null ? void 0 : _b2.emissive) ? colorToRgb(sdf.materialProps.emissive, "#000000") : BLACK,
38376
- emissiveIntensity: ((_c = sdf.materialProps) == null ? void 0 : _c.emissiveIntensity) ?? 0,
38377
- metalness: clamp01(((_d = sdf.materialProps) == null ? void 0 : _d.metalness) ?? 0.05),
38378
- roughness: clamp01(((_e = sdf.materialProps) == null ? void 0 : _e.roughness) ?? 0.35),
38379
- clearcoat: clamp01(((_f = sdf.materialProps) == null ? void 0 : _f.clearcoat) ?? 0.1),
38380
- clearcoatRoughness: clamp01(((_g = sdf.materialProps) == null ? void 0 : _g.clearcoatRoughness) ?? 0.4),
38381
- transmission: clamp01(((_h = sdf.materialProps) == null ? void 0 : _h.transmission) ?? 0),
38382
- reflectivity: clamp01(((_i = sdf.materialProps) == null ? void 0 : _i.reflectivity) ?? 0.5)
38383
- };
38384
- }, [color2, sdf, settings == null ? void 0 : settings.opacity]);
38385
- const isTransparent = ((leaf == null ? void 0 : leaf.alpha) ?? 1) < 1 || ((leaf == null ? void 0 : leaf.transmission) ?? 0) > 0;
38386
- const fragmentShader2 = reactExports.useMemo(() => leaf ? buildSdfRaymarchFragmentShader([leaf]) : null, [leaf]);
38387
- const clipPlaneUniforms = reactExports.useMemo(() => Array.from({ length: MAX_SDF_CLIP_PLANES }, () => new Vector4()), []);
38388
- const material = reactExports.useMemo(() => {
38389
- if (!fragmentShader2) return null;
38390
- return new ShaderMaterial({
38391
- vertexShader: SDF_RAYMARCH_PROXY_VERTEX_SHADER,
38392
- fragmentShader: fragmentShader2,
38393
- side: BackSide,
38394
- transparent: isTransparent,
38395
- depthTest: true,
38396
- // SDF previews render a proxy box, but the shader writes the actual
38397
- // raymarched hit depth. Keep depth writes on for transparent SDFs so
38398
- // overlapping leaves sort by their real surface depth instead of by the
38399
- // proxy box centers used by Three's transparent object sorter.
38400
- depthWrite: true,
38401
- uniforms: {
38402
- uCameraMatrixWorld: { value: new Matrix4() },
38403
- uViewProjectionMatrix: { value: new Matrix4() },
38404
- uSceneMin: { value: new Vector3() },
38405
- uSceneMax: { value: new Vector3() },
38406
- uClipPlaneCount: { value: 0 },
38407
- uClipPlanes: { value: clipPlaneUniforms },
38408
- uHoverLeafIndex: { value: -1 },
38409
- uHoverColor: { value: new Vector3() },
38410
- uHoverIntensity: { value: 0 },
38411
- uIsOrthographic: { value: 0 }
38412
- }
38413
- });
38414
- }, [clipPlaneUniforms, fragmentShader2, isTransparent]);
38415
- reactExports.useEffect(() => () => material == null ? void 0 : material.dispose(), [material]);
38416
- useFrame(({ camera }) => {
38417
- if (!bounds || !material) return;
38418
- material.uniforms.uCameraMatrixWorld.value.copy(camera.matrixWorld);
38419
- material.uniforms.uViewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
38420
- material.uniforms.uSceneMin.value.set(bounds.min[0], bounds.min[1], bounds.min[2]);
38421
- material.uniforms.uSceneMax.value.set(bounds.max[0], bounds.max[1], bounds.max[2]);
38422
- material.uniforms.uClipPlaneCount.value = setClipPlaneUniforms(clipPlaneUniforms, clippingPlanes);
38423
- material.uniforms.uHoverLeafIndex.value = isHovered ? 0 : -1;
38424
- material.uniforms.uHoverColor.value.set(color2[0], color2[1], color2[2]);
38425
- material.uniforms.uHoverIntensity.value = isHovered ? 0.3 : 0;
38426
- material.uniforms.uIsOrthographic.value = camera.isOrthographicCamera ? 1 : 0;
38427
- });
38428
- const raycast = reactExports.useCallback(
38429
- (raycaster, intersections) => {
38430
- const object = meshRef.current;
38431
- if (!object || !boundsBox || !sdfFn) return;
38432
- const point = raymarchHit(raycaster.ray, boundsBox, boundsDiagonal, sdfFn, clippingPlanes);
38433
- if (!point) return;
38434
- const distance = raycaster.ray.origin.distanceTo(point);
38435
- if (distance < raycaster.near || distance > raycaster.far) return;
38436
- intersections.push({ distance, point, object });
38437
- },
38438
- [boundsBox, boundsDiagonal, clippingPlanes, sdfFn]
38439
- );
38440
- if (!bounds || !material || !sdfFn) return null;
38441
- const size = [
38442
- Math.max(bounds.max[0] - bounds.min[0], 1e-3),
38443
- Math.max(bounds.max[1] - bounds.min[1], 1e-3),
38444
- Math.max(bounds.max[2] - bounds.min[2], 1e-3)
38445
- ];
38446
- const center = [
38447
- (bounds.min[0] + bounds.max[0]) * 0.5,
38448
- (bounds.min[1] + bounds.max[1]) * 0.5,
38449
- (bounds.min[2] + bounds.max[2]) * 0.5
38450
- ];
38451
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
38452
- "mesh",
38453
- {
38454
- ref: meshRef,
38455
- position: center,
38456
- frustumCulled: false,
38457
- raycast,
38458
- onPointerEnter,
38459
- onPointerMove,
38460
- onPointerLeave,
38461
- onClick,
38462
- onDoubleClick,
38463
- onContextMenu,
38464
- children: [
38465
- /* @__PURE__ */ jsxRuntimeExports.jsx("boxGeometry", { args: size }),
38466
- /* @__PURE__ */ jsxRuntimeExports.jsx("primitive", { object: material, attach: "material" })
38467
- ]
38468
- }
38469
- );
38470
- }
38471
- function SdfRaymarchLayer({
38472
- objects,
38473
- objectSettings,
38474
- clippingPlanesById,
38475
- hoveredObjectId,
38476
- onPointerEnter,
38477
- onPointerMove,
38478
- onPointerLeave,
38479
- onClick,
38480
- onDoubleClick,
38481
- onContextMenu
38482
- }) {
38483
- const visibleSdfObjects = reactExports.useMemo(
38484
- () => objects.filter((obj) => {
38485
- var _a3;
38486
- return obj.sdf && (((_a3 = objectSettings[obj.id]) == null ? void 0 : _a3.visible) ?? true);
38487
- }),
38488
- [objectSettings, objects]
38489
- );
38490
- if (visibleSdfObjects.length === 0) return null;
38491
- return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { children: visibleSdfObjects.map((obj) => /* @__PURE__ */ jsxRuntimeExports.jsx(
38492
- SdfRaymarchObject,
38493
- {
38494
- obj,
38495
- settings: objectSettings[obj.id],
38496
- clippingPlanes: clippingPlanesById[obj.id] ?? EMPTY_CLIPPING_PLANES,
38497
- isHovered: hoveredObjectId === obj.id,
38498
- onPointerEnter: onPointerEnter ? (event) => onPointerEnter(obj, event) : void 0,
38499
- onPointerMove: onPointerMove ? (event) => onPointerMove(obj, event) : void 0,
38500
- onPointerLeave: onPointerLeave ? (event) => onPointerLeave(obj, event) : void 0,
38501
- onClick: onClick ? (event) => onClick(obj, event) : void 0,
38502
- onDoubleClick: onDoubleClick ? (event) => onDoubleClick(obj, event) : void 0,
38503
- onContextMenu: onContextMenu ? (event) => onContextMenu(obj, event) : void 0
38504
- },
38505
- obj.id
38506
- )) });
38507
- }
38508
39314
  function findHoveredSurface(x, y, meta) {
38509
39315
  for (let i = meta.surfaces.length - 1; i >= 0; i--) {
38510
39316
  const s = meta.surfaces[i];
@@ -40846,6 +41652,37 @@ function ZoomIndicatorPanel({ mmPerPx }) {
40846
41652
  }
40847
41653
  );
40848
41654
  }
41655
+ function roundDebugNumber(value) {
41656
+ return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : value;
41657
+ }
41658
+ function vectorTuple(vector) {
41659
+ return [roundDebugNumber(vector.x), roundDebugNumber(vector.y), roundDebugNumber(vector.z)];
41660
+ }
41661
+ function snapshotViewportCamera(camera, target) {
41662
+ const ortho = camera;
41663
+ const perspective = camera;
41664
+ const isOrtho = ortho.isOrthographicCamera === true;
41665
+ return {
41666
+ position: vectorTuple(camera.position),
41667
+ target: target ? vectorTuple(target) : null,
41668
+ up: vectorTuple(camera.up),
41669
+ projection: isOrtho ? "orthographic" : "perspective",
41670
+ fov: isOrtho ? void 0 : roundDebugNumber(perspective.fov),
41671
+ zoom: isOrtho ? roundDebugNumber(ortho.zoom) : void 0
41672
+ };
41673
+ }
41674
+ function recordViewportCameraDebug(entry) {
41675
+ if (typeof window === "undefined") return;
41676
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
41677
+ window.__forgecadCameraDebug = [...window.__forgecadCameraDebug ?? [], entry].slice(-50);
41678
+ console.debug("[forgecad camera]", entry);
41679
+ }
41680
+ function recordViewportCameraLifecycleDebug(entry) {
41681
+ if (typeof window === "undefined") return;
41682
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
41683
+ window.__forgecadCameraLifecycleDebug = [...window.__forgecadCameraLifecycleDebug ?? [], entry].slice(-50);
41684
+ console.debug("[forgecad view-controller]", entry);
41685
+ }
40849
41686
  const readPersistedViewportCameraState = () => {
40850
41687
  if (typeof window === "undefined") return null;
40851
41688
  try {
@@ -40869,19 +41706,117 @@ const resolveHoverObjectName = (name, knownFileNames) => {
40869
41706
  if (knownFileNames.has(trimmed)) return null;
40870
41707
  return trimmed;
40871
41708
  };
41709
+ const SNAP_VIEW_DIRECTIONS = {
41710
+ front: new Vector3(0, -1, 0),
41711
+ back: new Vector3(0, 1, 0),
41712
+ right: new Vector3(1, 0, 0),
41713
+ left: new Vector3(-1, 0, 0),
41714
+ top: new Vector3(0, 0, 1),
41715
+ bottom: new Vector3(0, 0, -1),
41716
+ iso: new Vector3(1, -1, 1)
41717
+ };
41718
+ const DEFAULT_CAMERA_UP = new Vector3(0, 0, 1);
41719
+ const SNAP_VIEW_UP = {
41720
+ top: new Vector3(0, 1, 0),
41721
+ bottom: new Vector3(0, -1, 0)
41722
+ };
41723
+ function isFiniteVector$1(vector) {
41724
+ return Number.isFinite(vector.x) && Number.isFinite(vector.y) && Number.isFinite(vector.z);
41725
+ }
41726
+ function computeBoundsFraming(bounds) {
41727
+ if (bounds.isEmpty()) return null;
41728
+ const target = new Vector3();
41729
+ bounds.getCenter(target);
41730
+ const size = new Vector3();
41731
+ bounds.getSize(size);
41732
+ if (!isFiniteVector$1(target) || !isFiniteVector$1(size)) return null;
41733
+ const reach = Math.max(size.x, size.y, size.z, 1);
41734
+ if (!Number.isFinite(reach) || reach <= 0) return null;
41735
+ return { target, size, reach };
41736
+ }
41737
+ function snapViewDirection(view2) {
41738
+ return SNAP_VIEW_DIRECTIONS[view2 ?? "iso"].clone().normalize();
41739
+ }
41740
+ function snapViewUp(view2) {
41741
+ return (SNAP_VIEW_UP[view2 ?? "iso"] ?? DEFAULT_CAMERA_UP).clone();
41742
+ }
41743
+ function computeSnapOrbitPose({
41744
+ position,
41745
+ target,
41746
+ up,
41747
+ view: view2
41748
+ }) {
41749
+ if (!isFiniteVector$1(position) || !isFiniteVector$1(target) || !isFiniteVector$1(up)) return null;
41750
+ const distance = position.distanceTo(target);
41751
+ if (!Number.isFinite(distance) || distance <= 1e-6) return null;
41752
+ const nextPosition = target.clone().add(snapViewDirection(view2).multiplyScalar(distance));
41753
+ if (!isFiniteVector$1(nextPosition)) return null;
41754
+ return {
41755
+ position: nextPosition,
41756
+ target: target.clone(),
41757
+ up: up.clone()
41758
+ };
41759
+ }
41760
+ function ActiveCameraBridge({ onCameraChange }) {
41761
+ const camera = useThree((state2) => state2.camera);
41762
+ reactExports.useEffect(() => {
41763
+ onCameraChange(camera);
41764
+ recordViewportCameraLifecycleDebug({
41765
+ path: "active-camera",
41766
+ snapshot: snapshotViewportCamera(camera, null)
41767
+ });
41768
+ }, [camera, onCameraChange]);
41769
+ reactExports.useEffect(() => {
41770
+ return () => {
41771
+ onCameraChange(null);
41772
+ recordViewportCameraLifecycleDebug({ path: "active-camera-cleared" });
41773
+ };
41774
+ }, [onCameraChange]);
41775
+ return null;
41776
+ }
40872
41777
  function ViewController({
41778
+ camera,
40873
41779
  controlsRef,
40874
- command,
41780
+ viewportRef,
40875
41781
  objects,
40876
41782
  objectMatrices,
40877
41783
  fallbackBounds,
40878
41784
  settings,
40879
- focusedObjectIds,
40880
- clearCommand
41785
+ focusedObjectIds
40881
41786
  }) {
40882
- const { camera, size } = useThree();
41787
+ const command = useForgeStore((s) => s.viewCommand);
41788
+ const clearCommand = useForgeStore((s) => s.clearViewCommand);
41789
+ const getActiveCamera = reactExports.useCallback(() => {
41790
+ var _a3;
41791
+ return camera ?? ((_a3 = controlsRef.current) == null ? void 0 : _a3.object) ?? null;
41792
+ }, [camera, controlsRef]);
41793
+ reactExports.useEffect(() => {
41794
+ recordViewportCameraLifecycleDebug({ path: "mounted" });
41795
+ return () => {
41796
+ recordViewportCameraLifecycleDebug({ path: "unmounted" });
41797
+ };
41798
+ }, []);
40883
41799
  reactExports.useEffect(() => {
41800
+ var _a3;
40884
41801
  if (!command) return;
41802
+ const activeCamera = getActiveCamera();
41803
+ if (!activeCamera) {
41804
+ recordViewportCameraLifecycleDebug({ path: "command-no-camera" });
41805
+ return;
41806
+ }
41807
+ recordViewportCameraLifecycleDebug({
41808
+ path: "command-change",
41809
+ snapshot: snapshotViewportCamera(activeCamera, ((_a3 = controlsRef.current) == null ? void 0 : _a3.target) ?? null)
41810
+ });
41811
+ }, [command, controlsRef, getActiveCamera]);
41812
+ reactExports.useEffect(() => {
41813
+ var _a3;
41814
+ if (!command) return;
41815
+ const camera2 = getActiveCamera();
41816
+ if (!camera2) {
41817
+ recordViewportCameraLifecycleDebug({ path: "command-no-camera-handler" });
41818
+ return;
41819
+ }
40885
41820
  if (command.type === "sceneCamera") return;
40886
41821
  if (command.type === "camera") {
40887
41822
  const state2 = command.camera;
@@ -40889,17 +41824,17 @@ function ViewController({
40889
41824
  clearCommand();
40890
41825
  return;
40891
41826
  }
40892
- camera.position.set(state2.position[0], state2.position[1], state2.position[2]);
40893
- camera.up.set(state2.up[0], state2.up[1], state2.up[2]);
40894
- if ("fov" in camera && state2.fov !== void 0) {
40895
- camera.fov = state2.fov;
41827
+ camera2.position.set(state2.position[0], state2.position[1], state2.position[2]);
41828
+ camera2.up.set(state2.up[0], state2.up[1], state2.up[2]);
41829
+ if ("fov" in camera2 && state2.fov !== void 0) {
41830
+ camera2.fov = state2.fov;
40896
41831
  }
40897
- if (camera.isOrthographicCamera && state2.orthoZoom !== void 0) {
40898
- camera.zoom = state2.orthoZoom;
41832
+ if (camera2.isOrthographicCamera && state2.orthoZoom !== void 0) {
41833
+ camera2.zoom = state2.orthoZoom;
40899
41834
  }
40900
- camera.lookAt(state2.target[0], state2.target[1], state2.target[2]);
40901
- if ("updateProjectionMatrix" in camera) {
40902
- camera.updateProjectionMatrix();
41835
+ camera2.lookAt(state2.target[0], state2.target[1], state2.target[2]);
41836
+ if ("updateProjectionMatrix" in camera2) {
41837
+ camera2.updateProjectionMatrix();
40903
41838
  }
40904
41839
  const controls2 = controlsRef.current;
40905
41840
  if (controls2) {
@@ -40909,9 +41844,43 @@ function ViewController({
40909
41844
  clearCommand();
40910
41845
  return;
40911
41846
  }
41847
+ const controls = controlsRef.current;
41848
+ const commandSnapshot = snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null);
41849
+ recordViewportCameraDebug({
41850
+ path: "received",
41851
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41852
+ before: commandSnapshot,
41853
+ after: commandSnapshot
41854
+ });
41855
+ if (command.type === "snap" && controls) {
41856
+ const before2 = commandSnapshot;
41857
+ const snapPose = computeSnapOrbitPose({
41858
+ position: camera2.position,
41859
+ target: controls.target,
41860
+ up: camera2.up,
41861
+ view: command.view
41862
+ });
41863
+ if (snapPose) {
41864
+ camera2.position.copy(snapPose.position);
41865
+ camera2.up.copy(snapPose.up);
41866
+ if ("updateProjectionMatrix" in camera2) {
41867
+ camera2.updateProjectionMatrix();
41868
+ }
41869
+ controls.target.copy(snapPose.target);
41870
+ controls.update();
41871
+ recordViewportCameraDebug({
41872
+ path: "snap-orbit",
41873
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41874
+ before: before2,
41875
+ after: snapshotViewportCamera(camera2, controls.target)
41876
+ });
41877
+ clearCommand();
41878
+ return;
41879
+ }
41880
+ }
40912
41881
  const visibleObjects = objects.filter((obj) => {
40913
- var _a3;
40914
- return ((_a3 = settings[obj.id]) == null ? void 0 : _a3.visible) !== false;
41882
+ var _a4;
41883
+ return ((_a4 = settings[obj.id]) == null ? void 0 : _a4.visible) !== false;
40915
41884
  });
40916
41885
  const focusedIdSet = new Set(focusedObjectIds);
40917
41886
  const focusedVisibleObjects = focusedIdSet.size > 0 ? visibleObjects.filter((obj) => focusedIdSet.has(obj.id)) : [];
@@ -40920,46 +41889,34 @@ function ViewController({
40920
41889
  const canUseFallbackBounds = !command.targetId && !useFocusedScope;
40921
41890
  const bounds = computeSceneBounds(targetObjects, objectMatrices) ?? (canUseFallbackBounds ? fallbackBounds : null);
40922
41891
  if (!bounds) {
41892
+ recordViewportCameraDebug({
41893
+ path: "no-bounds",
41894
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41895
+ before: commandSnapshot,
41896
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41897
+ });
40923
41898
  clearCommand();
40924
41899
  return;
40925
41900
  }
40926
- const center = new Vector3();
40927
- bounds.getCenter(center);
40928
- const sizeVec = new Vector3();
40929
- bounds.getSize(sizeVec);
40930
- if (!Number.isFinite(center.x) || !Number.isFinite(center.y) || !Number.isFinite(center.z) || !Number.isFinite(sizeVec.x) || !Number.isFinite(sizeVec.y) || !Number.isFinite(sizeVec.z)) {
40931
- clearCommand();
40932
- return;
40933
- }
40934
- const maxDim = Math.max(sizeVec.x, sizeVec.y, sizeVec.z, 1);
40935
- const snapUsesScopedCenter = command.type === "snap" && useFocusedScope;
40936
- const target = command.type === "snap" && !snapUsesScopedCenter ? new Vector3(0, 0, 0) : center;
40937
- const maxReach = command.type === "snap" && !snapUsesScopedCenter ? Math.max(sizeVec.x / 2 + Math.abs(center.x), sizeVec.y / 2 + Math.abs(center.y), sizeVec.z / 2 + Math.abs(center.z)) * 2 : maxDim;
40938
- if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z) || !Number.isFinite(maxReach) || maxReach <= 0) {
41901
+ const framing = computeBoundsFraming(bounds);
41902
+ if (!framing) {
41903
+ recordViewportCameraDebug({
41904
+ path: "invalid-bounds",
41905
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41906
+ before: commandSnapshot,
41907
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41908
+ });
40939
41909
  clearCommand();
40940
41910
  return;
40941
41911
  }
40942
- const controls = controlsRef.current;
41912
+ const { target, reach: maxReach } = framing;
41913
+ const before = snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null);
40943
41914
  const camDir = new Vector3();
40944
41915
  if (command.type === "snap") {
40945
- const viewMap = {
40946
- front: new Vector3(0, -1, 0),
40947
- back: new Vector3(0, 1, 0),
40948
- right: new Vector3(1, 0, 0),
40949
- left: new Vector3(-1, 0, 0),
40950
- top: new Vector3(0, 0, 1),
40951
- bottom: new Vector3(0, 0, -1),
40952
- iso: new Vector3(1, -1, 1)
40953
- };
40954
- const upMap = {
40955
- top: new Vector3(0, 1, 0),
40956
- bottom: new Vector3(0, -1, 0)
40957
- };
40958
- camDir.copy(viewMap[command.view ?? "iso"]).normalize();
40959
- const up = upMap[command.view ?? ""] ?? new Vector3(0, 0, 1);
40960
- camera.up.copy(up);
41916
+ camDir.copy(snapViewDirection(command.view));
41917
+ camera2.up.copy(snapViewUp(command.view));
40961
41918
  } else if (controls) {
40962
- camDir.subVectors(camera.position, controls.target).normalize();
41919
+ camDir.subVectors(camera2.position, controls.target).normalize();
40963
41920
  if (camDir.lengthSq() === 0) camDir.set(1, 1, 1).normalize();
40964
41921
  } else {
40965
41922
  camDir.set(1, 1, 1).normalize();
@@ -40968,10 +41925,13 @@ function ViewController({
40968
41925
  clearCommand();
40969
41926
  return;
40970
41927
  }
40971
- const isOrtho = camera.isOrthographicCamera;
41928
+ const isOrtho = camera2.isOrthographicCamera;
41929
+ const viewportRect = (_a3 = viewportRef.current) == null ? void 0 : _a3.getBoundingClientRect();
41930
+ const viewportWidth = (viewportRect == null ? void 0 : viewportRect.width) ?? 800;
41931
+ const viewportHeight = (viewportRect == null ? void 0 : viewportRect.height) ?? 600;
40972
41932
  if (isOrtho) {
40973
- const ortho = camera;
40974
- const zoom = Math.min(size.width, size.height) / maxReach / 2.2;
41933
+ const ortho = camera2;
41934
+ const zoom = Math.min(viewportWidth, viewportHeight) / maxReach / 2.2;
40975
41935
  if (!Number.isFinite(zoom) || zoom <= 0) {
40976
41936
  clearCommand();
40977
41937
  return;
@@ -40985,7 +41945,7 @@ function ViewController({
40985
41945
  ortho.position.copy(nextPosition);
40986
41946
  ortho.updateProjectionMatrix();
40987
41947
  } else {
40988
- const persp = camera;
41948
+ const persp = camera2;
40989
41949
  const dist = maxReach / (2 * Math.tan(persp.fov * Math.PI / 360)) * 1.4;
40990
41950
  if (!Number.isFinite(dist) || dist <= 0) {
40991
41951
  clearCommand();
@@ -41003,21 +41963,26 @@ function ViewController({
41003
41963
  controls.target.copy(target);
41004
41964
  controls.update();
41005
41965
  } else {
41006
- camera.lookAt(target);
41966
+ camera2.lookAt(target);
41007
41967
  }
41968
+ recordViewportCameraDebug({
41969
+ path: command.type === "snap" ? "snap-framing-fallback" : "framing",
41970
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41971
+ before,
41972
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41973
+ });
41008
41974
  clearCommand();
41009
41975
  }, [
41010
- camera,
41011
41976
  clearCommand,
41012
41977
  command,
41013
41978
  controlsRef,
41014
41979
  fallbackBounds,
41015
41980
  focusedObjectIds,
41981
+ getActiveCamera,
41016
41982
  objectMatrices,
41017
41983
  objects,
41018
41984
  settings,
41019
- size.height,
41020
- size.width
41985
+ viewportRef
41021
41986
  ]);
41022
41987
  return null;
41023
41988
  }
@@ -41151,6 +42116,293 @@ function ViewPersistence({
41151
42116
  }, [camera, controlsRef, isSketchOnly, projectionMode, setViewportCameraState]);
41152
42117
  return null;
41153
42118
  }
42119
+ const CUBE_SIZE = 116;
42120
+ const CUBE_CENTER = CUBE_SIZE / 2;
42121
+ const CUBE_SCALE = 26;
42122
+ const DEFAULT_CAMERA_STATE = {
42123
+ position: [120, 80, 120],
42124
+ target: [0, 0, 0],
42125
+ up: [0, 0, 1]
42126
+ };
42127
+ const FACE_DEFS = [
42128
+ {
42129
+ view: "right",
42130
+ label: "RIGHT",
42131
+ normal: new Vector3(1, 0, 0),
42132
+ corners: [new Vector3(1, -1, -1), new Vector3(1, 1, -1), new Vector3(1, 1, 1), new Vector3(1, -1, 1)]
42133
+ },
42134
+ {
42135
+ view: "left",
42136
+ label: "LEFT",
42137
+ normal: new Vector3(-1, 0, 0),
42138
+ corners: [new Vector3(-1, 1, -1), new Vector3(-1, -1, -1), new Vector3(-1, -1, 1), new Vector3(-1, 1, 1)]
42139
+ },
42140
+ {
42141
+ view: "back",
42142
+ label: "BACK",
42143
+ normal: new Vector3(0, 1, 0),
42144
+ corners: [new Vector3(1, 1, -1), new Vector3(-1, 1, -1), new Vector3(-1, 1, 1), new Vector3(1, 1, 1)]
42145
+ },
42146
+ {
42147
+ view: "front",
42148
+ label: "FRONT",
42149
+ normal: new Vector3(0, -1, 0),
42150
+ corners: [new Vector3(-1, -1, -1), new Vector3(1, -1, -1), new Vector3(1, -1, 1), new Vector3(-1, -1, 1)]
42151
+ },
42152
+ {
42153
+ view: "top",
42154
+ label: "TOP",
42155
+ normal: new Vector3(0, 0, 1),
42156
+ corners: [new Vector3(-1, -1, 1), new Vector3(1, -1, 1), new Vector3(1, 1, 1), new Vector3(-1, 1, 1)]
42157
+ },
42158
+ {
42159
+ view: "bottom",
42160
+ label: "BOTTOM",
42161
+ normal: new Vector3(0, 0, -1),
42162
+ corners: [new Vector3(-1, 1, -1), new Vector3(1, 1, -1), new Vector3(1, -1, -1), new Vector3(-1, -1, -1)]
42163
+ }
42164
+ ];
42165
+ const SNAP_VIEWS = /* @__PURE__ */ new Set(["front", "back", "left", "right", "top", "bottom", "iso"]);
42166
+ function vectorFromTuple(tuple) {
42167
+ return new Vector3(tuple[0], tuple[1], tuple[2]);
42168
+ }
42169
+ function normalizedOr(vector, fallback) {
42170
+ return Number.isFinite(vector.x) && Number.isFinite(vector.y) && Number.isFinite(vector.z) && vector.lengthSq() > 1e-10 ? vector.clone().normalize() : fallback.clone().normalize();
42171
+ }
42172
+ function buildViewBasisFromCameraState(cameraState) {
42173
+ const position = vectorFromTuple(cameraState.position);
42174
+ const target = vectorFromTuple(cameraState.target);
42175
+ const up = normalizedOr(vectorFromTuple(cameraState.up), new Vector3(0, 0, 1));
42176
+ const viewer = normalizedOr(position.clone().sub(target), new Vector3(1, -1, 1));
42177
+ const forward = viewer.clone().negate();
42178
+ let right = forward.clone().cross(up);
42179
+ if (right.lengthSq() <= 1e-10) {
42180
+ right = Math.abs(forward.z) > 0.9 ? new Vector3(1, 0, 0) : forward.clone().cross(new Vector3(0, 0, 1));
42181
+ }
42182
+ right.normalize();
42183
+ const screenUp = normalizedOr(right.clone().cross(forward), new Vector3(0, 0, 1));
42184
+ return { viewer, right, screenUp };
42185
+ }
42186
+ const DEFAULT_VIEW_BASIS = buildViewBasisFromCameraState(DEFAULT_CAMERA_STATE);
42187
+ function buildViewBasisFromLiveCamera(camera) {
42188
+ camera.updateMatrixWorld();
42189
+ const quaternion = camera.getWorldQuaternion(new Quaternion());
42190
+ const viewer = normalizedOr(new Vector3(0, 0, 1).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.viewer);
42191
+ const right = normalizedOr(new Vector3(1, 0, 0).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.right);
42192
+ const screenUp = normalizedOr(new Vector3(0, 1, 0).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.screenUp);
42193
+ return { viewer, right, screenUp };
42194
+ }
42195
+ function faceFill(brightness) {
42196
+ const litPercent = Math.round(42 + brightness * 46);
42197
+ return `color-mix(in srgb, var(--fc-view-cube-face-lit) ${litPercent}%, var(--fc-view-cube-face-dim))`;
42198
+ }
42199
+ function projectVector(vector, right, screenUp, viewer) {
42200
+ return {
42201
+ x: CUBE_CENTER + vector.dot(right) * CUBE_SCALE,
42202
+ y: CUBE_CENTER - vector.dot(screenUp) * CUBE_SCALE,
42203
+ depth: vector.dot(viewer)
42204
+ };
42205
+ }
42206
+ function polygonPoints(points) {
42207
+ return points.map((point) => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(" ");
42208
+ }
42209
+ function snapViewFromEventTarget(target) {
42210
+ var _a3;
42211
+ if (!(target instanceof Element)) return null;
42212
+ const view2 = (_a3 = target.closest(".fc-view-cube-face")) == null ? void 0 : _a3.dataset.view;
42213
+ return view2 && SNAP_VIEWS.has(view2) ? view2 : null;
42214
+ }
42215
+ function cameraRightVector(camera) {
42216
+ return buildViewBasisFromLiveCamera(camera).right;
42217
+ }
42218
+ function rotateCameraFromDrag(camera, controls, drag, clientX, clientY) {
42219
+ const dx = clientX - drag.startX;
42220
+ const dy = clientY - drag.startY;
42221
+ if (Math.hypot(dx, dy) > 3) drag.moved = true;
42222
+ const sensitivity = 8e-3;
42223
+ const startOffset = drag.startPosition.clone().sub(drag.target);
42224
+ const yaw = new Quaternion().setFromAxisAngle(drag.up, -dx * sensitivity);
42225
+ const pitchAxis = drag.right.clone().applyQuaternion(yaw).normalize();
42226
+ const pitch = new Quaternion().setFromAxisAngle(pitchAxis, -dy * sensitivity);
42227
+ const nextOffset = startOffset.applyQuaternion(yaw).applyQuaternion(pitch);
42228
+ camera.position.copy(drag.target).add(nextOffset);
42229
+ camera.up.copy(drag.up);
42230
+ controls.target.copy(drag.target);
42231
+ if ("updateProjectionMatrix" in camera) {
42232
+ camera.updateProjectionMatrix();
42233
+ }
42234
+ controls.update();
42235
+ }
42236
+ function ViewCube({ camera, controlsRef, onSnap }) {
42237
+ const [viewBasis, setViewBasis] = reactExports.useState(null);
42238
+ const [dragging, setDragging] = reactExports.useState(false);
42239
+ const [pressedView, setPressedView] = reactExports.useState(null);
42240
+ const dragInfoRef = reactExports.useRef(null);
42241
+ const pressedTimeoutRef = reactExports.useRef(null);
42242
+ const getLiveCamera = reactExports.useCallback(() => {
42243
+ var _a3;
42244
+ return camera ?? ((_a3 = controlsRef.current) == null ? void 0 : _a3.object) ?? null;
42245
+ }, [camera, controlsRef]);
42246
+ const triggerSnap = reactExports.useCallback(
42247
+ (view2) => {
42248
+ setPressedView(view2);
42249
+ if (pressedTimeoutRef.current !== null) {
42250
+ window.clearTimeout(pressedTimeoutRef.current);
42251
+ }
42252
+ pressedTimeoutRef.current = window.setTimeout(() => {
42253
+ pressedTimeoutRef.current = null;
42254
+ setPressedView(null);
42255
+ }, 180);
42256
+ onSnap(view2);
42257
+ },
42258
+ [onSnap]
42259
+ );
42260
+ const syncCameraState = reactExports.useCallback(() => {
42261
+ const controls = controlsRef.current;
42262
+ const liveCamera = getLiveCamera();
42263
+ if (!liveCamera || !controls) return;
42264
+ setViewBasis(buildViewBasisFromLiveCamera(liveCamera));
42265
+ }, [controlsRef, getLiveCamera]);
42266
+ reactExports.useEffect(() => {
42267
+ let disposed = false;
42268
+ let retryFrame = 0;
42269
+ let controls = null;
42270
+ const attach2 = () => {
42271
+ if (disposed) return;
42272
+ controls = controlsRef.current;
42273
+ if (!getLiveCamera() || !controls) {
42274
+ retryFrame = window.requestAnimationFrame(attach2);
42275
+ return;
42276
+ }
42277
+ syncCameraState();
42278
+ controls.addEventListener("change", syncCameraState);
42279
+ };
42280
+ attach2();
42281
+ return () => {
42282
+ disposed = true;
42283
+ if (retryFrame) window.cancelAnimationFrame(retryFrame);
42284
+ controls == null ? void 0 : controls.removeEventListener("change", syncCameraState);
42285
+ };
42286
+ }, [controlsRef, getLiveCamera, syncCameraState]);
42287
+ reactExports.useEffect(() => {
42288
+ return () => {
42289
+ if (pressedTimeoutRef.current !== null) {
42290
+ window.clearTimeout(pressedTimeoutRef.current);
42291
+ }
42292
+ };
42293
+ }, []);
42294
+ const faces = reactExports.useMemo(() => {
42295
+ const { viewer, right, screenUp } = viewBasis ?? DEFAULT_VIEW_BASIS;
42296
+ const projectedFaces = FACE_DEFS.map((face) => {
42297
+ const visibility = face.normal.dot(viewer);
42298
+ if (visibility <= 0.02) return null;
42299
+ const points = face.corners.map((corner) => projectVector(corner, right, screenUp, viewer));
42300
+ const center = projectVector(face.normal, right, screenUp, viewer);
42301
+ const depth = points.reduce((sum, point) => sum + point.depth, 0) / points.length;
42302
+ return {
42303
+ view: face.view,
42304
+ label: face.label,
42305
+ points,
42306
+ center,
42307
+ depth,
42308
+ brightness: MathUtils.clamp(visibility, 0, 1)
42309
+ };
42310
+ }).filter((face) => face !== null).sort((a2, b2) => a2.depth - b2.depth);
42311
+ return projectedFaces;
42312
+ }, [viewBasis]);
42313
+ const handlePointerDown = reactExports.useCallback(
42314
+ (event) => {
42315
+ if (event.button !== 0) return;
42316
+ const liveCamera = getLiveCamera();
42317
+ const clickView = snapViewFromEventTarget(event.target);
42318
+ const controls = controlsRef.current;
42319
+ if (!clickView && (!liveCamera || !controls)) return;
42320
+ const target = (controls == null ? void 0 : controls.target.clone()) ?? new Vector3();
42321
+ const up = liveCamera ? normalizedOr(liveCamera.up, new Vector3(0, 0, 1)) : new Vector3(0, 0, 1);
42322
+ dragInfoRef.current = {
42323
+ pointerId: event.pointerId,
42324
+ startX: event.clientX,
42325
+ startY: event.clientY,
42326
+ startPosition: (liveCamera == null ? void 0 : liveCamera.position.clone()) ?? new Vector3(),
42327
+ target,
42328
+ up,
42329
+ right: liveCamera && controls ? cameraRightVector(liveCamera) : new Vector3(1, 0, 0),
42330
+ moved: false,
42331
+ clickView
42332
+ };
42333
+ event.currentTarget.setPointerCapture(event.pointerId);
42334
+ setDragging(true);
42335
+ },
42336
+ [controlsRef, getLiveCamera]
42337
+ );
42338
+ const handlePointerMove = reactExports.useCallback(
42339
+ (event) => {
42340
+ const drag = dragInfoRef.current;
42341
+ if (!drag || drag.pointerId !== event.pointerId) return;
42342
+ if (Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) > 3) {
42343
+ drag.moved = true;
42344
+ }
42345
+ const controls = controlsRef.current;
42346
+ const liveCamera = getLiveCamera();
42347
+ if (!liveCamera || !controls) return;
42348
+ event.preventDefault();
42349
+ rotateCameraFromDrag(liveCamera, controls, drag, event.clientX, event.clientY);
42350
+ syncCameraState();
42351
+ },
42352
+ [controlsRef, getLiveCamera, syncCameraState]
42353
+ );
42354
+ const finishPointerDrag = reactExports.useCallback(
42355
+ (event) => {
42356
+ const drag = dragInfoRef.current;
42357
+ if (!drag || drag.pointerId !== event.pointerId) return;
42358
+ dragInfoRef.current = null;
42359
+ event.currentTarget.releasePointerCapture(event.pointerId);
42360
+ setDragging(false);
42361
+ if (!drag.moved && drag.clickView) {
42362
+ triggerSnap(drag.clickView);
42363
+ }
42364
+ },
42365
+ [triggerSnap]
42366
+ );
42367
+ const handleFaceKeyDown = reactExports.useCallback(
42368
+ (event, view2) => {
42369
+ if (event.key !== "Enter" && event.key !== " ") return;
42370
+ event.preventDefault();
42371
+ triggerSnap(view2);
42372
+ },
42373
+ [triggerSnap]
42374
+ );
42375
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
42376
+ "div",
42377
+ {
42378
+ className: `fc-view-cube${dragging ? " dragging" : ""}`,
42379
+ "aria-label": "View cube",
42380
+ onPointerDown: handlePointerDown,
42381
+ onPointerMove: handlePointerMove,
42382
+ onPointerUp: finishPointerDrag,
42383
+ onPointerCancel: finishPointerDrag,
42384
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "fc-view-cube-svg", viewBox: `0 0 ${CUBE_SIZE} ${CUBE_SIZE}`, role: "img", "aria-label": "Viewport orientation", children: [
42385
+ /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("filter", { id: "fc-view-cube-shadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ jsxRuntimeExports.jsx("feDropShadow", { dx: "0", dy: "2", stdDeviation: "2", floodColor: "#07101a", floodOpacity: "0.28" }) }) }),
42386
+ /* @__PURE__ */ jsxRuntimeExports.jsx("g", { filter: "url(#fc-view-cube-shadow)", children: faces.map((face) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
42387
+ "g",
42388
+ {
42389
+ className: `fc-view-cube-face${pressedView === face.view ? " active" : ""}`,
42390
+ role: "button",
42391
+ tabIndex: 0,
42392
+ "data-view": face.view,
42393
+ "aria-label": `View ${face.label.toLowerCase()}`,
42394
+ onKeyDown: (event) => handleFaceKeyDown(event, face.view),
42395
+ children: [
42396
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: polygonPoints(face.points), fill: faceFill(face.brightness) }),
42397
+ /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: face.center.x, y: face.center.y, className: "fc-view-cube-face-label", children: face.label })
42398
+ ]
42399
+ },
42400
+ face.view
42401
+ )) })
42402
+ ] })
42403
+ }
42404
+ );
42405
+ }
41154
42406
  const FLAG_DEFINITIONS = {
41155
42407
  drawMode: {
41156
42408
  label: "Draw Mode (interactive sketch editor)",
@@ -41763,6 +43015,7 @@ function useViewportState() {
41763
43015
  const projectionMode = useForgeStore((s) => s.projectionMode);
41764
43016
  const gridEnabled = useForgeStore((s) => s.gridEnabled);
41765
43017
  const axesVisible = useForgeStore((s) => s.axesVisible);
43018
+ const viewCubeVisible = useForgeStore((s) => s.viewCubeVisible);
41766
43019
  const gridSize = useForgeStore((s) => s.gridSize);
41767
43020
  const showPerformanceInfo = useForgeStore((s) => s.showPerformanceInfo);
41768
43021
  const objectSettings = useForgeStore((s) => s.objectSettings);
@@ -41776,9 +43029,7 @@ function useViewportState() {
41776
43029
  const clearFocusedObject = useForgeStore((s) => s.clearFocusedObject);
41777
43030
  const objectPickSyncEnabled = useForgeStore((s) => s.objectPickSyncEnabled);
41778
43031
  const explodeAmount = useForgeStore((s) => s.explodeAmount);
41779
- const viewCommand = useForgeStore((s) => s.viewCommand);
41780
43032
  const requestViewCommand = useForgeStore((s) => s.requestViewCommand);
41781
- const clearViewCommand = useForgeStore((s) => s.clearViewCommand);
41782
43033
  const jointValues = useForgeStore((s) => s.jointValues);
41783
43034
  const jointAnimationClip = useForgeStore((s) => s.jointAnimationClip);
41784
43035
  const jointAnimationProgress = useForgeStore((s) => s.jointAnimationProgress);
@@ -41912,6 +43163,10 @@ function useViewportState() {
41912
43163
  }
41913
43164
  return;
41914
43165
  }
43166
+ if (obj.sdf) {
43167
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) hasBounds = true;
43168
+ return;
43169
+ }
41915
43170
  if (obj.sketch) {
41916
43171
  try {
41917
43172
  const bb = obj.sketch.bounds();
@@ -41920,15 +43175,16 @@ function useViewportState() {
41920
43175
  }
41921
43176
  return;
41922
43177
  }
43178
+ if (obj.curve3d) {
43179
+ const cb = obj.curve3d.bounds;
43180
+ if (expandBoundsByTransformedAabb(bounds, cb.min, cb.max, matrix)) hasBounds = true;
43181
+ return;
43182
+ }
41923
43183
  if (obj.toolpath) {
41924
43184
  const tb = obj.toolpath.bounds;
41925
43185
  if (expandBoundsByTransformedAabb(bounds, tb.min, tb.max, matrix)) hasBounds = true;
41926
43186
  return;
41927
43187
  }
41928
- if (obj.sdf) {
41929
- const sb = obj.sdf.bounds;
41930
- if (expandBoundsByTransformedAabb(bounds, sb.min, sb.max, matrix)) hasBounds = true;
41931
- }
41932
43188
  });
41933
43189
  return hasBounds ? bounds.min.z : null;
41934
43190
  }, [objects, objectMatrices]);
@@ -41945,6 +43201,10 @@ function useViewportState() {
41945
43201
  if (expandBoundsByTransformedAabb(bounds, bb.min, bb.max, matrix)) hasBounds = true;
41946
43202
  } catch {
41947
43203
  }
43204
+ return;
43205
+ }
43206
+ if (obj.sdf) {
43207
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) hasBounds = true;
41948
43208
  }
41949
43209
  });
41950
43210
  if (!hasBounds) return { surfaceFieldScale: 1, scanProxyGrid: null };
@@ -41972,6 +43232,10 @@ function useViewportState() {
41972
43232
  }
41973
43233
  return;
41974
43234
  }
43235
+ if (obj.sdf) {
43236
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) hasBounds = true;
43237
+ return;
43238
+ }
41975
43239
  if (obj.sketch) {
41976
43240
  try {
41977
43241
  const bb = obj.sketch.bounds();
@@ -41980,15 +43244,16 @@ function useViewportState() {
41980
43244
  }
41981
43245
  return;
41982
43246
  }
43247
+ if (obj.curve3d) {
43248
+ const cb = obj.curve3d.bounds;
43249
+ if (expandBoundsByTransformedAabb(bounds, cb.min, cb.max, matrix)) hasBounds = true;
43250
+ return;
43251
+ }
41983
43252
  if (obj.toolpath) {
41984
43253
  const tb = obj.toolpath.bounds;
41985
43254
  if (expandBoundsByTransformedAabb(bounds, tb.min, tb.max, matrix)) hasBounds = true;
41986
43255
  return;
41987
43256
  }
41988
- if (obj.sdf) {
41989
- const sb = obj.sdf.bounds;
41990
- if (expandBoundsByTransformedAabb(bounds, sb.min, sb.max, matrix)) hasBounds = true;
41991
- }
41992
43257
  });
41993
43258
  if (!hasBounds) return Math.max(60, gridSize * 8);
41994
43259
  const size = new Vector3();
@@ -42008,6 +43273,10 @@ function useViewportState() {
42008
43273
  }
42009
43274
  return;
42010
43275
  }
43276
+ if (obj.sdf) {
43277
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, IDENTITY_MATRIX$2)) hasBounds = true;
43278
+ return;
43279
+ }
42011
43280
  if (obj.sketch) {
42012
43281
  try {
42013
43282
  const bb = obj.sketch.bounds();
@@ -42023,15 +43292,16 @@ function useViewportState() {
42023
43292
  }
42024
43293
  return;
42025
43294
  }
43295
+ if (obj.curve3d) {
43296
+ const cb = obj.curve3d.bounds;
43297
+ if (expandBoundsByTransformedAabb(bounds, cb.min, cb.max, IDENTITY_MATRIX$2)) hasBounds = true;
43298
+ return;
43299
+ }
42026
43300
  if (obj.toolpath) {
42027
43301
  const tb = obj.toolpath.bounds;
42028
43302
  if (expandBoundsByTransformedAabb(bounds, tb.min, tb.max, IDENTITY_MATRIX$2)) hasBounds = true;
42029
43303
  return;
42030
43304
  }
42031
- if (obj.sdf) {
42032
- const sb = obj.sdf.bounds;
42033
- if (expandBoundsByTransformedAabb(bounds, sb.min, sb.max, IDENTITY_MATRIX$2)) hasBounds = true;
42034
- }
42035
43305
  });
42036
43306
  if (!hasBounds) return Math.max(60, gridSize * 8);
42037
43307
  const size = new Vector3();
@@ -42104,8 +43374,7 @@ function useViewportState() {
42104
43374
  ]);
42105
43375
  const rigInspectionBounds = rigInspectionOverlay.bounds;
42106
43376
  const hasShape = objects.some((obj) => obj.shape);
42107
- const hasSdf = objects.some((obj) => obj.sdf);
42108
- const isSketchOnly = !hasShape && !hasSdf && objects.some((obj) => obj.sketch);
43377
+ const isSketchOnly = !hasShape && objects.some((obj) => obj.sketch);
42109
43378
  const knownFileNames = reactExports.useMemo(() => new Set(Object.keys(files)), [files]);
42110
43379
  const focusedObjectIdSet = reactExports.useMemo(() => new Set(focusedObjectIds), [focusedObjectIds]);
42111
43380
  const { visibleSceneObjectCount, visibleModelTriangles } = reactExports.useMemo(() => {
@@ -42156,6 +43425,7 @@ function useViewportState() {
42156
43425
  projectionMode,
42157
43426
  gridEnabled,
42158
43427
  axesVisible,
43428
+ viewCubeVisible,
42159
43429
  gridSize,
42160
43430
  showPerformanceInfo,
42161
43431
  objectSettings,
@@ -42169,9 +43439,7 @@ function useViewportState() {
42169
43439
  clearFocusedObject,
42170
43440
  objectPickSyncEnabled,
42171
43441
  explodeAmount,
42172
- viewCommand,
42173
43442
  requestViewCommand,
42174
- clearViewCommand,
42175
43443
  jointValues,
42176
43444
  jointAnimationClip,
42177
43445
  jointAnimationProgress,
@@ -43791,7 +45059,7 @@ function useGeometryComparison(args) {
43791
45059
  class InspectWorkerClient {
43792
45060
  constructor(workerFactory = () => new Worker(new URL(
43793
45061
  /* @vite-ignore */
43794
- "/assets/inspectWorker-CZsCFtQT.js",
45062
+ "/assets/inspectWorker-D5T5VbfK.js",
43795
45063
  import.meta.url
43796
45064
  ), { type: "module" })) {
43797
45065
  __publicField(this, "worker", null);
@@ -44532,6 +45800,7 @@ function VoxelIntentOverlay() {
44532
45800
  ))
44533
45801
  ] });
44534
45802
  }
45803
+ const DEFAULT_OBJECT_SETTINGS = { visible: true, opacity: 1, color: "#5b9bd5" };
44535
45804
  function buildManualSceneConfig(base, manualScene, renderStylePreset) {
44536
45805
  var _a3, _b2, _c;
44537
45806
  const lights = [
@@ -44876,6 +46145,7 @@ function Viewport() {
44876
46145
  projectionMode,
44877
46146
  gridEnabled,
44878
46147
  axesVisible,
46148
+ viewCubeVisible,
44879
46149
  gridSize,
44880
46150
  showPerformanceInfo,
44881
46151
  objectSettings,
@@ -44888,9 +46158,7 @@ function Viewport() {
44888
46158
  focusObject,
44889
46159
  clearFocusedObject,
44890
46160
  objectPickSyncEnabled,
44891
- viewCommand,
44892
46161
  requestViewCommand,
44893
- clearViewCommand,
44894
46162
  lengthUnit,
44895
46163
  constructionGhost,
44896
46164
  objects,
@@ -44943,6 +46211,7 @@ function Viewport() {
44943
46211
  previewFile,
44944
46212
  knownFileNames
44945
46213
  } = state2;
46214
+ const viewportPerformanceProbeEnabled = reactExports.useMemo(() => isViewportPerformanceProbeEnabled(), []);
44946
46215
  const voxelContextFile = previewFile ?? ((activeFile == null ? void 0 : activeFile.toLowerCase().endsWith(".forge.js")) ? activeFile : null);
44947
46216
  const historyMode = useForgeStore((s) => s.historyMode);
44948
46217
  const historyObjectIds = useForgeStore((s) => s.historyObjectIds);
@@ -44961,10 +46230,17 @@ function Viewport() {
44961
46230
  const vRulerRef = reactExports.useRef(null);
44962
46231
  const adaptiveGrid = useAdaptiveGrid(zoomMmPerPx, isSketchOnly && gridEnabled);
44963
46232
  const controlsRef = reactExports.useRef(null);
46233
+ const [activeCanvasCamera, setActiveCanvasCamera] = reactExports.useState(null);
44964
46234
  const initialFitRequestedRef = reactExports.useRef(false);
44965
46235
  const prevPreviewFileRef = reactExports.useRef(void 0);
44966
46236
  const containerRef = reactExports.useRef(null);
44967
46237
  const contextMenuRef = reactExports.useRef(null);
46238
+ const handleActiveCameraChange = reactExports.useCallback((camera) => {
46239
+ setActiveCanvasCamera(camera);
46240
+ }, []);
46241
+ const handleCanvasCreated = reactExports.useCallback((state22) => {
46242
+ setActiveCanvasCamera(state22.camera);
46243
+ }, []);
44968
46244
  const t2 = themes[themeName];
44969
46245
  const renderStylePreset = reactExports.useMemo(() => getRenderStylePreset(renderStyle), [renderStyle]);
44970
46246
  const effectiveSceneConfig = reactExports.useMemo(
@@ -44976,10 +46252,6 @@ function Viewport() {
44976
46252
  const inspectChannelActive = inspectChannel !== "none";
44977
46253
  const comparisonInspectActive = inspectChannel === "comparison";
44978
46254
  const rigInspectActive = inspectChannel === "rig";
44979
- const hasVisibleSdfObjects = objects.some((obj) => {
44980
- var _a4;
44981
- return obj.sdf && (((_a4 = objectSettings[obj.id]) == null ? void 0 : _a4.visible) ?? true);
44982
- });
44983
46255
  const viewportDisabledMessage = reactExports.useMemo(() => {
44984
46256
  if (activeFile && !isModelFile(activeFile) && !isSvgActive && !meshPreviewFile) {
44985
46257
  return {
@@ -44995,15 +46267,12 @@ function Viewport() {
44995
46267
  }
44996
46268
  return null;
44997
46269
  }, [activeFile, isSvgActive, meshPreviewFile, objects.length, result, rigInspectActive]);
44998
- const canvasDpr = reactExports.useMemo(() => {
44999
- if (hasVisibleSdfObjects) return renderStylePreset.canvasDpr.live;
45000
- return [1, renderStylePreset.canvasDpr.idleMax];
45001
- }, [hasVisibleSdfObjects, renderStylePreset.canvasDpr.idleMax, renderStylePreset.canvasDpr.live]);
45002
- const effectiveSdfObjectSettings = reactExports.useMemo(() => {
46270
+ const canvasDpr = isViewportInteracting ? renderStylePreset.canvasDpr.live : [1, renderStylePreset.canvasDpr.idleMax];
46271
+ const sdfObjectSettings = reactExports.useMemo(() => {
45003
46272
  const next = {};
45004
46273
  for (const obj of objects) {
45005
46274
  if (!obj.sdf) continue;
45006
- const settings = objectSettings[obj.id] ?? { visible: true, opacity: 1, color: "#5b9bd5" };
46275
+ const settings = objectSettings[obj.id] ?? DEFAULT_OBJECT_SETTINGS;
45007
46276
  const isDimmedByFocus = focusedObjectIdSet.size > 0 && !focusedObjectIdSet.has(obj.id);
45008
46277
  const isDimmedByGhost = constructionGhost !== null && obj.id !== constructionGhost.objectId;
45009
46278
  next[obj.id] = isDimmedByFocus || isDimmedByGhost ? { ...settings, opacity: Math.min(settings.opacity, FOCUS_MODE_DIM_OPACITY) } : settings;
@@ -45325,7 +46594,9 @@ function Viewport() {
45325
46594
  raycaster: { params: { Line: { threshold: 0.5 } } },
45326
46595
  camera: { up: [0, 0, 1] },
45327
46596
  onPointerMissed: handleViewportPointerMissed,
46597
+ onCreated: handleCanvasCreated,
45328
46598
  children: [
46599
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ActiveCameraBridge, { onCameraChange: handleActiveCameraChange }),
45329
46600
  /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Suspense, { fallback: null, children: [
45330
46601
  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] }),
45331
46602
  ((_d = effectiveSceneConfig == null ? void 0 : effectiveSceneConfig.postProcessing) == null ? void 0 : _d.toneMappingExposure) === void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(RenderStyleExposure, { exposure: renderStylePreset.toneMappingExposure }),
@@ -45396,7 +46667,7 @@ function Viewport() {
45396
46667
  }
45397
46668
  ),
45398
46669
  objects.map((obj, objIndex) => {
45399
- const settings = objectSettings[obj.id] ?? { visible: true, opacity: 1, color: "#5b9bd5" };
46670
+ const settings = objectSettings[obj.id] ?? DEFAULT_OBJECT_SETTINGS;
45400
46671
  const isDimmedByFocus = focusedObjectIdSet.size > 0 && !focusedObjectIdSet.has(obj.id);
45401
46672
  const isDimmedByGhost = constructionGhost !== null && obj.id !== constructionGhost.objectId;
45402
46673
  const isDimmedByVoxel = voxelIntentMode && !!obj.shape;
@@ -45446,6 +46717,26 @@ function Viewport() {
45446
46717
  obj.id
45447
46718
  );
45448
46719
  }
46720
+ if (canRenderSdfDirectly(obj.sdf)) {
46721
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
46722
+ SdfDirectObject,
46723
+ {
46724
+ sdf: obj.sdf,
46725
+ settings: sdfObjectSettings[obj.id] ?? DEFAULT_OBJECT_SETTINGS,
46726
+ matrix,
46727
+ materialProps: obj.materialProps,
46728
+ isHovered,
46729
+ clippingPlanes: objectClippingPlanes,
46730
+ onPointerEnter: (event) => updateHoverLabel(obj, event),
46731
+ onPointerMove: (event) => updateHoverLabel(obj, event),
46732
+ onPointerLeave: (event) => clearHoverLabel(obj, event),
46733
+ onClick: (event) => handleObjectClick(obj, event),
46734
+ onDoubleClick: (event) => handleObjectDoubleClick(obj, event),
46735
+ onContextMenu: (event) => handleObjectContextMenu(obj, event)
46736
+ },
46737
+ obj.id
46738
+ );
46739
+ }
45449
46740
  if (obj.sketch) {
45450
46741
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
45451
46742
  SketchObject,
@@ -45559,7 +46850,7 @@ function Viewport() {
45559
46850
  /* @__PURE__ */ jsxRuntimeExports.jsx(
45560
46851
  PerformanceInfoSampler,
45561
46852
  {
45562
- enabled: showPerformanceInfo,
46853
+ enabled: showPerformanceInfo || viewportPerformanceProbeEnabled,
45563
46854
  sceneObjects: visibleSceneObjectCount,
45564
46855
  modelTriangles: visibleModelTriangles,
45565
46856
  reactRenderCountRef,
@@ -45582,21 +46873,6 @@ function Viewport() {
45582
46873
  infiniteGrid: true
45583
46874
  }
45584
46875
  ),
45585
- !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
45586
- SdfRaymarchLayer,
45587
- {
45588
- objects,
45589
- objectSettings: effectiveSdfObjectSettings,
45590
- clippingPlanesById: objectClippingPlanesById,
45591
- hoveredObjectId,
45592
- onPointerEnter: (obj, event) => updateHoverLabel(obj, event),
45593
- onPointerMove: (obj, event) => updateHoverLabel(obj, event),
45594
- onPointerLeave: (obj, event) => clearHoverLabel(obj, event),
45595
- onClick: (obj, event) => voxelIntentMode ? handleVoxelObjectSurfaceClick(event, objectMatrices[obj.id] ?? new Matrix4()) : handleObjectClick(obj, event),
45596
- onDoubleClick: (obj, event) => handleObjectDoubleClick(obj, event),
45597
- onContextMenu: (obj, event) => handleObjectContextMenu(obj, event)
45598
- }
45599
- ),
45600
46876
  axesVisible && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(LabeledAxes, {}),
45601
46877
  axesVisible && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchAxes, {}),
45602
46878
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(CursorTracker, { onMove: (x, y) => setCursorPos({ x, y }) }),
@@ -45639,23 +46915,24 @@ function Viewport() {
45639
46915
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportRecordingBridge, { controlsRef }),
45640
46916
  historyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryRecorderBridge, {}),
45641
46917
  /* @__PURE__ */ jsxRuntimeExports.jsx(TrajectoryRecorderBridge, { controlsRef })
45642
- ] }),
45643
- /* @__PURE__ */ jsxRuntimeExports.jsx(
45644
- ViewController,
45645
- {
45646
- controlsRef,
45647
- command: viewCommand,
45648
- objects,
45649
- objectMatrices,
45650
- fallbackBounds: rigInspectActive ? rigInspectionBounds : null,
45651
- settings: objectSettings,
45652
- focusedObjectIds,
45653
- clearCommand: clearViewCommand
45654
- }
45655
- )
46918
+ ] })
45656
46919
  ]
45657
46920
  }
45658
46921
  ),
46922
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
46923
+ ViewController,
46924
+ {
46925
+ camera: activeCanvasCamera,
46926
+ controlsRef,
46927
+ viewportRef: containerRef,
46928
+ objects,
46929
+ objectMatrices,
46930
+ fallbackBounds: rigInspectActive ? rigInspectionBounds : null,
46931
+ settings: objectSettings,
46932
+ focusedObjectIds
46933
+ }
46934
+ ),
46935
+ viewCubeVisible && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(ViewCube, { camera: activeCanvasCamera, controlsRef, onSnap: (view2) => requestViewCommand({ type: "snap", view: view2 }) }),
45659
46936
  (() => {
45660
46937
  const toolpathObj = objects.find((o2) => o2.toolpath && o2.toolpath.segments.length > 0);
45661
46938
  if (!(toolpathObj == null ? void 0 : toolpathObj.toolpath)) return null;
@@ -46211,7 +47488,7 @@ function Viewport() {
46211
47488
  }
46212
47489
  );
46213
47490
  }
46214
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-QXsAISLR.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
47491
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-Bfd3jbtC.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46215
47492
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
46216
47493
  function storePendingShareCopy(shareId) {
46217
47494
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -46245,7 +47522,7 @@ function PublishedModelPage() {
46245
47522
  checkSession();
46246
47523
  }, [checkSession]);
46247
47524
  reactExports.useEffect(() => {
46248
- Promise.all([initKernel(), initSolverWasm()]).then(() => {
47525
+ initBackendForEvaluation(useForgeStore.getState().activeBackend).then(() => {
46249
47526
  setKernelReady(true);
46250
47527
  });
46251
47528
  }, []);
@@ -46400,7 +47677,7 @@ const PublishedModelPage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Objec
46400
47677
  }, Symbol.toStringTag, { value: "Module" }));
46401
47678
  const siteUrl = "https://forgecad.io";
46402
47679
  const defaultImage = "https://forgecad.io/brand/social-card.png";
46403
- const pages$1 = [{ "path": "/", "title": "ForgeCAD - AI-Native CAD for Manufacturable Models", "description": "ForgeCAD turns prompts, JavaScript, and product ideas into editable parametric CAD with agent validation, manufacturing feedback, and STEP/STL exports.", "priority": "1.0", "changefreq": "weekly" }, { "path": "/docs", "title": "ForgeCAD Documentation - AI-Native Parametric CAD", "description": "Learn ForgeCAD's JavaScript CAD API, agent workflow, validation loop, exports, assemblies, sketches, and manufacturing-ready model checks.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/ai-native-cad", "title": "AI-Native CAD Workflow - ForgeCAD", "description": "How ForgeCAD supports AI-driven CAD: editable code, validation evidence, manufacturing feedback, and parallel product idea iteration.", "priority": "0.9", "changefreq": "weekly" }, { "path": "/docs/ai-usage", "title": "AI CAD Agent Setup - ForgeCAD", "description": "Set up Codex, Claude Code, OpenCode, or another agent to build, validate, inspect, export, and publish ForgeCAD models.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/cli", "title": "ForgeCAD CLI - Validate, Inspect, Export, Publish", "description": "Use the ForgeCAD CLI to run .forge.js models, render evidence, inspect manufacturability, export STEP/STL/3MF, and sync projects.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/docs/skills/forgecad-make-a-model", "title": "AI CAD Model-Making Skill - ForgeCAD", "description": "A workflow for AI agents to create manufacture-realistic ForgeCAD models with validation, renders, inspection evidence, and exports.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/benchmark", "title": "ForgeCAD Benchmark - AI CAD Model Generation", "description": "Benchmark AI agents generating real ForgeCAD models with executable code, visual evidence, validation output, and geometry checks.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/blog", "title": "ForgeCAD Blog - AI-Native CAD and Manufacturing Workflows", "description": "Product notes, tutorials, examples, and technical updates on AI-native CAD, parametric modeling, validation, and manufacturing exports.", "priority": "0.6", "changefreq": "weekly" }, { "path": "/pricing", "title": "ForgeCAD Pricing", "description": "ForgeCAD plans for browser CAD, local CLI workflows, AI agent model generation, project publishing, and commercial use.", "priority": "0.5", "changefreq": "monthly" }, { "path": "/terms", "title": "ForgeCAD Terms of Service", "description": "Terms for ForgeCAD accounts, projects, hosted services, CLI use, commercial use, automation, and AI-assisted workflows.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/privacy", "title": "ForgeCAD Privacy Policy", "description": "How ForgeCAD handles account, project, billing, analytics, and service data for the website, CLI, and hosted app.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/license", "title": "ForgeCAD Software License", "description": "ForgeCAD license terms for Free, Pro, Enterprise, embedded, backend, local CLI, and AI workflow usage.", "priority": "0.3", "changefreq": "yearly" }];
47680
+ const pages$1 = [{ "path": "/", "title": "ForgeCAD - AI-Native CAD for Manufacturable Models", "description": "ForgeCAD turns prompts, JavaScript, and product ideas into editable parametric CAD with agent validation, manufacturing feedback, and STEP/STL exports.", "priority": "1.0", "changefreq": "weekly" }, { "path": "/docs", "title": "ForgeCAD Documentation - AI-Native Parametric CAD", "description": "Learn ForgeCAD's JavaScript CAD API, agent workflow, validation loop, exports, assemblies, sketches, and manufacturing-ready model checks.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/ai-native-cad", "title": "AI-Native CAD Workflow - ForgeCAD", "description": "How ForgeCAD supports AI-driven CAD: editable code, validation evidence, manufacturing feedback, and parallel product idea iteration.", "priority": "0.9", "changefreq": "weekly" }, { "path": "/docs/ai-usage", "title": "AI CAD Agent Setup - ForgeCAD", "description": "Set up Codex, Claude Code, OpenCode, or another agent to build, validate, inspect, export, and publish ForgeCAD models.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/cli", "title": "ForgeCAD CLI - Validate, Inspect, Export, Publish", "description": "Use the ForgeCAD CLI to run .forge.js models, render evidence, inspect manufacturability, export STEP/STL/3MF, and sync projects.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/docs/simready-quickstart", "title": "ForgeCAD SimReady Quickstart - Source-Authored Simulation Metadata", "description": "Author ForgeCAD assemblies with physical bodies, materials, colliders, joints, controllers, offline SimReady checks, and simulator exports.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/simulation-workflow", "title": "ForgeCAD Simulation Workflow - MuJoCo, Gym, and Isaac Lab Handoff", "description": "Take a ForgeCAD robot through MJCF export, MuJoCo playback, Gymnasium training, force-push interaction tests, and Isaac Lab integration planning.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/skills/forgecad-make-a-model", "title": "AI CAD Model-Making Skill - ForgeCAD", "description": "A workflow for AI agents to create manufacture-realistic ForgeCAD models with validation, renders, inspection evidence, and exports.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/benchmark", "title": "ForgeCAD Benchmark - AI CAD Model Generation", "description": "Benchmark AI agents generating real ForgeCAD models with executable code, visual evidence, validation output, and geometry checks.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/blog", "title": "ForgeCAD Blog - AI-Native CAD and Manufacturing Workflows", "description": "Product notes, tutorials, examples, and technical updates on AI-native CAD, parametric modeling, validation, and manufacturing exports.", "priority": "0.6", "changefreq": "weekly" }, { "path": "/pricing", "title": "ForgeCAD Pricing", "description": "ForgeCAD plans for browser CAD, local CLI workflows, AI agent model generation, project publishing, and commercial use.", "priority": "0.5", "changefreq": "monthly" }, { "path": "/terms", "title": "ForgeCAD Terms of Service", "description": "Terms for ForgeCAD accounts, projects, hosted services, CLI use, commercial use, automation, and AI-assisted workflows.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/privacy", "title": "ForgeCAD Privacy Policy", "description": "How ForgeCAD handles account, project, billing, analytics, and service data for the website, CLI, and hosted app.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/license", "title": "ForgeCAD Software License", "description": "ForgeCAD license terms for Free, Pro, Enterprise, embedded, backend, local CLI, and AI workflow usage.", "priority": "0.3", "changefreq": "yearly" }];
46404
47681
  const seoConfig = {
46405
47682
  siteUrl,
46406
47683
  defaultImage,
@@ -46477,17 +47754,17 @@ function SeoMetadata() {
46477
47754
  }, [location.pathname]);
46478
47755
  return null;
46479
47756
  }
46480
- reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-yhhOodbf.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
46481
- const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-B5LePEuj.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
46482
- reactExports.lazy(() => __vitePreload(() => import("./BlogPage-DodHpvmf.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
46483
- reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-a9_f-1US.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
46484
- reactExports.lazy(() => __vitePreload(() => import("./AdminPage-DwYHz72L.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
47757
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-DbN7o-Be.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
47758
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-DsvdiRNK.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
47759
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-BssBbnb-.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
47760
+ reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-BcRT5iGN.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
47761
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-CHY6ZN-p.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
46485
47762
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
46486
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-BJZcM97j.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
46487
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-E3Rma7aV.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
46488
- reactExports.lazy(() => __vitePreload(() => import("./LegalPage-5RbKRGYK.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
46489
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-QXsAISLR.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46490
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-DdEHGUMU.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
47763
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-DZlyu4d4.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
47764
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-Nczr3pRz.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
47765
+ reactExports.lazy(() => __vitePreload(() => import("./LegalPage-DNGrrY0p.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
47766
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-Bfd3jbtC.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
47767
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-D5t8WamV.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
46491
47768
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
46492
47769
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg|dxf)$/i;
46493
47770
  function firstMeaningfulLine(text) {
@@ -46745,11 +48022,12 @@ export {
46745
48022
  hasProjectTextFileExtension as a6,
46746
48023
  expandBoundsByTransformedAabb as a7,
46747
48024
  Canvas as a8,
46748
- PerspectiveCamera as a9,
46749
- buildShareUrl as aA,
46750
- share as aB,
46751
- ControlsInteractionBridge as aa,
46752
- ViewController as ab,
48025
+ ActiveCameraBridge as a9,
48026
+ storePendingShareCopy as aA,
48027
+ buildShareUrl as aB,
48028
+ share as aC,
48029
+ PerspectiveCamera as aa,
48030
+ ControlsInteractionBridge as ab,
46753
48031
  SceneConfigurator as ac,
46754
48032
  LocalEnvironment as ad,
46755
48033
  ForgeObject as ae,
@@ -46758,22 +48036,22 @@ export {
46758
48036
  OrbitControls2 as ah,
46759
48037
  TOUCH_GESTURES_3D as ai,
46760
48038
  MOUSE_BUTTONS_3D as aj,
46761
- ModelJourneyBar as ak,
46762
- useJointAnimationLoop as al,
46763
- computeJointNodeMatrices as am,
46764
- computeObjectJointMatrices as an,
46765
- readLastActiveFileForUser as ao,
46766
- ToastContainer as ap,
46767
- isMobile as aq,
46768
- useFeatureFlag as ar,
46769
- decodeSharedHash as as,
46770
- decodeSharedBundle as at,
46771
- getExternalUrl as au,
46772
- getGistId as av,
46773
- Viewport as aw,
46774
- shouldBlockBrowserShortcut as ax,
46775
- useDrawStore as ay,
46776
- storePendingShareCopy as az,
48039
+ ViewController as ak,
48040
+ ModelJourneyBar as al,
48041
+ useJointAnimationLoop as am,
48042
+ computeJointNodeMatrices as an,
48043
+ computeObjectJointMatrices as ao,
48044
+ readLastActiveFileForUser as ap,
48045
+ ToastContainer as aq,
48046
+ isMobile as ar,
48047
+ useFeatureFlag as as,
48048
+ decodeSharedHash as at,
48049
+ decodeSharedBundle as au,
48050
+ getExternalUrl as av,
48051
+ getGistId as aw,
48052
+ Viewport as ax,
48053
+ shouldBlockBrowserShortcut as ay,
48054
+ useDrawStore as az,
46777
48055
  authFetch as b,
46778
48056
  authApi as c,
46779
48057
  showToast as d,