forgecad 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/assets/{AdminPage-DwYHz72L.js → AdminPage-DcCnj0qo.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-a9_f-1US.js → BenchmarkPage-BVEpJSVk.js} +1 -1
  3. package/dist/assets/{BlogPage-DodHpvmf.js → BlogPage-DHaGP50_.js} +1 -1
  4. package/dist/assets/{DocsPage-B5LePEuj.js → DocsPage-CDoxHkz8.js} +33 -2
  5. package/dist/assets/{EditorApp-QXsAISLR.js → EditorApp-BJ0Dloyh.js} +174 -35
  6. package/dist/assets/{EmbedViewer-DdEHGUMU.js → EmbedViewer-CRKZbY0y.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-yhhOodbf.js → LandingPageProofDriven-BxHkYRE7.js} +1 -1
  8. package/dist/assets/{LegalPage-5RbKRGYK.js → LegalPage-B-u6FrVv.js} +1 -1
  9. package/dist/assets/{PricingPage-E3Rma7aV.js → PricingPage-CzpZ6-Ce.js} +1 -1
  10. package/dist/assets/{SettingsPage-BJZcM97j.js → SettingsPage-CIZSSAd0.js} +1 -1
  11. package/dist/assets/{app-CE3sYcV7.css → app-CjsbDlb7.css} +143 -0
  12. package/dist/assets/{app-DSYrDg0V.js → app-DaTMg3nH.js} +612 -120
  13. package/dist/assets/cli/{render-ZMHR9HkV.js → render-DPf4AYJK.js} +38 -16
  14. package/dist/assets/{evalWorker-DbNs7Dkp.js → evalWorker-CjZZWRWW.js} +1428 -1038
  15. package/dist/assets/{jointPose-DO6mnXn_.js → jointPose-DzQOViQH.js} +1 -1
  16. package/dist/assets/{manifold-BGlQBBH9.js → manifold-BYlzU521.js} +1 -1
  17. package/dist/assets/{manifold-fy2MV7K1.js → manifold-DgXo0T5P.js} +2 -2
  18. package/dist/assets/{manifold-BU-tJwQh.js → manifold-K1SkarlQ.js} +1 -1
  19. package/dist/assets/{reportWorker-DO6hcQbh.js → reportWorker-B9nWwSrB.js} +1402 -1012
  20. package/dist/assets/{scalar-sampling-budget-o90NSNmF.js → scalar-sampling-budget-prBw_s8t.js} +2139 -1749
  21. package/dist/cli/render.html +1 -1
  22. package/dist/docs/index.html +2 -2
  23. package/dist/docs-raw/CLI.md +18 -3
  24. package/dist/docs-raw/generated/assembly.md +70 -5
  25. package/dist/docs-raw/generated/concepts.md +16 -2
  26. package/dist/docs-raw/generated/core.md +9 -2
  27. package/dist/docs-raw/generated/lib.md +1 -1
  28. package/dist/docs-raw/generated/output.md +14 -43
  29. package/dist/docs-raw/generated/runtime-names.md +4 -4
  30. package/dist/docs-raw/guides/simready-quickstart.md +171 -0
  31. package/dist/docs-raw/simulation-workflow.md +273 -0
  32. package/dist/index.html +2 -2
  33. package/dist/sitemap.xml +25 -13
  34. package/dist-cli/{check-compiler-JTVBITCR.js → check-compiler-II7NLPAB.js} +1 -1
  35. package/dist-cli/{check-query-propagation-3FFLSMVN.js → check-query-propagation-7462TR3R.js} +1 -1
  36. package/dist-cli/{chunk-OAN5T4XD.js → chunk-UWTJCGXF.js} +1455 -722
  37. package/dist-cli/forgecad.js +2994 -529
  38. package/dist-skill/CONTEXT.md +94 -55
  39. package/dist-skill/docs/API/core/concepts.md +1 -1
  40. package/dist-skill/docs/CLI.md +18 -3
  41. package/dist-skill/docs/generated/assembly.md +66 -5
  42. package/dist-skill/docs/generated/core.md +9 -2
  43. package/dist-skill/docs/generated/lib.md +1 -1
  44. package/dist-skill/docs/generated/output.md +14 -43
  45. package/dist-skill/docs/generated/runtime-names.md +4 -4
  46. package/examples/robotics/README.md +46 -0
  47. package/examples/robotics/scout-cam-rover-simready/README.md +119 -0
  48. package/examples/robotics/scout-cam-rover-simready/lib/dims.js +140 -0
  49. package/examples/robotics/scout-cam-rover-simready/main.forge.js +343 -0
  50. package/examples/robotics/scout-cam-rover-simready/parts/body.forge.js +304 -0
  51. package/examples/robotics/scout-cam-rover-simready/parts/chassis.forge.js +320 -0
  52. package/examples/robotics/scout-cam-rover-simready/parts/hardware.forge.js +21 -0
  53. package/examples/robotics/scout-cam-rover-simready/parts/turret.forge.js +70 -0
  54. package/examples/robotics/scout-cam-rover-simready/parts/wheel.forge.js +116 -0
  55. package/examples/robotics/simready-asset-crate.forge.js +79 -0
  56. package/examples/robotics/simready-diff-drive-rover.forge.js +141 -0
  57. package/examples/robotics/simready-parallel-gripper.forge.js +102 -0
  58. package/package.json +1 -1
@@ -4,7 +4,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  var _a2;
6
6
  import { c as create, j as jsxRuntimeExports, r as reactExports, a as createWithEqualityFn, R as React, T as Tb, s as schedulerExports, b as clientExports, d as reactDomExports, u as useParams, e as useSearchParams, f as useNavigate, L as Link, g as useLocation, B as BrowserRouter, h as Routes, i as Route, N as Navigate } from "./vendor-react-6j1Kke-Y.js";
