ue-mcp 1.2.4 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (243) hide show
  1. package/README.md +4 -3
  2. package/dist/action-class.js +56 -3
  3. package/dist/action-class.js.map +1 -1
  4. package/dist/bridge-timeouts.d.ts +64 -0
  5. package/dist/bridge-timeouts.js +118 -0
  6. package/dist/bridge-timeouts.js.map +1 -0
  7. package/dist/bridge.js +10 -2
  8. package/dist/bridge.js.map +1 -1
  9. package/dist/editor-control.d.ts +72 -5
  10. package/dist/editor-control.js +164 -140
  11. package/dist/editor-control.js.map +1 -1
  12. package/dist/editor-target.d.ts +55 -1
  13. package/dist/editor-target.js +107 -10
  14. package/dist/editor-target.js.map +1 -1
  15. package/dist/engine-observer.d.ts +59 -5
  16. package/dist/engine-observer.js +249 -29
  17. package/dist/engine-observer.js.map +1 -1
  18. package/dist/engine-root.d.ts +91 -0
  19. package/dist/engine-root.js +448 -0
  20. package/dist/engine-root.js.map +1 -0
  21. package/dist/epic-enrich.js +16 -1
  22. package/dist/epic-enrich.js.map +1 -1
  23. package/dist/feedback-cli.js +4 -0
  24. package/dist/feedback-cli.js.map +1 -1
  25. package/dist/feedback-deferred.d.ts +9 -0
  26. package/dist/feedback-deferred.js +2 -0
  27. package/dist/feedback-deferred.js.map +1 -1
  28. package/dist/feedback-fallback.d.ts +99 -0
  29. package/dist/feedback-fallback.js +173 -0
  30. package/dist/feedback-fallback.js.map +1 -0
  31. package/dist/flow/write-methods.js +20 -0
  32. package/dist/flow/write-methods.js.map +1 -1
  33. package/dist/lean-context.js +8 -3
  34. package/dist/lean-context.js.map +1 -1
  35. package/dist/locking.js +11 -2
  36. package/dist/locking.js.map +1 -1
  37. package/dist/project.d.ts +11 -0
  38. package/dist/project.js +23 -2
  39. package/dist/project.js.map +1 -1
  40. package/dist/python-gate.d.ts +95 -0
  41. package/dist/python-gate.js +175 -0
  42. package/dist/python-gate.js.map +1 -0
  43. package/dist/tool-counts.json +16 -16
  44. package/dist/tools/animation.js +288 -24
  45. package/dist/tools/animation.js.map +1 -1
  46. package/dist/tools/asset.js +79 -14
  47. package/dist/tools/asset.js.map +1 -1
  48. package/dist/tools/audio.js +59 -5
  49. package/dist/tools/audio.js.map +1 -1
  50. package/dist/tools/blueprint.js +29 -12
  51. package/dist/tools/blueprint.js.map +1 -1
  52. package/dist/tools/chooser.js +4 -1
  53. package/dist/tools/chooser.js.map +1 -1
  54. package/dist/tools/editor.js +81 -43
  55. package/dist/tools/editor.js.map +1 -1
  56. package/dist/tools/feedback.js +214 -48
  57. package/dist/tools/feedback.js.map +1 -1
  58. package/dist/tools/foliage.js +27 -1
  59. package/dist/tools/foliage.js.map +1 -1
  60. package/dist/tools/gameplay.js +38 -11
  61. package/dist/tools/gameplay.js.map +1 -1
  62. package/dist/tools/gas.js +17 -5
  63. package/dist/tools/gas.js.map +1 -1
  64. package/dist/tools/landscape.js +22 -7
  65. package/dist/tools/landscape.js.map +1 -1
  66. package/dist/tools/level.js +322 -51
  67. package/dist/tools/level.js.map +1 -1
  68. package/dist/tools/material.js +10 -4
  69. package/dist/tools/material.js.map +1 -1
  70. package/dist/tools/niagara.js +3 -2
  71. package/dist/tools/niagara.js.map +1 -1
  72. package/dist/tools/pcg.js +6 -5
  73. package/dist/tools/pcg.js.map +1 -1
  74. package/dist/tools/project.js +47 -20
  75. package/dist/tools/project.js.map +1 -1
  76. package/dist/types.d.ts +21 -0
  77. package/dist/types.js +46 -3
  78. package/dist/types.js.map +1 -1
  79. package/dist/ue-mcp.default.yml +493 -125
  80. package/package.json +2 -2
  81. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  82. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  83. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  84. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  85. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  86. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  87. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  242. package/skills/ue-mcp-animation/SKILL.md +154 -0
  243. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -63,6 +63,250 @@
63
63
  #include "Materials/MaterialInterface.h"
64
64
  #include "EngineUtils.h"
65
65
  #include "Widgets/SViewport.h"
