forgecad 0.9.16 → 0.10.0

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 (147) hide show
  1. package/dist/assets/{AdminPage-CXvls4-J.js → AdminPage-DwYHz72L.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-B27zk8xL.js → BenchmarkPage-a9_f-1US.js} +1 -1
  3. package/dist/assets/{BlogPage-CMAVvgQL.js → BlogPage-DodHpvmf.js} +1 -1
  4. package/dist/assets/{DocsPage-knf4I4h7.js → DocsPage-B5LePEuj.js} +8 -858
  5. package/dist/assets/EditorApp-QXsAISLR.js +16307 -0
  6. package/dist/assets/{EmbedViewer-D7ZGlFjx.js → EmbedViewer-DdEHGUMU.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-CnevhTE8.js → LandingPageProofDriven-yhhOodbf.js} +1 -1
  8. package/dist/assets/{LegalPage-BPTUmqeg.js → LegalPage-5RbKRGYK.js} +1 -1
  9. package/dist/assets/{PricingPage-B0D4goG_.js → PricingPage-E3Rma7aV.js} +1 -1
  10. package/dist/assets/{SettingsPage-CFF-UgjI.js → SettingsPage-BJZcM97j.js} +1 -1
  11. package/dist/assets/{app-T0pDcSX4.js → app-DSYrDg0V.js} +733 -205
  12. package/dist/assets/cli/{render-C5pcIISc.js → render-ZMHR9HkV.js} +19 -46
  13. package/dist/assets/{constructionHistoryWorker-Ba2Hm58b.js → constructionHistoryWorker-AwMMWSxg.js} +1103 -349
  14. package/dist/assets/{evalWorker-vkx310U2.js → evalWorker-DbNs7Dkp.js} +3798 -1622
  15. package/dist/assets/{inspectWorker-BuTJDVX6.js → inspectWorker-CZsCFtQT.js} +1163 -409
  16. package/dist/assets/{jointPose-B_Cgedn9.js → jointPose-DO6mnXn_.js} +1 -1
  17. package/dist/assets/{manifold-BWgsjmAM.js → manifold-BGlQBBH9.js} +1 -1
  18. package/dist/assets/{manifold-rZexZI0G.js → manifold-BU-tJwQh.js} +1 -1
  19. package/dist/assets/{manifold-D6IFSkhH.js → manifold-fy2MV7K1.js} +2 -2
  20. package/dist/assets/{reportWorker-0AGij1Ru.js → reportWorker-DO6hcQbh.js} +7155 -2437
  21. package/dist/assets/{scalar-sampling-budget-J5cuzxT1.js → scalar-sampling-budget-o90NSNmF.js} +3940 -1742
  22. package/dist/assets/{scanProxyWorker-Vl4Wxa1y.js → scanProxyWorker-2GtDLk-R.js} +1 -1
  23. package/dist/assets/{javascript-1kQXfVaz.js → typescript-DBQ6RN5l.js} +874 -22
  24. package/dist/cli/render.html +1 -1
  25. package/dist/docs/index.html +3 -3
  26. package/dist/docs-raw/AI/usage.md +1 -1
  27. package/dist/docs-raw/CLI.md +63 -241
  28. package/dist/docs-raw/README.md +6 -0
  29. package/dist/docs-raw/component-model.md +17 -150
  30. package/dist/docs-raw/generated/assembly.md +139 -598
  31. package/dist/docs-raw/generated/concepts.md +245 -3501
  32. package/dist/docs-raw/generated/core.md +277 -1251
  33. package/dist/docs-raw/generated/curves.md +387 -1608
  34. package/dist/docs-raw/generated/legacy.md +162 -0
  35. package/dist/docs-raw/generated/lib.md +227 -85
  36. package/dist/docs-raw/generated/output.md +38 -73
  37. package/dist/docs-raw/generated/runtime-names.md +23 -23
  38. package/dist/docs-raw/generated/sdf.md +68 -284
  39. package/dist/docs-raw/generated/sheet-metal.md +68 -335
  40. package/dist/docs-raw/generated/sketch.md +240 -1161
  41. package/dist/docs-raw/generated/viewport.md +75 -316
  42. package/dist/docs-raw/generated/wood.md +21 -49
  43. package/dist/docs-raw/guides/coordinate-system.md +4 -42
  44. package/dist/docs-raw/guides/inspection-bundles.md +44 -442
  45. package/dist/docs-raw/guides/joint-design.md +18 -79
  46. package/dist/docs-raw/guides/positioning.md +21 -143
  47. package/dist/docs-raw/guides/scene-presentation.md +89 -0
  48. package/dist/docs-raw/skills/forgecad-3d-reconstruction.md +25 -111
  49. package/dist/docs-raw/skills/forgecad-blockout-model.md +20 -117
  50. package/dist/docs-raw/skills/forgecad-component-model.md +23 -107
  51. package/dist/docs-raw/skills/forgecad-high-level-spec.md +47 -155
  52. package/dist/docs-raw/skills/forgecad-image-replicator.md +26 -143
  53. package/dist/docs-raw/skills/forgecad-lld.md +19 -113
  54. package/dist/docs-raw/skills/forgecad-make-a-model.md +112 -532
  55. package/dist/docs-raw/skills/forgecad-model-grader.md +38 -108
  56. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +24 -211
  57. package/dist/docs-raw/skills/forgecad-project.md +13 -131
  58. package/dist/docs-raw/skills/forgecad-reconstruction-benchmark.md +42 -134
  59. package/dist/docs-raw/skills/forgecad-render-inspect.md +27 -174
  60. package/dist/docs-raw/skills/forgecad-visual-spec.md +32 -112
  61. package/dist/docs-raw/skills/forgecad.md +19 -18
  62. package/dist/docs-raw/skills/index.md +2 -0
  63. package/dist/docs-raw/welcome.md +2 -2
  64. package/dist/index.html +1 -1
  65. package/dist/llms.txt +1 -2
  66. package/dist/sitemap.xml +13 -13
  67. package/dist-cli/{check-compiler-SYQ2PWOB.js → check-compiler-JTVBITCR.js} +1 -1
  68. package/dist-cli/{check-query-propagation-HIAGV62W.js → check-query-propagation-3FFLSMVN.js} +1 -1
  69. package/dist-cli/{chunk-SPZE3DUY.js → chunk-OAN5T4XD.js} +4412 -2212
  70. package/dist-cli/forgecad.js +507 -179
  71. package/dist-skill/CONTEXT.md +2172 -8377
  72. package/dist-skill/SKILL.md +15 -15
  73. package/dist-skill/docs/API/core/concepts.md +27 -157
  74. package/dist-skill/docs/CLI.md +63 -241
  75. package/dist-skill/docs/generated/assembly.md +138 -549
  76. package/dist-skill/docs/generated/core.md +277 -1251
  77. package/dist-skill/docs/generated/curves.md +387 -1609
  78. package/dist-skill/docs/generated/lib.md +227 -85
  79. package/dist-skill/docs/generated/output.md +38 -73
  80. package/dist-skill/docs/generated/runtime-names.md +16 -21
  81. package/dist-skill/docs/generated/sdf.md +68 -284
  82. package/dist-skill/docs/generated/sheet-metal.md +68 -335
  83. package/dist-skill/docs/generated/sketch.md +240 -1160
  84. package/dist-skill/docs/generated/viewport.md +75 -223
  85. package/dist-skill/docs/generated/wood.md +21 -49
  86. package/dist-skill/docs/guides/coordinate-system.md +4 -42
  87. package/dist-skill/docs/guides/inspection-bundles.md +44 -442
  88. package/dist-skill/docs/guides/joint-design.md +18 -79
  89. package/dist-skill/docs/guides/positioning.md +21 -143
  90. package/dist-skill/docs/guides/scene-presentation.md +89 -0
  91. package/dist-skill/docs/guides/surface-members.md +26 -0
  92. package/dist-skill/library/forgecad-3d-reconstruction/SKILL.md +23 -111
  93. package/dist-skill/library/forgecad-blockout-model/SKILL.md +18 -117
  94. package/dist-skill/library/forgecad-component-model/SKILL.md +21 -107
  95. package/dist-skill/library/forgecad-high-level-spec/SKILL.md +45 -155
  96. package/dist-skill/library/forgecad-image-replicator/SKILL.md +24 -143
  97. package/dist-skill/library/forgecad-lld/SKILL.md +17 -113
  98. package/dist-skill/library/forgecad-make-a-model/SKILL.md +110 -532
  99. package/dist-skill/library/forgecad-model-grader/SKILL.md +36 -108
  100. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +35 -224
  101. package/dist-skill/library/forgecad-prepare-prompt/references/default-profiles.md +43 -271
  102. package/dist-skill/library/forgecad-prepare-prompt/references/master-prompt.md +30 -99
  103. package/dist-skill/library/forgecad-project/SKILL.md +13 -133
  104. package/dist-skill/library/forgecad-reconstruction-benchmark/SKILL.md +29 -123
  105. package/dist-skill/library/forgecad-render-inspect/SKILL.md +25 -174
  106. package/dist-skill/library/forgecad-visual-spec/SKILL.md +30 -111
  107. package/dist-skill/website/skills/forgecad-3d-reconstruction.md +58 -0
  108. package/dist-skill/website/skills/forgecad-blockout-model.md +49 -0
  109. package/dist-skill/website/skills/forgecad-component-model.md +53 -0
  110. package/dist-skill/website/skills/forgecad-high-level-spec.md +101 -0
  111. package/dist-skill/website/skills/forgecad-image-replicator.md +63 -0
  112. package/dist-skill/website/skills/forgecad-lld.md +41 -0
  113. package/dist-skill/website/skills/forgecad-make-a-model.md +186 -0
  114. package/dist-skill/website/skills/forgecad-model-grader.md +82 -0
  115. package/dist-skill/website/skills/forgecad-prepare-prompt.md +63 -0
  116. package/dist-skill/website/skills/forgecad-project.md +26 -0
  117. package/dist-skill/website/skills/forgecad-reconstruction-benchmark.md +60 -0
  118. package/dist-skill/website/skills/forgecad-render-inspect.md +80 -0
  119. package/dist-skill/website/skills/forgecad-visual-spec.md +71 -0
  120. package/dist-skill/website/skills/forgecad.md +122 -0
  121. package/dist-skill/website/skills/index.md +26 -0
  122. package/examples/api/comparison-imported-sphere-candidate.forge.js +1 -1
  123. package/examples/api/conformal-product-ribbon.forge.js +1 -1
  124. package/examples/api/exact-sheet-shell-assembly.forge.js +1 -1
  125. package/examples/api/extrude-options.forge.js +4 -2
  126. package/examples/api/field-loft-drive-tip.forge.js +40 -0
  127. package/examples/api/guided-loft-olive-oil-bottle.forge.js +1 -1
  128. package/examples/api/highlight-debug.forge.js +10 -10
  129. package/examples/api/mesh-import-slats.forge.js +1 -1
  130. package/examples/api/real-product-curves.forge.js +1 -1
  131. package/examples/api/sculpt-box-circle-booleans.forge.js +1 -1
  132. package/examples/api/sdf-shapes.forge.js +2 -5
  133. package/examples/api/sketch-rounding-strategies.forge.js +6 -6
  134. package/examples/api/surface-member-bottle-cage.forge.js +3 -3
  135. package/examples/api/surface-member-conformal-product-ribbon.forge.js +3 -3
  136. package/examples/api/surface-member-razor-inlay.forge.js +1 -1
  137. package/examples/api/variable-sweep-test.forge.js +3 -3
  138. package/examples/mechanical/airplane-propeller.forge.js +74 -39
  139. package/examples/nurbs-surface.forge.js +1 -1
  140. package/examples/products/iphone.forge.js +1 -1
  141. package/package.json +1 -1
  142. package/dist/assets/EditorApp-BHMQlJ-D.js +0 -14686
  143. package/dist/docs-raw/guides/geometry-conventions.md +0 -52
  144. package/dist/docs-raw/guides/modeling-recipes.md +0 -78
  145. package/dist-skill/docs/guides/geometry-conventions.md +0 -52
  146. package/dist-skill/docs/guides/modeling-recipes.md +0 -78
  147. 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-o90NSNmF.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);
