@fps-games/editor 0.1.5-beta.5 → 0.1.7-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (469) hide show
  1. package/dist/agent-bridge/capabilities/assets.d.ts +8 -0
  2. package/dist/agent-bridge/capabilities/assets.d.ts.map +1 -0
  3. package/dist/agent-bridge/capabilities/assets.js +226 -0
  4. package/dist/agent-bridge/capabilities/assets.js.map +1 -0
  5. package/dist/agent-bridge/capabilities/editor-state.d.ts +5 -0
  6. package/dist/agent-bridge/capabilities/editor-state.d.ts.map +1 -0
  7. package/dist/agent-bridge/capabilities/editor-state.js +72 -0
  8. package/dist/agent-bridge/capabilities/editor-state.js.map +1 -0
  9. package/dist/agent-bridge/capabilities/host-lifecycle.d.ts +4 -0
  10. package/dist/agent-bridge/capabilities/host-lifecycle.d.ts.map +1 -0
  11. package/dist/agent-bridge/capabilities/host-lifecycle.js +87 -0
  12. package/dist/agent-bridge/capabilities/host-lifecycle.js.map +1 -0
  13. package/dist/agent-bridge/capabilities/prefabs.d.ts +5 -0
  14. package/dist/agent-bridge/capabilities/prefabs.d.ts.map +1 -0
  15. package/dist/agent-bridge/capabilities/prefabs.js +735 -0
  16. package/dist/agent-bridge/capabilities/prefabs.js.map +1 -0
  17. package/dist/agent-bridge/capabilities/scene-objects.d.ts +9 -0
  18. package/dist/agent-bridge/capabilities/scene-objects.d.ts.map +1 -0
  19. package/dist/agent-bridge/capabilities/scene-objects.js +394 -0
  20. package/dist/agent-bridge/capabilities/scene-objects.js.map +1 -0
  21. package/dist/agent-bridge/capabilities/selection.d.ts +5 -0
  22. package/dist/agent-bridge/capabilities/selection.d.ts.map +1 -0
  23. package/dist/agent-bridge/capabilities/selection.js +57 -0
  24. package/dist/agent-bridge/capabilities/selection.js.map +1 -0
  25. package/dist/agent-bridge/capabilities/shadows.d.ts +5 -0
  26. package/dist/agent-bridge/capabilities/shadows.d.ts.map +1 -0
  27. package/dist/agent-bridge/capabilities/shadows.js +1876 -0
  28. package/dist/agent-bridge/capabilities/shadows.js.map +1 -0
  29. package/dist/agent-bridge/capabilities/transform.d.ts +5 -0
  30. package/dist/agent-bridge/capabilities/transform.d.ts.map +1 -0
  31. package/dist/agent-bridge/capabilities/transform.js +162 -0
  32. package/dist/agent-bridge/capabilities/transform.js.map +1 -0
  33. package/dist/agent-bridge/capabilities/ui-regions.d.ts +4 -0
  34. package/dist/agent-bridge/capabilities/ui-regions.d.ts.map +1 -0
  35. package/dist/agent-bridge/capabilities/ui-regions.js +168 -0
  36. package/dist/agent-bridge/capabilities/ui-regions.js.map +1 -0
  37. package/dist/agent-bridge/capabilities/viewport.d.ts +5 -0
  38. package/dist/agent-bridge/capabilities/viewport.d.ts.map +1 -0
  39. package/dist/agent-bridge/capabilities/viewport.js +407 -0
  40. package/dist/agent-bridge/capabilities/viewport.js.map +1 -0
  41. package/dist/agent-bridge/capability-sources.d.ts +6 -0
  42. package/dist/agent-bridge/capability-sources.d.ts.map +1 -0
  43. package/dist/agent-bridge/capability-sources.js +26 -0
  44. package/dist/agent-bridge/capability-sources.js.map +1 -0
  45. package/dist/agent-bridge/diagnostics.d.ts +12 -0
  46. package/dist/agent-bridge/diagnostics.d.ts.map +1 -0
  47. package/dist/agent-bridge/diagnostics.js +45 -0
  48. package/dist/agent-bridge/diagnostics.js.map +1 -0
  49. package/dist/agent-bridge/index.d.ts +5 -0
  50. package/dist/agent-bridge/index.d.ts.map +1 -0
  51. package/dist/agent-bridge/index.js +5 -0
  52. package/dist/agent-bridge/index.js.map +1 -0
  53. package/dist/agent-bridge/register.d.ts +4 -0
  54. package/dist/agent-bridge/register.d.ts.map +1 -0
  55. package/dist/agent-bridge/register.js +908 -0
  56. package/dist/agent-bridge/register.js.map +1 -0
  57. package/dist/agent-bridge/shared.d.ts +189 -0
  58. package/dist/agent-bridge/shared.d.ts.map +1 -0
  59. package/dist/agent-bridge/shared.js +39 -0
  60. package/dist/agent-bridge/shared.js.map +1 -0
  61. package/dist/agent-bridge/source-types.d.ts +90 -0
  62. package/dist/agent-bridge/source-types.d.ts.map +1 -0
  63. package/dist/agent-bridge/source-types.js +2 -0
  64. package/dist/agent-bridge/source-types.js.map +1 -0
  65. package/dist/build-info.json +17 -17
  66. package/dist/local-editor-agent-actions.d.ts +8 -1
  67. package/dist/local-editor-agent-actions.d.ts.map +1 -1
  68. package/dist/local-editor-agent-actions.js +171 -12
  69. package/dist/local-editor-agent-actions.js.map +1 -1
  70. package/dist/local-editor-agent-bridge-transport.d.ts +95 -0
  71. package/dist/local-editor-agent-bridge-transport.d.ts.map +1 -0
  72. package/dist/local-editor-agent-bridge-transport.js +340 -0
  73. package/dist/local-editor-agent-bridge-transport.js.map +1 -0
  74. package/dist/local-editor-agent-bridge.d.ts +2 -0
  75. package/dist/local-editor-agent-bridge.d.ts.map +1 -0
  76. package/dist/local-editor-agent-bridge.js +2 -0
  77. package/dist/local-editor-agent-bridge.js.map +1 -0
  78. package/dist/local-editor-agent-context.d.ts +29 -1
  79. package/dist/local-editor-agent-context.d.ts.map +1 -1
  80. package/dist/local-editor-agent-context.js +403 -0
  81. package/dist/local-editor-agent-context.js.map +1 -1
  82. package/dist/local-editor-harness.d.ts +128 -6
  83. package/dist/local-editor-harness.d.ts.map +1 -1
  84. package/dist/local-editor-harness.js +2730 -151
  85. package/dist/local-editor-harness.js.map +1 -1
  86. package/dist/local-editor-marker-graph.d.ts +92 -0
  87. package/dist/local-editor-marker-graph.d.ts.map +1 -0
  88. package/dist/local-editor-marker-graph.js +1432 -0
  89. package/dist/local-editor-marker-graph.js.map +1 -0
  90. package/dist/local-editor-performance-stats.js +1 -1
  91. package/dist/local-editor-performance-stats.js.map +1 -1
  92. package/dist/playable-babylon-facade.d.ts +2 -2
  93. package/dist/playable-babylon-facade.d.ts.map +1 -1
  94. package/dist/playable-babylon-facade.js +1 -1
  95. package/dist/playable-babylon-facade.js.map +1 -1
  96. package/dist/playable-babylon-rendering.js +20 -0
  97. package/dist/playable-babylon-rendering.js.map +1 -1
  98. package/dist/playable-local-editor-host.d.ts +57 -7
  99. package/dist/playable-local-editor-host.d.ts.map +1 -1
  100. package/dist/playable-local-editor-host.js +25 -1
  101. package/dist/playable-local-editor-host.js.map +1 -1
  102. package/dist/playable-sdk.d.ts +4 -2
  103. package/dist/playable-sdk.d.ts.map +1 -1
  104. package/dist/playable-sdk.js +2 -1
  105. package/dist/playable-sdk.js.map +1 -1
  106. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-system.d.ts.map +1 -1
  107. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-system.js +6 -4
  108. package/node_modules/@fps-games/babylon-renderer/dist/shadows/blob-shadow-system.js.map +1 -1
  109. package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.d.ts.map +1 -1
  110. package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js +103 -1
  111. package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-shaders.js.map +1 -1
  112. package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.d.ts.map +1 -1
  113. package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js +330 -27
  114. package/node_modules/@fps-games/babylon-renderer/dist/shadows/planar-shadow-system.js.map +1 -1
  115. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-artifact-system.d.ts.map +1 -1
  116. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-artifact-system.js +159 -0
  117. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-artifact-system.js.map +1 -1
  118. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-baker.js +8 -1
  119. package/node_modules/@fps-games/babylon-renderer/dist/shadows/static-projected-shadow-baker.js.map +1 -1
  120. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts +53 -2
  121. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.d.ts.map +1 -1
  122. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js +1 -0
  123. package/node_modules/@fps-games/babylon-renderer/dist/shadows/types.js.map +1 -1
  124. package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
  125. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.d.ts.map +1 -1
  126. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js +11 -0
  127. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js.map +1 -1
  128. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +3 -1
  129. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
  130. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +114 -18
  131. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
  132. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +1 -0
  133. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
  134. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +3 -0
  135. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
  136. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +3 -0
  137. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
  138. package/node_modules/@fps-games/editor-babylon/dist/index.js +3 -0
  139. package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
  140. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts +52 -0
  141. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts.map +1 -0
  142. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js +1395 -0
  143. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js.map +1 -0
  144. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
  145. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +4 -65
  146. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
  147. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts +28 -0
  148. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts.map +1 -0
  149. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js +235 -0
  150. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js.map +1 -0
  151. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts +37 -0
  152. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts.map +1 -0
  153. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js +451 -0
  154. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js.map +1 -0
  155. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.d.ts.map +1 -1
  156. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js +14 -3
  157. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js.map +1 -1
  158. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts +1 -0
  159. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts.map +1 -1
  160. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js +20 -4
  161. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js.map +1 -1
  162. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts +3 -6
  163. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts.map +1 -1
  164. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js +4 -37
  165. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js.map +1 -1
  166. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +74 -1
  167. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
  168. package/node_modules/@fps-games/editor-babylon/dist/projection.js +641 -52
  169. package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
  170. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts +11 -0
  171. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.d.ts.map +1 -1
  172. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js +113 -29
  173. package/node_modules/@fps-games/editor-babylon/dist/scene-view-camera-controller.js.map +1 -1
  174. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts +13 -1
  175. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.d.ts.map +1 -1
  176. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js +543 -3
  177. package/node_modules/@fps-games/editor-babylon/dist/shadow-preview-controller.js.map +1 -1
  178. package/node_modules/@fps-games/editor-babylon/package.json +5 -5
  179. package/node_modules/@fps-games/editor-browser/dist/index.d.ts +8 -2
  180. package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
  181. package/node_modules/@fps-games/editor-browser/dist/index.js +4 -0
  182. package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
  183. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts +5 -0
  184. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts.map +1 -0
  185. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js +23 -0
  186. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js.map +1 -0
  187. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js +1 -1
  188. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-context-menu.js.map +1 -1
  189. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
  190. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +76 -9
  191. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
  192. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.d.ts.map +1 -1
  193. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js +3 -2
  194. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js.map +1 -1
  195. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-command-adapter.d.ts +21 -0
  196. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-command-adapter.d.ts.map +1 -0
  197. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-command-adapter.js +75 -0
  198. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-command-adapter.js.map +1 -0
  199. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +2 -0
  200. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
  201. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +326 -72
  202. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
  203. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.d.ts +5 -0
  204. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.d.ts.map +1 -0
  205. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.js +92 -0
  206. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-scope.js.map +1 -0
  207. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts +4 -1
  208. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.map +1 -1
  209. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js +22 -7
  210. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.js.map +1 -1
  211. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts +4 -0
  212. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
  213. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +24 -11
  214. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
  215. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +1 -1
  216. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
  217. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +7 -0
  218. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
  219. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js +1 -1
  220. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
  221. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +4 -0
  222. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
  223. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +1471 -29
  224. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
  225. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts +70 -0
  226. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts.map +1 -0
  227. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js +171 -0
  228. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js.map +1 -0
  229. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts +1 -1
  230. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts.map +1 -1
  231. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.js +2 -2
  232. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.js.map +1 -1
  233. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts.map +1 -1
  234. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +9 -2
  235. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js.map +1 -1
  236. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +301 -3
  237. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  238. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +12 -1
  239. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
  240. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +13 -1
  241. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
  242. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +6 -4
  243. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
  244. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +1501 -122
  245. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
  246. package/node_modules/@fps-games/editor-browser/package.json +3 -3
  247. package/node_modules/@fps-games/editor-core/dist/agent-bridge.d.ts +18 -0
  248. package/node_modules/@fps-games/editor-core/dist/agent-bridge.d.ts.map +1 -0
  249. package/node_modules/@fps-games/editor-core/dist/agent-bridge.js +102 -0
  250. package/node_modules/@fps-games/editor-core/dist/agent-bridge.js.map +1 -0
  251. package/node_modules/@fps-games/editor-core/dist/index.d.ts +1 -0
  252. package/node_modules/@fps-games/editor-core/dist/index.d.ts.map +1 -1
  253. package/node_modules/@fps-games/editor-core/dist/index.js +1 -0
  254. package/node_modules/@fps-games/editor-core/dist/index.js.map +1 -1
  255. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +1 -1
  256. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
  257. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +3 -1
  258. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -1
  259. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +1 -0
  260. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -1
  261. package/node_modules/@fps-games/editor-core/package.json +2 -2
  262. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  263. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/config.d.ts +3 -0
  264. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/config.d.ts.map +1 -0
  265. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/config.js +9 -0
  266. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/config.js.map +1 -0
  267. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/generated-module.d.ts +6 -0
  268. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/generated-module.d.ts.map +1 -0
  269. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/generated-module.js +89 -0
  270. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/generated-module.js.map +1 -0
  271. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/identity.d.ts +18 -0
  272. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/identity.d.ts.map +1 -0
  273. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/identity.js +121 -0
  274. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/identity.js.map +1 -0
  275. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/index.d.ts +4 -0
  276. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/index.d.ts.map +1 -0
  277. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/index.js +3 -0
  278. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/index.js.map +1 -0
  279. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/metadata.d.ts +27 -0
  280. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/metadata.d.ts.map +1 -0
  281. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/metadata.js +782 -0
  282. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/metadata.js.map +1 -0
  283. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/node.d.ts +5 -0
  284. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/node.d.ts.map +1 -0
  285. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/node.js +5 -0
  286. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/node.js.map +1 -0
  287. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/registry.d.ts +80 -0
  288. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/registry.d.ts.map +1 -0
  289. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/registry.js +600 -0
  290. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/registry.js.map +1 -0
  291. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/types.d.ts +51 -0
  292. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/types.d.ts.map +1 -0
  293. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/types.js +2 -0
  294. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-database/types.js.map +1 -0
  295. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.d.ts.map +1 -1
  296. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js +17 -3
  297. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js.map +1 -1
  298. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.d.ts.map +1 -1
  299. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js +2 -1
  300. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js.map +1 -1
  301. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts +3 -2
  302. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts.map +1 -1
  303. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts +1 -1
  304. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts.map +1 -1
  305. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js +48 -20
  306. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js.map +1 -1
  307. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts +1 -1
  308. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts.map +1 -1
  309. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js +1 -1
  310. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js.map +1 -1
  311. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/placement.d.ts.map +1 -1
  312. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/placement.js +2 -1
  313. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/placement.js.map +1 -1
  314. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/types.d.ts +3 -0
  315. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/types.d.ts.map +1 -1
  316. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts +97 -2
  317. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts.map +1 -1
  318. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js +1143 -8
  319. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js.map +1 -1
  320. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts +145 -2
  321. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts.map +1 -1
  322. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts +1 -1
  323. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts.map +1 -1
  324. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js +1 -1
  325. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js.map +1 -1
  326. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.d.ts.map +1 -1
  327. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js +24 -9
  328. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js.map +1 -1
  329. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts +6 -0
  330. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts.map +1 -1
  331. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js +31 -0
  332. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js.map +1 -1
  333. package/node_modules/@fps-games/editor-playable-sdk/dist/index.d.ts +3 -0
  334. package/node_modules/@fps-games/editor-playable-sdk/dist/index.d.ts.map +1 -1
  335. package/node_modules/@fps-games/editor-playable-sdk/dist/index.js +3 -0
  336. package/node_modules/@fps-games/editor-playable-sdk/dist/index.js.map +1 -1
  337. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.d.ts +82 -2
  338. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.d.ts.map +1 -1
  339. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js +396 -22
  340. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/document-sections.js.map +1 -1
  341. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.d.ts +1 -1
  342. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.d.ts.map +1 -1
  343. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.js +1 -1
  344. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/index.js.map +1 -1
  345. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/validation.d.ts.map +1 -1
  346. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/validation.js +0 -3
  347. package/node_modules/@fps-games/editor-playable-sdk/dist/inspector/validation.js.map +1 -1
  348. package/node_modules/@fps-games/editor-playable-sdk/dist/lighting-debug/math.d.ts +29 -1
  349. package/node_modules/@fps-games/editor-playable-sdk/dist/lighting-debug/math.d.ts.map +1 -1
  350. package/node_modules/@fps-games/editor-playable-sdk/dist/lighting-debug/math.js +143 -0
  351. package/node_modules/@fps-games/editor-playable-sdk/dist/lighting-debug/math.js.map +1 -1
  352. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.d.ts.map +1 -1
  353. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js +3 -7
  354. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js.map +1 -1
  355. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.d.ts.map +1 -1
  356. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js +6 -0
  357. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js.map +1 -1
  358. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts +2 -10
  359. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts.map +1 -1
  360. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts.map +1 -1
  361. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js +107 -11
  362. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js.map +1 -1
  363. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/reducer.d.ts.map +1 -1
  364. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/reducer.js +4 -1
  365. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/reducer.js.map +1 -1
  366. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/transform.d.ts.map +1 -1
  367. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/transform.js +19 -1
  368. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/transform.js.map +1 -1
  369. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.d.ts.map +1 -1
  370. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.js +19 -3
  371. package/node_modules/@fps-games/editor-playable-sdk/dist/rendering-profile/panel.js.map +1 -1
  372. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/assets.d.ts +1 -2
  373. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/assets.d.ts.map +1 -1
  374. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/assets.js +0 -14
  375. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/assets.js.map +1 -1
  376. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/import-plan.d.ts +1 -7
  377. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/import-plan.d.ts.map +1 -1
  378. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/import-plan.js +3 -32
  379. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/import-plan.js.map +1 -1
  380. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
  381. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +82 -63
  382. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
  383. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts +7 -23
  384. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts.map +1 -1
  385. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/compiler.d.ts +5 -0
  386. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/compiler.d.ts.map +1 -0
  387. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/compiler.js +996 -0
  388. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/compiler.js.map +1 -0
  389. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/index.d.ts +3 -0
  390. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/index.d.ts.map +1 -0
  391. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/index.js +3 -0
  392. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/index.js.map +1 -0
  393. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/types.d.ts +152 -0
  394. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/types.d.ts.map +1 -0
  395. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/types.js +2 -0
  396. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-compiler/types.js.map +1 -0
  397. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.d.ts.map +1 -1
  398. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js +44 -2
  399. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js.map +1 -1
  400. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts +1 -0
  401. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts.map +1 -1
  402. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js +244 -12
  403. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js.map +1 -1
  404. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.d.ts.map +1 -1
  405. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js +216 -67
  406. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js.map +1 -1
  407. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.d.ts.map +1 -1
  408. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js +7 -4
  409. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js.map +1 -1
  410. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/patch.d.ts.map +1 -1
  411. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/patch.js +45 -1
  412. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/patch.js.map +1 -1
  413. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.d.ts.map +1 -1
  414. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js +2 -1
  415. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js.map +1 -1
  416. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/index.d.ts +5 -0
  417. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/index.d.ts.map +1 -0
  418. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/index.js +5 -0
  419. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/index.js.map +1 -0
  420. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/legacy.d.ts +21 -0
  421. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/legacy.d.ts.map +1 -0
  422. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/legacy.js +158 -0
  423. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/legacy.js.map +1 -0
  424. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/quality.d.ts +5 -0
  425. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/quality.d.ts.map +1 -0
  426. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/quality.js +83 -0
  427. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/quality.js.map +1 -0
  428. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/resolver.d.ts +35 -0
  429. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/resolver.d.ts.map +1 -0
  430. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/resolver.js +363 -0
  431. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/resolver.js.map +1 -0
  432. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/types.d.ts +105 -0
  433. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/types.d.ts.map +1 -0
  434. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/types.js +6 -0
  435. package/node_modules/@fps-games/editor-playable-sdk/dist/shadow-system/types.js.map +1 -0
  436. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.d.ts +3 -0
  437. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.d.ts.map +1 -1
  438. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.js +44 -0
  439. package/node_modules/@fps-games/editor-playable-sdk/dist/static-shadows/artifact.js.map +1 -1
  440. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/asset-authoring.d.ts +26 -0
  441. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/asset-authoring.d.ts.map +1 -0
  442. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/asset-authoring.js +105 -0
  443. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/asset-authoring.js.map +1 -0
  444. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.d.ts +3 -0
  445. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.d.ts.map +1 -1
  446. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.js +3 -0
  447. package/node_modules/@fps-games/editor-playable-sdk/dist/vite/index.js.map +1 -1
  448. package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
  449. package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.d.ts +1127 -0
  450. package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.d.ts.map +1 -0
  451. package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.js +2180 -0
  452. package/node_modules/@fps-games/editor-protocol/dist/agent-bridge.js.map +1 -0
  453. package/node_modules/@fps-games/editor-protocol/dist/agent-trace.d.ts +68 -0
  454. package/node_modules/@fps-games/editor-protocol/dist/agent-trace.d.ts.map +1 -0
  455. package/node_modules/@fps-games/editor-protocol/dist/agent-trace.js +23 -0
  456. package/node_modules/@fps-games/editor-protocol/dist/agent-trace.js.map +1 -0
  457. package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts +57 -3
  458. package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts.map +1 -1
  459. package/node_modules/@fps-games/editor-protocol/dist/agent.js.map +1 -1
  460. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +21 -0
  461. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
  462. package/node_modules/@fps-games/editor-protocol/dist/index.js +3 -0
  463. package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
  464. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts +128 -0
  465. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts.map +1 -0
  466. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js +2 -0
  467. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js.map +1 -0
  468. package/node_modules/@fps-games/editor-protocol/package.json +1 -1
  469. package/package.json +8 -8
@@ -1,18 +1,65 @@
1
- import { createEditorSession, createInspectorRegistry, DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_VIEWPORT_TOOL_STATE, createEmptyEditorViewportSpatialOverlayState, cloneEditorTransformSnapshot as cloneCoreEditorTransformSnapshot, composeEditorTransformMatrix, editorTransformMatricesAlmostEqual, cloneEditorViewportToolState, cloneEditorViewportSpatialOverlayState, aggregateInspectorObjectsToMultiObject, createInspectorEditPayload, compareInspectorSections, computeEditorTransformActionTargets, mergeInspectorSections, isEditorTransformTrsSnapshot, normalizeEditorTransformConstraint, readEditorTransformPosition, resolveEditorSelectionCommand, serializedMultiObjectToInspectorObject, serializedObjectToInspectorObject, validateSceneGraphDelete, validateSceneGraphDrop, validateSceneGraphGroupSelection, validateSceneGraphMove, validateSceneGraphRename, } from '@fps-games/editor-core';
1
+ import { createEditorSession, createEditorAgentBridge, createInspectorRegistry, DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_VIEWPORT_TOOL_STATE, createEmptyEditorViewportSpatialOverlayState, cloneEditorTransformSnapshot as cloneCoreEditorTransformSnapshot, composeEditorTransformMatrix, editorTransformMatricesAlmostEqual, cloneEditorViewportToolState, cloneEditorViewportSpatialOverlayState, aggregateInspectorObjectsToMultiObject, createInspectorEditPayload, compareInspectorSections, computeEditorTransformActionTargets, mergeInspectorSections, isEditorTransformTrsSnapshot, normalizeEditorTransformConstraint, readEditorTransformPosition, resolveEditorSelectionCommand, serializedMultiObjectToInspectorObject, serializedObjectToInspectorObject, validateSceneGraphDelete, validateSceneGraphDrop, validateSceneGraphGroupSelection, validateSceneGraphMove, validateSceneGraphRename, } from '@fps-games/editor-core';
2
+ import {} from '@fps-games/editor-protocol';
2
3
  import { createLocalEditorBrowserUi, toTransformOperationPolicyStatusLabel, } from '@fps-games/editor-browser';
3
4
  import { createBabylonEnvironmentTextureController, createBabylonDefaultPostProcessPipelineController, } from '@fps-games/babylon-renderer';
