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