@@ -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-DbNs7Dkp.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,89 @@ 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
+ const lower = path.toLowerCase();
16336
+ if (lower.endsWith(".md")) return "markdown";
16337
+ if (lower.endsWith(".json")) return "json";
16338
+ if (lower.endsWith(".ts") || lower.endsWith(".tsx")) return "typescript";
16339
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs") || lower.endsWith(".jsx")) return "javascript";
16340
+ if (lower.endsWith(".html")) return "html";
16341
+ if (lower.endsWith(".css") || lower.endsWith(".scss")) return "css";
16342
+ if (lower.endsWith(".svg") || lower.endsWith(".xml")) return "xml";
16343
+ if (lower.endsWith(".yml") || lower.endsWith(".yaml")) return "yaml";
16344
+ return "plaintext";
16345
+ }
16346
+ function highlightLanguageForProjectFile(path) {
16347
+ const lower = path.toLowerCase();
16348
+ if (lower.endsWith(".md")) return "markdown";
16349
+ if (lower.endsWith(".json")) return "json";
16350
+ if (lower.endsWith(".ts") || lower.endsWith(".tsx")) return "typescript";
16351
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs") || lower.endsWith(".jsx")) return "javascript";
16352
+ if (lower.endsWith(".html") || lower.endsWith(".svg") || lower.endsWith(".xml")) return "xml";
16353
+ if (lower.endsWith(".css") || lower.endsWith(".scss")) return "css";
16354
+ return null;
16355
+ }
16356
+ function projectTextFileMimeType(path) {
16357
+ const lower = path.toLowerCase();
16358
+ if (lower.endsWith(".svg")) return "image/svg+xml";
16359
+ if (lower.endsWith(".dxf")) return "application/dxf";
16360
+ if (lower.endsWith(".json")) return "application/json";
16361
+ if (lower.endsWith(".md")) return "text/markdown";
16362
+ if (lower.endsWith(".html")) return "text/html";
16363
+ if (lower.endsWith(".css") || lower.endsWith(".scss")) return "text/css";
16364
+ if (lower.endsWith(".js") || lower.endsWith(".mjs") || lower.endsWith(".cjs") || lower.endsWith(".jsx")) return "text/javascript";
16365
+ return "text/plain";
16366
+ }
16367
+ const IMPORTABLE_PROJECT_TEXT_FILE_EXTS = EDITABLE_PROJECT_TEXT_FILE_EXTS;
16275
16368
  const IMPORTABLE_PROJECT_MESH_FILE_EXTS = [".stl", ".obj", ".3mf"];
16276
16369
  const IMPORTABLE_PROJECT_EXACT_FILE_EXTS = [".step", ".stp"];
16277
16370
  const IMPORTABLE_PROJECT_BINARY_FILE_EXTS = [...IMPORTABLE_PROJECT_MESH_FILE_EXTS, ...IMPORTABLE_PROJECT_EXACT_FILE_EXTS];
@@ -16282,7 +16375,7 @@ function hasImportableExt(path, extensions) {
16282
16375
  return extensions.some((ext) => lower.endsWith(ext));
16283
16376
  }
16284
16377
  function isImportableProjectTextFile(path) {
16285
- return IMPORTABLE_PROJECT_TEXT_FILE_EXTS.some((ext) => path.endsWith(ext));
16378
+ return hasProjectTextFileExtension(path);
16286
16379
  }
16287
16380
  function isImportableProjectMeshFile(path) {
16288
16381
  return hasImportableExt(path, IMPORTABLE_PROJECT_MESH_FILE_EXTS);
@@ -16985,7 +17078,9 @@ if (sharedBundle) {
16985
17078
  }
16986
17079
  }
16987
17080
  const LAST_ACTIVE_FILE_KEY_BASE = "fc-last-active-file";
17081
+ const RECENT_FILES_KEY_BASE = "fc-recent-files";
16988
17082
  const lastActiveFileKey = () => userScopedKey(LAST_ACTIVE_FILE_KEY_BASE);
