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,365 @@
|
|
|
1
|
+
import { toVec3Object, toRotObject } from '../utils/normalize.js';
|
|
2
|
+
export class EditorTools {
|
|
3
|
+
bridge;
|
|
4
|
+
constructor(bridge) {
|
|
5
|
+
this.bridge = bridge;
|
|
6
|
+
}
|
|
7
|
+
async isInPIE() {
|
|
8
|
+
try {
|
|
9
|
+
const pythonCmd = `
|
|
10
|
+
import unreal
|
|
11
|
+
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
12
|
+
if les:
|
|
13
|
+
print("PIE_STATE:" + str(les.is_in_play_in_editor()))
|
|
14
|
+
else:
|
|
15
|
+
print("PIE_STATE:False")
|
|
16
|
+
`.trim();
|
|
17
|
+
const resp = await this.bridge.executePython(pythonCmd);
|
|
18
|
+
const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
|
|
19
|
+
return out.includes('PIE_STATE:True');
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async ensureNotInPIE() {
|
|
26
|
+
if (await this.isInPIE()) {
|
|
27
|
+
await this.stopPlayInEditor();
|
|
28
|
+
// Wait a bit for PIE to fully stop
|
|
29
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async playInEditor() {
|
|
33
|
+
try {
|
|
34
|
+
// Set tick rate to match UI play (60 fps for game mode)
|
|
35
|
+
await this.bridge.executeConsoleCommand('t.MaxFPS 60');
|
|
36
|
+
// Try Python first using the modern LevelEditorSubsystem
|
|
37
|
+
try {
|
|
38
|
+
// Use LevelEditorSubsystem to play in the selected viewport (modern API)
|
|
39
|
+
const pythonCmd = `
|
|
40
|
+
import unreal, time, json
|
|
41
|
+
# Start PIE using LevelEditorSubsystem (modern approach)
|
|
42
|
+
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
43
|
+
if les:
|
|
44
|
+
# Store initial state
|
|
45
|
+
was_playing = les.is_in_play_in_editor()
|
|
46
|
+
|
|
47
|
+
# Request PIE in the current viewport
|
|
48
|
+
les.editor_play_simulate()
|
|
49
|
+
|
|
50
|
+
# Wait for PIE to start with multiple checks
|
|
51
|
+
max_attempts = 10
|
|
52
|
+
for i in range(max_attempts):
|
|
53
|
+
time.sleep(0.2) # Wait 200ms between checks
|
|
54
|
+
is_playing = les.is_in_play_in_editor()
|
|
55
|
+
if is_playing and not was_playing:
|
|
56
|
+
# PIE has started
|
|
57
|
+
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
58
|
+
break
|
|
59
|
+
else:
|
|
60
|
+
# If we've waited 2 seconds total and PIE hasn't started,
|
|
61
|
+
# but the command was sent, assume it will start
|
|
62
|
+
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
63
|
+
else:
|
|
64
|
+
# If subsystem not available, report error
|
|
65
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
66
|
+
`.trim();
|
|
67
|
+
const resp = await this.bridge.executePython(pythonCmd);
|
|
68
|
+
const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
|
|
69
|
+
const m = out.match(/RESULT:({.*})/);
|
|
70
|
+
if (m) {
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(m[1]);
|
|
73
|
+
if (parsed.success) {
|
|
74
|
+
const method = parsed.method || 'LevelEditorSubsystem';
|
|
75
|
+
return { success: true, message: `PIE started (via ${method})` };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
try {
|
|
80
|
+
// Fallback: handle non-JSON python dict-style output
|
|
81
|
+
const sanitized = m[1].replace(/'/g, '"').replace(/\bTrue\b/g, 'true').replace(/\bFalse\b/g, 'false');
|
|
82
|
+
const parsed = JSON.parse(sanitized);
|
|
83
|
+
if (parsed.success) {
|
|
84
|
+
const method = parsed.method || 'LevelEditorSubsystem';
|
|
85
|
+
return { success: true, message: `PIE started (via ${method})` };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch { }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// If not verified, fall through to fallback
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
// Log the error for debugging but continue
|
|
95
|
+
console.error('Python PIE start issue:', err);
|
|
96
|
+
}
|
|
97
|
+
// Fallback to console command which is more reliable
|
|
98
|
+
await this.bridge.executeConsoleCommand('PlayInViewport');
|
|
99
|
+
// Wait a moment and verify PIE started
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
101
|
+
// Check if PIE is now active
|
|
102
|
+
const isPlaying = await this.isInPIE();
|
|
103
|
+
return {
|
|
104
|
+
success: true,
|
|
105
|
+
message: isPlaying ? 'PIE started successfully' : 'PIE start command sent (may take a moment)'
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
return { success: false, error: `Failed to start PIE: ${err}` };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async stopPlayInEditor() {
|
|
113
|
+
try {
|
|
114
|
+
// Try Python first using the modern LevelEditorSubsystem
|
|
115
|
+
try {
|
|
116
|
+
const pythonCmd = `
|
|
117
|
+
import unreal, time, json
|
|
118
|
+
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
119
|
+
if les:
|
|
120
|
+
# Use correct method name for stopping PIE
|
|
121
|
+
les.editor_request_end_play() # Modern API method
|
|
122
|
+
print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
|
|
123
|
+
else:
|
|
124
|
+
# If subsystem not available, report error
|
|
125
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
126
|
+
`.trim();
|
|
127
|
+
const resp = await this.bridge.executePython(pythonCmd);
|
|
128
|
+
const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
|
|
129
|
+
const m = out.match(/RESULT:({.*})/);
|
|
130
|
+
if (m) {
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(m[1].replace(/'/g, '"'));
|
|
133
|
+
if (parsed.success) {
|
|
134
|
+
const method = parsed.method || 'LevelEditorSubsystem';
|
|
135
|
+
return { success: true, message: `PIE stopped via ${method}` };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch { }
|
|
139
|
+
}
|
|
140
|
+
// Default success message if parsing fails
|
|
141
|
+
return { success: true, message: 'PIE stopped successfully' };
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Fallback to console command
|
|
145
|
+
await this.bridge.executeConsoleCommand('stop');
|
|
146
|
+
return { success: true, message: 'PIE stopped via console command' };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
return { success: false, error: `Failed to stop PIE: ${err}` };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async pausePlayInEditor() {
|
|
154
|
+
try {
|
|
155
|
+
// Pause/Resume PIE
|
|
156
|
+
await this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
157
|
+
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
158
|
+
functionName: 'ExecuteConsoleCommand',
|
|
159
|
+
parameters: {
|
|
160
|
+
WorldContextObject: null,
|
|
161
|
+
Command: 'pause',
|
|
162
|
+
SpecificPlayer: null
|
|
163
|
+
},
|
|
164
|
+
generateTransaction: false
|
|
165
|
+
});
|
|
166
|
+
return { success: true, message: 'PIE paused/resumed' };
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
return { success: false, error: `Failed to pause PIE: ${err}` };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Alias for consistency with naming convention
|
|
173
|
+
async pauseInEditor() {
|
|
174
|
+
return this.pausePlayInEditor();
|
|
175
|
+
}
|
|
176
|
+
async buildLighting() {
|
|
177
|
+
try {
|
|
178
|
+
// Use modern LevelEditorSubsystem to build lighting
|
|
179
|
+
const py = `
|
|
180
|
+
import unreal
|
|
181
|
+
import json
|
|
182
|
+
try:
|
|
183
|
+
# Use modern LevelEditorSubsystem API
|
|
184
|
+
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
185
|
+
if les:
|
|
186
|
+
# build_light_maps(quality, with_reflection_captures)
|
|
187
|
+
les.build_light_maps(unreal.LightingBuildQuality.QUALITY_HIGH, True)
|
|
188
|
+
print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via LevelEditorSubsystem'}))
|
|
189
|
+
else:
|
|
190
|
+
# If subsystem not available, report error
|
|
191
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
194
|
+
`.trim();
|
|
195
|
+
const resp = await this.bridge.executePython(py);
|
|
196
|
+
const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
|
|
197
|
+
const m = out.match(/RESULT:({.*})/);
|
|
198
|
+
if (m) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(m[1].replace(/'/g, '"'));
|
|
201
|
+
return parsed.success ? { success: true, message: parsed.message } : { success: false, error: parsed.error };
|
|
202
|
+
}
|
|
203
|
+
catch { }
|
|
204
|
+
}
|
|
205
|
+
return { success: true, message: 'Lighting build started' };
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
return { success: false, error: `Failed to build lighting: ${err}` };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
async setViewportCamera(location, rotation) {
|
|
212
|
+
// Special handling for when both location and rotation are missing/invalid
|
|
213
|
+
// Allow rotation-only updates
|
|
214
|
+
if (location === null) {
|
|
215
|
+
// Explicit null is not allowed for location
|
|
216
|
+
throw new Error('Invalid location: null is not allowed');
|
|
217
|
+
}
|
|
218
|
+
if (location !== undefined && location !== null) {
|
|
219
|
+
const locObj = toVec3Object(location);
|
|
220
|
+
if (!locObj) {
|
|
221
|
+
throw new Error('Invalid location: must be {x,y,z} or [x,y,z]');
|
|
222
|
+
}
|
|
223
|
+
// Clamp extreme values to reasonable limits for Unreal Engine
|
|
224
|
+
const MAX_COORD = 1000000; // 1 million units is a reasonable max for UE
|
|
225
|
+
locObj.x = Math.max(-MAX_COORD, Math.min(MAX_COORD, locObj.x));
|
|
226
|
+
locObj.y = Math.max(-MAX_COORD, Math.min(MAX_COORD, locObj.y));
|
|
227
|
+
locObj.z = Math.max(-MAX_COORD, Math.min(MAX_COORD, locObj.z));
|
|
228
|
+
location = locObj;
|
|
229
|
+
}
|
|
230
|
+
// Validate rotation if provided
|
|
231
|
+
if (rotation !== undefined) {
|
|
232
|
+
if (rotation === null) {
|
|
233
|
+
throw new Error('Invalid rotation: null is not allowed');
|
|
234
|
+
}
|
|
235
|
+
const rotObj = toRotObject(rotation);
|
|
236
|
+
if (!rotObj) {
|
|
237
|
+
throw new Error('Invalid rotation: must be {pitch,yaw,roll} or [pitch,yaw,roll]');
|
|
238
|
+
}
|
|
239
|
+
// Normalize rotation values to 0-360 range
|
|
240
|
+
rotObj.pitch = ((rotObj.pitch % 360) + 360) % 360;
|
|
241
|
+
rotObj.yaw = ((rotObj.yaw % 360) + 360) % 360;
|
|
242
|
+
rotObj.roll = ((rotObj.roll % 360) + 360) % 360;
|
|
243
|
+
rotation = rotObj;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
// Try Python for actual viewport camera positioning
|
|
247
|
+
// Only proceed if we have a valid location
|
|
248
|
+
if (location) {
|
|
249
|
+
try {
|
|
250
|
+
const rot = rotation || { pitch: 0, yaw: 0, roll: 0 };
|
|
251
|
+
const pythonCmd = `
|
|
252
|
+
import unreal
|
|
253
|
+
# Use UnrealEditorSubsystem instead of deprecated EditorLevelLibrary
|
|
254
|
+
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
255
|
+
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
256
|
+
location = unreal.Vector(${location.x}, ${location.y}, ${location.z})
|
|
257
|
+
rotation = unreal.Rotator(${rot.pitch}, ${rot.yaw}, ${rot.roll})
|
|
258
|
+
if ues:
|
|
259
|
+
ues.set_level_viewport_camera_info(location, rotation)
|
|
260
|
+
# Invalidate viewports to ensure visual update
|
|
261
|
+
try:
|
|
262
|
+
if les:
|
|
263
|
+
les.editor_invalidate_viewports()
|
|
264
|
+
except Exception:
|
|
265
|
+
pass
|
|
266
|
+
`.trim();
|
|
267
|
+
await this.bridge.executePython(pythonCmd);
|
|
268
|
+
return {
|
|
269
|
+
success: true,
|
|
270
|
+
message: 'Viewport camera positioned via UnrealEditorSubsystem'
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// Fallback to camera speed control
|
|
275
|
+
await this.bridge.executeConsoleCommand('camspeed 4');
|
|
276
|
+
return {
|
|
277
|
+
success: true,
|
|
278
|
+
message: 'Camera speed set. Use debug camera (toggledebugcamera) for manual positioning'
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else if (rotation) {
|
|
283
|
+
// Only rotation provided, try to set just rotation
|
|
284
|
+
try {
|
|
285
|
+
const pythonCmd = `
|
|
286
|
+
import unreal
|
|
287
|
+
# Use UnrealEditorSubsystem to read/write viewport camera
|
|
288
|
+
ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
|
|
289
|
+
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
290
|
+
rotation = unreal.Rotator(${rotation.pitch}, ${rotation.yaw}, ${rotation.roll})
|
|
291
|
+
if ues:
|
|
292
|
+
info = ues.get_level_viewport_camera_info()
|
|
293
|
+
if info is not None:
|
|
294
|
+
current_location, _ = info
|
|
295
|
+
ues.set_level_viewport_camera_info(current_location, rotation)
|
|
296
|
+
try:
|
|
297
|
+
if les:
|
|
298
|
+
les.editor_invalidate_viewports()
|
|
299
|
+
except Exception:
|
|
300
|
+
pass
|
|
301
|
+
`.trim();
|
|
302
|
+
await this.bridge.executePython(pythonCmd);
|
|
303
|
+
return {
|
|
304
|
+
success: true,
|
|
305
|
+
message: 'Viewport camera rotation set via UnrealEditorSubsystem'
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
// Fallback
|
|
310
|
+
return {
|
|
311
|
+
success: true,
|
|
312
|
+
message: 'Camera rotation update attempted'
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
// Neither location nor rotation provided - this is valid, just no-op
|
|
318
|
+
return {
|
|
319
|
+
success: true,
|
|
320
|
+
message: 'No camera changes requested'
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
catch (err) {
|
|
325
|
+
return { success: false, error: `Failed to set camera: ${err}` };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
async setCameraSpeed(speed) {
|
|
329
|
+
try {
|
|
330
|
+
await this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
331
|
+
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
332
|
+
functionName: 'ExecuteConsoleCommand',
|
|
333
|
+
parameters: {
|
|
334
|
+
WorldContextObject: null,
|
|
335
|
+
Command: `camspeed ${speed}`,
|
|
336
|
+
SpecificPlayer: null
|
|
337
|
+
},
|
|
338
|
+
generateTransaction: false
|
|
339
|
+
});
|
|
340
|
+
return { success: true, message: `Camera speed set to ${speed}` };
|
|
341
|
+
}
|
|
342
|
+
catch (err) {
|
|
343
|
+
return { success: false, error: `Failed to set camera speed: ${err}` };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async setFOV(fov) {
|
|
347
|
+
try {
|
|
348
|
+
await this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
349
|
+
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
350
|
+
functionName: 'ExecuteConsoleCommand',
|
|
351
|
+
parameters: {
|
|
352
|
+
WorldContextObject: null,
|
|
353
|
+
Command: `fov ${fov}`,
|
|
354
|
+
SpecificPlayer: null
|
|
355
|
+
},
|
|
356
|
+
generateTransaction: false
|
|
357
|
+
});
|
|
358
|
+
return { success: true, message: `FOV set to ${fov}` };
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
return { success: false, error: `Failed to set FOV: ${err}` };
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
export declare class EngineTools {
|
|
3
|
+
private bridge;
|
|
4
|
+
private env;
|
|
5
|
+
constructor(bridge: UnrealBridge);
|
|
6
|
+
launchEditor(params?: {
|
|
7
|
+
editorExe?: string;
|
|
8
|
+
projectPath?: string;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
success: boolean;
|
|
11
|
+
error: string;
|
|
12
|
+
pid?: undefined;
|
|
13
|
+
message?: undefined;
|
|
14
|
+
} | {
|
|
15
|
+
success: boolean;
|
|
16
|
+
pid: number | undefined;
|
|
17
|
+
message: string;
|
|
18
|
+
error?: undefined;
|
|
19
|
+
}>;
|
|
20
|
+
quitEditor(): Promise<{
|
|
21
|
+
success: boolean;
|
|
22
|
+
message: string;
|
|
23
|
+
error?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
success: boolean;
|
|
26
|
+
error: string;
|
|
27
|
+
message?: undefined;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=engine.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { loadEnv } from '../types/env.js';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
export class EngineTools {
|
|
4
|
+
bridge;
|
|
5
|
+
env = loadEnv();
|
|
6
|
+
constructor(bridge) {
|
|
7
|
+
this.bridge = bridge;
|
|
8
|
+
}
|
|
9
|
+
async launchEditor(params) {
|
|
10
|
+
const exe = params?.editorExe || this.env.UE_EDITOR_EXE;
|
|
11
|
+
const proj = params?.projectPath || this.env.UE_PROJECT_PATH;
|
|
12
|
+
if (!exe)
|
|
13
|
+
return { success: false, error: 'UE_EDITOR_EXE not set and editorExe not provided' };
|
|
14
|
+
if (!proj)
|
|
15
|
+
return { success: false, error: 'UE_PROJECT_PATH not set and projectPath not provided' };
|
|
16
|
+
try {
|
|
17
|
+
const child = spawn(exe, [proj], { detached: true, stdio: 'ignore' });
|
|
18
|
+
child.unref();
|
|
19
|
+
return { success: true, pid: child.pid, message: 'Editor launch requested' };
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return { success: false, error: String(err?.message || err) };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
async quitEditor() {
|
|
26
|
+
try {
|
|
27
|
+
// Use Python SystemLibrary.quit_editor if available
|
|
28
|
+
await this.bridge.executePython('import unreal; unreal.SystemLibrary.quit_editor()');
|
|
29
|
+
return { success: true, message: 'Quit command sent' };
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
return { success: false, error: String(err?.message || err) };
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
export declare class FoliageTools {
|
|
3
|
+
private bridge;
|
|
4
|
+
constructor(bridge: UnrealBridge);
|
|
5
|
+
addFoliageType(params: {
|
|
6
|
+
name: string;
|
|
7
|
+
meshPath: string;
|
|
8
|
+
density?: number;
|
|
9
|
+
radius?: number;
|
|
10
|
+
minScale?: number;
|
|
11
|
+
maxScale?: number;
|
|
12
|
+
alignToNormal?: boolean;
|
|
13
|
+
randomYaw?: boolean;
|
|
14
|
+
groundSlope?: number;
|
|
15
|
+
}): Promise<{
|
|
16
|
+
success: boolean;
|
|
17
|
+
error: any;
|
|
18
|
+
created?: undefined;
|
|
19
|
+
exists?: undefined;
|
|
20
|
+
method?: undefined;
|
|
21
|
+
assetPath?: undefined;
|
|
22
|
+
usedMesh?: undefined;
|
|
23
|
+
note?: undefined;
|
|
24
|
+
message?: undefined;
|
|
25
|
+
} | {
|
|
26
|
+
success: boolean;
|
|
27
|
+
created: any;
|
|
28
|
+
exists: any;
|
|
29
|
+
method: any;
|
|
30
|
+
assetPath: any;
|
|
31
|
+
usedMesh: any;
|
|
32
|
+
note: any;
|
|
33
|
+
message: string;
|
|
34
|
+
error?: undefined;
|
|
35
|
+
}>;
|
|
36
|
+
paintFoliage(params: {
|
|
37
|
+
foliageType: string;
|
|
38
|
+
position: [number, number, number];
|
|
39
|
+
brushSize?: number;
|
|
40
|
+
paintDensity?: number;
|
|
41
|
+
eraseMode?: boolean;
|
|
42
|
+
}): Promise<{
|
|
43
|
+
success: boolean;
|
|
44
|
+
error: any;
|
|
45
|
+
added?: undefined;
|
|
46
|
+
actor?: undefined;
|
|
47
|
+
component?: undefined;
|
|
48
|
+
usedMesh?: undefined;
|
|
49
|
+
note?: undefined;
|
|
50
|
+
message?: undefined;
|
|
51
|
+
} | {
|
|
52
|
+
success: boolean;
|
|
53
|
+
added: any;
|
|
54
|
+
actor: any;
|
|
55
|
+
component: any;
|
|
56
|
+
usedMesh: any;
|
|
57
|
+
note: any;
|
|
58
|
+
message: string;
|
|
59
|
+
error?: undefined;
|
|
60
|
+
}>;
|
|
61
|
+
createInstancedMesh(params: {
|
|
62
|
+
name: string;
|
|
63
|
+
meshPath: string;
|
|
64
|
+
instances: Array<{
|
|
65
|
+
position: [number, number, number];
|
|
66
|
+
rotation?: [number, number, number];
|
|
67
|
+
scale?: [number, number, number];
|
|
68
|
+
}>;
|
|
69
|
+
enableCulling?: boolean;
|
|
70
|
+
cullDistance?: number;
|
|
71
|
+
}): Promise<{
|
|
72
|
+
success: boolean;
|
|
73
|
+
message: string;
|
|
74
|
+
}>;
|
|
75
|
+
setFoliageLOD(params: {
|
|
76
|
+
foliageType: string;
|
|
77
|
+
lodDistances?: number[];
|
|
78
|
+
screenSize?: number[];
|
|
79
|
+
}): Promise<{
|
|
80
|
+
success: boolean;
|
|
81
|
+
message: string;
|
|
82
|
+
}>;
|
|
83
|
+
createProceduralFoliage(params: {
|
|
84
|
+
volumeName: string;
|
|
85
|
+
position: [number, number, number];
|
|
86
|
+
size: [number, number, number];
|
|
87
|
+
foliageTypes: string[];
|
|
88
|
+
seed?: number;
|
|
89
|
+
tileSize?: number;
|
|
90
|
+
}): Promise<{
|
|
91
|
+
success: boolean;
|
|
92
|
+
message: string;
|
|
93
|
+
}>;
|
|
94
|
+
setFoliageCollision(params: {
|
|
95
|
+
foliageType: string;
|
|
96
|
+
collisionEnabled?: boolean;
|
|
97
|
+
collisionProfile?: string;
|
|
98
|
+
generateOverlapEvents?: boolean;
|
|
99
|
+
}): Promise<{
|
|
100
|
+
success: boolean;
|
|
101
|
+
message: string;
|
|
102
|
+
}>;
|
|
103
|
+
createGrassSystem(params: {
|
|
104
|
+
name: string;
|
|
105
|
+
grassTypes: Array<{
|
|
106
|
+
meshPath: string;
|
|
107
|
+
density: number;
|
|
108
|
+
minScale?: number;
|
|
109
|
+
maxScale?: number;
|
|
110
|
+
}>;
|
|
111
|
+
windStrength?: number;
|
|
112
|
+
windSpeed?: number;
|
|
113
|
+
}): Promise<{
|
|
114
|
+
success: boolean;
|
|
115
|
+
message: string;
|
|
116
|
+
}>;
|
|
117
|
+
removeFoliageInstances(params: {
|
|
118
|
+
foliageType: string;
|
|
119
|
+
position: [number, number, number];
|
|
120
|
+
radius: number;
|
|
121
|
+
}): Promise<any>;
|
|
122
|
+
selectFoliageInstances(params: {
|
|
123
|
+
foliageType: string;
|
|
124
|
+
position?: [number, number, number];
|
|
125
|
+
radius?: number;
|
|
126
|
+
selectAll?: boolean;
|
|
127
|
+
}): Promise<any>;
|
|
128
|
+
updateFoliageInstances(params: {
|
|
129
|
+
foliageType: string;
|
|
130
|
+
updateTransforms?: boolean;
|
|
131
|
+
updateMesh?: boolean;
|
|
132
|
+
newMeshPath?: string;
|
|
133
|
+
}): Promise<{
|
|
134
|
+
success: boolean;
|
|
135
|
+
message: string;
|
|
136
|
+
}>;
|
|
137
|
+
createFoliageSpawner(params: {
|
|
138
|
+
name: string;
|
|
139
|
+
spawnArea: 'Landscape' | 'StaticMesh' | 'BSP' | 'Foliage' | 'All';
|
|
140
|
+
excludeAreas?: Array<[number, number, number, number]>;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
success: boolean;
|
|
143
|
+
message: string;
|
|
144
|
+
}>;
|
|
145
|
+
optimizeFoliage(params: {
|
|
146
|
+
mergeInstances?: boolean;
|
|
147
|
+
generateClusters?: boolean;
|
|
148
|
+
clusterSize?: number;
|
|
149
|
+
reduceDrawCalls?: boolean;
|
|
150
|
+
}): Promise<{
|
|
151
|
+
success: boolean;
|
|
152
|
+
message: string;
|
|
153
|
+
}>;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=foliage.d.ts.map
|