unreal-engine-mcp-server 0.3.1 → 0.4.3
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/.env.production +1 -1
- package/.github/copilot-instructions.md +45 -0
- package/.github/workflows/publish-mcp.yml +1 -1
- package/README.md +22 -7
- package/dist/index.js +137 -46
- package/dist/prompts/index.d.ts +10 -3
- package/dist/prompts/index.js +186 -7
- package/dist/resources/actors.d.ts +19 -1
- package/dist/resources/actors.js +55 -64
- package/dist/resources/assets.d.ts +3 -2
- package/dist/resources/assets.js +117 -109
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +31 -56
- package/dist/tools/actors.d.ts +3 -14
- package/dist/tools/actors.js +246 -302
- package/dist/tools/animation.d.ts +57 -102
- package/dist/tools/animation.js +429 -450
- package/dist/tools/assets.d.ts +13 -2
- package/dist/tools/assets.js +58 -46
- package/dist/tools/audio.d.ts +22 -13
- package/dist/tools/audio.js +467 -121
- package/dist/tools/blueprint.d.ts +32 -13
- package/dist/tools/blueprint.js +699 -448
- package/dist/tools/build_environment_advanced.d.ts +0 -1
- package/dist/tools/build_environment_advanced.js +236 -87
- package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
- package/dist/tools/consolidated-tool-definitions.js +124 -255
- package/dist/tools/consolidated-tool-handlers.js +749 -766
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +170 -36
- package/dist/tools/editor.d.ts +9 -2
- package/dist/tools/editor.js +30 -44
- package/dist/tools/foliage.d.ts +34 -15
- package/dist/tools/foliage.js +97 -107
- package/dist/tools/introspection.js +19 -21
- package/dist/tools/landscape.d.ts +1 -2
- package/dist/tools/landscape.js +311 -168
- package/dist/tools/level.d.ts +3 -28
- package/dist/tools/level.js +642 -192
- package/dist/tools/lighting.d.ts +14 -3
- package/dist/tools/lighting.js +236 -123
- package/dist/tools/materials.d.ts +25 -7
- package/dist/tools/materials.js +102 -79
- package/dist/tools/niagara.d.ts +10 -12
- package/dist/tools/niagara.js +74 -94
- package/dist/tools/performance.d.ts +12 -4
- package/dist/tools/performance.js +38 -79
- package/dist/tools/physics.d.ts +34 -10
- package/dist/tools/physics.js +364 -292
- package/dist/tools/rc.js +98 -24
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +146 -24
- package/dist/tools/ui.d.ts +31 -4
- package/dist/tools/ui.js +83 -66
- package/dist/tools/visual.d.ts +11 -0
- package/dist/tools/visual.js +245 -30
- package/dist/types/tool-types.d.ts +0 -6
- package/dist/types/tool-types.js +1 -8
- package/dist/unreal-bridge.d.ts +32 -2
- package/dist/unreal-bridge.js +621 -127
- package/dist/utils/elicitation.d.ts +57 -0
- package/dist/utils/elicitation.js +104 -0
- package/dist/utils/error-handler.d.ts +0 -33
- package/dist/utils/error-handler.js +4 -111
- package/dist/utils/http.d.ts +2 -22
- package/dist/utils/http.js +12 -75
- package/dist/utils/normalize.d.ts +4 -4
- package/dist/utils/normalize.js +15 -7
- package/dist/utils/python-output.d.ts +18 -0
- package/dist/utils/python-output.js +290 -0
- package/dist/utils/python.d.ts +2 -0
- package/dist/utils/python.js +4 -0
- package/dist/utils/response-validator.d.ts +6 -1
- package/dist/utils/response-validator.js +66 -13
- package/dist/utils/result-helpers.d.ts +27 -0
- package/dist/utils/result-helpers.js +147 -0
- package/dist/utils/safe-json.d.ts +0 -2
- package/dist/utils/safe-json.js +0 -43
- package/dist/utils/validation.d.ts +16 -0
- package/dist/utils/validation.js +70 -7
- package/mcp-config-example.json +2 -2
- package/package.json +11 -10
- package/server.json +37 -14
- package/src/index.ts +146 -50
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +123 -102
- package/src/resources/levels.ts +37 -47
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +59 -45
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +312 -106
- package/src/tools/consolidated-tool-definitions.ts +136 -267
- package/src/tools/consolidated-tool-handlers.ts +871 -795
- package/src/tools/debug.ts +179 -38
- package/src/tools/editor.ts +35 -37
- package/src/tools/foliage.ts +110 -104
- package/src/tools/introspection.ts +24 -22
- package/src/tools/landscape.ts +334 -181
- package/src/tools/level.ts +683 -182
- package/src/tools/lighting.ts +244 -123
- package/src/tools/materials.ts +114 -83
- package/src/tools/niagara.ts +87 -81
- package/src/tools/performance.ts +49 -88
- package/src/tools/physics.ts +393 -299
- package/src/tools/rc.ts +103 -25
- package/src/tools/sequence.ts +157 -30
- package/src/tools/ui.ts +101 -70
- package/src/tools/visual.ts +250 -29
- package/src/types/tool-types.ts +0 -9
- package/src/unreal-bridge.ts +658 -140
- package/src/utils/elicitation.ts +129 -0
- package/src/utils/error-handler.ts +4 -159
- package/src/utils/http.ts +16 -115
- package/src/utils/normalize.ts +20 -10
- package/src/utils/python-output.ts +351 -0
- package/src/utils/python.ts +3 -0
- package/src/utils/response-validator.ts +68 -17
- package/src/utils/result-helpers.ts +193 -0
- package/src/utils/safe-json.ts +0 -50
- package/src/utils/validation.ts +94 -7
- package/tests/run-unreal-tool-tests.mjs +720 -0
- package/tsconfig.json +2 -2
- package/dist/python-utils.d.ts +0 -29
- package/dist/python-utils.js +0 -54
- package/dist/tools/tool-definitions.d.ts +0 -4919
- package/dist/tools/tool-definitions.js +0 -1065
- package/dist/tools/tool-handlers.d.ts +0 -47
- package/dist/tools/tool-handlers.js +0 -863
- package/dist/types/index.d.ts +0 -323
- package/dist/types/index.js +0 -28
- package/dist/utils/cache-manager.d.ts +0 -64
- package/dist/utils/cache-manager.js +0 -176
- package/dist/utils/errors.d.ts +0 -133
- package/dist/utils/errors.js +0 -256
- package/src/python/editor_compat.py +0 -181
- package/src/python-utils.ts +0 -57
- package/src/tools/tool-definitions.ts +0 -1081
- package/src/tools/tool-handlers.ts +0 -973
- package/src/types/index.ts +0 -414
- package/src/utils/cache-manager.ts +0 -213
- package/src/utils/errors.ts +0 -312
package/dist/tools/landscape.js
CHANGED
|
@@ -1,115 +1,246 @@
|
|
|
1
|
+
import { bestEffortInterpretedText, coerceBoolean, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
|
|
2
|
+
import { ensureVector3 } from '../utils/validation.js';
|
|
3
|
+
import { escapePythonString } from '../utils/python.js';
|
|
1
4
|
export class LandscapeTools {
|
|
2
5
|
bridge;
|
|
3
6
|
constructor(bridge) {
|
|
4
7
|
this.bridge = bridge;
|
|
5
8
|
}
|
|
6
|
-
// Execute console command
|
|
7
|
-
async _executeCommand(command) {
|
|
8
|
-
return this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
9
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
10
|
-
functionName: 'ExecuteConsoleCommand',
|
|
11
|
-
parameters: {
|
|
12
|
-
WorldContextObject: null,
|
|
13
|
-
Command: command,
|
|
14
|
-
SpecificPlayer: null
|
|
15
|
-
},
|
|
16
|
-
generateTransaction: false
|
|
17
|
-
});
|
|
18
|
-
}
|
|
19
9
|
// Create landscape with World Partition support (UE 5.6)
|
|
20
10
|
async createLandscape(params) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
11
|
+
const name = params.name?.trim();
|
|
12
|
+
if (!name) {
|
|
13
|
+
return { success: false, error: 'Landscape name is required' };
|
|
14
|
+
}
|
|
15
|
+
if (typeof params.sizeX === 'number' && params.sizeX <= 0) {
|
|
16
|
+
return {
|
|
17
|
+
success: false,
|
|
18
|
+
error: 'Landscape sizeX must be a positive number'
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (typeof params.sizeY === 'number' && params.sizeY <= 0) {
|
|
22
|
+
return {
|
|
23
|
+
success: false,
|
|
24
|
+
error: 'Landscape sizeY must be a positive number'
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const [locX, locY, locZ] = ensureVector3(params.location ?? [0, 0, 0], 'landscape location');
|
|
28
|
+
const sectionsPerComponent = Math.max(1, Math.floor(params.sectionsPerComponent ?? 1));
|
|
29
|
+
const quadsPerSection = Math.max(1, Math.floor(params.quadsPerSection ?? 63));
|
|
30
|
+
const componentCount = Math.max(1, Math.floor(params.componentCount ?? 1));
|
|
31
|
+
const defaultSize = 1000;
|
|
32
|
+
const scaleX = params.sizeX ? Math.max(0.1, params.sizeX / defaultSize) : 1;
|
|
33
|
+
const scaleY = params.sizeY ? Math.max(0.1, params.sizeY / defaultSize) : 1;
|
|
34
|
+
const escapedName = escapePythonString(name);
|
|
35
|
+
const escapedMaterial = params.materialPath && params.materialPath.trim().length > 0
|
|
36
|
+
? escapePythonString(params.materialPath.trim())
|
|
37
|
+
: '';
|
|
38
|
+
const runtimeGridFlag = params.runtimeGrid ? 'True' : 'False';
|
|
39
|
+
const spatiallyLoadedFlag = params.isSpatiallyLoaded ? 'True' : 'False';
|
|
40
|
+
const runtimeGridValue = params.runtimeGrid ? escapePythonString(params.runtimeGrid.trim()) : '';
|
|
41
|
+
const dataLayerNames = Array.isArray(params.dataLayers)
|
|
42
|
+
? params.dataLayers
|
|
43
|
+
.map(layer => layer?.trim())
|
|
44
|
+
.filter((layer) => Boolean(layer))
|
|
45
|
+
.map(layer => escapePythonString(layer))
|
|
46
|
+
: [];
|
|
47
|
+
const pythonScript = `
|
|
24
48
|
import unreal
|
|
25
49
|
import json
|
|
26
50
|
|
|
27
|
-
|
|
51
|
+
result = {
|
|
52
|
+
"success": False,
|
|
53
|
+
"message": "",
|
|
54
|
+
"error": "",
|
|
55
|
+
"warnings": [],
|
|
56
|
+
"details": [],
|
|
57
|
+
"landscapeName": "",
|
|
58
|
+
"landscapeActor": "",
|
|
59
|
+
"worldPartition": False,
|
|
60
|
+
"runtimeGridRequested": ${runtimeGridFlag},
|
|
61
|
+
"spatiallyLoaded": ${spatiallyLoadedFlag}
|
|
62
|
+
}
|
|
63
|
+
|
|
28
64
|
try:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
65
|
+
editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
66
|
+
world = editor_subsystem.get_editor_world() if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world') else None
|
|
67
|
+
data_layer_manager = None
|
|
68
|
+
if world:
|
|
33
69
|
try:
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
70
|
+
world_partition = world.get_world_partition()
|
|
71
|
+
result["worldPartition"] = world_partition is not None
|
|
72
|
+
if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
73
|
+
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
74
|
+
except Exception as wp_error:
|
|
75
|
+
result["warnings"].append(f"Failed to inspect world partition: {wp_error}")
|
|
37
76
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
77
|
+
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
78
|
+
if not actor_subsystem:
|
|
79
|
+
result["error"] = "EditorActorSubsystem unavailable"
|
|
80
|
+
else:
|
|
81
|
+
existing = None
|
|
42
82
|
try:
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
except:
|
|
50
|
-
pass
|
|
83
|
+
for actor in actor_subsystem.get_all_level_actors():
|
|
84
|
+
if actor and actor.get_actor_label() == "${escapedName}":
|
|
85
|
+
existing = actor
|
|
86
|
+
break
|
|
87
|
+
except Exception as scan_error:
|
|
88
|
+
result["warnings"].append(f"Actor scan failed: {scan_error}")
|
|
51
89
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
landscape_subsystem = None
|
|
60
|
-
try:
|
|
61
|
-
landscape_subsystem = unreal.get_editor_subsystem(unreal.LandscapeSubsystem)
|
|
62
|
-
except:
|
|
90
|
+
if existing:
|
|
91
|
+
result["success"] = True
|
|
92
|
+
result["message"] = "Landscape already exists"
|
|
93
|
+
result["landscapeName"] = existing.get_actor_label()
|
|
94
|
+
try:
|
|
95
|
+
result["landscapeActor"] = existing.get_path_name()
|
|
96
|
+
except Exception:
|
|
63
97
|
pass
|
|
64
|
-
|
|
65
|
-
if landscape_subsystem:
|
|
66
|
-
# Use subsystem if available
|
|
67
|
-
result = {"success": False, "error": "LandscapeSubsystem API limited via Python", "world_partition": is_world_partition}
|
|
68
98
|
else:
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
"
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
landscape_class = getattr(unreal, "Landscape", None)
|
|
100
|
+
if not landscape_class:
|
|
101
|
+
result["error"] = "Landscape class unavailable"
|
|
102
|
+
else:
|
|
103
|
+
location = unreal.Vector(${locX}, ${locY}, ${locZ})
|
|
104
|
+
rotation = unreal.Rotator(0.0, 0.0, 0.0)
|
|
105
|
+
landscape_actor = actor_subsystem.spawn_actor_from_class(landscape_class, location, rotation)
|
|
106
|
+
if not landscape_actor:
|
|
107
|
+
result["error"] = "Failed to spawn landscape actor"
|
|
108
|
+
else:
|
|
109
|
+
try:
|
|
110
|
+
landscape_actor.set_actor_label("${escapedName}", True)
|
|
111
|
+
except TypeError:
|
|
112
|
+
landscape_actor.set_actor_label("${escapedName}")
|
|
113
|
+
except Exception as label_error:
|
|
114
|
+
result["warnings"].append(f"Failed to set landscape label: {label_error}")
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
landscape_actor.set_actor_scale3d(unreal.Vector(${scaleX.toFixed(4)}, ${scaleY.toFixed(4)}, 1.0))
|
|
118
|
+
result["details"].append(f"Actor scale set to (${scaleX.toFixed(2)}, ${scaleY.toFixed(2)}, 1.0)")
|
|
119
|
+
except Exception as scale_error:
|
|
120
|
+
result["warnings"].append(f"Failed to set landscape scale: {scale_error}")
|
|
121
|
+
|
|
122
|
+
landscape_editor = None
|
|
123
|
+
try:
|
|
124
|
+
landscape_editor = unreal.get_editor_subsystem(unreal.LandscapeEditorSubsystem)
|
|
125
|
+
except Exception as editor_error:
|
|
126
|
+
result["warnings"].append(f"LandscapeEditorSubsystem unavailable: {editor_error}")
|
|
127
|
+
|
|
128
|
+
if landscape_editor:
|
|
129
|
+
try:
|
|
130
|
+
landscape_editor.set_component_size(${sectionsPerComponent}, ${quadsPerSection})
|
|
131
|
+
landscape_editor.set_component_count(${componentCount}, ${componentCount})
|
|
132
|
+
result["details"].append(f"Component size ${sectionsPerComponent}x${quadsPerSection}, count ${componentCount}x${componentCount}")
|
|
133
|
+
except Exception as config_error:
|
|
134
|
+
result["warnings"].append(f"Landscape configuration limited: {config_error}")
|
|
135
|
+
|
|
136
|
+
${escapedMaterial ? `try:
|
|
137
|
+
material = unreal.EditorAssetLibrary.load_asset("${escapedMaterial}")
|
|
138
|
+
if material:
|
|
139
|
+
try:
|
|
140
|
+
landscape_actor.set_landscape_material(material)
|
|
141
|
+
except Exception:
|
|
142
|
+
landscape_actor.editor_set_landscape_material(material)
|
|
143
|
+
result["details"].append("Landscape material applied")
|
|
144
|
+
else:
|
|
145
|
+
result["warnings"].append("Landscape material asset not found: ${escapedMaterial}")
|
|
146
|
+
except Exception as material_error:
|
|
147
|
+
result["warnings"].append(f"Failed to apply landscape material: {material_error}")
|
|
148
|
+
` : ''}
|
|
149
|
+
${runtimeGridValue ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
150
|
+
try:
|
|
151
|
+
unreal.WorldPartitionBlueprintLibrary.set_actor_runtime_grid(landscape_actor, "${runtimeGridValue}")
|
|
152
|
+
result["details"].append("Runtime grid assigned: ${runtimeGridValue}")
|
|
153
|
+
except Exception as grid_error:
|
|
154
|
+
result["warnings"].append(f"Failed to assign runtime grid: {grid_error}")
|
|
155
|
+
` : ''}
|
|
156
|
+
${params.isSpatiallyLoaded ? `if result["worldPartition"] and hasattr(unreal, "WorldPartitionBlueprintLibrary"):
|
|
157
|
+
try:
|
|
158
|
+
unreal.WorldPartitionBlueprintLibrary.set_actor_spatially_loaded(landscape_actor, True)
|
|
159
|
+
result["details"].append("Actor marked as spatially loaded")
|
|
160
|
+
except Exception as spatial_error:
|
|
161
|
+
result["warnings"].append(f"Failed to mark as spatially loaded: {spatial_error}")
|
|
162
|
+
` : ''}
|
|
163
|
+
${dataLayerNames.length ? `if result["worldPartition"] and data_layer_manager:
|
|
164
|
+
for layer_name in ${JSON.stringify(dataLayerNames)}:
|
|
165
|
+
try:
|
|
166
|
+
data_layer = data_layer_manager.get_data_layer(layer_name)
|
|
167
|
+
if data_layer:
|
|
168
|
+
unreal.WorldPartitionBlueprintLibrary.add_actor_to_data_layer(landscape_actor, data_layer)
|
|
169
|
+
result["details"].append(f"Added to data layer {layer_name}")
|
|
170
|
+
else:
|
|
171
|
+
result["warnings"].append(f"Data layer not found: {layer_name}")
|
|
172
|
+
except Exception as data_layer_error:
|
|
173
|
+
result["warnings"].append(f"Failed to assign data layer {layer_name}: {data_layer_error}")
|
|
174
|
+
` : ''}
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
result["landscapeName"] = landscape_actor.get_actor_label()
|
|
178
|
+
result["landscapeActor"] = landscape_actor.get_path_name()
|
|
179
|
+
except Exception:
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
result["success"] = True
|
|
183
|
+
result["message"] = "Landscape actor created"
|
|
86
184
|
except Exception as e:
|
|
87
|
-
|
|
185
|
+
result["error"] = str(e)
|
|
186
|
+
|
|
187
|
+
if result.get("success"):
|
|
188
|
+
result.pop("error", None)
|
|
189
|
+
else:
|
|
190
|
+
if not result.get("error"):
|
|
191
|
+
result["error"] = "Failed to create landscape actor"
|
|
192
|
+
if not result.get("message"):
|
|
193
|
+
result["message"] = result["error"]
|
|
194
|
+
|
|
195
|
+
if not result.get("warnings"):
|
|
196
|
+
result.pop("warnings", None)
|
|
197
|
+
if not result.get("details"):
|
|
198
|
+
result.pop("details", None)
|
|
199
|
+
|
|
200
|
+
print("RESULT:" + json.dumps(result))
|
|
88
201
|
`.trim();
|
|
202
|
+
try {
|
|
89
203
|
const response = await this.bridge.executePython(pythonScript);
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
204
|
+
const interpreted = interpretStandardResult(response, {
|
|
205
|
+
successMessage: 'Landscape actor created',
|
|
206
|
+
failureMessage: 'Failed to create landscape actor'
|
|
207
|
+
});
|
|
208
|
+
if (!interpreted.success) {
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
error: interpreted.error || interpreted.message
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const result = {
|
|
215
|
+
success: true,
|
|
216
|
+
message: interpreted.message,
|
|
217
|
+
landscapeName: coerceString(interpreted.payload.landscapeName) ?? name,
|
|
218
|
+
worldPartition: coerceBoolean(interpreted.payload.worldPartition)
|
|
219
|
+
};
|
|
220
|
+
const actorPath = coerceString(interpreted.payload.landscapeActor);
|
|
221
|
+
if (actorPath) {
|
|
222
|
+
result.landscapeActor = actorPath;
|
|
101
223
|
}
|
|
224
|
+
if (interpreted.warnings?.length) {
|
|
225
|
+
result.warnings = interpreted.warnings;
|
|
226
|
+
}
|
|
227
|
+
if (interpreted.details?.length) {
|
|
228
|
+
result.details = interpreted.details;
|
|
229
|
+
}
|
|
230
|
+
if (params.runtimeGrid) {
|
|
231
|
+
result.runtimeGrid = params.runtimeGrid;
|
|
232
|
+
}
|
|
233
|
+
if (typeof params.isSpatiallyLoaded === 'boolean') {
|
|
234
|
+
result.spatiallyLoaded = params.isSpatiallyLoaded;
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
102
237
|
}
|
|
103
|
-
catch {
|
|
104
|
-
|
|
238
|
+
catch (error) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: `Failed to create landscape actor: ${error}`
|
|
242
|
+
};
|
|
105
243
|
}
|
|
106
|
-
// Fallback message with World Partition info
|
|
107
|
-
return {
|
|
108
|
-
success: false,
|
|
109
|
-
error: 'Landscape creation via API is limited. Please use the Unreal Editor UI to create landscapes.',
|
|
110
|
-
worldPartitionSupport: params.enableWorldPartition ? 'Requested' : 'Not requested',
|
|
111
|
-
suggestion: 'Use the Landscape Mode in the editor toolbar to create and configure landscapes'
|
|
112
|
-
};
|
|
113
244
|
}
|
|
114
245
|
// Sculpt landscape
|
|
115
246
|
async sculptLandscape(_params) {
|
|
@@ -129,9 +260,7 @@ except Exception as e:
|
|
|
129
260
|
if (params.blendMode) {
|
|
130
261
|
commands.push(`SetLayerBlendMode ${params.layerName} ${params.blendMode}`);
|
|
131
262
|
}
|
|
132
|
-
|
|
133
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
134
|
-
}
|
|
263
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
135
264
|
return { success: true, message: `Layer ${params.layerName} added to landscape` };
|
|
136
265
|
}
|
|
137
266
|
// Create landscape spline
|
|
@@ -150,9 +279,7 @@ except Exception as e:
|
|
|
150
279
|
if (params.meshPath) {
|
|
151
280
|
commands.push(`SetSplineMesh ${params.splineName} ${params.meshPath}`);
|
|
152
281
|
}
|
|
153
|
-
|
|
154
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
155
|
-
}
|
|
282
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
156
283
|
return { success: true, message: `Landscape spline ${params.splineName} created` };
|
|
157
284
|
}
|
|
158
285
|
// Import heightmap
|
|
@@ -179,9 +306,7 @@ except Exception as e:
|
|
|
179
306
|
if (params.lodDistribution !== undefined) {
|
|
180
307
|
commands.push(`SetLandscapeLODDistribution ${params.landscapeName} ${params.lodDistribution}`);
|
|
181
308
|
}
|
|
182
|
-
|
|
183
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
184
|
-
}
|
|
309
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
185
310
|
return { success: true, message: 'Landscape LOD settings updated' };
|
|
186
311
|
}
|
|
187
312
|
// Create landscape grass
|
|
@@ -197,9 +322,7 @@ except Exception as e:
|
|
|
197
322
|
if (params.randomRotation !== undefined) {
|
|
198
323
|
commands.push(`SetGrassRandomRotation ${params.grassType} ${params.randomRotation}`);
|
|
199
324
|
}
|
|
200
|
-
|
|
201
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
202
|
-
}
|
|
325
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
203
326
|
return { success: true, message: `Grass type ${params.grassType} created on landscape` };
|
|
204
327
|
}
|
|
205
328
|
// Landscape collision
|
|
@@ -212,9 +335,7 @@ except Exception as e:
|
|
|
212
335
|
commands.push(`SetLandscapeSimpleCollision ${params.landscapeName} ${params.simpleCollision}`);
|
|
213
336
|
}
|
|
214
337
|
commands.push(`UpdateLandscapeCollision ${params.landscapeName}`);
|
|
215
|
-
|
|
216
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
217
|
-
}
|
|
338
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
218
339
|
return { success: true, message: 'Landscape collision updated' };
|
|
219
340
|
}
|
|
220
341
|
// Retopologize landscape
|
|
@@ -227,9 +348,7 @@ except Exception as e:
|
|
|
227
348
|
commands.push(`SetRetopologizePreserveDetails ${params.preserveDetails}`);
|
|
228
349
|
}
|
|
229
350
|
commands.push(`RetopologizeLandscape ${params.landscapeName}`);
|
|
230
|
-
|
|
231
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
232
|
-
}
|
|
351
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
233
352
|
return { success: true, message: 'Landscape retopologized' };
|
|
234
353
|
}
|
|
235
354
|
// Create water body
|
|
@@ -245,68 +364,96 @@ except Exception as e:
|
|
|
245
364
|
try {
|
|
246
365
|
const pythonScript = `
|
|
247
366
|
import unreal
|
|
367
|
+
import json
|
|
368
|
+
|
|
369
|
+
result = {'success': False, 'error': 'Landscape not found'}
|
|
248
370
|
|
|
249
371
|
try:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
372
|
+
# Get the landscape actor using modern EditorActorSubsystem
|
|
373
|
+
actors = []
|
|
374
|
+
try:
|
|
375
|
+
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
376
|
+
if actor_subsystem and hasattr(actor_subsystem, 'get_all_level_actors'):
|
|
377
|
+
actors = actor_subsystem.get_all_level_actors()
|
|
378
|
+
except Exception:
|
|
379
|
+
actors = []
|
|
380
|
+
landscape = None
|
|
381
|
+
|
|
382
|
+
for actor in actors:
|
|
383
|
+
if actor.get_name() == "${params.landscapeName}" or actor.get_actor_label() == "${params.landscapeName}":
|
|
384
|
+
if isinstance(actor, unreal.LandscapeProxy) or isinstance(actor, unreal.Landscape):
|
|
385
|
+
landscape = actor
|
|
386
|
+
break
|
|
387
|
+
|
|
388
|
+
if landscape:
|
|
389
|
+
changes_made = []
|
|
390
|
+
|
|
391
|
+
# Configure spatial loading (UE 5.6)
|
|
392
|
+
if ${params.enableSpatialLoading !== undefined ? 'True' : 'False'}:
|
|
393
|
+
try:
|
|
394
|
+
landscape.set_editor_property('is_spatially_loaded', ${params.enableSpatialLoading || false})
|
|
395
|
+
changes_made.append("Spatial loading: ${params.enableSpatialLoading}")
|
|
396
|
+
except:
|
|
397
|
+
pass
|
|
398
|
+
|
|
399
|
+
# Set runtime grid (UE 5.6 World Partition)
|
|
400
|
+
if "${params.runtimeGrid || ''}":
|
|
401
|
+
try:
|
|
402
|
+
landscape.set_editor_property('runtime_grid', unreal.Name("${params.runtimeGrid}"))
|
|
403
|
+
changes_made.append("Runtime grid: ${params.runtimeGrid}")
|
|
404
|
+
except:
|
|
405
|
+
pass
|
|
406
|
+
|
|
407
|
+
# Configure data layers (UE 5.6)
|
|
408
|
+
if ${params.dataLayers ? 'True' : 'False'}:
|
|
409
|
+
try:
|
|
410
|
+
# Try modern subsystem first
|
|
411
|
+
try:
|
|
412
|
+
world = None
|
|
413
|
+
editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
414
|
+
if editor_subsystem and hasattr(editor_subsystem, 'get_editor_world'):
|
|
415
|
+
world = editor_subsystem.get_editor_world()
|
|
416
|
+
if world is None:
|
|
417
|
+
world = unreal.EditorSubsystemLibrary.get_editor_world()
|
|
418
|
+
except Exception:
|
|
419
|
+
world = unreal.EditorSubsystemLibrary.get_editor_world()
|
|
420
|
+
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
421
|
+
if data_layer_manager:
|
|
422
|
+
# Note: Full data layer API requires additional setup
|
|
423
|
+
changes_made.append("Data layers: Requires manual configuration")
|
|
424
|
+
except:
|
|
425
|
+
pass
|
|
426
|
+
|
|
427
|
+
if changes_made:
|
|
428
|
+
result = {
|
|
429
|
+
'success': True,
|
|
430
|
+
'message': 'World Partition configured',
|
|
431
|
+
'changes': changes_made
|
|
432
|
+
}
|
|
262
433
|
else:
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
landscape.set_editor_property('is_spatially_loaded', ${params.enableSpatialLoading || false})
|
|
269
|
-
changes_made.append("Spatial loading: ${params.enableSpatialLoading}")
|
|
270
|
-
except:
|
|
271
|
-
pass
|
|
272
|
-
|
|
273
|
-
# Set runtime grid (UE 5.6 World Partition)
|
|
274
|
-
if "${params.runtimeGrid || ''}":
|
|
275
|
-
try:
|
|
276
|
-
landscape.set_editor_property('runtime_grid', unreal.Name("${params.runtimeGrid}"))
|
|
277
|
-
changes_made.append("Runtime grid: ${params.runtimeGrid}")
|
|
278
|
-
except:
|
|
279
|
-
pass
|
|
280
|
-
|
|
281
|
-
# Configure data layers (UE 5.6)
|
|
282
|
-
if ${params.dataLayers ? 'True' : 'False'}:
|
|
283
|
-
try:
|
|
284
|
-
world = unreal.EditorLevelLibrary.get_editor_world()
|
|
285
|
-
data_layer_manager = unreal.WorldPartitionBlueprintLibrary.get_data_layer_manager(world)
|
|
286
|
-
if data_layer_manager:
|
|
287
|
-
# Note: Full data layer API requires additional setup
|
|
288
|
-
changes_made.append("Data layers: Requires manual configuration")
|
|
289
|
-
except:
|
|
290
|
-
pass
|
|
291
|
-
|
|
292
|
-
if changes_made:
|
|
293
|
-
print('RESULT:{"success": true, "message": "World Partition configured", "changes": ' + str(changes_made).replace("'", '"') + '}')
|
|
294
|
-
else:
|
|
295
|
-
print('RESULT:{"success": false, "error": "No World Partition changes applied"}')
|
|
296
|
-
|
|
434
|
+
result = {
|
|
435
|
+
'success': False,
|
|
436
|
+
'error': 'No World Partition changes applied'
|
|
437
|
+
}
|
|
438
|
+
|
|
297
439
|
except Exception as e:
|
|
298
|
-
|
|
440
|
+
result = {'success': False, 'error': str(e)}
|
|
441
|
+
|
|
442
|
+
print('RESULT:' + json.dumps(result))
|
|
299
443
|
`.trim();
|
|
300
444
|
const response = await this.bridge.executePython(pythonScript);
|
|
301
|
-
const
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
catch { }
|
|
445
|
+
const interpreted = interpretStandardResult(response, {
|
|
446
|
+
successMessage: 'World Partition configuration attempted',
|
|
447
|
+
failureMessage: 'World Partition configuration failed'
|
|
448
|
+
});
|
|
449
|
+
if (interpreted.success) {
|
|
450
|
+
return interpreted.payload;
|
|
308
451
|
}
|
|
309
|
-
return {
|
|
452
|
+
return {
|
|
453
|
+
success: false,
|
|
454
|
+
error: interpreted.error ?? 'World Partition configuration failed',
|
|
455
|
+
details: bestEffortInterpretedText(interpreted)
|
|
456
|
+
};
|
|
310
457
|
}
|
|
311
458
|
catch (err) {
|
|
312
459
|
return { success: false, error: `Failed to configure World Partition: ${err}` };
|
|
@@ -328,9 +475,7 @@ except Exception as e:
|
|
|
328
475
|
}
|
|
329
476
|
}
|
|
330
477
|
// Execute commands
|
|
331
|
-
|
|
332
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
333
|
-
}
|
|
478
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
334
479
|
return {
|
|
335
480
|
success: true,
|
|
336
481
|
message: `Data layers ${params.operation === 'add' ? 'added' : params.operation === 'remove' ? 'removed' : 'set'} for landscape`,
|
|
@@ -354,9 +499,7 @@ except Exception as e:
|
|
|
354
499
|
// Debug visualization commands
|
|
355
500
|
commands.push('wp.Runtime.ToggleDrawRuntimeHash2D'); // Show 2D grid
|
|
356
501
|
try {
|
|
357
|
-
|
|
358
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
359
|
-
}
|
|
502
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
360
503
|
return {
|
|
361
504
|
success: true,
|
|
362
505
|
message: 'Streaming cells configured for World Partition',
|
package/dist/tools/level.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { UnrealBridge } from '../unreal-bridge.js';
|
|
|
2
2
|
export declare class LevelTools {
|
|
3
3
|
private bridge;
|
|
4
4
|
constructor(bridge: UnrealBridge);
|
|
5
|
-
private _executeCommand;
|
|
6
5
|
loadLevel(params: {
|
|
7
6
|
levelPath: string;
|
|
8
7
|
streaming?: boolean;
|
|
@@ -11,28 +10,12 @@ export declare class LevelTools {
|
|
|
11
10
|
saveLevel(_params: {
|
|
12
11
|
levelName?: string;
|
|
13
12
|
savePath?: string;
|
|
14
|
-
}): Promise<
|
|
15
|
-
success: boolean;
|
|
16
|
-
message: string;
|
|
17
|
-
error?: undefined;
|
|
18
|
-
} | {
|
|
19
|
-
success: boolean;
|
|
20
|
-
error: any;
|
|
21
|
-
message?: undefined;
|
|
22
|
-
}>;
|
|
13
|
+
}): Promise<Record<string, unknown>>;
|
|
23
14
|
createLevel(params: {
|
|
24
15
|
levelName: string;
|
|
25
16
|
template?: 'Empty' | 'Default' | 'VR' | 'TimeOfDay';
|
|
26
17
|
savePath?: string;
|
|
27
|
-
}): Promise<
|
|
28
|
-
success: boolean;
|
|
29
|
-
message: any;
|
|
30
|
-
error?: undefined;
|
|
31
|
-
} | {
|
|
32
|
-
success: boolean;
|
|
33
|
-
error: any;
|
|
34
|
-
message?: undefined;
|
|
35
|
-
}>;
|
|
18
|
+
}): Promise<Record<string, unknown>>;
|
|
36
19
|
streamLevel(params: {
|
|
37
20
|
levelName: string;
|
|
38
21
|
shouldBeLoaded: boolean;
|
|
@@ -79,15 +62,7 @@ export declare class LevelTools {
|
|
|
79
62
|
buildNavMesh(params: {
|
|
80
63
|
rebuildAll?: boolean;
|
|
81
64
|
selectedOnly?: boolean;
|
|
82
|
-
}): Promise<
|
|
83
|
-
success: boolean;
|
|
84
|
-
message: any;
|
|
85
|
-
error?: undefined;
|
|
86
|
-
} | {
|
|
87
|
-
success: boolean;
|
|
88
|
-
error: any;
|
|
89
|
-
message?: undefined;
|
|
90
|
-
}>;
|
|
65
|
+
}): Promise<Record<string, unknown>>;
|
|
91
66
|
setLevelVisibility(params: {
|
|
92
67
|
levelName: string;
|
|
93
68
|
visible: boolean;
|