17083
+ const recentFilesKey = () => userScopedKey(RECENT_FILES_KEY_BASE);
16989
17084
  const readLastActiveFileForUser = (userId) => {
16990
17085
  try {
16991
17086
  return localStorage.getItem(`${LAST_ACTIVE_FILE_KEY_BASE}-${userId}`);
@@ -16993,6 +17088,25 @@ const readLastActiveFileForUser = (userId) => {
16993
17088
  return null;
16994
17089
  }
16995
17090
  };
17091
+ const readRecentFiles = () => {
17092
+ try {
17093
+ if (typeof localStorage === "undefined") return [];
17094
+ const raw = localStorage.getItem(recentFilesKey());
17095
+ if (!raw) return [];
17096
+ const parsed = JSON.parse(raw);
17097
+ if (!Array.isArray(parsed)) return [];
17098
+ return parsed.filter((value) => typeof value === "string" && value.length > 0);
17099
+ } catch {
17100
+ return [];
17101
+ }
17102
+ };
17103
+ const writeRecentFiles = (files) => {
17104
+ try {
17105
+ if (typeof localStorage === "undefined") return;
17106
+ localStorage.setItem(recentFilesKey(), JSON.stringify(files));
17107
+ } catch {
17108
+ }
17109
+ };
16996
17110
  const clampJointValue = (value, min, max2) => {
16997
17111
  let next = Number.isFinite(value) ? value : 0;
16998
17112
  if (min !== void 0) next = Math.max(min, next);
@@ -17256,18 +17370,8 @@ const resolveThicknessColorRange = (value) => {
17256
17370
  return DEFAULT_THICKNESS_COLOR_RANGE;
17257
17371
  }
17258
17372
  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
- );
17373
+ const min = resolveClampedNumber(raw.min, DEFAULT_THICKNESS_COLOR_RANGE.min, THICKNESS_COLOR_RANGE_MIN, THICKNESS_COLOR_RANGE_MAX);
17374
+ const max2 = resolveClampedNumber(raw.max, DEFAULT_THICKNESS_COLOR_RANGE.max, THICKNESS_COLOR_RANGE_MIN, THICKNESS_COLOR_RANGE_MAX);
17271
17375
  if (max2 - min >= THICKNESS_COLOR_RANGE_MIN_SPAN) return { min, max: max2 };
17272
17376
  const defaultSpan = DEFAULT_THICKNESS_COLOR_RANGE.max - DEFAULT_THICKNESS_COLOR_RANGE.min;
17273
17377
  const expandedMax = Math.min(THICKNESS_COLOR_RANGE_MAX, min + defaultSpan);
@@ -17422,6 +17526,47 @@ const initialActive = (() => {
17422
17526
  return fallback;
17423
17527
  })();
17424
17528
  const INITIAL_SAVED = {};
17529
+ const MAX_RECENT_FILES = 200;
17530
+ function touchRecentFile(recentFiles, files, file) {
17531
+ const availableFiles = new Set(Object.keys(files));
17532
+ const next = [];
17533
+ const seen = /* @__PURE__ */ new Set();
17534
+ if (file && availableFiles.has(file)) {
17535
+ next.push(file);
17536
+ seen.add(file);
17537
+ }
17538
+ for (const recentFile of recentFiles) {
17539
+ if (!availableFiles.has(recentFile) || seen.has(recentFile)) continue;
17540
+ next.push(recentFile);
17541
+ seen.add(recentFile);
17542
+ if (next.length >= MAX_RECENT_FILES) break;
17543
+ }
17544
+ return next;
17545
+ }
17546
+ function pruneRecentFiles(recentFiles, files) {
17547
+ return touchRecentFile(recentFiles, files, "");
17548
+ }
17549
+ function remapRecentFiles(recentFiles, files, oldPath, newPath) {
17550
+ const remapped = recentFiles.map((file) => movePath(file, oldPath, newPath));
17551
+ return pruneRecentFiles(remapped, files);
17552
+ }
17553
+ function persistRecentFiles(recentFiles) {
17554
+ writeRecentFiles(recentFiles);
17555
+ return recentFiles;
17556
+ }
17557
+ function touchAndPersistRecentFile(recentFiles, files, file) {
17558
+ return persistRecentFiles(touchRecentFile(recentFiles, files, file));
17559
+ }
17560
+ function pruneAndPersistRecentFiles(recentFiles, files) {
17561
+ return persistRecentFiles(pruneRecentFiles(recentFiles, files));
17562
+ }
17563
+ function remapAndPersistRecentFiles(recentFiles, files, oldPath, newPath) {
17564
+ return persistRecentFiles(remapRecentFiles(recentFiles, files, oldPath, newPath));
17565
+ }
17566
+ function readRecentFilesOrFallback(fallback) {
17567
+ const stored = readRecentFiles();
17568
+ return stored.length > 0 ? stored : fallback;
17569
+ }
17425
17570
  const VIEW_INSPECT_CHANNELS = /* @__PURE__ */ new Set([
17426
17571
  "none",
17427
17572
  "mask",
@@ -17437,6 +17582,9 @@ const VIEW_INSPECT_CHANNELS = /* @__PURE__ */ new Set([
17437
17582
  "roughness"
17438
17583
  ]);
17439
17584
  const INSPECT_DISPLAY_MODES = /* @__PURE__ */ new Set(["heatmap", "points", "both", "scan"]);
17585
+ const SAVE_PICKER_TEXT_FILE_EXTS = EDITABLE_PROJECT_TEXT_FILE_EXTS.filter(
17586
+ (ext) => ![".forge.js", ".sketch.js", ".js", ".svg", ".dxf", ".md"].includes(ext)
17587
+ );
17440
17588
  function resolveViewInspectChannel(value) {
17441
17589
  return typeof value === "string" && VIEW_INSPECT_CHANNELS.has(value) ? value : "none";
17442
17590
  }
@@ -17547,6 +17695,10 @@ function abortServerCompute() {
17547
17695
  function canUseServerCompute() {
17548
17696
  return true;
17549
17697
  }
17698
+ function canHaveProjectImports(filename) {
17699
+ const lower = filename.toLowerCase();
17700
+ 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");
17701
+ }
17550
17702
  const PARAM_DEBOUNCE_MS = navigator.maxTouchPoints > 0 && window.innerWidth < 768 ? 250 : 80;
17551
17703
  async function lazyLoadFileAndDeps(filename, get, set) {
17552
17704
  if (!fileSystem.fetchFileContent) return;
@@ -17569,6 +17721,7 @@ async function lazyLoadFileAndDeps(filename, get, set) {
17569
17721
  }
17570
17722
  }
17571
17723
  if (!content) continue;
17724
+ if (!canHaveProjectImports(file)) continue;
17572
17725
  for (const ref of extractImports(content)) {
17573
17726
  const resolved = resolveImportPath(file, ref.raw);
17574
17727
  const matchKey = Object.keys(storeFiles).find((k2) => k2 === resolved || k2.replace(/^\.\//, "") === resolved.replace(/^\.\//, ""));
@@ -17622,6 +17775,7 @@ const useForgeStore = create((set, get) => ({
17622
17775
  savedFiles: { ...INITIAL_SAVED },
17623
17776
  folders: [...INITIAL_FOLDERS],
17624
17777
  activeFile: initialActive,
17778
+ recentFiles: touchRecentFile(readRecentFiles(), INITIAL_FILES, initialActive),
17625
17779
  setActiveFile: (name) => {
17626
17780
  abortServerCompute();
17627
17781
  if (name) {
@@ -17645,6 +17799,7 @@ const useForgeStore = create((set, get) => ({
17645
17799
  const restored = targetPreviewFile && nextByFile[targetPreviewFile] ? nextByFile[targetPreviewFile] : {};
17646
17800
  set({
17647
17801
  activeFile: name,
17802
+ recentFiles: touchAndPersistRecentFile(get().recentFiles, files, name),
17648
17803
  meshPreviewFile: null,
17649
17804
  // Clear mesh preview when switching files
17650
17805
  lastValidResult: null,
@@ -17693,15 +17848,13 @@ const useForgeStore = create((set, get) => ({
17693
17848
  if (!normalized) return;
17694
17849
  if (get().files[normalized] !== void 0) return;
17695
17850
  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";
17851
+ const template = projectTextFileTemplate(normalized);
17700
17852
  const newFolders = Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort();
17701
17853
  set((s) => ({
17702
17854
  files: { ...s.files, [normalized]: template },
17703
17855
  savedFiles: { ...s.savedFiles, [normalized]: template },
17704
17856
  activeFile: normalized,
17857
+ recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: template }, normalized),
17705
17858
  paramOverrides: {},
17706
17859
  jointValues: {},
17707
17860
  jointAnimationClip: null,
@@ -17739,6 +17892,7 @@ const useForgeStore = create((set, get) => ({
17739
17892
  files: remaining,
17740
17893
  savedFiles: remainingSaved,
17741
17894
  activeFile: newActive,
17895
+ recentFiles: touchAndPersistRecentFile(get().recentFiles, remaining, newActive),
17742
17896
  objectSettingsByFile: nextObjectSettingsByFile,
17743
17897
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17744
17898
  paramOverrides: {},
@@ -17772,6 +17926,7 @@ const useForgeStore = create((set, get) => ({
17772
17926
  files: remaining,
17773
17927
  savedFiles: remainingSaved,
17774
17928
  activeFile: oldName === activeFile ? normalized : activeFile,
17929
+ recentFiles: remapAndPersistRecentFiles(get().recentFiles, remaining, oldName, normalized),
17775
17930
  objectSettingsByFile: nextObjectSettingsByFile,
17776
17931
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17777
17932
  folders: Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort()
@@ -17828,6 +17983,7 @@ const useForgeStore = create((set, get) => ({
17828
17983
  savedFiles: updatedSaved,
17829
17984
  folders: updatedFolders,
17830
17985
  activeFile: nextActive,
17986
+ recentFiles: remapAndPersistRecentFiles(get().recentFiles, updatedFiles, normalizedOld, normalizedNew),
17831
17987
  objectSettingsByFile: nextObjectSettingsByFile,
17832
17988
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17833
17989
  paramOverrides: {},
@@ -17850,22 +18006,29 @@ const useForgeStore = create((set, get) => ({
17850
18006
  const prefix = `${normalized}/`;
17851
18007
  const remainingFiles = {};
17852
18008
  const remainingSaved = {};
17853
- const deletedFiles = [];
18009
+ const deletedFiles = /* @__PURE__ */ new Set();
17854
18010
  for (const key of Object.keys(files)) {
17855
18011
  if (key.startsWith(prefix)) {
17856
- deletedFiles.push(key);
18012
+ deletedFiles.add(key);
17857
18013
  } else {
17858
18014
  remainingFiles[key] = files[key];
17859
18015
  }
17860
18016
  }
17861
18017
  for (const key of Object.keys(savedFiles)) {
17862
- if (!key.startsWith(prefix)) {
18018
+ if (key.startsWith(prefix)) {
18019
+ deletedFiles.add(key);
18020
+ } else {
17863
18021
  remainingSaved[key] = savedFiles[key];
17864
18022
  }
17865
18023
  }
17866
- if (Object.keys(remainingFiles).length === 0) return;
18024
+ const replacementFile = Object.keys(remainingFiles).length === 0 ? "main.forge.js" : null;
18025
+ if (replacementFile) {
18026
+ const template = projectTextFileTemplate(replacementFile);
18027
+ remainingFiles[replacementFile] = template;
18028
+ remainingSaved[replacementFile] = template;
18029
+ }
17867
18030
  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;
18031
+ const newActive = (activeFile == null ? void 0 : activeFile.startsWith(prefix)) || !activeFile ? Object.keys(remainingFiles)[0] : activeFile;
17869
18032
  let nextObjectSettingsByFile = objectSettingsByFile;
17870
18033
  let nextParamSnapshotsByFile = paramSnapshotsByFile;
17871
18034
  for (const f2 of deletedFiles) {
@@ -17878,6 +18041,7 @@ const useForgeStore = create((set, get) => ({
17878
18041
  savedFiles: remainingSaved,
17879
18042
  folders: remainingFolders,
17880
18043
  activeFile: newActive,
18044
+ recentFiles: touchAndPersistRecentFile(get().recentFiles, remainingFiles, newActive),
17881
18045
  objectSettingsByFile: nextObjectSettingsByFile,
17882
18046
  paramSnapshotsByFile: nextParamSnapshotsByFile,
17883
18047
  paramOverrides: {},
@@ -17887,7 +18051,7 @@ const useForgeStore = create((set, get) => ({
17887
18051
  jointAnimationPlaying: false,
17888
18052
  hoveredJointName: null
17889
18053
  });
17890
- Promise.all(deletedFiles.map((f2) => fileSystem.delete(f2))).then(() => fileSystem.deleteFolder(normalized)).catch((e2) => console.error("Delete folder failed:", e2));
18054
+ 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
18055
  setTimeout(() => get().execute(), 0);
17892
18056
  },
17893
18057
  moveEntry: (oldPath, newPath) => {
@@ -18053,7 +18217,8 @@ const useForgeStore = create((set, get) => ({
18053
18217
  assemblyState,
18054
18218
  ...useServer ? {} : { assemblyDisplayMode: "instanceTransforms" },
18055
18219
  isNotebook: false,
18056
- activeBackend: executionBackend
18220
+ activeBackend: executionBackend,
18221
+ disableShapeBuildCache: get().disableRunCache
18057
18222
  };
18058
18223
  let serialized;
18059
18224
  if (useServer) {
@@ -19037,6 +19202,7 @@ const useForgeStore = create((set, get) => ({
19037
19202
  savedFiles: { ...INITIAL_SAVED },
19038
19203
  folders: [],
19039
19204
  activeFile: initialActive,
19205
+ recentFiles: touchAndPersistRecentFile([], INITIAL_FILES, initialActive),
19040
19206
  previewFile: initialPreviewFile,
19041
19207
  objectSettings: getObjectSettingsForPreviewFile(initialObjectSettingsByFile, initialPreviewFile),
19042
19208
  objectSettingsByFile: initialObjectSettingsByFile,
@@ -19077,7 +19243,9 @@ const useForgeStore = create((set, get) => ({
19077
19243
  const handle = await window.showSaveFilePicker({
19078
19244
  suggestedName: activeFile,
19079
19245
  types: [
19080
- { description: "ForgeCAD scripts", accept: { "text/javascript": [".forge.js", ".js"] } },
19246
+ { description: "ForgeCAD scripts", accept: { "text/javascript": [".forge.js", ".sketch.js", ".js"] } },
19247
+ { description: "Text files", accept: { "text/plain": SAVE_PICKER_TEXT_FILE_EXTS } },
19248
+ { description: "Markdown", accept: { "text/markdown": [".md"] } },
19081
19249
  { description: "SVG", accept: { "image/svg+xml": [".svg"] } },
19082
19250
  { description: "DXF", accept: { "application/dxf": [".dxf"] } }
19083
19251
  ]
@@ -19092,7 +19260,7 @@ const useForgeStore = create((set, get) => ({
19092
19260
  // This assumes we are satisfied with current content being "saved"
19093
19261
  }));
19094
19262
  } else {
19095
- const mime = activeFile.endsWith(".svg") ? "image/svg+xml" : activeFile.endsWith(".dxf") ? "application/dxf" : "text/javascript";
19263
+ const mime = projectTextFileMimeType(activeFile);
19096
19264
  const blob = new Blob([code], { type: mime });
19097
19265
  triggerDownload(blob, activeFile);
19098
19266
  set((s) => ({
@@ -19111,6 +19279,7 @@ const useForgeStore = create((set, get) => ({
19111
19279
  files: { ...s.files, [normalized]: text },
19112
19280
  savedFiles: { ...s.savedFiles, [normalized]: text },
19113
19281
  activeFile: normalized,
19282
+ recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: text }, normalized),
19114
19283
  fileHandle: null,
19115
19284
  dirty: false,
19116
19285
  previewFile: null,
@@ -19169,6 +19338,7 @@ const useForgeStore = create((set, get) => ({
19169
19338
  return {
19170
19339
  files: nextFiles,
19171
19340
  activeFile: nextActive,
19341
+ recentFiles: touchAndPersistRecentFile(s.recentFiles, nextFiles, nextActive),
19172
19342
  fileHandle: null,
19173
19343
  dirty: true,
19174
19344
  previewFile: null,
@@ -19247,9 +19417,15 @@ const useForgeStore = create((set, get) => ({
19247
19417
  const prevFiles = state2.files;
19248
19418
  const prevActiveFile = state2.activeFile;
19249
19419
  const nextState = computeServerSnapshot(state2, serverFiles, serverFolders, sharedModel, sharedBundle, initialFile);
19250
- set({ ...nextState, filesLoading: false });
19251
19420
  const nextFiles = nextState.files;
19252
- const newActiveFile = nextState.activeFile;
19421
+ const newActiveFile = nextState.activeFile ?? state2.activeFile;
19422
+ const baseRecentFiles = readRecentFilesOrFallback(state2.recentFiles);
19423
+ set({
19424
+ ...nextState,
19425
+ activeFile: newActiveFile,
19426
+ recentFiles: touchAndPersistRecentFile(baseRecentFiles, nextFiles, newActiveFile),
19427
+ filesLoading: false
19428
+ });
19253
19429
  const needsLazyLoad = newActiveFile && newActiveFile in nextFiles && nextFiles[newActiveFile] === "" && fileSystem.fetchFileContent;
19254
19430
  postApplyServerSnapshot(
19255
19431
  prevActiveFile,
@@ -19270,7 +19446,7 @@ const useForgeStore = create((set, get) => ({
19270
19446
  const state2 = get();
19271
19447
  const nextState = computeServerFileChange(state2, filename, content);
19272
19448
  if (!nextState) return;
19273
- set(nextState);
19449
+ set({ ...nextState, recentFiles: pruneAndPersistRecentFiles(state2.recentFiles, nextState.files) });
19274
19450
  const previewFile = resolvePreviewFile(state2.activeFile, nextState.files);
19275
19451
  if (previewFile === filename) setTimeout(() => get().execute(), 0);
19276
19452
  },
@@ -19279,13 +19455,18 @@ const useForgeStore = create((set, get) => ({
19279
19455
  const prevActiveFile = state2.activeFile;
19280
19456
  const nextState = computeServerFileDelete(state2, filename);
19281
19457
  if (!nextState) return;
19282
- set(nextState);
19283
- if (nextState.activeFile && nextState.activeFile !== prevActiveFile) {
19458
+ const newActiveFile = nextState.activeFile ?? "";
19459
+ set({
19460
+ ...nextState,
19461
+ activeFile: newActiveFile,
19462
+ recentFiles: touchAndPersistRecentFile(state2.recentFiles, nextState.files, newActiveFile)
19463
+ });
19464
+ if (newActiveFile && newActiveFile !== prevActiveFile) {
19284
19465
  set({ paramOverrides: {}, lastValidResult: null });
19285
19466
  setParamOverrides({});
19286
- window.history.replaceState(null, "", `#${nextState.activeFile}`);
19467
+ window.history.replaceState(null, "", `#${newActiveFile}`);
19287
19468
  try {
19288
- localStorage.setItem(lastActiveFileKey(), nextState.activeFile);
19469
+ localStorage.setItem(lastActiveFileKey(), newActiveFile);
19289
19470
  } catch {
19290
19471
  }
19291
19472
  setTimeout(() => get().execute(), 0);
@@ -19312,6 +19493,9 @@ const useForgeStore = create((set, get) => ({
19312
19493
  shareOpen: false,
19313
19494
  openShare: () => set({ shareOpen: true }),
19314
19495
  closeShare: () => set({ shareOpen: false }),
19496
+ captureOpen: false,
19497
+ openCapture: () => set({ captureOpen: true }),
19498
+ closeCapture: () => set({ captureOpen: false }),
19315
19499
  aiSkillOpen: false,
19316
19500
  openAISkill: () => set({ aiSkillOpen: true }),
19317
19501
  closeAISkill: () => set({ aiSkillOpen: false }),
@@ -19324,7 +19508,10 @@ const useForgeStore = create((set, get) => ({
19324
19508
  disableRunCache: initialViewPreferences.disableRunCache ?? false,
19325
19509
  setDisableRunCache: (disabled) => {
19326
19510
  writeViewPreferences({ disableRunCache: disabled });
19327
- if (disabled) clearRunResultCache();
19511
+ if (disabled) {
19512
+ clearRunResultCache();
19513
+ evalWorkerClient.clearCaches();
19514
+ }
19328
19515
  set({ disableRunCache: disabled });
19329
19516
  }
19330
19517
  }));
@@ -27435,7 +27622,7 @@ function ConstructionGhostOverlay({ matrix }) {
27435
27622
  class ConstructionHistoryWorkerClient {
27436
27623
  constructor(workerFactory = () => new Worker(new URL(
27437
27624
  /* @vite-ignore */
27438
- "/assets/constructionHistoryWorker-Ba2Hm58b.js",
27625
+ "/assets/constructionHistoryWorker-AwMMWSxg.js",
27439
27626
  import.meta.url
27440
27627
  ), { type: "module" })) {
27441
27628
  __publicField(this, "worker", null);
@@ -29591,14 +29778,89 @@ ensureNotFinalized_fn = function() {
29591
29778
  throw new Error("Cannot add new video or audio chunks after the file has been finalized.");
29592
29779
  }
29593
29780
  };
29781
+ const H264_HIGH_LEVEL_4_CODEC = "avc1.640028";
29782
+ const H264_LEVEL_4_MAX_CODED_AREA = 2097152;
29783
+ const H264_MACROBLOCK_SIZE = 16;
29594
29784
  function supportsMP4Recording() {
29595
29785
  return typeof VideoEncoder !== "undefined" && typeof VideoFrame !== "undefined";
29596
29786
  }
29787
+ function evenFloor(value) {
29788
+ return Math.max(2, Math.floor(value) & -2);
29789
+ }
29790
+ function codedDimension(value) {
29791
+ return Math.ceil(value / H264_MACROBLOCK_SIZE) * H264_MACROBLOCK_SIZE;
29792
+ }
29793
+ function codedArea(width, height) {
29794
+ return codedDimension(width) * codedDimension(height);
29795
+ }
29796
+ function resolveCanvasBackground(canvas) {
29797
+ const transparent = /* @__PURE__ */ new Set(["transparent", "rgba(0, 0, 0, 0)"]);
29798
+ let node = canvas;
29799
+ while (node) {
29800
+ const backgroundColor = getComputedStyle(node).backgroundColor;
29801
+ if (backgroundColor && !transparent.has(backgroundColor)) return backgroundColor;
29802
+ node = node.parentElement;
29803
+ }
29804
+ return "#252526";
29805
+ }
29806
+ function fitH264Level4RecordingSize(sourceWidth, sourceHeight) {
29807
+ if (!Number.isFinite(sourceWidth) || !Number.isFinite(sourceHeight) || sourceWidth <= 0 || sourceHeight <= 0) {
29808
+ throw new Error(`Cannot record MP4 from invalid canvas size ${sourceWidth}x${sourceHeight}.`);
29809
+ }
29810
+ const evenSourceWidth = evenFloor(sourceWidth);
29811
+ const evenSourceHeight = evenFloor(sourceHeight);
29812
+ let width = evenSourceWidth;
29813
+ let height = evenSourceHeight;
29814
+ if (codedArea(width, height) > H264_LEVEL_4_MAX_CODED_AREA) {
29815
+ let low = 0;
29816
+ let high = 1;
29817
+ let bestWidth = 2;
29818
+ let bestHeight = 2;
29819
+ for (let i = 0; i < 32; i += 1) {
29820
+ const scale = (low + high) / 2;
29821
+ const candidateWidth = evenFloor(evenSourceWidth * scale);
29822
+ const candidateHeight = evenFloor(evenSourceHeight * scale);
29823
+ if (codedArea(candidateWidth, candidateHeight) <= H264_LEVEL_4_MAX_CODED_AREA) {
29824
+ bestWidth = candidateWidth;
29825
+ bestHeight = candidateHeight;
29826
+ low = scale;
29827
+ } else {
29828
+ high = scale;
29829
+ }
29830
+ }
29831
+ width = bestWidth;
29832
+ height = bestHeight;
29833
+ }
29834
+ const codedWidth = codedDimension(width);
29835
+ const codedHeight = codedDimension(height);
29836
+ if (codedWidth * codedHeight > H264_LEVEL_4_MAX_CODED_AREA) {
29837
+ throw new Error(`Could not fit ${sourceWidth}x${sourceHeight} into the MP4 encoder limit.`);
29838
+ }
29839
+ return {
29840
+ sourceWidth: evenSourceWidth,
29841
+ sourceHeight: evenSourceHeight,
29842
+ width,
29843
+ height,
29844
+ codedWidth,
29845
+ codedHeight,
29846
+ scale: Math.min(width / evenSourceWidth, height / evenSourceHeight)
29847
+ };
29848
+ }
29849
+ async function requireSupportedEncoderConfig(config) {
29850
+ if (typeof VideoEncoder.isConfigSupported !== "function") return config;
29851
+ const support = await VideoEncoder.isConfigSupported(config);
29852
+ if (!support.supported) {
29853
+ throw new Error(`MP4 recording is not supported at ${config.width}x${config.height} in this browser.`);
29854
+ }
29855
+ return support.config ?? config;
29856
+ }
29597
29857
  class HistoryRecorder {
29598
29858
  constructor(canvas, options) {
29599
29859
  __publicField(this, "encoder", null);
29600
29860
  __publicField(this, "muxer", null);
29601
29861
  __publicField(this, "target", null);
29862
+ __publicField(this, "captureCanvas", null);
29863
+ __publicField(this, "captureContext", null);
29602
29864
  __publicField(this, "frameCount", 0);
29603
29865
  __publicField(this, "recording", false);
29604
29866
  __publicField(this, "startTime", 0);
@@ -29615,37 +29877,71 @@ class HistoryRecorder {
29615
29877
  /** Start recording. Must be called before captureFrame(). */
29616
29878
  async start() {
29617
29879
  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",
29880
+ if (!supportsMP4Recording()) {
29881
+ throw new Error("MP4 recording requires WebCodecs VideoEncoder and VideoFrame support.");
29882
+ }
29883
+ const size = fitH264Level4RecordingSize(this.canvas.width, this.canvas.height);
29884
+ const config = await requireSupportedEncoderConfig({
29885
+ codec: H264_HIGH_LEVEL_4_CODEC,
29639
29886
  // H.264 High Profile Level 4.0
29640
- width,
29641
- height,
29887
+ width: size.width,
29888
+ height: size.height,
29642
29889
  bitrate: this.bitrate,
29643
29890
  framerate: this.fps
29644
29891
  });
29892
+ try {
29893
+ this.prepareCaptureSurface(size);
29894
+ this.target = new ArrayBufferTarget();
29895
+ this.muxer = new Muxer({
29896
+ target: this.target,
29897
+ video: {
29898
+ codec: "avc",
29899
+ width: size.width,
29900
+ height: size.height
29901
+ },
29902
+ fastStart: "in-memory",
29903
+ firstTimestampBehavior: "offset"
29904
+ });
29905
+ this.encoder = new VideoEncoder({
29906
+ output: (chunk, meta) => {
29907
+ this.muxer.addVideoChunk(chunk, meta);
29908
+ },
29909
+ error: (e2) => {
29910
+ this.recording = false;
29911
+ console.error("VideoEncoder error:", e2);
29912
+ }
29913
+ });
29914
+ this.encoder.configure(config);
29915
+ } catch (err) {
29916
+ this.abort();
29917
+ throw err;
29918
+ }
29645
29919
  this.frameCount = 0;
29646
29920
  this.startTime = performance.now();
29647
29921
  this.recording = true;
29648
29922
  }
29923
+ prepareCaptureSurface(size) {
29924
+ this.captureCanvas = null;
29925
+ this.captureContext = null;
29926
+ const captureCanvas = document.createElement("canvas");
29927
+ captureCanvas.width = size.width;
29928
+ captureCanvas.height = size.height;
29929
+ const captureContext = captureCanvas.getContext("2d", { alpha: false });
29930
+ if (!captureContext) {
29931
+ throw new Error("Could not create MP4 recording capture canvas.");
29932
+ }
29933
+ this.captureCanvas = captureCanvas;
29934
+ this.captureContext = captureContext;
29935
+ }
29936
+ frameSourceCanvas() {
29937
+ if (!this.captureCanvas || !this.captureContext) {
29938
+ throw new Error("MP4 recording capture surface is not initialized.");
29939
+ }
29940
+ this.captureContext.fillStyle = resolveCanvasBackground(this.canvas);
29941
+ this.captureContext.fillRect(0, 0, this.captureCanvas.width, this.captureCanvas.height);
29942
+ this.captureContext.drawImage(this.canvas, 0, 0, this.captureCanvas.width, this.captureCanvas.height);
29943
+ return this.captureCanvas;
29944
+ }
29649
29945
  /**
29650
29946
  * Capture the current canvas frame. Forces a render so the drawing buffer
29651
29947
  * has fresh content — this avoids needing `preserveDrawingBuffer: true` on
@@ -29676,7 +29972,11 @@ class HistoryRecorder {
29676
29972
  this.encodeFrame(timestampMicros);
29677
29973
  }
29678
29974
  encodeFrame(timestampMicros) {
29679
- const frame2 = new VideoFrame(this.canvas, {
29975
+ if (!this.encoder || this.encoder.state !== "configured") {
29976
+ this.recording = false;
29977
+ return;
29978
+ }
29979
+ const frame2 = new VideoFrame(this.frameSourceCanvas(), {
29680
29980
  timestamp: timestampMicros
29681
29981
  });
29682
29982
  const keyFrame = this.frameCount % (this.fps * 2) === 0;
@@ -29684,20 +29984,32 @@ class HistoryRecorder {
29684
29984
  frame2.close();
29685
29985
  this.frameCount++;
29686
29986
  }
29987
+ reset() {
29988
+ this.encoder = null;
29989
+ this.muxer = null;
29990
+ this.target = null;
29991
+ this.captureCanvas = null;
29992
+ this.captureContext = null;
29993
+ this.frameCount = 0;
29994
+ this.startTime = 0;
29995
+ }
29687
29996
  /** Stop recording and return the MP4 blob. */
29688
29997
  async stop() {
29689
29998
  if (!this.encoder || !this.muxer || !this.target) {
29690
29999
  throw new Error("Not recording");
29691
30000
  }
30001
+ const encoder2 = this.encoder;
30002
+ const muxer = this.muxer;
30003
+ const target = this.target;
29692
30004
  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" });
30005
+ try {
30006
+ await encoder2.flush();
30007
+ if (encoder2.state !== "closed") encoder2.close();
30008
+ muxer.finalize();
30009
+ return new Blob([target.buffer], { type: "video/mp4" });
30010
+ } finally {
30011
+ this.reset();
30012
+ }
29701
30013
  }
29702
30014
  /** Abort recording without producing output. */
29703
30015
  abort() {
@@ -29709,10 +30021,7 @@ class HistoryRecorder {
29709
30021
  } catch {
29710
30022
  }
29711
30023
  }
29712
- this.encoder = null;
29713
- this.muxer = null;
29714
- this.target = null;
29715
- this.frameCount = 0;
30024
+ this.reset();
29716
30025
  }
29717
30026
  }
29718
30027
  let registeredStart$2 = null;
@@ -29768,6 +30077,12 @@ function HistoryRecorderBridge() {
29768
30077
  requestAnimationFrame(() => {
29769
30078
  useForgeStore.getState().setHistoryPlaying(true);
29770
30079
  });
30080
+ }).catch((err) => {
30081
+ recorderRef.current = null;
30082
+ state2.setHistoryRecording(false);
30083
+ state2.setHistoryPlaying(false);
30084
+ showToast("Recording failed", "error");
30085
+ console.error("History recording failed:", err);
29771
30086
  });
29772
30087
  }, [gl]);
29773
30088
  const stop = reactExports.useCallback(async () => {
@@ -29819,7 +30134,7 @@ function generateReportInWorker(options) {
29819
30134
  return new Promise((resolve2, reject) => {
29820
30135
  const worker = new Worker(new URL(
29821
30136
  /* @vite-ignore */
29822
- "/assets/reportWorker-0AGij1Ru.js",
30137
+ "/assets/reportWorker-DO6hcQbh.js",
29823
30138
  import.meta.url
29824
30139
  ), { type: "module" });
29825
30140
  const cleanup = () => {
@@ -30502,9 +30817,13 @@ function buildSketchExportArtifacts(format, stem, runResult) {
30502
30817
  });
30503
30818
  }
30504
30819
  let orbitVideoExporter = null;
30820
+ let viewportImageExporter = null;
30505
30821
  function registerOrbitVideoExporter(exporter) {
30506
30822
  orbitVideoExporter = exporter;
30507
30823
  }
30824
+ function registerViewportImageExporter(exporter) {
30825
+ viewportImageExporter = exporter;
30826
+ }
30508
30827
  function rerunActiveScriptForQuality(quality) {
30509
30828
  const { files, activeFile, paramOverrides } = useForgeStore.getState();
30510
30829
  const assemblyState = resolveAssemblyStateForExport();
@@ -30630,6 +30949,23 @@ async function exportOrbitVideoFromStore(preferredStem, options) {
30630
30949
  const ext = blob.type === "video/mp4" ? "mp4" : "gif";
30631
30950
  triggerDownload(blob, `${stem}.orbit.${ext}`);
30632
30951
  }
30952
+ async function captureViewportImageBlobFromStore(options) {
30953
+ if (!viewportImageExporter) {
30954
+ throw new Error("Viewport is not ready for image capture. Try again in a moment.");
30955
+ }
30956
+ const { activeFile, theme } = useForgeStore.getState();
30957
+ return viewportImageExporter({
30958
+ title: deriveExportStem(activeFile),
30959
+ themeName: theme,
30960
+ ...options ?? {}
30961
+ });
30962
+ }
30963
+ async function exportViewportImageFromStore(preferredStem, options) {
30964
+ const { activeFile } = useForgeStore.getState();
30965
+ const stem = sanitizeExportStem(deriveExportStem(activeFile));
30966
+ const blob = await captureViewportImageBlobFromStore(options);
30967
+ triggerDownload(blob, `${stem}.png`);
30968
+ }
30633
30969
  function exportSketchFromStore(format, preferredStem) {
30634
30970
  const { result, activeFile } = useForgeStore.getState();
30635
30971
  const stem = sanitizeExportStem(preferredStem ?? deriveExportStem(activeFile));
@@ -30890,6 +31226,11 @@ function ViewportRecordingBridge({ controlsRef }) {
30890
31226
  frameIntervalRef.current = 1e3 / TARGET_FPS;
30891
31227
  void recorder.start().then(() => {
30892
31228
  useForgeStore.getState().setAnimationRecording(true);
31229
+ }).catch((err) => {
31230
+ recorderRef.current = null;
31231
+ useForgeStore.getState().setAnimationRecording(false);
31232
+ showToast("Recording failed", "error");
31233
+ console.error("Viewport recording failed:", err);
30893
31234
  });
30894
31235
  }, [gl, snapshotOrbitTarget]);
30895
31236
  const stop = reactExports.useCallback(async () => {
@@ -31725,6 +32066,14 @@ function TrajectoryRecorderBridge({ controlsRef }) {
31725
32066
  state2.setTrajectoryProgress(0);
31726
32067
  void recorder.start().then(() => {
31727
32068
  state2.setTrajectoryRecording(true);
32069
+ }).catch((err) => {
32070
+ recorderRef.current = null;
32071
+ trajectoryRef.current = null;
32072
+ lastFrameRef.current = 0;
32073
+ manualStartRef.current = 0;
32074
+ state2.setTrajectoryRecording(false);
32075
+ showToast("Recording failed", "error");
32076
+ console.error("Trajectory recording failed:", err);
31728
32077
  });
31729
32078
  }, [gl]);
31730
32079
  const stop = reactExports.useCallback(async () => {
@@ -32681,6 +33030,164 @@ function OrbitExporterBridge({ controlsRef }) {
32681
33030
  }, [exportOrbitVideo]);
32682
33031
  return null;
32683
33032
  }
33033
+ function clampDimension(value, fallback) {
33034
+ const numeric = Number.isFinite(value) ? value : fallback;
33035
+ return Math.max(96, Math.min(4096, Math.round(numeric ?? 96)));
33036
+ }
33037
+ function computeFrameLayout(width, height, includeFrame) {
33038
+ if (!includeFrame) return { x: 0, y: 0, width, height, headerHeight: 0, pad: 0 };
33039
+ const pad = Math.max(14, Math.round(Math.min(width, height) * 0.035));
33040
+ const headerHeight = Math.max(50, Math.min(96, Math.round(height * 0.105)));
33041
+ return {
33042
+ x: pad,
33043
+ y: headerHeight,
33044
+ width: Math.max(1, width - pad * 2),
33045
+ height: Math.max(1, height - headerHeight - pad),
33046
+ headerHeight,
33047
+ pad
33048
+ };
33049
+ }
33050
+ function cssVar(name, fallback) {
33051
+ if (typeof document === "undefined") return fallback;
33052
+ const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
33053
+ return value || fallback;
33054
+ }
33055
+ function canvasBlob(canvas) {
33056
+ return new Promise((resolve2, reject) => {
33057
+ canvas.toBlob((blob) => blob ? resolve2(blob) : reject(new Error("Could not encode PNG capture.")), "image/png");
33058
+ });
33059
+ }
33060
+ function drawCorner(ctx, x, y, dx, dy, size) {
33061
+ ctx.beginPath();
33062
+ ctx.moveTo(x, y + dy * size);
33063
+ ctx.lineTo(x, y);
33064
+ ctx.lineTo(x + dx * size, y);
33065
+ ctx.stroke();
33066
+ }
33067
+ function composeFrame(source, outputWidth, outputHeight, layout, options) {
33068
+ var _a3;
33069
+ const canvas = document.createElement("canvas");
33070
+ canvas.width = outputWidth;
33071
+ canvas.height = outputHeight;
33072
+ const ctx = canvas.getContext("2d");
33073
+ if (!ctx) throw new Error("Could not create PNG capture context.");
33074
+ const bg = cssVar("--fc-bg", "#02070a");
33075
+ const surface = cssVar("--fc-bgSurface", "#0a2028");
33076
+ const border = cssVar("--fc-border", "#165363");
33077
+ const accent = cssVar("--fc-accent", "#36e8ff");
33078
+ const text = cssVar("--fc-text", "#d9fbff");
33079
+ const muted = cssVar("--fc-textMuted", "#8ebdc6");
33080
+ const dim = cssVar("--fc-textDim", "#4e7078");
33081
+ const title = ((_a3 = options.title) == null ? void 0 : _a3.trim()) || "ForgeCAD model";
33082
+ const themeLabel = options.themeName ? `${options.themeName} theme` : "current theme";
33083
+ ctx.fillStyle = bg;
33084
+ ctx.fillRect(0, 0, outputWidth, outputHeight);
33085
+ ctx.fillStyle = surface;
33086
+ ctx.fillRect(0, 0, outputWidth, layout.headerHeight);
33087
+ ctx.strokeStyle = border;
33088
+ ctx.lineWidth = Math.max(1, Math.round(outputWidth / 900));
33089
+ ctx.strokeRect(layout.x - 1, layout.y - 1, layout.width + 2, layout.height + 2);
33090
+ ctx.drawImage(source, layout.x, layout.y, layout.width, layout.height);
33091
+ ctx.strokeStyle = accent;
33092
+ ctx.globalAlpha = 0.85;
33093
+ ctx.beginPath();
33094
+ ctx.moveTo(layout.pad, layout.headerHeight - 1);
33095
+ ctx.lineTo(outputWidth - layout.pad, layout.headerHeight - 1);
33096
+ ctx.stroke();
33097
+ const corner = Math.max(18, Math.min(42, Math.round(Math.min(outputWidth, outputHeight) * 0.035)));
33098
+ drawCorner(ctx, layout.pad, layout.y, 1, 1, corner);
33099
+ drawCorner(ctx, outputWidth - layout.pad, layout.y, -1, 1, corner);
33100
+ drawCorner(ctx, layout.pad, outputHeight - layout.pad, 1, -1, corner);
33101
+ drawCorner(ctx, outputWidth - layout.pad, outputHeight - layout.pad, -1, -1, corner);
33102
+ ctx.globalAlpha = 1;
33103
+ const mono = "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace";
33104
+ ctx.fillStyle = accent;
33105
+ ctx.font = `700 ${Math.max(11, Math.round(outputHeight * 0.012))}px ${mono}`;
33106
+ ctx.fillText("FORGECAD CAPTURE", layout.pad, Math.round(layout.headerHeight * 0.35));
33107
+ ctx.fillStyle = text;
33108
+ ctx.font = `700 ${Math.max(16, Math.round(outputHeight * 0.021))}px Inter, system-ui, sans-serif`;
33109
+ ctx.fillText(title.slice(0, 64), layout.pad, Math.round(layout.headerHeight * 0.72));
33110
+ ctx.fillStyle = muted;
33111
+ ctx.font = `500 ${Math.max(11, Math.round(outputHeight * 0.012))}px ${mono}`;
33112
+ ctx.textAlign = "right";
33113
+ ctx.fillText(`${outputWidth}x${outputHeight}`, outputWidth - layout.pad, Math.round(layout.headerHeight * 0.35));
33114
+ ctx.fillStyle = dim;
33115
+ ctx.fillText(themeLabel, outputWidth - layout.pad, Math.round(layout.headerHeight * 0.72));
33116
+ ctx.textAlign = "left";
33117
+ return canvas;
33118
+ }
33119
+ function copyRenderedCanvas(source, width, height) {
33120
+ const canvas = document.createElement("canvas");
33121
+ canvas.width = width;
33122
+ canvas.height = height;
33123
+ const ctx = canvas.getContext("2d");
33124
+ if (!ctx) throw new Error("Could not create PNG capture context.");
33125
+ ctx.drawImage(source, 0, 0, width, height);
33126
+ return canvas;
33127
+ }
33128
+ function withCameraAspect(camera, width, height, render) {
33129
+ if (camera instanceof PerspectiveCamera$1) {
33130
+ const previousAspect = camera.aspect;
33131
+ camera.aspect = width / height;
33132
+ camera.updateProjectionMatrix();
33133
+ try {
33134
+ render();
33135
+ } finally {
33136
+ camera.aspect = previousAspect;
33137
+ camera.updateProjectionMatrix();
33138
+ }
33139
+ return;
33140
+ }
33141
+ if (camera instanceof OrthographicCamera$1) {
33142
+ const previous = { left: camera.left, right: camera.right, top: camera.top, bottom: camera.bottom };
33143
+ const midX = (camera.left + camera.right) / 2;
33144
+ const halfHeight = (camera.top - camera.bottom) / 2;
33145
+ const halfWidth = halfHeight * (width / height);
33146
+ camera.left = midX - halfWidth;
33147
+ camera.right = midX + halfWidth;
33148
+ camera.updateProjectionMatrix();
33149
+ try {
33150
+ render();
33151
+ } finally {
33152
+ Object.assign(camera, previous);
33153
+ camera.updateProjectionMatrix();
33154
+ }
33155
+ return;
33156
+ }
33157
+ render();
33158
+ }
33159
+ function ViewportImageCaptureBridge() {
33160
+ const { camera, gl, scene } = useThree();
33161
+ const capture = reactExports.useCallback(
33162
+ async (options) => {
33163
+ const currentSize = gl.getSize(new Vector2());
33164
+ const outputWidth = clampDimension(options == null ? void 0 : options.width, currentSize.x);
33165
+ const outputHeight = clampDimension(options == null ? void 0 : options.height, currentSize.y);
33166
+ const layout = computeFrameLayout(outputWidth, outputHeight, (options == null ? void 0 : options.includeFrame) ?? true);
33167
+ const previousSize = currentSize.clone();
33168
+ const previousPixelRatio = gl.getPixelRatio();
33169
+ try {
33170
+ await waitForAnimationFrame();
33171
+ gl.setPixelRatio(1);
33172
+ gl.setSize(layout.width, layout.height, false);
33173
+ withCameraAspect(camera, layout.width, layout.height, () => gl.render(scene, camera));
33174
+ const rendered = copyRenderedCanvas(gl.domElement, layout.width, layout.height);
33175
+ const finalCanvas = (options == null ? void 0 : options.includeFrame) === false ? rendered : composeFrame(rendered, outputWidth, outputHeight, layout, options ?? {});
33176
+ return await canvasBlob(finalCanvas);
33177
+ } finally {
33178
+ gl.setPixelRatio(previousPixelRatio);
33179
+ gl.setSize(previousSize.x, previousSize.y, false);
33180
+ gl.render(scene, camera);
33181
+ }
33182
+ },
33183
+ [camera, gl, scene]
33184
+ );
33185
+ reactExports.useEffect(() => {
33186
+ registerViewportImageExporter(capture);
33187
+ return () => registerViewportImageExporter(null);
33188
+ }, [capture]);
33189
+ return null;
33190
+ }
32684
33191
  const DEBUG_HL_DEFAULT_COLOR = "#ff00ff";
32685
33192
  const DEBUG_HL_DEFAULT_POINT_SIZE = 3;
32686
33193
  const DEBUG_HL_DEFAULT_PLANE_SIZE = 50;
@@ -33676,7 +34183,7 @@ function SectionCapPreview({
33676
34183
  class ScanProxyWorkerClient {
33677
34184
  constructor(workerFactory = () => new Worker(new URL(
33678
34185
  /* @vite-ignore */
33679
- "/assets/scanProxyWorker-Vl4Wxa1y.js",
34186
+ "/assets/scanProxyWorker-2GtDLk-R.js",
33680
34187
  import.meta.url
33681
34188
  ), { type: "module" })) {
33682
34189
  __publicField(this, "worker", null);
@@ -34191,20 +34698,34 @@ function ScanProxyLayer({
34191
34698
  fillOpacity,
34192
34699
  wireOpacity,
34193
34700
  clippingPlanes,
34194
- additiveWire,
34701
+ volumeFill = false,
34702
+ showWire = true,
34195
34703
  analysisGeometry
34196
34704
  }) {
34197
34705
  const fillGeometry = analysisGeometry ?? geometry;
34198
34706
  if (!geometry || !fillGeometry) return null;
34199
34707
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment, { children: [
34200
- /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: fillGeometry, raycast: () => null, children: analysisGeometry ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34708
+ /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: fillGeometry, raycast: () => null, children: volumeFill ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34201
34709
  "meshBasicMaterial",
34202
34710
  {
34203
- vertexColors: true,
34711
+ color: color2,
34204
34712
  transparent: true,
34205
- opacity: fillOpacity,
34713
+ opacity: Math.min(0.1, fillOpacity * 0.2),
34206
34714
  side: DoubleSide,
34207
34715
  depthWrite: false,
34716
+ blending: AdditiveBlending,
34717
+ toneMapped: false,
34718
+ clippingPlanes
34719
+ }
34720
+ ) : analysisGeometry ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34721
+ "meshBasicMaterial",
34722
+ {
34723
+ vertexColors: true,
34724
+ opacity: fillOpacity,
34725
+ side: DoubleSide,
34726
+ alphaHash: true,
34727
+ alphaToCoverage: true,
34728
+ depthWrite: true,
34208
34729
  toneMapped: false,
34209
34730
  clippingPlanes
34210
34731
  }
@@ -34212,41 +34733,40 @@ function ScanProxyLayer({
34212
34733
  "meshBasicMaterial",
34213
34734
  {
34214
34735
  color: color2,
34215
- transparent: true,
34216
34736
  opacity: fillOpacity,
34217
34737
  side: DoubleSide,
34218
- depthWrite: false,
34738
+ alphaHash: true,
34739
+ alphaToCoverage: true,
34740
+ depthWrite: true,
34219
34741
  toneMapped: false,
34220
34742
  clippingPlanes
34221
34743
  }
34222
34744
  ) }),
34223
- /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34745
+ showWire && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34224
34746
  "meshBasicMaterial",
34225
34747
  {
34226
34748
  color: color2,
34227
- transparent: true,
34228
34749
  opacity: wireOpacity,
34229
34750
  wireframe: true,
34230
34751
  side: DoubleSide,
34231
- depthWrite: false,
34232
- blending: additiveWire ? AdditiveBlending : NormalBlending,
34752
+ alphaHash: true,
34753
+ alphaToCoverage: true,
34754
+ depthWrite: true,
34233
34755
  toneMapped: false,
34234
34756
  clippingPlanes
34235
34757
  }
34236
34758
  ) })
34237
34759
  ] });
34238
34760
  }
34239
- function objectColorScanLayerStyles(color2) {
34240
- return SCAN_PROXY_LAYER_STYLES.map((layer) => ({ ...layer, color: color2 }));
34241
- }
34242
34761
  function ScanProxyVolume({
34243
34762
  proxy,
34244
34763
  clippingPlanes,
34245
34764
  objectColor,
34765
+ volumeFill = false,
34766
+ showWire = true,
34246
34767
  analysisGeometries
34247
34768
  }) {
34248
- const layers = objectColor ? objectColorScanLayerStyles(objectColor) : SCAN_PROXY_LAYER_STYLES;
34249
- const additiveWire = objectColor === void 0;
34769
+ const layers = reactExports.useMemo(() => objectColor ? scanMaterialLayerStyles(objectColor) : SCAN_PROXY_LAYER_STYLES, [objectColor]);
34250
34770
  return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { userData: { scanProxyTelemetry: proxy.telemetry }, children: layers.map((layer) => {
34251
34771
  const analysisGeometry = (analysisGeometries == null ? void 0 : analysisGeometries[layer.material]) ?? null;
34252
34772
  const fillOpacity = analysisGeometry ? Math.max(layer.fillOpacity, 0.78) : layer.fillOpacity;
@@ -34257,7 +34777,8 @@ function ScanProxyVolume({
34257
34777
  geometry: proxy.geometries[layer.material],
34258
34778
  analysisGeometry,
34259
34779
  clippingPlanes,
34260
- additiveWire,
34780
+ volumeFill,
34781
+ showWire,
34261
34782
  ...layer,
34262
34783
  fillOpacity,
34263
34784
  wireOpacity
@@ -34574,32 +35095,28 @@ function ForgeObject({
34574
35095
  ) }),
34575
35096
  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
35097
  ] }),
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
- ] })),
35098
+ showScanRenderStyle && (scanProxy ? /* @__PURE__ */ jsxRuntimeExports.jsx(
35099
+ ScanProxyVolume,
35100
+ {
35101
+ proxy: scanProxy,
35102
+ clippingPlanes: effectiveClippingPlanes,
35103
+ objectColor: settings.color,
35104
+ volumeFill: true,
35105
+ showWire: false
35106
+ }
35107
+ ) : showScanFallbackMesh && /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: solidGeo, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35108
+ "meshBasicMaterial",
35109
+ {
35110
+ color: scanMaterialShellColor(settings.color),
35111
+ transparent: true,
35112
+ opacity: 0.08,
35113
+ side: DoubleSide,
35114
+ depthWrite: false,
35115
+ blending: AdditiveBlending,
35116
+ toneMapped: false,
35117
+ clippingPlanes: effectiveClippingPlanes
35118
+ }
35119
+ ) })),
34603
35120
  showMatrixRenderStyle && (scanProxy ? /* @__PURE__ */ jsxRuntimeExports.jsx(
34604
35121
  MatrixGlyphVolume,
34605
35122
  {
@@ -43120,7 +43637,7 @@ function directCadKindForPath(path) {
43120
43637
  return null;
43121
43638
  }
43122
43639
  function directCadReferenceCode(path, kind) {
43123
- const fn = kind === "step" ? "importStep" : "importMesh";
43640
+ const fn = kind === "step" ? "Import.step" : "Import.mesh";
43124
43641
  const name = basename(path);
43125
43642
  return [`const imported = ${fn}(${JSON.stringify(path)});`, `return [{ name: ${JSON.stringify(name)}, shape: imported }];`].join("\n");
43126
43643
  }
@@ -43274,7 +43791,7 @@ function useGeometryComparison(args) {
43274
43791
  class InspectWorkerClient {
43275
43792
  constructor(workerFactory = () => new Worker(new URL(
43276
43793
  /* @vite-ignore */
43277
- "/assets/inspectWorker-BuTJDVX6.js",
43794
+ "/assets/inspectWorker-CZsCFtQT.js",
43278
43795
  import.meta.url
43279
43796
  ), { type: "module" })) {
43280
43797
  __publicField(this, "worker", null);
@@ -44467,7 +44984,7 @@ function Viewport() {
44467
44984
  if (activeFile && !isModelFile(activeFile) && !isSvgActive && !meshPreviewFile) {
44468
44985
  return {
44469
44986
  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.`
44987
+ body: `${activeFile} is a text file, not a .forge.js or .sketch.js model. Open a model file to render geometry.`
44471
44988
  };
44472
44989
  }
44473
44990
  if (!(result == null ? void 0 : result.error) && (result == null ? void 0 : result.assemblyKinematics) && objects.length === 0 && !rigInspectActive) {
@@ -44478,7 +44995,10 @@ function Viewport() {
44478
44995
  }
44479
44996
  return null;
44480
44997
  }, [activeFile, isSvgActive, meshPreviewFile, objects.length, result, rigInspectActive]);
44481
- const canvasDpr = isViewportInteracting || hasVisibleSdfObjects ? renderStylePreset.canvasDpr.live : [1, renderStylePreset.canvasDpr.idleMax];
44998
+ const canvasDpr = reactExports.useMemo(() => {
44999
+ if (hasVisibleSdfObjects) return renderStylePreset.canvasDpr.live;
45000
+ return [1, renderStylePreset.canvasDpr.idleMax];
45001
+ }, [hasVisibleSdfObjects, renderStylePreset.canvasDpr.idleMax, renderStylePreset.canvasDpr.live]);
44482
45002
  const effectiveSdfObjectSettings = reactExports.useMemo(() => {
44483
45003
  const next = {};
44484
45004
  for (const obj of objects) {
@@ -45115,6 +45635,7 @@ function Viewport() {
45115
45635
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewManager, { isSketchOnly, controlsRef }),
45116
45636
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewPersistence, { controlsRef, isSketchOnly, onResolved: handleViewPersistenceResolved }),
45117
45637
  /* @__PURE__ */ jsxRuntimeExports.jsx(OrbitExporterBridge, { controlsRef }),
45638
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportImageCaptureBridge, {}),
45118
45639
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportRecordingBridge, { controlsRef }),
45119
45640
  historyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryRecorderBridge, {}),
45120
45641
  /* @__PURE__ */ jsxRuntimeExports.jsx(TrajectoryRecorderBridge, { controlsRef })
@@ -45690,7 +46211,7 @@ function Viewport() {
45690
46211
  }
45691
46212
  );
45692
46213
  }
45693
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BHMQlJ-D.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46214
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-QXsAISLR.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
45694
46215
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
45695
46216
  function storePendingShareCopy(shareId) {
45696
46217
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -45956,17 +46477,17 @@ function SeoMetadata() {
45956
46477
  }, [location.pathname]);
45957
46478
  return null;
45958
46479
  }
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 })));
46480
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-yhhOodbf.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
46481
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-B5LePEuj.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
46482
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-DodHpvmf.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
46483
+ reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-a9_f-1US.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
46484
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-DwYHz72L.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
45964
46485
  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 })));
46486
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-BJZcM97j.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
46487
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-E3Rma7aV.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
46488
+ reactExports.lazy(() => __vitePreload(() => import("./LegalPage-5RbKRGYK.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
46489
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-QXsAISLR.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46490
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-DdEHGUMU.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
45970
46491
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
45971
46492
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg|dxf)$/i;
45972
46493
  function firstMeaningfulLine(text) {
@@ -46186,66 +46707,73 @@ function App() {
46186
46707
  applyTheme(localStorage.getItem("fc-theme") || "dark");
46187
46708
  clientExports.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsxRuntimeExports.jsx(App, {}));
46188
46709
  export {
46189
- VOXEL_INTENT_PLACEMENT_LABELS as $,
46710
+ INSPECT_POINT_SAMPLE_COUNT_MIN as $,
46190
46711
  AuthApiError as A,
46191
46712
  BrandMark as B,
46192
- hasExternalFiles as C,
46193
- isImportableProjectExactFile as D,
46194
- resolvePreviewFile as E,
46713
+ captureViewportImageBlobFromStore as C,
46714
+ exportExactFromStore as D,
46715
+ storageQuotaUpgradeMessage as E,
46195
46716
  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 _,
46717
+ isImportableProjectMeshFile as G,
46718
+ isImportableProjectBinaryFile as H,
46719
+ hasExternalFiles as I,
46720
+ isImportableProjectExactFile as J,
46721
+ resolvePreviewFile as K,
46722
+ countParamSnapshotDiff as L,
46723
+ buildProjectShareUrl as M,
46724
+ buildEmbedSnippet as N,
46725
+ publishProjectShare as O,
46726
+ unpublishProjectShare as P,
46727
+ formatComputeBackendLabel as Q,
46728
+ themes as R,
46729
+ computeBackendFromParts as S,
46730
+ formatArea as T,
46731
+ sliderToAnimationSpeed as U,
46732
+ animationSpeedToSlider as V,
46733
+ formatAnimationSpeed as W,
46734
+ resolveJointRange as X,
46735
+ useJointsConfig as Y,
46736
+ useJointAnimationValues as Z,
46737
+ INSPECT_POINT_SAMPLE_COUNT_MAX as _,
46217
46738
  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,
46739
+ VOXEL_INTENT_TOOL_ORDER as a0,
46740
+ VOXEL_INTENT_TOOL_LABELS as a1,
46741
+ VOXEL_INTENT_TOOL_COLORS as a2,
46742
+ VOXEL_INTENT_PLACEMENT_ORDER as a3,
46743
+ VOXEL_INTENT_PLACEMENT_LABELS as a4,
46744
+ highlightLanguageForProjectFile as a5,
46745
+ hasProjectTextFileExtension as a6,
46746
+ expandBoundsByTransformedAabb as a7,
46747
+ Canvas as a8,
46748
+ PerspectiveCamera as a9,
46749
+ buildShareUrl as aA,
46750
+ share as aB,
46751
+ ControlsInteractionBridge as aa,
46752
+ ViewController as ab,
46753
+ SceneConfigurator as ac,
46754
+ LocalEnvironment as ad,
46755
+ ForgeObject as ae,
46756
+ RenderLabelsOverlay as af,
46757
+ Grid as ag,
46758
+ OrbitControls2 as ah,
46759
+ TOUCH_GESTURES_3D as ai,
46760
+ MOUSE_BUTTONS_3D as aj,
46761
+ ModelJourneyBar as ak,
46762
+ useJointAnimationLoop as al,
46763
+ computeJointNodeMatrices as am,
46764
+ computeObjectJointMatrices as an,
46765
+ readLastActiveFileForUser as ao,
46766
+ ToastContainer as ap,
46767
+ isMobile as aq,
46768
+ useFeatureFlag as ar,
46769
+ decodeSharedHash as as,
46770
+ decodeSharedBundle as at,
46771
+ getExternalUrl as au,
46772
+ getGistId as av,
46773
+ Viewport as aw,
46774
+ shouldBlockBrowserShortcut as ax,
46775
+ useDrawStore as ay,
46776
+ storePendingShareCopy as az,
46249
46777
  authFetch as b,
46250
46778
  authApi as c,
46251
46779
  showToast as d,
@@ -46253,22 +46781,22 @@ export {
46253
46781
  formatStorageBytes as f,
46254
46782
  useForgeStore as g,
46255
46783
  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,
46784
+ isRunnableFile as i,
46785
+ copyTextToClipboard as j,
46786
+ useFeatureFlagStore as k,
46787
+ fetchGistModel as l,
46788
+ monacoLanguageForProjectFile as m,
46789
+ fetchUrlModel as n,
46790
+ exportMeshFromStore as o,
46791
+ exportReportFromStore as p,
46792
+ exportViewportImageFromStore as q,
46265
46793
  readProjectFilesFromDataTransfer as r,
46266
46794
  storageUsagePercent as s,
46267
46795
  triggerDownload as t,
46268
46796
  useAuthStore as u,
46269
- exportExactFromStore as v,
46270
- deriveExportStem as w,
46271
- storageQuotaUpgradeMessage as x,
46272
- isImportableProjectMeshFile as y,
46273
- isImportableProjectBinaryFile as z
46797
+ exportOrbitVideoFromStore as v,
46798
+ exportSketchFromStore as w,
46799
+ buildGistShareUrl as x,
46800
+ deriveExportStem as y,
46801
+ sanitizeExportStem as z
46274
46802
  };