4
- import { createBabylonEditorProjection, createBabylonEditorWorld, createBabylonProjectionSelectionController, createBabylonSceneCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonSceneViewMeasurementController, createBabylonSceneViewSpatialOverlayController, createBabylonEditorShadowPreviewController, createBabylonTransformGizmoController, createBabylonEditorPerformanceMonitor, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
5
+ import { createBabylonEditorProjection, createBabylonEditorMarkerOverlayController, createBabylonMarkerOverlayProjectionAdapter, createBabylonEditorWorld, createBabylonPrefabComparisonPreviewController, createBabylonProjectionSelectionController, createBabylonSceneCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonSceneViewMeasurementController, createBabylonSceneViewSpatialOverlayController, createBabylonEditorShadowPreviewController, createBabylonTransformGizmoController, createBabylonEditorPerformanceMonitor, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
5
6
  import { createLocalEditorSceneRenderScheduler, } from './local-editor-scene-render-scheduler.js';
6
7
  import { createPlayableBabylonOutlineInspectorSection, createPlayableBabylonOutlineInspectorSeedPatchValue, isPlayableBabylonOutlineInspectorPatchPath, } from './playable-babylon-outline-inspector.js';
7
8
  import { createLocalEditorViewportRenderCoordinator, } from './local-editor-viewport-render-coordinator.js';
8
9
  import { createLocalEditorSceneViewInteractionRuntime, } from './local-editor-scene-view-interaction-runtime.js';
9
10
  import { cloneLocalEditorPerformanceStats, countLocalEditorAuthoredObjects, createLocalEditorPerformanceStats, LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON, } from './local-editor-performance-stats.js';
11
+ import { registerLocalEditorAgentBridgeCapabilities } from './local-editor-agent-bridge.js';
12
+ import { createLocalEditorAgentBridgeCapabilitySources, } from './agent-bridge/capability-sources.js';
13
+ import { createDisabledLocalEditorAgentBridgeTransportStatus, createLocalEditorAgentBridgeWebSocketTransportFromQuery, } from './local-editor-agent-bridge-transport.js';
14
+ import { createMarkerGraphHierarchyItems, createMarkerGraphInspectorObject, createMarkerGraphRootInspectorObject, createMarkerPanelState, createMarkerRelationCommand, createMarkerRelationCreateManyCommand, createMarkerRelationDeleteConnectedCommand, createMarkerRelationReverseCommand, createMarkerGraphPatchFromInspectorEdit, findSpatialMarkerById, getSpatialMarkerDirectRelations, isMarkerGraphHierarchyId, isMarkerGraphHierarchyRootRowId, isSpatialMarkerBackedByHierarchyRow, isSpatialMarkerHierarchyVisible, readMarkerGraphHierarchyMarkerIdForGraph, readSpatialMarkerIdFromHierarchySelection, } from './local-editor-marker-graph.js';
10
15
  const BATCH_EDIT_REQUIRES_MULTI_PATCH_REASON = 'Batch edit requires multi-property patch support.';
11
16
  const LARGE_SELECTION_THRESHOLD = 200;
12
17
  const LARGE_SHADOW_MODE_BATCH_THRESHOLD = 200;
13
18
  const EDITOR_PROJECTION_IDLE_BEFORE_RUN_GAME_TIMEOUT_MS = 1500;
14
19
  const EDITOR_WORLD_DISPOSE_BOUNDARY_DELAY_MS = 500;
15
20
  const RENDERING_ACTION_PROGRESS_MIN_VISIBLE_MS = 1000;
21
+ const EMPTY_EDITOR_SELECTION = { selectedIds: [], activeId: null };
22
+ const MARKER_RELATION_HIGHLIGHT_RENDER_REASON = 'marker-relation-highlight';
23
+ const PREFAB_STAGE_STUDIO_WORLD_APPEARANCE = {
24
+ sky: {
25
+ preset: 'atmospheric',
26
+ topColor: { r: 0.68, g: 0.82, b: 0.9 },
27
+ horizonColor: { r: 0.98, g: 0.98, b: 0.93 },
28
+ bottomColor: { r: 0.44, g: 0.54, b: 0.53 },
29
+ cloudColor: { r: 1, g: 0.97, b: 0.86 },
30
+ cloudStrength: 0.1,
31
+ sunColor: { r: 1, g: 0.93, b: 0.72 },
32
+ sunDirection: { x: -0.24, y: 0.28, z: -0.93 },
33
+ },
34
+ };
35
+ const PREFAB_STAGE_STUDIO_ENVIRONMENT = {
36
+ textureUrl: 'https://assets.babylonjs.com/environments/studio.env',
37
+ intensity: 1,
38
+ rotationY: 0,
39
+ };
40
+ const PREFAB_STAGE_COMPARISON_PREVIEW_DEFINITIONS = [{
41
+ id: 'mesh-clone',
42
+ label: 'Mesh Clone',
43
+ mode: 'meshClone',
44
+ nodeIdSuffix: 'compare-clone',
45
+ }, {
46
+ id: 'instanced-mesh',
47
+ label: 'InstanceMesh',
48
+ mode: 'instancedMesh',
49
+ nodeIdSuffix: 'compare-instance',
50
+ }];
51
+ function createDefaultMarkerGraphVisibilityState() {
52
+ return {
53
+ rootVisible: true,
54
+ hiddenMarkerIds: new Set(),
55
+ };
56
+ }
57
+ function createEmptyMarkerCreateReservations() {
58
+ return {
59
+ ids: new Set(),
60
+ labels: new Set(),
61
+ };
62
+ }
16
63
  export function createLocalEditorHarness(options) {
17
64
  const root = options.root ?? document.body;
18
65
  const state = {
@@ -31,6 +78,16 @@ export function createLocalEditorHarness(options) {
31
78
  grid: null,
32
79
  gridVisible: true,
33
80
  projection: null,
81
+ previewVisibility: {
82
+ overrides: {},
83
+ isolatedObjectIds: [],
84
+ },
85
+ markerOverlay: null,
86
+ markerGraphVisibility: createDefaultMarkerGraphVisibilityState(),
87
+ activeMarkerRelationId: null,
88
+ markerRelationPick: null,
89
+ markerRelationHighlightContinuous: false,
90
+ markerCreateReservations: createEmptyMarkerCreateReservations(),
34
91
  shadowPreview: null,
35
92
  environmentTexture: null,
36
93
  postProcess: null,
@@ -45,6 +102,7 @@ export function createLocalEditorHarness(options) {
45
102
  sceneViewMeasurement: null,
46
103
  sceneViewSpatialOverlay: null,
47
104
  selectionController: null,
105
+ prefabComparisonPreview: null,
48
106
  sceneRenderScheduler: null,
49
107
  viewportRenderCoordinator: null,
50
108
  sceneFrameStats: null,
@@ -62,6 +120,7 @@ export function createLocalEditorHarness(options) {
62
120
  viewportTools: cloneEditorViewportToolState(DEFAULT_EDITOR_VIEWPORT_TOOL_STATE),
63
121
  viewportMeasurement: createEmptyViewportMeasurement(),
64
122
  viewportSpatialOverlay: createEmptyEditorViewportSpatialOverlayState(),
123
+ prefabStage: null,
65
124
  duplicateDrag: null,
66
125
  activeTransformPreview: null,
67
126
  armedPlacement: null,
@@ -72,6 +131,10 @@ export function createLocalEditorHarness(options) {
72
131
  statusDetails: '',
73
132
  renderingAction: null,
74
133
  summary: '',
134
+ agentBridge: createEditorAgentBridge(),
135
+ unregisterAgentBridgeCapabilities: null,
136
+ agentBridgeTransport: null,
137
+ agentBridgeStatus: createDisabledLocalEditorAgentBridgeTransportStatus(),
75
138
  };
76
139
  let harness;
77
140
  const ui = createLocalEditorBrowserUi({
@@ -116,6 +179,14 @@ export function createLocalEditorHarness(options) {
116
179
  if (handleBrowserAssetAction(state, options, input))
117
180
  harness.render();
118
181
  },
182
+ onExitPrefabStage: () => {
183
+ if (exitPrefabStage(state, options))
184
+ harness.render();
185
+ },
186
+ onSelectPrefabStageItem: (itemId) => {
187
+ if (selectPrefabStageItemWithPreviewSync(state, options, itemId))
188
+ harness.render();
189
+ },
119
190
  onSelectHierarchyItem: (input) => {
120
191
  selectItem(state, options, input, harness.render);
121
192
  },
@@ -123,6 +194,14 @@ export function createLocalEditorHarness(options) {
123
194
  if (patchHierarchyItemEnabled(state, options, input))
124
195
  harness.render();
125
196
  },
197
+ onMarkerTypeAction: (input) => {
198
+ if (patchMarkerTypeCatalog(state, options, input))
199
+ harness.render();
200
+ },
201
+ onMarkerRelationAction: (input) => {
202
+ if (patchMarkerRelation(state, options, input))
203
+ harness.render();
204
+ },
126
205
  onSelectionCommand: (command) => {
127
206
  dispatchSelectionCommand(state, options, command, harness.render);
128
207
  },
@@ -140,6 +219,12 @@ export function createLocalEditorHarness(options) {
140
219
  harness.render();
141
220
  }
142
221
  : undefined,
222
+ onSceneGraphCreateMarker: options.documentAdapter.createMarkerGraphPatch
223
+ ? (intent) => {
224
+ if (createSceneGraphMarker(state, options, intent))
225
+ harness.render();
226
+ }
227
+ : undefined,
143
228
  onSceneGraphDelete: (intent) => {
144
229
  if (deleteSceneGraphNodes(state, options, intent))
145
230
  harness.render();
@@ -181,6 +266,10 @@ export function createLocalEditorHarness(options) {
181
266
  if (patched || state.status !== previousStatus)
182
267
  harness.render();
183
268
  },
269
+ onInspectorAction: (input) => {
270
+ if (handleInspectorAction(state, options, input))
271
+ harness.render();
272
+ },
184
273
  onRenderingPropertyChange: options.documentAdapter.onRenderingPropertyChange
185
274
  ? (input) => {
186
275
  void applyRenderingPropertyChange(state, options, input)
@@ -201,7 +290,7 @@ export function createLocalEditorHarness(options) {
201
290
  }
202
291
  : undefined,
203
292
  onTransformToolChange: (tool) => {
204
- commitEditorStateChange(state, harness.render, 'transform-tool-change', () => {
293
+ commitEditorStateChange(state, options, harness.render, 'transform-tool-change', () => {
205
294
  state.transformTool = tool;
206
295
  state.transformConstraint = normalizeTransformConstraint(tool, state.transformConstraint);
207
296
  }, {
@@ -209,21 +298,21 @@ export function createLocalEditorHarness(options) {
209
298
  });
210
299
  },
211
300
  onTransformSpaceChange: (space) => {
212
- commitEditorStateChange(state, harness.render, 'transform-space-change', () => {
301
+ commitEditorStateChange(state, options, harness.render, 'transform-space-change', () => {
213
302
  state.transformSpace = space;
214
303
  }, {
215
304
  syncTransformArtifacts: true,
216
305
  });
217
306
  },
218
307
  onTransformConstraintChange: (constraint) => {
219
- commitEditorStateChange(state, harness.render, 'transform-constraint-change', () => {
308
+ commitEditorStateChange(state, options, harness.render, 'transform-constraint-change', () => {
220
309
  state.transformConstraint = normalizeTransformConstraint(state.transformTool, constraint);
221
310
  }, {
222
311
  syncTransformArtifacts: true,
223
312
  });
224
313
  },
225
314
  onTransformSnapEnabledChange: (enabled) => {
226
- commitEditorStateChange(state, harness.render, 'transform-operation-settings-change', () => {
315
+ commitEditorStateChange(state, options, harness.render, 'transform-operation-settings-change', () => {
227
316
  state.transformOperationSettings = updateTransformOperationSettings(state.transformOperationSettings, {
228
317
  snap: {
229
318
  ...state.transformOperationSettings.snap,
@@ -239,7 +328,7 @@ export function createLocalEditorHarness(options) {
239
328
  const value = normalizePositiveStep(input.value);
240
329
  if (value == null)
241
330
  return;
242
- commitEditorStateChange(state, harness.render, 'transform-operation-settings-change', () => {
331
+ commitEditorStateChange(state, options, harness.render, 'transform-operation-settings-change', () => {
243
332
  const snap = { ...state.transformOperationSettings.snap };
244
333
  if (input.kind === 'move')
245
334
  snap.moveStep = value;
@@ -254,7 +343,7 @@ export function createLocalEditorHarness(options) {
254
343
  });
255
344
  },
256
345
  onPlacementModeChange: (mode) => {
257
- commitEditorStateChange(state, harness.render, 'placement-mode-change', () => {
346
+ commitEditorStateChange(state, options, harness.render, 'placement-mode-change', () => {
258
347
  state.transformOperationSettings = updateTransformOperationSettings(state.transformOperationSettings, {
259
348
  placementMode: normalizePlacementMode(mode),
260
349
  });
@@ -270,7 +359,7 @@ export function createLocalEditorHarness(options) {
270
359
  harness.render();
271
360
  },
272
361
  onViewportViewPresetChange: (preset) => {
273
- if (setViewportViewPreset(state, preset))
362
+ if (setViewportViewPreset(state, options, preset))
274
363
  harness.render();
275
364
  },
276
365
  onViewportProjectionModeChange: (mode) => {
@@ -281,6 +370,10 @@ export function createLocalEditorHarness(options) {
281
370
  if (setViewportOverlaySettings(state, settings))
282
371
  harness.render();
283
372
  },
373
+ onMarkerViewportVisibilityChange: (visible) => {
374
+ if (setMarkerViewportVisibility(state, options, visible))
375
+ harness.render();
376
+ },
284
377
  onViewportUtilityToolChange: (tool) => {
285
378
  if (setViewportUtilityTool(state, tool))
286
379
  harness.render();
@@ -301,8 +394,20 @@ export function createLocalEditorHarness(options) {
301
394
  setLocalEditorPerformanceMonitorOpen(state, open);
302
395
  updateLocalEditorPerformanceStats(state, stats => ui.updatePerformanceStats?.(stats));
303
396
  },
397
+ ...{
398
+ onPrefabComparisonPreviewLayoutChange: () => {
399
+ harness.render();
400
+ },
401
+ onPrefabComparisonPreviewInteraction: (input) => {
402
+ if (!state.prefabStage)
403
+ return;
404
+ if (state.prefabComparisonPreview?.adjustView(input)) {
405
+ requestEditorSceneFrame(state, 'prefab-comparison-preview-interaction');
406
+ }
407
+ },
408
+ },
304
409
  onFocusSelection: () => {
305
- if (focusSelectedProjection(state))
410
+ if (focusSelectedProjection(state, options))
306
411
  harness.render();
307
412
  },
308
413
  onCancelEditorIntent: () => {
@@ -333,11 +438,12 @@ export function createLocalEditorHarness(options) {
333
438
  };
334
439
  harness = {
335
440
  render() {
336
- syncSceneCameraPreview(state, options);
337
441
  syncViewportCameraState(state);
338
442
  syncViewportMeasurementState(state);
339
- syncViewportSpatialOverlay(state);
443
+ syncViewportSpatialOverlay(state, options);
340
444
  ui.update(createUiState(state, options));
445
+ syncPrefabComparisonPreviewSurfaces(state, ui);
446
+ syncSceneCameraPreview(state, options);
341
447
  },
342
448
  notifyViewportRevealed(reason = 'viewport-revealed') {
343
449
  state.viewportRenderCoordinator?.requestRevealFrame(reason);
@@ -351,6 +457,9 @@ export function createLocalEditorHarness(options) {
351
457
  getHostServices() {
352
458
  return options.hostServices ?? null;
353
459
  },
460
+ getAgentBridge() {
461
+ return state.agentBridge;
462
+ },
354
463
  getWorkingDocument() {
355
464
  return state.session?.getState().workingDocument ?? null;
356
465
  },
@@ -412,6 +521,10 @@ export function createLocalEditorHarness(options) {
412
521
  const preparedDocument = options.documentAdapter.prepareDocument?.(document, assets) ?? document;
413
522
  state.assets = assets;
414
523
  state.source = source;
524
+ state.markerGraphVisibility = createDefaultMarkerGraphVisibilityState();
525
+ state.activeMarkerRelationId = null;
526
+ state.markerRelationHighlightContinuous = false;
527
+ state.markerCreateReservations = createEmptyMarkerCreateReservations();
415
528
  state.session = createEditorSession({
416
529
  source: source ?? undefined,
417
530
  persistedDocument: preparedDocument,
@@ -425,7 +538,8 @@ export function createLocalEditorHarness(options) {
425
538
  ui.updatePreview(previewState);
426
539
  else
427
540
  ui.update(createUiState(state, options));
428
- }, stats => ui.updateSceneFrameStats?.(stats), stats => ui.updatePerformanceStats?.(stats));
541
+ syncPrefabComparisonPreviewSurfaces(state, ui);
542
+ }, stats => ui.updateSceneFrameStats?.(stats), stats => ui.updatePerformanceStats?.(stats), () => ui.getPrefabComparisonPreviewSurfaces?.() ?? []);
429
543
  state.mode = 'editor';
430
544
  state.summary = loadedSource?.summary ?? summarizeDocument(options, preparedDocument, source);
431
545
  state.status = `GameWorld disposed; EditorWorld active; assets=${assets.length}`;
@@ -499,11 +613,102 @@ export function createLocalEditorHarness(options) {
499
613
  await runGameFromEditor('discard');
500
614
  },
501
615
  dispose() {
616
+ state.agentBridgeTransport?.dispose();
617
+ state.agentBridgeTransport = null;
618
+ state.unregisterAgentBridgeCapabilities?.();
619
+ state.unregisterAgentBridgeCapabilities = null;
502
620
  disposeEditorWorld(state);
503
621
  state.session = null;
504
622
  ui.dispose();
505
623
  },
506
624
  };
625
+ const agentBridgeCapabilitySourceOperations = {
626
+ createAssetFromAssetId: (assetId, createOptions) => harness.createAssetFromAssetId(assetId, createOptions),
627
+ getPrefabStage: () => summarizeAgentBridgePrefabStage(state, options),
628
+ getPrefabStageDetails: () => summarizeAgentBridgePrefabStageDetails(state, options),
629
+ openPrefabStage: (input) => {
630
+ const previousStage = summarizeAgentBridgePrefabStage(state, options);
631
+ const opened = enterPrefabStage(state, options, {
632
+ actionId: 'asset.edit-prefab',
633
+ assetId: input.assetId,
634
+ browserAssetId: input.browserAssetId,
635
+ assetKind: 'prefab',
636
+ });
637
+ const stage = summarizeAgentBridgePrefabStage(state, options);
638
+ if (!opened || !stage)
639
+ return null;
640
+ const alreadyOpen = isSameAgentBridgePrefabStage(previousStage, stage);
641
+ return {
642
+ opened: !alreadyOpen,
643
+ alreadyOpen,
644
+ prefab: createAgentBridgePrefabSummaryFromStage(stage),
645
+ stage,
646
+ status: state.status,
647
+ };
648
+ },
649
+ exitPrefabStage: () => {
650
+ const previousStage = summarizeAgentBridgePrefabStage(state, options);
651
+ const exited = exitPrefabStage(state, options);
652
+ return {
653
+ exited,
654
+ wasOpen: !!previousStage,
655
+ ...(previousStage ? { previousStage } : {}),
656
+ status: exited ? state.status : 'No Prefab stage is open.',
657
+ };
658
+ },
659
+ selectPrefabStageItem: itemId => selectPrefabStageItemWithPreviewSync(state, options, itemId),
660
+ runPrefabStageAssetAction: input => patchBrowserAssetAction(state, options, input),
661
+ getHierarchy: () => createUiState(state, options).hierarchy,
662
+ dispatchSelectionCommand: (command, shouldRenderUi = false) => dispatchSelectionCommand(state, options, command, shouldRenderUi ? () => harness.render() : undefined),
663
+ focusProjectionNode: objectId => frameViewportProjectionNode(state, objectId),
664
+ focusSelectedProjection: () => frameSelectedViewportProjection(state, options),
665
+ syncViewportCameraState: () => syncViewportCameraState(state),
666
+ requestSceneFrame: reason => requestEditorSceneFrame(state, reason),
667
+ cloneTransformSnapshot: transform => cloneEditorTransformSnapshot(transform),
668
+ cancelActiveOperation: () => cancelActiveOperation(state),
669
+ withProjectionImpact,
670
+ resolveProjectionImpact,
671
+ createReprojectProjectionImpact,
672
+ createTransformProjectionImpact,
673
+ createKnownCreatedReprojectProjectionImpact,
674
+ createDeletedNodeProjectionImpact,
675
+ reprojectProjectionForChangedIds: (document, changedIds) => reprojectProjectionForChangedIds(state, options, document, changedIds),
676
+ syncProjectionForChangedIds: (document, changedIds) => syncProjectionForChangedIds(state, options, document, changedIds),
677
+ rebuildProjectionFromDocument: (document, selection) => rebuildProjectionFromDocument(state, options, document, selection),
678
+ sanitizeSelection: (document, selection) => sanitizeSelection(state, options, document, selection),
679
+ summarizeDocument: (document, source) => summarizeDocument(options, document, source),
680
+ invalidateStaticShadowArtifact: () => {
681
+ if (!state.shadowPreview?.invalidateStaticProjectedArtifact)
682
+ return false;
683
+ state.shadowPreview.invalidateStaticProjectedArtifact();
684
+ requestEditorSceneFrame(state, 'agent-shadow-static-invalidate');
685
+ return true;
686
+ },
687
+ getShadowPreviewDebugInfo: () => state.shadowPreview?.getDebugInfo?.() ?? null,
688
+ setShadowDebugOverlay: (items) => {
689
+ const overlayIds = state.projection?.setShadowDebugOverlay?.(items) ?? [];
690
+ requestEditorSceneFrame(state, 'agent-shadow-debug-overlay-set');
691
+ return overlayIds;
692
+ },
693
+ clearShadowDebugOverlay: () => {
694
+ const cleared = state.projection?.clearShadowDebugOverlay?.() ?? false;
695
+ if (cleared)
696
+ requestEditorSceneFrame(state, 'agent-shadow-debug-overlay-clear');
697
+ return cleared;
698
+ },
699
+ resolvePrefabStageProjectionNodeId: (nodeId) => {
700
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument;
701
+ return document ? resolvePrefabStageProjectionNodeIdForNode(state, options, document, nodeId) : null;
702
+ },
703
+ runRenderingAction: actionId => applyRenderingAction(state, options, { actionId }, () => harness.render()),
704
+ };
705
+ state.unregisterAgentBridgeCapabilities = registerLocalEditorAgentBridgeCapabilities(state.agentBridge, createLocalEditorAgentBridgeCapabilitySources(state, options, () => harness.render(), () => runExclusive(state, harness.render, () => harness.enterEditor()), agentBridgeCapabilitySourceOperations));
706
+ state.agentBridgeTransport = createLocalEditorAgentBridgeWebSocketTransportFromQuery(state.agentBridge, {
707
+ onStatusChange: agentBridgeStatus => {
708
+ state.agentBridgeStatus = agentBridgeStatus;
709
+ harness.render();
710
+ },
711
+ });
507
712
  harness.render();
508
713
  return harness;
509
714
  }
@@ -533,6 +738,13 @@ export function mergeLocalEditorHarnessInspectorComponentSections(input) {
533
738
  }),
534
739
  };
535
740
  }
741
+ function isAgentBridgeObjectSelectable(options, document, objectId, hierarchyItems) {
742
+ const inHierarchy = hierarchyItems.some(item => item.id === objectId);
743
+ const hasProjectionNode = !!options.documentAdapter.getProjectionNode(document, objectId);
744
+ if (!inHierarchy && !hasProjectionNode)
745
+ return false;
746
+ return isNodeSelectableInDocument(options, document, objectId);
747
+ }
536
748
  function normalizeTransformConstraint(tool, constraint) {
537
749
  return normalizeEditorTransformConstraint(tool, constraint) ?? 'axis';
538
750
  }
@@ -568,7 +780,16 @@ function normalizePlacementMode(mode) {
568
780
  return mode === 'ground' || mode === 'surface' ? mode : 'off';
569
781
  }
570
782
  function normalizeViewportViewPreset(preset) {
571
- return preset === 'top' || preset === 'front' || preset === 'right' ? preset : 'perspective';
783
+ return preset === 'top'
784
+ || preset === 'bottom'
785
+ || preset === 'front'
786
+ || preset === 'back'
787
+ || preset === 'left'
788
+ || preset === 'right'
789
+ || preset === 'iso'
790
+ || preset === 'threeQuarterTop'
791
+ ? preset
792
+ : 'perspective';
572
793
  }
573
794
  function normalizeViewportProjectionMode(mode) {
574
795
  return mode === 'orthographic' ? 'orthographic' : 'perspective';
@@ -599,7 +820,7 @@ function cloneViewportMeasurement(measurement) {
599
820
  label: measurement.label ? { ...measurement.label } : null,
600
821
  };
601
822
  }
602
- function setViewportViewPreset(state, preset) {
823
+ function setViewportViewPreset(state, options, preset) {
603
824
  if (state.sceneCameraPreviewEnabled) {
604
825
  state.status = 'Viewport view unavailable during Scene Camera preview';
605
826
  state.statusTone = 'warning';
@@ -608,8 +829,8 @@ function setViewportViewPreset(state, preset) {
608
829
  return true;
609
830
  }
610
831
  const nextPreset = normalizeViewportViewPreset(preset);
611
- const target = resolveViewportPresetTarget(state);
612
- const radius = resolveViewportPresetRadius(state);
832
+ const target = resolveViewportPresetTarget(state, options);
833
+ const radius = resolveViewportPresetRadius(state, options);
613
834
  const changed = state.sceneViewCamera?.setViewPreset(nextPreset, { target, radius }) ?? false;
614
835
  if (!changed)
615
836
  return false;
@@ -667,6 +888,25 @@ function setViewportOverlaySettings(state, settings) {
667
888
  state.statusDetails = '';
668
889
  return true;
669
890
  }
891
+ function setMarkerViewportVisibility(state, options, visible) {
892
+ const nextVisible = visible !== false;
893
+ if ((state.viewportTools.markersVisible ?? true) === nextVisible)
894
+ return false;
895
+ state.viewportTools = {
896
+ ...state.viewportTools,
897
+ markersVisible: nextVisible,
898
+ };
899
+ state.status = nextVisible ? 'Marker view shown' : 'Marker view hidden';
900
+ state.statusTone = 'default';
901
+ state.statusToneStatus = state.status;
902
+ state.statusDetails = nextVisible
903
+ ? 'Marker overlays and relation lines are visible in the editor viewport.'
904
+ : 'Marker overlays and relation lines are temporarily hidden in the editor viewport.';
905
+ state.editorStateRevision += 1;
906
+ syncCurrentSelectionToSceneArtifacts(state, options, 'marker-viewport-visibility', state.editorStateRevision);
907
+ requestEditorSceneFrame(state, 'marker-viewport-visibility');
908
+ return true;
909
+ }
670
910
  function setViewportUtilityTool(state, tool) {
671
911
  const nextTool = tool === 'measure-distance' ? 'measure-distance' : 'none';
672
912
  state.viewportTools = {
@@ -769,18 +1009,18 @@ function formatViewportMeasurementDistance(value) {
769
1009
  const rounded = Math.abs(value) < 0.005 ? 0 : value;
770
1010
  return rounded.toFixed(2).replace(/\.?0+$/, '');
771
1011
  }
772
- function resolveViewportPresetTarget(state) {
773
- const selection = state.session?.getState().selection;
774
- const activeId = selection?.activeId ?? null;
775
- if (!activeId || selection?.selectedIds.length !== 1)
1012
+ function resolveViewportPresetTarget(state, options) {
1013
+ const selection = getCurrentPreviewSelection(state, options);
1014
+ const activeId = selection.activeId;
1015
+ if (!activeId || selection.selectedIds.length !== 1)
776
1016
  return null;
777
1017
  const bounds = state.projection?.getSelectionBounds([activeId]) ?? null;
778
1018
  return bounds?.center ?? null;
779
1019
  }
780
- function resolveViewportPresetRadius(state) {
781
- const selection = state.session?.getState().selection;
782
- const activeId = selection?.activeId ?? null;
783
- if (!activeId || selection?.selectedIds.length !== 1)
1020
+ function resolveViewportPresetRadius(state, options) {
1021
+ const selection = getCurrentPreviewSelection(state, options);
1022
+ const activeId = selection.activeId;
1023
+ if (!activeId || selection.selectedIds.length !== 1)
784
1024
  return undefined;
785
1025
  const bounds = state.projection?.getSelectionBounds([activeId]) ?? null;
786
1026
  if (!bounds)
@@ -801,7 +1041,7 @@ function validateTransformActionSelection(state, action) {
801
1041
  ? { ok: true }
802
1042
  : { ok: false, message: 'Distribute needs at least 3 selected objects' };
803
1043
  }
804
- async function createEditorWorld(state, options, renderUi, renderPreviewUi, updateSceneFrameStats, updatePerformanceStats) {
1044
+ async function createEditorWorld(state, options, renderUi, renderPreviewUi, updateSceneFrameStats, updatePerformanceStats, getPrefabComparisonPreviewSurfaces) {
805
1045
  disposeEditorWorld(state);
806
1046
  const render = (reason = 'editor-world-update') => {
807
1047
  renderUi();
@@ -816,9 +1056,9 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
816
1056
  const babylon = await options.worldAdapter.loadBabylon();
817
1057
  const engine = options.worldAdapter.createEngine(babylon, canvas);
818
1058
  const document = state.session?.getState().workingDocument;
819
- const initialAppearance = resolveLocalEditorWorldAppearance(options, document);
1059
+ const initialAppearance = resolveEffectiveLocalEditorWorldAppearance(state, options, document);
820
1060
  const initialAppearanceKey = serializeLocalEditorWorldAppearance(initialAppearance);
821
- const initialRendering = resolveLocalEditorWorldRendering(options, document);
1061
+ const initialRendering = resolveEffectiveLocalEditorWorldRendering(state, options, document);
822
1062
  const initialRenderingKey = serializeLocalEditorWorldRendering(initialRendering);
823
1063
  const world = createBabylonEditorWorld({
824
1064
  engine,
@@ -834,6 +1074,8 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
834
1074
  const performanceMonitor = createBabylonEditorPerformanceMonitor(world.scene);
835
1075
  const sceneRenderScheduler = createLocalEditorSceneRenderScheduler((frame) => {
836
1076
  state.sceneViewInteraction?.updateFrame(frame);
1077
+ syncPrefabComparisonPreviewSurfaces(state, { getPrefabComparisonPreviewSurfaces });
1078
+ state.prefabComparisonPreview?.prepareFrame();
837
1079
  world.render();
838
1080
  }, {
839
1081
  onStatsChange(stats) {
@@ -867,19 +1109,46 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
867
1109
  selectionEdgesPrewarm: options.world?.selectionEdgesPrewarm,
868
1110
  logger: console,
869
1111
  onProjectionReady(event) {
870
- syncCurrentSelectionToSceneArtifacts(state, `projection-${event.nodeId}-ready-selection`);
1112
+ if (state.prefabStage) {
1113
+ syncPrefabStageSelectionToProjection(state, options, `prefab-stage-projection-${event.nodeId}-ready-selection`);
1114
+ focusPrefabStagePreviewProjection(state, `prefab-stage-projection-${event.nodeId}-ready-focus`);
1115
+ scheduleProjectionShadowPreviewRebuild(state, `prefab-stage-projection-${event.nodeId}-ready-shadow`);
1116
+ viewportRenderCoordinator.invalidateScene(`prefab-stage-projection-${event.nodeId}-ready`);
1117
+ renderUi();
1118
+ return;
1119
+ }
1120
+ syncCurrentSelectionToSceneArtifacts(state, options, `projection-${event.nodeId}-ready-selection`);
871
1121
  scheduleProjectionShadowPreviewRebuild(state, `projection-${event.nodeId}-ready-shadow`);
872
1122
  viewportRenderCoordinator.invalidateScene(`projection-${event.nodeId}-ready`);
873
1123
  },
874
1124
  onProjectionBatchSettled(event) {
875
- syncCurrentSelectionToSceneArtifacts(state, `projection-batch-${event.batchId}-settled-selection`);
1125
+ if (state.prefabStage) {
1126
+ syncPrefabStageSelectionToProjection(state, options, `prefab-stage-projection-batch-${event.batchId}-settled-selection`);
1127
+ focusPrefabStagePreviewProjection(state, `prefab-stage-projection-batch-${event.batchId}-settled-focus`);
1128
+ scheduleProjectionShadowPreviewRebuild(state, `prefab-stage-projection-batch-${event.batchId}-settled`);
1129
+ renderUi();
1130
+ return;
1131
+ }
1132
+ syncCurrentSelectionToSceneArtifacts(state, options, `projection-batch-${event.batchId}-settled-selection`);
876
1133
  scheduleProjectionShadowPreviewRebuild(state, `projection-batch-${event.batchId}-settled`);
877
1134
  },
878
1135
  });
879
- const gizmo = createBabylonTransformGizmoController({
1136
+ const prefabComparisonPreview = createBabylonPrefabComparisonPreviewController({
880
1137
  babylon,
881
1138
  scene: world.scene,
1139
+ editorCamera: world.camera,
882
1140
  projection,
1141
+ getSkyBackdrop: () => world.skyBackdrop,
1142
+ });
1143
+ const markerOverlay = createBabylonEditorMarkerOverlayController({
1144
+ babylon,
1145
+ scene: world.scene,
1146
+ });
1147
+ const markerAwareProjection = createBabylonMarkerOverlayProjectionAdapter(projection, markerOverlay);
1148
+ const gizmo = createBabylonTransformGizmoController({
1149
+ babylon,
1150
+ scene: world.scene,
1151
+ projection: markerAwareProjection,
883
1152
  initialTool: state.transformTool,
884
1153
  initialSpace: state.transformSpace,
885
1154
  documentCapabilities: options.transformDocumentCapabilities,
@@ -897,6 +1166,8 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
897
1166
  },
898
1167
  onDragUpdate(event) {
899
1168
  updateActiveTransformPreview(state, event);
1169
+ syncMarkerOverlayToSceneArtifacts(state, options, state.session?.getState().selection ?? { selectedIds: [], activeId: null });
1170
+ state.gizmo?.refreshSelection();
900
1171
  preview('gizmo-drag-update');
901
1172
  },
902
1173
  onDragEnd(event) {
@@ -931,24 +1202,48 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
931
1202
  renderUi();
932
1203
  },
933
1204
  onDuplicateDragStart(input) {
1205
+ if (state.prefabStage) {
1206
+ state.status = 'Duplicate drag is not available in Prefab Stage';
1207
+ state.statusTone = 'warning';
1208
+ state.statusToneStatus = state.status;
1209
+ state.statusDetails = 'Use prefab.node.createFromAsset or prefab.node.createPrimitive to add prefab nodes.';
1210
+ renderUi();
1211
+ return null;
1212
+ }
934
1213
  return beginDuplicateDrag(state, options, input);
935
1214
  },
936
1215
  });
937
- gizmo.setOperationSettings(state.transformOperationSettings);
1216
+ gizmo.setOperationSettings(getScopedTransformOperationSettings(state));
938
1217
  gizmo.setConstraint(state.transformConstraint);
939
1218
  const selectionController = createBabylonProjectionSelectionController({
940
1219
  scene: world.scene,
941
1220
  canvas,
942
- projection,
1221
+ projection: markerAwareProjection,
943
1222
  getTool: () => state.transformTool,
944
- getSelection: () => state.session?.getSelection() ?? { selectedIds: [], activeId: null },
945
- isSelectable: (nodeId) => isDocumentNodeSelectable(state, options, nodeId),
946
- isLocked: (nodeId) => isDocumentNodeLocked(state, options, nodeId),
1223
+ getSelection: () => state.prefabStage
1224
+ ? getCurrentPrefabStageProjectionSelection(state, options)
1225
+ : state.session?.getSelection() ?? EMPTY_EDITOR_SELECTION,
1226
+ isSelectable: (nodeId) => state.prefabStage
1227
+ ? isPrefabStageProjectionSelectable(state, options, nodeId)
1228
+ : isDocumentNodeSelectable(state, options, nodeId),
1229
+ isLocked: (nodeId) => state.prefabStage
1230
+ ? !isPrefabStageProjectionSelectable(state, options, nodeId)
1231
+ : isDocumentNodeLocked(state, options, nodeId),
947
1232
  isOperationBlocked: () => state.gizmo?.getState().dragPhase === 'dragging',
948
1233
  onSelectionCommand(command) {
1234
+ if (state.prefabStage) {
1235
+ if (dispatchPrefabStageProjectionSelectionCommand(state, options, command))
1236
+ render();
1237
+ return;
1238
+ }
949
1239
  dispatchSelectionCommand(state, options, command, render);
950
1240
  },
951
1241
  onFocusIntent(nodeId) {
1242
+ if (state.prefabStage) {
1243
+ if (focusPrefabStageProjectionNode(state, options, nodeId, 'prefab-stage-preview-focus-intent'))
1244
+ render();
1245
+ return;
1246
+ }
952
1247
  if (focusProjectionNode(state, nodeId))
953
1248
  render();
954
1249
  },
@@ -967,7 +1262,7 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
967
1262
  isGizmoDragCandidate: (event) => gizmo.isGizmoDragCandidate(event),
968
1263
  isBoxSelectCandidate: (event) => selectionController.isBoxSelectionCandidate(event),
969
1264
  isViewPlaneMoveCandidate: (event) => gizmo.isViewPlaneMoveCandidate(event),
970
- isPlacementCandidate: () => isPlacementArmed(state),
1265
+ isPlacementCandidate: () => !state.prefabStage && isPlacementArmed(state),
971
1266
  isMeasurementCandidate: () => state.viewportTools.activeUtilityTool === 'measure-distance',
972
1267
  onPointerIntentStart(event) {
973
1268
  sceneViewInteraction.beginPointerIntent(event.state.intent);
@@ -1100,6 +1395,7 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1100
1395
  },
1101
1396
  });
1102
1397
  const environmentTexture = createBabylonEnvironmentTextureController(world.scene, initialRendering.environment ?? null);
1398
+ world.refreshSkyBackdrop();
1103
1399
  const postProcess = createBabylonDefaultPostProcessPipelineController(world.scene, [world.camera], initialRendering.postProcess ?? null, { name: 'editorDefaultPostProcessPipeline' });
1104
1400
  const resize = () => {
1105
1401
  engine.resize?.();
@@ -1110,7 +1406,9 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1110
1406
  state.engine = engine;
1111
1407
  state.world = world;
1112
1408
  state.grid = grid;
1113
- state.projection = projection;
1409
+ state.projection = markerAwareProjection;
1410
+ state.prefabComparisonPreview = prefabComparisonPreview;
1411
+ state.markerOverlay = markerOverlay;
1114
1412
  state.shadowPreview = shadowPreview;
1115
1413
  state.environmentTexture = environmentTexture;
1116
1414
  state.postProcess = postProcess;
@@ -1130,13 +1428,15 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1130
1428
  state.worldRenderingKey = initialRenderingKey;
1131
1429
  state.resizeHandler = resize;
1132
1430
  if (document) {
1133
- projection.projectNodes(options.documentAdapter.getProjectionNodes(document));
1431
+ projection.projectNodes(resolveCurrentEditorProjectionNodes(state, options, document));
1432
+ applyPreviewVisibilityStateToProjection(state);
1134
1433
  scheduleProjectionShadowPreviewRebuild(state, 'editor-world-initial-projection-shadow');
1135
1434
  }
1136
- syncCurrentSelectionToSceneArtifacts(state, 'editor-world-initial-selection');
1435
+ syncCurrentSelectionToSceneArtifacts(state, options, 'editor-world-initial-selection');
1137
1436
  viewportRenderCoordinator.requestFrame('editor-world-created');
1138
1437
  }
1139
1438
  function disposeEditorWorld(state) {
1439
+ clearPrefabStageForEditorTeardown(state);
1140
1440
  state.activeTransformPreview = null;
1141
1441
  state.sceneViewInteraction?.dispose();
1142
1442
  state.sceneViewInteraction = null;
@@ -1157,6 +1457,12 @@ function disposeEditorWorld(state) {
1157
1457
  state.sceneViewInput = null;
1158
1458
  state.selectionController?.dispose();
1159
1459
  state.selectionController = null;
1460
+ state.prefabComparisonPreview?.dispose();
1461
+ state.prefabComparisonPreview = null;
1462
+ if (state.markerRelationHighlightContinuous) {
1463
+ state.viewportRenderCoordinator?.endContinuous(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
1464
+ state.markerRelationHighlightContinuous = false;
1465
+ }
1160
1466
  if (state.performanceMonitorOpen) {
1161
1467
  state.viewportRenderCoordinator?.endContinuous(LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON);
1162
1468
  }
@@ -1179,6 +1485,8 @@ function disposeEditorWorld(state) {
1179
1485
  };
1180
1486
  state.gizmo?.dispose();
1181
1487
  state.gizmo = null;
1488
+ state.markerOverlay?.dispose();
1489
+ state.markerOverlay = null;
1182
1490
  if (state.projectionShadowPreviewRebuildFrame !== null) {
1183
1491
  window.cancelAnimationFrame(state.projectionShadowPreviewRebuildFrame);
1184
1492
  state.projectionShadowPreviewRebuildFrame = null;
@@ -1204,6 +1512,16 @@ function disposeEditorWorld(state) {
1204
1512
  state.world = null;
1205
1513
  state.engine = null;
1206
1514
  }
1515
+ function clearPrefabStageForEditorTeardown(state) {
1516
+ const stage = state.prefabStage;
1517
+ if (!stage)
1518
+ return;
1519
+ state.prefabStage = null;
1520
+ setGridVisibleForEditorContext(state, stage.restoreGridVisible, 'prefab-stage-editor-teardown-grid');
1521
+ state.sceneCameraPreviewEnabled = stage.restoreSceneCameraPreviewEnabled;
1522
+ if (!stage.restoreSceneCameraPreviewEnabled)
1523
+ state.sceneCameraPreview?.setActive(false);
1524
+ }
1207
1525
  async function waitForEditorProjectionIdleBeforeRunGame(state) {
1208
1526
  const projection = state.projection;
1209
1527
  if (!projection)
@@ -1267,10 +1585,31 @@ function requestEditorSceneFrame(state, reason) {
1267
1585
  function requestEditorPreviewFrame(state, options, renderPreviewUi, reason) {
1268
1586
  syncViewportCameraState(state);
1269
1587
  syncViewportMeasurementState(state);
1270
- syncViewportSpatialOverlay(state);
1588
+ syncViewportSpatialOverlay(state, options);
1271
1589
  renderPreviewUi(createUiPreviewState(state, options));
1272
1590
  requestEditorSceneFrame(state, reason);
1273
1591
  }
1592
+ function syncPrefabComparisonPreviewSurfaces(state, ui) {
1593
+ const controller = state.prefabComparisonPreview;
1594
+ if (!controller)
1595
+ return;
1596
+ if (!state.prefabStage) {
1597
+ if (controller.clear())
1598
+ requestEditorSceneFrame(state, 'prefab-comparison-preview-clear');
1599
+ return;
1600
+ }
1601
+ const surfaces = ui.getPrefabComparisonPreviewSurfaces?.() ?? [];
1602
+ const changed = controller.sync(surfaces.map(toBabylonPrefabComparisonPreviewSurface));
1603
+ if (changed)
1604
+ requestEditorSceneFrame(state, 'prefab-comparison-preview-sync');
1605
+ }
1606
+ function toBabylonPrefabComparisonPreviewSurface(surface) {
1607
+ return {
1608
+ previewId: surface.previewId,
1609
+ projectionNodeId: surface.projectionNodeId,
1610
+ rect: surface.rect,
1611
+ };
1612
+ }
1274
1613
  function updateActiveTransformPreview(state, event) {
1275
1614
  const activeId = event.activeId ?? event.nodeId ?? event.targetIds[0] ?? null;
1276
1615
  if (!activeId) {
@@ -1351,13 +1690,29 @@ function selectItem(state, options, input, renderUi) {
1351
1690
  return dispatchSelectionCommand(state, options, command, renderUi);
1352
1691
  }
1353
1692
  function createBrowserHierarchyContextActions(state, options) {
1354
- return (options.hierarchy?.contextActions ?? []).map((registration) => ({
1693
+ return createHierarchyContextActionRegistrations(state, options).map((registration) => ({
1355
1694
  id: registration.id,
1356
1695
  label: registration.label,
1357
1696
  shortcut: registration.shortcut,
1358
1697
  danger: registration.danger,
1359
1698
  placement: registration.placement,
1360
1699
  separatorBefore: registration.separatorBefore,
1700
+ children: (browserContext) => {
1701
+ const context = createHarnessHierarchyContextActionContext(state, options, browserContext);
1702
+ if (!context)
1703
+ return [];
1704
+ const children = typeof registration.children === 'function'
1705
+ ? registration.children(context)
1706
+ : registration.children ?? [];
1707
+ return children.map(child => ({
1708
+ id: child.id,
1709
+ label: child.label,
1710
+ shortcut: child.shortcut,
1711
+ danger: child.danger,
1712
+ disabled: () => child.disabled?.(context) ?? false,
1713
+ payload: () => child.payload?.(context),
1714
+ }));
1715
+ },
1361
1716
  visible: (browserContext) => {
1362
1717
  const context = createHarnessHierarchyContextActionContext(state, options, browserContext);
1363
1718
  if (!context)
@@ -1372,6 +1727,40 @@ function createBrowserHierarchyContextActions(state, options) {
1372
1727
  },
1373
1728
  }));
1374
1729
  }
1730
+ function createHierarchyContextActionRegistrations(state, options) {
1731
+ return [
1732
+ createPrefabFromGameObjectHierarchyContextAction(state, options),
1733
+ ...(options.hierarchy?.contextActions ?? []),
1734
+ ];
1735
+ }
1736
+ function createPrefabFromGameObjectHierarchyContextAction(state, options) {
1737
+ return {
1738
+ id: 'asset.create-prefab-from-game-object',
1739
+ label: 'Create Prefab',
1740
+ placement: 'after-create',
1741
+ visible: (context) => (!state.prefabStage
1742
+ && !!options.documentAdapter.createAssetActionPatch
1743
+ && context.targetIds.length === 1
1744
+ && context.activeId === context.targetIds[0]
1745
+ && !!context.projectionNode?.asset),
1746
+ disabled: (context) => {
1747
+ if (context.targetIds.length !== 1 || !context.activeId)
1748
+ return 'Select exactly one model-backed GameObject.';
1749
+ if (!context.projectionNode?.asset)
1750
+ return 'Only model-backed GameObjects can be converted to Prefab assets.';
1751
+ return false;
1752
+ },
1753
+ run: (context) => patchBrowserAssetAction(state, options, {
1754
+ actionId: 'asset.create-prefab-from-game-object',
1755
+ assetId: undefined,
1756
+ browserAssetId: undefined,
1757
+ assetKind: 'prefab',
1758
+ }, {
1759
+ activeId: context.activeId,
1760
+ selectedIds: context.targetIds,
1761
+ }),
1762
+ };
1763
+ }
1375
1764
  function createHarnessHierarchyContextActionContext(state, options, browserContext, payload) {
1376
1765
  const document = state.session?.getState().workingDocument ?? null;
1377
1766
  if (!document)
@@ -1409,17 +1798,22 @@ function createBrowserContextForCustomAction(state, options, action) {
1409
1798
  };
1410
1799
  }
1411
1800
  function runHierarchyCustomContextAction(state, options, action) {
1412
- const registration = options.hierarchy?.contextActions?.find(candidate => candidate.id === action.id);
1413
- if (!registration)
1414
- return false;
1415
1801
  const browserContext = createBrowserContextForCustomAction(state, options, action);
1416
1802
  const context = browserContext
1417
1803
  ? createHarnessHierarchyContextActionContext(state, options, browserContext, action.payload)
1418
1804
  : null;
1419
1805
  if (!context)
1420
1806
  return false;
1807
+ const registration = createHierarchyContextActionRegistrations(state, options)
1808
+ .find(candidate => candidate.id === action.id);
1809
+ if (!registration)
1810
+ return false;
1421
1811
  try {
1422
- return registration.run(context) === true;
1812
+ const result = registration.run(context);
1813
+ if (isLocalEditorHarnessPatchResult(result)) {
1814
+ return applyHierarchyContextActionPatch(state, options, result, registration.label);
1815
+ }
1816
+ return result === true;
1423
1817
  }
1424
1818
  catch (error) {
1425
1819
  const message = error instanceof Error ? error.message : String(error);
@@ -1431,6 +1825,55 @@ function runHierarchyCustomContextAction(state, options, action) {
1431
1825
  return true;
1432
1826
  }
1433
1827
  }
1828
+ function isLocalEditorHarnessPatchResult(value) {
1829
+ return !!value
1830
+ && typeof value === 'object'
1831
+ && 'patch' in value;
1832
+ }
1833
+ function applyHierarchyContextActionPatch(state, options, patch, fallbackLabel) {
1834
+ const document = state.session?.getState().workingDocument;
1835
+ if (state.mode !== 'editor' || !state.session || !document)
1836
+ return false;
1837
+ cancelActiveOperation(state);
1838
+ const label = patch.label ?? fallbackLabel;
1839
+ const projectionImpact = resolveProjectionImpact(patch, patch.createdId
1840
+ ? createKnownCreatedReprojectProjectionImpact({
1841
+ createdNodeIds: [patch.createdId],
1842
+ reprojectIds: patch.reprojectIds,
1843
+ })
1844
+ : patch.reprojectIds?.length
1845
+ ? createReprojectProjectionImpact(patch.reprojectIds)
1846
+ : patch.changedIds?.length
1847
+ ? createReprojectProjectionImpact(patch.changedIds)
1848
+ : patch.changedId
1849
+ ? createReprojectProjectionImpact([patch.changedId])
1850
+ : undefined);
1851
+ const result = state.session.dispatch({
1852
+ type: 'document.patch',
1853
+ label,
1854
+ patch: patch.patch,
1855
+ targetId: patch.createdId ?? patch.changedId ?? undefined,
1856
+ ...withProjectionImpact(projectionImpact),
1857
+ });
1858
+ if (!result.documentChanged) {
1859
+ state.status = `${label} unchanged`;
1860
+ return true;
1861
+ }
1862
+ const selectionId = patch.createdId ?? patch.changedId ?? null;
1863
+ let selection = result.selection;
1864
+ if (selectionId && isNodeSelectableInDocument(options, result.workingDocument, selectionId)) {
1865
+ selection = state.session.dispatch({
1866
+ type: 'selection.replace',
1867
+ selectedIds: [selectionId],
1868
+ activeId: selectionId,
1869
+ label: `Select ${selectionId}`,
1870
+ }).selection;
1871
+ }
1872
+ rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
1873
+ state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
1874
+ state.status = label;
1875
+ return true;
1876
+ }
1434
1877
  function handleContextAction(state, options, action) {
1435
1878
  if (action.region !== 'hierarchy')
1436
1879
  return false;
@@ -1446,7 +1889,7 @@ function handleContextAction(state, options, action) {
1446
1889
  label: 'Select Context Target',
1447
1890
  })
1448
1891
  : false;
1449
- return focusSelectedProjection(state) || selectionChanged;
1892
+ return focusSelectedProjection(state, options) || selectionChanged;
1450
1893
  }
1451
1894
  if (action.action === 'rename')
1452
1895
  return false;
@@ -1465,6 +1908,14 @@ function handleContextAction(state, options, action) {
1465
1908
  name: action.name,
1466
1909
  });
1467
1910
  }
1911
+ if (action.action === 'create-marker') {
1912
+ return createSceneGraphMarker(state, options, {
1913
+ parentId: action.parentId ?? null,
1914
+ activeId: action.activeId ?? null,
1915
+ markerType: action.markerType,
1916
+ name: action.name,
1917
+ });
1918
+ }
1468
1919
  if (action.action === 'delete') {
1469
1920
  return deleteSceneGraphNodes(state, options, {
1470
1921
  ids: action.targetIds,
@@ -1488,6 +1939,9 @@ function handleContextAction(state, options, action) {
1488
1939
  function dispatchSelectionCommand(state, options, command, renderUi) {
1489
1940
  if (state.mode !== 'editor')
1490
1941
  return false;
1942
+ if (state.markerRelationPick) {
1943
+ return consumeMarkerRelationPickSelectionCommand(state, options, command, renderUi);
1944
+ }
1491
1945
  cancelActiveOperation(state);
1492
1946
  const session = state.session;
1493
1947
  if (!session)
@@ -1499,16 +1953,109 @@ function dispatchSelectionCommand(state, options, command, renderUi) {
1499
1953
  if (!changed)
1500
1954
  return false;
1501
1955
  if (renderUi) {
1502
- commitEditorStateChange(state, renderUi, 'selection-command', () => { }, {
1956
+ commitEditorStateChange(state, options, renderUi, 'selection-command', () => { }, {
1503
1957
  syncSelectionArtifacts: selection,
1504
1958
  });
1505
1959
  }
1506
1960
  else {
1507
1961
  state.editorStateRevision += 1;
1508
- syncSelectionToProjection(state, selection, 'selection-command', state.editorStateRevision);
1962
+ syncSelectionToProjection(state, options, selection, 'selection-command', state.editorStateRevision);
1963
+ }
1964
+ return true;
1965
+ }
1966
+ function consumeMarkerRelationPickSelectionCommand(state, options, command, renderUi) {
1967
+ const pick = state.markerRelationPick;
1968
+ if (!pick?.active)
1969
+ return false;
1970
+ const document = state.session?.getState().workingDocument ?? null;
1971
+ const graph = document ? options.documentAdapter.getMarkerGraph?.(document) ?? null : null;
1972
+ if (!document || !graph) {
1973
+ updateMarkerRelationPickWarning(state, '当前文档没有 marker graph。');
1974
+ if (renderUi)
1975
+ renderUi();
1976
+ return true;
1977
+ }
1978
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
1979
+ const targetId = readMarkerRelationPickTargetId(command);
1980
+ if (!targetId) {
1981
+ updateMarkerRelationPickWarning(state, '没有拾取到有效端点,请点击标记或场景对象。');
1982
+ if (renderUi)
1983
+ renderUi();
1984
+ return true;
1985
+ }
1986
+ const endpoint = resolveMarkerRelationPickEndpoint(graph, hierarchy, targetId);
1987
+ if (!endpoint) {
1988
+ updateMarkerRelationPickWarning(state, `暂不支持的关系端点:${targetId}`);
1989
+ if (renderUi)
1990
+ renderUi();
1991
+ return true;
1992
+ }
1993
+ if (!pick.from) {
1994
+ const status = `关系拾取中:${formatMarkerRelationPickEndpointLabel(endpoint, graph, hierarchy)} 已作为起点,请点击终点。`;
1995
+ state.markerRelationPick = {
1996
+ ...pick,
1997
+ from: endpoint,
1998
+ status,
1999
+ statusTone: 'success',
2000
+ };
2001
+ state.status = status;
2002
+ state.statusTone = 'success';
2003
+ state.statusToneStatus = state.status;
2004
+ state.statusDetails = '关系拾取会消费下一次有效视口点击,不改变当前选择。';
2005
+ if (renderUi)
2006
+ renderUi();
2007
+ return true;
2008
+ }
2009
+ const handled = patchMarkerRelation(state, options, {
2010
+ action: 'create',
2011
+ type: pick.type ?? '',
2012
+ fromKind: pick.from.kind,
2013
+ fromId: pick.from.id,
2014
+ toKind: endpoint.kind,
2015
+ toId: endpoint.id,
2016
+ directed: pick.directed,
2017
+ });
2018
+ if (handled && state.markerRelationPick) {
2019
+ state.markerRelationPick = {
2020
+ ...state.markerRelationPick,
2021
+ status: state.status,
2022
+ statusTone: state.statusTone,
2023
+ };
1509
2024
  }
2025
+ if (renderUi)
2026
+ renderUi();
1510
2027
  return true;
1511
2028
  }
2029
+ function readMarkerRelationPickTargetId(command) {
2030
+ if (command.type === 'selection.clear' || command.type === 'selection.remove')
2031
+ return null;
2032
+ return command.activeId?.trim() || command.selectedIds[command.selectedIds.length - 1]?.trim() || null;
2033
+ }
2034
+ function resolveMarkerRelationPickEndpoint(graph, hierarchy, targetId) {
2035
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, targetId, hierarchy);
2036
+ if (markerId && findSpatialMarkerById(graph, markerId)) {
2037
+ return { kind: 'marker', id: markerId };
2038
+ }
2039
+ const hierarchyItem = hierarchy.find(item => item.id === targetId) ?? null;
2040
+ if (!hierarchyItem)
2041
+ return null;
2042
+ if (hierarchyItem.role === 'marker' || hierarchyItem.role === 'group' || hierarchyItem.role === 'root')
2043
+ return null;
2044
+ return { kind: 'scene-object', id: targetId };
2045
+ }
2046
+ function updateMarkerRelationPickWarning(state, status) {
2047
+ if (state.markerRelationPick) {
2048
+ state.markerRelationPick = {
2049
+ ...state.markerRelationPick,
2050
+ status,
2051
+ statusTone: 'warning',
2052
+ };
2053
+ }
2054
+ state.status = status;
2055
+ state.statusTone = 'warning';
2056
+ state.statusToneStatus = state.status;
2057
+ state.statusDetails = status;
2058
+ }
1512
2059
  function renameSceneGraphNode(state, options, intent) {
1513
2060
  const document = state.session?.getState().workingDocument;
1514
2061
  if (state.mode !== 'editor' || !state.session || !document)
@@ -1524,7 +2071,10 @@ function renameSceneGraphNode(state, options, intent) {
1524
2071
  state.status = `Rename rejected: ${intent.id}`;
1525
2072
  return true;
1526
2073
  }
1527
- const result = state.session.dispatch({
2074
+ const session = state.session;
2075
+ if (!session)
2076
+ return false;
2077
+ const result = session.dispatch({
1528
2078
  type: 'document.patch',
1529
2079
  label: patch.label ?? `Rename ${intent.id}`,
1530
2080
  patch: patch.patch,
@@ -1536,7 +2086,7 @@ function renameSceneGraphNode(state, options, intent) {
1536
2086
  return true;
1537
2087
  }
1538
2088
  rebuildProjectionFromDocument(state, options, result.workingDocument, result.selection);
1539
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2089
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
1540
2090
  state.status = patch.label ?? `Renamed ${intent.id}`;
1541
2091
  return true;
1542
2092
  }
@@ -1550,7 +2100,10 @@ function createSceneGraphGroup(state, options, intent) {
1550
2100
  state.status = 'Create empty rejected';
1551
2101
  return true;
1552
2102
  }
1553
- const result = state.session.dispatch({
2103
+ const session = state.session;
2104
+ if (!session)
2105
+ return false;
2106
+ const result = session.dispatch({
1554
2107
  type: 'document.patch',
1555
2108
  label: patch.label ?? 'Create Empty',
1556
2109
  patch: patch.patch,
@@ -1572,7 +2125,7 @@ function createSceneGraphGroup(state, options, intent) {
1572
2125
  }).selection;
1573
2126
  }
1574
2127
  rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
1575
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2128
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
1576
2129
  state.status = patch.label ?? (createdId ? `Created empty ${createdId}` : 'Created empty');
1577
2130
  return true;
1578
2131
  }
@@ -1615,6 +2168,182 @@ function createSceneGraphPrimitive(state, options, intent) {
1615
2168
  state.status = patch.label ?? (createdId ? `Created ${intent.shape} ${createdId}` : `Created ${intent.shape}`);
1616
2169
  return true;
1617
2170
  }
2171
+ function createSceneGraphMarker(state, options, intent) {
2172
+ const document = state.session?.getState().workingDocument;
2173
+ if (state.mode !== 'editor' || !state.session || !document)
2174
+ return false;
2175
+ cancelActiveOperation(state);
2176
+ const markerType = intent.markerType?.trim() ?? '';
2177
+ const markerTypes = options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [];
2178
+ const markerTypeDefinition = markerType
2179
+ ? markerTypes.find(definition => definition.type === markerType) ?? null
2180
+ : null;
2181
+ if (markerType && !markerTypeDefinition) {
2182
+ state.status = `Create marker rejected: unknown marker type ${markerType}`;
2183
+ state.statusTone = 'warning';
2184
+ state.statusToneStatus = state.status;
2185
+ return true;
2186
+ }
2187
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2188
+ const parentId = resolveCreateMarkerParentId(hierarchy, intent.parentId ?? null);
2189
+ const marker = createSceneGraphMarkerNode(state, options, document, intent, markerTypeDefinition);
2190
+ state.markerCreateReservations.ids.add(marker.id);
2191
+ state.markerCreateReservations.labels.add(marker.label);
2192
+ const patch = options.documentAdapter.createMarkerGraphPatch?.(document, {
2193
+ type: 'marker.create-box',
2194
+ marker,
2195
+ parentId,
2196
+ });
2197
+ if (!patch) {
2198
+ state.markerCreateReservations.ids.delete(marker.id);
2199
+ state.markerCreateReservations.labels.delete(marker.label);
2200
+ state.status = `Create marker rejected: ${marker.label}`;
2201
+ state.statusTone = 'warning';
2202
+ state.statusToneStatus = state.status;
2203
+ return true;
2204
+ }
2205
+ const result = state.session.dispatch({
2206
+ type: 'document.patch',
2207
+ label: patch.label ?? `Create marker ${marker.label}`,
2208
+ patch: patch.patch,
2209
+ targetId: patch.createdId ?? patch.changedId ?? marker.id,
2210
+ ...withProjectionImpact(resolveProjectionImpact(patch, createKnownCreatedReprojectProjectionImpact({
2211
+ createdNodeIds: [patch.createdId ?? patch.changedId ?? marker.id],
2212
+ reprojectIds: patch.changedIds,
2213
+ }))),
2214
+ });
2215
+ if (!result.documentChanged) {
2216
+ state.markerCreateReservations.ids.delete(marker.id);
2217
+ state.markerCreateReservations.labels.delete(marker.label);
2218
+ state.status = `Create marker unchanged: ${marker.label}`;
2219
+ state.statusTone = 'warning';
2220
+ state.statusToneStatus = state.status;
2221
+ return true;
2222
+ }
2223
+ const createdId = patch.createdId ?? patch.changedId ?? marker.id;
2224
+ releaseReflectedMarkerCreateReservation(state, options, result.workingDocument, marker, createdId);
2225
+ let selection = result.selection;
2226
+ if (createdId && isNodeSelectableInDocument(options, result.workingDocument, createdId)) {
2227
+ selection = state.session.dispatch({
2228
+ type: 'selection.replace',
2229
+ selectedIds: [createdId],
2230
+ activeId: createdId,
2231
+ label: 'Select Created Marker',
2232
+ }).selection;
2233
+ }
2234
+ rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
2235
+ state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2236
+ state.status = patch.label ?? `Created marker ${marker.label}`;
2237
+ state.statusTone = 'success';
2238
+ state.statusToneStatus = state.status;
2239
+ state.statusDetails = '';
2240
+ return true;
2241
+ }
2242
+ function releaseReflectedMarkerCreateReservation(state, options, document, marker, createdId) {
2243
+ const reflectedIds = new Set([
2244
+ marker.id,
2245
+ ...(createdId ? [createdId] : []),
2246
+ ]);
2247
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2248
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
2249
+ const markerReflected = hierarchy.some(item => reflectedIds.has(item.id))
2250
+ || (graph?.markers ?? []).some(candidate => reflectedIds.has(candidate.id));
2251
+ if (!markerReflected)
2252
+ return;
2253
+ state.markerCreateReservations.ids.delete(marker.id);
2254
+ state.markerCreateReservations.labels.delete(marker.label);
2255
+ }
2256
+ function createSceneGraphMarkerNode(state, options, document, intent, markerTypeDefinition) {
2257
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2258
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
2259
+ const existingIds = new Set([
2260
+ ...hierarchy.map(item => item.id),
2261
+ ...(graph?.markers ?? []).map(marker => marker.id),
2262
+ ...state.markerCreateReservations.ids,
2263
+ ]);
2264
+ const markerType = markerTypeDefinition?.type ?? intent.markerType?.trim() ?? '';
2265
+ const baseLabel = intent.name?.trim() || markerTypeDefinition?.label || 'Marker';
2266
+ const id = createUniqueMarkerId(existingIds, markerType || baseLabel);
2267
+ const label = createUniqueMarkerLabel(new Set([
2268
+ ...hierarchy.map(item => item.label),
2269
+ ...state.markerCreateReservations.labels,
2270
+ ]), baseLabel, id, markerType);
2271
+ const geometry = createInitialMarkerGeometry(state, markerTypeDefinition?.kind);
2272
+ return {
2273
+ id,
2274
+ kind: normalizeMarkerNodeKind(markerTypeDefinition?.kind),
2275
+ markerType,
2276
+ label,
2277
+ geometry,
2278
+ ...(markerTypeDefinition?.defaultTags?.length ? { tags: [...markerTypeDefinition.defaultTags] } : {}),
2279
+ };
2280
+ }
2281
+ function createInitialMarkerGeometry(state, markerKind) {
2282
+ const defaultScale = createDefaultMarkerScale(markerKind);
2283
+ const selection = state.session?.getState().selection ?? { selectedIds: [], activeId: null };
2284
+ const bounds = selection.selectedIds.length > 0
2285
+ ? state.projection?.getSelectionBounds(selection.selectedIds) ?? null
2286
+ : null;
2287
+ const center = bounds?.center ?? { x: 0, y: defaultScale.y / 2, z: 0 };
2288
+ if (!isRegionMarkerKind(markerKind)) {
2289
+ return {
2290
+ kind: 'point',
2291
+ coordinateSpace: 'world',
2292
+ position: { ...center },
2293
+ };
2294
+ }
2295
+ const size = bounds && isRegionMarkerKind(markerKind)
2296
+ ? normalizeMarkerBoxSize(bounds.size)
2297
+ : defaultScale;
2298
+ return {
2299
+ kind: 'box',
2300
+ coordinateSpace: 'world',
2301
+ center: { ...center },
2302
+ size: { ...size },
2303
+ };
2304
+ }
2305
+ function createDefaultMarkerScale(markerKind) {
2306
+ return isRegionMarkerKind(markerKind)
2307
+ ? { x: 4, y: 1, z: 4 }
2308
+ : { x: 0.5, y: 0.5, z: 0.5 };
2309
+ }
2310
+ function normalizeMarkerNodeKind(kind) {
2311
+ return kind?.trim() || 'point';
2312
+ }
2313
+ function isRegionMarkerKind(kind) {
2314
+ return (kind?.trim() || 'point') === 'region';
2315
+ }
2316
+ function createUniqueMarkerId(existingIds, markerType) {
2317
+ const base = sanitizeMarkerIdPart(markerType) || 'marker';
2318
+ if (!existingIds.has(base))
2319
+ return base;
2320
+ let index = 2;
2321
+ while (existingIds.has(`${base}-${index}`))
2322
+ index += 1;
2323
+ return `${base}-${index}`;
2324
+ }
2325
+ function createUniqueMarkerLabel(existingLabels, baseLabel, id, markerType) {
2326
+ const trimmed = baseLabel.trim() || markerType || id;
2327
+ if (!existingLabels.has(trimmed))
2328
+ return trimmed;
2329
+ let index = 2;
2330
+ while (existingLabels.has(`${trimmed} ${index}`))
2331
+ index += 1;
2332
+ return `${trimmed} ${index}`;
2333
+ }
2334
+ function sanitizeMarkerIdPart(value) {
2335
+ return value
2336
+ .trim()
2337
+ .toLowerCase()
2338
+ .replace(/[^a-z0-9_-]+/g, '-')
2339
+ .replace(/^-+|-+$/g, '');
2340
+ }
2341
+ function resolveCreateMarkerParentId(hierarchy, parentId) {
2342
+ if (!parentId)
2343
+ return null;
2344
+ const parent = hierarchy.find(item => item.id === parentId) ?? null;
2345
+ return parent?.canHaveChildren === true && parent.role !== 'marker' ? parent.id : null;
2346
+ }
1618
2347
  function deleteSceneGraphNodes(state, options, intent) {
1619
2348
  const document = state.session?.getState().workingDocument;
1620
2349
  if (state.mode !== 'editor' || !state.session || !document)
@@ -1866,7 +2595,12 @@ function groupSceneGraphSelection(state, options, intent) {
1866
2595
  return true;
1867
2596
  }
1868
2597
  function sanitizeSelection(state, options, document, selection) {
1869
- const selectedIds = selection.selectedIds.filter(id => isNodeSelectableInDocument(options, document, id));
2598
+ const documentHierarchy = options.documentAdapter.getHierarchyItems(document);
2599
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
2600
+ const hierarchy = graph
2601
+ ? createMarkerGraphHierarchyItems(documentHierarchy, graph, createMarkerGraphHierarchyVisibility(state))
2602
+ : documentHierarchy;
2603
+ const selectedIds = selection.selectedIds.filter(id => isAgentBridgeObjectSelectable(options, document, id, hierarchy));
1870
2604
  const activeId = selection.activeId && selectedIds.includes(selection.activeId)
1871
2605
  ? selection.activeId
1872
2606
  : selectedIds[selectedIds.length - 1] ?? null;
@@ -1880,7 +2614,7 @@ function sanitizeSelection(state, options, document, selection) {
1880
2614
  });
1881
2615
  return result?.selection ?? { selectedIds, activeId };
1882
2616
  }
1883
- function commitEditorStateChange(state, renderUi, reason, mutate, options = {}) {
2617
+ function commitEditorStateChange(state, harnessOptions, renderUi, reason, mutate, options = {}) {
1884
2618
  const beforeRevision = state.editorStateRevision;
1885
2619
  mutate();
1886
2620
  const revision = beforeRevision + 1;
@@ -1890,7 +2624,7 @@ function commitEditorStateChange(state, renderUi, reason, mutate, options = {})
1890
2624
  const selection = typeof options.syncSelectionArtifacts === 'boolean'
1891
2625
  ? state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null }
1892
2626
  : options.syncSelectionArtifacts;
1893
- syncSelectionToProjection(state, selection, reason, revision);
2627
+ syncSelectionToProjection(state, harnessOptions, selection, reason, revision);
1894
2628
  }
1895
2629
  else if (options.syncTransformOperationArtifacts) {
1896
2630
  syncTransformOperationSettingsToSceneArtifacts(state, reason, revision);
@@ -1900,15 +2634,288 @@ function commitEditorStateChange(state, renderUi, reason, mutate, options = {})
1900
2634
  }
1901
2635
  return revision;
1902
2636
  }
1903
- function syncSelectionToProjection(state, selection, reason = 'projection-selection', revision = state.editorStateRevision) {
2637
+ function syncSelectionToProjection(state, options, selection, reason = 'projection-selection', revision = state.editorStateRevision) {
1904
2638
  if (!beginSceneArtifactSync(state, reason, revision))
1905
2639
  return;
1906
- state.projection?.syncSelection(selection);
1907
- state.gizmo?.setSelection(selection);
2640
+ const projectedSelection = state.prefabStage ? EMPTY_EDITOR_SELECTION : selection;
2641
+ state.projection?.syncSelection(projectedSelection);
2642
+ syncMarkerOverlayToSceneArtifacts(state, options, projectedSelection);
2643
+ state.gizmo?.setSelection(projectedSelection);
1908
2644
  syncTransformGizmoToSceneArtifacts(state, reason, revision, false);
1909
2645
  }
1910
- function syncCurrentSelectionToSceneArtifacts(state, reason = 'projection-selection-current', revision = state.editorStateRevision) {
1911
- syncSelectionToProjection(state, state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null }, reason, revision);
2646
+ function syncCurrentSelectionToSceneArtifacts(state, options, reason = 'projection-selection-current', revision = state.editorStateRevision) {
2647
+ syncSelectionToProjection(state, options, state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null }, reason, revision);
2648
+ }
2649
+ function syncMarkerOverlayToSceneArtifacts(state, options, selection) {
2650
+ const overlay = state.markerOverlay;
2651
+ if (!overlay)
2652
+ return;
2653
+ const document = state.session?.getState().workingDocument ?? null;
2654
+ const input = document
2655
+ ? createMarkerOverlayInput(state, options, document, selection, createMarkerGraphHierarchyVisibility(state))
2656
+ : { markers: [] };
2657
+ const changed = overlay.sync(input);
2658
+ syncMarkerRelationHighlightRenderLoop(state, input);
2659
+ if (changed)
2660
+ requestEditorSceneFrame(state, 'marker-overlay-sync');
2661
+ }
2662
+ function syncMarkerRelationHighlightRenderLoop(state, input) {
2663
+ const shouldRun = (input.relations ?? []).some(relation => relation.active === true);
2664
+ if (shouldRun === state.markerRelationHighlightContinuous)
2665
+ return;
2666
+ state.markerRelationHighlightContinuous = shouldRun;
2667
+ if (shouldRun) {
2668
+ if (canUseContinuousViewportRenderLoop()) {
2669
+ state.viewportRenderCoordinator?.beginContinuous(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
2670
+ }
2671
+ else {
2672
+ state.viewportRenderCoordinator?.requestFrame(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
2673
+ }
2674
+ }
2675
+ else {
2676
+ state.viewportRenderCoordinator?.endContinuous(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
2677
+ }
2678
+ }
2679
+ function canUseContinuousViewportRenderLoop() {
2680
+ if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function')
2681
+ return true;
2682
+ if (typeof window.cancelAnimationFrame !== 'function')
2683
+ return true;
2684
+ let completedSynchronously = false;
2685
+ const frameId = window.requestAnimationFrame(() => {
2686
+ completedSynchronously = true;
2687
+ });
2688
+ window.cancelAnimationFrame(frameId);
2689
+ return !completedSynchronously;
2690
+ }
2691
+ function createMarkerOverlayInput(state, options, document, selection, visibility) {
2692
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
2693
+ if (!graph)
2694
+ return { markers: [] };
2695
+ if (state.viewportTools.markersVisible === false)
2696
+ return { markers: [], relations: [] };
2697
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2698
+ const fullHierarchy = createMarkerGraphHierarchyItems(hierarchy, graph, visibility);
2699
+ const selectedIds = new Set(selection.selectedIds);
2700
+ const markerTypeLabels = new Map((options.documentAdapter.getMarkerTypeCatalog?.(document) ?? []).map(definition => [definition.type, definition.label]));
2701
+ const activeMarkerId = selection.activeId
2702
+ ? readSpatialMarkerIdFromHierarchySelection(graph, selection.activeId, hierarchy)
2703
+ : null;
2704
+ return {
2705
+ activeMarkerRowId: selection.activeId,
2706
+ selectedMarkerRowIds: selection.selectedIds,
2707
+ markers: fullHierarchy
2708
+ .filter(item => item.role === 'marker')
2709
+ .map((item) => {
2710
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, item.id, hierarchy);
2711
+ const marker = markerId ? findSpatialMarkerById(graph, markerId) : null;
2712
+ if (!marker || !isMarkerOverlayRowVisible(graph, item, marker.id, hierarchy, visibility))
2713
+ return null;
2714
+ const overlayMarker = resolveMarkerForOverlay(state, applyMarkerOverlayTransformPreview(state, item.id, marker));
2715
+ return {
2716
+ markerRowId: item.id,
2717
+ marker: overlayMarker,
2718
+ markerTypeLabel: markerTypeLabels.get(marker.markerType),
2719
+ selected: selectedIds.has(item.id),
2720
+ active: selection.activeId === item.id,
2721
+ };
2722
+ })
2723
+ .filter((entry) => !!entry),
2724
+ relations: createSelectedMarkerRelationOverlayEntries(state, resolveMarkerGraphForOverlay(state, graph), activeMarkerId, hierarchy, visibility),
2725
+ };
2726
+ }
2727
+ function resolveMarkerGraphForOverlay(state, graph) {
2728
+ return {
2729
+ ...graph,
2730
+ markers: graph.markers.map(marker => resolveMarkerForOverlay(state, marker)),
2731
+ };
2732
+ }
2733
+ function resolveMarkerForOverlay(state, marker) {
2734
+ if (!isObjectBoundMarkerForOverlay(marker))
2735
+ return marker;
2736
+ const target = readSceneObjectMarkerTargetForOverlay(marker);
2737
+ if (!target)
2738
+ return marker;
2739
+ const bounds = state.projection?.getSelectionBounds([target.id]) ?? null;
2740
+ if (!bounds)
2741
+ return marker;
2742
+ return {
2743
+ ...marker,
2744
+ spatial: {
2745
+ status: 'resolved',
2746
+ coordinateSpace: 'world',
2747
+ origin: { ...bounds.center },
2748
+ bounds: {
2749
+ min: { ...bounds.min },
2750
+ max: { ...bounds.max },
2751
+ center: { ...bounds.center },
2752
+ size: { ...bounds.size },
2753
+ },
2754
+ dimensions: { ...bounds.size },
2755
+ localFrame: {
2756
+ origin: { ...bounds.center },
2757
+ forward: marker.semanticFrame?.forward ? { ...marker.semanticFrame.forward } : { x: 0, y: 0, z: 1 },
2758
+ right: marker.semanticFrame?.right ? { ...marker.semanticFrame.right } : { x: 1, y: 0, z: 0 },
2759
+ up: marker.semanticFrame?.up ? { ...marker.semanticFrame.up } : { x: 0, y: 1, z: 0 },
2760
+ },
2761
+ target,
2762
+ },
2763
+ };
2764
+ }
2765
+ function isObjectBoundMarkerForOverlay(marker) {
2766
+ const markerKind = marker.kind?.trim().toLowerCase();
2767
+ return markerKind === 'object'
2768
+ || markerKind === 'object-bound'
2769
+ || markerKind === 'object-bounds'
2770
+ || marker.geometry.kind === 'object-bounds';
2771
+ }
2772
+ function readSceneObjectMarkerTargetForOverlay(marker) {
2773
+ if (marker.target?.kind === 'scene-object')
2774
+ return marker.target;
2775
+ if (marker.geometry.kind === 'object-bounds' && marker.geometry.target.kind === 'scene-object') {
2776
+ return marker.geometry.target;
2777
+ }
2778
+ if (marker.geometry.kind === 'point' && marker.geometry.target?.kind === 'scene-object') {
2779
+ return marker.geometry.target;
2780
+ }
2781
+ return null;
2782
+ }
2783
+ function createSelectedMarkerRelationOverlayEntries(state, graph, activeMarkerId, hierarchy, visibility) {
2784
+ if (!activeMarkerId)
2785
+ return [];
2786
+ if (!findSpatialMarkerById(graph, activeMarkerId))
2787
+ return [];
2788
+ const entries = [];
2789
+ for (const relation of getSpatialMarkerDirectRelations(graph, activeMarkerId)) {
2790
+ const from = resolveRelationOverlayEndpoint(state, graph, activeMarkerId, relation.from, hierarchy, visibility);
2791
+ const to = resolveRelationOverlayEndpoint(state, graph, activeMarkerId, relation.to, hierarchy, visibility);
2792
+ if (!from || !to)
2793
+ continue;
2794
+ entries.push({
2795
+ id: relation.id,
2796
+ type: relation.type,
2797
+ ...(relation.label ? { label: relation.label } : {}),
2798
+ from,
2799
+ to,
2800
+ directed: relation.directed ?? true,
2801
+ active: state.activeMarkerRelationId === relation.id,
2802
+ });
2803
+ }
2804
+ return entries;
2805
+ }
2806
+ function applyMarkerOverlayTransformPreview(state, markerRowId, marker) {
2807
+ const transform = readActiveTransformPreviewTransform(state, markerRowId)
2808
+ ?? (markerRowId === marker.id ? null : readActiveTransformPreviewTransform(state, marker.id));
2809
+ if (!transform)
2810
+ return marker;
2811
+ const semanticFrame = createMarkerSemanticFrameFromTransform(marker, transform);
2812
+ if (marker.geometry.kind === 'point') {
2813
+ return {
2814
+ ...marker,
2815
+ geometry: {
2816
+ ...marker.geometry,
2817
+ coordinateSpace: 'world',
2818
+ position: { ...transform.position },
2819
+ },
2820
+ semanticFrame,
2821
+ };
2822
+ }
2823
+ if (marker.geometry.kind !== 'box') {
2824
+ return {
2825
+ ...marker,
2826
+ semanticFrame,
2827
+ };
2828
+ }
2829
+ return {
2830
+ ...marker,
2831
+ semanticFrame,
2832
+ geometry: {
2833
+ ...marker.geometry,
2834
+ center: { ...transform.position },
2835
+ rotation: { ...transform.rotation },
2836
+ size: { ...transform.scale },
2837
+ },
2838
+ };
2839
+ }
2840
+ function resolveRelationOverlayEndpoint(state, graph, activeMarkerId, endpoint, hierarchy, visibility) {
2841
+ if (endpoint.kind === 'marker') {
2842
+ if (!isMarkerVisibleForOverlayRelation(graph, endpoint.id, hierarchy, visibility))
2843
+ return null;
2844
+ const preview = endpoint.id === activeMarkerId ? readActiveTransformPreviewPosition(state) : null;
2845
+ if (preview)
2846
+ return preview;
2847
+ const marker = findSpatialMarkerById(graph, endpoint.id);
2848
+ return marker ? resolveMarkerOverlayEndpoint(marker) : null;
2849
+ }
2850
+ if (endpoint.kind === 'scene-object') {
2851
+ const preview = endpoint.id === state.activeTransformPreview?.activeId ? readActiveTransformPreviewPosition(state) : null;
2852
+ if (preview)
2853
+ return preview;
2854
+ const bounds = state.projection?.getSelectionBounds([endpoint.id]) ?? null;
2855
+ return bounds ? { ...bounds.center } : null;
2856
+ }
2857
+ return null;
2858
+ }
2859
+ function isMarkerVisibleForOverlayRelation(graph, markerId, hierarchy, visibility) {
2860
+ if (isSpatialMarkerBackedByHierarchyRow(graph, markerId, hierarchy)) {
2861
+ const markerRow = hierarchy.find(item => item.id === markerId);
2862
+ return markerRow?.enabled !== false && !isMarkerHiddenInHierarchyVisibility(markerId, visibility);
2863
+ }
2864
+ const markerRow = hierarchy.find(item => readMarkerGraphHierarchyMarkerIdForGraph(graph, item.id, hierarchy) === markerId);
2865
+ if (markerRow?.enabled === false)
2866
+ return false;
2867
+ return isSpatialMarkerHierarchyVisible(markerId, visibility);
2868
+ }
2869
+ function isMarkerHiddenInHierarchyVisibility(markerId, visibility) {
2870
+ const hiddenMarkerIds = visibility.hiddenMarkerIds;
2871
+ if (!hiddenMarkerIds)
2872
+ return false;
2873
+ return Array.isArray(hiddenMarkerIds)
2874
+ ? hiddenMarkerIds.includes(markerId)
2875
+ : hiddenMarkerIds.has(markerId);
2876
+ }
2877
+ function readActiveTransformPreviewPosition(state) {
2878
+ const transform = readActiveTransformPreviewTransform(state);
2879
+ return transform ? { ...transform.position } : null;
2880
+ }
2881
+ function readActiveTransformPreviewTransform(state, targetId) {
2882
+ const preview = state.activeTransformPreview;
2883
+ const activeId = targetId ?? preview?.activeId ?? null;
2884
+ if (!preview || !activeId)
2885
+ return null;
2886
+ const transform = preview.transforms[activeId] ?? null;
2887
+ return transform && isEditorTransformTrsSnapshot(transform) ? transform : null;
2888
+ }
2889
+ function resolveMarkerOverlayEndpoint(marker) {
2890
+ if (isObjectBoundMarkerForOverlay(marker) && marker.spatial?.bounds?.center) {
2891
+ return { ...marker.spatial.bounds.center };
2892
+ }
2893
+ if (marker.geometry.kind === 'box')
2894
+ return { ...marker.geometry.center };
2895
+ if (marker.geometry.kind === 'point')
2896
+ return { ...marker.geometry.position };
2897
+ return null;
2898
+ }
2899
+ function isMarkerOverlayRowVisible(graph, item, markerId, hierarchy, visibility) {
2900
+ if (item.enabled === false)
2901
+ return false;
2902
+ if (readMarkerGraphHierarchyMarkerIdForGraph(graph, item.id, hierarchy)) {
2903
+ return isSpatialMarkerHierarchyVisible(markerId, visibility);
2904
+ }
2905
+ return true;
2906
+ }
2907
+ function createMarkerGraphHierarchyVisibility(state) {
2908
+ return {
2909
+ rootVisible: state.markerGraphVisibility.rootVisible,
2910
+ hiddenMarkerIds: state.markerGraphVisibility.hiddenMarkerIds,
2911
+ };
2912
+ }
2913
+ function createMarkerRelationVisibilityOptions(state) {
2914
+ return {
2915
+ markersVisible: state.viewportTools.markersVisible !== false,
2916
+ rootVisible: state.markerGraphVisibility.rootVisible,
2917
+ hiddenMarkerIds: state.markerGraphVisibility.hiddenMarkerIds,
2918
+ };
1912
2919
  }
1913
2920
  function syncTransformGizmoToSceneArtifacts(state, reason, revision = state.editorStateRevision, beginSync = true) {
1914
2921
  if (beginSync && !beginSceneArtifactSync(state, reason, revision))
@@ -1925,9 +2932,20 @@ function syncTransformGizmoToSceneArtifacts(state, reason, revision = state.edit
1925
2932
  function syncTransformOperationSettingsToSceneArtifacts(state, reason, revision = state.editorStateRevision) {
1926
2933
  if (!beginSceneArtifactSync(state, reason, revision))
1927
2934
  return;
1928
- state.gizmo?.setOperationSettings(state.transformOperationSettings);
2935
+ state.gizmo?.setOperationSettings(getScopedTransformOperationSettings(state));
1929
2936
  requestEditorSceneFrame(state, reason);
1930
2937
  }
2938
+ function getScopedTransformOperationSettings(state) {
2939
+ if (!state.prefabStage)
2940
+ return state.transformOperationSettings;
2941
+ return updateTransformOperationSettings(state.transformOperationSettings, {
2942
+ snap: {
2943
+ ...state.transformOperationSettings.snap,
2944
+ enabled: false,
2945
+ },
2946
+ placementMode: 'off',
2947
+ });
2948
+ }
1931
2949
  function beginSceneArtifactSync(state, reason, revision) {
1932
2950
  if (revision < state.sceneArtifactRevision) {
1933
2951
  return false;
@@ -1949,7 +2967,7 @@ function requestEditorArtifactRevealFrame(state, reason) {
1949
2967
  coordinator.requestRevealFrame(`${reason}-artifact-reveal`);
1950
2968
  }
1951
2969
  function syncEditorWorldAppearanceFromDocument(state, options, document, reason) {
1952
- const appearance = resolveLocalEditorWorldAppearance(options, document);
2970
+ const appearance = resolveEffectiveLocalEditorWorldAppearance(state, options, document);
1953
2971
  const key = serializeLocalEditorWorldAppearance(appearance);
1954
2972
  if (key === state.worldAppearanceKey)
1955
2973
  return;
@@ -1962,16 +2980,18 @@ function syncEditorWorldAppearanceFromDocument(state, options, document, reason)
1962
2980
  requestEditorSceneFrame(state, reason);
1963
2981
  }
1964
2982
  function syncEditorWorldRenderingFromDocument(state, options, document, reason, refreshProjection = false) {
1965
- const rendering = resolveLocalEditorWorldRendering(options, document);
2983
+ const rendering = resolveEffectiveLocalEditorWorldRendering(state, options, document);
1966
2984
  const key = serializeLocalEditorWorldRendering(rendering);
1967
2985
  if (key === state.worldRenderingKey) {
1968
2986
  if (refreshProjection)
1969
2987
  state.shadowPreview?.refresh();
2988
+ state.world?.refreshSkyBackdrop();
1970
2989
  return;
1971
2990
  }
1972
2991
  state.worldRenderingKey = key;
1973
2992
  state.shadowPreview?.setOptions(rendering.shadowPreview);
1974
2993
  state.environmentTexture?.setProfile(rendering.environment);
2994
+ state.world?.refreshSkyBackdrop();
1975
2995
  state.postProcess?.setProfile(rendering.postProcess);
1976
2996
  requestEditorSceneFrame(state, reason);
1977
2997
  }
@@ -1985,6 +3005,14 @@ function resolveLocalEditorWorldAppearance(options, document) {
1985
3005
  grid: documentAppearance?.grid ?? options.world?.grid,
1986
3006
  };
1987
3007
  }
3008
+ function resolveEffectiveLocalEditorWorldAppearance(state, options, document) {
3009
+ if (!state.prefabStage)
3010
+ return resolveLocalEditorWorldAppearance(options, document);
3011
+ return {
3012
+ ...resolveLocalEditorWorldAppearance(options, document),
3013
+ ...PREFAB_STAGE_STUDIO_WORLD_APPEARANCE,
3014
+ };
3015
+ }
1988
3016
  function resolveLocalEditorWorldRendering(options, document) {
1989
3017
  const documentRendering = document
1990
3018
  ? options.documentAdapter.getWorldRendering?.(document) ?? null
@@ -1995,6 +3023,15 @@ function resolveLocalEditorWorldRendering(options, document) {
1995
3023
  postProcess: documentRendering?.postProcess ?? null,
1996
3024
  };
1997
3025
  }
3026
+ function resolveEffectiveLocalEditorWorldRendering(state, options, document) {
3027
+ const rendering = resolveLocalEditorWorldRendering(options, document);
3028
+ if (!state.prefabStage)
3029
+ return rendering;
3030
+ return {
3031
+ ...rendering,
3032
+ environment: PREFAB_STAGE_STUDIO_ENVIRONMENT,
3033
+ };
3034
+ }
1998
3035
  function hasEditorWorldAppearanceSky(appearance) {
1999
3036
  return !!appearance && Object.prototype.hasOwnProperty.call(appearance, 'sky');
2000
3037
  }
@@ -2119,6 +3156,9 @@ function selectBrowserAsset(state, assetId) {
2119
3156
  function handleBrowserAssetAction(state, options, input) {
2120
3157
  if (input.browserAssetId)
2121
3158
  state.selectedAssetId = input.browserAssetId;
3159
+ if (input.actionId === 'asset.edit-prefab') {
3160
+ return enterPrefabStage(state, options, input);
3161
+ }
2122
3162
  if (input.actionId === 'asset.add-to-scene') {
2123
3163
  return input.assetId ? addAssetToDocument(state, options, input.assetId).ok : false;
2124
3164
  }
@@ -2141,18 +3181,355 @@ function handleBrowserAssetAction(state, options, input) {
2141
3181
  state.statusToneStatus = state.status;
2142
3182
  return true;
2143
3183
  }
2144
- function patchBrowserAssetAction(state, options, input) {
3184
+ function handleInspectorAction(state, options, input) {
3185
+ if (!input.actionId.startsWith('prefab.')) {
3186
+ const activeId = input.targetId ?? state.session?.getState().selection.activeId ?? null;
3187
+ const selectedIds = input.targetIds ?? (activeId ? [activeId] : []);
3188
+ return patchBrowserAssetAction(state, options, {
3189
+ actionId: input.actionId,
3190
+ fieldPath: input.path,
3191
+ value: input.params,
3192
+ }, { activeId, selectedIds });
3193
+ }
3194
+ const assetId = readInspectorActionParamString(input, 'assetId');
3195
+ const browserAssetId = readInspectorActionParamString(input, 'browserAssetId')
3196
+ ?? (assetId ? resolveBrowserAssetItemId(state, options, assetId) : null)
3197
+ ?? undefined;
3198
+ if (input.actionId === 'prefab.edit-definition') {
3199
+ if (!assetId) {
3200
+ state.status = 'Prefab action unavailable: missing prefab asset id';
3201
+ state.statusTone = 'warning';
3202
+ state.statusToneStatus = state.status;
3203
+ return true;
3204
+ }
3205
+ return enterPrefabStage(state, options, {
3206
+ actionId: 'asset.edit-prefab',
3207
+ assetId,
3208
+ browserAssetId,
3209
+ assetKind: 'prefab',
3210
+ });
3211
+ }
3212
+ if (input.actionId === 'prefab.select-definition' || input.actionId === 'prefab.ping-source') {
3213
+ if (!assetId) {
3214
+ state.status = `Prefab action unavailable: ${input.actionId}`;
3215
+ state.statusTone = 'warning';
3216
+ state.statusToneStatus = state.status;
3217
+ return true;
3218
+ }
3219
+ const selected = browserAssetId ? selectBrowserAsset(state, browserAssetId) : false;
3220
+ if (!selected && browserAssetId) {
3221
+ state.status = `Selected asset ${browserAssetId}`;
3222
+ state.statusTone = 'success';
3223
+ state.statusToneStatus = state.status;
3224
+ return true;
3225
+ }
3226
+ if (!browserAssetId) {
3227
+ state.status = `Asset not visible in browser: ${assetId}`;
3228
+ state.statusTone = 'warning';
3229
+ state.statusToneStatus = state.status;
3230
+ return true;
3231
+ }
3232
+ return selected;
3233
+ }
3234
+ state.status = `Prefab inspector action unavailable: ${input.actionId}`;
3235
+ state.statusTone = 'warning';
3236
+ state.statusToneStatus = state.status;
3237
+ return true;
3238
+ }
3239
+ function readInspectorActionParamString(input, key) {
3240
+ const value = input.params?.[key];
3241
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
3242
+ }
3243
+ function enterPrefabStage(state, options, input) {
3244
+ if (state.mode !== 'editor')
3245
+ return false;
3246
+ const sessionState = state.session?.getState({ cloneDocuments: false });
3247
+ const document = sessionState?.workingDocument ?? null;
3248
+ const assetId = input.assetId?.trim();
3249
+ if (!document || !assetId)
3250
+ return false;
3251
+ cancelActiveOperation(state);
3252
+ const previousPrefabStage = state.prefabStage;
3253
+ const asset = findAssetByResolvedId(state, options, assetId);
3254
+ const browserItem = findBrowserAssetItemByResolvedId(state, options, document, input.browserAssetId ?? assetId)
3255
+ ?? (input.browserAssetId ? findBrowserAssetItemByResolvedId(state, options, document, assetId) : null);
3256
+ const descriptor = options.documentAdapter.getPrefabStageDescriptor?.(document, {
3257
+ assetId,
3258
+ browserAssetId: input.browserAssetId,
3259
+ asset,
3260
+ }) ?? createFallbackPrefabStageDescriptor(assetId, input.browserAssetId, browserItem);
3261
+ if (!descriptor) {
3262
+ state.status = `Prefab stage unavailable: ${assetId}`;
3263
+ state.statusTone = 'warning';
3264
+ state.statusToneStatus = state.status;
3265
+ return true;
3266
+ }
3267
+ if (previousPrefabStage)
3268
+ state.prefabComparisonPreview?.clear();
3269
+ state.prefabStage = {
3270
+ descriptor,
3271
+ asset,
3272
+ selectedItemId: 'prefab-root',
3273
+ restoreGridVisible: previousPrefabStage?.restoreGridVisible ?? state.gridVisible,
3274
+ restoreSceneCameraPreviewEnabled: previousPrefabStage?.restoreSceneCameraPreviewEnabled ?? state.sceneCameraPreviewEnabled,
3275
+ };
3276
+ setGridVisibleForEditorContext(state, false, 'prefab-stage-enter-grid');
3277
+ state.sceneCameraPreviewEnabled = false;
3278
+ state.sceneCameraPreview?.setActive(false);
3279
+ state.status = `Editing Prefab ${descriptor.label}`;
3280
+ state.statusTone = 'success';
3281
+ state.statusToneStatus = state.status;
3282
+ state.statusDetails = '';
3283
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
3284
+ syncTransformOperationSettingsToSceneArtifacts(state, 'prefab-stage-enter-operation-settings');
3285
+ return true;
3286
+ }
3287
+ function exitPrefabStage(state, options) {
3288
+ if (!state.prefabStage)
3289
+ return false;
3290
+ const sessionState = state.session?.getState({ cloneDocuments: false });
3291
+ const label = sessionState?.workingDocument
3292
+ ? resolvePrefabStageDescriptorForDocument(state, options, sessionState.workingDocument)?.label ?? state.prefabStage.descriptor.label
3293
+ : state.prefabStage.descriptor.label;
3294
+ const restoreGridVisible = state.prefabStage.restoreGridVisible;
3295
+ const restoreSceneCameraPreviewEnabled = state.prefabStage.restoreSceneCameraPreviewEnabled;
3296
+ state.prefabStage = null;
3297
+ setGridVisibleForEditorContext(state, restoreGridVisible, 'prefab-stage-exit-grid');
3298
+ if (sessionState?.workingDocument) {
3299
+ rebuildProjectionFromDocument(state, options, sessionState.workingDocument, sessionState.selection);
3300
+ }
3301
+ syncTransformOperationSettingsToSceneArtifacts(state, 'prefab-stage-exit-operation-settings');
3302
+ state.sceneCameraPreviewEnabled = restoreSceneCameraPreviewEnabled;
3303
+ if (restoreSceneCameraPreviewEnabled)
3304
+ syncSceneCameraPreview(state, options);
3305
+ else
3306
+ state.sceneCameraPreview?.setActive(false);
3307
+ state.status = `Exited Prefab ${label}`;
3308
+ state.statusTone = 'default';
3309
+ state.statusToneStatus = state.status;
3310
+ state.statusDetails = '';
3311
+ return true;
3312
+ }
3313
+ function selectPrefabStageItem(state, itemId) {
3314
+ if (!state.prefabStage || state.prefabStage.selectedItemId === itemId)
3315
+ return false;
3316
+ state.prefabStage = {
3317
+ ...state.prefabStage,
3318
+ selectedItemId: itemId,
3319
+ };
3320
+ return true;
3321
+ }
3322
+ function selectPrefabStageItemWithPreviewSync(state, options, itemId) {
3323
+ const changed = selectPrefabStageItem(state, itemId);
3324
+ if (changed)
3325
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection');
3326
+ return changed;
3327
+ }
3328
+ function getCurrentPrefabStageProjectionSelection(state, options) {
3329
+ const stage = state.prefabStage;
3330
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3331
+ if (!stage || !document || !stage.selectedItemId)
3332
+ return EMPTY_EDITOR_SELECTION;
3333
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? stage.descriptor;
3334
+ const context = createPrefabStageContext(state, descriptor);
3335
+ const item = findPrefabStageStructureItemById(options.documentAdapter.getPrefabStageStructure?.(document, descriptor, context) ?? createFallbackPrefabStageStructure(descriptor), stage.selectedItemId);
3336
+ if (!item?.nodeId)
3337
+ return EMPTY_EDITOR_SELECTION;
3338
+ const projectionNodeId = resolvePrefabStageProjectionNodeIdForNode(state, options, document, item.nodeId);
3339
+ if (!projectionNodeId)
3340
+ return EMPTY_EDITOR_SELECTION;
3341
+ const target = resolvePrefabStagePreviewTarget(state, options, document, projectionNodeId);
3342
+ if (!target?.selectable)
3343
+ return EMPTY_EDITOR_SELECTION;
3344
+ return {
3345
+ selectedIds: [projectionNodeId],
3346
+ activeId: projectionNodeId,
3347
+ };
3348
+ }
3349
+ function syncPrefabStageSelectionToProjection(state, options, reason = 'prefab-stage-projection-selection', revision = state.editorStateRevision) {
3350
+ if (!state.prefabStage)
3351
+ return;
3352
+ if (!beginSceneArtifactSync(state, reason, revision))
3353
+ return;
3354
+ const projectionSelection = getCurrentPrefabStageProjectionSelection(state, options);
3355
+ state.projection?.syncSelection(projectionSelection);
3356
+ state.gizmo?.setSelection(getCurrentPrefabStageTransformGizmoSelection(state, options, projectionSelection));
3357
+ syncTransformGizmoToSceneArtifacts(state, reason, revision, false);
3358
+ }
3359
+ function getCurrentPrefabStageTransformGizmoSelection(state, options, selection = getCurrentPrefabStageProjectionSelection(state, options)) {
3360
+ const activeId = selection.activeId;
3361
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3362
+ if (!activeId || !state.prefabStage || !document)
3363
+ return EMPTY_EDITOR_SELECTION;
3364
+ const target = resolvePrefabStagePreviewTarget(state, options, document, activeId);
3365
+ return target?.transformable ? selection : EMPTY_EDITOR_SELECTION;
3366
+ }
3367
+ function isPrefabStageProjectionSelectable(state, options, projectionNodeId) {
3368
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3369
+ if (!state.prefabStage || !document)
3370
+ return false;
3371
+ return resolvePrefabStagePreviewTarget(state, options, document, projectionNodeId)?.selectable === true;
3372
+ }
3373
+ function dispatchPrefabStageProjectionSelectionCommand(state, options, command) {
3374
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3375
+ if (!state.prefabStage || !document)
3376
+ return false;
3377
+ const currentProjectionSelection = getCurrentPrefabStageProjectionSelection(state, options);
3378
+ if (command.type === 'selection.clear') {
3379
+ if (state.prefabStage.selectedItemId === null)
3380
+ return false;
3381
+ state.prefabStage = { ...state.prefabStage, selectedItemId: null };
3382
+ state.editorStateRevision += 1;
3383
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-clear', state.editorStateRevision);
3384
+ return true;
3385
+ }
3386
+ const candidateId = ('activeId' in command ? command.activeId : null)
3387
+ ?? command.selectedIds[command.selectedIds.length - 1]
3388
+ ?? null;
3389
+ if (!candidateId)
3390
+ return false;
3391
+ const target = resolvePrefabStagePreviewTarget(state, options, document, candidateId);
3392
+ if (!target?.selectable)
3393
+ return false;
3394
+ const currentlySelected = currentProjectionSelection.selectedIds.includes(candidateId);
3395
+ if ((command.type === 'selection.toggle' && currentlySelected)
3396
+ || (command.type === 'selection.remove' && currentlySelected)) {
3397
+ state.prefabStage = { ...state.prefabStage, selectedItemId: null };
3398
+ state.editorStateRevision += 1;
3399
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-remove', state.editorStateRevision);
3400
+ return true;
3401
+ }
3402
+ if (command.type === 'selection.remove')
3403
+ return false;
3404
+ const nextItemId = target.itemId ?? `prefab-node:${target.targetId}`;
3405
+ if (state.prefabStage.selectedItemId === nextItemId)
3406
+ return false;
3407
+ state.prefabStage = { ...state.prefabStage, selectedItemId: nextItemId };
3408
+ state.editorStateRevision += 1;
3409
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-command', state.editorStateRevision);
3410
+ return true;
3411
+ }
3412
+ function resolvePrefabStagePreviewTarget(state, options, document, projectionNodeId) {
3413
+ const stage = state.prefabStage;
3414
+ if (!stage)
3415
+ return null;
3416
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? stage.descriptor;
3417
+ const context = createPrefabStageContext(state, descriptor);
3418
+ const adapterTarget = options.documentAdapter.resolvePrefabStagePreviewTarget?.(document, descriptor, projectionNodeId, context);
3419
+ if (adapterTarget)
3420
+ return adapterTarget;
3421
+ return resolvePrefabStagePreviewTargetFromStructure(descriptor, projectionNodeId, options.documentAdapter.getPrefabStageStructure?.(document, descriptor, context) ?? createFallbackPrefabStageStructure(descriptor));
3422
+ }
3423
+ function resolvePrefabStageProjectionNodeIdForNode(state, options, document, nodeId) {
3424
+ const stage = state.prefabStage;
3425
+ if (!stage)
3426
+ return null;
3427
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? stage.descriptor;
3428
+ const context = createPrefabStageContext(state, descriptor);
3429
+ const adapterProjectionNodeId = options.documentAdapter.getPrefabStageProjectionNodeId?.(document, descriptor, nodeId, context);
3430
+ if (adapterProjectionNodeId)
3431
+ return adapterProjectionNodeId;
3432
+ const target = findPrefabStageStructureNodeByNodeId(options.documentAdapter.getPrefabStageStructure?.(document, descriptor, context) ?? createFallbackPrefabStageStructure(descriptor), nodeId);
3433
+ return target?.nodeId ? createFallbackPrefabStageProjectionNodeId(descriptor, target.nodeId) : null;
3434
+ }
3435
+ function resolvePrefabStagePreviewTargetFromStructure(descriptor, projectionNodeId, items) {
3436
+ const prefix = descriptor.previewNodeId;
3437
+ if (!prefix || projectionNodeId.includes(':compare-'))
3438
+ return null;
3439
+ for (const item of flattenPrefabStageStructureNodes(items)) {
3440
+ if (!item.nodeId)
3441
+ continue;
3442
+ const expectedProjectionNodeId = createFallbackPrefabStageProjectionNodeId(descriptor, item.nodeId);
3443
+ if (expectedProjectionNodeId !== projectionNodeId)
3444
+ continue;
3445
+ const readonly = descriptor.readonly === true || item.readonly === true;
3446
+ const isRoot = item.kind === 'root';
3447
+ return {
3448
+ scopeId: `prefab-stage:${descriptor.assetId}`,
3449
+ scopeKind: 'prefab-stage',
3450
+ projectionNodeId,
3451
+ targetId: item.nodeId,
3452
+ itemId: item.id,
3453
+ label: item.label,
3454
+ kind: item.kind,
3455
+ selectable: true,
3456
+ transformable: !readonly && !isRoot,
3457
+ readonly,
3458
+ ...(!readonly && !isRoot ? {} : { disabledReason: readonly ? 'Prefab stage is readonly.' : 'Prefab root cannot be transformed from Preview.' }),
3459
+ };
3460
+ }
3461
+ return null;
3462
+ }
3463
+ function createFallbackPrefabStageProjectionNodeId(descriptor, nodeId) {
3464
+ const prefix = descriptor.previewNodeId;
3465
+ if (!prefix)
3466
+ return null;
3467
+ return nodeId === 'prefab-root' ? prefix : `${prefix}:${nodeId}`;
3468
+ }
3469
+ function findPrefabStageStructureNodeByNodeId(items, nodeId) {
3470
+ return flattenPrefabStageStructureNodes(items).find(item => item.nodeId === nodeId) ?? null;
3471
+ }
3472
+ function findPrefabStageStructureItemById(items, itemId) {
3473
+ return flattenPrefabStageStructureNodes(items).find(item => item.id === itemId) ?? null;
3474
+ }
3475
+ function flattenPrefabStageStructureNodes(items) {
3476
+ const flattened = [];
3477
+ for (const item of items) {
3478
+ if (item.itemType === 'prefab-node' && item.nodeId)
3479
+ flattened.push(item);
3480
+ flattened.push(...flattenPrefabStageStructureNodes(item.children ?? []));
3481
+ }
3482
+ return flattened;
3483
+ }
3484
+ function createFallbackPrefabStageDescriptor(assetId, browserAssetId, item) {
3485
+ if (!item?.prefab)
3486
+ return null;
3487
+ return {
3488
+ assetId: item.prefab.id || item.assetId || assetId,
3489
+ browserAssetId: browserAssetId ?? item.id,
3490
+ label: item.prefab.name || item.label || item.prefab.id || assetId,
3491
+ sourceAssetId: item.prefab.sourceAssetId,
3492
+ sourceAssetGuid: item.prefab.sourceAssetGuid,
3493
+ readonly: item.prefab.readonly === true,
3494
+ previewNodeId: `prefab-stage:${item.prefab.id || item.assetId || assetId}`,
3495
+ };
3496
+ }
3497
+ function resolvePrefabStageDescriptorForDocument(state, options, document) {
3498
+ const stage = state.prefabStage;
3499
+ if (!stage)
3500
+ return null;
3501
+ const assetId = stage.descriptor.assetId;
3502
+ const browserAssetId = stage.descriptor.browserAssetId;
3503
+ const descriptor = options.documentAdapter.getPrefabStageDescriptor?.(document, {
3504
+ assetId,
3505
+ browserAssetId,
3506
+ asset: stage.asset,
3507
+ });
3508
+ if (descriptor) {
3509
+ return {
3510
+ ...stage.descriptor,
3511
+ ...descriptor,
3512
+ browserAssetId: descriptor.browserAssetId ?? browserAssetId,
3513
+ previewNodeId: descriptor.previewNodeId ?? stage.descriptor.previewNodeId,
3514
+ };
3515
+ }
3516
+ const browserItem = findBrowserAssetItemByResolvedId(state, options, document, browserAssetId ?? assetId)
3517
+ ?? (browserAssetId ? findBrowserAssetItemByResolvedId(state, options, document, assetId) : null);
3518
+ return createFallbackPrefabStageDescriptor(assetId, browserAssetId, browserItem) ?? stage.descriptor;
3519
+ }
3520
+ function patchBrowserAssetAction(state, options, input, selectionOverride) {
2145
3521
  const session = state.session;
2146
3522
  const document = session?.getState().workingDocument ?? null;
2147
3523
  if (!session || !document || !options.documentAdapter.createAssetActionPatch)
2148
3524
  return false;
2149
3525
  const asset = input.assetId ? findAssetByResolvedId(state, options, input.assetId) : null;
3526
+ const sessionSelection = session.getState().selection;
2150
3527
  const patch = options.documentAdapter.createAssetActionPatch({
2151
3528
  ...input,
2152
3529
  document,
2153
3530
  asset,
2154
- activeId: session.getState().selection.activeId,
2155
- selectedIds: [...session.getState().selection.selectedIds],
3531
+ activeId: selectionOverride?.activeId ?? sessionSelection.activeId,
3532
+ selectedIds: selectionOverride?.selectedIds ?? [...sessionSelection.selectedIds],
2156
3533
  });
2157
3534
  if (!patch)
2158
3535
  return false;
@@ -2175,12 +3552,52 @@ function patchBrowserAssetAction(state, options, input) {
2175
3552
  reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
2176
3553
  else if (patch.changedIds)
2177
3554
  syncProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds);
3555
+ else if (state.prefabStage && result.documentChanged) {
3556
+ rebuildProjectionFromDocument(state, options, result.workingDocument, EMPTY_EDITOR_SELECTION);
3557
+ }
3558
+ if (result.documentChanged && isPrefabStageShadowAssetAction(input)) {
3559
+ state.shadowPreview?.invalidateStaticProjectedArtifact?.();
3560
+ scheduleProjectionShadowPreviewRefresh(state, 'prefab-stage-shadow-policy-action');
3561
+ }
3562
+ syncPrefabStageSelectionFromAssetAction(state, options, input, patch.createdId ?? patch.changedId ?? null);
2178
3563
  if (input.actionId === 'asset.delete-material' && input.browserAssetId && state.selectedAssetId === input.browserAssetId) {
2179
3564
  state.selectedAssetId = null;
2180
3565
  }
2181
3566
  selectCreatedBrowserAssetFromPatch(state, options, patch.createdId ?? null, input);
2182
3567
  return true;
2183
3568
  }
3569
+ function isPrefabStageShadowAssetAction(input) {
3570
+ if (input.actionId === 'prefab.node.setShadow')
3571
+ return true;
3572
+ if (input.actionId !== 'asset.edit-prefab-field')
3573
+ return false;
3574
+ const fieldPath = typeof input.fieldPath === 'string' ? input.fieldPath.trim() : '';
3575
+ return fieldPath === 'prefab.defaults.shadowMode' || fieldPath.startsWith('prefab.defaults.shadow.');
3576
+ }
3577
+ function syncPrefabStageSelectionFromAssetAction(state, options, input, nodeId) {
3578
+ if (!state.prefabStage)
3579
+ return;
3580
+ const isPrefabNodeAction = input.actionId.startsWith('prefab.node.')
3581
+ || input.actionId === 'asset.add-to-current-prefab';
3582
+ if (!isPrefabNodeAction)
3583
+ return;
3584
+ if (input.actionId === 'prefab.node.delete') {
3585
+ const rootId = typeof nodeId === 'string' && nodeId ? nodeId : 'prefab-root';
3586
+ state.prefabStage = {
3587
+ ...state.prefabStage,
3588
+ selectedItemId: `prefab-node:${rootId}`,
3589
+ };
3590
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-delete');
3591
+ return;
3592
+ }
3593
+ if (!nodeId)
3594
+ return;
3595
+ state.prefabStage = {
3596
+ ...state.prefabStage,
3597
+ selectedItemId: `prefab-node:${nodeId}`,
3598
+ };
3599
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-asset-action');
3600
+ }
2184
3601
  function selectCreatedBrowserAssetFromPatch(state, options, createdId, input) {
2185
3602
  if (!createdId)
2186
3603
  return;
@@ -2191,12 +3608,18 @@ function selectCreatedBrowserAssetFromPatch(state, options, createdId, input) {
2191
3608
  else if (input.actionId === 'asset.create-material' || input.actionId === 'asset.duplicate-material') {
2192
3609
  state.selectedAssetId = `material:${createdId}`;
2193
3610
  }
3611
+ else if (input.actionId === 'asset.create-prefab'
3612
+ || input.actionId === 'asset.create-prefab-from-game-object'
3613
+ || input.actionId === 'asset.duplicate-prefab') {
3614
+ state.selectedAssetId = resolveBrowserAssetItemId(state, options, `prefab:${createdId}`) ?? `prefab:${createdId}`;
3615
+ }
2194
3616
  }
2195
3617
  function armAssetPlacement(state, options, assetId) {
2196
3618
  if (state.mode !== 'editor')
2197
3619
  return false;
2198
3620
  cancelActiveOperation(state);
2199
- const asset = findAssetByResolvedId(state, options, assetId);
3621
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3622
+ const asset = findAssetOrDocumentBrowserItemByResolvedId(state, options, document, assetId);
2200
3623
  if (!asset)
2201
3624
  return false;
2202
3625
  state.armedPlacement = { assetId, asset };
@@ -2298,7 +3721,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
2298
3721
  state.status = 'Asset creation rejected: document is not loaded';
2299
3722
  return { ok: false, assetId, changed: false, status: state.status, error: 'document_not_loaded' };
2300
3723
  }
2301
- const asset = findAssetByResolvedId(state, options, assetId);
3724
+ const asset = findAssetOrDocumentBrowserItemByResolvedId(state, options, beforeDocument, assetId);
2302
3725
  if (!asset) {
2303
3726
  state.status = `Asset creation rejected: ${assetId} not found`;
2304
3727
  return { ok: false, assetId, changed: false, status: state.status, error: 'asset_not_found' };
@@ -2307,6 +3730,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
2307
3730
  document: beforeDocument,
2308
3731
  asset,
2309
3732
  assetId,
3733
+ name: createOptions.name,
2310
3734
  placement: createOptions.placement,
2311
3735
  });
2312
3736
  const result = session.dispatch({
@@ -2322,7 +3746,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
2322
3746
  ?? options.documentAdapter.findCreatedId?.(beforeDocument, result.workingDocument)
2323
3747
  ?? null;
2324
3748
  let selectionResult = null;
2325
- if (createdId) {
3749
+ if (createdId && createOptions.select !== false) {
2326
3750
  selectionResult = session.dispatch({
2327
3751
  type: 'selection.replace',
2328
3752
  selectedIds: [createdId],
@@ -2340,7 +3764,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
2340
3764
  if (projectedNode) {
2341
3765
  state.projection?.projectNode(projectedNode);
2342
3766
  if (selectionResult)
2343
- syncSelectionToProjection(state, selectionResult.selection);
3767
+ syncSelectionToProjection(state, options, selectionResult.selection);
2344
3768
  invalidateEditorScene(state, 'projection-project-node');
2345
3769
  }
2346
3770
  }
@@ -2367,6 +3791,12 @@ function patchSerializedProperty(state, options, input) {
2367
3791
  return false;
2368
3792
  }
2369
3793
  const payload = transaction.payload;
3794
+ if (state.prefabStage) {
3795
+ return patchPrefabStageInspectorProperty(state, options, payload);
3796
+ }
3797
+ const markerGraphPatched = patchMarkerGraphInspectorProperty(state, options, document, payload);
3798
+ if (markerGraphPatched !== null)
3799
+ return markerGraphPatched;
2370
3800
  const shouldReprojectInspectorEdit = shouldReprojectEditorInspectorEditPath(payload.path);
2371
3801
  if (targetIds.length > 1) {
2372
3802
  const patch = options.documentAdapter.createSerializedMultiPropertyPatch?.({
@@ -2385,9 +3815,11 @@ function patchSerializedProperty(state, options, input) {
2385
3815
  return false;
2386
3816
  const changedIds = patch.changedIds ?? targetIds;
2387
3817
  const projectionMetadataPatch = isSerializedProjectionMetadataPath(payload.path);
3818
+ const shadowTopologyMetadataPatch = isSerializedShadowTopologyMetadataPath(payload.path);
2388
3819
  const largeProjectionMetadataPatch = projectionMetadataPatch
3820
+ && !shadowTopologyMetadataPatch
2389
3821
  && changedIds.length >= LARGE_SHADOW_MODE_BATCH_THRESHOLD;
2390
- const invalidateStaticShadowArtifact = projectionMetadataPatch && payload.path === 'shadowMode';
3822
+ const invalidateStaticShadowArtifact = projectionMetadataPatch && isSerializedShadowMetadataPath(payload.path);
2391
3823
  const result = state.session.dispatch({
2392
3824
  type: 'document.patch',
2393
3825
  label: patch.label ?? `Patch ${payload.path} on ${targetIds.length} objects`,
@@ -2416,6 +3848,9 @@ function patchSerializedProperty(state, options, input) {
2416
3848
  reprojectProjectionForChangedIds(state, options, workingDocument, patch.reprojectIds);
2417
3849
  else if (shouldReprojectInspectorEdit)
2418
3850
  reprojectProjectionForChangedIds(state, options, workingDocument, changedIds);
3851
+ else if (shadowTopologyMetadataPatch) {
3852
+ rebuildProjectionAfterShadowTopologyMetadataPatch(state, options, workingDocument, sessionState.selection);
3853
+ }
2419
3854
  else if (isSerializedProjectionMetadataPath(payload.path)) {
2420
3855
  syncProjectionMetadataForChangedIds(state, options, workingDocument, projectionSyncIds, {
2421
3856
  deferShadowPreviewRefresh: largeProjectionMetadataPatch,
@@ -2424,6 +3859,10 @@ function patchSerializedProperty(state, options, input) {
2424
3859
  }
2425
3860
  else
2426
3861
  syncProjectionForChangedIds(state, options, workingDocument, projectionSyncIds);
3862
+ if (isMarkerInspectorEditPath(payload.path)) {
3863
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
3864
+ state.gizmo?.refreshSelection();
3865
+ }
2427
3866
  state.summary = summarizeDocument(options, workingDocument, state.session.getSource());
2428
3867
  state.status = patch.label ?? `Patch ${payload.path} on ${targetIds.length} objects`;
2429
3868
  return true;
@@ -2459,9 +3898,13 @@ function patchSerializedProperty(state, options, input) {
2459
3898
  else if (shouldReprojectInspectorEdit) {
2460
3899
  reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds ?? [patch.changedId ?? payload.targetId]);
2461
3900
  }
3901
+ else if (isSerializedShadowTopologyMetadataPath(payload.path)) {
3902
+ const selection = state.session?.getState({ cloneDocuments: false }).selection ?? EMPTY_EDITOR_SELECTION;
3903
+ rebuildProjectionAfterShadowTopologyMetadataPatch(state, options, result.workingDocument, selection);
3904
+ }
2462
3905
  else if (isSerializedProjectionMetadataPath(payload.path)) {
2463
- syncProjectionMetadataForChangedIds(state, options, result.workingDocument, [patch.changedId ?? payload.targetId], {
2464
- invalidateStaticShadowArtifact: payload.path === 'shadowMode',
3906
+ syncProjectionMetadataForChangedIds(state, options, result.workingDocument, patch.changedIds ?? [patch.changedId ?? payload.targetId], {
3907
+ invalidateStaticShadowArtifact: isSerializedShadowMetadataPath(payload.path),
2465
3908
  });
2466
3909
  }
2467
3910
  else if (isSerializedTransformPath(payload.path))
@@ -2470,11 +3913,313 @@ function patchSerializedProperty(state, options, input) {
2470
3913
  syncProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds);
2471
3914
  else
2472
3915
  syncProjectionForDispatchResult(state, options, result, patch.changedId ?? payload.targetId);
3916
+ if (isMarkerInspectorEditPath(payload.path)) {
3917
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
3918
+ state.gizmo?.refreshSelection();
3919
+ }
2473
3920
  state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2474
3921
  state.status = patch.label ?? `Patched ${payload.path}`;
2475
3922
  return true;
2476
3923
  }
3924
+ function patchPrefabStageInspectorProperty(state, options, payload) {
3925
+ if (payload.persistence !== 'document') {
3926
+ state.status = `Prefab field is not document-editable: ${payload.path}.`;
3927
+ return false;
3928
+ }
3929
+ const session = state.session;
3930
+ const document = session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3931
+ if (!document)
3932
+ return false;
3933
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? state.prefabStage?.descriptor ?? null;
3934
+ if (!descriptor) {
3935
+ state.status = 'Prefab stage is not ready.';
3936
+ return false;
3937
+ }
3938
+ return patchBrowserAssetAction(state, options, {
3939
+ actionId: 'asset.edit-prefab-field',
3940
+ assetId: descriptor.assetId,
3941
+ browserAssetId: descriptor.browserAssetId,
3942
+ assetKind: 'prefab',
3943
+ fieldPath: payload.path,
3944
+ value: payload.value,
3945
+ });
3946
+ }
3947
+ function isMarkerInspectorEditPath(path) {
3948
+ return path === 'name' || path.startsWith('marker.');
3949
+ }
3950
+ function patchMarkerGraphInspectorProperty(state, options, document, payload) {
3951
+ if (!isMarkerInspectorEditPath(payload.path))
3952
+ return null;
3953
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
3954
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
3955
+ if (!readMarkerGraphHierarchyMarkerIdForGraph(graph, payload.targetId, hierarchy))
3956
+ return null;
3957
+ const markerTypes = options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [];
3958
+ const command = createMarkerGraphPatchFromInspectorEdit(graph, payload, hierarchy, markerTypes);
3959
+ if (!command) {
3960
+ state.status = `Marker edit rejected: ${payload.path}`;
3961
+ return false;
3962
+ }
3963
+ const patch = options.documentAdapter.createMarkerGraphPatch?.(document, command);
3964
+ if (!patch) {
3965
+ state.status = `Marker edit rejected: ${payload.targetId}`;
3966
+ return false;
3967
+ }
3968
+ const result = state.session?.dispatch({
3969
+ type: 'document.patch',
3970
+ label: patch.label ?? `Patch marker ${payload.path}`,
3971
+ patch: patch.patch,
3972
+ targetId: payload.targetId,
3973
+ });
3974
+ if (!result?.documentChanged)
3975
+ return false;
3976
+ syncProjectionForDispatchResult(state, options, result, patch.changedId ?? payload.targetId);
3977
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
3978
+ state.gizmo?.refreshSelection();
3979
+ state.summary = summarizeDocument(options, result.workingDocument, state.session?.getSource());
3980
+ state.status = patch.label ?? `Patched marker ${payload.path}`;
3981
+ return true;
3982
+ }
3983
+ function patchMarkerTypeCatalog(state, options, input) {
3984
+ if (state.mode !== 'editor')
3985
+ return false;
3986
+ cancelActiveOperation(state);
3987
+ const document = state.session?.getState().workingDocument ?? null;
3988
+ if (!document)
3989
+ return false;
3990
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
3991
+ if (!graph || !options.documentAdapter.createMarkerGraphPatch)
3992
+ return false;
3993
+ const command = createMarkerTypeCatalogCommand(input);
3994
+ if (!command) {
3995
+ state.status = 'Marker type edit rejected';
3996
+ state.statusTone = 'warning';
3997
+ state.statusToneStatus = state.status;
3998
+ return false;
3999
+ }
4000
+ const patch = options.documentAdapter.createMarkerGraphPatch(document, command);
4001
+ if (!patch) {
4002
+ state.status = `Marker type edit rejected: ${input.type}`;
4003
+ state.statusTone = 'warning';
4004
+ state.statusToneStatus = state.status;
4005
+ return false;
4006
+ }
4007
+ const session = state.session;
4008
+ if (!session)
4009
+ return false;
4010
+ const result = session.dispatch({
4011
+ type: 'document.patch',
4012
+ label: patch.label ?? 'Patch marker type catalog',
4013
+ patch: patch.patch,
4014
+ targetId: session.getState().selection.activeId ?? undefined,
4015
+ });
4016
+ if (!result.documentChanged)
4017
+ return false;
4018
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
4019
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
4020
+ state.status = patch.label ?? 'Patched marker type catalog';
4021
+ state.statusTone = 'success';
4022
+ state.statusToneStatus = state.status;
4023
+ state.statusDetails = '';
4024
+ return true;
4025
+ }
4026
+ function patchMarkerRelation(state, options, input) {
4027
+ if (state.mode !== 'editor')
4028
+ return false;
4029
+ cancelActiveOperation(state);
4030
+ const document = state.session?.getState().workingDocument ?? null;
4031
+ if (!document)
4032
+ return false;
4033
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
4034
+ if (!graph)
4035
+ return false;
4036
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
4037
+ const activeId = state.session?.getState().selection.activeId ?? null;
4038
+ const activeMarkerId = activeId ? readSpatialMarkerIdFromHierarchySelection(graph, activeId, hierarchy) : null;
4039
+ if (input.action === 'pick-cancel') {
4040
+ state.markerRelationPick = null;
4041
+ state.status = '已取消关系端点拾取。';
4042
+ state.statusTone = 'success';
4043
+ state.statusToneStatus = state.status;
4044
+ state.statusDetails = '';
4045
+ return true;
4046
+ }
4047
+ if (input.action === 'pick-start') {
4048
+ const relationTypes = options.documentAdapter.getRelationTypeCatalog?.(document) ?? [];
4049
+ const type = input.type.trim();
4050
+ if (!type) {
4051
+ state.status = '请选择关系类型。';
4052
+ state.statusTone = 'warning';
4053
+ state.statusToneStatus = state.status;
4054
+ state.statusDetails = state.status;
4055
+ return true;
4056
+ }
4057
+ const typeDefinition = relationTypes.find(definition => definition.type === type);
4058
+ const from = createMarkerRelationPickEndpoint(input.fromKind, input.fromId);
4059
+ const directed = input.directed ?? typeDefinition?.directed ?? true;
4060
+ const typeLabel = typeDefinition?.label ?? type;
4061
+ const status = from
4062
+ ? `关系拾取中:${formatMarkerRelationPickEndpointLabel(from, graph, hierarchy)} ${typeLabel} ... 请在视口点击终点。`
4063
+ : `关系拾取中:请选择 ${typeLabel} 的起点。`;
4064
+ state.markerRelationPick = {
4065
+ active: true,
4066
+ type,
4067
+ typeLabel,
4068
+ directed,
4069
+ from,
4070
+ status,
4071
+ statusTone: 'success',
4072
+ };
4073
+ state.status = status;
4074
+ state.statusTone = 'success';
4075
+ state.statusToneStatus = state.status;
4076
+ state.statusDetails = '关系拾取会消费下一次有效视口点击,不改变当前选择。';
4077
+ return true;
4078
+ }
4079
+ if (input.action === 'select') {
4080
+ const relationId = input.relationId.trim();
4081
+ if (!relationId)
4082
+ return false;
4083
+ const relation = graph.relations.find(candidate => candidate.id === relationId) ?? null;
4084
+ state.activeMarkerRelationId = relationId;
4085
+ state.status = relation ? `高亮关系:${relation.label ?? relation.type}` : `关系不存在:${relationId}`;
4086
+ state.statusTone = relation ? 'success' : 'warning';
4087
+ state.statusToneStatus = state.status;
4088
+ state.statusDetails = relation ? relationId : state.status;
4089
+ syncMarkerOverlayToSceneArtifacts(state, options, state.session?.getState().selection ?? { selectedIds: [], activeId: null });
4090
+ requestEditorSceneFrame(state, 'marker-relation-select');
4091
+ return true;
4092
+ }
4093
+ if (!options.documentAdapter.createMarkerGraphPatch)
4094
+ return false;
4095
+ const relationTypes = options.documentAdapter.getRelationTypeCatalog?.(document) ?? [];
4096
+ const result = input.action === 'reverse'
4097
+ ? createMarkerRelationReverseCommand(input.relationId, graph)
4098
+ : input.action === 'delete-connected'
4099
+ ? createMarkerRelationDeleteConnectedCommand(input.markerId ?? activeMarkerId ?? '', graph)
4100
+ : input.action === 'create' && input.toEndpoints && input.toEndpoints.length > 0
4101
+ ? createMarkerRelationCreateManyCommand({
4102
+ type: input.type,
4103
+ fromKind: input.fromKind,
4104
+ fromId: input.fromId,
4105
+ toEndpoints: input.toEndpoints,
4106
+ directed: input.directed,
4107
+ }, graph, relationTypes, hierarchy, activeMarkerId)
4108
+ : createMarkerRelationCommand(input, graph, relationTypes, hierarchy, activeMarkerId);
4109
+ if (!result.command) {
4110
+ state.status = result.status;
4111
+ state.statusTone = result.statusTone;
4112
+ state.statusToneStatus = state.status;
4113
+ state.statusDetails = result.status;
4114
+ return true;
4115
+ }
4116
+ const patch = options.documentAdapter.createMarkerGraphPatch(document, result.command);
4117
+ if (!patch) {
4118
+ state.status = result.status || 'Marker relation edit rejected';
4119
+ state.statusTone = 'warning';
4120
+ state.statusToneStatus = state.status;
4121
+ state.statusDetails = state.status;
4122
+ return true;
4123
+ }
4124
+ const session = state.session;
4125
+ if (!session)
4126
+ return false;
4127
+ const patchResult = session.dispatch({
4128
+ type: 'document.patch',
4129
+ label: patch.label ?? result.status,
4130
+ patch: patch.patch,
4131
+ targetId: session.getState().selection.activeId ?? undefined,
4132
+ });
4133
+ if (!patchResult.documentChanged)
4134
+ return false;
4135
+ if (doesMarkerGraphCommandDeleteRelation(result.command, state.activeMarkerRelationId)) {
4136
+ state.activeMarkerRelationId = null;
4137
+ }
4138
+ if (!isMarkerRelationStillPresent(options, patchResult.workingDocument, state.activeMarkerRelationId)) {
4139
+ state.activeMarkerRelationId = null;
4140
+ }
4141
+ state.markerRelationPick = null;
4142
+ syncMarkerOverlayToSceneArtifacts(state, options, patchResult.selection);
4143
+ state.gizmo?.refreshSelection();
4144
+ state.summary = summarizeDocument(options, patchResult.workingDocument, session.getSource());
4145
+ state.status = patch.label ?? result.status;
4146
+ state.statusTone = result.statusTone;
4147
+ state.statusToneStatus = state.status;
4148
+ state.statusDetails = '';
4149
+ return true;
4150
+ }
4151
+ function createMarkerRelationPickEndpoint(kind, id) {
4152
+ const endpointKind = kind?.trim();
4153
+ const endpointId = id?.trim();
4154
+ if (!endpointKind || !endpointId)
4155
+ return null;
4156
+ if (endpointKind !== 'marker' && endpointKind !== 'scene-object')
4157
+ return null;
4158
+ return { kind: endpointKind, id: endpointId };
4159
+ }
4160
+ function formatMarkerRelationPickEndpointLabel(endpoint, graph, hierarchy) {
4161
+ const label = endpoint.kind === 'marker'
4162
+ ? findSpatialMarkerById(graph, endpoint.id)?.label ?? endpoint.id
4163
+ : hierarchy.find(item => item.id === endpoint.id)?.label ?? endpoint.id;
4164
+ const kindLabel = endpoint.kind === 'marker' ? '标记' : '对象';
4165
+ return `${kindLabel} ${label}`;
4166
+ }
4167
+ function doesMarkerGraphCommandDeleteRelation(command, relationId) {
4168
+ if (!relationId)
4169
+ return false;
4170
+ switch (command.type) {
4171
+ case 'relation.delete':
4172
+ return command.relationId === relationId;
4173
+ case 'relation.delete-many':
4174
+ return command.relationIds.includes(relationId);
4175
+ default:
4176
+ return false;
4177
+ }
4178
+ }
4179
+ function isMarkerRelationStillPresent(options, document, relationId) {
4180
+ if (!relationId)
4181
+ return true;
4182
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
4183
+ return !!graph?.relations.some(relation => relation.id === relationId);
4184
+ }
4185
+ function createMarkerTypeCatalogCommand(input) {
4186
+ const type = input.type.trim();
4187
+ if (!type)
4188
+ return null;
4189
+ if (input.action === 'delete') {
4190
+ return {
4191
+ type: 'marker-type.delete',
4192
+ typeId: type,
4193
+ };
4194
+ }
4195
+ const label = input.label?.trim() || type;
4196
+ const kind = input.kind?.trim() || undefined;
4197
+ const description = 'description' in input ? input.description?.trim() ?? '' : undefined;
4198
+ if (input.action === 'create') {
4199
+ return {
4200
+ type: 'marker-type.create',
4201
+ definition: {
4202
+ type,
4203
+ label,
4204
+ ...(kind ? { kind } : {}),
4205
+ ...(description ? { description } : {}),
4206
+ },
4207
+ };
4208
+ }
4209
+ return {
4210
+ type: 'marker-type.update',
4211
+ typeId: type,
4212
+ patch: {
4213
+ label,
4214
+ ...(kind !== undefined ? { kind } : {}),
4215
+ ...(description !== undefined ? { description } : {}),
4216
+ },
4217
+ };
4218
+ }
2477
4219
  function patchHierarchyItemEnabled(state, options, input) {
4220
+ const markerVisibilityResult = patchMarkerGraphHierarchyVisibility(state, options, input);
4221
+ if (markerVisibilityResult !== 'unhandled')
4222
+ return markerVisibilityResult === 'changed';
2478
4223
  return patchSerializedProperty(state, options, {
2479
4224
  targetId: input.id,
2480
4225
  path: 'enabled',
@@ -2486,6 +4231,47 @@ function patchHierarchyItemEnabled(state, options, input) {
2486
4231
  source: 'toggle',
2487
4232
  });
2488
4233
  }
4234
+ function patchMarkerGraphHierarchyVisibility(state, options, input) {
4235
+ const document = state.session?.getState().workingDocument ?? null;
4236
+ if (!document)
4237
+ return 'unhandled';
4238
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
4239
+ if (!graph)
4240
+ return 'unhandled';
4241
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
4242
+ const visibility = state.markerGraphVisibility;
4243
+ if (isMarkerGraphHierarchyRootRowId(graph, input.id, hierarchy)) {
4244
+ if (visibility.rootVisible === input.enabled)
4245
+ return 'handled';
4246
+ visibility.rootVisible = input.enabled;
4247
+ state.editorStateRevision += 1;
4248
+ syncCurrentSelectionToSceneArtifacts(state, options, 'marker-graph-visibility-toggle', state.editorStateRevision);
4249
+ state.status = input.enabled ? 'Markers shown' : 'Markers hidden';
4250
+ state.statusTone = 'success';
4251
+ state.statusToneStatus = state.status;
4252
+ state.statusDetails = '';
4253
+ return 'changed';
4254
+ }
4255
+ const markerId = readMarkerGraphHierarchyMarkerIdForGraph(graph, input.id, hierarchy)
4256
+ ?? (isSpatialMarkerBackedByHierarchyRow(graph, input.id, hierarchy) ? input.id : null);
4257
+ if (!markerId)
4258
+ return 'unhandled';
4259
+ const changed = input.enabled
4260
+ ? visibility.hiddenMarkerIds.delete(markerId)
4261
+ : !visibility.hiddenMarkerIds.has(markerId);
4262
+ if (!input.enabled)
4263
+ visibility.hiddenMarkerIds.add(markerId);
4264
+ if (!changed)
4265
+ return 'handled';
4266
+ state.editorStateRevision += 1;
4267
+ syncCurrentSelectionToSceneArtifacts(state, options, 'marker-visibility-toggle', state.editorStateRevision);
4268
+ const marker = findSpatialMarkerById(graph, markerId);
4269
+ state.status = `${marker?.label ?? markerId} ${input.enabled ? 'shown' : 'hidden'}`;
4270
+ state.statusTone = 'success';
4271
+ state.statusToneStatus = state.status;
4272
+ state.statusDetails = '';
4273
+ return 'changed';
4274
+ }
2489
4275
  async function applyRenderingPropertyChange(state, options, input) {
2490
4276
  if (state.mode !== 'editor')
2491
4277
  return false;
@@ -2547,17 +4333,26 @@ async function applyRenderingPropertyChange(state, options, input) {
2547
4333
  }
2548
4334
  }
2549
4335
  async function applyRenderingAction(state, options, input, render) {
2550
- if (state.mode !== 'editor')
2551
- return false;
2552
- if (!state.session || !options.documentAdapter.onRenderingAction)
2553
- return false;
4336
+ if (state.mode !== 'editor') {
4337
+ return { ok: false, changed: false, rendered: false, status: 'Editor is not in edit mode.', statusTone: 'warning' };
4338
+ }
4339
+ if (!state.session || !options.documentAdapter.onRenderingAction) {
4340
+ return { ok: false, changed: false, rendered: false, status: 'Rendering action pipeline is unavailable.', statusTone: 'warning' };
4341
+ }
2554
4342
  if (state.renderingAction) {
2555
4343
  state.status = state.renderingAction.status ?? `Rendering action already running: ${state.renderingAction.actionId}`;
2556
4344
  state.statusTone = state.renderingAction.statusTone ?? 'default';
2557
4345
  state.statusToneStatus = state.status;
2558
4346
  state.statusDetails = state.renderingAction.statusDetails ?? '';
2559
4347
  render();
2560
- return true;
4348
+ return {
4349
+ ok: false,
4350
+ changed: false,
4351
+ rendered: true,
4352
+ status: state.status,
4353
+ statusTone: state.statusTone,
4354
+ ...(state.statusDetails ? { statusDetails: state.statusDetails } : {}),
4355
+ };
2561
4356
  }
2562
4357
  const document = state.session.getState().workingDocument;
2563
4358
  const progressStartedAt = Date.now();
@@ -2606,6 +4401,7 @@ async function applyRenderingAction(state, options, input, render) {
2606
4401
  || resultObject?.changed === true
2607
4402
  || resultObject?.refreshWorldRendering === true
2608
4403
  || documentPatchResult?.changed === true;
4404
+ const rendered = changed || !!resultObject?.status;
2609
4405
  if (resultObject?.status) {
2610
4406
  state.status = resultObject.status;
2611
4407
  state.statusTone = resultObject.statusTone ?? 'default';
@@ -2629,7 +4425,15 @@ async function applyRenderingAction(state, options, input, render) {
2629
4425
  invalidateEditorScene(state, 'rendering-panel-action', options);
2630
4426
  }
2631
4427
  clearRenderingActionAfterMinimumDuration(state, progressStartedAt, input.actionId, render);
2632
- return changed || !!resultObject?.status;
4428
+ return {
4429
+ ok: resultObject?.statusTone !== 'error',
4430
+ changed,
4431
+ rendered,
4432
+ ...(state.status ? { status: state.status } : {}),
4433
+ statusTone: state.statusTone,
4434
+ ...(state.statusDetails ? { statusDetails: state.statusDetails } : {}),
4435
+ ...(resultObject?.details ? { details: resultObject.details } : {}),
4436
+ };
2633
4437
  }
2634
4438
  catch (error) {
2635
4439
  state.renderingAction = null;
@@ -2638,7 +4442,15 @@ async function applyRenderingAction(state, options, input, render) {
2638
4442
  state.statusToneStatus = state.status;
2639
4443
  state.statusDetails = state.status;
2640
4444
  console.error('[LocalEditorHarness] rendering action failed', error);
2641
- return true;
4445
+ return {
4446
+ ok: false,
4447
+ changed: false,
4448
+ rendered: true,
4449
+ status: state.status,
4450
+ statusTone: 'error',
4451
+ statusDetails: state.statusDetails,
4452
+ error: state.status,
4453
+ };
2642
4454
  }
2643
4455
  }
2644
4456
  function clearRenderingActionAfterMinimumDuration(state, startedAt, actionId, render) {
@@ -2735,6 +4547,11 @@ function findInspectorPropertyForEdit(state, options, document, input, targetIds
2735
4547
  return findInspectorPropertyByPath(inspector, input.path);
2736
4548
  }
2737
4549
  function createInspectorObjectForEdit(state, options, document, input, targetIds) {
4550
+ if (targetIds.length === 1 && state.prefabStage) {
4551
+ const inspector = createPrefabStageInspectorObjectForEdit(state, options, document);
4552
+ if (inspector && inspector.targetIds.includes(input.targetId))
4553
+ return inspector;
4554
+ }
2738
4555
  if (targetIds.length > 1) {
2739
4556
  const activeId = state.session?.getState().selection.activeId ?? input.targetId ?? null;
2740
4557
  const inspector = createInspectorMultiObjectBase(state, options, document, targetIds, activeId);
@@ -2742,6 +4559,9 @@ function createInspectorObjectForEdit(state, options, document, input, targetIds
2742
4559
  return withDefaultEditorInspectorSections(state, options, document, inspector);
2743
4560
  return null;
2744
4561
  }
4562
+ const markerInspector = createMarkerInspectorObjectBase(options, document, input.targetId);
4563
+ if (markerInspector)
4564
+ return markerInspector;
2745
4565
  const inspector = options.documentAdapter.getInspectorObject?.(document, input.targetId) ?? null;
2746
4566
  if (inspector)
2747
4567
  return withDefaultEditorInspectorSections(state, options, document, inspector);
@@ -2750,6 +4570,13 @@ function createInspectorObjectForEdit(state, options, document, input, targetIds
2750
4570
  ? withDefaultEditorInspectorSections(state, options, document, serializedObjectToInspectorObject(serializedObject, document))
2751
4571
  : null;
2752
4572
  }
4573
+ function createPrefabStageInspectorObjectForEdit(state, options, document) {
4574
+ const stage = state.prefabStage;
4575
+ if (!stage || !options.documentAdapter.getPrefabStageInspectorObject)
4576
+ return null;
4577
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? stage.descriptor;
4578
+ return options.documentAdapter.getPrefabStageInspectorObject(document, descriptor, stage.selectedItemId, createPrefabStageContext(state, descriptor)) ?? null;
4579
+ }
2753
4580
  function findInspectorPropertyByPath(inspector, path) {
2754
4581
  for (const section of inspector.sections) {
2755
4582
  const property = section.properties.find(candidate => candidate.path === path);
@@ -2793,14 +4620,28 @@ function isSerializedTransformPath(path) {
2793
4620
  || path.startsWith('transform.scale.');
2794
4621
  }
2795
4622
  function isSerializedProjectionMetadataPath(path) {
2796
- return path === 'shadowMode';
4623
+ return isSerializedShadowMetadataPath(path);
4624
+ }
4625
+ function isSerializedShadowMetadataPath(path) {
4626
+ return path === 'shadowMode' || path.startsWith('shadow.');
4627
+ }
4628
+ function isSerializedShadowTopologyMetadataPath(path) {
4629
+ return path === 'shadowMode'
4630
+ || path === 'shadow.cast'
4631
+ || path === 'shadow.receive'
4632
+ || path === 'shadow.mode'
4633
+ || path === 'shadow.quality'
4634
+ || path === 'shadow.light.mode'
4635
+ || path === 'shadow.light.lightId'
4636
+ || path.startsWith('shadow.light.');
2797
4637
  }
2798
4638
  function beginDuplicateDrag(state, options, input) {
2799
4639
  if (state.mode !== 'editor' || !state.session)
2800
4640
  return null;
2801
4641
  if (state.duplicateDrag)
2802
4642
  return null;
2803
- const document = state.session.getState().workingDocument;
4643
+ const session = state.session;
4644
+ const document = session.getState().workingDocument;
2804
4645
  const patch = options.documentAdapter.createDuplicateSelectionPatch?.({
2805
4646
  document,
2806
4647
  targetIds: input.targetIds,
@@ -2811,9 +4652,9 @@ function beginDuplicateDrag(state, options, input) {
2811
4652
  state.status = 'Duplicate drag rejected';
2812
4653
  return null;
2813
4654
  }
2814
- const originalSelection = state.session.getSelection();
4655
+ const originalSelection = session.getSelection();
2815
4656
  const projectionImpact = resolveProjectionImpact(patch, createDuplicateProjectionImpact(patch));
2816
- const result = state.session.dispatch({
4657
+ const result = session.dispatch({
2817
4658
  type: 'document.patch',
2818
4659
  label: patch.label ?? `Duplicate ${input.targetIds.length} object(s)`,
2819
4660
  patch: patch.patch,
@@ -2854,7 +4695,7 @@ function beginDuplicateDrag(state, options, input) {
2854
4695
  })) {
2855
4696
  rebuildProjectionFromDocument(state, options, result.workingDocument, selectionResult.selection);
2856
4697
  }
2857
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
4698
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
2858
4699
  state.status = patch.label ?? `Duplicated ${createdIds.length} object(s)`;
2859
4700
  return {
2860
4701
  targetIds: createdIds,
@@ -2883,7 +4724,12 @@ function cancelDuplicateDrag(state, options) {
2883
4724
  function commitGizmoTransform(state, options, event) {
2884
4725
  if (state.mode !== 'editor' || !state.session)
2885
4726
  return false;
4727
+ if (state.prefabStage)
4728
+ return commitPrefabStageGizmoTransform(state, options, event);
2886
4729
  const document = state.session.getState().workingDocument;
4730
+ const markerChanged = commitMarkerGizmoTransform(state, options, document, event);
4731
+ if (markerChanged !== null)
4732
+ return markerChanged;
2887
4733
  if (isTransformBatchCommit(event)) {
2888
4734
  const patch = options.documentAdapter.createTransformBatchPatch?.({
2889
4735
  ...event,
@@ -2933,30 +4779,233 @@ function commitGizmoTransform(state, options, event) {
2933
4779
  state.status = `Ignored ${event.tool} ${targetId}`;
2934
4780
  }
2935
4781
  return false;
2936
- }
2937
- const result = state.session.dispatch({
4782
+ }
4783
+ const session = state.session;
4784
+ if (!session)
4785
+ return false;
4786
+ const result = session.dispatch({
4787
+ type: 'document.patch',
4788
+ label: patch.label ?? `${event.tool} ${targetId}`,
4789
+ patch: patch.patch,
4790
+ targetId,
4791
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
4792
+ ? createReprojectProjectionImpact(patch.reprojectIds)
4793
+ : createTransformProjectionImpact([patch.changedId ?? targetId]))),
4794
+ }, {
4795
+ mergeWithPrevious: state.duplicateDrag?.createdIds.includes(targetId) === true,
4796
+ });
4797
+ if (patch.reprojectIds?.length)
4798
+ reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
4799
+ else
4800
+ syncProjectionForChangedIds(state, options, result.workingDocument, [patch.changedId ?? targetId]);
4801
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
4802
+ state.status = patch.label ?? `${event.tool} ${targetId}`;
4803
+ state.duplicateDrag = null;
4804
+ return result.documentChanged;
4805
+ }
4806
+ function commitPrefabStageGizmoTransform(state, options, event) {
4807
+ const session = state.session;
4808
+ const document = session?.getState({ cloneDocuments: false }).workingDocument ?? null;
4809
+ if (!state.prefabStage || !session || !document || !options.documentAdapter.createAssetActionPatch)
4810
+ return false;
4811
+ if (isTransformBatchCommit(event)) {
4812
+ restoreBatchTransformPreview(state, event.targets);
4813
+ state.status = 'Ignored Prefab Stage multi-selection transform';
4814
+ state.statusTone = 'warning';
4815
+ state.statusToneStatus = state.status;
4816
+ return false;
4817
+ }
4818
+ const target = resolvePrefabStagePreviewTarget(state, options, document, event.nodeId);
4819
+ if (!target?.selectable || !target.transformable) {
4820
+ state.projection?.setNodeTransformPreview(event.nodeId, event.before);
4821
+ state.status = target?.disabledReason ?? `Ignored ${event.tool} ${event.nodeId}`;
4822
+ state.statusTone = 'warning';
4823
+ state.statusToneStatus = state.status;
4824
+ return false;
4825
+ }
4826
+ const transformActionValue = createPrefabStageTransformActionValue(event.after);
4827
+ if (!transformActionValue) {
4828
+ state.projection?.setNodeTransformPreview(event.nodeId, event.before);
4829
+ state.status = `Ignored ${event.tool} ${target.label ?? target.targetId}: Prefab Stage transform requires TRS data`;
4830
+ state.statusTone = 'warning';
4831
+ state.statusToneStatus = state.status;
4832
+ return false;
4833
+ }
4834
+ const patch = options.documentAdapter.createAssetActionPatch({
4835
+ actionId: 'prefab.node.setTransform',
4836
+ targetPrefabAssetId: state.prefabStage.descriptor.assetId,
4837
+ nodeId: target.targetId,
4838
+ document,
4839
+ asset: null,
4840
+ activeId: target.targetId,
4841
+ selectedIds: [target.targetId],
4842
+ value: transformActionValue,
4843
+ });
4844
+ if (!patch) {
4845
+ state.projection?.setNodeTransformPreview(event.nodeId, event.before);
4846
+ state.status = `Ignored ${event.tool} ${target.label ?? target.targetId}`;
4847
+ state.statusTone = 'warning';
4848
+ state.statusToneStatus = state.status;
4849
+ return false;
4850
+ }
4851
+ const result = session.dispatch({
4852
+ type: 'document.patch',
4853
+ label: patch.label ?? `Set prefab node ${target.targetId} transform`,
4854
+ patch: patch.patch,
4855
+ targetId: target.targetId,
4856
+ ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
4857
+ ? createReprojectProjectionImpact(patch.reprojectIds)
4858
+ : createTransformProjectionImpact([patch.changedId ?? target.targetId]))),
4859
+ });
4860
+ syncProjectionForDispatchResult(state, options, result);
4861
+ rebuildProjectionFromDocument(state, options, result.workingDocument, EMPTY_EDITOR_SELECTION);
4862
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
4863
+ state.status = patch.label ?? `Set prefab node ${target.label ?? target.targetId} transform`;
4864
+ state.statusTone = 'success';
4865
+ state.statusToneStatus = state.status;
4866
+ state.statusDetails = '';
4867
+ syncPrefabStageSelectionFromAssetAction(state, options, {
4868
+ actionId: 'prefab.node.setTransform',
4869
+ }, patch.changedId ?? target.targetId);
4870
+ return result.documentChanged;
4871
+ }
4872
+ function createPrefabStageTransformActionValue(transform) {
4873
+ if (!isEditorTransformTrsSnapshot(transform))
4874
+ return null;
4875
+ return {
4876
+ type: 'Transform',
4877
+ position: cloneEditorTransformVec3(transform.position),
4878
+ rotation: cloneEditorTransformVec3(transform.rotation),
4879
+ scale: cloneEditorTransformVec3(transform.scale),
4880
+ };
4881
+ }
4882
+ function cloneEditorTransformVec3(value) {
4883
+ return { x: value.x, y: value.y, z: value.z };
4884
+ }
4885
+ function commitMarkerGizmoTransform(state, options, document, event) {
4886
+ if (isTransformBatchCommit(event))
4887
+ return null;
4888
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
4889
+ const hierarchy = graph ? options.documentAdapter.getHierarchyItems(document) : [];
4890
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, event.nodeId, hierarchy);
4891
+ if (!markerId)
4892
+ return null;
4893
+ if (markerId === event.nodeId)
4894
+ return null;
4895
+ const marker = findSpatialMarkerById(graph, markerId);
4896
+ if (!marker)
4897
+ return false;
4898
+ if (!isEditorTransformTrsSnapshot(event.after)) {
4899
+ state.status = `Ignored ${event.tool} marker ${marker.label}: transform is not editable as TRS`;
4900
+ return false;
4901
+ }
4902
+ if (editorTransformSnapshotsEqual(event.before, event.after)) {
4903
+ state.status = `Unchanged ${event.tool} ${marker.label}`;
4904
+ return false;
4905
+ }
4906
+ const nextPatch = createMarkerTransformPatchFromGizmo(marker, event.after);
4907
+ const patch = options.documentAdapter.createMarkerGraphPatch?.(document, {
4908
+ type: 'marker.update',
4909
+ markerId,
4910
+ patch: nextPatch,
4911
+ });
4912
+ if (!patch) {
4913
+ state.markerOverlay?.setMarkerTransformPreview(event.nodeId, event.before);
4914
+ state.status = `Ignored ${event.tool} marker ${marker.label}`;
4915
+ return false;
4916
+ }
4917
+ const session = state.session;
4918
+ if (!session)
4919
+ return false;
4920
+ const result = session.dispatch({
2938
4921
  type: 'document.patch',
2939
- label: patch.label ?? `${event.tool} ${targetId}`,
4922
+ label: patch.label ?? `${event.tool} marker ${marker.label}`,
2940
4923
  patch: patch.patch,
2941
- targetId,
2942
- ...withProjectionImpact(resolveProjectionImpact(patch, patch.reprojectIds?.length
2943
- ? createReprojectProjectionImpact(patch.reprojectIds)
2944
- : createTransformProjectionImpact([patch.changedId ?? targetId]))),
2945
- }, {
2946
- mergeWithPrevious: state.duplicateDrag?.createdIds.includes(targetId) === true,
4924
+ targetId: event.nodeId,
2947
4925
  });
2948
- if (patch.reprojectIds?.length)
2949
- reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
2950
- else
2951
- syncProjectionForChangedIds(state, options, result.workingDocument, [patch.changedId ?? targetId]);
2952
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2953
- state.status = patch.label ?? `${event.tool} ${targetId}`;
2954
- state.duplicateDrag = null;
4926
+ syncProjectionForDispatchResult(state, options, result, patch.changedId ?? markerId);
4927
+ state.gizmo?.refreshSelection();
4928
+ requestEditorSceneFrame(state, 'marker-gizmo-transform');
4929
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
4930
+ state.status = patch.label ?? `${event.tool} marker ${marker.label}`;
2955
4931
  return result.documentChanged;
2956
4932
  }
4933
+ function createMarkerTransformPatchFromGizmo(marker, transform) {
4934
+ const semanticFrame = createMarkerSemanticFrameFromTransform(marker, transform);
4935
+ if (marker.geometry.kind === 'box') {
4936
+ const nextGeometry = {
4937
+ ...marker.geometry,
4938
+ center: { ...transform.position },
4939
+ size: normalizeMarkerBoxSize(transform.scale),
4940
+ };
4941
+ if (marker.geometry.rotation || !editorVec3Equal(transform.rotation, { x: 0, y: 0, z: 0 })) {
4942
+ nextGeometry.rotation = { ...transform.rotation };
4943
+ }
4944
+ else {
4945
+ delete nextGeometry.rotation;
4946
+ }
4947
+ return {
4948
+ geometry: nextGeometry,
4949
+ semanticFrame,
4950
+ };
4951
+ }
4952
+ if (marker.geometry.kind === 'point') {
4953
+ return {
4954
+ geometry: {
4955
+ ...marker.geometry,
4956
+ coordinateSpace: 'world',
4957
+ position: { ...transform.position },
4958
+ },
4959
+ semanticFrame,
4960
+ };
4961
+ }
4962
+ return {
4963
+ semanticFrame,
4964
+ };
4965
+ }
4966
+ function createMarkerSemanticFrameFromTransform(marker, transform) {
4967
+ return {
4968
+ ...(marker.semanticFrame ?? {}),
4969
+ origin: { ...transform.position },
4970
+ right: rotateEditorVec3ByEuler({ x: 1, y: 0, z: 0 }, transform.rotation),
4971
+ up: rotateEditorVec3ByEuler({ x: 0, y: 1, z: 0 }, transform.rotation),
4972
+ forward: rotateEditorVec3ByEuler({ x: 0, y: 0, z: 1 }, transform.rotation),
4973
+ label: marker.semanticFrame?.label ?? marker.label,
4974
+ };
4975
+ }
4976
+ function rotateEditorVec3ByEuler(value, rotation) {
4977
+ const cx = Math.cos(rotation.x);
4978
+ const sx = Math.sin(rotation.x);
4979
+ const cy = Math.cos(rotation.y);
4980
+ const sy = Math.sin(rotation.y);
4981
+ const cz = Math.cos(rotation.z);
4982
+ const sz = Math.sin(rotation.z);
4983
+ const afterX = {
4984
+ x: value.x,
4985
+ y: value.y * cx - value.z * sx,
4986
+ z: value.y * sx + value.z * cx,
4987
+ };
4988
+ const afterY = {
4989
+ x: afterX.x * cy + afterX.z * sy,
4990
+ y: afterX.y,
4991
+ z: -afterX.x * sy + afterX.z * cy,
4992
+ };
4993
+ return {
4994
+ x: afterY.x * cz - afterY.y * sz,
4995
+ y: afterY.x * sz + afterY.y * cz,
4996
+ z: afterY.z,
4997
+ };
4998
+ }
2957
4999
  function isTransformBatchCommit(event) {
2958
5000
  return 'targets' in event;
2959
5001
  }
5002
+ function normalizeMarkerBoxSize(size) {
5003
+ return {
5004
+ x: Math.max(0.000001, Math.abs(size.x)),
5005
+ y: Math.max(0.000001, Math.abs(size.y)),
5006
+ z: Math.max(0.000001, Math.abs(size.z)),
5007
+ };
5008
+ }
2960
5009
  function restoreBatchTransformPreview(state, targets) {
2961
5010
  const transforms = {};
2962
5011
  for (const target of targets)
@@ -2991,11 +5040,38 @@ function cancelActiveOperation(state) {
2991
5040
  function cancelEditorIntent(state, options) {
2992
5041
  if (state.mode !== 'editor')
2993
5042
  return false;
5043
+ if (state.markerRelationPick) {
5044
+ state.markerRelationPick = null;
5045
+ state.status = '已取消关系端点拾取。';
5046
+ state.statusTone = 'success';
5047
+ state.statusToneStatus = state.status;
5048
+ state.statusDetails = '';
5049
+ return true;
5050
+ }
2994
5051
  if (state.viewportTools.activeUtilityTool === 'measure-distance'
2995
5052
  || state.viewportMeasurement.start
2996
5053
  || state.viewportMeasurement.end) {
2997
5054
  return clearViewportMeasurement(state);
2998
5055
  }
5056
+ if (state.prefabStage?.selectedItemId) {
5057
+ cancelActiveOperation(state);
5058
+ state.prefabStage = { ...state.prefabStage, selectedItemId: null };
5059
+ state.editorStateRevision += 1;
5060
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-selection-cancel', state.editorStateRevision);
5061
+ state.status = 'Cleared Prefab Stage selection';
5062
+ state.statusTone = 'default';
5063
+ state.statusToneStatus = state.status;
5064
+ state.statusDetails = '';
5065
+ return true;
5066
+ }
5067
+ if (state.prefabStage) {
5068
+ cancelActiveOperation(state);
5069
+ state.status = 'Canceled Prefab Stage operation';
5070
+ state.statusTone = 'default';
5071
+ state.statusToneStatus = state.status;
5072
+ state.statusDetails = '';
5073
+ return true;
5074
+ }
2999
5075
  const selection = state.session?.getSelection();
3000
5076
  if (selection && selection.selectedIds.length > 0) {
3001
5077
  return dispatchSelectionCommand(state, options, { type: 'selection.clear', label: 'Clear Selection' });
@@ -3003,9 +5079,17 @@ function cancelEditorIntent(state, options) {
3003
5079
  cancelActiveOperation(state);
3004
5080
  return true;
3005
5081
  }
3006
- function focusSelectedProjection(state) {
5082
+ function focusSelectedProjection(state, options) {
3007
5083
  if (state.mode !== 'editor')
3008
5084
  return false;
5085
+ if (state.prefabStage) {
5086
+ const activeId = getCurrentPrefabStageProjectionSelection(state, options).activeId;
5087
+ if (!activeId) {
5088
+ state.status = 'Focus failed: no Prefab Stage selection';
5089
+ return true;
5090
+ }
5091
+ return focusPrefabStageProjectionNode(state, options, activeId, 'prefab-stage-focus-selection');
5092
+ }
3009
5093
  const activeId = state.session?.getState().selection.activeId ?? null;
3010
5094
  if (!activeId) {
3011
5095
  state.status = 'Focus failed: no selection';
@@ -3031,6 +5115,79 @@ function focusProjectionNode(state, nodeId) {
3031
5115
  requestEditorSceneFrame(state, 'viewport-focus-selection');
3032
5116
  return true;
3033
5117
  }
5118
+ function frameViewportProjectionNode(state, nodeId) {
5119
+ if (state.mode !== 'editor')
5120
+ return false;
5121
+ const root = state.projection?.getAttachableRoot(nodeId) ?? null;
5122
+ if (!root) {
5123
+ state.status = `Focus failed: missing projection for ${nodeId}`;
5124
+ return false;
5125
+ }
5126
+ const focused = focusEditorViewportSelection(state.world?.camera ?? null, root, {
5127
+ babylon: state.babylon ?? undefined,
5128
+ });
5129
+ state.status = focused
5130
+ ? `Focused ${nodeId} · ${formatEditorStatusTime(Date.now())}`
5131
+ : `Focus failed: camera could not frame ${nodeId}`;
5132
+ if (focused)
5133
+ requestEditorSceneFrame(state, 'viewport-focus-object');
5134
+ return focused;
5135
+ }
5136
+ function frameSelectedViewportProjection(state, options) {
5137
+ if (state.mode !== 'editor')
5138
+ return false;
5139
+ if (state.prefabStage) {
5140
+ const activeId = getCurrentPrefabStageProjectionSelection(state, options).activeId;
5141
+ if (!activeId) {
5142
+ state.status = 'Focus failed: no Prefab Stage selection';
5143
+ return false;
5144
+ }
5145
+ return focusPrefabStageProjectionNode(state, options, activeId, 'prefab-stage-frame-selection');
5146
+ }
5147
+ const activeId = state.session?.getState({ cloneDocuments: false }).selection.activeId ?? null;
5148
+ if (!activeId) {
5149
+ state.status = 'Focus failed: no selection';
5150
+ return false;
5151
+ }
5152
+ return frameViewportProjectionNode(state, activeId);
5153
+ }
5154
+ function focusPrefabStageProjectionNode(state, options, projectionNodeId, reason) {
5155
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
5156
+ if (!state.prefabStage || !document)
5157
+ return false;
5158
+ const target = resolvePrefabStagePreviewTarget(state, options, document, projectionNodeId);
5159
+ if (!target?.selectable) {
5160
+ state.status = `Focus failed: ${projectionNodeId} is not an editable Prefab Stage preview target`;
5161
+ return true;
5162
+ }
5163
+ const root = state.projection?.getAttachableRoot(projectionNodeId) ?? null;
5164
+ if (!root) {
5165
+ state.status = `Focus failed: missing projection for ${projectionNodeId}`;
5166
+ return true;
5167
+ }
5168
+ const focused = focusEditorViewportSelection(state.world?.camera ?? null, root, {
5169
+ babylon: state.babylon ?? undefined,
5170
+ });
5171
+ state.status = focused
5172
+ ? `Focused ${target.label ?? projectionNodeId} · ${formatEditorStatusTime(Date.now())}`
5173
+ : `Focus failed: camera could not frame ${target.label ?? projectionNodeId}`;
5174
+ if (focused)
5175
+ requestEditorSceneFrame(state, reason);
5176
+ return true;
5177
+ }
5178
+ function focusPrefabStagePreviewProjection(state, reason) {
5179
+ const previewNodeId = state.prefabStage?.descriptor.previewNodeId;
5180
+ if (!previewNodeId)
5181
+ return;
5182
+ const root = state.projection?.getAttachableRoot(previewNodeId) ?? null;
5183
+ if (!root)
5184
+ return;
5185
+ const focused = focusEditorViewportSelection(state.world?.camera ?? null, root, {
5186
+ babylon: state.babylon ?? undefined,
5187
+ });
5188
+ if (focused)
5189
+ requestEditorSceneFrame(state, reason);
5190
+ }
3034
5191
  function formatEditorStatusTime(timestamp) {
3035
5192
  const date = new Date(timestamp);
3036
5193
  const pad = (value) => String(value).padStart(2, '0');
@@ -3232,6 +5389,10 @@ function applyIncrementalHistoryProjectionImpact(state, options, result, operati
3232
5389
  function applyIncrementalProjectionImpactToDocument(state, options, input) {
3233
5390
  if (!state.projection)
3234
5391
  return false;
5392
+ if (state.prefabStage) {
5393
+ rebuildProjectionFromDocument(state, options, input.document, EMPTY_EDITOR_SELECTION);
5394
+ return true;
5395
+ }
3235
5396
  const syncTransformIds = uniqueProjectionIds(input.syncTransformIds);
3236
5397
  const reprojectIds = uniqueProjectionIds(input.reprojectIds);
3237
5398
  const projectNodeIds = uniqueProjectionIds(input.projectNodeIds);
@@ -3254,13 +5415,14 @@ function applyIncrementalProjectionImpactToDocument(state, options, input) {
3254
5415
  state.projection.projectNode(projectedNode);
3255
5416
  }
3256
5417
  for (const projectedNode of reprojectNodes) {
3257
- state.projection.projectNode(projectedNode);
5418
+ syncProjectionNodeTransformOrReprojectRuntime(state, projectedNode, { forceReproject: true });
3258
5419
  }
3259
5420
  for (const projectedNode of syncTransformNodes) {
3260
- state.projection.syncNodeTransform(projectedNode);
5421
+ syncProjectionNodeTransformOrReprojectRuntime(state, projectedNode);
3261
5422
  }
5423
+ applyPreviewVisibilityStateToProjection(state);
3262
5424
  const sanitized = sanitizeSelection(state, options, input.document, input.selection);
3263
- syncSelectionToProjection(state, sanitized ?? input.selection);
5425
+ syncSelectionToProjection(state, options, sanitized ?? input.selection);
3264
5426
  syncEditorWorldAppearanceFromDocument(state, options, input.document, `world-appearance-${input.reason}`);
3265
5427
  syncEditorWorldRenderingFromDocument(state, options, input.document, `world-rendering-${input.reason}`, true);
3266
5428
  invalidateEditorScene(state, `projection-${input.reason}`, options);
@@ -3276,27 +5438,53 @@ function resolveProjectionNodes(options, document, ids) {
3276
5438
  }
3277
5439
  return projectedNodes;
3278
5440
  }
5441
+ function resolveCurrentEditorProjectionNodes(state, options, document) {
5442
+ if (!state.prefabStage)
5443
+ return options.documentAdapter.getProjectionNodes(document);
5444
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document);
5445
+ if (!descriptor)
5446
+ return [];
5447
+ return options.documentAdapter.getPrefabStageProjectionNodes?.(document, descriptor) ?? [];
5448
+ }
3279
5449
  function rebuildProjectionFromDocument(state, options, document, selection) {
3280
- state.projection?.rebuild(options.documentAdapter.getProjectionNodes(document));
3281
- const sanitized = sanitizeSelection(state, options, document, selection);
3282
- syncSelectionToProjection(state, sanitized ?? selection);
5450
+ const nodes = resolveCurrentEditorProjectionNodes(state, options, document);
5451
+ state.projection?.rebuild(nodes);
5452
+ applyPreviewVisibilityStateToProjection(state);
5453
+ if (state.prefabStage) {
5454
+ syncPrefabStageSelectionToProjection(state, options, 'prefab-stage-projection-rebuild');
5455
+ }
5456
+ else {
5457
+ const sanitized = sanitizeSelection(state, options, document, selection);
5458
+ syncSelectionToProjection(state, options, sanitized ?? selection);
5459
+ }
3283
5460
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-rebuild');
3284
5461
  syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-rebuild', true);
5462
+ if (state.prefabStage && nodes.length === 0) {
5463
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document);
5464
+ state.status = `Prefab preview unavailable: ${descriptor?.label ?? state.prefabStage.descriptor.label}`;
5465
+ state.statusTone = 'warning';
5466
+ state.statusToneStatus = state.status;
5467
+ state.statusDetails = 'The prefab source model asset could not be resolved for preview.';
5468
+ }
3285
5469
  invalidateEditorScene(state, 'projection-rebuild');
3286
5470
  }
3287
5471
  function syncProjectionForDispatchResult(state, options, result, changedId) {
5472
+ if (state.prefabStage && result.documentChanged && result.workingDocument) {
5473
+ rebuildProjectionFromDocument(state, options, result.workingDocument, EMPTY_EDITOR_SELECTION);
5474
+ return;
5475
+ }
3288
5476
  if (result.documentChanged && changedId && result.workingDocument) {
3289
5477
  const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, changedId);
3290
5478
  if (projectedNode)
3291
- state.projection?.syncNodeTransform(projectedNode);
5479
+ syncProjectionNodeTransformOrReprojectRuntime(state, projectedNode);
3292
5480
  }
3293
5481
  if (result.selectionChanged) {
3294
- syncSelectionToProjection(state, result.selection ?? { selectedIds: [], activeId: null });
5482
+ syncSelectionToProjection(state, options, result.selection ?? { selectedIds: [], activeId: null });
3295
5483
  }
3296
5484
  else if (result.documentChanged) {
3297
5485
  const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3298
5486
  const sanitized = result.workingDocument ? sanitizeSelection(state, options, result.workingDocument, selection) : null;
3299
- syncSelectionToProjection(state, sanitized ?? selection);
5487
+ syncSelectionToProjection(state, options, sanitized ?? selection);
3300
5488
  }
3301
5489
  if (result.documentChanged && result.workingDocument) {
3302
5490
  syncEditorWorldAppearanceFromDocument(state, options, result.workingDocument, 'world-appearance-dispatch-result');
@@ -3307,18 +5495,44 @@ function syncProjectionForDispatchResult(state, options, result, changedId) {
3307
5495
  }
3308
5496
  }
3309
5497
  function syncProjectionForChangedIds(state, options, document, changedIds) {
5498
+ if (state.prefabStage) {
5499
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
5500
+ return;
5501
+ }
3310
5502
  for (const changedId of changedIds) {
3311
5503
  const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
3312
5504
  if (projectedNode)
3313
- state.projection?.syncNodeTransform(projectedNode);
5505
+ syncProjectionNodeTransformOrReprojectRuntime(state, projectedNode);
3314
5506
  }
3315
5507
  const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3316
- syncSelectionToProjection(state, selection);
5508
+ syncSelectionToProjection(state, options, selection);
3317
5509
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-sync-changed-ids');
3318
5510
  syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-sync-changed-ids', true);
3319
5511
  invalidateEditorScene(state, 'projection-sync-changed-ids');
3320
5512
  }
5513
+ function syncProjectionNodeTransformOrReprojectRuntime(state, projectedNode, options = {}) {
5514
+ if (!state.projection)
5515
+ return;
5516
+ if (projectedNode.runtimeKind === 'light' && state.projection.syncRuntimeNode(projectedNode)) {
5517
+ state.shadowPreview?.refresh();
5518
+ return;
5519
+ }
5520
+ if (options.forceReproject || projectedNode.runtimeKind === 'light' || shouldReprojectRuntimeProjectionNode(projectedNode)) {
5521
+ state.projection.projectNode(projectedNode);
5522
+ if (projectedNode.runtimeKind === 'light')
5523
+ state.shadowPreview?.refresh();
5524
+ return;
5525
+ }
5526
+ state.projection.syncNodeTransform(projectedNode);
5527
+ }
5528
+ function shouldReprojectRuntimeProjectionNode(projectedNode) {
5529
+ return projectedNode.runtimeKind === 'camera';
5530
+ }
3321
5531
  function syncProjectionMetadataForChangedIds(state, options, document, changedIds, syncOptions = {}) {
5532
+ if (state.prefabStage) {
5533
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
5534
+ return;
5535
+ }
3322
5536
  for (const changedId of changedIds) {
3323
5537
  const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
3324
5538
  if (projectedNode) {
@@ -3326,7 +5540,7 @@ function syncProjectionMetadataForChangedIds(state, options, document, changedId
3326
5540
  }
3327
5541
  }
3328
5542
  const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3329
- syncSelectionToProjection(state, selection);
5543
+ syncSelectionToProjection(state, options, selection);
3330
5544
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-sync-metadata-ids');
3331
5545
  if (syncOptions.invalidateStaticShadowArtifact) {
3332
5546
  state.shadowPreview?.invalidateStaticProjectedArtifact?.();
@@ -3342,17 +5556,37 @@ function syncProjectionMetadataForChangedIds(state, options, document, changedId
3342
5556
  }
3343
5557
  invalidateEditorScene(state, 'projection-sync-metadata-ids');
3344
5558
  }
5559
+ function rebuildProjectionAfterShadowTopologyMetadataPatch(state, options, document, selection) {
5560
+ rebuildProjectionFromDocument(state, options, document, selection);
5561
+ state.shadowPreview?.invalidateStaticProjectedArtifact?.();
5562
+ scheduleProjectionShadowPreviewRebuild(state, 'projection-shadow-topology-metadata-rebuild');
5563
+ }
3345
5564
  function reprojectProjectionForChangedIds(state, options, document, changedIds) {
5565
+ if (state.prefabStage) {
5566
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
5567
+ return;
5568
+ }
3346
5569
  for (const changedId of changedIds) {
3347
5570
  const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
3348
5571
  if (projectedNode)
3349
5572
  state.projection?.projectNode(projectedNode);
3350
5573
  }
3351
- syncCurrentSelectionToSceneArtifacts(state);
5574
+ applyPreviewVisibilityStateToProjection(state);
5575
+ syncCurrentSelectionToSceneArtifacts(state, options);
3352
5576
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-reproject-changed-ids');
3353
5577
  syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-reproject-changed-ids', true);
3354
5578
  invalidateEditorScene(state, 'projection-reproject-changed-ids', options);
3355
5579
  }
5580
+ function applyPreviewVisibilityStateToProjection(state) {
5581
+ const projection = state.projection;
5582
+ if (!projection?.clearPreviewVisibilityOverrides || !projection.setPreviewVisibilityOverrides)
5583
+ return;
5584
+ projection.clearPreviewVisibilityOverrides();
5585
+ const overrides = state.previewVisibility.overrides;
5586
+ if (Object.keys(overrides).length > 0) {
5587
+ projection.setPreviewVisibilityOverrides(overrides);
5588
+ }
5589
+ }
3356
5590
  function setSceneCameraPreviewEnabled(state, options, enabled) {
3357
5591
  if (!enabled) {
3358
5592
  state.sceneCameraPreviewEnabled = false;
@@ -3376,15 +5610,21 @@ function setSceneCameraPreviewEnabled(state, options, enabled) {
3376
5610
  }
3377
5611
  function setGridVisible(state, visible) {
3378
5612
  const nextVisible = visible === true;
3379
- if (state.gridVisible === nextVisible)
5613
+ if (!setGridVisibleForEditorContext(state, nextVisible, 'grid-visible-change'))
3380
5614
  return false;
3381
- state.gridVisible = nextVisible;
3382
- state.grid?.setVisible(nextVisible);
3383
5615
  state.status = nextVisible ? 'Grid visible' : 'Grid hidden';
3384
5616
  state.statusTone = 'default';
3385
5617
  state.statusToneStatus = state.status;
3386
5618
  state.statusDetails = '';
3387
- requestEditorSceneFrame(state, 'grid-visible-change');
5619
+ return true;
5620
+ }
5621
+ function setGridVisibleForEditorContext(state, visible, reason) {
5622
+ const nextVisible = visible === true;
5623
+ if (state.gridVisible === nextVisible)
5624
+ return false;
5625
+ state.gridVisible = nextVisible;
5626
+ state.grid?.setVisible(nextVisible);
5627
+ requestEditorSceneFrame(state, reason);
3388
5628
  return true;
3389
5629
  }
3390
5630
  function syncSceneCameraPreview(state, options) {
@@ -3414,16 +5654,16 @@ function syncViewportCameraState(state) {
3414
5654
  projectionMode: cameraState.projectionMode,
3415
5655
  };
3416
5656
  }
3417
- function syncViewportSpatialOverlay(state) {
5657
+ function syncViewportSpatialOverlay(state, options) {
3418
5658
  const settings = state.viewportTools.overlay;
3419
5659
  if (!settings.bounds && !settings.dimensions && !settings.edgeLengths && !settings.anchor) {
3420
5660
  state.viewportSpatialOverlay = createEmptyEditorViewportSpatialOverlayState();
3421
5661
  return;
3422
5662
  }
3423
5663
  const overlay = state.sceneViewSpatialOverlay;
3424
- const selection = state.session?.getState().selection;
3425
- const activeId = selection?.activeId ?? null;
3426
- if (state.mode !== 'editor' || !overlay || !activeId || selection?.selectedIds.length !== 1) {
5664
+ const selection = getCurrentPreviewSelection(state, options);
5665
+ const activeId = selection.activeId;
5666
+ if (state.mode !== 'editor' || !overlay || !activeId || selection.selectedIds.length !== 1) {
3427
5667
  state.viewportSpatialOverlay = createEmptyEditorViewportSpatialOverlayState();
3428
5668
  return;
3429
5669
  }
@@ -3523,25 +5763,46 @@ function radiansToInspectorDegrees(value) {
3523
5763
  function createUiState(state, options) {
3524
5764
  const sessionState = state.session?.getState({ cloneDocuments: false });
3525
5765
  const document = sessionState?.workingDocument ?? null;
3526
- const selectedIds = sessionState?.selection.selectedIds ?? [];
3527
- const activeId = sessionState?.selection.activeId ?? null;
3528
- const largeSelection = isLargeSelection(selectedIds);
5766
+ const documentSelectedIds = sessionState?.selection.selectedIds ?? [];
5767
+ const documentActiveId = sessionState?.selection.activeId ?? null;
5768
+ const scopedPreviewSelection = getScopedPreviewSelectionForUiState(state, options, {
5769
+ selectedIds: documentSelectedIds,
5770
+ activeId: documentActiveId,
5771
+ });
5772
+ const selectedIds = scopedPreviewSelection.selectedIds;
5773
+ const activeId = scopedPreviewSelection.activeId;
5774
+ const largeSelection = isLargeSelection(documentSelectedIds);
5775
+ const transformSelectionIsLarge = isLargeSelection(selectedIds);
5776
+ const markerGraph = document ? options.documentAdapter.getMarkerGraph?.(document) ?? null : null;
5777
+ const markerTypes = document ? options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [] : [];
5778
+ const relationTypes = document ? options.documentAdapter.getRelationTypeCatalog?.(document) ?? [] : [];
5779
+ const documentHierarchy = document ? options.documentAdapter.getHierarchyItems(document) : [];
5780
+ const activeMarkerId = activeId ? readSpatialMarkerIdFromHierarchySelection(markerGraph, activeId, documentHierarchy) : null;
5781
+ const markerRelationVisibility = createMarkerRelationVisibilityOptions(state);
5782
+ const markerInspectorObject = document && activeId
5783
+ ? createMarkerInspectorObjectBase(options, document, activeId, markerGraph, documentHierarchy, markerTypes, state.activeMarkerRelationId, state.markerRelationPick, markerRelationVisibility)
5784
+ : null;
3529
5785
  const serializedObject = document && activeId && selectedIds.length === 1
3530
- ? options.documentAdapter.getSerializedObject(document, activeId)
5786
+ ? markerInspectorObject ? null : options.documentAdapter.getSerializedObject(document, activeId)
3531
5787
  : null;
3532
- const serializedMultiObject = document && selectedIds.length > 1 && !largeSelection
3533
- ? options.documentAdapter.getSerializedMultiObject?.(document, selectedIds, activeId) ?? null
5788
+ const serializedMultiObject = document && documentSelectedIds.length > 1 && !largeSelection
5789
+ ? options.documentAdapter.getSerializedMultiObject?.(document, documentSelectedIds, documentActiveId) ?? null
3534
5790
  : null;
3535
- const inspectorObjectBase = document && activeId && selectedIds.length === 1
3536
- ? options.documentAdapter.getInspectorObject?.(document, activeId) ?? (serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null)
5791
+ const inspectorObjectBase = document && activeId && (selectedIds.length === 1 || markerInspectorObject)
5792
+ ? markerInspectorObject
5793
+ ?? options.documentAdapter.getInspectorObject?.(document, activeId)
5794
+ ?? (serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null)
3537
5795
  : null;
3538
- const inspectorMultiObjectBase = document && selectedIds.length > 1
3539
- ? createInspectorMultiObjectBase(state, options, document, selectedIds, activeId, serializedMultiObject, {
5796
+ const inspectorMultiObjectBase = document && documentSelectedIds.length > 1
5797
+ ? createInspectorMultiObjectBase(state, options, document, documentSelectedIds, documentActiveId, serializedMultiObject, {
3540
5798
  largeSelection,
3541
5799
  })
3542
5800
  : null;
3543
- const inspectorObject = document && inspectorObjectBase
3544
- ? withRuntimeInspectorSections(state, options, document, inspectorObjectBase)
5801
+ const markerRelationInspectorObjectBase = document && inspectorObjectBase
5802
+ ? withMarkerGraphRelationInspectorSection(options, document, inspectorObjectBase, markerGraph, documentHierarchy, markerTypes, relationTypes, state.activeMarkerRelationId, state.markerRelationPick, markerRelationVisibility)
5803
+ : inspectorObjectBase;
5804
+ const inspectorObject = document && markerRelationInspectorObjectBase
5805
+ ? withRuntimeInspectorSections(state, options, document, markerRelationInspectorObjectBase)
3545
5806
  : inspectorObjectBase;
3546
5807
  const inspectorMultiObject = document && inspectorMultiObjectBase
3547
5808
  ? withRuntimeInspectorSections(state, options, document, inspectorMultiObjectBase)
@@ -3551,8 +5812,10 @@ function createUiState(state, options) {
3551
5812
  const documentAssetItems = document
3552
5813
  ? options.documentAdapter.getBrowserAssetItems?.(document) ?? []
3553
5814
  : [];
3554
- const hierarchy = document ? options.documentAdapter.getHierarchyItems(document) : [];
3555
- state.performanceObjectCount = document ? countLocalEditorAuthoredObjects(hierarchy) : null;
5815
+ const hierarchy = document
5816
+ ? createMarkerGraphHierarchyItems(documentHierarchy, markerGraph, createMarkerGraphHierarchyVisibility(state))
5817
+ : [];
5818
+ state.performanceObjectCount = document ? countLocalEditorAuthoredObjects(documentHierarchy) : null;
3556
5819
  state.performanceStats = createLocalEditorPerformanceStats({
3557
5820
  frameStats: state.sceneFrameStats,
3558
5821
  runtimeStats: state.performanceMonitor?.readSnapshot() ?? null,
@@ -3565,6 +5828,7 @@ function createUiState(state, options) {
3565
5828
  return {
3566
5829
  mode: state.mode,
3567
5830
  busy: state.busy,
5831
+ agentBridge: state.agentBridgeStatus,
3568
5832
  status: state.status,
3569
5833
  statusTone: state.statusToneStatus === state.status ? state.statusTone : 'default',
3570
5834
  statusDetails: state.statusToneStatus === state.status ? state.statusDetails : '',
@@ -3591,6 +5855,9 @@ function createUiState(state, options) {
3591
5855
  renderingAction: state.renderingAction,
3592
5856
  }) ?? null
3593
5857
  : null,
5858
+ markerPanel: document
5859
+ ? createMarkerPanelState(markerGraph, markerTypes, relationTypes, documentHierarchy, activeMarkerId, state.activeMarkerRelationId, state.markerRelationPick, markerRelationVisibility)
5860
+ : null,
3594
5861
  boxSelection: state.boxSelection,
3595
5862
  coordinateAxes: options.world?.coordinateAxes === false
3596
5863
  ? null
@@ -3603,11 +5870,11 @@ function createUiState(state, options) {
3603
5870
  draggingNodeId: state.gizmo?.getState().draggingNodeId ?? null,
3604
5871
  },
3605
5872
  transformOperations: {
3606
- settings: cloneTransformOperationSettings(state.transformOperationSettings),
5873
+ settings: cloneTransformOperationSettings(getScopedTransformOperationSettings(state)),
3607
5874
  selectedCount: selectedIds.length,
3608
5875
  activeId,
3609
- canAlign: !largeSelection && selectedIds.length >= 2 && activeId != null,
3610
- canDistribute: !largeSelection && selectedIds.length >= 3,
5876
+ canAlign: !transformSelectionIsLarge && selectedIds.length >= 2 && activeId != null && !state.prefabStage,
5877
+ canDistribute: !transformSelectionIsLarge && selectedIds.length >= 3 && !state.prefabStage,
3611
5878
  },
3612
5879
  viewportTools: cloneEditorViewportToolState(state.viewportTools),
3613
5880
  viewportMeasurement: cloneViewportMeasurement(state.viewportMeasurement),
@@ -3620,6 +5887,7 @@ function createUiState(state, options) {
3620
5887
  enabled: state.sceneCameraPreviewEnabled,
3621
5888
  available: hasSceneCameraPreviewRig(state, options),
3622
5889
  },
5890
+ prefabStage: createBrowserPrefabStageState(state, options, document),
3623
5891
  grid: {
3624
5892
  visible: state.gridVisible,
3625
5893
  available: !!state.grid,
@@ -3635,18 +5903,196 @@ function createUiState(state, options) {
3635
5903
  : null,
3636
5904
  };
3637
5905
  }
5906
+ function createBrowserPrefabStageState(state, options, document) {
5907
+ const stage = state.prefabStage;
5908
+ if (!stage || !document)
5909
+ return null;
5910
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? stage.descriptor;
5911
+ const context = createPrefabStageContext(state, descriptor);
5912
+ const structureItems = options.documentAdapter.getPrefabStageStructure?.(document, descriptor, context)
5913
+ ?? createFallbackPrefabStageStructure(descriptor);
5914
+ const inspectorObject = options.documentAdapter.getPrefabStageInspectorObject?.(document, descriptor, stage.selectedItemId, context) ?? null;
5915
+ return {
5916
+ assetId: descriptor.assetId,
5917
+ browserAssetId: descriptor.browserAssetId,
5918
+ label: descriptor.label,
5919
+ sourceAssetId: descriptor.sourceAssetId,
5920
+ sourceAssetGuid: descriptor.sourceAssetGuid,
5921
+ readonly: descriptor.readonly,
5922
+ selectedItemId: stage.selectedItemId,
5923
+ structureItems,
5924
+ inspectorObject,
5925
+ importStructureReady: context.importStructureReady,
5926
+ importStats: createPrefabStageImportStats(context),
5927
+ previewNodeId: descriptor.previewNodeId,
5928
+ comparisonPreviews: createPrefabStageComparisonPreviews(state, descriptor),
5929
+ };
5930
+ }
5931
+ function summarizeAgentBridgePrefabStage(state, options) {
5932
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
5933
+ const stage = createBrowserPrefabStageState(state, options, document);
5934
+ if (!stage)
5935
+ return null;
5936
+ return createAgentBridgePrefabStageState(stage);
5937
+ }
5938
+ function summarizeAgentBridgePrefabStageDetails(state, options) {
5939
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
5940
+ const stage = createBrowserPrefabStageState(state, options, document);
5941
+ if (!stage)
5942
+ return null;
5943
+ return {
5944
+ stage: createAgentBridgePrefabStageState(stage),
5945
+ structureItems: stage.structureItems,
5946
+ inspectorObject: stage.inspectorObject ?? null,
5947
+ };
5948
+ }
5949
+ function createAgentBridgePrefabStageState(stage) {
5950
+ return {
5951
+ assetId: stage.assetId,
5952
+ ...(stage.browserAssetId ? { browserAssetId: stage.browserAssetId } : {}),
5953
+ label: stage.label,
5954
+ ...(stage.sourceAssetId ? { sourceAssetId: stage.sourceAssetId } : {}),
5955
+ ...(stage.sourceAssetGuid ? { sourceAssetGuid: stage.sourceAssetGuid } : {}),
5956
+ ...(stage.previewNodeId ? { previewNodeId: stage.previewNodeId } : {}),
5957
+ selectedItemId: stage.selectedItemId ?? null,
5958
+ ...(stage.readonly != null ? { readonly: stage.readonly } : {}),
5959
+ };
5960
+ }
5961
+ function createAgentBridgePrefabSummaryFromStage(stage) {
5962
+ return {
5963
+ assetId: stage.assetId,
5964
+ ...(stage.browserAssetId ? { browserAssetId: stage.browserAssetId } : {}),
5965
+ displayName: stage.label,
5966
+ ...(stage.sourceAssetId ? { sourceAssetId: stage.sourceAssetId } : {}),
5967
+ ...(stage.sourceAssetGuid ? { sourceAssetGuid: stage.sourceAssetGuid } : {}),
5968
+ ...(stage.readonly != null ? { readonly: stage.readonly } : {}),
5969
+ status: 'ready',
5970
+ };
5971
+ }
5972
+ function isSameAgentBridgePrefabStage(left, right) {
5973
+ if (!left || !right)
5974
+ return false;
5975
+ return left.assetId === right.assetId
5976
+ || (!!left.browserAssetId && left.browserAssetId === right.browserAssetId);
5977
+ }
5978
+ function createPrefabStageContext(state, descriptor) {
5979
+ const previewNodeId = descriptor.previewNodeId;
5980
+ if (!previewNodeId) {
5981
+ return {
5982
+ importStructure: null,
5983
+ importStructureReady: false,
5984
+ };
5985
+ }
5986
+ const projectedNode = state.projection?.getProjectedNode(previewNodeId) ?? null;
5987
+ return {
5988
+ previewNodeId,
5989
+ importStructure: state.projection?.readNodeImportStructure(previewNodeId) ?? null,
5990
+ importStructureReady: !!projectedNode && !projectedNode.loadPromise,
5991
+ };
5992
+ }
5993
+ function createPrefabStageImportStats(context) {
5994
+ const snapshot = context.importStructure;
5995
+ if (!context.importStructureReady) {
5996
+ return createEmptyPrefabStageImportStats('loading', snapshot);
5997
+ }
5998
+ if (!snapshot) {
5999
+ return createEmptyPrefabStageImportStats('missing', null);
6000
+ }
6001
+ const nodes = snapshot.nodes ?? [];
6002
+ return {
6003
+ status: 'ready',
6004
+ projectionNodeId: snapshot.projectionNodeId,
6005
+ ...(snapshot.assetId ? { assetId: snapshot.assetId } : {}),
6006
+ ...(snapshot.sourceId ? { sourceId: snapshot.sourceId } : {}),
6007
+ nodeCount: nodes.length,
6008
+ transformCount: nodes.filter(node => node.kind === 'root' || node.kind === 'transform').length,
6009
+ meshCount: nodes.filter(node => node.kind === 'mesh').length,
6010
+ materialCount: snapshot.materials?.length ?? 0,
6011
+ textureCount: snapshot.textures?.length ?? 0,
6012
+ animationCount: snapshot.animations?.length ?? 0,
6013
+ };
6014
+ }
6015
+ function createEmptyPrefabStageImportStats(status, snapshot) {
6016
+ return {
6017
+ status,
6018
+ ...(snapshot?.projectionNodeId ? { projectionNodeId: snapshot.projectionNodeId } : {}),
6019
+ ...(snapshot?.assetId ? { assetId: snapshot.assetId } : {}),
6020
+ ...(snapshot?.sourceId ? { sourceId: snapshot.sourceId } : {}),
6021
+ nodeCount: 0,
6022
+ transformCount: 0,
6023
+ meshCount: 0,
6024
+ materialCount: 0,
6025
+ textureCount: 0,
6026
+ animationCount: 0,
6027
+ };
6028
+ }
6029
+ function createPrefabStageComparisonPreviews(state, descriptor) {
6030
+ return PREFAB_STAGE_COMPARISON_PREVIEW_DEFINITIONS.map((definition) => {
6031
+ const projectionNodeId = createPrefabStageComparisonProjectionNodeId(descriptor, definition.nodeIdSuffix);
6032
+ const projectedNode = state.projection?.getProjectedNode(projectionNodeId) ?? null;
6033
+ const status = !descriptor.sourceAssetId || !projectedNode
6034
+ ? 'unavailable'
6035
+ : projectedNode.loadPromise
6036
+ ? 'loading'
6037
+ : 'ready';
6038
+ return {
6039
+ id: definition.id,
6040
+ label: definition.label,
6041
+ mode: definition.mode,
6042
+ projectionNodeId,
6043
+ status,
6044
+ };
6045
+ });
6046
+ }
6047
+ function createPrefabStageComparisonProjectionNodeId(descriptor, suffix) {
6048
+ const previewNodeId = descriptor.previewNodeId ?? `prefab-stage:${descriptor.assetId}`;
6049
+ return `${previewNodeId}:${suffix}`;
6050
+ }
6051
+ function createFallbackPrefabStageStructure(descriptor) {
6052
+ return [{
6053
+ id: 'prefab-root',
6054
+ label: descriptor.label,
6055
+ kind: 'root',
6056
+ icon: 'prefab',
6057
+ meta: descriptor.assetId,
6058
+ readonly: descriptor.readonly,
6059
+ children: [{
6060
+ id: 'prefab-source',
6061
+ label: 'Source Model',
6062
+ kind: 'source',
6063
+ icon: 'asset',
6064
+ sourceAssetId: descriptor.sourceAssetId,
6065
+ meta: descriptor.sourceAssetId ?? 'Missing source asset',
6066
+ readonly: true,
6067
+ }],
6068
+ }];
6069
+ }
6070
+ function getScopedPreviewSelectionForUiState(state, options, fallbackSelection) {
6071
+ return state.prefabStage
6072
+ ? getCurrentPrefabStageProjectionSelection(state, options)
6073
+ : fallbackSelection;
6074
+ }
6075
+ function getCurrentPreviewSelection(state, options) {
6076
+ return getScopedPreviewSelectionForUiState(state, options, state.session?.getState({ cloneDocuments: false }).selection ?? EMPTY_EDITOR_SELECTION);
6077
+ }
3638
6078
  function createUiPreviewState(state, options) {
3639
6079
  const sessionState = state.session?.getState({ cloneDocuments: false });
3640
6080
  const selection = sessionState?.selection;
3641
6081
  const document = sessionState?.workingDocument ?? null;
3642
- const selectedIds = selection?.selectedIds ?? [];
3643
- const activeId = selection?.activeId ?? null;
3644
- const largeSelection = isLargeSelection(selectedIds);
3645
- const shouldPreviewInspector = !!state.activeTransformPreview && document && activeId && selectedIds.length === 1;
6082
+ const documentSelectedIds = selection?.selectedIds ?? [];
6083
+ const documentActiveId = selection?.activeId ?? null;
6084
+ const scopedPreviewSelection = getScopedPreviewSelectionForUiState(state, options, {
6085
+ selectedIds: documentSelectedIds,
6086
+ activeId: documentActiveId,
6087
+ });
6088
+ const selectedIds = scopedPreviewSelection.selectedIds;
6089
+ const activeId = scopedPreviewSelection.activeId;
6090
+ const transformSelectionIsLarge = isLargeSelection(selectedIds);
6091
+ const shouldPreviewInspector = !!state.activeTransformPreview && document && documentActiveId && documentSelectedIds.length === 1;
3646
6092
  const inspectorObjectBase = shouldPreviewInspector
3647
- ? options.documentAdapter.getInspectorObject?.(document, activeId)
6093
+ ? options.documentAdapter.getInspectorObject?.(document, documentActiveId)
3648
6094
  ?? (() => {
3649
- const serializedObject = options.documentAdapter.getSerializedObject(document, activeId);
6095
+ const serializedObject = options.documentAdapter.getSerializedObject(document, documentActiveId);
3650
6096
  return serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null;
3651
6097
  })()
3652
6098
  : null;
@@ -3670,11 +6116,11 @@ function createUiPreviewState(state, options) {
3670
6116
  draggingNodeId: state.gizmo?.getState().draggingNodeId ?? null,
3671
6117
  },
3672
6118
  transformOperations: {
3673
- settings: cloneTransformOperationSettings(state.transformOperationSettings),
6119
+ settings: cloneTransformOperationSettings(getScopedTransformOperationSettings(state)),
3674
6120
  selectedCount: selectedIds.length,
3675
6121
  activeId,
3676
- canAlign: !largeSelection && selectedIds.length >= 2 && activeId != null,
3677
- canDistribute: !largeSelection && selectedIds.length >= 3,
6122
+ canAlign: !transformSelectionIsLarge && selectedIds.length >= 2 && activeId != null && !state.prefabStage,
6123
+ canDistribute: !transformSelectionIsLarge && selectedIds.length >= 3 && !state.prefabStage,
3678
6124
  },
3679
6125
  viewportTools: cloneEditorViewportToolState(state.viewportTools),
3680
6126
  viewportMeasurement: cloneViewportMeasurement(state.viewportMeasurement),
@@ -3891,12 +6337,108 @@ function createAggregatedInspectorMultiObjectBase(_state, options, document, sel
3891
6337
  return aggregatedInspector;
3892
6338
  }
3893
6339
  function createSingleInspectorObjectBase(options, document, targetId) {
6340
+ const markerInspector = createMarkerInspectorObjectBase(options, document, targetId);
6341
+ if (markerInspector)
6342
+ return markerInspector;
3894
6343
  const inspectorObject = options.documentAdapter.getInspectorObject?.(document, targetId) ?? null;
3895
6344
  if (inspectorObject)
3896
- return inspectorObject;
6345
+ return withMarkerGraphRelationInspectorSection(options, document, inspectorObject);
3897
6346
  const serializedObject = options.documentAdapter.getSerializedObject(document, targetId);
3898
6347
  return serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null;
3899
6348
  }
6349
+ function withMarkerGraphRelationInspectorSection(options, document, inspectorObject, graph = options.documentAdapter.getMarkerGraph?.(document) ?? null, hierarchy = options.documentAdapter.getHierarchyItems(document), markerTypes = options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [], relationTypes = options.documentAdapter.getRelationTypeCatalog?.(document) ?? [], activeRelationId = null, relationPick = null, relationVisibility = {}) {
6350
+ if (inspectorObject.targetIds.length !== 1)
6351
+ return inspectorObject;
6352
+ if (inspectorObject.sections.some(section => section.id === 'markerRelations'))
6353
+ return inspectorObject;
6354
+ if (inspectorObject.sections.some(section => section.properties.some(property => property.path === 'marker.relations.editor'))) {
6355
+ return inspectorObject;
6356
+ }
6357
+ const targetId = inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? null;
6358
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, targetId, hierarchy);
6359
+ if (!targetId || !markerId)
6360
+ return inspectorObject;
6361
+ const markerInspector = createMarkerGraphInspectorObject({
6362
+ document,
6363
+ graph,
6364
+ markerId: targetId,
6365
+ hierarchy,
6366
+ markerTypes,
6367
+ relationTypes,
6368
+ activeRelationId,
6369
+ relationPick,
6370
+ relationVisibility,
6371
+ });
6372
+ const relationSection = markerInspector?.sections.find(section => section.id === 'markerRelations') ?? null;
6373
+ if (!relationSection)
6374
+ return inspectorObject;
6375
+ const capabilities = new Set(inspectorObject.selection.capabilities ?? []);
6376
+ capabilities.add('markerGraph');
6377
+ const markerSection = inspectorObject.sections.find(section => section.id === 'marker') ?? null;
6378
+ const sections = markerSection
6379
+ ? inspectorObject.sections.map(section => section.id === 'marker'
6380
+ ? {
6381
+ ...section,
6382
+ properties: [
6383
+ ...section.properties,
6384
+ ...createIntegratedMarkerRelationProperties(section.properties, relationSection.properties),
6385
+ ],
6386
+ }
6387
+ : section)
6388
+ : mergeInspectorSections(inspectorObject.sections, [relationSection], { propertyConflict: 'ignore' });
6389
+ return {
6390
+ ...inspectorObject,
6391
+ selection: {
6392
+ ...inspectorObject.selection,
6393
+ targetKind: inspectorObject.selection.targetKind ?? 'marker',
6394
+ capabilities: [...capabilities],
6395
+ },
6396
+ sections,
6397
+ };
6398
+ }
6399
+ function createIntegratedMarkerRelationProperties(markerProperties, relationProperties) {
6400
+ const existingPaths = new Set(markerProperties.map(property => property.path));
6401
+ const baseOrder = markerProperties.reduce((maxOrder, property, index) => (Math.max(maxOrder, property.order ?? index)), -1);
6402
+ return relationProperties
6403
+ .filter(property => !existingPaths.has(property.path))
6404
+ .map((property, index) => ({
6405
+ ...property,
6406
+ label: formatIntegratedMarkerRelationPropertyLabel(property),
6407
+ order: baseOrder + index + 1,
6408
+ }));
6409
+ }
6410
+ function formatIntegratedMarkerRelationPropertyLabel(property) {
6411
+ switch (property.path) {
6412
+ case 'marker.relations.count': return '关系数量';
6413
+ case 'marker.relations.status': return '关系状态';
6414
+ case 'marker.relations.editor': return '关系';
6415
+ default: return property.label;
6416
+ }
6417
+ }
6418
+ function createMarkerInspectorObjectBase(options, document, targetId, graph = options.documentAdapter.getMarkerGraph?.(document) ?? null, hierarchy = options.documentAdapter.getHierarchyItems(document), markerTypes = options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [], activeRelationId = null, relationPick = null, relationVisibility = {}) {
6419
+ const virtualMarkerId = readMarkerGraphHierarchyMarkerIdForGraph(graph, targetId, hierarchy);
6420
+ const isVirtualRoot = isMarkerGraphHierarchyRootRowId(graph, targetId, hierarchy);
6421
+ if (!virtualMarkerId && !isVirtualRoot)
6422
+ return null;
6423
+ return createMarkerGraphInspectorObject({
6424
+ document,
6425
+ graph,
6426
+ markerId: targetId,
6427
+ hierarchy,
6428
+ markerTypes,
6429
+ relationTypes: options.documentAdapter.getRelationTypeCatalog?.(document) ?? [],
6430
+ activeRelationId,
6431
+ relationPick,
6432
+ relationVisibility,
6433
+ }) ?? createMarkerGraphRootInspectorObject({
6434
+ document,
6435
+ graph,
6436
+ rootId: targetId,
6437
+ hierarchy,
6438
+ markerTypes,
6439
+ relationTypes: options.documentAdapter.getRelationTypeCatalog?.(document) ?? [],
6440
+ });
6441
+ }
3900
6442
  function applyMultiInspectorBatchCapabilityGate(options, document, inspectorObject) {
3901
6443
  return options.documentAdapter.createSerializedMultiPropertyPatch
3902
6444
  ? applyAggregatedBatchInspectorCapabilityGate(options, document, inspectorObject)
@@ -4225,6 +6767,12 @@ function isDocumentNodeLocked(state, options, id) {
4225
6767
  return options.documentAdapter.isLocked?.(document, id) ?? false;
4226
6768
  }
4227
6769
  function isNodeSelectableInDocument(options, document, id) {
6770
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
6771
+ const hierarchy = graph ? options.documentAdapter.getHierarchyItems(document) : [];
6772
+ if (isMarkerGraphHierarchyRootRowId(graph, id, hierarchy))
6773
+ return true;
6774
+ if (isMarkerGraphHierarchyId(graph, id, hierarchy))
6775
+ return true;
4228
6776
  if (options.documentAdapter.isLocked?.(document, id))
4229
6777
  return false;
4230
6778
  return options.documentAdapter.isSelectable?.(document, id) ?? true;
@@ -4246,6 +6794,36 @@ function findAssetByResolvedId(state, options, assetId) {
4246
6794
  }
4247
6795
  return selected?.asset ?? null;
4248
6796
  }
6797
+ function findBrowserAssetItemByResolvedId(state, options, document, assetId) {
6798
+ let selected = null;
6799
+ for (const asset of state.assets) {
6800
+ const item = toBrowserAssetItem(options, asset);
6801
+ if (item.id !== assetId && item.assetId !== assetId && resolveAssetId(options, asset) !== assetId)
6802
+ continue;
6803
+ if (!selected || shouldReplaceBrowserAssetItem(selected, item))
6804
+ selected = item;
6805
+ }
6806
+ const documentItem = document ? findDocumentBrowserAssetItemByResolvedId(options, document, assetId) : null;
6807
+ if (documentItem && (!selected || shouldReplaceBrowserAssetItem(selected, documentItem)))
6808
+ selected = documentItem;
6809
+ return selected;
6810
+ }
6811
+ function findAssetOrDocumentBrowserItemByResolvedId(state, options, document, assetId) {
6812
+ return findAssetByResolvedId(state, options, assetId)
6813
+ ?? (document ? findDocumentBrowserAssetItemByResolvedId(options, document, assetId) : null);
6814
+ }
6815
+ function findDocumentBrowserAssetItemByResolvedId(options, document, assetId) {
6816
+ let selected = null;
6817
+ for (const item of options.documentAdapter.getBrowserAssetItems?.(document) ?? []) {
6818
+ if (item.id !== assetId && item.assetId !== assetId)
6819
+ continue;
6820
+ if (item.placeable === false || item.disabled === true)
6821
+ continue;
6822
+ if (!selected || shouldReplaceBrowserAssetItem(selected, item))
6823
+ selected = item;
6824
+ }
6825
+ return selected;
6826
+ }
4249
6827
  function resolveBrowserAssetItemId(state, options, assetId) {
4250
6828
  let selected = null;
4251
6829
  for (const asset of state.assets) {
@@ -4271,6 +6849,7 @@ function toBrowserAssetItem(options, asset) {
4271
6849
  placeable: asset.placeable,
4272
6850
  preview: asset.preview,
4273
6851
  material: asset.material,
6852
+ prefab: asset.prefab,
4274
6853
  meta: asset.meta,
4275
6854
  disabled: asset.disabled ?? asset.placeable === false,
4276
6855
  };