unreal-engine-mcp-server 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/.dockerignore +57 -0
  2. package/.env.production +25 -0
  3. package/.eslintrc.json +54 -0
  4. package/.github/workflows/publish-mcp.yml +75 -0
  5. package/Dockerfile +54 -0
  6. package/LICENSE +21 -0
  7. package/Public/icon.png +0 -0
  8. package/README.md +209 -0
  9. package/claude_desktop_config_example.json +13 -0
  10. package/dist/cli.d.ts +3 -0
  11. package/dist/cli.js +7 -0
  12. package/dist/index.d.ts +31 -0
  13. package/dist/index.js +484 -0
  14. package/dist/prompts/index.d.ts +14 -0
  15. package/dist/prompts/index.js +38 -0
  16. package/dist/python-utils.d.ts +29 -0
  17. package/dist/python-utils.js +54 -0
  18. package/dist/resources/actors.d.ts +13 -0
  19. package/dist/resources/actors.js +83 -0
  20. package/dist/resources/assets.d.ts +23 -0
  21. package/dist/resources/assets.js +245 -0
  22. package/dist/resources/levels.d.ts +17 -0
  23. package/dist/resources/levels.js +94 -0
  24. package/dist/tools/actors.d.ts +51 -0
  25. package/dist/tools/actors.js +459 -0
  26. package/dist/tools/animation.d.ts +196 -0
  27. package/dist/tools/animation.js +579 -0
  28. package/dist/tools/assets.d.ts +21 -0
  29. package/dist/tools/assets.js +304 -0
  30. package/dist/tools/audio.d.ts +170 -0
  31. package/dist/tools/audio.js +416 -0
  32. package/dist/tools/blueprint.d.ts +144 -0
  33. package/dist/tools/blueprint.js +652 -0
  34. package/dist/tools/build_environment_advanced.d.ts +66 -0
  35. package/dist/tools/build_environment_advanced.js +484 -0
  36. package/dist/tools/consolidated-tool-definitions.d.ts +2598 -0
  37. package/dist/tools/consolidated-tool-definitions.js +607 -0
  38. package/dist/tools/consolidated-tool-handlers.d.ts +2 -0
  39. package/dist/tools/consolidated-tool-handlers.js +1050 -0
  40. package/dist/tools/debug.d.ts +185 -0
  41. package/dist/tools/debug.js +265 -0
  42. package/dist/tools/editor.d.ts +88 -0
  43. package/dist/tools/editor.js +365 -0
  44. package/dist/tools/engine.d.ts +30 -0
  45. package/dist/tools/engine.js +36 -0
  46. package/dist/tools/foliage.d.ts +155 -0
  47. package/dist/tools/foliage.js +525 -0
  48. package/dist/tools/introspection.d.ts +98 -0
  49. package/dist/tools/introspection.js +683 -0
  50. package/dist/tools/landscape.d.ts +158 -0
  51. package/dist/tools/landscape.js +375 -0
  52. package/dist/tools/level.d.ts +110 -0
  53. package/dist/tools/level.js +362 -0
  54. package/dist/tools/lighting.d.ts +159 -0
  55. package/dist/tools/lighting.js +1179 -0
  56. package/dist/tools/materials.d.ts +34 -0
  57. package/dist/tools/materials.js +146 -0
  58. package/dist/tools/niagara.d.ts +145 -0
  59. package/dist/tools/niagara.js +289 -0
  60. package/dist/tools/performance.d.ts +163 -0
  61. package/dist/tools/performance.js +412 -0
  62. package/dist/tools/physics.d.ts +189 -0
  63. package/dist/tools/physics.js +784 -0
  64. package/dist/tools/rc.d.ts +110 -0
  65. package/dist/tools/rc.js +363 -0
  66. package/dist/tools/sequence.d.ts +112 -0
  67. package/dist/tools/sequence.js +675 -0
  68. package/dist/tools/tool-definitions.d.ts +4919 -0
  69. package/dist/tools/tool-definitions.js +891 -0
  70. package/dist/tools/tool-handlers.d.ts +47 -0
  71. package/dist/tools/tool-handlers.js +830 -0
  72. package/dist/tools/ui.d.ts +171 -0
  73. package/dist/tools/ui.js +337 -0
  74. package/dist/tools/visual.d.ts +29 -0
  75. package/dist/tools/visual.js +67 -0
  76. package/dist/types/env.d.ts +10 -0
  77. package/dist/types/env.js +18 -0
  78. package/dist/types/index.d.ts +323 -0
  79. package/dist/types/index.js +28 -0
  80. package/dist/types/tool-types.d.ts +274 -0
  81. package/dist/types/tool-types.js +13 -0
  82. package/dist/unreal-bridge.d.ts +126 -0
  83. package/dist/unreal-bridge.js +992 -0
  84. package/dist/utils/cache-manager.d.ts +64 -0
  85. package/dist/utils/cache-manager.js +176 -0
  86. package/dist/utils/error-handler.d.ts +66 -0
  87. package/dist/utils/error-handler.js +243 -0
  88. package/dist/utils/errors.d.ts +133 -0
  89. package/dist/utils/errors.js +256 -0
  90. package/dist/utils/http.d.ts +26 -0
  91. package/dist/utils/http.js +135 -0
  92. package/dist/utils/logger.d.ts +12 -0
  93. package/dist/utils/logger.js +32 -0
  94. package/dist/utils/normalize.d.ts +17 -0
  95. package/dist/utils/normalize.js +49 -0
  96. package/dist/utils/response-validator.d.ts +34 -0
  97. package/dist/utils/response-validator.js +121 -0
  98. package/dist/utils/safe-json.d.ts +4 -0
  99. package/dist/utils/safe-json.js +97 -0
  100. package/dist/utils/stdio-redirect.d.ts +2 -0
  101. package/dist/utils/stdio-redirect.js +20 -0
  102. package/dist/utils/validation.d.ts +50 -0
  103. package/dist/utils/validation.js +173 -0
  104. package/mcp-config-example.json +14 -0
  105. package/package.json +63 -0
  106. package/server.json +60 -0
  107. package/src/cli.ts +7 -0
  108. package/src/index.ts +543 -0
  109. package/src/prompts/index.ts +51 -0
  110. package/src/python/editor_compat.py +181 -0
  111. package/src/python-utils.ts +57 -0
  112. package/src/resources/actors.ts +92 -0
  113. package/src/resources/assets.ts +251 -0
  114. package/src/resources/levels.ts +83 -0
  115. package/src/tools/actors.ts +480 -0
  116. package/src/tools/animation.ts +713 -0
  117. package/src/tools/assets.ts +305 -0
  118. package/src/tools/audio.ts +548 -0
  119. package/src/tools/blueprint.ts +736 -0
  120. package/src/tools/build_environment_advanced.ts +526 -0
  121. package/src/tools/consolidated-tool-definitions.ts +619 -0
  122. package/src/tools/consolidated-tool-handlers.ts +1093 -0
  123. package/src/tools/debug.ts +368 -0
  124. package/src/tools/editor.ts +360 -0
  125. package/src/tools/engine.ts +32 -0
  126. package/src/tools/foliage.ts +652 -0
  127. package/src/tools/introspection.ts +778 -0
  128. package/src/tools/landscape.ts +523 -0
  129. package/src/tools/level.ts +410 -0
  130. package/src/tools/lighting.ts +1316 -0
  131. package/src/tools/materials.ts +148 -0
  132. package/src/tools/niagara.ts +312 -0
  133. package/src/tools/performance.ts +549 -0
  134. package/src/tools/physics.ts +924 -0
  135. package/src/tools/rc.ts +437 -0
  136. package/src/tools/sequence.ts +791 -0
  137. package/src/tools/tool-definitions.ts +907 -0
  138. package/src/tools/tool-handlers.ts +941 -0
  139. package/src/tools/ui.ts +499 -0
  140. package/src/tools/visual.ts +60 -0
  141. package/src/types/env.ts +27 -0
  142. package/src/types/index.ts +414 -0
  143. package/src/types/tool-types.ts +343 -0
  144. package/src/unreal-bridge.ts +1118 -0
  145. package/src/utils/cache-manager.ts +213 -0
  146. package/src/utils/error-handler.ts +320 -0
  147. package/src/utils/errors.ts +312 -0
  148. package/src/utils/http.ts +184 -0
  149. package/src/utils/logger.ts +30 -0
  150. package/src/utils/normalize.ts +54 -0
  151. package/src/utils/response-validator.ts +145 -0
  152. package/src/utils/safe-json.ts +112 -0
  153. package/src/utils/stdio-redirect.ts +18 -0
  154. package/src/utils/validation.ts +212 -0
  155. package/tsconfig.json +33 -0
