ue-mcp 1.2.3 → 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 (255) 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/index.js +41 -2
  34. package/dist/index.js.map +1 -1
  35. package/dist/lean-context.js +8 -3
  36. package/dist/lean-context.js.map +1 -1
  37. package/dist/locking.js +11 -2
  38. package/dist/locking.js.map +1 -1
  39. package/dist/plugin/loader.d.ts +4 -0
  40. package/dist/plugin/loader.js +22 -8
  41. package/dist/plugin/loader.js.map +1 -1
  42. package/dist/plugin/manifest.d.ts +72 -52
  43. package/dist/plugin/manifest.js +118 -21
  44. package/dist/plugin/manifest.js.map +1 -1
  45. package/dist/plugin-cli.js +16 -3
  46. package/dist/plugin-cli.js.map +1 -1
  47. package/dist/project.d.ts +11 -0
  48. package/dist/project.js +23 -2
  49. package/dist/project.js.map +1 -1
  50. package/dist/python-gate.d.ts +95 -0
  51. package/dist/python-gate.js +175 -0
  52. package/dist/python-gate.js.map +1 -0
  53. package/dist/tool-counts.json +16 -16
  54. package/dist/tools/animation.js +288 -24
  55. package/dist/tools/animation.js.map +1 -1
  56. package/dist/tools/asset.js +79 -14
  57. package/dist/tools/asset.js.map +1 -1
  58. package/dist/tools/audio.js +59 -5
  59. package/dist/tools/audio.js.map +1 -1
  60. package/dist/tools/blueprint.js +29 -12
  61. package/dist/tools/blueprint.js.map +1 -1
  62. package/dist/tools/chooser.js +4 -1
  63. package/dist/tools/chooser.js.map +1 -1
  64. package/dist/tools/editor.js +81 -43
  65. package/dist/tools/editor.js.map +1 -1
  66. package/dist/tools/feedback.js +214 -48
  67. package/dist/tools/feedback.js.map +1 -1
  68. package/dist/tools/foliage.js +27 -1
  69. package/dist/tools/foliage.js.map +1 -1
  70. package/dist/tools/gameplay.js +38 -11
  71. package/dist/tools/gameplay.js.map +1 -1
  72. package/dist/tools/gas.js +17 -5
  73. package/dist/tools/gas.js.map +1 -1
  74. package/dist/tools/landscape.js +22 -7
  75. package/dist/tools/landscape.js.map +1 -1
  76. package/dist/tools/level.js +322 -51
  77. package/dist/tools/level.js.map +1 -1
  78. package/dist/tools/material.js +10 -4
  79. package/dist/tools/material.js.map +1 -1
  80. package/dist/tools/niagara.js +3 -2
  81. package/dist/tools/niagara.js.map +1 -1
  82. package/dist/tools/pcg.js +6 -5
  83. package/dist/tools/pcg.js.map +1 -1
  84. package/dist/tools/plugins.js +2 -0
  85. package/dist/tools/plugins.js.map +1 -1
  86. package/dist/tools/project.js +47 -20
  87. package/dist/tools/project.js.map +1 -1
  88. package/dist/types.d.ts +24 -0
  89. package/dist/types.js +46 -3
  90. package/dist/types.js.map +1 -1
  91. package/dist/ue-mcp.default.yml +493 -125
  92. package/package.json +2 -2
  93. package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
  94. package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
  95. package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
  141. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
  142. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
  143. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
  144. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
  145. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
  146. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
  147. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
  148. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
  149. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
  150. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
  151. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
  152. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
  153. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
  154. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
  155. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
  156. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
  157. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
  158. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
  159. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
  160. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
  161. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
  162. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
  163. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
  164. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
  165. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
  166. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
  167. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
  168. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
  169. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
  170. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
  171. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
  172. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
  173. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
  174. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
  175. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
  176. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
  177. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
  178. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
  179. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
  180. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
  181. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
  182. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
  183. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
  184. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
  185. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
  186. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
  187. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
  188. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
  189. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
  190. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
  191. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
  192. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
  193. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
  194. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
  195. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
  196. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
  197. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
  198. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
  199. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
  200. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
  201. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
  202. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
  203. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
  204. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
  205. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
  206. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
  207. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
  208. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
  209. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
  210. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
  211. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
  212. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
  213. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
  214. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
  215. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
  216. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
  217. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
  218. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
  219. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
  220. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
  221. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
  222. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
  223. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
  224. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
  225. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
  226. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
  227. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
  228. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
  229. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
  230. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
  231. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
  232. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
  233. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
  234. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
  235. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
  236. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
  237. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
  238. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
  239. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
  240. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
  241. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
  242. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
  243. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
  244. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
  245. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
  246. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
  247. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
  248. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
  249. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  250. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
  251. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
  252. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
  253. package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
  254. package/skills/ue-mcp-animation/SKILL.md +154 -0
  255. package/skills/ue-mcp-blueprint/SKILL.md +71 -0
