unreal-engine-mcp-server 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dockerignore +57 -0
- package/.env.production +25 -0
- package/.eslintrc.json +54 -0
- package/.github/workflows/publish-mcp.yml +75 -0
- package/Dockerfile +54 -0
- package/LICENSE +21 -0
- package/Public/icon.png +0 -0
- package/README.md +209 -0
- package/claude_desktop_config_example.json +13 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +7 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +484 -0
- package/dist/prompts/index.d.ts +14 -0
- package/dist/prompts/index.js +38 -0
- package/dist/python-utils.d.ts +29 -0
- package/dist/python-utils.js +54 -0
- package/dist/resources/actors.d.ts +13 -0
- package/dist/resources/actors.js +83 -0
- package/dist/resources/assets.d.ts +23 -0
- package/dist/resources/assets.js +245 -0
- package/dist/resources/levels.d.ts +17 -0
- package/dist/resources/levels.js +94 -0
- package/dist/tools/actors.d.ts +51 -0
- package/dist/tools/actors.js +459 -0
- package/dist/tools/animation.d.ts +196 -0
- package/dist/tools/animation.js +579 -0
- package/dist/tools/assets.d.ts +21 -0
- package/dist/tools/assets.js +304 -0
- package/dist/tools/audio.d.ts +170 -0
- package/dist/tools/audio.js +416 -0
- package/dist/tools/blueprint.d.ts +144 -0
- package/dist/tools/blueprint.js +652 -0
- package/dist/tools/build_environment_advanced.d.ts +66 -0
- package/dist/tools/build_environment_advanced.js +484 -0
- package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
- package/dist/tools/consolidated-tool-definitions.js +607 -0
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
- package/dist/tools/consolidated-tool-handlers.js +1050 -0
- package/dist/tools/debug.d.ts +185 -0
- package/dist/tools/debug.js +265 -0
- package/dist/tools/editor.d.ts +88 -0
- package/dist/tools/editor.js +365 -0
- package/dist/tools/engine.d.ts +30 -0
- package/dist/tools/engine.js +36 -0
- package/dist/tools/foliage.d.ts +155 -0
- package/dist/tools/foliage.js +525 -0
- package/dist/tools/introspection.d.ts +98 -0
- package/dist/tools/introspection.js +683 -0
- package/dist/tools/landscape.d.ts +158 -0
- package/dist/tools/landscape.js +375 -0
- package/dist/tools/level.d.ts +110 -0
- package/dist/tools/level.js +362 -0
- package/dist/tools/lighting.d.ts +159 -0
- package/dist/tools/lighting.js +1179 -0
- package/dist/tools/materials.d.ts +34 -0
- package/dist/tools/materials.js +146 -0
- package/dist/tools/niagara.d.ts +145 -0
- package/dist/tools/niagara.js +289 -0
- package/dist/tools/performance.d.ts +163 -0
- package/dist/tools/performance.js +412 -0
- package/dist/tools/physics.d.ts +189 -0
- package/dist/tools/physics.js +784 -0
- package/dist/tools/rc.d.ts +110 -0
- package/dist/tools/rc.js +363 -0
- package/dist/tools/sequence.d.ts +112 -0
- package/dist/tools/sequence.js +675 -0
- package/dist/tools/tool-definitions.d.ts +4919 -0
- package/dist/tools/tool-definitions.js +891 -0
- package/dist/tools/tool-handlers.d.ts +47 -0
- package/dist/tools/tool-handlers.js +830 -0
- package/dist/tools/ui.d.ts +171 -0
- package/dist/tools/ui.js +337 -0
- package/dist/tools/visual.d.ts +29 -0
- package/dist/tools/visual.js +67 -0
- package/dist/types/env.d.ts +10 -0
- package/dist/types/env.js +18 -0
- package/dist/types/index.d.ts +323 -0
- package/dist/types/index.js +28 -0
- package/dist/types/tool-types.d.ts +274 -0
- package/dist/types/tool-types.js +13 -0
- package/dist/unreal-bridge.d.ts +126 -0
- package/dist/unreal-bridge.js +992 -0
- package/dist/utils/cache-manager.d.ts +64 -0
- package/dist/utils/cache-manager.js +176 -0
- package/dist/utils/error-handler.d.ts +66 -0
- package/dist/utils/error-handler.js +243 -0
- package/dist/utils/errors.d.ts +133 -0
- package/dist/utils/errors.js +256 -0
- package/dist/utils/http.d.ts +26 -0
- package/dist/utils/http.js +135 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.js +32 -0
- package/dist/utils/normalize.d.ts +17 -0
- package/dist/utils/normalize.js +49 -0
- package/dist/utils/response-validator.d.ts +34 -0
- package/dist/utils/response-validator.js +121 -0
- package/dist/utils/safe-json.d.ts +4 -0
- package/dist/utils/safe-json.js +97 -0
- package/dist/utils/stdio-redirect.d.ts +2 -0
- package/dist/utils/stdio-redirect.js +20 -0
- package/dist/utils/validation.d.ts +50 -0
- package/dist/utils/validation.js +173 -0
- package/mcp-config-example.json +14 -0
- package/package.json +63 -0
- package/server.json +60 -0
- package/src/cli.ts +7 -0
- package/src/index.ts +543 -0
- package/src/prompts/index.ts +51 -0
- package/src/python/editor_compat.py +181 -0
- package/src/python-utils.ts +57 -0
- package/src/resources/actors.ts +92 -0
- package/src/resources/assets.ts +251 -0
- package/src/resources/levels.ts +83 -0
- package/src/tools/actors.ts +480 -0
- package/src/tools/animation.ts +713 -0
- package/src/tools/assets.ts +305 -0
- package/src/tools/audio.ts +548 -0
- package/src/tools/blueprint.ts +736 -0
- package/src/tools/build_environment_advanced.ts +526 -0
- package/src/tools/consolidated-tool-definitions.ts +619 -0
- package/src/tools/consolidated-tool-handlers.ts +1093 -0
- package/src/tools/debug.ts +368 -0
- package/src/tools/editor.ts +360 -0
- package/src/tools/engine.ts +32 -0
- package/src/tools/foliage.ts +652 -0
- package/src/tools/introspection.ts +778 -0
- package/src/tools/landscape.ts +523 -0
- package/src/tools/level.ts +410 -0
- package/src/tools/lighting.ts +1316 -0
- package/src/tools/materials.ts +148 -0
- package/src/tools/niagara.ts +312 -0
- package/src/tools/performance.ts +549 -0
- package/src/tools/physics.ts +924 -0
- package/src/tools/rc.ts +437 -0
- package/src/tools/sequence.ts +791 -0
- package/src/tools/tool-definitions.ts +907 -0
- package/src/tools/tool-handlers.ts +941 -0
- package/src/tools/ui.ts +499 -0
- package/src/tools/visual.ts +60 -0
- package/src/types/env.ts +27 -0
- package/src/types/index.ts +414 -0
- package/src/types/tool-types.ts +343 -0
- package/src/unreal-bridge.ts +1118 -0
- package/src/utils/cache-manager.ts +213 -0
- package/src/utils/error-handler.ts +320 -0
- package/src/utils/errors.ts +312 -0
- package/src/utils/http.ts +184 -0
- package/src/utils/logger.ts +30 -0
- package/src/utils/normalize.ts +54 -0
- package/src/utils/response-validator.ts +145 -0
- package/src/utils/safe-json.ts +112 -0
- package/src/utils/stdio-redirect.ts +18 -0
- package/src/utils/validation.ts +212 -0
- package/tsconfig.json +33 -0
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
// Landscape tools for Unreal Engine with UE 5.6 World Partition support
|
|
2
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
3
|
+
|
|
4
|
+
export class LandscapeTools {
|
|
5
|
+
constructor(private bridge: UnrealBridge) {}
|
|
6
|
+
|
|
7
|
+
// Execute console command
|
|
8
|
+
private async _executeCommand(command: string) {
|
|
9
|
+
return this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
10
|
+
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
11
|
+
functionName: 'ExecuteConsoleCommand',
|
|
12
|
+
parameters: {
|
|
13
|
+
WorldContextObject: null,
|
|
14
|
+
Command: command,
|
|
15
|
+
SpecificPlayer: null
|
|
16
|
+
},
|
|
17
|
+
generateTransaction: false
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Create landscape with World Partition support (UE 5.6)
|
|
22
|
+
async createLandscape(params: {
|
|
23
|
+
name: string;
|
|
24
|
+
location?: [number, number, number];
|
|
25
|
+
sizeX?: number;
|
|
26
|
+
sizeY?: number;
|
|
27
|
+
quadsPerSection?: number;
|
|
28
|
+
sectionsPerComponent?: number;
|
|
29
|
+
componentCount?: number;
|
|
30
|
+
materialPath?: string;
|
|
31
|
+
// World Partition specific (UE 5.6)
|
|
32
|
+
enableWorldPartition?: boolean;
|
|
33
|
+
runtimeGrid?: string;
|
|
34
|
+
isSpatiallyLoaded?: boolean;
|
|
35
|
+
dataLayers?: string[];
|
|
36
|
+
}) {
|
|
37
|
+
// Try Python API with World Partition support for UE 5.6
|
|
38
|
+
try {
|
|
39
|
+
const pythonScript = `
|
|
40
|
+
import unreal
|
|
41
|
+
import json
|
|
42
|
+
|
|
43
|
+
# Get the editor world using the proper subsystem
|
|
44
|
+
try:
|
|
45
|
+
editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
46
|
+
world = editor_subsystem.get_editor_world() if hasattr(editor_subsystem, 'get_editor_world') else None
|
|
47
|
+
except:
|
|
48
|
+
# Fallback for older API
|
|
49
|
+
try:
|
|
50
|
+
world = unreal.EditorLevelLibrary.get_editor_world()
|
|
51
|
+
except:
|
|
52
|
+
world = None
|
|
53
|
+
|
|
54
|
+
is_world_partition = False
|
|
55
|
+
data_layer_manager = None
|
|
56
|
+
|
|
57
|
+
if world:
|
|
58
|
+
try:
|
|
59
|
+
# Check if World Partition is enabled (UE 5.6)
|
|
60
|
+
world_partition = world.get_world_partition()
|
|
61
|
+
is_world_partition = world_partition is not None
|
|
62
|
+
if is_world_partition:
|
|
63
|
+
# Get Data Layer Manager for UE 5.6
|
|
64
|
+
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
65
|
+
except:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
# Try to create a basic landscape using available API
|
|
69
|
+
try:
|
|
70
|
+
# Use EditorLevelLibrary to spawn a landscape actor
|
|
71
|
+
location = unreal.Vector(${params.location?.[0] || 0}, ${params.location?.[1] || 0}, ${params.location?.[2] || 0})
|
|
72
|
+
rotation = unreal.Rotator(0, 0, 0)
|
|
73
|
+
|
|
74
|
+
# Check if LandscapeSubsystem is available (not LandscapeEditorSubsystem)
|
|
75
|
+
landscape_subsystem = None
|
|
76
|
+
try:
|
|
77
|
+
landscape_subsystem = unreal.get_editor_subsystem(unreal.LandscapeSubsystem)
|
|
78
|
+
except:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
if landscape_subsystem:
|
|
82
|
+
# Use subsystem if available
|
|
83
|
+
result = {"success": False, "error": "LandscapeSubsystem API limited via Python", "world_partition": is_world_partition}
|
|
84
|
+
else:
|
|
85
|
+
# Landscape actors cannot be properly spawned via Python API
|
|
86
|
+
# The component registration issues are inherent to how landscapes work
|
|
87
|
+
# Direct users to the proper workflow
|
|
88
|
+
result = {
|
|
89
|
+
"success": False,
|
|
90
|
+
"error": "Landscape creation is not supported via Python API",
|
|
91
|
+
"suggestion": "Please use Landscape Mode in the Unreal Editor toolbar:",
|
|
92
|
+
"steps": [
|
|
93
|
+
"1. Click 'Modes' dropdown in toolbar",
|
|
94
|
+
"2. Select 'Landscape'",
|
|
95
|
+
"3. Configure size and materials",
|
|
96
|
+
"4. Click 'Create' to generate landscape"
|
|
97
|
+
],
|
|
98
|
+
"world_partition": is_world_partition
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
print(f'RESULT:{json.dumps(result)}')
|
|
102
|
+
except Exception as e:
|
|
103
|
+
print(f'RESULT:{{"success": false, "error": "{str(e)}"}}')
|
|
104
|
+
`.trim();
|
|
105
|
+
|
|
106
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
107
|
+
const output = typeof response === 'string' ? response : JSON.stringify(response);
|
|
108
|
+
const match = output.match(/RESULT:({.*})/);
|
|
109
|
+
|
|
110
|
+
if (match) {
|
|
111
|
+
try {
|
|
112
|
+
const result = JSON.parse(match[1]);
|
|
113
|
+
if (result.world_partition) {
|
|
114
|
+
result.message = 'World Partition detected. Manual landscape creation required in editor.';
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
} catch {}
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Continue to fallback
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fallback message with World Partition info
|
|
124
|
+
return {
|
|
125
|
+
success: false,
|
|
126
|
+
error: 'Landscape creation via API is limited. Please use the Unreal Editor UI to create landscapes.',
|
|
127
|
+
worldPartitionSupport: params.enableWorldPartition ? 'Requested' : 'Not requested',
|
|
128
|
+
suggestion: 'Use the Landscape Mode in the editor toolbar to create and configure landscapes'
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Sculpt landscape
|
|
133
|
+
async sculptLandscape(_params: {
|
|
134
|
+
landscapeName: string;
|
|
135
|
+
tool: 'Sculpt' | 'Smooth' | 'Flatten' | 'Ramp' | 'Erosion' | 'Hydro' | 'Noise' | 'Retopologize';
|
|
136
|
+
brushSize?: number;
|
|
137
|
+
brushFalloff?: number;
|
|
138
|
+
strength?: number;
|
|
139
|
+
position?: [number, number, number];
|
|
140
|
+
}) {
|
|
141
|
+
return { success: false, error: 'sculptLandscape not implemented via Remote Control. Requires Landscape editor tools.' };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Paint landscape
|
|
145
|
+
async paintLandscape(_params: {
|
|
146
|
+
landscapeName: string;
|
|
147
|
+
layerName: string;
|
|
148
|
+
position: [number, number, number];
|
|
149
|
+
brushSize?: number;
|
|
150
|
+
strength?: number;
|
|
151
|
+
targetValue?: number;
|
|
152
|
+
}) {
|
|
153
|
+
return { success: false, error: 'paintLandscape not implemented via Remote Control. Requires Landscape editor tools.' };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Add landscape layer
|
|
157
|
+
async addLandscapeLayer(params: {
|
|
158
|
+
landscapeName: string;
|
|
159
|
+
layerName: string;
|
|
160
|
+
weightMapPath?: string;
|
|
161
|
+
blendMode?: 'Weight' | 'Alpha';
|
|
162
|
+
}) {
|
|
163
|
+
const commands = [];
|
|
164
|
+
|
|
165
|
+
commands.push(`AddLandscapeLayer ${params.landscapeName} ${params.layerName}`);
|
|
166
|
+
|
|
167
|
+
if (params.weightMapPath) {
|
|
168
|
+
commands.push(`SetLayerWeightMap ${params.layerName} ${params.weightMapPath}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (params.blendMode) {
|
|
172
|
+
commands.push(`SetLayerBlendMode ${params.layerName} ${params.blendMode}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const cmd of commands) {
|
|
176
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return { success: true, message: `Layer ${params.layerName} added to landscape` };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Create landscape spline
|
|
183
|
+
async createLandscapeSpline(params: {
|
|
184
|
+
landscapeName: string;
|
|
185
|
+
splineName: string;
|
|
186
|
+
points: Array<[number, number, number]>;
|
|
187
|
+
width?: number;
|
|
188
|
+
falloffWidth?: number;
|
|
189
|
+
meshPath?: string;
|
|
190
|
+
}) {
|
|
191
|
+
const commands = [];
|
|
192
|
+
|
|
193
|
+
commands.push(`CreateLandscapeSpline ${params.landscapeName} ${params.splineName}`);
|
|
194
|
+
|
|
195
|
+
for (const point of params.points) {
|
|
196
|
+
commands.push(`AddSplinePoint ${params.splineName} ${point.join(' ')}`);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (params.width !== undefined) {
|
|
200
|
+
commands.push(`SetSplineWidth ${params.splineName} ${params.width}`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (params.falloffWidth !== undefined) {
|
|
204
|
+
commands.push(`SetSplineFalloffWidth ${params.splineName} ${params.falloffWidth}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (params.meshPath) {
|
|
208
|
+
commands.push(`SetSplineMesh ${params.splineName} ${params.meshPath}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
for (const cmd of commands) {
|
|
212
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return { success: true, message: `Landscape spline ${params.splineName} created` };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Import heightmap
|
|
219
|
+
async importHeightmap(params: {
|
|
220
|
+
landscapeName: string;
|
|
221
|
+
heightmapPath: string;
|
|
222
|
+
scale?: [number, number, number];
|
|
223
|
+
}) {
|
|
224
|
+
const scale = params.scale || [100, 100, 100];
|
|
225
|
+
const command = `ImportLandscapeHeightmap ${params.landscapeName} ${params.heightmapPath} ${scale.join(' ')}`;
|
|
226
|
+
|
|
227
|
+
return this.bridge.executeConsoleCommand(command);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Export heightmap
|
|
231
|
+
async exportHeightmap(params: {
|
|
232
|
+
landscapeName: string;
|
|
233
|
+
exportPath: string;
|
|
234
|
+
format?: 'PNG' | 'RAW';
|
|
235
|
+
}) {
|
|
236
|
+
const format = params.format || 'PNG';
|
|
237
|
+
const command = `ExportLandscapeHeightmap ${params.landscapeName} ${params.exportPath} ${format}`;
|
|
238
|
+
|
|
239
|
+
return this.bridge.executeConsoleCommand(command);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Set landscape LOD
|
|
243
|
+
async setLandscapeLOD(params: {
|
|
244
|
+
landscapeName: string;
|
|
245
|
+
lodBias?: number;
|
|
246
|
+
forcedLOD?: number;
|
|
247
|
+
lodDistribution?: number;
|
|
248
|
+
}) {
|
|
249
|
+
const commands = [];
|
|
250
|
+
|
|
251
|
+
if (params.lodBias !== undefined) {
|
|
252
|
+
commands.push(`SetLandscapeLODBias ${params.landscapeName} ${params.lodBias}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (params.forcedLOD !== undefined) {
|
|
256
|
+
commands.push(`SetLandscapeForcedLOD ${params.landscapeName} ${params.forcedLOD}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (params.lodDistribution !== undefined) {
|
|
260
|
+
commands.push(`SetLandscapeLODDistribution ${params.landscapeName} ${params.lodDistribution}`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
for (const cmd of commands) {
|
|
264
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return { success: true, message: 'Landscape LOD settings updated' };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Create landscape grass
|
|
271
|
+
async createLandscapeGrass(params: {
|
|
272
|
+
landscapeName: string;
|
|
273
|
+
grassType: string;
|
|
274
|
+
density?: number;
|
|
275
|
+
minScale?: number;
|
|
276
|
+
maxScale?: number;
|
|
277
|
+
randomRotation?: boolean;
|
|
278
|
+
}) {
|
|
279
|
+
const commands = [];
|
|
280
|
+
|
|
281
|
+
commands.push(`CreateLandscapeGrass ${params.landscapeName} ${params.grassType}`);
|
|
282
|
+
|
|
283
|
+
if (params.density !== undefined) {
|
|
284
|
+
commands.push(`SetGrassDensity ${params.grassType} ${params.density}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
if (params.minScale !== undefined && params.maxScale !== undefined) {
|
|
288
|
+
commands.push(`SetGrassScale ${params.grassType} ${params.minScale} ${params.maxScale}`);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (params.randomRotation !== undefined) {
|
|
292
|
+
commands.push(`SetGrassRandomRotation ${params.grassType} ${params.randomRotation}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (const cmd of commands) {
|
|
296
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { success: true, message: `Grass type ${params.grassType} created on landscape` };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Landscape collision
|
|
303
|
+
async updateLandscapeCollision(params: {
|
|
304
|
+
landscapeName: string;
|
|
305
|
+
collisionMipLevel?: number;
|
|
306
|
+
simpleCollision?: boolean;
|
|
307
|
+
}) {
|
|
308
|
+
const commands = [];
|
|
309
|
+
|
|
310
|
+
if (params.collisionMipLevel !== undefined) {
|
|
311
|
+
commands.push(`SetLandscapeCollisionMipLevel ${params.landscapeName} ${params.collisionMipLevel}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (params.simpleCollision !== undefined) {
|
|
315
|
+
commands.push(`SetLandscapeSimpleCollision ${params.landscapeName} ${params.simpleCollision}`);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
commands.push(`UpdateLandscapeCollision ${params.landscapeName}`);
|
|
319
|
+
|
|
320
|
+
for (const cmd of commands) {
|
|
321
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return { success: true, message: 'Landscape collision updated' };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Retopologize landscape
|
|
328
|
+
async retopologizeLandscape(params: {
|
|
329
|
+
landscapeName: string;
|
|
330
|
+
targetTriangleCount?: number;
|
|
331
|
+
preserveDetails?: boolean;
|
|
332
|
+
}) {
|
|
333
|
+
const commands = [];
|
|
334
|
+
|
|
335
|
+
if (params.targetTriangleCount !== undefined) {
|
|
336
|
+
commands.push(`SetRetopologizeTarget ${params.targetTriangleCount}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if (params.preserveDetails !== undefined) {
|
|
340
|
+
commands.push(`SetRetopologizePreserveDetails ${params.preserveDetails}`);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
commands.push(`RetopologizeLandscape ${params.landscapeName}`);
|
|
344
|
+
|
|
345
|
+
for (const cmd of commands) {
|
|
346
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return { success: true, message: 'Landscape retopologized' };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Create water body
|
|
353
|
+
async createWaterBody(params: {
|
|
354
|
+
type: 'Ocean' | 'Lake' | 'River' | 'Stream';
|
|
355
|
+
name: string;
|
|
356
|
+
location?: [number, number, number];
|
|
357
|
+
size?: [number, number];
|
|
358
|
+
depth?: number;
|
|
359
|
+
}) {
|
|
360
|
+
const loc = params.location || [0, 0, 0];
|
|
361
|
+
const size = params.size || [1000, 1000];
|
|
362
|
+
const depth = params.depth || 100;
|
|
363
|
+
|
|
364
|
+
const command = `CreateWaterBody ${params.type} ${params.name} ${loc.join(' ')} ${size.join(' ')} ${depth}`;
|
|
365
|
+
|
|
366
|
+
return this.bridge.executeConsoleCommand(command);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// World Partition support for landscapes (UE 5.6)
|
|
370
|
+
async configureWorldPartition(params: {
|
|
371
|
+
landscapeName: string;
|
|
372
|
+
enableSpatialLoading?: boolean;
|
|
373
|
+
runtimeGrid?: string;
|
|
374
|
+
dataLayers?: string[];
|
|
375
|
+
streamingDistance?: number;
|
|
376
|
+
}) {
|
|
377
|
+
try {
|
|
378
|
+
const pythonScript = `
|
|
379
|
+
import unreal
|
|
380
|
+
|
|
381
|
+
try:
|
|
382
|
+
# Get the landscape actor
|
|
383
|
+
actors = unreal.EditorLevelLibrary.get_all_level_actors()
|
|
384
|
+
landscape = None
|
|
385
|
+
|
|
386
|
+
for actor in actors:
|
|
387
|
+
if actor.get_name() == "${params.landscapeName}" or actor.get_actor_label() == "${params.landscapeName}":
|
|
388
|
+
if isinstance(actor, unreal.LandscapeProxy) or isinstance(actor, unreal.Landscape):
|
|
389
|
+
landscape = actor
|
|
390
|
+
break
|
|
391
|
+
|
|
392
|
+
if not landscape:
|
|
393
|
+
print('RESULT:{"success": false, "error": "Landscape not found"}')
|
|
394
|
+
else:
|
|
395
|
+
changes_made = []
|
|
396
|
+
|
|
397
|
+
# Configure spatial loading (UE 5.6)
|
|
398
|
+
if ${params.enableSpatialLoading !== undefined ? 'True' : 'False'}:
|
|
399
|
+
try:
|
|
400
|
+
landscape.set_editor_property('is_spatially_loaded', ${params.enableSpatialLoading || false})
|
|
401
|
+
changes_made.append("Spatial loading: ${params.enableSpatialLoading}")
|
|
402
|
+
except:
|
|
403
|
+
pass
|
|
404
|
+
|
|
405
|
+
# Set runtime grid (UE 5.6 World Partition)
|
|
406
|
+
if "${params.runtimeGrid || ''}":
|
|
407
|
+
try:
|
|
408
|
+
landscape.set_editor_property('runtime_grid', unreal.Name("${params.runtimeGrid}"))
|
|
409
|
+
changes_made.append("Runtime grid: ${params.runtimeGrid}")
|
|
410
|
+
except:
|
|
411
|
+
pass
|
|
412
|
+
|
|
413
|
+
# Configure data layers (UE 5.6)
|
|
414
|
+
if ${params.dataLayers ? 'True' : 'False'}:
|
|
415
|
+
try:
|
|
416
|
+
world = unreal.EditorLevelLibrary.get_editor_world()
|
|
417
|
+
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
418
|
+
if data_layer_manager:
|
|
419
|
+
# Note: Full data layer API requires additional setup
|
|
420
|
+
changes_made.append("Data layers: Requires manual configuration")
|
|
421
|
+
except:
|
|
422
|
+
pass
|
|
423
|
+
|
|
424
|
+
if changes_made:
|
|
425
|
+
print('RESULT:{"success": true, "message": "World Partition configured", "changes": ' + str(changes_made).replace("'", '"') + '}')
|
|
426
|
+
else:
|
|
427
|
+
print('RESULT:{"success": false, "error": "No World Partition changes applied"}')
|
|
428
|
+
|
|
429
|
+
except Exception as e:
|
|
430
|
+
print(f'RESULT:{{"success": false, "error": "{str(e)}"}}')
|
|
431
|
+
`.trim();
|
|
432
|
+
|
|
433
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
434
|
+
const output = typeof response === 'string' ? response : JSON.stringify(response);
|
|
435
|
+
const match = output.match(/RESULT:({.*})/);
|
|
436
|
+
|
|
437
|
+
if (match) {
|
|
438
|
+
try {
|
|
439
|
+
return JSON.parse(match[1]);
|
|
440
|
+
} catch {}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return { success: true, message: 'World Partition configuration attempted' };
|
|
444
|
+
} catch (err) {
|
|
445
|
+
return { success: false, error: `Failed to configure World Partition: ${err}` };
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Set landscape data layers (UE 5.6)
|
|
450
|
+
async setDataLayers(params: {
|
|
451
|
+
landscapeName: string;
|
|
452
|
+
dataLayerNames: string[];
|
|
453
|
+
operation: 'add' | 'remove' | 'set';
|
|
454
|
+
}) {
|
|
455
|
+
try {
|
|
456
|
+
const commands = [];
|
|
457
|
+
|
|
458
|
+
// Use console commands for data layer management
|
|
459
|
+
if (params.operation === 'set' || params.operation === 'add') {
|
|
460
|
+
for (const layerName of params.dataLayerNames) {
|
|
461
|
+
commands.push(`wp.Runtime.SetDataLayerRuntimeState Loaded ${layerName}`);
|
|
462
|
+
}
|
|
463
|
+
} else if (params.operation === 'remove') {
|
|
464
|
+
for (const layerName of params.dataLayerNames) {
|
|
465
|
+
commands.push(`wp.Runtime.SetDataLayerRuntimeState Unloaded ${layerName}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Execute commands
|
|
470
|
+
for (const cmd of commands) {
|
|
471
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
success: true,
|
|
476
|
+
message: `Data layers ${params.operation === 'add' ? 'added' : params.operation === 'remove' ? 'removed' : 'set'} for landscape`,
|
|
477
|
+
layers: params.dataLayerNames
|
|
478
|
+
};
|
|
479
|
+
} catch (err) {
|
|
480
|
+
return { success: false, error: `Failed to manage data layers: ${err}` };
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Configure landscape streaming cells (UE 5.6 World Partition)
|
|
485
|
+
async configureStreamingCells(params: {
|
|
486
|
+
landscapeName: string;
|
|
487
|
+
cellSize?: number;
|
|
488
|
+
loadingRange?: number;
|
|
489
|
+
enableHLOD?: boolean;
|
|
490
|
+
}) {
|
|
491
|
+
const commands = [];
|
|
492
|
+
|
|
493
|
+
// World Partition runtime commands
|
|
494
|
+
if (params.loadingRange !== undefined) {
|
|
495
|
+
commands.push(`wp.Runtime.OverrideRuntimeSpatialHashLoadingRange -grid=0 -range=${params.loadingRange}`);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (params.enableHLOD !== undefined) {
|
|
499
|
+
commands.push(`wp.Runtime.HLOD ${params.enableHLOD ? '1' : '0'}`);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// Debug visualization commands
|
|
503
|
+
commands.push('wp.Runtime.ToggleDrawRuntimeHash2D'); // Show 2D grid
|
|
504
|
+
|
|
505
|
+
try {
|
|
506
|
+
for (const cmd of commands) {
|
|
507
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
success: true,
|
|
512
|
+
message: 'Streaming cells configured for World Partition',
|
|
513
|
+
settings: {
|
|
514
|
+
cellSize: params.cellSize,
|
|
515
|
+
loadingRange: params.loadingRange,
|
|
516
|
+
hlod: params.enableHLOD
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
} catch (err) {
|
|
520
|
+
return { success: false, error: `Failed to configure streaming cells: ${err}` };
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|