ue-mcp 0.4.0-alpha

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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/dist/bridge.d.ts +25 -0
  4. package/dist/bridge.js +124 -0
  5. package/dist/bridge.js.map +1 -0
  6. package/dist/deployer.d.ts +18 -0
  7. package/dist/deployer.js +175 -0
  8. package/dist/deployer.js.map +1 -0
  9. package/dist/editor-control.d.ts +15 -0
  10. package/dist/editor-control.js +181 -0
  11. package/dist/editor-control.js.map +1 -0
  12. package/dist/github-app.d.ts +4 -0
  13. package/dist/github-app.js +94 -0
  14. package/dist/github-app.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +125 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/init.d.ts +2 -0
  19. package/dist/init.js +290 -0
  20. package/dist/init.js.map +1 -0
  21. package/dist/instructions.d.ts +1 -0
  22. package/dist/instructions.js +150 -0
  23. package/dist/instructions.js.map +1 -0
  24. package/dist/project.d.ts +32 -0
  25. package/dist/project.js +175 -0
  26. package/dist/project.js.map +1 -0
  27. package/dist/tools/animation.d.ts +2 -0
  28. package/dist/tools/animation.js +62 -0
  29. package/dist/tools/animation.js.map +1 -0
  30. package/dist/tools/asset.d.ts +2 -0
  31. package/dist/tools/asset.js +128 -0
  32. package/dist/tools/asset.js.map +1 -0
  33. package/dist/tools/audio.d.ts +2 -0
  34. package/dist/tools/audio.js +24 -0
  35. package/dist/tools/audio.js.map +1 -0
  36. package/dist/tools/blueprint.d.ts +2 -0
  37. package/dist/tools/blueprint.js +64 -0
  38. package/dist/tools/blueprint.js.map +1 -0
  39. package/dist/tools/demo.d.ts +2 -0
  40. package/dist/tools/demo.js +10 -0
  41. package/dist/tools/demo.js.map +1 -0
  42. package/dist/tools/editor.d.ts +2 -0
  43. package/dist/tools/editor.js +133 -0
  44. package/dist/tools/editor.js.map +1 -0
  45. package/dist/tools/feedback.d.ts +2 -0
  46. package/dist/tools/feedback.js +44 -0
  47. package/dist/tools/feedback.js.map +1 -0
  48. package/dist/tools/foliage.d.ts +2 -0
  49. package/dist/tools/foliage.js +29 -0
  50. package/dist/tools/foliage.js.map +1 -0
  51. package/dist/tools/gameplay.d.ts +2 -0
  52. package/dist/tools/gameplay.js +101 -0
  53. package/dist/tools/gameplay.js.map +1 -0
  54. package/dist/tools/gas.d.ts +2 -0
  55. package/dist/tools/gas.js +43 -0
  56. package/dist/tools/gas.js.map +1 -0
  57. package/dist/tools/landscape.d.ts +2 -0
  58. package/dist/tools/landscape.js +32 -0
  59. package/dist/tools/landscape.js.map +1 -0
  60. package/dist/tools/level.d.ts +2 -0
  61. package/dist/tools/level.js +66 -0
  62. package/dist/tools/level.js.map +1 -0
  63. package/dist/tools/material.d.ts +2 -0
  64. package/dist/tools/material.js +59 -0
  65. package/dist/tools/material.js.map +1 -0
  66. package/dist/tools/networking.d.ts +2 -0
  67. package/dist/tools/networking.js +42 -0
  68. package/dist/tools/networking.js.map +1 -0
  69. package/dist/tools/niagara.d.ts +2 -0
  70. package/dist/tools/niagara.js +42 -0
  71. package/dist/tools/niagara.js.map +1 -0
  72. package/dist/tools/pcg.d.ts +2 -0
  73. package/dist/tools/pcg.js +39 -0
  74. package/dist/tools/pcg.js.map +1 -0
  75. package/dist/tools/project.d.ts +2 -0
  76. package/dist/tools/project.js +282 -0
  77. package/dist/tools/project.js.map +1 -0
  78. package/dist/tools/reflection.d.ts +2 -0
  79. package/dist/tools/reflection.js +26 -0
  80. package/dist/tools/reflection.js.map +1 -0
  81. package/dist/tools/widget.d.ts +2 -0
  82. package/dist/tools/widget.js +34 -0
  83. package/dist/tools/widget.js.map +1 -0
  84. package/dist/types.d.ts +21 -0
  85. package/dist/types.js +38 -0
  86. package/dist/types.js.map +1 -0
  87. package/package.json +76 -0
  88. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +746 -0
  89. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +81 -0
  90. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +49 -0
  91. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +37 -0
  92. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.cpp +75 -0
  93. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerRegistry.h +50 -0
  94. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +1418 -0
  95. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +43 -0
  96. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +1773 -0
  97. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +48 -0
  98. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +289 -0
  99. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +18 -0
  100. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +1982 -0
  101. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +44 -0
  102. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +1217 -0
  103. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.h +71 -0
  104. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +465 -0
  105. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +49 -0
  106. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +1676 -0
  107. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +53 -0
  108. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +876 -0
  109. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +22 -0
  110. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +2222 -0
  111. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +54 -0
  112. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +783 -0
  113. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +24 -0
  114. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +898 -0
  115. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +24 -0
  116. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +1270 -0
  117. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.h +34 -0
  118. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +2190 -0
  119. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +53 -0
  120. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +554 -0
  121. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.h +29 -0
  122. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +601 -0
  123. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +25 -0
  124. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +1024 -0
  125. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.h +25 -0
  126. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +370 -0
  127. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.h +19 -0
  128. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +499 -0
  129. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +27 -0
  130. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +426 -0
  131. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.h +19 -0
  132. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.cpp +303 -0
  133. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SplineHandlers.h +18 -0
  134. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +985 -0
  135. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +27 -0
  136. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.cpp +181 -0
  137. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/JsonSerializer.h +42 -0
  138. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +39 -0
  139. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/UE_MCP_BridgeModule.h +14 -0
  140. package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +87 -0
  141. package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +55 -0
