forgecad 0.9.16 → 0.10.1

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 (162) hide show
  1. package/dist/assets/{AdminPage-CXvls4-J.js → AdminPage-DcCnj0qo.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-B27zk8xL.js → BenchmarkPage-BVEpJSVk.js} +1 -1
  3. package/dist/assets/{BlogPage-CMAVvgQL.js → BlogPage-DHaGP50_.js} +1 -1
  4. package/dist/assets/{DocsPage-knf4I4h7.js → DocsPage-CDoxHkz8.js} +40 -859
  5. package/dist/assets/EditorApp-BJ0Dloyh.js +16446 -0
  6. package/dist/assets/{EmbedViewer-D7ZGlFjx.js → EmbedViewer-CRKZbY0y.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-CnevhTE8.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
  8. package/dist/assets/{LegalPage-BPTUmqeg.js → LegalPage-B-u6FrVv.js} +1 -1
  9. package/dist/assets/{PricingPage-B0D4goG_.js → PricingPage-CzpZ6-Ce.js} +1 -1
  10. package/dist/assets/{SettingsPage-CFF-UgjI.js → SettingsPage-CIZSSAd0.js} +1 -1
  11. package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
  12. package/dist/assets/{app-T0pDcSX4.js → app-DaTMg3nH.js} +1310 -290
  13. package/dist/assets/cli/{render-C5pcIISc.js → render-DPf4AYJK.js} +55 -60
  14. package/dist/assets/{constructionHistoryWorker-Ba2Hm58b.js → constructionHistoryWorker-AwMMWSxg.js} +1103 -349
  15. package/dist/assets/{evalWorker-vkx310U2.js → evalWorker-CjZZWRWW.js} +5209 -2643
  16. package/dist/assets/{inspectWorker-BuTJDVX6.js → inspectWorker-CZsCFtQT.js} +1163 -409
  17. package/dist/assets/{jointPose-B_Cgedn9.js → jointPose-DzQOViQH.js} +1 -1
  18. package/dist/assets/{manifold-BWgsjmAM.js → manifold-BYlzU521.js} +1 -1
  19. package/dist/assets/{manifold-D6IFSkhH.js → manifold-DgXo0T5P.js} +2 -2
  20. package/dist/assets/{manifold-rZexZI0G.js → manifold-K1SkarlQ.js} +1 -1
  21. package/dist/assets/{reportWorker-0AGij1Ru.js → reportWorker-B9nWwSrB.js} +8501 -3393
  22. package/dist/assets/{scalar-sampling-budget-J5cuzxT1.js → scalar-sampling-budget-prBw_s8t.js} +6067 -3479
  23. package/dist/assets/{scanProxyWorker-Vl4Wxa1y.js → scanProxyWorker-2GtDLk-R.js} +1 -1
  24. package/dist/assets/{javascript-1kQXfVaz.js → typescript-DBQ6RN5l.js} +874 -22
  25. package/dist/cli/render.html +1 -1
  26. package/dist/docs/index.html +3 -3
  27. package/dist/docs-raw/AI/usage.md +1 -1
  28. package/dist/docs-raw/CLI.md +77 -240
  29. package/dist/docs-raw/README.md +6 -0
  30. package/dist/docs-raw/component-model.md +17 -150
  31. package/dist/docs-raw/generated/assembly.md +188 -582
  32. package/dist/docs-raw/generated/concepts.md +259 -3501
  33. package/dist/docs-raw/generated/core.md +283 -1250
  34. package/dist/docs-raw/generated/curves.md +387 -1608
  35. package/dist/docs-raw/generated/legacy.md +162 -0
  36. package/dist/docs-raw/generated/lib.md +227 -85
  37. package/dist/docs-raw/generated/output.md +35 -99
  38. package/dist/docs-raw/generated/runtime-names.md +23 -23
  39. package/dist/docs-raw/generated/sdf.md +68 -284
  40. package/dist/docs-raw/generated/sheet-metal.md +68 -335
  41. package/dist/docs-raw/generated/sketch.md +240 -1161
  42. package/dist/docs-raw/generated/viewport.md +75 -316
  43. package/dist/docs-raw/generated/wood.md +21 -49
  44. package/dist/docs-raw/guides/coordinate-system.md +4 -42
  45. package/dist/docs-raw/guides/inspection-bundles.md +44 -442
  46. package/dist/docs-raw/guides/joint-design.md +18 -79
  47. package/dist/docs-raw/guides/positioning.md +21 -143
  48. package/dist/docs-raw/guides/scene-presentation.md +89 -0
  49. package/dist/docs-raw/guides/simready-quickstart.md +171 -0
  50. package/dist/docs-raw/simulation-workflow.md +273 -0
  51. package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +25 -111
  52. package/dist/docs-raw/skills/forgecad-blockout-model.md +20 -117
  53. package/dist/docs-raw/skills/forgecad-component-model.md +23 -107
  54. package/dist/docs-raw/skills/forgecad-high-level-spec.md +47 -155
  55. package/dist/docs-raw/skills/forgecad-image-replicator.md +26 -143
  56. package/dist/docs-raw/skills/forgecad-lld.md +19 -113
  57. package/dist/docs-raw/skills/forgecad-make-a-model.md +112 -532
  58. package/dist/docs-raw/skills/forgecad-model-grader.md +38 -108
  59. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +24 -211
  60. package/dist/docs-raw/skills/forgecad-project.md +13 -131
  61. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +42 -134
  62. package/dist/docs-raw/skills/forgecad-render-inspect.md +27 -174
  63. package/dist/docs-raw/skills/forgecad-visual-spec.md +32 -112
  64. package/dist/docs-raw/skills/forgecad.md +19 -18
  65. package/dist/docs-raw/skills/index.md +2 -0
  66. package/dist/docs-raw/welcome.md +2 -2
  67. package/dist/index.html +2 -2
  68. package/dist/llms.txt +1 -2
  69. package/dist/sitemap.xml +25 -13
  70. package/dist-cli/{check-compiler-SYQ2PWOB.js → check-compiler-II7NLPAB.js} +1 -1
  71. package/dist-cli/{check-query-propagation-HIAGV62W.js → check-query-propagation-7462TR3R.js} +1 -1
  72. package/dist-cli/{chunk-SPZE3DUY.js → chunk-UWTJCGXF.js} +5848 -2915
  73. package/dist-cli/forgecad.js +3496 -703
  74. package/dist-skill/CONTEXT.md +1797 -7963
  75. package/dist-skill/SKILL.md +15 -15
  76. package/dist-skill/docs/API/core/concepts.md +27 -157
  77. package/dist-skill/docs/CLI.md +77 -240
  78. package/dist-skill/docs/generated/assembly.md +182 -532
  79. package/dist-skill/docs/generated/core.md +283 -1250
  80. package/dist-skill/docs/generated/curves.md +387 -1609
  81. package/dist-skill/docs/generated/lib.md +227 -85
  82. package/dist-skill/docs/generated/output.md +35 -99
  83. package/dist-skill/docs/generated/runtime-names.md +16 -21
  84. package/dist-skill/docs/generated/sdf.md +68 -284
  85. package/dist-skill/docs/generated/sheet-metal.md +68 -335
  86. package/dist-skill/docs/generated/sketch.md +240 -1160
  87. package/dist-skill/docs/generated/viewport.md +75 -223
  88. package/dist-skill/docs/generated/wood.md +21 -49
  89. package/dist-skill/docs/guides/coordinate-system.md +4 -42
  90. package/dist-skill/docs/guides/inspection-bundles.md +44 -442
  91. package/dist-skill/docs/guides/joint-design.md +18 -79
  92. package/dist-skill/docs/guides/positioning.md +21 -143
  93. package/dist-skill/docs/guides/scene-presentation.md +89 -0
  94. package/dist-skill/docs/guides/surface-members.md +26 -0
  95. package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +23 -111
  96. package/dist-skill/library/forgecad-blockout-model/SKILL.md +18 -117
  97. package/dist-skill/library/forgecad-component-model/SKILL.md +21 -107
  98. package/dist-skill/library/forgecad-high-level-spec/SKILL.md +45 -155
  99. package/dist-skill/library/forgecad-image-replicator/SKILL.md +24 -143
  100. package/dist-skill/library/forgecad-lld/SKILL.md +17 -113
  101. package/dist-skill/library/forgecad-make-a-model/SKILL.md +110 -532
  102. package/dist-skill/library/forgecad-model-grader/SKILL.md +36 -108
  103. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +35 -224
  104. package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +43 -271
  105. package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +30 -99
  106. package/dist-skill/library/forgecad-project/SKILL.md +13 -133
  107. package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +29 -123
  108. package/dist-skill/library/forgecad-render-inspect/SKILL.md +25 -174
  109. package/dist-skill/library/forgecad-visual-spec/SKILL.md +30 -111
  110. package/dist-skill/website/skills/forgecad-3d-reconstruction.md +58 -0
  111. package/dist-skill/website/skills/forgecad-blockout-model.md +49 -0
  112. package/dist-skill/website/skills/forgecad-component-model.md +53 -0
  113. package/dist-skill/website/skills/forgecad-high-level-spec.md +101 -0
  114. package/dist-skill/website/skills/forgecad-image-replicator.md +63 -0
  115. package/dist-skill/website/skills/forgecad-lld.md +41 -0
  116. package/dist-skill/website/skills/forgecad-make-a-model.md +186 -0
  117. package/dist-skill/website/skills/forgecad-model-grader.md +82 -0
  118. package/dist-skill/website/skills/forgecad-prepare-prompt.md +63 -0
  119. package/dist-skill/website/skills/forgecad-project.md +26 -0
  120. package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +60 -0
  121. package/dist-skill/website/skills/forgecad-render-inspect.md +80 -0
  122. package/dist-skill/website/skills/forgecad-visual-spec.md +71 -0
  123. package/dist-skill/website/skills/forgecad.md +122 -0
  124. package/dist-skill/website/skills/index.md +26 -0
  125. package/examples/api/comparison-imported-sphere-candidate.forge.js +1 -1
  126. package/examples/api/conformal-product-ribbon.forge.js +1 -1
  127. package/examples/api/exact-sheet-shell-assembly.forge.js +1 -1
  128. package/examples/api/extrude-options.forge.js +4 -2
  129. package/examples/api/field-loft-drive-tip.forge.js +40 -0
  130. package/examples/api/guided-loft-olive-oil-bottle.forge.js +1 -1
  131. package/examples/api/highlight-debug.forge.js +10 -10
  132. package/examples/api/mesh-import-slats.forge.js +1 -1
  133. package/examples/api/real-product-curves.forge.js +1 -1
  134. package/examples/api/sculpt-box-circle-booleans.forge.js +1 -1
  135. package/examples/api/sdf-shapes.forge.js +2 -5
  136. package/examples/api/sketch-rounding-strategies.forge.js +6 -6
  137. package/examples/api/surface-member-bottle-cage.forge.js +3 -3
  138. package/examples/api/surface-member-conformal-product-ribbon.forge.js +3 -3
  139. package/examples/api/surface-member-razor-inlay.forge.js +1 -1
  140. package/examples/api/variable-sweep-test.forge.js +3 -3
  141. package/examples/mechanical/airplane-propeller.forge.js +74 -39
  142. package/examples/nurbs-surface.forge.js +1 -1
  143. package/examples/products/iphone.forge.js +1 -1
  144. package/examples/robotics/README.md +46 -0
  145. package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
  146. package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
  147. package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
  148. package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
  149. package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
  150. package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
  151. package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
  152. package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
  153. package/examples/robotics/simready-asset-crate.forge.js +79 -0
  154. package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
  155. package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
  156. package/package.json +1 -1
  157. package/dist/assets/EditorApp-BHMQlJ-D.js +0 -14686
  158. package/dist/docs-raw/guides/geometry-conventions.md +0 -52
  159. package/dist/docs-raw/guides/modeling-recipes.md +0 -78
  160. package/dist-skill/docs/guides/geometry-conventions.md +0 -52
  161. package/dist-skill/docs/guides/modeling-recipes.md +0 -78
  162. package/dist-skill/library/forgecad-visual-spec/references/prompt-template.md +0 -79
@@ -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 ZEBRA_STRIPE_SOFTNESS, bC as ZEBRA_STRIPE_SCALE, bD as ZEBRA_LIGHT_COLOR, bE as ZEBRA_DARK_COLOR, bF as ZEBRA_ACCENT_COLOR, bG as ZEBRA_STRIPE_FRAGMENT_SHADER, bH as ZEBRA_STRIPE_VERTEX_SHADER, bI as SCAN_PROXY_LAYER_STYLES, bJ as SURFACE_FIELD_FRAGMENT_SHADER, bK as SURFACE_FIELD_VERTEX_SHADER, bL as WORLD_UP, bM as CatmullRomCurve3, bN as TubeGeometry, bO as THICKNESS_GRADIENT_COLORS, bP as ROUGHNESS_COLORS, bQ as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bR as PERFORMANCE_SAMPLE_INTERVAL_SEC, bS as formatPerformanceCount, bT as NON_TEXT_INPUT_TYPES, bU as MeshStandardMaterial, bV as compileSdfNode3, bW as buildSdfRaymarchFragmentShader, bX as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bY as Shape, bZ as ShapeGeometry, b_ as ShaderLib, b$ as CylinderGeometry, c0 as VIEWPORT_CAMERA_STORAGE_KEY, c1 as parseViewportCameraState, c2 as createResolvedExplodeConfig, c3 as explodeBoundsCenter, c4 as explodeMergeBounds, c5 as resolveExplodeDirective, c6 as computeExplodeMotion, c7 as getSketchWorldMatrix, c8 as explodeAdd, c9 as hasExplodeOverride, ca as resolveExplodeLocalFanDirection, cb as explodeMul, cc as explodeLeafFanStage, cd as normalizeCutPlane, ce as toClippingPlane, cf as isObjectExcludedFromCutPlane, cg as getShapePorts, ch as getShapeUsedPorts, ci as DEFAULT_VIEW_CONFIG, cj as SECTION_EXPLORER_PLANE_NAME, ck as ZERO_OFFSET, cl as scanProxyGridForBounds, cm as IDENTITY_MATRIX$2, cn as OBJECT_CONTEXT_MENU_MARGIN, co as OBJECT_CONTEXT_MENU_WIDTH, cp as OBJECT_CONTEXT_MENU_HEIGHT, cq as getKernelFaceNameForTriangle, cr as triangleSoupFromMeshes, cs as compareTriangleSoups, ct as buildGeometryComparisonPointCloud, cu as aabbOverlaps, cv as aabbOverlapVolume, cw as DEFAULT_COLLISION_INSPECTION_OPTIONS, cx as resolveScalarSceneSampleBudget, cy as FOCUS_MODE_DIM_OPACITY, cz as comparisonCandidateContextOpacity, cA as Matrix3, cB as initKernel, cC as initSolverWasm } from "./scalar-sampling-budget-J5cuzxT1.js";
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-prBw_s8t.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];
@@ -296,6 +296,8 @@ function lookupCache(filePath, code, files, paramOverrides, assemblyState, quali
296
296
  if (!entry) return null;
297
297
  if (entry.code !== code || entry.quality !== quality || JSON.stringify(entry.paramOverrides) !== JSON.stringify(paramOverrides) || JSON.stringify(entry.assemblyState) !== JSON.stringify(assemblyState) || JSON.stringify(entry.files) !== JSON.stringify(files))
298
298
  return null;
299
+ runResultCache.delete(key);
300
+ runResultCache.set(key, entry);
299
301
  return entry.result;
300
302
  }
301
303
  function storeCache(filePath, code, files, paramOverrides, assemblyState, quality, backend, result, serialized) {
@@ -15583,7 +15585,7 @@ const movePath = (value, from, to) => {
15583
15585
  if (value.startsWith(`${from}/`)) return `${to}${value.slice(from.length)}`;
15584
15586
  return value;
15585
15587
  };
15586
- const FORGE_IMPORT_RE = /\b(?:importMesh|importStep|importSvgSketch|Import\.dxfSketch|compareWith)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
15588
+ const FORGE_IMPORT_RE = /\b(?:importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch|compareWith)\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
15587
15589
  const REQUIRE_RE = /\brequire\s*\(\s*(?:"([^"]+)"|'([^']+)')/g;
15588
15590
  const ES_IMPORT_RE = /\bfrom\s+(?:"([^"]+)"|'([^']+)')/g;
15589
15591
  const VIRTUAL_MODULES = /* @__PURE__ */ new Set(["forgecad", "@forge/runtime", "@forgecad/runtime"]);
@@ -15601,6 +15603,9 @@ function extractImports(code) {
15601
15603
  importMesh: "forgeMesh",
15602
15604
  importStep: "forgeMesh",
15603
15605
  importSvgSketch: "forgeSvg",
15606
+ "Import.mesh": "forgeMesh",
15607
+ "Import.step": "forgeMesh",
15608
+ "Import.svgSketch": "forgeSvg",
15604
15609
  "Import.dxfSketch": "forgeDxf",
15605
15610
  compareWith: "compareWith"
15606
15611
  };
