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.
- package/README.md +4 -3
- package/dist/action-class.js +56 -3
- package/dist/action-class.js.map +1 -1
- package/dist/bridge-timeouts.d.ts +64 -0
- package/dist/bridge-timeouts.js +118 -0
- package/dist/bridge-timeouts.js.map +1 -0
- package/dist/bridge.js +10 -2
- package/dist/bridge.js.map +1 -1
- package/dist/editor-control.d.ts +72 -5
- package/dist/editor-control.js +164 -140
- package/dist/editor-control.js.map +1 -1
- package/dist/editor-target.d.ts +55 -1
- package/dist/editor-target.js +107 -10
- package/dist/editor-target.js.map +1 -1
- package/dist/engine-observer.d.ts +59 -5
- package/dist/engine-observer.js +249 -29
- package/dist/engine-observer.js.map +1 -1
- package/dist/engine-root.d.ts +91 -0
- package/dist/engine-root.js +448 -0
- package/dist/engine-root.js.map +1 -0
- package/dist/epic-enrich.js +16 -1
- package/dist/epic-enrich.js.map +1 -1
- package/dist/feedback-cli.js +4 -0
- package/dist/feedback-cli.js.map +1 -1
- package/dist/feedback-deferred.d.ts +9 -0
- package/dist/feedback-deferred.js +2 -0
- package/dist/feedback-deferred.js.map +1 -1
- package/dist/feedback-fallback.d.ts +99 -0
- package/dist/feedback-fallback.js +173 -0
- package/dist/feedback-fallback.js.map +1 -0
- package/dist/flow/write-methods.js +20 -0
- package/dist/flow/write-methods.js.map +1 -1
- package/dist/index.js +41 -2
- package/dist/index.js.map +1 -1
- package/dist/lean-context.js +8 -3
- package/dist/lean-context.js.map +1 -1
- package/dist/locking.js +11 -2
- package/dist/locking.js.map +1 -1
- package/dist/plugin/loader.d.ts +4 -0
- package/dist/plugin/loader.js +22 -8
- package/dist/plugin/loader.js.map +1 -1
- package/dist/plugin/manifest.d.ts +72 -52
- package/dist/plugin/manifest.js +118 -21
- package/dist/plugin/manifest.js.map +1 -1
- package/dist/plugin-cli.js +16 -3
- package/dist/plugin-cli.js.map +1 -1
- package/dist/project.d.ts +11 -0
- package/dist/project.js +23 -2
- package/dist/project.js.map +1 -1
- package/dist/python-gate.d.ts +95 -0
- package/dist/python-gate.js +175 -0
- package/dist/python-gate.js.map +1 -0
- package/dist/tool-counts.json +16 -16
- package/dist/tools/animation.js +288 -24
- package/dist/tools/animation.js.map +1 -1
- package/dist/tools/asset.js +79 -14
- package/dist/tools/asset.js.map +1 -1
- package/dist/tools/audio.js +59 -5
- package/dist/tools/audio.js.map +1 -1
- package/dist/tools/blueprint.js +29 -12
- package/dist/tools/blueprint.js.map +1 -1
- package/dist/tools/chooser.js +4 -1
- package/dist/tools/chooser.js.map +1 -1
- package/dist/tools/editor.js +81 -43
- package/dist/tools/editor.js.map +1 -1
- package/dist/tools/feedback.js +214 -48
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/foliage.js +27 -1
- package/dist/tools/foliage.js.map +1 -1
- package/dist/tools/gameplay.js +38 -11
- package/dist/tools/gameplay.js.map +1 -1
- package/dist/tools/gas.js +17 -5
- package/dist/tools/gas.js.map +1 -1
- package/dist/tools/landscape.js +22 -7
- package/dist/tools/landscape.js.map +1 -1
- package/dist/tools/level.js +322 -51
- package/dist/tools/level.js.map +1 -1
- package/dist/tools/material.js +10 -4
- package/dist/tools/material.js.map +1 -1
- package/dist/tools/niagara.js +3 -2
- package/dist/tools/niagara.js.map +1 -1
- package/dist/tools/pcg.js +6 -5
- package/dist/tools/pcg.js.map +1 -1
- package/dist/tools/plugins.js +2 -0
- package/dist/tools/plugins.js.map +1 -1
- package/dist/tools/project.js +47 -20
- package/dist/tools/project.js.map +1 -1
- package/dist/types.d.ts +24 -0
- package/dist/types.js +46 -3
- package/dist/types.js.map +1 -1
- package/dist/ue-mcp.default.yml +493 -125
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/NATIVE_RUNTIME_VISIBILITY.md +53 -0
- package/plugin/ue_mcp_bridge/NATIVE_STATIC_MESH_USAGE_PROGRESS.md +115 -0
- package/plugin/ue_mcp_bridge/README_MassHandlers.md +97 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +21 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +395 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +79 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +25 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +3061 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +961 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +900 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Pose.cpp +769 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +44 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +217 -11
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +551 -24
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +735 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +352 -73
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +8 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkProperties.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.cpp +503 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkRead.h +21 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_BulkUpsert.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Enum.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.cpp +902 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Geometry.h +33 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +478 -134
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +5 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.cpp +933 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_MeshBoolean.h +35 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +478 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_RenderTarget.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +4 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +319 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +3 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +482 -35
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +10 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.cpp +502 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Collision.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +89 -7
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +17 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +107 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +10 -10
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +648 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +5 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.cpp +252 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DiffHandlers.h +10 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +252 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +4 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +250 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +303 -125
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +73 -50
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +757 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +11 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +419 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +39 -161
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +66 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +1323 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BehaviorTree.cpp +1211 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +363 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/HandlerEditorState.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +1288 -36
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +3 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +41 -16
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +959 -213
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +66 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +1571 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +316 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp +280 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +666 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection_Internal.h +63 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +43 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -47
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +527 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1169 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +723 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +325 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Spatial.cpp +360 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Trace.cpp +178 -58
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +337 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +25 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +10 -9
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +686 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.cpp +467 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MassHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +452 -175
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +5 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +597 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +17 -15
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +38 -168
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +39 -30
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +392 -78
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +2 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers_Internal.h +83 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +200 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.h +15 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +19 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +31 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +307 -69
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +84 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +8 -41
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +33 -52
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/ActorSelectorTests.cpp +344 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationControlRigTimelineTests.cpp +60 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AnimationPoseTests.cpp +115 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetBulkReadTests.cpp +177 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetDeleteGuardTests.cpp +276 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPathResolutionTests.cpp +243 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetPropertyPersistTests.cpp +213 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AssetSubobjectTests.cpp +235 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/AutomationRunnerTests.cpp +72 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeAuthoringTests.cpp +526 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BehaviorTreeNodeTests.cpp +334 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BlueprintHandlerSurfaceTests.cpp +362 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/BridgeProtocolTests.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/CollisionQueryTests.cpp +288 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +453 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/EditorFunctionCallTests.cpp +223 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/FoliageBatchTests.cpp +147 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GameThreadGateTests.cpp +82 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/GasLiveAttributeTests.cpp +183 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LandscapeSampleTests.cpp +119 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelBatchWriteTests.cpp +176 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelComponentTreeInstanceCountTests.cpp +189 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelConvertBrushesTests.cpp +100 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelInstanceProjectionTests.cpp +265 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelPostProcessOverrideTests.cpp +258 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelQueryComponentsTests.cpp +195 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelRefreshTests.cpp +142 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelSaveReportTests.cpp +125 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelScriptHandlersTests.cpp +42 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelStaticMeshUsageTests.cpp +412 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTraceWorldScopeTests.cpp +133 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelTransientActorTests.cpp +205 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/LevelWorldPartitionSettingsTests.cpp +168 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTestTypes.h +51 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MCPInstancedStructPathTests.cpp +379 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MaterialBuildTests.cpp +238 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshBooleanTests.cpp +170 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/MeshGeometryTests.cpp +116 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageSaveExtensionTests.cpp +161 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/PackageWriteGuardTests.cpp +253 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/RuntimeVisibilityTests.cpp +143 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerHandlerTests.cpp +660 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SequencerScrubTests.cpp +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/SkeletonDiffTests.cpp +685 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/WidgetBlueprintResolveTests.cpp +145 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +80 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerFunctionCall.h +509 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerQuery.h +407 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +1008 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/MCPEngineStatus.cpp +152 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/Tests/MCPEngineStatusTests.cpp +65 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Public/MCPEngineStatus.h +32 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/UE_MCP_BridgeStatus.Build.cs +2 -0
- package/skills/ue-mcp-animation/SKILL.md +154 -0
- package/skills/ue-mcp-blueprint/SKILL.md +71 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// level(convert_brushes_to_static_mesh) (#911).
|
|
2
|
+
//
|
|
3
|
+
// BSP-to-StaticMesh conversion is a first-class editor operation
|
|
4
|
+
// (UEditorActorSubsystem::ConvertActors) with no bridge action, so a blockout
|
|
5
|
+
// pass had to be finished by hand or through Python. The wrapper is thin; the
|
|
6
|
+
// work here is the safety checks, because ConvertActors DESTROYS the actors it
|
|
7
|
+
// converts and there is no undo path back to a builder brush once the map is
|
|
8
|
+
// saved.
|
|
9
|
+
//
|
|
10
|
+
// What it refuses, and why each one is a refusal rather than a warning:
|
|
11
|
+
//
|
|
12
|
+
// The default builder brush. It is the editor's own scratch brush, not level
|
|
13
|
+
// geometry, and destroying it leaves the brush tools without a subject.
|
|
14
|
+
// Subtractive brushes. They carve space out of additive geometry and have no
|
|
15
|
+
// surface of their own, so converting one produces an empty mesh that looks
|
|
16
|
+
// like a successful conversion.
|
|
17
|
+
// Volumes and brush shapes. Both derive from ABrush and neither is geometry
|
|
18
|
+
// a player sees; a trigger volume converted to a StaticMeshActor is a
|
|
19
|
+
// collision change disguised as a mesh.
|
|
20
|
+
//
|
|
21
|
+
// Each of those can be opted into by name, which is the difference between a
|
|
22
|
+
// guardrail and an obstacle.
|
|
23
|
+
|
|
24
|
+
#include "LevelHandlers.h"
|
|
25
|
+
|
|
26
|
+
#include "Dom/JsonObject.h"
|
|
27
|
+
#include "Dom/JsonValue.h"
|
|
28
|
+
#include "Editor.h"
|
|
29
|
+
#include "Engine/Brush.h"
|
|
30
|
+
#include "Components/StaticMeshComponent.h"
|
|
31
|
+
#include "Engine/StaticMesh.h"
|
|
32
|
+
#include "Engine/StaticMeshActor.h"
|
|
33
|
+
#include "Engine/World.h"
|
|
34
|
+
#include "EngineUtils.h"
|
|
35
|
+
#include "GameFramework/Actor.h"
|
|
36
|
+
#include "HandlerRegistry.h"
|
|
37
|
+
#include "HandlerUtils.h"
|
|
38
|
+
#include "Misc/PackageName.h"
|
|
39
|
+
#include "Subsystems/EditorActorSubsystem.h"
|
|
40
|
+
|
|
41
|
+
namespace
|
|
42
|
+
{
|
|
43
|
+
constexpr int32 MCPConvertMaxBrushes = 500;
|
|
44
|
+
|
|
45
|
+
/** Per-brush verdict, so every candidate the selector picked is accounted
|
|
46
|
+
* for rather than silently dropped from the count. */
|
|
47
|
+
const TCHAR* const MCPConvertStatusEligible = TEXT("eligible");
|
|
48
|
+
const TCHAR* const MCPConvertStatusDefaultBrush = TEXT("skipped_default_builder_brush");
|
|
49
|
+
const TCHAR* const MCPConvertStatusSubtractive = TEXT("skipped_subtractive");
|
|
50
|
+
const TCHAR* const MCPConvertStatusVolume = TEXT("skipped_volume");
|
|
51
|
+
const TCHAR* const MCPConvertStatusBrushShape = TEXT("skipped_brush_shape");
|
|
52
|
+
const TCHAR* const MCPConvertStatusNoGeometry = TEXT("skipped_no_brush_geometry");
|
|
53
|
+
const TCHAR* const MCPConvertStatusWrongClass = TEXT("skipped_class_filter");
|
|
54
|
+
|
|
55
|
+
struct FMCPConvertCandidate
|
|
56
|
+
{
|
|
57
|
+
ABrush* Brush = nullptr;
|
|
58
|
+
FString Label;
|
|
59
|
+
FString ClassName;
|
|
60
|
+
FString Status;
|
|
61
|
+
FVector Location = FVector::ZeroVector;
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
TSharedPtr<FJsonValue> FLevelHandlers::ConvertBrushesToStaticMesh(const TSharedPtr<FJsonObject>& Params)
|
|
66
|
+
{
|
|
67
|
+
MCP_CHECK_GAME_THREAD();
|
|
68
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
69
|
+
|
|
70
|
+
if (!GEditor)
|
|
71
|
+
{
|
|
72
|
+
return MCPError(TEXT("GEditor is not available"));
|
|
73
|
+
}
|
|
74
|
+
if (GEditor->PlayWorld != nullptr || GEditor->bIsSimulatingInEditor)
|
|
75
|
+
{
|
|
76
|
+
return MCPError(TEXT("Stop PIE or SIE before converting brushes"));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Selection ───────────────────────────────────────────────────────────
|
|
80
|
+
TArray<FString> ActorLabels;
|
|
81
|
+
{
|
|
82
|
+
const TArray<TSharedPtr<FJsonValue>>* LabelValues = nullptr;
|
|
83
|
+
if (Params->TryGetArrayField(TEXT("actorLabels"), LabelValues) && LabelValues)
|
|
84
|
+
{
|
|
85
|
+
ActorLabels = JsonArrayToStringList(LabelValues);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const FString FolderPath = OptionalString(Params, TEXT("folderPath"));
|
|
89
|
+
const bool bRecursiveFolder = OptionalBool(Params, TEXT("recursiveFolder"), true);
|
|
90
|
+
if (ActorLabels.IsEmpty() && FolderPath.IsEmpty())
|
|
91
|
+
{
|
|
92
|
+
return MCPError(TEXT("Pass 'actorLabels' or 'folderPath'. This destroys the actors it converts, so it will not run against a whole level implicitly."));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// An EXACT class filter, which is the thing the folder queries could not
|
|
96
|
+
// do: level(epic_get_actors_in_folder) has no class filter at all, so
|
|
97
|
+
// narrowing a folder to one brush class meant a get_actor_details round
|
|
98
|
+
// trip per entry (#911).
|
|
99
|
+
const FString ClassFilterSpec = OptionalString(Params, TEXT("classFilter"));
|
|
100
|
+
UClass* ClassFilter = nullptr;
|
|
101
|
+
if (!ClassFilterSpec.IsEmpty())
|
|
102
|
+
{
|
|
103
|
+
ClassFilter = MCPResolveClassOfType(ClassFilterSpec, ABrush::StaticClass(), true);
|
|
104
|
+
if (!ClassFilter)
|
|
105
|
+
{
|
|
106
|
+
return MCPClassNotFoundError(ClassFilterSpec, TEXT("classFilter"));
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const bool bExactClass = OptionalBool(Params, TEXT("exactClass"), true);
|
|
110
|
+
|
|
111
|
+
// ── Destination ─────────────────────────────────────────────────────────
|
|
112
|
+
FString DestinationPath = OptionalString(Params, TEXT("destinationPath"), TEXT("/Game/Meshes/Converted"));
|
|
113
|
+
DestinationPath.RemoveFromEnd(TEXT("/"));
|
|
114
|
+
if (!FPackageName::IsValidLongPackageName(DestinationPath / TEXT("Probe")))
|
|
115
|
+
{
|
|
116
|
+
return MCPError(FString::Printf(
|
|
117
|
+
TEXT("'destinationPath' is not a valid content path: %s"), *DestinationPath));
|
|
118
|
+
}
|
|
119
|
+
if (DestinationPath.StartsWith(TEXT("/Engine/")) ||
|
|
120
|
+
DestinationPath.StartsWith(TEXT("/Script/")) ||
|
|
121
|
+
DestinationPath.StartsWith(TEXT("/Temp/")))
|
|
122
|
+
{
|
|
123
|
+
return MCPError(FString::Printf(
|
|
124
|
+
TEXT("'destinationPath' is outside writable project content: %s"), *DestinationPath));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// ── Safety switches ─────────────────────────────────────────────────────
|
|
128
|
+
// dryRun defaults to TRUE. Conversion destroys the source brushes, and a
|
|
129
|
+
// preview is the only chance to notice that the selector picked up a
|
|
130
|
+
// trigger volume.
|
|
131
|
+
const bool bDryRun = OptionalBool(Params, TEXT("dryRun"), true);
|
|
132
|
+
const bool bAllowSubtractive = OptionalBool(Params, TEXT("allowSubtractive"), false);
|
|
133
|
+
const bool bIncludeVolumes = OptionalBool(Params, TEXT("includeVolumes"), false);
|
|
134
|
+
|
|
135
|
+
// ── Gather ──────────────────────────────────────────────────────────────
|
|
136
|
+
ABrush* DefaultBrush = World->GetDefaultBrush();
|
|
137
|
+
TSet<FString> WantedLabels(ActorLabels);
|
|
138
|
+
TSet<FString> FoundLabels;
|
|
139
|
+
TArray<FMCPConvertCandidate> Candidates;
|
|
140
|
+
|
|
141
|
+
for (TActorIterator<ABrush> It(World); It; ++It)
|
|
142
|
+
{
|
|
143
|
+
ABrush* Brush = *It;
|
|
144
|
+
if (!Brush) continue;
|
|
145
|
+
|
|
146
|
+
const FString Label = Brush->GetActorLabel();
|
|
147
|
+
bool bSelected = false;
|
|
148
|
+
if (!WantedLabels.IsEmpty() && WantedLabels.Contains(Label))
|
|
149
|
+
{
|
|
150
|
+
bSelected = true;
|
|
151
|
+
FoundLabels.Add(Label);
|
|
152
|
+
}
|
|
153
|
+
if (!bSelected && !FolderPath.IsEmpty())
|
|
154
|
+
{
|
|
155
|
+
const FString Folder = Brush->GetFolderPath().ToString();
|
|
156
|
+
bSelected = bRecursiveFolder
|
|
157
|
+
? (Folder.Equals(FolderPath, ESearchCase::IgnoreCase) ||
|
|
158
|
+
Folder.StartsWith(FolderPath + TEXT("/"), ESearchCase::IgnoreCase))
|
|
159
|
+
: Folder.Equals(FolderPath, ESearchCase::IgnoreCase);
|
|
160
|
+
}
|
|
161
|
+
if (!bSelected) continue;
|
|
162
|
+
|
|
163
|
+
FMCPConvertCandidate Candidate;
|
|
164
|
+
Candidate.Brush = Brush;
|
|
165
|
+
Candidate.Label = Label;
|
|
166
|
+
Candidate.ClassName = Brush->GetClass()->GetName();
|
|
167
|
+
Candidate.Location = Brush->GetActorLocation();
|
|
168
|
+
|
|
169
|
+
if (ClassFilter)
|
|
170
|
+
{
|
|
171
|
+
const bool bClassMatches = bExactClass
|
|
172
|
+
? (Brush->GetClass() == ClassFilter)
|
|
173
|
+
: Brush->IsA(ClassFilter);
|
|
174
|
+
if (!bClassMatches)
|
|
175
|
+
{
|
|
176
|
+
Candidate.Status = MCPConvertStatusWrongClass;
|
|
177
|
+
Candidates.Add(MoveTemp(Candidate));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (Brush == DefaultBrush) Candidate.Status = MCPConvertStatusDefaultBrush;
|
|
183
|
+
else if (Brush->IsBrushShape()) Candidate.Status = MCPConvertStatusBrushShape;
|
|
184
|
+
else if (Brush->IsVolumeBrush() && !bIncludeVolumes) Candidate.Status = MCPConvertStatusVolume;
|
|
185
|
+
else if (Brush->BrushType == Brush_Subtract && !bAllowSubtractive) Candidate.Status = MCPConvertStatusSubtractive;
|
|
186
|
+
else if (Brush->Brush == nullptr) Candidate.Status = MCPConvertStatusNoGeometry;
|
|
187
|
+
else Candidate.Status = MCPConvertStatusEligible;
|
|
188
|
+
|
|
189
|
+
Candidates.Add(MoveTemp(Candidate));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
TArray<AActor*> Eligible;
|
|
193
|
+
TArray<TSharedPtr<FJsonValue>> CandidateRows;
|
|
194
|
+
TMap<FString, int32> StatusCounts;
|
|
195
|
+
for (const FMCPConvertCandidate& Candidate : Candidates)
|
|
196
|
+
{
|
|
197
|
+
++StatusCounts.FindOrAdd(Candidate.Status);
|
|
198
|
+
if (Candidate.Status == MCPConvertStatusEligible)
|
|
199
|
+
{
|
|
200
|
+
Eligible.Add(Candidate.Brush);
|
|
201
|
+
}
|
|
202
|
+
TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
|
|
203
|
+
Row->SetStringField(TEXT("actorLabel"), Candidate.Label);
|
|
204
|
+
Row->SetStringField(TEXT("actorClass"), Candidate.ClassName);
|
|
205
|
+
Row->SetStringField(TEXT("status"), Candidate.Status);
|
|
206
|
+
Row->SetObjectField(TEXT("location"), MCPVec3ToJsonObject(Candidate.Location));
|
|
207
|
+
CandidateRows.Add(MakeShared<FJsonValueObject>(Row));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
TArray<FString> MissingLabels;
|
|
211
|
+
for (const FString& Label : ActorLabels)
|
|
212
|
+
{
|
|
213
|
+
if (!FoundLabels.Contains(Label)) MissingLabels.Add(Label);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
auto Result = MCPSuccess();
|
|
217
|
+
Result->SetBoolField(TEXT("dryRun"), bDryRun);
|
|
218
|
+
Result->SetStringField(TEXT("destinationPath"), DestinationPath);
|
|
219
|
+
Result->SetNumberField(TEXT("candidates"), Candidates.Num());
|
|
220
|
+
Result->SetNumberField(TEXT("eligible"), Eligible.Num());
|
|
221
|
+
Result->SetArrayField(TEXT("missingLabels"), MCPStringListToJson(MissingLabels));
|
|
222
|
+
Result->SetArrayField(TEXT("brushes"), CandidateRows);
|
|
223
|
+
{
|
|
224
|
+
TSharedPtr<FJsonObject> Summary = MakeShared<FJsonObject>();
|
|
225
|
+
for (const TPair<FString, int32>& Pair : StatusCounts)
|
|
226
|
+
{
|
|
227
|
+
Summary->SetNumberField(Pair.Key, Pair.Value);
|
|
228
|
+
}
|
|
229
|
+
Result->SetObjectField(TEXT("statusSummary"), Summary);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!MissingLabels.IsEmpty())
|
|
233
|
+
{
|
|
234
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
235
|
+
Result->SetStringField(TEXT("error"), FString::Printf(
|
|
236
|
+
TEXT("%d requested label(s) matched no brush actor in this level. Note the selector only iterates ABrush actors, so a label belonging to a non-brush actor will always miss; see missingLabels."),
|
|
237
|
+
MissingLabels.Num()));
|
|
238
|
+
return MCPResult(Result);
|
|
239
|
+
}
|
|
240
|
+
if (Eligible.Num() > MCPConvertMaxBrushes)
|
|
241
|
+
{
|
|
242
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
243
|
+
Result->SetStringField(TEXT("error"), FString::Printf(
|
|
244
|
+
TEXT("%d eligible brushes, over the maximum of %d for one call. Convert in folders."),
|
|
245
|
+
Eligible.Num(), MCPConvertMaxBrushes));
|
|
246
|
+
return MCPResult(Result);
|
|
247
|
+
}
|
|
248
|
+
if (Eligible.IsEmpty())
|
|
249
|
+
{
|
|
250
|
+
Result->SetStringField(TEXT("zeroMatchNote"),
|
|
251
|
+
TEXT("No brush was eligible. Read statusSummary: subtractive brushes, volumes, brush shapes and the default builder brush are excluded unless you opt in with allowSubtractive or includeVolumes."));
|
|
252
|
+
return MCPResult(Result);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (bDryRun)
|
|
256
|
+
{
|
|
257
|
+
Result->SetNumberField(TEXT("wouldConvert"), Eligible.Num());
|
|
258
|
+
Result->SetStringField(TEXT("dryRunNote"),
|
|
259
|
+
TEXT("dryRun defaults to TRUE for this action because conversion DESTROYS the source brushes. Pass dryRun=false to commit."));
|
|
260
|
+
return MCPResult(Result);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
UEditorActorSubsystem* ActorSubsystem = GEditor->GetEditorSubsystem<UEditorActorSubsystem>();
|
|
264
|
+
if (!ActorSubsystem)
|
|
265
|
+
{
|
|
266
|
+
return MCPError(TEXT("UEditorActorSubsystem is not available"));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Record the source identity before the call, because the actors are gone
|
|
270
|
+
// afterwards and the report would otherwise be able to say only "some
|
|
271
|
+
// number of brushes used to be here".
|
|
272
|
+
TArray<FString> ConvertedFrom;
|
|
273
|
+
for (AActor* Actor : Eligible)
|
|
274
|
+
{
|
|
275
|
+
ConvertedFrom.Add(FString::Printf(TEXT("%s (%s)"), *Actor->GetActorLabel(), *Actor->GetClass()->GetName()));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const TArray<AActor*> NewActors = ActorSubsystem->ConvertActors(
|
|
279
|
+
Eligible, AStaticMeshActor::StaticClass(), DestinationPath / FString());
|
|
280
|
+
|
|
281
|
+
TArray<FString> NewLabels;
|
|
282
|
+
TArray<FString> NewMeshPaths;
|
|
283
|
+
for (AActor* Actor : NewActors)
|
|
284
|
+
{
|
|
285
|
+
if (!Actor) continue;
|
|
286
|
+
NewLabels.Add(Actor->GetActorLabel());
|
|
287
|
+
if (const AStaticMeshActor* MeshActor = Cast<AStaticMeshActor>(Actor))
|
|
288
|
+
{
|
|
289
|
+
if (const UStaticMeshComponent* Component = MeshActor->GetStaticMeshComponent())
|
|
290
|
+
{
|
|
291
|
+
if (const UStaticMesh* Mesh = Component->GetStaticMesh())
|
|
292
|
+
{
|
|
293
|
+
NewMeshPaths.AddUnique(Mesh->GetPathName());
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
Result->SetNumberField(TEXT("converted"), NewActors.Num());
|
|
300
|
+
Result->SetArrayField(TEXT("convertedFrom"), MCPStringListToJson(ConvertedFrom));
|
|
301
|
+
Result->SetArrayField(TEXT("newActorLabels"), MCPStringListToJson(NewLabels));
|
|
302
|
+
Result->SetArrayField(TEXT("newStaticMeshes"), MCPStringListToJson(NewMeshPaths));
|
|
303
|
+
if (NewActors.IsEmpty())
|
|
304
|
+
{
|
|
305
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
306
|
+
Result->SetStringField(TEXT("error"),
|
|
307
|
+
TEXT("ConvertActors returned no new actors. The editor defers a brush conversion when it needs a destination package decision, so check whether a modal prompt is waiting in the editor window."));
|
|
308
|
+
}
|
|
309
|
+
else
|
|
310
|
+
{
|
|
311
|
+
MCPSetCreated(Result);
|
|
312
|
+
Result->SetStringField(TEXT("saveNote"),
|
|
313
|
+
TEXT("The level is left dirty and is NOT saved, and the generated static meshes are new unsaved packages. Save both when you have checked the result."));
|
|
314
|
+
}
|
|
315
|
+
return MCPResult(Result);
|
|
316
|
+
}
|
package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Inspection.cpp
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#include "LevelHandlers.h"
|
|
2
|
+
|
|
3
|
+
#include "HandlerUtils.h"
|
|
4
|
+
|
|
5
|
+
#include "Components/InstancedStaticMeshComponent.h"
|
|
6
|
+
#include "Components/StaticMeshComponent.h"
|
|
7
|
+
#include "Engine/StaticMesh.h"
|
|
8
|
+
#include "Engine/World.h"
|
|
9
|
+
#include "EngineUtils.h"
|
|
10
|
+
#include "GameFramework/Actor.h"
|
|
11
|
+
|
|
12
|
+
namespace
|
|
13
|
+
{
|
|
14
|
+
constexpr int32 DefaultStaticMeshResults = 100;
|
|
15
|
+
constexpr int32 MaxStaticMeshResults = 500;
|
|
16
|
+
constexpr int32 HardMaxUniqueStaticMeshes = 8192;
|
|
17
|
+
constexpr int32 DefaultStaticMeshOccurrences = 32;
|
|
18
|
+
constexpr int32 MaxStaticMeshOccurrences = 256;
|
|
19
|
+
|
|
20
|
+
struct FStaticMeshAggregate
|
|
21
|
+
{
|
|
22
|
+
FString MeshPath;
|
|
23
|
+
FString LastActorPath;
|
|
24
|
+
int64 ActorCount = 0;
|
|
25
|
+
int64 ComponentCount = 0;
|
|
26
|
+
int64 PlacementCount = 0;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
int64 GetPlacementCount(const UStaticMeshComponent* Component)
|
|
30
|
+
{
|
|
31
|
+
if (const UInstancedStaticMeshComponent* Instanced = Cast<UInstancedStaticMeshComponent>(Component))
|
|
32
|
+
{
|
|
33
|
+
return FMath::Max(0, Instanced->GetInstanceCount());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return 1;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void GetSortedActors(UWorld* World, TArray<AActor*>& OutActors)
|
|
40
|
+
{
|
|
41
|
+
for (TActorIterator<AActor> It(World); It; ++It)
|
|
42
|
+
{
|
|
43
|
+
if (IsValid(*It))
|
|
44
|
+
{
|
|
45
|
+
OutActors.Add(*It);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
OutActors.Sort([](const AActor& Left, const AActor& Right)
|
|
50
|
+
{
|
|
51
|
+
return Left.GetPathName() < Right.GetPathName();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
void GetSortedStaticMeshComponents(AActor* Actor, TArray<UStaticMeshComponent*>& OutComponents)
|
|
56
|
+
{
|
|
57
|
+
Actor->GetComponents<UStaticMeshComponent>(OutComponents);
|
|
58
|
+
OutComponents.RemoveAll([](const UStaticMeshComponent* Component)
|
|
59
|
+
{
|
|
60
|
+
return !IsValid(Component);
|
|
61
|
+
});
|
|
62
|
+
OutComponents.Sort([](const UStaticMeshComponent& Left, const UStaticMeshComponent& Right)
|
|
63
|
+
{
|
|
64
|
+
return Left.GetPathName() < Right.GetPathName();
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
TSharedPtr<FJsonValue> FLevelHandlers::SummarizeStaticMeshUsage(const TSharedPtr<FJsonObject>& Params)
|
|
70
|
+
{
|
|
71
|
+
check(IsInGameThread());
|
|
72
|
+
|
|
73
|
+
const FString RequestedWorld = OptionalString(Params, TEXT("world"), TEXT("editor"));
|
|
74
|
+
const bool bEditorWorld = RequestedWorld.Equals(TEXT("editor"), ESearchCase::IgnoreCase);
|
|
75
|
+
const bool bPieWorld = RequestedWorld.Equals(TEXT("pie"), ESearchCase::IgnoreCase);
|
|
76
|
+
if (!bEditorWorld && !bPieWorld)
|
|
77
|
+
{
|
|
78
|
+
return MCPError(TEXT("Invalid 'world': expected 'editor' or 'pie'"));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const FString WorldScope = bEditorWorld ? TEXT("editor") : TEXT("pie");
|
|
82
|
+
UWorld* World = ResolveWorldFromParams(Params, *WorldScope);
|
|
83
|
+
if (!World)
|
|
84
|
+
{
|
|
85
|
+
return MCPError(FString::Printf(TEXT("World not available for scope '%s'"), *WorldScope));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const int32 MaxResults = FMath::Clamp(
|
|
89
|
+
OptionalInt(Params, TEXT("maxResults"), DefaultStaticMeshResults),
|
|
90
|
+
1,
|
|
91
|
+
MaxStaticMeshResults);
|
|
92
|
+
const bool bIncludeOccurrences = OptionalBool(Params, TEXT("includeOccurrences"), false);
|
|
93
|
+
const int32 MaxOccurrences = FMath::Clamp(
|
|
94
|
+
OptionalInt(Params, TEXT("maxOccurrences"), DefaultStaticMeshOccurrences),
|
|
95
|
+
0,
|
|
96
|
+
MaxStaticMeshOccurrences);
|
|
97
|
+
|
|
98
|
+
// Actor/component iteration order is not an API guarantee. Exact object-path
|
|
99
|
+
// ordering makes aggregation and the bounded occurrence sample reproducible.
|
|
100
|
+
TArray<AActor*> Actors;
|
|
101
|
+
GetSortedActors(World, Actors);
|
|
102
|
+
|
|
103
|
+
TMap<FString, FStaticMeshAggregate> Aggregates;
|
|
104
|
+
int64 StaticMeshActors = 0;
|
|
105
|
+
int64 ScannedStaticMeshComponents = 0;
|
|
106
|
+
int64 NullMeshComponents = 0;
|
|
107
|
+
int64 TotalComponentCount = 0;
|
|
108
|
+
int64 TotalPlacementCount = 0;
|
|
109
|
+
|
|
110
|
+
for (AActor* Actor : Actors)
|
|
111
|
+
{
|
|
112
|
+
if (!IsValid(Actor))
|
|
113
|
+
{
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
TArray<UStaticMeshComponent*> Components;
|
|
118
|
+
GetSortedStaticMeshComponents(Actor, Components);
|
|
119
|
+
if (Components.Num() > 0)
|
|
120
|
+
{
|
|
121
|
+
++StaticMeshActors;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const FString ActorPath = Actor->GetPathName();
|
|
125
|
+
for (UStaticMeshComponent* Component : Components)
|
|
126
|
+
{
|
|
127
|
+
++ScannedStaticMeshComponents;
|
|
128
|
+
UStaticMesh* Mesh = Component->GetStaticMesh();
|
|
129
|
+
if (!IsValid(Mesh))
|
|
130
|
+
{
|
|
131
|
+
++NullMeshComponents;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const FString MeshPath = Mesh->GetPathName();
|
|
136
|
+
FStaticMeshAggregate* Aggregate = Aggregates.Find(MeshPath);
|
|
137
|
+
if (!Aggregate)
|
|
138
|
+
{
|
|
139
|
+
if (Aggregates.Num() >= HardMaxUniqueStaticMeshes)
|
|
140
|
+
{
|
|
141
|
+
return MCPError(FString::Printf(
|
|
142
|
+
TEXT("Static mesh usage exceeds the hard limit of %d unique loaded meshes"),
|
|
143
|
+
HardMaxUniqueStaticMeshes));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
FStaticMeshAggregate NewAggregate;
|
|
147
|
+
NewAggregate.MeshPath = MeshPath;
|
|
148
|
+
Aggregate = &Aggregates.Add(MeshPath, MoveTemp(NewAggregate));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const int64 PlacementCount = GetPlacementCount(Component);
|
|
152
|
+
++TotalComponentCount;
|
|
153
|
+
TotalPlacementCount += PlacementCount;
|
|
154
|
+
++Aggregate->ComponentCount;
|
|
155
|
+
Aggregate->PlacementCount += PlacementCount;
|
|
156
|
+
if (Aggregate->LastActorPath != ActorPath)
|
|
157
|
+
{
|
|
158
|
+
Aggregate->LastActorPath = ActorPath;
|
|
159
|
+
++Aggregate->ActorCount;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
TArray<FString> SortedMeshPaths;
|
|
165
|
+
Aggregates.GenerateKeyArray(SortedMeshPaths);
|
|
166
|
+
SortedMeshPaths.Sort([&Aggregates](const FString& LeftPath, const FString& RightPath)
|
|
167
|
+
{
|
|
168
|
+
const FStaticMeshAggregate& Left = Aggregates.FindChecked(LeftPath);
|
|
169
|
+
const FStaticMeshAggregate& Right = Aggregates.FindChecked(RightPath);
|
|
170
|
+
if (Left.PlacementCount != Right.PlacementCount)
|
|
171
|
+
{
|
|
172
|
+
return Left.PlacementCount > Right.PlacementCount;
|
|
173
|
+
}
|
|
174
|
+
return LeftPath < RightPath;
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const int32 ReturnedMeshCount = FMath::Min(MaxResults, SortedMeshPaths.Num());
|
|
178
|
+
SortedMeshPaths.SetNum(ReturnedMeshCount);
|
|
179
|
+
TSet<FString> ReturnedMeshPaths;
|
|
180
|
+
ReturnedMeshPaths.Reserve(ReturnedMeshCount);
|
|
181
|
+
for (const FString& MeshPath : SortedMeshPaths)
|
|
182
|
+
{
|
|
183
|
+
ReturnedMeshPaths.Add(MeshPath);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Occurrences are examples, globally capped across only the returned meshes.
|
|
187
|
+
// A second deterministic pass keeps omitted aggregates from consuming the cap.
|
|
188
|
+
TMap<FString, TArray<TSharedPtr<FJsonValue>>> OccurrencesByMesh;
|
|
189
|
+
int32 ReturnedOccurrences = 0;
|
|
190
|
+
if (bIncludeOccurrences && MaxOccurrences > 0)
|
|
191
|
+
{
|
|
192
|
+
for (AActor* Actor : Actors)
|
|
193
|
+
{
|
|
194
|
+
if (!IsValid(Actor) || ReturnedOccurrences >= MaxOccurrences)
|
|
195
|
+
{
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
TArray<UStaticMeshComponent*> Components;
|
|
200
|
+
GetSortedStaticMeshComponents(Actor, Components);
|
|
201
|
+
for (UStaticMeshComponent* Component : Components)
|
|
202
|
+
{
|
|
203
|
+
if (ReturnedOccurrences >= MaxOccurrences)
|
|
204
|
+
{
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
UStaticMesh* Mesh = Component->GetStaticMesh();
|
|
209
|
+
if (!IsValid(Mesh) || !ReturnedMeshPaths.Contains(Mesh->GetPathName()))
|
|
210
|
+
{
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
TSharedPtr<FJsonObject> Occurrence = MakeShared<FJsonObject>();
|
|
215
|
+
Occurrence->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
216
|
+
Occurrence->SetStringField(TEXT("actorLabel"), Actor->GetActorLabel());
|
|
217
|
+
Occurrence->SetStringField(TEXT("componentPath"), Component->GetPathName());
|
|
218
|
+
Occurrence->SetStringField(TEXT("componentName"), Component->GetName());
|
|
219
|
+
Occurrence->SetStringField(TEXT("componentClass"), Component->GetClass()->GetPathName());
|
|
220
|
+
Occurrence->SetNumberField(TEXT("placementCount"), static_cast<double>(GetPlacementCount(Component)));
|
|
221
|
+
OccurrencesByMesh.FindOrAdd(Mesh->GetPathName()).Add(MakeShared<FJsonValueObject>(Occurrence));
|
|
222
|
+
++ReturnedOccurrences;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
TArray<TSharedPtr<FJsonValue>> MeshesJson;
|
|
228
|
+
MeshesJson.Reserve(ReturnedMeshCount);
|
|
229
|
+
int64 ReturnedComponentCount = 0;
|
|
230
|
+
for (const FString& MeshPath : SortedMeshPaths)
|
|
231
|
+
{
|
|
232
|
+
const FStaticMeshAggregate& Aggregate = Aggregates.FindChecked(MeshPath);
|
|
233
|
+
ReturnedComponentCount += Aggregate.ComponentCount;
|
|
234
|
+
|
|
235
|
+
TSharedPtr<FJsonObject> MeshJson = MakeShared<FJsonObject>();
|
|
236
|
+
MeshJson->SetStringField(TEXT("meshPath"), Aggregate.MeshPath);
|
|
237
|
+
MeshJson->SetNumberField(TEXT("actorCount"), static_cast<double>(Aggregate.ActorCount));
|
|
238
|
+
MeshJson->SetNumberField(TEXT("componentCount"), static_cast<double>(Aggregate.ComponentCount));
|
|
239
|
+
MeshJson->SetNumberField(TEXT("placementCount"), static_cast<double>(Aggregate.PlacementCount));
|
|
240
|
+
|
|
241
|
+
if (bIncludeOccurrences)
|
|
242
|
+
{
|
|
243
|
+
const TArray<TSharedPtr<FJsonValue>>* Occurrences = OccurrencesByMesh.Find(MeshPath);
|
|
244
|
+
MeshJson->SetArrayField(
|
|
245
|
+
TEXT("occurrences"),
|
|
246
|
+
Occurrences ? *Occurrences : TArray<TSharedPtr<FJsonValue>>());
|
|
247
|
+
MeshJson->SetBoolField(
|
|
248
|
+
TEXT("occurrencesTruncated"),
|
|
249
|
+
!Occurrences || Occurrences->Num() < Aggregate.ComponentCount);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
MeshesJson.Add(MakeShared<FJsonValueObject>(MeshJson));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
TSharedPtr<FJsonObject> Result = MCPSuccess();
|
|
256
|
+
Result->SetStringField(TEXT("world"), WorldScope);
|
|
257
|
+
Result->SetStringField(TEXT("worldName"), World->GetName());
|
|
258
|
+
Result->SetBoolField(TEXT("loadedOnly"), true);
|
|
259
|
+
Result->SetNumberField(TEXT("maxResults"), MaxResults);
|
|
260
|
+
Result->SetBoolField(TEXT("includeOccurrences"), bIncludeOccurrences);
|
|
261
|
+
Result->SetNumberField(TEXT("maxOccurrences"), MaxOccurrences);
|
|
262
|
+
Result->SetNumberField(TEXT("hardUniqueMeshLimit"), HardMaxUniqueStaticMeshes);
|
|
263
|
+
Result->SetNumberField(TEXT("actorsScanned"), Actors.Num());
|
|
264
|
+
Result->SetNumberField(TEXT("staticMeshActors"), static_cast<double>(StaticMeshActors));
|
|
265
|
+
Result->SetNumberField(TEXT("scannedStaticMeshComponents"), static_cast<double>(ScannedStaticMeshComponents));
|
|
266
|
+
Result->SetNumberField(TEXT("nullMeshComponents"), static_cast<double>(NullMeshComponents));
|
|
267
|
+
Result->SetNumberField(TEXT("totalComponentCount"), static_cast<double>(TotalComponentCount));
|
|
268
|
+
Result->SetNumberField(TEXT("totalPlacementCount"), static_cast<double>(TotalPlacementCount));
|
|
269
|
+
Result->SetNumberField(TEXT("totalUniqueMeshes"), Aggregates.Num());
|
|
270
|
+
Result->SetNumberField(TEXT("returnedMeshCount"), ReturnedMeshCount);
|
|
271
|
+
Result->SetBoolField(TEXT("truncated"), ReturnedMeshCount < Aggregates.Num());
|
|
272
|
+
Result->SetNumberField(TEXT("totalOccurrences"), static_cast<double>(TotalComponentCount));
|
|
273
|
+
Result->SetNumberField(TEXT("eligibleOccurrencesForReturnedMeshes"), static_cast<double>(ReturnedComponentCount));
|
|
274
|
+
Result->SetNumberField(TEXT("returnedOccurrences"), ReturnedOccurrences);
|
|
275
|
+
Result->SetBoolField(
|
|
276
|
+
TEXT("occurrencesTruncated"),
|
|
277
|
+
bIncludeOccurrences && ReturnedOccurrences < TotalComponentCount);
|
|
278
|
+
Result->SetArrayField(TEXT("meshes"), MeshesJson);
|
|
279
|
+
return MCPResult(Result);
|
|
280
|
+
}
|