@@ -0,0 +1,526 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+
3
+ /**
4
+ * Advanced Build Environment Tools
5
+ * Implements procedural terrain and foliage using documented Unreal Engine Python APIs
6
+ */
7
+ export class BuildEnvironmentAdvanced {
8
+ constructor(private bridge: UnrealBridge) {}
9
+
10
+ /**
11
+ * Create procedural terrain using ProceduralMeshComponent
12
+ * This works around the landscape API limitations
13
+ */
14
+ async createProceduralTerrain(params: {
15
+ name: string;
16
+ location?: [number, number, number];
17
+ sizeX?: number;
18
+ sizeY?: number;
19
+ subdivisions?: number;
20
+ heightFunction?: string; // Python expression for height calculation
21
+ material?: string;
22
+ }) {
23
+ const pythonScript = `
24
+ import unreal
25
+ import json
26
+ import math
27
+
28
+ name = ${JSON.stringify(params.name)}
29
+ location = unreal.Vector(${params.location?.[0] || 0}, ${params.location?.[1] || 0}, ${params.location?.[2] || 0})
30
+ size_x = ${params.sizeX || 2000}
31
+ size_y = ${params.sizeY || 2000}
32
+ subdivisions = ${params.subdivisions || 50}
33
+ height_function = ${JSON.stringify(params.heightFunction || 'math.sin(x/100) * 50 + math.cos(y/100) * 30')}
34
+
35
+ result = {}
36
+
37
+ try:
38
+ # Get editor subsystem
39
+ subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
40
+
41
+ # Create ProceduralMeshActor
42
+ proc_actor = None
43
+ proc_mesh_comp = None
44
+
45
+ # Try ProceduralMeshActor first
46
+ try:
47
+ proc_actor = subsys.spawn_actor_from_class(
48
+ unreal.ProceduralMeshActor,
49
+ location,
50
+ unreal.Rotator(0, 0, 0)
51
+ )
52
+ proc_actor.set_actor_label(f"{name}_ProceduralTerrain")
53
+ proc_mesh_comp = proc_actor.get_component_by_class(unreal.ProceduralMeshComponent)
54
+ except:
55
+ # Fallback: Create empty actor and add ProceduralMeshComponent
56
+ proc_actor = subsys.spawn_actor_from_class(
57
+ unreal.Actor,
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()
67
+
68
+ if proc_mesh_comp:
69
+ # Generate terrain mesh
70
+ vertices = []
71
+ triangles = []
72
+ normals = []
73
+ uvs = []
74
+ vertex_colors = []
75
+
76
+ step_x = size_x / subdivisions
77
+ step_y = size_y / subdivisions
78
+
79
+ # Create vertices with height variation
80
+ for y in range(subdivisions + 1):
81
+ for x in range(subdivisions + 1):
82
+ # Position
83
+ vert_x = x * step_x - size_x / 2
84
+ vert_y = y * step_y - size_y / 2
85
+
86
+ # Calculate height using the provided function
87
+ try:
88
+ vert_z = eval(height_function, {"x": vert_x, "y": vert_y, "math": math})
89
+ except:
90
+ vert_z = 0 # Fallback to flat if function fails
91
+
92
+ vertices.append(unreal.Vector(vert_x, vert_y, vert_z))
93
+ normals.append(unreal.Vector(0, 0, 1)) # Will be recalculated
94
+ uvs.append(unreal.Vector2D(x / subdivisions, y / subdivisions))
95
+
96
+ # Color based on height
97
+ height_normalized = min(1.0, max(0.0, (vert_z + 100) / 200))
98
+ vertex_colors.append(unreal.LinearColor(height_normalized, 1 - height_normalized, 0.2, 1))
99
+
100
+ # Create triangles
101
+ for y in range(subdivisions):
102
+ for x in range(subdivisions):
103
+ idx = y * (subdivisions + 1) + x
104
+
105
+ # First triangle
106
+ triangles.extend([idx, idx + subdivisions + 1, idx + 1])
107
+ # Second triangle
108
+ triangles.extend([idx + 1, idx + subdivisions + 1, idx + subdivisions + 2])
109
+
110
+ # Create mesh section
111
+ proc_mesh_comp.create_mesh_section_linear_color(
112
+ 0, # Section index
113
+ vertices,
114
+ triangles,
115
+ normals,
116
+ uvs,
117
+ vertex_colors,
118
+ [], # Tangents
119
+ True # Create collision
120
+ )
121
+
122
+ # Apply material if specified
123
+ if ${JSON.stringify(params.material || '')}:
124
+ material = unreal.EditorAssetLibrary.load_asset(${JSON.stringify(params.material || '/Engine/MapTemplates/Materials/BasicGrid01')})
125
+ if material:
126
+ proc_mesh_comp.set_material(0, material)
127
+
128
+ # Enable collision
129
+ proc_mesh_comp.set_collision_enabled(unreal.CollisionEnabled.QUERY_AND_PHYSICS)
130
+
131
+ result = {
132
+ "success": True,
133
+ "message": f"Created procedural terrain '{name}'",
134
+ "actor_name": proc_actor.get_actor_label(),
135
+ "vertices": len(vertices),
136
+ "triangles": len(triangles) // 3,
137
+ "size": [size_x, size_y],
138
+ "subdivisions": subdivisions
139
+ }
140
+ else:
141
+ result = {"success": False, "error": "Could not create ProceduralMeshComponent"}
142
+
143
+ except Exception as e:
144
+ result = {"success": False, "error": str(e)}
145
+
146
+ print(f"RESULT:{json.dumps(result)}")
147
+ `.trim();
148
+
149
+ const response = await this.bridge.executePython(pythonScript);
150
+ const output = this.parseResponse(response);
151
+ const match = output.match(/RESULT:({.*})/);
152
+
153
+ if (match) {
154
+ try {
155
+ return JSON.parse(match[1]);
156
+ } catch (_e) {
157
+ return { success: false, error: 'Failed to parse result', details: output };
158
+ }
159
+ }
160
+
161
+ return { success: false, error: 'No result found', output };
162
+ }
163
+
164
+ /**
165
+ * Create procedural foliage using ProceduralFoliageSpawner
166
+ * Uses the documented Unreal Engine API
167
+ */
168
+ async createProceduralFoliage(params: {
169
+ name: string;
170
+ bounds: { location: [number, number, number]; size: [number, number, number] };
171
+ foliageTypes: Array<{
172
+ meshPath: string;
173
+ density: number;
174
+ minScale?: number;
175
+ maxScale?: number;
176
+ alignToNormal?: boolean;
177
+ randomYaw?: boolean;
178
+ }>;
179
+ seed?: number;
180
+ }) {
181
+ const pythonScript = `
182
+ import unreal
183
+ import json
184
+
185
+ name = ${JSON.stringify(params.name)}
186
+ bounds_location = unreal.Vector(${params.bounds.location[0]}, ${params.bounds.location[1]}, ${params.bounds.location[2]})
187
+ bounds_size = unreal.Vector(${params.bounds.size[0]}, ${params.bounds.size[1]}, ${params.bounds.size[2]})
188
+ seed = ${params.seed || 12345}
189
+
190
+ result = {}
191
+
192
+ try:
193
+ subsys = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
194
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
195
+
196
+ # Create ProceduralFoliageVolume
197
+ volume_actor = subsys.spawn_actor_from_class(
198
+ unreal.ProceduralFoliageVolume,
199
+ bounds_location,
200
+ unreal.Rotator(0, 0, 0)
201
+ )
202
+ volume_actor.set_actor_label(f"{name}_ProceduralFoliageVolume")
203
+ volume_actor.set_actor_scale3d(bounds_size / 100) # Scale is in meters
204
+
205
+ # Get the procedural component
206
+ proc_comp = volume_actor.procedural_component
207
+ if not proc_comp:
208
+ proc_comp = volume_actor.get_component_by_class(unreal.ProceduralFoliageComponent)
209
+
210
+ if proc_comp:
211
+ # Create ProceduralFoliageSpawner asset
212
+ spawner_path = f"/Game/Foliage/Spawners/{name}_Spawner"
213
+ package_path = "/Game/Foliage/Spawners"
214
+
215
+ # Ensure directory exists
216
+ if not unreal.EditorAssetLibrary.does_directory_exist(package_path):
217
+ unreal.EditorAssetLibrary.make_directory(package_path)
218
+
219
+ # Create spawner
220
+ spawner = None
221
+ if unreal.EditorAssetLibrary.does_asset_exist(spawner_path):
222
+ spawner = unreal.EditorAssetLibrary.load_asset(spawner_path)
223
+ else:
224
+ # Create new spawner
225
+ factory = unreal.ProceduralFoliageSpawnerFactory()
226
+ spawner = asset_tools.create_asset(
227
+ asset_name=f"{name}_Spawner",
228
+ package_path=package_path,
229
+ asset_class=unreal.ProceduralFoliageSpawner,
230
+ factory=factory
231
+ )
232
+
233
+ if spawner:
234
+ # Configure spawner
235
+ spawner.random_seed = seed
236
+ spawner.tile_size = max(bounds_size.x, bounds_size.y)
237
+
238
+ # Create foliage types
239
+ foliage_types = []
240
+ for ft_params in ${JSON.stringify(params.foliageTypes)}:
241
+ # Load mesh
242
+ mesh = unreal.EditorAssetLibrary.load_asset(ft_params['meshPath'])
243
+ if mesh:
244
+ # Create FoliageTypeObject
245
+ ft_obj = unreal.FoliageTypeObject()
246
+
247
+ # Try to create or load FoliageType_InstancedStaticMesh
248
+ ft_asset_name = f"FT_{name}_{len(foliage_types)}"
249
+ ft_asset_path = f"/Game/Foliage/Types/{ft_asset_name}"
250
+
251
+ ft_asset = None
252
+ if unreal.EditorAssetLibrary.does_asset_exist(ft_asset_path):
253
+ ft_asset = unreal.EditorAssetLibrary.load_asset(ft_asset_path)
254
+ else:
255
+ # Create simple foliage type
256
+ ft_asset = unreal.FoliageType_InstancedStaticMesh()
257
+
258
+ if ft_asset:
259
+ # Configure foliage type
260
+ ft_asset.mesh = mesh
261
+ ft_asset.density = ft_params.get('density', 1.0)
262
+ ft_asset.random_yaw = ft_params.get('randomYaw', True)
263
+ ft_asset.align_to_normal = ft_params.get('alignToNormal', True)
264
+
265
+ min_scale = ft_params.get('minScale', 0.8)
266
+ max_scale = ft_params.get('maxScale', 1.2)
267
+ ft_asset.scale_x = unreal.FloatInterval(min_scale, max_scale)
268
+ ft_asset.scale_y = unreal.FloatInterval(min_scale, max_scale)
269
+ ft_asset.scale_z = unreal.FloatInterval(min_scale, max_scale)
270
+
271
+ ft_obj.foliage_type_object = ft_asset
272
+ foliage_types.append(ft_obj)
273
+
274
+ # Set foliage types on spawner
275
+ spawner.foliage_types = foliage_types
276
+
277
+ # Assign spawner to component
278
+ proc_comp.foliage_spawner = spawner
279
+
280
+ # Save spawner asset
281
+ unreal.EditorAssetLibrary.save_asset(spawner.get_path_name())
282
+
283
+ # Resimulate
284
+ try:
285
+ unreal.ProceduralFoliageEditorLibrary.resimulate_procedural_foliage_volumes([volume_actor])
286
+ result['resimulated'] = True
287
+ except:
288
+ # Manual simulation
289
+ spawner.simulate(num_steps=-1)
290
+ result['resimulated'] = False
291
+ result['note'] = 'Used manual simulation'
292
+
293
+ result['success'] = True
294
+ result['message'] = f"Created procedural foliage volume '{name}'"
295
+ result['volume_actor'] = volume_actor.get_actor_label()
296
+ result['spawner_path'] = spawner.get_path_name()
297
+ result['foliage_types_count'] = len(foliage_types)
298
+ else:
299
+ result['success'] = False
300
+ result['error'] = 'Could not create ProceduralFoliageSpawner'
301
+ else:
302
+ result['success'] = False
303
+ result['error'] = 'Could not get ProceduralFoliageComponent'
304
+
305
+ except Exception as e:
306
+ result['success'] = False
307
+ result['error'] = str(e)
308
+
309
+ print(f"RESULT:{json.dumps(result)}")
310
+ `.trim();
311
+
312
+ const response = await this.bridge.executePython(pythonScript);
313
+ const output = this.parseResponse(response);
314
+ const match = output.match(/RESULT:({.*})/);
315
+
316
+ if (match) {
317
+ try {
318
+ return JSON.parse(match[1]);
319
+ } catch (_e) {
320
+ return { success: false, error: 'Failed to parse result', details: output };
321
+ }
322
+ }
323
+
324
+ return { success: false, error: 'No result found', output };
325
+ }
326
+
327
+ /**
328
+ * Add foliage instances using InstancedFoliageActor
329
+ * Direct instance placement approach
330
+ */
331
+ async addFoliageInstances(params: {
332
+ foliageType: string; // Path to FoliageType or mesh
333
+ transforms: Array<{
334
+ location: [number, number, number];
335
+ rotation?: [number, number, number];
336
+ scale?: [number, number, number];
337
+ }>;
338
+ }) {
339
+ const pythonScript = `
340
+ import unreal
341
+ import json
342
+
343
+ foliage_type_path = ${JSON.stringify(params.foliageType)}
344
+ transforms_data = ${JSON.stringify(params.transforms)}
345
+
346
+ result = {}
347
+
348
+ try:
349
+ # Get world context
350
+ editor_subsystem = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
351
+ world = editor_subsystem.get_editor_world()
352
+
353
+ # Load foliage type or mesh
354
+ foliage_asset = unreal.EditorAssetLibrary.load_asset(foliage_type_path)
355
+
356
+ if foliage_asset:
357
+ # Prepare transforms
358
+ transforms = []
359
+ for t_data in transforms_data:
360
+ location = unreal.Vector(t_data['location'][0], t_data['location'][1], t_data['location'][2])
361
+ rotation = unreal.Rotator(
362
+ t_data.get('rotation', [0, 0, 0])[0],
363
+ t_data.get('rotation', [0, 0, 0])[1],
364
+ t_data.get('rotation', [0, 0, 0])[2]
365
+ )
366
+ scale = unreal.Vector(
367
+ t_data.get('scale', [1, 1, 1])[0],
368
+ t_data.get('scale', [1, 1, 1])[1],
369
+ t_data.get('scale', [1, 1, 1])[2]
370
+ )
371
+
372
+ transform = unreal.Transform(location, rotation, scale)
373
+ transforms.append(transform)
374
+
375
+ # Add instances using InstancedFoliageActor
376
+ unreal.InstancedFoliageActor.add_instances(
377
+ world,
378
+ foliage_asset,
379
+ transforms
380
+ )
381
+
382
+ result['success'] = True
383
+ result['message'] = f"Added {len(transforms)} foliage instances"
384
+ result['instances_count'] = len(transforms)
385
+ else:
386
+ result['success'] = False
387
+ result['error'] = f"Could not load foliage asset: {foliage_type_path}"
388
+
389
+ except Exception as e:
390
+ result['success'] = False
391
+ result['error'] = str(e)
392
+
393
+ print(f"RESULT:{json.dumps(result)}")
394
+ `.trim();
395
+
396
+ const response = await this.bridge.executePython(pythonScript);
397
+ const output = this.parseResponse(response);
398
+ const match = output.match(/RESULT:({.*})/);
399
+
400
+ if (match) {
401
+ try {
402
+ return JSON.parse(match[1]);
403
+ } catch (_e) {
404
+ return { success: false, error: 'Failed to parse result', details: output };
405
+ }
406
+ }
407
+
408
+ return { success: false, error: 'No result found', output };
409
+ }
410
+
411
+ /**
412
+ * Create landscape grass type for automatic foliage on landscape
413
+ */
414
+ async createLandscapeGrassType(params: {
415
+ name: string;
416
+ meshPath: string;
417
+ density?: number;
418
+ minScale?: number;
419
+ maxScale?: number;
420
+ }) {
421
+ const pythonScript = `
422
+ import unreal
423
+ import json
424
+
425
+ name = ${JSON.stringify(params.name)}
426
+ mesh_path = ${JSON.stringify(params.meshPath)}
427
+ density = ${params.density || 1.0}
428
+ min_scale = ${params.minScale || 0.8}
429
+ max_scale = ${params.maxScale || 1.2}
430
+
431
+ result = {}
432
+
433
+ try:
434
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
435
+
436
+ # Create directory
437
+ package_path = "/Game/Landscape/GrassTypes"
438
+ if not unreal.EditorAssetLibrary.does_directory_exist(package_path):
439
+ unreal.EditorAssetLibrary.make_directory(package_path)
440
+
441
+ # Create LandscapeGrassType
442
+ grass_type_path = f"{package_path}/{name}"
443
+
444
+ if not unreal.EditorAssetLibrary.does_asset_exist(grass_type_path):
445
+ # Create using factory
446
+ factory = unreal.LandscapeGrassTypeFactory()
447
+ grass_type = asset_tools.create_asset(
448
+ asset_name=name,
449
+ package_path=package_path,
450
+ asset_class=unreal.LandscapeGrassType,
451
+ factory=factory
452
+ )
453
+
454
+ if grass_type:
455
+ # Load mesh
456
+ mesh = unreal.EditorAssetLibrary.load_asset(mesh_path)
457
+ if mesh:
458
+ # Configure grass type
459
+ grass_variety = unreal.GrassVariety()
460
+ grass_variety.grass_mesh = mesh
461
+ grass_variety.grass_density = density * 100 # Convert to per square meter
462
+ grass_variety.use_grid = True
463
+ grass_variety.placement_jitter = 1.0
464
+ grass_variety.start_cull_distance = 10000
465
+ grass_variety.end_cull_distance = 20000
466
+ grass_variety.min_lod = -1
467
+ grass_variety.scaling = unreal.GrassScaling.UNIFORM
468
+ grass_variety.scale_x = unreal.FloatInterval(min_scale, max_scale)
469
+ grass_variety.scale_y = unreal.FloatInterval(min_scale, max_scale)
470
+ grass_variety.scale_z = unreal.FloatInterval(min_scale, max_scale)
471
+ grass_variety.random_rotation = True
472
+ grass_variety.align_to_surface = True
473
+
474
+ grass_type.grass_varieties = [grass_variety]
475
+
476
+ # Save asset
477
+ unreal.EditorAssetLibrary.save_asset(grass_type.get_path_name())
478
+
479
+ result['success'] = True
480
+ result['message'] = f"Created landscape grass type '{name}'"
481
+ result['asset_path'] = grass_type.get_path_name()
482
+ else:
483
+ result['success'] = False
484
+ result['error'] = f"Could not load mesh: {mesh_path}"
485
+ else:
486
+ result['success'] = False
487
+ result['error'] = "Could not create LandscapeGrassType"
488
+ else:
489
+ result['success'] = False
490
+ result['error'] = f"Grass type already exists: {grass_type_path}"
491
+
492
+ except Exception as e:
493
+ result['success'] = False
494
+ result['error'] = str(e)
495
+
496
+ print(f"RESULT:{json.dumps(result)}")
497
+ `.trim();
498
+
499
+ const response = await this.bridge.executePython(pythonScript);
500
+ const output = this.parseResponse(response);
501
+ const match = output.match(/RESULT:({.*})/);
502
+
503
+ if (match) {
504
+ try {
505
+ return JSON.parse(match[1]);
506
+ } catch (_e) {
507
+ return { success: false, error: 'Failed to parse result', details: output };
508
+ }
509
+ }
510
+
511
+ return { success: false, error: 'No result found', output };
512
+ }
513
+
514
+ private parseResponse(response: any): string {
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);
525
+ }
526
+ }