7
- import { _ as __vitePreload, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, S as Scene, a as PCFSoftShadowMap, V as VSMShadowMap, b as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, c as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, d as Layers, e as Color, f as RGBAFormat, U as UnsignedByteType, g as Vector3, h as Vector2, i as Clock, T as THREE, D as DoubleSide, j as REVISION, M as Mesh, I as IcosahedronGeometry, k as ShaderMaterial, l as Spherical, Q as Quaternion, m as MOUSE, n as TOUCH, o as Ray, p as Plane, q as DataTextureLoader, H as HalfFloatType, F as FloatType, r as DataUtils, t as LinearFilter, u as RedFormat, v as InstancedBufferGeometry, w as Float32BufferAttribute, x as InstancedInterleavedBuffer, y as InterleavedBufferAttribute, E as WireframeGeometry, G as Box3, J as Sphere, K as UniformsUtils, X as UniformsLib, Y as Vector4, Z as Line3, $ as Matrix4, a0 as MathUtils, a1 as Uniform, a2 as WebGLRenderTarget, a3 as DepthTexture, a4 as BackSide, a5 as ClampToEdgeWrapping, a6 as PlaneGeometry, a7 as UVMapping, a8 as DataTexture, a9 as Texture, aa as MeshBasicMaterial, ab as IntType, ac as ShortType, ad as ByteType, ae as UnsignedIntType, af as Loader, ag as LoadingManager, ah as LinearMipMapLinearFilter, ai as FileLoader, aj as NoBlending, ak as CubeReflectionMapping, al as EquirectangularReflectionMapping, am as CubeTextureLoader, an as WebGLCubeRenderTarget, ao as ConstraintSketch, ap as setSketchPlacement3D, aq as Sketch, ar as PROFILE_BACKEND_MARKER, as as FrozenShape, at as setShapeCompilePlan, au as hasAnyPorts, av as setShapePortsInternal, aw as markShapePortsUsed, ax as setParamOverrides, ay as resolveForgeRenderStyle, az as DEFAULT_ACTIVE_BACKEND, aA as isConstraintSketch, aB as updateConstraintValue, aC as linearizeMultiObjectSteps, aD as getShapeCompilePlan, aE as SCAN_PROXY_GRANULARITY_DEFAULT, aF as publishSolverWasmRunDebug, aG as resolveForgeQualityPreset, aH as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aI as findJointAnimationClip, aJ as resolveJointAnimation, aK as resolveJointViewValues, aL as resolveImportPath, aM as resolveScanProxyGranularity, aN as BufferGeometry, aO as LineBasicMaterial, aP as Line$1, aQ as LineDashedMaterial, aR as DepthStencilFormat, aS as UnsignedInt248Type, aT as MeshNormalMaterial, aU as NearestFilter, aV as BasicDepthPacking, aW as EventDispatcher$1, aX as NoColorSpace, aY as FrontSide, aZ as Material, a_ as AlwaysDepth, a$ as BufferAttribute, b0 as CanvasTexture, b1 as Object3D, b2 as FogExp2, b3 as Fog, b4 as AmbientLight, b5 as HemisphereLight, b6 as SpotLight, b7 as PointLight, b8 as DirectionalLight, b9 as heatPointsForSide, ba as COMPARISON_COLORS, bb as comparisonHeatDepthTest, bc as comparisonHeatEdgeOpacity, bd as comparisonHeatPatchOpacity, be as shapeToGeometry, bf as buildComparisonHeatPatchGeometry, bg as EdgesGeometry, bh as buildShapeFromCompilePlan, bi as buildVisibleHistoryStacks, bj as sketchToSvg, bk as sketchToDxf, bl as runScript, bm as MeshPhysicalMaterial, bn as LineSegments, bo as findDesignTraceNodeForConstructionStep, bp as formatDesignTraceAnchor, bq as waitForAnimationFrame, br as selectBuildLedgerNodes, bs as worldAuthorPlaneToLocal, bt as scanProxySourceBytes, bu as disposeScanProxyGeometry, bv as scanProxyGeometryFromPayload, bw as AdditiveBlending, bx as geometryWithVisibleVertexColors, by as getRenderStylePreset, bz as SCAN_RENDER_COLORS, bA as NormalBlending, bB as scanMaterialShellColor, bC as ZEBRA_STRIPE_SOFTNESS, bD as ZEBRA_STRIPE_SCALE, bE as ZEBRA_LIGHT_COLOR, bF as ZEBRA_DARK_COLOR, bG as ZEBRA_ACCENT_COLOR, bH as ZEBRA_STRIPE_FRAGMENT_SHADER, bI as ZEBRA_STRIPE_VERTEX_SHADER, bJ as SCAN_PROXY_LAYER_STYLES, bK as scanMaterialLayerStyles, bL as SURFACE_FIELD_FRAGMENT_SHADER, bM as SURFACE_FIELD_VERTEX_SHADER, bN as WORLD_UP, bO as CatmullRomCurve3, bP as TubeGeometry, bQ as THICKNESS_GRADIENT_COLORS, bR as ROUGHNESS_COLORS, bS as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bT as PERFORMANCE_SAMPLE_INTERVAL_SEC, bU as formatPerformanceCount, bV as NON_TEXT_INPUT_TYPES, bW as MeshStandardMaterial, bX as compileSdfNode3, bY as buildSdfRaymarchFragmentShader, bZ as SDF_RAYMARCH_PROXY_VERTEX_SHADER, b_ as Shape, b$ as ShapeGeometry, c0 as ShaderLib, c1 as CylinderGeometry, c2 as VIEWPORT_CAMERA_STORAGE_KEY, c3 as parseViewportCameraState, c4 as createResolvedExplodeConfig, c5 as explodeBoundsCenter, c6 as explodeMergeBounds, c7 as resolveExplodeDirective, c8 as computeExplodeMotion, c9 as getSketchWorldMatrix, ca as explodeAdd, cb as hasExplodeOverride, cc as resolveExplodeLocalFanDirection, cd as explodeMul, ce as explodeLeafFanStage, cf as normalizeCutPlane, cg as toClippingPlane, ch as isObjectExcludedFromCutPlane, ci as getShapePorts, cj as getShapeUsedPorts, ck as DEFAULT_VIEW_CONFIG, cl as SECTION_EXPLORER_PLANE_NAME, cm as ZERO_OFFSET, cn as scanProxyGridForBounds, co as IDENTITY_MATRIX$2, cp as OBJECT_CONTEXT_MENU_MARGIN, cq as OBJECT_CONTEXT_MENU_WIDTH, cr as OBJECT_CONTEXT_MENU_HEIGHT, cs as getKernelFaceNameForTriangle, ct as triangleSoupFromMeshes, cu as compareTriangleSoups, cv as buildGeometryComparisonPointCloud, cw as aabbOverlaps, cx as aabbOverlapVolume, cy as DEFAULT_COLLISION_INSPECTION_OPTIONS, cz as resolveScalarSceneSampleBudget, cA as FOCUS_MODE_DIM_OPACITY, cB as comparisonCandidateContextOpacity, cC as Matrix3, cD as initKernel, cE as initSolverWasm } from "./scalar-sampling-budget-o90NSNmF.js";
7
+ import { _ as __vitePreload, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, S as Scene, a as PCFSoftShadowMap, V as VSMShadowMap, b as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, c as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, d as Layers, e as Color, f as RGBAFormat, U as UnsignedByteType, g as Vector3, h as Vector2, i as Clock, T as THREE, D as DoubleSide, j as REVISION, M as Mesh, I as IcosahedronGeometry, k as ShaderMaterial, l as Spherical, Q as Quaternion, m as MOUSE, n as TOUCH, o as Ray, p as Plane, q as DataTextureLoader, H as HalfFloatType, F as FloatType, r as DataUtils, t as LinearFilter, u as RedFormat, v as InstancedBufferGeometry, w as Float32BufferAttribute, x as InstancedInterleavedBuffer, y as InterleavedBufferAttribute, E as WireframeGeometry, G as Box3, J as Sphere, K as UniformsUtils, X as UniformsLib, Y as Vector4, Z as Line3, $ as Matrix4, a0 as MathUtils, a1 as Uniform, a2 as WebGLRenderTarget, a3 as DepthTexture, a4 as BackSide, a5 as ClampToEdgeWrapping, a6 as PlaneGeometry, a7 as UVMapping, a8 as DataTexture, a9 as Texture, aa as MeshBasicMaterial, ab as IntType, ac as ShortType, ad as ByteType, ae as UnsignedIntType, af as Loader, ag as LoadingManager, ah as LinearMipMapLinearFilter, ai as FileLoader, aj as NoBlending, ak as CubeReflectionMapping, al as EquirectangularReflectionMapping, am as CubeTextureLoader, an as WebGLCubeRenderTarget, ao as ConstraintSketch, ap as setSketchPlacement3D, aq as Sketch, ar as PROFILE_BACKEND_MARKER, as as FrozenShape, at as setShapeCompilePlan, au as hasAnyPorts, av as setShapePortsInternal, aw as markShapePortsUsed, ax as setParamOverrides, ay as resolveForgeRenderStyle, az as DEFAULT_ACTIVE_BACKEND, aA as isConstraintSketch, aB as updateConstraintValue, aC as linearizeMultiObjectSteps, aD as getShapeCompilePlan, aE as SCAN_PROXY_GRANULARITY_DEFAULT, aF as publishSolverWasmRunDebug, aG as resolveForgeQualityPreset, aH as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aI as findJointAnimationClip, aJ as resolveJointAnimation, aK as resolveJointViewValues, aL as resolveImportPath, aM as resolveScanProxyGranularity, aN as BufferGeometry, aO as LineBasicMaterial, aP as Line$1, aQ as LineDashedMaterial, aR as DepthStencilFormat, aS as UnsignedInt248Type, aT as MeshNormalMaterial, aU as NearestFilter, aV as BasicDepthPacking, aW as EventDispatcher$1, aX as NoColorSpace, aY as FrontSide, aZ as Material, a_ as AlwaysDepth, a$ as BufferAttribute, b0 as CanvasTexture, b1 as Object3D, b2 as FogExp2, b3 as Fog, b4 as AmbientLight, b5 as HemisphereLight, b6 as SpotLight, b7 as PointLight, b8 as DirectionalLight, b9 as heatPointsForSide, ba as COMPARISON_COLORS, bb as comparisonHeatDepthTest, bc as comparisonHeatEdgeOpacity, bd as comparisonHeatPatchOpacity, be as shapeToGeometry, bf as buildComparisonHeatPatchGeometry, bg as EdgesGeometry, bh as buildShapeFromCompilePlan, bi as buildVisibleHistoryStacks, bj as sketchToSvg, bk as sketchToDxf, bl as runScript, bm as MeshPhysicalMaterial, bn as LineSegments, bo as findDesignTraceNodeForConstructionStep, bp as formatDesignTraceAnchor, bq as waitForAnimationFrame, br as selectBuildLedgerNodes, bs as worldAuthorPlaneToLocal, bt as scanProxySourceBytes, bu as disposeScanProxyGeometry, bv as scanProxyGeometryFromPayload, bw as AdditiveBlending, bx as geometryWithVisibleVertexColors, by as getRenderStylePreset, bz as SCAN_RENDER_COLORS, bA as NormalBlending, bB as scanMaterialShellColor, bC as ZEBRA_STRIPE_SOFTNESS, bD as ZEBRA_STRIPE_SCALE, bE as ZEBRA_LIGHT_COLOR, bF as ZEBRA_DARK_COLOR, bG as ZEBRA_ACCENT_COLOR, bH as ZEBRA_STRIPE_FRAGMENT_SHADER, bI as ZEBRA_STRIPE_VERTEX_SHADER, bJ as SCAN_PROXY_LAYER_STYLES, bK as scanMaterialLayerStyles, bL as SURFACE_FIELD_FRAGMENT_SHADER, bM as SURFACE_FIELD_VERTEX_SHADER, bN as WORLD_UP, bO as CatmullRomCurve3, bP as TubeGeometry, bQ as THICKNESS_GRADIENT_COLORS, bR as ROUGHNESS_COLORS, bS as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bT as PERFORMANCE_SAMPLE_INTERVAL_SEC, bU as formatPerformanceCount, bV as NON_TEXT_INPUT_TYPES, bW as MeshStandardMaterial, bX as compileSdfNode3, bY as buildSdfRaymarchFragmentShader, bZ as SDF_RAYMARCH_PROXY_VERTEX_SHADER, b_ as Shape, b$ as ShapeGeometry, c0 as ShaderLib, c1 as CylinderGeometry, c2 as VIEWPORT_CAMERA_STORAGE_KEY, c3 as parseViewportCameraState, c4 as createResolvedExplodeConfig, c5 as explodeBoundsCenter, c6 as explodeMergeBounds, c7 as resolveExplodeDirective, c8 as computeExplodeMotion, c9 as getSketchWorldMatrix, ca as explodeAdd, cb as hasExplodeOverride, cc as resolveExplodeLocalFanDirection, cd as explodeMul, ce as explodeLeafFanStage, cf as normalizeCutPlane, cg as toClippingPlane, ch as isObjectExcludedFromCutPlane, ci as getShapePorts, cj as getShapeUsedPorts, ck as DEFAULT_VIEW_CONFIG, cl as SECTION_EXPLORER_PLANE_NAME, cm as ZERO_OFFSET, cn as scanProxyGridForBounds, co as IDENTITY_MATRIX$2, cp as OBJECT_CONTEXT_MENU_MARGIN, cq as OBJECT_CONTEXT_MENU_WIDTH, cr as OBJECT_CONTEXT_MENU_HEIGHT, cs as getKernelFaceNameForTriangle, ct as triangleSoupFromMeshes, cu as compareTriangleSoups, cv as buildGeometryComparisonPointCloud, cw as aabbOverlaps, cx as aabbOverlapVolume, cy as DEFAULT_COLLISION_INSPECTION_OPTIONS, cz as resolveScalarSceneSampleBudget, cA as FOCUS_MODE_DIM_OPACITY, cB as comparisonCandidateContextOpacity, cC as Matrix3, cD as initKernel, cE as initSolverWasm } from "./scalar-sampling-budget-prBw_s8t.js";
8
8
  function getCsrfToken() {
9
9
  const match = document.cookie.match(/(?:^|;\s*)fc-csrf-token=([^;]+)/);
10
10
  return match == null ? void 0 : match[1];
@@ -15753,7 +15753,7 @@ function computeServerSnapshot$1(state2, serverFiles, serverFolders, sharedModel
15753
15753
  const isMeshHash = startupHashFile && MESH_EXTS.some((ext) => startupHashFile.toLowerCase().endsWith(ext));
15754
15754
  const isBinaryHash = startupHashFile && BINARY_IMPORT_EXTS.some((ext) => startupHashFile.toLowerCase().endsWith(ext));
15755
15755
  const meshPreviewFile = isMeshHash && startupHashFile && nextFiles[startupHashFile] !== void 0 ? startupHashFile : null;
15756
- const newActiveFile = sharedBundle2 ? sharedBundle2.entry : sharedModel2 ? sharedModel2.filename : startupHashFile && !isBinaryHash && nextFiles[startupHashFile] !== void 0 ? startupHashFile : initialFile && nextFiles[initialFile] !== void 0 ? initialFile : activeFile && nextFiles[activeFile] ? activeFile : findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0];
15756
+ const newActiveFile = sharedBundle2 ? sharedBundle2.entry : sharedModel2 ? sharedModel2.filename : startupHashFile && !isBinaryHash && nextFiles[startupHashFile] !== void 0 ? startupHashFile : initialFile && nextFiles[initialFile] !== void 0 ? initialFile : activeFile && nextFiles[activeFile] !== void 0 ? activeFile : findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] || "";
15757
15757
  const nextDirty = Object.keys(nextFiles).some((p2) => nextSaved[p2] !== nextFiles[p2]);
