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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { interpretStandardResult, coerceBoolean, coerceNumber, coerceString, coerceStringArray } from '../utils/result-helpers.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Advanced Build Environment Tools
|
|
@@ -53,17 +54,8 @@ try:
|
|
|
53
54
|
proc_mesh_comp = proc_actor.get_component_by_class(unreal.ProceduralMeshComponent)
|
|
54
55
|
except:
|
|
55
56
|
# Fallback: Create empty actor and add ProceduralMeshComponent
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
location,
|
|
59
|
-
unreal.Rotator(0, 0, 0)
|
|
60
|
-
)
|
|
61
|
-
proc_actor.set_actor_label(f"{name}_ProceduralTerrain")
|
|
62
|
-
|
|
63
|
-
# Add procedural mesh component
|
|
64
|
-
proc_mesh_comp = unreal.ProceduralMeshComponent()
|
|
65
|
-
proc_actor.add_instance_component(proc_mesh_comp)
|
|
66
|
-
proc_mesh_comp.register_component()
|
|
57
|
+
# If spawning ProceduralMeshActor failed, surface a clear error about the plugin requirement
|
|
58
|
+
raise Exception("Failed to spawn ProceduralMeshActor. Ensure the 'Procedural Mesh Component' plugin is enabled and available.")
|
|
67
59
|
|
|
68
60
|
if proc_mesh_comp:
|
|
69
61
|
# Generate terrain mesh
|
|
@@ -146,19 +138,85 @@ except Exception as e:
|
|
|
146
138
|
print(f"RESULT:{json.dumps(result)}")
|
|
147
139
|
`.trim();
|
|
148
140
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
141
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
142
|
+
const interpreted = interpretStandardResult(response, {
|
|
143
|
+
successMessage: `Created procedural terrain '${params.name}'`,
|
|
144
|
+
failureMessage: `Failed to create procedural terrain '${params.name}'`
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
if (!interpreted.success) {
|
|
148
|
+
const failure: {
|
|
149
|
+
success: false;
|
|
150
|
+
error: string;
|
|
151
|
+
message: string;
|
|
152
|
+
warnings?: string[];
|
|
153
|
+
details?: string[];
|
|
154
|
+
payload?: Record<string, unknown>;
|
|
155
|
+
} = {
|
|
156
|
+
success: false,
|
|
157
|
+
error: interpreted.error ?? interpreted.message,
|
|
158
|
+
message: interpreted.message
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
if (interpreted.warnings) {
|
|
162
|
+
failure.warnings = interpreted.warnings;
|
|
163
|
+
}
|
|
164
|
+
if (interpreted.details) {
|
|
165
|
+
failure.details = interpreted.details;
|
|
166
|
+
}
|
|
167
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
168
|
+
failure.payload = interpreted.payload;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return failure;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const payload = { ...interpreted.payload } as Record<string, unknown>;
|
|
175
|
+
const actorName = coerceString(payload.actor_name) ?? coerceString(payload.actorName);
|
|
176
|
+
const vertices = coerceNumber(payload.vertices);
|
|
177
|
+
const triangles = coerceNumber(payload.triangles);
|
|
178
|
+
const subdivisions = coerceNumber(payload.subdivisions);
|
|
179
|
+
const sizeArray = Array.isArray(payload.size)
|
|
180
|
+
? (payload.size as unknown[]).map(entry => {
|
|
181
|
+
if (typeof entry === 'number' && Number.isFinite(entry)) {
|
|
182
|
+
return entry;
|
|
183
|
+
}
|
|
184
|
+
if (typeof entry === 'string') {
|
|
185
|
+
const parsed = Number(entry);
|
|
186
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}).filter((entry): entry is number => typeof entry === 'number')
|
|
190
|
+
: undefined;
|
|
191
|
+
|
|
192
|
+
payload.success = true;
|
|
193
|
+
payload.message = interpreted.message;
|
|
194
|
+
|
|
195
|
+
if (actorName) {
|
|
196
|
+
payload.actor_name = actorName;
|
|
197
|
+
payload.actorName = actorName;
|
|
198
|
+
}
|
|
199
|
+
if (typeof vertices === 'number') {
|
|
200
|
+
payload.vertices = vertices;
|
|
201
|
+
}
|
|
202
|
+
if (typeof triangles === 'number') {
|
|
203
|
+
payload.triangles = triangles;
|
|
204
|
+
}
|
|
205
|
+
if (typeof subdivisions === 'number') {
|
|
206
|
+
payload.subdivisions = subdivisions;
|
|
207
|
+
}
|
|
208
|
+
if (sizeArray && sizeArray.length === 2) {
|
|
209
|
+
payload.size = sizeArray;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (interpreted.warnings) {
|
|
213
|
+
payload.warnings = interpreted.warnings;
|
|
214
|
+
}
|
|
215
|
+
if (interpreted.details) {
|
|
216
|
+
payload.details = interpreted.details;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return payload as any;
|
|
162
220
|
}
|
|
163
221
|
|
|
164
222
|
/**
|
|
@@ -193,6 +251,10 @@ try:
|
|
|
193
251
|
subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
194
252
|
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
195
253
|
|
|
254
|
+
# Validate Procedural Foliage plugin/classes are available
|
|
255
|
+
if not hasattr(unreal, 'ProceduralFoliageVolume') or not hasattr(unreal, 'ProceduralFoliageSpawner'):
|
|
256
|
+
raise Exception("Procedural Foliage plugin not available. Please enable the 'Procedural Foliage' plugin and try again.")
|
|
257
|
+
|
|
196
258
|
# Create ProceduralFoliageVolume
|
|
197
259
|
volume_actor = subsys.spawn_actor_from_class(
|
|
198
260
|
unreal.ProceduralFoliageVolume,
|
|
@@ -200,7 +262,7 @@ try:
|
|
|
200
262
|
unreal.Rotator(0, 0, 0)
|
|
201
263
|
)
|
|
202
264
|
volume_actor.set_actor_label(f"{name}_ProceduralFoliageVolume")
|
|
203
|
-
volume_actor.set_actor_scale3d(bounds_size / 100) # Scale is in meters
|
|
265
|
+
volume_actor.set_actor_scale3d(unreal.Vector(bounds_size.x/100.0, bounds_size.y/100.0, bounds_size.z/100.0)) # Scale is in meters
|
|
204
266
|
|
|
205
267
|
# Get the procedural component
|
|
206
268
|
proc_comp = volume_actor.procedural_component
|
|
@@ -231,13 +293,14 @@ try:
|
|
|
231
293
|
)
|
|
232
294
|
|
|
233
295
|
if spawner:
|
|
234
|
-
# Configure spawner
|
|
235
|
-
spawner.random_seed
|
|
236
|
-
spawner.tile_size
|
|
296
|
+
# Configure spawner (use set_editor_property for read-only attributes)
|
|
297
|
+
spawner.set_editor_property('random_seed', seed)
|
|
298
|
+
spawner.set_editor_property('tile_size', max(bounds_size.x, bounds_size.y))
|
|
237
299
|
|
|
238
300
|
# Create foliage types
|
|
239
301
|
foliage_types = []
|
|
240
|
-
|
|
302
|
+
ft_input = json.loads(r'''${JSON.stringify(params.foliageTypes)}''')
|
|
303
|
+
for ft_params in ft_input:
|
|
241
304
|
# Load mesh
|
|
242
305
|
mesh = unreal.EditorAssetLibrary.load_asset(ft_params['meshPath'])
|
|
243
306
|
if mesh:
|
|
@@ -256,26 +319,26 @@ try:
|
|
|
256
319
|
ft_asset = unreal.FoliageType_InstancedStaticMesh()
|
|
257
320
|
|
|
258
321
|
if ft_asset:
|
|
259
|
-
# Configure foliage type
|
|
260
|
-
ft_asset.mesh
|
|
261
|
-
ft_asset.density
|
|
262
|
-
ft_asset.random_yaw
|
|
263
|
-
ft_asset.align_to_normal
|
|
322
|
+
# Configure foliage type (use set_editor_property)
|
|
323
|
+
ft_asset.set_editor_property('mesh', mesh)
|
|
324
|
+
ft_asset.set_editor_property('density', ft_params.get('density', 1.0))
|
|
325
|
+
ft_asset.set_editor_property('random_yaw', ft_params.get('randomYaw', True))
|
|
326
|
+
ft_asset.set_editor_property('align_to_normal', ft_params.get('alignToNormal', True))
|
|
264
327
|
|
|
265
328
|
min_scale = ft_params.get('minScale', 0.8)
|
|
266
329
|
max_scale = ft_params.get('maxScale', 1.2)
|
|
267
|
-
ft_asset.scale_x
|
|
268
|
-
ft_asset.scale_y
|
|
269
|
-
ft_asset.scale_z
|
|
330
|
+
ft_asset.set_editor_property('scale_x', unreal.FloatInterval(min_scale, max_scale))
|
|
331
|
+
ft_asset.set_editor_property('scale_y', unreal.FloatInterval(min_scale, max_scale))
|
|
332
|
+
ft_asset.set_editor_property('scale_z', unreal.FloatInterval(min_scale, max_scale))
|
|
270
333
|
|
|
271
|
-
ft_obj.foliage_type_object
|
|
334
|
+
ft_obj.set_editor_property('foliage_type_object', ft_asset)
|
|
272
335
|
foliage_types.append(ft_obj)
|
|
273
336
|
|
|
274
337
|
# Set foliage types on spawner
|
|
275
|
-
spawner.foliage_types
|
|
338
|
+
spawner.set_editor_property('foliage_types', foliage_types)
|
|
276
339
|
|
|
277
340
|
# Assign spawner to component
|
|
278
|
-
proc_comp.foliage_spawner
|
|
341
|
+
proc_comp.set_editor_property('foliage_spawner', spawner)
|
|
279
342
|
|
|
280
343
|
# Save spawner asset
|
|
281
344
|
unreal.EditorAssetLibrary.save_asset(spawner.get_path_name())
|
|
@@ -309,19 +372,80 @@ except Exception as e:
|
|
|
309
372
|
print(f"RESULT:{json.dumps(result)}")
|
|
310
373
|
`.trim();
|
|
311
374
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
375
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
376
|
+
const interpreted = interpretStandardResult(response, {
|
|
377
|
+
successMessage: `Created procedural foliage volume '${params.name}'`,
|
|
378
|
+
failureMessage: `Failed to create procedural foliage volume '${params.name}'`
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
if (!interpreted.success) {
|
|
382
|
+
const failure: {
|
|
383
|
+
success: false;
|
|
384
|
+
error: string;
|
|
385
|
+
message: string;
|
|
386
|
+
warnings?: string[];
|
|
387
|
+
details?: string[];
|
|
388
|
+
payload?: Record<string, unknown>;
|
|
389
|
+
} = {
|
|
390
|
+
success: false,
|
|
391
|
+
error: interpreted.error ?? interpreted.message,
|
|
392
|
+
message: interpreted.message
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
if (interpreted.warnings) {
|
|
396
|
+
failure.warnings = interpreted.warnings;
|
|
397
|
+
}
|
|
398
|
+
if (interpreted.details) {
|
|
399
|
+
failure.details = interpreted.details;
|
|
400
|
+
}
|
|
401
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
402
|
+
failure.payload = interpreted.payload;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return failure;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
const payload = { ...interpreted.payload } as Record<string, unknown>;
|
|
409
|
+
const volumeActor = coerceString(payload.volume_actor) ?? coerceString(payload.volumeActor);
|
|
410
|
+
const spawnerPath = coerceString(payload.spawner_path) ?? coerceString(payload.spawnerPath);
|
|
411
|
+
const foliageCount = coerceNumber(payload.foliage_types_count) ?? coerceNumber(payload.foliageTypesCount);
|
|
412
|
+
const resimulated = coerceBoolean(payload.resimulated);
|
|
413
|
+
const note = coerceString(payload.note);
|
|
414
|
+
const messages = coerceStringArray(payload.messages);
|
|
415
|
+
|
|
416
|
+
payload.success = true;
|
|
417
|
+
payload.message = interpreted.message;
|
|
418
|
+
|
|
419
|
+
if (volumeActor) {
|
|
420
|
+
payload.volume_actor = volumeActor;
|
|
421
|
+
payload.volumeActor = volumeActor;
|
|
422
|
+
}
|
|
423
|
+
if (spawnerPath) {
|
|
424
|
+
payload.spawner_path = spawnerPath;
|
|
425
|
+
payload.spawnerPath = spawnerPath;
|
|
426
|
+
}
|
|
427
|
+
if (typeof foliageCount === 'number') {
|
|
428
|
+
payload.foliage_types_count = foliageCount;
|
|
429
|
+
payload.foliageTypesCount = foliageCount;
|
|
430
|
+
}
|
|
431
|
+
if (typeof resimulated === 'boolean') {
|
|
432
|
+
payload.resimulated = resimulated;
|
|
433
|
+
}
|
|
434
|
+
if (note) {
|
|
435
|
+
payload.note = note;
|
|
436
|
+
}
|
|
437
|
+
if (messages && messages.length > 0) {
|
|
438
|
+
payload.messages = messages;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (interpreted.warnings) {
|
|
442
|
+
payload.warnings = interpreted.warnings;
|
|
443
|
+
}
|
|
444
|
+
if (interpreted.details) {
|
|
445
|
+
payload.details = interpreted.details;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return payload as any;
|
|
325
449
|
}
|
|
326
450
|
|
|
327
451
|
/**
|
|
@@ -393,19 +517,59 @@ except Exception as e:
|
|
|
393
517
|
print(f"RESULT:{json.dumps(result)}")
|
|
394
518
|
`.trim();
|
|
395
519
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
520
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
521
|
+
const interpreted = interpretStandardResult(response, {
|
|
522
|
+
successMessage: 'Foliage instances added',
|
|
523
|
+
failureMessage: 'Failed to add foliage instances'
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
if (!interpreted.success) {
|
|
527
|
+
const failure: {
|
|
528
|
+
success: false;
|
|
529
|
+
error: string;
|
|
530
|
+
message: string;
|
|
531
|
+
warnings?: string[];
|
|
532
|
+
details?: string[];
|
|
533
|
+
payload?: Record<string, unknown>;
|
|
534
|
+
} = {
|
|
535
|
+
success: false,
|
|
536
|
+
error: interpreted.error ?? interpreted.message,
|
|
537
|
+
message: interpreted.message
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
if (interpreted.warnings) {
|
|
541
|
+
failure.warnings = interpreted.warnings;
|
|
542
|
+
}
|
|
543
|
+
if (interpreted.details) {
|
|
544
|
+
failure.details = interpreted.details;
|
|
545
|
+
}
|
|
546
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
547
|
+
failure.payload = interpreted.payload;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
return failure;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const payload = { ...interpreted.payload } as Record<string, unknown>;
|
|
554
|
+
const count = coerceNumber(payload.instances_count) ?? coerceNumber(payload.instancesCount);
|
|
555
|
+
const message = coerceString(payload.message) ?? interpreted.message;
|
|
556
|
+
|
|
557
|
+
payload.success = true;
|
|
558
|
+
payload.message = message;
|
|
559
|
+
|
|
560
|
+
if (typeof count === 'number') {
|
|
561
|
+
payload.instances_count = count;
|
|
562
|
+
payload.instancesCount = count;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (interpreted.warnings) {
|
|
566
|
+
payload.warnings = interpreted.warnings;
|
|
567
|
+
}
|
|
568
|
+
if (interpreted.details) {
|
|
569
|
+
payload.details = interpreted.details;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
return payload as any;
|
|
409
573
|
}
|
|
410
574
|
|
|
411
575
|
/**
|
|
@@ -455,23 +619,31 @@ try:
|
|
|
455
619
|
# Load mesh
|
|
456
620
|
mesh = unreal.EditorAssetLibrary.load_asset(mesh_path)
|
|
457
621
|
if mesh:
|
|
458
|
-
# Configure grass type
|
|
622
|
+
# Configure grass type (use set_editor_property)
|
|
459
623
|
grass_variety = unreal.GrassVariety()
|
|
460
|
-
grass_variety.grass_mesh
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
grass_variety.
|
|
465
|
-
grass_variety.
|
|
466
|
-
grass_variety.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
grass_variety.
|
|
471
|
-
|
|
472
|
-
|
|
624
|
+
grass_variety.set_editor_property('grass_mesh', mesh)
|
|
625
|
+
# GrassDensity is PerPlatformFloat in UE5+; set via struct instance
|
|
626
|
+
pp_density = unreal.PerPlatformFloat()
|
|
627
|
+
pp_density.set_editor_property('Default', float(density * 100.0))
|
|
628
|
+
grass_variety.set_editor_property('grass_density', pp_density)
|
|
629
|
+
grass_variety.set_editor_property('use_grid', True)
|
|
630
|
+
grass_variety.set_editor_property('placement_jitter', 1.0)
|
|
631
|
+
# Set cull distances as PerPlatformInt and LOD as int (engine uses mixed types here)
|
|
632
|
+
pp_start = unreal.PerPlatformInt()
|
|
633
|
+
pp_start.set_editor_property('Default', 10000)
|
|
634
|
+
grass_variety.set_editor_property('start_cull_distance', pp_start)
|
|
635
|
+
pp_end = unreal.PerPlatformInt()
|
|
636
|
+
pp_end.set_editor_property('Default', 20000)
|
|
637
|
+
grass_variety.set_editor_property('end_cull_distance', pp_end)
|
|
638
|
+
grass_variety.set_editor_property('min_lod', -1)
|
|
639
|
+
grass_variety.set_editor_property('scaling', unreal.GrassScaling.UNIFORM)
|
|
640
|
+
grass_variety.set_editor_property('scale_x', unreal.FloatInterval(min_scale, max_scale))
|
|
641
|
+
grass_variety.set_editor_property('scale_y', unreal.FloatInterval(min_scale, max_scale))
|
|
642
|
+
grass_variety.set_editor_property('scale_z', unreal.FloatInterval(min_scale, max_scale))
|
|
643
|
+
grass_variety.set_editor_property('random_rotation', True)
|
|
644
|
+
grass_variety.set_editor_property('align_to_surface', True)
|
|
473
645
|
|
|
474
|
-
grass_type.grass_varieties
|
|
646
|
+
grass_type.set_editor_property('grass_varieties', [grass_variety])
|
|
475
647
|
|
|
476
648
|
# Save asset
|
|
477
649
|
unreal.EditorAssetLibrary.save_asset(grass_type.get_path_name())
|
|
@@ -496,31 +668,65 @@ except Exception as e:
|
|
|
496
668
|
print(f"RESULT:{json.dumps(result)}")
|
|
497
669
|
`.trim();
|
|
498
670
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
671
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
672
|
+
const interpreted = interpretStandardResult(response, {
|
|
673
|
+
successMessage: `Created landscape grass type '${params.name}'`,
|
|
674
|
+
failureMessage: `Failed to create landscape grass type '${params.name}'`
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
if (!interpreted.success) {
|
|
678
|
+
const failure: {
|
|
679
|
+
success: false;
|
|
680
|
+
error: string;
|
|
681
|
+
message: string;
|
|
682
|
+
warnings?: string[];
|
|
683
|
+
details?: string[];
|
|
684
|
+
payload?: Record<string, unknown>;
|
|
685
|
+
} = {
|
|
686
|
+
success: false,
|
|
687
|
+
error: interpreted.error ?? interpreted.message,
|
|
688
|
+
message: interpreted.message
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
if (interpreted.warnings) {
|
|
692
|
+
failure.warnings = interpreted.warnings;
|
|
693
|
+
}
|
|
694
|
+
if (interpreted.details) {
|
|
695
|
+
failure.details = interpreted.details;
|
|
696
|
+
}
|
|
697
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
698
|
+
failure.payload = interpreted.payload;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return failure;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const payload = { ...interpreted.payload } as Record<string, unknown>;
|
|
705
|
+
const assetPath = coerceString(payload.asset_path) ?? coerceString(payload.assetPath);
|
|
706
|
+
const note = coerceString(payload.note);
|
|
707
|
+
const messages = coerceStringArray(payload.messages);
|
|
708
|
+
|
|
709
|
+
payload.success = true;
|
|
710
|
+
payload.message = interpreted.message;
|
|
711
|
+
|
|
712
|
+
if (assetPath) {
|
|
713
|
+
payload.asset_path = assetPath;
|
|
714
|
+
payload.assetPath = assetPath;
|
|
715
|
+
}
|
|
716
|
+
if (note) {
|
|
717
|
+
payload.note = note;
|
|
718
|
+
}
|
|
719
|
+
if (messages && messages.length > 0) {
|
|
720
|
+
payload.messages = messages;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
if (interpreted.warnings) {
|
|
724
|
+
payload.warnings = interpreted.warnings;
|
|
725
|
+
}
|
|
726
|
+
if (interpreted.details) {
|
|
727
|
+
payload.details = interpreted.details;
|
|
728
|
+
}
|
|
513
729
|
|
|
514
|
-
|
|
515
|
-
if (response && typeof response === 'object') {
|
|
516
|
-
if (response.LogOutput && Array.isArray(response.LogOutput)) {
|
|
517
|
-
return response.LogOutput.map((log: any) => log.Output || '').join('');
|
|
518
|
-
} else if (response.CommandResult) {
|
|
519
|
-
return response.CommandResult;
|
|
520
|
-
} else if (response.ReturnValue) {
|
|
521
|
-
return JSON.stringify(response);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
return typeof response === 'string' ? response : JSON.stringify(response);
|
|
730
|
+
return payload as any;
|
|
525
731
|
}
|
|
526
732
|
}
|