66
+ #include "UObject/SoftObjectPath.h"
67
+
68
+ // ── WidgetBlueprint resolution (#972) ────────────────────────────────────────
69
+ // See the contract and the mechanism note on MCPWidget in WidgetHandlers.h.
70
+ // Everything here is defined exactly once, in this translation unit, and
71
+ // declared in the shared header, because the module is a unity build and a
72
+ // second file-local copy would be a redefinition on some grouping.
73
+ namespace MCPWidget
74
+ {
75
+
76
+ /**
77
+ * "WidgetBlueprint'/Game/UI/WBP_Foo.WBP_Foo'", "/Game/UI/WBP_Foo",
78
+ * "/Game/UI/WBP_Foo.WBP_Foo" and "/Game/UI/WBP_Foo.WBP_Foo_C" all normalise to
79
+ * "/Game/UI/WBP_Foo.WBP_Foo". A path with no object part gets one inferred from
80
+ * the package name, which is the convention every asset in the content browser
81
+ * follows.
82
+ */
83
+ static FString NormalizeWidgetBlueprintObjectPath(const FString& InAssetPath)
84
+ {
85
+ FString Path = InAssetPath;
86
+ Path.TrimStartAndEndInline();
87
+ if (Path.IsEmpty()) return Path;
88
+
89
+ // "Class'/Game/...'" and "Class /Game/..." export forms.
90
+ int32 QuoteIndex = INDEX_NONE;
91
+ if (Path.FindChar(TCHAR('\''), QuoteIndex))
92
+ {
93
+ Path = Path.RightChop(QuoteIndex + 1);
94
+ Path.RemoveFromEnd(TEXT("'"));
95
+ }
96
+ else
97
+ {
98
+ int32 SpaceIndex = INDEX_NONE;
99
+ if (Path.FindChar(TCHAR(' '), SpaceIndex))
100
+ {
101
+ Path = Path.RightChop(SpaceIndex + 1);
102
+ }
103
+ }
104
+ Path.TrimStartAndEndInline();
105
+
106
+ // Subobject part ("Package.Asset:Inner") is not ours to resolve.
107
+ int32 ColonIndex = INDEX_NONE;
108
+ if (Path.FindChar(TCHAR(':'), ColonIndex))
109
+ {
110
+ Path = Path.Left(ColonIndex);
111
+ }
112
+
113
+ const int32 LastSlash = Path.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
114
+ const int32 LastDot = Path.Find(TEXT("."), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
115
+ if (LastDot <= LastSlash)
116
+ {
117
+ // No object part. Infer it from the package name.
118
+ const FString AssetName = FPackageName::GetLongPackageAssetName(Path);
119
+ if (AssetName.IsEmpty()) return FString();
120
+ return Path + TEXT(".") + AssetName;
121
+ }
122
+
123
+ // "/Game/UI/WBP_Foo.WBP_Foo_C" names the generated class. Keep the trailing
124
+ // _C off the object path; the class is resolved from the blueprint anyway.
125
+ FString ObjectName = Path.RightChop(LastDot + 1);
126
+ if (ObjectName.EndsWith(TEXT("_C"), ESearchCase::CaseSensitive))
127
+ {
128
+ ObjectName.LeftChopInline(2);
129
+ Path = Path.Left(LastDot + 1) + ObjectName;
130
+ }
131
+ return Path;
132
+ }
133
+
134
+ /**
135
+ * Accept a candidate only if it is a WidgetBlueprint the editor still consults.
136
+ *
137
+ * RF_NewerVersionExists is the flag a package reload leaves on the object it
138
+ * replaced. Handing one of those back is exactly the failure #972 describes
139
+ * after asset(force_reload): every write lands on a corpse, the real asset
140
+ * never changes, and nothing reports an error. IsValid covers null and garbage.
141
+ */
142
+ static UWidgetBlueprint* AsLiveWidgetBlueprint(UObject* Candidate, FString& OutFoundClass)
143
+ {
144
+ if (!IsValid(Candidate)) return nullptr;
145
+ if (Candidate->HasAnyFlags(RF_NewerVersionExists)) return nullptr;
146
+
147
+ if (UWidgetBlueprint* AsBlueprint = Cast<UWidgetBlueprint>(Candidate))
148
+ {
149
+ return AsBlueprint;
150
+ }
151
+ // A caller who passed the generated class path gets the blueprint behind it.
152
+ if (UClass* AsClass = Cast<UClass>(Candidate))
153
+ {
154
+ if (UWidgetBlueprint* Generated = Cast<UWidgetBlueprint>(AsClass->ClassGeneratedBy))
155
+ {
156
+ return Generated;
157
+ }
158
+ }
159
+ OutFoundClass = Candidate->GetClass()->GetName();
160
+ return nullptr;
161
+ }
162
+
163
+ /** True when the AssetRegistry or the filesystem says the asset is really there. */
164
+ static bool WidgetBlueprintAssetExists(const FString& ObjectPath, const FString& PackageName)
165
+ {
166
+ if (!PackageName.IsEmpty() && FPackageName::DoesPackageExist(PackageName))
167
+ {
168
+ return true;
169
+ }
170
+ // An asset created this session and not yet saved has no file, so the
171
+ // registry is the only witness. Never load anything to answer this.
172
+ if (FAssetRegistryModule* ARM =
173
+ FModuleManager::GetModulePtr<FAssetRegistryModule>(TEXT("AssetRegistry")))
174
+ {
175
+ const FAssetData Data = ARM->Get().GetAssetByObjectPath(FSoftObjectPath(ObjectPath));
176
+ if (Data.IsValid()) return true;
177
+ }
178
+ return false;
179
+ }
180
+
181
+ FWidgetBlueprintResolve ResolveWidgetBlueprint(const FString& AssetPath)
182
+ {
183
+ FWidgetBlueprintResolve Out;
184
+ Out.ObjectPath = NormalizeWidgetBlueprintObjectPath(AssetPath);
185
+ if (Out.ObjectPath.IsEmpty())
186
+ {
187
+ Out.Failure = EWidgetBlueprintResolveFailure::NotFound;
188
+ return Out;
189
+ }
190
+
191
+ const FString PackageName = FPackageName::ObjectPathToPackageName(Out.ObjectPath);
192
+ Out.bAssetExists = WidgetBlueprintAssetExists(Out.ObjectPath, PackageName);
193
+
194
+ // Step 1. The object hash, first and cheapest. An asset already in memory
195
+ // answers here without the AssetRegistry round trip UEditorAssetLibrary
196
+ // makes, which is the step that was intermittently returning null.
197
+ if (UWidgetBlueprint* Live =
198
+ AsLiveWidgetBlueprint(FindObject<UObject>(nullptr, *Out.ObjectPath), Out.FoundClass))
199
+ {
200
+ Out.Blueprint = Live;
201
+ return Out;
202
+ }
203
+
204
+ // Step 2. The historical path. Kept because it understands more path
205
+ // spellings than the object hash does and it is what every other handler
206
+ // in this plugin uses.
207
+ if (UWidgetBlueprint* Live =
208
+ AsLiveWidgetBlueprint(UEditorAssetLibrary::LoadAsset(AssetPath), Out.FoundClass))
209
+ {
210
+ Out.Blueprint = Live;
211
+ return Out;
212
+ }
213
+
214
+ // Step 3. Load the object directly, bypassing the registry entirely. Only
215
+ // worth attempting when something really is there: StaticLoadObject on a
216
+ // path with no package behind it can force a blocking package search.
217
+ if (Out.bAssetExists)
218
+ {
219
+ if (UWidgetBlueprint* Live =
220
+ AsLiveWidgetBlueprint(LoadObject<UObject>(nullptr, *Out.ObjectPath), Out.FoundClass))
221
+ {
222
+ Out.Blueprint = Live;
223
+ return Out;
224
+ }
225
+
226
+ // Step 4. "Failed to find object 'Object /Game/x/WBP_Foo.WBP_Foo'" in
227
+ // the log means the package resolved but the object lookup inside it
228
+ // did not. Load the package explicitly and look again.
229
+ if (!PackageName.IsEmpty())
230
+ {
231
+ if (UPackage* Package = LoadPackage(nullptr, *PackageName, LOAD_None))
232
+ {
233
+ Package->FullyLoad();
234
+ const FString ObjectName = FPackageName::ObjectPathToObjectName(Out.ObjectPath);
235
+ if (UWidgetBlueprint* Live =
236
+ AsLiveWidgetBlueprint(FindObject<UObject>(Package, *ObjectName), Out.FoundClass))
237
+ {
238
+ Out.Blueprint = Live;
239
+ return Out;
240
+ }
241
+ }
242
+ }
243
+ }
244
+
245
+ if (!Out.FoundClass.IsEmpty())
246
+ {
247
+ Out.Failure = EWidgetBlueprintResolveFailure::WrongType;
248
+ }
249
+ else if (Out.bAssetExists)
250
+ {
251
+ Out.Failure = EWidgetBlueprintResolveFailure::Unresolvable;
252
+ }
253
+ else
254
+ {
255
+ Out.Failure = EWidgetBlueprintResolveFailure::NotFound;
256
+ }
257
+ return Out;
258
+ }
259
+
260
+ TSharedPtr<FJsonValue> WidgetBlueprintResolveError(
261
+ const FString& AssetPath,
262
+ const FWidgetBlueprintResolve& Resolved)
263
+ {
264
+ switch (Resolved.Failure)
265
+ {
266
+ case EWidgetBlueprintResolveFailure::WrongType:
267
+ return MCPError(FString::Printf(
268
+ TEXT("'%s' is a %s, not a WidgetBlueprint."),
269
+ *AssetPath, *Resolved.FoundClass));
270
+
271
+ case EWidgetBlueprintResolveFailure::Unresolvable:
272
+ // The distinction the caller needs: the asset is there, so retrying or
273
+ // reloading the bridge is the move. Renaming or re-creating it is not.
274
+ return MCPError(FString::Printf(
275
+ TEXT("'%s' exists but could not be resolved to a live WidgetBlueprint on this call. ")
276
+ TEXT("The object handle went stale (a package reload or a GC pass replaced it), the asset is not missing. ")
277
+ TEXT("Retry the call; if it keeps failing, editor(action=\"reload_bridge\") clears it."),
278
+ *AssetPath));
279
+
280
+ case EWidgetBlueprintResolveFailure::NotFound:
281
+ default:
282
+ return MCPError(FString::Printf(
283
+ TEXT("No asset exists at '%s'. Nothing of that name is in the AssetRegistry and no package of that name is on disk. ")
284
+ TEXT("Check the path with widget(action=\"list\") or asset(action=\"search\")."),
285
+ *AssetPath));
286
+ }
287
+ }
288
+
289
+ UWidgetBlueprint* ResolveWidgetBlueprintOrError(
290
+ const FString& AssetPath,
291
+ TSharedPtr<FJsonValue>& OutError)
292
+ {
293
+ const FWidgetBlueprintResolve Resolved = ResolveWidgetBlueprint(AssetPath);
294
+ if (!Resolved.Blueprint)
295
+ {
296
+ OutError = WidgetBlueprintResolveError(AssetPath, Resolved);
297
+ }
298
+ return Resolved.Blueprint;
299
+ }
300
+
301
+ TSharedPtr<FJsonValue> MissingWidgetTreeError(const FString& AssetPath)
302
+ {
303
+ return MCPError(FString::Printf(
304
+ TEXT("WidgetBlueprint '%s' resolved but has no WidgetTree. The asset is loaded and broken, not missing; ")
305
+ TEXT("open it in the editor or re-create it."),
306
+ *AssetPath));
307
+ }
308
+
309
+ }
66
310
 
67
311
  void FWidgetHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
68
312
  {
@@ -200,12 +444,9 @@ TSharedPtr<FJsonValue> FWidgetHandlers::ReadWidgetTree(const TSharedPtr<FJsonObj
200
444
  FString AssetPath;
201
445
  if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
202
446
 
203
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
204
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
205
- if (!WidgetBP)
206
- {
207
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
208
- }
447
+ TSharedPtr<FJsonValue> ResolveError;
448
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
449
+ if (!WidgetBP) return ResolveError;
209
450
 
210
451
  auto Result = MCPSuccess();
211
452
 
@@ -332,11 +573,18 @@ TSharedPtr<FJsonValue> FWidgetHandlers::RunEditorUtilityWidget(const TSharedPtr<
332
573
  FString AssetPath;
333
574
  if (auto Err = RequireStringAlt(Params, TEXT("assetPath"), TEXT("path"), AssetPath)) return Err;
334
575
 
335
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
336
- UEditorUtilityWidgetBlueprint* EUWidget = Cast<UEditorUtilityWidgetBlueprint>(LoadedAsset);
576
+ // UEditorUtilityWidgetBlueprint derives from UWidgetBlueprint, so it goes
577
+ // through the same revalidating resolver and gets the same stale-handle
578
+ // recovery every other widget action gets (#972).
579
+ TSharedPtr<FJsonValue> ResolveError;
580
+ UWidgetBlueprint* ResolvedBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
581
+ if (!ResolvedBP) return ResolveError;
582
+ UEditorUtilityWidgetBlueprint* EUWidget = Cast<UEditorUtilityWidgetBlueprint>(ResolvedBP);
337
583
  if (!EUWidget)
338
584
  {
339
- return MCPError(FString::Printf(TEXT("Failed to load EditorUtilityWidgetBlueprint at '%s'"), *AssetPath));
585
+ return MCPError(FString::Printf(
586
+ TEXT("'%s' is a %s, not an EditorUtilityWidgetBlueprint."),
587
+ *AssetPath, *ResolvedBP->GetClass()->GetName()));
340
588
  }
341
589
 
342
590
  UEditorUtilitySubsystem* Subsystem = GEditor->GetEditorSubsystem<UEditorUtilitySubsystem>();
@@ -604,17 +852,11 @@ TSharedPtr<FJsonValue> FWidgetHandlers::AddWidget(const TSharedPtr<FJsonObject>&
604
852
  FString ParentWidgetName = OptionalString(Params, TEXT("parentWidgetName"));
605
853
 
606
854
  // ── Load the WidgetBlueprint ──
607
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
608
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
609
- if (!WidgetBP)
610
- {
611
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
612
- }
855
+ TSharedPtr<FJsonValue> ResolveError;
856
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
857
+ if (!WidgetBP) return ResolveError;
613
858
 
614
- if (!WidgetBP->WidgetTree)
615
- {
616
- return MCPError(TEXT("WidgetTree is null"));
617
- }
859
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
618
860
 
619
861
  // ── Resolve the UClass ──
620
862
  UClass* WClass = ResolveWidgetClass(WidgetClassName);
@@ -784,17 +1026,11 @@ TSharedPtr<FJsonValue> FWidgetHandlers::RemoveWidget(const TSharedPtr<FJsonObjec
784
1026
  FString WidgetName;
785
1027
  if (auto Err = RequireString(Params, TEXT("widgetName"), WidgetName)) return Err;
786
1028
 
787
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
788
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
789
- if (!WidgetBP)
790
- {
791
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
792
- }
1029
+ TSharedPtr<FJsonValue> ResolveError;
1030
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1031
+ if (!WidgetBP) return ResolveError;
793
1032
 
794
- if (!WidgetBP->WidgetTree)
795
- {
796
- return MCPError(TEXT("WidgetTree is null"));
797
- }
1033
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
798
1034
 
799
1035
  // Find the widget
800
1036
  UWidget* FoundWidget = nullptr;
@@ -880,12 +1116,10 @@ TSharedPtr<FJsonValue> FWidgetHandlers::MoveWidget(const TSharedPtr<FJsonObject>
880
1116
  FString NewParentName;
881
1117
  if (auto Err = RequireStringAlt(Params, TEXT("newParentWidgetName"), TEXT("parentWidgetName"), NewParentName)) return Err;
882
1118
 
883
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
884
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
885
- if (!WidgetBP || !WidgetBP->WidgetTree)
886
- {
887
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
888
- }
1119
+ TSharedPtr<FJsonValue> ResolveError;
1120
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1121
+ if (!WidgetBP) return ResolveError;
1122
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
889
1123
 
890
1124
  // Find the widget to move
891
1125
  UWidget* WidgetToMove = nullptr;
@@ -1002,12 +1236,10 @@ TSharedPtr<FJsonValue> FWidgetHandlers::SetRoot(const TSharedPtr<FJsonObject>& P
1002
1236
  FString WidgetName;
1003
1237
  if (auto Err = RequireString(Params, TEXT("widgetName"), WidgetName)) return Err;
1004
1238
 
1005
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
1006
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
1007
- if (!WidgetBP || !WidgetBP->WidgetTree)
1008
- {
1009
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
1010
- }
1239
+ TSharedPtr<FJsonValue> ResolveError;
1240
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1241
+ if (!WidgetBP) return ResolveError;
1242
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
1011
1243
 
1012
1244
  UWidget* NewRoot = nullptr;
1013
1245
  WidgetBP->WidgetTree->ForEachWidget([&](UWidget* W)
@@ -1068,12 +1300,10 @@ TSharedPtr<FJsonValue> FWidgetHandlers::WrapRoot(const TSharedPtr<FJsonObject>&
1068
1300
  FString WrapperClassName;
1069
1301
  if (auto Err = RequireStringAlt(Params, TEXT("wrapperClass"), TEXT("widgetClass"), WrapperClassName)) return Err;
1070
1302
 
1071
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
1072
- UWidgetBlueprint* WidgetBP = Cast<UWidgetBlueprint>(LoadedAsset);
1073
- if (!WidgetBP || !WidgetBP->WidgetTree)
1074
- {
1075
- return MCPError(FString::Printf(TEXT("Failed to load WidgetBlueprint at '%s'"), *AssetPath));
1076
- }
1303
+ TSharedPtr<FJsonValue> ResolveError;
1304
+ UWidgetBlueprint* WidgetBP = MCPWidget::ResolveWidgetBlueprintOrError(AssetPath, ResolveError);
1305
+ if (!WidgetBP) return ResolveError;
1306
+ if (!WidgetBP->WidgetTree) return MCPWidget::MissingWidgetTreeError(AssetPath);
1077
1307
 
1078
1308
  UWidget* OldRoot = WidgetBP->WidgetTree->RootWidget;
1079
1309
  if (!OldRoot)
@@ -1200,6 +1430,14 @@ TSharedPtr<FJsonValue> FWidgetHandlers::ListWidgetClasses(const TSharedPtr<FJson
1200
1430
  // ─────────────────────────────────────────────────────────────
1201
1431
  namespace WidgetRuntime_Internal
1202
1432
  {
1433
+ // These names are deliberately widget-specific. This namespace is opened with
1434
+ // a block-scope using-directive at several call sites, which injects its names
1435
+ // into the global namespace at exactly the point where a unity-blob neighbour's
1436
+ // anonymous-namespace definitions live. GasHandlers_Runtime.cpp defines a
1437
+ // ResolveRuntimeWorld and EditorHandlers_PIERuntime.cpp a VectorJson; those
1438
+ // pairs resolved as overloads only by arity and by the absence of an implicit
1439
+ // FVector/FVector2D conversion. audit:unity cannot see this class of collision,
1440
+ // because it walks anonymous-namespace bodies only.
1203
1441
  struct FDerivedClipState
1204
1442
  {
1205
1443
  bool bHasRect = false;
@@ -1239,7 +1477,7 @@ namespace WidgetRuntime_Internal
1239
1477
  static TMap<FString, uint64> PreviousLayoutCaptureFrames;
1240
1478
  static uint64 LayoutCaptureSequence = 0;
1241
1479
 
1242
- static UWorld* ResolveRuntimeWorld()
1480
+ static UWorld* ResolveWidgetRuntimeWorld()
1243
1481
  {
1244
1482
  if (!GEditor) return nullptr;
1245
1483
  FWorldContext* PIE = GEditor->GetPIEWorldContext();
@@ -1274,7 +1512,7 @@ namespace WidgetRuntime_Internal
1274
1512
  return TEXT("Unknown");
1275
1513
  }
1276
1514
 
1277
- static TSharedPtr<FJsonObject> VectorJson(const FVector2D& Value)
1515
+ static TSharedPtr<FJsonObject> WidgetVector2DJson(const FVector2D& Value)
1278
1516
  {
1279
1517
  TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
1280
1518
  Obj->SetNumberField(TEXT("x"), Value.X);
@@ -1348,13 +1586,13 @@ namespace WidgetRuntime_Internal
1348
1586
  const FVector2D Alignment = CanvasSlot->GetAlignment();
1349
1587
  TSharedPtr<FJsonObject> Canvas = MakeShared<FJsonObject>();
1350
1588
  TSharedPtr<FJsonObject> AnchorsObj = MakeShared<FJsonObject>();
1351
- AnchorsObj->SetObjectField(TEXT("minimum"), VectorJson(Anchors.Minimum));
1352
- AnchorsObj->SetObjectField(TEXT("maximum"), VectorJson(Anchors.Maximum));
1589
+ AnchorsObj->SetObjectField(TEXT("minimum"), WidgetVector2DJson(Anchors.Minimum));
1590
+ AnchorsObj->SetObjectField(TEXT("maximum"), WidgetVector2DJson(Anchors.Maximum));
1353
1591
  AnchorsObj->SetBoolField(TEXT("stretchedHorizontally"), !FMath::IsNearlyEqual(Anchors.Minimum.X, Anchors.Maximum.X));
1354
1592
  AnchorsObj->SetBoolField(TEXT("stretchedVertically"), !FMath::IsNearlyEqual(Anchors.Minimum.Y, Anchors.Maximum.Y));
1355
1593
  Canvas->SetObjectField(TEXT("anchors"), AnchorsObj);
1356
1594
  Canvas->SetObjectField(TEXT("offsets"), MarginJson(Offsets));
1357
- Canvas->SetObjectField(TEXT("alignment"), VectorJson(Alignment));
1595
+ Canvas->SetObjectField(TEXT("alignment"), WidgetVector2DJson(Alignment));
1358
1596
  Canvas->SetBoolField(TEXT("autoSize"), CanvasSlot->GetAutoSize());
1359
1597
  Canvas->SetNumberField(TEXT("zOrder"), CanvasSlot->GetZOrder());
1360
1598
  SlotObj->SetObjectField(TEXT("canvas"), Canvas);
@@ -1577,21 +1815,21 @@ namespace WidgetRuntime_Internal
1577
1815
 
1578
1816
  TSharedPtr<FJsonObject> GeometryObj = MakeShared<FJsonObject>();
1579
1817
  GeometryObj->SetBoolField(TEXT("hasCachedSlateWidget"), bHasCachedSlateWidget);
1580
- GeometryObj->SetObjectField(TEXT("desiredSize"), VectorJson(DesiredSize));
1581
- GeometryObj->SetObjectField(TEXT("localSize"), VectorJson(LocalSize));
1582
- GeometryObj->SetObjectField(TEXT("absoluteSize"), VectorJson(AbsoluteSize));
1583
- GeometryObj->SetObjectField(TEXT("absolutePosition"), VectorJson(AbsolutePosition));
1818
+ GeometryObj->SetObjectField(TEXT("desiredSize"), WidgetVector2DJson(DesiredSize));
1819
+ GeometryObj->SetObjectField(TEXT("localSize"), WidgetVector2DJson(LocalSize));
1820
+ GeometryObj->SetObjectField(TEXT("absoluteSize"), WidgetVector2DJson(AbsoluteSize));
1821
+ GeometryObj->SetObjectField(TEXT("absolutePosition"), WidgetVector2DJson(AbsolutePosition));
1584
1822
  GeometryObj->SetObjectField(TEXT("layoutBoundingRect"), RectJson(LayoutRect));
1585
1823
  GeometryObj->SetObjectField(TEXT("renderBoundingRect"), RectJson(RenderRect));
1586
1824
  GeometryObj->SetNumberField(TEXT("accumulatedLayoutScale"), Geometry.GetAccumulatedLayoutTransform().GetScale());
1587
1825
  Obj->SetObjectField(TEXT("geometry"), GeometryObj);
1588
1826
 
1589
1827
  TSharedPtr<FJsonObject> TransformObj = MakeShared<FJsonObject>();
1590
- TransformObj->SetObjectField(TEXT("translation"), VectorJson(RenderTransform.Translation));
1591
- TransformObj->SetObjectField(TEXT("scale"), VectorJson(RenderTransform.Scale));
1592
- TransformObj->SetObjectField(TEXT("shear"), VectorJson(RenderTransform.Shear));
1828
+ TransformObj->SetObjectField(TEXT("translation"), WidgetVector2DJson(RenderTransform.Translation));
1829
+ TransformObj->SetObjectField(TEXT("scale"), WidgetVector2DJson(RenderTransform.Scale));
1830
+ TransformObj->SetObjectField(TEXT("shear"), WidgetVector2DJson(RenderTransform.Shear));
1593
1831
  TransformObj->SetNumberField(TEXT("angleDegrees"), RenderTransform.Angle);
1594
- TransformObj->SetObjectField(TEXT("pivot"), VectorJson(Widget->GetRenderTransformPivot()));
1832
+ TransformObj->SetObjectField(TEXT("pivot"), WidgetVector2DJson(Widget->GetRenderTransformPivot()));
1595
1833
  Obj->SetObjectField(TEXT("renderTransform"), TransformObj);
1596
1834
 
1597
1835
  TSharedPtr<FJsonObject> ClipObj = MakeShared<FJsonObject>();
@@ -1742,10 +1980,10 @@ namespace WidgetRuntime_Internal
1742
1980
  bSlotChanged;
1743
1981
  DeltaObj->SetBoolField(TEXT("hasPreviousCapture"), true);
1744
1982
  DeltaObj->SetBoolField(TEXT("changed"), bChanged);
1745
- DeltaObj->SetObjectField(TEXT("absolutePositionDelta"), VectorJson(PositionDelta));
1746
- DeltaObj->SetObjectField(TEXT("localSizeDelta"), VectorJson(LocalSizeDelta));
1747
- DeltaObj->SetObjectField(TEXT("absoluteSizeDelta"), VectorJson(AbsoluteSizeDelta));
1748
- DeltaObj->SetObjectField(TEXT("desiredSizeDelta"), VectorJson(DesiredSizeDelta));
1983
+ DeltaObj->SetObjectField(TEXT("absolutePositionDelta"), WidgetVector2DJson(PositionDelta));
1984
+ DeltaObj->SetObjectField(TEXT("localSizeDelta"), WidgetVector2DJson(LocalSizeDelta));
1985
+ DeltaObj->SetObjectField(TEXT("absoluteSizeDelta"), WidgetVector2DJson(AbsoluteSizeDelta));
1986
+ DeltaObj->SetObjectField(TEXT("desiredSizeDelta"), WidgetVector2DJson(DesiredSizeDelta));
1749
1987
  DeltaObj->SetBoolField(TEXT("slotPropertiesChanged"), bSlotChanged);
1750
1988
  if (bChanged)
1751
1989
  {
@@ -1846,7 +2084,7 @@ TSharedPtr<FJsonValue> FWidgetHandlers::ListRuntimeWidgets(const TSharedPtr<FJso
1846
2084
  {
1847
2085
  using namespace WidgetRuntime_Internal;
1848
2086
 
1849
- UWorld* World = ResolveRuntimeWorld();
2087
+ UWorld* World = ResolveWidgetRuntimeWorld();
1850
2088
  if (!World)
1851
2089
  {
1852
2090
  return MCPError(TEXT("No PIE world available. Is Play-In-Editor running?"));
@@ -1898,7 +2136,7 @@ TSharedPtr<FJsonValue> FWidgetHandlers::GetRuntimeWidget(const TSharedPtr<FJsonO
1898
2136
  {
1899
2137
  using namespace WidgetRuntime_Internal;
1900
2138
 
1901
- UWorld* World = ResolveRuntimeWorld();
2139
+ UWorld* World = ResolveWidgetRuntimeWorld();
1902
2140
  if (!World)
1903
2141
  {
1904
2142
  return MCPError(TEXT("No PIE world available. Is Play-In-Editor running?"));
@@ -2094,7 +2332,7 @@ TSharedPtr<FJsonValue> FWidgetHandlers::GetRuntimeWidget(const TSharedPtr<FJsonO
2094
2332
  TSharedPtr<FJsonValue> FWidgetHandlers::AddWidgetToViewport(const TSharedPtr<FJsonObject>& Params)
2095
2333
  {
2096
2334
  using namespace WidgetRuntime_Internal;
2097
- UWorld* World = ResolveRuntimeWorld();
2335
+ UWorld* World = ResolveWidgetRuntimeWorld();
2098
2336
  if (!World)
2099
2337
  {
2100
2338
  return MCPError(TEXT("No PIE world available. Start Play-In-Editor first (editor pie_control action=play)."));
@@ -2151,7 +2389,7 @@ TSharedPtr<FJsonValue> FWidgetHandlers::AddWidgetToViewport(const TSharedPtr<FJs
2151
2389
  TSharedPtr<FJsonValue> FWidgetHandlers::InvokeRuntimeWidgetFunction(const TSharedPtr<FJsonObject>& Params)
2152
2390
  {
2153
2391
  using namespace WidgetRuntime_Internal;
2154
- UWorld* World = ResolveRuntimeWorld();
2392
+ UWorld* World = ResolveWidgetRuntimeWorld();
2155
2393
  if (!World)
2156
2394
  {
2157
2395
  return MCPError(TEXT("No PIE world available. Is Play-In-Editor running?"));
@@ -2242,7 +2480,7 @@ TSharedPtr<FJsonValue> FWidgetHandlers::GetRuntimeDelegates(const TSharedPtr<FJs
2242
2480
  {
2243
2481
  using namespace WidgetRuntime_Internal;
2244
2482
 
2245
- UWorld* World = ResolveRuntimeWorld();
2483
+ UWorld* World = ResolveWidgetRuntimeWorld();
2246
2484
  if (!World)
2247
2485
  {
2248
2486
  return MCPError(TEXT("No PIE world available. Is Play-In-Editor running?"));
@@ -4,6 +4,90 @@
4
4
  #include "Dom/JsonValue.h"
5
5
  #include "Dom/JsonObject.h"
6
6
 
7
+ class UWidgetBlueprint;
8
+
9
+ /**
10
+ * Shared WidgetBlueprint resolution for every widget action (#972).
11
+ *
12
+ * Symptom: building a WidgetBlueprint incrementally failed every second or
13
+ * third add_widget with "Failed to load WidgetBlueprint", on an asset that was
14
+ * on disk, was in the AssetRegistry, and that asset(search) could see. Then it
15
+ * wedged permanently, and asset(force_reload) made it worse while
16
+ * editor(reload_bridge) cleared it instantly.
17
+ *
18
+ * Mechanism, confirmed by reading every widget translation unit: the handlers
19
+ * hold NO pointer between calls. Each one re-resolved from the path through
20
+ * UEditorAssetLibrary::LoadAsset, which is not a plain load. It converts the
21
+ * path, looks the asset up in the AssetRegistry, and loads the FAssetData it
22
+ * got back. That round trip is what intermittently answers null: the registry
23
+ * entry a call resolved through can be mid-rescan, and after a package reload
24
+ * it names an object that has been consigned to oblivion (RF_NewerVersionExists)
25
+ * while a new one holds the name. So the stale handle is real, it just lives in
26
+ * the engine's asset plumbing rather than in a static in this plugin, and
27
+ * "resolve fresh per call" was already true and not enough on its own.
28
+ *
29
+ * The resolver below therefore does three things the old one-liner did not:
30
+ *
31
+ * 1. Asks the object hash FIRST (FindObject on the object path). An asset
32
+ * already in memory answers without touching the registry round trip at
33
+ * all, which is the step that removes the intermittency.
34
+ * 2. Revalidates whatever it gets. IsValid plus an RF_NewerVersionExists
35
+ * check, so a reload's corpse is never handed to a caller who would then
36
+ * mutate an object the editor no longer consults.
37
+ * 3. Falls through progressively (EditorAssetLibrary, direct LoadObject,
38
+ * explicit LoadPackage then look inside it) instead of giving up on the
39
+ * first null.
40
+ *
41
+ * It also separates "there is no such asset" from "the asset is there and the
42
+ * handle went stale", because the caller's next move differs: fix the path, or
43
+ * retry / reload the bridge.
44
+ */
45
+ namespace MCPWidget
46
+ {
47
+
48
+ enum class EWidgetBlueprintResolveFailure : uint8
49
+ {
50
+ /** Resolved. */
51
+ None,
52
+ /** Nothing in the AssetRegistry and no package of that name on disk. */
53
+ NotFound,
54
+ /** The path names something real that is not a WidgetBlueprint. */
55
+ WrongType,
56
+ /** The asset exists but no live object could be reached this call. */
57
+ Unresolvable,
58
+ };
59
+
60
+ struct FWidgetBlueprintResolve
61
+ {
62
+ UWidgetBlueprint* Blueprint = nullptr;
63
+ EWidgetBlueprintResolveFailure Failure = EWidgetBlueprintResolveFailure::None;
64
+ /** The normalised object path that was searched for. */
65
+ FString ObjectPath;
66
+ /** Class of the object that was found, when Failure is WrongType. */
67
+ FString FoundClass;
68
+ /** True when the registry or the filesystem says the asset is really there. */
69
+ bool bAssetExists = false;
70
+ };
71
+
72
+ /** Resolve fresh, revalidate, and say why when it fails. Never caches. */
73
+ FWidgetBlueprintResolve ResolveWidgetBlueprint(const FString& AssetPath);
74
+
75
+ /** Error JSON for a failed resolve, worded per failure kind. */
76
+ TSharedPtr<FJsonValue> WidgetBlueprintResolveError(
77
+ const FString& AssetPath,
78
+ const FWidgetBlueprintResolve& Resolved);
79
+
80
+ /** The `if (!WidgetBP) return Err;` shape every handler wants. */
81
+ UWidgetBlueprint* ResolveWidgetBlueprintOrError(
82
+ const FString& AssetPath,
83
+ TSharedPtr<FJsonValue>& OutError);
84
+
85
+ /** A resolved blueprint whose WidgetTree is missing is a broken asset, not a
86
+ * failed load. Separate message so the two stop looking identical. */
87
+ TSharedPtr<FJsonValue> MissingWidgetTreeError(const FString& AssetPath);
88
+
89
+ }
90
+
7
91
  class FWidgetHandlers
8
92
  {
9
93
  public:
@@ -18,49 +18,16 @@
18
18
 
19
19
  namespace
20
20
  {
21
- FString MakeExtractionWidgetBlueprintObjectPath(const FString& InAssetPath)
22
- {
23
- FString ObjectPath = InAssetPath;
24
- ObjectPath.TrimStartAndEndInline();
25
- ObjectPath.RemoveFromStart(TEXT("WidgetBlueprint'"));
26
- ObjectPath.RemoveFromEnd(TEXT("'"));
27
-
28
- const int32 LastSlashIndex = ObjectPath.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
29
- const int32 DotIndex = ObjectPath.Find(TEXT("."), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
30
- if (DotIndex <= LastSlashIndex)
31
- {
32
- const FString AssetName = FPackageName::GetLongPackageAssetName(ObjectPath);
33
- if (!AssetName.IsEmpty())
34
- {
35
- ObjectPath += TEXT(".") + AssetName;
36
- }
37
- }
38
- return ObjectPath;
39
- }
40
-
21
+ /**
22
+ * Extraction's loader. Delegates to the one shared resolver (#972) rather than
23
+ * carrying a second path-normalising, second-chance-loading copy of it: the
24
+ * module is a unity build, and two file-local copies of the same helper both
25
+ * collide and drift. A null return is still the normal "destination does not
26
+ * exist yet, create it" outcome here.
27
+ */
41
28
  UWidgetBlueprint* LoadWidgetBlueprintForExtraction(const FString& AssetPath)
42
29
  {
43
- UObject* LoadedAsset = UEditorAssetLibrary::LoadAsset(AssetPath);
44
- // A missing destination is the normal create path. StaticLoadObject on a
45
- // non-existent package can force a blocking package search, so only use the
46
- // fallback for explicit object/generated-class paths supplied by callers.
47
- if (!LoadedAsset && AssetPath.Contains(TEXT(".")))
48
- {
49
- const FString ObjectPath = MakeExtractionWidgetBlueprintObjectPath(AssetPath);
50
- LoadedAsset = StaticLoadObject(UObject::StaticClass(), nullptr, *ObjectPath);
51
- }
52
-
53
- if (UWidgetBlueprint* WidgetBlueprint = Cast<UWidgetBlueprint>(LoadedAsset))
54
- {
55
- return WidgetBlueprint;
56
- }
57
-
58
- if (UClass* LoadedClass = Cast<UClass>(LoadedAsset))
59
- {
60
- return Cast<UWidgetBlueprint>(LoadedClass->ClassGeneratedBy);
61
- }
62
-
63
- return nullptr;
30
+ return MCPWidget::ResolveWidgetBlueprint(AssetPath).Blueprint;
64
31
  }
65
32
 
66
33
  struct FExtractedWidgetPlanEntry