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,660 @@
|
|
|
1
|
+
#if WITH_DEV_AUTOMATION_TESTS
|
|
2
|
+
|
|
3
|
+
#include "Handlers/SequencerHandlers.h"
|
|
4
|
+
#include "HandlerRegistry.h"
|
|
5
|
+
|
|
6
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
7
|
+
#include "Channels/MovieSceneChannelProxy.h"
|
|
8
|
+
#include "Channels/MovieSceneDoubleChannel.h"
|
|
9
|
+
#include "Dom/JsonObject.h"
|
|
10
|
+
#include "Dom/JsonValue.h"
|
|
11
|
+
#include "GameFramework/Actor.h"
|
|
12
|
+
#include "LevelSequence.h"
|
|
13
|
+
#include "Misc/AutomationTest.h"
|
|
14
|
+
#include "Misc/Guid.h"
|
|
15
|
+
#include "MovieScene.h"
|
|
16
|
+
#include "MovieSceneSection.h"
|
|
17
|
+
#include "MovieSceneTrack.h"
|
|
18
|
+
#include "Sections/MovieScene3DTransformSection.h"
|
|
19
|
+
#include "Tracks/MovieScene3DTransformTrack.h"
|
|
20
|
+
#include "Tracks/MovieSceneCameraCutTrack.h"
|
|
21
|
+
#include "Tracks/MovieSceneFadeTrack.h"
|
|
22
|
+
#include "UObject/Package.h"
|
|
23
|
+
|
|
24
|
+
namespace UEMCPSequencerTimingTests
|
|
25
|
+
{
|
|
26
|
+
/**
|
|
27
|
+
* An Asset Registry visible LevelSequence that exists only for one test.
|
|
28
|
+
*
|
|
29
|
+
* UEditorAssetLibrary intentionally loads registered assets, so a plain
|
|
30
|
+
* object in GetTransientPackage cannot exercise the real handler route. This
|
|
31
|
+
* fixture creates a uniquely named in-memory package, never saves it, removes
|
|
32
|
+
* its registry entry at scope exit, and leaves every package it owns clean.
|
|
33
|
+
*/
|
|
34
|
+
class FTransientSequenceAsset
|
|
35
|
+
{
|
|
36
|
+
public:
|
|
37
|
+
explicit FTransientSequenceAsset(const TCHAR* Prefix)
|
|
38
|
+
{
|
|
39
|
+
const FString UniqueName = FString::Printf(
|
|
40
|
+
TEXT("%s_%s"),
|
|
41
|
+
Prefix,
|
|
42
|
+
*FGuid::NewGuid().ToString(EGuidFormats::Digits));
|
|
43
|
+
const FString PackageName = FString::Printf(
|
|
44
|
+
TEXT("/Game/__UE_MCP_Automation/%s"),
|
|
45
|
+
*UniqueName);
|
|
46
|
+
|
|
47
|
+
Package = CreatePackage(*PackageName);
|
|
48
|
+
Sequence = NewObject<ULevelSequence>(
|
|
49
|
+
Package,
|
|
50
|
+
FName(*UniqueName),
|
|
51
|
+
RF_Public | RF_Standalone);
|
|
52
|
+
Sequence->Initialize();
|
|
53
|
+
Sequence->AddToRoot();
|
|
54
|
+
FAssetRegistryModule::AssetCreated(Sequence);
|
|
55
|
+
ClearDirtyState();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
~FTransientSequenceAsset()
|
|
59
|
+
{
|
|
60
|
+
if (Sequence)
|
|
61
|
+
{
|
|
62
|
+
FAssetRegistryModule::AssetDeleted(Sequence);
|
|
63
|
+
Sequence->RemoveFromRoot();
|
|
64
|
+
Sequence->ClearFlags(RF_Public | RF_Standalone);
|
|
65
|
+
Sequence->MarkAsGarbage();
|
|
66
|
+
}
|
|
67
|
+
if (Package)
|
|
68
|
+
{
|
|
69
|
+
Package->SetDirtyFlag(false);
|
|
70
|
+
Package->MarkAsGarbage();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
FTransientSequenceAsset(const FTransientSequenceAsset&) = delete;
|
|
75
|
+
FTransientSequenceAsset& operator=(const FTransientSequenceAsset&) = delete;
|
|
76
|
+
|
|
77
|
+
ULevelSequence* Get() const
|
|
78
|
+
{
|
|
79
|
+
return Sequence;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
FString GetAssetPath() const
|
|
83
|
+
{
|
|
84
|
+
return Sequence ? Sequence->GetPathName() : FString();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
void ClearDirtyState() const
|
|
88
|
+
{
|
|
89
|
+
if (Package)
|
|
90
|
+
{
|
|
91
|
+
Package->SetDirtyFlag(false);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
bool IsDirty() const
|
|
96
|
+
{
|
|
97
|
+
return Package && Package->IsDirty();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private:
|
|
101
|
+
UPackage* Package = nullptr;
|
|
102
|
+
ULevelSequence* Sequence = nullptr;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
TSharedPtr<FJsonObject> ExecuteSequenceInfo(
|
|
106
|
+
FMCPHandlerRegistry& Registry,
|
|
107
|
+
const FString& AssetPath,
|
|
108
|
+
const bool bIncludeSectionDetails)
|
|
109
|
+
{
|
|
110
|
+
TSharedPtr<FJsonObject> Params = MakeShared<FJsonObject>();
|
|
111
|
+
Params->SetStringField(TEXT("assetPath"), AssetPath);
|
|
112
|
+
Params->SetBoolField(TEXT("includeSectionDetails"), bIncludeSectionDetails);
|
|
113
|
+
|
|
114
|
+
const TSharedPtr<FJsonValue> Response = Registry.ExecuteHandler(
|
|
115
|
+
TEXT("get_sequence_info"), Params);
|
|
116
|
+
return Response.IsValid() && Response->Type == EJson::Object
|
|
117
|
+
? Response->AsObject()
|
|
118
|
+
: nullptr;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
TSharedPtr<FJsonObject> GetObjectField(
|
|
122
|
+
const TSharedPtr<FJsonObject>& Parent,
|
|
123
|
+
const TCHAR* FieldName)
|
|
124
|
+
{
|
|
125
|
+
const TSharedPtr<FJsonObject>* Value = nullptr;
|
|
126
|
+
return Parent.IsValid() && Parent->TryGetObjectField(FieldName, Value) && Value
|
|
127
|
+
? *Value
|
|
128
|
+
: nullptr;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const TArray<TSharedPtr<FJsonValue>>* GetArrayField(
|
|
132
|
+
const TSharedPtr<FJsonObject>& Parent,
|
|
133
|
+
const TCHAR* FieldName)
|
|
134
|
+
{
|
|
135
|
+
const TArray<TSharedPtr<FJsonValue>>* Value = nullptr;
|
|
136
|
+
return Parent.IsValid() && Parent->TryGetArrayField(FieldName, Value)
|
|
137
|
+
? Value
|
|
138
|
+
: nullptr;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
TSharedPtr<FJsonObject> FindObjectByStringField(
|
|
142
|
+
const TArray<TSharedPtr<FJsonValue>>* Values,
|
|
143
|
+
const TCHAR* FieldName,
|
|
144
|
+
const FString& Expected)
|
|
145
|
+
{
|
|
146
|
+
if (!Values)
|
|
147
|
+
{
|
|
148
|
+
return nullptr;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const TSharedPtr<FJsonValue>& Value : *Values)
|
|
152
|
+
{
|
|
153
|
+
if (!Value.IsValid() || Value->Type != EJson::Object)
|
|
154
|
+
{
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const TSharedPtr<FJsonObject> Object = Value->AsObject();
|
|
159
|
+
FString Actual;
|
|
160
|
+
if (Object.IsValid() && Object->TryGetStringField(FieldName, Actual) && Actual == Expected)
|
|
161
|
+
{
|
|
162
|
+
return Object;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return nullptr;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
TSharedPtr<FJsonObject> GetArrayObjectAt(
|
|
169
|
+
const TArray<TSharedPtr<FJsonValue>>* Values,
|
|
170
|
+
const int32 Index)
|
|
171
|
+
{
|
|
172
|
+
if (!Values || !Values->IsValidIndex(Index))
|
|
173
|
+
{
|
|
174
|
+
return nullptr;
|
|
175
|
+
}
|
|
176
|
+
const TSharedPtr<FJsonValue>& Value = (*Values)[Index];
|
|
177
|
+
return Value.IsValid() && Value->Type == EJson::Object
|
|
178
|
+
? Value->AsObject()
|
|
179
|
+
: nullptr;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
UMovieSceneSection* AddSection(
|
|
183
|
+
UMovieSceneTrack* Track,
|
|
184
|
+
const TRange<FFrameNumber>& Range)
|
|
185
|
+
{
|
|
186
|
+
if (!Track)
|
|
187
|
+
{
|
|
188
|
+
return nullptr;
|
|
189
|
+
}
|
|
190
|
+
UMovieSceneSection* Section = Track->CreateNewSection();
|
|
191
|
+
if (Section)
|
|
192
|
+
{
|
|
193
|
+
Section->SetRange(Range);
|
|
194
|
+
Track->AddSection(*Section);
|
|
195
|
+
}
|
|
196
|
+
return Section;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
bool ExpectNumber(
|
|
200
|
+
FAutomationTestBase& Test,
|
|
201
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
202
|
+
const TCHAR* FieldName,
|
|
203
|
+
const double Expected,
|
|
204
|
+
const double Tolerance = UE_KINDA_SMALL_NUMBER)
|
|
205
|
+
{
|
|
206
|
+
double Actual = 0.0;
|
|
207
|
+
const bool bFound = Object.IsValid() && Object->TryGetNumberField(FieldName, Actual);
|
|
208
|
+
Test.TestTrue(FString::Printf(TEXT("%s is present"), FieldName), bFound);
|
|
209
|
+
return bFound && Test.TestEqual(FieldName, Actual, Expected, Tolerance);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
bool ExpectBool(
|
|
213
|
+
FAutomationTestBase& Test,
|
|
214
|
+
const TSharedPtr<FJsonObject>& Object,
|
|
215
|
+
const TCHAR* FieldName,
|
|
216
|
+
const bool Expected)
|
|
217
|
+
{
|
|
218
|
+
bool bActual = false;
|
|
219
|
+
const bool bFound = Object.IsValid() && Object->TryGetBoolField(FieldName, bActual);
|
|
220
|
+
Test.TestTrue(FString::Printf(TEXT("%s is present"), FieldName), bFound);
|
|
221
|
+
return bFound && Test.TestEqual(FieldName, bActual, Expected);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
void ExpectClosedRange(
|
|
225
|
+
FAutomationTestBase& Test,
|
|
226
|
+
const TSharedPtr<FJsonObject>& Range,
|
|
227
|
+
const int32 StartTick,
|
|
228
|
+
const int32 EndTick,
|
|
229
|
+
const double StartDisplayFrame,
|
|
230
|
+
const double EndDisplayFrame,
|
|
231
|
+
const double StartSeconds,
|
|
232
|
+
const double EndSeconds)
|
|
233
|
+
{
|
|
234
|
+
ExpectBool(Test, Range, TEXT("hasStart"), true);
|
|
235
|
+
ExpectBool(Test, Range, TEXT("hasEnd"), true);
|
|
236
|
+
ExpectBool(Test, Range, TEXT("startInclusive"), true);
|
|
237
|
+
ExpectBool(Test, Range, TEXT("endExclusive"), true);
|
|
238
|
+
ExpectNumber(Test, Range, TEXT("startTick"), StartTick);
|
|
239
|
+
ExpectNumber(Test, Range, TEXT("endTick"), EndTick);
|
|
240
|
+
ExpectNumber(Test, Range, TEXT("startDisplayFrame"), StartDisplayFrame);
|
|
241
|
+
ExpectNumber(Test, Range, TEXT("endDisplayFrame"), EndDisplayFrame);
|
|
242
|
+
ExpectNumber(Test, Range, TEXT("startSeconds"), StartSeconds, 1.e-9);
|
|
243
|
+
ExpectNumber(Test, Range, TEXT("endSeconds"), EndSeconds, 1.e-9);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
248
|
+
FSequencerInfoRegistrationAndValidationTest,
|
|
249
|
+
"UE.MCP.Sequencer.GetSequenceInfo.RegistrationAndValidation",
|
|
250
|
+
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
251
|
+
|
|
252
|
+
bool FSequencerInfoRegistrationAndValidationTest::RunTest(const FString& Parameters)
|
|
253
|
+
{
|
|
254
|
+
FMCPHandlerRegistry Registry;
|
|
255
|
+
FSequencerHandlers::RegisterHandlers(Registry);
|
|
256
|
+
TestTrue(TEXT("get_sequence_info is registered"), Registry.HasHandler(TEXT("get_sequence_info")));
|
|
257
|
+
|
|
258
|
+
const TSharedPtr<FJsonValue> MissingResponse = Registry.ExecuteHandler(
|
|
259
|
+
TEXT("get_sequence_info"), MakeShared<FJsonObject>());
|
|
260
|
+
TestTrue(TEXT("missing path returns an object"),
|
|
261
|
+
MissingResponse.IsValid() && MissingResponse->Type == EJson::Object);
|
|
262
|
+
if (MissingResponse.IsValid() && MissingResponse->Type == EJson::Object)
|
|
263
|
+
{
|
|
264
|
+
const TSharedPtr<FJsonObject> Missing = MissingResponse->AsObject();
|
|
265
|
+
TestFalse(TEXT("missing path is unsuccessful"), Missing->GetBoolField(TEXT("success")));
|
|
266
|
+
TestTrue(TEXT("missing path identifies the accepted field"),
|
|
267
|
+
Missing->GetStringField(TEXT("error")).Contains(TEXT("assetPath")));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
AddExpectedError(TEXT("LoadAsset failed"), EAutomationExpectedErrorFlags::Contains, 1);
|
|
271
|
+
TSharedPtr<FJsonObject> InvalidParams = MakeShared<FJsonObject>();
|
|
272
|
+
InvalidParams->SetStringField(
|
|
273
|
+
TEXT("assetPath"),
|
|
274
|
+
TEXT("/Game/__UE_MCP_Automation/DefinitelyMissing.DefinitelyMissing"));
|
|
275
|
+
const TSharedPtr<FJsonValue> InvalidResponse = Registry.ExecuteHandler(
|
|
276
|
+
TEXT("get_sequence_info"), InvalidParams);
|
|
277
|
+
TestTrue(TEXT("invalid asset path returns an object"),
|
|
278
|
+
InvalidResponse.IsValid() && InvalidResponse->Type == EJson::Object);
|
|
279
|
+
if (InvalidResponse.IsValid() && InvalidResponse->Type == EJson::Object)
|
|
280
|
+
{
|
|
281
|
+
const TSharedPtr<FJsonObject> Invalid = InvalidResponse->AsObject();
|
|
282
|
+
TestFalse(TEXT("invalid asset path is unsuccessful"), Invalid->GetBoolField(TEXT("success")));
|
|
283
|
+
TestTrue(TEXT("invalid asset path is named in the error"),
|
|
284
|
+
Invalid->GetStringField(TEXT("error")).Contains(TEXT("DefinitelyMissing")));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
291
|
+
FSequencerInfoTimingDetailsTest,
|
|
292
|
+
"UE.MCP.Sequencer.GetSequenceInfo.TimingDetails",
|
|
293
|
+
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
294
|
+
|
|
295
|
+
bool FSequencerInfoTimingDetailsTest::RunTest(const FString& Parameters)
|
|
296
|
+
{
|
|
297
|
+
using namespace UEMCPSequencerTimingTests;
|
|
298
|
+
|
|
299
|
+
FTransientSequenceAsset Fixture(TEXT("TimingDetails"));
|
|
300
|
+
ULevelSequence* Sequence = Fixture.Get();
|
|
301
|
+
TestNotNull(TEXT("transient LevelSequence exists"), Sequence);
|
|
302
|
+
if (!Sequence)
|
|
303
|
+
{
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
UMovieScene* MovieScene = Sequence->GetMovieScene();
|
|
308
|
+
TestNotNull(TEXT("transient LevelSequence has a MovieScene"), MovieScene);
|
|
309
|
+
if (!MovieScene)
|
|
310
|
+
{
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
MovieScene->SetDisplayRate(FFrameRate(24, 1));
|
|
315
|
+
MovieScene->SetTickResolutionDirectly(FFrameRate(24000, 1));
|
|
316
|
+
MovieScene->SetPlaybackRange(
|
|
317
|
+
TRange<FFrameNumber>(
|
|
318
|
+
TRangeBound<FFrameNumber>::Inclusive(FFrameNumber(12000)),
|
|
319
|
+
TRangeBound<FFrameNumber>::Exclusive(FFrameNumber(60000))),
|
|
320
|
+
false);
|
|
321
|
+
|
|
322
|
+
UMovieSceneCameraCutTrack* CameraCutTrack = Cast<UMovieSceneCameraCutTrack>(
|
|
323
|
+
MovieScene->AddCameraCutTrack(UMovieSceneCameraCutTrack::StaticClass()));
|
|
324
|
+
TestNotNull(TEXT("camera cut root track was created"), CameraCutTrack);
|
|
325
|
+
UMovieSceneSection* CameraCutSection = AddSection(
|
|
326
|
+
CameraCutTrack,
|
|
327
|
+
TRange<FFrameNumber>(FFrameNumber(24000), FFrameNumber(48000)));
|
|
328
|
+
TestNotNull(TEXT("camera cut section was created"), CameraCutSection);
|
|
329
|
+
|
|
330
|
+
UMovieSceneFadeTrack* FadeTrack = MovieScene->AddTrack<UMovieSceneFadeTrack>();
|
|
331
|
+
TestNotNull(TEXT("ordinary root track was created"), FadeTrack);
|
|
332
|
+
UMovieSceneSection* OpenSection = AddSection(
|
|
333
|
+
FadeTrack,
|
|
334
|
+
TRange<FFrameNumber>(
|
|
335
|
+
TRangeBound<FFrameNumber>::Open(),
|
|
336
|
+
TRangeBound<FFrameNumber>::Exclusive(FFrameNumber(36000))));
|
|
337
|
+
TestNotNull(TEXT("open-bound root section was created"), OpenSection);
|
|
338
|
+
|
|
339
|
+
const FGuid BindingGuid = MovieScene->AddPossessable(TEXT("TimingActor"), AActor::StaticClass());
|
|
340
|
+
UMovieScene3DTransformTrack* TransformTrack =
|
|
341
|
+
MovieScene->AddTrack<UMovieScene3DTransformTrack>(BindingGuid);
|
|
342
|
+
TestNotNull(TEXT("binding transform track was created"), TransformTrack);
|
|
343
|
+
UMovieScene3DTransformSection* TransformSection = Cast<UMovieScene3DTransformSection>(
|
|
344
|
+
AddSection(
|
|
345
|
+
TransformTrack,
|
|
346
|
+
TRange<FFrameNumber>(FFrameNumber(0), FFrameNumber(72000))));
|
|
347
|
+
TestNotNull(TEXT("transform section was created"), TransformSection);
|
|
348
|
+
if (!CameraCutTrack || !CameraCutSection || !FadeTrack || !OpenSection ||
|
|
349
|
+
!TransformTrack || !TransformSection)
|
|
350
|
+
{
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
FMovieSceneDoubleChannel* LocationX = TransformSection->GetChannelProxy()
|
|
355
|
+
.GetChannelByName<FMovieSceneDoubleChannel>(TEXT("Location.X"))
|
|
356
|
+
.Get();
|
|
357
|
+
TestNotNull(TEXT("Location.X channel exists"), LocationX);
|
|
358
|
+
if (!LocationX)
|
|
359
|
+
{
|
|
360
|
+
return false;
|
|
361
|
+
}
|
|
362
|
+
LocationX->AddLinearKey(FFrameNumber(60000), 30.0);
|
|
363
|
+
LocationX->AddLinearKey(FFrameNumber(500), 10.0);
|
|
364
|
+
LocationX->AddLinearKey(FFrameNumber(24000), 20.0);
|
|
365
|
+
Fixture.ClearDirtyState();
|
|
366
|
+
|
|
367
|
+
FMCPHandlerRegistry Registry;
|
|
368
|
+
FSequencerHandlers::RegisterHandlers(Registry);
|
|
369
|
+
|
|
370
|
+
// Details remain opt-in even though top-level timing rates are always useful.
|
|
371
|
+
const TSharedPtr<FJsonObject> Compact = ExecuteSequenceInfo(
|
|
372
|
+
Registry, Fixture.GetAssetPath(), false);
|
|
373
|
+
TestTrue(TEXT("compact response succeeds"),
|
|
374
|
+
Compact.IsValid() && Compact->GetBoolField(TEXT("success")));
|
|
375
|
+
TestFalse(TEXT("compact inspection leaves its fixture package clean"), Fixture.IsDirty());
|
|
376
|
+
if (Compact.IsValid())
|
|
377
|
+
{
|
|
378
|
+
TestFalse(TEXT("compact response omits detail limits"), Compact->HasField(TEXT("detailLimits")));
|
|
379
|
+
TestFalse(TEXT("compact response omits aggregate truncation"), Compact->HasField(TEXT("detailsTruncated")));
|
|
380
|
+
|
|
381
|
+
const TArray<TSharedPtr<FJsonValue>>* CompactRoots = GetArrayField(Compact, TEXT("masterTracks"));
|
|
382
|
+
const TSharedPtr<FJsonObject> CompactCamera = FindObjectByStringField(
|
|
383
|
+
CompactRoots, TEXT("class"), TEXT("MovieSceneCameraCutTrack"));
|
|
384
|
+
TestTrue(TEXT("compact response still lists the separate camera cut root"), CompactCamera.IsValid());
|
|
385
|
+
if (CompactCamera.IsValid())
|
|
386
|
+
{
|
|
387
|
+
TestFalse(TEXT("compact root track omits sections"), CompactCamera->HasField(TEXT("sections")));
|
|
388
|
+
TestFalse(TEXT("compact root track omits section truncation"), CompactCamera->HasField(TEXT("sectionsTruncated")));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const TSharedPtr<FJsonObject> CompactBinding = FindObjectByStringField(
|
|
392
|
+
GetArrayField(Compact, TEXT("bindings")), TEXT("name"), TEXT("TimingActor"));
|
|
393
|
+
const TSharedPtr<FJsonObject> CompactTransform = FindObjectByStringField(
|
|
394
|
+
GetArrayField(CompactBinding, TEXT("tracks")), TEXT("class"), TEXT("MovieScene3DTransformTrack"));
|
|
395
|
+
TestTrue(TEXT("compact response still lists binding tracks"), CompactTransform.IsValid());
|
|
396
|
+
if (CompactTransform.IsValid())
|
|
397
|
+
{
|
|
398
|
+
TestFalse(TEXT("compact binding track omits sections"), CompactTransform->HasField(TEXT("sections")));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const TSharedPtr<FJsonObject> Result = ExecuteSequenceInfo(
|
|
403
|
+
Registry, Fixture.GetAssetPath(), true);
|
|
404
|
+
TestTrue(TEXT("detailed response succeeds"),
|
|
405
|
+
Result.IsValid() && Result->GetBoolField(TEXT("success")));
|
|
406
|
+
TestFalse(TEXT("detailed inspection leaves its fixture package clean"), Fixture.IsDirty());
|
|
407
|
+
if (!Result.IsValid())
|
|
408
|
+
{
|
|
409
|
+
return false;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const TSharedPtr<FJsonObject> DisplayRate = GetObjectField(Result, TEXT("displayRate"));
|
|
413
|
+
const TSharedPtr<FJsonObject> TickResolution = GetObjectField(Result, TEXT("tickResolution"));
|
|
414
|
+
TestTrue(TEXT("display rate is present"), DisplayRate.IsValid());
|
|
415
|
+
TestTrue(TEXT("tick resolution is present"), TickResolution.IsValid());
|
|
416
|
+
ExpectNumber(*this, DisplayRate, TEXT("numerator"), 24);
|
|
417
|
+
ExpectNumber(*this, DisplayRate, TEXT("denominator"), 1);
|
|
418
|
+
ExpectNumber(*this, TickResolution, TEXT("numerator"), 24000);
|
|
419
|
+
ExpectNumber(*this, TickResolution, TEXT("denominator"), 1);
|
|
420
|
+
|
|
421
|
+
const TSharedPtr<FJsonObject> PlaybackRange = GetObjectField(Result, TEXT("playbackRange"));
|
|
422
|
+
TestTrue(TEXT("playback range is present"), PlaybackRange.IsValid());
|
|
423
|
+
ExpectClosedRange(*this, PlaybackRange, 12000, 60000, 12.0, 60.0, 0.5, 2.5);
|
|
424
|
+
// These two legacy fields remain in tick-resolution units for compatibility.
|
|
425
|
+
ExpectNumber(*this, PlaybackRange, TEXT("startFrame"), 12000);
|
|
426
|
+
ExpectNumber(*this, PlaybackRange, TEXT("endFrame"), 60000);
|
|
427
|
+
|
|
428
|
+
const TArray<TSharedPtr<FJsonValue>>* RootTracks = GetArrayField(Result, TEXT("masterTracks"));
|
|
429
|
+
const TSharedPtr<FJsonObject> CameraTrackObject = FindObjectByStringField(
|
|
430
|
+
RootTracks, TEXT("class"), TEXT("MovieSceneCameraCutTrack"));
|
|
431
|
+
const TSharedPtr<FJsonObject> FadeTrackObject = FindObjectByStringField(
|
|
432
|
+
RootTracks, TEXT("class"), TEXT("MovieSceneFadeTrack"));
|
|
433
|
+
TestTrue(TEXT("camera cut track is included with root tracks"), CameraTrackObject.IsValid());
|
|
434
|
+
TestTrue(TEXT("ordinary root track is included"), FadeTrackObject.IsValid());
|
|
435
|
+
const TSharedPtr<FJsonObject> FirstRootTrack = GetArrayObjectAt(RootTracks, 0);
|
|
436
|
+
FString FirstRootClass;
|
|
437
|
+
TestTrue(TEXT("camera cut track has deterministic first root priority"),
|
|
438
|
+
FirstRootTrack.IsValid() &&
|
|
439
|
+
FirstRootTrack->TryGetStringField(TEXT("class"), FirstRootClass) &&
|
|
440
|
+
FirstRootClass == TEXT("MovieSceneCameraCutTrack"));
|
|
441
|
+
|
|
442
|
+
const TSharedPtr<FJsonObject> CameraSectionObject = GetArrayObjectAt(
|
|
443
|
+
GetArrayField(CameraTrackObject, TEXT("sections")), 0);
|
|
444
|
+
TestTrue(TEXT("camera cut section details are present"), CameraSectionObject.IsValid());
|
|
445
|
+
ExpectClosedRange(*this, CameraSectionObject, 24000, 48000, 24.0, 48.0, 1.0, 2.0);
|
|
446
|
+
|
|
447
|
+
const TSharedPtr<FJsonObject> OpenSectionObject = GetArrayObjectAt(
|
|
448
|
+
GetArrayField(FadeTrackObject, TEXT("sections")), 0);
|
|
449
|
+
TestTrue(TEXT("open-bound section details are present"), OpenSectionObject.IsValid());
|
|
450
|
+
if (OpenSectionObject.IsValid())
|
|
451
|
+
{
|
|
452
|
+
ExpectBool(*this, OpenSectionObject, TEXT("hasStart"), false);
|
|
453
|
+
ExpectBool(*this, OpenSectionObject, TEXT("hasEnd"), true);
|
|
454
|
+
ExpectBool(*this, OpenSectionObject, TEXT("endExclusive"), true);
|
|
455
|
+
ExpectNumber(*this, OpenSectionObject, TEXT("endTick"), 36000);
|
|
456
|
+
ExpectNumber(*this, OpenSectionObject, TEXT("endDisplayFrame"), 36.0);
|
|
457
|
+
ExpectNumber(*this, OpenSectionObject, TEXT("endSeconds"), 1.5);
|
|
458
|
+
TestFalse(TEXT("open start omits startInclusive"), OpenSectionObject->HasField(TEXT("startInclusive")));
|
|
459
|
+
TestFalse(TEXT("open start omits startTick"), OpenSectionObject->HasField(TEXT("startTick")));
|
|
460
|
+
TestFalse(TEXT("open start omits startDisplayFrame"), OpenSectionObject->HasField(TEXT("startDisplayFrame")));
|
|
461
|
+
TestFalse(TEXT("open start omits startSeconds"), OpenSectionObject->HasField(TEXT("startSeconds")));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
const TSharedPtr<FJsonObject> BindingObject = FindObjectByStringField(
|
|
465
|
+
GetArrayField(Result, TEXT("bindings")), TEXT("name"), TEXT("TimingActor"));
|
|
466
|
+
const TSharedPtr<FJsonObject> TransformTrackObject = FindObjectByStringField(
|
|
467
|
+
GetArrayField(BindingObject, TEXT("tracks")), TEXT("class"), TEXT("MovieScene3DTransformTrack"));
|
|
468
|
+
const TSharedPtr<FJsonObject> TransformSectionObject = GetArrayObjectAt(
|
|
469
|
+
GetArrayField(TransformTrackObject, TEXT("sections")), 0);
|
|
470
|
+
TestTrue(TEXT("binding transform section details are present"), TransformSectionObject.IsValid());
|
|
471
|
+
ExpectClosedRange(*this, TransformSectionObject, 0, 72000, 0.0, 72.0, 0.0, 3.0);
|
|
472
|
+
|
|
473
|
+
const TSharedPtr<FJsonObject> LocationXObject = FindObjectByStringField(
|
|
474
|
+
GetArrayField(TransformSectionObject, TEXT("channels")), TEXT("name"), TEXT("Location.X"));
|
|
475
|
+
TestTrue(TEXT("Location.X channel details are present"), LocationXObject.IsValid());
|
|
476
|
+
if (LocationXObject.IsValid())
|
|
477
|
+
{
|
|
478
|
+
ExpectNumber(*this, LocationXObject, TEXT("keyCount"), 3);
|
|
479
|
+
ExpectBool(*this, LocationXObject, TEXT("keyTimesTruncated"), false);
|
|
480
|
+
FString ChannelType;
|
|
481
|
+
TestTrue(TEXT("channel type is named"),
|
|
482
|
+
LocationXObject->TryGetStringField(TEXT("type"), ChannelType) && !ChannelType.IsEmpty());
|
|
483
|
+
|
|
484
|
+
const TArray<TSharedPtr<FJsonValue>>* KeyTimes = GetArrayField(LocationXObject, TEXT("keyTimes"));
|
|
485
|
+
TestEqual(TEXT("all transform key times are returned"), KeyTimes ? KeyTimes->Num() : -1, 3);
|
|
486
|
+
const TSharedPtr<FJsonObject> FractionalKey = GetArrayObjectAt(KeyTimes, 0);
|
|
487
|
+
const TSharedPtr<FJsonObject> OneSecondKey = GetArrayObjectAt(KeyTimes, 1);
|
|
488
|
+
const TSharedPtr<FJsonObject> LateKey = GetArrayObjectAt(KeyTimes, 2);
|
|
489
|
+
ExpectNumber(*this, FractionalKey, TEXT("tick"), 500);
|
|
490
|
+
ExpectNumber(*this, FractionalKey, TEXT("displayFrame"), 0.5);
|
|
491
|
+
ExpectNumber(*this, FractionalKey, TEXT("seconds"), 1.0 / 48.0, 1.e-9);
|
|
492
|
+
ExpectNumber(*this, OneSecondKey, TEXT("tick"), 24000);
|
|
493
|
+
ExpectNumber(*this, OneSecondKey, TEXT("displayFrame"), 24.0);
|
|
494
|
+
ExpectNumber(*this, OneSecondKey, TEXT("seconds"), 1.0);
|
|
495
|
+
ExpectNumber(*this, LateKey, TEXT("tick"), 60000);
|
|
496
|
+
ExpectNumber(*this, LateKey, TEXT("displayFrame"), 60.0);
|
|
497
|
+
ExpectNumber(*this, LateKey, TEXT("seconds"), 2.5);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
ExpectBool(*this, Result, TEXT("detailsTruncated"), false);
|
|
501
|
+
Fixture.ClearDirtyState();
|
|
502
|
+
return true;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
506
|
+
FSequencerInfoKeyTimeLimitTest,
|
|
507
|
+
"UE.MCP.Sequencer.GetSequenceInfo.KeyTimeLimit",
|
|
508
|
+
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
509
|
+
|
|
510
|
+
bool FSequencerInfoKeyTimeLimitTest::RunTest(const FString& Parameters)
|
|
511
|
+
{
|
|
512
|
+
using namespace UEMCPSequencerTimingTests;
|
|
513
|
+
|
|
514
|
+
FTransientSequenceAsset Fixture(TEXT("KeyLimit"));
|
|
515
|
+
UMovieScene* MovieScene = Fixture.Get()->GetMovieScene();
|
|
516
|
+
MovieScene->SetDisplayRate(FFrameRate(24, 1));
|
|
517
|
+
MovieScene->SetTickResolutionDirectly(FFrameRate(24000, 1));
|
|
518
|
+
|
|
519
|
+
const FGuid BindingGuid = MovieScene->AddPossessable(TEXT("CappedActor"), AActor::StaticClass());
|
|
520
|
+
UMovieScene3DTransformTrack* TransformTrack =
|
|
521
|
+
MovieScene->AddTrack<UMovieScene3DTransformTrack>(BindingGuid);
|
|
522
|
+
UMovieScene3DTransformSection* Section = Cast<UMovieScene3DTransformSection>(
|
|
523
|
+
AddSection(
|
|
524
|
+
TransformTrack,
|
|
525
|
+
TRange<FFrameNumber>(FFrameNumber(0), FFrameNumber(1000))));
|
|
526
|
+
TestNotNull(TEXT("key limit fixture has a transform section"), Section);
|
|
527
|
+
if (!Section)
|
|
528
|
+
{
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
FMovieSceneDoubleChannel* LocationX = Section->GetChannelProxy()
|
|
533
|
+
.GetChannelByName<FMovieSceneDoubleChannel>(TEXT("Location.X"))
|
|
534
|
+
.Get();
|
|
535
|
+
TestNotNull(TEXT("key limit fixture has Location.X"), LocationX);
|
|
536
|
+
if (!LocationX)
|
|
537
|
+
{
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Insert in reverse order. Output must sort before applying the 256-key cap.
|
|
542
|
+
for (int32 Tick = 259; Tick >= 0; --Tick)
|
|
543
|
+
{
|
|
544
|
+
LocationX->AddLinearKey(FFrameNumber(Tick), Tick * 1.0);
|
|
545
|
+
}
|
|
546
|
+
Fixture.ClearDirtyState();
|
|
547
|
+
|
|
548
|
+
FMCPHandlerRegistry Registry;
|
|
549
|
+
FSequencerHandlers::RegisterHandlers(Registry);
|
|
550
|
+
const TSharedPtr<FJsonObject> Result = ExecuteSequenceInfo(
|
|
551
|
+
Registry, Fixture.GetAssetPath(), true);
|
|
552
|
+
TestTrue(TEXT("key limit response succeeds"),
|
|
553
|
+
Result.IsValid() && Result->GetBoolField(TEXT("success")));
|
|
554
|
+
TestFalse(TEXT("key limit inspection leaves its fixture package clean"), Fixture.IsDirty());
|
|
555
|
+
if (!Result.IsValid())
|
|
556
|
+
{
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const TSharedPtr<FJsonObject> Limits = GetObjectField(Result, TEXT("detailLimits"));
|
|
561
|
+
ExpectNumber(*this, Limits, TEXT("maxSections"), 256);
|
|
562
|
+
ExpectNumber(*this, Limits, TEXT("maxChannels"), 1024);
|
|
563
|
+
ExpectNumber(*this, Limits, TEXT("maxKeyTimes"), 4096);
|
|
564
|
+
ExpectNumber(*this, Limits, TEXT("maxKeyTimesPerChannel"), 256);
|
|
565
|
+
|
|
566
|
+
const TSharedPtr<FJsonObject> Binding = FindObjectByStringField(
|
|
567
|
+
GetArrayField(Result, TEXT("bindings")), TEXT("name"), TEXT("CappedActor"));
|
|
568
|
+
const TSharedPtr<FJsonObject> Track = FindObjectByStringField(
|
|
569
|
+
GetArrayField(Binding, TEXT("tracks")), TEXT("class"), TEXT("MovieScene3DTransformTrack"));
|
|
570
|
+
const TSharedPtr<FJsonObject> SectionObject = GetArrayObjectAt(
|
|
571
|
+
GetArrayField(Track, TEXT("sections")), 0);
|
|
572
|
+
const TSharedPtr<FJsonObject> Channel = FindObjectByStringField(
|
|
573
|
+
GetArrayField(SectionObject, TEXT("channels")), TEXT("name"), TEXT("Location.X"));
|
|
574
|
+
TestTrue(TEXT("capped Location.X output is present"), Channel.IsValid());
|
|
575
|
+
if (Channel.IsValid())
|
|
576
|
+
{
|
|
577
|
+
ExpectNumber(*this, Channel, TEXT("keyCount"), 260);
|
|
578
|
+
ExpectBool(*this, Channel, TEXT("keyTimesTruncated"), true);
|
|
579
|
+
const TArray<TSharedPtr<FJsonValue>>* KeyTimes = GetArrayField(Channel, TEXT("keyTimes"));
|
|
580
|
+
TestEqual(TEXT("per-channel key cap is applied"), KeyTimes ? KeyTimes->Num() : -1, 256);
|
|
581
|
+
const TSharedPtr<FJsonObject> First = GetArrayObjectAt(KeyTimes, 0);
|
|
582
|
+
const TSharedPtr<FJsonObject> Last = GetArrayObjectAt(KeyTimes, 255);
|
|
583
|
+
ExpectNumber(*this, First, TEXT("tick"), 0);
|
|
584
|
+
ExpectNumber(*this, Last, TEXT("tick"), 255);
|
|
585
|
+
}
|
|
586
|
+
ExpectBool(*this, Result, TEXT("detailsTruncated"), true);
|
|
587
|
+
|
|
588
|
+
Fixture.ClearDirtyState();
|
|
589
|
+
return true;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
|
|
593
|
+
FSequencerInfoSectionLimitTest,
|
|
594
|
+
"UE.MCP.Sequencer.GetSequenceInfo.SectionLimit",
|
|
595
|
+
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter)
|
|
596
|
+
|
|
597
|
+
bool FSequencerInfoSectionLimitTest::RunTest(const FString& Parameters)
|
|
598
|
+
{
|
|
599
|
+
using namespace UEMCPSequencerTimingTests;
|
|
600
|
+
|
|
601
|
+
FTransientSequenceAsset Fixture(TEXT("SectionLimit"));
|
|
602
|
+
UMovieScene* MovieScene = Fixture.Get()->GetMovieScene();
|
|
603
|
+
MovieScene->SetDisplayRate(FFrameRate(24, 1));
|
|
604
|
+
MovieScene->SetTickResolutionDirectly(FFrameRate(24000, 1));
|
|
605
|
+
|
|
606
|
+
UMovieSceneFadeTrack* FadeTrack = MovieScene->AddTrack<UMovieSceneFadeTrack>();
|
|
607
|
+
TestNotNull(TEXT("section limit fixture has a root track"), FadeTrack);
|
|
608
|
+
if (!FadeTrack)
|
|
609
|
+
{
|
|
610
|
+
return false;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
for (int32 Index = 0; Index < 257; ++Index)
|
|
614
|
+
{
|
|
615
|
+
const FFrameNumber Start(Index * 1000);
|
|
616
|
+
const FFrameNumber End((Index + 1) * 1000);
|
|
617
|
+
if (!AddSection(FadeTrack, TRange<FFrameNumber>(Start, End)))
|
|
618
|
+
{
|
|
619
|
+
AddError(FString::Printf(TEXT("failed to create section %d"), Index));
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
Fixture.ClearDirtyState();
|
|
624
|
+
|
|
625
|
+
FMCPHandlerRegistry Registry;
|
|
626
|
+
FSequencerHandlers::RegisterHandlers(Registry);
|
|
627
|
+
const TSharedPtr<FJsonObject> Result = ExecuteSequenceInfo(
|
|
628
|
+
Registry, Fixture.GetAssetPath(), true);
|
|
629
|
+
TestTrue(TEXT("section limit response succeeds"),
|
|
630
|
+
Result.IsValid() && Result->GetBoolField(TEXT("success")));
|
|
631
|
+
TestFalse(TEXT("section limit inspection leaves its fixture package clean"), Fixture.IsDirty());
|
|
632
|
+
if (!Result.IsValid())
|
|
633
|
+
{
|
|
634
|
+
return false;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const TSharedPtr<FJsonObject> Track = FindObjectByStringField(
|
|
638
|
+
GetArrayField(Result, TEXT("masterTracks")), TEXT("class"), TEXT("MovieSceneFadeTrack"));
|
|
639
|
+
TestTrue(TEXT("capped root track output is present"), Track.IsValid());
|
|
640
|
+
if (Track.IsValid())
|
|
641
|
+
{
|
|
642
|
+
ExpectNumber(*this, Track, TEXT("sectionCount"), 257);
|
|
643
|
+
ExpectBool(*this, Track, TEXT("sectionsTruncated"), true);
|
|
644
|
+
const TArray<TSharedPtr<FJsonValue>>* Sections = GetArrayField(Track, TEXT("sections"));
|
|
645
|
+
TestEqual(TEXT("global section cap is applied"), Sections ? Sections->Num() : -1, 256);
|
|
646
|
+
|
|
647
|
+
const TSharedPtr<FJsonObject> First = GetArrayObjectAt(Sections, 0);
|
|
648
|
+
const TSharedPtr<FJsonObject> Last = GetArrayObjectAt(Sections, 255);
|
|
649
|
+
ExpectNumber(*this, First, TEXT("index"), 0);
|
|
650
|
+
ExpectNumber(*this, First, TEXT("startTick"), 0);
|
|
651
|
+
ExpectNumber(*this, Last, TEXT("index"), 255);
|
|
652
|
+
ExpectNumber(*this, Last, TEXT("startTick"), 255000);
|
|
653
|
+
}
|
|
654
|
+
ExpectBool(*this, Result, TEXT("detailsTruncated"), true);
|
|
655
|
+
|
|
656
|
+
Fixture.ClearDirtyState();
|
|
657
|
+
return true;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
#endif
|