forgecad 0.10.5 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/dist/assets/{AdminPage-raksfnNA.js → AdminPage-B1nIvqLS.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-DP3RxhPs.js → BenchmarkPage-YZJbw5nd.js} +1 -1
  3. package/dist/assets/{BlogPage-D7Dos-vl.js → BlogPage-DIWRApKS.js} +1 -1
  4. package/dist/assets/{DocsPage-DO1kvBns.js → DocsPage-ClL6X1hR.js} +2 -22
  5. package/dist/assets/{EditorApp-DQJmcmRT.js → EditorApp-CYBDvSyT.js} +575 -119
  6. package/dist/assets/{EmbedViewer-DFDUhOma.js → EmbedViewer-Dmfu_LIw.js} +2 -2
  7. package/dist/assets/{LandingPageProofDriven-DbE_tp8-.js → LandingPageProofDriven-XYTiYxfM.js} +1 -1
  8. package/dist/assets/{LegalPage-CominSso.js → LegalPage-D5Z3CscF.js} +1 -1
  9. package/dist/assets/{PricingPage-CcVIN9yj.js → PricingPage-BP4lIGio.js} +1 -1
  10. package/dist/assets/{SettingsPage-DLWcP289.js → SettingsPage-D3bcPBsC.js} +1 -1
  11. package/dist/assets/{app-xW3hOdq9.js → app-BKjogwIZ.js} +2192 -231
  12. package/dist/assets/{backendInit-mDHk97u7.js → backendInit-6a9-ilom.js} +76448 -75066
  13. package/dist/assets/cli/{render--SIU27W_.js → render-CMNudGb0.js} +3 -3
  14. package/dist/assets/{constructionHistoryWorker-uEe_Q7Kg.js → constructionHistoryWorker-BuZgc606.js} +6985 -6706
  15. package/dist/assets/{evalWorker-BqyDHDcI.js → evalWorker-DQ82ueGu.js} +40862 -39497
  16. package/dist/assets/{inspectWorker-UXMxlcR8.js → inspectWorker-Cuby2qfT.js} +2078 -478
  17. package/dist/assets/{jointPose-bYMlwU3v.js → jointPose-CFql5I-u.js} +1 -1
  18. package/dist/assets/{manifold-CyOV5B9S.js → manifold-02pmr7O7.js} +2 -2
  19. package/dist/assets/{manifold-BR7UYI4P.js → manifold-C6KU0oII.js} +1 -1
  20. package/dist/assets/{manifold-D4d5NQst.js → manifold-P1yF3GKn.js} +1 -1
  21. package/dist/assets/{reportWorker-DsaICZsn.js → reportWorker-kg065BVL.js} +85183 -78309
  22. package/dist/cli/render.html +1 -1
  23. package/dist/docs/index.html +2 -2
  24. package/dist/docs-raw/AI/usage.md +6 -8
  25. package/dist/docs-raw/CLI.md +10 -10
  26. package/dist/docs-raw/component-model.md +28 -9
  27. package/dist/docs-raw/generated/concepts.md +13 -4
  28. package/dist/docs-raw/generated/core.md +244 -56
  29. package/dist/docs-raw/generated/curves.md +13 -0
  30. package/dist/docs-raw/generated/runtime-names.md +2 -2
  31. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  32. package/dist/docs-raw/guides/structural-fea.md +11 -0
  33. package/dist/docs-raw/skills/forgecad-build-model.md +70 -147
  34. package/dist/docs-raw/skills/forgecad-image-prompt.md +1 -1
  35. package/dist/docs-raw/skills/forgecad-project-sync.md +3 -3
  36. package/dist/docs-raw/skills/forgecad-reconstruct-cad-file.md +2 -2
  37. package/dist/docs-raw/skills/forgecad-reconstruct-from-images.md +4 -5
  38. package/dist/docs-raw/skills/forgecad.md +3 -1
  39. package/dist/docs-raw/skills/index.md +1 -5
  40. package/dist/docs-raw/welcome.md +3 -4
  41. package/dist/index.html +1 -1
  42. package/dist/llms.txt +1 -2
  43. package/dist/sitemap.xml +15 -15
  44. package/dist-cli/{check-compiler-7YAHVXYM.js → check-compiler-UJWUEIDC.js} +1 -1
  45. package/dist-cli/{check-query-propagation-ZRR6IOJW.js → check-query-propagation-O2EPDJSY.js} +1 -1
  46. package/dist-cli/{chunk-VNM67DIV.js → chunk-MNDROM7T.js} +77145 -75767
  47. package/dist-cli/forgecad.js +1145 -441
  48. package/dist-skill/CONTEXT.md +429 -64
  49. package/dist-skill/SKILL.md +3 -1
  50. package/dist-skill/docs/API/core/concepts.md +31 -4
  51. package/dist-skill/docs/CLI.md +10 -10
  52. package/dist-skill/docs/generated/core.md +240 -57
  53. package/dist-skill/docs/generated/curves.md +13 -0
  54. package/dist-skill/docs/generated/runtime-names.md +2 -2
  55. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  56. package/dist-skill/docs/guides/manual-parameters.md +130 -0
  57. package/dist-skill/docs/guides/structural-fea.md +11 -0
  58. package/dist-skill/library/README.md +0 -4
  59. package/dist-skill/library/forgecad-build-model/SKILL.md +57 -150
  60. package/dist-skill/library/forgecad-build-model/references/inspection-feedback.md +58 -0
  61. package/dist-skill/library/forgecad-build-model/references/module-contracts.md +53 -0
  62. package/dist-skill/library/forgecad-build-model/references/parameter-controls.md +22 -0
  63. package/dist-skill/library/forgecad-build-model/references/readiness-review.md +43 -0
  64. package/dist-skill/library/forgecad-build-model/references/simulation-feedback.md +49 -0
  65. package/dist-skill/library/forgecad-build-model/references/stage-1-design-intent.md +21 -0
  66. package/dist-skill/library/forgecad-build-model/references/stage-2-architecture-plan.md +23 -0
  67. package/dist-skill/library/forgecad-build-model/references/stage-3-build-slices.md +39 -0
  68. package/dist-skill/library/forgecad-build-model/references/stage-4-feedback-iteration.md +24 -0
  69. package/dist-skill/library/forgecad-build-model/references/stage-5-readiness-package.md +34 -0
  70. package/dist-skill/library/forgecad-image-prompt/SKILL.md +1 -1
  71. package/dist-skill/library/forgecad-project-sync/SKILL.md +3 -3
  72. package/dist-skill/library/forgecad-reconstruct-cad-file/SKILL.md +2 -2
  73. package/dist-skill/library/forgecad-reconstruct-from-images/SKILL.md +4 -5
  74. package/dist-skill/website/skills/forgecad-build-model.md +70 -147
  75. package/dist-skill/website/skills/forgecad-image-prompt.md +1 -1
  76. package/dist-skill/website/skills/forgecad-project-sync.md +3 -3
  77. package/dist-skill/website/skills/forgecad-reconstruct-cad-file.md +2 -2
  78. package/dist-skill/website/skills/forgecad-reconstruct-from-images.md +4 -5
  79. package/dist-skill/website/skills/forgecad.md +3 -1
  80. package/dist-skill/website/skills/index.md +1 -5
  81. package/examples/api/param-path2d.forge.js +65 -0
  82. package/examples/api/param-placement2d.forge.js +80 -0
  83. package/examples/api/param-spline2d-g-continuity.forge.js +57 -0
  84. package/examples/api/spoon-full-tang-handle.forge.js +57 -17
  85. package/examples/api/surface-variable-thickness-panel.forge.js +62 -0
  86. package/examples/mechanical/airplane-propeller.forge.js +81 -28
  87. package/package.json +2 -2
  88. package/dist/docs-raw/skills/forgecad-design-spec.md +0 -145
  89. package/dist/docs-raw/skills/forgecad-grade-model.md +0 -84
  90. package/dist/docs-raw/skills/forgecad-inspect-model.md +0 -80
  91. package/dist/docs-raw/skills/forgecad-verify-mujoco.md +0 -78
  92. package/dist-skill/library/forgecad-design-spec/SKILL.md +0 -132
  93. package/dist-skill/library/forgecad-design-spec/references/default-profiles.md +0 -99
  94. package/dist-skill/library/forgecad-design-spec/references/master-prompt.md +0 -73
  95. package/dist-skill/library/forgecad-grade-model/SKILL.md +0 -72
  96. package/dist-skill/library/forgecad-grade-model/agents/openai.yaml +0 -4
  97. package/dist-skill/library/forgecad-inspect-model/SKILL.md +0 -68
  98. package/dist-skill/library/forgecad-verify-mujoco/SKILL.md +0 -66
  99. package/dist-skill/website/skills/forgecad-design-spec.md +0 -145
  100. package/dist-skill/website/skills/forgecad-grade-model.md +0 -84
  101. package/dist-skill/website/skills/forgecad-inspect-model.md +0 -80
  102. package/dist-skill/website/skills/forgecad-verify-mujoco.md +0 -78
  103. /package/dist-skill/library/{forgecad-verify-mujoco → forgecad-build-model}/scripts/mujoco_verify.py +0 -0
  104. /package/dist-skill/library/{forgecad-inspect-model → forgecad-build-model/scripts}/summarize_manifest.py +0 -0
@@ -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, S as SDF_PROGRAM_OP, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, a as Scene, b as PCFSoftShadowMap, V as VSMShadowMap, c as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, d as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, e as Layers, f as Color, g as RGBAFormat, U as UnsignedByteType, h as Vector3, i as Vector2, j as Clock, T as THREE, D as DoubleSide, k as REVISION, M as Mesh, I as IcosahedronGeometry, l as ShaderMaterial, m as Spherical, Q as Quaternion, n as MOUSE, o as TOUCH, p as Ray, q as Plane, r as DataTextureLoader, H as HalfFloatType, F as FloatType, t as DataUtils, u as LinearFilter, v as RedFormat, w as InstancedBufferGeometry, x as Float32BufferAttribute, y as InstancedInterleavedBuffer, E as InterleavedBufferAttribute, G as WireframeGeometry, J as Box3, K as Sphere, X as UniformsUtils, Y as UniformsLib, Z as Vector4, $ as Line3, a0 as Matrix4, a1 as MathUtils, a2 as Uniform, a3 as WebGLRenderTarget, a4 as DepthTexture, a5 as BackSide, a6 as ClampToEdgeWrapping, a7 as PlaneGeometry, a8 as UVMapping, a9 as DataTexture, aa as Texture, ab as MeshBasicMaterial, ac as IntType, ad as ShortType, ae as ByteType, af as UnsignedIntType, ag as Loader, ah as LoadingManager, ai as LinearMipMapLinearFilter, aj as FileLoader, ak as NoBlending, al as CubeReflectionMapping, am as EquirectangularReflectionMapping, an as CubeTextureLoader, ao as WebGLCubeRenderTarget, ap as ConstraintSketch, aq as setSketchPlacement3D, ar as Sketch, as as PROFILE_BACKEND_MARKER, at as FrozenShape, au as setShapeCompilePlan, av as hasAnyPorts, aw as setShapePortsInternal, ax as markShapePortsUsed, ay as writeViewPreferences, az as setParamOverrides, aA as readViewPreferences, aB as resolveForgeRenderStyle, aC as isConstraintSketch, aD as updateConstraintValue, aE as linearizeMultiObjectSteps, aF as getShapeCompilePlan, aG as resolveCameraControlMode, aH as resolveComparisonOpacity, aI as resolveComparisonInspectMode, aJ as resolveBooleanPref, aK as resolveInspectQuantizeBands, aL as resolveInspectIsolineSpacing, aM as resolveInspectColormap, aN as resolveThicknessColorRange, aO as resolveInspectPointSampleCount, aP as SCAN_PROXY_GRANULARITY_DEFAULT, aQ as DEFAULT_MANUAL_SCENE_SETTINGS, aR as resolveManualSceneSettings, aS as publishSolverWasmRunDebug, aT as resolveForgeQualityPreset, aU as DEFAULT_INSPECT_ISOLINES_ENABLED, aV as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aW as findJointAnimationClip, aX as resolveJointAnimation, aY as resolveJointViewValues, aZ as resolveImportPath, a_ as DEFAULT_INSPECT_CRITICAL_LINE_ENABLED, a$ as resolveScanProxyGranularity, b0 as DEFAULT_COMPARISON_REFERENCE_OPACITY, b1 as DEFAULT_COMPARISON_CANDIDATE_OPACITY, b2 as BufferGeometry, b3 as LineBasicMaterial, b4 as Line$1, b5 as LineDashedMaterial, b6 as CanvasTexture, b7 as Object3D, b8 as FogExp2, b9 as Fog, ba as AmbientLight, bb as HemisphereLight, bc as SpotLight, bd as PointLight, be as DirectionalLight, bf as BufferAttribute, bg as heatPointsForSide, bh as COMPARISON_COLORS, bi as comparisonHeatDepthTest, bj as comparisonHeatEdgeOpacity, bk as comparisonHeatPatchOpacity, bl as shapeToGeometry, bm as buildComparisonHeatPatchGeometry, bn as EdgesGeometry, bo as buildShapeFromCompilePlan, bp as VIEWPORT_CAMERA_STORAGE_KEY, bq as parseViewportCameraState, br as getKernelFaceNameForTriangle, bs as OBJECT_CONTEXT_MENU_MARGIN, bt as buildVisibleHistoryStacks, bu as sketchToSvg, bv as sketchToDxf, bw as runScript, bx as MeshPhysicalMaterial, by as LineSegments, bz as findDesignTraceNodeForConstructionStep, bA as formatDesignTraceAnchor, bB as waitForAnimationFrame, bC as selectBuildLedgerNodes, bD as UNIDENTIFIED_FACE_NAME, bE as getBakedEdges, bF as worldAuthorPlaneToLocal, bG as compileSdfProgramEvaluator3, bH as SDF_LINEAR_OUTPUT_COLOR_GLSL, bI as GLSL3, bJ as BoxGeometry, bK as Data3DTexture, bL as buildSdfRaymarchFragmentShader, bM as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bN as scanProxySourceBytes, bO as disposeScanProxyGeometry, bP as scanProxyGeometryFromPayload, bQ as AdditiveBlending, bR as geometryWithVisibleVertexColors, bS as MeshBVH, bT as makeColorScaleTexture, bU as colorScaleLUT, bV as makeScalarValueTexture, bW as makeInspectScalarUniforms, bX as updateInspectScalarUniforms, bY as descriptorToThreeTexture, bZ as applyProjectedTexture, b_ as getRenderStylePreset, b$ as SCAN_RENDER_COLORS, c0 as NormalBlending, c1 as acceleratedRaycast, c2 as INSPECT_SCALAR_FRAGMENT_SHADER, c3 as INSPECT_SCALAR_VERTEX_SHADER, c4 as scanMaterialShellColor, c5 as ZEBRA_STRIPE_SOFTNESS, c6 as ZEBRA_STRIPE_SCALE, c7 as ZEBRA_LIGHT_COLOR, c8 as ZEBRA_DARK_COLOR, c9 as ZEBRA_ACCENT_COLOR, ca as ZEBRA_STRIPE_FRAGMENT_SHADER, cb as ZEBRA_STRIPE_VERTEX_SHADER, cc as SCAN_PROXY_LAYER_STYLES, cd as scanMaterialLayerStyles, ce as SURFACE_FIELD_FRAGMENT_SHADER, cf as SURFACE_FIELD_VERTEX_SHADER, cg as WORLD_UP$1, ch as CatmullRomCurve3, ci as TubeGeometry, cj as DEFAULT_THICKNESS_COLOR_RANGE, ck as DEFAULT_COLORMAP, cl as colorScaleHexStops, cm as PERFORMANCE_SAMPLE_INTERVAL_SEC, cn as formatPerformanceCount, co as NON_TEXT_INPUT_TYPES, cp as MeshStandardMaterial, cq as Shape, cr as ShapeGeometry, cs as ShaderLib, ct as CylinderGeometry, cu as createResolvedExplodeConfig, cv as explodeBoundsCenter, cw as explodeMergeBounds, cx as resolveExplodeDirective, cy as computeExplodeMotion, cz 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_WIDTH, cQ as OBJECT_CONTEXT_MENU_HEIGHT, cR as triangleSoupFromMeshes, cS as compareTriangleSoups, cT as buildGeometryComparisonPointCloud, cU as aabbOverlaps, cV as aabbOverlapVolume, cW as DEFAULT_COLLISION_INSPECTION_OPTIONS, cX as resolveScalarSceneSampleBudget, cY as INSPECT_POINT_SAMPLE_COUNT_MIN, cZ as FOCUS_MODE_DIM_OPACITY, c_ as DEFAULT_THICKNESS_INSPECTION_OPTIONS, c$ as comparisonCandidateContextOpacity, d0 as Matrix3, d1 as initBackendForEvaluation } from "./backendInit-mDHk97u7.js";
7
+ import { _ as __vitePreload, S as SDF_PROGRAM_OP, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, a as Scene, b as PCFSoftShadowMap, V as VSMShadowMap, c as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, d as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, e as Layers, f as Color, g as RGBAFormat, U as UnsignedByteType, h as Vector3, i as Vector2, j as Clock, T as THREE, D as DoubleSide, k as REVISION, M as Mesh, I as IcosahedronGeometry, l as ShaderMaterial, m as Spherical, Q as Quaternion, n as MOUSE, o as TOUCH, p as Ray, q as Plane, r as DataTextureLoader, H as HalfFloatType, F as FloatType, t as DataUtils, u as LinearFilter, v as RedFormat, w as InstancedBufferGeometry, x as Float32BufferAttribute, y as InstancedInterleavedBuffer, E as InterleavedBufferAttribute, G as WireframeGeometry, J as Box3, K as Sphere, X as UniformsUtils, Y as UniformsLib, Z as Vector4, $ as Line3, a0 as Matrix4, a1 as MathUtils, a2 as Uniform, a3 as WebGLRenderTarget, a4 as DepthTexture, a5 as BackSide, a6 as ClampToEdgeWrapping, a7 as PlaneGeometry, a8 as UVMapping, a9 as DataTexture, aa as Texture, ab as MeshBasicMaterial, ac as IntType, ad as ShortType, ae as ByteType, af as UnsignedIntType, ag as Loader, ah as LoadingManager, ai as LinearMipMapLinearFilter, aj as FileLoader, ak as NoBlending, al as CubeReflectionMapping, am as EquirectangularReflectionMapping, an as CubeTextureLoader, ao as WebGLCubeRenderTarget, ap as ConstraintSketch, aq as setSketchPlacement3D, ar as Sketch, as as PROFILE_BACKEND_MARKER, at as FrozenShape, au as setShapeCompilePlan, av as hasAnyPorts, aw as setShapePortsInternal, ax as markShapePortsUsed, ay as writeViewPreferences, az as setParamOverrides, aA as readViewPreferences, aB as resolveForgeRenderStyle, aC as isConstraintSketch, aD as updateConstraintValue, aE as linearizeMultiObjectSteps, aF as getShapeCompilePlan, aG as resolveCameraControlMode, aH as resolveComparisonOpacity, aI as resolveComparisonInspectMode, aJ as resolveBooleanPref, aK as resolveInspectQuantizeBands, aL as resolveInspectIsolineSpacing, aM as resolveInspectColormap, aN as resolveThicknessColorRange, aO as resolveInspectPointSampleCount, aP as SCAN_PROXY_GRANULARITY_DEFAULT, aQ as DEFAULT_MANUAL_SCENE_SETTINGS, aR as resolveManualSceneSettings, aS as publishSolverWasmRunDebug, aT as resolveForgeQualityPreset, aU as DEFAULT_INSPECT_ISOLINES_ENABLED, aV as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aW as findJointAnimationClip, aX as resolveJointAnimation, aY as resolveJointViewValues, aZ as resolveImportPath, a_ as DEFAULT_INSPECT_CRITICAL_LINE_ENABLED, a$ as resolveScanProxyGranularity, b0 as DEFAULT_COMPARISON_REFERENCE_OPACITY, b1 as DEFAULT_COMPARISON_CANDIDATE_OPACITY, b2 as BufferGeometry, b3 as LineBasicMaterial, b4 as Line$1, b5 as LineDashedMaterial, b6 as CanvasTexture, b7 as Object3D, b8 as FogExp2, b9 as Fog, ba as AmbientLight, bb as HemisphereLight, bc as SpotLight, bd as PointLight, be as DirectionalLight, bf as BufferAttribute, bg as heatPointsForSide, bh as COMPARISON_COLORS, bi as comparisonHeatDepthTest, bj as comparisonHeatEdgeOpacity, bk as comparisonHeatPatchOpacity, bl as shapeToGeometry, bm as buildComparisonHeatPatchGeometry, bn as EdgesGeometry, bo as buildShapeFromCompilePlan, bp as VIEWPORT_CAMERA_STORAGE_KEY, bq as parseViewportCameraState, br as getKernelFaceNameForTriangle, bs as OBJECT_CONTEXT_MENU_MARGIN, bt as buildVisibleHistoryStacks, bu as sketchToSvg, bv as sketchToDxf, bw as runScript, bx as MeshPhysicalMaterial, by as LineSegments, bz as findDesignTraceNodeForConstructionStep, bA as formatDesignTraceAnchor, bB as waitForAnimationFrame, bC as selectBuildLedgerNodes, bD as UNIDENTIFIED_FACE_NAME, bE as getBakedEdges, bF as worldAuthorPlaneToLocal, bG as compileSdfProgramEvaluator3, bH as SDF_LINEAR_OUTPUT_COLOR_GLSL, bI as GLSL3, bJ as BoxGeometry, bK as Data3DTexture, bL as buildSdfRaymarchFragmentShader, bM as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bN as scanProxySourceBytes, bO as disposeScanProxyGeometry, bP as scanProxyGeometryFromPayload, bQ as AdditiveBlending, bR as geometryWithVisibleVertexColors, bS as MeshBVH, bT as makeColorScaleTexture, bU as colorScaleLUT, bV as makeScalarValueTexture, bW as makeInspectScalarUniforms, bX as updateInspectScalarUniforms, bY as descriptorToThreeTexture, bZ as applyProjectedTexture, b_ as getRenderStylePreset, b$ as SCAN_RENDER_COLORS, c0 as NormalBlending, c1 as acceleratedRaycast, c2 as INSPECT_SCALAR_FRAGMENT_SHADER, c3 as INSPECT_SCALAR_VERTEX_SHADER, c4 as scanMaterialShellColor, c5 as ZEBRA_STRIPE_SOFTNESS, c6 as ZEBRA_STRIPE_SCALE, c7 as ZEBRA_LIGHT_COLOR, c8 as ZEBRA_DARK_COLOR, c9 as ZEBRA_ACCENT_COLOR, ca as ZEBRA_STRIPE_FRAGMENT_SHADER, cb as ZEBRA_STRIPE_VERTEX_SHADER, cc as SCAN_PROXY_LAYER_STYLES, cd as scanMaterialLayerStyles, ce as SURFACE_FIELD_FRAGMENT_SHADER, cf as SURFACE_FIELD_VERTEX_SHADER, cg as WORLD_UP$1, ch as CatmullRomCurve3, ci as TubeGeometry, cj as DEFAULT_THICKNESS_COLOR_RANGE, ck as DEFAULT_COLORMAP, cl as colorScaleHexStops, cm as PERFORMANCE_SAMPLE_INTERVAL_SEC, cn as formatPerformanceCount, co as NON_TEXT_INPUT_TYPES, cp as MeshStandardMaterial, cq as Shape, cr as ShapeGeometry, cs as ShaderLib, ct as CylinderGeometry, cu as createResolvedExplodeConfig, cv as explodeBoundsCenter, cw as explodeMergeBounds, cx as resolveExplodeDirective, cy as computeExplodeMotion, cz 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_WIDTH, cQ as OBJECT_CONTEXT_MENU_HEIGHT, cR as placement2DItemsOverlap, cS as placement2DFootprintInsideFrame, cT as placement2DPointInsideFrame, cU as placement2DFrameBounds, cV as placement2DClampCenterToFrame, cW as triangleSoupFromMeshes, cX as compareTriangleSoups, cY as buildGeometryComparisonPointCloud, cZ as aabbOverlaps, c_ as aabbOverlapVolume, c$ as DEFAULT_COLLISION_INSPECTION_OPTIONS, d0 as resolveScalarSceneSampleBudget, d1 as INSPECT_POINT_SAMPLE_COUNT_MIN, d2 as FOCUS_MODE_DIM_OPACITY, d3 as DEFAULT_THICKNESS_INSPECTION_OPTIONS, d4 as comparisonCandidateContextOpacity, d5 as Matrix3, d6 as initBackendForEvaluation } from "./backendInit-6a9-ilom.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];
@@ -213,7 +213,7 @@ function trimRunResultCache() {
213
213
  }