@@ -76,6 +76,11 @@ private:
76
76
  static TSharedPtr<FJsonValue> BeginMaterialTransaction(const TSharedPtr<FJsonObject>& Params);
77
77
  static TSharedPtr<FJsonValue> EndMaterialTransaction(const TSharedPtr<FJsonObject>& Params);
78
78
 
79
+ // #946: single-call PBR build from a texture set. Picks each TextureSample's
80
+ // sampler type from the texture itself, which is the step that fails
81
+ // silently by hand when the texture is virtual (UDIM).
82
+ static TSharedPtr<FJsonValue> BuildMaterial(const TSharedPtr<FJsonObject>& Params);
83
+
79
84
  // #225: single-call simple material authoring + EMaterialUsage flag
80
85
  static TSharedPtr<FJsonValue> CreateMaterialSimple(const TSharedPtr<FJsonObject>& Params);
81
86
  static TSharedPtr<FJsonValue> SetMaterialUsage(const TSharedPtr<FJsonObject>& Params);
@@ -0,0 +1,597 @@
1
+ // Split from MaterialHandlers.cpp to keep that file under 3k lines.
2
+ // All functions below are still members of FMaterialHandlers - this file is a
3
+ // translation-unit partition, not a new class. Handler registration
4
+ // stays in MaterialHandlers.cpp::RegisterHandlers.
5
+ //
6
+ // #946: building a PBR material out of an imported texture set was a sequence
7
+ // of add_expression / connect_to_property / recompile calls, and one step in
8
+ // that sequence fails silently. A virtual texture (which is what every UDIM
9
+ // set imports as) needs the VIRTUAL variant of its sampler type; assign one to
10
+ // a sampler left on SAMPLERTYPE_Color and the connection reports success, then
11
+ // the material property comes back empty after the recompile with no error
12
+ // anywhere. Selecting that sampler type from the texture, and reporting what
13
+ // survived the recompile, is the whole point of this action.
14
+
15
+ #include "MaterialHandlers.h"
16
+ #include "UE_MCP_BridgeModule.h"
17
+ #include "HandlerRegistry.h"
18
+ #include "HandlerUtils.h"
19
+ #include "UObject/Class.h"
20
+ #include "SceneTypes.h"
21
+ #include "Materials/Material.h"
22
+ #include "Materials/MaterialExpressionTextureBase.h"
23
+ #include "Materials/MaterialExpressionTextureSample.h"
24
+ #include "Materials/MaterialInterface.h"
25
+ #include "MaterialEditingLibrary.h"
26
+ #include "Factories/MaterialFactoryNew.h"
27
+ #include "Engine/EngineTypes.h"
28
+ #include "Engine/Texture.h"
29
+ #include "Engine/Texture2D.h"
30
+ #include "Engine/StaticMesh.h"
31
+ #include "Engine/SkeletalMesh.h"
32
+ // FSkeletalMaterial moved out of Engine/SkeletalMesh.h in later UE versions.
33
+ #if __has_include("Engine/SkinnedAssetCommon.h")
34
+ #include "Engine/SkinnedAssetCommon.h"
35
+ #endif
36
+ #include "AssetToolsModule.h"
37
+ #include "IAssetTools.h"
38
+ #include "UObject/UObjectGlobals.h"
39
+ #include "Dom/JsonObject.h"
40
+ #include "Dom/JsonValue.h"
41
+
42
+ namespace
43
+ {
44
+ /** The sampler types that only differ from a plain type by reading a virtual
45
+ * texture. Kept as one table so promote and demote cannot disagree. */
46
+ struct FMaterialBuildVirtualSamplerPair
47
+ {
48
+ EMaterialSamplerType Plain;
49
+ EMaterialSamplerType Virtual;
50
+ };
51
+
52
+ const FMaterialBuildVirtualSamplerPair GMaterialBuildVirtualSamplerPairs[] =
53
+ {
54
+ { SAMPLERTYPE_Color, SAMPLERTYPE_VirtualColor },
55
+ { SAMPLERTYPE_Grayscale, SAMPLERTYPE_VirtualGrayscale },
56
+ { SAMPLERTYPE_Alpha, SAMPLERTYPE_VirtualAlpha },
57
+ { SAMPLERTYPE_Normal, SAMPLERTYPE_VirtualNormal },
58
+ { SAMPLERTYPE_Masks, SAMPLERTYPE_VirtualMasks },
59
+ { SAMPLERTYPE_LinearColor, SAMPLERTYPE_VirtualLinearColor },
60
+ { SAMPLERTYPE_LinearGrayscale, SAMPLERTYPE_VirtualLinearGrayscale },
61
+ };
62
+
63
+ EMaterialSamplerType MaterialBuildPromoteSamplerToVirtual(EMaterialSamplerType In)
64
+ {
65
+ for (const FMaterialBuildVirtualSamplerPair& Pair : GMaterialBuildVirtualSamplerPairs)
66
+ {
67
+ if (Pair.Plain == In) return Pair.Virtual;
68
+ }
69
+ return In;
70
+ }
71
+
72
+ EMaterialSamplerType MaterialBuildDemoteSamplerFromVirtual(EMaterialSamplerType In)
73
+ {
74
+ for (const FMaterialBuildVirtualSamplerPair& Pair : GMaterialBuildVirtualSamplerPairs)
75
+ {
76
+ if (Pair.Virtual == In) return Pair.Plain;
77
+ }
78
+ return In;
79
+ }
80
+
81
+ FString MaterialBuildSamplerTypeName(EMaterialSamplerType Type)
82
+ {
83
+ if (UEnum* Enum = StaticEnum<EMaterialSamplerType>())
84
+ {
85
+ return Enum->GetNameStringByValue((int64)Type);
86
+ }
87
+ return FString::FromInt((int32)Type);
88
+ }
89
+
90
+ /** Accepts "SAMPLERTYPE_VirtualColor", "VirtualColor", "virtual color" and
91
+ * "virtual_color" alike, so an override never has to be spelled the way the
92
+ * header happens to spell it. */
93
+ bool MaterialBuildParseSamplerType(const FString& Input, EMaterialSamplerType& OutType)
94
+ {
95
+ FString Normalised = Input;
96
+ Normalised.RemoveFromStart(TEXT("SAMPLERTYPE_"), ESearchCase::IgnoreCase);
97
+ Normalised.ReplaceInline(TEXT("_"), TEXT(""), ESearchCase::CaseSensitive);
98
+ Normalised.ReplaceInline(TEXT(" "), TEXT(""), ESearchCase::CaseSensitive);
99
+ Normalised = Normalised.ToLower();
100
+
101
+ UEnum* Enum = StaticEnum<EMaterialSamplerType>();
102
+ if (!Enum) return false;
103
+
104
+ for (int32 Index = 0; Index < Enum->NumEnums(); ++Index)
105
+ {
106
+ const int64 Value = Enum->GetValueByIndex(Index);
107
+ if (Value == (int64)SAMPLERTYPE_MAX) continue;
108
+ FString Candidate = Enum->GetNameStringByIndex(Index);
109
+ Candidate.RemoveFromStart(TEXT("SAMPLERTYPE_"), ESearchCase::IgnoreCase);
110
+ if (Candidate.ToLower() == Normalised)
111
+ {
112
+ OutType = (EMaterialSamplerType)Value;
113
+ return true;
114
+ }
115
+ }
116
+ return false;
117
+ }
118
+
119
+ /** One entry of the requested texture set, after the key has been resolved
120
+ * to the material property (or properties) it drives. */
121
+ struct FMaterialBuildSlot
122
+ {
123
+ FString Key;
124
+ FString TexturePath;
125
+ UTexture* Texture = nullptr;
126
+ /** Property + the sampler output name that feeds it. An ORM map fans one
127
+ * sample out across three properties, so this is a list. */
128
+ TArray<TPair<EMaterialProperty, FString>> Targets;
129
+ TArray<FString> TargetNames;
130
+ };
131
+
132
+ /** ORM / RMA style packed maps: one sample, three properties, one channel
133
+ * each. Named explicitly because guessing from the texture alone is not
134
+ * something a build step should do silently. */
135
+ bool MaterialBuildResolvePackedKey(const FString& LowerKey, TArray<TPair<EMaterialProperty, FString>>& OutTargets, TArray<FString>& OutNames)
136
+ {
137
+ if (LowerKey == TEXT("orm") || LowerKey == TEXT("occlusionroughnessmetallic"))
138
+ {
139
+ OutTargets.Add({ MP_AmbientOcclusion, TEXT("R") });
140
+ OutTargets.Add({ MP_Roughness, TEXT("G") });
141
+ OutTargets.Add({ MP_Metallic, TEXT("B") });
142
+ OutNames = { TEXT("AmbientOcclusion"), TEXT("Roughness"), TEXT("Metallic") };
143
+ return true;
144
+ }
145
+ if (LowerKey == TEXT("rma") || LowerKey == TEXT("roughnessmetallicao"))
146
+ {
147
+ OutTargets.Add({ MP_Roughness, TEXT("R") });
148
+ OutTargets.Add({ MP_Metallic, TEXT("G") });
149
+ OutTargets.Add({ MP_AmbientOcclusion, TEXT("B") });
150
+ OutNames = { TEXT("Roughness"), TEXT("Metallic"), TEXT("AmbientOcclusion") };
151
+ return true;
152
+ }
153
+ return false;
154
+ }
155
+ }
156
+
157
+ TSharedPtr<FJsonValue> FMaterialHandlers::BuildMaterial(const TSharedPtr<FJsonObject>& Params)
158
+ {
159
+ // ── Texture set ──────────────────────────────────────────────────────────
160
+ const TSharedPtr<FJsonObject>* TexturesObj = nullptr;
161
+ if (!Params->TryGetObjectField(TEXT("textures"), TexturesObj) || !TexturesObj || !(*TexturesObj).IsValid())
162
+ {
163
+ return MCPError(TEXT("Missing 'textures' object mapping a material property to a texture asset path, e.g. {\"baseColor\":\"/Game/T_Body_D\",\"normal\":\"/Game/T_Body_N\",\"orm\":\"/Game/T_Body_ORM\"}"));
164
+ }
165
+ if ((*TexturesObj)->Values.Num() == 0)
166
+ {
167
+ return MCPError(TEXT("'textures' is empty - name at least one property, e.g. baseColor"));
168
+ }
169
+
170
+ // ── Optional per-property sampler-type overrides ─────────────────────────
171
+ const TSharedPtr<FJsonObject>* SamplerTypesObj = nullptr;
172
+ Params->TryGetObjectField(TEXT("samplerTypes"), SamplerTypesObj);
173
+
174
+ // ── Resolve every requested texture before touching the material, so a
175
+ // typo in the set never leaves a half-built graph behind. ─────────────
176
+ TArray<FMaterialBuildSlot> Slots;
177
+ TArray<FString> Warnings;
178
+ for (const TPair<FString, TSharedPtr<FJsonValue>>& Entry : (*TexturesObj)->Values)
179
+ {
180
+ FMaterialBuildSlot Slot;
181
+ Slot.Key = Entry.Key;
182
+ if (!Entry.Value.IsValid() || !Entry.Value->TryGetString(Slot.TexturePath) || Slot.TexturePath.IsEmpty())
183
+ {
184
+ return MCPError(FString::Printf(TEXT("textures['%s'] must be a texture asset path string"), *Entry.Key));
185
+ }
186
+
187
+ const FString LowerKey = Slot.Key.ToLower();
188
+ if (!MaterialBuildResolvePackedKey(LowerKey, Slot.Targets, Slot.TargetNames))
189
+ {
190
+ EMaterialProperty Property;
191
+ if (!ParseMaterialProperty(Slot.Key, Property))
192
+ {
193
+ return MCPError(FString::Printf(
194
+ TEXT("textures['%s'] is not a material property. Use baseColor, normal, roughness, metallic, specular, ambientOcclusion, emissive, opacity, opacityMask, or the packed keys orm / rma."),
195
+ *Slot.Key));
196
+ }
197
+ // The whole sample (RGB) drives a single property.
198
+ Slot.Targets.Add({ Property, FString() });
199
+ Slot.TargetNames.Add(Slot.Key);
200
+ }
201
+
202
+ Slot.Texture = LoadAssetByPath<UTexture>(Slot.TexturePath);
203
+ if (!Slot.Texture)
204
+ {
205
+ return MCPError(FString::Printf(TEXT("Failed to load texture at '%s' for textures['%s']"), *Slot.TexturePath, *Slot.Key));
206
+ }
207
+ Slots.Add(MoveTemp(Slot));
208
+ }
209
+
210
+ // ── Resolve or create the material ───────────────────────────────────────
211
+ FString MaterialPath = OptionalString(Params, TEXT("materialPath"));
212
+ if (MaterialPath.IsEmpty()) MaterialPath = OptionalString(Params, TEXT("assetPath"));
213
+
214
+ UMaterial* Material = nullptr;
215
+ bool bCreated = false;
216
+ FString CreatedPath;
217
+
218
+ if (!MaterialPath.IsEmpty())
219
+ {
220
+ Material = LoadMaterialFromPath(MaterialPath);
221
+ if (!Material)
222
+ {
223
+ return MCPError(FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
224
+ }
225
+ }
226
+ else
227
+ {
228
+ FString Name;
229
+ if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
230
+ const FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Materials"));
231
+
232
+ // Building into a material that is already there is the same request as
233
+ // building a new one, so this reuses rather than refusing.
234
+ Material = LoadAssetByPath<UMaterial>(PackagePath / Name);
235
+ if (!Material)
236
+ {
237
+ UMaterialFactoryNew* Factory = NewObject<UMaterialFactoryNew>();
238
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
239
+ UObject* NewAsset = AssetToolsModule.Get().CreateAsset(Name, PackagePath, UMaterial::StaticClass(), Factory);
240
+ Material = Cast<UMaterial>(NewAsset);
241
+ if (!Material)
242
+ {
243
+ return MCPError(FString::Printf(TEXT("Failed to create material '%s' in '%s'"), *Name, *PackagePath));
244
+ }
245
+ bCreated = true;
246
+ CreatedPath = Material->GetPathName();
247
+ }
248
+ }
249
+
250
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] BuildMaterial: %s (%d texture slots)"), *Material->GetPathName(), Slots.Num());
251
+
252
+ const bool bClearExisting = OptionalBool(Params, TEXT("clearExisting"), false);
253
+ if (bClearExisting)
254
+ {
255
+ UMaterialEditingLibrary::DeleteAllMaterialExpressions(Material);
256
+ }
257
+
258
+ UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData();
259
+ if (!EditorOnlyData)
260
+ {
261
+ return MCPError(TEXT("Material has no editor-only data (is this material domain supported?)"));
262
+ }
263
+
264
+ // ── Build one TextureSample per entry ────────────────────────────────────
265
+ Material->Modify();
266
+
267
+ TArray<TSharedPtr<FJsonValue>> SamplerReport;
268
+ // Property plus the name the caller used for it, so the post-recompile
269
+ // report speaks the caller's vocabulary rather than MP_ enum spellings.
270
+ TArray<TPair<EMaterialProperty, FString>> RequestedProperties;
271
+ int32 NodeY = -300;
272
+
273
+ for (const FMaterialBuildSlot& Slot : Slots)
274
+ {
275
+ // Reuse a sample already wired to the first target property with the
276
+ // same texture, so building twice does not stack duplicate nodes.
277
+ UMaterialExpressionTextureSample* Sample = nullptr;
278
+ bool bReused = false;
279
+ if (FExpressionInput* ExistingInput = GetMaterialPropertyInput(EditorOnlyData, Slot.Targets[0].Key))
280
+ {
281
+ UMaterialExpressionTextureSample* Existing = Cast<UMaterialExpressionTextureSample>(ExistingInput->Expression);
282
+ if (Existing && Existing->Texture == Slot.Texture)
283
+ {
284
+ Sample = Existing;
285
+ bReused = true;
286
+ }
287
+ }
288
+
289
+ if (!Sample)
290
+ {
291
+ UMaterialExpression* Created = UMaterialEditingLibrary::CreateMaterialExpression(
292
+ Material, UMaterialExpressionTextureSample::StaticClass(), -450, NodeY);
293
+ Sample = Cast<UMaterialExpressionTextureSample>(Created);
294
+ if (!Sample)
295
+ {
296
+ return MCPError(FString::Printf(TEXT("Failed to create a TextureSample node for textures['%s']"), *Slot.Key));
297
+ }
298
+ }
299
+ NodeY += 300;
300
+
301
+ Sample->Texture = Slot.Texture;
302
+
303
+ // ── Sampler type. This is the step that is silently fatal when it is
304
+ // wrong: a virtual texture read through a non-virtual sampler leaves
305
+ // the material input empty after the recompile and reports nothing.
306
+ Sample->AutoSetSampleType();
307
+ const EMaterialSamplerType AutoType = Sample->SamplerType;
308
+
309
+ bool bVirtualTexture = Slot.Texture->VirtualTextureStreaming != 0
310
+ || Slot.Texture->IsCurrentlyVirtualTextured();
311
+ int32 UdimBlocks = 1;
312
+ #if WITH_EDITORONLY_DATA
313
+ if (Slot.Texture->Source.IsValid())
314
+ {
315
+ UdimBlocks = Slot.Texture->Source.GetNumBlocks();
316
+ }
317
+ // A UDIM set cannot be built without virtual texturing, so it samples
318
+ // as virtual whether or not the flag has been round-tripped yet.
319
+ if (Slot.Texture->RequiresVirtualTexturing())
320
+ {
321
+ bVirtualTexture = true;
322
+ }
323
+ #endif
324
+ if (UdimBlocks > 1)
325
+ {
326
+ bVirtualTexture = true;
327
+ }
328
+
329
+ // Reconcile the engine's guess with what the texture actually is. This
330
+ // is the correction the manual workflow has to know to make by hand.
331
+ EMaterialSamplerType ResolvedType = AutoType;
332
+ if (bVirtualTexture)
333
+ {
334
+ ResolvedType = MaterialBuildPromoteSamplerToVirtual(ResolvedType);
335
+ }
336
+ else
337
+ {
338
+ ResolvedType = MaterialBuildDemoteSamplerFromVirtual(ResolvedType);
339
+ }
340
+
341
+ // An explicit override wins over everything above.
342
+ FString OverrideName;
343
+ bool bOverridden = false;
344
+ if (SamplerTypesObj && (*SamplerTypesObj).IsValid()
345
+ && (*SamplerTypesObj)->TryGetStringField(Slot.Key, OverrideName) && !OverrideName.IsEmpty())
346
+ {
347
+ EMaterialSamplerType Parsed;
348
+ if (!MaterialBuildParseSamplerType(OverrideName, Parsed))
349
+ {
350
+ return MCPError(FString::Printf(
351
+ TEXT("samplerTypes['%s'] = '%s' is not an EMaterialSamplerType. Use e.g. Color, Normal, Masks, LinearColor, VirtualColor, VirtualNormal."),
352
+ *Slot.Key, *OverrideName));
353
+ }
354
+ ResolvedType = Parsed;
355
+ bOverridden = true;
356
+ }
357
+
358
+ Sample->SamplerType = ResolvedType;
359
+
360
+ if (bVirtualTexture && !IsVirtualSamplerType(ResolvedType))
361
+ {
362
+ Warnings.Add(FString::Printf(
363
+ TEXT("textures['%s'] is a virtual texture but samples as %s. The material input will be dropped on recompile; use a Virtual* sampler type."),
364
+ *Slot.Key, *MaterialBuildSamplerTypeName(ResolvedType)));
365
+ }
366
+ if (!bVirtualTexture && IsVirtualSamplerType(ResolvedType))
367
+ {
368
+ Warnings.Add(FString::Printf(
369
+ TEXT("textures['%s'] is not a virtual texture but samples as %s."),
370
+ *Slot.Key, *MaterialBuildSamplerTypeName(ResolvedType)));
371
+ }
372
+ if (Slot.Targets.Num() > 1
373
+ && (ResolvedType == SAMPLERTYPE_Color || ResolvedType == SAMPLERTYPE_VirtualColor))
374
+ {
375
+ Warnings.Add(FString::Printf(
376
+ TEXT("textures['%s'] is a packed map sampled as %s (sRGB). Its channels will be gamma decoded; import it as Masks or disable sRGB on the texture."),
377
+ *Slot.Key, *MaterialBuildSamplerTypeName(ResolvedType)));
378
+ }
379
+
380
+ // ── Wire the sample to its property or properties ────────────────────
381
+ const TArray<FString> OutputNames = UMaterialEditingLibrary::GetMaterialExpressionOutputNames(Sample);
382
+ TArray<TSharedPtr<FJsonValue>> ConnectedTargets;
383
+ for (int32 TargetIndex = 0; TargetIndex < Slot.Targets.Num(); ++TargetIndex)
384
+ {
385
+ const EMaterialProperty Property = Slot.Targets[TargetIndex].Key;
386
+ const FString& OutputName = Slot.Targets[TargetIndex].Value;
387
+ const FString TargetName = Slot.TargetNames.IsValidIndex(TargetIndex) ? Slot.TargetNames[TargetIndex] : Slot.Key;
388
+
389
+ bool bConnected = false;
390
+ FString ResolvedBy = TEXT("name");
391
+ if (OutputName.IsEmpty() || OutputNames.Contains(OutputName))
392
+ {
393
+ bConnected = UMaterialEditingLibrary::ConnectMaterialProperty(Sample, OutputName, Property);
394
+ }
395
+ else
396
+ {
397
+ // The channel pin names are not what this engine build calls
398
+ // them. Fall back to the conventional TextureSample output order
399
+ // rather than dropping the channel and leaving the caller to
400
+ // discover a half-wired packed map later.
401
+ static const TCHAR* ChannelOrder[] = { TEXT("RGB"), TEXT("R"), TEXT("G"), TEXT("B"), TEXT("A") };
402
+ int32 ChannelIndex = INDEX_NONE;
403
+ for (int32 Channel = 0; Channel < (int32)UE_ARRAY_COUNT(ChannelOrder); ++Channel)
404
+ {
405
+ if (OutputName.Equals(ChannelOrder[Channel], ESearchCase::IgnoreCase)) { ChannelIndex = Channel; break; }
406
+ }
407
+ FExpressionInput* PropertyInput = (ChannelIndex != INDEX_NONE)
408
+ ? GetMaterialPropertyInput(EditorOnlyData, Property)
409
+ : nullptr;
410
+ if (PropertyInput)
411
+ {
412
+ PropertyInput->Connect(ChannelIndex, Sample);
413
+ bConnected = true;
414
+ ResolvedBy = TEXT("index");
415
+ }
416
+ Warnings.Add(FString::Printf(
417
+ TEXT("textures['%s'] has no '%s' output (available: %s); %s wired by channel index instead: %s."),
418
+ *Slot.Key, *OutputName, *FString::Join(OutputNames, TEXT(", ")), *TargetName,
419
+ bConnected ? TEXT("connected") : TEXT("left unconnected")));
420
+ }
421
+ bool bAlreadyTracked = false;
422
+ for (const TPair<EMaterialProperty, FString>& Tracked : RequestedProperties)
423
+ {
424
+ if (Tracked.Key == Property) { bAlreadyTracked = true; break; }
425
+ }
426
+ if (!bAlreadyTracked)
427
+ {
428
+ RequestedProperties.Add({ Property, TargetName });
429
+ }
430
+
431
+ TSharedPtr<FJsonObject> TargetObj = MakeShared<FJsonObject>();
432
+ TargetObj->SetStringField(TEXT("property"), TargetName);
433
+ TargetObj->SetStringField(TEXT("output"), OutputName.IsEmpty() ? FString(TEXT("RGB")) : OutputName);
434
+ TargetObj->SetStringField(TEXT("outputResolvedBy"), ResolvedBy);
435
+ TargetObj->SetBoolField(TEXT("connected"), bConnected);
436
+ ConnectedTargets.Add(MakeShared<FJsonValueObject>(TargetObj));
437
+ }
438
+
439
+ TSharedPtr<FJsonObject> SlotObj = MakeShared<FJsonObject>();
440
+ SlotObj->SetStringField(TEXT("key"), Slot.Key);
441
+ SlotObj->SetStringField(TEXT("texturePath"), Slot.Texture->GetPathName());
442
+ SlotObj->SetStringField(TEXT("samplerType"), MaterialBuildSamplerTypeName(ResolvedType));
443
+ SlotObj->SetStringField(TEXT("autoSamplerType"), MaterialBuildSamplerTypeName(AutoType));
444
+ SlotObj->SetBoolField(TEXT("samplerTypeOverridden"), bOverridden);
445
+ SlotObj->SetBoolField(TEXT("virtualTexture"), bVirtualTexture);
446
+ SlotObj->SetNumberField(TEXT("udimBlocks"), UdimBlocks);
447
+ SlotObj->SetBoolField(TEXT("reusedExistingNode"), bReused);
448
+ SlotObj->SetArrayField(TEXT("targets"), ConnectedTargets);
449
+ SamplerReport.Add(MakeShared<FJsonValueObject>(SlotObj));
450
+ }
451
+
452
+ // ── Recompile, then report what actually survived it ─────────────────────
453
+ const TArray<FString> CompileErrors = UMaterialEditingLibrary::RecompileMaterial(Material);
454
+ const bool bSaved = SaveAssetPackage(Material);
455
+
456
+ // Re-read the editor-only data: RecompileMaterial can rebuild it, so the
457
+ // pointer taken before the recompile is not the one to inspect after it.
458
+ EditorOnlyData = Material->GetEditorOnlyData();
459
+
460
+ TSharedPtr<FJsonObject> Persisted = MakeShared<FJsonObject>();
461
+ int32 DroppedCount = 0;
462
+ for (const TPair<EMaterialProperty, FString>& Requested : RequestedProperties)
463
+ {
464
+ const EMaterialProperty Property = Requested.Key;
465
+ const FString& PropertyName = Requested.Value;
466
+
467
+ FExpressionInput* Input = EditorOnlyData ? GetMaterialPropertyInput(EditorOnlyData, Property) : nullptr;
468
+ const bool bStillConnected = Input && Input->Expression != nullptr;
469
+ Persisted->SetBoolField(PropertyName, bStillConnected);
470
+ if (!bStillConnected)
471
+ {
472
+ ++DroppedCount;
473
+ Warnings.Add(FString::Printf(
474
+ TEXT("%s lost its connection during recompile. This is what a mismatched sampler type looks like: check the samplerType reported for the texture feeding it."),
475
+ *PropertyName));
476
+ }
477
+ }
478
+
479
+ auto Result = MCPSuccess();
480
+ if (bCreated) MCPSetCreated(Result); else MCPSetUpdated(Result);
481
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
482
+ Result->SetBoolField(TEXT("createdMaterial"), bCreated);
483
+ Result->SetArrayField(TEXT("samplers"), SamplerReport);
484
+ Result->SetObjectField(TEXT("connectedAfterRecompile"), Persisted);
485
+ Result->SetNumberField(TEXT("droppedConnections"), DroppedCount);
486
+ Result->SetBoolField(TEXT("saved"), bSaved);
487
+
488
+ if (CompileErrors.Num() > 0)
489
+ {
490
+ TArray<TSharedPtr<FJsonValue>> Errors;
491
+ for (const FString& Error : CompileErrors) Errors.Add(MakeShared<FJsonValueString>(Error));
492
+ Result->SetArrayField(TEXT("compileErrors"), Errors);
493
+ }
494
+
495
+ // ── Optional: put the finished material on a mesh's slots ────────────────
496
+ FString MeshPath = OptionalString(Params, TEXT("assignToMesh"));
497
+ if (MeshPath.IsEmpty()) MeshPath = OptionalString(Params, TEXT("meshPath"));
498
+ if (!MeshPath.IsEmpty())
499
+ {
500
+ // Slots may be named or indexed; an empty list means every slot.
501
+ TArray<FString> WantedNames;
502
+ TArray<int32> WantedIndices;
503
+ const TArray<TSharedPtr<FJsonValue>>* SlotSpec = nullptr;
504
+ if (Params->TryGetArrayField(TEXT("meshSlots"), SlotSpec) && SlotSpec)
505
+ {
506
+ for (const TSharedPtr<FJsonValue>& Value : *SlotSpec)
507
+ {
508
+ if (!Value.IsValid()) continue;
509
+ double AsNumber = 0.0;
510
+ FString AsString;
511
+ if (Value->TryGetNumber(AsNumber)) WantedIndices.Add((int32)AsNumber);
512
+ else if (Value->TryGetString(AsString) && !AsString.IsEmpty()) WantedNames.Add(AsString);
513
+ }
514
+ }
515
+ const bool bAllSlots = WantedNames.Num() == 0 && WantedIndices.Num() == 0;
516
+
517
+ auto SlotWanted = [&](int32 Index, const FName& SlotName) -> bool
518
+ {
519
+ if (bAllSlots) return true;
520
+ if (WantedIndices.Contains(Index)) return true;
521
+ for (const FString& Name : WantedNames)
522
+ {
523
+ if (SlotName.ToString().Equals(Name, ESearchCase::IgnoreCase)) return true;
524
+ }
525
+ return false;
526
+ };
527
+
528
+ TArray<TSharedPtr<FJsonValue>> Assigned;
529
+
530
+ if (UStaticMesh* StaticMesh = LoadAssetByPath<UStaticMesh>(MeshPath))
531
+ {
532
+ StaticMesh->Modify();
533
+ const int32 SlotCount = StaticMesh->GetStaticMaterials().Num();
534
+ for (int32 Index = 0; Index < SlotCount; ++Index)
535
+ {
536
+ const FName SlotName = StaticMesh->GetStaticMaterials()[Index].MaterialSlotName;
537
+ if (!SlotWanted(Index, SlotName)) continue;
538
+ StaticMesh->SetMaterial(Index, Material);
539
+
540
+ TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
541
+ Entry->SetNumberField(TEXT("slotIndex"), Index);
542
+ Entry->SetStringField(TEXT("slotName"), SlotName.ToString());
543
+ Assigned.Add(MakeShared<FJsonValueObject>(Entry));
544
+ }
545
+ StaticMesh->PostEditChange();
546
+ Result->SetStringField(TEXT("meshPath"), StaticMesh->GetPathName());
547
+ Result->SetStringField(TEXT("meshType"), TEXT("StaticMesh"));
548
+ Result->SetBoolField(TEXT("meshSaved"), SaveAssetPackage(StaticMesh));
549
+ }
550
+ else if (USkeletalMesh* SkeletalMesh = LoadAssetByPath<USkeletalMesh>(MeshPath))
551
+ {
552
+ // Assigning through the slot array rather than a per-index setter:
553
+ // the skeletal path is the one AssetHandlers found to stick.
554
+ SkeletalMesh->Modify();
555
+ TArray<FSkeletalMaterial> Materials = SkeletalMesh->GetMaterials();
556
+ for (int32 Index = 0; Index < Materials.Num(); ++Index)
557
+ {
558
+ if (!SlotWanted(Index, Materials[Index].MaterialSlotName)) continue;
559
+ Materials[Index].MaterialInterface = Material;
560
+
561
+ TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
562
+ Entry->SetNumberField(TEXT("slotIndex"), Index);
563
+ Entry->SetStringField(TEXT("slotName"), Materials[Index].MaterialSlotName.ToString());
564
+ Assigned.Add(MakeShared<FJsonValueObject>(Entry));
565
+ }
566
+ SkeletalMesh->SetMaterials(Materials);
567
+ SkeletalMesh->PostEditChange();
568
+ Result->SetStringField(TEXT("meshPath"), SkeletalMesh->GetPathName());
569
+ Result->SetStringField(TEXT("meshType"), TEXT("SkeletalMesh"));
570
+ Result->SetBoolField(TEXT("meshSaved"), SaveAssetPackage(SkeletalMesh));
571
+ }
572
+ else
573
+ {
574
+ Warnings.Add(FString::Printf(TEXT("assignToMesh '%s' is neither a StaticMesh nor a SkeletalMesh; the material was built but assigned to nothing."), *MeshPath));
575
+ }
576
+
577
+ Result->SetArrayField(TEXT("assignedSlots"), Assigned);
578
+ if (Assigned.Num() == 0 && !bAllSlots)
579
+ {
580
+ Warnings.Add(TEXT("No mesh slot matched 'meshSlots'; nothing was assigned."));
581
+ }
582
+ }
583
+
584
+ if (Warnings.Num() > 0)
585
+ {
586
+ TArray<TSharedPtr<FJsonValue>> WarningValues;
587
+ for (const FString& Warning : Warnings) WarningValues.Add(MakeShared<FJsonValueString>(Warning));
588
+ Result->SetArrayField(TEXT("warnings"), WarningValues);
589
+ }
590
+
591
+ if (bCreated && !CreatedPath.IsEmpty())
592
+ {
593
+ MCPSetDeleteAssetRollback(Result, CreatedPath);
594
+ }
595
+
596
+ return MCPResult(Result);
597
+ }