ue-mcp 1.2.1 → 1.2.2
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/dist/context-cli.js +16 -16
- package/dist/engine-observer.js +68 -68
- package/dist/flow/guard.d.ts +26 -37
- package/dist/flow/guard.js +40 -25
- package/dist/flow/guard.js.map +1 -1
- package/dist/flow/guarded-bridge.d.ts +0 -10
- package/dist/flow/guarded-bridge.js +18 -23
- package/dist/flow/guarded-bridge.js.map +1 -1
- package/dist/flow/schema.d.ts +276 -16
- package/dist/flow/task-guards.d.ts +1 -1
- package/dist/flow/task-guards.js +44 -68
- package/dist/flow/task-guards.js.map +1 -1
- package/dist/instructions.js +130 -130
- package/dist/plugin-cli.js +299 -299
- package/dist/tool-counts.json +2 -2
- package/package.json +2 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +76 -76
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.h +27 -27
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +128 -128
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +63 -63
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +230 -230
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Struct.cpp +323 -323
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +20 -20
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +537 -537
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +77 -77
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +353 -353
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +18 -18
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_InputRuntime.cpp +246 -246
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +597 -597
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +51 -51
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +878 -878
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +449 -449
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +275 -275
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +551 -551
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +531 -531
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +28 -28
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +677 -677
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +822 -822
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +22 -22
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +218 -218
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +17 -17
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.cpp +46 -46
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/VolumeHelpers_Internal.h +23 -23
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/MCPExternalRegistry.cpp +83 -83
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_BridgeStatus/Private/UE_MCP_BridgeStatusModule.cpp +37 -37
- package/dist/manifest-signature.d.ts +0 -1
- package/dist/manifest-signature.js +0 -53
- package/dist/manifest-signature.js.map +0 -1
- package/dist/pie/schema.d.ts +0 -791
- package/dist/pie/schema.js +0 -151
- package/dist/pie/schema.js.map +0 -1
- package/dist/watch-cli.js.map +0 -1
- /package/plugin/ue_mcp_bridge/{ue_mcp_bridge.uplugin → UE_MCP_Bridge.uplugin} +0 -0
package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp
CHANGED
|
@@ -1,275 +1,275 @@
|
|
|
1
|
-
// #463: MaterialFunction creation + per-function expression authoring.
|
|
2
|
-
// The material tool surface previously only covered UMaterial asset graphs.
|
|
3
|
-
// MaterialFunction assets had no native create path or per-function
|
|
4
|
-
// expression APIs, forcing execute_python for what is a normal authoring
|
|
5
|
-
// workflow (color packs, reusable shading functions, math helpers).
|
|
6
|
-
//
|
|
7
|
-
// Split into its own TU so the existing MaterialHandlers.cpp doesn't grow.
|
|
8
|
-
// All functions are still members of FMaterialHandlers - registration
|
|
9
|
-
// happens in MaterialHandlers.cpp::RegisterHandlers.
|
|
10
|
-
|
|
11
|
-
#include "MaterialHandlers.h"
|
|
12
|
-
#include "HandlerRegistry.h"
|
|
13
|
-
#include "HandlerUtils.h"
|
|
14
|
-
#include "HandlerAssetCreate.h"
|
|
15
|
-
#include "AssetToolsModule.h"
|
|
16
|
-
#include "IAssetTools.h"
|
|
17
|
-
#include "Factories/MaterialFunctionFactoryNew.h"
|
|
18
|
-
#include "Materials/MaterialFunction.h"
|
|
19
|
-
#include "Materials/MaterialExpression.h"
|
|
20
|
-
#include "Materials/MaterialExpressionFunctionInput.h"
|
|
21
|
-
#include "Materials/MaterialExpressionFunctionOutput.h"
|
|
22
|
-
#include "MaterialEditingLibrary.h"
|
|
23
|
-
#include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
|
|
24
|
-
#include "UObject/UObjectIterator.h"
|
|
25
|
-
|
|
26
|
-
namespace
|
|
27
|
-
{
|
|
28
|
-
UMaterialFunction* LoadMaterialFunction(const FString& Path)
|
|
29
|
-
{
|
|
30
|
-
UMaterialFunction* MF = LoadObject<UMaterialFunction>(nullptr, *Path);
|
|
31
|
-
if (!MF)
|
|
32
|
-
{
|
|
33
|
-
MF = Cast<UMaterialFunction>(UEditorAssetLibrary::LoadAsset(Path));
|
|
34
|
-
}
|
|
35
|
-
return MF;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
UClass* ResolveExpressionClass(const FString& InType)
|
|
39
|
-
{
|
|
40
|
-
FString ClassName = InType;
|
|
41
|
-
if (!ClassName.StartsWith(TEXT("MaterialExpression")) && !ClassName.StartsWith(TEXT("UMaterialExpression")))
|
|
42
|
-
{
|
|
43
|
-
ClassName = TEXT("UMaterialExpression") + ClassName;
|
|
44
|
-
}
|
|
45
|
-
else if (!ClassName.StartsWith(TEXT("U")))
|
|
46
|
-
{
|
|
47
|
-
ClassName = TEXT("U") + ClassName;
|
|
48
|
-
}
|
|
49
|
-
UClass* Result = FindFirstObject<UClass>(*ClassName, EFindFirstObjectOptions::ExactClass);
|
|
50
|
-
if (!Result)
|
|
51
|
-
{
|
|
52
|
-
Result = FindFirstObject<UClass>(*InType, EFindFirstObjectOptions::ExactClass);
|
|
53
|
-
}
|
|
54
|
-
if (!Result || !Result->IsChildOf(UMaterialExpression::StaticClass())) return nullptr;
|
|
55
|
-
return Result;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// material(action="create_function", name, packagePath?, description?, onConflict?)
|
|
60
|
-
TSharedPtr<FJsonValue> FMaterialHandlers::CreateMaterialFunction(const TSharedPtr<FJsonObject>& Params)
|
|
61
|
-
{
|
|
62
|
-
FString Name;
|
|
63
|
-
if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
|
|
64
|
-
FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Materials/Functions"));
|
|
65
|
-
const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
|
|
66
|
-
|
|
67
|
-
UMaterialFunctionFactoryNew* Factory = NewObject<UMaterialFunctionFactoryNew>();
|
|
68
|
-
auto Created = MCPCreateAssetIdempotent<UMaterialFunction>(Name, PackagePath, OnConflict, TEXT("MaterialFunction"), Factory);
|
|
69
|
-
if (Created.EarlyReturn) return Created.EarlyReturn;
|
|
70
|
-
|
|
71
|
-
UMaterialFunction* MF = Created.Asset;
|
|
72
|
-
FString Description;
|
|
73
|
-
if (Params->TryGetStringField(TEXT("description"), Description))
|
|
74
|
-
{
|
|
75
|
-
MF->Description = Description;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
UEditorAssetLibrary::SaveAsset(MF->GetPathName());
|
|
79
|
-
|
|
80
|
-
auto Result = MCPSuccess();
|
|
81
|
-
MCPSetCreated(Result);
|
|
82
|
-
Result->SetStringField(TEXT("path"), MF->GetPathName());
|
|
83
|
-
Result->SetStringField(TEXT("name"), Name);
|
|
84
|
-
Result->SetStringField(TEXT("packagePath"), PackagePath);
|
|
85
|
-
MCPSetDeleteAssetRollback(Result, MF->GetPathName());
|
|
86
|
-
return MCPResult(Result);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// material(action="add_expression_in_function", functionPath, expressionType,
|
|
90
|
-
// positionX?, positionY?, inputName?, outputName?)
|
|
91
|
-
TSharedPtr<FJsonValue> FMaterialHandlers::AddMaterialFunctionExpression(const TSharedPtr<FJsonObject>& Params)
|
|
92
|
-
{
|
|
93
|
-
FString FunctionPath;
|
|
94
|
-
if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
|
|
95
|
-
|
|
96
|
-
FString ExpressionType;
|
|
97
|
-
if (auto Err = RequireString(Params, TEXT("expressionType"), ExpressionType)) return Err;
|
|
98
|
-
|
|
99
|
-
UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
|
|
100
|
-
if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
|
|
101
|
-
|
|
102
|
-
UClass* ExprClass = ResolveExpressionClass(ExpressionType);
|
|
103
|
-
if (!ExprClass) return MCPError(FString::Printf(TEXT("Unknown expression type: %s"), *ExpressionType));
|
|
104
|
-
|
|
105
|
-
int32 PosX = (int32)OptionalNumber(Params, TEXT("positionX"), 0.0);
|
|
106
|
-
int32 PosY = (int32)OptionalNumber(Params, TEXT("positionY"), 0.0);
|
|
107
|
-
|
|
108
|
-
UMaterialExpression* NewExpr = UMaterialEditingLibrary::CreateMaterialExpressionInFunction(MF, ExprClass, PosX, PosY);
|
|
109
|
-
if (!NewExpr) return MCPError(TEXT("CreateMaterialExpressionInFunction returned null"));
|
|
110
|
-
|
|
111
|
-
// Input/Output expressions: name them so callers can reference them by name.
|
|
112
|
-
if (UMaterialExpressionFunctionInput* AsInput = Cast<UMaterialExpressionFunctionInput>(NewExpr))
|
|
113
|
-
{
|
|
114
|
-
FString InputName;
|
|
115
|
-
if (Params->TryGetStringField(TEXT("inputName"), InputName) || Params->TryGetStringField(TEXT("name"), InputName))
|
|
116
|
-
{
|
|
117
|
-
AsInput->InputName = FName(*InputName);
|
|
118
|
-
}
|
|
119
|
-
FString InputTypeStr;
|
|
120
|
-
if (Params->TryGetStringField(TEXT("inputType"), InputTypeStr))
|
|
121
|
-
{
|
|
122
|
-
static const TMap<FString, EFunctionInputType> Map = {
|
|
123
|
-
{TEXT("Scalar"), FunctionInput_Scalar},
|
|
124
|
-
{TEXT("Vector2"), FunctionInput_Vector2},
|
|
125
|
-
{TEXT("Vector3"), FunctionInput_Vector3},
|
|
126
|
-
{TEXT("Vector4"), FunctionInput_Vector4},
|
|
127
|
-
{TEXT("Texture2D"), FunctionInput_Texture2D},
|
|
128
|
-
{TEXT("TextureCube"), FunctionInput_TextureCube},
|
|
129
|
-
{TEXT("StaticBool"), FunctionInput_StaticBool},
|
|
130
|
-
{TEXT("MaterialAttributes"), FunctionInput_MaterialAttributes},
|
|
131
|
-
};
|
|
132
|
-
if (const EFunctionInputType* Found = Map.Find(InputTypeStr))
|
|
133
|
-
{
|
|
134
|
-
AsInput->InputType = *Found;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (UMaterialExpressionFunctionOutput* AsOutput = Cast<UMaterialExpressionFunctionOutput>(NewExpr))
|
|
139
|
-
{
|
|
140
|
-
FString OutputName;
|
|
141
|
-
if (Params->TryGetStringField(TEXT("outputName"), OutputName) || Params->TryGetStringField(TEXT("name"), OutputName))
|
|
142
|
-
{
|
|
143
|
-
AsOutput->OutputName = FName(*OutputName);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
|
|
148
|
-
UEditorAssetLibrary::SaveAsset(MF->GetPathName());
|
|
149
|
-
|
|
150
|
-
int32 Index = MF->GetExpressions().IndexOfByKey(NewExpr);
|
|
151
|
-
|
|
152
|
-
auto Result = MCPSuccess();
|
|
153
|
-
MCPSetCreated(Result);
|
|
154
|
-
Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
|
|
155
|
-
Result->SetStringField(TEXT("expressionClass"), NewExpr->GetClass()->GetName());
|
|
156
|
-
Result->SetNumberField(TEXT("expressionIndex"), Index);
|
|
157
|
-
Result->SetStringField(TEXT("nodeId"), FString::FromInt(Index));
|
|
158
|
-
return MCPResult(Result);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// material(action="connect_expressions_in_function", functionPath,
|
|
162
|
-
// sourceExpression (name or index), sourceOutput?,
|
|
163
|
-
// targetExpression (name or index), targetInput?)
|
|
164
|
-
TSharedPtr<FJsonValue> FMaterialHandlers::ConnectMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
|
|
165
|
-
{
|
|
166
|
-
FString FunctionPath;
|
|
167
|
-
if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
|
|
168
|
-
|
|
169
|
-
UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
|
|
170
|
-
if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
|
|
171
|
-
|
|
172
|
-
auto ResolveExpr = [&](const TCHAR* Key) -> UMaterialExpression*
|
|
173
|
-
{
|
|
174
|
-
// Numeric index?
|
|
175
|
-
int32 Idx = -1;
|
|
176
|
-
if (Params->TryGetNumberField(Key, Idx))
|
|
177
|
-
{
|
|
178
|
-
if (Idx >= 0 && Idx < MF->GetExpressions().Num()) return MF->GetExpressions()[Idx];
|
|
179
|
-
return nullptr;
|
|
180
|
-
}
|
|
181
|
-
FString Str;
|
|
182
|
-
if (Params->TryGetStringField(Key, Str))
|
|
183
|
-
{
|
|
184
|
-
// FunctionInput/Output exposes InputName/OutputName; everything else uses Desc.
|
|
185
|
-
for (UMaterialExpression* Expr : MF->GetExpressions())
|
|
186
|
-
{
|
|
187
|
-
if (!Expr) continue;
|
|
188
|
-
if (Expr->Desc == Str) return Expr;
|
|
189
|
-
if (Expr->GetName() == Str) return Expr;
|
|
190
|
-
if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
|
|
191
|
-
{
|
|
192
|
-
if (In->InputName.ToString() == Str) return Expr;
|
|
193
|
-
}
|
|
194
|
-
if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
|
|
195
|
-
{
|
|
196
|
-
if (Out->OutputName.ToString() == Str) return Expr;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
// Numeric in string form
|
|
200
|
-
int32 ParsedIdx = FCString::Atoi(*Str);
|
|
201
|
-
if (ParsedIdx >= 0 && ParsedIdx < MF->GetExpressions().Num() && Str.IsNumeric())
|
|
202
|
-
{
|
|
203
|
-
return MF->GetExpressions()[ParsedIdx];
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
return nullptr;
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
UMaterialExpression* From = ResolveExpr(TEXT("sourceExpression"));
|
|
210
|
-
UMaterialExpression* To = ResolveExpr(TEXT("targetExpression"));
|
|
211
|
-
if (!From) return MCPError(TEXT("sourceExpression not found in function"));
|
|
212
|
-
if (!To) return MCPError(TEXT("targetExpression not found in function"));
|
|
213
|
-
|
|
214
|
-
FString SourceOutput = OptionalString(Params, TEXT("sourceOutput"));
|
|
215
|
-
FString TargetInput = OptionalString(Params, TEXT("targetInput"));
|
|
216
|
-
|
|
217
|
-
const bool bOk = UMaterialEditingLibrary::ConnectMaterialExpressions(From, SourceOutput, To, TargetInput);
|
|
218
|
-
if (!bOk)
|
|
219
|
-
{
|
|
220
|
-
return MCPError(FString::Printf(TEXT("ConnectMaterialExpressions failed: '%s' -> '%s' (output='%s' input='%s')"),
|
|
221
|
-
*From->GetName(), *To->GetName(), *SourceOutput, *TargetInput));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
|
|
225
|
-
UEditorAssetLibrary::SaveAsset(MF->GetPathName());
|
|
226
|
-
|
|
227
|
-
auto Result = MCPSuccess();
|
|
228
|
-
MCPSetCreated(Result);
|
|
229
|
-
Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
|
|
230
|
-
Result->SetStringField(TEXT("sourceExpression"), From->GetName());
|
|
231
|
-
Result->SetStringField(TEXT("targetExpression"), To->GetName());
|
|
232
|
-
Result->SetStringField(TEXT("sourceOutput"), SourceOutput);
|
|
233
|
-
Result->SetStringField(TEXT("targetInput"), TargetInput);
|
|
234
|
-
return MCPResult(Result);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// material(action="list_expressions_in_function", functionPath)
|
|
238
|
-
TSharedPtr<FJsonValue> FMaterialHandlers::ListMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
|
|
239
|
-
{
|
|
240
|
-
FString FunctionPath;
|
|
241
|
-
if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
|
|
242
|
-
|
|
243
|
-
UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
|
|
244
|
-
if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
|
|
245
|
-
|
|
246
|
-
TArray<TSharedPtr<FJsonValue>> Arr;
|
|
247
|
-
int32 Index = 0;
|
|
248
|
-
for (UMaterialExpression* Expr : MF->GetExpressions())
|
|
249
|
-
{
|
|
250
|
-
if (!Expr) { ++Index; continue; }
|
|
251
|
-
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
252
|
-
Obj->SetNumberField(TEXT("index"), Index);
|
|
253
|
-
Obj->SetStringField(TEXT("class"), Expr->GetClass()->GetName());
|
|
254
|
-
Obj->SetStringField(TEXT("name"), Expr->GetName());
|
|
255
|
-
Obj->SetStringField(TEXT("description"), Expr->Desc);
|
|
256
|
-
Obj->SetNumberField(TEXT("positionX"), Expr->MaterialExpressionEditorX);
|
|
257
|
-
Obj->SetNumberField(TEXT("positionY"), Expr->MaterialExpressionEditorY);
|
|
258
|
-
if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
|
|
259
|
-
{
|
|
260
|
-
Obj->SetStringField(TEXT("inputName"), In->InputName.ToString());
|
|
261
|
-
}
|
|
262
|
-
if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
|
|
263
|
-
{
|
|
264
|
-
Obj->SetStringField(TEXT("outputName"), Out->OutputName.ToString());
|
|
265
|
-
}
|
|
266
|
-
Arr.Add(MakeShared<FJsonValueObject>(Obj));
|
|
267
|
-
++Index;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
auto Result = MCPSuccess();
|
|
271
|
-
Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
|
|
272
|
-
Result->SetArrayField(TEXT("expressions"), Arr);
|
|
273
|
-
Result->SetNumberField(TEXT("count"), Arr.Num());
|
|
274
|
-
return MCPResult(Result);
|
|
275
|
-
}
|
|
1
|
+
// #463: MaterialFunction creation + per-function expression authoring.
|
|
2
|
+
// The material tool surface previously only covered UMaterial asset graphs.
|
|
3
|
+
// MaterialFunction assets had no native create path or per-function
|
|
4
|
+
// expression APIs, forcing execute_python for what is a normal authoring
|
|
5
|
+
// workflow (color packs, reusable shading functions, math helpers).
|
|
6
|
+
//
|
|
7
|
+
// Split into its own TU so the existing MaterialHandlers.cpp doesn't grow.
|
|
8
|
+
// All functions are still members of FMaterialHandlers - registration
|
|
9
|
+
// happens in MaterialHandlers.cpp::RegisterHandlers.
|
|
10
|
+
|
|
11
|
+
#include "MaterialHandlers.h"
|
|
12
|
+
#include "HandlerRegistry.h"
|
|
13
|
+
#include "HandlerUtils.h"
|
|
14
|
+
#include "HandlerAssetCreate.h"
|
|
15
|
+
#include "AssetToolsModule.h"
|
|
16
|
+
#include "IAssetTools.h"
|
|
17
|
+
#include "Factories/MaterialFunctionFactoryNew.h"
|
|
18
|
+
#include "Materials/MaterialFunction.h"
|
|
19
|
+
#include "Materials/MaterialExpression.h"
|
|
20
|
+
#include "Materials/MaterialExpressionFunctionInput.h"
|
|
21
|
+
#include "Materials/MaterialExpressionFunctionOutput.h"
|
|
22
|
+
#include "MaterialEditingLibrary.h"
|
|
23
|
+
#include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
|
|
24
|
+
#include "UObject/UObjectIterator.h"
|
|
25
|
+
|
|
26
|
+
namespace
|
|
27
|
+
{
|
|
28
|
+
UMaterialFunction* LoadMaterialFunction(const FString& Path)
|
|
29
|
+
{
|
|
30
|
+
UMaterialFunction* MF = LoadObject<UMaterialFunction>(nullptr, *Path);
|
|
31
|
+
if (!MF)
|
|
32
|
+
{
|
|
33
|
+
MF = Cast<UMaterialFunction>(UEditorAssetLibrary::LoadAsset(Path));
|
|
34
|
+
}
|
|
35
|
+
return MF;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
UClass* ResolveExpressionClass(const FString& InType)
|
|
39
|
+
{
|
|
40
|
+
FString ClassName = InType;
|
|
41
|
+
if (!ClassName.StartsWith(TEXT("MaterialExpression")) && !ClassName.StartsWith(TEXT("UMaterialExpression")))
|
|
42
|
+
{
|
|
43
|
+
ClassName = TEXT("UMaterialExpression") + ClassName;
|
|
44
|
+
}
|
|
45
|
+
else if (!ClassName.StartsWith(TEXT("U")))
|
|
46
|
+
{
|
|
47
|
+
ClassName = TEXT("U") + ClassName;
|
|
48
|
+
}
|
|
49
|
+
UClass* Result = FindFirstObject<UClass>(*ClassName, EFindFirstObjectOptions::ExactClass);
|
|
50
|
+
if (!Result)
|
|
51
|
+
{
|
|
52
|
+
Result = FindFirstObject<UClass>(*InType, EFindFirstObjectOptions::ExactClass);
|
|
53
|
+
}
|
|
54
|
+
if (!Result || !Result->IsChildOf(UMaterialExpression::StaticClass())) return nullptr;
|
|
55
|
+
return Result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// material(action="create_function", name, packagePath?, description?, onConflict?)
|
|
60
|
+
TSharedPtr<FJsonValue> FMaterialHandlers::CreateMaterialFunction(const TSharedPtr<FJsonObject>& Params)
|
|
61
|
+
{
|
|
62
|
+
FString Name;
|
|
63
|
+
if (auto Err = RequireString(Params, TEXT("name"), Name)) return Err;
|
|
64
|
+
FString PackagePath = OptionalString(Params, TEXT("packagePath"), TEXT("/Game/Materials/Functions"));
|
|
65
|
+
const FString OnConflict = OptionalString(Params, TEXT("onConflict"), TEXT("skip"));
|
|
66
|
+
|
|
67
|
+
UMaterialFunctionFactoryNew* Factory = NewObject<UMaterialFunctionFactoryNew>();
|
|
68
|
+
auto Created = MCPCreateAssetIdempotent<UMaterialFunction>(Name, PackagePath, OnConflict, TEXT("MaterialFunction"), Factory);
|
|
69
|
+
if (Created.EarlyReturn) return Created.EarlyReturn;
|
|
70
|
+
|
|
71
|
+
UMaterialFunction* MF = Created.Asset;
|
|
72
|
+
FString Description;
|
|
73
|
+
if (Params->TryGetStringField(TEXT("description"), Description))
|
|
74
|
+
{
|
|
75
|
+
MF->Description = Description;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
UEditorAssetLibrary::SaveAsset(MF->GetPathName());
|
|
79
|
+
|
|
80
|
+
auto Result = MCPSuccess();
|
|
81
|
+
MCPSetCreated(Result);
|
|
82
|
+
Result->SetStringField(TEXT("path"), MF->GetPathName());
|
|
83
|
+
Result->SetStringField(TEXT("name"), Name);
|
|
84
|
+
Result->SetStringField(TEXT("packagePath"), PackagePath);
|
|
85
|
+
MCPSetDeleteAssetRollback(Result, MF->GetPathName());
|
|
86
|
+
return MCPResult(Result);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// material(action="add_expression_in_function", functionPath, expressionType,
|
|
90
|
+
// positionX?, positionY?, inputName?, outputName?)
|
|
91
|
+
TSharedPtr<FJsonValue> FMaterialHandlers::AddMaterialFunctionExpression(const TSharedPtr<FJsonObject>& Params)
|
|
92
|
+
{
|
|
93
|
+
FString FunctionPath;
|
|
94
|
+
if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
|
|
95
|
+
|
|
96
|
+
FString ExpressionType;
|
|
97
|
+
if (auto Err = RequireString(Params, TEXT("expressionType"), ExpressionType)) return Err;
|
|
98
|
+
|
|
99
|
+
UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
|
|
100
|
+
if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
|
|
101
|
+
|
|
102
|
+
UClass* ExprClass = ResolveExpressionClass(ExpressionType);
|
|
103
|
+
if (!ExprClass) return MCPError(FString::Printf(TEXT("Unknown expression type: %s"), *ExpressionType));
|
|
104
|
+
|
|
105
|
+
int32 PosX = (int32)OptionalNumber(Params, TEXT("positionX"), 0.0);
|
|
106
|
+
int32 PosY = (int32)OptionalNumber(Params, TEXT("positionY"), 0.0);
|
|
107
|
+
|
|
108
|
+
UMaterialExpression* NewExpr = UMaterialEditingLibrary::CreateMaterialExpressionInFunction(MF, ExprClass, PosX, PosY);
|
|
109
|
+
if (!NewExpr) return MCPError(TEXT("CreateMaterialExpressionInFunction returned null"));
|
|
110
|
+
|
|
111
|
+
// Input/Output expressions: name them so callers can reference them by name.
|
|
112
|
+
if (UMaterialExpressionFunctionInput* AsInput = Cast<UMaterialExpressionFunctionInput>(NewExpr))
|
|
113
|
+
{
|
|
114
|
+
FString InputName;
|
|
115
|
+
if (Params->TryGetStringField(TEXT("inputName"), InputName) || Params->TryGetStringField(TEXT("name"), InputName))
|
|
116
|
+
{
|
|
117
|
+
AsInput->InputName = FName(*InputName);
|
|
118
|
+
}
|
|
119
|
+
FString InputTypeStr;
|
|
120
|
+
if (Params->TryGetStringField(TEXT("inputType"), InputTypeStr))
|
|
121
|
+
{
|
|
122
|
+
static const TMap<FString, EFunctionInputType> Map = {
|
|
123
|
+
{TEXT("Scalar"), FunctionInput_Scalar},
|
|
124
|
+
{TEXT("Vector2"), FunctionInput_Vector2},
|
|
125
|
+
{TEXT("Vector3"), FunctionInput_Vector3},
|
|
126
|
+
{TEXT("Vector4"), FunctionInput_Vector4},
|
|
127
|
+
{TEXT("Texture2D"), FunctionInput_Texture2D},
|
|
128
|
+
{TEXT("TextureCube"), FunctionInput_TextureCube},
|
|
129
|
+
{TEXT("StaticBool"), FunctionInput_StaticBool},
|
|
130
|
+
{TEXT("MaterialAttributes"), FunctionInput_MaterialAttributes},
|
|
131
|
+
};
|
|
132
|
+
if (const EFunctionInputType* Found = Map.Find(InputTypeStr))
|
|
133
|
+
{
|
|
134
|
+
AsInput->InputType = *Found;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (UMaterialExpressionFunctionOutput* AsOutput = Cast<UMaterialExpressionFunctionOutput>(NewExpr))
|
|
139
|
+
{
|
|
140
|
+
FString OutputName;
|
|
141
|
+
if (Params->TryGetStringField(TEXT("outputName"), OutputName) || Params->TryGetStringField(TEXT("name"), OutputName))
|
|
142
|
+
{
|
|
143
|
+
AsOutput->OutputName = FName(*OutputName);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
|
|
148
|
+
UEditorAssetLibrary::SaveAsset(MF->GetPathName());
|
|
149
|
+
|
|
150
|
+
int32 Index = MF->GetExpressions().IndexOfByKey(NewExpr);
|
|
151
|
+
|
|
152
|
+
auto Result = MCPSuccess();
|
|
153
|
+
MCPSetCreated(Result);
|
|
154
|
+
Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
|
|
155
|
+
Result->SetStringField(TEXT("expressionClass"), NewExpr->GetClass()->GetName());
|
|
156
|
+
Result->SetNumberField(TEXT("expressionIndex"), Index);
|
|
157
|
+
Result->SetStringField(TEXT("nodeId"), FString::FromInt(Index));
|
|
158
|
+
return MCPResult(Result);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// material(action="connect_expressions_in_function", functionPath,
|
|
162
|
+
// sourceExpression (name or index), sourceOutput?,
|
|
163
|
+
// targetExpression (name or index), targetInput?)
|
|
164
|
+
TSharedPtr<FJsonValue> FMaterialHandlers::ConnectMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
|
|
165
|
+
{
|
|
166
|
+
FString FunctionPath;
|
|
167
|
+
if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
|
|
168
|
+
|
|
169
|
+
UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
|
|
170
|
+
if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
|
|
171
|
+
|
|
172
|
+
auto ResolveExpr = [&](const TCHAR* Key) -> UMaterialExpression*
|
|
173
|
+
{
|
|
174
|
+
// Numeric index?
|
|
175
|
+
int32 Idx = -1;
|
|
176
|
+
if (Params->TryGetNumberField(Key, Idx))
|
|
177
|
+
{
|
|
178
|
+
if (Idx >= 0 && Idx < MF->GetExpressions().Num()) return MF->GetExpressions()[Idx];
|
|
179
|
+
return nullptr;
|
|
180
|
+
}
|
|
181
|
+
FString Str;
|
|
182
|
+
if (Params->TryGetStringField(Key, Str))
|
|
183
|
+
{
|
|
184
|
+
// FunctionInput/Output exposes InputName/OutputName; everything else uses Desc.
|
|
185
|
+
for (UMaterialExpression* Expr : MF->GetExpressions())
|
|
186
|
+
{
|
|
187
|
+
if (!Expr) continue;
|
|
188
|
+
if (Expr->Desc == Str) return Expr;
|
|
189
|
+
if (Expr->GetName() == Str) return Expr;
|
|
190
|
+
if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
|
|
191
|
+
{
|
|
192
|
+
if (In->InputName.ToString() == Str) return Expr;
|
|
193
|
+
}
|
|
194
|
+
if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
|
|
195
|
+
{
|
|
196
|
+
if (Out->OutputName.ToString() == Str) return Expr;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Numeric in string form
|
|
200
|
+
int32 ParsedIdx = FCString::Atoi(*Str);
|
|
201
|
+
if (ParsedIdx >= 0 && ParsedIdx < MF->GetExpressions().Num() && Str.IsNumeric())
|
|
202
|
+
{
|
|
203
|
+
return MF->GetExpressions()[ParsedIdx];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return nullptr;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
UMaterialExpression* From = ResolveExpr(TEXT("sourceExpression"));
|
|
210
|
+
UMaterialExpression* To = ResolveExpr(TEXT("targetExpression"));
|
|
211
|
+
if (!From) return MCPError(TEXT("sourceExpression not found in function"));
|
|
212
|
+
if (!To) return MCPError(TEXT("targetExpression not found in function"));
|
|
213
|
+
|
|
214
|
+
FString SourceOutput = OptionalString(Params, TEXT("sourceOutput"));
|
|
215
|
+
FString TargetInput = OptionalString(Params, TEXT("targetInput"));
|
|
216
|
+
|
|
217
|
+
const bool bOk = UMaterialEditingLibrary::ConnectMaterialExpressions(From, SourceOutput, To, TargetInput);
|
|
218
|
+
if (!bOk)
|
|
219
|
+
{
|
|
220
|
+
return MCPError(FString::Printf(TEXT("ConnectMaterialExpressions failed: '%s' -> '%s' (output='%s' input='%s')"),
|
|
221
|
+
*From->GetName(), *To->GetName(), *SourceOutput, *TargetInput));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
UMaterialEditingLibrary::UpdateMaterialFunction(MF, nullptr);
|
|
225
|
+
UEditorAssetLibrary::SaveAsset(MF->GetPathName());
|
|
226
|
+
|
|
227
|
+
auto Result = MCPSuccess();
|
|
228
|
+
MCPSetCreated(Result);
|
|
229
|
+
Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
|
|
230
|
+
Result->SetStringField(TEXT("sourceExpression"), From->GetName());
|
|
231
|
+
Result->SetStringField(TEXT("targetExpression"), To->GetName());
|
|
232
|
+
Result->SetStringField(TEXT("sourceOutput"), SourceOutput);
|
|
233
|
+
Result->SetStringField(TEXT("targetInput"), TargetInput);
|
|
234
|
+
return MCPResult(Result);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// material(action="list_expressions_in_function", functionPath)
|
|
238
|
+
TSharedPtr<FJsonValue> FMaterialHandlers::ListMaterialFunctionExpressions(const TSharedPtr<FJsonObject>& Params)
|
|
239
|
+
{
|
|
240
|
+
FString FunctionPath;
|
|
241
|
+
if (auto Err = RequireStringAlt(Params, TEXT("functionPath"), TEXT("materialFunctionPath"), FunctionPath)) return Err;
|
|
242
|
+
|
|
243
|
+
UMaterialFunction* MF = LoadMaterialFunction(FunctionPath);
|
|
244
|
+
if (!MF) return MCPError(FString::Printf(TEXT("MaterialFunction not found: %s"), *FunctionPath));
|
|
245
|
+
|
|
246
|
+
TArray<TSharedPtr<FJsonValue>> Arr;
|
|
247
|
+
int32 Index = 0;
|
|
248
|
+
for (UMaterialExpression* Expr : MF->GetExpressions())
|
|
249
|
+
{
|
|
250
|
+
if (!Expr) { ++Index; continue; }
|
|
251
|
+
TSharedPtr<FJsonObject> Obj = MakeShared<FJsonObject>();
|
|
252
|
+
Obj->SetNumberField(TEXT("index"), Index);
|
|
253
|
+
Obj->SetStringField(TEXT("class"), Expr->GetClass()->GetName());
|
|
254
|
+
Obj->SetStringField(TEXT("name"), Expr->GetName());
|
|
255
|
+
Obj->SetStringField(TEXT("description"), Expr->Desc);
|
|
256
|
+
Obj->SetNumberField(TEXT("positionX"), Expr->MaterialExpressionEditorX);
|
|
257
|
+
Obj->SetNumberField(TEXT("positionY"), Expr->MaterialExpressionEditorY);
|
|
258
|
+
if (UMaterialExpressionFunctionInput* In = Cast<UMaterialExpressionFunctionInput>(Expr))
|
|
259
|
+
{
|
|
260
|
+
Obj->SetStringField(TEXT("inputName"), In->InputName.ToString());
|
|
261
|
+
}
|
|
262
|
+
if (UMaterialExpressionFunctionOutput* Out = Cast<UMaterialExpressionFunctionOutput>(Expr))
|
|
263
|
+
{
|
|
264
|
+
Obj->SetStringField(TEXT("outputName"), Out->OutputName.ToString());
|
|
265
|
+
}
|
|
266
|
+
Arr.Add(MakeShared<FJsonValueObject>(Obj));
|
|
267
|
+
++Index;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
auto Result = MCPSuccess();
|
|
271
|
+
Result->SetStringField(TEXT("functionPath"), MF->GetPathName());
|
|
272
|
+
Result->SetArrayField(TEXT("expressions"), Arr);
|
|
273
|
+
Result->SetNumberField(TEXT("count"), Arr.Num());
|
|
274
|
+
return MCPResult(Result);
|
|
275
|
+
}
|