214
214
  function persistCache() {
215
215
  try {
216
- const entries = {};
216
+ const entries2 = {};
217
217
  let estimatedPersistBytes = 0;
218
218
  for (const [key, entry] of runResultCache) {
219
219
  estimatedPersistBytes += entry.estimatedBytes;
@@ -224,7 +224,7 @@ function persistCache() {
224
224
  }
225
225
  return;
226
226
  }
227
- entries[key] = {
227
+ entries2[key] = {
228
228
  code: entry.code,
229
229
  files: entry.files,
230
230
  paramOverrides: entry.paramOverrides,
@@ -234,7 +234,7 @@ function persistCache() {
234
234
  serialized: serializedResultToJson(entry.serialized)
235
235
  };
236
236
  }
237
- const json = JSON.stringify({ v: CACHE_VERSION, entries });
237
+ const json = JSON.stringify({ v: CACHE_VERSION, entries: entries2 });
238
238
  if (json.length > MAX_PERSIST_BYTES) {
239
239
  try {
240
240
  sessionStorage.removeItem(sessionStorageKey());
@@ -666,14 +666,14 @@ function BrandMark({
666
666
  }
667
667
  );
668
668
  }
669
- let nextId = 0;
669
+ let nextId$1 = 0;
670
670
  let toasts = [];
671
671
  const listeners$2 = /* @__PURE__ */ new Set();
672
672
  function notify$1() {
673
673
  listeners$2.forEach((l2) => l2());
674
674
  }
675
675
  function showToast(message, variant = "info", durationMs = 3e3) {
676
- const toast = { id: nextId++, message, variant, durationMs };
676
+ const toast = { id: nextId$1++, message, variant, durationMs };
677
677
  toasts = [...toasts, toast];
678
678
  notify$1();
679
679
  setTimeout(() => {
@@ -14866,6 +14866,7 @@ const dark = {
14866
14866
  bg: "#0d1117",
14867
14867
  bgPanel: "#161b22",
14868
14868
  bgSurface: "#1c2128",
14869
+ bgSubtle: "#131920",
14869
14870
  bgHover: "#242b35",
14870
14871
  bgActive: "#2d333b",
14871
14872
  bgInput: "#0a0e14",
@@ -14902,6 +14903,7 @@ const light = {
14902
14903
  bg: "#f0f4f8",
14903
14904
  bgPanel: "#f8fafc",
14904
14905
  bgSurface: "#e8edf2",
14906
+ bgSubtle: "#eef2f6",
14905
14907
  bgHover: "#dfe5ec",
14906
14908
  bgActive: "#d2dae3",
14907
14909
  bgInput: "#f8fafc",
@@ -14938,6 +14940,7 @@ const gruvbox = {
14938
14940
  bg: "#282828",
14939
14941
  bgPanel: "#1d2021",
14940
14942
  bgSurface: "#3c3836",
14943
+ bgSubtle: "#32302f",
14941
14944
  bgHover: "#504945",
14942
14945
  bgActive: "#665c54",
14943
14946
  bgInput: "#1d2021",
@@ -14974,6 +14977,7 @@ const tokyoNight = {
14974
14977
  bg: "#1a1b26",
14975
14978
  bgPanel: "#16161e",
14976
14979
  bgSurface: "#24283b",
14980
+ bgSubtle: "#1f2335",
14977
14981
  bgHover: "#292e42",
14978
14982
  bgActive: "#33467c",
14979
14983
  bgInput: "#16161e",
@@ -15010,6 +15014,7 @@ const kanagawaLotus = {
15010
15014
  bg: "#f2ecbc",
15011
15015
  bgPanel: "#f7f3d7",
15012
15016
  bgSurface: "#e7dba0",
15017
+ bgSubtle: "#eee6b2",
15013
15018
  bgHover: "#d9d08e",
15014
15019
  bgActive: "#c9b97a",
15015
15020
  bgInput: "#f7f3d7",
@@ -15046,6 +15051,7 @@ const shield = {
15046
15051
  bg: "#02070a",
15047
15052
  bgPanel: "#06141a",
15048
15053
  bgSurface: "#0a2028",
15054
+ bgSubtle: "#031016",
15049
15055
  bgHover: "#0d2b35",
15050
15056
  bgActive: "#113d4a",
15051
15057
  bgInput: "#01080c",
@@ -15082,6 +15088,7 @@ const shieldLight = {
15082
15088
  bg: "#cdd5d3",
15083
15089
  bgPanel: "#dbe3e0",
15084
15090
  bgSurface: "#b9c8c5",
15091
+ bgSubtle: "#c4cfcc",
15085
15092
  bgHover: "#a9b8b5",
15086
15093
  bgActive: "#8ea3a0",
15087
15094
  bgInput: "#eef3f1",
@@ -15287,15 +15294,14 @@ const runResultDeserializer = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Obje
15287
15294
  __proto__: null,
15288
15295
  deserializeRunResult
15289
15296
  }, Symbol.toStringTag, { value: "Module" }));
15290
- function compilePlanNeedsNativeShapeLowering(plan) {
15291
- if (plan.kind === "queryOwner") return compilePlanNeedsNativeShapeLowering(plan.base);
15292
- return plan.kind !== "sdf";
15297
+ function compilePlanNeedsServerShapeLowering(_plan) {
15298
+ return true;
15293
15299
  }
15294
- function serializedSceneObjectNeedsNativeShapeLowering(object) {
15295
- return object.compilePlan !== null && object.compilePlan !== void 0 && compilePlanNeedsNativeShapeLowering(object.compilePlan);
15300
+ function serializedSceneObjectNeedsServerShapeLowering(object) {
15301
+ return object.compilePlan !== null && object.compilePlan !== void 0 && compilePlanNeedsServerShapeLowering(object.compilePlan);
15296
15302
  }
15297
- function serializedRunResultNeedsNativeOcctLowering(result) {
15298
- return result.objects.some(serializedSceneObjectNeedsNativeShapeLowering);
15303
+ function serializedRunResultNeedsServerIrLowering(result) {
15304
+ return result.objects.some(serializedSceneObjectNeedsServerShapeLowering);
15299
15305
  }
15300
15306
  function decodeFromWire(data) {
15301
15307
  const view2 = new DataView(data);
@@ -15976,7 +15982,7 @@ const CRASH_COOLDOWN_MS = 2e3;
15976
15982
  class EvalWorkerClient {
15977
15983
  constructor(workerFactory = () => new Worker(new URL(
15978
15984
  /* @vite-ignore */
15979
- "/assets/evalWorker-BqyDHDcI.js",
15985
+ "/assets/evalWorker-DQ82ueGu.js",
15980
15986
  import.meta.url
15981
15987
  ), { type: "module" })) {
15982
15988
  __publicField(this, "worker", null);
@@ -17168,8 +17174,8 @@ async function fetchGistModel(gistId) {
17168
17174
  if (!res.ok) throw new Error(`Failed to fetch gist: ${res.status} ${res.statusText}`);
17169
17175
  const data = await res.json();
17170
17176
  const files = data.files;
17171
- const entries = Object.values(files);
17172
- const forgeFile = entries.find((f2) => f2.filename.endsWith(".forge.js")) || entries.find((f2) => f2.filename.endsWith(".sketch.js")) || entries[0];
17177
+ const entries2 = Object.values(files);
17178
+ const forgeFile = entries2.find((f2) => f2.filename.endsWith(".forge.js")) || entries2.find((f2) => f2.filename.endsWith(".sketch.js")) || entries2[0];
17173
17179
  if (!forgeFile) throw new Error("Gist contains no files");
17174
17180
  return { filename: forgeFile.filename, code: forgeFile.content };
17175
17181
  }
@@ -17360,6 +17366,9 @@ function createErrorRunResult(message, quality) {
17360
17366
  params: [],
17361
17367
  stringParams: [],
17362
17368
  listParams: [],
17369
+ path2dParams: [],
17370
+ spline2dParams: [],
17371
+ placement2dParams: [],
17363
17372
  dimensions: [],
17364
17373
  highlights: [],
17365
17374
  debugHighlights3D: [],
@@ -17431,6 +17440,9 @@ function buildRunState(previewFile, runResult, state2) {
17431
17440
  params: runResult.params,
17432
17441
  stringParams: runResult.stringParams,
17433
17442
  listParams: runResult.listParams,
17443
+ path2dParams: runResult.path2dParams ?? [],
17444
+ spline2dParams: runResult.spline2dParams ?? [],
17445
+ placement2dParams: runResult.placement2dParams ?? [],
17434
17446
  jointValues: nextJointValues,
17435
17447
  jointAnimationClip: nextAnimationState.clip,
17436
17448
  jointAnimationProgress: nextAnimationState.progress,
@@ -17452,13 +17464,15 @@ const KERNELS = [
17452
17464
  { id: "occt", name: "occt", location: "local", label: "OCCT" },
17453
17465
  { id: "truck", name: "truck", location: "local", label: "Truck" },
17454
17466
  { id: "sdf", name: "sdf", location: "local", label: "SDF" },
17455
- { id: "server-occt", name: "occt", location: "server", label: "Server OCCT" }
17467
+ { id: "server-occt", name: "occt", location: "server", label: "Server OCCT" },
17468
+ { id: "server-sdf", name: "sdf", location: "server", label: "Server SDF" }
17456
17469
  ];
17457
17470
  function availableKernels(canUseServer) {
17458
17471
  return KERNELS.filter((kernel) => kernel.location === "local" || canUseServer);
17459
17472
  }
17460
17473
  function kernelId(kernel) {
17461
- return kernel.location === "server" ? "server-occt" : kernel.name;
17474
+ if (kernel.location === "local") return kernel.name;
17475
+ return kernel.name === "sdf" ? "server-sdf" : "server-occt";
17462
17476
  }
17463
17477
  function kernelFromId(id) {
17464
17478
  const kernel = KERNELS.find((entry) => entry.id === id);
@@ -17533,6 +17547,9 @@ function errorRunResult(error, timeMs = 0) {
17533
17547
  params: [],
17534
17548
  stringParams: [],
17535
17549
  listParams: [],
17550
+ path2dParams: [],
17551
+ spline2dParams: [],
17552
+ placement2dParams: [],
17536
17553
  dimensions: [],
17537
17554
  highlights: [],
17538
17555
  debugHighlights3D: [],
@@ -17715,7 +17732,8 @@ function codeEditorPatchForActiveFile(activeFile, files, meshPreviewFile) {
17715
17732
  }
17716
17733
  function runResultHasViewportContent(result) {
17717
17734
  var _a3, _b2, _c, _d;
17718
- return result.objects.length > 0 || !!result.assemblyKinematics || (((_a3 = result.jointsView) == null ? void 0 : _a3.joints.length) ?? 0) > 0 && ((_b2 = result.jointsView) == null ? void 0 : _b2.enabled) !== false || (((_c = result.renderLabels) == null ? void 0 : _c.length) ?? 0) > 0 || (((_d = result.debugHighlights3D) == null ? void 0 : _d.length) ?? 0) > 0;
17735
+ const hasAnchoredParams = result.params.some((param) => !!param.anchor) || result.stringParams.some((param) => !!param.anchor) || result.listParams.some((param) => !!param.anchor) || (result.path2dParams ?? []).some((param) => !!param.anchor) || (result.spline2dParams ?? []).some((param) => !!param.anchor) || (result.placement2dParams ?? []).some((param) => !!param.anchor);
17736
+ return result.objects.length > 0 || !!result.assemblyKinematics || (((_a3 = result.jointsView) == null ? void 0 : _a3.joints.length) ?? 0) > 0 && ((_b2 = result.jointsView) == null ? void 0 : _b2.enabled) !== false || (((_c = result.renderLabels) == null ? void 0 : _c.length) ?? 0) > 0 || (((_d = result.debugHighlights3D) == null ? void 0 : _d.length) ?? 0) > 0 || hasAnchoredParams;
17719
17737
  }
17720
17738
  function codeEditorPatchForRunResult(result, meshPreviewFile) {
17721
17739
  if (meshPreviewFile || runResultHasViewportContent(result)) return {};
@@ -17749,7 +17767,7 @@ const makeParamSnapshotId = () => {
17749
17767
  if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID();
17750
17768
  return `param-snapshot-${Date.now()}-${Math.random().toString(36).slice(2)}`;
17751
17769
  };
17752
- function filterOverridesForCurrentParams(overrides, params, stringParams, listParams) {
17770
+ function filterOverridesForCurrentParams(overrides, params, stringParams, listParams, path2dParams, spline2dParams, placement2dParams) {
17753
17771
  const scalarNames = /* @__PURE__ */ new Set([...params.map((param) => param.name), ...stringParams.map((param) => param.name)]);
17754
17772
  const filtered = {};
17755
17773
  let ignoredCount = 0;
@@ -17759,17 +17777,51 @@ function filterOverridesForCurrentParams(overrides, params, stringParams, listPa
17759
17777
  continue;
17760
17778
  }
17761
17779
  const listDef = listParams.find((candidate) => key === `${candidate.name}.__count__` || key.startsWith(`${candidate.name}[`));
17762
- if (!listDef) {
17763
- ignoredCount += 1;
17780
+ if (listDef) {
17781
+ if (key === `${listDef.name}.__count__`) {
17782
+ filtered[key] = value;
17783
+ continue;
17784
+ }
17785
+ const fieldName = key.slice(key.lastIndexOf(".") + 1);
17786
+ if (listDef.fieldOrder.includes(fieldName)) filtered[key] = value;
17787
+ else ignoredCount += 1;
17764
17788
  continue;
17765
17789
  }
17766
- if (key === `${listDef.name}.__count__`) {
17767
- filtered[key] = value;
17790
+ const pathDef = path2dParams.find((candidate) => key === `${candidate.name}.__count__` || key.startsWith(`${candidate.name}[`));
17791
+ if (pathDef) {
17792
+ if (key === `${pathDef.name}.__count__`) {
17793
+ filtered[key] = value;
17794
+ continue;
17795
+ }
17796
+ const fieldName = key.slice(key.lastIndexOf(".") + 1);
17797
+ if (fieldName === "x" || fieldName === "y") filtered[key] = value;
17798
+ else ignoredCount += 1;
17799
+ continue;
17800
+ }
17801
+ const splineDef = spline2dParams.find((candidate) => key === `${candidate.name}.__count__` || key.startsWith(`${candidate.name}[`));
17802
+ if (splineDef) {
17803
+ if (key === `${splineDef.name}.__count__`) {
17804
+ filtered[key] = value;
17805
+ continue;
17806
+ }
17807
+ const fieldName = key.slice(key.lastIndexOf(".") + 1);
17808
+ if (fieldName === "x" || fieldName === "y" || fieldName === "g") filtered[key] = value;
17809
+ else ignoredCount += 1;
17810
+ continue;
17811
+ }
17812
+ const placementDef = placement2dParams.find((candidate) => key.startsWith(`${candidate.name}.`));
17813
+ if (placementDef) {
17814
+ const suffix = key.slice(placementDef.name.length + 1);
17815
+ const [itemId, fieldName, ...extra] = suffix.split(".");
17816
+ const item = placementDef.items.find((candidate) => candidate.id === itemId);
17817
+ if (item && extra.length === 0 && (fieldName === "x" || fieldName === "y" || fieldName === "angle" || fieldName === "zone")) {
17818
+ filtered[key] = value;
17819
+ } else {
17820
+ ignoredCount += 1;
17821
+ }
17768
17822
  continue;
17769
17823
  }
17770
- const fieldName = key.slice(key.lastIndexOf(".") + 1);
17771
- if (listDef.fieldOrder.includes(fieldName)) filtered[key] = value;
17772
- else ignoredCount += 1;
17824
+ ignoredCount += 1;
17773
17825
  }
17774
17826
  return { overrides: filtered, ignoredCount };
17775
17827
  }
@@ -18186,6 +18238,9 @@ const useForgeStore = create((set, get) => ({
18186
18238
  params: [],
18187
18239
  stringParams: [],
18188
18240
  listParams: [],
18241
+ path2dParams: [],
18242
+ spline2dParams: [],
18243
+ placement2dParams: [],
18189
18244
  runQuality: resolveForgeQualityPreset(initialViewPreferences.runQuality ?? "live"),
18190
18245
  setRunQuality: (quality) => {
18191
18246
  const next = resolveForgeQualityPreset(quality);
@@ -18196,6 +18251,9 @@ const useForgeStore = create((set, get) => ({
18196
18251
  paramOverrides: {},
18197
18252
  paramOverridesByFile: {},
18198
18253
  paramSnapshotsByFile: initialParamSnapshotsByFile,
18254
+ focusedParamName: null,
18255
+ spatialParamSheetName: null,
18256
+ expandedSpatialParamSheetName: null,
18199
18257
  jointValues: {},
18200
18258
  jointAnimationClip: null,
18201
18259
  jointAnimationProgress: 0,
@@ -18234,8 +18292,9 @@ const useForgeStore = create((set, get) => ({
18234
18292
  },
18235
18293
  computeTarget: initialKernel.location,
18236
18294
  setComputeTarget: (target) => {
18295
+ const preferredServerBackend = get().activeBackend === "sdf" ? "sdf" : "occt";
18237
18296
  const kernel = resolveAvailableKernel(
18238
- { name: target === "server" ? "occt" : get().activeBackend, location: target },
18297
+ { name: target === "server" ? preferredServerBackend : get().activeBackend, location: target },
18239
18298
  availableKernels(canUseServerCompute())
18240
18299
  );
18241
18300
  writeViewPreferences({ activeBackend: kernel.name, computeTarget: kernel.location });
@@ -18293,6 +18352,9 @@ const useForgeStore = create((set, get) => ({
18293
18352
  params: [],
18294
18353
  stringParams: [],
18295
18354
  listParams: [],
18355
+ path2dParams: [],
18356
+ spline2dParams: [],
18357
+ placement2dParams: [],
18296
18358
  previewFile: null,
18297
18359
  objectSettings: {},
18298
18360
  buildLedgerEvents: []
@@ -18346,14 +18408,15 @@ const useForgeStore = create((set, get) => ({
18346
18408
  serverComputeAbortController = abortController;
18347
18409
  try {
18348
18410
  const irRequest = await evalWorkerClient.runIrPlan(runPayload);
18349
- if (irRequest.planResult.error || !serializedRunResultNeedsNativeOcctLowering(irRequest.planResult)) {
18411
+ if (irRequest.planResult.error || !serializedRunResultNeedsServerIrLowering(irRequest.planResult)) {
18350
18412
  serialized = irRequest.planResult;
18351
18413
  } else {
18352
18414
  const refreshTimer2 = window.setTimeout(() => {
18353
18415
  void get().refreshServerJobs();
18354
18416
  }, 250);
18355
18417
  try {
18356
- serialized = await computeIr(irRequest, abortController.signal);
18418
+ const serverBackend = get().activeBackend === "sdf" ? "sdf" : "occt";
18419
+ serialized = await computeIr({ ...irRequest, serverBackend }, abortController.signal);
18357
18420
  } finally {
18358
18421
  window.clearTimeout(refreshTimer2);
18359
18422
  void get().refreshServerJobs();
@@ -18494,6 +18557,19 @@ const useForgeStore = create((set, get) => ({
18494
18557
  get().execute();
18495
18558
  }, PARAM_DEBOUNCE_MS);
18496
18559
  },
18560
+ setParams: (values, deleteKeys = []) => {
18561
+ const overrides = { ...get().paramOverrides, ...values };
18562
+ for (const key of deleteKeys) delete overrides[key];
18563
+ const { activeFile: curFile, paramOverridesByFile } = get();
18564
+ const previewKey = curFile ? resolvePreviewFile(curFile, get().files) : null;
18565
+ const nextByFile = previewKey ? { ...paramOverridesByFile, [previewKey]: overrides } : paramOverridesByFile;
18566
+ set({ paramOverrides: overrides, paramOverridesByFile: nextByFile });
18567
+ if (paramExecuteTimer) clearTimeout(paramExecuteTimer);
18568
+ paramExecuteTimer = setTimeout(() => {
18569
+ paramExecuteTimer = null;
18570
+ get().execute();
18571
+ }, PARAM_DEBOUNCE_MS);
18572
+ },
18497
18573
  resetParamOverrides: () => {
18498
18574
  const { activeFile, files, paramOverridesByFile } = get();
18499
18575
  const previewKey = activeFile ? resolvePreviewFile(activeFile, files) : null;
@@ -18503,6 +18579,11 @@ const useForgeStore = create((set, get) => ({
18503
18579
  setParamOverrides({});
18504
18580
  get().execute();
18505
18581
  },
18582
+ focusParam: (name) => set({ focusedParamName: name }),
18583
+ openSpatialParamSheet: (name) => set({ focusedParamName: name, spatialParamSheetName: name, expandedSpatialParamSheetName: null }),
18584
+ expandSpatialParamSheet: (name) => set({ focusedParamName: name, spatialParamSheetName: name, expandedSpatialParamSheetName: name }),
18585
+ closeExpandedSpatialParamSheet: () => set({ expandedSpatialParamSheetName: null }),
18586
+ closeSpatialParamSheet: () => set({ spatialParamSheetName: null, expandedSpatialParamSheetName: null }),
18506
18587
  captureParamSnapshot: () => {
18507
18588
  var _a3;
18508
18589
  const { activeFile, files, paramOverrides, paramSnapshotsByFile } = get();
@@ -18522,11 +18603,30 @@ const useForgeStore = create((set, get) => ({
18522
18603
  },
18523
18604
  applyParamSnapshot: (id) => {
18524
18605
  var _a3;
18525
- const { activeFile, files, paramOverridesByFile, paramSnapshotsByFile, params, stringParams, listParams } = get();
18606
+ const {
18607
+ activeFile,
18608
+ files,
18609
+ paramOverridesByFile,
18610
+ paramSnapshotsByFile,
18611
+ params,
18612
+ stringParams,
18613
+ listParams,
18614
+ path2dParams,
18615
+ spline2dParams,
18616
+ placement2dParams
18617
+ } = get();
18526
18618
  const previewKey = activeFile ? resolvePreviewFile(activeFile, files) : null;
18527
18619
  const snapshot = previewKey ? (_a3 = paramSnapshotsByFile[previewKey]) == null ? void 0 : _a3.find((candidate) => candidate.id === id) : null;
18528
18620
  if (!previewKey || !snapshot) return;
18529
- const { overrides, ignoredCount } = filterOverridesForCurrentParams(snapshot.overrides, params, stringParams, listParams);
18621
+ const { overrides, ignoredCount } = filterOverridesForCurrentParams(
18622
+ snapshot.overrides,
18623
+ params,
18624
+ stringParams,
18625
+ listParams,
18626
+ path2dParams,
18627
+ spline2dParams,
18628
+ placement2dParams
18629
+ );
18530
18630
  const nextByFile = { ...paramOverridesByFile };
18531
18631
  if (Object.keys(overrides).length > 0) nextByFile[previewKey] = overrides;
18532
18632
  else delete nextByFile[previewKey];
@@ -19351,6 +19451,12 @@ const useForgeStore = create((set, get) => ({
19351
19451
  writeViewPreferences({ dimensionsVisible: nextDimensionsVisible });
19352
19452
  return { dimensionsVisible: nextDimensionsVisible };
19353
19453
  }),
19454
+ paramAnchorsVisible: initialViewPreferences.paramAnchorsVisible ?? true,
19455
+ toggleParamAnchors: () => set((s) => {
19456
+ const nextParamAnchorsVisible = !s.paramAnchorsVisible;
19457
+ writeViewPreferences({ paramAnchorsVisible: nextParamAnchorsVisible });
19458
+ return { paramAnchorsVisible: nextParamAnchorsVisible };
19459
+ }),
19354
19460
  attachmentsVisible: initialViewPreferences.attachmentsVisible ?? "none",
19355
19461
  setAttachmentsVisible: (mode) => {
19356
19462
  writeViewPreferences({ attachmentsVisible: mode });
@@ -19528,11 +19634,11 @@ const useForgeStore = create((set, get) => ({
19528
19634
  fileSystem.save(normalized, text).catch((e2) => console.error("Save failed:", e2));
19529
19635
  setTimeout(() => get().execute(), 0);
19530
19636
  },
19531
- importTextFiles: async (entries, options = {}) => {
19637
+ importTextFiles: async (entries2, options = {}) => {
19532
19638
  const { files, folders, activeFile } = get();
19533
19639
  const occupied = /* @__PURE__ */ new Set([...Object.keys(files), ...folders]);
19534
19640
  const imported = [];
19535
- for (const entry of entries) {
19641
+ for (const entry of entries2) {
19536
19642
  const targetPath = resolveImportedProjectPath(entry.path, options.targetFolder);
19537
19643
  if (!targetPath) continue;
19538
19644
  const path = uniquifyProjectPath(targetPath, occupied);
@@ -19771,8 +19877,9 @@ onServerAvailabilityChange((available) => {
19771
19877
  useForgeStore.setState({ serverAvailable: available });
19772
19878
  });
19773
19879
  const savedComputeTarget = initialViewPreferences.computeTarget;
19880
+ const savedServerBackend = initialViewPreferences.activeBackend === "sdf" ? "sdf" : "occt";
19774
19881
  if (savedComputeTarget === "server" && canUseServerCompute()) {
19775
- useForgeStore.setState({ activeBackend: "occt", computeTarget: "server" });
19882
+ useForgeStore.setState({ activeBackend: savedServerBackend, computeTarget: "server" });
19776
19883
  startServerPolling();
19777
19884
  }
19778
19885
  {
@@ -22972,6 +23079,51 @@ function DroneCrosshairPicker({
22972
23079
  }, [active, focusHitOrClear, gl.domElement, hideLabel, showHitInfo]);
22973
23080
  return null;
22974
23081
  }
23082
+ const ESCAPE_PRIORITY = {
23083
+ viewport: 20,
23084
+ popover: 50,
23085
+ modal: 100
23086
+ };
23087
+ let nextId = 1;
23088
+ let entries = [];
23089
+ let listening = false;
23090
+ function dispatchEscape(event) {
23091
+ if (event.key !== "Escape" || event.isComposing) return;
23092
+ const ordered = [...entries].sort((a2, b2) => b2.priority - a2.priority || b2.id - a2.id);
23093
+ for (const entry of ordered) {
23094
+ if (!entry.actionRef.current(event)) continue;
23095
+ event.preventDefault();
23096
+ event.stopPropagation();
23097
+ event.stopImmediatePropagation();
23098
+ return;
23099
+ }
23100
+ }
23101
+ function ensureEscapeListener() {
23102
+ if (listening || typeof window === "undefined") return;
23103
+ window.addEventListener("keydown", dispatchEscape, { capture: true });
23104
+ listening = true;
23105
+ }
23106
+ function removeEscapeListenerIfIdle() {
23107
+ if (!listening || entries.length > 0 || typeof window === "undefined") return;
23108
+ window.removeEventListener("keydown", dispatchEscape, { capture: true });
23109
+ listening = false;
23110
+ }
23111
+ function useEscapeAction(action, { active, label, priority = ESCAPE_PRIORITY.viewport }) {
23112
+ const actionRef = reactExports.useRef(action);
23113
+ reactExports.useEffect(() => {
23114
+ actionRef.current = action;
23115
+ }, [action]);
23116
+ reactExports.useEffect(() => {
23117
+ if (!active) return;
23118
+ ensureEscapeListener();
23119
+ const entry = { id: nextId++, label, priority, actionRef };
23120
+ entries = [...entries, entry];
23121
+ return () => {
23122
+ entries = entries.filter((candidate) => candidate !== entry);
23123
+ removeEscapeListenerIfIdle();
23124
+ };
23125
+ }, [active, label, priority]);
23126
+ }
22975
23127
  const DEFAULT_DRONE_CAMERA_STATUS = {
22976
23128
  speed: 0,
22977
23129
  pointerLocked: false,
@@ -23070,6 +23222,13 @@ function DroneCameraController({
23070
23222
  const pointerLockedRef = reactExports.useRef(false);
23071
23223
  const lastStatusRef = reactExports.useRef(DEFAULT_DRONE_CAMERA_STATUS);
23072
23224
  const handledKeyboardEventsRef = reactExports.useRef(/* @__PURE__ */ new WeakSet());
23225
+ const handleEscape = reactExports.useCallback(() => {
23226
+ if (!active) return false;
23227
+ if (onEscape == null ? void 0 : onEscape()) return true;
23228
+ onExit();
23229
+ return true;
23230
+ }, [active, onEscape, onExit]);
23231
+ useEscapeAction(handleEscape, { active, label: "Fly camera", priority: ESCAPE_PRIORITY.viewport + 5 });
23073
23232
  const publishStatus = reactExports.useCallback(
23074
23233
  (patch) => {
23075
23234
  const next = { ...lastStatusRef.current, ...patch };
@@ -23185,11 +23344,6 @@ function DroneCameraController({
23185
23344
  if (handledKeyboardEventsRef.current.has(event)) return;
23186
23345
  handledKeyboardEventsRef.current.add(event);
23187
23346
  if (event.key === "Escape") {
23188
- consumeFlightKey(event);
23189
- if (onEscape == null ? void 0 : onEscape()) {
23190
- return;
23191
- }
23192
- onExit();
23193
23347
  return;
23194
23348
  }
23195
23349
  const code = flightCodeFromEvent(event);
@@ -23293,19 +23447,7 @@ function DroneCameraController({
23293
23447
  publishStatus(DEFAULT_DRONE_CAMERA_STATUS);
23294
23448
  recordDroneDebug({ active: false, keys: [], lastSpeed: 0, pointerLocked: false });
23295
23449
  };
23296
- }, [
23297
- active,
23298
- applyLook,
23299
- camera,
23300
- controlsRef,
23301
- gl.domElement,
23302
- onEscape,
23303
- onExit,
23304
- onInteractionChange,
23305
- publishStatus,
23306
- rotateByMouse,
23307
- stepFlight
23308
- ]);
23450
+ }, [active, applyLook, camera, controlsRef, gl.domElement, onInteractionChange, publishStatus, rotateByMouse, stepFlight]);
23309
23451
  return null;
23310
23452
  }
23311
23453
  const panelStyle$1 = {
@@ -23742,7 +23884,7 @@ function OrbitTargetPulseLayer() {
23742
23884
  class ConstructionHistoryWorkerClient {
23743
23885
  constructor(workerFactory = () => new Worker(new URL(
23744
23886
  /* @vite-ignore */
23745
- "/assets/constructionHistoryWorker-uEe_Q7Kg.js",
23887
+ "/assets/constructionHistoryWorker-BuZgc606.js",
23746
23888
  import.meta.url
23747
23889
  ), { type: "module" })) {
23748
23890
  __publicField(this, "worker", null);
@@ -26254,7 +26396,7 @@ function generateReportInWorker(options) {
26254
26396
  return new Promise((resolve2, reject) => {
26255
26397
  const worker = new Worker(new URL(
26256
26398
  /* @vite-ignore */
26257
- "/assets/reportWorker-DsaICZsn.js",
26399
+ "/assets/reportWorker-kg065BVL.js",
26258
26400
  import.meta.url
26259
26401
  ), { type: "module" });
26260
26402
  const cleanup = () => {
@@ -27607,6 +27749,14 @@ function AnimationBar() {
27607
27749
  const [historySpeedInput, setHistorySpeedInput] = reactExports.useState(() => formatHistorySpeedInput(historySpeed));
27608
27750
  const [editingHistorySpeed, setEditingHistorySpeed] = reactExports.useState(false);
27609
27751
  const [copiedTraceId, setCopiedTraceId] = reactExports.useState(null);
27752
+ useEscapeAction(
27753
+ () => {
27754
+ if (!animationMode) return false;
27755
+ if (!recording) exit();
27756
+ return true;
27757
+ },
27758
+ { active: Boolean(animationMode), label: "Animation mode", priority: ESCAPE_PRIORITY.viewport + 4 }
27759
+ );
27610
27760
  const elapsed = useElapsedTime(recording);
27611
27761
  reactExports.useEffect(() => {
27612
27762
  if (!editingHistorySpeed) {
@@ -27616,12 +27766,6 @@ function AnimationBar() {
27616
27766
  const handleKeyDown = reactExports.useCallback(
27617
27767
  (e2) => {
27618
27768
  if (!animationMode) return;
27619
- if (e2.key === "Escape") {
27620
- e2.preventDefault();
27621
- if (recording) return;
27622
- exit();
27623
- return;
27624
- }
27625
27769
  if (animationMode === "construction") {
27626
27770
  switch (e2.key) {
27627
27771
  case " ":
@@ -27639,7 +27783,7 @@ function AnimationBar() {
27639
27783
  }
27640
27784
  }
27641
27785
  },
27642
- [animationMode, recording, historyCurrentStep, toggleHistoryPlayback, setHistoryStep, exit]
27786
+ [animationMode, recording, historyCurrentStep, toggleHistoryPlayback, setHistoryStep]
27643
27787
  );
27644
27788
  reactExports.useEffect(() => {
27645
27789
  window.addEventListener("keydown", handleKeyDown);
@@ -28539,20 +28683,13 @@ function TrajectoryTimeline() {
28539
28683
  const trajectoryDuration = useForgeStore((s) => s.trajectoryDuration);
28540
28684
  const exitTrajectoryMode = useForgeStore((s) => s.exitTrajectoryMode);
28541
28685
  const setTrajectoryPresetParams = useForgeStore((s) => s.setTrajectoryPresetParams);
28542
- const handleKeyDown = reactExports.useCallback(
28543
- (e2) => {
28544
- if (!trajectoryMode) return;
28545
- if (e2.key === "Escape") {
28546
- e2.preventDefault();
28547
- exitTrajectoryMode();
28548
- }
28686
+ useEscapeAction(
28687
+ () => {
28688
+ exitTrajectoryMode();
28689
+ return true;
28549
28690
  },
28550
- [trajectoryMode, exitTrajectoryMode]
28691
+ { active: Boolean(trajectoryMode), label: "Trajectory mode", priority: ESCAPE_PRIORITY.viewport + 4 }
28551
28692
  );
28552
- reactExports.useEffect(() => {
28553
- window.addEventListener("keydown", handleKeyDown);
28554
- return () => window.removeEventListener("keydown", handleKeyDown);
28555
- }, [handleKeyDown]);
28556
28693
  if (!trajectoryMode) return null;
28557
28694
  const isActive = trajectoryRecording || trajectoryPreviewing;
28558
28695
  const presetConfig = TRAJECTORY_PRESETS.find((p2) => p2.id === trajectoryPreset);
@@ -29713,7 +29850,7 @@ const PHASE_CONFIG = {
29713
29850
  const PHASE_ORDER = ["kernel-init", "evaluating", "serializing"];
29714
29851
  function formatEvaluationBackendLabel(activeBackend, computeTarget) {
29715
29852
  const backend = activeBackend === "occt" ? "OCCT" : activeBackend === "manifold" ? "Manifold" : activeBackend === "truck" ? "Truck" : activeBackend === "sdf" ? "SDF" : "kernel";
29716
- return computeTarget === "server" ? "Server OCCT" : `Local ${backend}`;
29853
+ return computeTarget === "server" ? `Server ${backend}` : `Local ${backend}`;
29717
29854
  }
29718
29855
  function EvaluationIndicator({
29719
29856
  phase,
@@ -32997,7 +33134,7 @@ function rangeDraftValue(value) {
32997
33134
  function rangeKey(range) {
32998
33135
  return `${range.min}:${range.max}`;
32999
33136
  }
33000
- function clamp(value, min, max2) {
33137
+ function clamp$1(value, min, max2) {
33001
33138
  return Math.max(min, Math.min(max2, value));
33002
33139
  }
33003
33140
  function snapSliderValue(value) {
@@ -33022,20 +33159,20 @@ function DualRangeSlider({
33022
33159
  onCommit
33023
33160
  }) {
33024
33161
  const trackRef = reactExports.useRef(null);
33025
- const minPercent = clamp(range.min, 0, sliderMax) / sliderMax * 100;
33026
- const maxPercent = clamp(range.max, 0, sliderMax) / sliderMax * 100;
33162
+ const minPercent = clamp$1(range.min, 0, sliderMax) / sliderMax * 100;
33163
+ const maxPercent = clamp$1(range.max, 0, sliderMax) / sliderMax * 100;
33027
33164
  const valueFromPointer = (event) => {
33028
33165
  var _a3;
33029
33166
  const rect = (_a3 = trackRef.current) == null ? void 0 : _a3.getBoundingClientRect();
33030
33167
  if (!rect || rect.width <= 0) return null;
33031
- const ratio = clamp((event.clientX - rect.left) / rect.width, 0, 1);
33168
+ const ratio = clamp$1((event.clientX - rect.left) / rect.width, 0, 1);
33032
33169
  return snapSliderValue(ratio * sliderMax);
33033
33170
  };
33034
33171
  const updateThumb = (thumb, value) => {
33035
33172
  if (thumb === "min") {
33036
- onDraftChange({ min: clamp(value, 0, range.max - MIN_RANGE_SPAN), max: range.max });
33173
+ onDraftChange({ min: clamp$1(value, 0, range.max - MIN_RANGE_SPAN), max: range.max });
33037
33174
  } else {
33038
- onDraftChange({ min: range.min, max: Math.max(range.min + MIN_RANGE_SPAN, clamp(value, MIN_RANGE_SPAN, sliderMax)) });
33175
+ onDraftChange({ min: range.min, max: Math.max(range.min + MIN_RANGE_SPAN, clamp$1(value, MIN_RANGE_SPAN, sliderMax)) });
33039
33176
  }
33040
33177
  };
33041
33178
  const handlePointerMove = (thumb, event) => {
@@ -33275,7 +33412,7 @@ function InspectionLegend({
33275
33412
  criticalValue
33276
33413
  });
33277
33414
  const swatches = liveSwatches && liveSwatches.length > 0 ? liveSwatches : definition == null ? void 0 : definition.swatches;
33278
- const sliderGradient = colorScaleHexStops((colorScale == null ? void 0 : colorScale.colormap) ?? "viridis", 8, (colorScale == null ? void 0 : colorScale.reversed) === true);
33415
+ const sliderGradient = colorScaleHexStops((colorScale == null ? void 0 : colorScale.colormap) ?? DEFAULT_COLORMAP, 8, (colorScale == null ? void 0 : colorScale.reversed) === true);
33279
33416
  reactExports.useEffect(() => {
33280
33417
  var _a3;
33281
33418
  const parent = (_a3 = panelRef.current) == null ? void 0 : _a3.parentElement;
@@ -37893,10 +38030,10 @@ function slotMaxWidth(slot, occupied, inset) {
37893
38030
  if (occupied.has(opposite)) return `min(390px, calc(50% - ${inset + 4}px))`;
37894
38031
  return `min(390px, calc(100% - ${inset * 2}px))`;
37895
38032
  }
37896
- function ViewportOverlayHost({ entries, inset = 12, gap = 8 }) {
38033
+ function ViewportOverlayHost({ entries: entries2, inset = 12, gap = 8 }) {
37897
38034
  const grouped = /* @__PURE__ */ new Map();
37898
38035
  const occupied = /* @__PURE__ */ new Set();
37899
- for (const entry of entries) {
38036
+ for (const entry of entries2) {
37900
38037
  if (entry.content === null || entry.content === void 0 || typeof entry.content === "boolean") continue;
37901
38038
  occupied.add(entry.slot);
37902
38039
  const slotEntries = grouped.get(entry.slot) ?? [];
@@ -39373,6 +39510,7 @@ function useViewportState() {
39373
39510
  const lengthUnit = useForgeStore((s) => s.lengthUnit);
39374
39511
  const constructionGhost = useForgeStore((s) => s.constructionGhost);
39375
39512
  const dimensionsVisible = useForgeStore((s) => s.dimensionsVisible);
39513
+ const paramAnchorsVisible = useForgeStore((s) => s.paramAnchorsVisible);
39376
39514
  const attachmentsVisible = useForgeStore((s) => s.attachmentsVisible);
39377
39515
  const _surfacesVisible = useForgeStore((s) => s.surfacesVisible);
39378
39516
  const cutPlaneEnabled = useForgeStore((s) => s.cutPlaneEnabled);
@@ -39793,6 +39931,7 @@ function useViewportState() {
39793
39931
  renderLabels,
39794
39932
  debugHighlights3D,
39795
39933
  dimensionsVisible,
39934
+ paramAnchorsVisible,
39796
39935
  attachmentsVisible,
39797
39936
  attachmentPoints,
39798
39937
  cutPlaneEnabled,
@@ -40045,58 +40184,56 @@ function useViewportHandlers({
40045
40184
  hideHoverTooltip();
40046
40185
  setHoveredObjectId(null);
40047
40186
  }, [hideHoverTooltip, objectPickSyncEnabled, setHoveredObjectId]);
40048
- reactExports.useEffect(() => {
40049
- const handleEscape = (event) => {
40050
- if (event.key !== "Escape") return;
40051
- if (objectContextMenu) {
40052
- closeObjectContextMenu();
40053
- return;
40054
- }
40055
- const store = useForgeStore.getState();
40056
- if (store.selectedVertex) {
40057
- store.setSelectedVertex(null);
40058
- return;
40059
- }
40060
- if (store.selectedEdge) {
40061
- store.setSelectedEdge(null);
40062
- return;
40063
- }
40064
- if (store.selectedFace) {
40065
- store.setSelectedFace(null);
40066
- setFaceInfoPanel(null);
40067
- return;
40068
- }
40069
- if (faceInfoPanel) {
40070
- setFaceInfoPanel(null);
40071
- return;
40072
- }
40073
- if (sketchEntityInfo) {
40074
- setSketchEntityInfo(null);
40075
- return;
40076
- }
40077
- if (store.measureMode) {
40078
- if (store.measureSelections.length > 0) {
40079
- store.clearMeasureSelections();
40080
- } else {
40081
- store.toggleMeasure();
40082
- }
40083
- return;
40084
- }
40085
- if (store.constructionGhost !== null) {
40086
- store.setConstructionGhost(null);
40087
- return;
40088
- }
40089
- if (store.focusedObjectIds.length > 0) {
40090
- clearFocusedObject();
40091
- return;
40092
- }
40093
- if (store.selectedObjectId) {
40094
- store.selectObject(null);
40187
+ const handleViewportEscape = reactExports.useCallback(() => {
40188
+ if (objectContextMenu) {
40189
+ closeObjectContextMenu();
40190
+ return true;
40191
+ }
40192
+ const store = useForgeStore.getState();
40193
+ if (store.selectedVertex) {
40194
+ store.setSelectedVertex(null);
40195
+ return true;
40196
+ }
40197
+ if (store.selectedEdge) {
40198
+ store.setSelectedEdge(null);
40199
+ return true;
40200
+ }
40201
+ if (store.selectedFace) {
40202
+ store.setSelectedFace(null);
40203
+ setFaceInfoPanel(null);
40204
+ return true;
40205
+ }
40206
+ if (faceInfoPanel) {
40207
+ setFaceInfoPanel(null);
40208
+ return true;
40209
+ }
40210
+ if (sketchEntityInfo) {
40211
+ setSketchEntityInfo(null);
40212
+ return true;
40213
+ }
40214
+ if (store.measureMode) {
40215
+ if (store.measureSelections.length > 0) {
40216
+ store.clearMeasureSelections();
40217
+ } else {
40218
+ store.toggleMeasure();
40095
40219
  }
40096
- };
40097
- window.addEventListener("keydown", handleEscape);
40098
- return () => window.removeEventListener("keydown", handleEscape);
40220
+ return true;
40221
+ }
40222
+ if (store.constructionGhost !== null) {
40223
+ store.setConstructionGhost(null);
40224
+ return true;
40225
+ }
40226
+ if (store.focusedObjectIds.length > 0) {
40227
+ clearFocusedObject();
40228
+ return true;
40229
+ }
40230
+ if (store.selectedObjectId) {
40231
+ store.selectObject(null);
40232
+ return true;
40233
+ }
40234
+ return false;
40099
40235
  }, [clearFocusedObject, closeObjectContextMenu, faceInfoPanel, objectContextMenu, sketchEntityInfo]);
40236
+ useEscapeAction(handleViewportEscape, { active: true, label: "Viewport selection", priority: ESCAPE_PRIORITY.viewport });
40100
40237
  reactExports.useEffect(() => {
40101
40238
  const handleViewShortcut = (event) => {
40102
40239
  if (event.isComposing || event.repeat) return;
@@ -40941,7 +41078,7 @@ const HoverTooltipLayer = reactExports.forwardRef(function HoverTooltipLayer2({
40941
41078
  }
40942
41079
  );
40943
41080
  });
40944
- const buttonStyle = {
41081
+ const buttonStyle$3 = {
40945
41082
  border: "1px solid var(--fc-border)",
40946
41083
  background: "var(--fc-btn)",
40947
41084
  color: "var(--fc-text)",
@@ -40952,7 +41089,7 @@ const buttonStyle = {
40952
41089
  lineHeight: 1
40953
41090
  };
40954
41091
  const iconButtonStyle = {
40955
- ...buttonStyle,
41092
+ ...buttonStyle$3,
40956
41093
  width: 30,
40957
41094
  height: 30,
40958
41095
  padding: 0,
@@ -41104,7 +41241,7 @@ function ModelJourneyBar({
41104
41241
  padding: 4
41105
41242
  },
41106
41243
  children: [
41107
- /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", style: buttonStyle, onClick: () => activateStep(firstId, getStepStartIndex(firstJourney)), children: [
41244
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("button", { type: "button", style: buttonStyle$3, onClick: () => activateStep(firstId, getStepStartIndex(firstJourney)), children: [
41108
41245
  "Explore: ",
41109
41246
  label
41110
41247
  ] }),
@@ -41198,7 +41335,7 @@ function ModelJourneyBar({
41198
41335
  "button",
41199
41336
  {
41200
41337
  type: "button",
41201
- style: buttonStyle,
41338
+ style: buttonStyle$3,
41202
41339
  onClick: () => {
41203
41340
  setActive({ ...active, interrupted: false });
41204
41341
  applyStep(activeStep);
@@ -41206,7 +41343,7 @@ function ModelJourneyBar({
41206
41343
  children: "Resume"
41207
41344
  }
41208
41345
  ),
41209
- /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", style: buttonStyle, disabled: atEnd, onClick: () => activateStep(active.journeyId, active.stepIndex + 1), children: "Next" })
41346
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", style: buttonStyle$3, disabled: atEnd, onClick: () => activateStep(active.journeyId, active.stepIndex + 1), children: "Next" })
41210
41347
  ] }),
41211
41348
  caption && /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { marginTop: 6, paddingLeft: 76, fontSize: 12, color: "var(--fc-textDim)" }, children: caption })
41212
41349
  ]
@@ -41337,6 +41474,1819 @@ function RigInspectionOverlay({ state: state2, config }) {
41337
41474
  state2.joints.map((joint) => /* @__PURE__ */ jsxRuntimeExports.jsx(HoveredJointOverlay, { state: joint, config: joint.hidden ? hiddenConfig : config }, joint.joint.name))
41338
41475
  ] });
41339
41476
  }
41477
+ function roundToStep(value, step) {
41478
+ return Number((Math.round(value / step) * step).toFixed(8));
41479
+ }
41480
+ function clamp(value, min, max2) {
41481
+ return Math.max(min, Math.min(max2, value));
41482
+ }
41483
+ function currentPath2DPoints(pathDef, overrides) {
41484
+ const countOverride = overrides[`${pathDef.name}.__count__`];
41485
+ const count = typeof countOverride === "number" ? clamp(Math.round(countOverride), pathDef.minPoints, pathDef.maxPoints) : pathDef.points.length;
41486
+ const points = [];
41487
+ for (let i = 0; i < count; i += 1) {
41488
+ const base = pathDef.points[i] ?? pathDef.defaultPoints[i] ?? { x: 0, y: 0 };
41489
+ const rawX = overrides[`${pathDef.name}[${i}].x`];
41490
+ const rawY = overrides[`${pathDef.name}[${i}].y`];
41491
+ points.push({
41492
+ x: typeof rawX === "number" ? rawX : base.x,
41493
+ y: typeof rawY === "number" ? rawY : base.y
41494
+ });
41495
+ }
41496
+ return points;
41497
+ }
41498
+ function frameForPath2D(width, height, pathDef) {
41499
+ const spanX = Math.max(1, pathDef.x.max - pathDef.x.min);
41500
+ const spanY = Math.max(1, pathDef.y.max - pathDef.y.min);
41501
+ const availableWidth = Math.max(1, width - 28);
41502
+ const availableHeight = Math.max(1, height - 28);
41503
+ const scale = Math.min(availableWidth / spanX, availableHeight / spanY);
41504
+ return {
41505
+ originX: (width - spanX * scale) / 2 - pathDef.x.min * scale,
41506
+ originY: (height + spanY * scale) / 2 + pathDef.y.min * scale,
41507
+ scale
41508
+ };
41509
+ }
41510
+ function path2DToScreen(point, frame2) {
41511
+ return { x: frame2.originX + point.x * frame2.scale, y: frame2.originY - point.y * frame2.scale };
41512
+ }
41513
+ function screenToPath2D(x, y, frame2) {
41514
+ return { x: (x - frame2.originX) / frame2.scale, y: (frame2.originY - y) / frame2.scale };
41515
+ }
41516
+ function panPath2DFrame(frame2, dx, dy) {
41517
+ return { ...frame2, originX: frame2.originX + dx, originY: frame2.originY + dy };
41518
+ }
41519
+ function zoomPath2DFrame(frame2, centerX, centerY, factor) {
41520
+ const nextScale = clamp(frame2.scale * factor, 0.05, 60);
41521
+ const model = screenToPath2D(centerX, centerY, frame2);
41522
+ return {
41523
+ originX: centerX - model.x * nextScale,
41524
+ originY: centerY + model.y * nextScale,
41525
+ scale: nextScale
41526
+ };
41527
+ }
41528
+ function drawPath2DGrid(ctx, width, height, frame2) {
41529
+ ctx.strokeStyle = "rgba(255,255,255,0.08)";
41530
+ ctx.lineWidth = 1;
41531
+ const modelGridStep = frame2.scale < 0.35 ? 100 : frame2.scale < 1 ? 50 : frame2.scale < 2.5 ? 20 : 10;
41532
+ const gridStep = modelGridStep * frame2.scale;
41533
+ for (let x = frame2.originX % gridStep; x < width; x += gridStep) {
41534
+ ctx.beginPath();
41535
+ ctx.moveTo(x, 0);
41536
+ ctx.lineTo(x, height);
41537
+ ctx.stroke();
41538
+ }
41539
+ for (let y = frame2.originY % gridStep; y < height; y += gridStep) {
41540
+ ctx.beginPath();
41541
+ ctx.moveTo(0, y);
41542
+ ctx.lineTo(width, y);
41543
+ ctx.stroke();
41544
+ }
41545
+ }
41546
+ function path2DPatch(pathName, points) {
41547
+ const patch = { [`${pathName}.__count__`]: points.length };
41548
+ points.forEach((point, index) => {
41549
+ patch[`${pathName}[${index}].x`] = point.x;
41550
+ patch[`${pathName}[${index}].y`] = point.y;
41551
+ });
41552
+ return patch;
41553
+ }
41554
+ function trailingPath2DKeys(pathName, oldCount, newCount) {
41555
+ const keys = [];
41556
+ for (let i = newCount; i < oldCount; i += 1) {
41557
+ keys.push(`${pathName}[${i}].x`, `${pathName}[${i}].y`);
41558
+ }
41559
+ return keys;
41560
+ }
41561
+ function hasPointSelectionModifier(event) {
41562
+ return event.shiftKey || event.metaKey || event.ctrlKey;
41563
+ }
41564
+ function normalizePointSelection(selectedIndices, pointCount, activeIndex) {
41565
+ if (pointCount <= 0) return [];
41566
+ const boundedActive = Math.max(0, Math.min(activeIndex, pointCount - 1));
41567
+ const normalized = [];
41568
+ const seen = /* @__PURE__ */ new Set();
41569
+ for (const index of selectedIndices) {
41570
+ if (!Number.isInteger(index) || index < 0 || index >= pointCount || seen.has(index)) continue;
41571
+ seen.add(index);
41572
+ normalized.push(index);
41573
+ }
41574
+ if (!seen.has(boundedActive)) normalized.push(boundedActive);
41575
+ return normalized.sort((a2, b2) => a2 - b2);
41576
+ }
41577
+ function pointSelectionForPointerDown(currentSelectedIndices, hitIndex, activeIndex, additive, pointCount) {
41578
+ const current = normalizePointSelection(currentSelectedIndices, pointCount, activeIndex);
41579
+ const alreadySelected = current.includes(hitIndex);
41580
+ if (!additive) {
41581
+ return {
41582
+ activeIndex: hitIndex,
41583
+ selectedIndices: alreadySelected ? current : [hitIndex],
41584
+ shouldDrag: true
41585
+ };
41586
+ }
41587
+ if (alreadySelected && current.length > 1) {
41588
+ const selectedIndices = current.filter((index) => index !== hitIndex);
41589
+ return {
41590
+ activeIndex: selectedIndices.includes(activeIndex) ? activeIndex : selectedIndices[0],
41591
+ selectedIndices,
41592
+ shouldDrag: false
41593
+ };
41594
+ }
41595
+ if (alreadySelected) {
41596
+ return { activeIndex: hitIndex, selectedIndices: current, shouldDrag: true };
41597
+ }
41598
+ return {
41599
+ activeIndex: hitIndex,
41600
+ selectedIndices: normalizePointSelection([...current, hitIndex], pointCount, hitIndex),
41601
+ shouldDrag: true
41602
+ };
41603
+ }
41604
+ const buttonStyle$2 = {
41605
+ background: "var(--fc-small-button-bg, none)",
41606
+ border: "1px solid var(--fc-small-button-border, var(--fc-border))",
41607
+ borderRadius: 3,
41608
+ color: "var(--fc-textDim)",
41609
+ fontSize: 10,
41610
+ padding: "1px 5px",
41611
+ cursor: "pointer",
41612
+ lineHeight: "14px",
41613
+ userSelect: "none"
41614
+ };
41615
+ function Path2DParamEditor({
41616
+ pathDef,
41617
+ allowFullScreen = true,
41618
+ large = false
41619
+ }) {
41620
+ const canvasRef = reactExports.useRef(null);
41621
+ const dragState = reactExports.useRef(null);
41622
+ const draftPointsRef = reactExports.useRef(null);
41623
+ const panStart = reactExports.useRef(null);
41624
+ const paramOverrides = useForgeStore((state2) => state2.paramOverrides);
41625
+ const setParams = useForgeStore((state2) => state2.setParams);
41626
+ const [expanded, setExpanded] = reactExports.useState(true);
41627
+ const [fullScreen, setFullScreen] = reactExports.useState(false);
41628
+ const [viewFrame, setViewFrame] = reactExports.useState(null);
41629
+ const [draftPoints, setDraftPoints] = reactExports.useState(null);
41630
+ const [selected, setSelected] = reactExports.useState(0);
41631
+ const [selectedIndices, setSelectedIndices] = reactExports.useState([0]);
41632
+ useEscapeAction(
41633
+ () => {
41634
+ setFullScreen(false);
41635
+ return true;
41636
+ },
41637
+ { active: fullScreen, label: `${pathDef.name} path editor`, priority: ESCAPE_PRIORITY.modal }
41638
+ );
41639
+ const committedPoints = reactExports.useMemo(() => currentPath2DPoints(pathDef, paramOverrides), [paramOverrides, pathDef]);
41640
+ const points = draftPoints ?? committedPoints;
41641
+ const activeIndex = points.length > 0 ? Math.min(selected, points.length - 1) : 0;
41642
+ const normalizedSelectedIndices = reactExports.useMemo(
41643
+ () => normalizePointSelection(selectedIndices, points.length, activeIndex),
41644
+ [activeIndex, points.length, selectedIndices]
41645
+ );
41646
+ const selectedIndexSet = reactExports.useMemo(() => new Set(normalizedSelectedIndices), [normalizedSelectedIndices]);
41647
+ const selectedPoint = points[activeIndex] ?? points[0];
41648
+ const unitLabel = pathDef.unit ? ` ${pathDef.unit}` : "";
41649
+ const canAdd = points.length < pathDef.maxPoints;
41650
+ const canRemove = points.length - normalizedSelectedIndices.length >= pathDef.minPoints;
41651
+ reactExports.useEffect(() => {
41652
+ if (selected >= points.length) setSelected(Math.max(0, points.length - 1));
41653
+ }, [points.length, selected]);
41654
+ reactExports.useEffect(() => {
41655
+ const canvas = canvasRef.current;
41656
+ if (!canvas) return;
41657
+ const ctx = canvas.getContext("2d");
41658
+ if (!ctx) return;
41659
+ const draw = () => {
41660
+ const rect = canvas.getBoundingClientRect();
41661
+ const dpr = window.devicePixelRatio || 1;
41662
+ const nextWidth = Math.max(1, Math.round(rect.width * dpr));
41663
+ const nextHeight = Math.max(1, Math.round(rect.height * dpr));
41664
+ if (canvas.width !== nextWidth) canvas.width = nextWidth;
41665
+ if (canvas.height !== nextHeight) canvas.height = nextHeight;
41666
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
41667
+ ctx.clearRect(0, 0, rect.width, rect.height);
41668
+ const frame2 = viewFrame ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, pathDef);
41669
+ drawPath2DGrid(ctx, rect.width, rect.height, frame2);
41670
+ if (points.length > 0) {
41671
+ const projected = points.map((point) => path2DToScreen(point, frame2));
41672
+ ctx.beginPath();
41673
+ ctx.moveTo(projected[0].x, projected[0].y);
41674
+ for (let i = 1; i < projected.length; i += 1) ctx.lineTo(projected[i].x, projected[i].y);
41675
+ if (pathDef.closed) ctx.closePath();
41676
+ ctx.fillStyle = pathDef.closed ? "rgba(77, 147, 191, 0.22)" : "transparent";
41677
+ ctx.strokeStyle = pathDef.closed ? "#4d93bf" : "#3c8f83";
41678
+ ctx.lineWidth = pathDef.closed ? 2 : 7;
41679
+ if (pathDef.closed) ctx.fill();
41680
+ ctx.stroke();
41681
+ projected.forEach((point, index) => {
41682
+ const isSelected = selectedIndexSet.has(index);
41683
+ const isActive = index === activeIndex;
41684
+ ctx.beginPath();
41685
+ ctx.arc(point.x, point.y, isActive ? 8 : isSelected ? 7 : 6, 0, Math.PI * 2);
41686
+ ctx.fillStyle = index === 0 ? "#d84f45" : "#e2b647";
41687
+ ctx.fill();
41688
+ ctx.lineWidth = isActive ? 3 : isSelected ? 2.25 : 1.5;
41689
+ ctx.strokeStyle = isSelected ? "#fff6d4" : "#171914";
41690
+ ctx.stroke();
41691
+ });
41692
+ }
41693
+ };
41694
+ draw();
41695
+ const observer = new ResizeObserver(draw);
41696
+ observer.observe(canvas);
41697
+ return () => observer.disconnect();
41698
+ }, [activeIndex, fullScreen, large, pathDef, points, selectedIndexSet, viewFrame]);
41699
+ reactExports.useEffect(() => {
41700
+ const canvas = canvasRef.current;
41701
+ if (!canvas) return;
41702
+ const handleWheel = (event) => {
41703
+ event.preventDefault();
41704
+ event.stopPropagation();
41705
+ const rect = canvas.getBoundingClientRect();
41706
+ const x = event.clientX - rect.left;
41707
+ const y = event.clientY - rect.top;
41708
+ setViewFrame(
41709
+ (current) => zoomPath2DFrame(current ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, pathDef), x, y, event.deltaY > 0 ? 0.88 : 1.14)
41710
+ );
41711
+ };
41712
+ canvas.addEventListener("wheel", handleWheel, { passive: false });
41713
+ return () => canvas.removeEventListener("wheel", handleWheel);
41714
+ }, [pathDef]);
41715
+ const pointerPoint = (event) => {
41716
+ const canvas = event.currentTarget;
41717
+ const rect = canvas.getBoundingClientRect();
41718
+ return { x: event.clientX - rect.left, y: event.clientY - rect.top };
41719
+ };
41720
+ const nearestHandle = (canvas, x, y) => {
41721
+ const frame2 = viewFrame ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, pathDef);
41722
+ let bestIndex = null;
41723
+ let bestDistance = Infinity;
41724
+ points.forEach((point, index) => {
41725
+ const screen2 = path2DToScreen(point, frame2);
41726
+ const distance = Math.hypot(screen2.x - x, screen2.y - y);
41727
+ if (distance < bestDistance) {
41728
+ bestDistance = distance;
41729
+ bestIndex = index;
41730
+ }
41731
+ });
41732
+ return bestDistance <= 18 ? bestIndex : null;
41733
+ };
41734
+ const moveDraggedPoints = (drag, pointer) => {
41735
+ const dx = pointer.x - drag.startPointer.x;
41736
+ const dy = pointer.y - drag.startPointer.y;
41737
+ const dragIndices = new Set(drag.indices);
41738
+ const nextPoints = drag.startPoints.map(
41739
+ (point, index) => dragIndices.has(index) ? {
41740
+ x: roundToStep(point.x + dx, pathDef.x.step),
41741
+ y: roundToStep(point.y + dy, pathDef.y.step)
41742
+ } : point
41743
+ );
41744
+ draftPointsRef.current = nextPoints;
41745
+ setDraftPoints(nextPoints);
41746
+ };
41747
+ const commitDraftDrag = () => {
41748
+ const nextPoints = draftPointsRef.current;
41749
+ draftPointsRef.current = null;
41750
+ setDraftPoints(null);
41751
+ if (nextPoints) setParams(path2DPatch(pathDef.name, nextPoints));
41752
+ };
41753
+ const cancelDraftDrag = () => {
41754
+ draftPointsRef.current = null;
41755
+ setDraftPoints(null);
41756
+ };
41757
+ const insertPointAfterSelection = () => {
41758
+ if (!canAdd) return;
41759
+ const insertAt = Math.min(activeIndex + 1, points.length);
41760
+ const a2 = points[activeIndex] ?? points[points.length - 1] ?? { x: 0, y: 0 };
41761
+ const b2 = points[insertAt] ?? (pathDef.closed ? points[0] : a2);
41762
+ const nextPoint = {
41763
+ x: roundToStep((a2.x + b2.x) / 2, pathDef.x.step),
41764
+ y: roundToStep((a2.y + b2.y) / 2, pathDef.y.step)
41765
+ };
41766
+ const nextPoints = [...points];
41767
+ nextPoints.splice(insertAt, 0, nextPoint);
41768
+ setSelected(insertAt);
41769
+ setSelectedIndices([insertAt]);
41770
+ setParams(path2DPatch(pathDef.name, nextPoints));
41771
+ };
41772
+ const removeSelectedPoint = () => {
41773
+ if (!canRemove) return;
41774
+ const removeIndices = new Set(normalizedSelectedIndices);
41775
+ const nextPoints = points.filter((_, index) => !removeIndices.has(index));
41776
+ const firstRemoved = normalizedSelectedIndices[0] ?? activeIndex;
41777
+ const nextSelected = Math.max(0, Math.min(firstRemoved, nextPoints.length - 1));
41778
+ setSelected(nextSelected);
41779
+ setSelectedIndices([nextSelected]);
41780
+ setParams(path2DPatch(pathDef.name, nextPoints), trailingPath2DKeys(pathDef.name, points.length, nextPoints.length));
41781
+ };
41782
+ const canvasFrame = (canvas) => viewFrame ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, pathDef);
41783
+ const canvasHeight = large ? "min(62vh, 640px)" : fullScreen ? "calc(100vh - 150px)" : 220;
41784
+ const expandedLayout = large || fullScreen;
41785
+ const editorPanel = /* @__PURE__ */ jsxRuntimeExports.jsxs(
41786
+ "div",
41787
+ {
41788
+ style: {
41789
+ border: "1px solid var(--fc-border)",
41790
+ borderRadius: 4,
41791
+ padding: 8,
41792
+ background: "var(--fc-bgSubtle, var(--fc-bgSurface, #1c2128))",
41793
+ userSelect: "none",
41794
+ WebkitUserSelect: "none",
41795
+ ...expandedLayout ? {
41796
+ display: "flex",
41797
+ flexDirection: "column"
41798
+ } : {},
41799
+ ...fullScreen ? {
41800
+ position: "fixed",
41801
+ inset: 16,
41802
+ zIndex: 2200,
41803
+ boxShadow: "0 18px 52px rgba(0, 0, 0, 0.45)",
41804
+ boxSizing: "border-box",
41805
+ color: "var(--fc-text)"
41806
+ } : {}
41807
+ },
41808
+ children: [
41809
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 4, marginBottom: 6 }, children: [
41810
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setViewFrame(null), title: "Fit path in editor", style: buttonStyle$2, children: "Fit" }),
41811
+ allowFullScreen && /* @__PURE__ */ jsxRuntimeExports.jsx(
41812
+ "button",
41813
+ {
41814
+ type: "button",
41815
+ onClick: () => setFullScreen(!fullScreen),
41816
+ title: fullScreen ? "Close expanded editor" : "Expand editor",
41817
+ style: buttonStyle$2,
41818
+ children: fullScreen ? "Close" : "Full"
41819
+ }
41820
+ )
41821
+ ] }),
41822
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
41823
+ "canvas",
41824
+ {
41825
+ ref: canvasRef,
41826
+ height: 220,
41827
+ "aria-label": `${pathDef.name} editor`,
41828
+ onPointerDown: (event) => {
41829
+ event.preventDefault();
41830
+ const pos = pointerPoint(event);
41831
+ const hit = nearestHandle(event.currentTarget, pos.x, pos.y);
41832
+ if (hit === null) panStart.current = { x: pos.x, y: pos.y, frame: canvasFrame(event.currentTarget) };
41833
+ else {
41834
+ const nextSelection = pointSelectionForPointerDown(
41835
+ normalizedSelectedIndices,
41836
+ hit,
41837
+ activeIndex,
41838
+ hasPointSelectionModifier(event),
41839
+ points.length
41840
+ );
41841
+ setSelected(nextSelection.activeIndex);
41842
+ setSelectedIndices(nextSelection.selectedIndices);
41843
+ if (nextSelection.shouldDrag) {
41844
+ const frame2 = canvasFrame(event.currentTarget);
41845
+ draftPointsRef.current = null;
41846
+ setDraftPoints(null);
41847
+ dragState.current = {
41848
+ indices: nextSelection.selectedIndices,
41849
+ startPointer: screenToPath2D(pos.x, pos.y, frame2),
41850
+ startPoints: points.map((point) => ({ ...point })),
41851
+ frame: frame2
41852
+ };
41853
+ }
41854
+ }
41855
+ event.currentTarget.setPointerCapture(event.pointerId);
41856
+ },
41857
+ onPointerMove: (event) => {
41858
+ const pos = pointerPoint(event);
41859
+ if (dragState.current) {
41860
+ moveDraggedPoints(dragState.current, screenToPath2D(pos.x, pos.y, dragState.current.frame));
41861
+ return;
41862
+ }
41863
+ if (panStart.current) {
41864
+ setViewFrame(panPath2DFrame(panStart.current.frame, pos.x - panStart.current.x, pos.y - panStart.current.y));
41865
+ }
41866
+ },
41867
+ onPointerUp: (event) => {
41868
+ if (dragState.current) commitDraftDrag();
41869
+ dragState.current = null;
41870
+ panStart.current = null;
41871
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
41872
+ },
41873
+ onPointerCancel: (event) => {
41874
+ if (dragState.current) cancelDraftDrag();
41875
+ dragState.current = null;
41876
+ panStart.current = null;
41877
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
41878
+ },
41879
+ style: { display: "block", width: "100%", height: canvasHeight, borderRadius: 3, background: "var(--fc-bg)", touchAction: "none" }
41880
+ }
41881
+ ),
41882
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, marginTop: 6 }, children: [
41883
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { color: "var(--fc-textDim)", fontSize: 10, fontVariantNumeric: "tabular-nums" }, children: [
41884
+ normalizedSelectedIndices.length > 1 ? `${normalizedSelectedIndices.length} selected · ` : "",
41885
+ "P",
41886
+ Math.min(activeIndex + 1, points.length),
41887
+ " x ",
41888
+ selectedPoint ? selectedPoint.x : 0,
41889
+ unitLabel,
41890
+ " · y ",
41891
+ selectedPoint ? selectedPoint.y : 0,
41892
+ unitLabel
41893
+ ] }),
41894
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: 4 }, children: [
41895
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: insertPointAfterSelection, disabled: !canAdd, title: "Add point after selected", style: buttonStyle$2, children: "+ Point" }),
41896
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: removeSelectedPoint, disabled: !canRemove, title: "Remove selected point(s)", style: buttonStyle$2, children: "Remove" })
41897
+ ] })
41898
+ ] })
41899
+ ]
41900
+ }
41901
+ );
41902
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginBottom: 8 }, children: [
41903
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
41904
+ "div",
41905
+ {
41906
+ onClick: () => setExpanded(!expanded),
41907
+ style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 12, cursor: "pointer", marginBottom: 4 },
41908
+ children: [
41909
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-text)", fontWeight: 600 }, children: [
41910
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 9, marginRight: 4 }, children: expanded ? "▼" : "▶" }),
41911
+ pathDef.name,
41912
+ " (",
41913
+ points.length,
41914
+ ")"
41915
+ ] }),
41916
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: "var(--fc-textDim)", fontSize: 10 }, children: pathDef.closed ? "outline" : "centerline" })
41917
+ ]
41918
+ }
41919
+ ),
41920
+ expanded && editorPanel
41921
+ ] });
41922
+ }
41923
+ function currentPlacement2DItems(layoutDef, overrides) {
41924
+ const zones = new Set(layoutDef.zones.map((zone) => zone.id));
41925
+ return layoutDef.items.map((item) => {
41926
+ const rawX = overrides[`${layoutDef.name}.${item.id}.x`];
41927
+ const rawY = overrides[`${layoutDef.name}.${item.id}.y`];
41928
+ const rawAngle = overrides[`${layoutDef.name}.${item.id}.angle`];
41929
+ const rawZone = overrides[`${layoutDef.name}.${item.id}.zone`];
41930
+ const zone = typeof rawZone === "string" && zones.has(rawZone) ? rawZone : item.zone;
41931
+ return {
41932
+ ...item,
41933
+ footprint: item.footprint.type === "rect" ? { ...item.footprint } : { ...item.footprint },
41934
+ x: typeof rawX === "number" ? rawX : item.x,
41935
+ y: typeof rawY === "number" ? rawY : item.y,
41936
+ angle: typeof rawAngle === "number" ? rawAngle : item.angle,
41937
+ zone
41938
+ };
41939
+ });
41940
+ }
41941
+ function placement2DItemPatch(layoutName, item) {
41942
+ const patch = {
41943
+ [`${layoutName}.${item.id}.x`]: item.x,
41944
+ [`${layoutName}.${item.id}.y`]: item.y,
41945
+ [`${layoutName}.${item.id}.angle`]: item.angle
41946
+ };
41947
+ if (item.zone) patch[`${layoutName}.${item.id}.zone`] = item.zone;
41948
+ return patch;
41949
+ }
41950
+ function placement2DFrameForItem(item, frame2, zones) {
41951
+ return (item.zone ? zones.find((zone) => zone.id === item.zone) : void 0) ?? frame2;
41952
+ }
41953
+ function placement2DZoneAtPoint(zones, point) {
41954
+ return zones.find((zone) => placement2DPointInsideFrame(point, zone));
41955
+ }
41956
+ function placement2DClampItem(item, frame2, zones) {
41957
+ const target = placement2DFrameForItem(item, frame2, zones);
41958
+ const center = placement2DClampCenterToFrame(item, target);
41959
+ return { ...item, x: center.x, y: center.y };
41960
+ }
41961
+ function placement2DHasBoundsViolation(layoutDef, item) {
41962
+ return !placement2DFootprintInsideFrame(item, placement2DFrameForItem(item, layoutDef.frame, layoutDef.zones));
41963
+ }
41964
+ function placement2DCollisionPairs(items) {
41965
+ const pairs = [];
41966
+ for (let i = 0; i < items.length; i += 1) {
41967
+ for (let j2 = i + 1; j2 < items.length; j2 += 1) {
41968
+ if ((items[i].zone ?? "") !== (items[j2].zone ?? "")) continue;
41969
+ if (placement2DItemsOverlap(items[i], items[j2])) pairs.push([items[i].id, items[j2].id]);
41970
+ }
41971
+ }
41972
+ return pairs;
41973
+ }
41974
+ function placement2DItemCollides(candidate, items) {
41975
+ return items.some(
41976
+ (item) => item.id !== candidate.id && (item.zone ?? "") === (candidate.zone ?? "") && placement2DItemsOverlap(candidate, item)
41977
+ );
41978
+ }
41979
+ function placement2DSnapItem(item, snap) {
41980
+ return { ...item, x: roundToStep(item.x, snap), y: roundToStep(item.y, snap), angle: roundToStep(item.angle, 1) };
41981
+ }
41982
+ function placement2DFrameForCanvas(width, height, layoutDef) {
41983
+ const bounds = placement2DFrameBounds(layoutDef.frame);
41984
+ const spanX = Math.max(1, bounds.maxX - bounds.minX);
41985
+ const spanY = Math.max(1, bounds.maxY - bounds.minY);
41986
+ const availableWidth = Math.max(1, width - 28);
41987
+ const availableHeight = Math.max(1, height - 28);
41988
+ const scale = Math.min(availableWidth / spanX, availableHeight / spanY);
41989
+ return {
41990
+ originX: (width - spanX * scale) / 2 - bounds.minX * scale,
41991
+ originY: (height + spanY * scale) / 2 + bounds.minY * scale,
41992
+ scale
41993
+ };
41994
+ }
41995
+ function placement2DPointHitsItem(point, item) {
41996
+ if (item.footprint.type === "circle") return Math.hypot(point.x - item.x, point.y - item.y) <= item.footprint.radius;
41997
+ const angle = -item.angle * Math.PI / 180;
41998
+ const dx = point.x - item.x;
41999
+ const dy = point.y - item.y;
42000
+ const localX = dx * Math.cos(angle) - dy * Math.sin(angle);
42001
+ const localY = dx * Math.sin(angle) + dy * Math.cos(angle);
42002
+ return Math.abs(localX) <= item.footprint.width / 2 && Math.abs(localY) <= item.footprint.height / 2;
42003
+ }
42004
+ const buttonStyle$1 = {
42005
+ background: "var(--fc-small-button-bg, none)",
42006
+ border: "1px solid var(--fc-small-button-border, var(--fc-border))",
42007
+ borderRadius: 3,
42008
+ color: "var(--fc-textDim)",
42009
+ fontSize: 10,
42010
+ padding: "1px 5px",
42011
+ cursor: "pointer",
42012
+ lineHeight: "14px",
42013
+ userSelect: "none"
42014
+ };
42015
+ const selectStyle$1 = {
42016
+ background: "var(--fc-bg)",
42017
+ color: "var(--fc-text)",
42018
+ border: "1px solid var(--fc-border)",
42019
+ borderRadius: 3,
42020
+ padding: "1px 4px",
42021
+ fontSize: 11,
42022
+ cursor: "pointer",
42023
+ lineHeight: "16px"
42024
+ };
42025
+ function drawFrame(ctx, frame2, view2, label, active) {
42026
+ const bounds = placement2DFrameBounds(frame2);
42027
+ const min = path2DToScreen({ x: bounds.minX, y: bounds.minY }, view2);
42028
+ const max2 = path2DToScreen({ x: bounds.maxX, y: bounds.maxY }, view2);
42029
+ const x = Math.min(min.x, max2.x);
42030
+ const y = Math.min(min.y, max2.y);
42031
+ const width = Math.abs(max2.x - min.x);
42032
+ const height = Math.abs(max2.y - min.y);
42033
+ ctx.save();
42034
+ ctx.fillStyle = active ? "rgba(77, 147, 191, 0.13)" : "rgba(255,255,255,0.035)";
42035
+ ctx.strokeStyle = active ? "#4d93bf" : "rgba(255,255,255,0.22)";
42036
+ ctx.lineWidth = active ? 2 : 1;
42037
+ ctx.setLineDash(active ? [] : [5, 5]);
42038
+ ctx.fillRect(x, y, width, height);
42039
+ ctx.strokeRect(x, y, width, height);
42040
+ ctx.setLineDash([]);
42041
+ if (label) {
42042
+ ctx.fillStyle = active ? "#9ed6ff" : "rgba(255,255,255,0.55)";
42043
+ ctx.font = "11px system-ui, sans-serif";
42044
+ ctx.fillText(label, x + 6, y + 15, Math.max(20, width - 12));
42045
+ }
42046
+ ctx.restore();
42047
+ }
42048
+ function drawItem(ctx, item, view2, selected, conflicted) {
42049
+ const center = path2DToScreen({ x: item.x, y: item.y }, view2);
42050
+ const color = conflicted ? "#d65f45" : selected ? "#e2b647" : "#4d93bf";
42051
+ ctx.save();
42052
+ ctx.translate(center.x, center.y);
42053
+ ctx.rotate(-item.angle * Math.PI / 180);
42054
+ ctx.fillStyle = conflicted ? "rgba(214, 95, 69, 0.24)" : selected ? "rgba(226, 182, 71, 0.25)" : "rgba(77, 147, 191, 0.22)";
42055
+ ctx.strokeStyle = color;
42056
+ ctx.lineWidth = selected ? 2.5 : 1.5;
42057
+ if (item.locked) ctx.setLineDash([4, 4]);
42058
+ if (item.footprint.type === "circle") {
42059
+ ctx.beginPath();
42060
+ ctx.arc(0, 0, item.footprint.radius * view2.scale, 0, Math.PI * 2);
42061
+ ctx.fill();
42062
+ ctx.stroke();
42063
+ } else {
42064
+ const width = item.footprint.width * view2.scale;
42065
+ const height = item.footprint.height * view2.scale;
42066
+ ctx.beginPath();
42067
+ ctx.rect(-width / 2, -height / 2, width, height);
42068
+ ctx.fill();
42069
+ ctx.stroke();
42070
+ }
42071
+ ctx.setLineDash([]);
42072
+ ctx.rotate(item.angle * Math.PI / 180);
42073
+ ctx.fillStyle = "#f4f7f0";
42074
+ ctx.font = selected ? "600 11px system-ui, sans-serif" : "11px system-ui, sans-serif";
42075
+ ctx.textAlign = "center";
42076
+ ctx.textBaseline = "middle";
42077
+ ctx.fillText(item.label ?? item.id, 0, 0, 90);
42078
+ ctx.restore();
42079
+ }
42080
+ function Placement2DParamEditor({
42081
+ layoutDef,
42082
+ allowFullScreen = true,
42083
+ large = false
42084
+ }) {
42085
+ var _a3, _b2;
42086
+ const canvasRef = reactExports.useRef(null);
42087
+ const dragRef = reactExports.useRef(null);
42088
+ const draftItemsRef = reactExports.useRef(null);
42089
+ const panStart = reactExports.useRef(null);
42090
+ const paramOverrides = useForgeStore((state2) => state2.paramOverrides);
42091
+ const setParams = useForgeStore((state2) => state2.setParams);
42092
+ const [expanded, setExpanded] = reactExports.useState(true);
42093
+ const [fullScreen, setFullScreen] = reactExports.useState(false);
42094
+ const [viewFrame, setViewFrame] = reactExports.useState(null);
42095
+ const [draftItems, setDraftItems] = reactExports.useState(null);
42096
+ const [selectedId, setSelectedId] = reactExports.useState(((_a3 = layoutDef.items[0]) == null ? void 0 : _a3.id) ?? "");
42097
+ const [blockedId, setBlockedId] = reactExports.useState(null);
42098
+ useEscapeAction(
42099
+ () => {
42100
+ setFullScreen(false);
42101
+ return true;
42102
+ },
42103
+ { active: fullScreen, label: `${layoutDef.name} placement editor`, priority: ESCAPE_PRIORITY.modal }
42104
+ );
42105
+ const committedItems = reactExports.useMemo(() => currentPlacement2DItems(layoutDef, paramOverrides), [layoutDef, paramOverrides]);
42106
+ const items = draftItems ?? committedItems;
42107
+ const collisionPairs = reactExports.useMemo(() => placement2DCollisionPairs(items), [items]);
42108
+ const conflictedIds = reactExports.useMemo(() => new Set(collisionPairs.flat()), [collisionPairs]);
42109
+ const selectedItem = items.find((item) => item.id === selectedId) ?? items[0];
42110
+ const unitLabel = layoutDef.unit ? ` ${layoutDef.unit}` : "";
42111
+ const collisionCount = collisionPairs.length;
42112
+ const boundsCount = items.filter((item) => placement2DHasBoundsViolation(layoutDef, item)).length;
42113
+ reactExports.useEffect(() => {
42114
+ var _a4;
42115
+ if (!items.some((item) => item.id === selectedId)) setSelectedId(((_a4 = items[0]) == null ? void 0 : _a4.id) ?? "");
42116
+ }, [items, selectedId]);
42117
+ reactExports.useEffect(() => {
42118
+ const canvas = canvasRef.current;
42119
+ if (!canvas) return;
42120
+ const ctx = canvas.getContext("2d");
42121
+ if (!ctx) return;
42122
+ const draw = () => {
42123
+ const rect = canvas.getBoundingClientRect();
42124
+ const dpr = window.devicePixelRatio || 1;
42125
+ const nextWidth = Math.max(1, Math.round(rect.width * dpr));
42126
+ const nextHeight = Math.max(1, Math.round(rect.height * dpr));
42127
+ if (canvas.width !== nextWidth) canvas.width = nextWidth;
42128
+ if (canvas.height !== nextHeight) canvas.height = nextHeight;
42129
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
42130
+ ctx.clearRect(0, 0, rect.width, rect.height);
42131
+ const frame2 = viewFrame ?? placement2DFrameForCanvas(canvas.clientWidth, canvas.clientHeight, layoutDef);
42132
+ drawPath2DGrid(ctx, rect.width, rect.height, frame2);
42133
+ if (layoutDef.zones.length === 0) {
42134
+ drawFrame(ctx, layoutDef.frame, frame2, "Frame", true);
42135
+ } else {
42136
+ for (const zone of layoutDef.zones) {
42137
+ drawFrame(
42138
+ ctx,
42139
+ zone,
42140
+ frame2,
42141
+ zone.label ?? zone.id,
42142
+ items.some((item) => item.zone === zone.id)
42143
+ );
42144
+ }
42145
+ }
42146
+ for (const item of items) drawItem(ctx, item, frame2, item.id === selectedId, conflictedIds.has(item.id) || blockedId === item.id);
42147
+ };
42148
+ draw();
42149
+ const observer = new ResizeObserver(draw);
42150
+ observer.observe(canvas);
42151
+ return () => observer.disconnect();
42152
+ }, [blockedId, conflictedIds, items, layoutDef, selectedId, viewFrame]);
42153
+ reactExports.useEffect(() => {
42154
+ const canvas = canvasRef.current;
42155
+ if (!canvas) return;
42156
+ const handleWheel = (event) => {
42157
+ event.preventDefault();
42158
+ event.stopPropagation();
42159
+ const rect = canvas.getBoundingClientRect();
42160
+ const x = event.clientX - rect.left;
42161
+ const y = event.clientY - rect.top;
42162
+ setViewFrame(
42163
+ (current) => zoomPath2DFrame(
42164
+ current ?? placement2DFrameForCanvas(canvas.clientWidth, canvas.clientHeight, layoutDef),
42165
+ x,
42166
+ y,
42167
+ event.deltaY > 0 ? 0.88 : 1.14
42168
+ )
42169
+ );
42170
+ };
42171
+ canvas.addEventListener("wheel", handleWheel, { passive: false });
42172
+ return () => canvas.removeEventListener("wheel", handleWheel);
42173
+ }, [layoutDef]);
42174
+ const canvasFrame = (canvas) => viewFrame ?? placement2DFrameForCanvas(canvas.clientWidth, canvas.clientHeight, layoutDef);
42175
+ const canvasHeight = large ? "min(62vh, 640px)" : fullScreen ? "calc(100vh - 166px)" : 240;
42176
+ const expandedLayout = large || fullScreen;
42177
+ const pointerPoint = (event) => {
42178
+ const rect = event.currentTarget.getBoundingClientRect();
42179
+ return { x: event.clientX - rect.left, y: event.clientY - rect.top };
42180
+ };
42181
+ const itemAtPointer = (canvas, x, y) => {
42182
+ const model = screenToPath2D(x, y, canvasFrame(canvas));
42183
+ for (let index = items.length - 1; index >= 0; index -= 1) {
42184
+ if (placement2DPointHitsItem(model, items[index])) return items[index];
42185
+ }
42186
+ return null;
42187
+ };
42188
+ const resolveCandidateItem = (rawCandidate, sourceItems) => {
42189
+ let candidate = placement2DSnapItem(rawCandidate, layoutDef.rules.snap);
42190
+ if (layoutDef.rules.bounds === "prevent") {
42191
+ candidate = placement2DClampItem(candidate, layoutDef.frame, layoutDef.zones);
42192
+ if (placement2DHasBoundsViolation(layoutDef, candidate)) {
42193
+ setBlockedId(candidate.id);
42194
+ return null;
42195
+ }
42196
+ }
42197
+ const collides = placement2DItemCollides(candidate, sourceItems);
42198
+ setBlockedId(collides ? candidate.id : null);
42199
+ if (layoutDef.rules.collisions === "prevent" && collides) return null;
42200
+ return candidate;
42201
+ };
42202
+ const commitItem = (rawCandidate) => {
42203
+ const candidate = resolveCandidateItem(rawCandidate, items);
42204
+ if (!candidate) return;
42205
+ setParams(placement2DItemPatch(layoutDef.name, candidate));
42206
+ };
42207
+ const previewDraggedItem = (rawCandidate) => {
42208
+ const sourceItems = draftItemsRef.current ?? items;
42209
+ const candidate = resolveCandidateItem(rawCandidate, sourceItems);
42210
+ if (!candidate) return;
42211
+ const nextItems = sourceItems.map((item) => item.id === candidate.id ? candidate : item);
42212
+ draftItemsRef.current = nextItems;
42213
+ setDraftItems(nextItems);
42214
+ };
42215
+ const commitDraftDrag = (id) => {
42216
+ const nextItems = draftItemsRef.current;
42217
+ draftItemsRef.current = null;
42218
+ setDraftItems(null);
42219
+ const item = nextItems == null ? void 0 : nextItems.find((candidate) => candidate.id === id);
42220
+ if (item) setParams(placement2DItemPatch(layoutDef.name, item));
42221
+ };
42222
+ const cancelDraftDrag = () => {
42223
+ draftItemsRef.current = null;
42224
+ setDraftItems(null);
42225
+ };
42226
+ const setSelectedZone = (zone) => {
42227
+ if (!selectedItem) return;
42228
+ commitItem({ ...selectedItem, zone });
42229
+ };
42230
+ const setSelectedAngle = (angle) => {
42231
+ if (!selectedItem) return;
42232
+ commitItem({ ...selectedItem, angle });
42233
+ };
42234
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginBottom: 8 }, children: [
42235
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
42236
+ "div",
42237
+ {
42238
+ onClick: () => setExpanded(!expanded),
42239
+ style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 12, cursor: "pointer", marginBottom: 4 },
42240
+ children: [
42241
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-text)", fontWeight: 600 }, children: [
42242
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 9, marginRight: 4 }, children: expanded ? "v" : ">" }),
42243
+ layoutDef.name,
42244
+ " (",
42245
+ items.length,
42246
+ ")"
42247
+ ] }),
42248
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { color: collisionCount > 0 || boundsCount > 0 ? "#d65f45" : "var(--fc-textDim)", fontSize: 10 }, children: "Placement Sheet" })
42249
+ ]
42250
+ }
42251
+ ),
42252
+ expanded && /* @__PURE__ */ jsxRuntimeExports.jsxs(
42253
+ "div",
42254
+ {
42255
+ style: {
42256
+ border: "1px solid var(--fc-border)",
42257
+ borderRadius: 4,
42258
+ padding: 8,
42259
+ background: "var(--fc-bgSubtle, var(--fc-bgSurface, #1c2128))",
42260
+ userSelect: "none",
42261
+ WebkitUserSelect: "none",
42262
+ ...expandedLayout ? {
42263
+ display: "flex",
42264
+ flexDirection: "column"
42265
+ } : {},
42266
+ ...fullScreen ? {
42267
+ position: "fixed",
42268
+ inset: 16,
42269
+ zIndex: 2200,
42270
+ boxShadow: "0 18px 52px rgba(0, 0, 0, 0.45)",
42271
+ boxSizing: "border-box",
42272
+ color: "var(--fc-text)"
42273
+ } : {}
42274
+ },
42275
+ children: [
42276
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 6, marginBottom: 6 }, children: [
42277
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42278
+ "select",
42279
+ {
42280
+ "aria-label": `${layoutDef.name} selected item`,
42281
+ value: (selectedItem == null ? void 0 : selectedItem.id) ?? "",
42282
+ onChange: (event) => setSelectedId(event.target.value),
42283
+ style: { ...selectStyle$1, minWidth: 0, flex: "1 1 auto" },
42284
+ children: items.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: item.id, children: item.label ?? item.id }, item.id))
42285
+ }
42286
+ ),
42287
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: 4 }, children: [
42288
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setViewFrame(null), title: "Fit placement sheet in editor", style: buttonStyle$1, children: "Fit" }),
42289
+ allowFullScreen && /* @__PURE__ */ jsxRuntimeExports.jsx(
42290
+ "button",
42291
+ {
42292
+ type: "button",
42293
+ onClick: () => setFullScreen(!fullScreen),
42294
+ title: fullScreen ? "Close expanded editor" : "Expand editor",
42295
+ style: buttonStyle$1,
42296
+ children: fullScreen ? "Close" : "Full"
42297
+ }
42298
+ )
42299
+ ] })
42300
+ ] }),
42301
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42302
+ "canvas",
42303
+ {
42304
+ ref: canvasRef,
42305
+ height: 240,
42306
+ "aria-label": `${layoutDef.name} placement editor`,
42307
+ onPointerDown: (event) => {
42308
+ event.preventDefault();
42309
+ const pos = pointerPoint(event);
42310
+ const hit = itemAtPointer(event.currentTarget, pos.x, pos.y);
42311
+ if (!hit) {
42312
+ panStart.current = { x: pos.x, y: pos.y, frame: canvasFrame(event.currentTarget) };
42313
+ } else {
42314
+ setSelectedId(hit.id);
42315
+ const model = screenToPath2D(pos.x, pos.y, canvasFrame(event.currentTarget));
42316
+ if (!hit.locked) {
42317
+ draftItemsRef.current = null;
42318
+ setDraftItems(null);
42319
+ dragRef.current = { id: hit.id, offsetX: model.x - hit.x, offsetY: model.y - hit.y };
42320
+ }
42321
+ }
42322
+ event.currentTarget.setPointerCapture(event.pointerId);
42323
+ },
42324
+ onPointerMove: (event) => {
42325
+ const pos = pointerPoint(event);
42326
+ const drag = dragRef.current;
42327
+ if (drag) {
42328
+ const item = items.find((candidate) => candidate.id === drag.id);
42329
+ if (!item) return;
42330
+ const model = screenToPath2D(pos.x, pos.y, canvasFrame(event.currentTarget));
42331
+ const center = { x: model.x - drag.offsetX, y: model.y - drag.offsetY };
42332
+ const zone = placement2DZoneAtPoint(layoutDef.zones, center);
42333
+ previewDraggedItem({ ...item, x: center.x, y: center.y, zone: (zone == null ? void 0 : zone.id) ?? item.zone });
42334
+ return;
42335
+ }
42336
+ if (panStart.current) {
42337
+ setViewFrame(panPath2DFrame(panStart.current.frame, pos.x - panStart.current.x, pos.y - panStart.current.y));
42338
+ }
42339
+ },
42340
+ onPointerUp: (event) => {
42341
+ const drag = dragRef.current;
42342
+ if (drag) commitDraftDrag(drag.id);
42343
+ dragRef.current = null;
42344
+ panStart.current = null;
42345
+ setBlockedId(null);
42346
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
42347
+ },
42348
+ onPointerCancel: (event) => {
42349
+ if (dragRef.current) cancelDraftDrag();
42350
+ dragRef.current = null;
42351
+ panStart.current = null;
42352
+ setBlockedId(null);
42353
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
42354
+ },
42355
+ style: {
42356
+ display: "block",
42357
+ width: "100%",
42358
+ height: canvasHeight,
42359
+ borderRadius: 3,
42360
+ background: "var(--fc-bg)",
42361
+ touchAction: "none"
42362
+ }
42363
+ }
42364
+ ),
42365
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "grid", gridTemplateColumns: "minmax(0, 1fr) auto", gap: 8, alignItems: "center", marginTop: 6 }, children: [
42366
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { color: "var(--fc-textDim)", fontSize: 10, fontVariantNumeric: "tabular-nums", minWidth: 0 }, children: [
42367
+ selectedItem ? `${selectedItem.id} x ${selectedItem.x}${unitLabel} | y ${selectedItem.y}${unitLabel}` : "No item",
42368
+ collisionCount > 0 ? ` | ${collisionCount} overlap${collisionCount === 1 ? "" : "s"}` : "",
42369
+ boundsCount > 0 ? ` | ${boundsCount} out of bounds` : ""
42370
+ ] }),
42371
+ selectedItem && /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [
42372
+ layoutDef.zones.length > 0 && /* @__PURE__ */ jsxRuntimeExports.jsx(
42373
+ "select",
42374
+ {
42375
+ "aria-label": `${layoutDef.name} selected item zone`,
42376
+ value: selectedItem.zone ?? ((_b2 = layoutDef.zones[0]) == null ? void 0 : _b2.id) ?? "",
42377
+ onChange: (event) => setSelectedZone(event.target.value),
42378
+ style: selectStyle$1,
42379
+ children: layoutDef.zones.map((zone) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: zone.id, children: zone.label ?? zone.id }, zone.id))
42380
+ }
42381
+ ),
42382
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42383
+ "button",
42384
+ {
42385
+ type: "button",
42386
+ onClick: () => setSelectedAngle(selectedItem.angle - 15),
42387
+ title: "Rotate selected item left",
42388
+ style: buttonStyle$1,
42389
+ children: "-15"
42390
+ }
42391
+ ),
42392
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-textDim)", fontSize: 10, minWidth: 42, textAlign: "center" }, children: [
42393
+ selectedItem.angle,
42394
+ " deg"
42395
+ ] }),
42396
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42397
+ "button",
42398
+ {
42399
+ type: "button",
42400
+ onClick: () => setSelectedAngle(selectedItem.angle + 15),
42401
+ title: "Rotate selected item right",
42402
+ style: buttonStyle$1,
42403
+ children: "+15"
42404
+ }
42405
+ )
42406
+ ] })
42407
+ ] })
42408
+ ]
42409
+ }
42410
+ )
42411
+ ] });
42412
+ }
42413
+ const SPLINE_2D_CONTINUITIES = ["G0", "G1", "G2"];
42414
+ function continuityFromOverride(value, fallback) {
42415
+ if (value === "G0" || value === "G1" || value === "G2") return value;
42416
+ if (typeof value === "number") {
42417
+ const index = Math.round(value);
42418
+ return SPLINE_2D_CONTINUITIES[index] ?? fallback;
42419
+ }
42420
+ return fallback;
42421
+ }
42422
+ function currentSpline2DPoints(curveDef, overrides) {
42423
+ const countOverride = overrides[`${curveDef.name}.__count__`];
42424
+ const count = typeof countOverride === "number" ? clamp(Math.round(countOverride), curveDef.minPoints, curveDef.maxPoints) : curveDef.points.length;
42425
+ const points = [];
42426
+ for (let i = 0; i < count; i += 1) {
42427
+ const base = curveDef.points[i] ?? curveDef.defaultPoints[i] ?? { x: 0, y: 0, g: "G2" };
42428
+ const rawX = overrides[`${curveDef.name}[${i}].x`];
42429
+ const rawY = overrides[`${curveDef.name}[${i}].y`];
42430
+ const rawG = overrides[`${curveDef.name}[${i}].g`];
42431
+ points.push({
42432
+ x: typeof rawX === "number" ? rawX : base.x,
42433
+ y: typeof rawY === "number" ? rawY : base.y,
42434
+ g: continuityFromOverride(rawG, base.g)
42435
+ });
42436
+ }
42437
+ return points;
42438
+ }
42439
+ function spline2DPatch(curveName, points) {
42440
+ const patch = { [`${curveName}.__count__`]: points.length };
42441
+ points.forEach((point, index) => {
42442
+ patch[`${curveName}[${index}].x`] = point.x;
42443
+ patch[`${curveName}[${index}].y`] = point.y;
42444
+ patch[`${curveName}[${index}].g`] = point.g;
42445
+ });
42446
+ return patch;
42447
+ }
42448
+ function trailingSpline2DKeys(curveName, oldCount, newCount) {
42449
+ const keys = [];
42450
+ for (let i = newCount; i < oldCount; i += 1) {
42451
+ keys.push(`${curveName}[${i}].x`, `${curveName}[${i}].y`, `${curveName}[${i}].g`);
42452
+ }
42453
+ return keys;
42454
+ }
42455
+ function splineContinuityColor(continuity) {
42456
+ if (continuity === "G0") return "#d65f45";
42457
+ if (continuity === "G1") return "#e2b647";
42458
+ return "#4d93bf";
42459
+ }
42460
+ const buttonStyle = {
42461
+ background: "var(--fc-small-button-bg, none)",
42462
+ border: "1px solid var(--fc-small-button-border, var(--fc-border))",
42463
+ borderRadius: 3,
42464
+ color: "var(--fc-textDim)",
42465
+ fontSize: 10,
42466
+ padding: "1px 5px",
42467
+ cursor: "pointer",
42468
+ lineHeight: "14px",
42469
+ userSelect: "none"
42470
+ };
42471
+ const selectStyle = {
42472
+ background: "var(--fc-bg)",
42473
+ color: "var(--fc-text)",
42474
+ border: "1px solid var(--fc-border)",
42475
+ borderRadius: 3,
42476
+ padding: "1px 4px",
42477
+ fontSize: 11,
42478
+ cursor: "pointer",
42479
+ lineHeight: "16px"
42480
+ };
42481
+ function drawSmoothPreview(ctx, projected, points, closed) {
42482
+ if (projected.length < 2) return;
42483
+ ctx.beginPath();
42484
+ ctx.moveTo(projected[0].x, projected[0].y);
42485
+ const segmentCount = closed ? projected.length : projected.length - 1;
42486
+ for (let index = 0; index < segmentCount; index += 1) {
42487
+ const nextIndex = (index + 1) % projected.length;
42488
+ const current = projected[index];
42489
+ const next = projected[nextIndex];
42490
+ const currentG = points[index].g;
42491
+ const nextG = points[nextIndex].g;
42492
+ if (currentG === "G0" || nextG === "G0") {
42493
+ ctx.lineTo(next.x, next.y);
42494
+ continue;
42495
+ }
42496
+ const prev = projected[index === 0 ? closed ? projected.length - 1 : 0 : index - 1];
42497
+ const after = projected[nextIndex === projected.length - 1 ? closed ? 0 : nextIndex : nextIndex + 1];
42498
+ const reach = currentG === "G2" || nextG === "G2" ? 1 / 6 : 1 / 8;
42499
+ const c1 = { x: current.x + (next.x - prev.x) * reach, y: current.y + (next.y - prev.y) * reach };
42500
+ const c2 = { x: next.x - (after.x - current.x) * reach, y: next.y - (after.y - current.y) * reach };
42501
+ ctx.bezierCurveTo(c1.x, c1.y, c2.x, c2.y, next.x, next.y);
42502
+ }
42503
+ if (closed) ctx.closePath();
42504
+ ctx.stroke();
42505
+ }
42506
+ function Spline2DParamEditor({
42507
+ curveDef,
42508
+ allowFullScreen = true,
42509
+ large = false
42510
+ }) {
42511
+ const canvasRef = reactExports.useRef(null);
42512
+ const dragState = reactExports.useRef(null);
42513
+ const draftPointsRef = reactExports.useRef(null);
42514
+ const panStart = reactExports.useRef(null);
42515
+ const paramOverrides = useForgeStore((state2) => state2.paramOverrides);
42516
+ const setParams = useForgeStore((state2) => state2.setParams);
42517
+ const [expanded, setExpanded] = reactExports.useState(true);
42518
+ const [fullScreen, setFullScreen] = reactExports.useState(false);
42519
+ const [viewFrame, setViewFrame] = reactExports.useState(null);
42520
+ const [draftPoints, setDraftPoints] = reactExports.useState(null);
42521
+ const [selected, setSelected] = reactExports.useState(0);
42522
+ const [selectedIndices, setSelectedIndices] = reactExports.useState([0]);
42523
+ useEscapeAction(
42524
+ () => {
42525
+ setFullScreen(false);
42526
+ return true;
42527
+ },
42528
+ { active: fullScreen, label: `${curveDef.name} spline editor`, priority: ESCAPE_PRIORITY.modal }
42529
+ );
42530
+ const committedPoints = reactExports.useMemo(() => currentSpline2DPoints(curveDef, paramOverrides), [curveDef, paramOverrides]);
42531
+ const points = draftPoints ?? committedPoints;
42532
+ const activeIndex = points.length > 0 ? Math.min(selected, points.length - 1) : 0;
42533
+ const normalizedSelectedIndices = reactExports.useMemo(
42534
+ () => normalizePointSelection(selectedIndices, points.length, activeIndex),
42535
+ [activeIndex, points.length, selectedIndices]
42536
+ );
42537
+ const selectedIndexSet = reactExports.useMemo(() => new Set(normalizedSelectedIndices), [normalizedSelectedIndices]);
42538
+ const selectedPoint = points[activeIndex] ?? points[0];
42539
+ const unitLabel = curveDef.unit ? ` ${curveDef.unit}` : "";
42540
+ const canAdd = points.length < curveDef.maxPoints;
42541
+ const canRemove = points.length - normalizedSelectedIndices.length >= curveDef.minPoints;
42542
+ reactExports.useEffect(() => {
42543
+ if (selected >= points.length) setSelected(Math.max(0, points.length - 1));
42544
+ }, [points.length, selected]);
42545
+ reactExports.useEffect(() => {
42546
+ const canvas = canvasRef.current;
42547
+ if (!canvas) return;
42548
+ const ctx = canvas.getContext("2d");
42549
+ if (!ctx) return;
42550
+ const draw = () => {
42551
+ const rect = canvas.getBoundingClientRect();
42552
+ const dpr = window.devicePixelRatio || 1;
42553
+ const nextWidth = Math.max(1, Math.round(rect.width * dpr));
42554
+ const nextHeight = Math.max(1, Math.round(rect.height * dpr));
42555
+ if (canvas.width !== nextWidth) canvas.width = nextWidth;
42556
+ if (canvas.height !== nextHeight) canvas.height = nextHeight;
42557
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
42558
+ ctx.clearRect(0, 0, rect.width, rect.height);
42559
+ const frame2 = viewFrame ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, curveDef);
42560
+ drawPath2DGrid(ctx, rect.width, rect.height, frame2);
42561
+ if (points.length === 0) return;
42562
+ const projected = points.map((point) => path2DToScreen(point, frame2));
42563
+ ctx.save();
42564
+ ctx.setLineDash([5, 5]);
42565
+ ctx.strokeStyle = "rgba(255,255,255,0.18)";
42566
+ ctx.lineWidth = 1;
42567
+ ctx.beginPath();
42568
+ ctx.moveTo(projected[0].x, projected[0].y);
42569
+ for (let index = 1; index < projected.length; index += 1) ctx.lineTo(projected[index].x, projected[index].y);
42570
+ if (curveDef.closed) ctx.closePath();
42571
+ ctx.stroke();
42572
+ ctx.restore();
42573
+ ctx.strokeStyle = "#4d93bf";
42574
+ ctx.lineWidth = 4;
42575
+ drawSmoothPreview(ctx, projected, points, curveDef.closed);
42576
+ projected.forEach((point, index) => {
42577
+ const isSelected = selectedIndexSet.has(index);
42578
+ const isActive = index === activeIndex;
42579
+ ctx.beginPath();
42580
+ ctx.arc(point.x, point.y, isActive ? 8 : isSelected ? 7 : 6, 0, Math.PI * 2);
42581
+ ctx.fillStyle = splineContinuityColor(points[index].g);
42582
+ ctx.fill();
42583
+ ctx.lineWidth = isActive ? 3 : isSelected ? 2.25 : 1.5;
42584
+ ctx.strokeStyle = isSelected ? "#fff6d4" : "#171914";
42585
+ ctx.stroke();
42586
+ });
42587
+ };
42588
+ draw();
42589
+ const observer = new ResizeObserver(draw);
42590
+ observer.observe(canvas);
42591
+ return () => observer.disconnect();
42592
+ }, [activeIndex, curveDef, fullScreen, large, points, selectedIndexSet, viewFrame]);
42593
+ reactExports.useEffect(() => {
42594
+ const canvas = canvasRef.current;
42595
+ if (!canvas) return;
42596
+ const handleWheel = (event) => {
42597
+ event.preventDefault();
42598
+ event.stopPropagation();
42599
+ const rect = canvas.getBoundingClientRect();
42600
+ const x = event.clientX - rect.left;
42601
+ const y = event.clientY - rect.top;
42602
+ setViewFrame(
42603
+ (current) => zoomPath2DFrame(current ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, curveDef), x, y, event.deltaY > 0 ? 0.88 : 1.14)
42604
+ );
42605
+ };
42606
+ canvas.addEventListener("wheel", handleWheel, { passive: false });
42607
+ return () => canvas.removeEventListener("wheel", handleWheel);
42608
+ }, [curveDef]);
42609
+ const setContinuityForSelection = (continuity) => {
42610
+ const nextPoints = points.map((point, index) => selectedIndexSet.has(index) ? { ...point, g: continuity } : point);
42611
+ setParams(spline2DPatch(curveDef.name, nextPoints));
42612
+ };
42613
+ const pointerPoint = (event) => {
42614
+ const canvas = event.currentTarget;
42615
+ const rect = canvas.getBoundingClientRect();
42616
+ return { x: event.clientX - rect.left, y: event.clientY - rect.top };
42617
+ };
42618
+ const nearestHandle = (canvas, x, y) => {
42619
+ const frame2 = viewFrame ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, curveDef);
42620
+ let bestIndex = null;
42621
+ let bestDistance = Infinity;
42622
+ points.forEach((point, index) => {
42623
+ const screen2 = path2DToScreen(point, frame2);
42624
+ const distance = Math.hypot(screen2.x - x, screen2.y - y);
42625
+ if (distance < bestDistance) {
42626
+ bestDistance = distance;
42627
+ bestIndex = index;
42628
+ }
42629
+ });
42630
+ return bestDistance <= 18 ? bestIndex : null;
42631
+ };
42632
+ const moveDraggedPoints = (drag, pointer) => {
42633
+ const dx = pointer.x - drag.startPointer.x;
42634
+ const dy = pointer.y - drag.startPointer.y;
42635
+ const dragIndices = new Set(drag.indices);
42636
+ const nextPoints = drag.startPoints.map(
42637
+ (point, index) => dragIndices.has(index) ? {
42638
+ ...point,
42639
+ x: roundToStep(point.x + dx, curveDef.x.step),
42640
+ y: roundToStep(point.y + dy, curveDef.y.step)
42641
+ } : point
42642
+ );
42643
+ draftPointsRef.current = nextPoints;
42644
+ setDraftPoints(nextPoints);
42645
+ };
42646
+ const commitDraftDrag = () => {
42647
+ const nextPoints = draftPointsRef.current;
42648
+ draftPointsRef.current = null;
42649
+ setDraftPoints(null);
42650
+ if (nextPoints) setParams(spline2DPatch(curveDef.name, nextPoints));
42651
+ };
42652
+ const cancelDraftDrag = () => {
42653
+ draftPointsRef.current = null;
42654
+ setDraftPoints(null);
42655
+ };
42656
+ const insertPointAfterSelection = () => {
42657
+ if (!canAdd) return;
42658
+ const insertAt = Math.min(activeIndex + 1, points.length);
42659
+ const a2 = points[activeIndex] ?? points[points.length - 1] ?? { x: 0, y: 0, g: "G2" };
42660
+ const b2 = points[insertAt] ?? (curveDef.closed ? points[0] : a2);
42661
+ const nextPoint = {
42662
+ x: roundToStep((a2.x + b2.x) / 2, curveDef.x.step),
42663
+ y: roundToStep((a2.y + b2.y) / 2, curveDef.y.step),
42664
+ g: a2.g === "G0" ? "G1" : a2.g
42665
+ };
42666
+ const nextPoints = [...points];
42667
+ nextPoints.splice(insertAt, 0, nextPoint);
42668
+ setSelected(insertAt);
42669
+ setSelectedIndices([insertAt]);
42670
+ setParams(spline2DPatch(curveDef.name, nextPoints));
42671
+ };
42672
+ const removeSelectedPoint = () => {
42673
+ if (!canRemove) return;
42674
+ const removeIndices = new Set(normalizedSelectedIndices);
42675
+ const nextPoints = points.filter((_, index) => !removeIndices.has(index));
42676
+ const firstRemoved = normalizedSelectedIndices[0] ?? activeIndex;
42677
+ const nextSelected = Math.max(0, Math.min(firstRemoved, nextPoints.length - 1));
42678
+ setSelected(nextSelected);
42679
+ setSelectedIndices([nextSelected]);
42680
+ setParams(spline2DPatch(curveDef.name, nextPoints), trailingSpline2DKeys(curveDef.name, points.length, nextPoints.length));
42681
+ };
42682
+ const handleButtonPointerDown = (event, action) => {
42683
+ event.preventDefault();
42684
+ event.stopPropagation();
42685
+ action();
42686
+ };
42687
+ const handleButtonKeyDown = (event, action) => {
42688
+ if (event.key !== "Enter" && event.key !== " ") return;
42689
+ event.preventDefault();
42690
+ event.stopPropagation();
42691
+ action();
42692
+ };
42693
+ const canvasFrame = (canvas) => viewFrame ?? frameForPath2D(canvas.clientWidth, canvas.clientHeight, curveDef);
42694
+ const canvasHeight = large ? "min(62vh, 640px)" : fullScreen ? "calc(100vh - 150px)" : 220;
42695
+ const expandedLayout = large || fullScreen;
42696
+ const editorPanel = /* @__PURE__ */ jsxRuntimeExports.jsxs(
42697
+ "div",
42698
+ {
42699
+ style: {
42700
+ border: "1px solid var(--fc-border)",
42701
+ borderRadius: 4,
42702
+ padding: 8,
42703
+ background: "var(--fc-bgSubtle, var(--fc-bgSurface, #1c2128))",
42704
+ userSelect: "none",
42705
+ WebkitUserSelect: "none",
42706
+ ...expandedLayout ? {
42707
+ display: "flex",
42708
+ flexDirection: "column"
42709
+ } : {},
42710
+ ...fullScreen ? {
42711
+ position: "fixed",
42712
+ inset: 16,
42713
+ zIndex: 2200,
42714
+ boxShadow: "0 18px 52px rgba(0, 0, 0, 0.45)",
42715
+ boxSizing: "border-box",
42716
+ color: "var(--fc-text)"
42717
+ } : {}
42718
+ },
42719
+ children: [
42720
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "flex-end", gap: 4, marginBottom: 6 }, children: [
42721
+ /* @__PURE__ */ jsxRuntimeExports.jsx("button", { type: "button", onClick: () => setViewFrame(null), title: "Fit spline in editor", style: buttonStyle, children: "Fit" }),
42722
+ allowFullScreen && /* @__PURE__ */ jsxRuntimeExports.jsx(
42723
+ "button",
42724
+ {
42725
+ type: "button",
42726
+ onClick: () => setFullScreen(!fullScreen),
42727
+ title: fullScreen ? "Close expanded editor" : "Expand editor",
42728
+ style: buttonStyle,
42729
+ children: fullScreen ? "Close" : "Full"
42730
+ }
42731
+ )
42732
+ ] }),
42733
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42734
+ "canvas",
42735
+ {
42736
+ ref: canvasRef,
42737
+ height: 220,
42738
+ "aria-label": `${curveDef.name} spline editor`,
42739
+ onPointerDown: (event) => {
42740
+ event.preventDefault();
42741
+ const pos = pointerPoint(event);
42742
+ const hit = nearestHandle(event.currentTarget, pos.x, pos.y);
42743
+ if (hit === null) panStart.current = { x: pos.x, y: pos.y, frame: canvasFrame(event.currentTarget) };
42744
+ else {
42745
+ const nextSelection = pointSelectionForPointerDown(
42746
+ normalizedSelectedIndices,
42747
+ hit,
42748
+ activeIndex,
42749
+ hasPointSelectionModifier(event),
42750
+ points.length
42751
+ );
42752
+ setSelected(nextSelection.activeIndex);
42753
+ setSelectedIndices(nextSelection.selectedIndices);
42754
+ if (nextSelection.shouldDrag) {
42755
+ const frame2 = canvasFrame(event.currentTarget);
42756
+ draftPointsRef.current = null;
42757
+ setDraftPoints(null);
42758
+ dragState.current = {
42759
+ indices: nextSelection.selectedIndices,
42760
+ startPointer: screenToPath2D(pos.x, pos.y, frame2),
42761
+ startPoints: points.map((point) => ({ ...point })),
42762
+ frame: frame2
42763
+ };
42764
+ }
42765
+ }
42766
+ event.currentTarget.setPointerCapture(event.pointerId);
42767
+ },
42768
+ onPointerMove: (event) => {
42769
+ const pos = pointerPoint(event);
42770
+ if (dragState.current) {
42771
+ moveDraggedPoints(dragState.current, screenToPath2D(pos.x, pos.y, dragState.current.frame));
42772
+ return;
42773
+ }
42774
+ if (panStart.current) {
42775
+ setViewFrame(panPath2DFrame(panStart.current.frame, pos.x - panStart.current.x, pos.y - panStart.current.y));
42776
+ }
42777
+ },
42778
+ onPointerUp: (event) => {
42779
+ if (dragState.current) commitDraftDrag();
42780
+ dragState.current = null;
42781
+ panStart.current = null;
42782
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
42783
+ },
42784
+ onPointerCancel: (event) => {
42785
+ if (dragState.current) cancelDraftDrag();
42786
+ dragState.current = null;
42787
+ panStart.current = null;
42788
+ if (event.currentTarget.hasPointerCapture(event.pointerId)) event.currentTarget.releasePointerCapture(event.pointerId);
42789
+ },
42790
+ style: { display: "block", width: "100%", height: canvasHeight, borderRadius: 3, background: "var(--fc-bg)", touchAction: "none" }
42791
+ }
42792
+ ),
42793
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, marginTop: 6 }, children: [
42794
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { color: "var(--fc-textDim)", fontSize: 10, fontVariantNumeric: "tabular-nums", minWidth: 0 }, children: [
42795
+ normalizedSelectedIndices.length > 1 ? `${normalizedSelectedIndices.length} selected · ` : "",
42796
+ "P",
42797
+ Math.min(activeIndex + 1, points.length),
42798
+ " x ",
42799
+ selectedPoint ? selectedPoint.x : 0,
42800
+ unitLabel,
42801
+ " · y ",
42802
+ selectedPoint ? selectedPoint.y : 0,
42803
+ unitLabel
42804
+ ] }),
42805
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: 4, alignItems: "center" }, children: [
42806
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42807
+ "select",
42808
+ {
42809
+ "aria-label": `${curveDef.name} selected point continuity`,
42810
+ value: (selectedPoint == null ? void 0 : selectedPoint.g) ?? "G2",
42811
+ onChange: (event) => setContinuityForSelection(event.target.value),
42812
+ style: selectStyle,
42813
+ children: SPLINE_2D_CONTINUITIES.map((continuity) => /* @__PURE__ */ jsxRuntimeExports.jsx("option", { value: continuity, children: continuity }, continuity))
42814
+ }
42815
+ ),
42816
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42817
+ "button",
42818
+ {
42819
+ type: "button",
42820
+ onPointerDown: (event) => handleButtonPointerDown(event, insertPointAfterSelection),
42821
+ onKeyDown: (event) => handleButtonKeyDown(event, insertPointAfterSelection),
42822
+ disabled: !canAdd,
42823
+ title: "Add point after selected",
42824
+ style: buttonStyle,
42825
+ children: "+ Point"
42826
+ }
42827
+ ),
42828
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42829
+ "button",
42830
+ {
42831
+ type: "button",
42832
+ onPointerDown: (event) => handleButtonPointerDown(event, removeSelectedPoint),
42833
+ onKeyDown: (event) => handleButtonKeyDown(event, removeSelectedPoint),
42834
+ disabled: !canRemove,
42835
+ title: "Remove selected point(s)",
42836
+ style: buttonStyle,
42837
+ children: "Remove"
42838
+ }
42839
+ )
42840
+ ] })
42841
+ ] })
42842
+ ]
42843
+ }
42844
+ );
42845
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { marginBottom: 8 }, children: [
42846
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
42847
+ "div",
42848
+ {
42849
+ onClick: () => setExpanded(!expanded),
42850
+ style: { display: "flex", justifyContent: "space-between", alignItems: "center", fontSize: 12, cursor: "pointer", marginBottom: 4 },
42851
+ children: [
42852
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-text)", fontWeight: 600 }, children: [
42853
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { fontSize: 9, marginRight: 4 }, children: expanded ? "▼" : "▶" }),
42854
+ curveDef.name,
42855
+ " (",
42856
+ points.length,
42857
+ ")"
42858
+ ] }),
42859
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("span", { style: { color: "var(--fc-textDim)", fontSize: 10 }, children: [
42860
+ "Spline d",
42861
+ curveDef.degree
42862
+ ] })
42863
+ ]
42864
+ }
42865
+ ),
42866
+ expanded && editorPanel
42867
+ ] });
42868
+ }
42869
+ const SPATIAL_PARAM_KIND_COLORS = {
42870
+ scalar: "#46b6a9",
42871
+ text: "#9ca3af",
42872
+ list: "#e5a83c",
42873
+ path2d: "#e5a83c",
42874
+ spline2d: "#5da9e9",
42875
+ placement2d: "#c77dff"
42876
+ };
42877
+ function spatialParamAnchorPosition(anchor) {
42878
+ return anchor.kind === "point" ? anchor.at : anchor.origin;
42879
+ }
42880
+ function spatialParamAnchorLabel(name, anchor) {
42881
+ return anchor.label ?? name;
42882
+ }
42883
+ function scalarSummary(param) {
42884
+ if (param.boolean) return param.value >= 0.5 ? "on" : "off";
42885
+ if (param.choices) return param.choices[Math.max(0, Math.min(Math.round(param.value), param.choices.length - 1))] ?? String(param.value);
42886
+ return `${param.value}${param.unit ? ` ${param.unit}` : ""}`;
42887
+ }
42888
+ function collectSpatialParamAnchors(params, stringParams, listParams, path2dParams, spline2dParams, placement2dParams) {
42889
+ const anchors = [];
42890
+ params.forEach((param) => {
42891
+ if (param.anchor) anchors.push({ name: param.name, kind: "scalar", anchor: param.anchor, summary: scalarSummary(param) });
42892
+ });
42893
+ stringParams.forEach((param) => {
42894
+ if (param.anchor) anchors.push({ name: param.name, kind: "text", anchor: param.anchor, summary: param.value });
42895
+ });
42896
+ listParams.forEach((param) => {
42897
+ if (param.anchor) anchors.push({ name: param.name, kind: "list", anchor: param.anchor, summary: `${param.items.length} items` });
42898
+ });
42899
+ path2dParams.forEach((param) => {
42900
+ if (param.anchor)
42901
+ anchors.push({ name: param.name, kind: "path2d", anchor: param.anchor, summary: `${param.points.length} pts`, pathDef: param });
42902
+ });
42903
+ spline2dParams.forEach((param) => {
42904
+ if (param.anchor)
42905
+ anchors.push({ name: param.name, kind: "spline2d", anchor: param.anchor, summary: `${param.points.length} pts`, splineDef: param });
42906
+ });
42907
+ placement2dParams.forEach((param) => {
42908
+ if (param.anchor)
42909
+ anchors.push({
42910
+ name: param.name,
42911
+ kind: "placement2d",
42912
+ anchor: param.anchor,
42913
+ summary: `${param.items.length} items`,
42914
+ placementDef: param
42915
+ });
42916
+ });
42917
+ return anchors;
42918
+ }
42919
+ function stopSpatialPointerDown$1(event) {
42920
+ event.stopPropagation();
42921
+ }
42922
+ function preventControlTextSelection$1(event) {
42923
+ const target = event.target;
42924
+ if (target instanceof HTMLElement && target.closest("input, textarea, select, button, canvas")) return;
42925
+ event.preventDefault();
42926
+ }
42927
+ function preventMarkerDrag(event) {
42928
+ event.preventDefault();
42929
+ }
42930
+ function stopSpatialWheel$1(event) {
42931
+ event.stopPropagation();
42932
+ }
42933
+ function SpatialParamMarker({ item }) {
42934
+ const focusedParamName = useForgeStore((s) => s.focusedParamName);
42935
+ const focusParam = useForgeStore((s) => s.focusParam);
42936
+ const openSpatialParamSheet = useForgeStore((s) => s.openSpatialParamSheet);
42937
+ const closeSpatialParamSheet = useForgeStore((s) => s.closeSpatialParamSheet);
42938
+ const selected = focusedParamName === item.name;
42939
+ const color = item.anchor.color ?? SPATIAL_PARAM_KIND_COLORS[item.kind];
42940
+ const isManual = item.kind === "path2d" || item.kind === "spline2d" || item.kind === "placement2d";
42941
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
42942
+ Html,
42943
+ {
42944
+ position: spatialParamAnchorPosition(item.anchor),
42945
+ zIndexRange: [90, 0],
42946
+ style: { pointerEvents: "auto", userSelect: "none", WebkitUserSelect: "none" },
42947
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
42948
+ "button",
42949
+ {
42950
+ type: "button",
42951
+ "aria-label": `Focus parameter ${item.name}`,
42952
+ draggable: false,
42953
+ onDragStart: preventMarkerDrag,
42954
+ onPointerDown: (event) => {
42955
+ event.stopPropagation();
42956
+ event.preventDefault();
42957
+ },
42958
+ onClick: (event) => {
42959
+ event.stopPropagation();
42960
+ focusParam(item.name);
42961
+ if (isManual) openSpatialParamSheet(item.name);
42962
+ else closeSpatialParamSheet();
42963
+ },
42964
+ style: {
42965
+ display: "inline-flex",
42966
+ gap: 7,
42967
+ alignItems: "center",
42968
+ width: "max-content",
42969
+ maxWidth: 220,
42970
+ minWidth: 0,
42971
+ minHeight: 0,
42972
+ padding: 0,
42973
+ border: 0,
42974
+ background: "transparent",
42975
+ color: "var(--fc-text, #f8fafc)",
42976
+ cursor: "pointer",
42977
+ font: "inherit",
42978
+ lineHeight: 1,
42979
+ transform: "translate(8px, -50%)",
42980
+ userSelect: "none",
42981
+ WebkitUserSelect: "none",
42982
+ touchAction: "none"
42983
+ },
42984
+ children: [
42985
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
42986
+ "span",
42987
+ {
42988
+ style: {
42989
+ width: 18,
42990
+ height: 18,
42991
+ flex: "0 0 18px",
42992
+ borderRadius: 999,
42993
+ background: color,
42994
+ border: selected ? "2px solid #fff6d4" : "2px solid rgba(255,255,255,0.78)",
42995
+ boxShadow: selected ? "0 0 0 4px rgba(255, 246, 212, 0.18), 0 8px 20px rgba(0,0,0,0.32)" : "0 8px 20px rgba(0,0,0,0.32)"
42996
+ }
42997
+ }
42998
+ ),
42999
+ /* @__PURE__ */ jsxRuntimeExports.jsxs(
43000
+ "span",
43001
+ {
43002
+ style: {
43003
+ padding: "4px 7px",
43004
+ borderRadius: 4,
43005
+ border: selected ? `1px solid ${color}` : "1px solid rgba(255,255,255,0.14)",
43006
+ background: "rgba(18, 24, 27, 0.9)",
43007
+ boxShadow: "0 10px 22px rgba(0,0,0,0.24)",
43008
+ boxSizing: "border-box",
43009
+ display: "block",
43010
+ maxWidth: 176,
43011
+ minWidth: 0,
43012
+ overflow: "hidden",
43013
+ fontSize: 11,
43014
+ fontWeight: 700,
43015
+ lineHeight: 1.15,
43016
+ textAlign: "left",
43017
+ whiteSpace: "nowrap",
43018
+ textOverflow: "ellipsis",
43019
+ userSelect: "none",
43020
+ WebkitUserSelect: "none"
43021
+ },
43022
+ children: [
43023
+ /* @__PURE__ */ jsxRuntimeExports.jsx("span", { style: { display: "block", overflow: "hidden", textOverflow: "ellipsis" }, children: spatialParamAnchorLabel(item.name, item.anchor) }),
43024
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
43025
+ "span",
43026
+ {
43027
+ style: {
43028
+ display: "block",
43029
+ overflow: "hidden",
43030
+ textOverflow: "ellipsis",
43031
+ color: "var(--fc-textDim, #9ca3af)",
43032
+ fontSize: 10,
43033
+ fontWeight: 650
43034
+ },
43035
+ children: item.summary
43036
+ }
43037
+ )
43038
+ ]
43039
+ }
43040
+ )
43041
+ ]
43042
+ }
43043
+ )
43044
+ }
43045
+ );
43046
+ }
43047
+ function SpatialParamSheet({ item }) {
43048
+ const expandSpatialParamSheet = useForgeStore((s) => s.expandSpatialParamSheet);
43049
+ const closeSpatialParamSheet = useForgeStore((s) => s.closeSpatialParamSheet);
43050
+ useEscapeAction(
43051
+ () => {
43052
+ closeSpatialParamSheet();
43053
+ return true;
43054
+ },
43055
+ { active: true, label: "Spatial parameter sheet", priority: ESCAPE_PRIORITY.popover }
43056
+ );
43057
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
43058
+ Html,
43059
+ {
43060
+ position: spatialParamAnchorPosition(item.anchor),
43061
+ zIndexRange: [95, 0],
43062
+ style: { pointerEvents: "auto", userSelect: "none", WebkitUserSelect: "none" },
43063
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
43064
+ "div",
43065
+ {
43066
+ className: "fc-viewport-floating-panel fc-spatial-param-sheet",
43067
+ onPointerDown: stopSpatialPointerDown$1,
43068
+ onMouseDown: preventControlTextSelection$1,
43069
+ onWheel: stopSpatialWheel$1,
43070
+ style: {
43071
+ width: "clamp(320px, 34vw, 430px)",
43072
+ maxWidth: "calc(100vw - 32px)",
43073
+ maxHeight: "min(72vh, 560px)",
43074
+ transform: "translate(24px, -50%)",
43075
+ border: "1px solid var(--fc-floating-panel-border, var(--fc-border, #333))",
43076
+ borderRadius: 6,
43077
+ background: "var(--fc-floating-panel-bg, rgba(18, 24, 27, 0.96))",
43078
+ boxShadow: "0 18px 52px rgba(0,0,0,0.38)",
43079
+ boxSizing: "border-box",
43080
+ padding: 10,
43081
+ overflow: "hidden",
43082
+ color: "var(--fc-text)",
43083
+ userSelect: "none",
43084
+ WebkitUserSelect: "none"
43085
+ },
43086
+ children: [
43087
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", justifyContent: "space-between", gap: 10, alignItems: "center", marginBottom: 8 }, children: [
43088
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { minWidth: 0 }, children: [
43089
+ /* @__PURE__ */ jsxRuntimeExports.jsx("strong", { style: { display: "block", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontSize: 12 }, children: item.name }),
43090
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
43091
+ "span",
43092
+ {
43093
+ style: {
43094
+ display: "block",
43095
+ overflow: "hidden",
43096
+ textOverflow: "ellipsis",
43097
+ whiteSpace: "nowrap",
43098
+ fontSize: 10,
43099
+ color: "var(--fc-textDim)",
43100
+ marginTop: 1
43101
+ },
43102
+ children: spatialParamAnchorLabel(item.name, item.anchor)
43103
+ }
43104
+ )
43105
+ ] }),
43106
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", gap: 4 }, children: [
43107
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
43108
+ "button",
43109
+ {
43110
+ type: "button",
43111
+ onClick: () => expandSpatialParamSheet(item.name),
43112
+ style: {
43113
+ border: "1px solid var(--fc-border)",
43114
+ borderRadius: 4,
43115
+ background: "none",
43116
+ color: "var(--fc-textDim)",
43117
+ cursor: "pointer",
43118
+ fontSize: 11,
43119
+ lineHeight: "16px",
43120
+ padding: "2px 7px"
43121
+ },
43122
+ children: "Full"
43123
+ }
43124
+ ),
43125
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
43126
+ "button",
43127
+ {
43128
+ type: "button",
43129
+ onClick: closeSpatialParamSheet,
43130
+ style: {
43131
+ border: "1px solid var(--fc-border)",
43132
+ borderRadius: 4,
43133
+ background: "none",
43134
+ color: "var(--fc-textDim)",
43135
+ cursor: "pointer",
43136
+ fontSize: 11,
43137
+ lineHeight: "16px",
43138
+ padding: "2px 7px"
43139
+ },
43140
+ children: "Close"
43141
+ }
43142
+ )
43143
+ ] })
43144
+ ] }),
43145
+ item.pathDef && /* @__PURE__ */ jsxRuntimeExports.jsx(Path2DParamEditor, { pathDef: item.pathDef, allowFullScreen: false }),
43146
+ item.splineDef && /* @__PURE__ */ jsxRuntimeExports.jsx(Spline2DParamEditor, { curveDef: item.splineDef, allowFullScreen: false }),
43147
+ item.placementDef && /* @__PURE__ */ jsxRuntimeExports.jsx(Placement2DParamEditor, { layoutDef: item.placementDef, allowFullScreen: false })
43148
+ ]
43149
+ }
43150
+ )
43151
+ }
43152
+ );
43153
+ }
43154
+ function SpatialParamAnchorsOverlay() {
43155
+ const params = useForgeStore((s) => s.params);
43156
+ const stringParams = useForgeStore((s) => s.stringParams);
43157
+ const listParams = useForgeStore((s) => s.listParams);
43158
+ const path2dParams = useForgeStore((s) => s.path2dParams);
43159
+ const spline2dParams = useForgeStore((s) => s.spline2dParams);
43160
+ const placement2dParams = useForgeStore((s) => s.placement2dParams);
43161
+ const sheetName = useForgeStore((s) => s.spatialParamSheetName);
43162
+ const anchors = reactExports.useMemo(
43163
+ () => collectSpatialParamAnchors(params, stringParams, listParams, path2dParams, spline2dParams, placement2dParams),
43164
+ [params, stringParams, listParams, path2dParams, placement2dParams, spline2dParams]
43165
+ );
43166
+ const sheetItem = reactExports.useMemo(
43167
+ () => anchors.find((item) => item.name === sheetName && (item.pathDef || item.splineDef || item.placementDef)) ?? null,
43168
+ [anchors, sheetName]
43169
+ );
43170
+ if (anchors.length === 0) return null;
43171
+ return /* @__PURE__ */ jsxRuntimeExports.jsxs("group", { renderOrder: 13, children: [
43172
+ anchors.map((item) => /* @__PURE__ */ jsxRuntimeExports.jsx(SpatialParamMarker, { item }, item.name)),
43173
+ sheetItem && /* @__PURE__ */ jsxRuntimeExports.jsx(SpatialParamSheet, { item: sheetItem })
43174
+ ] });
43175
+ }
43176
+ function stopSpatialPointerDown(event) {
43177
+ event.stopPropagation();
43178
+ }
43179
+ function preventControlTextSelection(event) {
43180
+ const target = event.target;
43181
+ if (target instanceof HTMLElement && target.closest("input, textarea, select, button, canvas")) return;
43182
+ event.preventDefault();
43183
+ }
43184
+ function stopSpatialWheel(event) {
43185
+ event.stopPropagation();
43186
+ }
43187
+ function SpatialParamExpandedSheetOverlay() {
43188
+ const params = useForgeStore((s) => s.params);
43189
+ const stringParams = useForgeStore((s) => s.stringParams);
43190
+ const listParams = useForgeStore((s) => s.listParams);
43191
+ const path2dParams = useForgeStore((s) => s.path2dParams);
43192
+ const spline2dParams = useForgeStore((s) => s.spline2dParams);
43193
+ const placement2dParams = useForgeStore((s) => s.placement2dParams);
43194
+ const sheetName = useForgeStore((s) => s.expandedSpatialParamSheetName);
43195
+ const closeExpandedSpatialParamSheet = useForgeStore((s) => s.closeExpandedSpatialParamSheet);
43196
+ const anchors = reactExports.useMemo(
43197
+ () => collectSpatialParamAnchors(params, stringParams, listParams, path2dParams, spline2dParams, placement2dParams),
43198
+ [params, stringParams, listParams, path2dParams, placement2dParams, spline2dParams]
43199
+ );
43200
+ const item = reactExports.useMemo(
43201
+ () => anchors.find((anchor) => anchor.name === sheetName && (anchor.pathDef || anchor.splineDef || anchor.placementDef)) ?? null,
43202
+ [anchors, sheetName]
43203
+ );
43204
+ useEscapeAction(
43205
+ () => {
43206
+ closeExpandedSpatialParamSheet();
43207
+ return true;
43208
+ },
43209
+ { active: item !== null, label: "Expanded spatial parameter sheet", priority: ESCAPE_PRIORITY.modal }
43210
+ );
43211
+ if (!item) return null;
43212
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
43213
+ "div",
43214
+ {
43215
+ className: "fc-spatial-param-expanded-overlay",
43216
+ role: "dialog",
43217
+ "aria-modal": "true",
43218
+ "aria-label": `${item.name} spatial parameter editor`,
43219
+ style: {
43220
+ position: "fixed",
43221
+ inset: 0,
43222
+ zIndex: 2200,
43223
+ padding: 24,
43224
+ boxSizing: "border-box",
43225
+ display: "flex",
43226
+ background: "rgba(2, 7, 10, 0.58)",
43227
+ backdropFilter: "blur(2px)",
43228
+ WebkitBackdropFilter: "blur(2px)",
43229
+ pointerEvents: "auto",
43230
+ userSelect: "none",
43231
+ WebkitUserSelect: "none"
43232
+ },
43233
+ onPointerDown: stopSpatialPointerDown,
43234
+ onMouseDown: preventControlTextSelection,
43235
+ onWheel: stopSpatialWheel,
43236
+ onContextMenu: (event) => event.preventDefault(),
43237
+ children: /* @__PURE__ */ jsxRuntimeExports.jsxs(
43238
+ "div",
43239
+ {
43240
+ className: "fc-viewport-floating-panel fc-spatial-param-expanded-surface",
43241
+ style: {
43242
+ display: "flex",
43243
+ flexDirection: "column",
43244
+ width: "100%",
43245
+ minWidth: 0,
43246
+ minHeight: 0,
43247
+ border: "1px solid var(--fc-floating-panel-border, var(--fc-border))",
43248
+ borderRadius: 6,
43249
+ background: "var(--fc-bgPanel, #161b22)",
43250
+ boxShadow: "0 18px 52px rgba(0,0,0,0.42)",
43251
+ padding: 12,
43252
+ color: "var(--fc-text)",
43253
+ overflow: "hidden"
43254
+ },
43255
+ children: [
43256
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, marginBottom: 10 }, children: [
43257
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { minWidth: 0 }, children: [
43258
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 13, fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: item.name }),
43259
+ /* @__PURE__ */ jsxRuntimeExports.jsx("div", { style: { fontSize: 11, color: "var(--fc-textDim)", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: spatialParamAnchorLabel(item.name, item.anchor) })
43260
+ ] }),
43261
+ /* @__PURE__ */ jsxRuntimeExports.jsx(
43262
+ "button",
43263
+ {
43264
+ type: "button",
43265
+ onClick: closeExpandedSpatialParamSheet,
43266
+ style: {
43267
+ border: "1px solid var(--fc-border)",
43268
+ borderRadius: 4,
43269
+ background: "transparent",
43270
+ color: "var(--fc-textDim)",
43271
+ cursor: "pointer",
43272
+ fontSize: 12,
43273
+ padding: "3px 8px"
43274
+ },
43275
+ children: "Close"
43276
+ }
43277
+ )
43278
+ ] }),
43279
+ /* @__PURE__ */ jsxRuntimeExports.jsxs("div", { style: { minHeight: 0, overflow: "auto" }, children: [
43280
+ item.pathDef && /* @__PURE__ */ jsxRuntimeExports.jsx(Path2DParamEditor, { pathDef: item.pathDef, allowFullScreen: false, large: true }),
43281
+ item.splineDef && /* @__PURE__ */ jsxRuntimeExports.jsx(Spline2DParamEditor, { curveDef: item.splineDef, allowFullScreen: false, large: true }),
43282
+ item.placementDef && /* @__PURE__ */ jsxRuntimeExports.jsx(Placement2DParamEditor, { layoutDef: item.placementDef, allowFullScreen: false, large: true })
43283
+ ] })
43284
+ ]
43285
+ }
43286
+ )
43287
+ }
43288
+ );
43289
+ }
41340
43290
  const IDLE_STATE = {
41341
43291
  status: "idle",
41342
43292
  error: null,
@@ -41562,7 +43512,7 @@ function mergeResults(results, channel, analysisId) {
41562
43512
  class InspectWorkerClient {
41563
43513
  constructor(workerFactory = () => new Worker(new URL(
41564
43514
  /* @vite-ignore */
41565
- "/assets/inspectWorker-UXMxlcR8.js",
43515
+ "/assets/inspectWorker-Cuby2qfT.js",
41566
43516
  import.meta.url
41567
43517
  ), { type: "module" })) {
41568
43518
  __publicField(this, "reqId", 0);
@@ -41760,14 +43710,14 @@ function addCandidate(candidates, candidate) {
41760
43710
  }
41761
43711
  if (candidate.bboxOverlapVolume > candidates[smallestIndex].bboxOverlapVolume) candidates[smallestIndex] = candidate;
41762
43712
  }
41763
- async function collectCollisionCandidates(entries, maybeYield) {
43713
+ async function collectCollisionCandidates(entries2, maybeYield) {
41764
43714
  const candidates = [];
41765
43715
  let candidateCount = 0;
41766
43716
  let prunedPairCount = 0;
41767
- for (let sourceIndex = 0; sourceIndex < entries.length; sourceIndex += 1) {
41768
- const source = entries[sourceIndex];
41769
- for (let targetIndex = sourceIndex + 1; targetIndex < entries.length; targetIndex += 1) {
41770
- const target = entries[targetIndex];
43717
+ for (let sourceIndex = 0; sourceIndex < entries2.length; sourceIndex += 1) {
43718
+ const source = entries2[sourceIndex];
43719
+ for (let targetIndex = sourceIndex + 1; targetIndex < entries2.length; targetIndex += 1) {
43720
+ const target = entries2[targetIndex];
41771
43721
  if (!aabbOverlaps(source.worldBounds, target.worldBounds)) continue;
41772
43722
  const bboxOverlapVolume = aabbOverlapVolume(source.worldBounds, target.worldBounds);
41773
43723
  if (bboxOverlapVolume <= DEFAULT_COLLISION_INSPECTION_OPTIONS.minOverlapVolume) {
@@ -41814,7 +43764,7 @@ async function cloneCollisionShapePayloadForWorker(shape, maybeYield) {
41814
43764
  async function buildCollisionWorkerObjects(args) {
41815
43765
  var _a3;
41816
43766
  const warnings = [];
41817
- const entries = [];
43767
+ const entries2 = [];
41818
43768
  const maybeYield = createBuildYield(args.isCancelled);
41819
43769
  for (const obj of args.objects) {
41820
43770
  if (args.isCancelled()) throw new InspectBuildCancelledError();
@@ -41824,10 +43774,10 @@ async function buildCollisionWorkerObjects(args) {
41824
43774
  const local = localBounds(obj);
41825
43775
  const world = transformedBounds(obj, matrix);
41826
43776
  if (!local || !world) continue;
41827
- entries.push({ obj, matrix, localBounds: local, worldBounds: world });
43777
+ entries2.push({ obj, matrix, localBounds: local, worldBounds: world });
41828
43778
  await maybeYield();
41829
43779
  }
41830
- const { candidates, candidateCount, prunedPairCount } = await collectCollisionCandidates(entries, maybeYield);
43780
+ const { candidates, candidateCount, prunedPairCount } = await collectCollisionCandidates(entries2, maybeYield);
41831
43781
  if (candidateCount === 0) {
41832
43782
  return {
41833
43783
  objects: [],
@@ -41859,7 +43809,7 @@ async function buildCollisionWorkerObjects(args) {
41859
43809
  const out = [];
41860
43810
  let payloadBytes = 0;
41861
43811
  for (const entryIndex of selectedIndexes) {
41862
- const entry = entries[entryIndex];
43812
+ const entry = entries2[entryIndex];
41863
43813
  const collisionShape = await cloneCollisionShapePayloadForWorker(entry.obj.shape, maybeYield);
41864
43814
  const nextBytes = collisionPayloadBytes(collisionShape);
41865
43815
  if (payloadBytes + nextBytes > VIEWPORT_COLLISION_MAX_PAYLOAD_BYTES) {
@@ -42640,6 +44590,7 @@ function Viewport() {
42640
44590
  renderLabels,
42641
44591
  debugHighlights3D,
42642
44592
  dimensionsVisible,
44593
+ paramAnchorsVisible,
42643
44594
  attachmentsVisible,
42644
44595
  attachmentPoints,
42645
44596
  sectionPlaneGuidesEnabled,
@@ -43419,6 +45370,7 @@ function Viewport() {
43419
45370
  !rigInspectActive && hoveredJointOverlay && /* @__PURE__ */ jsxRuntimeExports.jsx(HoveredJointOverlay, { state: hoveredJointOverlay, config: jointOverlayConfig }),
43420
45371
  !rigInspectActive && dimensionsVisible && dimensions.map((d) => /* @__PURE__ */ jsxRuntimeExports.jsx(DimensionAnnotation, { def: d, lengthUnit }, d.id)),
43421
45372
  !rigInspectActive && /* @__PURE__ */ jsxRuntimeExports.jsx(RenderLabelsOverlay, { labels: renderLabels }),
45373
+ !rigInspectActive && paramAnchorsVisible && /* @__PURE__ */ jsxRuntimeExports.jsx(SpatialParamAnchorsOverlay, {}),
43422
45374
  !rigInspectActive && attachmentsVisible !== "none" && attachmentPoints.map((ap) => {
43423
45375
  const matrix = objectMatrices[ap.objectId];
43424
45376
  return matrix ? /* @__PURE__ */ jsxRuntimeExports.jsx("group", { matrixAutoUpdate: false, matrix, children: /* @__PURE__ */ jsxRuntimeExports.jsx(ConnectorAttachmentAnnotation, { def: ap }) }, `${ap.objectId}:${ap.name}`) : /* @__PURE__ */ jsxRuntimeExports.jsx(ConnectorAttachmentAnnotation, { def: ap }, `${ap.objectId}:${ap.name}`);
@@ -43629,6 +45581,7 @@ function Viewport() {
43629
45581
  }
43630
45582
  ),
43631
45583
  /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportOverlayHost, { entries: overlayEntries }),
45584
+ /* @__PURE__ */ jsxRuntimeExports.jsx(SpatialParamExpandedSheetOverlay, {}),
43632
45585
  viewportDisabledMessage && /* @__PURE__ */ jsxRuntimeExports.jsx(ViewportDisabledOverlay, { title: viewportDisabledMessage.title, body: viewportDisabledMessage.body }),
43633
45586
  drawFlagEnabled && /* @__PURE__ */ jsxRuntimeExports.jsx(DrawToolbar, {}),
43634
45587
  /* @__PURE__ */ jsxRuntimeExports.jsx(HoverTooltipLayer, { ref: hoverTooltipRef, enabled: objectPickSyncEnabled && !measureMode && !voxelIntentMode }),
@@ -44309,7 +46262,7 @@ function Viewport() {
44309
46262
  }
44310
46263
  );
44311
46264
  }
44312
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-DQJmcmRT.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46265
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-CYBDvSyT.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
44313
46266
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
44314
46267
  function storePendingShareCopy(shareId) {
44315
46268
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -44575,17 +46528,17 @@ function SeoMetadata() {
44575
46528
  }, [location.pathname]);
44576
46529
  return null;
44577
46530
  }
44578
- reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-DbE_tp8-.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
44579
- const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-DO1kvBns.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
44580
- reactExports.lazy(() => __vitePreload(() => import("./BlogPage-D7Dos-vl.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
44581
- reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-DP3RxhPs.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
44582
- reactExports.lazy(() => __vitePreload(() => import("./AdminPage-raksfnNA.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
46531
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-XYTiYxfM.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
46532
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-ClL6X1hR.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
46533
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-DIWRApKS.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
46534
+ reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-YZJbw5nd.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
46535
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-B1nIvqLS.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
44583
46536
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
44584
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-DLWcP289.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
44585
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-CcVIN9yj.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
44586
- reactExports.lazy(() => __vitePreload(() => import("./LegalPage-CominSso.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
44587
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-DQJmcmRT.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
44588
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-DFDUhOma.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
46537
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-D3bcPBsC.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
46538
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-BP4lIGio.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
46539
+ reactExports.lazy(() => __vitePreload(() => import("./LegalPage-D5Z3CscF.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
46540
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-CYBDvSyT.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46541
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-Dmfu_LIw.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
44589
46542
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
44590
46543
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg|dxf)$/i;
44591
46544
  function firstMeaningfulLine(text) {
@@ -44805,73 +46758,81 @@ function App() {
44805
46758
  applyTheme(localStorage.getItem("fc-theme") || "dark");
44806
46759
  clientExports.createRoot(document.getElementById("root")).render(/* @__PURE__ */ jsxRuntimeExports.jsx(App, {}));
44807
46760
  export {
44808
- VOXEL_INTENT_TOOL_ORDER as $,
46761
+ formatArea as $,
44809
46762
  AuthApiError as A,
44810
46763
  BrandMark as B,
44811
- captureViewportImageBlobFromStore as C,
44812
- exportExactFromStore as D,
44813
- storageQuotaUpgradeMessage as E,
46764
+ sanitizeExportStem as C,
46765
+ captureViewportImageBlobFromStore as D,
46766
+ ESCAPE_PRIORITY as E,
44814
46767
  FLAG_DEFINITIONS as F,
44815
- isImportableProjectMeshFile as G,
44816
- isImportableProjectBinaryFile as H,
44817
- hasExternalFiles as I,
44818
- isImportableProjectExactFile as J,
44819
- resolvePreviewFile as K,
44820
- countParamSnapshotDiff as L,
44821
- buildProjectShareUrl as M,
44822
- buildEmbedSnippet as N,
44823
- publishProjectShare as O,
44824
- unpublishProjectShare as P,
44825
- formatComputeBackendLabel as Q,
44826
- themes as R,
44827
- computeBackendFromParts as S,
44828
- formatArea as T,
44829
- sliderToAnimationSpeed as U,
44830
- animationSpeedToSlider as V,
44831
- formatAnimationSpeed as W,
44832
- resolveJointRange as X,
44833
- availableKernels as Y,
44834
- useJointsConfig as Z,
44835
- useJointAnimationValues as _,
46768
+ exportExactFromStore as G,
46769
+ storageQuotaUpgradeMessage as H,
46770
+ isImportableProjectMeshFile as I,
46771
+ isImportableProjectBinaryFile as J,
46772
+ hasExternalFiles as K,
46773
+ isImportableProjectExactFile as L,
46774
+ currentPath2DPoints as M,
46775
+ currentSpline2DPoints as N,
46776
+ currentPlacement2DItems as O,
46777
+ resolvePreviewFile as P,
46778
+ Path2DParamEditor as Q,
46779
+ Placement2DParamEditor as R,
46780
+ Spline2DParamEditor as S,
46781
+ countParamSnapshotDiff as T,
46782
+ buildProjectShareUrl as U,
46783
+ buildEmbedSnippet as V,
46784
+ publishProjectShare as W,
46785
+ unpublishProjectShare as X,
46786
+ formatComputeBackendLabel as Y,
46787
+ themes as Z,
46788
+ computeBackendFromParts as _,
44836
46789
  applyTheme as a,
44837
- VOXEL_INTENT_TOOL_LABELS as a0,
44838
- VOXEL_INTENT_TOOL_COLORS as a1,
44839
- VOXEL_INTENT_PLACEMENT_ORDER as a2,
44840
- VOXEL_INTENT_PLACEMENT_LABELS as a3,
44841
- highlightLanguageForProjectFile as a4,
44842
- hasProjectTextFileExtension as a5,
44843
- expandBoundsByTransformedAabb as a6,
44844
- Canvas as a7,
44845
- ActiveCameraBridge as a8,
44846
- PerspectiveCamera as a9,
44847
- buildShareUrl as aA,
44848
- share as aB,
44849
- ControlsInteractionBridge as aa,
44850
- SceneConfigurator as ab,
44851
- LocalEnvironment as ac,
44852
- ForgeObject as ad,
44853
- RenderLabelsOverlay as ae,
44854
- Grid as af,
44855
- OrbitControls2 as ag,
44856
- TOUCH_GESTURES_3D as ah,
44857
- MOUSE_BUTTONS_3D as ai,
44858
- ViewController as aj,
44859
- ModelJourneyBar as ak,
44860
- useJointAnimationLoop as al,
44861
- computeJointNodeMatrices as am,
44862
- computeObjectJointMatrices as an,
44863
- readLastActiveFileForUser as ao,
44864
- ToastContainer as ap,
44865
- isMobile as aq,
44866
- useFeatureFlag as ar,
44867
- decodeSharedHash as as,
44868
- decodeSharedBundle as at,
44869
- getExternalUrl as au,
44870
- getGistId as av,
44871
- Viewport as aw,
44872
- shouldBlockBrowserShortcut as ax,
44873
- useDrawStore as ay,
44874
- storePendingShareCopy as az,
46790
+ sliderToAnimationSpeed as a0,
46791
+ animationSpeedToSlider as a1,
46792
+ formatAnimationSpeed as a2,
46793
+ resolveJointRange as a3,
46794
+ availableKernels as a4,
46795
+ useJointsConfig as a5,
46796
+ useJointAnimationValues as a6,
46797
+ VOXEL_INTENT_TOOL_ORDER as a7,
46798
+ VOXEL_INTENT_TOOL_LABELS as a8,
46799
+ VOXEL_INTENT_TOOL_COLORS as a9,
46800
+ decodeSharedHash as aA,
46801
+ decodeSharedBundle as aB,
46802
+ getExternalUrl as aC,
46803
+ getGistId as aD,
46804
+ Viewport as aE,
46805
+ shouldBlockBrowserShortcut as aF,
46806
+ useDrawStore as aG,
46807
+ storePendingShareCopy as aH,
46808
+ buildShareUrl as aI,
46809
+ share as aJ,
46810
+ VOXEL_INTENT_PLACEMENT_ORDER as aa,
46811
+ VOXEL_INTENT_PLACEMENT_LABELS as ab,
46812
+ highlightLanguageForProjectFile as ac,
46813
+ hasProjectTextFileExtension as ad,
46814
+ expandBoundsByTransformedAabb as ae,
46815
+ Canvas as af,
46816
+ ActiveCameraBridge as ag,
46817
+ PerspectiveCamera as ah,
46818
+ ControlsInteractionBridge as ai,
46819
+ SceneConfigurator as aj,
46820
+ LocalEnvironment as ak,
46821
+ ForgeObject as al,
46822
+ RenderLabelsOverlay as am,
46823
+ Grid as an,
46824
+ OrbitControls2 as ao,
46825
+ TOUCH_GESTURES_3D as ap,
46826
+ MOUSE_BUTTONS_3D as aq,
46827
+ ViewController as ar,
46828
+ ModelJourneyBar as as,
46829
+ useJointAnimationLoop as at,
46830
+ computeJointNodeMatrices as au,
46831
+ computeObjectJointMatrices as av,
46832
+ readLastActiveFileForUser as aw,
46833
+ ToastContainer as ax,
46834
+ isMobile as ay,
46835
+ useFeatureFlag as az,
44875
46836
  authFetch as b,
44876
46837
  authApi as c,
44877
46838
  showToast as d,
@@ -44885,16 +46846,16 @@ export {
44885
46846
  fetchGistModel as l,
44886
46847
  monacoLanguageForProjectFile as m,
44887
46848
  fetchUrlModel as n,
44888
- exportMeshFromStore as o,
44889
- exportReportFromStore as p,
44890
- exportViewportImageFromStore as q,
46849
+ useEscapeAction as o,
46850
+ exportMeshFromStore as p,
46851
+ exportReportFromStore as q,
44891
46852
  readProjectFilesFromDataTransfer as r,
44892
46853
  storageUsagePercent as s,
44893
46854
  triggerDownload as t,
44894
46855
  useAuthStore as u,
44895
- exportOrbitVideoFromStore as v,
44896
- exportSketchFromStore as w,
44897
- buildGistShareUrl as x,
44898
- deriveExportStem as y,
44899
- sanitizeExportStem as z
46856
+ exportViewportImageFromStore as v,
46857
+ exportOrbitVideoFromStore as w,
46858
+ exportSketchFromStore as x,
46859
+ buildGistShareUrl as y,
46860
+ deriveExportStem as z
44900
46861
  };