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