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,511 @@
|
|
|
1
|
+
// MCPMaterialCommands.cpp
|
|
2
|
+
// Implements four material command handlers for the MCP bridge:
|
|
3
|
+
// material.params -- list scalar/vector/texture parameters of a UMaterialInterface (MAT-01)
|
|
4
|
+
// material.createInstance -- create a UMaterialInstanceConstant from a parent material (MAT-02)
|
|
5
|
+
// material.setParam -- apply a parameter override to a MIC (MAT-03)
|
|
6
|
+
// material.actorMaterials -- list material paths used by an actor or static mesh asset (MAT-04)
|
|
7
|
+
//
|
|
8
|
+
// All handlers run on the game thread (guaranteed by FMCPCommandRouter::Dispatch).
|
|
9
|
+
// Threat mitigations applied:
|
|
10
|
+
// T-15-01: param_type uses explicit allowlist; only "scalar"/"vector"/"texture" accepted.
|
|
11
|
+
// T-15-02: instance_path validated to start with "/Game/"; engine asset overwrite prevented.
|
|
12
|
+
// T-15-04: texture StaticLoadObject null-checked before SetTextureParameterValue.
|
|
13
|
+
|
|
14
|
+
#include "MCPMaterialCommands.h"
|
|
15
|
+
|
|
16
|
+
#include "Editor.h"
|
|
17
|
+
#include "Engine/World.h"
|
|
18
|
+
#include "GameFramework/Actor.h"
|
|
19
|
+
#include "EngineUtils.h"
|
|
20
|
+
#include "Materials/MaterialInterface.h"
|
|
21
|
+
#include "Materials/MaterialInstanceConstant.h"
|
|
22
|
+
#include "MaterialEditingLibrary.h"
|
|
23
|
+
#include "Components/PrimitiveComponent.h"
|
|
24
|
+
#include "Components/StaticMeshComponent.h"
|
|
25
|
+
#include "Engine/StaticMesh.h"
|
|
26
|
+
#include "AssetRegistry/AssetRegistryModule.h"
|
|
27
|
+
#include "Serialization/JsonSerializer.h"
|
|
28
|
+
#include "Serialization/JsonWriter.h"
|
|
29
|
+
#include "Dom/JsonObject.h"
|
|
30
|
+
#include "Dom/JsonValue.h"
|
|
31
|
+
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Internal helpers
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
|
|
36
|
+
/** Returns a JSON success response string (without trailing newline). */
|
|
37
|
+
static FString BuildMatSuccessResponse(const FString& CorrId, TSharedPtr<FJsonObject> Data)
|
|
38
|
+
{
|
|
39
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
40
|
+
Obj->SetBoolField(TEXT("success"), true);
|
|
41
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
42
|
+
if (Data.IsValid())
|
|
43
|
+
{
|
|
44
|
+
Obj->SetObjectField(TEXT("data"), Data);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
FString Output;
|
|
48
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
49
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
50
|
+
return Output;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Returns a JSON error response string (without trailing newline). */
|
|
54
|
+
static FString BuildMatErrorResponse(const FString& CorrId, const FString& Error)
|
|
55
|
+
{
|
|
56
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
57
|
+
Obj->SetBoolField(TEXT("success"), false);
|
|
58
|
+
Obj->SetStringField(TEXT("correlationId"), CorrId);
|
|
59
|
+
Obj->SetStringField(TEXT("error"), Error);
|
|
60
|
+
|
|
61
|
+
FString Output;
|
|
62
|
+
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Output);
|
|
63
|
+
FJsonSerializer::Serialize(Obj.ToSharedRef(), Writer);
|
|
64
|
+
return Output;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// RegisterMaterialCommands
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
void RegisterMaterialCommands(FMCPCommandRouter& Router)
|
|
72
|
+
{
|
|
73
|
+
// -----------------------------------------------------------------------
|
|
74
|
+
// material.params (MAT-01)
|
|
75
|
+
// Reads all scalar, vector, and texture parameters from a UMaterialInterface.
|
|
76
|
+
// Works on base materials and any instance type.
|
|
77
|
+
//
|
|
78
|
+
// Payload field:
|
|
79
|
+
// asset_path string (required) -- e.g., "/Game/Materials/M_Rock"
|
|
80
|
+
// -----------------------------------------------------------------------
|
|
81
|
+
Router.RegisterHandler(TEXT("material.params"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
82
|
+
{
|
|
83
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
84
|
+
|
|
85
|
+
// Extract payload.
|
|
86
|
+
TSharedPtr<FJsonObject> Payload;
|
|
87
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
88
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
89
|
+
{
|
|
90
|
+
Payload = (*PayloadVal)->AsObject();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Require asset_path.
|
|
94
|
+
FString AssetPath;
|
|
95
|
+
if (!Payload.IsValid() || !Payload->TryGetStringField(TEXT("asset_path"), AssetPath) || AssetPath.IsEmpty())
|
|
96
|
+
{
|
|
97
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("missing_asset_path")) + TEXT("\n"));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Load the material interface asset.
|
|
102
|
+
UMaterialInterface* MatIface = Cast<UMaterialInterface>(
|
|
103
|
+
StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *AssetPath));
|
|
104
|
+
if (!MatIface)
|
|
105
|
+
{
|
|
106
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("material_not_found")) + TEXT("\n"));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Collect parameter names by type.
|
|
111
|
+
TArray<FName> ScalarNames;
|
|
112
|
+
TArray<FName> VectorNames;
|
|
113
|
+
TArray<FName> TextureNames;
|
|
114
|
+
UMaterialEditingLibrary::GetScalarParameterNames(MatIface, ScalarNames);
|
|
115
|
+
UMaterialEditingLibrary::GetVectorParameterNames(MatIface, VectorNames);
|
|
116
|
+
UMaterialEditingLibrary::GetTextureParameterNames(MatIface, TextureNames);
|
|
117
|
+
|
|
118
|
+
// Build the parameters JSON array.
|
|
119
|
+
TArray<TSharedPtr<FJsonValue>> ParamsArray;
|
|
120
|
+
|
|
121
|
+
// Scalar parameters.
|
|
122
|
+
for (const FName& Name : ScalarNames)
|
|
123
|
+
{
|
|
124
|
+
float OutFloat = 0.0f;
|
|
125
|
+
UMaterialEditingLibrary::GetScalarParameterValue(MatIface, Name, OutFloat);
|
|
126
|
+
|
|
127
|
+
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
128
|
+
Entry->SetStringField(TEXT("name"), Name.ToString());
|
|
129
|
+
Entry->SetStringField(TEXT("type"), TEXT("scalar"));
|
|
130
|
+
Entry->SetNumberField(TEXT("value"), static_cast<double>(OutFloat));
|
|
131
|
+
Entry->SetNumberField(TEXT("default_value"), static_cast<double>(OutFloat));
|
|
132
|
+
ParamsArray.Add(MakeShared<FJsonValueObject>(Entry));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Vector parameters.
|
|
136
|
+
for (const FName& Name : VectorNames)
|
|
137
|
+
{
|
|
138
|
+
FLinearColor OutColor(ForceInitToZero);
|
|
139
|
+
UMaterialEditingLibrary::GetVectorParameterValue(MatIface, Name, OutColor);
|
|
140
|
+
|
|
141
|
+
TSharedPtr<FJsonObject> ColorObj = MakeShared<FJsonObject>();
|
|
142
|
+
ColorObj->SetNumberField(TEXT("r"), static_cast<double>(OutColor.R));
|
|
143
|
+
ColorObj->SetNumberField(TEXT("g"), static_cast<double>(OutColor.G));
|
|
144
|
+
ColorObj->SetNumberField(TEXT("b"), static_cast<double>(OutColor.B));
|
|
145
|
+
ColorObj->SetNumberField(TEXT("a"), static_cast<double>(OutColor.A));
|
|
146
|
+
|
|
147
|
+
// Clone for default_value (same effective value for both).
|
|
148
|
+
TSharedPtr<FJsonObject> DefaultColorObj = MakeShared<FJsonObject>();
|
|
149
|
+
DefaultColorObj->SetNumberField(TEXT("r"), static_cast<double>(OutColor.R));
|
|
150
|
+
DefaultColorObj->SetNumberField(TEXT("g"), static_cast<double>(OutColor.G));
|
|
151
|
+
DefaultColorObj->SetNumberField(TEXT("b"), static_cast<double>(OutColor.B));
|
|
152
|
+
DefaultColorObj->SetNumberField(TEXT("a"), static_cast<double>(OutColor.A));
|
|
153
|
+
|
|
154
|
+
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
155
|
+
Entry->SetStringField(TEXT("name"), Name.ToString());
|
|
156
|
+
Entry->SetStringField(TEXT("type"), TEXT("vector"));
|
|
157
|
+
Entry->SetObjectField(TEXT("value"), ColorObj);
|
|
158
|
+
Entry->SetObjectField(TEXT("default_value"), DefaultColorObj);
|
|
159
|
+
ParamsArray.Add(MakeShared<FJsonValueObject>(Entry));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Texture parameters.
|
|
163
|
+
for (const FName& Name : TextureNames)
|
|
164
|
+
{
|
|
165
|
+
UTexture* OutTexture = nullptr;
|
|
166
|
+
UMaterialEditingLibrary::GetTextureParameterValue(MatIface, Name, OutTexture);
|
|
167
|
+
|
|
168
|
+
const FString TexPath = OutTexture ? OutTexture->GetPathName() : FString(TEXT(""));
|
|
169
|
+
|
|
170
|
+
TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
|
|
171
|
+
Entry->SetStringField(TEXT("name"), Name.ToString());
|
|
172
|
+
Entry->SetStringField(TEXT("type"), TEXT("texture"));
|
|
173
|
+
Entry->SetStringField(TEXT("value"), TexPath);
|
|
174
|
+
Entry->SetStringField(TEXT("default_value"), TexPath);
|
|
175
|
+
ParamsArray.Add(MakeShared<FJsonValueObject>(Entry));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Build response data.
|
|
179
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
180
|
+
Data->SetStringField(TEXT("asset_path"), AssetPath);
|
|
181
|
+
Data->SetArrayField(TEXT("parameters"), ParamsArray);
|
|
182
|
+
|
|
183
|
+
SendResponse(BuildMatSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// -----------------------------------------------------------------------
|
|
187
|
+
// material.createInstance (MAT-02)
|
|
188
|
+
// Creates a new Material Instance Constant asset from a parent material.
|
|
189
|
+
// Persistent asset (saved to disk), not a runtime dynamic instance.
|
|
190
|
+
//
|
|
191
|
+
// Payload fields:
|
|
192
|
+
// parent_path string (required) -- asset path of parent UMaterialInterface
|
|
193
|
+
// instance_path string (required) -- package directory for the new instance (must start with /Game/)
|
|
194
|
+
// instance_name string (required) -- asset name portion, e.g. "MI_Rock_Red"
|
|
195
|
+
//
|
|
196
|
+
// Threat T-15-02: instance_path validated to start with "/Game/".
|
|
197
|
+
// -----------------------------------------------------------------------
|
|
198
|
+
Router.RegisterHandler(TEXT("material.createInstance"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
199
|
+
{
|
|
200
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
201
|
+
|
|
202
|
+
// Extract payload.
|
|
203
|
+
TSharedPtr<FJsonObject> Payload;
|
|
204
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
205
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
206
|
+
{
|
|
207
|
+
Payload = (*PayloadVal)->AsObject();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Require all three fields.
|
|
211
|
+
FString ParentPath;
|
|
212
|
+
FString InstancePath;
|
|
213
|
+
FString InstanceName;
|
|
214
|
+
|
|
215
|
+
const bool bHasParent = Payload.IsValid() && Payload->TryGetStringField(TEXT("parent_path"), ParentPath) && !ParentPath.IsEmpty();
|
|
216
|
+
const bool bHasInstPath = Payload.IsValid() && Payload->TryGetStringField(TEXT("instance_path"), InstancePath) && !InstancePath.IsEmpty();
|
|
217
|
+
const bool bHasInstName = Payload.IsValid() && Payload->TryGetStringField(TEXT("instance_name"), InstanceName) && !InstanceName.IsEmpty();
|
|
218
|
+
|
|
219
|
+
if (!bHasParent || !bHasInstPath || !bHasInstName)
|
|
220
|
+
{
|
|
221
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("missing_required_fields")) + TEXT("\n"));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Threat T-15-02: Validate instance_path starts with "/Game/" to prevent overwriting engine assets.
|
|
226
|
+
if (!InstancePath.StartsWith(TEXT("/Game/")))
|
|
227
|
+
{
|
|
228
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("invalid_instance_path: must start with /Game/")) + TEXT("\n"));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Load parent material.
|
|
233
|
+
UMaterialInterface* Parent = Cast<UMaterialInterface>(
|
|
234
|
+
StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *ParentPath));
|
|
235
|
+
if (!Parent)
|
|
236
|
+
{
|
|
237
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("parent_material_not_found")) + TEXT("\n"));
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Create the material instance asset.
|
|
242
|
+
// UMaterialEditingLibrary::CreateMaterialInstanceAsset(ParentMaterial, Name, PackagePath)
|
|
243
|
+
// PackagePath is the directory (e.g., "/Game/Materials"), Name is the asset name.
|
|
244
|
+
UMaterialInstanceConstant* NewInst = Cast<UMaterialInstanceConstant>(
|
|
245
|
+
UMaterialEditingLibrary::CreateMaterialInstanceAsset(Parent, InstanceName, InstancePath));
|
|
246
|
+
|
|
247
|
+
if (!NewInst)
|
|
248
|
+
{
|
|
249
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("create_instance_failed")) + TEXT("\n"));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
254
|
+
Data->SetStringField(TEXT("instance_path"), NewInst->GetPathName());
|
|
255
|
+
Data->SetStringField(TEXT("parent_path"), ParentPath);
|
|
256
|
+
Data->SetBoolField(TEXT("success"), true);
|
|
257
|
+
|
|
258
|
+
SendResponse(BuildMatSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// -----------------------------------------------------------------------
|
|
262
|
+
// material.setParam (MAT-03)
|
|
263
|
+
// Sets a single parameter override (scalar, vector, or texture) on a
|
|
264
|
+
// Material Instance Constant. Calls Modify() before any change so UE undo
|
|
265
|
+
// history is preserved, and PostEditChange() after to notify the editor.
|
|
266
|
+
//
|
|
267
|
+
// Payload fields:
|
|
268
|
+
// asset_path string (required) -- path to UMaterialInstanceConstant
|
|
269
|
+
// param_name string (required) -- parameter name
|
|
270
|
+
// param_type string (required) -- "scalar", "vector", or "texture"
|
|
271
|
+
// value varies -- float for scalar; {r,g,b,a} for vector; string path for texture
|
|
272
|
+
//
|
|
273
|
+
// Threat T-15-01: param_type uses explicit allowlist.
|
|
274
|
+
// Threat T-15-04: texture null-checked before SetTextureParameterValue.
|
|
275
|
+
// -----------------------------------------------------------------------
|
|
276
|
+
Router.RegisterHandler(TEXT("material.setParam"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
277
|
+
{
|
|
278
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
279
|
+
|
|
280
|
+
// Extract payload.
|
|
281
|
+
TSharedPtr<FJsonObject> Payload;
|
|
282
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
283
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
284
|
+
{
|
|
285
|
+
Payload = (*PayloadVal)->AsObject();
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Require asset_path, param_name, param_type.
|
|
289
|
+
FString AssetPath;
|
|
290
|
+
FString ParamName;
|
|
291
|
+
FString ParamType;
|
|
292
|
+
|
|
293
|
+
const bool bHasAsset = Payload.IsValid() && Payload->TryGetStringField(TEXT("asset_path"), AssetPath) && !AssetPath.IsEmpty();
|
|
294
|
+
const bool bHasParamName = Payload.IsValid() && Payload->TryGetStringField(TEXT("param_name"), ParamName) && !ParamName.IsEmpty();
|
|
295
|
+
const bool bHasParamType = Payload.IsValid() && Payload->TryGetStringField(TEXT("param_type"), ParamType) && !ParamType.IsEmpty();
|
|
296
|
+
|
|
297
|
+
if (!bHasAsset || !bHasParamName || !bHasParamType)
|
|
298
|
+
{
|
|
299
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("missing_required_fields")) + TEXT("\n"));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Threat T-15-01: Explicit allowlist for param_type.
|
|
304
|
+
const bool bIsScalar = (ParamType == TEXT("scalar"));
|
|
305
|
+
const bool bIsVector = (ParamType == TEXT("vector"));
|
|
306
|
+
const bool bIsTexture = (ParamType == TEXT("texture"));
|
|
307
|
+
|
|
308
|
+
if (!bIsScalar && !bIsVector && !bIsTexture)
|
|
309
|
+
{
|
|
310
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("invalid_param_type")) + TEXT("\n"));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Load the material instance constant.
|
|
315
|
+
UMaterialInstanceConstant* MIC = Cast<UMaterialInstanceConstant>(
|
|
316
|
+
StaticLoadObject(UMaterialInstanceConstant::StaticClass(), nullptr, *AssetPath));
|
|
317
|
+
if (!MIC)
|
|
318
|
+
{
|
|
319
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("material_instance_not_found")) + TEXT("\n"));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Modify() before any state change -- preserves UE undo history (non-negotiable).
|
|
324
|
+
MIC->Modify();
|
|
325
|
+
|
|
326
|
+
if (bIsScalar)
|
|
327
|
+
{
|
|
328
|
+
double Val = 0.0;
|
|
329
|
+
if (Payload->TryGetNumberField(TEXT("value"), Val))
|
|
330
|
+
{
|
|
331
|
+
UMaterialEditingLibrary::SetScalarParameterValue(MIC, FName(*ParamName), static_cast<float>(Val));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else if (bIsVector)
|
|
335
|
+
{
|
|
336
|
+
const TSharedPtr<FJsonObject>* ValObj;
|
|
337
|
+
if (Payload->TryGetObjectField(TEXT("value"), ValObj))
|
|
338
|
+
{
|
|
339
|
+
double R = 0.0, G = 0.0, B = 0.0, A = 1.0;
|
|
340
|
+
(*ValObj)->TryGetNumberField(TEXT("r"), R);
|
|
341
|
+
(*ValObj)->TryGetNumberField(TEXT("g"), G);
|
|
342
|
+
(*ValObj)->TryGetNumberField(TEXT("b"), B);
|
|
343
|
+
(*ValObj)->TryGetNumberField(TEXT("a"), A);
|
|
344
|
+
UMaterialEditingLibrary::SetVectorParameterValue(
|
|
345
|
+
MIC, FName(*ParamName), FLinearColor(
|
|
346
|
+
static_cast<float>(R),
|
|
347
|
+
static_cast<float>(G),
|
|
348
|
+
static_cast<float>(B),
|
|
349
|
+
static_cast<float>(A)));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else // bIsTexture
|
|
353
|
+
{
|
|
354
|
+
FString TexPath;
|
|
355
|
+
if (Payload->TryGetStringField(TEXT("value"), TexPath) && !TexPath.IsEmpty())
|
|
356
|
+
{
|
|
357
|
+
// Threat T-15-04: null-check before calling SetTextureParameterValue.
|
|
358
|
+
UTexture* Tex = Cast<UTexture>(
|
|
359
|
+
StaticLoadObject(UTexture::StaticClass(), nullptr, *TexPath));
|
|
360
|
+
if (!Tex)
|
|
361
|
+
{
|
|
362
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("texture_not_found")) + TEXT("\n"));
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
UMaterialEditingLibrary::SetTextureParameterValue(MIC, FName(*ParamName), Tex);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// Notify the editor of the change.
|
|
370
|
+
MIC->PostEditChange();
|
|
371
|
+
|
|
372
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
373
|
+
Data->SetStringField(TEXT("asset_path"), AssetPath);
|
|
374
|
+
Data->SetStringField(TEXT("param_name"), ParamName);
|
|
375
|
+
Data->SetStringField(TEXT("param_type"), ParamType);
|
|
376
|
+
Data->SetBoolField(TEXT("applied"), true);
|
|
377
|
+
|
|
378
|
+
SendResponse(BuildMatSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
// -----------------------------------------------------------------------
|
|
382
|
+
// material.actorMaterials (MAT-04)
|
|
383
|
+
// Lists all material asset paths used by an actor (via primitive components)
|
|
384
|
+
// or a static mesh asset. Returns unique paths with a count.
|
|
385
|
+
//
|
|
386
|
+
// Payload fields (at least one required):
|
|
387
|
+
// actor_label string (optional) -- name of actor in the open level
|
|
388
|
+
// asset_path string (optional) -- path to a UStaticMesh asset
|
|
389
|
+
// -----------------------------------------------------------------------
|
|
390
|
+
Router.RegisterHandler(TEXT("material.actorMaterials"), [](TSharedPtr<FJsonObject> Cmd, FMCPResponseSender SendResponse)
|
|
391
|
+
{
|
|
392
|
+
const FString CorrId = Cmd->GetStringField(TEXT("correlationId"));
|
|
393
|
+
|
|
394
|
+
// Extract payload.
|
|
395
|
+
TSharedPtr<FJsonObject> Payload;
|
|
396
|
+
const TSharedPtr<FJsonValue>* PayloadVal = Cmd->Values.Find(TEXT("payload"));
|
|
397
|
+
if (PayloadVal && (*PayloadVal)->Type == EJson::Object)
|
|
398
|
+
{
|
|
399
|
+
Payload = (*PayloadVal)->AsObject();
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
FString ActorLabel;
|
|
403
|
+
FString AssetPathStr;
|
|
404
|
+
|
|
405
|
+
const bool bHasActorLabel = Payload.IsValid() && Payload->TryGetStringField(TEXT("actor_label"), ActorLabel) && !ActorLabel.IsEmpty();
|
|
406
|
+
const bool bHasAssetPath = Payload.IsValid() && Payload->TryGetStringField(TEXT("asset_path"), AssetPathStr) && !AssetPathStr.IsEmpty();
|
|
407
|
+
|
|
408
|
+
if (!bHasActorLabel && !bHasAssetPath)
|
|
409
|
+
{
|
|
410
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("missing_actor_label_or_asset_path")) + TEXT("\n"));
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
TArray<FString> MaterialPaths;
|
|
415
|
+
|
|
416
|
+
if (bHasActorLabel)
|
|
417
|
+
{
|
|
418
|
+
// Actor path: find actor by label in the open world.
|
|
419
|
+
if (!GEditor)
|
|
420
|
+
{
|
|
421
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
|
|
422
|
+
return;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
UWorld* World = GEditor->GetEditorWorldContext().World();
|
|
426
|
+
if (!World)
|
|
427
|
+
{
|
|
428
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("no_world_open")) + TEXT("\n"));
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
AActor* FoundActor = nullptr;
|
|
433
|
+
for (TActorIterator<AActor> It(World); It; ++It)
|
|
434
|
+
{
|
|
435
|
+
if (It->GetActorLabel() == ActorLabel)
|
|
436
|
+
{
|
|
437
|
+
FoundActor = *It;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (!FoundActor)
|
|
443
|
+
{
|
|
444
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("actor_not_found")) + TEXT("\n"));
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Collect materials from all primitive components.
|
|
449
|
+
TArray<UPrimitiveComponent*> Components;
|
|
450
|
+
FoundActor->GetComponents<UPrimitiveComponent>(Components);
|
|
451
|
+
|
|
452
|
+
TSet<FString> Unique;
|
|
453
|
+
for (UPrimitiveComponent* Comp : Components)
|
|
454
|
+
{
|
|
455
|
+
if (!Comp)
|
|
456
|
+
{
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
TArray<UMaterialInterface*> Mats = Comp->GetMaterials();
|
|
460
|
+
for (UMaterialInterface* Mat : Mats)
|
|
461
|
+
{
|
|
462
|
+
if (Mat)
|
|
463
|
+
{
|
|
464
|
+
Unique.Add(Mat->GetPathName());
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
MaterialPaths = Unique.Array();
|
|
470
|
+
}
|
|
471
|
+
else
|
|
472
|
+
{
|
|
473
|
+
// Static mesh asset path.
|
|
474
|
+
UStaticMesh* Mesh = Cast<UStaticMesh>(
|
|
475
|
+
StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *AssetPathStr));
|
|
476
|
+
if (!Mesh)
|
|
477
|
+
{
|
|
478
|
+
SendResponse(BuildMatErrorResponse(CorrId, TEXT("static_mesh_not_found")) + TEXT("\n"));
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const TArray<FStaticMaterial>& StaticMats = Mesh->GetStaticMaterials();
|
|
483
|
+
TSet<FString> Unique;
|
|
484
|
+
for (const FStaticMaterial& Entry : StaticMats)
|
|
485
|
+
{
|
|
486
|
+
if (Entry.MaterialInterface)
|
|
487
|
+
{
|
|
488
|
+
Unique.Add(Entry.MaterialInterface->GetPathName());
|
|
489
|
+
}
|
|
490
|
+
else
|
|
491
|
+
{
|
|
492
|
+
Unique.Add(FString(TEXT("")));
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
MaterialPaths = Unique.Array();
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Build response data.
|
|
499
|
+
TArray<TSharedPtr<FJsonValue>> MatArray;
|
|
500
|
+
for (const FString& Path : MaterialPaths)
|
|
501
|
+
{
|
|
502
|
+
MatArray.Add(MakeShared<FJsonValueString>(Path));
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
TSharedPtr<FJsonObject> Data = MakeShared<FJsonObject>();
|
|
506
|
+
Data->SetArrayField(TEXT("materials"), MatArray);
|
|
507
|
+
Data->SetNumberField(TEXT("count"), static_cast<double>(MaterialPaths.Num()));
|
|
508
|
+
|
|
509
|
+
SendResponse(BuildMatSuccessResponse(CorrId, Data) + TEXT("\n"));
|
|
510
|
+
});
|
|
511
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// MCPMaterialCommands.h
|
|
2
|
+
// Declares handler registration for material inspection and modification commands.
|
|
3
|
+
// Handles: material.params, material.createInstance, material.setParam, material.actorMaterials
|
|
4
|
+
//
|
|
5
|
+
// Call RegisterMaterialCommands(*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 material command handlers into the given router.
|
|
14
|
+
* Must be called on the game thread before connections arrive.
|
|
15
|
+
*
|
|
16
|
+
* Registered commands:
|
|
17
|
+
* material.params -- list scalar/vector/texture parameters of a UMaterialInterface (MAT-01)
|
|
18
|
+
* material.createInstance -- create a UMaterialInstanceConstant asset from a parent material (MAT-02)
|
|
19
|
+
* material.setParam -- apply a scalar, vector, or texture override to a MIC (MAT-03)
|
|
20
|
+
* material.actorMaterials -- list all material asset paths used by an actor or static mesh (MAT-04)
|
|
21
|
+
*/
|
|
22
|
+
void RegisterMaterialCommands(FMCPCommandRouter& Router);
|