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.
Files changed (144) hide show
  1. package/.env.production +1 -1
  2. package/.github/copilot-instructions.md +45 -0
  3. package/.github/workflows/publish-mcp.yml +1 -1
  4. package/README.md +22 -7
  5. package/dist/index.js +137 -46
  6. package/dist/prompts/index.d.ts +10 -3
  7. package/dist/prompts/index.js +186 -7
  8. package/dist/resources/actors.d.ts +19 -1
  9. package/dist/resources/actors.js +55 -64
  10. package/dist/resources/assets.d.ts +3 -2
  11. package/dist/resources/assets.js +117 -109
  12. package/dist/resources/levels.d.ts +21 -3
  13. package/dist/resources/levels.js +31 -56
  14. package/dist/tools/actors.d.ts +3 -14
  15. package/dist/tools/actors.js +246 -302
  16. package/dist/tools/animation.d.ts +57 -102
  17. package/dist/tools/animation.js +429 -450
  18. package/dist/tools/assets.d.ts +13 -2
  19. package/dist/tools/assets.js +58 -46
  20. package/dist/tools/audio.d.ts +22 -13
  21. package/dist/tools/audio.js +467 -121
  22. package/dist/tools/blueprint.d.ts +32 -13
  23. package/dist/tools/blueprint.js +699 -448
  24. package/dist/tools/build_environment_advanced.d.ts +0 -1
  25. package/dist/tools/build_environment_advanced.js +236 -87
  26. package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
  27. package/dist/tools/consolidated-tool-definitions.js +124 -255
  28. package/dist/tools/consolidated-tool-handlers.js +749 -766
  29. package/dist/tools/debug.d.ts +72 -10
  30. package/dist/tools/debug.js +170 -36
  31. package/dist/tools/editor.d.ts +9 -2
  32. package/dist/tools/editor.js +30 -44
  33. package/dist/tools/foliage.d.ts +34 -15
  34. package/dist/tools/foliage.js +97 -107
  35. package/dist/tools/introspection.js +19 -21
  36. package/dist/tools/landscape.d.ts +1 -2
  37. package/dist/tools/landscape.js +311 -168
  38. package/dist/tools/level.d.ts +3 -28
  39. package/dist/tools/level.js +642 -192
  40. package/dist/tools/lighting.d.ts +14 -3
  41. package/dist/tools/lighting.js +236 -123
  42. package/dist/tools/materials.d.ts +25 -7
  43. package/dist/tools/materials.js +102 -79
  44. package/dist/tools/niagara.d.ts +10 -12
  45. package/dist/tools/niagara.js +74 -94
  46. package/dist/tools/performance.d.ts +12 -4
  47. package/dist/tools/performance.js +38 -79
  48. package/dist/tools/physics.d.ts +34 -10
  49. package/dist/tools/physics.js +364 -292
  50. package/dist/tools/rc.js +98 -24
  51. package/dist/tools/sequence.d.ts +1 -0
  52. package/dist/tools/sequence.js +146 -24
  53. package/dist/tools/ui.d.ts +31 -4
  54. package/dist/tools/ui.js +83 -66
  55. package/dist/tools/visual.d.ts +11 -0
  56. package/dist/tools/visual.js +245 -30
  57. package/dist/types/tool-types.d.ts +0 -6
  58. package/dist/types/tool-types.js +1 -8
  59. package/dist/unreal-bridge.d.ts +32 -2
  60. package/dist/unreal-bridge.js +621 -127
  61. package/dist/utils/elicitation.d.ts +57 -0
  62. package/dist/utils/elicitation.js +104 -0
  63. package/dist/utils/error-handler.d.ts +0 -33
  64. package/dist/utils/error-handler.js +4 -111
  65. package/dist/utils/http.d.ts +2 -22
  66. package/dist/utils/http.js +12 -75
  67. package/dist/utils/normalize.d.ts +4 -4
  68. package/dist/utils/normalize.js +15 -7
  69. package/dist/utils/python-output.d.ts +18 -0
  70. package/dist/utils/python-output.js +290 -0
  71. package/dist/utils/python.d.ts +2 -0
  72. package/dist/utils/python.js +4 -0
  73. package/dist/utils/response-validator.d.ts +6 -1
  74. package/dist/utils/response-validator.js +66 -13
  75. package/dist/utils/result-helpers.d.ts +27 -0
  76. package/dist/utils/result-helpers.js +147 -0
  77. package/dist/utils/safe-json.d.ts +0 -2
  78. package/dist/utils/safe-json.js +0 -43
  79. package/dist/utils/validation.d.ts +16 -0
  80. package/dist/utils/validation.js +70 -7
  81. package/mcp-config-example.json +2 -2
  82. package/package.json +11 -10
  83. package/server.json +37 -14
  84. package/src/index.ts +146 -50
  85. package/src/prompts/index.ts +211 -13
  86. package/src/resources/actors.ts +59 -44
  87. package/src/resources/assets.ts +123 -102
  88. package/src/resources/levels.ts +37 -47
  89. package/src/tools/actors.ts +269 -313
  90. package/src/tools/animation.ts +556 -539
  91. package/src/tools/assets.ts +59 -45
  92. package/src/tools/audio.ts +507 -113
  93. package/src/tools/blueprint.ts +778 -462
  94. package/src/tools/build_environment_advanced.ts +312 -106
  95. package/src/tools/consolidated-tool-definitions.ts +136 -267
  96. package/src/tools/consolidated-tool-handlers.ts +871 -795
  97. package/src/tools/debug.ts +179 -38
  98. package/src/tools/editor.ts +35 -37
  99. package/src/tools/foliage.ts +110 -104
  100. package/src/tools/introspection.ts +24 -22
  101. package/src/tools/landscape.ts +334 -181
  102. package/src/tools/level.ts +683 -182
  103. package/src/tools/lighting.ts +244 -123
  104. package/src/tools/materials.ts +114 -83
  105. package/src/tools/niagara.ts +87 -81
  106. package/src/tools/performance.ts +49 -88
  107. package/src/tools/physics.ts +393 -299
  108. package/src/tools/rc.ts +103 -25
  109. package/src/tools/sequence.ts +157 -30
  110. package/src/tools/ui.ts +101 -70
  111. package/src/tools/visual.ts +250 -29
  112. package/src/types/tool-types.ts +0 -9
  113. package/src/unreal-bridge.ts +658 -140
  114. package/src/utils/elicitation.ts +129 -0
  115. package/src/utils/error-handler.ts +4 -159
  116. package/src/utils/http.ts +16 -115
  117. package/src/utils/normalize.ts +20 -10
  118. package/src/utils/python-output.ts +351 -0
  119. package/src/utils/python.ts +3 -0
  120. package/src/utils/response-validator.ts +68 -17
  121. package/src/utils/result-helpers.ts +193 -0
  122. package/src/utils/safe-json.ts +0 -50
  123. package/src/utils/validation.ts +94 -7
  124. package/tests/run-unreal-tool-tests.mjs +720 -0
  125. package/tsconfig.json +2 -2
  126. package/dist/python-utils.d.ts +0 -29
  127. package/dist/python-utils.js +0 -54
  128. package/dist/tools/tool-definitions.d.ts +0 -4919
  129. package/dist/tools/tool-definitions.js +0 -1065
  130. package/dist/tools/tool-handlers.d.ts +0 -47
  131. package/dist/tools/tool-handlers.js +0 -863
  132. package/dist/types/index.d.ts +0 -323
  133. package/dist/types/index.js +0 -28
  134. package/dist/utils/cache-manager.d.ts +0 -64
  135. package/dist/utils/cache-manager.js +0 -176
  136. package/dist/utils/errors.d.ts +0 -133
  137. package/dist/utils/errors.js +0 -256
  138. package/src/python/editor_compat.py +0 -181
  139. package/src/python-utils.ts +0 -57
  140. package/src/tools/tool-definitions.ts +0 -1081
  141. package/src/tools/tool-handlers.ts +0 -973
  142. package/src/types/index.ts +0 -414
  143. package/src/utils/cache-manager.ts +0 -213
  144. package/src/utils/errors.ts +0 -312
