@fps-games/editor 0.1.5-beta.5 → 0.1.6-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 (218) hide show
  1. package/dist/build-info.json +16 -16
  2. package/dist/local-editor-agent-actions.d.ts +8 -1
  3. package/dist/local-editor-agent-actions.d.ts.map +1 -1
  4. package/dist/local-editor-agent-actions.js +171 -12
  5. package/dist/local-editor-agent-actions.js.map +1 -1
  6. package/dist/local-editor-agent-context.d.ts +29 -1
  7. package/dist/local-editor-agent-context.d.ts.map +1 -1
  8. package/dist/local-editor-agent-context.js +403 -0
  9. package/dist/local-editor-agent-context.js.map +1 -1
  10. package/dist/local-editor-harness.d.ts +103 -5
  11. package/dist/local-editor-harness.d.ts.map +1 -1
  12. package/dist/local-editor-harness.js +1982 -101
  13. package/dist/local-editor-harness.js.map +1 -1
  14. package/dist/local-editor-marker-graph.d.ts +92 -0
  15. package/dist/local-editor-marker-graph.d.ts.map +1 -0
  16. package/dist/local-editor-marker-graph.js +1432 -0
  17. package/dist/local-editor-marker-graph.js.map +1 -0
  18. package/dist/local-editor-performance-stats.js +1 -1
  19. package/dist/local-editor-performance-stats.js.map +1 -1
  20. package/dist/playable-babylon-facade.d.ts +2 -2
  21. package/dist/playable-babylon-facade.d.ts.map +1 -1
  22. package/dist/playable-babylon-facade.js +1 -1
  23. package/dist/playable-babylon-facade.js.map +1 -1
  24. package/dist/playable-local-editor-host.d.ts +52 -7
  25. package/dist/playable-local-editor-host.d.ts.map +1 -1
  26. package/dist/playable-local-editor-host.js +23 -1
  27. package/dist/playable-local-editor-host.js.map +1 -1
  28. package/dist/playable-sdk.d.ts +4 -2
  29. package/dist/playable-sdk.d.ts.map +1 -1
  30. package/dist/playable-sdk.js +2 -1
  31. package/dist/playable-sdk.js.map +1 -1
  32. package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
  33. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.d.ts.map +1 -1
  34. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js +11 -0
  35. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js.map +1 -1
  36. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +3 -1
  37. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
  38. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +114 -18
  39. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
  40. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +1 -0
  41. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
  42. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +3 -0
  43. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
  44. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +3 -0
  45. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
  46. package/node_modules/@fps-games/editor-babylon/dist/index.js +3 -0
  47. package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
  48. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts +52 -0
  49. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts.map +1 -0
  50. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js +1395 -0
  51. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js.map +1 -0
  52. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
  53. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +4 -65
  54. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
  55. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts +28 -0
  56. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts.map +1 -0
  57. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js +235 -0
  58. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js.map +1 -0
  59. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts +37 -0
  60. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts.map +1 -0
  61. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js +451 -0
  62. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js.map +1 -0
  63. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.d.ts.map +1 -1
  64. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js +14 -3
  65. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js.map +1 -1
  66. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts +1 -0
  67. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts.map +1 -1
  68. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js +20 -4
  69. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js.map +1 -1
  70. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts +3 -6
  71. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts.map +1 -1
  72. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js +4 -37
  73. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js.map +1 -1
  74. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +44 -1
  75. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
  76. package/node_modules/@fps-games/editor-babylon/dist/projection.js +295 -41
  77. package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
  78. package/node_modules/@fps-games/editor-babylon/package.json +5 -5
  79. package/node_modules/@fps-games/editor-browser/dist/index.d.ts +5 -2
  80. package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
  81. package/node_modules/@fps-games/editor-browser/dist/index.js +2 -0
  82. package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
  83. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts +5 -0
  84. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts.map +1 -0
  85. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js +23 -0
  86. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js.map +1 -0
  87. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
  88. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +69 -0
  89. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
  90. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.d.ts.map +1 -1
  91. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js +1 -0
  92. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js.map +1 -1
  93. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +2 -0
  94. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
  95. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +38 -1
  96. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
  97. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts +1 -1
  98. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.map +1 -1
  99. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
  100. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +8 -2
  101. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
  102. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +1 -1
  103. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
  104. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +7 -0
  105. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
  106. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js +1 -1
  107. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
  108. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +4 -0
  109. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
  110. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +1471 -29
  111. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
  112. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts +52 -0
  113. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts.map +1 -0
  114. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js +131 -0
  115. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js.map +1 -0
  116. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts +1 -1
  117. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts.map +1 -1
  118. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts.map +1 -1
  119. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +9 -2
  120. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js.map +1 -1
  121. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +234 -3
  122. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  123. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +10 -1
  124. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
  125. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +3 -1
  126. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
  127. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +4 -3
  128. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
  129. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +1274 -55
  130. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
  131. package/node_modules/@fps-games/editor-browser/package.json +3 -3
  132. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +1 -1
  133. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
  134. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +2 -0
  135. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -1
  136. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +1 -0
  137. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -1
  138. package/node_modules/@fps-games/editor-core/package.json +2 -2
  139. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  140. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.d.ts.map +1 -1
  141. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js +17 -3
  142. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js.map +1 -1
  143. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.d.ts.map +1 -1
  144. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js +2 -1
  145. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js.map +1 -1
  146. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts +3 -2
  147. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts.map +1 -1
  148. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts +1 -0
  149. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts.map +1 -1
  150. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js +68 -2
  151. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js.map +1 -1
  152. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts +1 -1
  153. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts.map +1 -1
  154. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js +1 -1
  155. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js.map +1 -1
  156. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts +56 -1
  157. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts.map +1 -1
  158. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js +531 -0
  159. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js.map +1 -1
  160. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts +103 -2
  161. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts.map +1 -1
  162. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts +1 -1
  163. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts.map +1 -1
  164. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js +1 -1
  165. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js.map +1 -1
  166. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.d.ts.map +1 -1
  167. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js +9 -1
  168. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js.map +1 -1
  169. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts +1 -0
  170. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts.map +1 -1
  171. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js +3 -0
  172. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js.map +1 -1
  173. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.d.ts.map +1 -1
  174. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js +3 -7
  175. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js.map +1 -1
  176. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.d.ts.map +1 -1
  177. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js +6 -0
  178. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js.map +1 -1
  179. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts +2 -10
  180. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts.map +1 -1
  181. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts.map +1 -1
  182. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js +8 -0
  183. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js.map +1 -1
  184. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
  185. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +66 -31
  186. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
  187. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts +2 -1
  188. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts.map +1 -1
  189. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.d.ts.map +1 -1
  190. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js +40 -2
  191. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js.map +1 -1
  192. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts +1 -0
  193. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts.map +1 -1
  194. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js +20 -0
  195. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js.map +1 -1
  196. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.d.ts.map +1 -1
  197. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js +68 -27
  198. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js.map +1 -1
  199. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.d.ts.map +1 -1
  200. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js +7 -4
  201. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js.map +1 -1
  202. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.d.ts.map +1 -1
  203. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js +2 -1
  204. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js.map +1 -1
  205. package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
  206. package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts +57 -3
  207. package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts.map +1 -1
  208. package/node_modules/@fps-games/editor-protocol/dist/agent.js.map +1 -1
  209. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +19 -0
  210. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
  211. package/node_modules/@fps-games/editor-protocol/dist/index.js +1 -0
  212. package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
  213. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts +128 -0
  214. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts.map +1 -0
  215. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js +2 -0
  216. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js.map +1 -0
  217. package/node_modules/@fps-games/editor-protocol/package.json +1 -1
  218. package/package.json +8 -8
@@ -1,18 +1,61 @@
1
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';
2
2
  import { createLocalEditorBrowserUi, toTransformOperationPolicyStatusLabel, } from '@fps-games/editor-browser';
3
3
  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';
4
+ import { createBabylonEditorProjection, createBabylonEditorMarkerOverlayController, createBabylonMarkerOverlayProjectionAdapter, createBabylonEditorWorld, createBabylonPrefabComparisonPreviewController, createBabylonProjectionSelectionController, createBabylonSceneCameraPreviewController, createBabylonSceneViewCameraController, createBabylonSceneViewInputController, createBabylonSceneViewMeasurementController, createBabylonSceneViewSpatialOverlayController, createBabylonEditorShadowPreviewController, createBabylonTransformGizmoController, createBabylonEditorPerformanceMonitor, focusEditorViewportSelection, } from '@fps-games/editor-babylon';
5
5
  import { createLocalEditorSceneRenderScheduler, } from './local-editor-scene-render-scheduler.js';
6
6
  import { createPlayableBabylonOutlineInspectorSection, createPlayableBabylonOutlineInspectorSeedPatchValue, isPlayableBabylonOutlineInspectorPatchPath, } from './playable-babylon-outline-inspector.js';
7
7
  import { createLocalEditorViewportRenderCoordinator, } from './local-editor-viewport-render-coordinator.js';
8
8
  import { createLocalEditorSceneViewInteractionRuntime, } from './local-editor-scene-view-interaction-runtime.js';
9
9
  import { cloneLocalEditorPerformanceStats, countLocalEditorAuthoredObjects, createLocalEditorPerformanceStats, LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON, } from './local-editor-performance-stats.js';
10
+ 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
11
  const BATCH_EDIT_REQUIRES_MULTI_PATCH_REASON = 'Batch edit requires multi-property patch support.';
11
12
  const LARGE_SELECTION_THRESHOLD = 200;
12
13
  const LARGE_SHADOW_MODE_BATCH_THRESHOLD = 200;
13
14
  const EDITOR_PROJECTION_IDLE_BEFORE_RUN_GAME_TIMEOUT_MS = 1500;
14
15
  const EDITOR_WORLD_DISPOSE_BOUNDARY_DELAY_MS = 500;
15
16
  const RENDERING_ACTION_PROGRESS_MIN_VISIBLE_MS = 1000;
