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,666 @@
|
|
|
1
|
+
// Bounded batch projection for ISMC/HISMC instances.
|
|
2
|
+
// Registration stays in LevelHandlers.cpp::RegisterHandlers.
|
|
3
|
+
|
|
4
|
+
#include "LevelHandlers.h"
|
|
5
|
+
#include "LevelHandlers_InstanceProjection_Internal.h"
|
|
6
|
+
#include "HandlerUtils.h"
|
|
7
|
+
#include "Editor.h"
|
|
8
|
+
#include "ScopedTransaction.h"
|
|
9
|
+
#include "CollisionQueryParams.h"
|
|
10
|
+
#include "Components/InstancedStaticMeshComponent.h"
|
|
11
|
+
#include "Components/PrimitiveComponent.h"
|
|
12
|
+
#include "Engine/CollisionProfile.h"
|
|
13
|
+
#include "Engine/EngineTypes.h"
|
|
14
|
+
#include "Engine/HitResult.h"
|
|
15
|
+
#include "Engine/World.h"
|
|
16
|
+
#include "EngineUtils.h"
|
|
17
|
+
#include "GameFramework/Actor.h"
|
|
18
|
+
#include "UObject/Package.h"
|
|
19
|
+
|
|
20
|
+
namespace UEMCPInstanceProjection
|
|
21
|
+
{
|
|
22
|
+
bool ParseMissPolicy(const FString& InValue, EMissPolicy& OutPolicy)
|
|
23
|
+
{
|
|
24
|
+
const FString Value = InValue.TrimStartAndEnd();
|
|
25
|
+
if (Value.Equals(TEXT("error"), ESearchCase::IgnoreCase))
|
|
26
|
+
{
|
|
27
|
+
OutPolicy = EMissPolicy::Error;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (Value.Equals(TEXT("skip"), ESearchCase::IgnoreCase))
|
|
31
|
+
{
|
|
32
|
+
OutPolicy = EMissPolicy::Skip;
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
FVector ApplySurfaceOffset(const FVector& ImpactPoint, const FVector& ImpactNormal, double SurfaceOffset)
|
|
39
|
+
{
|
|
40
|
+
return ImpactPoint + ImpactNormal.GetSafeNormal() * SurfaceOffset;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
namespace
|
|
45
|
+
{
|
|
46
|
+
using namespace UEMCPInstanceProjection;
|
|
47
|
+
|
|
48
|
+
UInstancedStaticMeshComponent* ResolveProjectionComponent(AActor* Actor, const FString& ComponentName, FString& OutError)
|
|
49
|
+
{
|
|
50
|
+
if (!Actor)
|
|
51
|
+
{
|
|
52
|
+
OutError = TEXT("Target actor is null");
|
|
53
|
+
return nullptr;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
TArray<UInstancedStaticMeshComponent*> Matches;
|
|
57
|
+
for (UActorComponent* Component : Actor->GetComponents())
|
|
58
|
+
{
|
|
59
|
+
if (UInstancedStaticMeshComponent* ISMC = Cast<UInstancedStaticMeshComponent>(Component))
|
|
60
|
+
{
|
|
61
|
+
if (ComponentName.IsEmpty() || ISMC->GetName() == ComponentName)
|
|
62
|
+
{
|
|
63
|
+
Matches.Add(ISMC);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (Matches.Num() == 1) return Matches[0];
|
|
69
|
+
if (Matches.IsEmpty())
|
|
70
|
+
{
|
|
71
|
+
OutError = ComponentName.IsEmpty()
|
|
72
|
+
? FString::Printf(TEXT("Actor '%s' has no InstancedStaticMeshComponent"), *Actor->GetActorLabel())
|
|
73
|
+
: FString::Printf(TEXT("Actor '%s' has no InstancedStaticMeshComponent named '%s'"), *Actor->GetActorLabel(), *ComponentName);
|
|
74
|
+
return nullptr;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
TArray<FString> Names;
|
|
78
|
+
for (const UInstancedStaticMeshComponent* Match : Matches) Names.Add(Match->GetName());
|
|
79
|
+
OutError = FString::Printf(
|
|
80
|
+
TEXT("Actor '%s' has %d instanced mesh components (%s); pass componentName to select exactly one"),
|
|
81
|
+
*Actor->GetActorLabel(), Matches.Num(), *FString::Join(Names, TEXT(", ")));
|
|
82
|
+
return nullptr;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
bool ResolveProjectionTraceChannel(const FString& InName, ECollisionChannel& OutChannel, FString& OutResolvedName)
|
|
86
|
+
{
|
|
87
|
+
FString Name = InName.TrimStartAndEnd();
|
|
88
|
+
if (Name.StartsWith(TEXT("ECC_"))) Name = Name.RightChop(4);
|
|
89
|
+
if (Name.IsEmpty()) Name = TEXT("Visibility");
|
|
90
|
+
|
|
91
|
+
if (const UCollisionProfile* Profile = UCollisionProfile::Get())
|
|
92
|
+
{
|
|
93
|
+
for (int32 Index = 0; Index < ECC_MAX; ++Index)
|
|
94
|
+
{
|
|
95
|
+
const FName ChannelName = Profile->ReturnChannelNameFromContainerIndex(Index);
|
|
96
|
+
if (!ChannelName.IsNone() && ChannelName.ToString().Equals(Name, ESearchCase::IgnoreCase))
|
|
97
|
+
{
|
|
98
|
+
OutChannel = static_cast<ECollisionChannel>(Index);
|
|
99
|
+
OutResolvedName = ChannelName.ToString();
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
struct FBuiltInChannel { const TCHAR* Name; ECollisionChannel Channel; };
|
|
106
|
+
static const FBuiltInChannel BuiltIns[] = {
|
|
107
|
+
{ TEXT("WorldStatic"), ECC_WorldStatic }, { TEXT("WorldDynamic"), ECC_WorldDynamic },
|
|
108
|
+
{ TEXT("Pawn"), ECC_Pawn }, { TEXT("Visibility"), ECC_Visibility },
|
|
109
|
+
{ TEXT("Camera"), ECC_Camera }, { TEXT("PhysicsBody"), ECC_PhysicsBody },
|
|
110
|
+
{ TEXT("Vehicle"), ECC_Vehicle }, { TEXT("Destructible"), ECC_Destructible },
|
|
111
|
+
};
|
|
112
|
+
for (const FBuiltInChannel& Entry : BuiltIns)
|
|
113
|
+
{
|
|
114
|
+
if (Name.Equals(Entry.Name, ESearchCase::IgnoreCase))
|
|
115
|
+
{
|
|
116
|
+
OutChannel = Entry.Channel;
|
|
117
|
+
OutResolvedName = Entry.Name;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
TSharedPtr<FJsonValue> ParseInstanceIndices(
|
|
125
|
+
const TSharedPtr<FJsonObject>& Params,
|
|
126
|
+
int32 InstanceCount,
|
|
127
|
+
int32 MaxInstances,
|
|
128
|
+
TArray<int32>& OutIndices)
|
|
129
|
+
{
|
|
130
|
+
const TArray<TSharedPtr<FJsonValue>>* Values = nullptr;
|
|
131
|
+
if (!Params->HasField(TEXT("instanceIndices")))
|
|
132
|
+
{
|
|
133
|
+
if (InstanceCount > MaxInstances)
|
|
134
|
+
{
|
|
135
|
+
return MCPError(FString::Printf(
|
|
136
|
+
TEXT("Refusing to project all %d instances because maxInstances is %d. Pass a bounded instanceIndices subset or explicitly raise maxInstances up to %d."),
|
|
137
|
+
InstanceCount, MaxInstances, HardMaxInstances));
|
|
138
|
+
}
|
|
139
|
+
OutIndices.Reserve(InstanceCount);
|
|
140
|
+
for (int32 Index = 0; Index < InstanceCount; ++Index) OutIndices.Add(Index);
|
|
141
|
+
return nullptr;
|
|
142
|
+
}
|
|
143
|
+
if (!Params->TryGetArrayField(TEXT("instanceIndices"), Values) || !Values)
|
|
144
|
+
{
|
|
145
|
+
return MCPError(TEXT("instanceIndices must be an array of integers"));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (Values->IsEmpty()) return MCPError(TEXT("instanceIndices must not be empty"));
|
|
149
|
+
if (Values->Num() > MaxInstances)
|
|
150
|
+
{
|
|
151
|
+
return MCPError(FString::Printf(
|
|
152
|
+
TEXT("Refusing to project %d requested instances because maxInstances is %d. Raise maxInstances only for an intentionally bounded batch (hard limit %d)."),
|
|
153
|
+
Values->Num(), MaxInstances, HardMaxInstances));
|
|
154
|
+
}
|
|
155
|
+
TSet<int32> Seen;
|
|
156
|
+
OutIndices.Reserve(Values->Num());
|
|
157
|
+
for (int32 Position = 0; Position < Values->Num(); ++Position)
|
|
158
|
+
{
|
|
159
|
+
double Raw = 0.0;
|
|
160
|
+
if (!(*Values)[Position].IsValid()
|
|
161
|
+
|| !(*Values)[Position]->TryGetNumber(Raw)
|
|
162
|
+
|| !FMath::IsFinite(Raw)
|
|
163
|
+
|| Raw != FMath::RoundToDouble(Raw))
|
|
164
|
+
{
|
|
165
|
+
return MCPError(FString::Printf(TEXT("instanceIndices[%d] must be an integer"), Position));
|
|
166
|
+
}
|
|
167
|
+
const int64 WideIndex = FMath::RoundToInt64(Raw);
|
|
168
|
+
if (WideIndex < 0 || WideIndex >= InstanceCount)
|
|
169
|
+
{
|
|
170
|
+
return MCPError(FString::Printf(
|
|
171
|
+
TEXT("instanceIndices[%d] is %lld, outside the valid range 0..%d"),
|
|
172
|
+
Position, WideIndex, InstanceCount - 1));
|
|
173
|
+
}
|
|
174
|
+
const int32 Index = static_cast<int32>(WideIndex);
|
|
175
|
+
if (Seen.Contains(Index))
|
|
176
|
+
{
|
|
177
|
+
return MCPError(FString::Printf(TEXT("instanceIndices contains duplicate index %d"), Index));
|
|
178
|
+
}
|
|
179
|
+
Seen.Add(Index);
|
|
180
|
+
OutIndices.Add(Index);
|
|
181
|
+
}
|
|
182
|
+
OutIndices.Sort();
|
|
183
|
+
return nullptr;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
bool HitMatchesSurface(
|
|
187
|
+
const FHitResult& Hit,
|
|
188
|
+
UClass* RequiredClass,
|
|
189
|
+
const TSet<FString>& RequiredLabels)
|
|
190
|
+
{
|
|
191
|
+
AActor* HitActor = Hit.GetActor();
|
|
192
|
+
if (!HitActor) return false;
|
|
193
|
+
if (RequiredClass && !HitActor->IsA(RequiredClass)) return false;
|
|
194
|
+
if (!RequiredLabels.IsEmpty() && !RequiredLabels.Contains(HitActor->GetActorLabel())) return false;
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
bool FindMatchingSurfaceHit(
|
|
199
|
+
UWorld* World,
|
|
200
|
+
const FVector& Start,
|
|
201
|
+
const FVector& End,
|
|
202
|
+
ECollisionChannel Channel,
|
|
203
|
+
bool bTraceComplex,
|
|
204
|
+
AActor* SourceActor,
|
|
205
|
+
UClass* RequiredClass,
|
|
206
|
+
const TSet<FString>& RequiredLabels,
|
|
207
|
+
FHitResult& OutHit,
|
|
208
|
+
bool& bOutRejectedHit,
|
|
209
|
+
bool& bOutReachedFilterLimit)
|
|
210
|
+
{
|
|
211
|
+
bOutRejectedHit = false;
|
|
212
|
+
bOutReachedFilterLimit = false;
|
|
213
|
+
FCollisionQueryParams Query(SCENE_QUERY_STAT(MCPSnapInstancesToSurface), bTraceComplex);
|
|
214
|
+
Query.bReturnFaceIndex = bTraceComplex;
|
|
215
|
+
Query.AddIgnoredActor(SourceActor);
|
|
216
|
+
|
|
217
|
+
// A channel trace stops at its closest blocking hit. Iteratively ignore a
|
|
218
|
+
// rejected blocker so a requested landscape or named surface behind it can
|
|
219
|
+
// still be reached without executing an unbounded trace loop.
|
|
220
|
+
for (int32 Attempt = 0; Attempt < MaxFilteredSurfaceHits; ++Attempt)
|
|
221
|
+
{
|
|
222
|
+
FHitResult Candidate;
|
|
223
|
+
if (!World->LineTraceSingleByChannel(Candidate, Start, End, Channel, Query))
|
|
224
|
+
{
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
if (HitMatchesSurface(Candidate, RequiredClass, RequiredLabels))
|
|
228
|
+
{
|
|
229
|
+
OutHit = Candidate;
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
bOutRejectedHit = true;
|
|
234
|
+
if (AActor* RejectedActor = Candidate.GetActor())
|
|
235
|
+
{
|
|
236
|
+
Query.AddIgnoredActor(RejectedActor);
|
|
237
|
+
}
|
|
238
|
+
else if (UPrimitiveComponent* RejectedComponent = Candidate.GetComponent())
|
|
239
|
+
{
|
|
240
|
+
Query.AddIgnoredComponent(RejectedComponent);
|
|
241
|
+
}
|
|
242
|
+
else
|
|
243
|
+
{
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
bOutReachedFilterLimit = true;
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
struct FProjectionRun
|
|
253
|
+
{
|
|
254
|
+
int32 StartIndex = INDEX_NONE;
|
|
255
|
+
TArray<FTransform> Before;
|
|
256
|
+
TArray<FTransform> After;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
TArray<FProjectionRun> BuildProjectionRuns(const FProjectionPlan& Plan)
|
|
260
|
+
{
|
|
261
|
+
TArray<FProjectionRun> Runs;
|
|
262
|
+
for (const FProjectionPlanEntry& Entry : Plan.Entries)
|
|
263
|
+
{
|
|
264
|
+
if (!Entry.bHit) continue;
|
|
265
|
+
if (Runs.IsEmpty() || Entry.InstanceIndex != Runs.Last().StartIndex + Runs.Last().After.Num())
|
|
266
|
+
{
|
|
267
|
+
FProjectionRun& NewRun = Runs.AddDefaulted_GetRef();
|
|
268
|
+
NewRun.StartIndex = Entry.InstanceIndex;
|
|
269
|
+
}
|
|
270
|
+
Runs.Last().Before.Add(Entry.Before);
|
|
271
|
+
Runs.Last().After.Add(Entry.After);
|
|
272
|
+
}
|
|
273
|
+
return Runs;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
TSharedPtr<FJsonObject> MakeProjectionSample(const FProjectionPlanEntry& Entry)
|
|
277
|
+
{
|
|
278
|
+
TSharedPtr<FJsonObject> Sample = MakeShared<FJsonObject>();
|
|
279
|
+
Sample->SetNumberField(TEXT("index"), Entry.InstanceIndex);
|
|
280
|
+
Sample->SetBoolField(TEXT("hit"), Entry.bHit);
|
|
281
|
+
Sample->SetObjectField(TEXT("from"), MCPVec3ToJsonObject(Entry.Before.GetLocation()));
|
|
282
|
+
if (Entry.bHit)
|
|
283
|
+
{
|
|
284
|
+
Sample->SetObjectField(TEXT("to"), MCPVec3ToJsonObject(Entry.After.GetLocation()));
|
|
285
|
+
Sample->SetStringField(TEXT("hitActorLabel"), Entry.HitActorLabel);
|
|
286
|
+
Sample->SetStringField(TEXT("hitActorClass"), Entry.HitActorClass);
|
|
287
|
+
}
|
|
288
|
+
else
|
|
289
|
+
{
|
|
290
|
+
Sample->SetStringField(TEXT("missReason"), Entry.MissReason);
|
|
291
|
+
}
|
|
292
|
+
return Sample;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
TSharedPtr<FJsonValue> UEMCPInstanceProjection::SnapInstancesToSurfaceInWorld(
|
|
297
|
+
UWorld* World,
|
|
298
|
+
const TSharedPtr<FJsonObject>& Params)
|
|
299
|
+
{
|
|
300
|
+
using namespace UEMCPInstanceProjection;
|
|
301
|
+
if (!World) return MCPError(TEXT("World is not available"));
|
|
302
|
+
if (!Params) return MCPError(TEXT("params must be an object"));
|
|
303
|
+
|
|
304
|
+
FString ActorLabel;
|
|
305
|
+
if (TSharedPtr<FJsonValue> Error = RequireStringAlt(Params, TEXT("actorLabel"), TEXT("actorPath"), ActorLabel)) return Error;
|
|
306
|
+
TSharedPtr<FJsonValue> ActorErr;
|
|
307
|
+
AActor* Actor = MCPResolveActor(World, Params, ActorErr);
|
|
308
|
+
if (!Actor) return ActorErr;
|
|
309
|
+
ActorLabel = Actor->GetActorLabel();
|
|
310
|
+
|
|
311
|
+
FString ComponentName;
|
|
312
|
+
if (Params->HasField(TEXT("componentName"))
|
|
313
|
+
&& !Params->TryGetStringField(TEXT("componentName"), ComponentName))
|
|
314
|
+
{
|
|
315
|
+
return MCPError(TEXT("componentName must be a string"));
|
|
316
|
+
}
|
|
317
|
+
FString ComponentError;
|
|
318
|
+
UInstancedStaticMeshComponent* ISMC = ResolveProjectionComponent(
|
|
319
|
+
Actor, ComponentName, ComponentError);
|
|
320
|
+
if (!ISMC) return MCPError(ComponentError);
|
|
321
|
+
|
|
322
|
+
const int32 InstanceCount = ISMC->GetInstanceCount();
|
|
323
|
+
if (InstanceCount <= 0) return MCPError(FString::Printf(TEXT("Component '%s' has no instances"), *ISMC->GetName()));
|
|
324
|
+
|
|
325
|
+
int32 MaxInstances = DefaultMaxInstances;
|
|
326
|
+
if (Params->HasField(TEXT("maxInstances")))
|
|
327
|
+
{
|
|
328
|
+
double RawMaxInstances = 0.0;
|
|
329
|
+
if (!Params->TryGetNumberField(TEXT("maxInstances"), RawMaxInstances)
|
|
330
|
+
|| !FMath::IsFinite(RawMaxInstances)
|
|
331
|
+
|| RawMaxInstances != FMath::RoundToDouble(RawMaxInstances))
|
|
332
|
+
{
|
|
333
|
+
return MCPError(TEXT("maxInstances must be an integer"));
|
|
334
|
+
}
|
|
335
|
+
if (RawMaxInstances < 1.0 || RawMaxInstances > HardMaxInstances)
|
|
336
|
+
{
|
|
337
|
+
return MCPError(FString::Printf(
|
|
338
|
+
TEXT("maxInstances must be between 1 and %d (default %d)"),
|
|
339
|
+
HardMaxInstances, DefaultMaxInstances));
|
|
340
|
+
}
|
|
341
|
+
MaxInstances = static_cast<int32>(RawMaxInstances);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
TArray<int32> Indices;
|
|
345
|
+
if (TSharedPtr<FJsonValue> Error = ParseInstanceIndices(
|
|
346
|
+
Params, InstanceCount, MaxInstances, Indices))
|
|
347
|
+
{
|
|
348
|
+
return Error;
|
|
349
|
+
}
|
|
350
|
+
if (Indices.Num() > MaxInstances)
|
|
351
|
+
{
|
|
352
|
+
return MCPError(FString::Printf(
|
|
353
|
+
TEXT("Refusing to project %d instances because maxInstances is %d. Pass a bounded instanceIndices subset or explicitly raise maxInstances up to %d."),
|
|
354
|
+
Indices.Num(), MaxInstances, HardMaxInstances));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
FVector Direction(0.0, 0.0, -1.0);
|
|
358
|
+
if (Params->HasField(TEXT("direction")))
|
|
359
|
+
{
|
|
360
|
+
if (TSharedPtr<FJsonValue> Error = RequireVec3(Params, TEXT("direction"), Direction)) return Error;
|
|
361
|
+
}
|
|
362
|
+
if (Direction.ContainsNaN() || !Direction.Normalize())
|
|
363
|
+
{
|
|
364
|
+
return MCPError(TEXT("direction must be a finite, non-zero vector"));
|
|
365
|
+
}
|
|
366
|
+
double TraceStartOffset = 1000.0;
|
|
367
|
+
double TraceDistance = 200000.0;
|
|
368
|
+
double SurfaceOffset = 0.0;
|
|
369
|
+
if (Params->HasField(TEXT("traceStartOffset"))
|
|
370
|
+
&& !Params->TryGetNumberField(TEXT("traceStartOffset"), TraceStartOffset))
|
|
371
|
+
{
|
|
372
|
+
return MCPError(TEXT("traceStartOffset must be a number"));
|
|
373
|
+
}
|
|
374
|
+
if (Params->HasField(TEXT("traceDistance"))
|
|
375
|
+
&& !Params->TryGetNumberField(TEXT("traceDistance"), TraceDistance))
|
|
376
|
+
{
|
|
377
|
+
return MCPError(TEXT("traceDistance must be a number"));
|
|
378
|
+
}
|
|
379
|
+
if (Params->HasField(TEXT("surfaceOffset"))
|
|
380
|
+
&& !Params->TryGetNumberField(TEXT("surfaceOffset"), SurfaceOffset))
|
|
381
|
+
{
|
|
382
|
+
return MCPError(TEXT("surfaceOffset must be a number"));
|
|
383
|
+
}
|
|
384
|
+
if (!FMath::IsFinite(TraceStartOffset) || TraceStartOffset < 0.0)
|
|
385
|
+
{
|
|
386
|
+
return MCPError(TEXT("traceStartOffset must be a finite number greater than or equal to zero"));
|
|
387
|
+
}
|
|
388
|
+
if (!FMath::IsFinite(TraceDistance) || TraceDistance <= 0.0)
|
|
389
|
+
{
|
|
390
|
+
return MCPError(TEXT("traceDistance must be a finite number greater than zero"));
|
|
391
|
+
}
|
|
392
|
+
if (!FMath::IsFinite(SurfaceOffset)) return MCPError(TEXT("surfaceOffset must be finite"));
|
|
393
|
+
|
|
394
|
+
EMissPolicy MissPolicy = EMissPolicy::Error;
|
|
395
|
+
FString MissPolicyText = TEXT("error");
|
|
396
|
+
if (Params->HasField(TEXT("onMiss"))
|
|
397
|
+
&& !Params->TryGetStringField(TEXT("onMiss"), MissPolicyText))
|
|
398
|
+
{
|
|
399
|
+
return MCPError(TEXT("onMiss must be a string"));
|
|
400
|
+
}
|
|
401
|
+
if (!ParseMissPolicy(MissPolicyText, MissPolicy))
|
|
402
|
+
{
|
|
403
|
+
return MCPError(FString::Printf(TEXT("Unknown onMiss policy '%s'; expected 'error' or 'skip'"), *MissPolicyText));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
UClass* SurfaceClass = nullptr;
|
|
407
|
+
FString SurfaceClassSpec;
|
|
408
|
+
if (Params->HasField(TEXT("surfaceActorClass"))
|
|
409
|
+
&& !Params->TryGetStringField(TEXT("surfaceActorClass"), SurfaceClassSpec))
|
|
410
|
+
{
|
|
411
|
+
return MCPError(TEXT("surfaceActorClass must be a string"));
|
|
412
|
+
}
|
|
413
|
+
if (!SurfaceClassSpec.IsEmpty())
|
|
414
|
+
{
|
|
415
|
+
SurfaceClass = MCPResolveClassOfType(SurfaceClassSpec, AActor::StaticClass());
|
|
416
|
+
if (!SurfaceClass) return MCPClassNotFoundError(SurfaceClassSpec, TEXT("surfaceActorClass"));
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
TSet<FString> SurfaceActorLabels;
|
|
420
|
+
const TArray<TSharedPtr<FJsonValue>>* LabelValues = nullptr;
|
|
421
|
+
if (Params->HasField(TEXT("surfaceActorLabels"))
|
|
422
|
+
&& (!Params->TryGetArrayField(TEXT("surfaceActorLabels"), LabelValues) || !LabelValues))
|
|
423
|
+
{
|
|
424
|
+
return MCPError(TEXT("surfaceActorLabels must be an array of non-empty strings"));
|
|
425
|
+
}
|
|
426
|
+
if (LabelValues && LabelValues->IsEmpty())
|
|
427
|
+
{
|
|
428
|
+
return MCPError(TEXT("surfaceActorLabels must not be empty when provided; omit it for no label filter"));
|
|
429
|
+
}
|
|
430
|
+
if (LabelValues)
|
|
431
|
+
{
|
|
432
|
+
for (int32 Position = 0; Position < LabelValues->Num(); ++Position)
|
|
433
|
+
{
|
|
434
|
+
FString Label;
|
|
435
|
+
if (!(*LabelValues)[Position].IsValid() || !(*LabelValues)[Position]->TryGetString(Label))
|
|
436
|
+
{
|
|
437
|
+
return MCPError(FString::Printf(TEXT("surfaceActorLabels[%d] must be a non-empty string"), Position));
|
|
438
|
+
}
|
|
439
|
+
Label = Label.TrimStartAndEnd();
|
|
440
|
+
if (Label.IsEmpty())
|
|
441
|
+
{
|
|
442
|
+
return MCPError(FString::Printf(TEXT("surfaceActorLabels[%d] must be a non-empty string"), Position));
|
|
443
|
+
}
|
|
444
|
+
SurfaceActorLabels.Add(Label);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
ECollisionChannel Channel = ECC_Visibility;
|
|
449
|
+
FString ChannelName;
|
|
450
|
+
FString RequestedChannel = TEXT("Visibility");
|
|
451
|
+
if (Params->HasField(TEXT("channel"))
|
|
452
|
+
&& !Params->TryGetStringField(TEXT("channel"), RequestedChannel))
|
|
453
|
+
{
|
|
454
|
+
return MCPError(TEXT("channel must be a string"));
|
|
455
|
+
}
|
|
456
|
+
if (!ResolveProjectionTraceChannel(RequestedChannel, Channel, ChannelName))
|
|
457
|
+
{
|
|
458
|
+
return MCPError(FString::Printf(TEXT("Unknown collision channel '%s'"), *RequestedChannel));
|
|
459
|
+
}
|
|
460
|
+
bool bTraceComplex = false;
|
|
461
|
+
bool bDryRun = true;
|
|
462
|
+
if (Params->HasField(TEXT("traceComplex"))
|
|
463
|
+
&& !Params->TryGetBoolField(TEXT("traceComplex"), bTraceComplex))
|
|
464
|
+
{
|
|
465
|
+
return MCPError(TEXT("traceComplex must be a boolean"));
|
|
466
|
+
}
|
|
467
|
+
if (Params->HasField(TEXT("dryRun"))
|
|
468
|
+
&& !Params->TryGetBoolField(TEXT("dryRun"), bDryRun))
|
|
469
|
+
{
|
|
470
|
+
return MCPError(TEXT("dryRun must be a boolean"));
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
FProjectionPlan Plan;
|
|
474
|
+
Plan.Entries.Reserve(Indices.Num());
|
|
475
|
+
for (const int32 Index : Indices)
|
|
476
|
+
{
|
|
477
|
+
FProjectionPlanEntry Entry;
|
|
478
|
+
Entry.InstanceIndex = Index;
|
|
479
|
+
if (!ISMC->GetInstanceTransform(Index, Entry.Before, true))
|
|
480
|
+
{
|
|
481
|
+
return MCPError(FString::Printf(TEXT("Failed to read instance transform at index %d"), Index));
|
|
482
|
+
}
|
|
483
|
+
if (Entry.Before.ContainsNaN())
|
|
484
|
+
{
|
|
485
|
+
return MCPError(FString::Printf(TEXT("Instance transform at index %d is not finite"), Index));
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const FVector Start = Entry.Before.GetLocation() - Direction * TraceStartOffset;
|
|
489
|
+
const FVector End = Start + Direction * (TraceStartOffset + TraceDistance);
|
|
490
|
+
if (Start.ContainsNaN() || End.ContainsNaN())
|
|
491
|
+
{
|
|
492
|
+
return MCPError(FString::Printf(
|
|
493
|
+
TEXT("Trace bounds overflowed for instance %d; reduce traceStartOffset or traceDistance"),
|
|
494
|
+
Index));
|
|
495
|
+
}
|
|
496
|
+
FHitResult AcceptedHit;
|
|
497
|
+
bool bRejectedHit = false;
|
|
498
|
+
bool bReachedFilterLimit = false;
|
|
499
|
+
if (!FindMatchingSurfaceHit(
|
|
500
|
+
World,
|
|
501
|
+
Start,
|
|
502
|
+
End,
|
|
503
|
+
Channel,
|
|
504
|
+
bTraceComplex,
|
|
505
|
+
Actor,
|
|
506
|
+
SurfaceClass,
|
|
507
|
+
SurfaceActorLabels,
|
|
508
|
+
AcceptedHit,
|
|
509
|
+
bRejectedHit,
|
|
510
|
+
bReachedFilterLimit))
|
|
511
|
+
{
|
|
512
|
+
Entry.MissReason = bReachedFilterLimit
|
|
513
|
+
? TEXT("surface_filter_limit_reached")
|
|
514
|
+
: (bRejectedHit ? TEXT("no_hit_matched_surface_filter") : TEXT("trace_missed"));
|
|
515
|
+
++Plan.MissCount;
|
|
516
|
+
}
|
|
517
|
+
else
|
|
518
|
+
{
|
|
519
|
+
Entry.bHit = true;
|
|
520
|
+
Entry.After = Entry.Before;
|
|
521
|
+
Entry.After.SetLocation(ApplySurfaceOffset(AcceptedHit.ImpactPoint, AcceptedHit.ImpactNormal, SurfaceOffset));
|
|
522
|
+
if (Entry.After.ContainsNaN())
|
|
523
|
+
{
|
|
524
|
+
return MCPError(FString::Printf(
|
|
525
|
+
TEXT("Projected transform overflowed for instance %d; reduce surfaceOffset"),
|
|
526
|
+
Index));
|
|
527
|
+
}
|
|
528
|
+
if (AActor* HitActor = AcceptedHit.GetActor())
|
|
529
|
+
{
|
|
530
|
+
Entry.HitActorLabel = HitActor->GetActorLabel();
|
|
531
|
+
Entry.HitActorClass = HitActor->GetClass()->GetPathName();
|
|
532
|
+
}
|
|
533
|
+
++Plan.HitCount;
|
|
534
|
+
Plan.MinDistance = FMath::Min(Plan.MinDistance, static_cast<double>(AcceptedHit.Distance));
|
|
535
|
+
Plan.MaxDistance = FMath::Max(Plan.MaxDistance, static_cast<double>(AcceptedHit.Distance));
|
|
536
|
+
}
|
|
537
|
+
Plan.Entries.Add(MoveTemp(Entry));
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
TArray<TSharedPtr<FJsonValue>> Samples;
|
|
541
|
+
const int32 SampleCount = FMath::Min(Plan.Entries.Num(), MaxReportedSamples);
|
|
542
|
+
Samples.Reserve(SampleCount);
|
|
543
|
+
for (int32 Position = 0; Position < SampleCount; ++Position)
|
|
544
|
+
{
|
|
545
|
+
Samples.Add(MakeShared<FJsonValueObject>(MakeProjectionSample(Plan.Entries[Position])));
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (MissPolicy == EMissPolicy::Error && Plan.MissCount > 0)
|
|
549
|
+
{
|
|
550
|
+
auto Result = MCPSuccess();
|
|
551
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
552
|
+
Result->SetStringField(TEXT("error"), FString::Printf(
|
|
553
|
+
TEXT("Preflight found %d misses; onMiss='error' prevents any mutation."), Plan.MissCount));
|
|
554
|
+
Result->SetBoolField(TEXT("dryRun"), bDryRun);
|
|
555
|
+
Result->SetBoolField(TEXT("preflightPassed"), false);
|
|
556
|
+
Result->SetBoolField(TEXT("mutationPerformed"), false);
|
|
557
|
+
Result->SetNumberField(TEXT("requestedCount"), Plan.Entries.Num());
|
|
558
|
+
Result->SetNumberField(TEXT("hitCount"), Plan.HitCount);
|
|
559
|
+
Result->SetNumberField(TEXT("missCount"), Plan.MissCount);
|
|
560
|
+
Result->SetNumberField(TEXT("sampleCount"), Samples.Num());
|
|
561
|
+
Result->SetBoolField(TEXT("samplesTruncated"), Plan.Entries.Num() > Samples.Num());
|
|
562
|
+
Result->SetArrayField(TEXT("samples"), Samples);
|
|
563
|
+
return MCPResult(Result);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
bool bMutationPerformed = false;
|
|
567
|
+
if (!bDryRun && Plan.HitCount > 0)
|
|
568
|
+
{
|
|
569
|
+
const bool bShouldActuallyTransact = GEditor && World->WorldType == EWorldType::Editor;
|
|
570
|
+
FScopedTransaction Transaction(
|
|
571
|
+
NSLOCTEXT("UEMCP", "SnapInstancesToSurface", "Snap Instanced Meshes To Surface"),
|
|
572
|
+
bShouldActuallyTransact);
|
|
573
|
+
UPackage* Package = ISMC->GetOutermost();
|
|
574
|
+
const bool bPackageWasDirty = Package && Package->IsDirty();
|
|
575
|
+
Actor->Modify();
|
|
576
|
+
ISMC->Modify();
|
|
577
|
+
const TArray<FProjectionRun> Runs = BuildProjectionRuns(Plan);
|
|
578
|
+
int32 AppliedRunCount = 0;
|
|
579
|
+
for (const FProjectionRun& Run : Runs)
|
|
580
|
+
{
|
|
581
|
+
if (!ISMC->BatchUpdateInstancesTransforms(
|
|
582
|
+
Run.StartIndex,
|
|
583
|
+
Run.After,
|
|
584
|
+
true,
|
|
585
|
+
false,
|
|
586
|
+
true))
|
|
587
|
+
{
|
|
588
|
+
// Treat a false batch result as potentially partial. Restore the
|
|
589
|
+
// current run as well as every previously accepted run.
|
|
590
|
+
bool bRollbackSucceeded = ISMC->BatchUpdateInstancesTransforms(
|
|
591
|
+
Run.StartIndex,
|
|
592
|
+
Run.Before,
|
|
593
|
+
true,
|
|
594
|
+
false,
|
|
595
|
+
true);
|
|
596
|
+
for (int32 RollbackIndex = AppliedRunCount - 1; RollbackIndex >= 0; --RollbackIndex)
|
|
597
|
+
{
|
|
598
|
+
const FProjectionRun& AppliedRun = Runs[RollbackIndex];
|
|
599
|
+
bRollbackSucceeded &= ISMC->BatchUpdateInstancesTransforms(
|
|
600
|
+
AppliedRun.StartIndex,
|
|
601
|
+
AppliedRun.Before,
|
|
602
|
+
true,
|
|
603
|
+
false,
|
|
604
|
+
true);
|
|
605
|
+
}
|
|
606
|
+
ISMC->MarkRenderStateDirty();
|
|
607
|
+
if (Package && !bPackageWasDirty) Package->ClearDirtyFlag();
|
|
608
|
+
Transaction.Cancel();
|
|
609
|
+
return MCPError(FString::Printf(
|
|
610
|
+
TEXT("Unexpected apply failure at instance %d after a successful preflight; rollback %s"),
|
|
611
|
+
Run.StartIndex,
|
|
612
|
+
bRollbackSucceeded ? TEXT("succeeded") : TEXT("failed")));
|
|
613
|
+
}
|
|
614
|
+
++AppliedRunCount;
|
|
615
|
+
}
|
|
616
|
+
ISMC->MarkRenderStateDirty();
|
|
617
|
+
// PKG_Transient no longer exists in UE 5.8's EPackageFlags. Transience is
|
|
618
|
+
// an object flag, and the engine's own transient package is a distinct
|
|
619
|
+
// object, so test both rather than a package flag that was removed.
|
|
620
|
+
if (Package && Package != GetTransientPackage() && !Package->HasAnyFlags(RF_Transient))
|
|
621
|
+
{
|
|
622
|
+
ISMC->MarkPackageDirty();
|
|
623
|
+
}
|
|
624
|
+
bMutationPerformed = true;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
auto Result = MCPSuccess();
|
|
628
|
+
Result->SetBoolField(TEXT("dryRun"), bDryRun);
|
|
629
|
+
Result->SetBoolField(TEXT("preflightPassed"), true);
|
|
630
|
+
Result->SetBoolField(TEXT("mutationPerformed"), bMutationPerformed);
|
|
631
|
+
Result->SetStringField(TEXT("actorLabel"), ActorLabel);
|
|
632
|
+
Result->SetStringField(TEXT("actorPath"), Actor->GetPathName());
|
|
633
|
+
Result->SetStringField(TEXT("componentName"), ISMC->GetName());
|
|
634
|
+
Result->SetStringField(TEXT("componentClass"), ISMC->GetClass()->GetPathName());
|
|
635
|
+
Result->SetStringField(TEXT("channel"), ChannelName);
|
|
636
|
+
Result->SetStringField(TEXT("onMiss"), MissPolicy == EMissPolicy::Error ? TEXT("error") : TEXT("skip"));
|
|
637
|
+
Result->SetNumberField(TEXT("requestedCount"), Plan.Entries.Num());
|
|
638
|
+
Result->SetNumberField(TEXT("hitCount"), Plan.HitCount);
|
|
639
|
+
Result->SetNumberField(TEXT("missCount"), Plan.MissCount);
|
|
640
|
+
Result->SetNumberField(TEXT("updatedCount"), bMutationPerformed ? Plan.HitCount : 0);
|
|
641
|
+
Result->SetNumberField(TEXT("sampleCount"), Samples.Num());
|
|
642
|
+
Result->SetBoolField(TEXT("samplesTruncated"), Plan.Entries.Num() > Samples.Num());
|
|
643
|
+
Result->SetArrayField(TEXT("samples"), Samples);
|
|
644
|
+
if (Plan.HitCount > 0)
|
|
645
|
+
{
|
|
646
|
+
Result->SetNumberField(TEXT("minHitDistance"), Plan.MinDistance);
|
|
647
|
+
Result->SetNumberField(TEXT("maxHitDistance"), Plan.MaxDistance);
|
|
648
|
+
}
|
|
649
|
+
if (bMutationPerformed)
|
|
650
|
+
{
|
|
651
|
+
MCPSetUpdated(Result);
|
|
652
|
+
Result->SetStringField(TEXT("dirtyPackage"), ISMC->GetOutermost()->GetName());
|
|
653
|
+
Result->SetStringField(TEXT("note"), TEXT("Changes are dirty in memory and were not saved."));
|
|
654
|
+
}
|
|
655
|
+
else if (bDryRun)
|
|
656
|
+
{
|
|
657
|
+
Result->SetStringField(TEXT("note"), TEXT("Dry run only; re-call with dryRun=false to apply the preflighted projection."));
|
|
658
|
+
}
|
|
659
|
+
return MCPResult(Result);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
TSharedPtr<FJsonValue> FLevelHandlers::SnapInstancesToSurface(const TSharedPtr<FJsonObject>& Params)
|
|
663
|
+
{
|
|
664
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
665
|
+
return UEMCPInstanceProjection::SnapInstancesToSurfaceInWorld(World, Params);
|
|
666
|
+
}
|