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,3 +1,4 @@
|
|
|
1
|
+
import { interpretStandardResult, coerceBoolean, coerceNumber, coerceString, coerceStringArray } from '../utils/result-helpers.js';
|
|
1
2
|
/**
|
|
2
3
|
* Advanced Build Environment Tools
|
|
3
4
|
* Implements procedural terrain and foliage using documented Unreal Engine Python APIs
|
|
@@ -45,17 +46,8 @@ try:
|
|
|
45
46
|
proc_mesh_comp = proc_actor.get_component_by_class(unreal.ProceduralMeshComponent)
|
|
46
47
|
except:
|
|
47
48
|
# Fallback: Create empty actor and add ProceduralMeshComponent
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
location,
|
|
51
|
-
unreal.Rotator(0, 0, 0)
|
|
52
|
-
)
|
|
53
|
-
proc_actor.set_actor_label(f"{name}_ProceduralTerrain")
|
|
54
|
-
|
|
55
|
-
# Add procedural mesh component
|
|
56
|
-
proc_mesh_comp = unreal.ProceduralMeshComponent()
|
|
57
|
-
proc_actor.add_instance_component(proc_mesh_comp)
|
|
58
|
-
proc_mesh_comp.register_component()
|
|
49
|
+
# If spawning ProceduralMeshActor failed, surface a clear error about the plugin requirement
|
|
50
|
+
raise Exception("Failed to spawn ProceduralMeshActor. Ensure the 'Procedural Mesh Component' plugin is enabled and available.")
|
|
59
51
|
|
|
60
52
|
if proc_mesh_comp:
|
|
61
53
|
# Generate terrain mesh
|
|
@@ -138,17 +130,69 @@ except Exception as e:
|
|
|
138
130
|
print(f"RESULT:{json.dumps(result)}")
|
|
139
131
|
`.trim();
|
|
140
132
|
const response = await this.bridge.executePython(pythonScript);
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
const interpreted = interpretStandardResult(response, {
|
|
134
|
+
successMessage: `Created procedural terrain '${params.name}'`,
|
|
135
|
+
failureMessage: `Failed to create procedural terrain '${params.name}'`
|
|
136
|
+
});
|
|
137
|
+
if (!interpreted.success) {
|
|
138
|
+
const failure = {
|
|
139
|
+
success: false,
|
|
140
|
+
error: interpreted.error ?? interpreted.message,
|
|
141
|
+
message: interpreted.message
|
|
142
|
+
};
|
|
143
|
+
if (interpreted.warnings) {
|
|
144
|
+
failure.warnings = interpreted.warnings;
|
|
145
|
+
}
|
|
146
|
+
if (interpreted.details) {
|
|
147
|
+
failure.details = interpreted.details;
|
|
146
148
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
150
|
+
failure.payload = interpreted.payload;
|
|
149
151
|
}
|
|
152
|
+
return failure;
|
|
153
|
+
}
|
|
154
|
+
const payload = { ...interpreted.payload };
|
|
155
|
+
const actorName = coerceString(payload.actor_name) ?? coerceString(payload.actorName);
|
|
156
|
+
const vertices = coerceNumber(payload.vertices);
|
|
157
|
+
const triangles = coerceNumber(payload.triangles);
|
|
158
|
+
const subdivisions = coerceNumber(payload.subdivisions);
|
|
159
|
+
const sizeArray = Array.isArray(payload.size)
|
|
160
|
+
? payload.size.map(entry => {
|
|
161
|
+
if (typeof entry === 'number' && Number.isFinite(entry)) {
|
|
162
|
+
return entry;
|
|
163
|
+
}
|
|
164
|
+
if (typeof entry === 'string') {
|
|
165
|
+
const parsed = Number(entry);
|
|
166
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
167
|
+
}
|
|
168
|
+
return undefined;
|
|
169
|
+
}).filter((entry) => typeof entry === 'number')
|
|
170
|
+
: undefined;
|
|
171
|
+
payload.success = true;
|
|
172
|
+
payload.message = interpreted.message;
|
|
173
|
+
if (actorName) {
|
|
174
|
+
payload.actor_name = actorName;
|
|
175
|
+
payload.actorName = actorName;
|
|
176
|
+
}
|
|
177
|
+
if (typeof vertices === 'number') {
|
|
178
|
+
payload.vertices = vertices;
|
|
179
|
+
}
|
|
180
|
+
if (typeof triangles === 'number') {
|
|
181
|
+
payload.triangles = triangles;
|
|
150
182
|
}
|
|
151
|
-
|
|
183
|
+
if (typeof subdivisions === 'number') {
|
|
184
|
+
payload.subdivisions = subdivisions;
|
|
185
|
+
}
|
|
186
|
+
if (sizeArray && sizeArray.length === 2) {
|
|
187
|
+
payload.size = sizeArray;
|
|
188
|
+
}
|
|
189
|
+
if (interpreted.warnings) {
|
|
190
|
+
payload.warnings = interpreted.warnings;
|
|
191
|
+
}
|
|
192
|
+
if (interpreted.details) {
|
|
193
|
+
payload.details = interpreted.details;
|
|
194
|
+
}
|
|
195
|
+
return payload;
|
|
152
196
|
}
|
|
153
197
|
/**
|
|
154
198
|
* Create procedural foliage using ProceduralFoliageSpawner
|
|
@@ -170,6 +214,10 @@ try:
|
|
|
170
214
|
subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
171
215
|
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
172
216
|
|
|
217
|
+
# Validate Procedural Foliage plugin/classes are available
|
|
218
|
+
if not hasattr(unreal, 'ProceduralFoliageVolume') or not hasattr(unreal, 'ProceduralFoliageSpawner'):
|
|
219
|
+
raise Exception("Procedural Foliage plugin not available. Please enable the 'Procedural Foliage' plugin and try again.")
|
|
220
|
+
|
|
173
221
|
# Create ProceduralFoliageVolume
|
|
174
222
|
volume_actor = subsys.spawn_actor_from_class(
|
|
175
223
|
unreal.ProceduralFoliageVolume,
|
|
@@ -177,7 +225,7 @@ try:
|
|
|
177
225
|
unreal.Rotator(0, 0, 0)
|
|
178
226
|
)
|
|
179
227
|
volume_actor.set_actor_label(f"{name}_ProceduralFoliageVolume")
|
|
180
|
-
volume_actor.set_actor_scale3d(bounds_size / 100) # Scale is in meters
|
|
228
|
+
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
|
|
181
229
|
|
|
182
230
|
# Get the procedural component
|
|
183
231
|
proc_comp = volume_actor.procedural_component
|
|
@@ -208,13 +256,14 @@ try:
|
|
|
208
256
|
)
|
|
209
257
|
|
|
210
258
|
if spawner:
|
|
211
|
-
# Configure spawner
|
|
212
|
-
spawner.random_seed
|
|
213
|
-
spawner.tile_size
|
|
259
|
+
# Configure spawner (use set_editor_property for read-only attributes)
|
|
260
|
+
spawner.set_editor_property('random_seed', seed)
|
|
261
|
+
spawner.set_editor_property('tile_size', max(bounds_size.x, bounds_size.y))
|
|
214
262
|
|
|
215
263
|
# Create foliage types
|
|
216
264
|
foliage_types = []
|
|
217
|
-
|
|
265
|
+
ft_input = json.loads(r'''${JSON.stringify(params.foliageTypes)}''')
|
|
266
|
+
for ft_params in ft_input:
|
|
218
267
|
# Load mesh
|
|
219
268
|
mesh = unreal.EditorAssetLibrary.load_asset(ft_params['meshPath'])
|
|
220
269
|
if mesh:
|
|
@@ -233,26 +282,26 @@ try:
|
|
|
233
282
|
ft_asset = unreal.FoliageType_InstancedStaticMesh()
|
|
234
283
|
|
|
235
284
|
if ft_asset:
|
|
236
|
-
# Configure foliage type
|
|
237
|
-
ft_asset.mesh
|
|
238
|
-
ft_asset.density
|
|
239
|
-
ft_asset.random_yaw
|
|
240
|
-
ft_asset.align_to_normal
|
|
285
|
+
# Configure foliage type (use set_editor_property)
|
|
286
|
+
ft_asset.set_editor_property('mesh', mesh)
|
|
287
|
+
ft_asset.set_editor_property('density', ft_params.get('density', 1.0))
|
|
288
|
+
ft_asset.set_editor_property('random_yaw', ft_params.get('randomYaw', True))
|
|
289
|
+
ft_asset.set_editor_property('align_to_normal', ft_params.get('alignToNormal', True))
|
|
241
290
|
|
|
242
291
|
min_scale = ft_params.get('minScale', 0.8)
|
|
243
292
|
max_scale = ft_params.get('maxScale', 1.2)
|
|
244
|
-
ft_asset.scale_x
|
|
245
|
-
ft_asset.scale_y
|
|
246
|
-
ft_asset.scale_z
|
|
293
|
+
ft_asset.set_editor_property('scale_x', unreal.FloatInterval(min_scale, max_scale))
|
|
294
|
+
ft_asset.set_editor_property('scale_y', unreal.FloatInterval(min_scale, max_scale))
|
|
295
|
+
ft_asset.set_editor_property('scale_z', unreal.FloatInterval(min_scale, max_scale))
|
|
247
296
|
|
|
248
|
-
ft_obj.foliage_type_object
|
|
297
|
+
ft_obj.set_editor_property('foliage_type_object', ft_asset)
|
|
249
298
|
foliage_types.append(ft_obj)
|
|
250
299
|
|
|
251
300
|
# Set foliage types on spawner
|
|
252
|
-
spawner.foliage_types
|
|
301
|
+
spawner.set_editor_property('foliage_types', foliage_types)
|
|
253
302
|
|
|
254
303
|
# Assign spawner to component
|
|
255
|
-
proc_comp.foliage_spawner
|
|
304
|
+
proc_comp.set_editor_property('foliage_spawner', spawner)
|
|
256
305
|
|
|
257
306
|
# Save spawner asset
|
|
258
307
|
unreal.EditorAssetLibrary.save_asset(spawner.get_path_name())
|
|
@@ -286,17 +335,64 @@ except Exception as e:
|
|
|
286
335
|
print(f"RESULT:{json.dumps(result)}")
|
|
287
336
|
`.trim();
|
|
288
337
|
const response = await this.bridge.executePython(pythonScript);
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
338
|
+
const interpreted = interpretStandardResult(response, {
|
|
339
|
+
successMessage: `Created procedural foliage volume '${params.name}'`,
|
|
340
|
+
failureMessage: `Failed to create procedural foliage volume '${params.name}'`
|
|
341
|
+
});
|
|
342
|
+
if (!interpreted.success) {
|
|
343
|
+
const failure = {
|
|
344
|
+
success: false,
|
|
345
|
+
error: interpreted.error ?? interpreted.message,
|
|
346
|
+
message: interpreted.message
|
|
347
|
+
};
|
|
348
|
+
if (interpreted.warnings) {
|
|
349
|
+
failure.warnings = interpreted.warnings;
|
|
294
350
|
}
|
|
295
|
-
|
|
296
|
-
|
|
351
|
+
if (interpreted.details) {
|
|
352
|
+
failure.details = interpreted.details;
|
|
297
353
|
}
|
|
354
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
355
|
+
failure.payload = interpreted.payload;
|
|
356
|
+
}
|
|
357
|
+
return failure;
|
|
358
|
+
}
|
|
359
|
+
const payload = { ...interpreted.payload };
|
|
360
|
+
const volumeActor = coerceString(payload.volume_actor) ?? coerceString(payload.volumeActor);
|
|
361
|
+
const spawnerPath = coerceString(payload.spawner_path) ?? coerceString(payload.spawnerPath);
|
|
362
|
+
const foliageCount = coerceNumber(payload.foliage_types_count) ?? coerceNumber(payload.foliageTypesCount);
|
|
363
|
+
const resimulated = coerceBoolean(payload.resimulated);
|
|
364
|
+
const note = coerceString(payload.note);
|
|
365
|
+
const messages = coerceStringArray(payload.messages);
|
|
366
|
+
payload.success = true;
|
|
367
|
+
payload.message = interpreted.message;
|
|
368
|
+
if (volumeActor) {
|
|
369
|
+
payload.volume_actor = volumeActor;
|
|
370
|
+
payload.volumeActor = volumeActor;
|
|
371
|
+
}
|
|
372
|
+
if (spawnerPath) {
|
|
373
|
+
payload.spawner_path = spawnerPath;
|
|
374
|
+
payload.spawnerPath = spawnerPath;
|
|
375
|
+
}
|
|
376
|
+
if (typeof foliageCount === 'number') {
|
|
377
|
+
payload.foliage_types_count = foliageCount;
|
|
378
|
+
payload.foliageTypesCount = foliageCount;
|
|
298
379
|
}
|
|
299
|
-
|
|
380
|
+
if (typeof resimulated === 'boolean') {
|
|
381
|
+
payload.resimulated = resimulated;
|
|
382
|
+
}
|
|
383
|
+
if (note) {
|
|
384
|
+
payload.note = note;
|
|
385
|
+
}
|
|
386
|
+
if (messages && messages.length > 0) {
|
|
387
|
+
payload.messages = messages;
|
|
388
|
+
}
|
|
389
|
+
if (interpreted.warnings) {
|
|
390
|
+
payload.warnings = interpreted.warnings;
|
|
391
|
+
}
|
|
392
|
+
if (interpreted.details) {
|
|
393
|
+
payload.details = interpreted.details;
|
|
394
|
+
}
|
|
395
|
+
return payload;
|
|
300
396
|
}
|
|
301
397
|
/**
|
|
302
398
|
* Add foliage instances using InstancedFoliageActor
|
|
@@ -360,17 +456,43 @@ except Exception as e:
|
|
|
360
456
|
print(f"RESULT:{json.dumps(result)}")
|
|
361
457
|
`.trim();
|
|
362
458
|
const response = await this.bridge.executePython(pythonScript);
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
459
|
+
const interpreted = interpretStandardResult(response, {
|
|
460
|
+
successMessage: 'Foliage instances added',
|
|
461
|
+
failureMessage: 'Failed to add foliage instances'
|
|
462
|
+
});
|
|
463
|
+
if (!interpreted.success) {
|
|
464
|
+
const failure = {
|
|
465
|
+
success: false,
|
|
466
|
+
error: interpreted.error ?? interpreted.message,
|
|
467
|
+
message: interpreted.message
|
|
468
|
+
};
|
|
469
|
+
if (interpreted.warnings) {
|
|
470
|
+
failure.warnings = interpreted.warnings;
|
|
368
471
|
}
|
|
369
|
-
|
|
370
|
-
|
|
472
|
+
if (interpreted.details) {
|
|
473
|
+
failure.details = interpreted.details;
|
|
371
474
|
}
|
|
475
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
476
|
+
failure.payload = interpreted.payload;
|
|
477
|
+
}
|
|
478
|
+
return failure;
|
|
479
|
+
}
|
|
480
|
+
const payload = { ...interpreted.payload };
|
|
481
|
+
const count = coerceNumber(payload.instances_count) ?? coerceNumber(payload.instancesCount);
|
|
482
|
+
const message = coerceString(payload.message) ?? interpreted.message;
|
|
483
|
+
payload.success = true;
|
|
484
|
+
payload.message = message;
|
|
485
|
+
if (typeof count === 'number') {
|
|
486
|
+
payload.instances_count = count;
|
|
487
|
+
payload.instancesCount = count;
|
|
488
|
+
}
|
|
489
|
+
if (interpreted.warnings) {
|
|
490
|
+
payload.warnings = interpreted.warnings;
|
|
491
|
+
}
|
|
492
|
+
if (interpreted.details) {
|
|
493
|
+
payload.details = interpreted.details;
|
|
372
494
|
}
|
|
373
|
-
return
|
|
495
|
+
return payload;
|
|
374
496
|
}
|
|
375
497
|
/**
|
|
376
498
|
* Create landscape grass type for automatic foliage on landscape
|
|
@@ -413,23 +535,31 @@ try:
|
|
|
413
535
|
# Load mesh
|
|
414
536
|
mesh = unreal.EditorAssetLibrary.load_asset(mesh_path)
|
|
415
537
|
if mesh:
|
|
416
|
-
# Configure grass type
|
|
538
|
+
# Configure grass type (use set_editor_property)
|
|
417
539
|
grass_variety = unreal.GrassVariety()
|
|
418
|
-
grass_variety.grass_mesh
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
grass_variety.
|
|
423
|
-
grass_variety.
|
|
424
|
-
grass_variety.
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
grass_variety.
|
|
429
|
-
|
|
430
|
-
|
|
540
|
+
grass_variety.set_editor_property('grass_mesh', mesh)
|
|
541
|
+
# GrassDensity is PerPlatformFloat in UE5+; set via struct instance
|
|
542
|
+
pp_density = unreal.PerPlatformFloat()
|
|
543
|
+
pp_density.set_editor_property('Default', float(density * 100.0))
|
|
544
|
+
grass_variety.set_editor_property('grass_density', pp_density)
|
|
545
|
+
grass_variety.set_editor_property('use_grid', True)
|
|
546
|
+
grass_variety.set_editor_property('placement_jitter', 1.0)
|
|
547
|
+
# Set cull distances as PerPlatformInt and LOD as int (engine uses mixed types here)
|
|
548
|
+
pp_start = unreal.PerPlatformInt()
|
|
549
|
+
pp_start.set_editor_property('Default', 10000)
|
|
550
|
+
grass_variety.set_editor_property('start_cull_distance', pp_start)
|
|
551
|
+
pp_end = unreal.PerPlatformInt()
|
|
552
|
+
pp_end.set_editor_property('Default', 20000)
|
|
553
|
+
grass_variety.set_editor_property('end_cull_distance', pp_end)
|
|
554
|
+
grass_variety.set_editor_property('min_lod', -1)
|
|
555
|
+
grass_variety.set_editor_property('scaling', unreal.GrassScaling.UNIFORM)
|
|
556
|
+
grass_variety.set_editor_property('scale_x', unreal.FloatInterval(min_scale, max_scale))
|
|
557
|
+
grass_variety.set_editor_property('scale_y', unreal.FloatInterval(min_scale, max_scale))
|
|
558
|
+
grass_variety.set_editor_property('scale_z', unreal.FloatInterval(min_scale, max_scale))
|
|
559
|
+
grass_variety.set_editor_property('random_rotation', True)
|
|
560
|
+
grass_variety.set_editor_property('align_to_surface', True)
|
|
431
561
|
|
|
432
|
-
grass_type.grass_varieties
|
|
562
|
+
grass_type.set_editor_property('grass_varieties', [grass_variety])
|
|
433
563
|
|
|
434
564
|
# Save asset
|
|
435
565
|
unreal.EditorAssetLibrary.save_asset(grass_type.get_path_name())
|
|
@@ -454,31 +584,50 @@ except Exception as e:
|
|
|
454
584
|
print(f"RESULT:{json.dumps(result)}")
|
|
455
585
|
`.trim();
|
|
456
586
|
const response = await this.bridge.executePython(pythonScript);
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
587
|
+
const interpreted = interpretStandardResult(response, {
|
|
588
|
+
successMessage: `Created landscape grass type '${params.name}'`,
|
|
589
|
+
failureMessage: `Failed to create landscape grass type '${params.name}'`
|
|
590
|
+
});
|
|
591
|
+
if (!interpreted.success) {
|
|
592
|
+
const failure = {
|
|
593
|
+
success: false,
|
|
594
|
+
error: interpreted.error ?? interpreted.message,
|
|
595
|
+
message: interpreted.message
|
|
596
|
+
};
|
|
597
|
+
if (interpreted.warnings) {
|
|
598
|
+
failure.warnings = interpreted.warnings;
|
|
462
599
|
}
|
|
463
|
-
|
|
464
|
-
|
|
600
|
+
if (interpreted.details) {
|
|
601
|
+
failure.details = interpreted.details;
|
|
465
602
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
parseResponse(response) {
|
|
470
|
-
if (response && typeof response === 'object') {
|
|
471
|
-
if (response.LogOutput && Array.isArray(response.LogOutput)) {
|
|
472
|
-
return response.LogOutput.map((log) => log.Output || '').join('');
|
|
473
|
-
}
|
|
474
|
-
else if (response.CommandResult) {
|
|
475
|
-
return response.CommandResult;
|
|
476
|
-
}
|
|
477
|
-
else if (response.ReturnValue) {
|
|
478
|
-
return JSON.stringify(response);
|
|
603
|
+
if (interpreted.payload && Object.keys(interpreted.payload).length > 0) {
|
|
604
|
+
failure.payload = interpreted.payload;
|
|
479
605
|
}
|
|
606
|
+
return failure;
|
|
607
|
+
}
|
|
608
|
+
const payload = { ...interpreted.payload };
|
|
609
|
+
const assetPath = coerceString(payload.asset_path) ?? coerceString(payload.assetPath);
|
|
610
|
+
const note = coerceString(payload.note);
|
|
611
|
+
const messages = coerceStringArray(payload.messages);
|
|
612
|
+
payload.success = true;
|
|
613
|
+
payload.message = interpreted.message;
|
|
614
|
+
if (assetPath) {
|
|
615
|
+
payload.asset_path = assetPath;
|
|
616
|
+
payload.assetPath = assetPath;
|
|
617
|
+
}
|
|
618
|
+
if (note) {
|
|
619
|
+
payload.note = note;
|
|
620
|
+
}
|
|
621
|
+
if (messages && messages.length > 0) {
|
|
622
|
+
payload.messages = messages;
|
|
623
|
+
}
|
|
624
|
+
if (interpreted.warnings) {
|
|
625
|
+
payload.warnings = interpreted.warnings;
|
|
626
|
+
}
|
|
627
|
+
if (interpreted.details) {
|
|
628
|
+
payload.details = interpreted.details;
|
|
480
629
|
}
|
|
481
|
-
return
|
|
630
|
+
return payload;
|
|
482
631
|
}
|
|
483
632
|
}
|
|
484
633
|
//# sourceMappingURL=build_environment_advanced.js.map
|