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,1773 @@
1
+ #include "AssetHandlers.h"
2
+ #include "HandlerRegistry.h"
3
+ #include "AssetRegistry/AssetRegistryModule.h"
4
+ #include "AssetRegistry/IAssetRegistry.h"
5
+ #include "UObject/UObjectGlobals.h"
6
+ #include "UObject/UnrealType.h"
7
+ #include "EditorScriptingUtilities/Public/EditorAssetLibrary.h"
8
+ #include "UObject/Package.h"
9
+ #include "Misc/PackageName.h"
10
+ #include "Misc/Paths.h"
11
+ #include "Dom/JsonObject.h"
12
+ #include "Dom/JsonValue.h"
13
+ #include "UObject/TopLevelAssetPath.h"
14
+
15
+ // DataTable
16
+ #include "Engine/DataTable.h"
17
+ #include "Factories/DataTableFactory.h"
18
+ #include "Kismet/DataTableFunctionLibrary.h"
19
+ #include "Serialization/JsonReader.h"
20
+ #include "Serialization/JsonSerializer.h"
21
+ #include "Misc/FileHelper.h"
22
+
23
+ // Import tasks
24
+ #include "AssetImportTask.h"
25
+ #include "AssetToolsModule.h"
26
+ #include "IAssetTools.h"
27
+
28
+ // FBX
29
+ #include "Factories/FbxFactory.h"
30
+ #include "Factories/FbxImportUI.h"
31
+ #include "Factories/FbxStaticMeshImportData.h"
32
+ #include "Factories/FbxSkeletalMeshImportData.h"
33
+ #include "Factories/FbxAnimSequenceImportData.h"
34
+
35
+ // Texture
36
+ #include "Engine/Texture2D.h"
37
+ #include "Factories/TextureFactory.h"
38
+
39
+ void FAssetHandlers::RegisterHandlers(FMCPHandlerRegistry& Registry)
40
+ {
41
+ Registry.RegisterHandler(TEXT("list_assets"), &ListAssets);
42
+ Registry.RegisterHandler(TEXT("search_assets"), &SearchAssets);
43
+ Registry.RegisterHandler(TEXT("read_asset"), &ReadAsset);
44
+ Registry.RegisterHandler(TEXT("read_asset_properties"), &ReadAssetProperties);
45
+ Registry.RegisterHandler(TEXT("duplicate_asset"), &DuplicateAsset);
46
+ Registry.RegisterHandler(TEXT("rename_asset"), &RenameAsset);
47
+ Registry.RegisterHandler(TEXT("move_asset"), &MoveAsset);
48
+ Registry.RegisterHandler(TEXT("delete_asset"), &DeleteAsset);
49
+ Registry.RegisterHandler(TEXT("save_asset"), &SaveAsset);
50
+ Registry.RegisterHandler(TEXT("list_textures"), &ListTextures);
51
+
52
+ // DataTable handlers
53
+ Registry.RegisterHandler(TEXT("import_datatable_json"), &ImportDataTableJson);
54
+ Registry.RegisterHandler(TEXT("export_datatable_json"), &ExportDataTableJson);
55
+
56
+ // FBX import handlers
57
+ Registry.RegisterHandler(TEXT("import_static_mesh"), &ImportStaticMesh);
58
+ Registry.RegisterHandler(TEXT("import_skeletal_mesh"), &ImportSkeletalMesh);
59
+ Registry.RegisterHandler(TEXT("import_animation"), &ImportAnimation);
60
+
61
+ // Texture handlers
62
+ Registry.RegisterHandler(TEXT("list_texture_properties"), &ListTextureProperties);
63
+ Registry.RegisterHandler(TEXT("set_texture_properties"), &SetTextureProperties);
64
+ Registry.RegisterHandler(TEXT("import_texture"), &ImportTexture);
65
+
66
+ // Aliases for TS tool compatibility
67
+ Registry.RegisterHandler(TEXT("get_texture_info"), &ListTextureProperties);
68
+ Registry.RegisterHandler(TEXT("set_texture_settings"), &SetTextureProperties);
69
+
70
+ // Mesh handlers
71
+ Registry.RegisterHandler(TEXT("set_mesh_material"), &SetMeshMaterial);
72
+ Registry.RegisterHandler(TEXT("recenter_pivot"), &RecenterPivot);
73
+
74
+ // Additional DataTable handlers
75
+ Registry.RegisterHandler(TEXT("create_datatable"), &CreateDataTable);
76
+ Registry.RegisterHandler(TEXT("read_datatable"), &ReadDataTable);
77
+ Registry.RegisterHandler(TEXT("reimport_datatable"), &ReimportDataTable);
78
+ }
79
+
80
+ TSharedPtr<FJsonValue> FAssetHandlers::ListAssets(const TSharedPtr<FJsonObject>& Params)
81
+ {
82
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
83
+
84
+ FString Query = TEXT("*");
85
+ Params->TryGetStringField(TEXT("query"), Query);
86
+
87
+ FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
88
+ IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
89
+
90
+ TArray<FAssetData> AssetDataList;
91
+ AssetRegistry.GetAllAssets(AssetDataList);
92
+
93
+ TArray<TSharedPtr<FJsonValue>> AssetsArray;
94
+ for (const FAssetData& AssetData : AssetDataList)
95
+ {
96
+ FString AssetPath = AssetData.GetObjectPathString();
97
+ if (Query == TEXT("*") || AssetPath.Contains(Query))
98
+ {
99
+ TSharedPtr<FJsonObject> AssetObj = MakeShared<FJsonObject>();
100
+ AssetObj->SetStringField(TEXT("path"), AssetPath);
101
+ AssetObj->SetStringField(TEXT("className"), AssetData.AssetClassPath.GetAssetName().ToString());
102
+ AssetObj->SetStringField(TEXT("name"), AssetData.AssetName.ToString());
103
+ AssetsArray.Add(MakeShared<FJsonValueObject>(AssetObj));
104
+ }
105
+ }
106
+
107
+ Result->SetArrayField(TEXT("assets"), AssetsArray);
108
+ Result->SetNumberField(TEXT("count"), AssetsArray.Num());
109
+ Result->SetBoolField(TEXT("success"), true);
110
+
111
+ return MakeShared<FJsonValueObject>(Result);
112
+ }
113
+
114
+ TSharedPtr<FJsonValue> FAssetHandlers::SearchAssets(const TSharedPtr<FJsonObject>& Params)
115
+ {
116
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
117
+
118
+ FString Query;
119
+ Params->TryGetStringField(TEXT("query"), Query);
120
+ FString Directory;
121
+ bool bHasDirectory = Params->TryGetStringField(TEXT("directory"), Directory);
122
+ if (!bHasDirectory)
123
+ {
124
+ Directory = TEXT("/Game/");
125
+ }
126
+ int32 MaxResults = 50;
127
+ Params->TryGetNumberField(TEXT("maxResults"), MaxResults);
128
+ bool bSearchAll = false;
129
+ Params->TryGetBoolField(TEXT("searchAll"), bSearchAll);
130
+
131
+ // Use AssetRegistry for global search when searchAll is true or directory contains wildcards
132
+ if (bSearchAll || (!bHasDirectory && !Query.IsEmpty() && Query.Contains(TEXT("*"))))
133
+ {
134
+ // Use the AssetRegistry for indexed search across all content roots
135
+ IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
136
+ FARFilter Filter;
137
+ Filter.bRecursivePaths = true;
138
+
139
+ // If a directory is explicitly provided with searchAll, scope to that directory
140
+ if (bHasDirectory)
141
+ {
142
+ Filter.PackagePaths.Add(FName(*Directory));
143
+ }
144
+
145
+ TArray<FAssetData> AllAssets;
146
+ AssetRegistry.GetAssets(Filter, AllAssets);
147
+
148
+ TArray<TSharedPtr<FJsonValue>> ResultsArray;
149
+ FString QueryLower = Query.ToLower();
150
+ for (const FAssetData& AssetData : AllAssets)
151
+ {
152
+ if (ResultsArray.Num() >= MaxResults) break;
153
+ FString AssetPath = AssetData.GetObjectPathString();
154
+ FString AssetName = AssetData.AssetName.ToString();
155
+ if (!Query.IsEmpty())
156
+ {
157
+ // Support wildcard matching
158
+ if (Query.Contains(TEXT("*")))
159
+ {
160
+ if (!AssetPath.MatchesWildcard(Query))
161
+ {
162
+ continue;
163
+ }
164
+ }
165
+ else if (!AssetPath.ToLower().Contains(QueryLower) && !AssetName.ToLower().Contains(QueryLower))
166
+ {
167
+ continue;
168
+ }
169
+ }
170
+
171
+ TSharedPtr<FJsonObject> Item = MakeShared<FJsonObject>();
172
+ Item->SetStringField(TEXT("path"), AssetData.PackageName.ToString());
173
+ Item->SetStringField(TEXT("name"), AssetName);
174
+ Item->SetStringField(TEXT("className"), AssetData.AssetClassPath.GetAssetName().ToString());
175
+ ResultsArray.Add(MakeShared<FJsonValueObject>(Item));
176
+ }
177
+
178
+ Result->SetStringField(TEXT("query"), Query);
179
+ Result->SetStringField(TEXT("searchScope"), bHasDirectory ? Directory : TEXT("all"));
180
+ Result->SetNumberField(TEXT("resultCount"), ResultsArray.Num());
181
+ Result->SetArrayField(TEXT("results"), ResultsArray);
182
+ Result->SetBoolField(TEXT("success"), true);
183
+ return MakeShared<FJsonValueObject>(Result);
184
+ }
185
+
186
+ // Default: directory-based search (original behavior)
187
+ TArray<FString> AssetPaths = UEditorAssetLibrary::ListAssets(Directory, true, false);
188
+
189
+ TArray<TSharedPtr<FJsonValue>> ResultsArray;
190
+ FString QueryLower = Query.ToLower();
191
+ for (const FString& AssetPath : AssetPaths)
192
+ {
193
+ if (ResultsArray.Num() >= MaxResults) break;
194
+ if (!Query.IsEmpty() && !AssetPath.ToLower().Contains(QueryLower)) continue;
195
+
196
+ TSharedPtr<FJsonObject> Item = MakeShared<FJsonObject>();
197
+ Item->SetStringField(TEXT("path"), AssetPath);
198
+ FString Name;
199
+ int32 SlashIdx = 0;
200
+ if (AssetPath.FindLastChar(TEXT('/'), SlashIdx))
201
+ {
202
+ Name = AssetPath.Mid(SlashIdx + 1);
203
+ int32 DotIdx = 0;
204
+ if (Name.FindChar(TEXT('.'), DotIdx))
205
+ {
206
+ Name = Name.Left(DotIdx);
207
+ }
208
+ }
209
+ else
210
+ {
211
+ Name = AssetPath;
212
+ }
213
+ Item->SetStringField(TEXT("name"), Name);
214
+
215
+ FAssetData AssetData = UEditorAssetLibrary::FindAssetData(AssetPath);
216
+ if (AssetData.IsValid())
217
+ {
218
+ Item->SetStringField(TEXT("className"), AssetData.AssetClassPath.GetAssetName().ToString());
219
+ }
220
+ ResultsArray.Add(MakeShared<FJsonValueObject>(Item));
221
+ }
222
+
223
+ Result->SetStringField(TEXT("query"), Query);
224
+ Result->SetStringField(TEXT("directory"), Directory);
225
+ Result->SetNumberField(TEXT("resultCount"), ResultsArray.Num());
226
+ Result->SetArrayField(TEXT("results"), ResultsArray);
227
+ Result->SetBoolField(TEXT("success"), true);
228
+
229
+ return MakeShared<FJsonValueObject>(Result);
230
+ }
231
+
232
+ TSharedPtr<FJsonValue> FAssetHandlers::ReadAsset(const TSharedPtr<FJsonObject>& Params)
233
+ {
234
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
235
+
236
+ FString AssetPath;
237
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath))
238
+ {
239
+ Params->TryGetStringField(TEXT("assetPath"), AssetPath);
240
+ }
241
+ if (AssetPath.IsEmpty())
242
+ {
243
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
244
+ Result->SetBoolField(TEXT("success"), false);
245
+ return MakeShared<FJsonValueObject>(Result);
246
+ }
247
+
248
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
249
+ if (!Asset)
250
+ {
251
+ // Fallback to LoadObject for full object paths
252
+ Asset = LoadObject<UObject>(nullptr, *AssetPath);
253
+ }
254
+ if (!Asset)
255
+ {
256
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
257
+ Result->SetBoolField(TEXT("success"), false);
258
+ return MakeShared<FJsonValueObject>(Result);
259
+ }
260
+
261
+ Result->SetStringField(TEXT("path"), AssetPath);
262
+ Result->SetStringField(TEXT("className"), Asset->GetClass()->GetName());
263
+ Result->SetStringField(TEXT("objectName"), Asset->GetName());
264
+
265
+ // Read properties via reflection
266
+ TSharedPtr<FJsonObject> PropertiesObj = MakeShared<FJsonObject>();
267
+ for (TFieldIterator<FProperty> It(Asset->GetClass()); It; ++It)
268
+ {
269
+ FProperty* Prop = *It;
270
+ if (!Prop) continue;
271
+
272
+ // Skip editor-only internal properties that aren't useful
273
+ if (Prop->HasAnyPropertyFlags(CPF_Transient | CPF_DuplicateTransient)) continue;
274
+
275
+ const FString PropName = Prop->GetName();
276
+ const void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Asset);
277
+
278
+ if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
279
+ {
280
+ PropertiesObj->SetBoolField(PropName, BoolProp->GetPropertyValue(ValuePtr));
281
+ }
282
+ else if (FIntProperty* IntProp = CastField<FIntProperty>(Prop))
283
+ {
284
+ PropertiesObj->SetNumberField(PropName, IntProp->GetPropertyValue(ValuePtr));
285
+ }
286
+ else if (FInt64Property* Int64Prop = CastField<FInt64Property>(Prop))
287
+ {
288
+ PropertiesObj->SetNumberField(PropName, static_cast<double>(Int64Prop->GetPropertyValue(ValuePtr)));
289
+ }
290
+ else if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Prop))
291
+ {
292
+ PropertiesObj->SetNumberField(PropName, FloatProp->GetPropertyValue(ValuePtr));
293
+ }
294
+ else if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(Prop))
295
+ {
296
+ PropertiesObj->SetNumberField(PropName, DoubleProp->GetPropertyValue(ValuePtr));
297
+ }
298
+ else if (FStrProperty* StrProp = CastField<FStrProperty>(Prop))
299
+ {
300
+ PropertiesObj->SetStringField(PropName, StrProp->GetPropertyValue(ValuePtr));
301
+ }
302
+ else if (FNameProperty* NameProp = CastField<FNameProperty>(Prop))
303
+ {
304
+ PropertiesObj->SetStringField(PropName, NameProp->GetPropertyValue(ValuePtr).ToString());
305
+ }
306
+ else if (FTextProperty* TextProp = CastField<FTextProperty>(Prop))
307
+ {
308
+ PropertiesObj->SetStringField(PropName, TextProp->GetPropertyValue(ValuePtr).ToString());
309
+ }
310
+ else if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
311
+ {
312
+ FNumericProperty* UnderlyingProp = EnumProp->GetUnderlyingProperty();
313
+ int64 EnumValue = UnderlyingProp->GetSignedIntPropertyValue(ValuePtr);
314
+ if (UEnum* Enum = EnumProp->GetEnum())
315
+ {
316
+ FString EnumName = Enum->GetNameStringByValue(EnumValue);
317
+ PropertiesObj->SetStringField(PropName, EnumName);
318
+ }
319
+ else
320
+ {
321
+ PropertiesObj->SetNumberField(PropName, static_cast<double>(EnumValue));
322
+ }
323
+ }
324
+ else if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
325
+ {
326
+ if (ByteProp->Enum)
327
+ {
328
+ uint8 ByteVal = ByteProp->GetPropertyValue(ValuePtr);
329
+ FString EnumName = ByteProp->Enum->GetNameStringByValue(ByteVal);
330
+ PropertiesObj->SetStringField(PropName, EnumName);
331
+ }
332
+ else
333
+ {
334
+ PropertiesObj->SetNumberField(PropName, ByteProp->GetPropertyValue(ValuePtr));
335
+ }
336
+ }
337
+ else if (FObjectProperty* ObjProp = CastField<FObjectProperty>(Prop))
338
+ {
339
+ UObject* RefObj = ObjProp->GetPropertyValue(ValuePtr);
340
+ if (RefObj)
341
+ {
342
+ PropertiesObj->SetStringField(PropName, RefObj->GetPathName());
343
+ }
344
+ else
345
+ {
346
+ PropertiesObj->SetField(PropName, MakeShared<FJsonValueNull>());
347
+ }
348
+ }
349
+ else if (FSoftObjectProperty* SoftObjProp = CastField<FSoftObjectProperty>(Prop))
350
+ {
351
+ FSoftObjectPtr SoftPtr = SoftObjProp->GetPropertyValue(ValuePtr);
352
+ PropertiesObj->SetStringField(PropName, SoftPtr.ToString());
353
+ }
354
+ else
355
+ {
356
+ // For complex types, export as string
357
+ FString ValueStr;
358
+ Prop->ExportTextItem_Direct(ValueStr, ValuePtr, nullptr, nullptr, PPF_None);
359
+ if (!ValueStr.IsEmpty())
360
+ {
361
+ PropertiesObj->SetStringField(PropName, ValueStr);
362
+ }
363
+ else
364
+ {
365
+ PropertiesObj->SetStringField(PropName, FString::Printf(TEXT("<%s>"), *Prop->GetCPPType()));
366
+ }
367
+ }
368
+ }
369
+
370
+ Result->SetObjectField(TEXT("properties"), PropertiesObj);
371
+ Result->SetBoolField(TEXT("success"), true);
372
+
373
+ return MakeShared<FJsonValueObject>(Result);
374
+ }
375
+
376
+ TSharedPtr<FJsonValue> FAssetHandlers::ReadAssetProperties(const TSharedPtr<FJsonObject>& Params)
377
+ {
378
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
379
+
380
+ FString AssetPath;
381
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath))
382
+ {
383
+ Params->TryGetStringField(TEXT("assetPath"), AssetPath);
384
+ }
385
+ if (AssetPath.IsEmpty())
386
+ {
387
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
388
+ Result->SetBoolField(TEXT("success"), false);
389
+ return MakeShared<FJsonValueObject>(Result);
390
+ }
391
+
392
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
393
+ if (!Asset)
394
+ {
395
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
396
+ Result->SetBoolField(TEXT("success"), false);
397
+ return MakeShared<FJsonValueObject>(Result);
398
+ }
399
+
400
+ FString PropertyName;
401
+ if (Params->TryGetStringField(TEXT("propertyName"), PropertyName) && !PropertyName.IsEmpty())
402
+ {
403
+ FProperty* Prop = Asset->GetClass()->FindPropertyByName(*PropertyName);
404
+ if (!Prop)
405
+ {
406
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Property not found: %s"), *PropertyName));
407
+ Result->SetBoolField(TEXT("success"), false);
408
+ return MakeShared<FJsonValueObject>(Result);
409
+ }
410
+ // Basic serialization - extend as needed
411
+ Result->SetStringField(TEXT("path"), AssetPath);
412
+ Result->SetStringField(TEXT("propertyName"), PropertyName);
413
+ Result->SetStringField(TEXT("type"), Prop->GetCPPType());
414
+ Result->SetBoolField(TEXT("success"), true);
415
+ return MakeShared<FJsonValueObject>(Result);
416
+ }
417
+
418
+ TArray<TSharedPtr<FJsonValue>> PropsArray;
419
+ for (TFieldIterator<FProperty> It(Asset->GetClass()); It; ++It)
420
+ {
421
+ TSharedPtr<FJsonObject> P = MakeShared<FJsonObject>();
422
+ P->SetStringField(TEXT("name"), (*It)->GetName());
423
+ P->SetStringField(TEXT("type"), (*It)->GetCPPType());
424
+ PropsArray.Add(MakeShared<FJsonValueObject>(P));
425
+ }
426
+ Result->SetStringField(TEXT("path"), AssetPath);
427
+ Result->SetStringField(TEXT("className"), Asset->GetClass()->GetName());
428
+ Result->SetNumberField(TEXT("propertyCount"), PropsArray.Num());
429
+ Result->SetArrayField(TEXT("properties"), PropsArray);
430
+ Result->SetBoolField(TEXT("success"), true);
431
+
432
+ return MakeShared<FJsonValueObject>(Result);
433
+ }
434
+
435
+ TSharedPtr<FJsonValue> FAssetHandlers::DuplicateAsset(const TSharedPtr<FJsonObject>& Params)
436
+ {
437
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
438
+
439
+ FString SourcePath, DestPath;
440
+ if (!Params->TryGetStringField(TEXT("sourcePath"), SourcePath) || !Params->TryGetStringField(TEXT("destinationPath"), DestPath))
441
+ {
442
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'sourcePath' and 'destinationPath'"));
443
+ Result->SetBoolField(TEXT("success"), false);
444
+ return MakeShared<FJsonValueObject>(Result);
445
+ }
446
+
447
+ if (!UEditorAssetLibrary::DoesAssetExist(SourcePath))
448
+ {
449
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Source asset not found: %s"), *SourcePath));
450
+ Result->SetBoolField(TEXT("success"), false);
451
+ return MakeShared<FJsonValueObject>(Result);
452
+ }
453
+
454
+ UObject* Dup = UEditorAssetLibrary::DuplicateAsset(SourcePath, DestPath);
455
+ Result->SetStringField(TEXT("sourcePath"), SourcePath);
456
+ Result->SetStringField(TEXT("destinationPath"), DestPath);
457
+ Result->SetBoolField(TEXT("success"), Dup != nullptr);
458
+
459
+ return MakeShared<FJsonValueObject>(Result);
460
+ }
461
+
462
+ TSharedPtr<FJsonValue> FAssetHandlers::RenameAsset(const TSharedPtr<FJsonObject>& Params)
463
+ {
464
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
465
+
466
+ FString SourcePath, DestPath;
467
+ if (Params->TryGetStringField(TEXT("sourcePath"), SourcePath) && Params->TryGetStringField(TEXT("destinationPath"), DestPath))
468
+ {
469
+ // Use sourcePath/destinationPath directly
470
+ }
471
+ else
472
+ {
473
+ FString AssetPath, NewName;
474
+ if (Params->TryGetStringField(TEXT("assetPath"), AssetPath) && Params->TryGetStringField(TEXT("newName"), NewName))
475
+ {
476
+ SourcePath = AssetPath;
477
+ FString PackageName, AssetName;
478
+ AssetPath.Split(TEXT("."), &PackageName, &AssetName, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
479
+ FString ParentDir = FPaths::GetPath(PackageName);
480
+ if (ParentDir.IsEmpty()) ParentDir = PackageName;
481
+ DestPath = FString::Printf(TEXT("%s/%s.%s"), *ParentDir, *NewName, *NewName);
482
+ }
483
+ }
484
+
485
+ if (SourcePath.IsEmpty() || DestPath.IsEmpty())
486
+ {
487
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'sourcePath'+'destinationPath' or 'assetPath'+'newName'"));
488
+ Result->SetBoolField(TEXT("success"), false);
489
+ return MakeShared<FJsonValueObject>(Result);
490
+ }
491
+
492
+ if (!UEditorAssetLibrary::DoesAssetExist(SourcePath))
493
+ {
494
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *SourcePath));
495
+ Result->SetBoolField(TEXT("success"), false);
496
+ return MakeShared<FJsonValueObject>(Result);
497
+ }
498
+
499
+ bool bOk = UEditorAssetLibrary::RenameAsset(SourcePath, DestPath);
500
+ Result->SetStringField(TEXT("sourcePath"), SourcePath);
501
+ Result->SetStringField(TEXT("destinationPath"), DestPath);
502
+ Result->SetBoolField(TEXT("success"), bOk);
503
+
504
+ return MakeShared<FJsonValueObject>(Result);
505
+ }
506
+
507
+ TSharedPtr<FJsonValue> FAssetHandlers::MoveAsset(const TSharedPtr<FJsonObject>& Params)
508
+ {
509
+ // Move is equivalent to Rename in UE
510
+ return RenameAsset(Params);
511
+ }
512
+
513
+ TSharedPtr<FJsonValue> FAssetHandlers::DeleteAsset(const TSharedPtr<FJsonObject>& Params)
514
+ {
515
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
516
+
517
+ FString AssetPath;
518
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath) && !Params->TryGetStringField(TEXT("assetPath"), AssetPath))
519
+ {
520
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
521
+ Result->SetBoolField(TEXT("success"), false);
522
+ return MakeShared<FJsonValueObject>(Result);
523
+ }
524
+
525
+ bool bSuccess = UEditorAssetLibrary::DeleteAsset(AssetPath);
526
+ Result->SetStringField(TEXT("path"), AssetPath);
527
+ Result->SetBoolField(TEXT("success"), bSuccess);
528
+
529
+ return MakeShared<FJsonValueObject>(Result);
530
+ }
531
+
532
+ TSharedPtr<FJsonValue> FAssetHandlers::SaveAsset(const TSharedPtr<FJsonObject>& Params)
533
+ {
534
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
535
+
536
+ FString AssetPath;
537
+ if ((Params->TryGetStringField(TEXT("path"), AssetPath) || Params->TryGetStringField(TEXT("assetPath"), AssetPath)) && !AssetPath.IsEmpty() && AssetPath != TEXT("all"))
538
+ {
539
+ bool bSuccess = UEditorAssetLibrary::SaveAsset(AssetPath);
540
+ Result->SetStringField(TEXT("path"), AssetPath);
541
+ Result->SetBoolField(TEXT("success"), bSuccess);
542
+ }
543
+ else
544
+ {
545
+ // Save all dirty assets
546
+ UEditorAssetLibrary::SaveDirectory(TEXT("/Game"));
547
+ Result->SetStringField(TEXT("message"), TEXT("All modified assets saved"));
548
+ Result->SetBoolField(TEXT("success"), true);
549
+ }
550
+
551
+ return MakeShared<FJsonValueObject>(Result);
552
+ }
553
+
554
+ TSharedPtr<FJsonValue> FAssetHandlers::ListTextures(const TSharedPtr<FJsonObject>& Params)
555
+ {
556
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
557
+
558
+ FString Directory = TEXT("/Game/");
559
+ Params->TryGetStringField(TEXT("directory"), Directory);
560
+ int32 MaxResults = 50;
561
+ Params->TryGetNumberField(TEXT("maxResults"), MaxResults);
562
+
563
+ FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
564
+ IAssetRegistry& AssetRegistry = AssetRegistryModule.Get();
565
+
566
+ TArray<FAssetData> AssetDataList;
567
+ AssetRegistry.GetAssetsByClass(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("Texture2D")), AssetDataList, true);
568
+
569
+ TArray<TSharedPtr<FJsonValue>> TexturesArray;
570
+ for (const FAssetData& AssetData : AssetDataList)
571
+ {
572
+ if (TexturesArray.Num() >= MaxResults) break;
573
+ FString AssetPath = AssetData.GetObjectPathString();
574
+ if (!AssetPath.StartsWith(Directory)) continue;
575
+
576
+ TSharedPtr<FJsonObject> TexObj = MakeShared<FJsonObject>();
577
+ TexObj->SetStringField(TEXT("name"), AssetData.AssetName.ToString());
578
+ TexObj->SetStringField(TEXT("path"), AssetPath);
579
+ TexturesArray.Add(MakeShared<FJsonValueObject>(TexObj));
580
+ }
581
+
582
+ Result->SetArrayField(TEXT("textures"), TexturesArray);
583
+ Result->SetNumberField(TEXT("count"), TexturesArray.Num());
584
+ Result->SetBoolField(TEXT("success"), true);
585
+ return MakeShared<FJsonValueObject>(Result);
586
+ }
587
+
588
+ // ============================================================================
589
+ // DataTable handlers
590
+ // ============================================================================
591
+
592
+ TSharedPtr<FJsonValue> FAssetHandlers::ImportDataTableJson(const TSharedPtr<FJsonObject>& Params)
593
+ {
594
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
595
+
596
+ FString AssetPath;
597
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
598
+ {
599
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
600
+ Result->SetBoolField(TEXT("success"), false);
601
+ return MakeShared<FJsonValueObject>(Result);
602
+ }
603
+
604
+ FString JsonString;
605
+ if (!Params->TryGetStringField(TEXT("jsonString"), JsonString))
606
+ {
607
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'jsonString' parameter"));
608
+ Result->SetBoolField(TEXT("success"), false);
609
+ return MakeShared<FJsonValueObject>(Result);
610
+ }
611
+
612
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
613
+ if (!Asset)
614
+ {
615
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
616
+ Result->SetBoolField(TEXT("success"), false);
617
+ return MakeShared<FJsonValueObject>(Result);
618
+ }
619
+
620
+ UDataTable* DataTable = Cast<UDataTable>(Asset);
621
+ if (!DataTable)
622
+ {
623
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
624
+ Result->SetBoolField(TEXT("success"), false);
625
+ return MakeShared<FJsonValueObject>(Result);
626
+ }
627
+
628
+ TArray<FString> Errors = DataTable->CreateTableFromJSONString(JsonString);
629
+
630
+ if (Errors.Num() > 0)
631
+ {
632
+ TArray<TSharedPtr<FJsonValue>> ErrorsArray;
633
+ for (const FString& Error : Errors)
634
+ {
635
+ ErrorsArray.Add(MakeShared<FJsonValueString>(Error));
636
+ }
637
+ Result->SetArrayField(TEXT("errors"), ErrorsArray);
638
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Import completed with %d error(s)"), Errors.Num()));
639
+ Result->SetBoolField(TEXT("success"), false);
640
+ return MakeShared<FJsonValueObject>(Result);
641
+ }
642
+
643
+ DataTable->MarkPackageDirty();
644
+
645
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
646
+ Result->SetNumberField(TEXT("rowCount"), DataTable->GetRowMap().Num());
647
+ Result->SetStringField(TEXT("message"), TEXT("DataTable imported successfully from JSON"));
648
+ Result->SetBoolField(TEXT("success"), true);
649
+
650
+ return MakeShared<FJsonValueObject>(Result);
651
+ }
652
+
653
+ TSharedPtr<FJsonValue> FAssetHandlers::ExportDataTableJson(const TSharedPtr<FJsonObject>& Params)
654
+ {
655
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
656
+
657
+ FString AssetPath;
658
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath))
659
+ {
660
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
661
+ Result->SetBoolField(TEXT("success"), false);
662
+ return MakeShared<FJsonValueObject>(Result);
663
+ }
664
+
665
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
666
+ if (!Asset)
667
+ {
668
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
669
+ Result->SetBoolField(TEXT("success"), false);
670
+ return MakeShared<FJsonValueObject>(Result);
671
+ }
672
+
673
+ UDataTable* DataTable = Cast<UDataTable>(Asset);
674
+ if (!DataTable)
675
+ {
676
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
677
+ Result->SetBoolField(TEXT("success"), false);
678
+ return MakeShared<FJsonValueObject>(Result);
679
+ }
680
+
681
+ FString JsonString = DataTable->GetTableAsJSON(EDataTableExportFlags::UseJsonObjectsForStructs);
682
+
683
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
684
+ Result->SetStringField(TEXT("json"), JsonString);
685
+ Result->SetNumberField(TEXT("rowCount"), DataTable->GetRowMap().Num());
686
+ Result->SetBoolField(TEXT("success"), true);
687
+
688
+ return MakeShared<FJsonValueObject>(Result);
689
+ }
690
+
691
+ // ============================================================================
692
+ // FBX import handlers
693
+ // ============================================================================
694
+
695
+ TSharedPtr<FJsonValue> FAssetHandlers::ImportStaticMesh(const TSharedPtr<FJsonObject>& Params)
696
+ {
697
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
698
+
699
+ FString FileName;
700
+ if (!Params->TryGetStringField(TEXT("filename"), FileName))
701
+ {
702
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'filename' parameter (path to FBX file)"));
703
+ Result->SetBoolField(TEXT("success"), false);
704
+ return MakeShared<FJsonValueObject>(Result);
705
+ }
706
+
707
+ FString DestinationPath = TEXT("/Game/Meshes");
708
+ Params->TryGetStringField(TEXT("destinationPath"), DestinationPath);
709
+
710
+ if (!FPaths::FileExists(FileName))
711
+ {
712
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("File not found: %s"), *FileName));
713
+ Result->SetBoolField(TEXT("success"), false);
714
+ return MakeShared<FJsonValueObject>(Result);
715
+ }
716
+
717
+ UFbxFactory* FbxFactory = NewObject<UFbxFactory>();
718
+ FbxFactory->AddToRoot();
719
+
720
+ UFbxImportUI* ImportUI = NewObject<UFbxImportUI>();
721
+ ImportUI->bImportMesh = true;
722
+ ImportUI->bImportAnimations = false;
723
+ ImportUI->bImportMaterials = true;
724
+ ImportUI->bImportTextures = true;
725
+ ImportUI->bIsObjImport = false;
726
+ ImportUI->MeshTypeToImport = FBXIT_StaticMesh;
727
+
728
+ // Apply optional settings
729
+ bool bImportMaterials = true;
730
+ if (Params->TryGetBoolField(TEXT("importMaterials"), bImportMaterials))
731
+ {
732
+ ImportUI->bImportMaterials = bImportMaterials;
733
+ }
734
+ bool bImportTextures = true;
735
+ if (Params->TryGetBoolField(TEXT("importTextures"), bImportTextures))
736
+ {
737
+ ImportUI->bImportTextures = bImportTextures;
738
+ }
739
+ bool bCombineMeshes = false;
740
+ if (Params->TryGetBoolField(TEXT("combineMeshes"), bCombineMeshes))
741
+ {
742
+ ImportUI->StaticMeshImportData->bCombineMeshes = bCombineMeshes;
743
+ }
744
+ bool bGenerateLightmapUVs = true;
745
+ if (Params->TryGetBoolField(TEXT("generateLightmapUVs"), bGenerateLightmapUVs))
746
+ {
747
+ ImportUI->StaticMeshImportData->bGenerateLightmapUVs = bGenerateLightmapUVs;
748
+ }
749
+
750
+ FbxFactory->ImportUI = ImportUI;
751
+
752
+ UAssetImportTask* Task = NewObject<UAssetImportTask>();
753
+ Task->AddToRoot();
754
+ Task->bAutomated = true;
755
+ Task->bReplaceExisting = true;
756
+ Task->bSave = false;
757
+ Task->Filename = FileName;
758
+ Task->DestinationPath = DestinationPath;
759
+ Task->Factory = FbxFactory;
760
+
761
+ // Optional asset name
762
+ FString AssetName;
763
+ if (Params->TryGetStringField(TEXT("assetName"), AssetName) && !AssetName.IsEmpty())
764
+ {
765
+ Task->DestinationName = AssetName;
766
+ }
767
+
768
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
769
+ TArray<UAssetImportTask*> Tasks;
770
+ Tasks.Add(Task);
771
+ AssetToolsModule.Get().ImportAssetTasks(Tasks);
772
+
773
+ TArray<TSharedPtr<FJsonValue>> ImportedPaths;
774
+ if (Task->GetObjects().Num() > 0)
775
+ {
776
+ for (UObject* ImportedObj : Task->GetObjects())
777
+ {
778
+ if (ImportedObj)
779
+ {
780
+ FString ObjPath = ImportedObj->GetPathName();
781
+ ImportedPaths.Add(MakeShared<FJsonValueString>(ObjPath));
782
+ }
783
+ }
784
+ }
785
+
786
+ Result->SetStringField(TEXT("filename"), FileName);
787
+ Result->SetStringField(TEXT("destinationPath"), DestinationPath);
788
+ Result->SetArrayField(TEXT("importedAssets"), ImportedPaths);
789
+ Result->SetNumberField(TEXT("importedCount"), ImportedPaths.Num());
790
+ Result->SetBoolField(TEXT("success"), ImportedPaths.Num() > 0);
791
+ if (ImportedPaths.Num() == 0)
792
+ {
793
+ Result->SetStringField(TEXT("error"), TEXT("Import task completed but no assets were produced"));
794
+ }
795
+
796
+ Task->RemoveFromRoot();
797
+ FbxFactory->RemoveFromRoot();
798
+
799
+ return MakeShared<FJsonValueObject>(Result);
800
+ }
801
+
802
+ TSharedPtr<FJsonValue> FAssetHandlers::ImportSkeletalMesh(const TSharedPtr<FJsonObject>& Params)
803
+ {
804
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
805
+
806
+ FString FileName;
807
+ if (!Params->TryGetStringField(TEXT("filename"), FileName))
808
+ {
809
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'filename' parameter (path to FBX file)"));
810
+ Result->SetBoolField(TEXT("success"), false);
811
+ return MakeShared<FJsonValueObject>(Result);
812
+ }
813
+
814
+ FString DestinationPath = TEXT("/Game/Meshes");
815
+ Params->TryGetStringField(TEXT("destinationPath"), DestinationPath);
816
+
817
+ if (!FPaths::FileExists(FileName))
818
+ {
819
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("File not found: %s"), *FileName));
820
+ Result->SetBoolField(TEXT("success"), false);
821
+ return MakeShared<FJsonValueObject>(Result);
822
+ }
823
+
824
+ UFbxFactory* FbxFactory = NewObject<UFbxFactory>();
825
+ FbxFactory->AddToRoot();
826
+
827
+ UFbxImportUI* ImportUI = NewObject<UFbxImportUI>();
828
+ ImportUI->bImportMesh = true;
829
+ ImportUI->bImportAnimations = false;
830
+ ImportUI->bImportMaterials = true;
831
+ ImportUI->bImportTextures = true;
832
+ ImportUI->bIsObjImport = false;
833
+ ImportUI->MeshTypeToImport = FBXIT_SkeletalMesh;
834
+
835
+ // Apply optional settings
836
+ bool bImportMaterials = true;
837
+ if (Params->TryGetBoolField(TEXT("importMaterials"), bImportMaterials))
838
+ {
839
+ ImportUI->bImportMaterials = bImportMaterials;
840
+ }
841
+ bool bImportTextures = true;
842
+ if (Params->TryGetBoolField(TEXT("importTextures"), bImportTextures))
843
+ {
844
+ ImportUI->bImportTextures = bImportTextures;
845
+ }
846
+
847
+ // Optionally set an existing skeleton
848
+ FString SkeletonPath;
849
+ if (Params->TryGetStringField(TEXT("skeletonPath"), SkeletonPath) && !SkeletonPath.IsEmpty())
850
+ {
851
+ USkeleton* Skeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
852
+ if (Skeleton)
853
+ {
854
+ ImportUI->Skeleton = Skeleton;
855
+ }
856
+ else
857
+ {
858
+ Result->SetStringField(TEXT("warning"), FString::Printf(TEXT("Skeleton not found: %s, importing without skeleton target"), *SkeletonPath));
859
+ }
860
+ }
861
+
862
+ FbxFactory->ImportUI = ImportUI;
863
+
864
+ UAssetImportTask* Task = NewObject<UAssetImportTask>();
865
+ Task->AddToRoot();
866
+ Task->bAutomated = true;
867
+ Task->bReplaceExisting = true;
868
+ Task->bSave = false;
869
+ Task->Filename = FileName;
870
+ Task->DestinationPath = DestinationPath;
871
+ Task->Factory = FbxFactory;
872
+
873
+ // Optional asset name
874
+ FString AssetName;
875
+ if (Params->TryGetStringField(TEXT("assetName"), AssetName) && !AssetName.IsEmpty())
876
+ {
877
+ Task->DestinationName = AssetName;
878
+ }
879
+
880
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
881
+ TArray<UAssetImportTask*> Tasks;
882
+ Tasks.Add(Task);
883
+ AssetToolsModule.Get().ImportAssetTasks(Tasks);
884
+
885
+ TArray<TSharedPtr<FJsonValue>> ImportedPaths;
886
+ if (Task->GetObjects().Num() > 0)
887
+ {
888
+ for (UObject* ImportedObj : Task->GetObjects())
889
+ {
890
+ if (ImportedObj)
891
+ {
892
+ FString ObjPath = ImportedObj->GetPathName();
893
+ ImportedPaths.Add(MakeShared<FJsonValueString>(ObjPath));
894
+ }
895
+ }
896
+ }
897
+
898
+ Result->SetStringField(TEXT("filename"), FileName);
899
+ Result->SetStringField(TEXT("destinationPath"), DestinationPath);
900
+ Result->SetArrayField(TEXT("importedAssets"), ImportedPaths);
901
+ Result->SetNumberField(TEXT("importedCount"), ImportedPaths.Num());
902
+ Result->SetBoolField(TEXT("success"), ImportedPaths.Num() > 0);
903
+ if (ImportedPaths.Num() == 0)
904
+ {
905
+ Result->SetStringField(TEXT("error"), TEXT("Import task completed but no assets were produced"));
906
+ }
907
+
908
+ Task->RemoveFromRoot();
909
+ FbxFactory->RemoveFromRoot();
910
+
911
+ return MakeShared<FJsonValueObject>(Result);
912
+ }
913
+
914
+ TSharedPtr<FJsonValue> FAssetHandlers::ImportAnimation(const TSharedPtr<FJsonObject>& Params)
915
+ {
916
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
917
+
918
+ FString FileName;
919
+ if (!Params->TryGetStringField(TEXT("filename"), FileName))
920
+ {
921
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'filename' parameter (path to FBX file)"));
922
+ Result->SetBoolField(TEXT("success"), false);
923
+ return MakeShared<FJsonValueObject>(Result);
924
+ }
925
+
926
+ FString SkeletonPath;
927
+ if (!Params->TryGetStringField(TEXT("skeletonPath"), SkeletonPath))
928
+ {
929
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'skeletonPath' parameter (path to target Skeleton asset)"));
930
+ Result->SetBoolField(TEXT("success"), false);
931
+ return MakeShared<FJsonValueObject>(Result);
932
+ }
933
+
934
+ FString DestinationPath = TEXT("/Game/Animations");
935
+ Params->TryGetStringField(TEXT("destinationPath"), DestinationPath);
936
+
937
+ if (!FPaths::FileExists(FileName))
938
+ {
939
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("File not found: %s"), *FileName));
940
+ Result->SetBoolField(TEXT("success"), false);
941
+ return MakeShared<FJsonValueObject>(Result);
942
+ }
943
+
944
+ USkeleton* Skeleton = LoadObject<USkeleton>(nullptr, *SkeletonPath);
945
+ if (!Skeleton)
946
+ {
947
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Skeleton not found: %s"), *SkeletonPath));
948
+ Result->SetBoolField(TEXT("success"), false);
949
+ return MakeShared<FJsonValueObject>(Result);
950
+ }
951
+
952
+ UFbxFactory* FbxFactory = NewObject<UFbxFactory>();
953
+ FbxFactory->AddToRoot();
954
+
955
+ UFbxImportUI* ImportUI = NewObject<UFbxImportUI>();
956
+ ImportUI->bImportMesh = false;
957
+ ImportUI->bImportAnimations = true;
958
+ ImportUI->bImportMaterials = false;
959
+ ImportUI->bImportTextures = false;
960
+ ImportUI->bIsObjImport = false;
961
+ ImportUI->MeshTypeToImport = FBXIT_Animation;
962
+ ImportUI->Skeleton = Skeleton;
963
+
964
+ // Apply optional animation settings
965
+ bool bImportCustomAttribute = true;
966
+ if (Params->TryGetBoolField(TEXT("importCustomAttribute"), bImportCustomAttribute))
967
+ {
968
+ ImportUI->AnimSequenceImportData->bImportCustomAttribute = bImportCustomAttribute;
969
+ }
970
+ bool bRemoveRedundantKeys = true;
971
+ if (Params->TryGetBoolField(TEXT("removeRedundantKeys"), bRemoveRedundantKeys))
972
+ {
973
+ ImportUI->AnimSequenceImportData->bRemoveRedundantKeys = bRemoveRedundantKeys;
974
+ }
975
+
976
+ FbxFactory->ImportUI = ImportUI;
977
+
978
+ UAssetImportTask* Task = NewObject<UAssetImportTask>();
979
+ Task->AddToRoot();
980
+ Task->bAutomated = true;
981
+ Task->bReplaceExisting = true;
982
+ Task->bSave = false;
983
+ Task->Filename = FileName;
984
+ Task->DestinationPath = DestinationPath;
985
+ Task->Factory = FbxFactory;
986
+
987
+ // Optional asset name
988
+ FString AssetName;
989
+ if (Params->TryGetStringField(TEXT("assetName"), AssetName) && !AssetName.IsEmpty())
990
+ {
991
+ Task->DestinationName = AssetName;
992
+ }
993
+
994
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
995
+ TArray<UAssetImportTask*> Tasks;
996
+ Tasks.Add(Task);
997
+ AssetToolsModule.Get().ImportAssetTasks(Tasks);
998
+
999
+ TArray<TSharedPtr<FJsonValue>> ImportedPaths;
1000
+ if (Task->GetObjects().Num() > 0)
1001
+ {
1002
+ for (UObject* ImportedObj : Task->GetObjects())
1003
+ {
1004
+ if (ImportedObj)
1005
+ {
1006
+ FString ObjPath = ImportedObj->GetPathName();
1007
+ ImportedPaths.Add(MakeShared<FJsonValueString>(ObjPath));
1008
+ }
1009
+ }
1010
+ }
1011
+
1012
+ Result->SetStringField(TEXT("filename"), FileName);
1013
+ Result->SetStringField(TEXT("skeletonPath"), SkeletonPath);
1014
+ Result->SetStringField(TEXT("destinationPath"), DestinationPath);
1015
+ Result->SetArrayField(TEXT("importedAssets"), ImportedPaths);
1016
+ Result->SetNumberField(TEXT("importedCount"), ImportedPaths.Num());
1017
+ Result->SetBoolField(TEXT("success"), ImportedPaths.Num() > 0);
1018
+ if (ImportedPaths.Num() == 0)
1019
+ {
1020
+ Result->SetStringField(TEXT("error"), TEXT("Import task completed but no assets were produced"));
1021
+ }
1022
+
1023
+ Task->RemoveFromRoot();
1024
+ FbxFactory->RemoveFromRoot();
1025
+
1026
+ return MakeShared<FJsonValueObject>(Result);
1027
+ }
1028
+
1029
+ // ============================================================================
1030
+ // Texture handlers
1031
+ // ============================================================================
1032
+
1033
+ TSharedPtr<FJsonValue> FAssetHandlers::ListTextureProperties(const TSharedPtr<FJsonObject>& Params)
1034
+ {
1035
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1036
+
1037
+ FString AssetPath;
1038
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath))
1039
+ {
1040
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath))
1041
+ {
1042
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'assetPath' or 'path' parameter"));
1043
+ Result->SetBoolField(TEXT("success"), false);
1044
+ return MakeShared<FJsonValueObject>(Result);
1045
+ }
1046
+ }
1047
+
1048
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
1049
+ if (!Asset)
1050
+ {
1051
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
1052
+ Result->SetBoolField(TEXT("success"), false);
1053
+ return MakeShared<FJsonValueObject>(Result);
1054
+ }
1055
+
1056
+ UTexture2D* Texture = Cast<UTexture2D>(Asset);
1057
+ if (!Texture)
1058
+ {
1059
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset is not a Texture2D: %s"), *AssetPath));
1060
+ Result->SetBoolField(TEXT("success"), false);
1061
+ return MakeShared<FJsonValueObject>(Result);
1062
+ }
1063
+
1064
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1065
+ Result->SetStringField(TEXT("name"), Texture->GetName());
1066
+
1067
+ // Dimensions
1068
+ Result->SetNumberField(TEXT("sizeX"), Texture->GetSizeX());
1069
+ Result->SetNumberField(TEXT("sizeY"), Texture->GetSizeY());
1070
+
1071
+ // Compression settings
1072
+ FString CompressionStr;
1073
+ switch (Texture->CompressionSettings)
1074
+ {
1075
+ case TC_Default: CompressionStr = TEXT("Default"); break;
1076
+ case TC_Normalmap: CompressionStr = TEXT("Normalmap"); break;
1077
+ case TC_Grayscale: CompressionStr = TEXT("Grayscale"); break;
1078
+ case TC_Displacementmap: CompressionStr = TEXT("Displacementmap"); break;
1079
+ case TC_VectorDisplacementmap: CompressionStr = TEXT("VectorDisplacementmap"); break;
1080
+ case TC_HDR: CompressionStr = TEXT("HDR"); break;
1081
+ case TC_EditorIcon: CompressionStr = TEXT("EditorIcon"); break;
1082
+ case TC_Alpha: CompressionStr = TEXT("Alpha"); break;
1083
+ case TC_DistanceFieldFont: CompressionStr = TEXT("DistanceFieldFont"); break;
1084
+ case TC_HDR_Compressed: CompressionStr = TEXT("HDR_Compressed"); break;
1085
+ case TC_BC7: CompressionStr = TEXT("BC7"); break;
1086
+ default: CompressionStr = TEXT("Unknown"); break;
1087
+ }
1088
+ Result->SetStringField(TEXT("compressionSettings"), CompressionStr);
1089
+
1090
+ // LOD group
1091
+ FString LODGroupStr;
1092
+ switch (Texture->LODGroup)
1093
+ {
1094
+ case TEXTUREGROUP_World: LODGroupStr = TEXT("World"); break;
1095
+ case TEXTUREGROUP_WorldNormalMap: LODGroupStr = TEXT("WorldNormalMap"); break;
1096
+ case TEXTUREGROUP_WorldSpecular: LODGroupStr = TEXT("WorldSpecular"); break;
1097
+ case TEXTUREGROUP_Character: LODGroupStr = TEXT("Character"); break;
1098
+ case TEXTUREGROUP_CharacterNormalMap: LODGroupStr = TEXT("CharacterNormalMap"); break;
1099
+ case TEXTUREGROUP_CharacterSpecular: LODGroupStr = TEXT("CharacterSpecular"); break;
1100
+ case TEXTUREGROUP_Weapon: LODGroupStr = TEXT("Weapon"); break;
1101
+ case TEXTUREGROUP_WeaponNormalMap: LODGroupStr = TEXT("WeaponNormalMap"); break;
1102
+ case TEXTUREGROUP_WeaponSpecular: LODGroupStr = TEXT("WeaponSpecular"); break;
1103
+ case TEXTUREGROUP_Vehicle: LODGroupStr = TEXT("Vehicle"); break;
1104
+ case TEXTUREGROUP_VehicleNormalMap: LODGroupStr = TEXT("VehicleNormalMap"); break;
1105
+ case TEXTUREGROUP_VehicleSpecular: LODGroupStr = TEXT("VehicleSpecular"); break;
1106
+ case TEXTUREGROUP_UI: LODGroupStr = TEXT("UI"); break;
1107
+ case TEXTUREGROUP_Lightmap: LODGroupStr = TEXT("Lightmap"); break;
1108
+ case TEXTUREGROUP_Shadowmap: LODGroupStr = TEXT("Shadowmap"); break;
1109
+ case TEXTUREGROUP_Effects: LODGroupStr = TEXT("Effects"); break;
1110
+ case TEXTUREGROUP_EffectsNotFiltered: LODGroupStr = TEXT("EffectsNotFiltered"); break;
1111
+ case TEXTUREGROUP_Skybox: LODGroupStr = TEXT("Skybox"); break;
1112
+ case TEXTUREGROUP_Pixels2D: LODGroupStr = TEXT("Pixels2D"); break;
1113
+ default: LODGroupStr = TEXT("Unknown"); break;
1114
+ }
1115
+ Result->SetStringField(TEXT("lodGroup"), LODGroupStr);
1116
+
1117
+ // Other properties
1118
+ Result->SetBoolField(TEXT("sRGB"), Texture->SRGB);
1119
+ Result->SetBoolField(TEXT("neverStream"), Texture->NeverStream);
1120
+
1121
+ // Num mips
1122
+ Result->SetNumberField(TEXT("numMips"), Texture->GetNumMips());
1123
+
1124
+ // Pixel format
1125
+ Result->SetStringField(TEXT("pixelFormat"), GPixelFormats[Texture->GetPixelFormat()].Name);
1126
+
1127
+ Result->SetBoolField(TEXT("success"), true);
1128
+
1129
+ return MakeShared<FJsonValueObject>(Result);
1130
+ }
1131
+
1132
+ TSharedPtr<FJsonValue> FAssetHandlers::SetTextureProperties(const TSharedPtr<FJsonObject>& Params)
1133
+ {
1134
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1135
+
1136
+ FString AssetPath;
1137
+ if (!Params->TryGetStringField(TEXT("assetPath"), AssetPath))
1138
+ {
1139
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath))
1140
+ {
1141
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'assetPath' or 'path' parameter"));
1142
+ Result->SetBoolField(TEXT("success"), false);
1143
+ return MakeShared<FJsonValueObject>(Result);
1144
+ }
1145
+ }
1146
+
1147
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
1148
+ if (!Asset)
1149
+ {
1150
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
1151
+ Result->SetBoolField(TEXT("success"), false);
1152
+ return MakeShared<FJsonValueObject>(Result);
1153
+ }
1154
+
1155
+ UTexture2D* Texture = Cast<UTexture2D>(Asset);
1156
+ if (!Texture)
1157
+ {
1158
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset is not a Texture2D: %s"), *AssetPath));
1159
+ Result->SetBoolField(TEXT("success"), false);
1160
+ return MakeShared<FJsonValueObject>(Result);
1161
+ }
1162
+
1163
+ TArray<FString> ModifiedProperties;
1164
+
1165
+ // Compression settings
1166
+ FString CompressionStr;
1167
+ if (Params->TryGetStringField(TEXT("compressionSettings"), CompressionStr))
1168
+ {
1169
+ TextureCompressionSettings NewCompression = TC_Default;
1170
+ if (CompressionStr == TEXT("Default")) NewCompression = TC_Default;
1171
+ else if (CompressionStr == TEXT("Normalmap")) NewCompression = TC_Normalmap;
1172
+ else if (CompressionStr == TEXT("Grayscale")) NewCompression = TC_Grayscale;
1173
+ else if (CompressionStr == TEXT("Displacementmap")) NewCompression = TC_Displacementmap;
1174
+ else if (CompressionStr == TEXT("VectorDisplacementmap")) NewCompression = TC_VectorDisplacementmap;
1175
+ else if (CompressionStr == TEXT("HDR")) NewCompression = TC_HDR;
1176
+ else if (CompressionStr == TEXT("EditorIcon")) NewCompression = TC_EditorIcon;
1177
+ else if (CompressionStr == TEXT("Alpha")) NewCompression = TC_Alpha;
1178
+ else if (CompressionStr == TEXT("DistanceFieldFont")) NewCompression = TC_DistanceFieldFont;
1179
+ else if (CompressionStr == TEXT("HDR_Compressed")) NewCompression = TC_HDR_Compressed;
1180
+ else if (CompressionStr == TEXT("BC7")) NewCompression = TC_BC7;
1181
+
1182
+ Texture->CompressionSettings = NewCompression;
1183
+ ModifiedProperties.Add(TEXT("compressionSettings"));
1184
+ }
1185
+
1186
+ // LOD group
1187
+ FString LODGroupStr;
1188
+ if (Params->TryGetStringField(TEXT("lodGroup"), LODGroupStr))
1189
+ {
1190
+ TextureGroup NewGroup = TEXTUREGROUP_World;
1191
+ if (LODGroupStr == TEXT("World")) NewGroup = TEXTUREGROUP_World;
1192
+ else if (LODGroupStr == TEXT("WorldNormalMap")) NewGroup = TEXTUREGROUP_WorldNormalMap;
1193
+ else if (LODGroupStr == TEXT("WorldSpecular")) NewGroup = TEXTUREGROUP_WorldSpecular;
1194
+ else if (LODGroupStr == TEXT("Character")) NewGroup = TEXTUREGROUP_Character;
1195
+ else if (LODGroupStr == TEXT("CharacterNormalMap")) NewGroup = TEXTUREGROUP_CharacterNormalMap;
1196
+ else if (LODGroupStr == TEXT("CharacterSpecular")) NewGroup = TEXTUREGROUP_CharacterSpecular;
1197
+ else if (LODGroupStr == TEXT("Weapon")) NewGroup = TEXTUREGROUP_Weapon;
1198
+ else if (LODGroupStr == TEXT("WeaponNormalMap")) NewGroup = TEXTUREGROUP_WeaponNormalMap;
1199
+ else if (LODGroupStr == TEXT("WeaponSpecular")) NewGroup = TEXTUREGROUP_WeaponSpecular;
1200
+ else if (LODGroupStr == TEXT("Vehicle")) NewGroup = TEXTUREGROUP_Vehicle;
1201
+ else if (LODGroupStr == TEXT("VehicleNormalMap")) NewGroup = TEXTUREGROUP_VehicleNormalMap;
1202
+ else if (LODGroupStr == TEXT("VehicleSpecular")) NewGroup = TEXTUREGROUP_VehicleSpecular;
1203
+ else if (LODGroupStr == TEXT("UI")) NewGroup = TEXTUREGROUP_UI;
1204
+ else if (LODGroupStr == TEXT("Lightmap")) NewGroup = TEXTUREGROUP_Lightmap;
1205
+ else if (LODGroupStr == TEXT("Shadowmap")) NewGroup = TEXTUREGROUP_Shadowmap;
1206
+ else if (LODGroupStr == TEXT("Effects")) NewGroup = TEXTUREGROUP_Effects;
1207
+ else if (LODGroupStr == TEXT("EffectsNotFiltered")) NewGroup = TEXTUREGROUP_EffectsNotFiltered;
1208
+ else if (LODGroupStr == TEXT("Skybox")) NewGroup = TEXTUREGROUP_Skybox;
1209
+ else if (LODGroupStr == TEXT("Pixels2D")) NewGroup = TEXTUREGROUP_Pixels2D;
1210
+
1211
+ Texture->LODGroup = NewGroup;
1212
+ ModifiedProperties.Add(TEXT("lodGroup"));
1213
+ }
1214
+
1215
+ // sRGB
1216
+ bool bSRGB;
1217
+ if (Params->TryGetBoolField(TEXT("sRGB"), bSRGB))
1218
+ {
1219
+ Texture->SRGB = bSRGB;
1220
+ ModifiedProperties.Add(TEXT("sRGB"));
1221
+ }
1222
+
1223
+ // NeverStream
1224
+ bool bNeverStream;
1225
+ if (Params->TryGetBoolField(TEXT("neverStream"), bNeverStream))
1226
+ {
1227
+ Texture->NeverStream = bNeverStream;
1228
+ ModifiedProperties.Add(TEXT("neverStream"));
1229
+ }
1230
+
1231
+ if (ModifiedProperties.Num() == 0)
1232
+ {
1233
+ Result->SetStringField(TEXT("error"), TEXT("No valid properties specified to set"));
1234
+ Result->SetBoolField(TEXT("success"), false);
1235
+ return MakeShared<FJsonValueObject>(Result);
1236
+ }
1237
+
1238
+ // Notify the engine of property changes and mark dirty
1239
+ Texture->PostEditChange();
1240
+ Texture->MarkPackageDirty();
1241
+
1242
+ TArray<TSharedPtr<FJsonValue>> ModifiedArray;
1243
+ for (const FString& PropName : ModifiedProperties)
1244
+ {
1245
+ ModifiedArray.Add(MakeShared<FJsonValueString>(PropName));
1246
+ }
1247
+
1248
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1249
+ Result->SetArrayField(TEXT("modifiedProperties"), ModifiedArray);
1250
+ Result->SetStringField(TEXT("message"), FString::Printf(TEXT("Modified %d texture properties"), ModifiedProperties.Num()));
1251
+ Result->SetBoolField(TEXT("success"), true);
1252
+
1253
+ return MakeShared<FJsonValueObject>(Result);
1254
+ }
1255
+
1256
+ TSharedPtr<FJsonValue> FAssetHandlers::SetMeshMaterial(const TSharedPtr<FJsonObject>& Params)
1257
+ {
1258
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1259
+
1260
+ FString AssetPath;
1261
+ if ((!Params->TryGetStringField(TEXT("assetPath"), AssetPath) && !Params->TryGetStringField(TEXT("path"), AssetPath)) || AssetPath.IsEmpty())
1262
+ {
1263
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'assetPath' parameter (static mesh path)"));
1264
+ Result->SetBoolField(TEXT("success"), false);
1265
+ return MakeShared<FJsonValueObject>(Result);
1266
+ }
1267
+
1268
+ FString MaterialPath;
1269
+ if (!Params->TryGetStringField(TEXT("materialPath"), MaterialPath) || MaterialPath.IsEmpty())
1270
+ {
1271
+ Result->SetStringField(TEXT("error"), TEXT("Missing or empty 'materialPath' parameter"));
1272
+ Result->SetBoolField(TEXT("success"), false);
1273
+ return MakeShared<FJsonValueObject>(Result);
1274
+ }
1275
+
1276
+ int32 SlotIndex = 0;
1277
+ Params->TryGetNumberField(TEXT("slotIndex"), SlotIndex);
1278
+
1279
+ UStaticMesh* Mesh = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *AssetPath));
1280
+ if (!Mesh)
1281
+ {
1282
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load static mesh at '%s'"), *AssetPath));
1283
+ Result->SetBoolField(TEXT("success"), false);
1284
+ return MakeShared<FJsonValueObject>(Result);
1285
+ }
1286
+
1287
+ UMaterialInterface* Material = Cast<UMaterialInterface>(StaticLoadObject(UMaterialInterface::StaticClass(), nullptr, *MaterialPath));
1288
+ if (!Material)
1289
+ {
1290
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load material at '%s'"), *MaterialPath));
1291
+ Result->SetBoolField(TEXT("success"), false);
1292
+ return MakeShared<FJsonValueObject>(Result);
1293
+ }
1294
+
1295
+ if (SlotIndex < 0 || SlotIndex >= Mesh->GetStaticMaterials().Num())
1296
+ {
1297
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Slot index %d out of range (mesh has %d slots)"), SlotIndex, Mesh->GetStaticMaterials().Num()));
1298
+ Result->SetBoolField(TEXT("success"), false);
1299
+ return MakeShared<FJsonValueObject>(Result);
1300
+ }
1301
+
1302
+ Mesh->SetMaterial(SlotIndex, Material);
1303
+ UEditorAssetLibrary::SaveAsset(AssetPath, false);
1304
+
1305
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1306
+ Result->SetStringField(TEXT("materialPath"), MaterialPath);
1307
+ Result->SetNumberField(TEXT("slotIndex"), SlotIndex);
1308
+ Result->SetBoolField(TEXT("success"), true);
1309
+
1310
+ return MakeShared<FJsonValueObject>(Result);
1311
+ }
1312
+
1313
+ TSharedPtr<FJsonValue> FAssetHandlers::ImportTexture(const TSharedPtr<FJsonObject>& Params)
1314
+ {
1315
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1316
+
1317
+ FString FileName;
1318
+ if (!Params->TryGetStringField(TEXT("filename"), FileName))
1319
+ {
1320
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'filename' parameter (path to texture file)"));
1321
+ Result->SetBoolField(TEXT("success"), false);
1322
+ return MakeShared<FJsonValueObject>(Result);
1323
+ }
1324
+
1325
+ FString DestinationPath = TEXT("/Game/Textures");
1326
+ Params->TryGetStringField(TEXT("destinationPath"), DestinationPath);
1327
+
1328
+ if (!FPaths::FileExists(FileName))
1329
+ {
1330
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("File not found: %s"), *FileName));
1331
+ Result->SetBoolField(TEXT("success"), false);
1332
+ return MakeShared<FJsonValueObject>(Result);
1333
+ }
1334
+
1335
+ UTextureFactory* TextureFactory = NewObject<UTextureFactory>();
1336
+ TextureFactory->AddToRoot();
1337
+
1338
+ // Apply optional settings
1339
+ bool bNoCompression = false;
1340
+ if (Params->TryGetBoolField(TEXT("noCompression"), bNoCompression))
1341
+ {
1342
+ TextureFactory->NoCompression = bNoCompression;
1343
+ }
1344
+ bool bNoAlpha = false;
1345
+ if (Params->TryGetBoolField(TEXT("noAlpha"), bNoAlpha))
1346
+ {
1347
+ TextureFactory->NoAlpha = bNoAlpha;
1348
+ }
1349
+
1350
+ UAssetImportTask* Task = NewObject<UAssetImportTask>();
1351
+ Task->AddToRoot();
1352
+ Task->bAutomated = true;
1353
+ Task->bReplaceExisting = true;
1354
+ Task->bSave = false;
1355
+ Task->Filename = FileName;
1356
+ Task->DestinationPath = DestinationPath;
1357
+ Task->Factory = TextureFactory;
1358
+
1359
+ // Optional asset name
1360
+ FString AssetName;
1361
+ if (Params->TryGetStringField(TEXT("assetName"), AssetName) && !AssetName.IsEmpty())
1362
+ {
1363
+ Task->DestinationName = AssetName;
1364
+ }
1365
+
1366
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
1367
+ TArray<UAssetImportTask*> Tasks;
1368
+ Tasks.Add(Task);
1369
+ AssetToolsModule.Get().ImportAssetTasks(Tasks);
1370
+
1371
+ TArray<TSharedPtr<FJsonValue>> ImportedPaths;
1372
+ if (Task->GetObjects().Num() > 0)
1373
+ {
1374
+ for (UObject* ImportedObj : Task->GetObjects())
1375
+ {
1376
+ if (ImportedObj)
1377
+ {
1378
+ FString ObjPath = ImportedObj->GetPathName();
1379
+ ImportedPaths.Add(MakeShared<FJsonValueString>(ObjPath));
1380
+ }
1381
+ }
1382
+ }
1383
+
1384
+ Result->SetStringField(TEXT("filename"), FileName);
1385
+ Result->SetStringField(TEXT("destinationPath"), DestinationPath);
1386
+ Result->SetArrayField(TEXT("importedAssets"), ImportedPaths);
1387
+ Result->SetNumberField(TEXT("importedCount"), ImportedPaths.Num());
1388
+ Result->SetBoolField(TEXT("success"), ImportedPaths.Num() > 0);
1389
+ if (ImportedPaths.Num() == 0)
1390
+ {
1391
+ Result->SetStringField(TEXT("error"), TEXT("Import task completed but no assets were produced"));
1392
+ }
1393
+
1394
+ Task->RemoveFromRoot();
1395
+ TextureFactory->RemoveFromRoot();
1396
+
1397
+ return MakeShared<FJsonValueObject>(Result);
1398
+ }
1399
+
1400
+ TSharedPtr<FJsonValue> FAssetHandlers::RecenterPivot(const TSharedPtr<FJsonObject>& Params)
1401
+ {
1402
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1403
+
1404
+ // Support single assetPath or array of assetPaths
1405
+ TArray<FString> AssetPaths;
1406
+ const TArray<TSharedPtr<FJsonValue>>* PathsArray = nullptr;
1407
+ FString SinglePath;
1408
+
1409
+ if (Params->TryGetArrayField(TEXT("assetPaths"), PathsArray))
1410
+ {
1411
+ for (const auto& Val : *PathsArray)
1412
+ {
1413
+ FString P;
1414
+ if (Val->TryGetString(P) && !P.IsEmpty())
1415
+ {
1416
+ AssetPaths.Add(P);
1417
+ }
1418
+ }
1419
+ }
1420
+ else if (Params->TryGetStringField(TEXT("assetPath"), SinglePath) || Params->TryGetStringField(TEXT("path"), SinglePath))
1421
+ {
1422
+ if (!SinglePath.IsEmpty())
1423
+ {
1424
+ AssetPaths.Add(SinglePath);
1425
+ }
1426
+ }
1427
+
1428
+ if (AssetPaths.Num() == 0)
1429
+ {
1430
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'assetPath' (string) or 'assetPaths' (array of strings)"));
1431
+ Result->SetBoolField(TEXT("success"), false);
1432
+ return MakeShared<FJsonValueObject>(Result);
1433
+ }
1434
+
1435
+ // Load all meshes
1436
+ TArray<UStaticMesh*> Meshes;
1437
+ for (const FString& Path : AssetPaths)
1438
+ {
1439
+ UStaticMesh* Mesh = Cast<UStaticMesh>(StaticLoadObject(UStaticMesh::StaticClass(), nullptr, *Path));
1440
+ if (!Mesh)
1441
+ {
1442
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to load static mesh at '%s'"), *Path));
1443
+ Result->SetBoolField(TEXT("success"), false);
1444
+ return MakeShared<FJsonValueObject>(Result);
1445
+ }
1446
+ Meshes.Add(Mesh);
1447
+ }
1448
+
1449
+ // Compute the center from the FIRST mesh (reference mesh)
1450
+ FMeshDescription* RefDesc = Meshes[0]->GetMeshDescription(0);
1451
+ if (!RefDesc)
1452
+ {
1453
+ Result->SetStringField(TEXT("error"), TEXT("Failed to get mesh description for reference mesh LOD 0"));
1454
+ Result->SetBoolField(TEXT("success"), false);
1455
+ return MakeShared<FJsonValueObject>(Result);
1456
+ }
1457
+
1458
+ FVertexArray& RefVerts = RefDesc->Vertices();
1459
+ TVertexAttributesRef<FVector3f> RefPositions = RefDesc->GetVertexPositions();
1460
+
1461
+ FVector3f Center = FVector3f::ZeroVector;
1462
+ int32 RefVertCount = RefVerts.Num();
1463
+ if (RefVertCount == 0)
1464
+ {
1465
+ Result->SetStringField(TEXT("error"), TEXT("Reference mesh has no vertices"));
1466
+ Result->SetBoolField(TEXT("success"), false);
1467
+ return MakeShared<FJsonValueObject>(Result);
1468
+ }
1469
+
1470
+ for (FVertexID VertID : RefVerts.GetElementIDs())
1471
+ {
1472
+ Center += RefPositions[VertID];
1473
+ }
1474
+ Center /= (float)RefVertCount;
1475
+
1476
+ // Apply the SAME offset to ALL meshes
1477
+ TArray<TSharedPtr<FJsonValue>> ResultArray;
1478
+ for (int32 i = 0; i < Meshes.Num(); i++)
1479
+ {
1480
+ FMeshDescription* MeshDesc = Meshes[i]->GetMeshDescription(0);
1481
+ if (!MeshDesc) continue;
1482
+
1483
+ FVertexArray& Verts = MeshDesc->Vertices();
1484
+ TVertexAttributesRef<FVector3f> Positions = MeshDesc->GetVertexPositions();
1485
+
1486
+ for (FVertexID VertID : Verts.GetElementIDs())
1487
+ {
1488
+ Positions[VertID] -= Center;
1489
+ }
1490
+
1491
+ Meshes[i]->CommitMeshDescription(0);
1492
+ Meshes[i]->Build(false);
1493
+ Meshes[i]->PostEditChange();
1494
+ Meshes[i]->MarkPackageDirty();
1495
+ UEditorAssetLibrary::SaveAsset(AssetPaths[i], false);
1496
+
1497
+ TSharedPtr<FJsonObject> Entry = MakeShared<FJsonObject>();
1498
+ Entry->SetStringField(TEXT("assetPath"), AssetPaths[i]);
1499
+ Entry->SetNumberField(TEXT("vertexCount"), Verts.Num());
1500
+ ResultArray.Add(MakeShared<FJsonValueObject>(Entry));
1501
+ }
1502
+
1503
+ Result->SetArrayField(TEXT("meshes"), ResultArray);
1504
+ Result->SetStringField(TEXT("offsetApplied"), FString::Printf(TEXT("(%.2f, %.2f, %.2f)"), Center.X, Center.Y, Center.Z));
1505
+ Result->SetNumberField(TEXT("meshCount"), Meshes.Num());
1506
+ Result->SetBoolField(TEXT("success"), true);
1507
+
1508
+ return MakeShared<FJsonValueObject>(Result);
1509
+ }
1510
+
1511
+ // ============================================================================
1512
+ // Additional DataTable handlers
1513
+ // ============================================================================
1514
+
1515
+ TSharedPtr<FJsonValue> FAssetHandlers::CreateDataTable(const TSharedPtr<FJsonObject>& Params)
1516
+ {
1517
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1518
+
1519
+ FString Name;
1520
+ if (!Params->TryGetStringField(TEXT("name"), Name))
1521
+ {
1522
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'name' parameter"));
1523
+ Result->SetBoolField(TEXT("success"), false);
1524
+ return MakeShared<FJsonValueObject>(Result);
1525
+ }
1526
+
1527
+ FString RowStruct;
1528
+ if (!Params->TryGetStringField(TEXT("rowStruct"), RowStruct))
1529
+ {
1530
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'rowStruct' parameter (e.g. '/Script/Engine.DataTableRowHandle' or a struct name)"));
1531
+ Result->SetBoolField(TEXT("success"), false);
1532
+ return MakeShared<FJsonValueObject>(Result);
1533
+ }
1534
+
1535
+ FString PackagePath = TEXT("/Game/DataTables");
1536
+ Params->TryGetStringField(TEXT("packagePath"), PackagePath);
1537
+
1538
+ // Find the row struct type
1539
+ UScriptStruct* ScriptStruct = nullptr;
1540
+
1541
+ // First try as a full path
1542
+ ScriptStruct = LoadObject<UScriptStruct>(nullptr, *RowStruct);
1543
+
1544
+ // If not found, try finding by short name
1545
+ if (!ScriptStruct)
1546
+ {
1547
+ // Try common patterns: search for the struct by name in all packages
1548
+ for (TObjectIterator<UScriptStruct> It; It; ++It)
1549
+ {
1550
+ if (It->GetName() == RowStruct)
1551
+ {
1552
+ ScriptStruct = *It;
1553
+ break;
1554
+ }
1555
+ }
1556
+ }
1557
+
1558
+ if (!ScriptStruct)
1559
+ {
1560
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Row struct not found: %s"), *RowStruct));
1561
+ Result->SetBoolField(TEXT("success"), false);
1562
+ return MakeShared<FJsonValueObject>(Result);
1563
+ }
1564
+
1565
+ // Create the DataTable asset
1566
+ FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(TEXT("AssetTools"));
1567
+ IAssetTools& AssetTools = AssetToolsModule.Get();
1568
+
1569
+ UDataTableFactory* Factory = NewObject<UDataTableFactory>();
1570
+ Factory->Struct = ScriptStruct;
1571
+
1572
+ UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UDataTable::StaticClass(), Factory);
1573
+ if (!NewAsset)
1574
+ {
1575
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to create DataTable: %s/%s"), *PackagePath, *Name));
1576
+ Result->SetBoolField(TEXT("success"), false);
1577
+ return MakeShared<FJsonValueObject>(Result);
1578
+ }
1579
+
1580
+ UDataTable* DataTable = Cast<UDataTable>(NewAsset);
1581
+
1582
+ Result->SetStringField(TEXT("name"), Name);
1583
+ Result->SetStringField(TEXT("packagePath"), PackagePath);
1584
+ Result->SetStringField(TEXT("assetPath"), NewAsset->GetPathName());
1585
+ Result->SetStringField(TEXT("rowStruct"), ScriptStruct->GetName());
1586
+ Result->SetNumberField(TEXT("rowCount"), DataTable ? DataTable->GetRowMap().Num() : 0);
1587
+ Result->SetBoolField(TEXT("success"), true);
1588
+
1589
+ return MakeShared<FJsonValueObject>(Result);
1590
+ }
1591
+
1592
+ TSharedPtr<FJsonValue> FAssetHandlers::ReadDataTable(const TSharedPtr<FJsonObject>& Params)
1593
+ {
1594
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1595
+
1596
+ FString AssetPath;
1597
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath))
1598
+ {
1599
+ Params->TryGetStringField(TEXT("assetPath"), AssetPath);
1600
+ }
1601
+ if (AssetPath.IsEmpty())
1602
+ {
1603
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
1604
+ Result->SetBoolField(TEXT("success"), false);
1605
+ return MakeShared<FJsonValueObject>(Result);
1606
+ }
1607
+
1608
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
1609
+ if (!Asset)
1610
+ {
1611
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
1612
+ Result->SetBoolField(TEXT("success"), false);
1613
+ return MakeShared<FJsonValueObject>(Result);
1614
+ }
1615
+
1616
+ UDataTable* DataTable = Cast<UDataTable>(Asset);
1617
+ if (!DataTable)
1618
+ {
1619
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
1620
+ Result->SetBoolField(TEXT("success"), false);
1621
+ return MakeShared<FJsonValueObject>(Result);
1622
+ }
1623
+
1624
+ FString RowFilter;
1625
+ Params->TryGetStringField(TEXT("rowFilter"), RowFilter);
1626
+
1627
+ // Get the row struct for property iteration
1628
+ const UScriptStruct* RowStruct = DataTable->GetRowStruct();
1629
+ if (!RowStruct)
1630
+ {
1631
+ Result->SetStringField(TEXT("error"), TEXT("DataTable has no row struct"));
1632
+ Result->SetBoolField(TEXT("success"), false);
1633
+ return MakeShared<FJsonValueObject>(Result);
1634
+ }
1635
+
1636
+ // Export the table as JSON for reliable serialization, then parse it
1637
+ FString JsonString = DataTable->GetTableAsJSON(EDataTableExportFlags::UseJsonObjectsForStructs);
1638
+
1639
+ TArray<TSharedPtr<FJsonValue>> ParsedRows;
1640
+ TSharedRef<TJsonReader<>> JsonReader = TJsonReaderFactory<>::Create(JsonString);
1641
+ if (FJsonSerializer::Deserialize(JsonReader, ParsedRows))
1642
+ {
1643
+ // Apply row filter if specified
1644
+ if (!RowFilter.IsEmpty())
1645
+ {
1646
+ FString FilterLower = RowFilter.ToLower();
1647
+ TArray<TSharedPtr<FJsonValue>> FilteredRows;
1648
+ for (const TSharedPtr<FJsonValue>& RowValue : ParsedRows)
1649
+ {
1650
+ if (RowValue.IsValid() && RowValue->Type == EJson::Object)
1651
+ {
1652
+ const TSharedPtr<FJsonObject>& RowObj = RowValue->AsObject();
1653
+ FString RowName;
1654
+ if (RowObj->TryGetStringField(TEXT("Name"), RowName) && RowName.ToLower().Contains(FilterLower))
1655
+ {
1656
+ FilteredRows.Add(RowValue);
1657
+ }
1658
+ }
1659
+ }
1660
+ Result->SetArrayField(TEXT("rows"), FilteredRows);
1661
+ Result->SetNumberField(TEXT("filteredCount"), FilteredRows.Num());
1662
+ }
1663
+ else
1664
+ {
1665
+ Result->SetArrayField(TEXT("rows"), ParsedRows);
1666
+ }
1667
+ }
1668
+ else
1669
+ {
1670
+ // Fallback: return the raw JSON string
1671
+ Result->SetStringField(TEXT("rawJson"), JsonString);
1672
+ }
1673
+
1674
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1675
+ Result->SetStringField(TEXT("rowStruct"), RowStruct->GetName());
1676
+ Result->SetNumberField(TEXT("totalRowCount"), DataTable->GetRowMap().Num());
1677
+
1678
+ // Also list the row names
1679
+ TArray<TSharedPtr<FJsonValue>> RowNames;
1680
+ for (const auto& Pair : DataTable->GetRowMap())
1681
+ {
1682
+ RowNames.Add(MakeShared<FJsonValueString>(Pair.Key.ToString()));
1683
+ }
1684
+ Result->SetArrayField(TEXT("rowNames"), RowNames);
1685
+ Result->SetBoolField(TEXT("success"), true);
1686
+
1687
+ return MakeShared<FJsonValueObject>(Result);
1688
+ }
1689
+
1690
+ TSharedPtr<FJsonValue> FAssetHandlers::ReimportDataTable(const TSharedPtr<FJsonObject>& Params)
1691
+ {
1692
+ TSharedPtr<FJsonObject> Result = MakeShared<FJsonObject>();
1693
+
1694
+ FString AssetPath;
1695
+ if (!Params->TryGetStringField(TEXT("path"), AssetPath))
1696
+ {
1697
+ Params->TryGetStringField(TEXT("assetPath"), AssetPath);
1698
+ }
1699
+ if (AssetPath.IsEmpty())
1700
+ {
1701
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'path' or 'assetPath' parameter"));
1702
+ Result->SetBoolField(TEXT("success"), false);
1703
+ return MakeShared<FJsonValueObject>(Result);
1704
+ }
1705
+
1706
+ UObject* Asset = UEditorAssetLibrary::LoadAsset(AssetPath);
1707
+ if (!Asset)
1708
+ {
1709
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset not found: %s"), *AssetPath));
1710
+ Result->SetBoolField(TEXT("success"), false);
1711
+ return MakeShared<FJsonValueObject>(Result);
1712
+ }
1713
+
1714
+ UDataTable* DataTable = Cast<UDataTable>(Asset);
1715
+ if (!DataTable)
1716
+ {
1717
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Asset is not a DataTable: %s"), *AssetPath));
1718
+ Result->SetBoolField(TEXT("success"), false);
1719
+ return MakeShared<FJsonValueObject>(Result);
1720
+ }
1721
+
1722
+ // Get JSON string from either inline jsonString or from a file path
1723
+ FString JsonString;
1724
+ if (!Params->TryGetStringField(TEXT("jsonString"), JsonString) || JsonString.IsEmpty())
1725
+ {
1726
+ FString JsonPath;
1727
+ if (Params->TryGetStringField(TEXT("jsonPath"), JsonPath) && !JsonPath.IsEmpty())
1728
+ {
1729
+ if (!FPaths::FileExists(JsonPath))
1730
+ {
1731
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("JSON file not found: %s"), *JsonPath));
1732
+ Result->SetBoolField(TEXT("success"), false);
1733
+ return MakeShared<FJsonValueObject>(Result);
1734
+ }
1735
+ if (!FFileHelper::LoadFileToString(JsonString, *JsonPath))
1736
+ {
1737
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Failed to read JSON file: %s"), *JsonPath));
1738
+ Result->SetBoolField(TEXT("success"), false);
1739
+ return MakeShared<FJsonValueObject>(Result);
1740
+ }
1741
+ }
1742
+ else
1743
+ {
1744
+ Result->SetStringField(TEXT("error"), TEXT("Missing 'jsonString' or 'jsonPath' parameter"));
1745
+ Result->SetBoolField(TEXT("success"), false);
1746
+ return MakeShared<FJsonValueObject>(Result);
1747
+ }
1748
+ }
1749
+
1750
+ TArray<FString> Errors = DataTable->CreateTableFromJSONString(JsonString);
1751
+
1752
+ if (Errors.Num() > 0)
1753
+ {
1754
+ TArray<TSharedPtr<FJsonValue>> ErrorsArray;
1755
+ for (const FString& Error : Errors)
1756
+ {
1757
+ ErrorsArray.Add(MakeShared<FJsonValueString>(Error));
1758
+ }
1759
+ Result->SetArrayField(TEXT("errors"), ErrorsArray);
1760
+ Result->SetStringField(TEXT("error"), FString::Printf(TEXT("Reimport completed with %d error(s)"), Errors.Num()));
1761
+ Result->SetBoolField(TEXT("success"), false);
1762
+ return MakeShared<FJsonValueObject>(Result);
1763
+ }
1764
+
1765
+ DataTable->MarkPackageDirty();
1766
+
1767
+ Result->SetStringField(TEXT("assetPath"), AssetPath);
1768
+ Result->SetNumberField(TEXT("rowCount"), DataTable->GetRowMap().Num());
1769
+ Result->SetStringField(TEXT("message"), TEXT("DataTable reimported successfully from JSON"));
1770
+ Result->SetBoolField(TEXT("success"), true);
1771
+
1772
+ return MakeShared<FJsonValueObject>(Result);
1773
+ }