ue-mcp 1.2.4 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/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/project.js +47 -20
- package/dist/tools/project.js.map +1 -1
- package/dist/types.d.ts +21 -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,686 @@
|
|
|
1
|
+
// World Partition streaming settings and runtime cell transformers (#985).
|
|
2
|
+
//
|
|
3
|
+
// A city-scale World Partition map is tuned through three things the bridge
|
|
4
|
+
// could not reach at all:
|
|
5
|
+
//
|
|
6
|
+
// 1. The runtime hash's grid settings, CellSize and LoadingRange. This is the
|
|
7
|
+
// primary streaming knob on any WP map and there was no native read or
|
|
8
|
+
// write of it.
|
|
9
|
+
// 2. RuntimeCellsTransformerStack. The transformer object inside each entry is
|
|
10
|
+
// an INSTANCED subobject of the world partition, which is read-only through
|
|
11
|
+
// Python, so the reporter set it by hand in the editor UI. A manual fallback
|
|
12
|
+
// in the editor UI is exactly what this bridge exists to remove.
|
|
13
|
+
// 3. HLOD layer assignment per actor, in bulk. That one lives with the other
|
|
14
|
+
// selector-driven batch writes, in LevelHandlers_BatchWrite.cpp.
|
|
15
|
+
//
|
|
16
|
+
// These are actions in the `level` category rather than a new `worldpartition`
|
|
17
|
+
// category on purpose: a new category means a new tool, a new schema file and a
|
|
18
|
+
// new entry in every surface that enumerates categories, for three actions that
|
|
19
|
+
// are all about the level that is currently open. The level category already
|
|
20
|
+
// owns list_actor_descs and load_actor_descs, which are World Partition only for
|
|
21
|
+
// the same reason.
|
|
22
|
+
//
|
|
23
|
+
// Everything here works through REFLECTION rather than against the concrete
|
|
24
|
+
// runtime hash classes. Two reasons, and both are load-bearing. The grid
|
|
25
|
+
// settings live in different places on the two hashes Unreal ships
|
|
26
|
+
// (URuntimePartition under UWorldPartitionRuntimeHashSet, FSpatialHashRuntimeGrid
|
|
27
|
+
// under UWorldPartitionRuntimeSpatialHash), and a third could appear; and
|
|
28
|
+
// RuntimeCellsTransformerStack is a private UPROPERTY whose add helper is not
|
|
29
|
+
// guaranteed to be linkable from an external module on every engine version the
|
|
30
|
+
// plugin builds against. A reflected walk answers for all of them and cannot
|
|
31
|
+
// fail to link.
|
|
32
|
+
//
|
|
33
|
+
// Translation-unit partition of FLevelHandlers - registration lives in
|
|
34
|
+
// LevelHandlers.cpp::RegisterHandlers.
|
|
35
|
+
|
|
36
|
+
#include "LevelHandlers.h"
|
|
37
|
+
#include "HandlerRegistry.h"
|
|
38
|
+
#include "HandlerUtils.h"
|
|
39
|
+
#include "HandlerJsonProperty.h"
|
|
40
|
+
|
|
41
|
+
#include "Editor.h"
|
|
42
|
+
#include "Engine/World.h"
|
|
43
|
+
#include "ScopedTransaction.h"
|
|
44
|
+
#include "UObject/Package.h"
|
|
45
|
+
#include "UObject/UnrealType.h"
|
|
46
|
+
#include "UObject/UObjectGlobals.h"
|
|
47
|
+
// UWorldPartition forward-declares both of these, and the reads below convert
|
|
48
|
+
// them to UObject*, which needs the complete type.
|
|
49
|
+
#include "WorldPartition/HLOD/HLODLayer.h"
|
|
50
|
+
#include "WorldPartition/WorldPartition.h"
|
|
51
|
+
#include "WorldPartition/WorldPartitionRuntimeCellTransformer.h"
|
|
52
|
+
#include "WorldPartition/WorldPartitionRuntimeHash.h"
|
|
53
|
+
#include "Dom/JsonObject.h"
|
|
54
|
+
#include "Dom/JsonValue.h"
|
|
55
|
+
|
|
56
|
+
namespace
|
|
57
|
+
{
|
|
58
|
+
/** How deep the reflected walk looks for grid settings. */
|
|
59
|
+
constexpr int32 MCPWorldPartitionWalkDepth = 5;
|
|
60
|
+
/** Cap on discovered grids, so a pathological graph cannot run away. */
|
|
61
|
+
constexpr int32 MCPWorldPartitionMaxGrids = 64;
|
|
62
|
+
|
|
63
|
+
/** The world partition of the open map, or a reason there is none. */
|
|
64
|
+
UWorldPartition* MCPResolveWorldPartition(UWorld* World, FString& OutError)
|
|
65
|
+
{
|
|
66
|
+
if (!World)
|
|
67
|
+
{
|
|
68
|
+
OutError = TEXT("No editor world");
|
|
69
|
+
return nullptr;
|
|
70
|
+
}
|
|
71
|
+
UWorldPartition* WorldPartition = World->GetWorldPartition();
|
|
72
|
+
if (!WorldPartition)
|
|
73
|
+
{
|
|
74
|
+
OutError = FString::Printf(
|
|
75
|
+
TEXT("'%s' is not a World Partition map, so it has no runtime hash or cell transformer stack."),
|
|
76
|
+
*World->GetName());
|
|
77
|
+
return nullptr;
|
|
78
|
+
}
|
|
79
|
+
return WorldPartition;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Read any numeric property as a double, whatever its width or sign. */
|
|
83
|
+
bool MCPWPReadNumeric(const FProperty* Prop, const void* Container, double& OutValue)
|
|
84
|
+
{
|
|
85
|
+
const FNumericProperty* Numeric = CastField<FNumericProperty>(Prop);
|
|
86
|
+
if (!Numeric || !Container) return false;
|
|
87
|
+
const void* Addr = Prop->ContainerPtrToValuePtr<void>(Container);
|
|
88
|
+
OutValue = Numeric->IsFloatingPoint()
|
|
89
|
+
? Numeric->GetFloatingPointPropertyValue(Addr)
|
|
90
|
+
: static_cast<double>(Numeric->GetSignedIntPropertyValue(Addr));
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** One discovered streaming grid, addressed by a dotted path a setter can reuse. */
|
|
95
|
+
struct FMCPWorldPartitionGrid
|
|
96
|
+
{
|
|
97
|
+
FString Path; // dotted, rooted at the world partition object
|
|
98
|
+
FString Name; // the grid's own Name / GridName, when it has one
|
|
99
|
+
FString TypeName; // the class or struct that carries the settings
|
|
100
|
+
double CellSize = 0.0;
|
|
101
|
+
double LoadingRange = 0.0;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/** The grid's own name field. Both shipped hashes have one, under two names. */
|
|
105
|
+
FString MCPWPReadGridName(const UStruct* Struct, const void* Container)
|
|
106
|
+
{
|
|
107
|
+
for (const TCHAR* Candidate : { TEXT("Name"), TEXT("GridName") })
|
|
108
|
+
{
|
|
109
|
+
if (const FNameProperty* NameProp =
|
|
110
|
+
CastField<FNameProperty>(Struct->FindPropertyByName(FName(Candidate))))
|
|
111
|
+
{
|
|
112
|
+
const FName Value = NameProp->GetPropertyValue_InContainer(Container);
|
|
113
|
+
if (!Value.IsNone()) return Value.ToString();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return FString();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Walk the reflected graph under the world partition looking for anything
|
|
121
|
+
* that declares both CellSize and LoadingRange. That pair IS the streaming
|
|
122
|
+
* grid, wherever the engine version happens to keep it.
|
|
123
|
+
*
|
|
124
|
+
* Object references are followed only into objects owned by the world
|
|
125
|
+
* partition, so the walk cannot wander out into referenced assets.
|
|
126
|
+
*/
|
|
127
|
+
void MCPWPCollectGrids(
|
|
128
|
+
const UObject* Root,
|
|
129
|
+
const UStruct* Struct,
|
|
130
|
+
const void* Container,
|
|
131
|
+
const FString& PathPrefix,
|
|
132
|
+
int32 Depth,
|
|
133
|
+
TArray<FMCPWorldPartitionGrid>& OutGrids)
|
|
134
|
+
{
|
|
135
|
+
if (!Struct || !Container || Depth > MCPWorldPartitionWalkDepth) return;
|
|
136
|
+
if (OutGrids.Num() >= MCPWorldPartitionMaxGrids) return;
|
|
137
|
+
|
|
138
|
+
const FProperty* CellSizeProp = Struct->FindPropertyByName(TEXT("CellSize"));
|
|
139
|
+
const FProperty* LoadingRangeProp = Struct->FindPropertyByName(TEXT("LoadingRange"));
|
|
140
|
+
double CellSize = 0.0;
|
|
141
|
+
double LoadingRange = 0.0;
|
|
142
|
+
if (CellSizeProp && LoadingRangeProp &&
|
|
143
|
+
MCPWPReadNumeric(CellSizeProp, Container, CellSize) &&
|
|
144
|
+
MCPWPReadNumeric(LoadingRangeProp, Container, LoadingRange))
|
|
145
|
+
{
|
|
146
|
+
FMCPWorldPartitionGrid Grid;
|
|
147
|
+
Grid.Path = PathPrefix;
|
|
148
|
+
Grid.Name = MCPWPReadGridName(Struct, Container);
|
|
149
|
+
Grid.TypeName = Struct->GetName();
|
|
150
|
+
Grid.CellSize = CellSize;
|
|
151
|
+
Grid.LoadingRange = LoadingRange;
|
|
152
|
+
OutGrids.Add(Grid);
|
|
153
|
+
// A grid does not contain another grid, so this branch stops here.
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
for (TFieldIterator<FProperty> It(Struct); It; ++It)
|
|
158
|
+
{
|
|
159
|
+
FProperty* Prop = *It;
|
|
160
|
+
if (!Prop) continue;
|
|
161
|
+
const FString Name = Prop->GetName();
|
|
162
|
+
const FString ChildPath = PathPrefix.IsEmpty() ? Name : (PathPrefix + TEXT(".") + Name);
|
|
163
|
+
const void* ValueAddr = Prop->ContainerPtrToValuePtr<void>(Container);
|
|
164
|
+
|
|
165
|
+
if (const FStructProperty* StructProp = CastField<FStructProperty>(Prop))
|
|
166
|
+
{
|
|
167
|
+
MCPWPCollectGrids(Root, StructProp->Struct, ValueAddr, ChildPath, Depth + 1, OutGrids);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (const FObjectProperty* ObjectProp = CastField<FObjectProperty>(Prop))
|
|
171
|
+
{
|
|
172
|
+
// A class reference names a type, not an instance to walk into.
|
|
173
|
+
if (CastField<FClassProperty>(Prop)) continue;
|
|
174
|
+
UObject* Sub = ObjectProp->GetObjectPropertyValue(ValueAddr);
|
|
175
|
+
if (!Sub || !Sub->IsIn(Root)) continue;
|
|
176
|
+
MCPWPCollectGrids(Root, Sub->GetClass(), Sub, ChildPath, Depth + 1, OutGrids);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
if (const FArrayProperty* ArrayProp = CastField<FArrayProperty>(Prop))
|
|
180
|
+
{
|
|
181
|
+
FScriptArrayHelper Helper(ArrayProp, ValueAddr);
|
|
182
|
+
for (int32 Index = 0; Index < Helper.Num(); ++Index)
|
|
183
|
+
{
|
|
184
|
+
const FString ElementPath = FString::Printf(TEXT("%s[%d]"), *ChildPath, Index);
|
|
185
|
+
const void* ElementAddr = Helper.GetRawPtr(Index);
|
|
186
|
+
if (const FStructProperty* InnerStruct = CastField<FStructProperty>(ArrayProp->Inner))
|
|
187
|
+
{
|
|
188
|
+
MCPWPCollectGrids(Root, InnerStruct->Struct, ElementAddr, ElementPath, Depth + 1, OutGrids);
|
|
189
|
+
}
|
|
190
|
+
else if (const FObjectProperty* InnerObject = CastField<FObjectProperty>(ArrayProp->Inner))
|
|
191
|
+
{
|
|
192
|
+
if (CastField<FClassProperty>(ArrayProp->Inner)) continue;
|
|
193
|
+
UObject* Sub = InnerObject->GetObjectPropertyValue(ElementAddr);
|
|
194
|
+
if (!Sub || !Sub->IsIn(Root)) continue;
|
|
195
|
+
MCPWPCollectGrids(Root, Sub->GetClass(), Sub, ElementPath, Depth + 1, OutGrids);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The RuntimeCellsTransformerStack array property, or null on an engine without it. */
|
|
203
|
+
FArrayProperty* MCPWPTransformerStackProperty()
|
|
204
|
+
{
|
|
205
|
+
return CastField<FArrayProperty>(
|
|
206
|
+
UWorldPartition::StaticClass()->FindPropertyByName(TEXT("RuntimeCellsTransformerStack")));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The Class and Instance members of one FRuntimeCellTransformerInstance. */
|
|
210
|
+
struct FMCPWPTransformerFields
|
|
211
|
+
{
|
|
212
|
+
FStructProperty* EntryProp = nullptr;
|
|
213
|
+
FObjectPropertyBase* ClassProp = nullptr;
|
|
214
|
+
FObjectProperty* InstanceProp = nullptr;
|
|
215
|
+
bool IsValid() const { return EntryProp && ClassProp && InstanceProp; }
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
FMCPWPTransformerFields MCPWPTransformerFields(FArrayProperty* ArrayProp)
|
|
219
|
+
{
|
|
220
|
+
FMCPWPTransformerFields Fields;
|
|
221
|
+
if (!ArrayProp) return Fields;
|
|
222
|
+
Fields.EntryProp = CastField<FStructProperty>(ArrayProp->Inner);
|
|
223
|
+
if (!Fields.EntryProp || !Fields.EntryProp->Struct) return Fields;
|
|
224
|
+
Fields.ClassProp = CastField<FObjectPropertyBase>(
|
|
225
|
+
Fields.EntryProp->Struct->FindPropertyByName(TEXT("Class")));
|
|
226
|
+
Fields.InstanceProp = CastField<FObjectProperty>(
|
|
227
|
+
Fields.EntryProp->Struct->FindPropertyByName(TEXT("Instance")));
|
|
228
|
+
return Fields;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Every EditAnywhere property on a transformer instance, with its value. */
|
|
232
|
+
TArray<TSharedPtr<FJsonValue>> MCPWPDescribeInstanceProperties(UObject* Instance)
|
|
233
|
+
{
|
|
234
|
+
TArray<TSharedPtr<FJsonValue>> Rows;
|
|
235
|
+
if (!Instance) return Rows;
|
|
236
|
+
for (TFieldIterator<FProperty> It(Instance->GetClass()); It; ++It)
|
|
237
|
+
{
|
|
238
|
+
FProperty* Prop = *It;
|
|
239
|
+
if (!Prop || !Prop->HasAnyPropertyFlags(CPF_Edit)) continue;
|
|
240
|
+
TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
|
|
241
|
+
Row->SetStringField(TEXT("name"), Prop->GetName());
|
|
242
|
+
Row->SetStringField(TEXT("type"), Prop->GetCPPType());
|
|
243
|
+
Row->SetField(TEXT("value"), MCPExportPropertyValue(Prop, Instance));
|
|
244
|
+
Rows.Add(MakeShared<FJsonValueObject>(Row));
|
|
245
|
+
}
|
|
246
|
+
return Rows;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The transformer stack as JSON, each entry addressable by its dotted path. */
|
|
250
|
+
TArray<TSharedPtr<FJsonValue>> MCPWPDescribeTransformerStack(UWorldPartition* WorldPartition)
|
|
251
|
+
{
|
|
252
|
+
TArray<TSharedPtr<FJsonValue>> Rows;
|
|
253
|
+
FArrayProperty* ArrayProp = MCPWPTransformerStackProperty();
|
|
254
|
+
const FMCPWPTransformerFields Fields = MCPWPTransformerFields(ArrayProp);
|
|
255
|
+
if (!Fields.IsValid()) return Rows;
|
|
256
|
+
|
|
257
|
+
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(WorldPartition));
|
|
258
|
+
for (int32 Index = 0; Index < Helper.Num(); ++Index)
|
|
259
|
+
{
|
|
260
|
+
void* EntryAddr = Helper.GetRawPtr(Index);
|
|
261
|
+
UObject* ClassObject = Fields.ClassProp->GetObjectPropertyValue(
|
|
262
|
+
Fields.ClassProp->ContainerPtrToValuePtr<void>(EntryAddr));
|
|
263
|
+
UObject* Instance = Fields.InstanceProp->GetObjectPropertyValue(
|
|
264
|
+
Fields.InstanceProp->ContainerPtrToValuePtr<void>(EntryAddr));
|
|
265
|
+
|
|
266
|
+
TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
|
|
267
|
+
Row->SetNumberField(TEXT("index"), Index);
|
|
268
|
+
Row->SetStringField(TEXT("class"), ClassObject ? ClassObject->GetPathName() : FString());
|
|
269
|
+
// The path a caller passes to set_world_partition_settings to write
|
|
270
|
+
// a property on the instanced transformer object.
|
|
271
|
+
Row->SetStringField(TEXT("instancePath"),
|
|
272
|
+
FString::Printf(TEXT("RuntimeCellsTransformerStack[%d].Instance"), Index));
|
|
273
|
+
Row->SetBoolField(TEXT("hasInstance"), Instance != nullptr);
|
|
274
|
+
if (Instance)
|
|
275
|
+
{
|
|
276
|
+
Row->SetStringField(TEXT("instanceClass"), Instance->GetClass()->GetPathName());
|
|
277
|
+
Row->SetArrayField(TEXT("properties"), MCPWPDescribeInstanceProperties(Instance));
|
|
278
|
+
}
|
|
279
|
+
Rows.Add(MakeShared<FJsonValueObject>(Row));
|
|
280
|
+
}
|
|
281
|
+
return Rows;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Export a property from its VALUE address rather than from a container.
|
|
286
|
+
*
|
|
287
|
+
* A dotted path can end inside a struct element of an array, where the
|
|
288
|
+
* nearest UObject is not the property's container, so the usual
|
|
289
|
+
* container-based export would read the wrong address. Every setting this
|
|
290
|
+
* action writes is a scalar, which is what makes the direct export safe
|
|
291
|
+
* here: it is the one case where a fixed array would need the indexed form.
|
|
292
|
+
*/
|
|
293
|
+
TSharedPtr<FJsonValue> MCPWPExportAtAddress(const FProperty* Prop, const void* ValueAddr)
|
|
294
|
+
{
|
|
295
|
+
FString Text;
|
|
296
|
+
if (Prop && ValueAddr)
|
|
297
|
+
{
|
|
298
|
+
Prop->ExportTextItem_Direct(Text, ValueAddr, nullptr, nullptr, PPF_None);
|
|
299
|
+
}
|
|
300
|
+
return MakeShared<FJsonValueString>(Text);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** Mark the world's own package dirty after a world partition edit. */
|
|
304
|
+
void MCPWPMarkDirty(UWorld* World, UWorldPartition* WorldPartition)
|
|
305
|
+
{
|
|
306
|
+
if (WorldPartition) WorldPartition->PostEditChange();
|
|
307
|
+
if (World && World->GetOutermost()) World->GetOutermost()->MarkPackageDirty();
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
// level(get_world_partition_settings): the streaming knobs on the open map.
|
|
313
|
+
TSharedPtr<FJsonValue> FLevelHandlers::GetWorldPartitionSettings(const TSharedPtr<FJsonObject>& Params)
|
|
314
|
+
{
|
|
315
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
316
|
+
|
|
317
|
+
FString ResolveError;
|
|
318
|
+
UWorldPartition* WorldPartition = MCPResolveWorldPartition(World, ResolveError);
|
|
319
|
+
if (!WorldPartition) return MCPError(ResolveError);
|
|
320
|
+
|
|
321
|
+
TArray<FMCPWorldPartitionGrid> Grids;
|
|
322
|
+
MCPWPCollectGrids(WorldPartition, WorldPartition->GetClass(), WorldPartition, FString(), 0, Grids);
|
|
323
|
+
|
|
324
|
+
TArray<TSharedPtr<FJsonValue>> GridRows;
|
|
325
|
+
for (const FMCPWorldPartitionGrid& Grid : Grids)
|
|
326
|
+
{
|
|
327
|
+
TSharedPtr<FJsonObject> Row = MakeShared<FJsonObject>();
|
|
328
|
+
Row->SetStringField(TEXT("name"), Grid.Name);
|
|
329
|
+
Row->SetStringField(TEXT("path"), Grid.Path);
|
|
330
|
+
Row->SetStringField(TEXT("type"), Grid.TypeName);
|
|
331
|
+
Row->SetNumberField(TEXT("cellSize"), Grid.CellSize);
|
|
332
|
+
Row->SetNumberField(TEXT("loadingRange"), Grid.LoadingRange);
|
|
333
|
+
GridRows.Add(MakeShared<FJsonValueObject>(Row));
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
auto Result = MCPSuccess();
|
|
337
|
+
Result->SetStringField(TEXT("world"), World->GetName());
|
|
338
|
+
Result->SetStringField(TEXT("worldPartition"), WorldPartition->GetPathName());
|
|
339
|
+
Result->SetStringField(TEXT("worldPartitionClass"), WorldPartition->GetClass()->GetPathName());
|
|
340
|
+
Result->SetBoolField(TEXT("enableStreaming"), WorldPartition->IsStreamingEnabled());
|
|
341
|
+
if (UObject* RuntimeHash = WorldPartition->RuntimeHash)
|
|
342
|
+
{
|
|
343
|
+
Result->SetStringField(TEXT("runtimeHashClass"), RuntimeHash->GetClass()->GetPathName());
|
|
344
|
+
}
|
|
345
|
+
if (UObject* DefaultHLOD = WorldPartition->GetDefaultHLODLayer())
|
|
346
|
+
{
|
|
347
|
+
Result->SetStringField(TEXT("defaultHLODLayer"), DefaultHLOD->GetPathName());
|
|
348
|
+
}
|
|
349
|
+
Result->SetArrayField(TEXT("grids"), GridRows);
|
|
350
|
+
Result->SetNumberField(TEXT("gridCount"), GridRows.Num());
|
|
351
|
+
Result->SetArrayField(TEXT("cellTransformers"), MCPWPDescribeTransformerStack(WorldPartition));
|
|
352
|
+
Result->SetStringField(TEXT("note"),
|
|
353
|
+
TEXT("Each grid's 'path' and each transformer's 'instancePath' are dotted paths rooted at the world partition object; pass either to level(set_world_partition_settings) in its 'settings' map to write anything they contain."));
|
|
354
|
+
return MCPResult(Result);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
// level(set_world_partition_settings): write the streaming knobs, and anything
|
|
359
|
+
// else reachable from the world partition, including the properties of an
|
|
360
|
+
// INSTANCED runtime cell transformer, which is the object the reporter had to
|
|
361
|
+
// configure by hand in the editor UI.
|
|
362
|
+
TSharedPtr<FJsonValue> FLevelHandlers::SetWorldPartitionSettings(const TSharedPtr<FJsonObject>& Params)
|
|
363
|
+
{
|
|
364
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
365
|
+
|
|
366
|
+
FString ResolveError;
|
|
367
|
+
UWorldPartition* WorldPartition = MCPResolveWorldPartition(World, ResolveError);
|
|
368
|
+
if (!WorldPartition) return MCPError(ResolveError);
|
|
369
|
+
|
|
370
|
+
// Every write is a dotted path rooted at the world partition. cellSize and
|
|
371
|
+
// loadingRange are the two that get a shorthand, because they are the
|
|
372
|
+
// primary streaming knob and asking a caller to spell out
|
|
373
|
+
// RuntimeHash.RuntimePartitions[0].MainLayer.CellSize for them would be a
|
|
374
|
+
// riddle rather than an interface.
|
|
375
|
+
TArray<TPair<FString, TSharedPtr<FJsonValue>>> Writes;
|
|
376
|
+
|
|
377
|
+
const bool bWantsCellSize = Params->HasField(TEXT("cellSize"));
|
|
378
|
+
const bool bWantsLoadingRange = Params->HasField(TEXT("loadingRange"));
|
|
379
|
+
if (bWantsCellSize || bWantsLoadingRange)
|
|
380
|
+
{
|
|
381
|
+
TArray<FMCPWorldPartitionGrid> Grids;
|
|
382
|
+
MCPWPCollectGrids(WorldPartition, WorldPartition->GetClass(), WorldPartition, FString(), 0, Grids);
|
|
383
|
+
if (Grids.Num() == 0)
|
|
384
|
+
{
|
|
385
|
+
return MCPError(TEXT("This world partition exposes no streaming grid with a CellSize and a LoadingRange. Read level(get_world_partition_settings) to see what it does expose."));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const FString WantedGrid = OptionalString(Params, TEXT("grid"));
|
|
389
|
+
const FString ExplicitPath = OptionalString(Params, TEXT("gridPath"));
|
|
390
|
+
const FMCPWorldPartitionGrid* Target = nullptr;
|
|
391
|
+
if (!ExplicitPath.IsEmpty())
|
|
392
|
+
{
|
|
393
|
+
Target = Grids.FindByPredicate([&ExplicitPath](const FMCPWorldPartitionGrid& G)
|
|
394
|
+
{
|
|
395
|
+
return G.Path.Equals(ExplicitPath, ESearchCase::IgnoreCase);
|
|
396
|
+
});
|
|
397
|
+
if (!Target)
|
|
398
|
+
{
|
|
399
|
+
return MCPError(FString::Printf(
|
|
400
|
+
TEXT("No streaming grid at path '%s'. Read level(get_world_partition_settings) for the paths this map has."),
|
|
401
|
+
*ExplicitPath));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
else if (!WantedGrid.IsEmpty())
|
|
405
|
+
{
|
|
406
|
+
Target = Grids.FindByPredicate([&WantedGrid](const FMCPWorldPartitionGrid& G)
|
|
407
|
+
{
|
|
408
|
+
return G.Name.Equals(WantedGrid, ESearchCase::IgnoreCase);
|
|
409
|
+
});
|
|
410
|
+
if (!Target)
|
|
411
|
+
{
|
|
412
|
+
TArray<FString> Names;
|
|
413
|
+
for (const FMCPWorldPartitionGrid& G : Grids) Names.Add(G.Name.IsEmpty() ? G.Path : G.Name);
|
|
414
|
+
return MCPError(FString::Printf(
|
|
415
|
+
TEXT("No streaming grid named '%s'. This map has: [%s]"),
|
|
416
|
+
*WantedGrid, *FString::Join(Names, TEXT(", "))));
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else if (Grids.Num() == 1)
|
|
420
|
+
{
|
|
421
|
+
Target = &Grids[0];
|
|
422
|
+
}
|
|
423
|
+
else
|
|
424
|
+
{
|
|
425
|
+
TArray<FString> Names;
|
|
426
|
+
for (const FMCPWorldPartitionGrid& G : Grids) Names.Add(G.Name.IsEmpty() ? G.Path : G.Name);
|
|
427
|
+
return MCPError(FString::Printf(
|
|
428
|
+
TEXT("This map has %d streaming grids; pass 'grid' (name) or 'gridPath' to choose. Available: [%s]"),
|
|
429
|
+
Grids.Num(), *FString::Join(Names, TEXT(", "))));
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (bWantsCellSize)
|
|
433
|
+
{
|
|
434
|
+
const double CellSize = OptionalNumber(Params, TEXT("cellSize"), 0.0);
|
|
435
|
+
if (CellSize <= 0.0) return MCPError(TEXT("'cellSize' must be positive (world centimetres per streaming cell)"));
|
|
436
|
+
Writes.Add(TPair<FString, TSharedPtr<FJsonValue>>(
|
|
437
|
+
Target->Path + TEXT(".CellSize"), MakeShared<FJsonValueNumber>(CellSize)));
|
|
438
|
+
}
|
|
439
|
+
if (bWantsLoadingRange)
|
|
440
|
+
{
|
|
441
|
+
const double LoadingRange = OptionalNumber(Params, TEXT("loadingRange"), 0.0);
|
|
442
|
+
if (LoadingRange <= 0.0) return MCPError(TEXT("'loadingRange' must be positive (world centimetres of streaming radius)"));
|
|
443
|
+
Writes.Add(TPair<FString, TSharedPtr<FJsonValue>>(
|
|
444
|
+
Target->Path + TEXT(".LoadingRange"), MakeShared<FJsonValueNumber>(LoadingRange)));
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const TSharedPtr<FJsonObject>* SettingsObj = nullptr;
|
|
449
|
+
if (Params->TryGetObjectField(TEXT("settings"), SettingsObj) && SettingsObj && SettingsObj->IsValid())
|
|
450
|
+
{
|
|
451
|
+
for (const auto& Pair : (*SettingsObj)->Values)
|
|
452
|
+
{
|
|
453
|
+
Writes.Add(TPair<FString, TSharedPtr<FJsonValue>>(FString(*Pair.Key), Pair.Value));
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (Writes.Num() == 0)
|
|
458
|
+
{
|
|
459
|
+
return MCPError(TEXT("Nothing to write. Pass cellSize and/or loadingRange, or a 'settings' object of dotted path -> value rooted at the world partition (see level(get_world_partition_settings))."));
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Resolve every path before writing any of them. Streaming settings are the
|
|
463
|
+
// kind of thing a caller changes in one batch, and a half-applied batch
|
|
464
|
+
// leaves the map streaming on a mixture nobody asked for.
|
|
465
|
+
for (const TPair<FString, TSharedPtr<FJsonValue>>& Write : Writes)
|
|
466
|
+
{
|
|
467
|
+
FProperty* Prop = nullptr;
|
|
468
|
+
void* ValueAddr = nullptr;
|
|
469
|
+
UObject* Owner = nullptr;
|
|
470
|
+
FString PathError;
|
|
471
|
+
if (!MCPJsonProperty::ResolveDottedPath(WorldPartition, Write.Key, Prop, ValueAddr, Owner, PathError))
|
|
472
|
+
{
|
|
473
|
+
return MCPError(FString::Printf(
|
|
474
|
+
TEXT("'%s' does not resolve from the world partition (%s). Nothing was written. Read level(get_world_partition_settings) for the paths this map has."),
|
|
475
|
+
*Write.Key, *PathError));
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
TArray<TSharedPtr<FJsonValue>> Applied;
|
|
480
|
+
int32 FailureCount = 0;
|
|
481
|
+
{
|
|
482
|
+
const FScopedTransaction Transaction(FText::FromString(TEXT("MCP world partition settings")));
|
|
483
|
+
WorldPartition->Modify();
|
|
484
|
+
|
|
485
|
+
for (const TPair<FString, TSharedPtr<FJsonValue>>& Write : Writes)
|
|
486
|
+
{
|
|
487
|
+
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
488
|
+
Entry->SetStringField(TEXT("path"), Write.Key);
|
|
489
|
+
|
|
490
|
+
FProperty* Prop = nullptr;
|
|
491
|
+
void* ValueAddr = nullptr;
|
|
492
|
+
UObject* Owner = nullptr;
|
|
493
|
+
FString PathError;
|
|
494
|
+
if (!MCPJsonProperty::ResolveDottedPath(WorldPartition, Write.Key, Prop, ValueAddr, Owner, PathError))
|
|
495
|
+
{
|
|
496
|
+
Entry->SetBoolField(TEXT("applied"), false);
|
|
497
|
+
Entry->SetStringField(TEXT("error"), PathError);
|
|
498
|
+
Applied.Add(MakeShared<FJsonValueObject>(Entry));
|
|
499
|
+
++FailureCount;
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
// The leaf owner is what actually changes, and on a transformer
|
|
503
|
+
// instance that is a different object from the world partition.
|
|
504
|
+
if (Owner && Owner != WorldPartition) Owner->Modify();
|
|
505
|
+
Entry->SetField(TEXT("previousValue"), MCPWPExportAtAddress(Prop, ValueAddr));
|
|
506
|
+
|
|
507
|
+
FString SetError;
|
|
508
|
+
if (!MCPJsonProperty::SetJsonOnProperty(Prop, ValueAddr, Write.Value, SetError))
|
|
509
|
+
{
|
|
510
|
+
Entry->SetBoolField(TEXT("applied"), false);
|
|
511
|
+
Entry->SetStringField(TEXT("error"), SetError);
|
|
512
|
+
++FailureCount;
|
|
513
|
+
}
|
|
514
|
+
else
|
|
515
|
+
{
|
|
516
|
+
Entry->SetBoolField(TEXT("applied"), true);
|
|
517
|
+
Entry->SetStringField(TEXT("owner"), Owner ? Owner->GetPathName() : FString());
|
|
518
|
+
// Read back rather than echo, so the response says what the
|
|
519
|
+
// object holds now.
|
|
520
|
+
Entry->SetField(TEXT("value"), MCPWPExportAtAddress(Prop, ValueAddr));
|
|
521
|
+
if (Owner) Owner->PostEditChange();
|
|
522
|
+
}
|
|
523
|
+
Applied.Add(MakeShared<FJsonValueObject>(Entry));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
MCPWPMarkDirty(World, WorldPartition);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
auto Result = MCPSuccess();
|
|
530
|
+
MCPSetUpdated(Result);
|
|
531
|
+
Result->SetStringField(TEXT("worldPartition"), WorldPartition->GetPathName());
|
|
532
|
+
Result->SetNumberField(TEXT("appliedCount"), Applied.Num() - FailureCount);
|
|
533
|
+
Result->SetNumberField(TEXT("failedCount"), FailureCount);
|
|
534
|
+
Result->SetArrayField(TEXT("writes"), Applied);
|
|
535
|
+
if (FailureCount > 0) Result->SetBoolField(TEXT("success"), false);
|
|
536
|
+
Result->SetStringField(TEXT("note"),
|
|
537
|
+
TEXT("Streaming settings only take effect for cells generated after the change, so rebuild the streaming data (or re-cook) before measuring. The map package is left dirty and is NOT saved."));
|
|
538
|
+
return MCPResult(Result);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
// level(add_runtime_cell_transformer): append a transformer to
|
|
543
|
+
// RuntimeCellsTransformerStack and configure its instanced object in the same
|
|
544
|
+
// call. Adding the entry without being able to configure the instance would
|
|
545
|
+
// leave the manual editor step exactly where it was.
|
|
546
|
+
TSharedPtr<FJsonValue> FLevelHandlers::AddRuntimeCellTransformer(const TSharedPtr<FJsonObject>& Params)
|
|
547
|
+
{
|
|
548
|
+
REQUIRE_EDITOR_WORLD(World);
|
|
549
|
+
|
|
550
|
+
FString ResolveError;
|
|
551
|
+
UWorldPartition* WorldPartition = MCPResolveWorldPartition(World, ResolveError);
|
|
552
|
+
if (!WorldPartition) return MCPError(ResolveError);
|
|
553
|
+
|
|
554
|
+
FString ClassName;
|
|
555
|
+
if (auto Err = RequireStringAlt(Params, TEXT("transformerClass"), TEXT("className"), ClassName)) return Err;
|
|
556
|
+
|
|
557
|
+
// The base check and the concrete check both come from the shared guard, so
|
|
558
|
+
// a wrong class here reads the same way it does everywhere else in the
|
|
559
|
+
// bridge rather than growing its own phrasing.
|
|
560
|
+
UClass* TransformerClass = MCPResolveClass(ClassName);
|
|
561
|
+
if (auto Err = MCPCheckClassUsable(
|
|
562
|
+
ClassName, TransformerClass, UWorldPartitionRuntimeCellTransformer::StaticClass()))
|
|
563
|
+
{
|
|
564
|
+
return Err;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
FArrayProperty* ArrayProp = MCPWPTransformerStackProperty();
|
|
568
|
+
const FMCPWPTransformerFields Fields = MCPWPTransformerFields(ArrayProp);
|
|
569
|
+
if (!Fields.IsValid())
|
|
570
|
+
{
|
|
571
|
+
return MCPError(TEXT("This engine's UWorldPartition does not expose RuntimeCellsTransformerStack with the Class and Instance members this action writes."));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const bool bSkipIfPresent = OptionalBool(Params, TEXT("skipIfPresent"), true);
|
|
575
|
+
FScriptArrayHelper Helper(ArrayProp, ArrayProp->ContainerPtrToValuePtr<void>(WorldPartition));
|
|
576
|
+
|
|
577
|
+
if (bSkipIfPresent)
|
|
578
|
+
{
|
|
579
|
+
for (int32 Index = 0; Index < Helper.Num(); ++Index)
|
|
580
|
+
{
|
|
581
|
+
void* EntryAddr = Helper.GetRawPtr(Index);
|
|
582
|
+
UObject* ExistingClass = Fields.ClassProp->GetObjectPropertyValue(
|
|
583
|
+
Fields.ClassProp->ContainerPtrToValuePtr<void>(EntryAddr));
|
|
584
|
+
if (ExistingClass == TransformerClass)
|
|
585
|
+
{
|
|
586
|
+
auto Existing = MCPSuccess();
|
|
587
|
+
MCPSetExisted(Existing);
|
|
588
|
+
Existing->SetNumberField(TEXT("index"), Index);
|
|
589
|
+
Existing->SetStringField(TEXT("class"), TransformerClass->GetPathName());
|
|
590
|
+
Existing->SetStringField(TEXT("instancePath"),
|
|
591
|
+
FString::Printf(TEXT("RuntimeCellsTransformerStack[%d].Instance"), Index));
|
|
592
|
+
Existing->SetStringField(TEXT("note"),
|
|
593
|
+
TEXT("A transformer of this class is already in the stack; nothing was added. Pass skipIfPresent=false to add a second one, or write its properties with level(set_world_partition_settings) using instancePath."));
|
|
594
|
+
return MCPResult(Existing);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// -1 (the default) appends. Any other position inserts there, because the
|
|
600
|
+
// stack is ordered and a transformer that has to run before another one has
|
|
601
|
+
// no other way to say so.
|
|
602
|
+
const int32 RequestedPosition = OptionalInt(Params, TEXT("position"), -1);
|
|
603
|
+
if (RequestedPosition < -1 || RequestedPosition > Helper.Num())
|
|
604
|
+
{
|
|
605
|
+
return MCPError(FString::Printf(
|
|
606
|
+
TEXT("'position' %d is out of range for a stack of %d entries (use -1 to append)"),
|
|
607
|
+
RequestedPosition, Helper.Num()));
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const TSharedPtr<FJsonObject>* PropertiesField = nullptr;
|
|
611
|
+
TSharedPtr<FJsonObject> Properties;
|
|
612
|
+
if (Params->TryGetObjectField(TEXT("properties"), PropertiesField) && PropertiesField && (*PropertiesField).IsValid())
|
|
613
|
+
{
|
|
614
|
+
Properties = *PropertiesField;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
int32 NewIndex = INDEX_NONE;
|
|
618
|
+
TArray<TSharedPtr<FJsonValue>> AppliedProperties;
|
|
619
|
+
TArray<FString> PropertyErrors;
|
|
620
|
+
{
|
|
621
|
+
const FScopedTransaction Transaction(FText::FromString(TEXT("MCP add runtime cell transformer")));
|
|
622
|
+
WorldPartition->Modify();
|
|
623
|
+
|
|
624
|
+
if (RequestedPosition < 0)
|
|
625
|
+
{
|
|
626
|
+
NewIndex = Helper.AddValue();
|
|
627
|
+
}
|
|
628
|
+
else
|
|
629
|
+
{
|
|
630
|
+
Helper.InsertValues(RequestedPosition, 1);
|
|
631
|
+
NewIndex = RequestedPosition;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
void* EntryAddr = Helper.GetRawPtr(NewIndex);
|
|
635
|
+
Fields.ClassProp->SetObjectPropertyValue(
|
|
636
|
+
Fields.ClassProp->ContainerPtrToValuePtr<void>(EntryAddr), TransformerClass);
|
|
637
|
+
|
|
638
|
+
// The instanced object is the part Python could not write, so it is
|
|
639
|
+
// created here rather than left for the editor UI to fill in.
|
|
640
|
+
UObject* Instance = NewObject<UObject>(
|
|
641
|
+
WorldPartition, TransformerClass, NAME_None, RF_Transactional);
|
|
642
|
+
Fields.InstanceProp->SetObjectPropertyValue(
|
|
643
|
+
Fields.InstanceProp->ContainerPtrToValuePtr<void>(EntryAddr), Instance);
|
|
644
|
+
|
|
645
|
+
if (Instance && Properties.IsValid())
|
|
646
|
+
{
|
|
647
|
+
Instance->Modify();
|
|
648
|
+
for (const auto& Pair : Properties->Values)
|
|
649
|
+
{
|
|
650
|
+
const FString PropertyPath(*Pair.Key);
|
|
651
|
+
FString SetError;
|
|
652
|
+
if (MCPJsonProperty::SetDottedPropertyFromJson(Instance, PropertyPath, Pair.Value, SetError))
|
|
653
|
+
{
|
|
654
|
+
AppliedProperties.Add(MakeShared<FJsonValueString>(PropertyPath));
|
|
655
|
+
}
|
|
656
|
+
else
|
|
657
|
+
{
|
|
658
|
+
PropertyErrors.Add(FString::Printf(TEXT("%s: %s"), *PropertyPath, *SetError));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
Instance->PostEditChange();
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
MCPWPMarkDirty(World, WorldPartition);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
auto Result = MCPSuccess();
|
|
668
|
+
MCPSetCreated(Result);
|
|
669
|
+
Result->SetStringField(TEXT("worldPartition"), WorldPartition->GetPathName());
|
|
670
|
+
Result->SetNumberField(TEXT("index"), NewIndex);
|
|
671
|
+
Result->SetNumberField(TEXT("stackSize"), Helper.Num());
|
|
672
|
+
Result->SetStringField(TEXT("class"), TransformerClass->GetPathName());
|
|
673
|
+
Result->SetStringField(TEXT("instancePath"),
|
|
674
|
+
FString::Printf(TEXT("RuntimeCellsTransformerStack[%d].Instance"), NewIndex));
|
|
675
|
+
Result->SetArrayField(TEXT("appliedProperties"), AppliedProperties);
|
|
676
|
+
if (PropertyErrors.Num() > 0)
|
|
677
|
+
{
|
|
678
|
+
Result->SetBoolField(TEXT("success"), false);
|
|
679
|
+
Result->SetStringField(TEXT("error"), FString::Printf(
|
|
680
|
+
TEXT("The transformer was added but %d of its properties would not apply: %s"),
|
|
681
|
+
PropertyErrors.Num(), *FString::Join(PropertyErrors, TEXT("; "))));
|
|
682
|
+
}
|
|
683
|
+
Result->SetStringField(TEXT("note"),
|
|
684
|
+
TEXT("Transformers run when streaming cells are generated, so rebuild the streaming data before measuring. The map package is left dirty and is NOT saved."));
|
|
685
|
+
return MCPResult(Result);
|
|
686
|
+
}
|