15758
15758
  const nextObjectSettingsByFile = Object.fromEntries(Object.entries(objectSettingsByFile).filter(([f2]) => f2 in nextFiles));
15759
15759
  return {
@@ -15798,7 +15798,7 @@ function computeServerFileDelete$1(state2, filename) {
15798
15798
  const newFolders = /* @__PURE__ */ new Set();
15799
15799
  Object.keys(nextFiles).forEach((p2) => collectParentPaths(p2).forEach((f2) => newFolders.add(f2)));
15800
15800
  const availableFiles = Object.keys(nextFiles);
15801
- const newActiveFile = activeFile === filename ? findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] : activeFile;
15801
+ const newActiveFile = activeFile === filename ? findPreferredEntryFile(availableFiles) || availableFiles.find((n) => n.endsWith(".js")) || availableFiles[0] || "" : activeFile;
15802
15802
  const nextObjectSettingsByFile = Object.fromEntries(Object.entries(objectSettingsByFile).filter(([f2]) => f2 in nextFiles));
15803
15803
  return {
15804
15804
  files: nextFiles,
@@ -15841,7 +15841,7 @@ const CRASH_COOLDOWN_MS = 2e3;
15841
15841
  class EvalWorkerClient {
15842
15842
  constructor(workerFactory = () => new Worker(new URL(
15843
15843
  /* @vite-ignore */
15844
- "/assets/evalWorker-DbNs7Dkp.js",
15844
+ "/assets/evalWorker-CjZZWRWW.js",
15845
15845
  import.meta.url
15846
15846
  ), { type: "module" })) {
15847
15847
  __publicField(this, "worker", null);
@@ -16332,6 +16332,7 @@ function projectTextFileTemplate(path) {
16332
16332
  return "";
16333
16333
  }
16334
16334
  function monacoLanguageForProjectFile(path) {
16335
+ if (!path) return "plaintext";
16335
16336
  const lower = path.toLowerCase();
16336
16337
  if (lower.endsWith(".md")) return "markdown";
16337
16338
  if (lower.endsWith(".json")) return "json";
@@ -16344,6 +16345,7 @@ function monacoLanguageForProjectFile(path) {
16344
16345
  return "plaintext";
16345
16346
  }
16346
16347
  function highlightLanguageForProjectFile(path) {
16348
+ if (!path) return null;
16347
16349
  const lower = path.toLowerCase();
16348
16350
  if (lower.endsWith(".md")) return "markdown";
16349
16351
  if (lower.endsWith(".json")) return "json";
@@ -17064,7 +17066,12 @@ const INITIAL_FOLDERS = collectInitialFolders(INITIAL_FILES);
17064
17066
  const getActiveFileFromHash = () => {
17065
17067
  const hash = window.location.hash.slice(1);
17066
17068
  if (hash.startsWith("code/") || hash.startsWith("bundle/")) return null;
17067
- return hash || null;
17069
+ if (!hash) return null;
17070
+ try {
17071
+ return decodeURIComponent(hash);
17072
+ } catch {
17073
+ return hash;
17074
+ }
17068
17075
  };
17069
17076
  const STARTUP_HASH_FILE = getActiveFileFromHash();
17070
17077
  const sharedModel = decodeSharedHash(window.location.hash);
@@ -17523,7 +17530,7 @@ const initialActive = (() => {
17523
17530
  }
17524
17531
  const names = Object.keys(INITIAL_FILES);
17525
17532
  const fallback = findPreferredEntryFile(names) || names.find((n) => n.endsWith(".js")) || names[0];
17526
- return fallback;
17533
+ return fallback || "";
17527
17534
  })();
17528
17535
  const INITIAL_SAVED = {};
17529
17536
  const MAX_RECENT_FILES = 200;
@@ -17606,6 +17613,19 @@ function isScalarInspectChannel(channel) {
17606
17613
  function shouldUseScanRenderStyle(channel, displayMode) {
17607
17614
  return isScalarInspectChannel(channel) && displayMode === "scan";
17608
17615
  }
17616
+ function recordViewCommandDebug(command) {
17617
+ if (typeof window === "undefined") return;
17618
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
17619
+ const entry = {
17620
+ id: command.id,
17621
+ type: command.type,
17622
+ view: command.view,
17623
+ targetId: command.targetId,
17624
+ camera: command.camera
17625
+ };
17626
+ window.__forgecadViewCommandDebug = [...window.__forgecadViewCommandDebug ?? [], entry].slice(-50);
17627
+ console.debug("[forgecad view-command]", entry);
17628
+ }
17609
17629
  const initialViewPreferences = readViewPreferences();
17610
17630
  const initialInspectChannel = resolveInitialViewInspectChannel(initialViewPreferences.inspectChannel);
17611
17631
  const initialInspectDisplayMode = resolveInspectDisplayMode(initialViewPreferences.inspectDisplayMode);
@@ -18068,7 +18088,7 @@ const useForgeStore = create((set, get) => ({
18068
18088
  }
18069
18089
  },
18070
18090
  dirty: false,
18071
- filesLoading: true,
18091
+ filesLoading: false,
18072
18092
  fileHandle: null,
18073
18093
  setFileHandle: (fileHandle) => set({ fileHandle }),
18074
18094
  result: null,
@@ -18657,6 +18677,7 @@ const useForgeStore = create((set, get) => ({
18657
18677
  },
18658
18678
  gridEnabled: initialViewPreferences.gridEnabled ?? true,
18659
18679
  axesVisible: initialViewPreferences.axesVisible ?? true,
18680
+ viewCubeVisible: initialViewPreferences.viewCubeVisible ?? true,
18660
18681
  gridSize: initialViewPreferences.gridSize ?? 10,
18661
18682
  setGridEnabled: (enabled) => {
18662
18683
  writeViewPreferences({ gridEnabled: enabled });
@@ -18666,6 +18687,10 @@ const useForgeStore = create((set, get) => ({
18666
18687
  writeViewPreferences({ axesVisible: visible });
18667
18688
  set({ axesVisible: visible });
18668
18689
  },
18690
+ setViewCubeVisible: (visible) => {
18691
+ writeViewPreferences({ viewCubeVisible: visible });
18692
+ set({ viewCubeVisible: visible });
18693
+ },
18669
18694
  setGridSize: (size) => {
18670
18695
  writeViewPreferences({ gridSize: size });
18671
18696
  set({ gridSize: size });
@@ -19002,7 +19027,11 @@ const useForgeStore = create((set, get) => ({
19002
19027
  set({ objectPickSyncEnabled: enabled });
19003
19028
  },
19004
19029
  viewCommand: null,
19005
- requestViewCommand: (command) => set({ viewCommand: { ...command, id: Date.now() } }),
19030
+ requestViewCommand: (command) => {
19031
+ const viewCommand = { ...command, id: Date.now() };
19032
+ recordViewCommandDebug(viewCommand);
19033
+ set({ viewCommand });
19034
+ },
19006
19035
  clearViewCommand: () => set({ viewCommand: null }),
19007
19036
  viewportCameraState: null,
19008
19037
  setViewportCameraState: (state2) => set({ viewportCameraState: state2 }),
@@ -19396,6 +19425,12 @@ const useForgeStore = create((set, get) => ({
19396
19425
  writeViewPreferences({ fileExplorerOpen: nextFileExplorerOpen });
19397
19426
  return { fileExplorerOpen: nextFileExplorerOpen };
19398
19427
  }),
19428
+ codeEditorOpen: initialViewPreferences.codeEditorOpen ?? true,
19429
+ toggleCodeEditor: () => set((s) => {
19430
+ const nextCodeEditorOpen = !s.codeEditorOpen;
19431
+ writeViewPreferences({ codeEditorOpen: nextCodeEditorOpen });
19432
+ return { codeEditorOpen: nextCodeEditorOpen };
19433
+ }),
19399
19434
  viewPanelOpen: initialViewPreferences.viewPanelOpen ?? true,
19400
19435
  toggleViewPanel: () => set((s) => {
19401
19436
  const nextViewPanelOpen = !s.viewPanelOpen;
@@ -19500,10 +19535,13 @@ const useForgeStore = create((set, get) => ({
19500
19535
  openAISkill: () => set({ aiSkillOpen: true }),
19501
19536
  closeAISkill: () => set({ aiSkillOpen: false }),
19502
19537
  editorNavigate: null,
19503
- requestEditorNavigate: (line) => set((s) => {
19504
- var _a3;
19505
- return { editorNavigate: { line, id: (((_a3 = s.editorNavigate) == null ? void 0 : _a3.id) ?? 0) + 1 } };
19506
- }),
19538
+ requestEditorNavigate: (line) => {
19539
+ writeViewPreferences({ codeEditorOpen: true });
19540
+ set((s) => {
19541
+ var _a3;
19542
+ return { codeEditorOpen: true, editorNavigate: { line, id: (((_a3 = s.editorNavigate) == null ? void 0 : _a3.id) ?? 0) + 1 } };
19543
+ });
19544
+ },
19507
19545
  clearEditorNavigate: () => set({ editorNavigate: null }),
19508
19546
  disableRunCache: initialViewPreferences.disableRunCache ?? false,
19509
19547
  setDisableRunCache: (disabled) => {
@@ -30134,7 +30172,7 @@ function generateReportInWorker(options) {
30134
30172
  return new Promise((resolve2, reject) => {
30135
30173
  const worker = new Worker(new URL(
30136
30174
  /* @vite-ignore */
30137
- "/assets/reportWorker-DO6hcQbh.js",
30175
+ "/assets/reportWorker-B9nWwSrB.js",
30138
30176
  import.meta.url
30139
30177
  ), { type: "module" });
30140
30178
  const cleanup = () => {
@@ -40846,6 +40884,37 @@ function ZoomIndicatorPanel({ mmPerPx }) {
40846
40884
  }
40847
40885
  );
40848
40886
  }
40887
+ function roundDebugNumber(value) {
40888
+ return Number.isFinite(value) ? Math.round(value * 1e3) / 1e3 : value;
40889
+ }
40890
+ function vectorTuple(vector) {
40891
+ return [roundDebugNumber(vector.x), roundDebugNumber(vector.y), roundDebugNumber(vector.z)];
40892
+ }
40893
+ function snapshotViewportCamera(camera, target) {
40894
+ const ortho = camera;
40895
+ const perspective = camera;
40896
+ const isOrtho = ortho.isOrthographicCamera === true;
40897
+ return {
40898
+ position: vectorTuple(camera.position),
40899
+ target: target ? vectorTuple(target) : null,
40900
+ up: vectorTuple(camera.up),
40901
+ projection: isOrtho ? "orthographic" : "perspective",
40902
+ fov: isOrtho ? void 0 : roundDebugNumber(perspective.fov),
40903
+ zoom: isOrtho ? roundDebugNumber(ortho.zoom) : void 0
40904
+ };
40905
+ }
40906
+ function recordViewportCameraDebug(entry) {
40907
+ if (typeof window === "undefined") return;
40908
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
40909
+ window.__forgecadCameraDebug = [...window.__forgecadCameraDebug ?? [], entry].slice(-50);
40910
+ console.debug("[forgecad camera]", entry);
40911
+ }
40912
+ function recordViewportCameraLifecycleDebug(entry) {
40913
+ if (typeof window === "undefined") return;
40914
+ if (window.localStorage.getItem("forgecad:debugCamera") !== "1") return;
40915
+ window.__forgecadCameraLifecycleDebug = [...window.__forgecadCameraLifecycleDebug ?? [], entry].slice(-50);
40916
+ console.debug("[forgecad view-controller]", entry);
40917
+ }
40849
40918
  const readPersistedViewportCameraState = () => {
40850
40919
  if (typeof window === "undefined") return null;
40851
40920
  try {
@@ -40869,19 +40938,117 @@ const resolveHoverObjectName = (name, knownFileNames) => {
40869
40938
  if (knownFileNames.has(trimmed)) return null;
40870
40939
  return trimmed;
40871
40940
  };
40941
+ const SNAP_VIEW_DIRECTIONS = {
40942
+ front: new Vector3(0, -1, 0),
40943
+ back: new Vector3(0, 1, 0),
40944
+ right: new Vector3(1, 0, 0),
40945
+ left: new Vector3(-1, 0, 0),
40946
+ top: new Vector3(0, 0, 1),
40947
+ bottom: new Vector3(0, 0, -1),
40948
+ iso: new Vector3(1, -1, 1)
40949
+ };
40950
+ const DEFAULT_CAMERA_UP = new Vector3(0, 0, 1);
40951
+ const SNAP_VIEW_UP = {
40952
+ top: new Vector3(0, 1, 0),
40953
+ bottom: new Vector3(0, -1, 0)
40954
+ };
40955
+ function isFiniteVector$1(vector) {
40956
+ return Number.isFinite(vector.x) && Number.isFinite(vector.y) && Number.isFinite(vector.z);
40957
+ }
40958
+ function computeBoundsFraming(bounds) {
40959
+ if (bounds.isEmpty()) return null;
40960
+ const target = new Vector3();
40961
+ bounds.getCenter(target);
40962
+ const size = new Vector3();
40963
+ bounds.getSize(size);
40964
+ if (!isFiniteVector$1(target) || !isFiniteVector$1(size)) return null;
40965
+ const reach = Math.max(size.x, size.y, size.z, 1);
40966
+ if (!Number.isFinite(reach) || reach <= 0) return null;
40967
+ return { target, size, reach };
40968
+ }
40969
+ function snapViewDirection(view2) {
40970
+ return SNAP_VIEW_DIRECTIONS[view2 ?? "iso"].clone().normalize();
40971
+ }
40972
+ function snapViewUp(view2) {
40973
+ return (SNAP_VIEW_UP[view2 ?? "iso"] ?? DEFAULT_CAMERA_UP).clone();
40974
+ }
40975
+ function computeSnapOrbitPose({
40976
+ position,
40977
+ target,
40978
+ up,
40979
+ view: view2
40980
+ }) {
40981
+ if (!isFiniteVector$1(position) || !isFiniteVector$1(target) || !isFiniteVector$1(up)) return null;
40982
+ const distance = position.distanceTo(target);
40983
+ if (!Number.isFinite(distance) || distance <= 1e-6) return null;
40984
+ const nextPosition = target.clone().add(snapViewDirection(view2).multiplyScalar(distance));
40985
+ if (!isFiniteVector$1(nextPosition)) return null;
40986
+ return {
40987
+ position: nextPosition,
40988
+ target: target.clone(),
40989
+ up: up.clone()
40990
+ };
40991
+ }
40992
+ function ActiveCameraBridge({ onCameraChange }) {
40993
+ const camera = useThree((state2) => state2.camera);
40994
+ reactExports.useEffect(() => {
40995
+ onCameraChange(camera);
40996
+ recordViewportCameraLifecycleDebug({
40997
+ path: "active-camera",
40998
+ snapshot: snapshotViewportCamera(camera, null)
40999
+ });
41000
+ }, [camera, onCameraChange]);
41001
+ reactExports.useEffect(() => {
41002
+ return () => {
41003
+ onCameraChange(null);
41004
+ recordViewportCameraLifecycleDebug({ path: "active-camera-cleared" });
41005
+ };
41006
+ }, [onCameraChange]);
41007
+ return null;
41008
+ }
40872
41009
  function ViewController({
41010
+ camera,
40873
41011
  controlsRef,
40874
- command,
41012
+ viewportRef,
40875
41013
  objects,
40876
41014
  objectMatrices,
40877
41015
  fallbackBounds,
40878
41016
  settings,
40879
- focusedObjectIds,
40880
- clearCommand
41017
+ focusedObjectIds
40881
41018
  }) {
40882
- const { camera, size } = useThree();
41019
+ const command = useForgeStore((s) => s.viewCommand);
41020
+ const clearCommand = useForgeStore((s) => s.clearViewCommand);
41021
+ const getActiveCamera = reactExports.useCallback(() => {
41022
+ var _a3;
41023
+ return camera ?? ((_a3 = controlsRef.current) == null ? void 0 : _a3.object) ?? null;
41024
+ }, [camera, controlsRef]);
41025
+ reactExports.useEffect(() => {
41026
+ recordViewportCameraLifecycleDebug({ path: "mounted" });
41027
+ return () => {
41028
+ recordViewportCameraLifecycleDebug({ path: "unmounted" });
41029
+ };
41030
+ }, []);
40883
41031
  reactExports.useEffect(() => {
41032
+ var _a3;
40884
41033
  if (!command) return;
41034
+ const activeCamera = getActiveCamera();
41035
+ if (!activeCamera) {
41036
+ recordViewportCameraLifecycleDebug({ path: "command-no-camera" });
41037
+ return;
41038
+ }
41039
+ recordViewportCameraLifecycleDebug({
41040
+ path: "command-change",
41041
+ snapshot: snapshotViewportCamera(activeCamera, ((_a3 = controlsRef.current) == null ? void 0 : _a3.target) ?? null)
41042
+ });
41043
+ }, [command, controlsRef, getActiveCamera]);
41044
+ reactExports.useEffect(() => {
41045
+ var _a3;
41046
+ if (!command) return;
41047
+ const camera2 = getActiveCamera();
41048
+ if (!camera2) {
41049
+ recordViewportCameraLifecycleDebug({ path: "command-no-camera-handler" });
41050
+ return;
41051
+ }
40885
41052
  if (command.type === "sceneCamera") return;
40886
41053
  if (command.type === "camera") {
40887
41054
  const state2 = command.camera;
@@ -40889,17 +41056,17 @@ function ViewController({
40889
41056
  clearCommand();
40890
41057
  return;
40891
41058
  }
40892
- camera.position.set(state2.position[0], state2.position[1], state2.position[2]);
40893
- camera.up.set(state2.up[0], state2.up[1], state2.up[2]);
40894
- if ("fov" in camera && state2.fov !== void 0) {
40895
- camera.fov = state2.fov;
41059
+ camera2.position.set(state2.position[0], state2.position[1], state2.position[2]);
41060
+ camera2.up.set(state2.up[0], state2.up[1], state2.up[2]);
41061
+ if ("fov" in camera2 && state2.fov !== void 0) {
41062
+ camera2.fov = state2.fov;
40896
41063
  }
40897
- if (camera.isOrthographicCamera && state2.orthoZoom !== void 0) {
40898
- camera.zoom = state2.orthoZoom;
41064
+ if (camera2.isOrthographicCamera && state2.orthoZoom !== void 0) {
41065
+ camera2.zoom = state2.orthoZoom;
40899
41066
  }
40900
- camera.lookAt(state2.target[0], state2.target[1], state2.target[2]);
40901
- if ("updateProjectionMatrix" in camera) {
40902
- camera.updateProjectionMatrix();
41067
+ camera2.lookAt(state2.target[0], state2.target[1], state2.target[2]);
41068
+ if ("updateProjectionMatrix" in camera2) {
41069
+ camera2.updateProjectionMatrix();
40903
41070
  }
40904
41071
  const controls2 = controlsRef.current;
40905
41072
  if (controls2) {
@@ -40909,9 +41076,43 @@ function ViewController({
40909
41076
  clearCommand();
40910
41077
  return;
40911
41078
  }
41079
+ const controls = controlsRef.current;
41080
+ const commandSnapshot = snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null);
41081
+ recordViewportCameraDebug({
41082
+ path: "received",
41083
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41084
+ before: commandSnapshot,
41085
+ after: commandSnapshot
41086
+ });
41087
+ if (command.type === "snap" && controls) {
41088
+ const before2 = commandSnapshot;
41089
+ const snapPose = computeSnapOrbitPose({
41090
+ position: camera2.position,
41091
+ target: controls.target,
41092
+ up: camera2.up,
41093
+ view: command.view
41094
+ });
41095
+ if (snapPose) {
41096
+ camera2.position.copy(snapPose.position);
41097
+ camera2.up.copy(snapPose.up);
41098
+ if ("updateProjectionMatrix" in camera2) {
41099
+ camera2.updateProjectionMatrix();
41100
+ }
41101
+ controls.target.copy(snapPose.target);
41102
+ controls.update();
41103
+ recordViewportCameraDebug({
41104
+ path: "snap-orbit",
41105
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41106
+ before: before2,
41107
+ after: snapshotViewportCamera(camera2, controls.target)
41108
+ });
41109
+ clearCommand();
41110
+ return;
41111
+ }
41112
+ }
40912
41113
  const visibleObjects = objects.filter((obj) => {
40913
- var _a3;
40914
- return ((_a3 = settings[obj.id]) == null ? void 0 : _a3.visible) !== false;
41114
+ var _a4;
41115
+ return ((_a4 = settings[obj.id]) == null ? void 0 : _a4.visible) !== false;
40915
41116
  });
40916
41117
  const focusedIdSet = new Set(focusedObjectIds);
40917
41118
  const focusedVisibleObjects = focusedIdSet.size > 0 ? visibleObjects.filter((obj) => focusedIdSet.has(obj.id)) : [];
@@ -40920,46 +41121,34 @@ function ViewController({
40920
41121
  const canUseFallbackBounds = !command.targetId && !useFocusedScope;
40921
41122
  const bounds = computeSceneBounds(targetObjects, objectMatrices) ?? (canUseFallbackBounds ? fallbackBounds : null);
40922
41123
  if (!bounds) {
41124
+ recordViewportCameraDebug({
41125
+ path: "no-bounds",
41126
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41127
+ before: commandSnapshot,
41128
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41129
+ });
40923
41130
  clearCommand();
40924
41131
  return;
40925
41132
  }
40926
- const center = new Vector3();
40927
- bounds.getCenter(center);
40928
- const sizeVec = new Vector3();
40929
- bounds.getSize(sizeVec);
40930
- if (!Number.isFinite(center.x) || !Number.isFinite(center.y) || !Number.isFinite(center.z) || !Number.isFinite(sizeVec.x) || !Number.isFinite(sizeVec.y) || !Number.isFinite(sizeVec.z)) {
40931
- clearCommand();
40932
- return;
40933
- }
40934
- const maxDim = Math.max(sizeVec.x, sizeVec.y, sizeVec.z, 1);
40935
- const snapUsesScopedCenter = command.type === "snap" && useFocusedScope;
40936
- const target = command.type === "snap" && !snapUsesScopedCenter ? new Vector3(0, 0, 0) : center;
40937
- const maxReach = command.type === "snap" && !snapUsesScopedCenter ? Math.max(sizeVec.x / 2 + Math.abs(center.x), sizeVec.y / 2 + Math.abs(center.y), sizeVec.z / 2 + Math.abs(center.z)) * 2 : maxDim;
40938
- if (!Number.isFinite(target.x) || !Number.isFinite(target.y) || !Number.isFinite(target.z) || !Number.isFinite(maxReach) || maxReach <= 0) {
41133
+ const framing = computeBoundsFraming(bounds);
41134
+ if (!framing) {
41135
+ recordViewportCameraDebug({
41136
+ path: "invalid-bounds",
41137
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41138
+ before: commandSnapshot,
41139
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41140
+ });
40939
41141
  clearCommand();
40940
41142
  return;
40941
41143
  }
40942
- const controls = controlsRef.current;
41144
+ const { target, reach: maxReach } = framing;
41145
+ const before = snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null);
40943
41146
  const camDir = new Vector3();
40944
41147
  if (command.type === "snap") {
40945
- const viewMap = {
40946
- front: new Vector3(0, -1, 0),
40947
- back: new Vector3(0, 1, 0),
40948
- right: new Vector3(1, 0, 0),
40949
- left: new Vector3(-1, 0, 0),
40950
- top: new Vector3(0, 0, 1),
40951
- bottom: new Vector3(0, 0, -1),
40952
- iso: new Vector3(1, -1, 1)
40953
- };
40954
- const upMap = {
40955
- top: new Vector3(0, 1, 0),
40956
- bottom: new Vector3(0, -1, 0)
40957
- };
40958
- camDir.copy(viewMap[command.view ?? "iso"]).normalize();
40959
- const up = upMap[command.view ?? ""] ?? new Vector3(0, 0, 1);
40960
- camera.up.copy(up);
41148
+ camDir.copy(snapViewDirection(command.view));
41149
+ camera2.up.copy(snapViewUp(command.view));
40961
41150
  } else if (controls) {
40962
- camDir.subVectors(camera.position, controls.target).normalize();
41151
+ camDir.subVectors(camera2.position, controls.target).normalize();
40963
41152
  if (camDir.lengthSq() === 0) camDir.set(1, 1, 1).normalize();
40964
41153
  } else {
40965
41154
  camDir.set(1, 1, 1).normalize();
@@ -40968,10 +41157,13 @@ function ViewController({
40968
41157
  clearCommand();
40969
41158
  return;
40970
41159
  }
40971
- const isOrtho = camera.isOrthographicCamera;
41160
+ const isOrtho = camera2.isOrthographicCamera;
41161
+ const viewportRect = (_a3 = viewportRef.current) == null ? void 0 : _a3.getBoundingClientRect();
41162
+ const viewportWidth = (viewportRect == null ? void 0 : viewportRect.width) ?? 800;
41163
+ const viewportHeight = (viewportRect == null ? void 0 : viewportRect.height) ?? 600;
40972
41164
  if (isOrtho) {
40973
- const ortho = camera;
40974
- const zoom = Math.min(size.width, size.height) / maxReach / 2.2;
41165
+ const ortho = camera2;
41166
+ const zoom = Math.min(viewportWidth, viewportHeight) / maxReach / 2.2;
40975
41167
  if (!Number.isFinite(zoom) || zoom <= 0) {
40976
41168
  clearCommand();
40977
41169
  return;
@@ -40985,7 +41177,7 @@ function ViewController({
40985
41177
  ortho.position.copy(nextPosition);
40986
41178
  ortho.updateProjectionMatrix();
40987
41179
  } else {
40988
- const persp = camera;
41180
+ const persp = camera2;
40989
41181
  const dist = maxReach / (2 * Math.tan(persp.fov * Math.PI / 360)) * 1.4;
40990
41182
  if (!Number.isFinite(dist) || dist <= 0) {
40991
41183
  clearCommand();
@@ -41003,21 +41195,26 @@ function ViewController({
41003
41195
  controls.target.copy(target);
41004
41196
  controls.update();
41005
41197
  } else {
41006
- camera.lookAt(target);
41198
+ camera2.lookAt(target);
41007
41199
  }
41200
+ recordViewportCameraDebug({
41201
+ path: command.type === "snap" ? "snap-framing-fallback" : "framing",
41202
+ command: { type: command.type, view: command.view, targetId: command.targetId },
41203
+ before,
41204
+ after: snapshotViewportCamera(camera2, (controls == null ? void 0 : controls.target) ?? null)
41205
+ });
41008
41206
  clearCommand();
41009
41207
  }, [
41010
- camera,
41011
41208
  clearCommand,
41012
41209
  command,
41013
41210
  controlsRef,
41014
41211
  fallbackBounds,
41015
41212
  focusedObjectIds,
41213
+ getActiveCamera,
41016
41214
  objectMatrices,
41017
41215
  objects,
41018
41216
  settings,
41019
- size.height,
41020
- size.width
41217
+ viewportRef
41021
41218
  ]);
41022
41219
  return null;
41023
41220
  }
@@ -41151,6 +41348,293 @@ function ViewPersistence({
41151
41348
  }, [camera, controlsRef, isSketchOnly, projectionMode, setViewportCameraState]);
41152
41349
  return null;
41153
41350
  }
41351
+ const CUBE_SIZE = 116;
41352
+ const CUBE_CENTER = CUBE_SIZE / 2;
41353
+ const CUBE_SCALE = 26;
41354
+ const DEFAULT_CAMERA_STATE = {
41355
+ position: [120, 80, 120],
41356
+ target: [0, 0, 0],
41357
+ up: [0, 0, 1]
41358
+ };
41359
+ const FACE_DEFS = [
41360
+ {
41361
+ view: "right",
41362
+ label: "RIGHT",
41363
+ normal: new Vector3(1, 0, 0),
41364
+ corners: [new Vector3(1, -1, -1), new Vector3(1, 1, -1), new Vector3(1, 1, 1), new Vector3(1, -1, 1)]
41365
+ },
41366
+ {
41367
+ view: "left",
41368
+ label: "LEFT",
41369
+ normal: new Vector3(-1, 0, 0),
41370
+ corners: [new Vector3(-1, 1, -1), new Vector3(-1, -1, -1), new Vector3(-1, -1, 1), new Vector3(-1, 1, 1)]
41371
+ },
41372
+ {
41373
+ view: "back",
41374
+ label: "BACK",
41375
+ normal: new Vector3(0, 1, 0),
41376
+ corners: [new Vector3(1, 1, -1), new Vector3(-1, 1, -1), new Vector3(-1, 1, 1), new Vector3(1, 1, 1)]
41377
+ },
41378
+ {
41379
+ view: "front",
41380
+ label: "FRONT",
41381
+ normal: new Vector3(0, -1, 0),
41382
+ corners: [new Vector3(-1, -1, -1), new Vector3(1, -1, -1), new Vector3(1, -1, 1), new Vector3(-1, -1, 1)]
41383
+ },
41384
+ {
41385
+ view: "top",
41386
+ label: "TOP",
41387
+ normal: new Vector3(0, 0, 1),
41388
+ corners: [new Vector3(-1, -1, 1), new Vector3(1, -1, 1), new Vector3(1, 1, 1), new Vector3(-1, 1, 1)]
41389
+ },
41390
+ {
41391
+ view: "bottom",
41392
+ label: "BOTTOM",
41393
+ normal: new Vector3(0, 0, -1),
41394
+ corners: [new Vector3(-1, 1, -1), new Vector3(1, 1, -1), new Vector3(1, -1, -1), new Vector3(-1, -1, -1)]
41395
+ }
41396
+ ];
41397
+ const SNAP_VIEWS = /* @__PURE__ */ new Set(["front", "back", "left", "right", "top", "bottom", "iso"]);
41398
+ function vectorFromTuple(tuple) {
41399
+ return new Vector3(tuple[0], tuple[1], tuple[2]);
41400
+ }
41401
+ function normalizedOr(vector, fallback) {
41402
+ return Number.isFinite(vector.x) && Number.isFinite(vector.y) && Number.isFinite(vector.z) && vector.lengthSq() > 1e-10 ? vector.clone().normalize() : fallback.clone().normalize();
41403
+ }
41404
+ function buildViewBasisFromCameraState(cameraState) {
41405
+ const position = vectorFromTuple(cameraState.position);
41406
+ const target = vectorFromTuple(cameraState.target);
41407
+ const up = normalizedOr(vectorFromTuple(cameraState.up), new Vector3(0, 0, 1));
41408
+ const viewer = normalizedOr(position.clone().sub(target), new Vector3(1, -1, 1));
41409
+ const forward = viewer.clone().negate();
41410
+ let right = forward.clone().cross(up);
41411
+ if (right.lengthSq() <= 1e-10) {
41412
+ right = Math.abs(forward.z) > 0.9 ? new Vector3(1, 0, 0) : forward.clone().cross(new Vector3(0, 0, 1));
41413
+ }
41414
+ right.normalize();
41415
+ const screenUp = normalizedOr(right.clone().cross(forward), new Vector3(0, 0, 1));
41416
+ return { viewer, right, screenUp };
41417
+ }
41418
+ const DEFAULT_VIEW_BASIS = buildViewBasisFromCameraState(DEFAULT_CAMERA_STATE);
41419
+ function buildViewBasisFromLiveCamera(camera) {
41420
+ camera.updateMatrixWorld();
41421
+ const quaternion = camera.getWorldQuaternion(new Quaternion());
41422
+ const viewer = normalizedOr(new Vector3(0, 0, 1).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.viewer);
41423
+ const right = normalizedOr(new Vector3(1, 0, 0).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.right);
41424
+ const screenUp = normalizedOr(new Vector3(0, 1, 0).applyQuaternion(quaternion), DEFAULT_VIEW_BASIS.screenUp);
41425
+ return { viewer, right, screenUp };
41426
+ }
41427
+ function faceFill(brightness) {
41428
+ const litPercent = Math.round(42 + brightness * 46);
41429
+ return `color-mix(in srgb, var(--fc-view-cube-face-lit) ${litPercent}%, var(--fc-view-cube-face-dim))`;
41430
+ }
41431
+ function projectVector(vector, right, screenUp, viewer) {
41432
+ return {
41433
+ x: CUBE_CENTER + vector.dot(right) * CUBE_SCALE,
41434
+ y: CUBE_CENTER - vector.dot(screenUp) * CUBE_SCALE,
41435
+ depth: vector.dot(viewer)
41436
+ };
41437
+ }
41438
+ function polygonPoints(points) {
41439
+ return points.map((point) => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(" ");
41440
+ }
41441
+ function snapViewFromEventTarget(target) {
41442
+ var _a3;
41443
+ if (!(target instanceof Element)) return null;
41444
+ const view2 = (_a3 = target.closest(".fc-view-cube-face")) == null ? void 0 : _a3.dataset.view;
41445
+ return view2 && SNAP_VIEWS.has(view2) ? view2 : null;
41446
+ }
41447
+ function cameraRightVector(camera) {
41448
+ return buildViewBasisFromLiveCamera(camera).right;
41449
+ }
41450
+ function rotateCameraFromDrag(camera, controls, drag, clientX, clientY) {
41451
+ const dx = clientX - drag.startX;
41452
+ const dy = clientY - drag.startY;
41453
+ if (Math.hypot(dx, dy) > 3) drag.moved = true;
41454
+ const sensitivity = 8e-3;
41455
+ const startOffset = drag.startPosition.clone().sub(drag.target);
41456
+ const yaw = new Quaternion().setFromAxisAngle(drag.up, -dx * sensitivity);
41457
+ const pitchAxis = drag.right.clone().applyQuaternion(yaw).normalize();
41458
+ const pitch = new Quaternion().setFromAxisAngle(pitchAxis, -dy * sensitivity);
41459
+ const nextOffset = startOffset.applyQuaternion(yaw).applyQuaternion(pitch);
41460
+ camera.position.copy(drag.target).add(nextOffset);
41461
+ camera.up.copy(drag.up);
41462
+ controls.target.copy(drag.target);
41463
+ if ("updateProjectionMatrix" in camera) {
41464
+ camera.updateProjectionMatrix();
41465
+ }
41466
+ controls.update();
41467
+ }
41468
+ function ViewCube({ camera, controlsRef, onSnap }) {
41469
+ const [viewBasis, setViewBasis] = reactExports.useState(null);
41470
+ const [dragging, setDragging] = reactExports.useState(false);
41471
+ const [pressedView, setPressedView] = reactExports.useState(null);
41472
+ const dragInfoRef = reactExports.useRef(null);
41473
+ const pressedTimeoutRef = reactExports.useRef(null);
41474
+ const getLiveCamera = reactExports.useCallback(() => {
41475
+ var _a3;
41476
+ return camera ?? ((_a3 = controlsRef.current) == null ? void 0 : _a3.object) ?? null;
41477
+ }, [camera, controlsRef]);
41478
+ const triggerSnap = reactExports.useCallback(
41479
+ (view2) => {
41480
+ setPressedView(view2);
41481
+ if (pressedTimeoutRef.current !== null) {
41482
+ window.clearTimeout(pressedTimeoutRef.current);
41483
+ }
41484
+ pressedTimeoutRef.current = window.setTimeout(() => {
41485
+ pressedTimeoutRef.current = null;
41486
+ setPressedView(null);
41487
+ }, 180);
41488
+ onSnap(view2);
41489
+ },
41490
+ [onSnap]
41491
+ );
41492
+ const syncCameraState = reactExports.useCallback(() => {
41493
+ const controls = controlsRef.current;
41494
+ const liveCamera = getLiveCamera();
41495
+ if (!liveCamera || !controls) return;
41496
+ setViewBasis(buildViewBasisFromLiveCamera(liveCamera));
41497
+ }, [controlsRef, getLiveCamera]);
41498
+ reactExports.useEffect(() => {
41499
+ let disposed = false;
41500
+ let retryFrame = 0;
41501
+ let controls = null;
41502
+ const attach2 = () => {
41503
+ if (disposed) return;
41504
+ controls = controlsRef.current;
41505
+ if (!getLiveCamera() || !controls) {
41506
+ retryFrame = window.requestAnimationFrame(attach2);
41507
+ return;
41508
+ }
41509
+ syncCameraState();
41510
+ controls.addEventListener("change", syncCameraState);
41511
+ };
41512
+ attach2();
41513
+ return () => {
41514
+ disposed = true;
41515
+ if (retryFrame) window.cancelAnimationFrame(retryFrame);
41516
+ controls == null ? void 0 : controls.removeEventListener("change", syncCameraState);
41517
+ };
41518
+ }, [controlsRef, getLiveCamera, syncCameraState]);
41519
+ reactExports.useEffect(() => {
41520
+ return () => {
41521
+ if (pressedTimeoutRef.current !== null) {
41522
+ window.clearTimeout(pressedTimeoutRef.current);
41523
+ }
41524
+ };
41525
+ }, []);
41526
+ const faces = reactExports.useMemo(() => {
41527
+ const { viewer, right, screenUp } = viewBasis ?? DEFAULT_VIEW_BASIS;
41528
+ const projectedFaces = FACE_DEFS.map((face) => {
41529
+ const visibility = face.normal.dot(viewer);
41530
+ if (visibility <= 0.02) return null;
41531
+ const points = face.corners.map((corner) => projectVector(corner, right, screenUp, viewer));
41532
+ const center = projectVector(face.normal, right, screenUp, viewer);
41533
+ const depth = points.reduce((sum, point) => sum + point.depth, 0) / points.length;
41534
+ return {
41535
+ view: face.view,
41536
+ label: face.label,
41537
+ points,
41538
+ center,
41539
+ depth,
41540
+ brightness: MathUtils.clamp(visibility, 0, 1)
41541
+ };
41542
+ }).filter((face) => face !== null).sort((a2, b2) => a2.depth - b2.depth);
41543
+ return projectedFaces;
41544
+ }, [viewBasis]);
41545
+ const handlePointerDown = reactExports.useCallback(
41546
+ (event) => {
41547
+ if (event.button !== 0) return;
41548
+ const liveCamera = getLiveCamera();
41549
+ const clickView = snapViewFromEventTarget(event.target);
41550
+ const controls = controlsRef.current;
41551
+ if (!clickView && (!liveCamera || !controls)) return;
41552
+ const target = (controls == null ? void 0 : controls.target.clone()) ?? new Vector3();
41553
+ const up = liveCamera ? normalizedOr(liveCamera.up, new Vector3(0, 0, 1)) : new Vector3(0, 0, 1);
41554
+ dragInfoRef.current = {
41555
+ pointerId: event.pointerId,
41556
+ startX: event.clientX,
41557
+ startY: event.clientY,
41558
+ startPosition: (liveCamera == null ? void 0 : liveCamera.position.clone()) ?? new Vector3(),
41559
+ target,
41560
+ up,
41561
+ right: liveCamera && controls ? cameraRightVector(liveCamera) : new Vector3(1, 0, 0),
41562
+ moved: false,
41563
+ clickView
41564
+ };
41565
+ event.currentTarget.setPointerCapture(event.pointerId);
41566
+ setDragging(true);
41567
+ },
41568
+ [controlsRef, getLiveCamera]
41569
+ );
41570
+ const handlePointerMove = reactExports.useCallback(
41571
+ (event) => {
41572
+ const drag = dragInfoRef.current;
41573
+ if (!drag || drag.pointerId !== event.pointerId) return;
41574
+ if (Math.hypot(event.clientX - drag.startX, event.clientY - drag.startY) > 3) {
41575
+ drag.moved = true;
41576
+ }
41577
+ const controls = controlsRef.current;
41578
+ const liveCamera = getLiveCamera();
41579
+ if (!liveCamera || !controls) return;
41580
+ event.preventDefault();
41581
+ rotateCameraFromDrag(liveCamera, controls, drag, event.clientX, event.clientY);
41582
+ syncCameraState();
41583
+ },
41584
+ [controlsRef, getLiveCamera, syncCameraState]
41585
+ );
41586
+ const finishPointerDrag = reactExports.useCallback(
41587
+ (event) => {
41588
+ const drag = dragInfoRef.current;
41589
+ if (!drag || drag.pointerId !== event.pointerId) return;
41590
+ dragInfoRef.current = null;
41591
+ event.currentTarget.releasePointerCapture(event.pointerId);
41592
+ setDragging(false);
41593
+ if (!drag.moved && drag.clickView) {
41594
+ triggerSnap(drag.clickView);
41595
+ }
41596
+ },
41597
+ [triggerSnap]
41598
+ );
41599
+ const handleFaceKeyDown = reactExports.useCallback(
41600
+ (event, view2) => {
41601
+ if (event.key !== "Enter" && event.key !== " ") return;
41602
+ event.preventDefault();
41603
+ triggerSnap(view2);
41604
+ },
41605
+ [triggerSnap]
41606
+ );
41607
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
41608
+ "div",
41609
+ {
41610
+ className: `fc-view-cube${dragging ? " dragging" : ""}`,
41611
+ "aria-label": "View cube",
41612
+ onPointerDown: handlePointerDown,
41613
+ onPointerMove: handlePointerMove,
41614
+ onPointerUp: finishPointerDrag,
41615
+ onPointerCancel: finishPointerDrag,
41616
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs("svg", { className: "fc-view-cube-svg", viewBox: `0 0 ${CUBE_SIZE} ${CUBE_SIZE}`, role: "img", "aria-label": "Viewport orientation", children: [
41617
+ /* @__PURE__ */ jsxRuntimeExports.jsx("defs", { children: /* @__PURE__ */ jsxRuntimeExports.jsx("filter", { id: "fc-view-cube-shadow", x: "-20%", y: "-20%", width: "140%", height: "140%", children: /* @__PURE__ */ jsxRuntimeExports.jsx("feDropShadow", { dx: "0", dy: "2", stdDeviation: "2", floodColor: "#07101a", floodOpacity: "0.28" }) }) }),
41618
+ /* @__PURE__ */ jsxRuntimeExports.jsx("g", { filter: "url(#fc-view-cube-shadow)", children: faces.map((face) => /* @__PURE__ */ jsxRuntimeExports.jsxs(
41619
+ "g",
41620
+ {
41621
+ className: `fc-view-cube-face${pressedView === face.view ? " active" : ""}`,
41622
+ role: "button",
41623
+ tabIndex: 0,
41624
+ "data-view": face.view,
41625
+ "aria-label": `View ${face.label.toLowerCase()}`,
41626
+ onKeyDown: (event) => handleFaceKeyDown(event, face.view),
41627
+ children: [
41628
+ /* @__PURE__ */ jsxRuntimeExports.jsx("polygon", { points: polygonPoints(face.points), fill: faceFill(face.brightness) }),
41629
+ /* @__PURE__ */ jsxRuntimeExports.jsx("text", { x: face.center.x, y: face.center.y, className: "fc-view-cube-face-label", children: face.label })
41630
+ ]
41631
+ },
41632
+ face.view
41633
+ )) })
41634
+ ] })
41635
+ }
41636
+ );
41637
+ }
41154
41638
  const FLAG_DEFINITIONS = {
41155
41639
  drawMode: {
41156
41640
  label: "Draw Mode (interactive sketch editor)",
@@ -41763,6 +42247,7 @@ function useViewportState() {
41763
42247
  const projectionMode = useForgeStore((s) => s.projectionMode);
41764
42248
  const gridEnabled = useForgeStore((s) => s.gridEnabled);
41765
42249
  const axesVisible = useForgeStore((s) => s.axesVisible);
42250
+ const viewCubeVisible = useForgeStore((s) => s.viewCubeVisible);
41766
42251
  const gridSize = useForgeStore((s) => s.gridSize);
41767
42252
  const showPerformanceInfo = useForgeStore((s) => s.showPerformanceInfo);
41768
42253
  const objectSettings = useForgeStore((s) => s.objectSettings);
@@ -41776,9 +42261,7 @@ function useViewportState() {
41776
42261
  const clearFocusedObject = useForgeStore((s) => s.clearFocusedObject);
41777
42262
  const objectPickSyncEnabled = useForgeStore((s) => s.objectPickSyncEnabled);
41778
42263
  const explodeAmount = useForgeStore((s) => s.explodeAmount);
41779
- const viewCommand = useForgeStore((s) => s.viewCommand);
41780
42264
  const requestViewCommand = useForgeStore((s) => s.requestViewCommand);
41781
- const clearViewCommand = useForgeStore((s) => s.clearViewCommand);
41782
42265
  const jointValues = useForgeStore((s) => s.jointValues);
41783
42266
  const jointAnimationClip = useForgeStore((s) => s.jointAnimationClip);
41784
42267
  const jointAnimationProgress = useForgeStore((s) => s.jointAnimationProgress);
@@ -42156,6 +42639,7 @@ function useViewportState() {
42156
42639
  projectionMode,
42157
42640
  gridEnabled,
42158
42641
  axesVisible,
42642
+ viewCubeVisible,
42159
42643
  gridSize,
42160
42644
  showPerformanceInfo,
42161
42645
  objectSettings,
@@ -42169,9 +42653,7 @@ function useViewportState() {
42169
42653
  clearFocusedObject,
42170
42654
  objectPickSyncEnabled,
42171
42655
  explodeAmount,
42172
- viewCommand,
42173
42656
  requestViewCommand,
42174
- clearViewCommand,
42175
42657
  jointValues,
42176
42658
  jointAnimationClip,
42177
42659
  jointAnimationProgress,
@@ -44876,6 +45358,7 @@ function Viewport() {
44876
45358
  projectionMode,
44877
45359
  gridEnabled,
44878
45360
  axesVisible,
45361
+ viewCubeVisible,
44879
45362
  gridSize,
44880
45363
  showPerformanceInfo,
44881
45364
  objectSettings,
@@ -44888,9 +45371,7 @@ function Viewport() {
44888
45371
  focusObject,
44889
45372
  clearFocusedObject,
44890
45373
  objectPickSyncEnabled,
44891
- viewCommand,
44892
45374
  requestViewCommand,
44893
- clearViewCommand,
44894
45375
  lengthUnit,
44895
45376
  constructionGhost,
44896
45377
  objects,
@@ -44961,10 +45442,17 @@ function Viewport() {
44961
45442
  const vRulerRef = reactExports.useRef(null);
44962
45443
  const adaptiveGrid = useAdaptiveGrid(zoomMmPerPx, isSketchOnly && gridEnabled);
44963
45444
  const controlsRef = reactExports.useRef(null);
45445
+ const [activeCanvasCamera, setActiveCanvasCamera] = reactExports.useState(null);
44964
45446
  const initialFitRequestedRef = reactExports.useRef(false);
44965
45447
  const prevPreviewFileRef = reactExports.useRef(void 0);
44966
45448
  const containerRef = reactExports.useRef(null);
44967
45449
  const contextMenuRef = reactExports.useRef(null);
45450
+ const handleActiveCameraChange = reactExports.useCallback((camera) => {
45451
+ setActiveCanvasCamera(camera);
45452
+ }, []);
45453
+ const handleCanvasCreated = reactExports.useCallback((state22) => {
45454
+ setActiveCanvasCamera(state22.camera);
45455
+ }, []);
44968
45456
  const t2 = themes[themeName];
44969
45457
  const renderStylePreset = reactExports.useMemo(() => getRenderStylePreset(renderStyle), [renderStyle]);
44970
45458
  const effectiveSceneConfig = reactExports.useMemo(
@@ -45325,7 +45813,9 @@ function Viewport() {
45325
45813
  raycaster: { params: { Line: { threshold: 0.5 } } },
45326
45814
  camera: { up: [0, 0, 1] },
45327
45815
  onPointerMissed: handleViewportPointerMissed,
45816
+ onCreated: handleCanvasCreated,
45328
45817
  children: [
45818
+ /* @__PURE__ */ jsxRuntimeExports.jsx(ActiveCameraBridge, { onCameraChange: handleActiveCameraChange }),
45329
45819
  /* @__PURE__ */ jsxRuntimeExports.jsxs(reactExports.Suspense, { fallback: null, children: [
45330
45820
  projectionMode === "orthographic" ? /* @__PURE__ */ jsxRuntimeExports.jsx(OrthographicCamera, { makeDefault: true, position: [120, 80, 120], zoom: 2, near: -5e4, far: 5e4, up: [0, 0, 1] }) : /* @__PURE__ */ jsxRuntimeExports.jsx(PerspectiveCamera, { makeDefault: true, position: [120, 80, 120], fov: 45, near: 0.1, far: 1e5, up: [0, 0, 1] }),
45331
45821
  ((_d = effectiveSceneConfig == null ? void 0 : effectiveSceneConfig.postProcessing) == null ? void 0 : _d.toneMappingExposure) === void 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(RenderStyleExposure, { exposure: renderStylePreset.toneMappingExposure }),
@@ -45639,23 +46129,24 @@ function Viewport() {
45639
46129
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportRecordingBridge, { controlsRef }),
45640
46130
  historyMode && /* @__PURE__ */ jsxRuntimeExports.jsx(HistoryRecorderBridge, {}),
45641
46131
  /* @__PURE__ */ jsxRuntimeExports.jsx(TrajectoryRecorderBridge, { controlsRef })
45642
- ] }),
45643
- /* @__PURE__ */ jsxRuntimeExports.jsx(
45644
- ViewController,
45645
- {
45646
- controlsRef,
45647
- command: viewCommand,
45648
- objects,
45649
- objectMatrices,
45650
- fallbackBounds: rigInspectActive ? rigInspectionBounds : null,
45651
- settings: objectSettings,
45652
- focusedObjectIds,
45653
- clearCommand: clearViewCommand
45654
- }
45655
- )
46132
+ ] })
45656
46133
  ]
45657
46134
  }
45658
46135
  ),
46136
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
46137
+ ViewController,
46138
+ {
46139
+ camera: activeCanvasCamera,
46140
+ controlsRef,
46141
+ viewportRef: containerRef,
46142
+ objects,
46143
+ objectMatrices,
46144
+ fallbackBounds: rigInspectActive ? rigInspectionBounds : null,
46145
+ settings: objectSettings,
46146
+ focusedObjectIds
46147
+ }
46148
+ ),
46149
+ viewCubeVisible && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(ViewCube, { camera: activeCanvasCamera, controlsRef, onSnap: (view2) => requestViewCommand({ type: "snap", view: view2 }) }),
45659
46150
  (() => {
45660
46151
  const toolpathObj = objects.find((o2) => o2.toolpath && o2.toolpath.segments.length > 0);
45661
46152
  if (!(toolpathObj == null ? void 0 : toolpathObj.toolpath)) return null;
@@ -46211,7 +46702,7 @@ function Viewport() {
46211
46702
  }
46212
46703
  );
46213
46704
  }
46214
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-QXsAISLR.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46705
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BJ0Dloyh.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46215
46706
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
46216
46707
  function storePendingShareCopy(shareId) {
46217
46708
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -46400,7 +46891,7 @@ const PublishedModelPage$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Objec
46400
46891
  }, Symbol.toStringTag, { value: "Module" }));
46401
46892
  const siteUrl = "https://forgecad.io";
46402
46893
  const defaultImage = "https://forgecad.io/brand/social-card.png";
46403
- const pages$1 = [{ "path": "/", "title": "ForgeCAD - AI-Native CAD for Manufacturable Models", "description": "ForgeCAD turns prompts, JavaScript, and product ideas into editable parametric CAD with agent validation, manufacturing feedback, and STEP/STL exports.", "priority": "1.0", "changefreq": "weekly" }, { "path": "/docs", "title": "ForgeCAD Documentation - AI-Native Parametric CAD", "description": "Learn ForgeCAD's JavaScript CAD API, agent workflow, validation loop, exports, assemblies, sketches, and manufacturing-ready model checks.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/ai-native-cad", "title": "AI-Native CAD Workflow - ForgeCAD", "description": "How ForgeCAD supports AI-driven CAD: editable code, validation evidence, manufacturing feedback, and parallel product idea iteration.", "priority": "0.9", "changefreq": "weekly" }, { "path": "/docs/ai-usage", "title": "AI CAD Agent Setup - ForgeCAD", "description": "Set up Codex, Claude Code, OpenCode, or another agent to build, validate, inspect, export, and publish ForgeCAD models.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/cli", "title": "ForgeCAD CLI - Validate, Inspect, Export, Publish", "description": "Use the ForgeCAD CLI to run .forge.js models, render evidence, inspect manufacturability, export STEP/STL/3MF, and sync projects.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/docs/skills/forgecad-make-a-model", "title": "AI CAD Model-Making Skill - ForgeCAD", "description": "A workflow for AI agents to create manufacture-realistic ForgeCAD models with validation, renders, inspection evidence, and exports.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/benchmark", "title": "ForgeCAD Benchmark - AI CAD Model Generation", "description": "Benchmark AI agents generating real ForgeCAD models with executable code, visual evidence, validation output, and geometry checks.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/blog", "title": "ForgeCAD Blog - AI-Native CAD and Manufacturing Workflows", "description": "Product notes, tutorials, examples, and technical updates on AI-native CAD, parametric modeling, validation, and manufacturing exports.", "priority": "0.6", "changefreq": "weekly" }, { "path": "/pricing", "title": "ForgeCAD Pricing", "description": "ForgeCAD plans for browser CAD, local CLI workflows, AI agent model generation, project publishing, and commercial use.", "priority": "0.5", "changefreq": "monthly" }, { "path": "/terms", "title": "ForgeCAD Terms of Service", "description": "Terms for ForgeCAD accounts, projects, hosted services, CLI use, commercial use, automation, and AI-assisted workflows.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/privacy", "title": "ForgeCAD Privacy Policy", "description": "How ForgeCAD handles account, project, billing, analytics, and service data for the website, CLI, and hosted app.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/license", "title": "ForgeCAD Software License", "description": "ForgeCAD license terms for Free, Pro, Enterprise, embedded, backend, local CLI, and AI workflow usage.", "priority": "0.3", "changefreq": "yearly" }];
46894
+ const pages$1 = [{ "path": "/", "title": "ForgeCAD - AI-Native CAD for Manufacturable Models", "description": "ForgeCAD turns prompts, JavaScript, and product ideas into editable parametric CAD with agent validation, manufacturing feedback, and STEP/STL exports.", "priority": "1.0", "changefreq": "weekly" }, { "path": "/docs", "title": "ForgeCAD Documentation - AI-Native Parametric CAD", "description": "Learn ForgeCAD's JavaScript CAD API, agent workflow, validation loop, exports, assemblies, sketches, and manufacturing-ready model checks.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/ai-native-cad", "title": "AI-Native CAD Workflow - ForgeCAD", "description": "How ForgeCAD supports AI-driven CAD: editable code, validation evidence, manufacturing feedback, and parallel product idea iteration.", "priority": "0.9", "changefreq": "weekly" }, { "path": "/docs/ai-usage", "title": "AI CAD Agent Setup - ForgeCAD", "description": "Set up Codex, Claude Code, OpenCode, or another agent to build, validate, inspect, export, and publish ForgeCAD models.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/cli", "title": "ForgeCAD CLI - Validate, Inspect, Export, Publish", "description": "Use the ForgeCAD CLI to run .forge.js models, render evidence, inspect manufacturability, export STEP/STL/3MF, and sync projects.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/docs/simready-quickstart", "title": "ForgeCAD SimReady Quickstart - Source-Authored Simulation Metadata", "description": "Author ForgeCAD assemblies with physical bodies, materials, colliders, joints, controllers, offline SimReady checks, and simulator exports.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/simulation-workflow", "title": "ForgeCAD Simulation Workflow - MuJoCo, Gym, and Isaac Lab Handoff", "description": "Take a ForgeCAD robot through MJCF export, MuJoCo playback, Gymnasium training, force-push interaction tests, and Isaac Lab integration planning.", "priority": "0.8", "changefreq": "weekly" }, { "path": "/docs/skills/forgecad-make-a-model", "title": "AI CAD Model-Making Skill - ForgeCAD", "description": "A workflow for AI agents to create manufacture-realistic ForgeCAD models with validation, renders, inspection evidence, and exports.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/benchmark", "title": "ForgeCAD Benchmark - AI CAD Model Generation", "description": "Benchmark AI agents generating real ForgeCAD models with executable code, visual evidence, validation output, and geometry checks.", "priority": "0.7", "changefreq": "weekly" }, { "path": "/blog", "title": "ForgeCAD Blog - AI-Native CAD and Manufacturing Workflows", "description": "Product notes, tutorials, examples, and technical updates on AI-native CAD, parametric modeling, validation, and manufacturing exports.", "priority": "0.6", "changefreq": "weekly" }, { "path": "/pricing", "title": "ForgeCAD Pricing", "description": "ForgeCAD plans for browser CAD, local CLI workflows, AI agent model generation, project publishing, and commercial use.", "priority": "0.5", "changefreq": "monthly" }, { "path": "/terms", "title": "ForgeCAD Terms of Service", "description": "Terms for ForgeCAD accounts, projects, hosted services, CLI use, commercial use, automation, and AI-assisted workflows.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/privacy", "title": "ForgeCAD Privacy Policy", "description": "How ForgeCAD handles account, project, billing, analytics, and service data for the website, CLI, and hosted app.", "priority": "0.3", "changefreq": "yearly" }, { "path": "/license", "title": "ForgeCAD Software License", "description": "ForgeCAD license terms for Free, Pro, Enterprise, embedded, backend, local CLI, and AI workflow usage.", "priority": "0.3", "changefreq": "yearly" }];
46404
46895
  const seoConfig = {
46405
46896
  siteUrl,
46406
46897
  defaultImage,
@@ -46477,17 +46968,17 @@ function SeoMetadata() {
46477
46968
  }, [location.pathname]);
46478
46969
  return null;
46479
46970
  }
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 })));
46971
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-BxHkYRE7.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
46972
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-CDoxHkz8.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
46973
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-DHaGP50_.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
46974
+ reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-BVEpJSVk.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
46975
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-DcCnj0qo.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
46485
46976
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
46486
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-BJZcM97j.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
46487
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-E3Rma7aV.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
46488
- reactExports.lazy(() => __vitePreload(() => import("./LegalPage-5RbKRGYK.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
46489
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-QXsAISLR.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46490
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-DdEHGUMU.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
46977
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-CIZSSAd0.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
46978
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-CzpZ6-Ce.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
46979
+ reactExports.lazy(() => __vitePreload(() => import("./LegalPage-B-u6FrVv.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
46980
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BJ0Dloyh.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46981
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-CRKZbY0y.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
46491
46982
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
46492
46983
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg|dxf)$/i;
46493
46984
  function firstMeaningfulLine(text) {
@@ -46745,11 +47236,12 @@ export {
46745
47236
  hasProjectTextFileExtension as a6,
46746
47237
  expandBoundsByTransformedAabb as a7,
46747
47238
  Canvas as a8,
46748
- PerspectiveCamera as a9,
46749
- buildShareUrl as aA,
46750
- share as aB,
46751
- ControlsInteractionBridge as aa,
46752
- ViewController as ab,
47239
+ ActiveCameraBridge as a9,
47240
+ storePendingShareCopy as aA,
47241
+ buildShareUrl as aB,
47242
+ share as aC,
47243
+ PerspectiveCamera as aa,
47244
+ ControlsInteractionBridge as ab,
46753
47245
  SceneConfigurator as ac,
46754
47246
  LocalEnvironment as ad,
46755
47247
  ForgeObject as ae,
@@ -46758,22 +47250,22 @@ export {
46758
47250
  OrbitControls2 as ah,
46759
47251
  TOUCH_GESTURES_3D as ai,
46760
47252
  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,
47253
+ ViewController as ak,
47254
+ ModelJourneyBar as al,
47255
+ useJointAnimationLoop as am,
47256
+ computeJointNodeMatrices as an,
47257
+ computeObjectJointMatrices as ao,
47258
+ readLastActiveFileForUser as ap,
47259
+ ToastContainer as aq,
47260
+ isMobile as ar,
47261
+ useFeatureFlag as as,
47262
+ decodeSharedHash as at,
47263
+ decodeSharedBundle as au,
47264
+ getExternalUrl as av,
47265
+ getGistId as aw,
47266
+ Viewport as ax,
47267
+ shouldBlockBrowserShortcut as ay,
47268
+ useDrawStore as az,
46777
47269
  authFetch as b,
46778
47270
  authApi as c,
46779
47271
  showToast as d,