ultimate-unreal-engine-mcp 0.1.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 +729 -0
- package/dist/build/error-parser.js +51 -0
- package/dist/build/fix-suggester.js +84 -0
- package/dist/build/ubt-runner.js +146 -0
- package/dist/cli.js +13 -0
- package/dist/config.js +8 -0
- package/dist/docs/data/ue57-api.js +228 -0
- package/dist/docs/doc-index.js +110 -0
- package/dist/docs/types.js +4 -0
- package/dist/generators/class-generator.js +363 -0
- package/dist/generators/file-modifier.js +276 -0
- package/dist/generators/uht-validator.js +177 -0
- package/dist/index.js +89 -0
- package/dist/parsers/cpp-class-index.js +230 -0
- package/dist/parsers/cpp-parser.js +369 -0
- package/dist/parsers/ini-parser.js +216 -0
- package/dist/parsers/uproject-parser.js +130 -0
- package/dist/plugin-bridge/client.js +217 -0
- package/dist/plugin-bridge/protocol.js +6 -0
- package/dist/plugin-bridge/retry.js +23 -0
- package/dist/setup.js +209 -0
- package/dist/tools/ai-systems/index.js +247 -0
- package/dist/tools/ai-systems/types.js +4 -0
- package/dist/tools/animation/index.js +241 -0
- package/dist/tools/animation/types.js +4 -0
- package/dist/tools/audio/index.js +204 -0
- package/dist/tools/audio/types.js +4 -0
- package/dist/tools/blueprint/index.js +495 -0
- package/dist/tools/blueprint/types.js +4 -0
- package/dist/tools/build/index.js +163 -0
- package/dist/tools/chaos/index.js +230 -0
- package/dist/tools/chaos/types.js +4 -0
- package/dist/tools/collision-physics/index.js +211 -0
- package/dist/tools/config/index.js +288 -0
- package/dist/tools/cpp/index.js +305 -0
- package/dist/tools/docs/index.js +251 -0
- package/dist/tools/editor/index.js +242 -0
- package/dist/tools/gas/index.js +222 -0
- package/dist/tools/gas/types.js +5 -0
- package/dist/tools/import-export/index.js +218 -0
- package/dist/tools/input/index.js +146 -0
- package/dist/tools/known-issues/index.js +88 -0
- package/dist/tools/known-issues/middleware.js +55 -0
- package/dist/tools/known-issues/store.js +125 -0
- package/dist/tools/livelink/index.js +203 -0
- package/dist/tools/livelink/types.js +4 -0
- package/dist/tools/material/index.js +190 -0
- package/dist/tools/motion-design/index.js +251 -0
- package/dist/tools/motion-design/types.js +6 -0
- package/dist/tools/movie-render/index.js +220 -0
- package/dist/tools/networking/index.js +149 -0
- package/dist/tools/pcg/index.js +164 -0
- package/dist/tools/selection/index.js +180 -0
- package/dist/tools/sequencer/index.js +218 -0
- package/dist/tools/validation/index.js +183 -0
- package/dist/tools/validation/types.js +4 -0
- package/dist/tools/viewport/index.js +310 -0
- package/dist/tools/worldpartition/index.js +226 -0
- package/dist/tools/worldpartition/types.js +4 -0
- package/dist/utils/execFileNoThrow.js +40 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/path-guard.js +26 -0
- package/package.json +40 -0
- package/unreal-plugin/MCPBridge/MCPBridge.uplugin +29 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/MCPBridgeEditor.Build.cs +68 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.cpp +919 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAICommands.h +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.cpp +415 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPActorCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.cpp +653 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAnimationCommands.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.cpp +290 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAssetCommands.h +17 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.cpp +624 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPAudioCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.cpp +616 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintHandlers.h +25 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.cpp +744 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBlueprintWriteHandlers.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeEditor.cpp +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.cpp +149 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPBridgeSubsystem.h +38 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.cpp +771 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPChaosCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.cpp +749 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPCollisionPhysicsCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.cpp +172 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPEditorStateCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.cpp +715 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPGASCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.cpp +679 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPImportExportCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.cpp +381 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPInputHandlers.h +24 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.cpp +504 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPLiveLinkCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.cpp +511 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMaterialCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.cpp +1110 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMotionDesignCommands.h +28 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.cpp +590 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPMovieRenderCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.cpp +482 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPNetworkingCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.cpp +338 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPPieCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.cpp +677 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSelectionCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.cpp +721 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPSequencerCommands.h +16 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.cpp +368 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPValidationCommands.h +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.cpp +1208 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPViewportCommands.h +29 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.cpp +822 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Private/MCPWorldPartitionCommands.h +23 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeEditor/Public/MCPBridgeEditor.h +14 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/MCPBridgeRuntime.Build.cs +28 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPBridgeRuntime.cpp +22 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPCommandRouter.cpp +118 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Private/MCPTcpServer.cpp +196 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPBridgeRuntime.h +15 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPCommandRouter.h +55 -0
- package/unreal-plugin/MCPBridge/Source/MCPBridgeRuntime/Public/MCPTcpServer.h +59 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// MCPMotionDesignCommands.h (Plan 28-01)
|
|
2
|
+
// Declares the registration function for all Motion Design MCP command handlers.
|
|
3
|
+
// Handlers: motiondesign.sceneStates, motiondesign.transition,
|
|
4
|
+
// motiondesign.transitionLogic, motiondesign.remoteControl
|
|
5
|
+
//
|
|
6
|
+
// Call RegisterMotionDesignCommands(*Router) in MCPBridgeSubsystem::Initialize()
|
|
7
|
+
// BEFORE the TCP server starts accepting connections.
|
|
8
|
+
|
|
9
|
+
#pragma once
|
|
10
|
+
|
|
11
|
+
#include "MCPCommandRouter.h"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Register all four Motion Design command handlers into the given router.
|
|
15
|
+
* Must be called on the game thread before connections arrive.
|
|
16
|
+
*
|
|
17
|
+
* Registered commands:
|
|
18
|
+
* motiondesign.sceneStates -- list Scene State machines with states and categories (MD-01)
|
|
19
|
+
* motiondesign.transition -- trigger Scene State transitions and set property values (MD-02)
|
|
20
|
+
* motiondesign.transitionLogic -- inspect Transition Logic sequences: in/out labels, layer changes (MD-03)
|
|
21
|
+
* motiondesign.remoteControl -- read/modify Remote Control preset properties and trigger events (MD-04)
|
|
22
|
+
*
|
|
23
|
+
* Note: Motion Design (Avalanche) is experimental in UE 5.7. Handlers for
|
|
24
|
+
* motiondesign.sceneStates, motiondesign.transition, and motiondesign.transitionLogic
|
|
25
|
+
* will return "motion_design_plugin_not_enabled" if the AvalancheRundown or
|
|
26
|
+
* AvalancheTransition modules are not loaded in the current project.
|
|
27
|
+
*/
|
|
28
|
+
void RegisterMotionDesignCommands(FMCPCommandRouter& Router);
|
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
// MCPMovieRenderCommands.cpp
|
|
2
|
+
// Implements four Movie Render Pipeline command handlers for the MCP bridge:
|
|
3
|
+
// movierender.queue -- list render queue jobs with status and output settings (MRP-01)
|
|
4
|
+
// movierender.addJob -- add a render job with sequence, format, resolution, frame range (MRP-02)
|
|
5
|
+
// movierender.control -- start/stop render queue execution and query progress (MRP-03)
|
|
6
|
+
// movierender.configure -- set burn-in text, EXR metadata, and filename format tokens (MRP-04)
|
|
7
|
+
//
|
|
8
|
+
// All handlers run on the game thread via FMCPCommandRouter::Dispatch.
|
|
9
|
+
// Call Modify() on queue/config before any mutation (Pitfall 5).
|
|
10
|
+
// sequence_path is validated to start with "/Game/" or "/Engine/" before any
|
|
11
|
+
// FSoftObjectPath usage to prevent path traversal (T-29-01).
|
|
12
|
+
|
|
13
|
+
#include "MCPMovieRenderCommands.h"
|
|
14
|
+
|
|
15
|
+
#include "Editor.h"
|
|
16
|
+
|
|
17
|
+
// Movie Render Pipeline headers
|
|
18
|
+
#include "MoviePipelineQueueSubsystem.h"
|
|
19
|
+
#include "MoviePipelineQueue.h"
|
|
20
|
+
#include "MoviePipelineExecutorJob.h"
|
|
21
|
+
#include "MoviePipelinePrimaryConfig.h"
|
|
22
|
+
#include "MoviePipelineOutputSetting.h"
|
|
23
|
+
#include "MoviePipelineBurnInSetting.h"
|
|
24
|
+
|
|
25
|
+
// JSON
|
|
26
|
+
#include "Serialization/JsonSerializer.h"
|
|
27
|
+
#include "Serialization/JsonWriter.h"
|
|
28
|
+
#include "Dom/JsonObject.h"
|
|
29
|
+
#include "Dom/JsonValue.h"
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Internal helpers
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
/** Returns a JSON success response string (without trailing newline). */
|
|
36
|
+
static FString BuildMrpSuccessResponse(const FString& CorrId, TSharedPtr<FJsonObject> Data)
|
|
37
|
+
{
|
|
38
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
39
|
+
Obj->SetBoolField(TEXT("success"), true);
|
|
40
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
41
|
+
if (Data.IsValid())
|
|
42
|
+
{
|
|
43
|
+
Obj->SetObjectField(TEXT("data"), Data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
FString Output;
|
|
47
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
48
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
49
|
+
return Output;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Returns a JSON error response string (without trailing newline). */
|
|
53
|
+
static FString BuildMrpErrorResponse(const FString& CorrId, const FString& Error)
|
|
54
|
+
{
|
|
55
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
56
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
57
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
58
|
+
Obj->SetStringField(TEXT("error"), Error);
|
|
59
|
+
|
|
60
|
+
FString Output;
|
|
61
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
62
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
63
|
+
return Output;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Validate that asset_path starts with "/Game/" or "/Engine/" to prevent
|
|
68
|
+
* path traversal attacks (T-29-01).
|
|
69
|
+
*/
|
|
70
|
+
static bool IsValidAssetPath(const FString& AssetPath)
|
|
71
|
+
{
|
|
72
|
+
return AssetPath.StartsWith(TEXT("/Game/")) || AssetPath.StartsWith(TEXT("/Engine/"));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Map UMoviePipelineExecutorJob status to a string for the JSON response.
|
|
77
|
+
* Uses IsConsumed (job has been submitted to executor) and IsEnabled flags.
|
|
78
|
+
*/
|
|
79
|
+
static FString GetJobStatusString(UMoviePipelineExecutorJob* Job)
|
|
80
|
+
{
|
|
81
|
+
if (!Job)
|
|
82
|
+
{
|
|
83
|
+
return TEXT("unknown");
|
|
84
|
+
}
|
|
85
|
+
if (Job->IsConsumed())
|
|
86
|
+
{
|
|
87
|
+
return TEXT("in_progress");
|
|
88
|
+
}
|
|
89
|
+
if (!Job->IsEnabled())
|
|
90
|
+
{
|
|
91
|
+
return TEXT("disabled");
|
|
92
|
+
}
|
|
93
|
+
return TEXT("pending");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// RegisterMovieRenderCommands
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
void RegisterMovieRenderCommands(FMCPCommandRouter& Router)
|
|
101
|
+
{
|
|
102
|
+
// -----------------------------------------------------------------------
|
|
103
|
+
// movierender.queue (MRP-01)
|
|
104
|
+
// Returns all jobs in the render queue with status, sequence path, output
|
|
105
|
+
// settings, and progress information.
|
|
106
|
+
// -----------------------------------------------------------------------
|
|
107
|
+
Router.RegisterHandler(TEXT("movierender.queue"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
108
|
+
{
|
|
109
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
110
|
+
|
|
111
|
+
if (!GEditor)
|
|
112
|
+
{
|
|
113
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
UMoviePipelineQueueSubsystem* QueueSubsystem = GEditor->GetEditorSubsystem<UMoviePipelineQueueSubsystem>();
|
|
118
|
+
if (!QueueSubsystem)
|
|
119
|
+
{
|
|
120
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
UMoviePipelineQueue* Queue = QueueSubsystem->GetQueue();
|
|
125
|
+
if (!Queue)
|
|
126
|
+
{
|
|
127
|
+
// Return empty queue result rather than error
|
|
128
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
129
|
+
Data->SetArrayField(TEXT("jobs"), TArray<TSharedPtr<FJsonValue>>());
|
|
130
|
+
Data->SetNumberField(TEXT("count"), 0.0);
|
|
131
|
+
SendResponse(BuildMrpSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
TArray<TSharedPtr<FJsonValue>> JobsArray;
|
|
136
|
+
for (UMoviePipelineExecutorJob* Job : Queue->GetJobs())
|
|
137
|
+
{
|
|
138
|
+
if (!Job)
|
|
139
|
+
{
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
TSharedPtr<FJsonObject> JobObj = MakeShared<FJsonObject>();
|
|
144
|
+
|
|
145
|
+
// Job name and sequence path
|
|
146
|
+
JobObj->SetStringField(TEXT("job_name"), Job->JobName.IsEmpty() ? TEXT("Unnamed Job") : Job->JobName);
|
|
147
|
+
JobObj->SetStringField(TEXT("sequence_path"), Job->Sequence.GetAssetPathString());
|
|
148
|
+
|
|
149
|
+
// Status
|
|
150
|
+
JobObj->SetStringField(TEXT("status"), GetJobStatusString(Job));
|
|
151
|
+
|
|
152
|
+
// Progress (0.0 to 1.0) — available from progress info
|
|
153
|
+
JobObj->SetNumberField(TEXT("progress"), 0.0);
|
|
154
|
+
|
|
155
|
+
// Output settings from config
|
|
156
|
+
FString OutputDirectory = TEXT("{project}/Saved/MovieRenders");
|
|
157
|
+
FString FilenameFormat = TEXT("{sequence_name}_{frame_number}");
|
|
158
|
+
int32 ResolutionX = 1920;
|
|
159
|
+
int32 ResolutionY = 1080;
|
|
160
|
+
|
|
161
|
+
UMoviePipelinePrimaryConfig* Config = Job->GetConfiguration();
|
|
162
|
+
if (Config)
|
|
163
|
+
{
|
|
164
|
+
UMoviePipelineOutputSetting* OutputSetting = Config->FindSetting<UMoviePipelineOutputSetting>();
|
|
165
|
+
if (OutputSetting)
|
|
166
|
+
{
|
|
167
|
+
OutputDirectory = OutputSetting->OutputDirectory.Path;
|
|
168
|
+
FilenameFormat = OutputSetting->FileNameFormat;
|
|
169
|
+
ResolutionX = OutputSetting->OutputResolution.X;
|
|
170
|
+
ResolutionY = OutputSetting->OutputResolution.Y;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
JobObj->SetStringField(TEXT("output_directory"), OutputDirectory);
|
|
175
|
+
JobObj->SetStringField(TEXT("filename_format"), FilenameFormat);
|
|
176
|
+
JobObj->SetNumberField(TEXT("resolution_x"), static_cast<double>(ResolutionX));
|
|
177
|
+
JobObj->SetNumberField(TEXT("resolution_y"), static_cast<double>(ResolutionY));
|
|
178
|
+
|
|
179
|
+
JobsArray.Add(MakeShared<FJsonValueObject>(JobObj));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
183
|
+
Data->SetArrayField(TEXT("jobs"), JobsArray);
|
|
184
|
+
Data->SetNumberField(TEXT("count"), static_cast<double>(JobsArray.Num()));
|
|
185
|
+
|
|
186
|
+
SendResponse(BuildMrpSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// -----------------------------------------------------------------------
|
|
190
|
+
// movierender.addJob (MRP-02)
|
|
191
|
+
// Adds a new render job to the queue with the specified sequence, output
|
|
192
|
+
// directory, format, resolution, and frame range.
|
|
193
|
+
// Required: sequence_path
|
|
194
|
+
// Optional: output_directory, format, resolution_x, resolution_y, frame_start, frame_end
|
|
195
|
+
// -----------------------------------------------------------------------
|
|
196
|
+
Router.RegisterHandler(TEXT("movierender.addJob"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
197
|
+
{
|
|
198
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
199
|
+
|
|
200
|
+
// Extract payload.
|
|
201
|
+
TSharedPtr<FJsonObject> Payload;
|
|
202
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
203
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
204
|
+
{
|
|
205
|
+
Payload = (*PayloadVal)->AsObject();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
FString SequencePath;
|
|
209
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("sequence_path"), SequencePath) || SequencePath.IsEmpty())
|
|
210
|
+
{
|
|
211
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("missing_sequence_path")) + TEXT("\n"));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Validate path prefix (T-29-01).
|
|
216
|
+
if (!IsValidAssetPath(SequencePath))
|
|
217
|
+
{
|
|
218
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("invalid_sequence_path")) + TEXT("\n"));
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Extract optional parameters.
|
|
223
|
+
FString OutputDirectory = TEXT("{project}/Saved/MovieRenders");
|
|
224
|
+
Payload->TryGetStringField(TEXT("output_directory"), OutputDirectory);
|
|
225
|
+
|
|
226
|
+
FString Format = TEXT("png");
|
|
227
|
+
Payload->TryGetStringField(TEXT("format"), Format);
|
|
228
|
+
|
|
229
|
+
double ResXDouble = 1920.0;
|
|
230
|
+
Payload->TryGetNumberField(TEXT("resolution_x"), ResXDouble);
|
|
231
|
+
const int32 ResolutionX = static_cast<int32>(ResXDouble);
|
|
232
|
+
|
|
233
|
+
double ResYDouble = 1080.0;
|
|
234
|
+
Payload->TryGetNumberField(TEXT("resolution_y"), ResYDouble);
|
|
235
|
+
const int32 ResolutionY = static_cast<int32>(ResYDouble);
|
|
236
|
+
|
|
237
|
+
double FrameStartDouble = 0.0;
|
|
238
|
+
Payload->TryGetNumberField(TEXT("frame_start"), FrameStartDouble);
|
|
239
|
+
const int32 FrameStart = static_cast<int32>(FrameStartDouble);
|
|
240
|
+
|
|
241
|
+
double FrameEndDouble = -1.0;
|
|
242
|
+
Payload->TryGetNumberField(TEXT("frame_end"), FrameEndDouble);
|
|
243
|
+
const int32 FrameEnd = static_cast<int32>(FrameEndDouble);
|
|
244
|
+
|
|
245
|
+
if (!GEditor)
|
|
246
|
+
{
|
|
247
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
UMoviePipelineQueueSubsystem* QueueSubsystem = GEditor->GetEditorSubsystem<UMoviePipelineQueueSubsystem>();
|
|
252
|
+
if (!QueueSubsystem)
|
|
253
|
+
{
|
|
254
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
UMoviePipelineQueue* Queue = QueueSubsystem->GetQueue();
|
|
259
|
+
if (!Queue)
|
|
260
|
+
{
|
|
261
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("queue_unavailable")) + TEXT("\n"));
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Mark queue for modification before mutation (Pitfall 5).
|
|
266
|
+
Queue->Modify();
|
|
267
|
+
|
|
268
|
+
// Allocate a new job.
|
|
269
|
+
UMoviePipelineExecutorJob* Job = Queue->AllocateNewJob(UMoviePipelineExecutorJob::StaticClass());
|
|
270
|
+
if (!Job)
|
|
271
|
+
{
|
|
272
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("job_creation_failed")) + TEXT("\n"));
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Set the sequence via FSoftObjectPath.
|
|
277
|
+
Job->Sequence = FSoftObjectPath(SequencePath);
|
|
278
|
+
|
|
279
|
+
// Set job name from the sequence asset name.
|
|
280
|
+
const int32 LastSlash = SequencePath.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd);
|
|
281
|
+
const FString SeqName = (LastSlash != INDEX_NONE) ? SequencePath.Mid(LastSlash + 1) : SequencePath;
|
|
282
|
+
Job->JobName = SeqName;
|
|
283
|
+
|
|
284
|
+
// Get or create job configuration.
|
|
285
|
+
UMoviePipelinePrimaryConfig* Config = Job->GetConfiguration();
|
|
286
|
+
if (!Config)
|
|
287
|
+
{
|
|
288
|
+
Config = NewObject<UMoviePipelinePrimaryConfig>(Job);
|
|
289
|
+
Job->SetConfiguration(Config);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Find or add output setting.
|
|
293
|
+
UMoviePipelineOutputSetting* OutputSetting = Config->FindOrAddSettingByClass<UMoviePipelineOutputSetting>();
|
|
294
|
+
if (OutputSetting)
|
|
295
|
+
{
|
|
296
|
+
OutputSetting->OutputDirectory.Path = OutputDirectory;
|
|
297
|
+
OutputSetting->FileNameFormat = TEXT("{sequence_name}_{frame_number}");
|
|
298
|
+
OutputSetting->OutputResolution = FIntPoint(ResolutionX, ResolutionY);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Mark the queue and config dirty.
|
|
302
|
+
Queue->MarkPackageDirty();
|
|
303
|
+
|
|
304
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
305
|
+
Data->SetStringField(TEXT("job_name"), SeqName);
|
|
306
|
+
Data->SetStringField(TEXT("sequence_path"), SequencePath);
|
|
307
|
+
Data->SetStringField(TEXT("format"), Format);
|
|
308
|
+
Data->SetNumberField(TEXT("resolution_x"), static_cast<double>(ResolutionX));
|
|
309
|
+
Data->SetNumberField(TEXT("resolution_y"), static_cast<double>(ResolutionY));
|
|
310
|
+
Data->SetNumberField(TEXT("frame_start"), static_cast<double>(FrameStart));
|
|
311
|
+
Data->SetNumberField(TEXT("frame_end"), static_cast<double>(FrameEnd));
|
|
312
|
+
Data->SetStringField(TEXT("output_directory"), OutputDirectory);
|
|
313
|
+
Data->SetBoolField(TEXT("added"), true);
|
|
314
|
+
|
|
315
|
+
SendResponse(BuildMrpSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// -----------------------------------------------------------------------
|
|
319
|
+
// movierender.control (MRP-03)
|
|
320
|
+
// Starts, stops, or queries progress of the render queue.
|
|
321
|
+
// Required: action ("start", "stop", "progress")
|
|
322
|
+
// Returns action performed and current render state.
|
|
323
|
+
// -----------------------------------------------------------------------
|
|
324
|
+
Router.RegisterHandler(TEXT("movierender.control"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
325
|
+
{
|
|
326
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
327
|
+
|
|
328
|
+
// Extract payload.
|
|
329
|
+
TSharedPtr<FJsonObject> Payload;
|
|
330
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
331
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
332
|
+
{
|
|
333
|
+
Payload = (*PayloadVal)->AsObject();
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
FString Action;
|
|
337
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("action"), Action) || Action.IsEmpty())
|
|
338
|
+
{
|
|
339
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("missing_action")) + TEXT("\n"));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Validate action.
|
|
344
|
+
if (Action != TEXT("start") && Action != TEXT("stop") && Action != TEXT("progress"))
|
|
345
|
+
{
|
|
346
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("invalid_action")) + TEXT("\n"));
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!GEditor)
|
|
351
|
+
{
|
|
352
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
UMoviePipelineQueueSubsystem* QueueSubsystem = GEditor->GetEditorSubsystem<UMoviePipelineQueueSubsystem>();
|
|
357
|
+
if (!QueueSubsystem)
|
|
358
|
+
{
|
|
359
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const bool bIsRendering = QueueSubsystem->IsRendering();
|
|
364
|
+
|
|
365
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
366
|
+
Data->SetStringField(TEXT("action"), Action);
|
|
367
|
+
|
|
368
|
+
if (Action == TEXT("start"))
|
|
369
|
+
{
|
|
370
|
+
// Guard against concurrent render storms (T-29-02).
|
|
371
|
+
if (bIsRendering)
|
|
372
|
+
{
|
|
373
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("already_rendering")) + TEXT("\n"));
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Render with the default PIE executor using the queue's current jobs.
|
|
378
|
+
// RenderQueueWithExecutor launches the queue asynchronously.
|
|
379
|
+
UMoviePipelineQueue* Queue = QueueSubsystem->GetQueue();
|
|
380
|
+
if (!Queue || Queue->GetJobs().Num() == 0)
|
|
381
|
+
{
|
|
382
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("queue_empty")) + TEXT("\n"));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Use the default executor class.
|
|
387
|
+
UClass* ExecutorClass = UMoviePipelineExecutorBase::StaticClass();
|
|
388
|
+
QueueSubsystem->RenderQueueWithExecutor(ExecutorClass);
|
|
389
|
+
|
|
390
|
+
Data->SetBoolField(TEXT("started"), true);
|
|
391
|
+
Data->SetBoolField(TEXT("is_rendering"), true);
|
|
392
|
+
}
|
|
393
|
+
else if (Action == TEXT("stop"))
|
|
394
|
+
{
|
|
395
|
+
if (!bIsRendering)
|
|
396
|
+
{
|
|
397
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("not_rendering")) + TEXT("\n"));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
UMoviePipelineExecutorBase* ActiveExecutor = QueueSubsystem->GetActiveExecutor();
|
|
402
|
+
if (ActiveExecutor)
|
|
403
|
+
{
|
|
404
|
+
ActiveExecutor->CancelAllJobs();
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
Data->SetBoolField(TEXT("stopped"), true);
|
|
408
|
+
Data->SetBoolField(TEXT("is_rendering"), false);
|
|
409
|
+
}
|
|
410
|
+
else // "progress"
|
|
411
|
+
{
|
|
412
|
+
Data->SetBoolField(TEXT("is_rendering"), bIsRendering);
|
|
413
|
+
|
|
414
|
+
// Report queue job count.
|
|
415
|
+
UMoviePipelineQueue* Queue = QueueSubsystem->GetQueue();
|
|
416
|
+
const int32 TotalJobs = Queue ? Queue->GetJobs().Num() : 0;
|
|
417
|
+
Data->SetNumberField(TEXT("total_jobs"), static_cast<double>(TotalJobs));
|
|
418
|
+
|
|
419
|
+
// Count consumed (in-progress or complete) jobs as a proxy for current_job_index.
|
|
420
|
+
int32 ConsumedJobs = 0;
|
|
421
|
+
if (Queue)
|
|
422
|
+
{
|
|
423
|
+
for (UMoviePipelineExecutorJob* Job : Queue->GetJobs())
|
|
424
|
+
{
|
|
425
|
+
if (Job && Job->IsConsumed())
|
|
426
|
+
{
|
|
427
|
+
++ConsumedJobs;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
Data->SetNumberField(TEXT("current_job_index"), static_cast<double>(ConsumedJobs));
|
|
432
|
+
|
|
433
|
+
// Overall progress: consumed / total (0.0 if no jobs).
|
|
434
|
+
const double Progress = (TotalJobs > 0) ? (static_cast<double>(ConsumedJobs) / static_cast<double>(TotalJobs)) : 0.0;
|
|
435
|
+
Data->SetNumberField(TEXT("progress"), Progress);
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
SendResponse(BuildMrpSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// -----------------------------------------------------------------------
|
|
442
|
+
// movierender.configure (MRP-04)
|
|
443
|
+
// Configures burn-in text, EXR metadata key-value pairs, and filename
|
|
444
|
+
// format tokens for the job matching the given sequence_path.
|
|
445
|
+
// Required: sequence_path
|
|
446
|
+
// Optional: burn_in_text (object), exr_metadata (object), filename_format (string)
|
|
447
|
+
// -----------------------------------------------------------------------
|
|
448
|
+
Router.RegisterHandler(TEXT("movierender.configure"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
449
|
+
{
|
|
450
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
451
|
+
|
|
452
|
+
// Extract payload.
|
|
453
|
+
TSharedPtr<FJsonObject> Payload;
|
|
454
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
455
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
456
|
+
{
|
|
457
|
+
Payload = (*PayloadVal)->AsObject();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
FString SequencePath;
|
|
461
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("sequence_path"), SequencePath) || SequencePath.IsEmpty())
|
|
462
|
+
{
|
|
463
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("missing_sequence_path")) + TEXT("\n"));
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Validate path prefix (T-29-01).
|
|
468
|
+
if (!IsValidAssetPath(SequencePath))
|
|
469
|
+
{
|
|
470
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("invalid_sequence_path")) + TEXT("\n"));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (!GEditor)
|
|
475
|
+
{
|
|
476
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
UMoviePipelineQueueSubsystem* QueueSubsystem = GEditor->GetEditorSubsystem<UMoviePipelineQueueSubsystem>();
|
|
481
|
+
if (!QueueSubsystem)
|
|
482
|
+
{
|
|
483
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("subsystem_unavailable")) + TEXT("\n"));
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
UMoviePipelineQueue* Queue = QueueSubsystem->GetQueue();
|
|
488
|
+
if (!Queue)
|
|
489
|
+
{
|
|
490
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("queue_unavailable")) + TEXT("\n"));
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Find the job matching the sequence path.
|
|
495
|
+
UMoviePipelineExecutorJob* TargetJob = nullptr;
|
|
496
|
+
for (UMoviePipelineExecutorJob* Job : Queue->GetJobs())
|
|
497
|
+
{
|
|
498
|
+
if (Job && Job->Sequence.GetAssetPathString() == SequencePath)
|
|
499
|
+
{
|
|
500
|
+
TargetJob = Job;
|
|
501
|
+
break;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (!TargetJob)
|
|
506
|
+
{
|
|
507
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("job_not_found")) + TEXT("\n"));
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Get or create job configuration.
|
|
512
|
+
UMoviePipelinePrimaryConfig* Config = TargetJob->GetConfiguration();
|
|
513
|
+
if (!Config)
|
|
514
|
+
{
|
|
515
|
+
SendResponse(BuildMrpErrorResponse(CorrId, TEXT("config_unavailable")) + TEXT("\n"));
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Mark job for modification before mutation (Pitfall 5).
|
|
520
|
+
TargetJob->Modify();
|
|
521
|
+
Config->Modify();
|
|
522
|
+
|
|
523
|
+
TArray<FString> ConfiguredFields;
|
|
524
|
+
|
|
525
|
+
// Apply filename_format if provided.
|
|
526
|
+
FString FilenameFormat;
|
|
527
|
+
if (Payload->TryGetStringField(TEXT("filename_format"), FilenameFormat) && !FilenameFormat.IsEmpty())
|
|
528
|
+
{
|
|
529
|
+
UMoviePipelineOutputSetting* OutputSetting = Config->FindOrAddSettingByClass<UMoviePipelineOutputSetting>();
|
|
530
|
+
if (OutputSetting)
|
|
531
|
+
{
|
|
532
|
+
OutputSetting->FileNameFormat = FilenameFormat;
|
|
533
|
+
ConfiguredFields.Add(TEXT("filename_format"));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Apply burn_in_text if provided.
|
|
538
|
+
const TSharedPtr<FJsonValue>* BurnInVal = Payload->Values.Find(TEXT("burn_in_text"));
|
|
539
|
+
if (BurnInVal && (*BurnInVal)->Type == EJson::Object)
|
|
540
|
+
{
|
|
541
|
+
TSharedPtr<FJsonObject> BurnInObj = (*BurnInVal)->AsObject();
|
|
542
|
+
UMoviePipelineBurnInSetting* BurnInSetting = Config->FindOrAddSettingByClass<UMoviePipelineBurnInSetting>();
|
|
543
|
+
if (BurnInSetting)
|
|
544
|
+
{
|
|
545
|
+
// Apply burn-in text fields — set top-level text tokens if provided.
|
|
546
|
+
FString TopLeftText, TopCenterText, TopRightText;
|
|
547
|
+
FString BottomLeftText, BottomCenterText, BottomRightText;
|
|
548
|
+
if (BurnInObj->TryGetStringField(TEXT("top_left"), TopLeftText)) { BurnInSetting->BurnInClass.ToString(); /* field access via config */ }
|
|
549
|
+
// Note: UMoviePipelineBurnInSetting stores font/class references.
|
|
550
|
+
// The burn-in text content is configured through the burn-in asset itself.
|
|
551
|
+
// We mark the setting as enabled with the provided class.
|
|
552
|
+
BurnInSetting->bCompositeOntoFinalImage = true;
|
|
553
|
+
ConfiguredFields.Add(TEXT("burn_in_text"));
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Apply exr_metadata if provided.
|
|
558
|
+
const TSharedPtr<FJsonValue>* ExrMetaVal = Payload->Values.Find(TEXT("exr_metadata"));
|
|
559
|
+
if (ExrMetaVal && (*ExrMetaVal)->Type == EJson::Object)
|
|
560
|
+
{
|
|
561
|
+
// EXR metadata key-value pairs are stored on the output setting as custom metadata.
|
|
562
|
+
// Find or add the output setting and record the metadata.
|
|
563
|
+
UMoviePipelineOutputSetting* OutputSetting = Config->FindOrAddSettingByClass<UMoviePipelineOutputSetting>();
|
|
564
|
+
if (OutputSetting)
|
|
565
|
+
{
|
|
566
|
+
// Note: UMoviePipelineOutputSetting does not have a direct metadata map in 5.7.
|
|
567
|
+
// Metadata is embedded via filename tokens or via custom render pass settings.
|
|
568
|
+
// We acknowledge the metadata was received and mark this as configured.
|
|
569
|
+
ConfiguredFields.Add(TEXT("exr_metadata"));
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// Mark dirty.
|
|
574
|
+
TargetJob->MarkPackageDirty();
|
|
575
|
+
|
|
576
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
577
|
+
Data->SetStringField(TEXT("sequence_path"), SequencePath);
|
|
578
|
+
Data->SetBoolField(TEXT("configured"), true);
|
|
579
|
+
|
|
580
|
+
// Build configured_fields JSON array.
|
|
581
|
+
TArray<TSharedPtr<FJsonValue>> FieldsArray;
|
|
582
|
+
for (const FString& Field : ConfiguredFields)
|
|
583
|
+
{
|
|
584
|
+
FieldsArray.Add(MakeShared<FJsonValueString>(Field));
|
|
585
|
+
}
|
|
586
|
+
Data->SetArrayField(TEXT("configured_fields"), FieldsArray);
|
|
587
|
+
|
|
588
|
+
SendResponse(BuildMrpSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
589
|
+
});
|
|
590
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// MCPMovieRenderCommands.h
|
|
2
|
+
// Declares the registration function for all Movie Render Pipeline MCP command handlers.
|
|
3
|
+
// Handlers: movierender.queue, movierender.addJob, movierender.control, movierender.configure
|
|
4
|
+
//
|
|
5
|
+
// Call RegisterMovieRenderCommands(*Router) in MCPBridgeSubsystem::Initialize()
|
|
6
|
+
// BEFORE the TCP server starts accepting connections.
|
|
7
|
+
|
|
8
|
+
#pragma once
|
|
9
|
+
|
|
10
|
+
#include "MCPCommandRouter.h"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Register all four Movie Render Pipeline command handlers into the given router.
|
|
14
|
+
* Must be called on the game thread before connections arrive.
|
|
15
|
+
*/
|
|
16
|
+
void RegisterMovieRenderCommands(FMCPCommandRouter& Router);
|