@@ -0,0 +1,2190 @@
1
+ #include "MaterialHandlers.h"
2
+ #include "UE_MCP_BridgeModule.h"
3
+ #include "HandlerRegistry.h"
4
+ #include "Materials/Material.h"
5
+ #include "Materials/MaterialInstance.h"
6
+ #include "Materials/MaterialInstanceConstant.h"
7
+ #include "Materials/MaterialExpressionConstant.h"
8
+ #include "Materials/MaterialExpressionConstant2Vector.h"
9
+ #include "Materials/MaterialExpressionConstant3Vector.h"
10
+ #include "Materials/MaterialExpressionConstant4Vector.h"
11
+ #include "Materials/MaterialExpressionTextureSample.h"
12
+ #include "Materials/MaterialExpressionScalarParameter.h"
13
+ #include "Materials/MaterialExpressionVectorParameter.h"
14
+ #include "Materials/MaterialExpressionTextureCoordinate.h"
15
+ #include "Materials/MaterialExpressionTextureObjectParameter.h"
16
+ #include "Materials/MaterialExpressionAdd.h"
17
+ #include "Materials/MaterialExpressionMultiply.h"
18
+ #include "Materials/MaterialExpressionLinearInterpolate.h"
19
+ #include "Materials/MaterialExpressionPower.h"
20
+ #include "Materials/MaterialExpressionClamp.h"
21
+ #include "Materials/MaterialExpressionOneMinus.h"
22
+ #include "Materials/MaterialExpressionFresnel.h"
23
+ #include "Factories/MaterialInstanceConstantFactoryNew.h"
24
+ #include "Factories/MaterialFactoryNew.h"
25
+ #include "AssetToolsModule.h"
26
+ #include "IAssetTools.h"
27
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
28
+ #include "UObject/UObjectGlobals.h"
29
+ #include "UObject/Package.h"
30
+ #include "Misc/PackageName.h"
31
+ #include "UObject/SavePackage.h"
32
+
33
+ void FMaterialHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
34
+ {
35
+ Registry.RegisterHandler(TEXT("list_expression_types"), &ListExpressionTypes);
36
+ Registry.RegisterHandler(TEXT("create_material"), &CreateMaterial);
37
+ Registry.RegisterHandler(TEXT("read_material"), &ReadMaterial);
38
+ Registry.RegisterHandler(TEXT("set_material_shading_model"), &SetMaterialShadingModel);
39
+ Registry.RegisterHandler(TEXT("set_material_blend_mode"), &SetMaterialBlendMode);
40
+ Registry.RegisterHandler(TEXT("set_material_base_color"), &SetMaterialBaseColor);
41
+ Registry.RegisterHandler(TEXT("add_material_expression"), &AddMaterialExpression);
42
+ Registry.RegisterHandler(TEXT("list_material_expressions"), &ListMaterialExpressions);
43
+ Registry.RegisterHandler(TEXT("list_material_parameters"), &ListMaterialParameters);
44
+ Registry.RegisterHandler(TEXT("recompile_material"), &RecompileMaterial);
45
+ Registry.RegisterHandler(TEXT("create_material_instance"), &CreateMaterialInstance);
46
+ Registry.RegisterHandler(TEXT("set_material_parameter"), &SetMaterialParameter);
47
+ Registry.RegisterHandler(TEXT("connect_expression"), &ConnectExpression);
48
+ Registry.RegisterHandler(TEXT("connect_material_property"), &ConnectMaterialProperty);
49
+ Registry.RegisterHandler(TEXT("delete_expression"), &DeleteExpression);
50
+ Registry.RegisterHandler(TEXT("set_expression_value"), &SetExpressionValue);
51
+ Registry.RegisterHandler(TEXT("create_material_from_texture"), &CreateMaterialFromTexture);
52
+ Registry.RegisterHandler(TEXT("read_material_instance"), &ReadMaterialInstance);
53
+
54
+ // TS-expected name aliases
55
+ Registry.RegisterHandler(TEXT("connect_texture_to_material"), &ConnectTextureToMaterial);
56
+ Registry.RegisterHandler(TEXT("connect_material_expressions"), &ConnectMaterialExpressions);
57
+ Registry.RegisterHandler(TEXT("connect_to_material_property"), &ConnectToMaterialProperty);
58
+ Registry.RegisterHandler(TEXT("delete_material_expression"), &DeleteMaterialExpression);
59
+ }
60
+
61
+ UMaterial* FMaterialHandlers::LoadMaterialFromPath(const FString& AssetPath)
62
+ {
63
+ UObject* LoadedObject = StaticLoadObject(UMaterial::StaticClass(), nullptr, *AssetPath);
64
+ if (!LoadedObject)
65
+ {
66
+ // Try with explicit class prefix
67
+ LoadedObject = StaticLoadObject(UMaterial::StaticClass(), nullptr, *(TEXT("Material'") + AssetPath + TEXT("'")));
68
+ }
69
+ return Cast<UMaterial>(LoadedObject);
70
+ }
71
+
72
+ UMaterialInstanceConstant* FMaterialHandlers::LoadMaterialInstanceFromPath(const FString& AssetPath)
73
+ {
74
+ UObject* LoadedObject = StaticLoadObject(UMaterialInstanceConstant::StaticClass(), nullptr, *AssetPath);
75
+ if (!LoadedObject)
76
+ {
77
+ // Try with explicit class prefix
78
+ LoadedObject = StaticLoadObject(UMaterialInstanceConstant::StaticClass(), nullptr,
79
+ *(TEXT("MaterialInstanceConstant'") + AssetPath + TEXT("'")));
80
+ }
81
+ return Cast<UMaterialInstanceConstant>(LoadedObject);
82
+ }
83
+
84
+ EMaterialShadingModel FMaterialHandlers::ParseShadingModel(const FString& ShadingModelStr)
85
+ {
86
+ FString Lower = ShadingModelStr.ToLower();
87
+ if (Lower == TEXT("unlit")) return MSM_Unlit;
88
+ if (Lower == TEXT("defaultlit")) return MSM_DefaultLit;
89
+ if (Lower == TEXT("subsurface")) return MSM_Subsurface;
90
+ if (Lower == TEXT("subsurfaceprofile")) return MSM_SubsurfaceProfile;
91
+ if (Lower == TEXT("preintegratedskin")) return MSM_PreintegratedSkin;
92
+ if (Lower == TEXT("clearcoa") || Lower == TEXT("clearcoat")) return MSM_ClearCoat;
93
+ if (Lower == TEXT("cloth")) return MSM_Cloth;
94
+ if (Lower == TEXT("eye")) return MSM_Eye;
95
+ if (Lower == TEXT("twosidedfoliage")) return MSM_TwoSidedFoliage;
96
+ return MSM_DefaultLit;
97
+ }
98
+
99
+ FString FMaterialHandlers::ShadingModelToString(EMaterialShadingModel ShadingModel)
100
+ {
101
+ switch (ShadingModel)
102
+ {
103
+ case MSM_Unlit: return TEXT("Unlit");
104
+ case MSM_DefaultLit: return TEXT("DefaultLit");
105
+ case MSM_Subsurface: return TEXT("Subsurface");
106
+ case MSM_SubsurfaceProfile: return TEXT("SubsurfaceProfile");
107
+ case MSM_PreintegratedSkin: return TEXT("PreintegratedSkin");
108
+ case MSM_ClearCoat: return TEXT("ClearCoat");
109
+ case MSM_Cloth: return TEXT("Cloth");
110
+ case MSM_Eye: return TEXT("Eye");
111
+ case MSM_TwoSidedFoliage: return TEXT("TwoSidedFoliage");
112
+ default: return TEXT("Unknown");
113
+ }
114
+ }
115
+
116
+ bool FMaterialHandlers::ParseMaterialProperty(const FString& PropertyName, EMaterialProperty& OutProperty)
117
+ {
118
+ FString Lower = PropertyName.ToLower();
119
+ if (Lower == TEXT("basecolor")) { OutProperty = MP_BaseColor; return true; }
120
+ if (Lower == TEXT("metallic")) { OutProperty = MP_Metallic; return true; }
121
+ if (Lower == TEXT("specular")) { OutProperty = MP_Specular; return true; }
122
+ if (Lower == TEXT("roughness")) { OutProperty = MP_Roughness; return true; }
123
+ if (Lower == TEXT("anisotropy")) { OutProperty = MP_Anisotropy; return true; }
124
+ if (Lower == TEXT("emissivecolor")) { OutProperty = MP_EmissiveColor; return true; }
125
+ if (Lower == TEXT("emissive")) { OutProperty = MP_EmissiveColor; return true; }
126
+ if (Lower == TEXT("opacity")) { OutProperty = MP_Opacity; return true; }
127
+ if (Lower == TEXT("opacitymask")) { OutProperty = MP_OpacityMask; return true; }
128
+ if (Lower == TEXT("normal")) { OutProperty = MP_Normal; return true; }
129
+ if (Lower == TEXT("tangent")) { OutProperty = MP_Tangent; return true; }
130
+ if (Lower == TEXT("worldpositionoffset")) { OutProperty = MP_WorldPositionOffset; return true; }
131
+ if (Lower == TEXT("subsurfacecolor")) { OutProperty = MP_SubsurfaceColor; return true; }
132
+ if (Lower == TEXT("ambientocclusion")) { OutProperty = MP_AmbientOcclusion; return true; }
133
+ if (Lower == TEXT("ao")) { OutProperty = MP_AmbientOcclusion; return true; }
134
+ if (Lower == TEXT("refraction")) { OutProperty = MP_Refraction; return true; }
135
+ if (Lower == TEXT("pixeldepthoffset")) { OutProperty = MP_PixelDepthOffset; return true; }
136
+ if (Lower == TEXT("shadingmodel")) { OutProperty = MP_ShadingModel; return true; }
137
+ return false;
138
+ }
139
+
140
+ TSharedPtr<FJsonValue> FMaterialHandlers::ListExpressionTypes(const TSharedPtr<FJsonObject>& Params)
141
+ {
142
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
143
+ TArray<TSharedPtr<FJsonValue>> TypesArray;
144
+
145
+ // Common material expression types
146
+ TArray<FString> ExpressionTypes = {
147
+ TEXT("MaterialExpressionConstant"),
148
+ TEXT("MaterialExpressionConstant2Vector"),
149
+ TEXT("MaterialExpressionConstant3Vector"),
150
+ TEXT("MaterialExpressionConstant4Vector"),
151
+ TEXT("MaterialExpressionTextureSample"),
152
+ TEXT("MaterialExpressionTextureCoordinate"),
153
+ TEXT("MaterialExpressionScalarParameter"),
154
+ TEXT("MaterialExpressionVectorParameter"),
155
+ TEXT("MaterialExpressionTextureObjectParameter"),
156
+ TEXT("MaterialExpressionStaticSwitchParameter"),
157
+ TEXT("MaterialExpressionAdd"),
158
+ TEXT("MaterialExpressionMultiply"),
159
+ TEXT("MaterialExpressionSubtract"),
160
+ TEXT("MaterialExpressionDivide"),
161
+ TEXT("MaterialExpressionLinearInterpolate"),
162
+ TEXT("MaterialExpressionPower"),
163
+ TEXT("MaterialExpressionClamp"),
164
+ TEXT("MaterialExpressionAppendVector"),
165
+ TEXT("MaterialExpressionComponentMask"),
166
+ TEXT("MaterialExpressionDotProduct"),
167
+ TEXT("MaterialExpressionCrossProduct"),
168
+ TEXT("MaterialExpressionNormalize"),
169
+ TEXT("MaterialExpressionOneMinus"),
170
+ TEXT("MaterialExpressionAbs"),
171
+ TEXT("MaterialExpressionTime"),
172
+ TEXT("MaterialExpressionWorldPosition"),
173
+ TEXT("MaterialExpressionVertexNormalWS"),
174
+ TEXT("MaterialExpressionCameraPositionWS"),
175
+ TEXT("MaterialExpressionFresnel"),
176
+ TEXT("MaterialExpressionPanner"),
177
+ TEXT("MaterialExpressionRotator"),
178
+ TEXT("MaterialExpressionDesaturation"),
179
+ TEXT("MaterialExpressionNoise"),
180
+ TEXT("MaterialExpressionParticleColor"),
181
+ TEXT("MaterialExpressionObjectPositionWS"),
182
+ TEXT("MaterialExpressionActorPositionWS")
183
+ };
184
+
185
+ for (const FString& TypeName : ExpressionTypes)
186
+ {
187
+ TypesArray.Add(MakeShared<FJsonValueString>(TypeName));
188
+ }
189
+
190
+ Result->SetArrayField(TEXT("expressionTypes"), TypesArray);
191
+ Result->SetNumberField(TEXT("count"), ExpressionTypes.Num());
192
+ Result->SetBoolField(TEXT("success"), true);
193
+
194
+ return MakeShared<FJsonValueObject>(Result);
195
+ }
196
+
197
+ TSharedPtr<FJsonValue> FMaterialHandlers::CreateMaterial(const TSharedPtr<FJsonObject>& Params)
198
+ {
199
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
200
+
201
+ FString Name;
202
+ if (!Params->TryGetStringField(TEXT("name"), Name) || Name.IsEmpty())
203
+ {
204
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'name' parameter"));
205
+ Result->SetBoolField(TEXT("success"), false);
206
+ return MakeShared<FJsonValueObject>(Result);
207
+ }
208
+
209
+ FString PackagePath = TEXT("/Game/Materials");
210
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
211
+
212
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] CreateMaterial: name=%s packagePath=%s"), *Name, *PackagePath);
213
+
214
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
215
+ IAssetTools& AssetTools = AssetToolsModule.Get();
216
+
217
+ UMaterialFactoryNew* MaterialFactory = NewObject<UMaterialFactoryNew>();
218
+ UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterial::StaticClass(), MaterialFactory);
219
+
220
+ if (!NewAsset)
221
+ {
222
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create material asset"));
223
+ Result->SetBoolField(TEXT("success"), false);
224
+ return MakeShared<FJsonValueObject>(Result);
225
+ }
226
+
227
+ UMaterial* NewMaterial = Cast<UMaterial>(NewAsset);
228
+ if (!NewMaterial)
229
+ {
230
+ Result->SetStringField(TEXT("error"), TEXT("Created asset is not a material"));
231
+ Result->SetBoolField(TEXT("success"), false);
232
+ return MakeShared<FJsonValueObject>(Result);
233
+ }
234
+
235
+ // Save the package
236
+ UPackage* Package = NewMaterial->GetOutermost();
237
+ if (Package)
238
+ {
239
+ Package->MarkPackageDirty();
240
+ FString PackageFileName = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
241
+ FSavePackageArgs SaveArgs;
242
+ SaveArgs.TopLevelFlags = RF_Standalone;
243
+ UPackage::SavePackage(Package, nullptr, *PackageFileName, SaveArgs);
244
+ }
245
+
246
+ FString AssetPath = NewMaterial->GetPathName();
247
+ Result->SetStringField(TEXT("path"), AssetPath);
248
+ Result->SetStringField(TEXT("name"), Name);
249
+ Result->SetStringField(TEXT("packagePath"), PackagePath);
250
+ Result->SetBoolField(TEXT("success"), true);
251
+
252
+ return MakeShared<FJsonValueObject>(Result);
253
+ }
254
+
255
+ TSharedPtr<FJsonValue> FMaterialHandlers::ReadMaterial(const TSharedPtr<FJsonObject>& Params)
256
+ {
257
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
258
+
259
+ FString AssetPath;
260
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
261
+ {
262
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'path' or 'assetPath' parameter"));
263
+ Result->SetBoolField(TEXT("success"), false);
264
+ return MakeShared<FJsonValueObject>(Result);
265
+ }
266
+
267
+ UMaterial* Material = LoadMaterialFromPath(AssetPath);
268
+ if (!Material)
269
+ {
270
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *AssetPath));
271
+ Result->SetBoolField(TEXT("success"), false);
272
+ return MakeShared<FJsonValueObject>(Result);
273
+ }
274
+
275
+ Result->SetStringField(TEXT("name"), Material->GetName());
276
+ Result->SetStringField(TEXT("path"), Material->GetPathName());
277
+ Result->SetStringField(TEXT("shadingModel"), ShadingModelToString(Material->GetShadingModels().GetFirstShadingModel()));
278
+ Result->SetStringField(TEXT("blendMode"), StaticEnum<EBlendMode>()->GetNameStringByValue((int64)Material->BlendMode));
279
+ Result->SetBoolField(TEXT("twoSided"), Material->IsTwoSided());
280
+
281
+ // Expressions list with details
282
+ TArray<TSharedPtr<FJsonValue>> ExpressionsArray;
283
+ int32 Index = 0;
284
+ for (UMaterialExpression* Expression : Material->GetExpressions())
285
+ {
286
+ if (!Expression) { Index++; continue; }
287
+
288
+ TSharedPtr<FJsonObject> ExprObj = MakeShared<FJsonObject>();
289
+ ExprObj->SetNumberField(TEXT("index"), Index);
290
+ ExprObj->SetStringField(TEXT("class"), Expression->GetClass()->GetName());
291
+ ExprObj->SetStringField(TEXT("description"), Expression->GetDescription());
292
+ ExprObj->SetNumberField(TEXT("positionX"), Expression->MaterialExpressionEditorX);
293
+ ExprObj->SetNumberField(TEXT("positionY"), Expression->MaterialExpressionEditorY);
294
+
295
+ // Extract parameter names for parameter expressions
296
+ if (UMaterialExpressionScalarParameter* ScalarParam = Cast<UMaterialExpressionScalarParameter>(Expression))
297
+ {
298
+ ExprObj->SetStringField(TEXT("parameterName"), ScalarParam->ParameterName.ToString());
299
+ ExprObj->SetNumberField(TEXT("defaultValue"), ScalarParam->DefaultValue);
300
+ }
301
+ else if (UMaterialExpressionVectorParameter* VectorParam = Cast<UMaterialExpressionVectorParameter>(Expression))
302
+ {
303
+ ExprObj->SetStringField(TEXT("parameterName"), VectorParam->ParameterName.ToString());
304
+ TSharedPtr<FJsonObject> DefColor = MakeShared<FJsonObject>();
305
+ DefColor->SetNumberField(TEXT("r"), VectorParam->DefaultValue.R);
306
+ DefColor->SetNumberField(TEXT("g"), VectorParam->DefaultValue.G);
307
+ DefColor->SetNumberField(TEXT("b"), VectorParam->DefaultValue.B);
308
+ DefColor->SetNumberField(TEXT("a"), VectorParam->DefaultValue.A);
309
+ ExprObj->SetObjectField(TEXT("defaultValue"), DefColor);
310
+ }
311
+ else if (UMaterialExpressionTextureSample* TexSample = Cast<UMaterialExpressionTextureSample>(Expression))
312
+ {
313
+ if (TexSample->Texture)
314
+ {
315
+ ExprObj->SetStringField(TEXT("texturePath"), TexSample->Texture->GetPathName());
316
+ }
317
+ }
318
+ else if (UMaterialExpressionConstant* ConstExpr = Cast<UMaterialExpressionConstant>(Expression))
319
+ {
320
+ ExprObj->SetNumberField(TEXT("value"), ConstExpr->R);
321
+ }
322
+ else if (UMaterialExpressionConstant3Vector* Const3Expr = Cast<UMaterialExpressionConstant3Vector>(Expression))
323
+ {
324
+ TSharedPtr<FJsonObject> ConstColor = MakeShared<FJsonObject>();
325
+ ConstColor->SetNumberField(TEXT("r"), Const3Expr->Constant.R);
326
+ ConstColor->SetNumberField(TEXT("g"), Const3Expr->Constant.G);
327
+ ConstColor->SetNumberField(TEXT("b"), Const3Expr->Constant.B);
328
+ ConstColor->SetNumberField(TEXT("a"), Const3Expr->Constant.A);
329
+ ExprObj->SetObjectField(TEXT("value"), ConstColor);
330
+ }
331
+ else if (UMaterialExpressionConstant4Vector* Const4Expr = Cast<UMaterialExpressionConstant4Vector>(Expression))
332
+ {
333
+ TSharedPtr<FJsonObject> ConstColor = MakeShared<FJsonObject>();
334
+ ConstColor->SetNumberField(TEXT("r"), Const4Expr->Constant.R);
335
+ ConstColor->SetNumberField(TEXT("g"), Const4Expr->Constant.G);
336
+ ConstColor->SetNumberField(TEXT("b"), Const4Expr->Constant.B);
337
+ ConstColor->SetNumberField(TEXT("a"), Const4Expr->Constant.A);
338
+ ExprObj->SetObjectField(TEXT("value"), ConstColor);
339
+ }
340
+
341
+ ExpressionsArray.Add(MakeShared<FJsonValueObject>(ExprObj));
342
+ Index++;
343
+ }
344
+ Result->SetArrayField(TEXT("expressions"), ExpressionsArray);
345
+ Result->SetNumberField(TEXT("expressionCount"), ExpressionsArray.Num());
346
+
347
+ // Material input connections (which expressions are wired to which material properties)
348
+ UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData();
349
+ if (EditorOnlyData)
350
+ {
351
+ TSharedPtr<FJsonObject> ConnectionsObj = MakeShared<FJsonObject>();
352
+
353
+ auto DescribeConnection = [&](const FExpressionInput& Input) -> TSharedPtr<FJsonValue>
354
+ {
355
+ if (Input.Expression)
356
+ {
357
+ TSharedPtr<FJsonObject> ConnObj = MakeShared<FJsonObject>();
358
+ ConnObj->SetStringField(TEXT("expressionClass"), Input.Expression->GetClass()->GetName());
359
+ ConnObj->SetStringField(TEXT("expressionDescription"), Input.Expression->GetDescription());
360
+ ConnObj->SetNumberField(TEXT("outputIndex"), Input.OutputIndex);
361
+
362
+ // Find the expression index
363
+ int32 ConnIdx = 0;
364
+ for (UMaterialExpression* Expr : Material->GetExpressions())
365
+ {
366
+ if (Expr == Input.Expression)
367
+ {
368
+ ConnObj->SetNumberField(TEXT("expressionIndex"), ConnIdx);
369
+ break;
370
+ }
371
+ ConnIdx++;
372
+ }
373
+ return MakeShared<FJsonValueObject>(ConnObj);
374
+ }
375
+ return MakeShared<FJsonValueNull>();
376
+ };
377
+
378
+ ConnectionsObj->SetField(TEXT("BaseColor"), DescribeConnection(EditorOnlyData->BaseColor));
379
+ ConnectionsObj->SetField(TEXT("Metallic"), DescribeConnection(EditorOnlyData->Metallic));
380
+ ConnectionsObj->SetField(TEXT("Specular"), DescribeConnection(EditorOnlyData->Specular));
381
+ ConnectionsObj->SetField(TEXT("Roughness"), DescribeConnection(EditorOnlyData->Roughness));
382
+ ConnectionsObj->SetField(TEXT("Anisotropy"), DescribeConnection(EditorOnlyData->Anisotropy));
383
+ ConnectionsObj->SetField(TEXT("EmissiveColor"), DescribeConnection(EditorOnlyData->EmissiveColor));
384
+ ConnectionsObj->SetField(TEXT("Opacity"), DescribeConnection(EditorOnlyData->Opacity));
385
+ ConnectionsObj->SetField(TEXT("OpacityMask"), DescribeConnection(EditorOnlyData->OpacityMask));
386
+ ConnectionsObj->SetField(TEXT("Normal"), DescribeConnection(EditorOnlyData->Normal));
387
+ ConnectionsObj->SetField(TEXT("Tangent"), DescribeConnection(EditorOnlyData->Tangent));
388
+ ConnectionsObj->SetField(TEXT("WorldPositionOffset"), DescribeConnection(EditorOnlyData->WorldPositionOffset));
389
+ ConnectionsObj->SetField(TEXT("SubsurfaceColor"), DescribeConnection(EditorOnlyData->SubsurfaceColor));
390
+ ConnectionsObj->SetField(TEXT("AmbientOcclusion"), DescribeConnection(EditorOnlyData->AmbientOcclusion));
391
+ ConnectionsObj->SetField(TEXT("Refraction"), DescribeConnection(EditorOnlyData->Refraction));
392
+ ConnectionsObj->SetField(TEXT("PixelDepthOffset"), DescribeConnection(EditorOnlyData->PixelDepthOffset));
393
+
394
+ Result->SetObjectField(TEXT("connections"), ConnectionsObj);
395
+ }
396
+
397
+ Result->SetBoolField(TEXT("success"), true);
398
+
399
+ return MakeShared<FJsonValueObject>(Result);
400
+ }
401
+
402
+ TSharedPtr<FJsonValue> FMaterialHandlers::SetMaterialShadingModel(const TSharedPtr<FJsonObject>& Params)
403
+ {
404
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
405
+
406
+ FString AssetPath;
407
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
408
+ {
409
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'path' or 'assetPath' parameter"));
410
+ Result->SetBoolField(TEXT("success"), false);
411
+ return MakeShared<FJsonValueObject>(Result);
412
+ }
413
+
414
+ FString ShadingModelStr;
415
+ if (!Params->TryGetStringField(TEXT("shadingModel"), ShadingModelStr) || ShadingModelStr.IsEmpty())
416
+ {
417
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'shadingModel' parameter"));
418
+ Result->SetBoolField(TEXT("success"), false);
419
+ return MakeShared<FJsonValueObject>(Result);
420
+ }
421
+
422
+ UMaterial* Material = LoadMaterialFromPath(AssetPath);
423
+ if (!Material)
424
+ {
425
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *AssetPath));
426
+ Result->SetBoolField(TEXT("success"), false);
427
+ return MakeShared<FJsonValueObject>(Result);
428
+ }
429
+
430
+ EMaterialShadingModel NewShadingModel = ParseShadingModel(ShadingModelStr);
431
+
432
+ Material->PreEditChange(nullptr);
433
+ Material->SetShadingModel(NewShadingModel);
434
+ Material->PostEditChange();
435
+
436
+ // Mark dirty and save
437
+ Material->MarkPackageDirty();
438
+
439
+ Result->SetStringField(TEXT("path"), Material->GetPathName());
440
+ Result->SetStringField(TEXT("shadingModel"), ShadingModelToString(NewShadingModel));
441
+ Result->SetBoolField(TEXT("success"), true);
442
+
443
+ return MakeShared<FJsonValueObject>(Result);
444
+ }
445
+
446
+ TSharedPtr<FJsonValue> FMaterialHandlers::SetMaterialBlendMode(const TSharedPtr<FJsonObject>& Params)
447
+ {
448
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
449
+
450
+ FString AssetPath;
451
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
452
+ {
453
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'path' or 'assetPath' parameter"));
454
+ Result->SetBoolField(TEXT("success"), false);
455
+ return MakeShared<FJsonValueObject>(Result);
456
+ }
457
+
458
+ FString BlendModeStr;
459
+ if (!Params->TryGetStringField(TEXT("blendMode"), BlendModeStr) || BlendModeStr.IsEmpty())
460
+ {
461
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'blendMode' parameter (Opaque, Masked, Translucent, Additive, Modulate, AlphaComposite, AlphaHoldout)"));
462
+ Result->SetBoolField(TEXT("success"), false);
463
+ return MakeShared<FJsonValueObject>(Result);
464
+ }
465
+
466
+ UMaterial* Material = LoadMaterialFromPath(AssetPath);
467
+ if (!Material)
468
+ {
469
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *AssetPath));
470
+ Result->SetBoolField(TEXT("success"), false);
471
+ return MakeShared<FJsonValueObject>(Result);
472
+ }
473
+
474
+ EBlendMode NewBlendMode = BLEND_Opaque;
475
+ if (BlendModeStr.Equals(TEXT("Opaque"), ESearchCase::IgnoreCase)) NewBlendMode = BLEND_Opaque;
476
+ else if (BlendModeStr.Equals(TEXT("Masked"), ESearchCase::IgnoreCase)) NewBlendMode = BLEND_Masked;
477
+ else if (BlendModeStr.Equals(TEXT("Translucent"), ESearchCase::IgnoreCase)) NewBlendMode = BLEND_Translucent;
478
+ else if (BlendModeStr.Equals(TEXT("Additive"), ESearchCase::IgnoreCase)) NewBlendMode = BLEND_Additive;
479
+ else if (BlendModeStr.Equals(TEXT("Modulate"), ESearchCase::IgnoreCase)) NewBlendMode = BLEND_Modulate;
480
+ else if (BlendModeStr.Equals(TEXT("AlphaComposite"), ESearchCase::IgnoreCase)) NewBlendMode = BLEND_AlphaComposite;
481
+ else if (BlendModeStr.Equals(TEXT("AlphaHoldout"), ESearchCase::IgnoreCase)) NewBlendMode = BLEND_AlphaHoldout;
482
+ else
483
+ {
484
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown blend mode: '%s'. Use Opaque, Masked, Translucent, Additive, Modulate, AlphaComposite, or AlphaHoldout"), *BlendModeStr));
485
+ Result->SetBoolField(TEXT("success"), false);
486
+ return MakeShared<FJsonValueObject>(Result);
487
+ }
488
+
489
+ Material->PreEditChange(nullptr);
490
+ Material->BlendMode = NewBlendMode;
491
+ Material->PostEditChange();
492
+ Material->MarkPackageDirty();
493
+
494
+ Result->SetStringField(TEXT("path"), Material->GetPathName());
495
+ Result->SetStringField(TEXT("blendMode"), BlendModeStr);
496
+ Result->SetBoolField(TEXT("success"), true);
497
+
498
+ return MakeShared<FJsonValueObject>(Result);
499
+ }
500
+
501
+ TSharedPtr<FJsonValue> FMaterialHandlers::SetMaterialBaseColor(const TSharedPtr<FJsonObject>& Params)
502
+ {
503
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
504
+
505
+ FString AssetPath;
506
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
507
+ {
508
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'path' or 'assetPath' parameter"));
509
+ Result->SetBoolField(TEXT("success"), false);
510
+ return MakeShared<FJsonValueObject>(Result);
511
+ }
512
+
513
+ const TSharedPtr<FJsonObject>* ColorObj = nullptr;
514
+ if (!Params->TryGetObjectField(TEXT("color"), ColorObj))
515
+ {
516
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'color' parameter (object with r,g,b,a)"));
517
+ Result->SetBoolField(TEXT("success"), false);
518
+ return MakeShared<FJsonValueObject>(Result);
519
+ }
520
+
521
+ double R = 1.0, G = 1.0, B = 1.0, A = 1.0;
522
+ (*ColorObj)->TryGetNumberField(TEXT("r"), R);
523
+ (*ColorObj)->TryGetNumberField(TEXT("g"), G);
524
+ (*ColorObj)->TryGetNumberField(TEXT("b"), B);
525
+ (*ColorObj)->TryGetNumberField(TEXT("a"), A);
526
+
527
+ UMaterial* Material = LoadMaterialFromPath(AssetPath);
528
+ if (!Material)
529
+ {
530
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *AssetPath));
531
+ Result->SetBoolField(TEXT("success"), false);
532
+ return MakeShared<FJsonValueObject>(Result);
533
+ }
534
+
535
+ Material->PreEditChange(nullptr);
536
+
537
+ // Create a Constant3Vector expression for the base color
538
+ UMaterialExpressionConstant3Vector* ColorExpression = NewObject<UMaterialExpressionConstant3Vector>(Material);
539
+ ColorExpression->Constant = FLinearColor(R, G, B, A);
540
+
541
+ // Add expression to material
542
+ Material->GetExpressionCollection().AddExpression(ColorExpression);
543
+
544
+ // Connect to base color input
545
+ Material->GetEditorOnlyData()->BaseColor.Connect(0, ColorExpression);
546
+
547
+ Material->PostEditChange();
548
+ Material->MarkPackageDirty();
549
+
550
+ TSharedPtr<FJsonObject> ColorResult = MakeShared<FJsonObject>();
551
+ ColorResult->SetNumberField(TEXT("r"), R);
552
+ ColorResult->SetNumberField(TEXT("g"), G);
553
+ ColorResult->SetNumberField(TEXT("b"), B);
554
+ ColorResult->SetNumberField(TEXT("a"), A);
555
+ Result->SetObjectField(TEXT("color"), ColorResult);
556
+ Result->SetStringField(TEXT("path"), Material->GetPathName());
557
+ Result->SetBoolField(TEXT("success"), true);
558
+
559
+ return MakeShared<FJsonValueObject>(Result);
560
+ }
561
+
562
+ TSharedPtr<FJsonValue> FMaterialHandlers::AddMaterialExpression(const TSharedPtr<FJsonObject>& Params)
563
+ {
564
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
565
+
566
+ FString MaterialPath;
567
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
568
+ {
569
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
570
+ Result->SetBoolField(TEXT("success"), false);
571
+ return MakeShared<FJsonValueObject>(Result);
572
+ }
573
+
574
+ FString ExpressionType;
575
+ if (!Params->TryGetStringField(TEXT("expressionType"), ExpressionType) || ExpressionType.IsEmpty())
576
+ {
577
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'expressionType' parameter"));
578
+ Result->SetBoolField(TEXT("success"), false);
579
+ return MakeShared<FJsonValueObject>(Result);
580
+ }
581
+
582
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
583
+ if (!Material)
584
+ {
585
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
586
+ Result->SetBoolField(TEXT("success"), false);
587
+ return MakeShared<FJsonValueObject>(Result);
588
+ }
589
+
590
+ // Ensure expression type has the U prefix for class lookup
591
+ FString ClassName = ExpressionType;
592
+ if (!ClassName.StartsWith(TEXT("U")))
593
+ {
594
+ ClassName = TEXT("U") + ClassName;
595
+ }
596
+
597
+ // Find the expression class
598
+ UClass* ExpressionClass = FindFirstObject<UClass>(*ClassName, EFindFirstObjectOptions::ExactClass);
599
+ if (!ExpressionClass)
600
+ {
601
+ // Try with /Script/Engine prefix
602
+ FString FullPath = FString::Printf(TEXT("/Script/Engine.%s"), *ExpressionType);
603
+ ExpressionClass = FindObject<UClass>(nullptr, *FullPath);
604
+ }
605
+
606
+ if (!ExpressionClass || !ExpressionClass->IsChildOf(UMaterialExpression::StaticClass()))
607
+ {
608
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown expression type: '%s'"), *ExpressionType));
609
+ Result->SetBoolField(TEXT("success"), false);
610
+ return MakeShared<FJsonValueObject>(Result);
611
+ }
612
+
613
+ Material->PreEditChange(nullptr);
614
+
615
+ UMaterialExpression* NewExpression = NewObject<UMaterialExpression>(Material, ExpressionClass);
616
+ Material->GetExpressionCollection().AddExpression(NewExpression);
617
+
618
+ Material->PostEditChange();
619
+ Material->MarkPackageDirty();
620
+
621
+ Result->SetStringField(TEXT("expressionType"), ExpressionType);
622
+ Result->SetStringField(TEXT("expressionClass"), NewExpression->GetClass()->GetName());
623
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
624
+ Result->SetNumberField(TEXT("expressionCount"), Material->GetExpressions().Num());
625
+ Result->SetBoolField(TEXT("success"), true);
626
+
627
+ return MakeShared<FJsonValueObject>(Result);
628
+ }
629
+
630
+ TSharedPtr<FJsonValue> FMaterialHandlers::ListMaterialExpressions(const TSharedPtr<FJsonObject>& Params)
631
+ {
632
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
633
+
634
+ FString MaterialPath;
635
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
636
+ {
637
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
638
+ Result->SetBoolField(TEXT("success"), false);
639
+ return MakeShared<FJsonValueObject>(Result);
640
+ }
641
+
642
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
643
+ if (!Material)
644
+ {
645
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
646
+ Result->SetBoolField(TEXT("success"), false);
647
+ return MakeShared<FJsonValueObject>(Result);
648
+ }
649
+
650
+ TArray<TSharedPtr<FJsonValue>> ExpressionsArray;
651
+ for (UMaterialExpression* Expression : Material->GetExpressions())
652
+ {
653
+ if (!Expression) continue;
654
+
655
+ TSharedPtr<FJsonObject> ExprObj = MakeShared<FJsonObject>();
656
+ ExprObj->SetStringField(TEXT("class"), Expression->GetClass()->GetName());
657
+ ExprObj->SetStringField(TEXT("description"), Expression->GetDescription());
658
+ ExprObj->SetNumberField(TEXT("positionX"), Expression->MaterialExpressionEditorX);
659
+ ExprObj->SetNumberField(TEXT("positionY"), Expression->MaterialExpressionEditorY);
660
+
661
+ ExpressionsArray.Add(MakeShared<FJsonValueObject>(ExprObj));
662
+ }
663
+
664
+ Result->SetArrayField(TEXT("expressions"), ExpressionsArray);
665
+ Result->SetNumberField(TEXT("count"), ExpressionsArray.Num());
666
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
667
+ Result->SetBoolField(TEXT("success"), true);
668
+
669
+ return MakeShared<FJsonValueObject>(Result);
670
+ }
671
+
672
+ TSharedPtr<FJsonValue> FMaterialHandlers::ListMaterialParameters(const TSharedPtr<FJsonObject>& Params)
673
+ {
674
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
675
+
676
+ FString AssetPath;
677
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
678
+ {
679
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'path' or 'assetPath' parameter"));
680
+ Result->SetBoolField(TEXT("success"), false);
681
+ return MakeShared<FJsonValueObject>(Result);
682
+ }
683
+
684
+ UMaterial* Material = LoadMaterialFromPath(AssetPath);
685
+ if (!Material)
686
+ {
687
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *AssetPath));
688
+ Result->SetBoolField(TEXT("success"), false);
689
+ return MakeShared<FJsonValueObject>(Result);
690
+ }
691
+
692
+ TArray<TSharedPtr<FJsonValue>> ScalarParams;
693
+ TArray<TSharedPtr<FJsonValue>> VectorParams;
694
+ TArray<TSharedPtr<FJsonValue>> TextureParams;
695
+
696
+ for (UMaterialExpression* Expression : Material->GetExpressions())
697
+ {
698
+ if (!Expression) continue;
699
+
700
+ if (UMaterialExpressionScalarParameter* ScalarParam = Cast<UMaterialExpressionScalarParameter>(Expression))
701
+ {
702
+ TSharedPtr<FJsonObject> ParamObj = MakeShared<FJsonObject>();
703
+ ParamObj->SetStringField(TEXT("name"), ScalarParam->ParameterName.ToString());
704
+ ParamObj->SetNumberField(TEXT("defaultValue"), ScalarParam->DefaultValue);
705
+ ScalarParams.Add(MakeShared<FJsonValueObject>(ParamObj));
706
+ }
707
+ else if (UMaterialExpressionVectorParameter* VectorParam = Cast<UMaterialExpressionVectorParameter>(Expression))
708
+ {
709
+ TSharedPtr<FJsonObject> ParamObj = MakeShared<FJsonObject>();
710
+ ParamObj->SetStringField(TEXT("name"), VectorParam->ParameterName.ToString());
711
+
712
+ TSharedPtr<FJsonObject> DefaultColor = MakeShared<FJsonObject>();
713
+ DefaultColor->SetNumberField(TEXT("r"), VectorParam->DefaultValue.R);
714
+ DefaultColor->SetNumberField(TEXT("g"), VectorParam->DefaultValue.G);
715
+ DefaultColor->SetNumberField(TEXT("b"), VectorParam->DefaultValue.B);
716
+ DefaultColor->SetNumberField(TEXT("a"), VectorParam->DefaultValue.A);
717
+ ParamObj->SetObjectField(TEXT("defaultValue"), DefaultColor);
718
+
719
+ VectorParams.Add(MakeShared<FJsonValueObject>(ParamObj));
720
+ }
721
+ else if (UMaterialExpressionTextureSample* TextureParam = Cast<UMaterialExpressionTextureSample>(Expression))
722
+ {
723
+ TSharedPtr<FJsonObject> ParamObj = MakeShared<FJsonObject>();
724
+ ParamObj->SetStringField(TEXT("class"), TEXT("TextureSample"));
725
+ if (TextureParam->Texture)
726
+ {
727
+ ParamObj->SetStringField(TEXT("texture"), TextureParam->Texture->GetPathName());
728
+ }
729
+ TextureParams.Add(MakeShared<FJsonValueObject>(ParamObj));
730
+ }
731
+ }
732
+
733
+ Result->SetArrayField(TEXT("scalarParameters"), ScalarParams);
734
+ Result->SetArrayField(TEXT("vectorParameters"), VectorParams);
735
+ Result->SetArrayField(TEXT("textureParameters"), TextureParams);
736
+ Result->SetStringField(TEXT("path"), Material->GetPathName());
737
+ Result->SetBoolField(TEXT("success"), true);
738
+
739
+ return MakeShared<FJsonValueObject>(Result);
740
+ }
741
+
742
+ TSharedPtr<FJsonValue> FMaterialHandlers::RecompileMaterial(const TSharedPtr<FJsonObject>& Params)
743
+ {
744
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
745
+
746
+ FString MaterialPath;
747
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
748
+ {
749
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
750
+ Result->SetBoolField(TEXT("success"), false);
751
+ return MakeShared<FJsonValueObject>(Result);
752
+ }
753
+
754
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
755
+ if (!Material)
756
+ {
757
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
758
+ Result->SetBoolField(TEXT("success"), false);
759
+ return MakeShared<FJsonValueObject>(Result);
760
+ }
761
+
762
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] Recompiling material: %s"), *MaterialPath);
763
+
764
+ Material->PreEditChange(nullptr);
765
+ Material->PostEditChange();
766
+ Material->MarkPackageDirty();
767
+
768
+ Result->SetStringField(TEXT("path"), Material->GetPathName());
769
+ Result->SetBoolField(TEXT("success"), true);
770
+
771
+ return MakeShared<FJsonValueObject>(Result);
772
+ }
773
+
774
+ TSharedPtr<FJsonValue> FMaterialHandlers::CreateMaterialInstance(const TSharedPtr<FJsonObject>& Params)
775
+ {
776
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
777
+
778
+ FString ParentPath;
779
+ if (!Params->TryGetStringField(TEXT("parentPath"), ParentPath) || ParentPath.IsEmpty())
780
+ {
781
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'parentPath' parameter"));
782
+ Result->SetBoolField(TEXT("success"), false);
783
+ return MakeShared<FJsonValueObject>(Result);
784
+ }
785
+
786
+ FString Name;
787
+ if (!Params->TryGetStringField(TEXT("name"), Name) || Name.IsEmpty())
788
+ {
789
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'name' parameter"));
790
+ Result->SetBoolField(TEXT("success"), false);
791
+ return MakeShared<FJsonValueObject>(Result);
792
+ }
793
+
794
+ FString PackagePath = TEXT("/Game/Materials");
795
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
796
+
797
+ // Load the parent material
798
+ UMaterialInterface* ParentMaterial = Cast<UMaterialInterface>(
799
+ StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *ParentPath));
800
+ if (!ParentMaterial)
801
+ {
802
+ // Try with class prefix
803
+ ParentMaterial = Cast<UMaterialInterface>(
804
+ StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *(TEXT("Material'") + ParentPath + TEXT("'"))));
805
+ }
806
+ if (!ParentMaterial)
807
+ {
808
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load parent material at '%s'"), *ParentPath));
809
+ Result->SetBoolField(TEXT("success"), false);
810
+ return MakeShared<FJsonValueObject>(Result);
811
+ }
812
+
813
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] CreateMaterialInstance: name=%s parent=%s packagePath=%s"), *Name, *ParentPath, *PackagePath);
814
+
815
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
816
+ IAssetTools& AssetTools = AssetToolsModule.Get();
817
+
818
+ UMaterialInstanceConstantFactoryNew* Factory = NewObject<UMaterialInstanceConstantFactoryNew>();
819
+ Factory->InitialParent = ParentMaterial;
820
+
821
+ UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterialInstanceConstant::StaticClass(), Factory);
822
+ if (!NewAsset)
823
+ {
824
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create material instance asset"));
825
+ Result->SetBoolField(TEXT("success"), false);
826
+ return MakeShared<FJsonValueObject>(Result);
827
+ }
828
+
829
+ UMaterialInstanceConstant* MaterialInstance = Cast<UMaterialInstanceConstant>(NewAsset);
830
+ if (!MaterialInstance)
831
+ {
832
+ Result->SetStringField(TEXT("error"), TEXT("Created asset is not a material instance"));
833
+ Result->SetBoolField(TEXT("success"), false);
834
+ return MakeShared<FJsonValueObject>(Result);
835
+ }
836
+
837
+ // Save the package
838
+ UPackage* Package = MaterialInstance->GetOutermost();
839
+ if (Package)
840
+ {
841
+ Package->MarkPackageDirty();
842
+ FString PackageFileName = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
843
+ FSavePackageArgs SaveArgs;
844
+ SaveArgs.TopLevelFlags = RF_Standalone;
845
+ UPackage::SavePackage(Package, nullptr, *PackageFileName, SaveArgs);
846
+ }
847
+
848
+ Result->SetStringField(TEXT("path"), MaterialInstance->GetPathName());
849
+ Result->SetStringField(TEXT("name"), Name);
850
+ Result->SetStringField(TEXT("parentPath"), ParentMaterial->GetPathName());
851
+ Result->SetStringField(TEXT("packagePath"), PackagePath);
852
+ Result->SetBoolField(TEXT("success"), true);
853
+
854
+ return MakeShared<FJsonValueObject>(Result);
855
+ }
856
+
857
+ TSharedPtr<FJsonValue> FMaterialHandlers::SetMaterialParameter(const TSharedPtr<FJsonObject>& Params)
858
+ {
859
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
860
+
861
+ FString AssetPath;
862
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
863
+ {
864
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'path' or 'assetPath' parameter"));
865
+ Result->SetBoolField(TEXT("success"), false);
866
+ return MakeShared<FJsonValueObject>(Result);
867
+ }
868
+
869
+ FString ParameterName;
870
+ if (!Params->TryGetStringField(TEXT("parameterName"), ParameterName) || ParameterName.IsEmpty())
871
+ {
872
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'parameterName' parameter"));
873
+ Result->SetBoolField(TEXT("success"), false);
874
+ return MakeShared<FJsonValueObject>(Result);
875
+ }
876
+
877
+ FString ParameterType;
878
+ if (!Params->TryGetStringField(TEXT("parameterType"), ParameterType) || ParameterType.IsEmpty())
879
+ {
880
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'parameterType' parameter (scalar/vector/texture)"));
881
+ Result->SetBoolField(TEXT("success"), false);
882
+ return MakeShared<FJsonValueObject>(Result);
883
+ }
884
+
885
+ UMaterialInstanceConstant* MaterialInstance = LoadMaterialInstanceFromPath(AssetPath);
886
+ if (!MaterialInstance)
887
+ {
888
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material instance at '%s'"), *AssetPath));
889
+ Result->SetBoolField(TEXT("success"), false);
890
+ return MakeShared<FJsonValueObject>(Result);
891
+ }
892
+
893
+ FString TypeLower = ParameterType.ToLower();
894
+
895
+ if (TypeLower == TEXT("scalar"))
896
+ {
897
+ double ScalarValue = 0.0;
898
+ if (!Params->TryGetNumberField(TEXT("value"), ScalarValue))
899
+ {
900
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'value' number field for scalar parameter"));
901
+ Result->SetBoolField(TEXT("success"), false);
902
+ return MakeShared<FJsonValueObject>(Result);
903
+ }
904
+
905
+ MaterialInstance->SetScalarParameterValueEditorOnly(FName(*ParameterName), static_cast<float>(ScalarValue));
906
+ MaterialInstance->MarkPackageDirty();
907
+
908
+ Result->SetStringField(TEXT("parameterName"), ParameterName);
909
+ Result->SetStringField(TEXT("parameterType"), TEXT("scalar"));
910
+ Result->SetNumberField(TEXT("value"), ScalarValue);
911
+ Result->SetStringField(TEXT("path"), MaterialInstance->GetPathName());
912
+ Result->SetBoolField(TEXT("success"), true);
913
+ }
914
+ else if (TypeLower == TEXT("vector"))
915
+ {
916
+ const TSharedPtr<FJsonObject>* ValueObj = nullptr;
917
+ if (!Params->TryGetObjectField(TEXT("value"), ValueObj))
918
+ {
919
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'value' object field (r,g,b,a) for vector parameter"));
920
+ Result->SetBoolField(TEXT("success"), false);
921
+ return MakeShared<FJsonValueObject>(Result);
922
+ }
923
+
924
+ double R = 0.0, G = 0.0, B = 0.0, A = 1.0;
925
+ (*ValueObj)->TryGetNumberField(TEXT("r"), R);
926
+ (*ValueObj)->TryGetNumberField(TEXT("g"), G);
927
+ (*ValueObj)->TryGetNumberField(TEXT("b"), B);
928
+ (*ValueObj)->TryGetNumberField(TEXT("a"), A);
929
+
930
+ FLinearColor ColorValue(R, G, B, A);
931
+ MaterialInstance->SetVectorParameterValueEditorOnly(FName(*ParameterName), ColorValue);
932
+ MaterialInstance->MarkPackageDirty();
933
+
934
+ TSharedPtr<FJsonObject> ValueResult = MakeShared<FJsonObject>();
935
+ ValueResult->SetNumberField(TEXT("r"), R);
936
+ ValueResult->SetNumberField(TEXT("g"), G);
937
+ ValueResult->SetNumberField(TEXT("b"), B);
938
+ ValueResult->SetNumberField(TEXT("a"), A);
939
+
940
+ Result->SetStringField(TEXT("parameterName"), ParameterName);
941
+ Result->SetStringField(TEXT("parameterType"), TEXT("vector"));
942
+ Result->SetObjectField(TEXT("value"), ValueResult);
943
+ Result->SetStringField(TEXT("path"), MaterialInstance->GetPathName());
944
+ Result->SetBoolField(TEXT("success"), true);
945
+ }
946
+ else if (TypeLower == TEXT("texture"))
947
+ {
948
+ FString TexturePath;
949
+ if (!Params->TryGetStringField(TEXT("value"), TexturePath) || TexturePath.IsEmpty())
950
+ {
951
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'value' string field (texture asset path) for texture parameter"));
952
+ Result->SetBoolField(TEXT("success"), false);
953
+ return MakeShared<FJsonValueObject>(Result);
954
+ }
955
+
956
+ UTexture* Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr, *TexturePath));
957
+ if (!Texture)
958
+ {
959
+ Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr,
960
+ *(TEXT("Texture2D'") + TexturePath + TEXT("'"))));
961
+ }
962
+ if (!Texture)
963
+ {
964
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load texture at '%s'"), *TexturePath));
965
+ Result->SetBoolField(TEXT("success"), false);
966
+ return MakeShared<FJsonValueObject>(Result);
967
+ }
968
+
969
+ MaterialInstance->SetTextureParameterValueEditorOnly(FName(*ParameterName), Texture);
970
+ MaterialInstance->MarkPackageDirty();
971
+
972
+ Result->SetStringField(TEXT("parameterName"), ParameterName);
973
+ Result->SetStringField(TEXT("parameterType"), TEXT("texture"));
974
+ Result->SetStringField(TEXT("value"), Texture->GetPathName());
975
+ Result->SetStringField(TEXT("path"), MaterialInstance->GetPathName());
976
+ Result->SetBoolField(TEXT("success"), true);
977
+ }
978
+ else
979
+ {
980
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown parameterType '%s'. Use 'scalar', 'vector', or 'texture'."), *ParameterType));
981
+ Result->SetBoolField(TEXT("success"), false);
982
+ }
983
+
984
+ return MakeShared<FJsonValueObject>(Result);
985
+ }
986
+
987
+ TSharedPtr<FJsonValue> FMaterialHandlers::ConnectExpression(const TSharedPtr<FJsonObject>& Params)
988
+ {
989
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
990
+
991
+ FString MaterialPath;
992
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
993
+ {
994
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
995
+ Result->SetBoolField(TEXT("success"), false);
996
+ return MakeShared<FJsonValueObject>(Result);
997
+ }
998
+
999
+ int32 SourceIndex = -1;
1000
+ if (!Params->TryGetNumberField(TEXT("sourceIndex"), SourceIndex))
1001
+ {
1002
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'sourceIndex' parameter"));
1003
+ Result->SetBoolField(TEXT("success"), false);
1004
+ return MakeShared<FJsonValueObject>(Result);
1005
+ }
1006
+
1007
+ int32 TargetIndex = -1;
1008
+ if (!Params->TryGetNumberField(TEXT("targetIndex"), TargetIndex))
1009
+ {
1010
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'targetIndex' parameter"));
1011
+ Result->SetBoolField(TEXT("success"), false);
1012
+ return MakeShared<FJsonValueObject>(Result);
1013
+ }
1014
+
1015
+ int32 SourceOutputIndex = 0;
1016
+ Params->TryGetNumberField(TEXT("sourceOutputIndex"), SourceOutputIndex);
1017
+
1018
+ int32 TargetInputIndex = 0;
1019
+ Params->TryGetNumberField(TEXT("targetInputIndex"), TargetInputIndex);
1020
+
1021
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
1022
+ if (!Material)
1023
+ {
1024
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
1025
+ Result->SetBoolField(TEXT("success"), false);
1026
+ return MakeShared<FJsonValueObject>(Result);
1027
+ }
1028
+
1029
+ auto Expressions = Material->GetExpressions();
1030
+
1031
+ if (SourceIndex < 0 || SourceIndex >= Expressions.Num())
1032
+ {
1033
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Source expression index %d out of range (0-%d)"), SourceIndex, Expressions.Num() - 1));
1034
+ Result->SetBoolField(TEXT("success"), false);
1035
+ return MakeShared<FJsonValueObject>(Result);
1036
+ }
1037
+
1038
+ if (TargetIndex < 0 || TargetIndex >= Expressions.Num())
1039
+ {
1040
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Target expression index %d out of range (0-%d)"), TargetIndex, Expressions.Num() - 1));
1041
+ Result->SetBoolField(TEXT("success"), false);
1042
+ return MakeShared<FJsonValueObject>(Result);
1043
+ }
1044
+
1045
+ UMaterialExpression* SourceExpression = Expressions[SourceIndex];
1046
+ UMaterialExpression* TargetExpression = Expressions[TargetIndex];
1047
+
1048
+ if (!SourceExpression || !TargetExpression)
1049
+ {
1050
+ Result->SetStringField(TEXT("error"), TEXT("Source or target expression is null"));
1051
+ Result->SetBoolField(TEXT("success"), false);
1052
+ return MakeShared<FJsonValueObject>(Result);
1053
+ }
1054
+
1055
+ // Validate target input index by probing GetInput()
1056
+ FExpressionInput* TargetInput = TargetExpression->GetInput(TargetInputIndex);
1057
+ if (!TargetInput)
1058
+ {
1059
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Target input index %d is out of range"), TargetInputIndex));
1060
+ Result->SetBoolField(TEXT("success"), false);
1061
+ return MakeShared<FJsonValueObject>(Result);
1062
+ }
1063
+
1064
+ Material->PreEditChange(nullptr);
1065
+ TargetInput->Connect(SourceOutputIndex, SourceExpression);
1066
+
1067
+ Material->PostEditChange();
1068
+ Material->MarkPackageDirty();
1069
+
1070
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
1071
+ Result->SetNumberField(TEXT("sourceIndex"), SourceIndex);
1072
+ Result->SetStringField(TEXT("sourceClass"), SourceExpression->GetClass()->GetName());
1073
+ Result->SetNumberField(TEXT("targetIndex"), TargetIndex);
1074
+ Result->SetStringField(TEXT("targetClass"), TargetExpression->GetClass()->GetName());
1075
+ Result->SetNumberField(TEXT("sourceOutputIndex"), SourceOutputIndex);
1076
+ Result->SetNumberField(TEXT("targetInputIndex"), TargetInputIndex);
1077
+ Result->SetBoolField(TEXT("success"), true);
1078
+
1079
+ return MakeShared<FJsonValueObject>(Result);
1080
+ }
1081
+
1082
+ TSharedPtr<FJsonValue> FMaterialHandlers::ConnectMaterialProperty(const TSharedPtr<FJsonObject>& Params)
1083
+ {
1084
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1085
+
1086
+ FString MaterialPath;
1087
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
1088
+ {
1089
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
1090
+ Result->SetBoolField(TEXT("success"), false);
1091
+ return MakeShared<FJsonValueObject>(Result);
1092
+ }
1093
+
1094
+ int32 ExpressionIndex = -1;
1095
+ if (!Params->TryGetNumberField(TEXT("expressionIndex"), ExpressionIndex))
1096
+ {
1097
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'expressionIndex' parameter"));
1098
+ Result->SetBoolField(TEXT("success"), false);
1099
+ return MakeShared<FJsonValueObject>(Result);
1100
+ }
1101
+
1102
+ FString PropertyName;
1103
+ if (!Params->TryGetStringField(TEXT("property"), PropertyName) || PropertyName.IsEmpty())
1104
+ {
1105
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'property' parameter (BaseColor, Normal, Metallic, Roughness, etc.)"));
1106
+ Result->SetBoolField(TEXT("success"), false);
1107
+ return MakeShared<FJsonValueObject>(Result);
1108
+ }
1109
+
1110
+ int32 OutputIndex = 0;
1111
+ Params->TryGetNumberField(TEXT("outputIndex"), OutputIndex);
1112
+
1113
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
1114
+ if (!Material)
1115
+ {
1116
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
1117
+ Result->SetBoolField(TEXT("success"), false);
1118
+ return MakeShared<FJsonValueObject>(Result);
1119
+ }
1120
+
1121
+ auto Expressions = Material->GetExpressions();
1122
+
1123
+ if (ExpressionIndex < 0 || ExpressionIndex >= Expressions.Num())
1124
+ {
1125
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Expression index %d out of range (0-%d)"), ExpressionIndex, Expressions.Num() - 1));
1126
+ Result->SetBoolField(TEXT("success"), false);
1127
+ return MakeShared<FJsonValueObject>(Result);
1128
+ }
1129
+
1130
+ UMaterialExpression* Expression = Expressions[ExpressionIndex];
1131
+ if (!Expression)
1132
+ {
1133
+ Result->SetStringField(TEXT("error"), TEXT("Expression at given index is null"));
1134
+ Result->SetBoolField(TEXT("success"), false);
1135
+ return MakeShared<FJsonValueObject>(Result);
1136
+ }
1137
+
1138
+ EMaterialProperty MatProperty;
1139
+ if (!ParseMaterialProperty(PropertyName, MatProperty))
1140
+ {
1141
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown material property '%s'. Available: BaseColor, Metallic, Specular, Roughness, Anisotropy, EmissiveColor, Opacity, OpacityMask, Normal, Tangent, WorldPositionOffset, SubsurfaceColor, AmbientOcclusion, Refraction, PixelDepthOffset, ShadingModel"), *PropertyName));
1142
+ Result->SetBoolField(TEXT("success"), false);
1143
+ return MakeShared<FJsonValueObject>(Result);
1144
+ }
1145
+
1146
+ Material->PreEditChange(nullptr);
1147
+
1148
+ // Get the editor-only data to access the material property inputs
1149
+ UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData();
1150
+ FExpressionInput* PropertyInput = nullptr;
1151
+
1152
+ switch (MatProperty)
1153
+ {
1154
+ case MP_BaseColor: PropertyInput = &EditorOnlyData->BaseColor; break;
1155
+ case MP_Metallic: PropertyInput = &EditorOnlyData->Metallic; break;
1156
+ case MP_Specular: PropertyInput = &EditorOnlyData->Specular; break;
1157
+ case MP_Roughness: PropertyInput = &EditorOnlyData->Roughness; break;
1158
+ case MP_Anisotropy: PropertyInput = &EditorOnlyData->Anisotropy; break;
1159
+ case MP_EmissiveColor: PropertyInput = &EditorOnlyData->EmissiveColor; break;
1160
+ case MP_Opacity: PropertyInput = &EditorOnlyData->Opacity; break;
1161
+ case MP_OpacityMask: PropertyInput = &EditorOnlyData->OpacityMask; break;
1162
+ case MP_Normal: PropertyInput = &EditorOnlyData->Normal; break;
1163
+ case MP_Tangent: PropertyInput = &EditorOnlyData->Tangent; break;
1164
+ case MP_WorldPositionOffset: PropertyInput = &EditorOnlyData->WorldPositionOffset; break;
1165
+ case MP_SubsurfaceColor: PropertyInput = &EditorOnlyData->SubsurfaceColor; break;
1166
+ case MP_AmbientOcclusion: PropertyInput = &EditorOnlyData->AmbientOcclusion; break;
1167
+ case MP_Refraction: PropertyInput = &EditorOnlyData->Refraction; break;
1168
+ case MP_PixelDepthOffset: PropertyInput = &EditorOnlyData->PixelDepthOffset; break;
1169
+ case MP_ShadingModel: PropertyInput = &EditorOnlyData->ShadingModelFromMaterialExpression; break;
1170
+ default:
1171
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Material property '%s' is not supported for direct connection"), *PropertyName));
1172
+ Result->SetBoolField(TEXT("success"), false);
1173
+ return MakeShared<FJsonValueObject>(Result);
1174
+ }
1175
+
1176
+ PropertyInput->Connect(OutputIndex, Expression);
1177
+
1178
+ Material->PostEditChange();
1179
+ Material->MarkPackageDirty();
1180
+
1181
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
1182
+ Result->SetNumberField(TEXT("expressionIndex"), ExpressionIndex);
1183
+ Result->SetStringField(TEXT("expressionClass"), Expression->GetClass()->GetName());
1184
+ Result->SetStringField(TEXT("property"), PropertyName);
1185
+ Result->SetNumberField(TEXT("outputIndex"), OutputIndex);
1186
+ Result->SetBoolField(TEXT("success"), true);
1187
+
1188
+ return MakeShared<FJsonValueObject>(Result);
1189
+ }
1190
+
1191
+ TSharedPtr<FJsonValue> FMaterialHandlers::DeleteExpression(const TSharedPtr<FJsonObject>& Params)
1192
+ {
1193
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1194
+
1195
+ FString MaterialPath;
1196
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
1197
+ {
1198
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
1199
+ Result->SetBoolField(TEXT("success"), false);
1200
+ return MakeShared<FJsonValueObject>(Result);
1201
+ }
1202
+
1203
+ int32 ExpressionIndex = -1;
1204
+ if (!Params->TryGetNumberField(TEXT("expressionIndex"), ExpressionIndex))
1205
+ {
1206
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'expressionIndex' parameter"));
1207
+ Result->SetBoolField(TEXT("success"), false);
1208
+ return MakeShared<FJsonValueObject>(Result);
1209
+ }
1210
+
1211
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
1212
+ if (!Material)
1213
+ {
1214
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
1215
+ Result->SetBoolField(TEXT("success"), false);
1216
+ return MakeShared<FJsonValueObject>(Result);
1217
+ }
1218
+
1219
+ auto Expressions = Material->GetExpressions();
1220
+
1221
+ if (ExpressionIndex < 0 || ExpressionIndex >= Expressions.Num())
1222
+ {
1223
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Expression index %d out of range (0-%d)"), ExpressionIndex, Expressions.Num() - 1));
1224
+ Result->SetBoolField(TEXT("success"), false);
1225
+ return MakeShared<FJsonValueObject>(Result);
1226
+ }
1227
+
1228
+ UMaterialExpression* Expression = Expressions[ExpressionIndex];
1229
+ if (!Expression)
1230
+ {
1231
+ Result->SetStringField(TEXT("error"), TEXT("Expression at given index is null"));
1232
+ Result->SetBoolField(TEXT("success"), false);
1233
+ return MakeShared<FJsonValueObject>(Result);
1234
+ }
1235
+
1236
+ FString DeletedClass = Expression->GetClass()->GetName();
1237
+
1238
+ Material->PreEditChange(nullptr);
1239
+
1240
+ Material->GetExpressionCollection().RemoveExpression(Expression);
1241
+
1242
+ Material->PostEditChange();
1243
+ Material->MarkPackageDirty();
1244
+
1245
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
1246
+ Result->SetNumberField(TEXT("deletedIndex"), ExpressionIndex);
1247
+ Result->SetStringField(TEXT("deletedClass"), DeletedClass);
1248
+ Result->SetNumberField(TEXT("expressionCount"), Material->GetExpressions().Num());
1249
+ Result->SetBoolField(TEXT("success"), true);
1250
+
1251
+ return MakeShared<FJsonValueObject>(Result);
1252
+ }
1253
+
1254
+ TSharedPtr<FJsonValue> FMaterialHandlers::SetExpressionValue(const TSharedPtr<FJsonObject>& Params)
1255
+ {
1256
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1257
+
1258
+ FString MaterialPath;
1259
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
1260
+ {
1261
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
1262
+ Result->SetBoolField(TEXT("success"), false);
1263
+ return MakeShared<FJsonValueObject>(Result);
1264
+ }
1265
+
1266
+ int32 ExpressionIndex = -1;
1267
+ if (!Params->TryGetNumberField(TEXT("expressionIndex"), ExpressionIndex))
1268
+ {
1269
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'expressionIndex' parameter"));
1270
+ Result->SetBoolField(TEXT("success"), false);
1271
+ return MakeShared<FJsonValueObject>(Result);
1272
+ }
1273
+
1274
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
1275
+ if (!Material)
1276
+ {
1277
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
1278
+ Result->SetBoolField(TEXT("success"), false);
1279
+ return MakeShared<FJsonValueObject>(Result);
1280
+ }
1281
+
1282
+ auto Expressions = Material->GetExpressions();
1283
+
1284
+ if (ExpressionIndex < 0 || ExpressionIndex >= Expressions.Num())
1285
+ {
1286
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Expression index %d out of range (0-%d)"), ExpressionIndex, Expressions.Num() - 1));
1287
+ Result->SetBoolField(TEXT("success"), false);
1288
+ return MakeShared<FJsonValueObject>(Result);
1289
+ }
1290
+
1291
+ UMaterialExpression* Expression = Expressions[ExpressionIndex];
1292
+ if (!Expression)
1293
+ {
1294
+ Result->SetStringField(TEXT("error"), TEXT("Expression at given index is null"));
1295
+ Result->SetBoolField(TEXT("success"), false);
1296
+ return MakeShared<FJsonValueObject>(Result);
1297
+ }
1298
+
1299
+ Material->PreEditChange(nullptr);
1300
+
1301
+ FString ExpressionClass = Expression->GetClass()->GetName();
1302
+ bool bValueSet = false;
1303
+
1304
+ // Handle UMaterialExpressionConstant - has a single float "R" value
1305
+ if (UMaterialExpressionConstant* ConstExpr = Cast<UMaterialExpressionConstant>(Expression))
1306
+ {
1307
+ double Value = 0.0;
1308
+ if (Params->TryGetNumberField(TEXT("value"), Value))
1309
+ {
1310
+ ConstExpr->R = static_cast<float>(Value);
1311
+ bValueSet = true;
1312
+ Result->SetNumberField(TEXT("value"), Value);
1313
+ }
1314
+ }
1315
+ // Handle UMaterialExpressionConstant2Vector
1316
+ else if (UMaterialExpressionConstant2Vector* Const2Expr = Cast<UMaterialExpressionConstant2Vector>(Expression))
1317
+ {
1318
+ double R = 0.0, G = 0.0;
1319
+ if (Params->TryGetNumberField(TEXT("r"), R)) { Const2Expr->R = static_cast<float>(R); bValueSet = true; }
1320
+ if (Params->TryGetNumberField(TEXT("g"), G)) { Const2Expr->G = static_cast<float>(G); bValueSet = true; }
1321
+
1322
+ if (bValueSet)
1323
+ {
1324
+ Result->SetNumberField(TEXT("r"), Const2Expr->R);
1325
+ Result->SetNumberField(TEXT("g"), Const2Expr->G);
1326
+ }
1327
+ }
1328
+ // Handle UMaterialExpressionConstant3Vector - has FLinearColor Constant
1329
+ else if (UMaterialExpressionConstant3Vector* Const3Expr = Cast<UMaterialExpressionConstant3Vector>(Expression))
1330
+ {
1331
+ const TSharedPtr<FJsonObject>* ColorObj = nullptr;
1332
+ if (Params->TryGetObjectField(TEXT("value"), ColorObj))
1333
+ {
1334
+ double R = 0.0, G = 0.0, B = 0.0, A = 1.0;
1335
+ (*ColorObj)->TryGetNumberField(TEXT("r"), R);
1336
+ (*ColorObj)->TryGetNumberField(TEXT("g"), G);
1337
+ (*ColorObj)->TryGetNumberField(TEXT("b"), B);
1338
+ (*ColorObj)->TryGetNumberField(TEXT("a"), A);
1339
+ Const3Expr->Constant = FLinearColor(R, G, B, A);
1340
+ bValueSet = true;
1341
+
1342
+ TSharedPtr<FJsonObject> ColorResult = MakeShared<FJsonObject>();
1343
+ ColorResult->SetNumberField(TEXT("r"), R);
1344
+ ColorResult->SetNumberField(TEXT("g"), G);
1345
+ ColorResult->SetNumberField(TEXT("b"), B);
1346
+ ColorResult->SetNumberField(TEXT("a"), A);
1347
+ Result->SetObjectField(TEXT("value"), ColorResult);
1348
+ }
1349
+ else
1350
+ {
1351
+ // Also support individual r, g, b, a fields directly
1352
+ double R = 0.0, G = 0.0, B = 0.0, A = 1.0;
1353
+ bool bHasR = Params->TryGetNumberField(TEXT("r"), R);
1354
+ bool bHasG = Params->TryGetNumberField(TEXT("g"), G);
1355
+ bool bHasB = Params->TryGetNumberField(TEXT("b"), B);
1356
+ Params->TryGetNumberField(TEXT("a"), A);
1357
+ if (bHasR || bHasG || bHasB)
1358
+ {
1359
+ Const3Expr->Constant = FLinearColor(R, G, B, A);
1360
+ bValueSet = true;
1361
+ Result->SetNumberField(TEXT("r"), R);
1362
+ Result->SetNumberField(TEXT("g"), G);
1363
+ Result->SetNumberField(TEXT("b"), B);
1364
+ Result->SetNumberField(TEXT("a"), A);
1365
+ }
1366
+ }
1367
+ }
1368
+ // Handle UMaterialExpressionConstant4Vector
1369
+ else if (UMaterialExpressionConstant4Vector* Const4Expr = Cast<UMaterialExpressionConstant4Vector>(Expression))
1370
+ {
1371
+ const TSharedPtr<FJsonObject>* ColorObj = nullptr;
1372
+ if (Params->TryGetObjectField(TEXT("value"), ColorObj))
1373
+ {
1374
+ double R = 0.0, G = 0.0, B = 0.0, A = 1.0;
1375
+ (*ColorObj)->TryGetNumberField(TEXT("r"), R);
1376
+ (*ColorObj)->TryGetNumberField(TEXT("g"), G);
1377
+ (*ColorObj)->TryGetNumberField(TEXT("b"), B);
1378
+ (*ColorObj)->TryGetNumberField(TEXT("a"), A);
1379
+ Const4Expr->Constant = FLinearColor(R, G, B, A);
1380
+ bValueSet = true;
1381
+
1382
+ TSharedPtr<FJsonObject> ColorResult = MakeShared<FJsonObject>();
1383
+ ColorResult->SetNumberField(TEXT("r"), R);
1384
+ ColorResult->SetNumberField(TEXT("g"), G);
1385
+ ColorResult->SetNumberField(TEXT("b"), B);
1386
+ ColorResult->SetNumberField(TEXT("a"), A);
1387
+ Result->SetObjectField(TEXT("value"), ColorResult);
1388
+ }
1389
+ else
1390
+ {
1391
+ double R = 0.0, G = 0.0, B = 0.0, A = 1.0;
1392
+ bool bHasR = Params->TryGetNumberField(TEXT("r"), R);
1393
+ bool bHasG = Params->TryGetNumberField(TEXT("g"), G);
1394
+ bool bHasB = Params->TryGetNumberField(TEXT("b"), B);
1395
+ Params->TryGetNumberField(TEXT("a"), A);
1396
+ if (bHasR || bHasG || bHasB)
1397
+ {
1398
+ Const4Expr->Constant = FLinearColor(R, G, B, A);
1399
+ bValueSet = true;
1400
+ Result->SetNumberField(TEXT("r"), R);
1401
+ Result->SetNumberField(TEXT("g"), G);
1402
+ Result->SetNumberField(TEXT("b"), B);
1403
+ Result->SetNumberField(TEXT("a"), A);
1404
+ }
1405
+ }
1406
+ }
1407
+ // Handle UMaterialExpressionScalarParameter - has float DefaultValue
1408
+ else if (UMaterialExpressionScalarParameter* ScalarParamExpr = Cast<UMaterialExpressionScalarParameter>(Expression))
1409
+ {
1410
+ double Value = 0.0;
1411
+ if (Params->TryGetNumberField(TEXT("value"), Value))
1412
+ {
1413
+ ScalarParamExpr->DefaultValue = static_cast<float>(Value);
1414
+ bValueSet = true;
1415
+ Result->SetNumberField(TEXT("value"), Value);
1416
+ }
1417
+
1418
+ FString ParamName;
1419
+ if (Params->TryGetStringField(TEXT("parameterName"), ParamName))
1420
+ {
1421
+ ScalarParamExpr->ParameterName = FName(*ParamName);
1422
+ bValueSet = true;
1423
+ Result->SetStringField(TEXT("parameterName"), ParamName);
1424
+ }
1425
+ }
1426
+ // Handle UMaterialExpressionVectorParameter - has FLinearColor DefaultValue
1427
+ else if (UMaterialExpressionVectorParameter* VectorParamExpr = Cast<UMaterialExpressionVectorParameter>(Expression))
1428
+ {
1429
+ const TSharedPtr<FJsonObject>* ValueObj = nullptr;
1430
+ if (Params->TryGetObjectField(TEXT("value"), ValueObj))
1431
+ {
1432
+ double R = 0.0, G = 0.0, B = 0.0, A = 1.0;
1433
+ (*ValueObj)->TryGetNumberField(TEXT("r"), R);
1434
+ (*ValueObj)->TryGetNumberField(TEXT("g"), G);
1435
+ (*ValueObj)->TryGetNumberField(TEXT("b"), B);
1436
+ (*ValueObj)->TryGetNumberField(TEXT("a"), A);
1437
+ VectorParamExpr->DefaultValue = FLinearColor(R, G, B, A);
1438
+ bValueSet = true;
1439
+
1440
+ TSharedPtr<FJsonObject> ColorResult = MakeShared<FJsonObject>();
1441
+ ColorResult->SetNumberField(TEXT("r"), R);
1442
+ ColorResult->SetNumberField(TEXT("g"), G);
1443
+ ColorResult->SetNumberField(TEXT("b"), B);
1444
+ ColorResult->SetNumberField(TEXT("a"), A);
1445
+ Result->SetObjectField(TEXT("value"), ColorResult);
1446
+ }
1447
+
1448
+ FString ParamName;
1449
+ if (Params->TryGetStringField(TEXT("parameterName"), ParamName))
1450
+ {
1451
+ VectorParamExpr->ParameterName = FName(*ParamName);
1452
+ bValueSet = true;
1453
+ Result->SetStringField(TEXT("parameterName"), ParamName);
1454
+ }
1455
+ }
1456
+ // Handle UMaterialExpressionTextureSample - has UTexture* Texture
1457
+ else if (UMaterialExpressionTextureSample* TexSampleExpr = Cast<UMaterialExpressionTextureSample>(Expression))
1458
+ {
1459
+ FString TexturePath;
1460
+ if (Params->TryGetStringField(TEXT("texturePath"), TexturePath))
1461
+ {
1462
+ UTexture* Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr, *TexturePath));
1463
+ if (!Texture)
1464
+ {
1465
+ Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr,
1466
+ *(TEXT("Texture2D'") + TexturePath + TEXT("'"))));
1467
+ }
1468
+ if (Texture)
1469
+ {
1470
+ TexSampleExpr->Texture = Texture;
1471
+ bValueSet = true;
1472
+ Result->SetStringField(TEXT("texturePath"), Texture->GetPathName());
1473
+ }
1474
+ else
1475
+ {
1476
+ Material->PostEditChange();
1477
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load texture at '%s'"), *TexturePath));
1478
+ Result->SetBoolField(TEXT("success"), false);
1479
+ return MakeShared<FJsonValueObject>(Result);
1480
+ }
1481
+ }
1482
+ }
1483
+ // Handle UMaterialExpressionTextureCoordinate
1484
+ else if (UMaterialExpressionTextureCoordinate* TexCoordExpr = Cast<UMaterialExpressionTextureCoordinate>(Expression))
1485
+ {
1486
+ double UTiling = 1.0, VTiling = 1.0;
1487
+ if (Params->TryGetNumberField(TEXT("uTiling"), UTiling))
1488
+ {
1489
+ TexCoordExpr->UTiling = static_cast<float>(UTiling);
1490
+ bValueSet = true;
1491
+ }
1492
+ if (Params->TryGetNumberField(TEXT("vTiling"), VTiling))
1493
+ {
1494
+ TexCoordExpr->VTiling = static_cast<float>(VTiling);
1495
+ bValueSet = true;
1496
+ }
1497
+
1498
+ int32 CoordinateIndex = 0;
1499
+ if (Params->TryGetNumberField(TEXT("coordinateIndex"), CoordinateIndex))
1500
+ {
1501
+ TexCoordExpr->CoordinateIndex = CoordinateIndex;
1502
+ bValueSet = true;
1503
+ }
1504
+
1505
+ if (bValueSet)
1506
+ {
1507
+ Result->SetNumberField(TEXT("uTiling"), TexCoordExpr->UTiling);
1508
+ Result->SetNumberField(TEXT("vTiling"), TexCoordExpr->VTiling);
1509
+ Result->SetNumberField(TEXT("coordinateIndex"), TexCoordExpr->CoordinateIndex);
1510
+ }
1511
+ }
1512
+
1513
+ if (!bValueSet)
1514
+ {
1515
+ Material->PostEditChange();
1516
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Could not set value on expression of type '%s'. Provide appropriate value parameters for this expression type."), *ExpressionClass));
1517
+ Result->SetBoolField(TEXT("success"), false);
1518
+ return MakeShared<FJsonValueObject>(Result);
1519
+ }
1520
+
1521
+ Material->PostEditChange();
1522
+ Material->MarkPackageDirty();
1523
+
1524
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
1525
+ Result->SetNumberField(TEXT("expressionIndex"), ExpressionIndex);
1526
+ Result->SetStringField(TEXT("expressionClass"), ExpressionClass);
1527
+ Result->SetBoolField(TEXT("success"), true);
1528
+
1529
+ return MakeShared<FJsonValueObject>(Result);
1530
+ }
1531
+
1532
+ TSharedPtr<FJsonValue> FMaterialHandlers::CreateMaterialFromTexture(const TSharedPtr<FJsonObject>& Params)
1533
+ {
1534
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1535
+
1536
+ FString TexturePath;
1537
+ if (!Params->TryGetStringField(TEXT("texturePath"), TexturePath) || TexturePath.IsEmpty())
1538
+ {
1539
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'texturePath' parameter"));
1540
+ Result->SetBoolField(TEXT("success"), false);
1541
+ return MakeShared<FJsonValueObject>(Result);
1542
+ }
1543
+
1544
+ FString MaterialName;
1545
+ if (!Params->TryGetStringField(TEXT("materialName"), MaterialName) || MaterialName.IsEmpty())
1546
+ {
1547
+ // Derive a material name from the texture name
1548
+ FString TextureName = FPaths::GetBaseFilename(TexturePath);
1549
+ MaterialName = TEXT("M_") + TextureName;
1550
+ }
1551
+
1552
+ FString PackagePath = TEXT("/Game/Materials");
1553
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
1554
+
1555
+ // Load the texture
1556
+ UTexture* Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr, *TexturePath));
1557
+ if (!Texture)
1558
+ {
1559
+ Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr,
1560
+ *(TEXT("Texture2D'") + TexturePath + TEXT("'"))));
1561
+ }
1562
+ if (!Texture)
1563
+ {
1564
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load texture at '%s'"), *TexturePath));
1565
+ Result->SetBoolField(TEXT("success"), false);
1566
+ return MakeShared<FJsonValueObject>(Result);
1567
+ }
1568
+
1569
+ UE_LOG(LogMCPBridge, Log, TEXT("[UE-MCP] CreateMaterialFromTexture: texture=%s materialName=%s packagePath=%s"), *TexturePath, *MaterialName, *PackagePath);
1570
+
1571
+ // Create the material
1572
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
1573
+ IAssetTools& AssetTools = AssetToolsModule.Get();
1574
+
1575
+ UMaterialFactoryNew* MaterialFactory = NewObject<UMaterialFactoryNew>();
1576
+ UObject* NewAsset = AssetTools.CreateAsset(MaterialName, PackagePath, UMaterial::StaticClass(), MaterialFactory);
1577
+
1578
+ if (!NewAsset)
1579
+ {
1580
+ Result->SetStringField(TEXT("error"), TEXT("Failed to create material asset"));
1581
+ Result->SetBoolField(TEXT("success"), false);
1582
+ return MakeShared<FJsonValueObject>(Result);
1583
+ }
1584
+
1585
+ UMaterial* NewMaterial = Cast<UMaterial>(NewAsset);
1586
+ if (!NewMaterial)
1587
+ {
1588
+ Result->SetStringField(TEXT("error"), TEXT("Created asset is not a material"));
1589
+ Result->SetBoolField(TEXT("success"), false);
1590
+ return MakeShared<FJsonValueObject>(Result);
1591
+ }
1592
+
1593
+ NewMaterial->PreEditChange(nullptr);
1594
+
1595
+ // Create a TextureSample expression
1596
+ UMaterialExpressionTextureSample* TextureSampleExpr = NewObject<UMaterialExpressionTextureSample>(NewMaterial);
1597
+ TextureSampleExpr->Texture = Texture;
1598
+ TextureSampleExpr->MaterialExpressionEditorX = -300;
1599
+ TextureSampleExpr->MaterialExpressionEditorY = 0;
1600
+
1601
+ // Add expression to material
1602
+ NewMaterial->GetExpressionCollection().AddExpression(TextureSampleExpr);
1603
+
1604
+ // Connect the RGB output (index 0) to the BaseColor input
1605
+ NewMaterial->GetEditorOnlyData()->BaseColor.Connect(0, TextureSampleExpr);
1606
+
1607
+ NewMaterial->PostEditChange();
1608
+
1609
+ // Save the package
1610
+ UPackage* Package = NewMaterial->GetOutermost();
1611
+ if (Package)
1612
+ {
1613
+ Package->MarkPackageDirty();
1614
+ FString PackageFileName = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
1615
+ FSavePackageArgs SaveArgs;
1616
+ SaveArgs.TopLevelFlags = RF_Standalone;
1617
+ UPackage::SavePackage(Package, nullptr, *PackageFileName, SaveArgs);
1618
+ }
1619
+
1620
+ Result->SetStringField(TEXT("materialPath"), NewMaterial->GetPathName());
1621
+ Result->SetStringField(TEXT("materialName"), MaterialName);
1622
+ Result->SetStringField(TEXT("texturePath"), Texture->GetPathName());
1623
+ Result->SetStringField(TEXT("packagePath"), PackagePath);
1624
+ Result->SetNumberField(TEXT("expressionCount"), NewMaterial->GetExpressions().Num());
1625
+ Result->SetBoolField(TEXT("success"), true);
1626
+
1627
+ return MakeShared<FJsonValueObject>(Result);
1628
+ }
1629
+
1630
+ TSharedPtr<FJsonValue> FMaterialHandlers::ReadMaterialInstance(const TSharedPtr<FJsonObject>& Params)
1631
+ {
1632
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1633
+
1634
+ FString AssetPath;
1635
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
1636
+ {
1637
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'path' or 'assetPath' parameter"));
1638
+ Result->SetBoolField(TEXT("success"), false);
1639
+ return MakeShared<FJsonValueObject>(Result);
1640
+ }
1641
+
1642
+ UMaterialInstanceConstant* MaterialInstance = LoadMaterialInstanceFromPath(AssetPath);
1643
+ if (!MaterialInstance)
1644
+ {
1645
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material instance at '%s'"), *AssetPath));
1646
+ Result->SetBoolField(TEXT("success"), false);
1647
+ return MakeShared<FJsonValueObject>(Result);
1648
+ }
1649
+
1650
+ Result->SetStringField(TEXT("name"), MaterialInstance->GetName());
1651
+ Result->SetStringField(TEXT("path"), MaterialInstance->GetPathName());
1652
+ Result->SetBoolField(TEXT("isMaterialInstance"), true);
1653
+
1654
+ // Parent material
1655
+ UMaterialInterface* Parent = MaterialInstance->Parent;
1656
+ if (Parent)
1657
+ {
1658
+ Result->SetStringField(TEXT("parent"), Parent->GetPathName());
1659
+ }
1660
+ else
1661
+ {
1662
+ Result->SetStringField(TEXT("parent"), TEXT(""));
1663
+ }
1664
+
1665
+ // Scalar parameter overrides
1666
+ TArray<TSharedPtr<FJsonValue>> ScalarOverrides;
1667
+ for (const FScalarParameterValue& ScalarParam : MaterialInstance->ScalarParameterValues)
1668
+ {
1669
+ TSharedPtr<FJsonObject> ParamObj = MakeShared<FJsonObject>();
1670
+ ParamObj->SetStringField(TEXT("name"), ScalarParam.ParameterInfo.Name.ToString());
1671
+ ParamObj->SetNumberField(TEXT("value"), ScalarParam.ParameterValue);
1672
+ ScalarOverrides.Add(MakeShared<FJsonValueObject>(ParamObj));
1673
+ }
1674
+
1675
+ // Vector parameter overrides
1676
+ TArray<TSharedPtr<FJsonValue>> VectorOverrides;
1677
+ for (const FVectorParameterValue& VectorParam : MaterialInstance->VectorParameterValues)
1678
+ {
1679
+ TSharedPtr<FJsonObject> ParamObj = MakeShared<FJsonObject>();
1680
+ ParamObj->SetStringField(TEXT("name"), VectorParam.ParameterInfo.Name.ToString());
1681
+
1682
+ TSharedPtr<FJsonObject> ValueObj = MakeShared<FJsonObject>();
1683
+ ValueObj->SetNumberField(TEXT("r"), VectorParam.ParameterValue.R);
1684
+ ValueObj->SetNumberField(TEXT("g"), VectorParam.ParameterValue.G);
1685
+ ValueObj->SetNumberField(TEXT("b"), VectorParam.ParameterValue.B);
1686
+ ValueObj->SetNumberField(TEXT("a"), VectorParam.ParameterValue.A);
1687
+ ParamObj->SetObjectField(TEXT("value"), ValueObj);
1688
+
1689
+ VectorOverrides.Add(MakeShared<FJsonValueObject>(ParamObj));
1690
+ }
1691
+
1692
+ // Texture parameter overrides
1693
+ TArray<TSharedPtr<FJsonValue>> TextureOverrides;
1694
+ for (const FTextureParameterValue& TextureParam : MaterialInstance->TextureParameterValues)
1695
+ {
1696
+ TSharedPtr<FJsonObject> ParamObj = MakeShared<FJsonObject>();
1697
+ ParamObj->SetStringField(TEXT("name"), TextureParam.ParameterInfo.Name.ToString());
1698
+ if (TextureParam.ParameterValue)
1699
+ {
1700
+ ParamObj->SetStringField(TEXT("value"), TextureParam.ParameterValue->GetPathName());
1701
+ }
1702
+ else
1703
+ {
1704
+ ParamObj->SetStringField(TEXT("value"), TEXT(""));
1705
+ }
1706
+ TextureOverrides.Add(MakeShared<FJsonValueObject>(ParamObj));
1707
+ }
1708
+
1709
+ Result->SetArrayField(TEXT("scalarOverrides"), ScalarOverrides);
1710
+ Result->SetArrayField(TEXT("vectorOverrides"), VectorOverrides);
1711
+ Result->SetArrayField(TEXT("textureOverrides"), TextureOverrides);
1712
+ Result->SetNumberField(TEXT("totalOverrides"), ScalarOverrides.Num() + VectorOverrides.Num() + TextureOverrides.Num());
1713
+ Result->SetBoolField(TEXT("success"), true);
1714
+
1715
+ return MakeShared<FJsonValueObject>(Result);
1716
+ }
1717
+
1718
+ UMaterialExpression* FMaterialHandlers::FindExpressionByName(UMaterial* Material, const FString& ExpressionName)
1719
+ {
1720
+ if (!Material || ExpressionName.IsEmpty()) return nullptr;
1721
+
1722
+ // Try matching by description first (most specific)
1723
+ for (UMaterialExpression* Expression : Material->GetExpressions())
1724
+ {
1725
+ if (Expression && Expression->GetDescription() == ExpressionName)
1726
+ {
1727
+ return Expression;
1728
+ }
1729
+ }
1730
+
1731
+ // Try matching by class name (with or without prefix)
1732
+ FString NameWithPrefix = ExpressionName;
1733
+ if (!NameWithPrefix.StartsWith(TEXT("MaterialExpression")))
1734
+ {
1735
+ NameWithPrefix = TEXT("MaterialExpression") + ExpressionName;
1736
+ }
1737
+
1738
+ for (UMaterialExpression* Expression : Material->GetExpressions())
1739
+ {
1740
+ if (!Expression) continue;
1741
+ FString ClassName = Expression->GetClass()->GetName();
1742
+ if (ClassName == ExpressionName || ClassName == NameWithPrefix)
1743
+ {
1744
+ return Expression;
1745
+ }
1746
+ }
1747
+
1748
+ // Try matching by parameter name for parameter expressions
1749
+ for (UMaterialExpression* Expression : Material->GetExpressions())
1750
+ {
1751
+ if (!Expression) continue;
1752
+ if (UMaterialExpressionScalarParameter* ScalarParam = Cast<UMaterialExpressionScalarParameter>(Expression))
1753
+ {
1754
+ if (ScalarParam->ParameterName.ToString() == ExpressionName)
1755
+ {
1756
+ return Expression;
1757
+ }
1758
+ }
1759
+ else if (UMaterialExpressionVectorParameter* VectorParam = Cast<UMaterialExpressionVectorParameter>(Expression))
1760
+ {
1761
+ if (VectorParam->ParameterName.ToString() == ExpressionName)
1762
+ {
1763
+ return Expression;
1764
+ }
1765
+ }
1766
+ }
1767
+
1768
+ // Try matching as an index string (e.g. "0", "1", "2")
1769
+ if (ExpressionName.IsNumeric())
1770
+ {
1771
+ int32 Idx = FCString::Atoi(*ExpressionName);
1772
+ auto Expressions = Material->GetExpressions();
1773
+ if (Idx >= 0 && Idx < Expressions.Num())
1774
+ {
1775
+ return Expressions[Idx];
1776
+ }
1777
+ }
1778
+
1779
+ return nullptr;
1780
+ }
1781
+
1782
+ TSharedPtr<FJsonValue> FMaterialHandlers::ConnectTextureToMaterial(const TSharedPtr<FJsonObject>& Params)
1783
+ {
1784
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1785
+
1786
+ FString MaterialPath;
1787
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
1788
+ {
1789
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
1790
+ Result->SetBoolField(TEXT("success"), false);
1791
+ return MakeShared<FJsonValueObject>(Result);
1792
+ }
1793
+
1794
+ FString TexturePath;
1795
+ if (!Params->TryGetStringField(TEXT("texturePath"), TexturePath) || TexturePath.IsEmpty())
1796
+ {
1797
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'texturePath' parameter"));
1798
+ Result->SetBoolField(TEXT("success"), false);
1799
+ return MakeShared<FJsonValueObject>(Result);
1800
+ }
1801
+
1802
+ FString PropertyName = TEXT("BaseColor");
1803
+ if (!Params->TryGetStringField(TEXT("property"), PropertyName))
1804
+ {
1805
+ Params->TryGetStringField(TEXT("materialProperty"), PropertyName);
1806
+ }
1807
+
1808
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
1809
+ if (!Material)
1810
+ {
1811
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
1812
+ Result->SetBoolField(TEXT("success"), false);
1813
+ return MakeShared<FJsonValueObject>(Result);
1814
+ }
1815
+
1816
+ // Load the texture
1817
+ UTexture* Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr, *TexturePath));
1818
+ if (!Texture)
1819
+ {
1820
+ Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr,
1821
+ *(TEXT("Texture2D'") + TexturePath + TEXT("'"))));
1822
+ }
1823
+ if (!Texture)
1824
+ {
1825
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load texture at '%s'"), *TexturePath));
1826
+ Result->SetBoolField(TEXT("success"), false);
1827
+ return MakeShared<FJsonValueObject>(Result);
1828
+ }
1829
+
1830
+ EMaterialProperty MatProperty;
1831
+ if (!ParseMaterialProperty(PropertyName, MatProperty))
1832
+ {
1833
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown material property '%s'"), *PropertyName));
1834
+ Result->SetBoolField(TEXT("success"), false);
1835
+ return MakeShared<FJsonValueObject>(Result);
1836
+ }
1837
+
1838
+ Material->PreEditChange(nullptr);
1839
+
1840
+ // Create a TextureSample expression
1841
+ UMaterialExpressionTextureSample* TextureSampleExpr = NewObject<UMaterialExpressionTextureSample>(Material);
1842
+ TextureSampleExpr->Texture = Texture;
1843
+ TextureSampleExpr->MaterialExpressionEditorX = -400;
1844
+ TextureSampleExpr->MaterialExpressionEditorY = 0;
1845
+
1846
+ Material->GetExpressionCollection().AddExpression(TextureSampleExpr);
1847
+
1848
+ // Connect RGB output (index 0) to the requested material property
1849
+ UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData();
1850
+ FExpressionInput* PropertyInput = nullptr;
1851
+
1852
+ switch (MatProperty)
1853
+ {
1854
+ case MP_BaseColor: PropertyInput = &EditorOnlyData->BaseColor; break;
1855
+ case MP_Metallic: PropertyInput = &EditorOnlyData->Metallic; break;
1856
+ case MP_Specular: PropertyInput = &EditorOnlyData->Specular; break;
1857
+ case MP_Roughness: PropertyInput = &EditorOnlyData->Roughness; break;
1858
+ case MP_Anisotropy: PropertyInput = &EditorOnlyData->Anisotropy; break;
1859
+ case MP_EmissiveColor: PropertyInput = &EditorOnlyData->EmissiveColor; break;
1860
+ case MP_Opacity: PropertyInput = &EditorOnlyData->Opacity; break;
1861
+ case MP_OpacityMask: PropertyInput = &EditorOnlyData->OpacityMask; break;
1862
+ case MP_Normal: PropertyInput = &EditorOnlyData->Normal; break;
1863
+ case MP_Tangent: PropertyInput = &EditorOnlyData->Tangent; break;
1864
+ case MP_WorldPositionOffset: PropertyInput = &EditorOnlyData->WorldPositionOffset; break;
1865
+ case MP_SubsurfaceColor: PropertyInput = &EditorOnlyData->SubsurfaceColor; break;
1866
+ case MP_AmbientOcclusion: PropertyInput = &EditorOnlyData->AmbientOcclusion; break;
1867
+ case MP_Refraction: PropertyInput = &EditorOnlyData->Refraction; break;
1868
+ case MP_PixelDepthOffset: PropertyInput = &EditorOnlyData->PixelDepthOffset; break;
1869
+ case MP_ShadingModel: PropertyInput = &EditorOnlyData->ShadingModelFromMaterialExpression; break;
1870
+ default: break;
1871
+ }
1872
+
1873
+ if (PropertyInput)
1874
+ {
1875
+ PropertyInput->Connect(0, TextureSampleExpr);
1876
+ }
1877
+
1878
+ Material->PostEditChange();
1879
+ Material->MarkPackageDirty();
1880
+
1881
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
1882
+ Result->SetStringField(TEXT("texturePath"), Texture->GetPathName());
1883
+ Result->SetStringField(TEXT("property"), PropertyName);
1884
+ Result->SetNumberField(TEXT("expressionCount"), Material->GetExpressions().Num());
1885
+ Result->SetBoolField(TEXT("success"), true);
1886
+
1887
+ return MakeShared<FJsonValueObject>(Result);
1888
+ }
1889
+
1890
+ TSharedPtr<FJsonValue> FMaterialHandlers::ConnectMaterialExpressions(const TSharedPtr<FJsonObject>& Params)
1891
+ {
1892
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1893
+
1894
+ FString MaterialPath;
1895
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
1896
+ {
1897
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
1898
+ Result->SetBoolField(TEXT("success"), false);
1899
+ return MakeShared<FJsonValueObject>(Result);
1900
+ }
1901
+
1902
+ FString SourceExpressionName;
1903
+ if (!Params->TryGetStringField(TEXT("sourceExpression"), SourceExpressionName) || SourceExpressionName.IsEmpty())
1904
+ {
1905
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'sourceExpression' parameter"));
1906
+ Result->SetBoolField(TEXT("success"), false);
1907
+ return MakeShared<FJsonValueObject>(Result);
1908
+ }
1909
+
1910
+ FString TargetExpressionName;
1911
+ if (!Params->TryGetStringField(TEXT("targetExpression"), TargetExpressionName) || TargetExpressionName.IsEmpty())
1912
+ {
1913
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'targetExpression' parameter"));
1914
+ Result->SetBoolField(TEXT("success"), false);
1915
+ return MakeShared<FJsonValueObject>(Result);
1916
+ }
1917
+
1918
+ // Source/target output/input can be specified by name or index
1919
+ FString SourceOutputName;
1920
+ Params->TryGetStringField(TEXT("sourceOutput"), SourceOutputName);
1921
+ FString TargetInputName;
1922
+ Params->TryGetStringField(TEXT("targetInput"), TargetInputName);
1923
+
1924
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
1925
+ if (!Material)
1926
+ {
1927
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
1928
+ Result->SetBoolField(TEXT("success"), false);
1929
+ return MakeShared<FJsonValueObject>(Result);
1930
+ }
1931
+
1932
+ UMaterialExpression* SourceExpression = FindExpressionByName(Material, SourceExpressionName);
1933
+ if (!SourceExpression)
1934
+ {
1935
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Source expression '%s' not found"), *SourceExpressionName));
1936
+ Result->SetBoolField(TEXT("success"), false);
1937
+ return MakeShared<FJsonValueObject>(Result);
1938
+ }
1939
+
1940
+ UMaterialExpression* TargetExpression = FindExpressionByName(Material, TargetExpressionName);
1941
+ if (!TargetExpression)
1942
+ {
1943
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Target expression '%s' not found"), *TargetExpressionName));
1944
+ Result->SetBoolField(TEXT("success"), false);
1945
+ return MakeShared<FJsonValueObject>(Result);
1946
+ }
1947
+
1948
+ // Resolve source output index
1949
+ int32 SourceOutputIndex = 0;
1950
+ if (!SourceOutputName.IsEmpty())
1951
+ {
1952
+ if (SourceOutputName.IsNumeric())
1953
+ {
1954
+ SourceOutputIndex = FCString::Atoi(*SourceOutputName);
1955
+ }
1956
+ else
1957
+ {
1958
+ // Try to find named output
1959
+ TArray<FExpressionOutput>& Outputs = SourceExpression->GetOutputs();
1960
+ for (int32 i = 0; i < Outputs.Num(); i++)
1961
+ {
1962
+ if (Outputs[i].OutputName.ToString().Equals(SourceOutputName, ESearchCase::IgnoreCase))
1963
+ {
1964
+ SourceOutputIndex = i;
1965
+ break;
1966
+ }
1967
+ }
1968
+ }
1969
+ }
1970
+
1971
+ // Resolve target input index
1972
+ int32 TargetInputIndex = 0;
1973
+ if (!TargetInputName.IsEmpty())
1974
+ {
1975
+ if (TargetInputName.IsNumeric())
1976
+ {
1977
+ TargetInputIndex = FCString::Atoi(*TargetInputName);
1978
+ }
1979
+ else
1980
+ {
1981
+ // Try to find named input
1982
+ for (int32 i = 0; ; i++)
1983
+ {
1984
+ FExpressionInput* Input = TargetExpression->GetInput(i);
1985
+ if (!Input) break;
1986
+ FName InputName = TargetExpression->GetInputName(i);
1987
+ if (InputName.ToString().Equals(TargetInputName, ESearchCase::IgnoreCase))
1988
+ {
1989
+ TargetInputIndex = i;
1990
+ break;
1991
+ }
1992
+ }
1993
+ }
1994
+ }
1995
+
1996
+ FExpressionInput* TargetInput = TargetExpression->GetInput(TargetInputIndex);
1997
+ if (!TargetInput)
1998
+ {
1999
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Target input index %d is out of range"), TargetInputIndex));
2000
+ Result->SetBoolField(TEXT("success"), false);
2001
+ return MakeShared<FJsonValueObject>(Result);
2002
+ }
2003
+
2004
+ Material->PreEditChange(nullptr);
2005
+ TargetInput->Connect(SourceOutputIndex, SourceExpression);
2006
+ Material->PostEditChange();
2007
+ Material->MarkPackageDirty();
2008
+
2009
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
2010
+ Result->SetStringField(TEXT("sourceExpression"), SourceExpression->GetClass()->GetName());
2011
+ Result->SetStringField(TEXT("targetExpression"), TargetExpression->GetClass()->GetName());
2012
+ Result->SetNumberField(TEXT("sourceOutputIndex"), SourceOutputIndex);
2013
+ Result->SetNumberField(TEXT("targetInputIndex"), TargetInputIndex);
2014
+ Result->SetBoolField(TEXT("success"), true);
2015
+
2016
+ return MakeShared<FJsonValueObject>(Result);
2017
+ }
2018
+
2019
+ TSharedPtr<FJsonValue> FMaterialHandlers::ConnectToMaterialProperty(const TSharedPtr<FJsonObject>& Params)
2020
+ {
2021
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
2022
+
2023
+ FString MaterialPath;
2024
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
2025
+ {
2026
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
2027
+ Result->SetBoolField(TEXT("success"), false);
2028
+ return MakeShared<FJsonValueObject>(Result);
2029
+ }
2030
+
2031
+ FString ExpressionName;
2032
+ if (!Params->TryGetStringField(TEXT("expressionName"), ExpressionName) || ExpressionName.IsEmpty())
2033
+ {
2034
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'expressionName' parameter"));
2035
+ Result->SetBoolField(TEXT("success"), false);
2036
+ return MakeShared<FJsonValueObject>(Result);
2037
+ }
2038
+
2039
+ FString PropertyName;
2040
+ if (!Params->TryGetStringField(TEXT("property"), PropertyName) || PropertyName.IsEmpty())
2041
+ {
2042
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'property' parameter (BaseColor, Normal, Metallic, Roughness, etc.)"));
2043
+ Result->SetBoolField(TEXT("success"), false);
2044
+ return MakeShared<FJsonValueObject>(Result);
2045
+ }
2046
+
2047
+ FString OutputName;
2048
+ Params->TryGetStringField(TEXT("outputName"), OutputName);
2049
+
2050
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
2051
+ if (!Material)
2052
+ {
2053
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
2054
+ Result->SetBoolField(TEXT("success"), false);
2055
+ return MakeShared<FJsonValueObject>(Result);
2056
+ }
2057
+
2058
+ UMaterialExpression* Expression = FindExpressionByName(Material, ExpressionName);
2059
+ if (!Expression)
2060
+ {
2061
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Expression '%s' not found"), *ExpressionName));
2062
+ Result->SetBoolField(TEXT("success"), false);
2063
+ return MakeShared<FJsonValueObject>(Result);
2064
+ }
2065
+
2066
+ // Resolve output index
2067
+ int32 OutputIndex = 0;
2068
+ if (!OutputName.IsEmpty())
2069
+ {
2070
+ if (OutputName.IsNumeric())
2071
+ {
2072
+ OutputIndex = FCString::Atoi(*OutputName);
2073
+ }
2074
+ else
2075
+ {
2076
+ TArray<FExpressionOutput>& Outputs = Expression->GetOutputs();
2077
+ for (int32 i = 0; i < Outputs.Num(); i++)
2078
+ {
2079
+ if (Outputs[i].OutputName.ToString().Equals(OutputName, ESearchCase::IgnoreCase))
2080
+ {
2081
+ OutputIndex = i;
2082
+ break;
2083
+ }
2084
+ }
2085
+ }
2086
+ }
2087
+
2088
+ EMaterialProperty MatProperty;
2089
+ if (!ParseMaterialProperty(PropertyName, MatProperty))
2090
+ {
2091
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Unknown material property '%s'"), *PropertyName));
2092
+ Result->SetBoolField(TEXT("success"), false);
2093
+ return MakeShared<FJsonValueObject>(Result);
2094
+ }
2095
+
2096
+ Material->PreEditChange(nullptr);
2097
+
2098
+ UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData();
2099
+ FExpressionInput* PropertyInput = nullptr;
2100
+
2101
+ switch (MatProperty)
2102
+ {
2103
+ case MP_BaseColor: PropertyInput = &EditorOnlyData->BaseColor; break;
2104
+ case MP_Metallic: PropertyInput = &EditorOnlyData->Metallic; break;
2105
+ case MP_Specular: PropertyInput = &EditorOnlyData->Specular; break;
2106
+ case MP_Roughness: PropertyInput = &EditorOnlyData->Roughness; break;
2107
+ case MP_Anisotropy: PropertyInput = &EditorOnlyData->Anisotropy; break;
2108
+ case MP_EmissiveColor: PropertyInput = &EditorOnlyData->EmissiveColor; break;
2109
+ case MP_Opacity: PropertyInput = &EditorOnlyData->Opacity; break;
2110
+ case MP_OpacityMask: PropertyInput = &EditorOnlyData->OpacityMask; break;
2111
+ case MP_Normal: PropertyInput = &EditorOnlyData->Normal; break;
2112
+ case MP_Tangent: PropertyInput = &EditorOnlyData->Tangent; break;
2113
+ case MP_WorldPositionOffset: PropertyInput = &EditorOnlyData->WorldPositionOffset; break;
2114
+ case MP_SubsurfaceColor: PropertyInput = &EditorOnlyData->SubsurfaceColor; break;
2115
+ case MP_AmbientOcclusion: PropertyInput = &EditorOnlyData->AmbientOcclusion; break;
2116
+ case MP_Refraction: PropertyInput = &EditorOnlyData->Refraction; break;
2117
+ case MP_PixelDepthOffset: PropertyInput = &EditorOnlyData->PixelDepthOffset; break;
2118
+ case MP_ShadingModel: PropertyInput = &EditorOnlyData->ShadingModelFromMaterialExpression; break;
2119
+ default:
2120
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Material property '%s' is not supported for direct connection"), *PropertyName));
2121
+ Result->SetBoolField(TEXT("success"), false);
2122
+ return MakeShared<FJsonValueObject>(Result);
2123
+ }
2124
+
2125
+ PropertyInput->Connect(OutputIndex, Expression);
2126
+
2127
+ Material->PostEditChange();
2128
+ Material->MarkPackageDirty();
2129
+
2130
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
2131
+ Result->SetStringField(TEXT("expressionName"), ExpressionName);
2132
+ Result->SetStringField(TEXT("expressionClass"), Expression->GetClass()->GetName());
2133
+ Result->SetStringField(TEXT("property"), PropertyName);
2134
+ Result->SetNumberField(TEXT("outputIndex"), OutputIndex);
2135
+ Result->SetBoolField(TEXT("success"), true);
2136
+
2137
+ return MakeShared<FJsonValueObject>(Result);
2138
+ }
2139
+
2140
+ TSharedPtr<FJsonValue> FMaterialHandlers::DeleteMaterialExpression(const TSharedPtr<FJsonObject>& Params)
2141
+ {
2142
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
2143
+
2144
+ FString MaterialPath;
2145
+ if ((!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) && !Params->TryGetStringField(TEXT("path"), MaterialPath) && !Params->TryGetStringField(TEXT("assetPath"), MaterialPath)) || MaterialPath.IsEmpty())
2146
+ {
2147
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath', 'path', or 'assetPath' parameter"));
2148
+ Result->SetBoolField(TEXT("success"), false);
2149
+ return MakeShared<FJsonValueObject>(Result);
2150
+ }
2151
+
2152
+ FString ExpressionName;
2153
+ if (!Params->TryGetStringField(TEXT("expressionName"), ExpressionName) || ExpressionName.IsEmpty())
2154
+ {
2155
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'expressionName' parameter"));
2156
+ Result->SetBoolField(TEXT("success"), false);
2157
+ return MakeShared<FJsonValueObject>(Result);
2158
+ }
2159
+
2160
+ UMaterial* Material = LoadMaterialFromPath(MaterialPath);
2161
+ if (!Material)
2162
+ {
2163
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
2164
+ Result->SetBoolField(TEXT("success"), false);
2165
+ return MakeShared<FJsonValueObject>(Result);
2166
+ }
2167
+
2168
+ UMaterialExpression* Expression = FindExpressionByName(Material, ExpressionName);
2169
+ if (!Expression)
2170
+ {
2171
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Expression '%s' not found"), *ExpressionName));
2172
+ Result->SetBoolField(TEXT("success"), false);
2173
+ return MakeShared<FJsonValueObject>(Result);
2174
+ }
2175
+
2176
+ FString DeletedClass = Expression->GetClass()->GetName();
2177
+
2178
+ Material->PreEditChange(nullptr);
2179
+ Material->GetExpressionCollection().RemoveExpression(Expression);
2180
+ Material->PostEditChange();
2181
+ Material->MarkPackageDirty();
2182
+
2183
+ Result->SetStringField(TEXT("materialPath"), Material->GetPathName());
2184
+ Result->SetStringField(TEXT("deletedExpression"), ExpressionName);
2185
+ Result->SetStringField(TEXT("deletedClass"), DeletedClass);
2186
+ Result->SetNumberField(TEXT("expressionCount"), Material->GetExpressions().Num());
2187
+ Result->SetBoolField(TEXT("success"), true);
2188
+
2189
+ return MakeShared<FJsonValueObject>(Result);
2190
+ }