@@ -61,6 +61,5 @@ export declare class BuildEnvironmentAdvanced {
61
61
  minScale?: number;
62
62
  maxScale?: number;
63
63
  }): Promise<any>;
64
- private parseResponse;
65
64
  }
66
65
  //# sourceMappingURL=build_environment_advanced.d.ts.map
@@ -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
- proc_actor = subsys.spawn_actor_from_class(
49
- unreal.Actor,
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 output = this.parseResponse(response);
142
- const match = output.match(/RESULT:({.*})/);
143
- if (match) {
144
- try {
145
- return JSON.parse(match[1]);
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
- catch (_e) {
148
- return { success: false, error: 'Failed to parse result', details: output };
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
- return { success: false, error: 'No result found', output };
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 = seed
213
- spawner.tile_size = max(bounds_size.x, bounds_size.y)
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
- for ft_params in ${JSON.stringify(params.foliageTypes)}:
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 = mesh
238
- ft_asset.density = ft_params.get('density', 1.0)
239
- ft_asset.random_yaw = ft_params.get('randomYaw', True)
240
- ft_asset.align_to_normal = ft_params.get('alignToNormal', True)
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 = unreal.FloatInterval(min_scale, max_scale)
245
- ft_asset.scale_y = unreal.FloatInterval(min_scale, max_scale)
246
- ft_asset.scale_z = unreal.FloatInterval(min_scale, max_scale)
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 = ft_asset
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 = foliage_types
301
+ spawner.set_editor_property('foliage_types', foliage_types)
253
302
 
254
303
  # Assign spawner to component
255
- proc_comp.foliage_spawner = 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 output = this.parseResponse(response);
290
- const match = output.match(/RESULT:({.*})/);
291
- if (match) {
292
- try {
293
- return JSON.parse(match[1]);
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
- catch (_e) {
296
- return { success: false, error: 'Failed to parse result', details: output };
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
- return { success: false, error: 'No result found', output };
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 output = this.parseResponse(response);
364
- const match = output.match(/RESULT:({.*})/);
365
- if (match) {
366
- try {
367
- return JSON.parse(match[1]);
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
- catch (_e) {
370
- return { success: false, error: 'Failed to parse result', details: output };
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 { success: false, error: 'No result found', output };
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 = mesh
419
- grass_variety.grass_density = density * 100 # Convert to per square meter
420
- grass_variety.use_grid = True
421
- grass_variety.placement_jitter = 1.0
422
- grass_variety.start_cull_distance = 10000
423
- grass_variety.end_cull_distance = 20000
424
- grass_variety.min_lod = -1
425
- grass_variety.scaling = unreal.GrassScaling.UNIFORM
426
- grass_variety.scale_x = unreal.FloatInterval(min_scale, max_scale)
427
- grass_variety.scale_y = unreal.FloatInterval(min_scale, max_scale)
428
- grass_variety.scale_z = unreal.FloatInterval(min_scale, max_scale)
429
- grass_variety.random_rotation = True
430
- grass_variety.align_to_surface = True
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 = [grass_variety]
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 output = this.parseResponse(response);
458
- const match = output.match(/RESULT:({.*})/);
459
- if (match) {
460
- try {
461
- return JSON.parse(match[1]);
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
- catch (_e) {
464
- return { success: false, error: 'Failed to parse result', details: output };
600
+ if (interpreted.details) {
601
+ failure.details = interpreted.details;
465
602
  }
466
- }
467
- return { success: false, error: 'No result found', output };
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 typeof response === 'string' ? response : JSON.stringify(response);
630
+ return payload;
482
631
  }
483
632
  }
484
633
  //# sourceMappingURL=build_environment_advanced.js.map