17
+ const EMPTY_EDITOR_SELECTION = { selectedIds: [], activeId: null };
18
+ const MARKER_RELATION_HIGHLIGHT_RENDER_REASON = 'marker-relation-highlight';
19
+ const PREFAB_STAGE_STUDIO_WORLD_APPEARANCE = {
20
+ sky: {
21
+ preset: 'atmospheric',
22
+ topColor: { r: 0.68, g: 0.82, b: 0.9 },
23
+ horizonColor: { r: 0.98, g: 0.98, b: 0.93 },
24
+ bottomColor: { r: 0.44, g: 0.54, b: 0.53 },
25
+ cloudColor: { r: 1, g: 0.97, b: 0.86 },
26
+ cloudStrength: 0.1,
27
+ sunColor: { r: 1, g: 0.93, b: 0.72 },
28
+ sunDirection: { x: -0.24, y: 0.28, z: -0.93 },
29
+ },
30
+ };
31
+ const PREFAB_STAGE_STUDIO_ENVIRONMENT = {
32
+ textureUrl: 'https://assets.babylonjs.com/environments/studio.env',
33
+ intensity: 1,
34
+ rotationY: 0,
35
+ };
36
+ const PREFAB_STAGE_COMPARISON_PREVIEW_DEFINITIONS = [{
37
+ id: 'mesh-clone',
38
+ label: 'Mesh Clone',
39
+ mode: 'meshClone',
40
+ nodeIdSuffix: 'compare-clone',
41
+ }, {
42
+ id: 'instanced-mesh',
43
+ label: 'InstanceMesh',
44
+ mode: 'instancedMesh',
45
+ nodeIdSuffix: 'compare-instance',
46
+ }];
47
+ function createDefaultMarkerGraphVisibilityState() {
48
+ return {
49
+ rootVisible: true,
50
+ hiddenMarkerIds: new Set(),
51
+ };
52
+ }
53
+ function createEmptyMarkerCreateReservations() {
54
+ return {
55
+ ids: new Set(),
56
+ labels: new Set(),
57
+ };
58
+ }
16
59
  export function createLocalEditorHarness(options) {
17
60
  const root = options.root ?? document.body;
18
61
  const state = {
@@ -31,6 +74,12 @@ export function createLocalEditorHarness(options) {
31
74
  grid: null,
32
75
  gridVisible: true,
33
76
  projection: null,
77
+ markerOverlay: null,
78
+ markerGraphVisibility: createDefaultMarkerGraphVisibilityState(),
79
+ activeMarkerRelationId: null,
80
+ markerRelationPick: null,
81
+ markerRelationHighlightContinuous: false,
82
+ markerCreateReservations: createEmptyMarkerCreateReservations(),
34
83
  shadowPreview: null,
35
84
  environmentTexture: null,
36
85
  postProcess: null,
@@ -45,6 +94,7 @@ export function createLocalEditorHarness(options) {
45
94
  sceneViewMeasurement: null,
46
95
  sceneViewSpatialOverlay: null,
47
96
  selectionController: null,
97
+ prefabComparisonPreview: null,
48
98
  sceneRenderScheduler: null,
49
99
  viewportRenderCoordinator: null,
50
100
  sceneFrameStats: null,
@@ -62,6 +112,7 @@ export function createLocalEditorHarness(options) {
62
112
  viewportTools: cloneEditorViewportToolState(DEFAULT_EDITOR_VIEWPORT_TOOL_STATE),
63
113
  viewportMeasurement: createEmptyViewportMeasurement(),
64
114
  viewportSpatialOverlay: createEmptyEditorViewportSpatialOverlayState(),
115
+ prefabStage: null,
65
116
  duplicateDrag: null,
66
117
  activeTransformPreview: null,
67
118
  armedPlacement: null,
@@ -116,6 +167,14 @@ export function createLocalEditorHarness(options) {
116
167
  if (handleBrowserAssetAction(state, options, input))
117
168
  harness.render();
118
169
  },
170
+ onExitPrefabStage: () => {
171
+ if (exitPrefabStage(state, options))
172
+ harness.render();
173
+ },
174
+ onSelectPrefabStageItem: (itemId) => {
175
+ if (selectPrefabStageItem(state, itemId))
176
+ harness.render();
177
+ },
119
178
  onSelectHierarchyItem: (input) => {
120
179
  selectItem(state, options, input, harness.render);
121
180
  },
@@ -123,6 +182,14 @@ export function createLocalEditorHarness(options) {
123
182
  if (patchHierarchyItemEnabled(state, options, input))
124
183
  harness.render();
125
184
  },
185
+ onMarkerTypeAction: (input) => {
186
+ if (patchMarkerTypeCatalog(state, options, input))
187
+ harness.render();
188
+ },
189
+ onMarkerRelationAction: (input) => {
190
+ if (patchMarkerRelation(state, options, input))
191
+ harness.render();
192
+ },
126
193
  onSelectionCommand: (command) => {
127
194
  dispatchSelectionCommand(state, options, command, harness.render);
128
195
  },
@@ -140,6 +207,12 @@ export function createLocalEditorHarness(options) {
140
207
  harness.render();
141
208
  }
142
209
  : undefined,
210
+ onSceneGraphCreateMarker: options.documentAdapter.createMarkerGraphPatch
211
+ ? (intent) => {
212
+ if (createSceneGraphMarker(state, options, intent))
213
+ harness.render();
214
+ }
215
+ : undefined,
143
216
  onSceneGraphDelete: (intent) => {
144
217
  if (deleteSceneGraphNodes(state, options, intent))
145
218
  harness.render();
@@ -181,6 +254,10 @@ export function createLocalEditorHarness(options) {
181
254
  if (patched || state.status !== previousStatus)
182
255
  harness.render();
183
256
  },
257
+ onInspectorAction: (input) => {
258
+ if (handleInspectorAction(state, options, input))
259
+ harness.render();
260
+ },
184
261
  onRenderingPropertyChange: options.documentAdapter.onRenderingPropertyChange
185
262
  ? (input) => {
186
263
  void applyRenderingPropertyChange(state, options, input)
@@ -201,7 +278,7 @@ export function createLocalEditorHarness(options) {
201
278
  }
202
279
  : undefined,
203
280
  onTransformToolChange: (tool) => {
204
- commitEditorStateChange(state, harness.render, 'transform-tool-change', () => {
281
+ commitEditorStateChange(state, options, harness.render, 'transform-tool-change', () => {
205
282
  state.transformTool = tool;
206
283
  state.transformConstraint = normalizeTransformConstraint(tool, state.transformConstraint);
207
284
  }, {
@@ -209,21 +286,21 @@ export function createLocalEditorHarness(options) {
209
286
  });
210
287
  },
211
288
  onTransformSpaceChange: (space) => {
212
- commitEditorStateChange(state, harness.render, 'transform-space-change', () => {
289
+ commitEditorStateChange(state, options, harness.render, 'transform-space-change', () => {
213
290
  state.transformSpace = space;
214
291
  }, {
215
292
  syncTransformArtifacts: true,
216
293
  });
217
294
  },
218
295
  onTransformConstraintChange: (constraint) => {
219
- commitEditorStateChange(state, harness.render, 'transform-constraint-change', () => {
296
+ commitEditorStateChange(state, options, harness.render, 'transform-constraint-change', () => {
220
297
  state.transformConstraint = normalizeTransformConstraint(state.transformTool, constraint);
221
298
  }, {
222
299
  syncTransformArtifacts: true,
223
300
  });
224
301
  },
225
302
  onTransformSnapEnabledChange: (enabled) => {
226
- commitEditorStateChange(state, harness.render, 'transform-operation-settings-change', () => {
303
+ commitEditorStateChange(state, options, harness.render, 'transform-operation-settings-change', () => {
227
304
  state.transformOperationSettings = updateTransformOperationSettings(state.transformOperationSettings, {
228
305
  snap: {
229
306
  ...state.transformOperationSettings.snap,
@@ -239,7 +316,7 @@ export function createLocalEditorHarness(options) {
239
316
  const value = normalizePositiveStep(input.value);
240
317
  if (value == null)
241
318
  return;
242
- commitEditorStateChange(state, harness.render, 'transform-operation-settings-change', () => {
319
+ commitEditorStateChange(state, options, harness.render, 'transform-operation-settings-change', () => {
243
320
  const snap = { ...state.transformOperationSettings.snap };
244
321
  if (input.kind === 'move')
245
322
  snap.moveStep = value;
@@ -254,7 +331,7 @@ export function createLocalEditorHarness(options) {
254
331
  });
255
332
  },
256
333
  onPlacementModeChange: (mode) => {
257
- commitEditorStateChange(state, harness.render, 'placement-mode-change', () => {
334
+ commitEditorStateChange(state, options, harness.render, 'placement-mode-change', () => {
258
335
  state.transformOperationSettings = updateTransformOperationSettings(state.transformOperationSettings, {
259
336
  placementMode: normalizePlacementMode(mode),
260
337
  });
@@ -281,6 +358,10 @@ export function createLocalEditorHarness(options) {
281
358
  if (setViewportOverlaySettings(state, settings))
282
359
  harness.render();
283
360
  },
361
+ onMarkerViewportVisibilityChange: (visible) => {
362
+ if (setMarkerViewportVisibility(state, options, visible))
363
+ harness.render();
364
+ },
284
365
  onViewportUtilityToolChange: (tool) => {
285
366
  if (setViewportUtilityTool(state, tool))
286
367
  harness.render();
@@ -301,6 +382,18 @@ export function createLocalEditorHarness(options) {
301
382
  setLocalEditorPerformanceMonitorOpen(state, open);
302
383
  updateLocalEditorPerformanceStats(state, stats => ui.updatePerformanceStats?.(stats));
303
384
  },
385
+ ...{
386
+ onPrefabComparisonPreviewLayoutChange: () => {
387
+ harness.render();
388
+ },
389
+ onPrefabComparisonPreviewInteraction: (input) => {
390
+ if (!state.prefabStage)
391
+ return;
392
+ if (state.prefabComparisonPreview?.adjustView(input)) {
393
+ requestEditorSceneFrame(state, 'prefab-comparison-preview-interaction');
394
+ }
395
+ },
396
+ },
304
397
  onFocusSelection: () => {
305
398
  if (focusSelectedProjection(state))
306
399
  harness.render();
@@ -333,11 +426,12 @@ export function createLocalEditorHarness(options) {
333
426
  };
334
427
  harness = {
335
428
  render() {
336
- syncSceneCameraPreview(state, options);
337
429
  syncViewportCameraState(state);
338
430
  syncViewportMeasurementState(state);
339
431
  syncViewportSpatialOverlay(state);
340
432
  ui.update(createUiState(state, options));
433
+ syncPrefabComparisonPreviewSurfaces(state, ui);
434
+ syncSceneCameraPreview(state, options);
341
435
  },
342
436
  notifyViewportRevealed(reason = 'viewport-revealed') {
343
437
  state.viewportRenderCoordinator?.requestRevealFrame(reason);
@@ -412,6 +506,10 @@ export function createLocalEditorHarness(options) {
412
506
  const preparedDocument = options.documentAdapter.prepareDocument?.(document, assets) ?? document;
413
507
  state.assets = assets;
414
508
  state.source = source;
509
+ state.markerGraphVisibility = createDefaultMarkerGraphVisibilityState();
510
+ state.activeMarkerRelationId = null;
511
+ state.markerRelationHighlightContinuous = false;
512
+ state.markerCreateReservations = createEmptyMarkerCreateReservations();
415
513
  state.session = createEditorSession({
416
514
  source: source ?? undefined,
417
515
  persistedDocument: preparedDocument,
@@ -425,7 +523,8 @@ export function createLocalEditorHarness(options) {
425
523
  ui.updatePreview(previewState);
426
524
  else
427
525
  ui.update(createUiState(state, options));
428
- }, stats => ui.updateSceneFrameStats?.(stats), stats => ui.updatePerformanceStats?.(stats));
526
+ syncPrefabComparisonPreviewSurfaces(state, ui);
527
+ }, stats => ui.updateSceneFrameStats?.(stats), stats => ui.updatePerformanceStats?.(stats), () => ui.getPrefabComparisonPreviewSurfaces?.() ?? []);
429
528
  state.mode = 'editor';
430
529
  state.summary = loadedSource?.summary ?? summarizeDocument(options, preparedDocument, source);
431
530
  state.status = `GameWorld disposed; EditorWorld active; assets=${assets.length}`;
@@ -667,6 +766,25 @@ function setViewportOverlaySettings(state, settings) {
667
766
  state.statusDetails = '';
668
767
  return true;
669
768
  }
769
+ function setMarkerViewportVisibility(state, options, visible) {
770
+ const nextVisible = visible !== false;
771
+ if ((state.viewportTools.markersVisible ?? true) === nextVisible)
772
+ return false;
773
+ state.viewportTools = {
774
+ ...state.viewportTools,
775
+ markersVisible: nextVisible,
776
+ };
777
+ state.status = nextVisible ? 'Marker view shown' : 'Marker view hidden';
778
+ state.statusTone = 'default';
779
+ state.statusToneStatus = state.status;
780
+ state.statusDetails = nextVisible
781
+ ? 'Marker overlays and relation lines are visible in the editor viewport.'
782
+ : 'Marker overlays and relation lines are temporarily hidden in the editor viewport.';
783
+ state.editorStateRevision += 1;
784
+ syncCurrentSelectionToSceneArtifacts(state, options, 'marker-viewport-visibility', state.editorStateRevision);
785
+ requestEditorSceneFrame(state, 'marker-viewport-visibility');
786
+ return true;
787
+ }
670
788
  function setViewportUtilityTool(state, tool) {
671
789
  const nextTool = tool === 'measure-distance' ? 'measure-distance' : 'none';
672
790
  state.viewportTools = {
@@ -801,7 +919,7 @@ function validateTransformActionSelection(state, action) {
801
919
  ? { ok: true }
802
920
  : { ok: false, message: 'Distribute needs at least 3 selected objects' };
803
921
  }
804
- async function createEditorWorld(state, options, renderUi, renderPreviewUi, updateSceneFrameStats, updatePerformanceStats) {
922
+ async function createEditorWorld(state, options, renderUi, renderPreviewUi, updateSceneFrameStats, updatePerformanceStats, getPrefabComparisonPreviewSurfaces) {
805
923
  disposeEditorWorld(state);
806
924
  const render = (reason = 'editor-world-update') => {
807
925
  renderUi();
@@ -816,9 +934,9 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
816
934
  const babylon = await options.worldAdapter.loadBabylon();
817
935
  const engine = options.worldAdapter.createEngine(babylon, canvas);
818
936
  const document = state.session?.getState().workingDocument;
819
- const initialAppearance = resolveLocalEditorWorldAppearance(options, document);
937
+ const initialAppearance = resolveEffectiveLocalEditorWorldAppearance(state, options, document);
820
938
  const initialAppearanceKey = serializeLocalEditorWorldAppearance(initialAppearance);
821
- const initialRendering = resolveLocalEditorWorldRendering(options, document);
939
+ const initialRendering = resolveEffectiveLocalEditorWorldRendering(state, options, document);
822
940
  const initialRenderingKey = serializeLocalEditorWorldRendering(initialRendering);
823
941
  const world = createBabylonEditorWorld({
824
942
  engine,
@@ -834,6 +952,8 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
834
952
  const performanceMonitor = createBabylonEditorPerformanceMonitor(world.scene);
835
953
  const sceneRenderScheduler = createLocalEditorSceneRenderScheduler((frame) => {
836
954
  state.sceneViewInteraction?.updateFrame(frame);
955
+ syncPrefabComparisonPreviewSurfaces(state, { getPrefabComparisonPreviewSurfaces });
956
+ state.prefabComparisonPreview?.prepareFrame();
837
957
  world.render();
838
958
  }, {
839
959
  onStatsChange(stats) {
@@ -867,19 +987,46 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
867
987
  selectionEdgesPrewarm: options.world?.selectionEdgesPrewarm,
868
988
  logger: console,
869
989
  onProjectionReady(event) {
870
- syncCurrentSelectionToSceneArtifacts(state, `projection-${event.nodeId}-ready-selection`);
990
+ if (state.prefabStage) {
991
+ syncCurrentSelectionToSceneArtifacts(state, options, `prefab-stage-projection-${event.nodeId}-ready-selection`);
992
+ focusPrefabStagePreviewProjection(state, `prefab-stage-projection-${event.nodeId}-ready-focus`);
993
+ scheduleProjectionShadowPreviewRebuild(state, `prefab-stage-projection-${event.nodeId}-ready-shadow`);
994
+ viewportRenderCoordinator.invalidateScene(`prefab-stage-projection-${event.nodeId}-ready`);
995
+ renderUi();
996
+ return;
997
+ }
998
+ syncCurrentSelectionToSceneArtifacts(state, options, `projection-${event.nodeId}-ready-selection`);
871
999
  scheduleProjectionShadowPreviewRebuild(state, `projection-${event.nodeId}-ready-shadow`);
872
1000
  viewportRenderCoordinator.invalidateScene(`projection-${event.nodeId}-ready`);
873
1001
  },
874
1002
  onProjectionBatchSettled(event) {
875
- syncCurrentSelectionToSceneArtifacts(state, `projection-batch-${event.batchId}-settled-selection`);
1003
+ if (state.prefabStage) {
1004
+ syncCurrentSelectionToSceneArtifacts(state, options, `prefab-stage-projection-batch-${event.batchId}-settled-selection`);
1005
+ focusPrefabStagePreviewProjection(state, `prefab-stage-projection-batch-${event.batchId}-settled-focus`);
1006
+ scheduleProjectionShadowPreviewRebuild(state, `prefab-stage-projection-batch-${event.batchId}-settled`);
1007
+ renderUi();
1008
+ return;
1009
+ }
1010
+ syncCurrentSelectionToSceneArtifacts(state, options, `projection-batch-${event.batchId}-settled-selection`);
876
1011
  scheduleProjectionShadowPreviewRebuild(state, `projection-batch-${event.batchId}-settled`);
877
1012
  },
878
1013
  });
879
- const gizmo = createBabylonTransformGizmoController({
1014
+ const prefabComparisonPreview = createBabylonPrefabComparisonPreviewController({
880
1015
  babylon,
881
1016
  scene: world.scene,
1017
+ editorCamera: world.camera,
882
1018
  projection,
1019
+ getSkyBackdrop: () => world.skyBackdrop,
1020
+ });
1021
+ const markerOverlay = createBabylonEditorMarkerOverlayController({
1022
+ babylon,
1023
+ scene: world.scene,
1024
+ });
1025
+ const markerAwareProjection = createBabylonMarkerOverlayProjectionAdapter(projection, markerOverlay);
1026
+ const gizmo = createBabylonTransformGizmoController({
1027
+ babylon,
1028
+ scene: world.scene,
1029
+ projection: markerAwareProjection,
883
1030
  initialTool: state.transformTool,
884
1031
  initialSpace: state.transformSpace,
885
1032
  documentCapabilities: options.transformDocumentCapabilities,
@@ -897,6 +1044,8 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
897
1044
  },
898
1045
  onDragUpdate(event) {
899
1046
  updateActiveTransformPreview(state, event);
1047
+ syncMarkerOverlayToSceneArtifacts(state, options, state.session?.getState().selection ?? { selectedIds: [], activeId: null });
1048
+ state.gizmo?.refreshSelection();
900
1049
  preview('gizmo-drag-update');
901
1050
  },
902
1051
  onDragEnd(event) {
@@ -939,13 +1088,15 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
939
1088
  const selectionController = createBabylonProjectionSelectionController({
940
1089
  scene: world.scene,
941
1090
  canvas,
942
- projection,
1091
+ projection: markerAwareProjection,
943
1092
  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),
1093
+ getSelection: () => state.prefabStage ? EMPTY_EDITOR_SELECTION : state.session?.getSelection() ?? EMPTY_EDITOR_SELECTION,
1094
+ isSelectable: (nodeId) => !state.prefabStage && isDocumentNodeSelectable(state, options, nodeId),
1095
+ isLocked: (nodeId) => state.prefabStage ? true : isDocumentNodeLocked(state, options, nodeId),
947
1096
  isOperationBlocked: () => state.gizmo?.getState().dragPhase === 'dragging',
948
1097
  onSelectionCommand(command) {
1098
+ if (state.prefabStage)
1099
+ return;
949
1100
  dispatchSelectionCommand(state, options, command, render);
950
1101
  },
951
1102
  onFocusIntent(nodeId) {
@@ -967,7 +1118,7 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
967
1118
  isGizmoDragCandidate: (event) => gizmo.isGizmoDragCandidate(event),
968
1119
  isBoxSelectCandidate: (event) => selectionController.isBoxSelectionCandidate(event),
969
1120
  isViewPlaneMoveCandidate: (event) => gizmo.isViewPlaneMoveCandidate(event),
970
- isPlacementCandidate: () => isPlacementArmed(state),
1121
+ isPlacementCandidate: () => !state.prefabStage && isPlacementArmed(state),
971
1122
  isMeasurementCandidate: () => state.viewportTools.activeUtilityTool === 'measure-distance',
972
1123
  onPointerIntentStart(event) {
973
1124
  sceneViewInteraction.beginPointerIntent(event.state.intent);
@@ -1100,6 +1251,7 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1100
1251
  },
1101
1252
  });
1102
1253
  const environmentTexture = createBabylonEnvironmentTextureController(world.scene, initialRendering.environment ?? null);
1254
+ world.refreshSkyBackdrop();
1103
1255
  const postProcess = createBabylonDefaultPostProcessPipelineController(world.scene, [world.camera], initialRendering.postProcess ?? null, { name: 'editorDefaultPostProcessPipeline' });
1104
1256
  const resize = () => {
1105
1257
  engine.resize?.();
@@ -1110,7 +1262,9 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1110
1262
  state.engine = engine;
1111
1263
  state.world = world;
1112
1264
  state.grid = grid;
1113
- state.projection = projection;
1265
+ state.projection = markerAwareProjection;
1266
+ state.prefabComparisonPreview = prefabComparisonPreview;
1267
+ state.markerOverlay = markerOverlay;
1114
1268
  state.shadowPreview = shadowPreview;
1115
1269
  state.environmentTexture = environmentTexture;
1116
1270
  state.postProcess = postProcess;
@@ -1130,13 +1284,14 @@ async function createEditorWorld(state, options, renderUi, renderPreviewUi, upda
1130
1284
  state.worldRenderingKey = initialRenderingKey;
1131
1285
  state.resizeHandler = resize;
1132
1286
  if (document) {
1133
- projection.projectNodes(options.documentAdapter.getProjectionNodes(document));
1287
+ projection.projectNodes(resolveCurrentEditorProjectionNodes(state, options, document));
1134
1288
  scheduleProjectionShadowPreviewRebuild(state, 'editor-world-initial-projection-shadow');
1135
1289
  }
1136
- syncCurrentSelectionToSceneArtifacts(state, 'editor-world-initial-selection');
1290
+ syncCurrentSelectionToSceneArtifacts(state, options, 'editor-world-initial-selection');
1137
1291
  viewportRenderCoordinator.requestFrame('editor-world-created');
1138
1292
  }
1139
1293
  function disposeEditorWorld(state) {
1294
+ clearPrefabStageForEditorTeardown(state);
1140
1295
  state.activeTransformPreview = null;
1141
1296
  state.sceneViewInteraction?.dispose();
1142
1297
  state.sceneViewInteraction = null;
@@ -1157,6 +1312,12 @@ function disposeEditorWorld(state) {
1157
1312
  state.sceneViewInput = null;
1158
1313
  state.selectionController?.dispose();
1159
1314
  state.selectionController = null;
1315
+ state.prefabComparisonPreview?.dispose();
1316
+ state.prefabComparisonPreview = null;
1317
+ if (state.markerRelationHighlightContinuous) {
1318
+ state.viewportRenderCoordinator?.endContinuous(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
1319
+ state.markerRelationHighlightContinuous = false;
1320
+ }
1160
1321
  if (state.performanceMonitorOpen) {
1161
1322
  state.viewportRenderCoordinator?.endContinuous(LOCAL_EDITOR_PERFORMANCE_MONITOR_REASON);
1162
1323
  }
@@ -1179,6 +1340,8 @@ function disposeEditorWorld(state) {
1179
1340
  };
1180
1341
  state.gizmo?.dispose();
1181
1342
  state.gizmo = null;
1343
+ state.markerOverlay?.dispose();
1344
+ state.markerOverlay = null;
1182
1345
  if (state.projectionShadowPreviewRebuildFrame !== null) {
1183
1346
  window.cancelAnimationFrame(state.projectionShadowPreviewRebuildFrame);
1184
1347
  state.projectionShadowPreviewRebuildFrame = null;
@@ -1204,6 +1367,16 @@ function disposeEditorWorld(state) {
1204
1367
  state.world = null;
1205
1368
  state.engine = null;
1206
1369
  }
1370
+ function clearPrefabStageForEditorTeardown(state) {
1371
+ const stage = state.prefabStage;
1372
+ if (!stage)
1373
+ return;
1374
+ state.prefabStage = null;
1375
+ setGridVisibleForEditorContext(state, stage.restoreGridVisible, 'prefab-stage-editor-teardown-grid');
1376
+ state.sceneCameraPreviewEnabled = stage.restoreSceneCameraPreviewEnabled;
1377
+ if (!stage.restoreSceneCameraPreviewEnabled)
1378
+ state.sceneCameraPreview?.setActive(false);
1379
+ }
1207
1380
  async function waitForEditorProjectionIdleBeforeRunGame(state) {
1208
1381
  const projection = state.projection;
1209
1382
  if (!projection)
@@ -1271,6 +1444,27 @@ function requestEditorPreviewFrame(state, options, renderPreviewUi, reason) {
1271
1444
  renderPreviewUi(createUiPreviewState(state, options));
1272
1445
  requestEditorSceneFrame(state, reason);
1273
1446
  }
1447
+ function syncPrefabComparisonPreviewSurfaces(state, ui) {
1448
+ const controller = state.prefabComparisonPreview;
1449
+ if (!controller)
1450
+ return;
1451
+ if (!state.prefabStage) {
1452
+ if (controller.clear())
1453
+ requestEditorSceneFrame(state, 'prefab-comparison-preview-clear');
1454
+ return;
1455
+ }
1456
+ const surfaces = ui.getPrefabComparisonPreviewSurfaces?.() ?? [];
1457
+ const changed = controller.sync(surfaces.map(toBabylonPrefabComparisonPreviewSurface));
1458
+ if (changed)
1459
+ requestEditorSceneFrame(state, 'prefab-comparison-preview-sync');
1460
+ }
1461
+ function toBabylonPrefabComparisonPreviewSurface(surface) {
1462
+ return {
1463
+ previewId: surface.previewId,
1464
+ projectionNodeId: surface.projectionNodeId,
1465
+ rect: surface.rect,
1466
+ };
1467
+ }
1274
1468
  function updateActiveTransformPreview(state, event) {
1275
1469
  const activeId = event.activeId ?? event.nodeId ?? event.targetIds[0] ?? null;
1276
1470
  if (!activeId) {
@@ -1351,13 +1545,29 @@ function selectItem(state, options, input, renderUi) {
1351
1545
  return dispatchSelectionCommand(state, options, command, renderUi);
1352
1546
  }
1353
1547
  function createBrowserHierarchyContextActions(state, options) {
1354
- return (options.hierarchy?.contextActions ?? []).map((registration) => ({
1548
+ return createHierarchyContextActionRegistrations(state, options).map((registration) => ({
1355
1549
  id: registration.id,
1356
1550
  label: registration.label,
1357
1551
  shortcut: registration.shortcut,
1358
1552
  danger: registration.danger,
1359
1553
  placement: registration.placement,
1360
1554
  separatorBefore: registration.separatorBefore,
1555
+ children: (browserContext) => {
1556
+ const context = createHarnessHierarchyContextActionContext(state, options, browserContext);
1557
+ if (!context)
1558
+ return [];
1559
+ const children = typeof registration.children === 'function'
1560
+ ? registration.children(context)
1561
+ : registration.children ?? [];
1562
+ return children.map(child => ({
1563
+ id: child.id,
1564
+ label: child.label,
1565
+ shortcut: child.shortcut,
1566
+ danger: child.danger,
1567
+ disabled: () => child.disabled?.(context) ?? false,
1568
+ payload: () => child.payload?.(context),
1569
+ }));
1570
+ },
1361
1571
  visible: (browserContext) => {
1362
1572
  const context = createHarnessHierarchyContextActionContext(state, options, browserContext);
1363
1573
  if (!context)
@@ -1372,6 +1582,40 @@ function createBrowserHierarchyContextActions(state, options) {
1372
1582
  },
1373
1583
  }));
1374
1584
  }
1585
+ function createHierarchyContextActionRegistrations(state, options) {
1586
+ return [
1587
+ createPrefabFromGameObjectHierarchyContextAction(state, options),
1588
+ ...(options.hierarchy?.contextActions ?? []),
1589
+ ];
1590
+ }
1591
+ function createPrefabFromGameObjectHierarchyContextAction(state, options) {
1592
+ return {
1593
+ id: 'asset.create-prefab-from-game-object',
1594
+ label: 'Create Prefab',
1595
+ placement: 'after-create',
1596
+ visible: (context) => (!state.prefabStage
1597
+ && !!options.documentAdapter.createAssetActionPatch
1598
+ && context.targetIds.length === 1
1599
+ && context.activeId === context.targetIds[0]
1600
+ && !!context.projectionNode?.asset),
1601
+ disabled: (context) => {
1602
+ if (context.targetIds.length !== 1 || !context.activeId)
1603
+ return 'Select exactly one model-backed GameObject.';
1604
+ if (!context.projectionNode?.asset)
1605
+ return 'Only model-backed GameObjects can be converted to Prefab assets.';
1606
+ return false;
1607
+ },
1608
+ run: (context) => patchBrowserAssetAction(state, options, {
1609
+ actionId: 'asset.create-prefab-from-game-object',
1610
+ assetId: undefined,
1611
+ browserAssetId: undefined,
1612
+ assetKind: 'prefab',
1613
+ }, {
1614
+ activeId: context.activeId,
1615
+ selectedIds: context.targetIds,
1616
+ }),
1617
+ };
1618
+ }
1375
1619
  function createHarnessHierarchyContextActionContext(state, options, browserContext, payload) {
1376
1620
  const document = state.session?.getState().workingDocument ?? null;
1377
1621
  if (!document)
@@ -1409,17 +1653,22 @@ function createBrowserContextForCustomAction(state, options, action) {
1409
1653
  };
1410
1654
  }
1411
1655
  function runHierarchyCustomContextAction(state, options, action) {
1412
- const registration = options.hierarchy?.contextActions?.find(candidate => candidate.id === action.id);
1413
- if (!registration)
1414
- return false;
1415
1656
  const browserContext = createBrowserContextForCustomAction(state, options, action);
1416
1657
  const context = browserContext
1417
1658
  ? createHarnessHierarchyContextActionContext(state, options, browserContext, action.payload)
1418
1659
  : null;
1419
1660
  if (!context)
1420
1661
  return false;
1662
+ const registration = createHierarchyContextActionRegistrations(state, options)
1663
+ .find(candidate => candidate.id === action.id);
1664
+ if (!registration)
1665
+ return false;
1421
1666
  try {
1422
- return registration.run(context) === true;
1667
+ const result = registration.run(context);
1668
+ if (isLocalEditorHarnessPatchResult(result)) {
1669
+ return applyHierarchyContextActionPatch(state, options, result, registration.label);
1670
+ }
1671
+ return result === true;
1423
1672
  }
1424
1673
  catch (error) {
1425
1674
  const message = error instanceof Error ? error.message : String(error);
@@ -1431,6 +1680,55 @@ function runHierarchyCustomContextAction(state, options, action) {
1431
1680
  return true;
1432
1681
  }
1433
1682
  }
1683
+ function isLocalEditorHarnessPatchResult(value) {
1684
+ return !!value
1685
+ && typeof value === 'object'
1686
+ && 'patch' in value;
1687
+ }
1688
+ function applyHierarchyContextActionPatch(state, options, patch, fallbackLabel) {
1689
+ const document = state.session?.getState().workingDocument;
1690
+ if (state.mode !== 'editor' || !state.session || !document)
1691
+ return false;
1692
+ cancelActiveOperation(state);
1693
+ const label = patch.label ?? fallbackLabel;
1694
+ const projectionImpact = resolveProjectionImpact(patch, patch.createdId
1695
+ ? createKnownCreatedReprojectProjectionImpact({
1696
+ createdNodeIds: [patch.createdId],
1697
+ reprojectIds: patch.reprojectIds,
1698
+ })
1699
+ : patch.reprojectIds?.length
1700
+ ? createReprojectProjectionImpact(patch.reprojectIds)
1701
+ : patch.changedIds?.length
1702
+ ? createReprojectProjectionImpact(patch.changedIds)
1703
+ : patch.changedId
1704
+ ? createReprojectProjectionImpact([patch.changedId])
1705
+ : undefined);
1706
+ const result = state.session.dispatch({
1707
+ type: 'document.patch',
1708
+ label,
1709
+ patch: patch.patch,
1710
+ targetId: patch.createdId ?? patch.changedId ?? undefined,
1711
+ ...withProjectionImpact(projectionImpact),
1712
+ });
1713
+ if (!result.documentChanged) {
1714
+ state.status = `${label} unchanged`;
1715
+ return true;
1716
+ }
1717
+ const selectionId = patch.createdId ?? patch.changedId ?? null;
1718
+ let selection = result.selection;
1719
+ if (selectionId && isNodeSelectableInDocument(options, result.workingDocument, selectionId)) {
1720
+ selection = state.session.dispatch({
1721
+ type: 'selection.replace',
1722
+ selectedIds: [selectionId],
1723
+ activeId: selectionId,
1724
+ label: `Select ${selectionId}`,
1725
+ }).selection;
1726
+ }
1727
+ rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
1728
+ state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
1729
+ state.status = label;
1730
+ return true;
1731
+ }
1434
1732
  function handleContextAction(state, options, action) {
1435
1733
  if (action.region !== 'hierarchy')
1436
1734
  return false;
@@ -1465,6 +1763,14 @@ function handleContextAction(state, options, action) {
1465
1763
  name: action.name,
1466
1764
  });
1467
1765
  }
1766
+ if (action.action === 'create-marker') {
1767
+ return createSceneGraphMarker(state, options, {
1768
+ parentId: action.parentId ?? null,
1769
+ activeId: action.activeId ?? null,
1770
+ markerType: action.markerType,
1771
+ name: action.name,
1772
+ });
1773
+ }
1468
1774
  if (action.action === 'delete') {
1469
1775
  return deleteSceneGraphNodes(state, options, {
1470
1776
  ids: action.targetIds,
@@ -1488,6 +1794,9 @@ function handleContextAction(state, options, action) {
1488
1794
  function dispatchSelectionCommand(state, options, command, renderUi) {
1489
1795
  if (state.mode !== 'editor')
1490
1796
  return false;
1797
+ if (state.markerRelationPick) {
1798
+ return consumeMarkerRelationPickSelectionCommand(state, options, command, renderUi);
1799
+ }
1491
1800
  cancelActiveOperation(state);
1492
1801
  const session = state.session;
1493
1802
  if (!session)
@@ -1499,16 +1808,109 @@ function dispatchSelectionCommand(state, options, command, renderUi) {
1499
1808
  if (!changed)
1500
1809
  return false;
1501
1810
  if (renderUi) {
1502
- commitEditorStateChange(state, renderUi, 'selection-command', () => { }, {
1811
+ commitEditorStateChange(state, options, renderUi, 'selection-command', () => { }, {
1503
1812
  syncSelectionArtifacts: selection,
1504
1813
  });
1505
1814
  }
1506
1815
  else {
1507
1816
  state.editorStateRevision += 1;
1508
- syncSelectionToProjection(state, selection, 'selection-command', state.editorStateRevision);
1817
+ syncSelectionToProjection(state, options, selection, 'selection-command', state.editorStateRevision);
1818
+ }
1819
+ return true;
1820
+ }
1821
+ function consumeMarkerRelationPickSelectionCommand(state, options, command, renderUi) {
1822
+ const pick = state.markerRelationPick;
1823
+ if (!pick?.active)
1824
+ return false;
1825
+ const document = state.session?.getState().workingDocument ?? null;
1826
+ const graph = document ? options.documentAdapter.getMarkerGraph?.(document) ?? null : null;
1827
+ if (!document || !graph) {
1828
+ updateMarkerRelationPickWarning(state, '当前文档没有 marker graph。');
1829
+ if (renderUi)
1830
+ renderUi();
1831
+ return true;
1832
+ }
1833
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
1834
+ const targetId = readMarkerRelationPickTargetId(command);
1835
+ if (!targetId) {
1836
+ updateMarkerRelationPickWarning(state, '没有拾取到有效端点,请点击标记或场景对象。');
1837
+ if (renderUi)
1838
+ renderUi();
1839
+ return true;
1840
+ }
1841
+ const endpoint = resolveMarkerRelationPickEndpoint(graph, hierarchy, targetId);
1842
+ if (!endpoint) {
1843
+ updateMarkerRelationPickWarning(state, `暂不支持的关系端点:${targetId}`);
1844
+ if (renderUi)
1845
+ renderUi();
1846
+ return true;
1847
+ }
1848
+ if (!pick.from) {
1849
+ const status = `关系拾取中:${formatMarkerRelationPickEndpointLabel(endpoint, graph, hierarchy)} 已作为起点,请点击终点。`;
1850
+ state.markerRelationPick = {
1851
+ ...pick,
1852
+ from: endpoint,
1853
+ status,
1854
+ statusTone: 'success',
1855
+ };
1856
+ state.status = status;
1857
+ state.statusTone = 'success';
1858
+ state.statusToneStatus = state.status;
1859
+ state.statusDetails = '关系拾取会消费下一次有效视口点击,不改变当前选择。';
1860
+ if (renderUi)
1861
+ renderUi();
1862
+ return true;
1863
+ }
1864
+ const handled = patchMarkerRelation(state, options, {
1865
+ action: 'create',
1866
+ type: pick.type ?? '',
1867
+ fromKind: pick.from.kind,
1868
+ fromId: pick.from.id,
1869
+ toKind: endpoint.kind,
1870
+ toId: endpoint.id,
1871
+ directed: pick.directed,
1872
+ });
1873
+ if (handled && state.markerRelationPick) {
1874
+ state.markerRelationPick = {
1875
+ ...state.markerRelationPick,
1876
+ status: state.status,
1877
+ statusTone: state.statusTone,
1878
+ };
1509
1879
  }
1880
+ if (renderUi)
1881
+ renderUi();
1510
1882
  return true;
1511
1883
  }
1884
+ function readMarkerRelationPickTargetId(command) {
1885
+ if (command.type === 'selection.clear' || command.type === 'selection.remove')
1886
+ return null;
1887
+ return command.activeId?.trim() || command.selectedIds[command.selectedIds.length - 1]?.trim() || null;
1888
+ }
1889
+ function resolveMarkerRelationPickEndpoint(graph, hierarchy, targetId) {
1890
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, targetId, hierarchy);
1891
+ if (markerId && findSpatialMarkerById(graph, markerId)) {
1892
+ return { kind: 'marker', id: markerId };
1893
+ }
1894
+ const hierarchyItem = hierarchy.find(item => item.id === targetId) ?? null;
1895
+ if (!hierarchyItem)
1896
+ return null;
1897
+ if (hierarchyItem.role === 'marker' || hierarchyItem.role === 'group' || hierarchyItem.role === 'root')
1898
+ return null;
1899
+ return { kind: 'scene-object', id: targetId };
1900
+ }
1901
+ function updateMarkerRelationPickWarning(state, status) {
1902
+ if (state.markerRelationPick) {
1903
+ state.markerRelationPick = {
1904
+ ...state.markerRelationPick,
1905
+ status,
1906
+ statusTone: 'warning',
1907
+ };
1908
+ }
1909
+ state.status = status;
1910
+ state.statusTone = 'warning';
1911
+ state.statusToneStatus = state.status;
1912
+ state.statusDetails = status;
1913
+ }
1512
1914
  function renameSceneGraphNode(state, options, intent) {
1513
1915
  const document = state.session?.getState().workingDocument;
1514
1916
  if (state.mode !== 'editor' || !state.session || !document)
@@ -1524,7 +1926,10 @@ function renameSceneGraphNode(state, options, intent) {
1524
1926
  state.status = `Rename rejected: ${intent.id}`;
1525
1927
  return true;
1526
1928
  }
1527
- const result = state.session.dispatch({
1929
+ const session = state.session;
1930
+ if (!session)
1931
+ return false;
1932
+ const result = session.dispatch({
1528
1933
  type: 'document.patch',
1529
1934
  label: patch.label ?? `Rename ${intent.id}`,
1530
1935
  patch: patch.patch,
@@ -1536,7 +1941,7 @@ function renameSceneGraphNode(state, options, intent) {
1536
1941
  return true;
1537
1942
  }
1538
1943
  rebuildProjectionFromDocument(state, options, result.workingDocument, result.selection);
1539
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
1944
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
1540
1945
  state.status = patch.label ?? `Renamed ${intent.id}`;
1541
1946
  return true;
1542
1947
  }
@@ -1550,7 +1955,10 @@ function createSceneGraphGroup(state, options, intent) {
1550
1955
  state.status = 'Create empty rejected';
1551
1956
  return true;
1552
1957
  }
1553
- const result = state.session.dispatch({
1958
+ const session = state.session;
1959
+ if (!session)
1960
+ return false;
1961
+ const result = session.dispatch({
1554
1962
  type: 'document.patch',
1555
1963
  label: patch.label ?? 'Create Empty',
1556
1964
  patch: patch.patch,
@@ -1572,7 +1980,7 @@ function createSceneGraphGroup(state, options, intent) {
1572
1980
  }).selection;
1573
1981
  }
1574
1982
  rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
1575
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
1983
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
1576
1984
  state.status = patch.label ?? (createdId ? `Created empty ${createdId}` : 'Created empty');
1577
1985
  return true;
1578
1986
  }
@@ -1615,6 +2023,182 @@ function createSceneGraphPrimitive(state, options, intent) {
1615
2023
  state.status = patch.label ?? (createdId ? `Created ${intent.shape} ${createdId}` : `Created ${intent.shape}`);
1616
2024
  return true;
1617
2025
  }
2026
+ function createSceneGraphMarker(state, options, intent) {
2027
+ const document = state.session?.getState().workingDocument;
2028
+ if (state.mode !== 'editor' || !state.session || !document)
2029
+ return false;
2030
+ cancelActiveOperation(state);
2031
+ const markerType = intent.markerType?.trim() ?? '';
2032
+ const markerTypes = options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [];
2033
+ const markerTypeDefinition = markerType
2034
+ ? markerTypes.find(definition => definition.type === markerType) ?? null
2035
+ : null;
2036
+ if (markerType && !markerTypeDefinition) {
2037
+ state.status = `Create marker rejected: unknown marker type ${markerType}`;
2038
+ state.statusTone = 'warning';
2039
+ state.statusToneStatus = state.status;
2040
+ return true;
2041
+ }
2042
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2043
+ const parentId = resolveCreateMarkerParentId(hierarchy, intent.parentId ?? null);
2044
+ const marker = createSceneGraphMarkerNode(state, options, document, intent, markerTypeDefinition);
2045
+ state.markerCreateReservations.ids.add(marker.id);
2046
+ state.markerCreateReservations.labels.add(marker.label);
2047
+ const patch = options.documentAdapter.createMarkerGraphPatch?.(document, {
2048
+ type: 'marker.create-box',
2049
+ marker,
2050
+ parentId,
2051
+ });
2052
+ if (!patch) {
2053
+ state.markerCreateReservations.ids.delete(marker.id);
2054
+ state.markerCreateReservations.labels.delete(marker.label);
2055
+ state.status = `Create marker rejected: ${marker.label}`;
2056
+ state.statusTone = 'warning';
2057
+ state.statusToneStatus = state.status;
2058
+ return true;
2059
+ }
2060
+ const result = state.session.dispatch({
2061
+ type: 'document.patch',
2062
+ label: patch.label ?? `Create marker ${marker.label}`,
2063
+ patch: patch.patch,
2064
+ targetId: patch.createdId ?? patch.changedId ?? marker.id,
2065
+ ...withProjectionImpact(resolveProjectionImpact(patch, createKnownCreatedReprojectProjectionImpact({
2066
+ createdNodeIds: [patch.createdId ?? patch.changedId ?? marker.id],
2067
+ reprojectIds: patch.changedIds,
2068
+ }))),
2069
+ });
2070
+ if (!result.documentChanged) {
2071
+ state.markerCreateReservations.ids.delete(marker.id);
2072
+ state.markerCreateReservations.labels.delete(marker.label);
2073
+ state.status = `Create marker unchanged: ${marker.label}`;
2074
+ state.statusTone = 'warning';
2075
+ state.statusToneStatus = state.status;
2076
+ return true;
2077
+ }
2078
+ const createdId = patch.createdId ?? patch.changedId ?? marker.id;
2079
+ releaseReflectedMarkerCreateReservation(state, options, result.workingDocument, marker, createdId);
2080
+ let selection = result.selection;
2081
+ if (createdId && isNodeSelectableInDocument(options, result.workingDocument, createdId)) {
2082
+ selection = state.session.dispatch({
2083
+ type: 'selection.replace',
2084
+ selectedIds: [createdId],
2085
+ activeId: createdId,
2086
+ label: 'Select Created Marker',
2087
+ }).selection;
2088
+ }
2089
+ rebuildProjectionFromDocument(state, options, result.workingDocument, selection);
2090
+ state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2091
+ state.status = patch.label ?? `Created marker ${marker.label}`;
2092
+ state.statusTone = 'success';
2093
+ state.statusToneStatus = state.status;
2094
+ state.statusDetails = '';
2095
+ return true;
2096
+ }
2097
+ function releaseReflectedMarkerCreateReservation(state, options, document, marker, createdId) {
2098
+ const reflectedIds = new Set([
2099
+ marker.id,
2100
+ ...(createdId ? [createdId] : []),
2101
+ ]);
2102
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2103
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
2104
+ const markerReflected = hierarchy.some(item => reflectedIds.has(item.id))
2105
+ || (graph?.markers ?? []).some(candidate => reflectedIds.has(candidate.id));
2106
+ if (!markerReflected)
2107
+ return;
2108
+ state.markerCreateReservations.ids.delete(marker.id);
2109
+ state.markerCreateReservations.labels.delete(marker.label);
2110
+ }
2111
+ function createSceneGraphMarkerNode(state, options, document, intent, markerTypeDefinition) {
2112
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2113
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
2114
+ const existingIds = new Set([
2115
+ ...hierarchy.map(item => item.id),
2116
+ ...(graph?.markers ?? []).map(marker => marker.id),
2117
+ ...state.markerCreateReservations.ids,
2118
+ ]);
2119
+ const markerType = markerTypeDefinition?.type ?? intent.markerType?.trim() ?? '';
2120
+ const baseLabel = intent.name?.trim() || markerTypeDefinition?.label || 'Marker';
2121
+ const id = createUniqueMarkerId(existingIds, markerType || baseLabel);
2122
+ const label = createUniqueMarkerLabel(new Set([
2123
+ ...hierarchy.map(item => item.label),
2124
+ ...state.markerCreateReservations.labels,
2125
+ ]), baseLabel, id, markerType);
2126
+ const geometry = createInitialMarkerGeometry(state, markerTypeDefinition?.kind);
2127
+ return {
2128
+ id,
2129
+ kind: normalizeMarkerNodeKind(markerTypeDefinition?.kind),
2130
+ markerType,
2131
+ label,
2132
+ geometry,
2133
+ ...(markerTypeDefinition?.defaultTags?.length ? { tags: [...markerTypeDefinition.defaultTags] } : {}),
2134
+ };
2135
+ }
2136
+ function createInitialMarkerGeometry(state, markerKind) {
2137
+ const defaultScale = createDefaultMarkerScale(markerKind);
2138
+ const selection = state.session?.getState().selection ?? { selectedIds: [], activeId: null };
2139
+ const bounds = selection.selectedIds.length > 0
2140
+ ? state.projection?.getSelectionBounds(selection.selectedIds) ?? null
2141
+ : null;
2142
+ const center = bounds?.center ?? { x: 0, y: defaultScale.y / 2, z: 0 };
2143
+ if (!isRegionMarkerKind(markerKind)) {
2144
+ return {
2145
+ kind: 'point',
2146
+ coordinateSpace: 'world',
2147
+ position: { ...center },
2148
+ };
2149
+ }
2150
+ const size = bounds && isRegionMarkerKind(markerKind)
2151
+ ? normalizeMarkerBoxSize(bounds.size)
2152
+ : defaultScale;
2153
+ return {
2154
+ kind: 'box',
2155
+ coordinateSpace: 'world',
2156
+ center: { ...center },
2157
+ size: { ...size },
2158
+ };
2159
+ }
2160
+ function createDefaultMarkerScale(markerKind) {
2161
+ return isRegionMarkerKind(markerKind)
2162
+ ? { x: 4, y: 1, z: 4 }
2163
+ : { x: 0.5, y: 0.5, z: 0.5 };
2164
+ }
2165
+ function normalizeMarkerNodeKind(kind) {
2166
+ return kind?.trim() || 'point';
2167
+ }
2168
+ function isRegionMarkerKind(kind) {
2169
+ return (kind?.trim() || 'point') === 'region';
2170
+ }
2171
+ function createUniqueMarkerId(existingIds, markerType) {
2172
+ const base = sanitizeMarkerIdPart(markerType) || 'marker';
2173
+ if (!existingIds.has(base))
2174
+ return base;
2175
+ let index = 2;
2176
+ while (existingIds.has(`${base}-${index}`))
2177
+ index += 1;
2178
+ return `${base}-${index}`;
2179
+ }
2180
+ function createUniqueMarkerLabel(existingLabels, baseLabel, id, markerType) {
2181
+ const trimmed = baseLabel.trim() || markerType || id;
2182
+ if (!existingLabels.has(trimmed))
2183
+ return trimmed;
2184
+ let index = 2;
2185
+ while (existingLabels.has(`${trimmed} ${index}`))
2186
+ index += 1;
2187
+ return `${trimmed} ${index}`;
2188
+ }
2189
+ function sanitizeMarkerIdPart(value) {
2190
+ return value
2191
+ .trim()
2192
+ .toLowerCase()
2193
+ .replace(/[^a-z0-9_-]+/g, '-')
2194
+ .replace(/^-+|-+$/g, '');
2195
+ }
2196
+ function resolveCreateMarkerParentId(hierarchy, parentId) {
2197
+ if (!parentId)
2198
+ return null;
2199
+ const parent = hierarchy.find(item => item.id === parentId) ?? null;
2200
+ return parent?.canHaveChildren === true && parent.role !== 'marker' ? parent.id : null;
2201
+ }
1618
2202
  function deleteSceneGraphNodes(state, options, intent) {
1619
2203
  const document = state.session?.getState().workingDocument;
1620
2204
  if (state.mode !== 'editor' || !state.session || !document)
@@ -1880,7 +2464,7 @@ function sanitizeSelection(state, options, document, selection) {
1880
2464
  });
1881
2465
  return result?.selection ?? { selectedIds, activeId };
1882
2466
  }
1883
- function commitEditorStateChange(state, renderUi, reason, mutate, options = {}) {
2467
+ function commitEditorStateChange(state, harnessOptions, renderUi, reason, mutate, options = {}) {
1884
2468
  const beforeRevision = state.editorStateRevision;
1885
2469
  mutate();
1886
2470
  const revision = beforeRevision + 1;
@@ -1890,7 +2474,7 @@ function commitEditorStateChange(state, renderUi, reason, mutate, options = {})
1890
2474
  const selection = typeof options.syncSelectionArtifacts === 'boolean'
1891
2475
  ? state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null }
1892
2476
  : options.syncSelectionArtifacts;
1893
- syncSelectionToProjection(state, selection, reason, revision);
2477
+ syncSelectionToProjection(state, harnessOptions, selection, reason, revision);
1894
2478
  }
1895
2479
  else if (options.syncTransformOperationArtifacts) {
1896
2480
  syncTransformOperationSettingsToSceneArtifacts(state, reason, revision);
@@ -1900,44 +2484,317 @@ function commitEditorStateChange(state, renderUi, reason, mutate, options = {})
1900
2484
  }
1901
2485
  return revision;
1902
2486
  }
1903
- function syncSelectionToProjection(state, selection, reason = 'projection-selection', revision = state.editorStateRevision) {
2487
+ function syncSelectionToProjection(state, options, selection, reason = 'projection-selection', revision = state.editorStateRevision) {
1904
2488
  if (!beginSceneArtifactSync(state, reason, revision))
1905
2489
  return;
1906
- state.projection?.syncSelection(selection);
1907
- state.gizmo?.setSelection(selection);
2490
+ const projectedSelection = state.prefabStage ? EMPTY_EDITOR_SELECTION : selection;
2491
+ state.projection?.syncSelection(projectedSelection);
2492
+ syncMarkerOverlayToSceneArtifacts(state, options, projectedSelection);
2493
+ state.gizmo?.setSelection(projectedSelection);
1908
2494
  syncTransformGizmoToSceneArtifacts(state, reason, revision, false);
1909
2495
  }
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);
2496
+ function syncCurrentSelectionToSceneArtifacts(state, options, reason = 'projection-selection-current', revision = state.editorStateRevision) {
2497
+ syncSelectionToProjection(state, options, state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null }, reason, revision);
1912
2498
  }
1913
- function syncTransformGizmoToSceneArtifacts(state, reason, revision = state.editorStateRevision, beginSync = true) {
1914
- if (beginSync && !beginSceneArtifactSync(state, reason, revision))
2499
+ function syncMarkerOverlayToSceneArtifacts(state, options, selection) {
2500
+ const overlay = state.markerOverlay;
2501
+ if (!overlay)
1915
2502
  return;
1916
- const gizmo = state.gizmo;
1917
- if (gizmo) {
1918
- gizmo.setTool(state.transformTool);
1919
- gizmo.setSpace(state.transformSpace);
1920
- gizmo.setConstraint(state.transformConstraint);
1921
- gizmo.refreshSelection();
1922
- }
1923
- requestEditorArtifactRevealFrame(state, reason);
1924
- }
1925
- function syncTransformOperationSettingsToSceneArtifacts(state, reason, revision = state.editorStateRevision) {
1926
- if (!beginSceneArtifactSync(state, reason, revision))
2503
+ const document = state.session?.getState().workingDocument ?? null;
2504
+ const input = document
2505
+ ? createMarkerOverlayInput(state, options, document, selection, createMarkerGraphHierarchyVisibility(state))
2506
+ : { markers: [] };
2507
+ const changed = overlay.sync(input);
2508
+ syncMarkerRelationHighlightRenderLoop(state, input);
2509
+ if (changed)
2510
+ requestEditorSceneFrame(state, 'marker-overlay-sync');
2511
+ }
2512
+ function syncMarkerRelationHighlightRenderLoop(state, input) {
2513
+ const shouldRun = (input.relations ?? []).some(relation => relation.active === true);
2514
+ if (shouldRun === state.markerRelationHighlightContinuous)
1927
2515
  return;
1928
- state.gizmo?.setOperationSettings(state.transformOperationSettings);
1929
- requestEditorSceneFrame(state, reason);
1930
- }
1931
- function beginSceneArtifactSync(state, reason, revision) {
1932
- if (revision < state.sceneArtifactRevision) {
1933
- return false;
2516
+ state.markerRelationHighlightContinuous = shouldRun;
2517
+ if (shouldRun) {
2518
+ if (canUseContinuousViewportRenderLoop()) {
2519
+ state.viewportRenderCoordinator?.beginContinuous(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
2520
+ }
2521
+ else {
2522
+ state.viewportRenderCoordinator?.requestFrame(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
2523
+ }
1934
2524
  }
1935
- state.sceneArtifactRevision = revision;
1936
- return true;
1937
- }
1938
- function invalidateEditorScene(state, reason, options) {
1939
- if (options && state.sceneCameraPreviewEnabled) {
1940
- syncSceneCameraPreview(state, options);
2525
+ else {
2526
+ state.viewportRenderCoordinator?.endContinuous(MARKER_RELATION_HIGHLIGHT_RENDER_REASON);
2527
+ }
2528
+ }
2529
+ function canUseContinuousViewportRenderLoop() {
2530
+ if (typeof window === 'undefined' || typeof window.requestAnimationFrame !== 'function')
2531
+ return true;
2532
+ if (typeof window.cancelAnimationFrame !== 'function')
2533
+ return true;
2534
+ let completedSynchronously = false;
2535
+ const frameId = window.requestAnimationFrame(() => {
2536
+ completedSynchronously = true;
2537
+ });
2538
+ window.cancelAnimationFrame(frameId);
2539
+ return !completedSynchronously;
2540
+ }
2541
+ function createMarkerOverlayInput(state, options, document, selection, visibility) {
2542
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
2543
+ if (!graph)
2544
+ return { markers: [] };
2545
+ if (state.viewportTools.markersVisible === false)
2546
+ return { markers: [], relations: [] };
2547
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
2548
+ const fullHierarchy = createMarkerGraphHierarchyItems(hierarchy, graph, visibility);
2549
+ const selectedIds = new Set(selection.selectedIds);
2550
+ const markerTypeLabels = new Map((options.documentAdapter.getMarkerTypeCatalog?.(document) ?? []).map(definition => [definition.type, definition.label]));
2551
+ const activeMarkerId = selection.activeId
2552
+ ? readSpatialMarkerIdFromHierarchySelection(graph, selection.activeId, hierarchy)
2553
+ : null;
2554
+ return {
2555
+ activeMarkerRowId: selection.activeId,
2556
+ selectedMarkerRowIds: selection.selectedIds,
2557
+ markers: fullHierarchy
2558
+ .filter(item => item.role === 'marker')
2559
+ .map((item) => {
2560
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, item.id, hierarchy);
2561
+ const marker = markerId ? findSpatialMarkerById(graph, markerId) : null;
2562
+ if (!marker || !isMarkerOverlayRowVisible(graph, item, marker.id, hierarchy, visibility))
2563
+ return null;
2564
+ const overlayMarker = resolveMarkerForOverlay(state, applyMarkerOverlayTransformPreview(state, item.id, marker));
2565
+ return {
2566
+ markerRowId: item.id,
2567
+ marker: overlayMarker,
2568
+ markerTypeLabel: markerTypeLabels.get(marker.markerType),
2569
+ selected: selectedIds.has(item.id),
2570
+ active: selection.activeId === item.id,
2571
+ };
2572
+ })
2573
+ .filter((entry) => !!entry),
2574
+ relations: createSelectedMarkerRelationOverlayEntries(state, resolveMarkerGraphForOverlay(state, graph), activeMarkerId, hierarchy, visibility),
2575
+ };
2576
+ }
2577
+ function resolveMarkerGraphForOverlay(state, graph) {
2578
+ return {
2579
+ ...graph,
2580
+ markers: graph.markers.map(marker => resolveMarkerForOverlay(state, marker)),
2581
+ };
2582
+ }
2583
+ function resolveMarkerForOverlay(state, marker) {
2584
+ if (!isObjectBoundMarkerForOverlay(marker))
2585
+ return marker;
2586
+ const target = readSceneObjectMarkerTargetForOverlay(marker);
2587
+ if (!target)
2588
+ return marker;
2589
+ const bounds = state.projection?.getSelectionBounds([target.id]) ?? null;
2590
+ if (!bounds)
2591
+ return marker;
2592
+ return {
2593
+ ...marker,
2594
+ spatial: {
2595
+ status: 'resolved',
2596
+ coordinateSpace: 'world',
2597
+ origin: { ...bounds.center },
2598
+ bounds: {
2599
+ min: { ...bounds.min },
2600
+ max: { ...bounds.max },
2601
+ center: { ...bounds.center },
2602
+ size: { ...bounds.size },
2603
+ },
2604
+ dimensions: { ...bounds.size },
2605
+ localFrame: {
2606
+ origin: { ...bounds.center },
2607
+ forward: marker.semanticFrame?.forward ? { ...marker.semanticFrame.forward } : { x: 0, y: 0, z: 1 },
2608
+ right: marker.semanticFrame?.right ? { ...marker.semanticFrame.right } : { x: 1, y: 0, z: 0 },
2609
+ up: marker.semanticFrame?.up ? { ...marker.semanticFrame.up } : { x: 0, y: 1, z: 0 },
2610
+ },
2611
+ target,
2612
+ },
2613
+ };
2614
+ }
2615
+ function isObjectBoundMarkerForOverlay(marker) {
2616
+ const markerKind = marker.kind?.trim().toLowerCase();
2617
+ return markerKind === 'object'
2618
+ || markerKind === 'object-bound'
2619
+ || markerKind === 'object-bounds'
2620
+ || marker.geometry.kind === 'object-bounds';
2621
+ }
2622
+ function readSceneObjectMarkerTargetForOverlay(marker) {
2623
+ if (marker.target?.kind === 'scene-object')
2624
+ return marker.target;
2625
+ if (marker.geometry.kind === 'object-bounds' && marker.geometry.target.kind === 'scene-object') {
2626
+ return marker.geometry.target;
2627
+ }
2628
+ if (marker.geometry.kind === 'point' && marker.geometry.target?.kind === 'scene-object') {
2629
+ return marker.geometry.target;
2630
+ }
2631
+ return null;
2632
+ }
2633
+ function createSelectedMarkerRelationOverlayEntries(state, graph, activeMarkerId, hierarchy, visibility) {
2634
+ if (!activeMarkerId)
2635
+ return [];
2636
+ if (!findSpatialMarkerById(graph, activeMarkerId))
2637
+ return [];
2638
+ const entries = [];
2639
+ for (const relation of getSpatialMarkerDirectRelations(graph, activeMarkerId)) {
2640
+ const from = resolveRelationOverlayEndpoint(state, graph, activeMarkerId, relation.from, hierarchy, visibility);
2641
+ const to = resolveRelationOverlayEndpoint(state, graph, activeMarkerId, relation.to, hierarchy, visibility);
2642
+ if (!from || !to)
2643
+ continue;
2644
+ entries.push({
2645
+ id: relation.id,
2646
+ type: relation.type,
2647
+ ...(relation.label ? { label: relation.label } : {}),
2648
+ from,
2649
+ to,
2650
+ directed: relation.directed ?? true,
2651
+ active: state.activeMarkerRelationId === relation.id,
2652
+ });
2653
+ }
2654
+ return entries;
2655
+ }
2656
+ function applyMarkerOverlayTransformPreview(state, markerRowId, marker) {
2657
+ const transform = readActiveTransformPreviewTransform(state, markerRowId)
2658
+ ?? (markerRowId === marker.id ? null : readActiveTransformPreviewTransform(state, marker.id));
2659
+ if (!transform)
2660
+ return marker;
2661
+ const semanticFrame = createMarkerSemanticFrameFromTransform(marker, transform);
2662
+ if (marker.geometry.kind === 'point') {
2663
+ return {
2664
+ ...marker,
2665
+ geometry: {
2666
+ ...marker.geometry,
2667
+ coordinateSpace: 'world',
2668
+ position: { ...transform.position },
2669
+ },
2670
+ semanticFrame,
2671
+ };
2672
+ }
2673
+ if (marker.geometry.kind !== 'box') {
2674
+ return {
2675
+ ...marker,
2676
+ semanticFrame,
2677
+ };
2678
+ }
2679
+ return {
2680
+ ...marker,
2681
+ semanticFrame,
2682
+ geometry: {
2683
+ ...marker.geometry,
2684
+ center: { ...transform.position },
2685
+ rotation: { ...transform.rotation },
2686
+ size: { ...transform.scale },
2687
+ },
2688
+ };
2689
+ }
2690
+ function resolveRelationOverlayEndpoint(state, graph, activeMarkerId, endpoint, hierarchy, visibility) {
2691
+ if (endpoint.kind === 'marker') {
2692
+ if (!isMarkerVisibleForOverlayRelation(graph, endpoint.id, hierarchy, visibility))
2693
+ return null;
2694
+ const preview = endpoint.id === activeMarkerId ? readActiveTransformPreviewPosition(state) : null;
2695
+ if (preview)
2696
+ return preview;
2697
+ const marker = findSpatialMarkerById(graph, endpoint.id);
2698
+ return marker ? resolveMarkerOverlayEndpoint(marker) : null;
2699
+ }
2700
+ if (endpoint.kind === 'scene-object') {
2701
+ const preview = endpoint.id === state.activeTransformPreview?.activeId ? readActiveTransformPreviewPosition(state) : null;
2702
+ if (preview)
2703
+ return preview;
2704
+ const bounds = state.projection?.getSelectionBounds([endpoint.id]) ?? null;
2705
+ return bounds ? { ...bounds.center } : null;
2706
+ }
2707
+ return null;
2708
+ }
2709
+ function isMarkerVisibleForOverlayRelation(graph, markerId, hierarchy, visibility) {
2710
+ if (isSpatialMarkerBackedByHierarchyRow(graph, markerId, hierarchy)) {
2711
+ const markerRow = hierarchy.find(item => item.id === markerId);
2712
+ return markerRow?.enabled !== false && !isMarkerHiddenInHierarchyVisibility(markerId, visibility);
2713
+ }
2714
+ const markerRow = hierarchy.find(item => readMarkerGraphHierarchyMarkerIdForGraph(graph, item.id, hierarchy) === markerId);
2715
+ if (markerRow?.enabled === false)
2716
+ return false;
2717
+ return isSpatialMarkerHierarchyVisible(markerId, visibility);
2718
+ }
2719
+ function isMarkerHiddenInHierarchyVisibility(markerId, visibility) {
2720
+ const hiddenMarkerIds = visibility.hiddenMarkerIds;
2721
+ if (!hiddenMarkerIds)
2722
+ return false;
2723
+ return Array.isArray(hiddenMarkerIds)
2724
+ ? hiddenMarkerIds.includes(markerId)
2725
+ : hiddenMarkerIds.has(markerId);
2726
+ }
2727
+ function readActiveTransformPreviewPosition(state) {
2728
+ const transform = readActiveTransformPreviewTransform(state);
2729
+ return transform ? { ...transform.position } : null;
2730
+ }
2731
+ function readActiveTransformPreviewTransform(state, targetId) {
2732
+ const preview = state.activeTransformPreview;
2733
+ const activeId = targetId ?? preview?.activeId ?? null;
2734
+ if (!preview || !activeId)
2735
+ return null;
2736
+ const transform = preview.transforms[activeId] ?? null;
2737
+ return transform && isEditorTransformTrsSnapshot(transform) ? transform : null;
2738
+ }
2739
+ function resolveMarkerOverlayEndpoint(marker) {
2740
+ if (isObjectBoundMarkerForOverlay(marker) && marker.spatial?.bounds?.center) {
2741
+ return { ...marker.spatial.bounds.center };
2742
+ }
2743
+ if (marker.geometry.kind === 'box')
2744
+ return { ...marker.geometry.center };
2745
+ if (marker.geometry.kind === 'point')
2746
+ return { ...marker.geometry.position };
2747
+ return null;
2748
+ }
2749
+ function isMarkerOverlayRowVisible(graph, item, markerId, hierarchy, visibility) {
2750
+ if (item.enabled === false)
2751
+ return false;
2752
+ if (readMarkerGraphHierarchyMarkerIdForGraph(graph, item.id, hierarchy)) {
2753
+ return isSpatialMarkerHierarchyVisible(markerId, visibility);
2754
+ }
2755
+ return true;
2756
+ }
2757
+ function createMarkerGraphHierarchyVisibility(state) {
2758
+ return {
2759
+ rootVisible: state.markerGraphVisibility.rootVisible,
2760
+ hiddenMarkerIds: state.markerGraphVisibility.hiddenMarkerIds,
2761
+ };
2762
+ }
2763
+ function createMarkerRelationVisibilityOptions(state) {
2764
+ return {
2765
+ markersVisible: state.viewportTools.markersVisible !== false,
2766
+ rootVisible: state.markerGraphVisibility.rootVisible,
2767
+ hiddenMarkerIds: state.markerGraphVisibility.hiddenMarkerIds,
2768
+ };
2769
+ }
2770
+ function syncTransformGizmoToSceneArtifacts(state, reason, revision = state.editorStateRevision, beginSync = true) {
2771
+ if (beginSync && !beginSceneArtifactSync(state, reason, revision))
2772
+ return;
2773
+ const gizmo = state.gizmo;
2774
+ if (gizmo) {
2775
+ gizmo.setTool(state.transformTool);
2776
+ gizmo.setSpace(state.transformSpace);
2777
+ gizmo.setConstraint(state.transformConstraint);
2778
+ gizmo.refreshSelection();
2779
+ }
2780
+ requestEditorArtifactRevealFrame(state, reason);
2781
+ }
2782
+ function syncTransformOperationSettingsToSceneArtifacts(state, reason, revision = state.editorStateRevision) {
2783
+ if (!beginSceneArtifactSync(state, reason, revision))
2784
+ return;
2785
+ state.gizmo?.setOperationSettings(state.transformOperationSettings);
2786
+ requestEditorSceneFrame(state, reason);
2787
+ }
2788
+ function beginSceneArtifactSync(state, reason, revision) {
2789
+ if (revision < state.sceneArtifactRevision) {
2790
+ return false;
2791
+ }
2792
+ state.sceneArtifactRevision = revision;
2793
+ return true;
2794
+ }
2795
+ function invalidateEditorScene(state, reason, options) {
2796
+ if (options && state.sceneCameraPreviewEnabled) {
2797
+ syncSceneCameraPreview(state, options);
1941
2798
  }
1942
2799
  state.viewportRenderCoordinator?.invalidateScene(reason);
1943
2800
  }
@@ -1949,7 +2806,7 @@ function requestEditorArtifactRevealFrame(state, reason) {
1949
2806
  coordinator.requestRevealFrame(`${reason}-artifact-reveal`);
1950
2807
  }
1951
2808
  function syncEditorWorldAppearanceFromDocument(state, options, document, reason) {
1952
- const appearance = resolveLocalEditorWorldAppearance(options, document);
2809
+ const appearance = resolveEffectiveLocalEditorWorldAppearance(state, options, document);
1953
2810
  const key = serializeLocalEditorWorldAppearance(appearance);
1954
2811
  if (key === state.worldAppearanceKey)
1955
2812
  return;
@@ -1962,16 +2819,18 @@ function syncEditorWorldAppearanceFromDocument(state, options, document, reason)
1962
2819
  requestEditorSceneFrame(state, reason);
1963
2820
  }
1964
2821
  function syncEditorWorldRenderingFromDocument(state, options, document, reason, refreshProjection = false) {
1965
- const rendering = resolveLocalEditorWorldRendering(options, document);
2822
+ const rendering = resolveEffectiveLocalEditorWorldRendering(state, options, document);
1966
2823
  const key = serializeLocalEditorWorldRendering(rendering);
1967
2824
  if (key === state.worldRenderingKey) {
1968
2825
  if (refreshProjection)
1969
2826
  state.shadowPreview?.refresh();
2827
+ state.world?.refreshSkyBackdrop();
1970
2828
  return;
1971
2829
  }
1972
2830
  state.worldRenderingKey = key;
1973
2831
  state.shadowPreview?.setOptions(rendering.shadowPreview);
1974
2832
  state.environmentTexture?.setProfile(rendering.environment);
2833
+ state.world?.refreshSkyBackdrop();
1975
2834
  state.postProcess?.setProfile(rendering.postProcess);
1976
2835
  requestEditorSceneFrame(state, reason);
1977
2836
  }
@@ -1985,6 +2844,14 @@ function resolveLocalEditorWorldAppearance(options, document) {
1985
2844
  grid: documentAppearance?.grid ?? options.world?.grid,
1986
2845
  };
1987
2846
  }
2847
+ function resolveEffectiveLocalEditorWorldAppearance(state, options, document) {
2848
+ if (!state.prefabStage)
2849
+ return resolveLocalEditorWorldAppearance(options, document);
2850
+ return {
2851
+ ...resolveLocalEditorWorldAppearance(options, document),
2852
+ ...PREFAB_STAGE_STUDIO_WORLD_APPEARANCE,
2853
+ };
2854
+ }
1988
2855
  function resolveLocalEditorWorldRendering(options, document) {
1989
2856
  const documentRendering = document
1990
2857
  ? options.documentAdapter.getWorldRendering?.(document) ?? null
@@ -1995,6 +2862,15 @@ function resolveLocalEditorWorldRendering(options, document) {
1995
2862
  postProcess: documentRendering?.postProcess ?? null,
1996
2863
  };
1997
2864
  }
2865
+ function resolveEffectiveLocalEditorWorldRendering(state, options, document) {
2866
+ const rendering = resolveLocalEditorWorldRendering(options, document);
2867
+ if (!state.prefabStage)
2868
+ return rendering;
2869
+ return {
2870
+ ...rendering,
2871
+ environment: PREFAB_STAGE_STUDIO_ENVIRONMENT,
2872
+ };
2873
+ }
1998
2874
  function hasEditorWorldAppearanceSky(appearance) {
1999
2875
  return !!appearance && Object.prototype.hasOwnProperty.call(appearance, 'sky');
2000
2876
  }
@@ -2119,6 +2995,9 @@ function selectBrowserAsset(state, assetId) {
2119
2995
  function handleBrowserAssetAction(state, options, input) {
2120
2996
  if (input.browserAssetId)
2121
2997
  state.selectedAssetId = input.browserAssetId;
2998
+ if (input.actionId === 'asset.edit-prefab') {
2999
+ return enterPrefabStage(state, options, input);
3000
+ }
2122
3001
  if (input.actionId === 'asset.add-to-scene') {
2123
3002
  return input.assetId ? addAssetToDocument(state, options, input.assetId).ok : false;
2124
3003
  }
@@ -2141,18 +3020,182 @@ function handleBrowserAssetAction(state, options, input) {
2141
3020
  state.statusToneStatus = state.status;
2142
3021
  return true;
2143
3022
  }
2144
- function patchBrowserAssetAction(state, options, input) {
3023
+ function handleInspectorAction(state, options, input) {
3024
+ if (!input.actionId.startsWith('prefab.'))
3025
+ return false;
3026
+ const assetId = readInspectorActionParamString(input, 'assetId');
3027
+ const browserAssetId = readInspectorActionParamString(input, 'browserAssetId')
3028
+ ?? (assetId ? resolveBrowserAssetItemId(state, options, assetId) : null)
3029
+ ?? undefined;
3030
+ if (input.actionId === 'prefab.edit-definition') {
3031
+ if (!assetId) {
3032
+ state.status = 'Prefab action unavailable: missing prefab asset id';
3033
+ state.statusTone = 'warning';
3034
+ state.statusToneStatus = state.status;
3035
+ return true;
3036
+ }
3037
+ return enterPrefabStage(state, options, {
3038
+ actionId: 'asset.edit-prefab',
3039
+ assetId,
3040
+ browserAssetId,
3041
+ assetKind: 'prefab',
3042
+ });
3043
+ }
3044
+ if (input.actionId === 'prefab.select-definition' || input.actionId === 'prefab.ping-source') {
3045
+ if (!assetId) {
3046
+ state.status = `Prefab action unavailable: ${input.actionId}`;
3047
+ state.statusTone = 'warning';
3048
+ state.statusToneStatus = state.status;
3049
+ return true;
3050
+ }
3051
+ const selected = browserAssetId ? selectBrowserAsset(state, browserAssetId) : false;
3052
+ if (!selected && browserAssetId) {
3053
+ state.status = `Selected asset ${browserAssetId}`;
3054
+ state.statusTone = 'success';
3055
+ state.statusToneStatus = state.status;
3056
+ return true;
3057
+ }
3058
+ if (!browserAssetId) {
3059
+ state.status = `Asset not visible in browser: ${assetId}`;
3060
+ state.statusTone = 'warning';
3061
+ state.statusToneStatus = state.status;
3062
+ return true;
3063
+ }
3064
+ return selected;
3065
+ }
3066
+ state.status = `Prefab inspector action unavailable: ${input.actionId}`;
3067
+ state.statusTone = 'warning';
3068
+ state.statusToneStatus = state.status;
3069
+ return true;
3070
+ }
3071
+ function readInspectorActionParamString(input, key) {
3072
+ const value = input.params?.[key];
3073
+ return typeof value === 'string' && value.trim().length > 0 ? value.trim() : undefined;
3074
+ }
3075
+ function enterPrefabStage(state, options, input) {
3076
+ if (state.mode !== 'editor')
3077
+ return false;
3078
+ const sessionState = state.session?.getState({ cloneDocuments: false });
3079
+ const document = sessionState?.workingDocument ?? null;
3080
+ const assetId = input.assetId?.trim();
3081
+ if (!document || !assetId)
3082
+ return false;
3083
+ cancelActiveOperation(state);
3084
+ const previousPrefabStage = state.prefabStage;
3085
+ const asset = findAssetByResolvedId(state, options, assetId);
3086
+ const browserItem = findBrowserAssetItemByResolvedId(state, options, document, input.browserAssetId ?? assetId);
3087
+ const descriptor = options.documentAdapter.getPrefabStageDescriptor?.(document, {
3088
+ assetId,
3089
+ browserAssetId: input.browserAssetId,
3090
+ asset,
3091
+ }) ?? createFallbackPrefabStageDescriptor(assetId, input.browserAssetId, browserItem);
3092
+ if (!descriptor) {
3093
+ state.status = `Prefab stage unavailable: ${assetId}`;
3094
+ state.statusTone = 'warning';
3095
+ state.statusToneStatus = state.status;
3096
+ return true;
3097
+ }
3098
+ if (previousPrefabStage)
3099
+ state.prefabComparisonPreview?.clear();
3100
+ state.prefabStage = {
3101
+ descriptor,
3102
+ asset,
3103
+ selectedItemId: 'prefab-root',
3104
+ restoreGridVisible: previousPrefabStage?.restoreGridVisible ?? state.gridVisible,
3105
+ restoreSceneCameraPreviewEnabled: previousPrefabStage?.restoreSceneCameraPreviewEnabled ?? state.sceneCameraPreviewEnabled,
3106
+ };
3107
+ setGridVisibleForEditorContext(state, false, 'prefab-stage-enter-grid');
3108
+ state.sceneCameraPreviewEnabled = false;
3109
+ state.sceneCameraPreview?.setActive(false);
3110
+ state.status = `Editing Prefab ${descriptor.label}`;
3111
+ state.statusTone = 'success';
3112
+ state.statusToneStatus = state.status;
3113
+ state.statusDetails = '';
3114
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
3115
+ return true;
3116
+ }
3117
+ function exitPrefabStage(state, options) {
3118
+ if (!state.prefabStage)
3119
+ return false;
3120
+ const sessionState = state.session?.getState({ cloneDocuments: false });
3121
+ const label = sessionState?.workingDocument
3122
+ ? resolvePrefabStageDescriptorForDocument(state, options, sessionState.workingDocument)?.label ?? state.prefabStage.descriptor.label
3123
+ : state.prefabStage.descriptor.label;
3124
+ const restoreGridVisible = state.prefabStage.restoreGridVisible;
3125
+ const restoreSceneCameraPreviewEnabled = state.prefabStage.restoreSceneCameraPreviewEnabled;
3126
+ state.prefabStage = null;
3127
+ setGridVisibleForEditorContext(state, restoreGridVisible, 'prefab-stage-exit-grid');
3128
+ if (sessionState?.workingDocument) {
3129
+ rebuildProjectionFromDocument(state, options, sessionState.workingDocument, sessionState.selection);
3130
+ }
3131
+ state.sceneCameraPreviewEnabled = restoreSceneCameraPreviewEnabled;
3132
+ if (restoreSceneCameraPreviewEnabled)
3133
+ syncSceneCameraPreview(state, options);
3134
+ else
3135
+ state.sceneCameraPreview?.setActive(false);
3136
+ state.status = `Exited Prefab ${label}`;
3137
+ state.statusTone = 'default';
3138
+ state.statusToneStatus = state.status;
3139
+ state.statusDetails = '';
3140
+ return true;
3141
+ }
3142
+ function selectPrefabStageItem(state, itemId) {
3143
+ if (!state.prefabStage || state.prefabStage.selectedItemId === itemId)
3144
+ return false;
3145
+ state.prefabStage = {
3146
+ ...state.prefabStage,
3147
+ selectedItemId: itemId,
3148
+ };
3149
+ return true;
3150
+ }
3151
+ function createFallbackPrefabStageDescriptor(assetId, browserAssetId, item) {
3152
+ if (!item?.prefab)
3153
+ return null;
3154
+ return {
3155
+ assetId: item.prefab.id || item.assetId || assetId,
3156
+ browserAssetId: browserAssetId ?? item.id,
3157
+ label: item.prefab.name || item.label || item.prefab.id || assetId,
3158
+ sourceAssetId: item.prefab.sourceAssetId,
3159
+ sourceAssetGuid: item.prefab.sourceAssetGuid,
3160
+ readonly: item.prefab.readonly === true,
3161
+ previewNodeId: `prefab-stage:${item.prefab.id || item.assetId || assetId}`,
3162
+ };
3163
+ }
3164
+ function resolvePrefabStageDescriptorForDocument(state, options, document) {
3165
+ const stage = state.prefabStage;
3166
+ if (!stage)
3167
+ return null;
3168
+ const assetId = stage.descriptor.assetId;
3169
+ const browserAssetId = stage.descriptor.browserAssetId;
3170
+ const descriptor = options.documentAdapter.getPrefabStageDescriptor?.(document, {
3171
+ assetId,
3172
+ browserAssetId,
3173
+ asset: stage.asset,
3174
+ });
3175
+ if (descriptor) {
3176
+ return {
3177
+ ...stage.descriptor,
3178
+ ...descriptor,
3179
+ browserAssetId: descriptor.browserAssetId ?? browserAssetId,
3180
+ previewNodeId: descriptor.previewNodeId ?? stage.descriptor.previewNodeId,
3181
+ };
3182
+ }
3183
+ const browserItem = findBrowserAssetItemByResolvedId(state, options, document, browserAssetId ?? assetId);
3184
+ return createFallbackPrefabStageDescriptor(assetId, browserAssetId, browserItem) ?? stage.descriptor;
3185
+ }
3186
+ function patchBrowserAssetAction(state, options, input, selectionOverride) {
2145
3187
  const session = state.session;
2146
3188
  const document = session?.getState().workingDocument ?? null;
2147
3189
  if (!session || !document || !options.documentAdapter.createAssetActionPatch)
2148
3190
  return false;
2149
3191
  const asset = input.assetId ? findAssetByResolvedId(state, options, input.assetId) : null;
3192
+ const sessionSelection = session.getState().selection;
2150
3193
  const patch = options.documentAdapter.createAssetActionPatch({
2151
3194
  ...input,
2152
3195
  document,
2153
3196
  asset,
2154
- activeId: session.getState().selection.activeId,
2155
- selectedIds: [...session.getState().selection.selectedIds],
3197
+ activeId: selectionOverride?.activeId ?? sessionSelection.activeId,
3198
+ selectedIds: selectionOverride?.selectedIds ?? [...sessionSelection.selectedIds],
2156
3199
  });
2157
3200
  if (!patch)
2158
3201
  return false;
@@ -2175,6 +3218,9 @@ function patchBrowserAssetAction(state, options, input) {
2175
3218
  reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
2176
3219
  else if (patch.changedIds)
2177
3220
  syncProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds);
3221
+ else if (state.prefabStage && result.documentChanged) {
3222
+ rebuildProjectionFromDocument(state, options, result.workingDocument, EMPTY_EDITOR_SELECTION);
3223
+ }
2178
3224
  if (input.actionId === 'asset.delete-material' && input.browserAssetId && state.selectedAssetId === input.browserAssetId) {
2179
3225
  state.selectedAssetId = null;
2180
3226
  }
@@ -2191,12 +3237,18 @@ function selectCreatedBrowserAssetFromPatch(state, options, createdId, input) {
2191
3237
  else if (input.actionId === 'asset.create-material' || input.actionId === 'asset.duplicate-material') {
2192
3238
  state.selectedAssetId = `material:${createdId}`;
2193
3239
  }
3240
+ else if (input.actionId === 'asset.create-prefab'
3241
+ || input.actionId === 'asset.create-prefab-from-game-object'
3242
+ || input.actionId === 'asset.duplicate-prefab') {
3243
+ state.selectedAssetId = resolveBrowserAssetItemId(state, options, `prefab:${createdId}`) ?? `prefab:${createdId}`;
3244
+ }
2194
3245
  }
2195
3246
  function armAssetPlacement(state, options, assetId) {
2196
3247
  if (state.mode !== 'editor')
2197
3248
  return false;
2198
3249
  cancelActiveOperation(state);
2199
- const asset = findAssetByResolvedId(state, options, assetId);
3250
+ const document = state.session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3251
+ const asset = findAssetOrDocumentBrowserItemByResolvedId(state, options, document, assetId);
2200
3252
  if (!asset)
2201
3253
  return false;
2202
3254
  state.armedPlacement = { assetId, asset };
@@ -2298,7 +3350,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
2298
3350
  state.status = 'Asset creation rejected: document is not loaded';
2299
3351
  return { ok: false, assetId, changed: false, status: state.status, error: 'document_not_loaded' };
2300
3352
  }
2301
- const asset = findAssetByResolvedId(state, options, assetId);
3353
+ const asset = findAssetOrDocumentBrowserItemByResolvedId(state, options, beforeDocument, assetId);
2302
3354
  if (!asset) {
2303
3355
  state.status = `Asset creation rejected: ${assetId} not found`;
2304
3356
  return { ok: false, assetId, changed: false, status: state.status, error: 'asset_not_found' };
@@ -2340,7 +3392,7 @@ function addAssetToDocument(state, options, assetId, createOptions = {}) {
2340
3392
  if (projectedNode) {
2341
3393
  state.projection?.projectNode(projectedNode);
2342
3394
  if (selectionResult)
2343
- syncSelectionToProjection(state, selectionResult.selection);
3395
+ syncSelectionToProjection(state, options, selectionResult.selection);
2344
3396
  invalidateEditorScene(state, 'projection-project-node');
2345
3397
  }
2346
3398
  }
@@ -2367,6 +3419,12 @@ function patchSerializedProperty(state, options, input) {
2367
3419
  return false;
2368
3420
  }
2369
3421
  const payload = transaction.payload;
3422
+ if (state.prefabStage) {
3423
+ return patchPrefabStageInspectorProperty(state, options, payload);
3424
+ }
3425
+ const markerGraphPatched = patchMarkerGraphInspectorProperty(state, options, document, payload);
3426
+ if (markerGraphPatched !== null)
3427
+ return markerGraphPatched;
2370
3428
  const shouldReprojectInspectorEdit = shouldReprojectEditorInspectorEditPath(payload.path);
2371
3429
  if (targetIds.length > 1) {
2372
3430
  const patch = options.documentAdapter.createSerializedMultiPropertyPatch?.({
@@ -2424,6 +3482,10 @@ function patchSerializedProperty(state, options, input) {
2424
3482
  }
2425
3483
  else
2426
3484
  syncProjectionForChangedIds(state, options, workingDocument, projectionSyncIds);
3485
+ if (isMarkerInspectorEditPath(payload.path)) {
3486
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
3487
+ state.gizmo?.refreshSelection();
3488
+ }
2427
3489
  state.summary = summarizeDocument(options, workingDocument, state.session.getSource());
2428
3490
  state.status = patch.label ?? `Patch ${payload.path} on ${targetIds.length} objects`;
2429
3491
  return true;
@@ -2460,7 +3522,7 @@ function patchSerializedProperty(state, options, input) {
2460
3522
  reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds ?? [patch.changedId ?? payload.targetId]);
2461
3523
  }
2462
3524
  else if (isSerializedProjectionMetadataPath(payload.path)) {
2463
- syncProjectionMetadataForChangedIds(state, options, result.workingDocument, [patch.changedId ?? payload.targetId], {
3525
+ syncProjectionMetadataForChangedIds(state, options, result.workingDocument, patch.changedIds ?? [patch.changedId ?? payload.targetId], {
2464
3526
  invalidateStaticShadowArtifact: payload.path === 'shadowMode',
2465
3527
  });
2466
3528
  }
@@ -2470,11 +3532,313 @@ function patchSerializedProperty(state, options, input) {
2470
3532
  syncProjectionForChangedIds(state, options, result.workingDocument, patch.changedIds);
2471
3533
  else
2472
3534
  syncProjectionForDispatchResult(state, options, result, patch.changedId ?? payload.targetId);
3535
+ if (isMarkerInspectorEditPath(payload.path)) {
3536
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
3537
+ state.gizmo?.refreshSelection();
3538
+ }
2473
3539
  state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
2474
3540
  state.status = patch.label ?? `Patched ${payload.path}`;
2475
3541
  return true;
2476
3542
  }
3543
+ function patchPrefabStageInspectorProperty(state, options, payload) {
3544
+ if (payload.persistence !== 'document') {
3545
+ state.status = `Prefab field is not document-editable: ${payload.path}.`;
3546
+ return false;
3547
+ }
3548
+ const session = state.session;
3549
+ const document = session?.getState({ cloneDocuments: false }).workingDocument ?? null;
3550
+ if (!document)
3551
+ return false;
3552
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? state.prefabStage?.descriptor ?? null;
3553
+ if (!descriptor) {
3554
+ state.status = 'Prefab stage is not ready.';
3555
+ return false;
3556
+ }
3557
+ return patchBrowserAssetAction(state, options, {
3558
+ actionId: 'asset.edit-prefab-field',
3559
+ assetId: descriptor.assetId,
3560
+ browserAssetId: descriptor.browserAssetId,
3561
+ assetKind: 'prefab',
3562
+ fieldPath: payload.path,
3563
+ value: payload.value,
3564
+ });
3565
+ }
3566
+ function isMarkerInspectorEditPath(path) {
3567
+ return path === 'name' || path.startsWith('marker.');
3568
+ }
3569
+ function patchMarkerGraphInspectorProperty(state, options, document, payload) {
3570
+ if (!isMarkerInspectorEditPath(payload.path))
3571
+ return null;
3572
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
3573
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
3574
+ if (!readMarkerGraphHierarchyMarkerIdForGraph(graph, payload.targetId, hierarchy))
3575
+ return null;
3576
+ const markerTypes = options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [];
3577
+ const command = createMarkerGraphPatchFromInspectorEdit(graph, payload, hierarchy, markerTypes);
3578
+ if (!command) {
3579
+ state.status = `Marker edit rejected: ${payload.path}`;
3580
+ return false;
3581
+ }
3582
+ const patch = options.documentAdapter.createMarkerGraphPatch?.(document, command);
3583
+ if (!patch) {
3584
+ state.status = `Marker edit rejected: ${payload.targetId}`;
3585
+ return false;
3586
+ }
3587
+ const result = state.session?.dispatch({
3588
+ type: 'document.patch',
3589
+ label: patch.label ?? `Patch marker ${payload.path}`,
3590
+ patch: patch.patch,
3591
+ targetId: payload.targetId,
3592
+ });
3593
+ if (!result?.documentChanged)
3594
+ return false;
3595
+ syncProjectionForDispatchResult(state, options, result, patch.changedId ?? payload.targetId);
3596
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
3597
+ state.gizmo?.refreshSelection();
3598
+ state.summary = summarizeDocument(options, result.workingDocument, state.session?.getSource());
3599
+ state.status = patch.label ?? `Patched marker ${payload.path}`;
3600
+ return true;
3601
+ }
3602
+ function patchMarkerTypeCatalog(state, options, input) {
3603
+ if (state.mode !== 'editor')
3604
+ return false;
3605
+ cancelActiveOperation(state);
3606
+ const document = state.session?.getState().workingDocument ?? null;
3607
+ if (!document)
3608
+ return false;
3609
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
3610
+ if (!graph || !options.documentAdapter.createMarkerGraphPatch)
3611
+ return false;
3612
+ const command = createMarkerTypeCatalogCommand(input);
3613
+ if (!command) {
3614
+ state.status = 'Marker type edit rejected';
3615
+ state.statusTone = 'warning';
3616
+ state.statusToneStatus = state.status;
3617
+ return false;
3618
+ }
3619
+ const patch = options.documentAdapter.createMarkerGraphPatch(document, command);
3620
+ if (!patch) {
3621
+ state.status = `Marker type edit rejected: ${input.type}`;
3622
+ state.statusTone = 'warning';
3623
+ state.statusToneStatus = state.status;
3624
+ return false;
3625
+ }
3626
+ const session = state.session;
3627
+ if (!session)
3628
+ return false;
3629
+ const result = session.dispatch({
3630
+ type: 'document.patch',
3631
+ label: patch.label ?? 'Patch marker type catalog',
3632
+ patch: patch.patch,
3633
+ targetId: session.getState().selection.activeId ?? undefined,
3634
+ });
3635
+ if (!result.documentChanged)
3636
+ return false;
3637
+ syncMarkerOverlayToSceneArtifacts(state, options, result.selection);
3638
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
3639
+ state.status = patch.label ?? 'Patched marker type catalog';
3640
+ state.statusTone = 'success';
3641
+ state.statusToneStatus = state.status;
3642
+ state.statusDetails = '';
3643
+ return true;
3644
+ }
3645
+ function patchMarkerRelation(state, options, input) {
3646
+ if (state.mode !== 'editor')
3647
+ return false;
3648
+ cancelActiveOperation(state);
3649
+ const document = state.session?.getState().workingDocument ?? null;
3650
+ if (!document)
3651
+ return false;
3652
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
3653
+ if (!graph)
3654
+ return false;
3655
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
3656
+ const activeId = state.session?.getState().selection.activeId ?? null;
3657
+ const activeMarkerId = activeId ? readSpatialMarkerIdFromHierarchySelection(graph, activeId, hierarchy) : null;
3658
+ if (input.action === 'pick-cancel') {
3659
+ state.markerRelationPick = null;
3660
+ state.status = '已取消关系端点拾取。';
3661
+ state.statusTone = 'success';
3662
+ state.statusToneStatus = state.status;
3663
+ state.statusDetails = '';
3664
+ return true;
3665
+ }
3666
+ if (input.action === 'pick-start') {
3667
+ const relationTypes = options.documentAdapter.getRelationTypeCatalog?.(document) ?? [];
3668
+ const type = input.type.trim();
3669
+ if (!type) {
3670
+ state.status = '请选择关系类型。';
3671
+ state.statusTone = 'warning';
3672
+ state.statusToneStatus = state.status;
3673
+ state.statusDetails = state.status;
3674
+ return true;
3675
+ }
3676
+ const typeDefinition = relationTypes.find(definition => definition.type === type);
3677
+ const from = createMarkerRelationPickEndpoint(input.fromKind, input.fromId);
3678
+ const directed = input.directed ?? typeDefinition?.directed ?? true;
3679
+ const typeLabel = typeDefinition?.label ?? type;
3680
+ const status = from
3681
+ ? `关系拾取中:${formatMarkerRelationPickEndpointLabel(from, graph, hierarchy)} ${typeLabel} ... 请在视口点击终点。`
3682
+ : `关系拾取中:请选择 ${typeLabel} 的起点。`;
3683
+ state.markerRelationPick = {
3684
+ active: true,
3685
+ type,
3686
+ typeLabel,
3687
+ directed,
3688
+ from,
3689
+ status,
3690
+ statusTone: 'success',
3691
+ };
3692
+ state.status = status;
3693
+ state.statusTone = 'success';
3694
+ state.statusToneStatus = state.status;
3695
+ state.statusDetails = '关系拾取会消费下一次有效视口点击,不改变当前选择。';
3696
+ return true;
3697
+ }
3698
+ if (input.action === 'select') {
3699
+ const relationId = input.relationId.trim();
3700
+ if (!relationId)
3701
+ return false;
3702
+ const relation = graph.relations.find(candidate => candidate.id === relationId) ?? null;
3703
+ state.activeMarkerRelationId = relationId;
3704
+ state.status = relation ? `高亮关系:${relation.label ?? relation.type}` : `关系不存在:${relationId}`;
3705
+ state.statusTone = relation ? 'success' : 'warning';
3706
+ state.statusToneStatus = state.status;
3707
+ state.statusDetails = relation ? relationId : state.status;
3708
+ syncMarkerOverlayToSceneArtifacts(state, options, state.session?.getState().selection ?? { selectedIds: [], activeId: null });
3709
+ requestEditorSceneFrame(state, 'marker-relation-select');
3710
+ return true;
3711
+ }
3712
+ if (!options.documentAdapter.createMarkerGraphPatch)
3713
+ return false;
3714
+ const relationTypes = options.documentAdapter.getRelationTypeCatalog?.(document) ?? [];
3715
+ const result = input.action === 'reverse'
3716
+ ? createMarkerRelationReverseCommand(input.relationId, graph)
3717
+ : input.action === 'delete-connected'
3718
+ ? createMarkerRelationDeleteConnectedCommand(input.markerId ?? activeMarkerId ?? '', graph)
3719
+ : input.action === 'create' && input.toEndpoints && input.toEndpoints.length > 0
3720
+ ? createMarkerRelationCreateManyCommand({
3721
+ type: input.type,
3722
+ fromKind: input.fromKind,
3723
+ fromId: input.fromId,
3724
+ toEndpoints: input.toEndpoints,
3725
+ directed: input.directed,
3726
+ }, graph, relationTypes, hierarchy, activeMarkerId)
3727
+ : createMarkerRelationCommand(input, graph, relationTypes, hierarchy, activeMarkerId);
3728
+ if (!result.command) {
3729
+ state.status = result.status;
3730
+ state.statusTone = result.statusTone;
3731
+ state.statusToneStatus = state.status;
3732
+ state.statusDetails = result.status;
3733
+ return true;
3734
+ }
3735
+ const patch = options.documentAdapter.createMarkerGraphPatch(document, result.command);
3736
+ if (!patch) {
3737
+ state.status = result.status || 'Marker relation edit rejected';
3738
+ state.statusTone = 'warning';
3739
+ state.statusToneStatus = state.status;
3740
+ state.statusDetails = state.status;
3741
+ return true;
3742
+ }
3743
+ const session = state.session;
3744
+ if (!session)
3745
+ return false;
3746
+ const patchResult = session.dispatch({
3747
+ type: 'document.patch',
3748
+ label: patch.label ?? result.status,
3749
+ patch: patch.patch,
3750
+ targetId: session.getState().selection.activeId ?? undefined,
3751
+ });
3752
+ if (!patchResult.documentChanged)
3753
+ return false;
3754
+ if (doesMarkerGraphCommandDeleteRelation(result.command, state.activeMarkerRelationId)) {
3755
+ state.activeMarkerRelationId = null;
3756
+ }
3757
+ if (!isMarkerRelationStillPresent(options, patchResult.workingDocument, state.activeMarkerRelationId)) {
3758
+ state.activeMarkerRelationId = null;
3759
+ }
3760
+ state.markerRelationPick = null;
3761
+ syncMarkerOverlayToSceneArtifacts(state, options, patchResult.selection);
3762
+ state.gizmo?.refreshSelection();
3763
+ state.summary = summarizeDocument(options, patchResult.workingDocument, session.getSource());
3764
+ state.status = patch.label ?? result.status;
3765
+ state.statusTone = result.statusTone;
3766
+ state.statusToneStatus = state.status;
3767
+ state.statusDetails = '';
3768
+ return true;
3769
+ }
3770
+ function createMarkerRelationPickEndpoint(kind, id) {
3771
+ const endpointKind = kind?.trim();
3772
+ const endpointId = id?.trim();
3773
+ if (!endpointKind || !endpointId)
3774
+ return null;
3775
+ if (endpointKind !== 'marker' && endpointKind !== 'scene-object')
3776
+ return null;
3777
+ return { kind: endpointKind, id: endpointId };
3778
+ }
3779
+ function formatMarkerRelationPickEndpointLabel(endpoint, graph, hierarchy) {
3780
+ const label = endpoint.kind === 'marker'
3781
+ ? findSpatialMarkerById(graph, endpoint.id)?.label ?? endpoint.id
3782
+ : hierarchy.find(item => item.id === endpoint.id)?.label ?? endpoint.id;
3783
+ const kindLabel = endpoint.kind === 'marker' ? '标记' : '对象';
3784
+ return `${kindLabel} ${label}`;
3785
+ }
3786
+ function doesMarkerGraphCommandDeleteRelation(command, relationId) {
3787
+ if (!relationId)
3788
+ return false;
3789
+ switch (command.type) {
3790
+ case 'relation.delete':
3791
+ return command.relationId === relationId;
3792
+ case 'relation.delete-many':
3793
+ return command.relationIds.includes(relationId);
3794
+ default:
3795
+ return false;
3796
+ }
3797
+ }
3798
+ function isMarkerRelationStillPresent(options, document, relationId) {
3799
+ if (!relationId)
3800
+ return true;
3801
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
3802
+ return !!graph?.relations.some(relation => relation.id === relationId);
3803
+ }
3804
+ function createMarkerTypeCatalogCommand(input) {
3805
+ const type = input.type.trim();
3806
+ if (!type)
3807
+ return null;
3808
+ if (input.action === 'delete') {
3809
+ return {
3810
+ type: 'marker-type.delete',
3811
+ typeId: type,
3812
+ };
3813
+ }
3814
+ const label = input.label?.trim() || type;
3815
+ const kind = input.kind?.trim() || undefined;
3816
+ const description = 'description' in input ? input.description?.trim() ?? '' : undefined;
3817
+ if (input.action === 'create') {
3818
+ return {
3819
+ type: 'marker-type.create',
3820
+ definition: {
3821
+ type,
3822
+ label,
3823
+ ...(kind ? { kind } : {}),
3824
+ ...(description ? { description } : {}),
3825
+ },
3826
+ };
3827
+ }
3828
+ return {
3829
+ type: 'marker-type.update',
3830
+ typeId: type,
3831
+ patch: {
3832
+ label,
3833
+ ...(kind !== undefined ? { kind } : {}),
3834
+ ...(description !== undefined ? { description } : {}),
3835
+ },
3836
+ };
3837
+ }
2477
3838
  function patchHierarchyItemEnabled(state, options, input) {
3839
+ const markerVisibilityResult = patchMarkerGraphHierarchyVisibility(state, options, input);
3840
+ if (markerVisibilityResult !== 'unhandled')
3841
+ return markerVisibilityResult === 'changed';
2478
3842
  return patchSerializedProperty(state, options, {
2479
3843
  targetId: input.id,
2480
3844
  path: 'enabled',
@@ -2486,6 +3850,47 @@ function patchHierarchyItemEnabled(state, options, input) {
2486
3850
  source: 'toggle',
2487
3851
  });
2488
3852
  }
3853
+ function patchMarkerGraphHierarchyVisibility(state, options, input) {
3854
+ const document = state.session?.getState().workingDocument ?? null;
3855
+ if (!document)
3856
+ return 'unhandled';
3857
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
3858
+ if (!graph)
3859
+ return 'unhandled';
3860
+ const hierarchy = options.documentAdapter.getHierarchyItems(document);
3861
+ const visibility = state.markerGraphVisibility;
3862
+ if (isMarkerGraphHierarchyRootRowId(graph, input.id, hierarchy)) {
3863
+ if (visibility.rootVisible === input.enabled)
3864
+ return 'handled';
3865
+ visibility.rootVisible = input.enabled;
3866
+ state.editorStateRevision += 1;
3867
+ syncCurrentSelectionToSceneArtifacts(state, options, 'marker-graph-visibility-toggle', state.editorStateRevision);
3868
+ state.status = input.enabled ? 'Markers shown' : 'Markers hidden';
3869
+ state.statusTone = 'success';
3870
+ state.statusToneStatus = state.status;
3871
+ state.statusDetails = '';
3872
+ return 'changed';
3873
+ }
3874
+ const markerId = readMarkerGraphHierarchyMarkerIdForGraph(graph, input.id, hierarchy)
3875
+ ?? (isSpatialMarkerBackedByHierarchyRow(graph, input.id, hierarchy) ? input.id : null);
3876
+ if (!markerId)
3877
+ return 'unhandled';
3878
+ const changed = input.enabled
3879
+ ? visibility.hiddenMarkerIds.delete(markerId)
3880
+ : !visibility.hiddenMarkerIds.has(markerId);
3881
+ if (!input.enabled)
3882
+ visibility.hiddenMarkerIds.add(markerId);
3883
+ if (!changed)
3884
+ return 'handled';
3885
+ state.editorStateRevision += 1;
3886
+ syncCurrentSelectionToSceneArtifacts(state, options, 'marker-visibility-toggle', state.editorStateRevision);
3887
+ const marker = findSpatialMarkerById(graph, markerId);
3888
+ state.status = `${marker?.label ?? markerId} ${input.enabled ? 'shown' : 'hidden'}`;
3889
+ state.statusTone = 'success';
3890
+ state.statusToneStatus = state.status;
3891
+ state.statusDetails = '';
3892
+ return 'changed';
3893
+ }
2489
3894
  async function applyRenderingPropertyChange(state, options, input) {
2490
3895
  if (state.mode !== 'editor')
2491
3896
  return false;
@@ -2735,6 +4140,11 @@ function findInspectorPropertyForEdit(state, options, document, input, targetIds
2735
4140
  return findInspectorPropertyByPath(inspector, input.path);
2736
4141
  }
2737
4142
  function createInspectorObjectForEdit(state, options, document, input, targetIds) {
4143
+ if (targetIds.length === 1 && state.prefabStage) {
4144
+ const inspector = createPrefabStageInspectorObjectForEdit(state, options, document);
4145
+ if (inspector && inspector.targetIds.includes(input.targetId))
4146
+ return inspector;
4147
+ }
2738
4148
  if (targetIds.length > 1) {
2739
4149
  const activeId = state.session?.getState().selection.activeId ?? input.targetId ?? null;
2740
4150
  const inspector = createInspectorMultiObjectBase(state, options, document, targetIds, activeId);
@@ -2742,6 +4152,9 @@ function createInspectorObjectForEdit(state, options, document, input, targetIds
2742
4152
  return withDefaultEditorInspectorSections(state, options, document, inspector);
2743
4153
  return null;
2744
4154
  }
4155
+ const markerInspector = createMarkerInspectorObjectBase(options, document, input.targetId);
4156
+ if (markerInspector)
4157
+ return markerInspector;
2745
4158
  const inspector = options.documentAdapter.getInspectorObject?.(document, input.targetId) ?? null;
2746
4159
  if (inspector)
2747
4160
  return withDefaultEditorInspectorSections(state, options, document, inspector);
@@ -2750,6 +4163,13 @@ function createInspectorObjectForEdit(state, options, document, input, targetIds
2750
4163
  ? withDefaultEditorInspectorSections(state, options, document, serializedObjectToInspectorObject(serializedObject, document))
2751
4164
  : null;
2752
4165
  }
4166
+ function createPrefabStageInspectorObjectForEdit(state, options, document) {
4167
+ const stage = state.prefabStage;
4168
+ if (!stage || !options.documentAdapter.getPrefabStageInspectorObject)
4169
+ return null;
4170
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? stage.descriptor;
4171
+ return options.documentAdapter.getPrefabStageInspectorObject(document, descriptor, stage.selectedItemId, createPrefabStageContext(state, descriptor)) ?? null;
4172
+ }
2753
4173
  function findInspectorPropertyByPath(inspector, path) {
2754
4174
  for (const section of inspector.sections) {
2755
4175
  const property = section.properties.find(candidate => candidate.path === path);
@@ -2800,7 +4220,8 @@ function beginDuplicateDrag(state, options, input) {
2800
4220
  return null;
2801
4221
  if (state.duplicateDrag)
2802
4222
  return null;
2803
- const document = state.session.getState().workingDocument;
4223
+ const session = state.session;
4224
+ const document = session.getState().workingDocument;
2804
4225
  const patch = options.documentAdapter.createDuplicateSelectionPatch?.({
2805
4226
  document,
2806
4227
  targetIds: input.targetIds,
@@ -2811,9 +4232,9 @@ function beginDuplicateDrag(state, options, input) {
2811
4232
  state.status = 'Duplicate drag rejected';
2812
4233
  return null;
2813
4234
  }
2814
- const originalSelection = state.session.getSelection();
4235
+ const originalSelection = session.getSelection();
2815
4236
  const projectionImpact = resolveProjectionImpact(patch, createDuplicateProjectionImpact(patch));
2816
- const result = state.session.dispatch({
4237
+ const result = session.dispatch({
2817
4238
  type: 'document.patch',
2818
4239
  label: patch.label ?? `Duplicate ${input.targetIds.length} object(s)`,
2819
4240
  patch: patch.patch,
@@ -2854,7 +4275,7 @@ function beginDuplicateDrag(state, options, input) {
2854
4275
  })) {
2855
4276
  rebuildProjectionFromDocument(state, options, result.workingDocument, selectionResult.selection);
2856
4277
  }
2857
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
4278
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
2858
4279
  state.status = patch.label ?? `Duplicated ${createdIds.length} object(s)`;
2859
4280
  return {
2860
4281
  targetIds: createdIds,
@@ -2884,6 +4305,9 @@ function commitGizmoTransform(state, options, event) {
2884
4305
  if (state.mode !== 'editor' || !state.session)
2885
4306
  return false;
2886
4307
  const document = state.session.getState().workingDocument;
4308
+ const markerChanged = commitMarkerGizmoTransform(state, options, document, event);
4309
+ if (markerChanged !== null)
4310
+ return markerChanged;
2887
4311
  if (isTransformBatchCommit(event)) {
2888
4312
  const patch = options.documentAdapter.createTransformBatchPatch?.({
2889
4313
  ...event,
@@ -2934,7 +4358,10 @@ function commitGizmoTransform(state, options, event) {
2934
4358
  }
2935
4359
  return false;
2936
4360
  }
2937
- const result = state.session.dispatch({
4361
+ const session = state.session;
4362
+ if (!session)
4363
+ return false;
4364
+ const result = session.dispatch({
2938
4365
  type: 'document.patch',
2939
4366
  label: patch.label ?? `${event.tool} ${targetId}`,
2940
4367
  patch: patch.patch,
@@ -2949,14 +4376,135 @@ function commitGizmoTransform(state, options, event) {
2949
4376
  reprojectProjectionForChangedIds(state, options, result.workingDocument, patch.reprojectIds);
2950
4377
  else
2951
4378
  syncProjectionForChangedIds(state, options, result.workingDocument, [patch.changedId ?? targetId]);
2952
- state.summary = summarizeDocument(options, result.workingDocument, state.session.getSource());
4379
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
2953
4380
  state.status = patch.label ?? `${event.tool} ${targetId}`;
2954
4381
  state.duplicateDrag = null;
2955
4382
  return result.documentChanged;
2956
4383
  }
4384
+ function commitMarkerGizmoTransform(state, options, document, event) {
4385
+ if (isTransformBatchCommit(event))
4386
+ return null;
4387
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
4388
+ const hierarchy = graph ? options.documentAdapter.getHierarchyItems(document) : [];
4389
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, event.nodeId, hierarchy);
4390
+ if (!markerId)
4391
+ return null;
4392
+ if (markerId === event.nodeId)
4393
+ return null;
4394
+ const marker = findSpatialMarkerById(graph, markerId);
4395
+ if (!marker)
4396
+ return false;
4397
+ if (!isEditorTransformTrsSnapshot(event.after)) {
4398
+ state.status = `Ignored ${event.tool} marker ${marker.label}: transform is not editable as TRS`;
4399
+ return false;
4400
+ }
4401
+ if (editorTransformSnapshotsEqual(event.before, event.after)) {
4402
+ state.status = `Unchanged ${event.tool} ${marker.label}`;
4403
+ return false;
4404
+ }
4405
+ const nextPatch = createMarkerTransformPatchFromGizmo(marker, event.after);
4406
+ const patch = options.documentAdapter.createMarkerGraphPatch?.(document, {
4407
+ type: 'marker.update',
4408
+ markerId,
4409
+ patch: nextPatch,
4410
+ });
4411
+ if (!patch) {
4412
+ state.markerOverlay?.setMarkerTransformPreview(event.nodeId, event.before);
4413
+ state.status = `Ignored ${event.tool} marker ${marker.label}`;
4414
+ return false;
4415
+ }
4416
+ const session = state.session;
4417
+ if (!session)
4418
+ return false;
4419
+ const result = session.dispatch({
4420
+ type: 'document.patch',
4421
+ label: patch.label ?? `${event.tool} marker ${marker.label}`,
4422
+ patch: patch.patch,
4423
+ targetId: event.nodeId,
4424
+ });
4425
+ syncProjectionForDispatchResult(state, options, result, patch.changedId ?? markerId);
4426
+ state.gizmo?.refreshSelection();
4427
+ requestEditorSceneFrame(state, 'marker-gizmo-transform');
4428
+ state.summary = summarizeDocument(options, result.workingDocument, session.getSource());
4429
+ state.status = patch.label ?? `${event.tool} marker ${marker.label}`;
4430
+ return result.documentChanged;
4431
+ }
4432
+ function createMarkerTransformPatchFromGizmo(marker, transform) {
4433
+ const semanticFrame = createMarkerSemanticFrameFromTransform(marker, transform);
4434
+ if (marker.geometry.kind === 'box') {
4435
+ const nextGeometry = {
4436
+ ...marker.geometry,
4437
+ center: { ...transform.position },
4438
+ size: normalizeMarkerBoxSize(transform.scale),
4439
+ };
4440
+ if (marker.geometry.rotation || !editorVec3Equal(transform.rotation, { x: 0, y: 0, z: 0 })) {
4441
+ nextGeometry.rotation = { ...transform.rotation };
4442
+ }
4443
+ else {
4444
+ delete nextGeometry.rotation;
4445
+ }
4446
+ return {
4447
+ geometry: nextGeometry,
4448
+ semanticFrame,
4449
+ };
4450
+ }
4451
+ if (marker.geometry.kind === 'point') {
4452
+ return {
4453
+ geometry: {
4454
+ ...marker.geometry,
4455
+ coordinateSpace: 'world',
4456
+ position: { ...transform.position },
4457
+ },
4458
+ semanticFrame,
4459
+ };
4460
+ }
4461
+ return {
4462
+ semanticFrame,
4463
+ };
4464
+ }
4465
+ function createMarkerSemanticFrameFromTransform(marker, transform) {
4466
+ return {
4467
+ ...(marker.semanticFrame ?? {}),
4468
+ origin: { ...transform.position },
4469
+ right: rotateEditorVec3ByEuler({ x: 1, y: 0, z: 0 }, transform.rotation),
4470
+ up: rotateEditorVec3ByEuler({ x: 0, y: 1, z: 0 }, transform.rotation),
4471
+ forward: rotateEditorVec3ByEuler({ x: 0, y: 0, z: 1 }, transform.rotation),
4472
+ label: marker.semanticFrame?.label ?? marker.label,
4473
+ };
4474
+ }
4475
+ function rotateEditorVec3ByEuler(value, rotation) {
4476
+ const cx = Math.cos(rotation.x);
4477
+ const sx = Math.sin(rotation.x);
4478
+ const cy = Math.cos(rotation.y);
4479
+ const sy = Math.sin(rotation.y);
4480
+ const cz = Math.cos(rotation.z);
4481
+ const sz = Math.sin(rotation.z);
4482
+ const afterX = {
4483
+ x: value.x,
4484
+ y: value.y * cx - value.z * sx,
4485
+ z: value.y * sx + value.z * cx,
4486
+ };
4487
+ const afterY = {
4488
+ x: afterX.x * cy + afterX.z * sy,
4489
+ y: afterX.y,
4490
+ z: -afterX.x * sy + afterX.z * cy,
4491
+ };
4492
+ return {
4493
+ x: afterY.x * cz - afterY.y * sz,
4494
+ y: afterY.x * sz + afterY.y * cz,
4495
+ z: afterY.z,
4496
+ };
4497
+ }
2957
4498
  function isTransformBatchCommit(event) {
2958
4499
  return 'targets' in event;
2959
4500
  }
4501
+ function normalizeMarkerBoxSize(size) {
4502
+ return {
4503
+ x: Math.max(0.000001, Math.abs(size.x)),
4504
+ y: Math.max(0.000001, Math.abs(size.y)),
4505
+ z: Math.max(0.000001, Math.abs(size.z)),
4506
+ };
4507
+ }
2960
4508
  function restoreBatchTransformPreview(state, targets) {
2961
4509
  const transforms = {};
2962
4510
  for (const target of targets)
@@ -2991,6 +4539,14 @@ function cancelActiveOperation(state) {
2991
4539
  function cancelEditorIntent(state, options) {
2992
4540
  if (state.mode !== 'editor')
2993
4541
  return false;
4542
+ if (state.markerRelationPick) {
4543
+ state.markerRelationPick = null;
4544
+ state.status = '已取消关系端点拾取。';
4545
+ state.statusTone = 'success';
4546
+ state.statusToneStatus = state.status;
4547
+ state.statusDetails = '';
4548
+ return true;
4549
+ }
2994
4550
  if (state.viewportTools.activeUtilityTool === 'measure-distance'
2995
4551
  || state.viewportMeasurement.start
2996
4552
  || state.viewportMeasurement.end) {
@@ -3031,6 +4587,19 @@ function focusProjectionNode(state, nodeId) {
3031
4587
  requestEditorSceneFrame(state, 'viewport-focus-selection');
3032
4588
  return true;
3033
4589
  }
4590
+ function focusPrefabStagePreviewProjection(state, reason) {
4591
+ const previewNodeId = state.prefabStage?.descriptor.previewNodeId;
4592
+ if (!previewNodeId)
4593
+ return;
4594
+ const root = state.projection?.getAttachableRoot(previewNodeId) ?? null;
4595
+ if (!root)
4596
+ return;
4597
+ const focused = focusEditorViewportSelection(state.world?.camera ?? null, root, {
4598
+ babylon: state.babylon ?? undefined,
4599
+ });
4600
+ if (focused)
4601
+ requestEditorSceneFrame(state, reason);
4602
+ }
3034
4603
  function formatEditorStatusTime(timestamp) {
3035
4604
  const date = new Date(timestamp);
3036
4605
  const pad = (value) => String(value).padStart(2, '0');
@@ -3232,6 +4801,10 @@ function applyIncrementalHistoryProjectionImpact(state, options, result, operati
3232
4801
  function applyIncrementalProjectionImpactToDocument(state, options, input) {
3233
4802
  if (!state.projection)
3234
4803
  return false;
4804
+ if (state.prefabStage) {
4805
+ rebuildProjectionFromDocument(state, options, input.document, EMPTY_EDITOR_SELECTION);
4806
+ return true;
4807
+ }
3235
4808
  const syncTransformIds = uniqueProjectionIds(input.syncTransformIds);
3236
4809
  const reprojectIds = uniqueProjectionIds(input.reprojectIds);
3237
4810
  const projectNodeIds = uniqueProjectionIds(input.projectNodeIds);
@@ -3260,7 +4833,7 @@ function applyIncrementalProjectionImpactToDocument(state, options, input) {
3260
4833
  state.projection.syncNodeTransform(projectedNode);
3261
4834
  }
3262
4835
  const sanitized = sanitizeSelection(state, options, input.document, input.selection);
3263
- syncSelectionToProjection(state, sanitized ?? input.selection);
4836
+ syncSelectionToProjection(state, options, sanitized ?? input.selection);
3264
4837
  syncEditorWorldAppearanceFromDocument(state, options, input.document, `world-appearance-${input.reason}`);
3265
4838
  syncEditorWorldRenderingFromDocument(state, options, input.document, `world-rendering-${input.reason}`, true);
3266
4839
  invalidateEditorScene(state, `projection-${input.reason}`, options);
@@ -3276,27 +4849,47 @@ function resolveProjectionNodes(options, document, ids) {
3276
4849
  }
3277
4850
  return projectedNodes;
3278
4851
  }
4852
+ function resolveCurrentEditorProjectionNodes(state, options, document) {
4853
+ if (!state.prefabStage)
4854
+ return options.documentAdapter.getProjectionNodes(document);
4855
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document);
4856
+ if (!descriptor)
4857
+ return [];
4858
+ return options.documentAdapter.getPrefabStageProjectionNodes?.(document, descriptor) ?? [];
4859
+ }
3279
4860
  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);
4861
+ const nodes = resolveCurrentEditorProjectionNodes(state, options, document);
4862
+ state.projection?.rebuild(nodes);
4863
+ const sanitized = state.prefabStage ? EMPTY_EDITOR_SELECTION : sanitizeSelection(state, options, document, selection);
4864
+ syncSelectionToProjection(state, options, sanitized ?? selection);
3283
4865
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-rebuild');
3284
4866
  syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-rebuild', true);
4867
+ if (state.prefabStage && nodes.length === 0) {
4868
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document);
4869
+ state.status = `Prefab preview unavailable: ${descriptor?.label ?? state.prefabStage.descriptor.label}`;
4870
+ state.statusTone = 'warning';
4871
+ state.statusToneStatus = state.status;
4872
+ state.statusDetails = 'The prefab source model asset could not be resolved for preview.';
4873
+ }
3285
4874
  invalidateEditorScene(state, 'projection-rebuild');
3286
4875
  }
3287
4876
  function syncProjectionForDispatchResult(state, options, result, changedId) {
4877
+ if (state.prefabStage && result.documentChanged && result.workingDocument) {
4878
+ rebuildProjectionFromDocument(state, options, result.workingDocument, EMPTY_EDITOR_SELECTION);
4879
+ return;
4880
+ }
3288
4881
  if (result.documentChanged && changedId && result.workingDocument) {
3289
4882
  const projectedNode = options.documentAdapter.getProjectionNode(result.workingDocument, changedId);
3290
4883
  if (projectedNode)
3291
4884
  state.projection?.syncNodeTransform(projectedNode);
3292
4885
  }
3293
4886
  if (result.selectionChanged) {
3294
- syncSelectionToProjection(state, result.selection ?? { selectedIds: [], activeId: null });
4887
+ syncSelectionToProjection(state, options, result.selection ?? { selectedIds: [], activeId: null });
3295
4888
  }
3296
4889
  else if (result.documentChanged) {
3297
4890
  const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3298
4891
  const sanitized = result.workingDocument ? sanitizeSelection(state, options, result.workingDocument, selection) : null;
3299
- syncSelectionToProjection(state, sanitized ?? selection);
4892
+ syncSelectionToProjection(state, options, sanitized ?? selection);
3300
4893
  }
3301
4894
  if (result.documentChanged && result.workingDocument) {
3302
4895
  syncEditorWorldAppearanceFromDocument(state, options, result.workingDocument, 'world-appearance-dispatch-result');
@@ -3307,18 +4900,26 @@ function syncProjectionForDispatchResult(state, options, result, changedId) {
3307
4900
  }
3308
4901
  }
3309
4902
  function syncProjectionForChangedIds(state, options, document, changedIds) {
4903
+ if (state.prefabStage) {
4904
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
4905
+ return;
4906
+ }
3310
4907
  for (const changedId of changedIds) {
3311
4908
  const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
3312
4909
  if (projectedNode)
3313
4910
  state.projection?.syncNodeTransform(projectedNode);
3314
4911
  }
3315
4912
  const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3316
- syncSelectionToProjection(state, selection);
4913
+ syncSelectionToProjection(state, options, selection);
3317
4914
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-sync-changed-ids');
3318
4915
  syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-sync-changed-ids', true);
3319
4916
  invalidateEditorScene(state, 'projection-sync-changed-ids');
3320
4917
  }
3321
4918
  function syncProjectionMetadataForChangedIds(state, options, document, changedIds, syncOptions = {}) {
4919
+ if (state.prefabStage) {
4920
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
4921
+ return;
4922
+ }
3322
4923
  for (const changedId of changedIds) {
3323
4924
  const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
3324
4925
  if (projectedNode) {
@@ -3326,7 +4927,7 @@ function syncProjectionMetadataForChangedIds(state, options, document, changedId
3326
4927
  }
3327
4928
  }
3328
4929
  const selection = state.session?.getState({ cloneDocuments: false }).selection ?? { selectedIds: [], activeId: null };
3329
- syncSelectionToProjection(state, selection);
4930
+ syncSelectionToProjection(state, options, selection);
3330
4931
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-sync-metadata-ids');
3331
4932
  if (syncOptions.invalidateStaticShadowArtifact) {
3332
4933
  state.shadowPreview?.invalidateStaticProjectedArtifact?.();
@@ -3343,12 +4944,16 @@ function syncProjectionMetadataForChangedIds(state, options, document, changedId
3343
4944
  invalidateEditorScene(state, 'projection-sync-metadata-ids');
3344
4945
  }
3345
4946
  function reprojectProjectionForChangedIds(state, options, document, changedIds) {
4947
+ if (state.prefabStage) {
4948
+ rebuildProjectionFromDocument(state, options, document, EMPTY_EDITOR_SELECTION);
4949
+ return;
4950
+ }
3346
4951
  for (const changedId of changedIds) {
3347
4952
  const projectedNode = options.documentAdapter.getProjectionNode(document, changedId);
3348
4953
  if (projectedNode)
3349
4954
  state.projection?.projectNode(projectedNode);
3350
4955
  }
3351
- syncCurrentSelectionToSceneArtifacts(state);
4956
+ syncCurrentSelectionToSceneArtifacts(state, options);
3352
4957
  syncEditorWorldAppearanceFromDocument(state, options, document, 'world-appearance-reproject-changed-ids');
3353
4958
  syncEditorWorldRenderingFromDocument(state, options, document, 'world-rendering-reproject-changed-ids', true);
3354
4959
  invalidateEditorScene(state, 'projection-reproject-changed-ids', options);
@@ -3376,15 +4981,21 @@ function setSceneCameraPreviewEnabled(state, options, enabled) {
3376
4981
  }
3377
4982
  function setGridVisible(state, visible) {
3378
4983
  const nextVisible = visible === true;
3379
- if (state.gridVisible === nextVisible)
4984
+ if (!setGridVisibleForEditorContext(state, nextVisible, 'grid-visible-change'))
3380
4985
  return false;
3381
- state.gridVisible = nextVisible;
3382
- state.grid?.setVisible(nextVisible);
3383
4986
  state.status = nextVisible ? 'Grid visible' : 'Grid hidden';
3384
4987
  state.statusTone = 'default';
3385
4988
  state.statusToneStatus = state.status;
3386
4989
  state.statusDetails = '';
3387
- requestEditorSceneFrame(state, 'grid-visible-change');
4990
+ return true;
4991
+ }
4992
+ function setGridVisibleForEditorContext(state, visible, reason) {
4993
+ const nextVisible = visible === true;
4994
+ if (state.gridVisible === nextVisible)
4995
+ return false;
4996
+ state.gridVisible = nextVisible;
4997
+ state.grid?.setVisible(nextVisible);
4998
+ requestEditorSceneFrame(state, reason);
3388
4999
  return true;
3389
5000
  }
3390
5001
  function syncSceneCameraPreview(state, options) {
@@ -3526,22 +5137,36 @@ function createUiState(state, options) {
3526
5137
  const selectedIds = sessionState?.selection.selectedIds ?? [];
3527
5138
  const activeId = sessionState?.selection.activeId ?? null;
3528
5139
  const largeSelection = isLargeSelection(selectedIds);
5140
+ const markerGraph = document ? options.documentAdapter.getMarkerGraph?.(document) ?? null : null;
5141
+ const markerTypes = document ? options.documentAdapter.getMarkerTypeCatalog?.(document) ?? [] : [];
5142
+ const relationTypes = document ? options.documentAdapter.getRelationTypeCatalog?.(document) ?? [] : [];
5143
+ const documentHierarchy = document ? options.documentAdapter.getHierarchyItems(document) : [];
5144
+ const activeMarkerId = activeId ? readSpatialMarkerIdFromHierarchySelection(markerGraph, activeId, documentHierarchy) : null;
5145
+ const markerRelationVisibility = createMarkerRelationVisibilityOptions(state);
5146
+ const markerInspectorObject = document && activeId && selectedIds.length === 1
5147
+ ? createMarkerInspectorObjectBase(options, document, activeId, markerGraph, documentHierarchy, markerTypes, state.activeMarkerRelationId, state.markerRelationPick, markerRelationVisibility)
5148
+ : null;
3529
5149
  const serializedObject = document && activeId && selectedIds.length === 1
3530
- ? options.documentAdapter.getSerializedObject(document, activeId)
5150
+ ? markerInspectorObject ? null : options.documentAdapter.getSerializedObject(document, activeId)
3531
5151
  : null;
3532
5152
  const serializedMultiObject = document && selectedIds.length > 1 && !largeSelection
3533
5153
  ? options.documentAdapter.getSerializedMultiObject?.(document, selectedIds, activeId) ?? null
3534
5154
  : null;
3535
5155
  const inspectorObjectBase = document && activeId && selectedIds.length === 1
3536
- ? options.documentAdapter.getInspectorObject?.(document, activeId) ?? (serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null)
5156
+ ? markerInspectorObject
5157
+ ?? options.documentAdapter.getInspectorObject?.(document, activeId)
5158
+ ?? (serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null)
3537
5159
  : null;
3538
5160
  const inspectorMultiObjectBase = document && selectedIds.length > 1
3539
5161
  ? createInspectorMultiObjectBase(state, options, document, selectedIds, activeId, serializedMultiObject, {
3540
5162
  largeSelection,
3541
5163
  })
3542
5164
  : null;
3543
- const inspectorObject = document && inspectorObjectBase
3544
- ? withRuntimeInspectorSections(state, options, document, inspectorObjectBase)
5165
+ const markerRelationInspectorObjectBase = document && inspectorObjectBase
5166
+ ? withMarkerGraphRelationInspectorSection(options, document, inspectorObjectBase, markerGraph, documentHierarchy, markerTypes, relationTypes, state.activeMarkerRelationId, state.markerRelationPick, markerRelationVisibility)
5167
+ : inspectorObjectBase;
5168
+ const inspectorObject = document && markerRelationInspectorObjectBase
5169
+ ? withRuntimeInspectorSections(state, options, document, markerRelationInspectorObjectBase)
3545
5170
  : inspectorObjectBase;
3546
5171
  const inspectorMultiObject = document && inspectorMultiObjectBase
3547
5172
  ? withRuntimeInspectorSections(state, options, document, inspectorMultiObjectBase)
@@ -3551,8 +5176,10 @@ function createUiState(state, options) {
3551
5176
  const documentAssetItems = document
3552
5177
  ? options.documentAdapter.getBrowserAssetItems?.(document) ?? []
3553
5178
  : [];
3554
- const hierarchy = document ? options.documentAdapter.getHierarchyItems(document) : [];
3555
- state.performanceObjectCount = document ? countLocalEditorAuthoredObjects(hierarchy) : null;
5179
+ const hierarchy = document
5180
+ ? createMarkerGraphHierarchyItems(documentHierarchy, markerGraph, createMarkerGraphHierarchyVisibility(state))
5181
+ : [];
5182
+ state.performanceObjectCount = document ? countLocalEditorAuthoredObjects(documentHierarchy) : null;
3556
5183
  state.performanceStats = createLocalEditorPerformanceStats({
3557
5184
  frameStats: state.sceneFrameStats,
3558
5185
  runtimeStats: state.performanceMonitor?.readSnapshot() ?? null,
@@ -3591,6 +5218,9 @@ function createUiState(state, options) {
3591
5218
  renderingAction: state.renderingAction,
3592
5219
  }) ?? null
3593
5220
  : null,
5221
+ markerPanel: document
5222
+ ? createMarkerPanelState(markerGraph, markerTypes, relationTypes, documentHierarchy, activeMarkerId, state.activeMarkerRelationId, state.markerRelationPick, markerRelationVisibility)
5223
+ : null,
3594
5224
  boxSelection: state.boxSelection,
3595
5225
  coordinateAxes: options.world?.coordinateAxes === false
3596
5226
  ? null
@@ -3620,6 +5250,7 @@ function createUiState(state, options) {
3620
5250
  enabled: state.sceneCameraPreviewEnabled,
3621
5251
  available: hasSceneCameraPreviewRig(state, options),
3622
5252
  },
5253
+ prefabStage: createBrowserPrefabStageState(state, options, document),
3623
5254
  grid: {
3624
5255
  visible: state.gridVisible,
3625
5256
  available: !!state.grid,
@@ -3635,6 +5266,123 @@ function createUiState(state, options) {
3635
5266
  : null,
3636
5267
  };
3637
5268
  }
5269
+ function createBrowserPrefabStageState(state, options, document) {
5270
+ const stage = state.prefabStage;
5271
+ if (!stage || !document)
5272
+ return null;
5273
+ const descriptor = resolvePrefabStageDescriptorForDocument(state, options, document) ?? stage.descriptor;
5274
+ const context = createPrefabStageContext(state, descriptor);
5275
+ const structureItems = options.documentAdapter.getPrefabStageStructure?.(document, descriptor, context)
5276
+ ?? createFallbackPrefabStageStructure(descriptor);
5277
+ const inspectorObject = options.documentAdapter.getPrefabStageInspectorObject?.(document, descriptor, stage.selectedItemId, context) ?? null;
5278
+ return {
5279
+ assetId: descriptor.assetId,
5280
+ browserAssetId: descriptor.browserAssetId,
5281
+ label: descriptor.label,
5282
+ sourceAssetId: descriptor.sourceAssetId,
5283
+ sourceAssetGuid: descriptor.sourceAssetGuid,
5284
+ readonly: descriptor.readonly,
5285
+ selectedItemId: stage.selectedItemId,
5286
+ structureItems,
5287
+ inspectorObject,
5288
+ importStructureReady: context.importStructureReady,
5289
+ importStats: createPrefabStageImportStats(context),
5290
+ previewNodeId: descriptor.previewNodeId,
5291
+ comparisonPreviews: createPrefabStageComparisonPreviews(state, descriptor),
5292
+ };
5293
+ }
5294
+ function createPrefabStageContext(state, descriptor) {
5295
+ const previewNodeId = descriptor.previewNodeId;
5296
+ if (!previewNodeId) {
5297
+ return {
5298
+ importStructure: null,
5299
+ importStructureReady: false,
5300
+ };
5301
+ }
5302
+ const projectedNode = state.projection?.getProjectedNode(previewNodeId) ?? null;
5303
+ return {
5304
+ previewNodeId,
5305
+ importStructure: state.projection?.readNodeImportStructure(previewNodeId) ?? null,
5306
+ importStructureReady: !!projectedNode && !projectedNode.loadPromise,
5307
+ };
5308
+ }
5309
+ function createPrefabStageImportStats(context) {
5310
+ const snapshot = context.importStructure;
5311
+ if (!context.importStructureReady) {
5312
+ return createEmptyPrefabStageImportStats('loading', snapshot);
5313
+ }
5314
+ if (!snapshot) {
5315
+ return createEmptyPrefabStageImportStats('missing', null);
5316
+ }
5317
+ const nodes = snapshot.nodes ?? [];
5318
+ return {
5319
+ status: 'ready',
5320
+ projectionNodeId: snapshot.projectionNodeId,
5321
+ ...(snapshot.assetId ? { assetId: snapshot.assetId } : {}),
5322
+ ...(snapshot.sourceId ? { sourceId: snapshot.sourceId } : {}),
5323
+ nodeCount: nodes.length,
5324
+ transformCount: nodes.filter(node => node.kind === 'root' || node.kind === 'transform').length,
5325
+ meshCount: nodes.filter(node => node.kind === 'mesh').length,
5326
+ materialCount: snapshot.materials?.length ?? 0,
5327
+ textureCount: snapshot.textures?.length ?? 0,
5328
+ animationCount: snapshot.animations?.length ?? 0,
5329
+ };
5330
+ }
5331
+ function createEmptyPrefabStageImportStats(status, snapshot) {
5332
+ return {
5333
+ status,
5334
+ ...(snapshot?.projectionNodeId ? { projectionNodeId: snapshot.projectionNodeId } : {}),
5335
+ ...(snapshot?.assetId ? { assetId: snapshot.assetId } : {}),
5336
+ ...(snapshot?.sourceId ? { sourceId: snapshot.sourceId } : {}),
5337
+ nodeCount: 0,
5338
+ transformCount: 0,
5339
+ meshCount: 0,
5340
+ materialCount: 0,
5341
+ textureCount: 0,
5342
+ animationCount: 0,
5343
+ };
5344
+ }
5345
+ function createPrefabStageComparisonPreviews(state, descriptor) {
5346
+ return PREFAB_STAGE_COMPARISON_PREVIEW_DEFINITIONS.map((definition) => {
5347
+ const projectionNodeId = createPrefabStageComparisonProjectionNodeId(descriptor, definition.nodeIdSuffix);
5348
+ const projectedNode = state.projection?.getProjectedNode(projectionNodeId) ?? null;
5349
+ const status = !descriptor.sourceAssetId || !projectedNode
5350
+ ? 'unavailable'
5351
+ : projectedNode.loadPromise
5352
+ ? 'loading'
5353
+ : 'ready';
5354
+ return {
5355
+ id: definition.id,
5356
+ label: definition.label,
5357
+ mode: definition.mode,
5358
+ projectionNodeId,
5359
+ status,
5360
+ };
5361
+ });
5362
+ }
5363
+ function createPrefabStageComparisonProjectionNodeId(descriptor, suffix) {
5364
+ const previewNodeId = descriptor.previewNodeId ?? `prefab-stage:${descriptor.assetId}`;
5365
+ return `${previewNodeId}:${suffix}`;
5366
+ }
5367
+ function createFallbackPrefabStageStructure(descriptor) {
5368
+ return [{
5369
+ id: 'prefab-root',
5370
+ label: descriptor.label,
5371
+ kind: 'root',
5372
+ icon: 'prefab',
5373
+ meta: descriptor.assetId,
5374
+ readonly: descriptor.readonly,
5375
+ children: [{
5376
+ id: 'prefab-source',
5377
+ label: 'Source Model',
5378
+ kind: 'source',
5379
+ icon: 'asset',
5380
+ sourceAssetId: descriptor.sourceAssetId,
5381
+ meta: descriptor.sourceAssetId ?? 'Missing source asset',
5382
+ readonly: true,
5383
+ }],
5384
+ }];
5385
+ }
3638
5386
  function createUiPreviewState(state, options) {
3639
5387
  const sessionState = state.session?.getState({ cloneDocuments: false });
3640
5388
  const selection = sessionState?.selection;
@@ -3891,12 +5639,108 @@ function createAggregatedInspectorMultiObjectBase(_state, options, document, sel
3891
5639
  return aggregatedInspector;
3892
5640
  }
3893
5641
  function createSingleInspectorObjectBase(options, document, targetId) {
5642
+ const markerInspector = createMarkerInspectorObjectBase(options, document, targetId);
5643
+ if (markerInspector)
5644
+ return markerInspector;
3894
5645
  const inspectorObject = options.documentAdapter.getInspectorObject?.(document, targetId) ?? null;
3895
5646
  if (inspectorObject)
3896
- return inspectorObject;
5647
+ return withMarkerGraphRelationInspectorSection(options, document, inspectorObject);
3897
5648
  const serializedObject = options.documentAdapter.getSerializedObject(document, targetId);
3898
5649
  return serializedObject ? serializedObjectToInspectorObject(serializedObject, document) : null;
3899
5650
  }
5651
+ 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 = {}) {
5652
+ if (inspectorObject.targetIds.length !== 1)
5653
+ return inspectorObject;
5654
+ if (inspectorObject.sections.some(section => section.id === 'markerRelations'))
5655
+ return inspectorObject;
5656
+ if (inspectorObject.sections.some(section => section.properties.some(property => property.path === 'marker.relations.editor'))) {
5657
+ return inspectorObject;
5658
+ }
5659
+ const targetId = inspectorObject.activeId ?? inspectorObject.targetIds[0] ?? null;
5660
+ const markerId = readSpatialMarkerIdFromHierarchySelection(graph, targetId, hierarchy);
5661
+ if (!targetId || !markerId)
5662
+ return inspectorObject;
5663
+ const markerInspector = createMarkerGraphInspectorObject({
5664
+ document,
5665
+ graph,
5666
+ markerId: targetId,
5667
+ hierarchy,
5668
+ markerTypes,
5669
+ relationTypes,
5670
+ activeRelationId,
5671
+ relationPick,
5672
+ relationVisibility,
5673
+ });
5674
+ const relationSection = markerInspector?.sections.find(section => section.id === 'markerRelations') ?? null;
5675
+ if (!relationSection)
5676
+ return inspectorObject;
5677
+ const capabilities = new Set(inspectorObject.selection.capabilities ?? []);
5678
+ capabilities.add('markerGraph');
5679
+ const markerSection = inspectorObject.sections.find(section => section.id === 'marker') ?? null;
5680
+ const sections = markerSection
5681
+ ? inspectorObject.sections.map(section => section.id === 'marker'
5682
+ ? {
5683
+ ...section,
5684
+ properties: [
5685
+ ...section.properties,
5686
+ ...createIntegratedMarkerRelationProperties(section.properties, relationSection.properties),
5687
+ ],
5688
+ }
5689
+ : section)
5690
+ : mergeInspectorSections(inspectorObject.sections, [relationSection], { propertyConflict: 'ignore' });
5691
+ return {
5692
+ ...inspectorObject,
5693
+ selection: {
5694
+ ...inspectorObject.selection,
5695
+ targetKind: inspectorObject.selection.targetKind ?? 'marker',
5696
+ capabilities: [...capabilities],
5697
+ },
5698
+ sections,
5699
+ };
5700
+ }
5701
+ function createIntegratedMarkerRelationProperties(markerProperties, relationProperties) {
5702
+ const existingPaths = new Set(markerProperties.map(property => property.path));
5703
+ const baseOrder = markerProperties.reduce((maxOrder, property, index) => (Math.max(maxOrder, property.order ?? index)), -1);
5704
+ return relationProperties
5705
+ .filter(property => !existingPaths.has(property.path))
5706
+ .map((property, index) => ({
5707
+ ...property,
5708
+ label: formatIntegratedMarkerRelationPropertyLabel(property),
5709
+ order: baseOrder + index + 1,
5710
+ }));
5711
+ }
5712
+ function formatIntegratedMarkerRelationPropertyLabel(property) {
5713
+ switch (property.path) {
5714
+ case 'marker.relations.count': return '关系数量';
5715
+ case 'marker.relations.status': return '关系状态';
5716
+ case 'marker.relations.editor': return '关系';
5717
+ default: return property.label;
5718
+ }
5719
+ }
5720
+ 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 = {}) {
5721
+ const virtualMarkerId = readMarkerGraphHierarchyMarkerIdForGraph(graph, targetId, hierarchy);
5722
+ const isVirtualRoot = isMarkerGraphHierarchyRootRowId(graph, targetId, hierarchy);
5723
+ if (!virtualMarkerId && !isVirtualRoot)
5724
+ return null;
5725
+ return createMarkerGraphInspectorObject({
5726
+ document,
5727
+ graph,
5728
+ markerId: targetId,
5729
+ hierarchy,
5730
+ markerTypes,
5731
+ relationTypes: options.documentAdapter.getRelationTypeCatalog?.(document) ?? [],
5732
+ activeRelationId,
5733
+ relationPick,
5734
+ relationVisibility,
5735
+ }) ?? createMarkerGraphRootInspectorObject({
5736
+ document,
5737
+ graph,
5738
+ rootId: targetId,
5739
+ hierarchy,
5740
+ markerTypes,
5741
+ relationTypes: options.documentAdapter.getRelationTypeCatalog?.(document) ?? [],
5742
+ });
5743
+ }
3900
5744
  function applyMultiInspectorBatchCapabilityGate(options, document, inspectorObject) {
3901
5745
  return options.documentAdapter.createSerializedMultiPropertyPatch
3902
5746
  ? applyAggregatedBatchInspectorCapabilityGate(options, document, inspectorObject)
@@ -4225,6 +6069,12 @@ function isDocumentNodeLocked(state, options, id) {
4225
6069
  return options.documentAdapter.isLocked?.(document, id) ?? false;
4226
6070
  }
4227
6071
  function isNodeSelectableInDocument(options, document, id) {
6072
+ const graph = options.documentAdapter.getMarkerGraph?.(document) ?? null;
6073
+ const hierarchy = graph ? options.documentAdapter.getHierarchyItems(document) : [];
6074
+ if (isMarkerGraphHierarchyRootRowId(graph, id, hierarchy))
6075
+ return true;
6076
+ if (isMarkerGraphHierarchyId(graph, id, hierarchy))
6077
+ return true;
4228
6078
  if (options.documentAdapter.isLocked?.(document, id))
4229
6079
  return false;
4230
6080
  return options.documentAdapter.isSelectable?.(document, id) ?? true;
@@ -4246,6 +6096,36 @@ function findAssetByResolvedId(state, options, assetId) {
4246
6096
  }
4247
6097
  return selected?.asset ?? null;
4248
6098
  }
6099
+ function findBrowserAssetItemByResolvedId(state, options, document, assetId) {
6100
+ let selected = null;
6101
+ for (const asset of state.assets) {
6102
+ const item = toBrowserAssetItem(options, asset);
6103
+ if (item.id !== assetId && item.assetId !== assetId && resolveAssetId(options, asset) !== assetId)
6104
+ continue;
6105
+ if (!selected || shouldReplaceBrowserAssetItem(selected, item))
6106
+ selected = item;
6107
+ }
6108
+ const documentItem = document ? findDocumentBrowserAssetItemByResolvedId(options, document, assetId) : null;
6109
+ if (documentItem && (!selected || shouldReplaceBrowserAssetItem(selected, documentItem)))
6110
+ selected = documentItem;
6111
+ return selected;
6112
+ }
6113
+ function findAssetOrDocumentBrowserItemByResolvedId(state, options, document, assetId) {
6114
+ return findAssetByResolvedId(state, options, assetId)
6115
+ ?? (document ? findDocumentBrowserAssetItemByResolvedId(options, document, assetId) : null);
6116
+ }
6117
+ function findDocumentBrowserAssetItemByResolvedId(options, document, assetId) {
6118
+ let selected = null;
6119
+ for (const item of options.documentAdapter.getBrowserAssetItems?.(document) ?? []) {
6120
+ if (item.id !== assetId && item.assetId !== assetId)
6121
+ continue;
6122
+ if (item.placeable === false || item.disabled === true)
6123
+ continue;
6124
+ if (!selected || shouldReplaceBrowserAssetItem(selected, item))
6125
+ selected = item;
6126
+ }
6127
+ return selected;
6128
+ }
4249
6129
  function resolveBrowserAssetItemId(state, options, assetId) {
4250
6130
  let selected = null;
4251
6131
  for (const asset of state.assets) {
@@ -4271,6 +6151,7 @@ function toBrowserAssetItem(options, asset) {
4271
6151
  placeable: asset.placeable,
4272
6152
  preview: asset.preview,
4273
6153
  material: asset.material,
6154
+ prefab: asset.prefab,
4274
6155
  meta: asset.meta,
4275
6156
  disabled: asset.disabled ?? asset.placeable === false,
4276
6157
  };