@@ -15608,7 +15613,9 @@ function extractImports(code) {
15608
15613
  const forgeRe = new RegExp(FORGE_IMPORT_RE.source, FORGE_IMPORT_RE.flags);
15609
15614
  while ((m2 = forgeRe.exec(code)) !== null) {
15610
15615
  const path = m2[1] ?? m2[2];
15611
- const fn = (_a3 = m2[0].match(/\b(importMesh|importStep|importSvgSketch|Import\.dxfSketch|compareWith)/)) == null ? void 0 : _a3[1];
15616
+ const fn = (_a3 = m2[0].match(
15617
+ /\b(importMesh|importStep|importSvgSketch|Import\.mesh|Import\.step|Import\.svgSketch|Import\.dxfSketch|compareWith)/
15618
+ )) == null ? void 0 : _a3[1];
15612
15619
  add(path, kindMap[fn] ?? "forgeMesh");
15613
15620
  }
15614
15621
  const reqRe = new RegExp(REQUIRE_RE.source, REQUIRE_RE.flags);
@@ -15746,7 +15753,7 @@ function computeServerSnapshot$1(state2, serverFiles, serverFolders, sharedModel
15746
15753
  const isMeshHash = startupHashFile && MESH_EXTS.some((ext) => startupHashFile.toLowerCase().endsWith(ext));
15747
15754
  const isBinaryHash = startupHashFile && BINARY_IMPORT_EXTS.some((ext) => startupHashFile.toLowerCase().endsWith(ext));
15748
15755
  const meshPreviewFile = isMeshHash && startupHashFile && nextFiles[startupHashFile] !== void 0 ? startupHashFile : null;
15749
- 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];
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] !== void 0 ? activeFile : findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] || "";
15750
15757
  const nextDirty = Object.keys(nextFiles).some((p2) => nextSaved[p2] !== nextFiles[p2]);
15751
15758
  const nextObjectSettingsByFile = Object.fromEntries(Object.entries(objectSettingsByFile).filter(([f2]) => f2 in nextFiles));
15752
15759
  return {
@@ -15791,7 +15798,7 @@ function computeServerFileDelete$1(state2, filename) {
15791
15798
  const newFolders = /* @__PURE__ */ new Set();
15792
15799
  Object.keys(nextFiles).forEach((p2) => collectParentPaths(p2).forEach((f2) => newFolders.add(f2)));
15793
15800
  const availableFiles = Object.keys(nextFiles);
15794
- const newActiveFile = activeFile === filename ? findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] : activeFile;
15801
+ const newActiveFile = activeFile === filename ? findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] || "" : activeFile;
15795
15802
  const nextObjectSettingsByFile = Object.fromEntries(Object.entries(objectSettingsByFile).filter(([f2]) => f2 in nextFiles));
15796
15803
  return {
15797
15804
  files: nextFiles,
@@ -15834,7 +15841,7 @@ const CRASH_COOLDOWN_MS = 2e3;
15834
15841
  class EvalWorkerClient {
15835
15842
  constructor(workerFactory = () => new Worker(new URL(
15836
15843
  /* @vite-ignore */
15837
- "/assets/evalWorker-vkx310U2.js",
15844
+ "/assets/evalWorker-CjZZWRWW.js",
15838
15845
  import.meta.url
15839
15846
  ), { type: "module" })) {
15840
15847
  __publicField(this, "worker", null);
@@ -16079,6 +16086,10 @@ class EvalWorkerClient {
16079
16086
  runIrPlan(options) {
16080
16087
  return this.startCancellableRun(options, "run-ir-plan");
16081
16088
  }
16089
+ clearCaches() {
16090
+ var _a3;
16091
+ (_a3 = this.worker) == null ? void 0 : _a3.postMessage({ type: "clear-caches" });
16092
+ }
16082
16093
  startCancellableRun(options, type) {
16083
16094
  if (this.shouldReplaceWorkerForSupersedingRun()) {
16084
16095
  this.replaceWorkerForSupersedingRun();
@@ -16271,7 +16282,91 @@ const removeParamSnapshotsForFile = (snapshotsByFile, file) => {
16271
16282
  delete next[file];
16272
16283
  return next;
16273
16284
  };
16274
- const IMPORTABLE_PROJECT_TEXT_FILE_EXTS = [".forge.js", ".js", ".svg", ".dxf"];
16285
+ const EDITABLE_PROJECT_TEXT_FILE_EXTS = [
16286
+ ".forge.js",
16287
+ ".sketch.js",
16288
+ ".js",
16289
+ ".mjs",
16290
+ ".cjs",
16291
+ ".jsx",
16292
+ ".ts",
16293
+ ".tsx",
16294
+ ".json",
16295
+ ".md",
16296
+ ".txt",
16297
+ ".svg",
16298
+ ".dxf",
16299
+ ".html",
16300
+ ".css",
16301
+ ".scss",
16302
+ ".yml",
16303
+ ".yaml",
16304
+ ".toml",
16305
+ ".xml",
16306
+ ".csv",
16307
+ ".ini"
16308
+ ];
16309
+ function hasProjectTextFileExtension(path) {
16310
+ const lower = path.toLowerCase();
16311
+ return EDITABLE_PROJECT_TEXT_FILE_EXTS.some((ext) => lower.endsWith(ext));
16312
+ }
16313
+ function projectTextFileTemplate(path) {
16314
+ const lower = path.toLowerCase();
16315
+ if (lower.endsWith(".svg")) {
16316
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
16317
+ <path d="M 10 10 L 90 10 L 90 90 L 10 90 Z" fill="none" stroke="#000" stroke-width="4" />
16318
+ </svg>
16319
+ `;
16320
+ }
16321
+ if (lower.endsWith(".dxf")) {
16322
+ return "0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n8\n0\n90\n4\n70\n1\n10\n0\n20\n0\n10\n100\n20\n0\n10\n100\n20\n60\n10\n0\n20\n60\n0\nENDSEC\n0\nEOF\n";
16323
+ }
16324
+ if (lower.endsWith(".forge.js") || lower.endsWith(".sketch.js")) {
16325
+ return "// 3D Part\n\nreturn box(50, 30, 10);\n";
16326
+ }
16327
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs")) {
16328
+ return "// Shared JS utilities for ForgeCAD.\n\nexport function exampleValue() {\n return 42;\n}\n";
16329
+ }
16330
+ if (lower.endsWith(".md")) return "# Notes\n\n";
16331
+ if (lower.endsWith(".json")) return "{\n \n}\n";
16332
+ return "";
16333
+ }
16334
+ function monacoLanguageForProjectFile(path) {
16335
+ if (!path) return "plaintext";
16336
+ const lower = path.toLowerCase();
16337
+ if (lower.endsWith(".md")) return "markdown";
16338
+ if (lower.endsWith(".json")) return "json";
16339
+ if (lower.endsWith(".ts") || lower.endsWith(".tsx")) return "typescript";
16340
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs") || lower.endsWith(".jsx")) return "javascript";
16341
+ if (lower.endsWith(".html")) return "html";
16342
+ if (lower.endsWith(".css") || lower.endsWith(".scss")) return "css";
16343
+ if (lower.endsWith(".svg") || lower.endsWith(".xml")) return "xml";
16344
+ if (lower.endsWith(".yml") || lower.endsWith(".yaml")) return "yaml";
16345
+ return "plaintext";
16346
+ }
16347
+ function highlightLanguageForProjectFile(path) {
16348
+ if (!path) return null;
16349
+ const lower = path.toLowerCase();
16350
+ if (lower.endsWith(".md")) return "markdown";
16351
+ if (lower.endsWith(".json")) return "json";
16352
+ if (lower.endsWith(".ts") || lower.endsWith(".tsx")) return "typescript";
16353
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs") || lower.endsWith(".jsx")) return "javascript";
16354
+ if (lower.endsWith(".html") || lower.endsWith(".svg") || lower.endsWith(".xml")) return "xml";
16355
+ if (lower.endsWith(".css") || lower.endsWith(".scss")) return "css";
16356
+ return null;
16357
+ }
16358
+ function projectTextFileMimeType(path) {
16359
+ const lower = path.toLowerCase();
16360
+ if (lower.endsWith(".svg")) return "image/svg+xml";
16361
+ if (lower.endsWith(".dxf")) return "application/dxf";
16362
+ if (lower.endsWith(".json")) return "application/json";
16363
+ if (lower.endsWith(".md")) return "text/markdown";
16364
+ if (lower.endsWith(".html")) return "text/html";
16365
+ if (lower.endsWith(".css") || lower.endsWith(".scss")) return "text/css";
16366
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs") || lower.endsWith(".jsx")) return "text/javascript";
16367
+ return "text/plain";
16368
+ }
16369
+ const IMPORTABLE_PROJECT_TEXT_FILE_EXTS = EDITABLE_PROJECT_TEXT_FILE_EXTS;
16275
16370
  const IMPORTABLE_PROJECT_MESH_FILE_EXTS = [".stl", ".obj", ".3mf"];
16276
16371
  const IMPORTABLE_PROJECT_EXACT_FILE_EXTS = [".step", ".stp"];
16277
16372
  const IMPORTABLE_PROJECT_BINARY_FILE_EXTS = [...IMPORTABLE_PROJECT_MESH_FILE_EXTS, ...IMPORTABLE_PROJECT_EXACT_FILE_EXTS];
@@ -16282,7 +16377,7 @@ function hasImportableExt(path, extensions) {
16282
16377
  return extensions.some((ext) => lower.endsWith(ext));
16283
16378
  }
16284
16379
  function isImportableProjectTextFile(path) {
16285
- return IMPORTABLE_PROJECT_TEXT_FILE_EXTS.some((ext) => path.endsWith(ext));
16380
+ return hasProjectTextFileExtension(path);
16286
16381
  }
16287
16382
  function isImportableProjectMeshFile(path) {
16288
16383
  return hasImportableExt(path, IMPORTABLE_PROJECT_MESH_FILE_EXTS);
@@ -16971,7 +17066,12 @@ const INITIAL_FOLDERS = collectInitialFolders(INITIAL_FILES);
16971
17066
  const getActiveFileFromHash = () => {
16972
17067
  const hash = window.location.hash.slice(1);
16973
17068
  if (hash.startsWith("code/") || hash.startsWith("bundle/")) return null;
16974
- return hash || null;
17069
+ if (!hash) return null;
17070
+ try {
17071
+ return decodeURIComponent(hash);
17072
+ } catch {
17073
+ return hash;
17074
+ }
16975
17075
  };
16976
17076
  const STARTUP_HASH_FILE = getActiveFileFromHash();
16977
17077
  const sharedModel = decodeSharedHash(window.location.hash);
@@ -16985,7 +17085,9 @@ if (sharedBundle) {
16985
17085
  }
16986
17086
  }
16987
17087
  const LAST_ACTIVE_FILE_KEY_BASE = "fc-last-active-file";
17088
+ const RECENT_FILES_KEY_BASE = "fc-recent-files";
16988
17089
  const lastActiveFileKey = () => userScopedKey(LAST_ACTIVE_FILE_KEY_BASE);
17090
+ const recentFilesKey = () => userScopedKey(RECENT_FILES_KEY_BASE);
16989
17091
  const readLastActiveFileForUser = (userId) => {
16990
17092
  try {
16991
17093
  return localStorage.getItem(`${LAST_ACTIVE_FILE_KEY_BASE}-${userId}`);
@@ -16993,6 +17095,25 @@ const readLastActiveFileForUser = (userId) => {
16993
17095
  return null;
16994
17096
  }
16995
17097
  };
17098
+ const readRecentFiles = () => {
17099
+ try {
17100
+ if (typeof localStorage === "undefined") return [];
17101
+ const raw = localStorage.getItem(recentFilesKey());
17102
+ if (!raw) return [];
17103
+ const parsed = JSON.parse(raw);
17104
+ if (!Array.isArray(parsed)) return [];
17105
+ return parsed.filter((value) => typeof value === "string" && value.length > 0);
17106
+ } catch {
17107
+ return [];
17108
+ }
17109
+ };
17110
+ const writeRecentFiles = (files) => {
17111
+ try {
17112
+ if (typeof localStorage === "undefined") return;
17113
+ localStorage.setItem(recentFilesKey(), JSON.stringify(files));
17114
+ } catch {
17115
+ }
17116
+ };
16996
17117
  const clampJointValue = (value, min, max2) => {
16997
17118
  let next = Number.isFinite(value) ? value : 0;
16998
17119
  if (min !== void 0) next = Math.max(min, next);
@@ -17256,18 +17377,8 @@ const resolveThicknessColorRange = (value) => {
17256
17377
  return DEFAULT_THICKNESS_COLOR_RANGE;
17257
17378
  }
17258
17379
  const raw = value;
17259
- const min = resolveClampedNumber(
17260
- raw.min,
17261
- DEFAULT_THICKNESS_COLOR_RANGE.min,
17262
- THICKNESS_COLOR_RANGE_MIN,
17263
- THICKNESS_COLOR_RANGE_MAX
17264
- );
17265
- const max2 = resolveClampedNumber(
17266
- raw.max,
17267
- DEFAULT_THICKNESS_COLOR_RANGE.max,
17268
- THICKNESS_COLOR_RANGE_MIN,
17269
- THICKNESS_COLOR_RANGE_MAX
17270
- );
17380
+ const min = resolveClampedNumber(raw.min, DEFAULT_THICKNESS_COLOR_RANGE.min, THICKNESS_COLOR_RANGE_MIN, THICKNESS_COLOR_RANGE_MAX);
17381
+ const max2 = resolveClampedNumber(raw.max, DEFAULT_THICKNESS_COLOR_RANGE.max, THICKNESS_COLOR_RANGE_MIN, THICKNESS_COLOR_RANGE_MAX);
17271
17382
  if (max2 - min >= THICKNESS_COLOR_RANGE_MIN_SPAN) return { min, max: max2 };
17272
17383
  const defaultSpan = DEFAULT_THICKNESS_COLOR_RANGE.max - DEFAULT_THICKNESS_COLOR_RANGE.min;
17273
17384
  const expandedMax = Math.min(THICKNESS_COLOR_RANGE_MAX, min + defaultSpan);
@@ -17419,9 +17530,50 @@ const initialActive = (() => {
17419
17530
  }
17420
17531
  const names = Object.keys(INITIAL_FILES);
17421
17532
  const fallback = findPreferredEntryFile(names) || names.find((n) => n.endsWith(".js")) || names[0];
17422
- return fallback;
17533
+ return fallback || "";
17423
17534
  })();
17424
17535
  const INITIAL_SAVED = {};
17536
+ const MAX_RECENT_FILES = 200;
17537
+ function touchRecentFile(recentFiles, files, file) {
17538
+ const availableFiles = new Set(Object.keys(files));
17539
+ const next = [];
17540
+ const seen = /* @__PURE__ */ new Set();
17541
+ if (file && availableFiles.has(file)) {
17542
+ next.push(file);
17543
+ seen.add(file);
17544
+ }
17545
+ for (const recentFile of recentFiles) {
17546
+ if (!availableFiles.has(recentFile) || seen.has(recentFile)) continue;
17547
+ next.push(recentFile);
17548
+ seen.add(recentFile);
17549
+ if (next.length >= MAX_RECENT_FILES) break;
17550
+ }
17551
+ return next;
17552
+ }
17553
+ function pruneRecentFiles(recentFiles, files) {
17554
+ return touchRecentFile(recentFiles, files, "");
17555
+ }
17556
+ function remapRecentFiles(recentFiles, files, oldPath, newPath) {
17557
+ const remapped = recentFiles.map((file) => movePath(file, oldPath, newPath));
17558
+ return pruneRecentFiles(remapped, files);
17559
+ }
17560
+ function persistRecentFiles(recentFiles) {
17561
+ writeRecentFiles(recentFiles);
17562
+ return recentFiles;
17563
+ }
17564
+ function touchAndPersistRecentFile(recentFiles, files, file) {
17565
+ return persistRecentFiles(touchRecentFile(recentFiles, files, file));
17566
+ }
17567
+ function pruneAndPersistRecentFiles(recentFiles, files) {
17568
+ return persistRecentFiles(pruneRecentFiles(recentFiles, files));
17569
+ }
17570
+ function remapAndPersistRecentFiles(recentFiles, files, oldPath, newPath) {
17571
+ return persistRecentFiles(remapRecentFiles(recentFiles, files, oldPath, newPath));
17572
+ }
17573
+ function readRecentFilesOrFallback(fallback) {
17574
+ const stored = readRecentFiles();
17575
+ return stored.length > 0 ? stored : fallback;
17576
+ }
17425
17577
  const VIEW_INSPECT_CHANNELS = /* @__PURE__ */ new Set([
17426
17578
  "none",
17427
17579
  "mask",
@@ -17437,6 +17589,9 @@ const VIEW_INSPECT_CHANNELS = /* @__PURE__ */ new Set([
17437
17589
  "roughness"
17438
17590
  ]);
17439
17591
  const INSPECT_DISPLAY_MODES = /* @__PURE__ */ new Set(["heatmap", "points", "both", "scan"]);
17592
+ const SAVE_PICKER_TEXT_FILE_EXTS = EDITABLE_PROJECT_TEXT_FILE_EXTS.filter(
17593
+ (ext) => ![".forge.js", ".sketch.js", ".js", ".svg", ".dxf", ".md"].includes(ext)
17594
+ );
17440
17595
  function resolveViewInspectChannel(value) {
17441
17596
  return typeof value === "string" && VIEW_INSPECT_CHANNELS.has(value) ? value : "none";
17442
17597
  }
@@ -17458,6 +17613,19 @@ function isScalarInspectChannel(channel) {
17458
17613
  function shouldUseScanRenderStyle(channel, displayMode) {
17459
17614
  return isScalarInspectChannel(channel) && displayMode === "scan";
17460
17615
  }
17616
+ function recordViewCommandDebug(command) {
17617
+ if (typeof window === "undefined") return;
17618
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
17619
+ const entry = {
17620
+ id: command.id,
17621
+ type: command.type,
17622
+ view: command.view,
17623
+ targetId: command.targetId,
17624
+ camera: command.camera
17625
+ };
17626
+ window.__forgecadViewCommandDebug = [...window.__forgecadViewCommandDebug ?? [], entry].slice(-50);
17627
+ console.debug("[forgecad view-command]", entry);
17628
+ }
17461
17629
  const initialViewPreferences = readViewPreferences();
17462
17630
  const initialInspectChannel = resolveInitialViewInspectChannel(initialViewPreferences.inspectChannel);
17463
17631
  const initialInspectDisplayMode = resolveInspectDisplayMode(initialViewPreferences.inspectDisplayMode);
@@ -17547,6 +17715,10 @@ function abortServerCompute() {
17547
17715
  function canUseServerCompute() {
17548
17716
  return true;
17549
17717
  }
17718
+ function canHaveProjectImports(filename) {
17719
+ const lower = filename.toLowerCase();
17720
+ return lower.endsWith(".forge.js") || lower.endsWith(".sketch.js") || lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs") || lower.endsWith(".jsx") || lower.endsWith(".ts") || lower.endsWith(".tsx");
17721
+ }
17550
17722
  const PARAM_DEBOUNCE_MS = navigator.maxTouchPoints > 0 && window.innerWidth < 768 ? 250 : 80;
17551
17723
  async function lazyLoadFileAndDeps(filename, get, set) {
17552
17724
  if (!fileSystem.fetchFileContent) return;
@@ -17569,6 +17741,7 @@ async function lazyLoadFileAndDeps(filename, get, set) {
17569
17741
  }
17570
17742
  }
17571
17743
  if (!content) continue;
17744
+ if (!canHaveProjectImports(file)) continue;
17572
17745
  for (const ref of extractImports(content)) {
17573
17746
  const resolved = resolveImportPath(file, ref.raw);
17574
17747
  const matchKey = Object.keys(storeFiles).find((k2) => k2 === resolved || k2.replace(/^\.\//, "") === resolved.replace(/^\.\//, ""));
@@ -17622,6 +17795,7 @@ const useForgeStore = create((set, get) => ({
17622
17795
  savedFiles: { ...INITIAL_SAVED },
17623
17796
  folders: [...INITIAL_FOLDERS],
17624
17797
  activeFile: initialActive,
17798
+ recentFiles: touchRecentFile(readRecentFiles(), INITIAL_FILES, initialActive),
17625
17799
  setActiveFile: (name) => {
17626
17800
  abortServerCompute();
17627
17801
  if (name) {
@@ -17645,6 +17819,7 @@ const useForgeStore = create((set, get) => ({
17645
17819
  const restored = targetPreviewFile && nextByFile[targetPreviewFile] ? nextByFile[targetPreviewFile] : {};
17646
17820
  set({
17647
17821
  activeFile: name,
17822
+ recentFiles: touchAndPersistRecentFile(get().recentFiles, files, name),
17648
17823
  meshPreviewFile: null,
17649
17824
  // Clear mesh preview when switching files
17650
17825
  lastValidResult: null,
@@ -17693,15 +17868,13 @@ const useForgeStore = create((set, get) => ({
17693
17868
  if (!normalized) return;
17694
17869
  if (get().files[normalized] !== void 0) return;
17695
17870
  if (get().folders.includes(normalized)) return;
17696
- const template = normalized.endsWith(".svg") ? `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
17697
- <path d="M 10 10 L 90 10 L 90 90 L 10 90 Z" fill="none" stroke="#000" stroke-width="4" />
17698
- </svg>
17699
- ` : normalized.endsWith(".dxf") ? "0\nSECTION\n2\nENTITIES\n0\nLWPOLYLINE\n8\n0\n90\n4\n70\n1\n10\n0\n20\n0\n10\n100\n20\n0\n10\n100\n20\n60\n10\n0\n20\n60\n0\nENDSEC\n0\nEOF\n" : normalized.endsWith(".forge.js") ? "// 3D Part\n\nreturn box(50, 30, 10);\n" : "// Shared JS utilities for ForgeCAD.\n\nexport function exampleValue() {\n return 42;\n}\n";
17871
+ const template = projectTextFileTemplate(normalized);
17700
17872
  const newFolders = Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort();
17701
17873
  set((s) => ({
17702
17874
  files: { ...s.files, [normalized]: template },
17703
17875
  savedFiles: { ...s.savedFiles, [normalized]: template },
17704
17876
  activeFile: normalized,
17877
+ recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: template }, normalized),
17705
17878
  paramOverrides: {},
17706
17879
  jointValues: {},
17707
17880
  jointAnimationClip: null,
@@ -17739,6 +17912,7 @@ const useForgeStore = create((set, get) => ({
17739
17912
  files: remaining,
17740
17913
  savedFiles: remainingSaved,
17741
17914
  activeFile: newActive,
17915
+ recentFiles: touchAndPersistRecentFile(get().recentFiles, remaining, newActive),
17742
17916
  objectSettingsByFile: nextObjectSettingsByFile,
17743
17917
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17744
17918
  paramOverrides: {},
@@ -17772,6 +17946,7 @@ const useForgeStore = create((set, get) => ({
17772
17946
  files: remaining,
17773
17947
  savedFiles: remainingSaved,
17774
17948
  activeFile: oldName === activeFile ? normalized : activeFile,
17949
+ recentFiles: remapAndPersistRecentFiles(get().recentFiles, remaining, oldName, normalized),
17775
17950
  objectSettingsByFile: nextObjectSettingsByFile,
17776
17951
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17777
17952
  folders: Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort()
@@ -17828,6 +18003,7 @@ const useForgeStore = create((set, get) => ({
17828
18003
  savedFiles: updatedSaved,
17829
18004
  folders: updatedFolders,
17830
18005
  activeFile: nextActive,
18006
+ recentFiles: remapAndPersistRecentFiles(get().recentFiles, updatedFiles, normalizedOld, normalizedNew),
17831
18007
  objectSettingsByFile: nextObjectSettingsByFile,
17832
18008
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17833
18009
  paramOverrides: {},
@@ -17850,22 +18026,29 @@ const useForgeStore = create((set, get) => ({
17850
18026
  const prefix = `${normalized}/`;
17851
18027
  const remainingFiles = {};
17852
18028
  const remainingSaved = {};
17853
- const deletedFiles = [];
18029
+ const deletedFiles = /* @__PURE__ */ new Set();
17854
18030
  for (const key of Object.keys(files)) {
17855
18031
  if (key.startsWith(prefix)) {
17856
- deletedFiles.push(key);
18032
+ deletedFiles.add(key);
17857
18033
  } else {
17858
18034
  remainingFiles[key] = files[key];
17859
18035
  }
17860
18036
  }
17861
18037
  for (const key of Object.keys(savedFiles)) {
17862
- if (!key.startsWith(prefix)) {
18038
+ if (key.startsWith(prefix)) {
18039
+ deletedFiles.add(key);
18040
+ } else {
17863
18041
  remainingSaved[key] = savedFiles[key];
17864
18042
  }
17865
18043
  }
17866
- if (Object.keys(remainingFiles).length === 0) return;
18044
+ const replacementFile = Object.keys(remainingFiles).length === 0 ? "main.forge.js" : null;
18045
+ if (replacementFile) {
18046
+ const template = projectTextFileTemplate(replacementFile);
18047
+ remainingFiles[replacementFile] = template;
18048
+ remainingSaved[replacementFile] = template;
18049
+ }
17867
18050
  const remainingFolders = folders.filter((f2) => f2 !== normalized && !f2.startsWith(prefix));
17868
- const newActive = (activeFile == null ? void 0 : activeFile.startsWith(prefix)) ? Object.keys(remainingFiles)[0] : activeFile;
18051
+ const newActive = (activeFile == null ? void 0 : activeFile.startsWith(prefix)) || !activeFile ? Object.keys(remainingFiles)[0] : activeFile;
17869
18052
  let nextObjectSettingsByFile = objectSettingsByFile;
17870
18053
  let nextParamSnapshotsByFile = paramSnapshotsByFile;
17871
18054
  for (const f2 of deletedFiles) {
@@ -17878,6 +18061,7 @@ const useForgeStore = create((set, get) => ({
17878
18061
  savedFiles: remainingSaved,
17879
18062
  folders: remainingFolders,
17880
18063
  activeFile: newActive,
18064
+ recentFiles: touchAndPersistRecentFile(get().recentFiles, remainingFiles, newActive),
17881
18065
  objectSettingsByFile: nextObjectSettingsByFile,
17882
18066
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17883
18067
  paramOverrides: {},
@@ -17887,7 +18071,7 @@ const useForgeStore = create((set, get) => ({
17887
18071
  jointAnimationPlaying: false,
17888
18072
  hoveredJointName: null
17889
18073
  });
17890
- Promise.all(deletedFiles.map((f2) => fileSystem.delete(f2))).then(() => fileSystem.deleteFolder(normalized)).catch((e2) => console.error("Delete folder failed:", e2));
18074
+ Promise.all(Array.from(deletedFiles).map((f2) => fileSystem.delete(f2))).then(() => replacementFile ? fileSystem.save(replacementFile, remainingFiles[replacementFile]) : void 0).then(() => fileSystem.deleteFolder(normalized)).catch((e2) => console.error("Delete folder failed:", e2));
17891
18075
  setTimeout(() => get().execute(), 0);
17892
18076
  },
17893
18077
  moveEntry: (oldPath, newPath) => {
@@ -17904,7 +18088,7 @@ const useForgeStore = create((set, get) => ({
17904
18088
  }
17905
18089
  },
17906
18090
  dirty: false,
17907
- filesLoading: true,
18091
+ filesLoading: false,
17908
18092
  fileHandle: null,
17909
18093
  setFileHandle: (fileHandle) => set({ fileHandle }),
17910
18094
  result: null,
@@ -18053,7 +18237,8 @@ const useForgeStore = create((set, get) => ({
18053
18237
  assemblyState,
18054
18238
  ...useServer ? {} : { assemblyDisplayMode: "instanceTransforms" },
18055
18239
  isNotebook: false,
18056
- activeBackend: executionBackend
18240
+ activeBackend: executionBackend,
18241
+ disableShapeBuildCache: get().disableRunCache
18057
18242
  };
18058
18243
  let serialized;
18059
18244
  if (useServer) {
@@ -18492,6 +18677,7 @@ const useForgeStore = create((set, get) => ({
18492
18677
  },
18493
18678
  gridEnabled: initialViewPreferences.gridEnabled ?? true,
18494
18679
  axesVisible: initialViewPreferences.axesVisible ?? true,
18680
+ viewCubeVisible: initialViewPreferences.viewCubeVisible ?? true,
18495
18681
  gridSize: initialViewPreferences.gridSize ?? 10,
18496
18682
  setGridEnabled: (enabled) => {
18497
18683
  writeViewPreferences({ gridEnabled: enabled });
@@ -18501,6 +18687,10 @@ const useForgeStore = create((set, get) => ({
18501
18687
  writeViewPreferences({ axesVisible: visible });
18502
18688
  set({ axesVisible: visible });
18503
18689
  },
18690
+ setViewCubeVisible: (visible) => {
18691
+ writeViewPreferences({ viewCubeVisible: visible });
18692
+ set({ viewCubeVisible: visible });
18693
+ },
18504
18694
  setGridSize: (size) => {
18505
18695
  writeViewPreferences({ gridSize: size });
18506
18696
  set({ gridSize: size });
@@ -18837,7 +19027,11 @@ const useForgeStore = create((set, get) => ({
18837
19027
  set({ objectPickSyncEnabled: enabled });
18838
19028
  },
18839
19029
  viewCommand: null,
18840
- requestViewCommand: (command) => set({ viewCommand: { ...command, id: Date.now() } }),
19030
+ requestViewCommand: (command) => {
19031
+ const viewCommand = { ...command, id: Date.now() };
19032
+ recordViewCommandDebug(viewCommand);
19033
+ set({ viewCommand });
19034
+ },
18841
19035
  clearViewCommand: () => set({ viewCommand: null }),
18842
19036
  viewportCameraState: null,
18843
19037
  setViewportCameraState: (state2) => set({ viewportCameraState: state2 }),
@@ -19037,6 +19231,7 @@ const useForgeStore = create((set, get) => ({
19037
19231
  savedFiles: { ...INITIAL_SAVED },
19038
19232
  folders: [],
19039
19233
  activeFile: initialActive,
19234
+ recentFiles: touchAndPersistRecentFile([], INITIAL_FILES, initialActive),
19040
19235
  previewFile: initialPreviewFile,
19041
19236
  objectSettings: getObjectSettingsForPreviewFile(initialObjectSettingsByFile, initialPreviewFile),
19042
19237
  objectSettingsByFile: initialObjectSettingsByFile,
@@ -19077,7 +19272,9 @@ const useForgeStore = create((set, get) => ({
19077
19272
  const handle = await window.showSaveFilePicker({
19078
19273
  suggestedName: activeFile,
19079
19274
  types: [
19080
- { description: "ForgeCAD scripts", accept: { "text/javascript": [".forge.js", ".js"] } },
19275
+ { description: "ForgeCAD scripts", accept: { "text/javascript": [".forge.js", ".sketch.js", ".js"] } },
19276
+ { description: "Text files", accept: { "text/plain": SAVE_PICKER_TEXT_FILE_EXTS } },
19277
+ { description: "Markdown", accept: { "text/markdown": [".md"] } },
19081
19278
  { description: "SVG", accept: { "image/svg+xml": [".svg"] } },
19082
19279
  { description: "DXF", accept: { "application/dxf": [".dxf"] } }
19083
19280
  ]
@@ -19092,7 +19289,7 @@ const useForgeStore = create((set, get) => ({
19092
19289
  // This assumes we are satisfied with current content being "saved"
19093
19290
  }));
19094
19291
  } else {
19095
- const mime = activeFile.endsWith(".svg") ? "image/svg+xml" : activeFile.endsWith(".dxf") ? "application/dxf" : "text/javascript";
19292
+ const mime = projectTextFileMimeType(activeFile);
19096
19293
  const blob = new Blob([code], { type: mime });
19097
19294
  triggerDownload(blob, activeFile);
19098
19295
  set((s) => ({
@@ -19111,6 +19308,7 @@ const useForgeStore = create((set, get) => ({
19111
19308
  files: { ...s.files, [normalized]: text },
19112
19309
  savedFiles: { ...s.savedFiles, [normalized]: text },
19113
19310
  activeFile: normalized,
19311
+ recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: text }, normalized),
19114
19312
  fileHandle: null,
19115
19313
  dirty: false,
19116
19314
  previewFile: null,
@@ -19169,6 +19367,7 @@ const useForgeStore = create((set, get) => ({
19169
19367
  return {
19170
19368
  files: nextFiles,
19171
19369
  activeFile: nextActive,
19370
+ recentFiles: touchAndPersistRecentFile(s.recentFiles, nextFiles, nextActive),
19172
19371
  fileHandle: null,
19173
19372
  dirty: true,
19174
19373
  previewFile: null,
@@ -19226,6 +19425,12 @@ const useForgeStore = create((set, get) => ({
19226
19425
  writeViewPreferences({ fileExplorerOpen: nextFileExplorerOpen });
19227
19426
  return { fileExplorerOpen: nextFileExplorerOpen };
19228
19427
  }),
19428
+ codeEditorOpen: initialViewPreferences.codeEditorOpen ?? true,
19429
+ toggleCodeEditor: () => set((s) => {
19430
+ const nextCodeEditorOpen = !s.codeEditorOpen;
19431
+ writeViewPreferences({ codeEditorOpen: nextCodeEditorOpen });
19432
+ return { codeEditorOpen: nextCodeEditorOpen };
19433
+ }),
19229
19434
  viewPanelOpen: initialViewPreferences.viewPanelOpen ?? true,
19230
19435
  toggleViewPanel: () => set((s) => {
19231
19436
  const nextViewPanelOpen = !s.viewPanelOpen;
@@ -19247,9 +19452,15 @@ const useForgeStore = create((set, get) => ({
19247
19452
  const prevFiles = state2.files;
19248
19453
  const prevActiveFile = state2.activeFile;
19249
19454
  const nextState = computeServerSnapshot(state2, serverFiles, serverFolders, sharedModel, sharedBundle, initialFile);
19250
- set({ ...nextState, filesLoading: false });
19251
19455
  const nextFiles = nextState.files;
19252
- const newActiveFile = nextState.activeFile;
19456
+ const newActiveFile = nextState.activeFile ?? state2.activeFile;
19457
+ const baseRecentFiles = readRecentFilesOrFallback(state2.recentFiles);
19458
+ set({
19459
+ ...nextState,
19460
+ activeFile: newActiveFile,
19461
+ recentFiles: touchAndPersistRecentFile(baseRecentFiles, nextFiles, newActiveFile),
19462
+ filesLoading: false
19463
+ });
19253
19464
  const needsLazyLoad = newActiveFile && newActiveFile in nextFiles && nextFiles[newActiveFile] === "" && fileSystem.fetchFileContent;
19254
19465
  postApplyServerSnapshot(
19255
19466
  prevActiveFile,
@@ -19270,7 +19481,7 @@ const useForgeStore = create((set, get) => ({
19270
19481
  const state2 = get();
19271
19482
  const nextState = computeServerFileChange(state2, filename, content);
19272
19483
  if (!nextState) return;
19273
- set(nextState);
19484
+ set({ ...nextState, recentFiles: pruneAndPersistRecentFiles(state2.recentFiles, nextState.files) });
19274
19485
  const previewFile = resolvePreviewFile(state2.activeFile, nextState.files);
19275
19486
  if (previewFile === filename) setTimeout(() => get().execute(), 0);
19276
19487
  },
@@ -19279,13 +19490,18 @@ const useForgeStore = create((set, get) => ({
19279
19490
  const prevActiveFile = state2.activeFile;
19280
19491
  const nextState = computeServerFileDelete(state2, filename);
19281
19492
  if (!nextState) return;
19282
- set(nextState);
19283
- if (nextState.activeFile && nextState.activeFile !== prevActiveFile) {
19493
+ const newActiveFile = nextState.activeFile ?? "";
19494
+ set({
19495
+ ...nextState,
19496
+ activeFile: newActiveFile,
19497
+ recentFiles: touchAndPersistRecentFile(state2.recentFiles, nextState.files, newActiveFile)
19498
+ });
19499
+ if (newActiveFile && newActiveFile !== prevActiveFile) {
19284
19500
  set({ paramOverrides: {}, lastValidResult: null });
19285
19501
  setParamOverrides({});
19286
- window.history.replaceState(null, "", `#${nextState.activeFile}`);
19502
+ window.history.replaceState(null, "", `#${newActiveFile}`);
19287
19503
  try {
19288
- localStorage.setItem(lastActiveFileKey(), nextState.activeFile);
19504
+ localStorage.setItem(lastActiveFileKey(), newActiveFile);
19289
19505
  } catch {
19290
19506
  }
19291
19507
  setTimeout(() => get().execute(), 0);
@@ -19312,19 +19528,28 @@ const useForgeStore = create((set, get) => ({
19312
19528
  shareOpen: false,
19313
19529
  openShare: () => set({ shareOpen: true }),
19314
19530
  closeShare: () => set({ shareOpen: false }),
19531
+ captureOpen: false,
19532
+ openCapture: () => set({ captureOpen: true }),
19533
+ closeCapture: () => set({ captureOpen: false }),
19315
19534
  aiSkillOpen: false,
19316
19535
  openAISkill: () => set({ aiSkillOpen: true }),
19317
19536
  closeAISkill: () => set({ aiSkillOpen: false }),
19318
19537
  editorNavigate: null,
19319
- requestEditorNavigate: (line) => set((s) => {
19320
- var _a3;
19321
- return { editorNavigate: { line, id: (((_a3 = s.editorNavigate) == null ? void 0 : _a3.id) ?? 0) + 1 } };
19322
- }),
19538
+ requestEditorNavigate: (line) => {
19539
+ writeViewPreferences({ codeEditorOpen: true });
19540
+ set((s) => {
19541
+ var _a3;
19542
+ return { codeEditorOpen: true, editorNavigate: { line, id: (((_a3 = s.editorNavigate) == null ? void 0 : _a3.id) ?? 0) + 1 } };
19543
+ });
19544
+ },
19323
19545
  clearEditorNavigate: () => set({ editorNavigate: null }),
19324
19546
  disableRunCache: initialViewPreferences.disableRunCache ?? false,
19325
19547
  setDisableRunCache: (disabled) => {
19326
19548
  writeViewPreferences({ disableRunCache: disabled });
19327
- if (disabled) clearRunResultCache();
19549
+ if (disabled) {
19550
+ clearRunResultCache();
19551
+ evalWorkerClient.clearCaches();
19552
+ }
19328
19553
  set({ disableRunCache: disabled });
19329
19554
  }
19330
19555
  }));
@@ -27435,7 +27660,7 @@ function ConstructionGhostOverlay({ matrix }) {
27435
27660
  class ConstructionHistoryWorkerClient {
27436
27661
  constructor(workerFactory = () => new Worker(new URL(
27437
27662
  /* @vite-ignore */
27438
- "/assets/constructionHistoryWorker-Ba2Hm58b.js",
27663
+ "/assets/constructionHistoryWorker-AwMMWSxg.js",
27439
27664
  import.meta.url
27440
27665
  ), { type: "module" })) {
27441
27666
  __publicField(this, "worker", null);
@@ -29591,14 +29816,89 @@ ensureNotFinalized_fn = function() {
29591
29816
  throw new Error("Cannot add new video or audio chunks after the file has been finalized.");
29592
29817
  }
29593
29818
  };
29819
+ const H264_HIGH_LEVEL_4_CODEC = "avc1.640028";
29820
+ const H264_LEVEL_4_MAX_CODED_AREA = 2097152;
29821
+ const H264_MACROBLOCK_SIZE = 16;
29594
29822
  function supportsMP4Recording() {
29595
29823
  return typeof VideoEncoder !== "undefined" && typeof VideoFrame !== "undefined";
29596
29824
  }
29825
+ function evenFloor(value) {
29826
+ return Math.max(2, Math.floor(value) & -2);
29827
+ }
29828
+ function codedDimension(value) {
29829
+ return Math.ceil(value / H264_MACROBLOCK_SIZE) * H264_MACROBLOCK_SIZE;
29830
+ }
29831
+ function codedArea(width, height) {
29832
+ return codedDimension(width) * codedDimension(height);
29833
+ }
29834
+ function resolveCanvasBackground(canvas) {
29835
+ const transparent = /* @__PURE__ */ new Set(["transparent", "rgba(0, 0, 0, 0)"]);
29836
+ let node = canvas;
29837
+ while (node) {
29838
+ const backgroundColor = getComputedStyle(node).backgroundColor;
29839
+ if (backgroundColor && !transparent.has(backgroundColor)) return backgroundColor;
29840
+ node = node.parentElement;
29841
+ }
29842
+ return "#252526";
29843
+ }
29844
+ function fitH264Level4RecordingSize(sourceWidth, sourceHeight) {
29845
+ if (!Number.isFinite(sourceWidth) || !Number.isFinite(sourceHeight) || sourceWidth <= 0 || sourceHeight <= 0) {
29846
+ throw new Error(`Cannot record MP4 from invalid canvas size ${sourceWidth}x${sourceHeight}.`);
29847
+ }
29848
+ const evenSourceWidth = evenFloor(sourceWidth);
29849
+ const evenSourceHeight = evenFloor(sourceHeight);
29850
+ let width = evenSourceWidth;
29851
+ let height = evenSourceHeight;
29852
+ if (codedArea(width, height) > H264_LEVEL_4_MAX_CODED_AREA) {
29853
+ let low = 0;
29854
+ let high = 1;
29855
+ let bestWidth = 2;
29856
+ let bestHeight = 2;
29857
+ for (let i = 0; i < 32; i += 1) {
29858
+ const scale = (low + high) / 2;
29859
+ const candidateWidth = evenFloor(evenSourceWidth * scale);
29860
+ const candidateHeight = evenFloor(evenSourceHeight * scale);
29861
+ if (codedArea(candidateWidth, candidateHeight) <= H264_LEVEL_4_MAX_CODED_AREA) {
29862
+ bestWidth = candidateWidth;
29863
+ bestHeight = candidateHeight;
29864
+ low = scale;
29865
+ } else {
29866
+ high = scale;
29867
+ }
29868
+ }
29869
+ width = bestWidth;
29870
+ height = bestHeight;
29871
+ }
29872
+ const codedWidth = codedDimension(width);
29873
+ const codedHeight = codedDimension(height);
29874
+ if (codedWidth * codedHeight > H264_LEVEL_4_MAX_CODED_AREA) {
29875
+ throw new Error(`Could not fit ${sourceWidth}x${sourceHeight} into the MP4 encoder limit.`);
29876
+ }
29877
+ return {
29878
+ sourceWidth: evenSourceWidth,
29879
+ sourceHeight: evenSourceHeight,
29880
+ width,
29881
+ height,
29882
+ codedWidth,
29883
+ codedHeight,
29884
+ scale: Math.min(width / evenSourceWidth, height / evenSourceHeight)
29885
+ };
29886
+ }
29887
+ async function requireSupportedEncoderConfig(config) {
29888
+ if (typeof VideoEncoder.isConfigSupported !== "function") return config;
29889
+ const support = await VideoEncoder.isConfigSupported(config);
29890
+ if (!support.supported) {
29891
+ throw new Error(`MP4 recording is not supported at ${config.width}x${config.height} in this browser.`);
29892
+ }
29893
+ return support.config ?? config;
29894
+ }
29597
29895
  class HistoryRecorder {
29598
29896
  constructor(canvas, options) {
29599
29897
  __publicField(this, "encoder", null);
29600
29898
  __publicField(this, "muxer", null);
29601
29899
  __publicField(this, "target", null);
29900
+ __publicField(this, "captureCanvas", null);
29901
+ __publicField(this, "captureContext", null);
29602
29902
  __publicField(this, "frameCount", 0);
29603
29903
  __publicField(this, "recording", false);
29604
29904
  __publicField(this, "startTime", 0);
@@ -29615,37 +29915,71 @@ class HistoryRecorder {
29615
29915
  /** Start recording. Must be called before captureFrame(). */
29616
29916
  async start() {
29617
29917
  if (this.recording) return;
29618
- const width = this.canvas.width & -2;
29619
- const height = this.canvas.height & -2;
29620
- this.target = new ArrayBufferTarget();
29621
- this.muxer = new Muxer({
29622
- target: this.target,
29623
- video: {
29624
- codec: "avc",
29625
- width,
29626
- height
29627
- },
29628
- fastStart: "in-memory",
29629
- firstTimestampBehavior: "offset"
29630
- });
29631
- this.encoder = new VideoEncoder({
29632
- output: (chunk, meta) => {
29633
- this.muxer.addVideoChunk(chunk, meta);
29634
- },
29635
- error: (e2) => console.error("VideoEncoder error:", e2)
29636
- });
29637
- this.encoder.configure({
29638
- codec: "avc1.640028",
29918
+ if (!supportsMP4Recording()) {
29919
+ throw new Error("MP4 recording requires WebCodecs VideoEncoder and VideoFrame support.");
29920
+ }
29921
+ const size = fitH264Level4RecordingSize(this.canvas.width, this.canvas.height);
29922
+ const config = await requireSupportedEncoderConfig({
29923
+ codec: H264_HIGH_LEVEL_4_CODEC,
29639
29924
  // H.264 High Profile Level 4.0
29640
- width,
29641
- height,
29925
+ width: size.width,
29926
+ height: size.height,
29642
29927
  bitrate: this.bitrate,
29643
29928
  framerate: this.fps
29644
29929
  });
29930
+ try {
29931
+ this.prepareCaptureSurface(size);
29932
+ this.target = new ArrayBufferTarget();
29933
+ this.muxer = new Muxer({
29934
+ target: this.target,
29935
+ video: {
29936
+ codec: "avc",
29937
+ width: size.width,
29938
+ height: size.height
29939
+ },
29940
+ fastStart: "in-memory",
29941
+ firstTimestampBehavior: "offset"
29942
+ });
29943
+ this.encoder = new VideoEncoder({
29944
+ output: (chunk, meta) => {
29945
+ this.muxer.addVideoChunk(chunk, meta);
29946
+ },
29947
+ error: (e2) => {
29948
+ this.recording = false;
29949
+ console.error("VideoEncoder error:", e2);
29950
+ }
29951
+ });
29952
+ this.encoder.configure(config);
29953
+ } catch (err) {
29954
+ this.abort();
29955
+ throw err;
29956
+ }
29645
29957
  this.frameCount = 0;
29646
29958
  this.startTime = performance.now();
29647
29959
  this.recording = true;
29648
29960
  }
29961
+ prepareCaptureSurface(size) {
29962
+ this.captureCanvas = null;
29963
+ this.captureContext = null;
29964
+ const captureCanvas = document.createElement("canvas");
29965
+ captureCanvas.width = size.width;
29966
+ captureCanvas.height = size.height;
29967
+ const captureContext = captureCanvas.getContext("2d", { alpha: false });
29968
+ if (!captureContext) {
29969
+ throw new Error("Could not create MP4 recording capture canvas.");
29970
+ }
29971
+ this.captureCanvas = captureCanvas;
29972
+ this.captureContext = captureContext;
29973
+ }
29974
+ frameSourceCanvas() {
29975
+ if (!this.captureCanvas || !this.captureContext) {
29976
+ throw new Error("MP4 recording capture surface is not initialized.");
29977
+ }
29978
+ this.captureContext.fillStyle = resolveCanvasBackground(this.canvas);
29979
+ this.captureContext.fillRect(0, 0, this.captureCanvas.width, this.captureCanvas.height);
29980
+ this.captureContext.drawImage(this.canvas, 0, 0, this.captureCanvas.width, this.captureCanvas.height);
29981
+ return this.captureCanvas;
29982
+ }
29649
29983
  /**
29650
29984
  * Capture the current canvas frame. Forces a render so the drawing buffer
29651
29985
  * has fresh content — this avoids needing `preserveDrawingBuffer: true` on
@@ -29676,7 +30010,11 @@ class HistoryRecorder {
29676
30010
  this.encodeFrame(timestampMicros);
29677
30011
  }
29678
30012
  encodeFrame(timestampMicros) {
29679
- const frame2 = new VideoFrame(this.canvas, {
30013
+ if (!this.encoder || this.encoder.state !== "configured") {
30014
+ this.recording = false;
30015
+ return;
30016
+ }
30017
+ const frame2 = new VideoFrame(this.frameSourceCanvas(), {
29680
30018
  timestamp: timestampMicros
29681
30019
  });
29682
30020
  const keyFrame = this.frameCount % (this.fps * 2) === 0;
@@ -29684,20 +30022,32 @@ class HistoryRecorder {
29684
30022
  frame2.close();
29685
30023
  this.frameCount++;
29686
30024
  }
30025
+ reset() {
30026
+ this.encoder = null;
30027
+ this.muxer = null;
30028
+ this.target = null;
30029
+ this.captureCanvas = null;
30030
+ this.captureContext = null;
30031
+ this.frameCount = 0;
30032
+ this.startTime = 0;
30033
+ }
29687
30034
  /** Stop recording and return the MP4 blob. */
29688
30035
  async stop() {
29689
30036
  if (!this.encoder || !this.muxer || !this.target) {
29690
30037
  throw new Error("Not recording");
29691
30038
  }
30039
+ const encoder2 = this.encoder;
30040
+ const muxer = this.muxer;
30041
+ const target = this.target;
29692
30042
  this.recording = false;
29693
- await this.encoder.flush();
29694
- this.encoder.close();
29695
- this.muxer.finalize();
29696
- const buffer = this.target.buffer;
29697
- this.encoder = null;
29698
- this.muxer = null;
29699
- this.target = null;
29700
- return new Blob([buffer], { type: "video/mp4" });
30043
+ try {
30044
+ await encoder2.flush();
30045
+ if (encoder2.state !== "closed") encoder2.close();
30046
+ muxer.finalize();
30047
+ return new Blob([target.buffer], { type: "video/mp4" });
30048
+ } finally {
30049
+ this.reset();
30050
+ }
29701
30051
  }
29702
30052
  /** Abort recording without producing output. */
29703
30053
  abort() {
@@ -29709,10 +30059,7 @@ class HistoryRecorder {
29709
30059
  } catch {
29710
30060
  }
29711
30061
  }
29712
- this.encoder = null;
29713
- this.muxer = null;
29714
- this.target = null;
29715
- this.frameCount = 0;
30062
+ this.reset();
29716
30063
  }
29717
30064
  }
29718
30065
  let registeredStart$2 = null;
@@ -29768,6 +30115,12 @@ function HistoryRecorderBridge() {
29768
30115
  requestAnimationFrame(() => {
29769
30116
  useForgeStore.getState().setHistoryPlaying(true);
29770
30117
  });
30118
+ }).catch((err) => {
30119
+ recorderRef.current = null;
30120
+ state2.setHistoryRecording(false);
30121
+ state2.setHistoryPlaying(false);
30122
+ showToast("Recording failed", "error");
30123
+ console.error("History recording failed:", err);
29771
30124
  });
29772
30125
  }, [gl]);
29773
30126
  const stop = reactExports.useCallback(async () => {
@@ -29819,7 +30172,7 @@ function generateReportInWorker(options) {
29819
30172
  return new Promise((resolve2, reject) => {
29820
30173
  const worker = new Worker(new URL(
29821
30174
  /* @vite-ignore */
29822
- "/assets/reportWorker-0AGij1Ru.js",
30175
+ "/assets/reportWorker-B9nWwSrB.js",
29823
30176
  import.meta.url
29824
30177
  ), { type: "module" });
29825
30178
  const cleanup = () => {
@@ -30502,9 +30855,13 @@ function buildSketchExportArtifacts(format, stem, runResult) {
30502
30855
  });
30503
30856
  }
30504
30857
  let orbitVideoExporter = null;
30858
+ let viewportImageExporter = null;
30505
30859
  function registerOrbitVideoExporter(exporter) {
30506
30860
  orbitVideoExporter = exporter;
30507
30861
  }
30862
+ function registerViewportImageExporter(exporter) {
30863
+ viewportImageExporter = exporter;
30864
+ }
30508
30865
  function rerunActiveScriptForQuality(quality) {
30509
30866
  const { files, activeFile, paramOverrides } = useForgeStore.getState();
30510
30867
  const assemblyState = resolveAssemblyStateForExport();
@@ -30630,6 +30987,23 @@ async function exportOrbitVideoFromStore(preferredStem, options) {
30630
30987
  const ext = blob.type === "video/mp4" ? "mp4" : "gif";
30631
30988
  triggerDownload(blob, `${stem}.orbit.${ext}`);
30632
30989
  }
30990
+ async function captureViewportImageBlobFromStore(options) {
30991
+ if (!viewportImageExporter) {
30992
+ throw new Error("Viewport is not ready for image capture. Try again in a moment.");
30993
+ }
30994
+ const { activeFile, theme } = useForgeStore.getState();
30995
+ return viewportImageExporter({
30996
+ title: deriveExportStem(activeFile),
30997
+ themeName: theme,
30998
+ ...options ?? {}
30999
+ });
31000
+ }
31001
+ async function exportViewportImageFromStore(preferredStem, options) {
31002
+ const { activeFile } = useForgeStore.getState();
31003
+ const stem = sanitizeExportStem(deriveExportStem(activeFile));
31004
+ const blob = await captureViewportImageBlobFromStore(options);
31005
+ triggerDownload(blob, `${stem}.png`);
31006
+ }
30633
31007
  function exportSketchFromStore(format, preferredStem) {
30634
31008
  const { result, activeFile } = useForgeStore.getState();
30635
31009
  const stem = sanitizeExportStem(preferredStem ?? deriveExportStem(activeFile));
@@ -30890,6 +31264,11 @@ function ViewportRecordingBridge({ controlsRef }) {
30890
31264
  frameIntervalRef.current = 1e3 / TARGET_FPS;
30891
31265
  void recorder.start().then(() => {
30892
31266
  useForgeStore.getState().setAnimationRecording(true);
31267
+ }).catch((err) => {
31268
+ recorderRef.current = null;
31269
+ useForgeStore.getState().setAnimationRecording(false);
31270
+ showToast("Recording failed", "error");
31271
+ console.error("Viewport recording failed:", err);
30893
31272
  });
30894
31273
  }, [gl, snapshotOrbitTarget]);
30895
31274
  const stop = reactExports.useCallback(async () => {
@@ -31725,6 +32104,14 @@ function TrajectoryRecorderBridge({ controlsRef }) {
31725
32104
  state2.setTrajectoryProgress(0);
31726
32105
  void recorder.start().then(() => {
31727
32106
  state2.setTrajectoryRecording(true);
32107
+ }).catch((err) => {
32108
+ recorderRef.current = null;
32109
+ trajectoryRef.current = null;
32110
+ lastFrameRef.current = 0;
32111
+ manualStartRef.current = 0;
32112
+ state2.setTrajectoryRecording(false);
32113
+ showToast("Recording failed", "error");
32114
+ console.error("Trajectory recording failed:", err);
31728
32115
  });
31729
32116
  }, [gl]);
31730
32117
  const stop = reactExports.useCallback(async () => {
@@ -32681,6 +33068,164 @@ function OrbitExporterBridge({ controlsRef }) {
32681
33068
  }, [exportOrbitVideo]);
32682
33069
  return null;
32683
33070
  }
33071
+ function clampDimension(value, fallback) {
33072
+ const numeric = Number.isFinite(value) ? value : fallback;
33073
+ return Math.max(96, Math.min(4096, Math.round(numeric ?? 96)));
33074
+ }
33075
+ function computeFrameLayout(width, height, includeFrame) {
33076
+ if (!includeFrame) return { x: 0, y: 0, width, height, headerHeight: 0, pad: 0 };
33077
+ const pad = Math.max(14, Math.round(Math.min(width, height) * 0.035));
33078
+ const headerHeight = Math.max(50, Math.min(96, Math.round(height * 0.105)));
33079
+ return {
33080
+ x: pad,
33081
+ y: headerHeight,
33082
+ width: Math.max(1, width - pad * 2),
33083
+ height: Math.max(1, height - headerHeight - pad),
33084
+ headerHeight,
33085
+ pad
33086
+ };
33087
+ }
33088
+ function cssVar(name, fallback) {
33089
+ if (typeof document === "undefined") return fallback;
33090
+ const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
33091
+ return value || fallback;
33092
+ }
33093
+ function canvasBlob(canvas) {
33094
+ return new Promise((resolve2, reject) => {
33095
+ canvas.toBlob((blob) => blob ? resolve2(blob) : reject(new Error("Could not encode PNG capture.")), "image/png");
33096
+ });
33097
+ }
33098
+ function drawCorner(ctx, x, y, dx, dy, size) {
33099
+ ctx.beginPath();
33100
+ ctx.moveTo(x, y + dy * size);
33101
+ ctx.lineTo(x, y);
33102
+ ctx.lineTo(x + dx * size, y);
33103
+ ctx.stroke();
33104
+ }
33105
+ function composeFrame(source, outputWidth, outputHeight, layout, options) {
33106
+ var _a3;
33107
+ const canvas = document.createElement("canvas");
33108
+ canvas.width = outputWidth;
33109
+ canvas.height = outputHeight;
33110
+ const ctx = canvas.getContext("2d");
33111
+ if (!ctx) throw new Error("Could not create PNG capture context.");
33112
+ const bg = cssVar("--fc-bg", "#02070a");
33113
+ const surface = cssVar("--fc-bgSurface", "#0a2028");
33114
+ const border = cssVar("--fc-border", "#165363");
33115
+ const accent = cssVar("--fc-accent", "#36e8ff");
33116
+ const text = cssVar("--fc-text", "#d9fbff");
33117
+ const muted = cssVar("--fc-textMuted", "#8ebdc6");
33118
+ const dim = cssVar("--fc-textDim", "#4e7078");
33119
+ const title = ((_a3 = options.title) == null ? void 0 : _a3.trim()) || "ForgeCAD model";
33120
+ const themeLabel = options.themeName ? `${options.themeName} theme` : "current theme";
33121
+ ctx.fillStyle = bg;
33122
+ ctx.fillRect(0, 0, outputWidth, outputHeight);
33123
+ ctx.fillStyle = surface;
33124
+ ctx.fillRect(0, 0, outputWidth, layout.headerHeight);
33125
+ ctx.strokeStyle = border;
33126
+ ctx.lineWidth = Math.max(1, Math.round(outputWidth / 900));
33127
+ ctx.strokeRect(layout.x - 1, layout.y - 1, layout.width + 2, layout.height + 2);
33128
+ ctx.drawImage(source, layout.x, layout.y, layout.width, layout.height);
33129
+ ctx.strokeStyle = accent;
33130
+ ctx.globalAlpha = 0.85;
33131
+ ctx.beginPath();
33132
+ ctx.moveTo(layout.pad, layout.headerHeight - 1);
33133
+ ctx.lineTo(outputWidth - layout.pad, layout.headerHeight - 1);
33134
+ ctx.stroke();
33135
+ const corner = Math.max(18, Math.min(42, Math.round(Math.min(outputWidth, outputHeight) * 0.035)));
33136
+ drawCorner(ctx, layout.pad, layout.y, 1, 1, corner);
33137
+ drawCorner(ctx, outputWidth - layout.pad, layout.y, -1, 1, corner);
33138
+ drawCorner(ctx, layout.pad, outputHeight - layout.pad, 1, -1, corner);
33139
+ drawCorner(ctx, outputWidth - layout.pad, outputHeight - layout.pad, -1, -1, corner);
33140
+ ctx.globalAlpha = 1;
33141
+ const mono = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
33142
+ ctx.fillStyle = accent;
33143
+ ctx.font = `700 ${Math.max(11, Math.round(outputHeight * 0.012))}px ${mono}`;
33144
+ ctx.fillText("FORGECAD CAPTURE", layout.pad, Math.round(layout.headerHeight * 0.35));
33145
+ ctx.fillStyle = text;
33146
+ ctx.font = `700 ${Math.max(16, Math.round(outputHeight * 0.021))}px Inter, system-ui, sans-serif`;
33147
+ ctx.fillText(title.slice(0, 64), layout.pad, Math.round(layout.headerHeight * 0.72));
33148
+ ctx.fillStyle = muted;
33149
+ ctx.font = `500 ${Math.max(11, Math.round(outputHeight * 0.012))}px ${mono}`;
33150
+ ctx.textAlign = "right";
33151
+ ctx.fillText(`${outputWidth}x${outputHeight}`, outputWidth - layout.pad, Math.round(layout.headerHeight * 0.35));
33152
+ ctx.fillStyle = dim;
33153
+ ctx.fillText(themeLabel, outputWidth - layout.pad, Math.round(layout.headerHeight * 0.72));
33154
+ ctx.textAlign = "left";
33155
+ return canvas;
33156
+ }
33157
+ function copyRenderedCanvas(source, width, height) {
33158
+ const canvas = document.createElement("canvas");
33159
+ canvas.width = width;
33160
+ canvas.height = height;
33161
+ const ctx = canvas.getContext("2d");
33162
+ if (!ctx) throw new Error("Could not create PNG capture context.");
33163
+ ctx.drawImage(source, 0, 0, width, height);
33164
+ return canvas;
33165
+ }
33166
+ function withCameraAspect(camera, width, height, render) {
33167
+ if (camera instanceof PerspectiveCamera$1) {
33168
+ const previousAspect = camera.aspect;
33169
+ camera.aspect = width / height;
33170
+ camera.updateProjectionMatrix();
33171
+ try {
33172
+ render();
33173
+ } finally {
33174
+ camera.aspect = previousAspect;
33175
+ camera.updateProjectionMatrix();
33176
+ }
33177
+ return;
33178
+ }
33179
+ if (camera instanceof OrthographicCamera$1) {
33180
+ const previous = { left: camera.left, right: camera.right, top: camera.top, bottom: camera.bottom };
33181
+ const midX = (camera.left + camera.right) / 2;
33182
+ const halfHeight = (camera.top - camera.bottom) / 2;
33183
+ const halfWidth = halfHeight * (width / height);
33184
+ camera.left = midX - halfWidth;
33185
+ camera.right = midX + halfWidth;
33186
+ camera.updateProjectionMatrix();
33187
+ try {
33188
+ render();
33189
+ } finally {
33190
+ Object.assign(camera, previous);
33191
+ camera.updateProjectionMatrix();
33192
+ }
33193
+ return;
33194
+ }
33195
+ render();
33196
+ }
33197
+ function ViewportImageCaptureBridge() {
33198
+ const { camera, gl, scene } = useThree();
33199
+ const capture = reactExports.useCallback(
33200
+ async (options) => {
33201
+ const currentSize = gl.getSize(new Vector2());
33202
+ const outputWidth = clampDimension(options == null ? void 0 : options.width, currentSize.x);
33203
+ const outputHeight = clampDimension(options == null ? void 0 : options.height, currentSize.y);
33204
+ const layout = computeFrameLayout(outputWidth, outputHeight, (options == null ? void 0 : options.includeFrame) ?? true);
33205
+ const previousSize = currentSize.clone();
33206
+ const previousPixelRatio = gl.getPixelRatio();
33207
+ try {
33208
+ await waitForAnimationFrame();
33209
+ gl.setPixelRatio(1);
33210
+ gl.setSize(layout.width, layout.height, false);
33211
+ withCameraAspect(camera, layout.width, layout.height, () => gl.render(scene, camera));
33212
+ const rendered = copyRenderedCanvas(gl.domElement, layout.width, layout.height);
33213
+ const finalCanvas = (options == null ? void 0 : options.includeFrame) === false ? rendered : composeFrame(rendered, outputWidth, outputHeight, layout, options ?? {});
33214
+ return await canvasBlob(finalCanvas);
33215
+ } finally {
33216
+ gl.setPixelRatio(previousPixelRatio);
33217
+ gl.setSize(previousSize.x, previousSize.y, false);
33218
+ gl.render(scene, camera);
33219
+ }
33220
+ },
33221
+ [camera, gl, scene]
33222
+ );
33223
+ reactExports.useEffect(() => {
33224
+ registerViewportImageExporter(capture);
33225
+ return () => registerViewportImageExporter(null);
33226
+ }, [capture]);
33227
+ return null;
33228
+ }
32684
33229
  const DEBUG_HL_DEFAULT_COLOR = "#ff00ff";
32685
33230
  const DEBUG_HL_DEFAULT_POINT_SIZE = 3;
32686
33231
  const DEBUG_HL_DEFAULT_PLANE_SIZE = 50;
@@ -33676,7 +34221,7 @@ function SectionCapPreview({
33676
34221
  class ScanProxyWorkerClient {
33677
34222
  constructor(workerFactory = () => new Worker(new URL(
33678
34223
  /* @vite-ignore */
33679
- "/assets/scanProxyWorker-Vl4Wxa1y.js",
34224
+ "/assets/scanProxyWorker-2GtDLk-R.js",
33680
34225
  import.meta.url
33681
34226
  ), { type: "module" })) {
33682
34227
  __publicField(this, "worker", null);
@@ -34191,20 +34736,34 @@ function ScanProxyLayer({
34191
34736
  fillOpacity,
34192
34737
  wireOpacity,
34193
34738
  clippingPlanes,
34194
- additiveWire,
34739
+ volumeFill = false,
34740
+ showWire = true,
34195
34741
  analysisGeometry
34196
34742
  }) {
34197
34743
  const fillGeometry = analysisGeometry ?? geometry;
34198
34744
  if (!geometry || !fillGeometry) return null;
34199
34745
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
34200
- /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: fillGeometry, raycast: () => null, children: analysisGeometry ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34746
+ /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: fillGeometry, raycast: () => null, children: volumeFill ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34201
34747
  "meshBasicMaterial",
34202
34748
  {
34203
- vertexColors: true,
34749
+ color: color2,
34204
34750
  transparent: true,
34205
- opacity: fillOpacity,
34751
+ opacity: Math.min(0.1, fillOpacity * 0.2),
34206
34752
  side: DoubleSide,
34207
34753
  depthWrite: false,
34754
+ blending: AdditiveBlending,
34755
+ toneMapped: false,
34756
+ clippingPlanes
34757
+ }
34758
+ ) : analysisGeometry ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34759
+ "meshBasicMaterial",
34760
+ {
34761
+ vertexColors: true,
34762
+ opacity: fillOpacity,
34763
+ side: DoubleSide,
34764
+ alphaHash: true,
34765
+ alphaToCoverage: true,
34766
+ depthWrite: true,
34208
34767
  toneMapped: false,
34209
34768
  clippingPlanes
34210
34769
  }
@@ -34212,41 +34771,40 @@ function ScanProxyLayer({
34212
34771
  "meshBasicMaterial",
34213
34772
  {
34214
34773
  color: color2,
34215
- transparent: true,
34216
34774
  opacity: fillOpacity,
34217
34775
  side: DoubleSide,
34218
- depthWrite: false,
34776
+ alphaHash: true,
34777
+ alphaToCoverage: true,
34778
+ depthWrite: true,
34219
34779
  toneMapped: false,
34220
34780
  clippingPlanes
34221
34781
  }
34222
34782
  ) }),
34223
- /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34783
+ showWire && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34224
34784
  "meshBasicMaterial",
34225
34785
  {
34226
34786
  color: color2,
34227
- transparent: true,
34228
34787
  opacity: wireOpacity,
34229
34788
  wireframe: true,
34230
34789
  side: DoubleSide,
34231
- depthWrite: false,
34232
- blending: additiveWire ? AdditiveBlending : NormalBlending,
34790
+ alphaHash: true,
34791
+ alphaToCoverage: true,
34792
+ depthWrite: true,
34233
34793
  toneMapped: false,
34234
34794
  clippingPlanes
34235
34795
  }
34236
34796
  ) })
34237
34797
  ] });
34238
34798
  }
34239
- function objectColorScanLayerStyles(color2) {
34240
- return SCAN_PROXY_LAYER_STYLES.map((layer) => ({ ...layer, color: color2 }));
34241
- }
34242
34799
  function ScanProxyVolume({
34243
34800
  proxy,
34244
34801
  clippingPlanes,
34245
34802
  objectColor,
34803
+ volumeFill = false,
34804
+ showWire = true,
34246
34805
  analysisGeometries
34247
34806
  }) {
34248
- const layers = objectColor ? objectColorScanLayerStyles(objectColor) : SCAN_PROXY_LAYER_STYLES;
34249
- const additiveWire = objectColor === void 0;
34807
+ const layers = reactExports.useMemo(() => objectColor ? scanMaterialLayerStyles(objectColor) : SCAN_PROXY_LAYER_STYLES, [objectColor]);
34250
34808
  return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { userData: { scanProxyTelemetry: proxy.telemetry }, children: layers.map((layer) => {
34251
34809
  const analysisGeometry = (analysisGeometries == null ? void 0 : analysisGeometries[layer.material]) ?? null;
34252
34810
  const fillOpacity = analysisGeometry ? Math.max(layer.fillOpacity, 0.78) : layer.fillOpacity;
@@ -34257,7 +34815,8 @@ function ScanProxyVolume({
34257
34815
  geometry: proxy.geometries[layer.material],
34258
34816
  analysisGeometry,
34259
34817
  clippingPlanes,
34260
- additiveWire,
34818
+ volumeFill,
34819
+ showWire,
34261
34820
  ...layer,
34262
34821
  fillOpacity,
34263
34822
  wireOpacity
@@ -34574,32 +35133,28 @@ function ForgeObject({
34574
35133
  ) }),
34575
35134
  edgesGeo && /* @__PURE__ */ jsxRuntimeExports.jsx("lineSegments", { geometry: edgesGeo, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx("lineBasicMaterial", { color: "#d7e1eb", transparent: true, opacity: 0.36, depthWrite: false, clippingPlanes: effectiveClippingPlanes }) })
34576
35135
  ] }),
34577
- showScanRenderStyle && (scanProxy ? /* @__PURE__ */ jsxRuntimeExports.jsx(ScanProxyVolume, { proxy: scanProxy, clippingPlanes: effectiveClippingPlanes, objectColor: settings.color }) : /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
34578
- showScanFallbackMesh && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34579
- "meshBasicMaterial",
34580
- {
34581
- color: settings.color,
34582
- transparent: true,
34583
- opacity: 0.14,
34584
- side: DoubleSide,
34585
- depthWrite: false,
34586
- blending: NormalBlending,
34587
- toneMapped: false,
34588
- clippingPlanes: effectiveClippingPlanes
34589
- }
34590
- ) }),
34591
- edgesGeo && /* @__PURE__ */ jsxRuntimeExports.jsx("lineSegments", { geometry: edgesGeo, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34592
- "lineBasicMaterial",
34593
- {
34594
- color: settings.color,
34595
- transparent: true,
34596
- opacity: 0.62,
34597
- depthWrite: false,
34598
- blending: NormalBlending,
34599
- clippingPlanes: effectiveClippingPlanes
34600
- }
34601
- ) })
34602
- ] })),
35136
+ showScanRenderStyle && (scanProxy ? /* @__PURE__ */ jsxRuntimeExports.jsx(
35137
+ ScanProxyVolume,
35138
+ {
35139
+ proxy: scanProxy,
35140
+ clippingPlanes: effectiveClippingPlanes,
35141
+ objectColor: settings.color,
35142
+ volumeFill: true,
35143
+ showWire: false
35144
+ }
35145
+ ) : showScanFallbackMesh && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35146
+ "meshBasicMaterial",
35147
+ {
35148
+ color: scanMaterialShellColor(settings.color),
35149
+ transparent: true,
35150
+ opacity: 0.08,
35151
+ side: DoubleSide,
35152
+ depthWrite: false,
35153
+ blending: AdditiveBlending,
35154
+ toneMapped: false,
35155
+ clippingPlanes: effectiveClippingPlanes
35156
+ }
35157
+ ) })),
34603
35158
  showMatrixRenderStyle && (scanProxy ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34604
35159
  MatrixGlyphVolume,
34605
35160
  {
@@ -40329,6 +40884,37 @@ function ZoomIndicatorPanel({ mmPerPx }) {
40329
40884
  }
40330
40885
  );
40331
40886
  }
40887
+ function roundDebugNumber(value) {
40888
+ return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : value;
40889
+ }
40890
+ function vectorTuple(vector) {
40891
+ return [roundDebugNumber(vector.x), roundDebugNumber(vector.y), roundDebugNumber(vector.z)];
40892
+ }
40893
+ function snapshotViewportCamera(camera, target) {
40894
+ const ortho = camera;
40895
+ const perspective = camera;
40896
+ const isOrtho = ortho.isOrthographicCamera === true;
40897
+ return {
40898
+ position: vectorTuple(camera.position),
40899
+ target: target ? vectorTuple(target) : null,
40900
+ up: vectorTuple(camera.up),
40901
+ projection: isOrtho ? "orthographic" : "perspective",
40902
+ fov: isOrtho ? void 0 : roundDebugNumber(perspective.fov),
40903
+ zoom: isOrtho ? roundDebugNumber(ortho.zoom) : void 0
40904
+ };
40905
+ }
40906
+ function recordViewportCameraDebug(entry) {
40907
+ if (typeof window === "undefined") return;
40908
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
40909
+ window.__forgecadCameraDebug = [...window.__forgecadCameraDebug ?? [], entry].slice(-50);
40910
+ console.debug("[forgecad camera]", entry);
40911
+ }
40912
+ function recordViewportCameraLifecycleDebug(entry) {
40913
+ if (typeof window === "undefined") return;
40914
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
40915
+ window.__forgecadCameraLifecycleDebug = [...window.__forgecadCameraLifecycleDebug ?? [], entry].slice(-50);
40916
+ console.debug("[forgecad view-controller]", entry);
40917
+ }
40332
40918
  const readPersistedViewportCameraState = () => {
40333
40919
  if (typeof window === "undefined") return null;
40334
40920
  try {
@@ -40352,19 +40938,117 @@ const resolveHoverObjectName = (name, knownFileNames) => {
40352
40938
  if (knownFileNames.has(trimmed)) return null;
40353
40939
  return trimmed;
40354
40940
  };
40941
+ const SNAP_VIEW_DIRECTIONS = {
40942
+ front: new Vector3(0, -1, 0),
40943
+ back: new Vector3(0, 1, 0),
40944
+ right: new Vector3(1, 0, 0),
40945
+ left: new Vector3(-1, 0, 0),
40946
+ top: new Vector3(0, 0, 1),
40947
+ bottom: new Vector3(0, 0, -1),
40948
+ iso: new Vector3(1, -1, 1)
40949
+ };
40950
+ const DEFAULT_CAMERA_UP = new Vector3(0, 0, 1);
40951
+ const SNAP_VIEW_UP = {
40952
+ top: new Vector3(0, 1, 0),
40953
+ bottom: new Vector3(0, -1, 0)
40954
+ };
40955
+ function isFiniteVector$1(vector) {
40956
+ return Number.isFinite(vector.x) && Number.isFinite(vector.y) && Number.isFinite(vector.z);
40957
+ }
40958
+ function computeBoundsFraming(bounds) {
40959
+ if (bounds.isEmpty()) return null;
40960
+ const target = new Vector3();
40961
+ bounds.getCenter(target);
40962
+ const size = new Vector3();
40963
+ bounds.getSize(size);
40964
+ if (!isFiniteVector$1(target) || !isFiniteVector$1(size)) return null;
40965
+ const reach = Math.max(size.x, size.y, size.z, 1);
40966
+ if (!Number.isFinite(reach) || reach <= 0) return null;
40967
+ return { target, size, reach };
40968
+ }
40969
+ function snapViewDirection(view2) {
40970
+ return SNAP_VIEW_DIRECTIONS[view2 ?? "iso"].clone().normalize();
40971
+ }
40972
+ function snapViewUp(view2) {
40973
+ return (SNAP_VIEW_UP[view2 ?? "iso"] ?? DEFAULT_CAMERA_UP).clone();
40974
+ }
40975
+ function computeSnapOrbitPose({
40976
+ position,
40977
+ target,
40978
+ up,
40979
+ view: view2
40980
+ }) {
40981
+ if (!isFiniteVector$1(position) || !isFiniteVector$1(target) || !isFiniteVector$1(up)) return null;
40982
+ const distance = position.distanceTo(target);
40983
+ if (!Number.isFinite(distance) || distance <= 1e-6) return null;
40984
+ const nextPosition = target.clone().add(snapViewDirection(view2).multiplyScalar(distance));
40985
+ if (!isFiniteVector$1(nextPosition)) return null;
40986
+ return {
40987
+ position: nextPosition,
40988
+ target: target.clone(),
40989
+ up: up.clone()
40990
+ };
40991
+ }
40992
+ function ActiveCameraBridge({ onCameraChange }) {
40993
+ const camera = useThree((state2) => state2.camera);
40994
+ reactExports.useEffect(() => {
40995
+ onCameraChange(camera);
40996
+ recordViewportCameraLifecycleDebug({
40997
+ path: "active-camera",
40998
+ snapshot: snapshotViewportCamera(camera, null)
40999
+ });
41000
+ }, [camera, onCameraChange]);
41001
+ reactExports.useEffect(() => {
41002
+ return () => {
41003
+ onCameraChange(null);
41004
+ recordViewportCameraLifecycleDebug({ path: "active-camera-cleared" });
41005
+ };
41006
+ }, [onCameraChange]);
41007
+ return null;
41008
+ }
40355
41009
  function ViewController({
41010
+ camera,
40356
41011
  controlsRef,
40357
- command,
41012
+ viewportRef,
40358
41013
  objects,
40359
41014
  objectMatrices,
40360
41015
  fallbackBounds,
40361
41016
  settings,
40362
- focusedObjectIds,
40363
- clearCommand
41017
+ focusedObjectIds
40364
41018
  }) {
40365
- const { camera, size } = useThree();
41019
+ const command = useForgeStore((s) => s.viewCommand);
41020
+ const clearCommand = useForgeStore((s) => s.clearViewCommand);
41021
+ const getActiveCamera = reactExports.useCallback(() => {
41022
+ var _a3;
41023
+ return camera ?? ((_a3 = controlsRef.current) == null ? void 0 : _a3.object) ?? null;
41024
+ }, [camera, controlsRef]);
41025
+ reactExports.useEffect(() => {
41026
+ recordViewportCameraLifecycleDebug({ path: "mounted" });
41027
+ return () => {
41028
+ recordViewportCameraLifecycleDebug({ path: "unmounted" });
41029
+ };
41030
+ }, []);
40366
41031
  reactExports.useEffect(() => {
41032
+ var _a3;
40367
41033
  if (!command) return;
41034
+ const activeCamera = getActiveCamera();
41035
+ if (!activeCamera) {
41036
+ recordViewportCameraLifecycleDebug({ path: "command-no-camera" });
41037
+ return;
41038
+ }
41039
+ recordViewportCameraLifecycleDebug({
41040
+ path: "command-change",
41041
+ snapshot: snapshotViewportCamera(activeCamera, ((_a3 = controlsRef.current) == null ? void 0 : _a3.target) ?? null)
41042
+ });
41043
+ }, [command, controlsRef, getActiveCamera]);
41044
+ reactExports.useEffect(() => {
41045
+ var _a3;
41046
+ if (!command) return;
41047
+ const camera2 = getActiveCamera();
41048
+ if (!camera2) {
41049
+ recordViewportCameraLifecycleDebug({ path: "command-no-camera-handler" });
41050
+ return;
41051
+ }
40368
41052
  if (command.type === "sceneCamera") return;
40369
41053
  if (command.type === "camera") {
40370
41054
  const state2 = command.camera;
@@ -40372,17 +41056,17 @@ function ViewController({
40372
41056
  clearCommand();
40373
41057
  return;
40374
41058
  }
40375
- camera.position.set(state2.position[0], state2.position[1], state2.position[2]);
40376
- camera.up.set(state2.up[0], state2.up[1], state2.up[2]);
40377
- if ("fov" in camera && state2.fov !== void 0) {
40378
- camera.fov = state2.fov;
41059
+ camera2.position.set(state2.position[0], state2.position[1], state2.position[2]);
41060
+ camera2.up.set(state2.up[0], state2.up[1], state2.up[2]);
41061
+ if ("fov" in camera2 && state2.fov !== void 0) {
41062
+ camera2.fov = state2.fov;
40379
41063
  }
40380
- if (camera.isOrthographicCamera && state2.orthoZoom !== void 0) {
40381
- camera.zoom = state2.orthoZoom;
41064
+ if (camera2.isOrthographicCamera && state2.orthoZoom !== void 0) {
41065
+ camera2.zoom = state2.orthoZoom;
40382
41066
  }
40383
- camera.lookAt(state2.target[0], state2.target[1], state2.target[2]);
40384
- if ("updateProjectionMatrix" in camera) {
40385
- camera.updateProjectionMatrix();
41067
+ camera2.lookAt(state2.target[0], state2.target[1], state2.target[2]);
41068
+ if ("updateProjectionMatrix" in camera2) {
41069
+ camera2.updateProjectionMatrix();
40386
41070
  }
40387
41071
  const controls2 = controlsRef.current;
40388
41072
  if (controls2) {
@@ -40392,9 +41076,43 @@ function ViewController({
40392
41076
  clearCommand();
40393
41077
  return;
40394
41078
  }
41079
+ const controls = controlsRef.current;
41080
+ const commandSnapshot = snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null);
41081
+ recordViewportCameraDebug({
41082
+ path: "received",
41083
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41084
+ before: commandSnapshot,
41085
+ after: commandSnapshot
41086
+ });
41087
+ if (command.type === "snap" && controls) {
41088
+ const before2 = commandSnapshot;
41089
+ const snapPose = computeSnapOrbitPose({
41090
+ position: camera2.position,
41091
+ target: controls.target,
41092
+ up: camera2.up,
41093
+ view: command.view
41094
+ });
41095
+ if (snapPose) {
41096
+ camera2.position.copy(snapPose.position);
41097
+ camera2.up.copy(snapPose.up);
41098
+ if ("updateProjectionMatrix" in camera2) {
41099
+ camera2.updateProjectionMatrix();
41100
+ }
41101
+ controls.target.copy(snapPose.target);
41102
+ controls.update();
41103
+ recordViewportCameraDebug({
41104
+ path: "snap-orbit",
41105
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41106
+ before: before2,
41107
+ after: snapshotViewportCamera(camera2, controls.target)
41108
+ });
41109
+ clearCommand();
41110
+ return;
41111
+ }
41112
+ }
40395
41113
  const visibleObjects = objects.filter((obj) => {
40396
- var _a3;
40397
- return ((_a3 = settings[obj.id]) == null ? void 0 : _a3.visible) !== false;
41114
+ var _a4;
41115
+ return ((_a4 = settings[obj.id]) == null ? void 0 : _a4.visible) !== false;
40398
41116
  });
40399
41117
  const focusedIdSet = new Set(focusedObjectIds);
40400
41118
  const focusedVisibleObjects = focusedIdSet.size > 0 ? visibleObjects.filter((obj) => focusedIdSet.has(obj.id)) : [];
@@ -40403,46 +41121,34 @@ function ViewController({
40403
41121
  const canUseFallbackBounds = !command.targetId && !useFocusedScope;
40404
41122
  const bounds = computeSceneBounds(targetObjects, objectMatrices) ?? (canUseFallbackBounds ? fallbackBounds : null);
40405
41123
  if (!bounds) {
41124
+ recordViewportCameraDebug({
41125
+ path: "no-bounds",
41126
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41127
+ before: commandSnapshot,
41128
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41129
+ });
40406
41130
  clearCommand();
40407
41131
  return;
40408
41132
  }
40409
- const center = new Vector3();
40410
- bounds.getCenter(center);
40411
- const sizeVec = new Vector3();
40412
- bounds.getSize(sizeVec);
40413
- 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)) {
40414
- clearCommand();
40415
- return;
40416
- }
40417
- const maxDim = Math.max(sizeVec.x, sizeVec.y, sizeVec.z, 1);
40418
- const snapUsesScopedCenter = command.type === "snap" && useFocusedScope;
40419
- const target = command.type === "snap" && !snapUsesScopedCenter ? new Vector3(0, 0, 0) : center;
40420
- 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;
40421
- if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z) || !Number.isFinite(maxReach) || maxReach <= 0) {
41133
+ const framing = computeBoundsFraming(bounds);
41134
+ if (!framing) {
41135
+ recordViewportCameraDebug({
41136
+ path: "invalid-bounds",
41137
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41138
+ before: commandSnapshot,
41139
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41140
+ });
40422
41141
  clearCommand();
40423
41142
  return;
40424
41143
  }
40425
- const controls = controlsRef.current;
41144
+ const { target, reach: maxReach } = framing;
41145
+ const before = snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null);
40426
41146
  const camDir = new Vector3();
40427
41147
  if (command.type === "snap") {
40428
- const viewMap = {
40429
- front: new Vector3(0, -1, 0),
40430
- back: new Vector3(0, 1, 0),
40431
- right: new Vector3(1, 0, 0),
40432
- left: new Vector3(-1, 0, 0),
40433
- top: new Vector3(0, 0, 1),
40434
- bottom: new Vector3(0, 0, -1),
40435
- iso: new Vector3(1, -1, 1)
40436
- };
40437
- const upMap = {
40438
- top: new Vector3(0, 1, 0),
40439
- bottom: new Vector3(0, -1, 0)
40440
- };
40441
- camDir.copy(viewMap[command.view ?? "iso"]).normalize();
40442
- const up = upMap[command.view ?? ""] ?? new Vector3(0, 0, 1);
40443
- camera.up.copy(up);
41148
+ camDir.copy(snapViewDirection(command.view));
41149
+ camera2.up.copy(snapViewUp(command.view));
40444
41150
  } else if (controls) {
40445
- camDir.subVectors(camera.position, controls.target).normalize();
41151
+ camDir.subVectors(camera2.position, controls.target).normalize();
40446
41152
  if (camDir.lengthSq() === 0) camDir.set(1, 1, 1).normalize();
40447
41153
  } else {
40448
41154
  camDir.set(1, 1, 1).normalize();
@@ -40451,10 +41157,13 @@ function ViewController({
40451
41157
  clearCommand();
40452
41158
  return;
40453
41159
  }
40454
- const isOrtho = camera.isOrthographicCamera;
41160
+ const isOrtho = camera2.isOrthographicCamera;
41161
+ const viewportRect = (_a3 = viewportRef.current) == null ? void 0 : _a3.getBoundingClientRect();
41162
+ const viewportWidth = (viewportRect == null ? void 0 : viewportRect.width) ?? 800;
41163
+ const viewportHeight = (viewportRect == null ? void 0 : viewportRect.height) ?? 600;
40455
41164
  if (isOrtho) {
40456
- const ortho = camera;
40457
- const zoom = Math.min(size.width, size.height) / maxReach / 2.2;
41165
+ const ortho = camera2;
41166
+ const zoom = Math.min(viewportWidth, viewportHeight) / maxReach / 2.2;
40458
41167
  if (!Number.isFinite(zoom) || zoom <= 0) {
40459
41168
  clearCommand();
40460
41169
  return;
@@ -40468,7 +41177,7 @@ function ViewController({
40468
41177
  ortho.position.copy(nextPosition);
40469
41178
  ortho.updateProjectionMatrix();
40470
41179
  } else {
40471
- const persp = camera;
41180
+ const persp = camera2;
40472
41181
  const dist = maxReach / (2 * Math.tan(persp.fov * Math.PI / 360)) * 1.4;
40473
41182
  if (!Number.isFinite(dist) || dist <= 0) {
40474
41183
  clearCommand();
@@ -40486,21 +41195,26 @@ function ViewController({
40486
41195
  controls.target.copy(target);
40487
41196
  controls.update();
40488
41197
  } else {
40489
- camera.lookAt(target);
41198
+ camera2.lookAt(target);
40490
41199
  }
41200
+ recordViewportCameraDebug({
41201
+ path: command.type === "snap" ? "snap-framing-fallback" : "framing",
41202
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41203
+ before,
41204
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41205
+ });
40491
41206
  clearCommand();
40492
41207
  }, [
40493
- camera,
40494
41208
  clearCommand,
40495
41209
  command,
40496
41210
  controlsRef,
40497
41211
  fallbackBounds,
40498
41212
  focusedObjectIds,
41213
+ getActiveCamera,
40499
41214
  objectMatrices,
40500
41215
  objects,
40501
41216
  settings,
40502
- size.height,
40503
- size.width
41217
+ viewportRef
40504
41218
  ]);
40505
41219
  return null;
40506
41220
  }
@@ -40634,6 +41348,293 @@ function ViewPersistence({
40634
41348
  }, [camera, controlsRef, isSketchOnly, projectionMode, setViewportCameraState]);
40635
41349
  return null;
40636
41350
  }
41351
+ const CUBE_SIZE = 116;
41352
+ const CUBE_CENTER = CUBE_SIZE / 2;
41353
+ const CUBE_SCALE = 26;
41354
+ const DEFAULT_CAMERA_STATE = {
41355
+ position: [120, 80, 120],
41356
+ target: [0, 0, 0],
41357
+ up: [0, 0, 1]
41358
+ };
41359
+ const FACE_DEFS = [
41360
+ {
41361
+ view: "right",
41362
+ label: "RIGHT",
41363
+ normal: new Vector3(1, 0, 0),
41364
+ corners: [new Vector3(1, -1, -1), new Vector3(1, 1, -1), new Vector3(1, 1, 1), new Vector3(1, -1, 1)]
41365
+ },
41366
+ {
41367
+ view: "left",
41368
+ label: "LEFT",
41369
+ normal: new Vector3(-1, 0, 0),
41370
+ corners: [new Vector3(-1, 1, -1), new Vector3(-1, -1, -1), new Vector3(-1, -1, 1), new Vector3(-1, 1, 1)]
41371
+ },
41372
+ {
41373
+ view: "back",
41374
+ label: "BACK",
41375
+ normal: new Vector3(0, 1, 0),
41376
+ corners: [new Vector3(1, 1, -1), new Vector3(-1, 1, -1), new Vector3(-1, 1, 1), new Vector3(1, 1, 1)]
41377
+ },
41378
+ {
41379
+ view: "front",
41380
+ label: "FRONT",
41381
+ normal: new Vector3(0, -1, 0),
41382
+ corners: [new Vector3(-1, -1, -1), new Vector3(1, -1, -1), new Vector3(1, -1, 1), new Vector3(-1, -1, 1)]
41383
+ },
41384
+ {
41385
+ view: "top",
41386
+ label: "TOP",
41387
+ normal: new Vector3(0, 0, 1),
41388
+ corners: [new Vector3(-1, -1, 1), new Vector3(1, -1, 1), new Vector3(1, 1, 1), new Vector3(-1, 1, 1)]
41389
+ },
41390
+ {
41391
+ view: "bottom",
41392
+ label: "BOTTOM",
41393
+ normal: new Vector3(0, 0, -1),
41394
+ corners: [new Vector3(-1, 1, -1), new Vector3(1, 1, -1), new Vector3(1, -1, -1), new Vector3(-1, -1, -1)]
41395
+ }
41396
+ ];
41397
+ const SNAP_VIEWS = /* @__PURE__ */ new Set(["front", "back", "left", "right", "top", "bottom", "iso"]);
41398
+ function vectorFromTuple(tuple) {
41399
+ return new Vector3(tuple[0], tuple[1], tuple[2]);
41400
+ }
41401
+ function normalizedOr(vector, fallback) {
41402
+ return Number.isFinite(vector.x) && Number.isFinite(vector.y) && Number.isFinite(vector.z) && vector.lengthSq() > 1e-10 ? vector.clone().normalize() : fallback.clone().normalize();
41403
+ }
41404
+ function buildViewBasisFromCameraState(cameraState) {
41405
+ const position = vectorFromTuple(cameraState.position);
41406
+ const target = vectorFromTuple(cameraState.target);
41407
+ const up = normalizedOr(vectorFromTuple(cameraState.up), new Vector3(0, 0, 1));
41408
+ const viewer = normalizedOr(position.clone().sub(target), new Vector3(1, -1, 1));
41409
+ const forward = viewer.clone().negate();
41410
+ let right = forward.clone().cross(up);
41411
+ if (right.lengthSq() <= 1e-10) {
41412
+ right = Math.abs(forward.z) > 0.9 ? new Vector3(1, 0, 0) : forward.clone().cross(new Vector3(0, 0, 1));
41413
+ }
41414
+ right.normalize();
41415
+ const screenUp = normalizedOr(right.clone().cross(forward), new Vector3(0, 0, 1));
41416
+ return { viewer, right, screenUp };
41417
+ }
41418
+ const DEFAULT_VIEW_BASIS = buildViewBasisFromCameraState(DEFAULT_CAMERA_STATE);
41419
+ function buildViewBasisFromLiveCamera(camera) {
41420
+ camera.updateMatrixWorld();
41421
+ const quaternion = camera.getWorldQuaternion(new Quaternion());
41422
+ const viewer = normalizedOr(new Vector3(0, 0, 1).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.viewer);
41423
+ const right = normalizedOr(new Vector3(1, 0, 0).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.right);
41424
+ const screenUp = normalizedOr(new Vector3(0, 1, 0).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.screenUp);
41425
+ return { viewer, right, screenUp };
41426
+ }
41427
+ function faceFill(brightness) {
41428
+ const litPercent = Math.round(42 + brightness * 46);
41429
+ return `color-mix(in srgb, var(--fc-view-cube-face-lit) ${litPercent}%, var(--fc-view-cube-face-dim))`;
41430
+ }
41431
+ function projectVector(vector, right, screenUp, viewer) {
41432
+ return {
41433
+ x: CUBE_CENTER + vector.dot(right) * CUBE_SCALE,
41434
+ y: CUBE_CENTER - vector.dot(screenUp) * CUBE_SCALE,
41435
+ depth: vector.dot(viewer)
41436
+ };
41437
+ }
41438
+ function polygonPoints(points) {
41439
+ return points.map((point) => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(" ");
41440
+ }
41441
+ function snapViewFromEventTarget(target) {
41442
+ var _a3;
41443
+ if (!(target instanceof Element)) return null;
41444
+ const view2 = (_a3 = target.closest(".fc-view-cube-face")) == null ? void 0 : _a3.dataset.view;
41445
+ return view2 && SNAP_VIEWS.has(view2) ? view2 : null;
41446
+ }
41447
+ function cameraRightVector(camera) {
41448
+ return buildViewBasisFromLiveCamera(camera).right;
41449
+ }
41450
+ function rotateCameraFromDrag(camera, controls, drag, clientX, clientY) {
41451
+ const dx = clientX - drag.startX;
41452
+ const dy = clientY - drag.startY;
41453
+ if (Math.hypot(dx, dy) > 3) drag.moved = true;
41454
+ const sensitivity = 8e-3;
41455
+ const startOffset = drag.startPosition.clone().sub(drag.target);
41456
+ const yaw = new Quaternion().setFromAxisAngle(drag.up, -dx * sensitivity);
41457
+ const pitchAxis = drag.right.clone().applyQuaternion(yaw).normalize();
41458
+ const pitch = new Quaternion().setFromAxisAngle(pitchAxis, -dy * sensitivity);
41459
+ const nextOffset = startOffset.applyQuaternion(yaw).applyQuaternion(pitch);
41460
+ camera.position.copy(drag.target).add(nextOffset);
41461
+ camera.up.copy(drag.up);
41462
+ controls.target.copy(drag.target);
41463
+ if ("updateProjectionMatrix" in camera) {
41464
+ camera.updateProjectionMatrix();
41465
+ }
41466
+ controls.update();
41467
+ }
41468
+ function ViewCube({ camera, controlsRef, onSnap }) {
41469
+ const [viewBasis, setViewBasis] = reactExports.useState(null);
41470
+ const [dragging, setDragging] = reactExports.useState(false);
41471
+ const [pressedView, setPressedView] = reactExports.useState(null);
41472
+ const dragInfoRef = reactExports.useRef(null);
41473
+ const pressedTimeoutRef = reactExports.useRef(null);
41474
+ const getLiveCamera = reactExports.useCallback(() => {
41475
+ var _a3;
41476
+ return camera ?? ((_a3 = controlsRef.current) == null ? void 0 : _a3.object) ?? null;
41477
+ }, [camera, controlsRef]);
41478
+ const triggerSnap = reactExports.useCallback(
41479
+ (view2) => {
41480
+ setPressedView(view2);
41481
+ if (pressedTimeoutRef.current !== null) {
41482
+ window.clearTimeout(pressedTimeoutRef.current);
41483
+ }
41484
+ pressedTimeoutRef.current = window.setTimeout(() => {
41485
+ pressedTimeoutRef.current = null;
41486
+ setPressedView(null);
41487
+ }, 180);
41488
+ onSnap(view2);
41489
+ },
41490
+ [onSnap]
41491
+ );
41492
+ const syncCameraState = reactExports.useCallback(() => {
41493
+ const controls = controlsRef.current;
41494
+ const liveCamera = getLiveCamera();
41495
+ if (!liveCamera || !controls) return;
41496
+ setViewBasis(buildViewBasisFromLiveCamera(liveCamera));
41497
+ }, [controlsRef, getLiveCamera]);
41498
+ reactExports.useEffect(() => {
41499
+ let disposed = false;
41500
+ let retryFrame = 0;
41501
+ let controls = null;
41502
+ const attach2 = () => {
41503
+ if (disposed) return;
41504
+ controls = controlsRef.current;
41505
+ if (!getLiveCamera() || !controls) {
41506
+ retryFrame = window.requestAnimationFrame(attach2);
41507
+ return;
41508
+ }
41509
+ syncCameraState();
41510
+ controls.addEventListener("change", syncCameraState);
41511
+ };
41512
+ attach2();
41513
+ return () => {
41514
+ disposed = true;
41515
+ if (retryFrame) window.cancelAnimationFrame(retryFrame);
41516
+ controls == null ? void 0 : controls.removeEventListener("change", syncCameraState);
41517
+ };
41518
+ }, [controlsRef, getLiveCamera, syncCameraState]);
41519
+ reactExports.useEffect(() => {
41520
+ return () => {
41521
+ if (pressedTimeoutRef.current !== null) {
41522
+ window.clearTimeout(pressedTimeoutRef.current);
41523
+ }
41524
+ };
41525
+ }, []);
41526
+ const faces = reactExports.useMemo(() => {
41527
+ const { viewer, right, screenUp } = viewBasis ?? DEFAULT_VIEW_BASIS;
41528
+ const projectedFaces = FACE_DEFS.map((face) => {
41529
+ const visibility = face.normal.dot(viewer);
41530
+ if (visibility <= 0.02) return null;
41531
+ const points = face.corners.map((corner) => projectVector(corner, right, screenUp, viewer));
41532
+ const center = projectVector(face.normal, right, screenUp, viewer);
41533
+ const depth = points.reduce((sum, point) => sum + point.depth, 0) / points.length;
41534
+ return {
41535
+ view: face.view,
41536
+ label: face.label,
41537
+ points,
41538
+ center,
41539
+ depth,
41540
+ brightness: MathUtils.clamp(visibility, 0, 1)
41541
+ };
41542
+ }).filter((face) => face !== null).sort((a2, b2) => a2.depth - b2.depth);
41543
+ return projectedFaces;
41544
+ }, [viewBasis]);
41545
+ const handlePointerDown = reactExports.useCallback(
41546
+ (event) => {
41547
+ if (event.button !== 0) return;
41548
+ const liveCamera = getLiveCamera();
41549
+ const clickView = snapViewFromEventTarget(event.target);
41550
+ const controls = controlsRef.current;
41551
+ if (!clickView && (!liveCamera || !controls)) return;
41552
+ const target = (controls == null ? void 0 : controls.target.clone()) ?? new Vector3();
41553
+ const up = liveCamera ? normalizedOr(liveCamera.up, new Vector3(0, 0, 1)) : new Vector3(0, 0, 1);
41554
+ dragInfoRef.current = {
41555
+ pointerId: event.pointerId,
41556
+ startX: event.clientX,
41557
+ startY: event.clientY,
41558
+ startPosition: (liveCamera == null ? void 0 : liveCamera.position.clone()) ?? new Vector3(),
41559
+ target,
41560
+ up,
41561
+ right: liveCamera && controls ? cameraRightVector(liveCamera) : new Vector3(1, 0, 0),
41562
+ moved: false,
41563
+ clickView
41564
+ };
41565
+ event.currentTarget.setPointerCapture(event.pointerId);
41566
+ setDragging(true);
41567
+ },
41568
+ [controlsRef, getLiveCamera]
41569
+ );
41570
+ const handlePointerMove = reactExports.useCallback(
41571
+ (event) => {
41572
+ const drag = dragInfoRef.current;
41573
+ if (!drag || drag.pointerId !== event.pointerId) return;
41574
+ if (Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) > 3) {
41575
+ drag.moved = true;
41576
+ }
41577
+ const controls = controlsRef.current;
41578
+ const liveCamera = getLiveCamera();
41579
+ if (!liveCamera || !controls) return;
41580
+ event.preventDefault();
41581
+ rotateCameraFromDrag(liveCamera, controls, drag, event.clientX, event.clientY);
41582
+ syncCameraState();
41583
+ },
41584
+ [controlsRef, getLiveCamera, syncCameraState]
41585
+ );
41586
+ const finishPointerDrag = reactExports.useCallback(
41587
+ (event) => {
41588
+ const drag = dragInfoRef.current;
41589
+ if (!drag || drag.pointerId !== event.pointerId) return;
41590
+ dragInfoRef.current = null;
41591
+ event.currentTarget.releasePointerCapture(event.pointerId);
41592
+ setDragging(false);
41593
+ if (!drag.moved && drag.clickView) {
41594
+ triggerSnap(drag.clickView);
41595
+ }
41596
+ },
41597
+ [triggerSnap]
41598
+ );
41599
+ const handleFaceKeyDown = reactExports.useCallback(
41600
+ (event, view2) => {
41601
+ if (event.key !== "Enter" && event.key !== " ") return;
41602
+ event.preventDefault();
41603
+ triggerSnap(view2);
41604
+ },
41605
+ [triggerSnap]
41606
+ );
41607
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
41608
+ "div",
41609
+ {
41610
+ className: `fc-view-cube${dragging ? " dragging" : ""}`,
41611
+ "aria-label": "View cube",
41612
+ onPointerDown: handlePointerDown,
41613
+ onPointerMove: handlePointerMove,
41614
+ onPointerUp: finishPointerDrag,
41615
+ onPointerCancel: finishPointerDrag,
41616
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "fc-view-cube-svg", viewBox: `0 0 ${CUBE_SIZE} ${CUBE_SIZE}`, role: "img", "aria-label": "Viewport orientation", children: [
41617
+ /* @__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" }) }) }),
41618
+ /* @__PURE__ */ jsxRuntimeExports.jsx("g", { filter: "url(#fc-view-cube-shadow)", children: faces.map((face) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
41619
+ "g",
41620
+ {
41621
+ className: `fc-view-cube-face${pressedView === face.view ? " active" : ""}`,
41622
+ role: "button",
41623
+ tabIndex: 0,
41624
+ "data-view": face.view,
41625
+ "aria-label": `View ${face.label.toLowerCase()}`,
41626
+ onKeyDown: (event) => handleFaceKeyDown(event, face.view),
41627
+ children: [
41628
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: polygonPoints(face.points), fill: faceFill(face.brightness) }),
41629
+ /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: face.center.x, y: face.center.y, className: "fc-view-cube-face-label", children: face.label })
41630
+ ]
41631
+ },
41632
+ face.view
41633
+ )) })
41634
+ ] })
41635
+ }
41636
+ );
41637
+ }
40637
41638
  const FLAG_DEFINITIONS = {
40638
41639
  drawMode: {
40639
41640
  label: "Draw Mode (interactive sketch editor)",
@@ -41246,6 +42247,7 @@ function useViewportState() {
41246
42247
  const projectionMode = useForgeStore((s) => s.projectionMode);
41247
42248
  const gridEnabled = useForgeStore((s) => s.gridEnabled);
41248
42249
  const axesVisible = useForgeStore((s) => s.axesVisible);
42250
+ const viewCubeVisible = useForgeStore((s) => s.viewCubeVisible);
41249
42251
  const gridSize = useForgeStore((s) => s.gridSize);
41250
42252
  const showPerformanceInfo = useForgeStore((s) => s.showPerformanceInfo);
41251
42253
  const objectSettings = useForgeStore((s) => s.objectSettings);
@@ -41259,9 +42261,7 @@ function useViewportState() {
41259
42261
  const clearFocusedObject = useForgeStore((s) => s.clearFocusedObject);
41260
42262
  const objectPickSyncEnabled = useForgeStore((s) => s.objectPickSyncEnabled);
41261
42263
  const explodeAmount = useForgeStore((s) => s.explodeAmount);
41262
- const viewCommand = useForgeStore((s) => s.viewCommand);
41263
42264
  const requestViewCommand = useForgeStore((s) => s.requestViewCommand);
41264
- const clearViewCommand = useForgeStore((s) => s.clearViewCommand);
41265
42265
  const jointValues = useForgeStore((s) => s.jointValues);
41266
42266
  const jointAnimationClip = useForgeStore((s) => s.jointAnimationClip);
41267
42267
  const jointAnimationProgress = useForgeStore((s) => s.jointAnimationProgress);
@@ -41639,6 +42639,7 @@ function useViewportState() {
41639
42639
  projectionMode,
41640
42640
  gridEnabled,
41641
42641
  axesVisible,
42642
+ viewCubeVisible,
41642
42643
  gridSize,
41643
42644
  showPerformanceInfo,
41644
42645
  objectSettings,
@@ -41652,9 +42653,7 @@ function useViewportState() {
41652
42653
  clearFocusedObject,
41653
42654
  objectPickSyncEnabled,
41654
42655
  explodeAmount,
41655
- viewCommand,
41656
42656
  requestViewCommand,
41657
- clearViewCommand,
41658
42657
  jointValues,
41659
42658
  jointAnimationClip,
41660
42659
  jointAnimationProgress,
@@ -43120,7 +44119,7 @@ function directCadKindForPath(path) {
43120
44119
  return null;
43121
44120
  }
43122
44121
  function directCadReferenceCode(path, kind) {
43123
- const fn = kind === "step" ? "importStep" : "importMesh";
44122
+ const fn = kind === "step" ? "Import.step" : "Import.mesh";
43124
44123
  const name = basename(path);
43125
44124
  return [`const imported = ${fn}(${JSON.stringify(path)});`, `return [{ name: ${JSON.stringify(name)}, shape: imported }];`].join("\n");
43126
44125
  }
@@ -43274,7 +44273,7 @@ function useGeometryComparison(args) {
43274
44273
  class InspectWorkerClient {
43275
44274
  constructor(workerFactory = () => new Worker(new URL(
43276
44275
  /* @vite-ignore */
43277
- "/assets/inspectWorker-BuTJDVX6.js",
44276
+ "/assets/inspectWorker-CZsCFtQT.js",
43278
44277
  import.meta.url
43279
44278
  ), { type: "module" })) {
43280
44279
  __publicField(this, "worker", null);
@@ -44359,6 +45358,7 @@ function Viewport() {
44359
45358
  projectionMode,
44360
45359
  gridEnabled,
44361
45360
  axesVisible,
45361
+ viewCubeVisible,
44362
45362
  gridSize,
44363
45363
  showPerformanceInfo,
44364
45364
  objectSettings,
@@ -44371,9 +45371,7 @@ function Viewport() {
44371
45371
  focusObject,
44372
45372
  clearFocusedObject,
44373
45373
  objectPickSyncEnabled,
44374
- viewCommand,
44375
45374
  requestViewCommand,
44376
- clearViewCommand,
44377
45375
  lengthUnit,
44378
45376
  constructionGhost,
44379
45377
  objects,
@@ -44444,10 +45442,17 @@ function Viewport() {
44444
45442
  const vRulerRef = reactExports.useRef(null);
44445
45443
  const adaptiveGrid = useAdaptiveGrid(zoomMmPerPx, isSketchOnly && gridEnabled);
44446
45444
  const controlsRef = reactExports.useRef(null);
45445
+ const [activeCanvasCamera, setActiveCanvasCamera] = reactExports.useState(null);
44447
45446
  const initialFitRequestedRef = reactExports.useRef(false);
44448
45447
  const prevPreviewFileRef = reactExports.useRef(void 0);
44449
45448
  const containerRef = reactExports.useRef(null);
44450
45449
  const contextMenuRef = reactExports.useRef(null);
45450
+ const handleActiveCameraChange = reactExports.useCallback((camera) => {
45451
+ setActiveCanvasCamera(camera);
45452
+ }, []);
45453
+ const handleCanvasCreated = reactExports.useCallback((state22) => {
45454
+ setActiveCanvasCamera(state22.camera);
45455
+ }, []);
44451
45456
  const t2 = themes[themeName];
44452
45457
  const renderStylePreset = reactExports.useMemo(() => getRenderStylePreset(renderStyle), [renderStyle]);
44453
45458
  const effectiveSceneConfig = reactExports.useMemo(
@@ -44467,7 +45472,7 @@ function Viewport() {
44467
45472
  if (activeFile && !isModelFile(activeFile) && !isSvgActive && !meshPreviewFile) {
44468
45473
  return {
44469
45474
  title: "Viewport disabled",
44470
- body: `${activeFile} is a helper file, not a .forge.js or .sketch.js model. Open a model file to render geometry.`
45475
+ body: `${activeFile} is a text file, not a .forge.js or .sketch.js model. Open a model file to render geometry.`
44471
45476
  };
44472
45477
  }
44473
45478
  if (!(result == null ? void 0 : result.error) && (result == null ? void 0 : result.assemblyKinematics) && objects.length === 0 && !rigInspectActive) {
@@ -44478,7 +45483,10 @@ function Viewport() {
44478
45483
  }
44479
45484
  return null;
44480
45485
  }, [activeFile, isSvgActive, meshPreviewFile, objects.length, result, rigInspectActive]);
44481
- const canvasDpr = isViewportInteracting || hasVisibleSdfObjects ? renderStylePreset.canvasDpr.live : [1, renderStylePreset.canvasDpr.idleMax];
45486
+ const canvasDpr = reactExports.useMemo(() => {
45487
+ if (hasVisibleSdfObjects) return renderStylePreset.canvasDpr.live;
45488
+ return [1, renderStylePreset.canvasDpr.idleMax];
45489
+ }, [hasVisibleSdfObjects, renderStylePreset.canvasDpr.idleMax, renderStylePreset.canvasDpr.live]);
44482
45490
  const effectiveSdfObjectSettings = reactExports.useMemo(() => {
44483
45491
  const next = {};
44484
45492
  for (const obj of objects) {
@@ -44805,7 +45813,9 @@ function Viewport() {
44805
45813
  raycaster: { params: { Line: { threshold: 0.5 } } },
44806
45814
  camera: { up: [0, 0, 1] },
44807
45815
  onPointerMissed: handleViewportPointerMissed,
45816
+ onCreated: handleCanvasCreated,
44808
45817
  children: [
45818
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ActiveCameraBridge, { onCameraChange: handleActiveCameraChange }),
44809
45819
  /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Suspense, { fallback: null, children: [
44810
45820
  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] }),
44811
45821
  ((_d = effectiveSceneConfig == null ? void 0 : effectiveSceneConfig.postProcessing) == null ? void 0 : _d.toneMappingExposure) === void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(RenderStyleExposure, { exposure: renderStylePreset.toneMappingExposure }),
@@ -45115,26 +46125,28 @@ function Viewport() {
45115
46125
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewManager, { isSketchOnly, controlsRef }),
45116
46126
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewPersistence, { controlsRef, isSketchOnly, onResolved: handleViewPersistenceResolved }),
45117
46127
  /* @__PURE__ */ jsxRuntimeExports.jsx(OrbitExporterBridge, { controlsRef }),
46128
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportImageCaptureBridge, {}),
45118
46129
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportRecordingBridge, { controlsRef }),
45119
46130
  historyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryRecorderBridge, {}),
45120
46131
  /* @__PURE__ */ jsxRuntimeExports.jsx(TrajectoryRecorderBridge, { controlsRef })
45121
- ] }),
45122
- /* @__PURE__ */ jsxRuntimeExports.jsx(
45123
- ViewController,
45124
- {
45125
- controlsRef,
45126
- command: viewCommand,
45127
- objects,
45128
- objectMatrices,
45129
- fallbackBounds: rigInspectActive ? rigInspectionBounds : null,
45130
- settings: objectSettings,
45131
- focusedObjectIds,
45132
- clearCommand: clearViewCommand
45133
- }
45134
- )
46132
+ ] })
45135
46133
  ]
45136
46134
  }
45137
46135
  ),
46136
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
46137
+ ViewController,
46138
+ {
46139
+ camera: activeCanvasCamera,
46140
+ controlsRef,
46141
+ viewportRef: containerRef,
46142
+ objects,
46143
+ objectMatrices,
46144
+ fallbackBounds: rigInspectActive ? rigInspectionBounds : null,
46145
+ settings: objectSettings,
46146
+ focusedObjectIds
46147
+ }
46148
+ ),
46149
+ viewCubeVisible && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(ViewCube, { camera: activeCanvasCamera, controlsRef, onSnap: (view2) => requestViewCommand({ type: "snap", view: view2 }) }),
45138
46150
  (() => {
45139
46151
  const toolpathObj = objects.find((o2) => o2.toolpath && o2.toolpath.segments.length > 0);
45140
46152
  if (!(toolpathObj == null ? void 0 : toolpathObj.toolpath)) return null;
@@ -45690,7 +46702,7 @@ function Viewport() {
45690
46702
  }
45691
46703
  );
45692
46704
  }
45693
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BHMQlJ-D.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46705
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BJ0Dloyh.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
45694
46706
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
45695
46707
  function storePendingShareCopy(shareId) {
45696
46708
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -45879,7 +46891,7 @@ const PublishedModelPage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Objec
45879
46891
  }, Symbol.toStringTag, { value: "Module" }));
45880
46892
  const siteUrl = "https://forgecad.io";
45881
46893
  const defaultImage = "https://forgecad.io/brand/social-card.png";
45882
- 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" }];
46894
+ 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" }];
45883
46895
  const seoConfig = {
45884
46896
  siteUrl,
45885
46897
  defaultImage,
@@ -45956,17 +46968,17 @@ function SeoMetadata() {
45956
46968
  }, [location.pathname]);
45957
46969
  return null;
45958
46970
  }
45959
- reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-CnevhTE8.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
45960
- const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-knf4I4h7.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
45961
- reactExports.lazy(() => __vitePreload(() => import("./BlogPage-CMAVvgQL.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
45962
- reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-B27zk8xL.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
45963
- reactExports.lazy(() => __vitePreload(() => import("./AdminPage-CXvls4-J.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
46971
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-BxHkYRE7.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
46972
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-CDoxHkz8.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
46973
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-DHaGP50_.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
46974
+ reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-BVEpJSVk.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
46975
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-DcCnj0qo.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
45964
46976
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
45965
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-CFF-UgjI.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
45966
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-B0D4goG_.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
45967
- reactExports.lazy(() => __vitePreload(() => import("./LegalPage-BPTUmqeg.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
45968
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BHMQlJ-D.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
45969
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-D7ZGlFjx.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
46977
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-CIZSSAd0.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
46978
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-CzpZ6-Ce.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
46979
+ reactExports.lazy(() => __vitePreload(() => import("./LegalPage-B-u6FrVv.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
46980
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BJ0Dloyh.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46981
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-CRKZbY0y.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
45970
46982
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
45971
46983
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg|dxf)$/i;
45972
46984
  function firstMeaningfulLine(text) {
@@ -46186,66 +47198,74 @@ function App() {
46186
47198
  applyTheme(localStorage.getItem("fc-theme") || "dark");
46187
47199
  clientExports.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsxRuntimeExports.jsx(App, {}));
46188
47200
  export {
46189
- VOXEL_INTENT_PLACEMENT_LABELS as $,
47201
+ INSPECT_POINT_SAMPLE_COUNT_MIN as $,
46190
47202
  AuthApiError as A,
46191
47203
  BrandMark as B,
46192
- hasExternalFiles as C,
46193
- isImportableProjectExactFile as D,
46194
- resolvePreviewFile as E,
47204
+ captureViewportImageBlobFromStore as C,
47205
+ exportExactFromStore as D,
47206
+ storageQuotaUpgradeMessage as E,
46195
47207
  FLAG_DEFINITIONS as F,
46196
- countParamSnapshotDiff as G,
46197
- buildProjectShareUrl as H,
46198
- buildEmbedSnippet as I,
46199
- publishProjectShare as J,
46200
- unpublishProjectShare as K,
46201
- formatComputeBackendLabel as L,
46202
- themes as M,
46203
- computeBackendFromParts as N,
46204
- formatArea as O,
46205
- sliderToAnimationSpeed as P,
46206
- animationSpeedToSlider as Q,
46207
- formatAnimationSpeed as R,
46208
- resolveJointRange as S,
46209
- useJointsConfig as T,
46210
- useJointAnimationValues as U,
46211
- INSPECT_POINT_SAMPLE_COUNT_MAX as V,
46212
- INSPECT_POINT_SAMPLE_COUNT_MIN as W,
46213
- VOXEL_INTENT_TOOL_ORDER as X,
46214
- VOXEL_INTENT_TOOL_LABELS as Y,
46215
- VOXEL_INTENT_TOOL_COLORS as Z,
46216
- VOXEL_INTENT_PLACEMENT_ORDER as _,
47208
+ isImportableProjectMeshFile as G,
47209
+ isImportableProjectBinaryFile as H,
47210
+ hasExternalFiles as I,
47211
+ isImportableProjectExactFile as J,
47212
+ resolvePreviewFile as K,
47213
+ countParamSnapshotDiff as L,
47214
+ buildProjectShareUrl as M,
47215
+ buildEmbedSnippet as N,
47216
+ publishProjectShare as O,
47217
+ unpublishProjectShare as P,
47218
+ formatComputeBackendLabel as Q,
47219
+ themes as R,
47220
+ computeBackendFromParts as S,
47221
+ formatArea as T,
47222
+ sliderToAnimationSpeed as U,
47223
+ animationSpeedToSlider as V,
47224
+ formatAnimationSpeed as W,
47225
+ resolveJointRange as X,
47226
+ useJointsConfig as Y,
47227
+ useJointAnimationValues as Z,
47228
+ INSPECT_POINT_SAMPLE_COUNT_MAX as _,
46217
47229
  applyTheme as a,
46218
- expandBoundsByTransformedAabb as a0,
46219
- Canvas as a1,
46220
- PerspectiveCamera as a2,
46221
- ControlsInteractionBridge as a3,
46222
- ViewController as a4,
46223
- SceneConfigurator as a5,
46224
- LocalEnvironment as a6,
46225
- ForgeObject as a7,
46226
- RenderLabelsOverlay as a8,
46227
- Grid as a9,
46228
- OrbitControls2 as aa,
46229
- TOUCH_GESTURES_3D as ab,
46230
- MOUSE_BUTTONS_3D as ac,
46231
- ModelJourneyBar as ad,
46232
- useJointAnimationLoop as ae,
46233
- computeJointNodeMatrices as af,
46234
- computeObjectJointMatrices as ag,
46235
- readLastActiveFileForUser as ah,
46236
- ToastContainer as ai,
46237
- isMobile as aj,
46238
- useFeatureFlag as ak,
46239
- decodeSharedHash as al,
46240
- decodeSharedBundle as am,
46241
- getExternalUrl as an,
46242
- getGistId as ao,
46243
- Viewport as ap,
46244
- shouldBlockBrowserShortcut as aq,
46245
- useDrawStore as ar,
46246
- storePendingShareCopy as as,
46247
- buildShareUrl as at,
46248
- share as au,
47230
+ VOXEL_INTENT_TOOL_ORDER as a0,
47231
+ VOXEL_INTENT_TOOL_LABELS as a1,
47232
+ VOXEL_INTENT_TOOL_COLORS as a2,
47233
+ VOXEL_INTENT_PLACEMENT_ORDER as a3,
47234
+ VOXEL_INTENT_PLACEMENT_LABELS as a4,
47235
+ highlightLanguageForProjectFile as a5,
47236
+ hasProjectTextFileExtension as a6,
47237
+ expandBoundsByTransformedAabb as a7,
47238
+ Canvas as a8,
47239
+ ActiveCameraBridge as a9,
47240
+ storePendingShareCopy as aA,
47241
+ buildShareUrl as aB,
47242
+ share as aC,
47243
+ PerspectiveCamera as aa,
47244
+ ControlsInteractionBridge as ab,
47245
+ SceneConfigurator as ac,
47246
+ LocalEnvironment as ad,
47247
+ ForgeObject as ae,
47248
+ RenderLabelsOverlay as af,
47249
+ Grid as ag,
47250
+ OrbitControls2 as ah,
47251
+ TOUCH_GESTURES_3D as ai,
47252
+ MOUSE_BUTTONS_3D as aj,
47253
+ ViewController as ak,
47254
+ ModelJourneyBar as al,
47255
+ useJointAnimationLoop as am,
47256
+ computeJointNodeMatrices as an,
47257
+ computeObjectJointMatrices as ao,
47258
+ readLastActiveFileForUser as ap,
47259
+ ToastContainer as aq,
47260
+ isMobile as ar,
47261
+ useFeatureFlag as as,
47262
+ decodeSharedHash as at,
47263
+ decodeSharedBundle as au,
47264
+ getExternalUrl as av,
47265
+ getGistId as aw,
47266
+ Viewport as ax,
47267
+ shouldBlockBrowserShortcut as ay,
47268
+ useDrawStore as az,
46249
47269
  authFetch as b,
46250
47270
  authApi as c,
46251
47271
  showToast as d,
@@ -46253,22 +47273,22 @@ export {
46253
47273
  formatStorageBytes as f,
46254
47274
  useForgeStore as g,
46255
47275
  fileSystem as h,
46256
- copyTextToClipboard as i,
46257
- useFeatureFlagStore as j,
46258
- fetchGistModel as k,
46259
- fetchUrlModel as l,
46260
- exportMeshFromStore as m,
46261
- exportReportFromStore as n,
46262
- exportOrbitVideoFromStore as o,
46263
- exportSketchFromStore as p,
46264
- buildGistShareUrl as q,
47276
+ isRunnableFile as i,
47277
+ copyTextToClipboard as j,
47278
+ useFeatureFlagStore as k,
47279
+ fetchGistModel as l,
47280
+ monacoLanguageForProjectFile as m,
47281
+ fetchUrlModel as n,
47282
+ exportMeshFromStore as o,
47283
+ exportReportFromStore as p,
47284
+ exportViewportImageFromStore as q,
46265
47285
  readProjectFilesFromDataTransfer as r,
46266
47286
  storageUsagePercent as s,
46267
47287
  triggerDownload as t,
46268
47288
  useAuthStore as u,
46269
- exportExactFromStore as v,
46270
- deriveExportStem as w,
46271
- storageQuotaUpgradeMessage as x,
46272
- isImportableProjectMeshFile as y,
46273
- isImportableProjectBinaryFile as z
47289
+ exportOrbitVideoFromStore as v,
47290
+ exportSketchFromStore as w,
47291
+ buildGistShareUrl as x,
47292
+ deriveExportStem as y,
47293
+ sanitizeExportStem as z
46274
47294
  };