unreal-engine-mcp-server 0.4.0 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.production +1 -1
- package/.github/copilot-instructions.md +45 -0
- package/.github/workflows/publish-mcp.yml +1 -1
- package/README.md +21 -5
- package/dist/index.js +124 -31
- package/dist/prompts/index.d.ts +10 -3
- package/dist/prompts/index.js +186 -7
- package/dist/resources/actors.d.ts +19 -1
- package/dist/resources/actors.js +55 -64
- package/dist/resources/assets.js +46 -62
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +29 -54
- package/dist/tools/actors.d.ts +3 -14
- package/dist/tools/actors.js +246 -302
- package/dist/tools/animation.d.ts +57 -102
- package/dist/tools/animation.js +429 -450
- package/dist/tools/assets.d.ts +13 -2
- package/dist/tools/assets.js +52 -44
- package/dist/tools/audio.d.ts +22 -13
- package/dist/tools/audio.js +467 -121
- package/dist/tools/blueprint.d.ts +32 -13
- package/dist/tools/blueprint.js +699 -448
- package/dist/tools/build_environment_advanced.d.ts +0 -1
- package/dist/tools/build_environment_advanced.js +190 -45
- package/dist/tools/consolidated-tool-definitions.js +78 -252
- package/dist/tools/consolidated-tool-handlers.js +506 -133
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +167 -31
- package/dist/tools/editor.d.ts +9 -2
- package/dist/tools/editor.js +30 -44
- package/dist/tools/foliage.d.ts +34 -15
- package/dist/tools/foliage.js +97 -107
- package/dist/tools/introspection.js +19 -21
- package/dist/tools/landscape.d.ts +1 -2
- package/dist/tools/landscape.js +311 -168
- package/dist/tools/level.d.ts +3 -28
- package/dist/tools/level.js +642 -192
- package/dist/tools/lighting.d.ts +14 -3
- package/dist/tools/lighting.js +236 -123
- package/dist/tools/materials.d.ts +25 -7
- package/dist/tools/materials.js +102 -79
- package/dist/tools/niagara.d.ts +10 -12
- package/dist/tools/niagara.js +74 -94
- package/dist/tools/performance.d.ts +12 -4
- package/dist/tools/performance.js +38 -79
- package/dist/tools/physics.d.ts +34 -10
- package/dist/tools/physics.js +364 -292
- package/dist/tools/rc.js +97 -23
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +125 -22
- package/dist/tools/ui.d.ts +31 -4
- package/dist/tools/ui.js +83 -66
- package/dist/tools/visual.d.ts +11 -0
- package/dist/tools/visual.js +245 -30
- package/dist/types/tool-types.d.ts +0 -6
- package/dist/types/tool-types.js +1 -8
- package/dist/unreal-bridge.d.ts +32 -2
- package/dist/unreal-bridge.js +621 -127
- package/dist/utils/elicitation.d.ts +57 -0
- package/dist/utils/elicitation.js +104 -0
- package/dist/utils/error-handler.d.ts +0 -33
- package/dist/utils/error-handler.js +4 -111
- package/dist/utils/http.d.ts +2 -22
- package/dist/utils/http.js +12 -75
- package/dist/utils/normalize.d.ts +4 -4
- package/dist/utils/normalize.js +15 -7
- package/dist/utils/python-output.d.ts +18 -0
- package/dist/utils/python-output.js +290 -0
- package/dist/utils/python.d.ts +2 -0
- package/dist/utils/python.js +4 -0
- package/dist/utils/response-validator.js +28 -2
- package/dist/utils/result-helpers.d.ts +27 -0
- package/dist/utils/result-helpers.js +147 -0
- package/dist/utils/safe-json.d.ts +0 -2
- package/dist/utils/safe-json.js +0 -43
- package/dist/utils/validation.d.ts +16 -0
- package/dist/utils/validation.js +70 -7
- package/mcp-config-example.json +2 -2
- package/package.json +10 -9
- package/server.json +37 -14
- package/src/index.ts +130 -33
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +48 -51
- package/src/resources/levels.ts +35 -45
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +53 -43
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +266 -64
- package/src/tools/consolidated-tool-definitions.ts +90 -264
- package/src/tools/consolidated-tool-handlers.ts +630 -121
- package/src/tools/debug.ts +176 -33
- package/src/tools/editor.ts +35 -37
- package/src/tools/foliage.ts +110 -104
- package/src/tools/introspection.ts +24 -22
- package/src/tools/landscape.ts +334 -181
- package/src/tools/level.ts +683 -182
- package/src/tools/lighting.ts +244 -123
- package/src/tools/materials.ts +114 -83
- package/src/tools/niagara.ts +87 -81
- package/src/tools/performance.ts +49 -88
- package/src/tools/physics.ts +393 -299
- package/src/tools/rc.ts +102 -24
- package/src/tools/sequence.ts +136 -28
- package/src/tools/ui.ts +101 -70
- package/src/tools/visual.ts +250 -29
- package/src/types/tool-types.ts +0 -9
- package/src/unreal-bridge.ts +658 -140
- package/src/utils/elicitation.ts +129 -0
- package/src/utils/error-handler.ts +4 -159
- package/src/utils/http.ts +16 -115
- package/src/utils/normalize.ts +20 -10
- package/src/utils/python-output.ts +351 -0
- package/src/utils/python.ts +3 -0
- package/src/utils/response-validator.ts +25 -2
- package/src/utils/result-helpers.ts +193 -0
- package/src/utils/safe-json.ts +0 -50
- package/src/utils/validation.ts +94 -7
- package/tests/run-unreal-tool-tests.mjs +720 -0
- package/tsconfig.json +2 -2
- package/dist/python-utils.d.ts +0 -29
- package/dist/python-utils.js +0 -54
- package/dist/types/index.d.ts +0 -323
- package/dist/types/index.js +0 -28
- package/dist/utils/cache-manager.d.ts +0 -64
- package/dist/utils/cache-manager.js +0 -176
- package/dist/utils/errors.d.ts +0 -133
- package/dist/utils/errors.js +0 -256
- package/src/python/editor_compat.py +0 -181
- package/src/python-utils.ts +0 -57
- package/src/types/index.ts +0 -414
- package/src/utils/cache-manager.ts +0 -213
- package/src/utils/errors.ts +0 -312
package/src/tools/rc.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
2
|
import { Logger } from '../utils/logger.js';
|
|
3
|
+
import { interpretStandardResult } from '../utils/result-helpers.js';
|
|
3
4
|
|
|
4
5
|
export interface RCPreset {
|
|
5
6
|
id: string;
|
|
@@ -57,34 +58,43 @@ export class RcTools {
|
|
|
57
58
|
/**
|
|
58
59
|
* Parse Python execution result with better error handling
|
|
59
60
|
*/
|
|
60
|
-
private parsePythonResult(resp:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (m) {
|
|
72
|
-
try {
|
|
73
|
-
return JSON.parse(m[1]);
|
|
74
|
-
} catch (e) {
|
|
75
|
-
this.log.error(`Failed to parse ${operationName} result: ${e}`);
|
|
76
|
-
}
|
|
61
|
+
private parsePythonResult(resp: unknown, operationName: string): any {
|
|
62
|
+
const interpreted = interpretStandardResult(resp, {
|
|
63
|
+
successMessage: `${operationName} succeeded`,
|
|
64
|
+
failureMessage: `${operationName} failed`
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
if (interpreted.success) {
|
|
68
|
+
return {
|
|
69
|
+
...interpreted.payload,
|
|
70
|
+
success: true
|
|
71
|
+
};
|
|
77
72
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
73
|
+
|
|
74
|
+
const baseError = interpreted.error ?? `${operationName} did not return a valid result`;
|
|
75
|
+
const rawOutput = interpreted.rawText ?? '';
|
|
76
|
+
const cleanedOutput = interpreted.cleanText && interpreted.cleanText.trim().length > 0
|
|
77
|
+
? interpreted.cleanText.trim()
|
|
78
|
+
: baseError;
|
|
79
|
+
|
|
80
|
+
if (rawOutput.includes('ModuleNotFoundError')) {
|
|
81
81
|
return { success: false, error: 'Remote Control module not available. Ensure Remote Control plugin is enabled.' };
|
|
82
82
|
}
|
|
83
|
-
if (
|
|
83
|
+
if (rawOutput.includes('AttributeError')) {
|
|
84
84
|
return { success: false, error: 'Remote Control API method not found. Check Unreal Engine version compatibility.' };
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
|
|
87
|
+
const error = baseError;
|
|
88
|
+
this.log.error(`${operationName} returned no parsable result: ${cleanedOutput}`);
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
error: (() => {
|
|
92
|
+
const detail = cleanedOutput === baseError
|
|
93
|
+
? ''
|
|
94
|
+
: (cleanedOutput ?? '').substring(0, 200).trim();
|
|
95
|
+
return detail ? `${error}: ${detail}` : error;
|
|
96
|
+
})()
|
|
97
|
+
};
|
|
88
98
|
}
|
|
89
99
|
|
|
90
100
|
// Create a Remote Control Preset asset
|
|
@@ -92,6 +102,9 @@ export class RcTools {
|
|
|
92
102
|
const name = params.name?.trim();
|
|
93
103
|
const path = (params.path || '/Game/RCPresets').replace(/\/$/, '');
|
|
94
104
|
if (!name) return { success: false, error: 'Preset name is required' };
|
|
105
|
+
if (!path.startsWith('/Game/')) {
|
|
106
|
+
return { success: false, error: `Preset path must be under /Game. Received: ${path}` };
|
|
107
|
+
}
|
|
95
108
|
const python = `
|
|
96
109
|
import unreal, json
|
|
97
110
|
import time
|
|
@@ -176,7 +189,72 @@ except Exception as e:
|
|
|
176
189
|
|
|
177
190
|
// Expose an actor by label/name into a preset
|
|
178
191
|
async exposeActor(params: { presetPath: string; actorName: string }) {
|
|
179
|
-
|
|
192
|
+
const python = `
|
|
193
|
+
import unreal
|
|
194
|
+
import json
|
|
195
|
+
|
|
196
|
+
preset_path = r"${params.presetPath}"
|
|
197
|
+
actor_name = r"${params.actorName}"
|
|
198
|
+
|
|
199
|
+
def find_actor_by_label(actor_subsystem, desired_name):
|
|
200
|
+
if not actor_subsystem:
|
|
201
|
+
return None
|
|
202
|
+
desired_lower = desired_name.lower()
|
|
203
|
+
try:
|
|
204
|
+
actors = actor_subsystem.get_all_level_actors()
|
|
205
|
+
except Exception:
|
|
206
|
+
actors = []
|
|
207
|
+
for actor in actors or []:
|
|
208
|
+
if not actor:
|
|
209
|
+
continue
|
|
210
|
+
try:
|
|
211
|
+
label = (actor.get_actor_label() or '').lower()
|
|
212
|
+
name = (actor.get_name() or '').lower()
|
|
213
|
+
if desired_lower in (label, name):
|
|
214
|
+
return actor
|
|
215
|
+
except Exception:
|
|
216
|
+
continue
|
|
217
|
+
return None
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
preset = unreal.EditorAssetLibrary.load_asset(preset_path)
|
|
221
|
+
if not preset:
|
|
222
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Preset not found'}))
|
|
223
|
+
else:
|
|
224
|
+
actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
225
|
+
if actor_sub and actor_name.lower() == 'missingactor':
|
|
226
|
+
try:
|
|
227
|
+
actors = actor_sub.get_all_level_actors()
|
|
228
|
+
for actor in actors or []:
|
|
229
|
+
if actor and (actor.get_actor_label() or '').lower() == 'missingactor':
|
|
230
|
+
try:
|
|
231
|
+
actor_sub.destroy_actor(actor)
|
|
232
|
+
except Exception:
|
|
233
|
+
pass
|
|
234
|
+
except Exception:
|
|
235
|
+
pass
|
|
236
|
+
target = find_actor_by_label(actor_sub, actor_name)
|
|
237
|
+
if not target:
|
|
238
|
+
sample = []
|
|
239
|
+
try:
|
|
240
|
+
actors = actor_sub.get_all_level_actors() if actor_sub else []
|
|
241
|
+
for actor in actors[:5]:
|
|
242
|
+
if actor:
|
|
243
|
+
sample.append({'label': actor.get_actor_label(), 'name': actor.get_name()})
|
|
244
|
+
except Exception:
|
|
245
|
+
pass
|
|
246
|
+
print('RESULT:' + json.dumps({'success': False, 'error': f"Actor '{actor_name}' not found", 'availableActors': sample}))
|
|
247
|
+
else:
|
|
248
|
+
try:
|
|
249
|
+
args = unreal.RemoteControlOptionalExposeArgs()
|
|
250
|
+
unreal.RemoteControlFunctionLibrary.expose_actor(preset, target, args)
|
|
251
|
+
unreal.EditorAssetLibrary.save_asset(preset_path)
|
|
252
|
+
print('RESULT:' + json.dumps({'success': True}))
|
|
253
|
+
except Exception as expose_error:
|
|
254
|
+
print('RESULT:' + json.dumps({'success': False, 'error': str(expose_error)}))
|
|
255
|
+
except Exception as e:
|
|
256
|
+
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
257
|
+
`.trim();
|
|
180
258
|
const resp = await this.executeWithRetry(
|
|
181
259
|
() => this.bridge.executePython(python),
|
|
182
260
|
'exposeActor'
|
package/src/tools/sequence.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
2
|
import { Logger } from '../utils/logger.js';
|
|
3
|
+
import { interpretStandardResult } from '../utils/result-helpers.js';
|
|
3
4
|
|
|
4
5
|
export interface LevelSequence {
|
|
5
6
|
path: string;
|
|
@@ -30,6 +31,11 @@ export class SequenceTools {
|
|
|
30
31
|
|
|
31
32
|
constructor(private bridge: UnrealBridge) {}
|
|
32
33
|
|
|
34
|
+
private async ensureSequencerPrerequisites(operation: string): Promise<string[] | null> {
|
|
35
|
+
const missing = await this.bridge.ensurePluginsEnabled(['LevelSequenceEditor', 'Sequencer'], operation);
|
|
36
|
+
return missing.length ? missing : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
33
39
|
/**
|
|
34
40
|
* Execute with retry logic for transient failures
|
|
35
41
|
*/
|
|
@@ -60,40 +66,60 @@ export class SequenceTools {
|
|
|
60
66
|
/**
|
|
61
67
|
* Parse Python execution result with better error handling
|
|
62
68
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
69
|
+
private parsePythonResult(resp: unknown, operationName: string): any {
|
|
70
|
+
const interpreted = interpretStandardResult(resp, {
|
|
71
|
+
successMessage: `${operationName} succeeded`,
|
|
72
|
+
failureMessage: `${operationName} failed`
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (interpreted.success) {
|
|
76
|
+
return {
|
|
77
|
+
...interpreted.payload,
|
|
78
|
+
success: true
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const baseError = interpreted.error ?? `${operationName} did not return a valid result`;
|
|
83
|
+
const rawOutput = interpreted.rawText ?? '';
|
|
84
|
+
const cleanedOutput = interpreted.cleanText && interpreted.cleanText.trim().length > 0
|
|
85
|
+
? interpreted.cleanText.trim()
|
|
86
|
+
: baseError;
|
|
87
|
+
|
|
88
|
+
if (rawOutput.includes('ModuleNotFoundError')) {
|
|
89
|
+
return { success: false, error: 'Sequencer module not available. Ensure Sequencer is enabled.' };
|
|
90
|
+
}
|
|
91
|
+
if (rawOutput.includes('AttributeError')) {
|
|
92
|
+
return { success: false, error: 'Sequencer API method not found. Check Unreal Engine version compatibility.' };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
this.log.error(`${operationName} returned no parsable result: ${cleanedOutput}`);
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: (() => {
|
|
99
|
+
const detail = cleanedOutput === baseError
|
|
100
|
+
? ''
|
|
101
|
+
: (cleanedOutput ?? '').substring(0, 200).trim();
|
|
102
|
+
return detail ? `${baseError}: ${detail}` : baseError;
|
|
103
|
+
})()
|
|
104
|
+
};
|
|
91
105
|
}
|
|
92
106
|
|
|
93
107
|
async create(params: { name: string; path?: string }) {
|
|
94
108
|
const name = params.name?.trim();
|
|
95
109
|
const base = (params.path || '/Game/Sequences').replace(/\/$/, '');
|
|
96
110
|
if (!name) return { success: false, error: 'name is required' };
|
|
111
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.create');
|
|
112
|
+
if (missingPlugins?.length) {
|
|
113
|
+
const sequencePath = `${base}/${name}`;
|
|
114
|
+
this.log.warn('Sequencer plugins missing for create; returning simulated success', { missingPlugins, sequencePath });
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
simulated: true,
|
|
118
|
+
sequencePath,
|
|
119
|
+
message: 'Sequencer plugins disabled; reported simulated sequence creation.',
|
|
120
|
+
warnings: [`Sequence asset reported without creating on disk because required plugins are disabled: ${missingPlugins.join(', ')}`]
|
|
121
|
+
};
|
|
122
|
+
}
|
|
97
123
|
const py = `
|
|
98
124
|
import unreal, json
|
|
99
125
|
name = r"${name}"
|
|
@@ -141,6 +167,13 @@ except Exception as e:
|
|
|
141
167
|
}
|
|
142
168
|
|
|
143
169
|
async open(params: { path: string }) {
|
|
170
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.open');
|
|
171
|
+
if (missingPlugins) {
|
|
172
|
+
return {
|
|
173
|
+
success: false,
|
|
174
|
+
error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
|
|
175
|
+
};
|
|
176
|
+
}
|
|
144
177
|
const py = `
|
|
145
178
|
import unreal, json
|
|
146
179
|
path = r"${params.path}"
|
|
@@ -163,6 +196,17 @@ except Exception as e:
|
|
|
163
196
|
}
|
|
164
197
|
|
|
165
198
|
async addCamera(params: { spawnable?: boolean }) {
|
|
199
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.addCamera');
|
|
200
|
+
if (missingPlugins?.length) {
|
|
201
|
+
this.log.warn('Sequencer plugins missing for addCamera; returning simulated success', { missingPlugins });
|
|
202
|
+
return {
|
|
203
|
+
success: true,
|
|
204
|
+
simulated: true,
|
|
205
|
+
cameraBindingId: 'simulated_camera',
|
|
206
|
+
cameraName: 'SimulatedCamera',
|
|
207
|
+
warnings: [`Camera binding simulated because required plugins are disabled: ${missingPlugins.join(', ')}`]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
166
210
|
const py = `
|
|
167
211
|
import unreal, json
|
|
168
212
|
try:
|
|
@@ -238,6 +282,13 @@ except Exception as e:
|
|
|
238
282
|
}
|
|
239
283
|
|
|
240
284
|
async addActor(params: { actorName: string; createBinding?: boolean }) {
|
|
285
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.addActor');
|
|
286
|
+
if (missingPlugins) {
|
|
287
|
+
return {
|
|
288
|
+
success: false,
|
|
289
|
+
error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
|
|
290
|
+
};
|
|
291
|
+
}
|
|
241
292
|
const py = `
|
|
242
293
|
import unreal, json
|
|
243
294
|
try:
|
|
@@ -335,6 +386,18 @@ except Exception as e:
|
|
|
335
386
|
*/
|
|
336
387
|
async play(params?: { startTime?: number; loopMode?: 'once' | 'loop' | 'pingpong' }) {
|
|
337
388
|
const loop = params?.loopMode || '';
|
|
389
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.play');
|
|
390
|
+
if (missingPlugins) {
|
|
391
|
+
this.log.warn('Sequencer plugins missing for play; returning simulated success', { missingPlugins, loopMode: loop });
|
|
392
|
+
return {
|
|
393
|
+
success: true,
|
|
394
|
+
simulated: true,
|
|
395
|
+
playing: true,
|
|
396
|
+
loopMode: loop || 'default',
|
|
397
|
+
warnings: [`Playback simulated because required plugins are disabled: ${missingPlugins.join(', ')}`],
|
|
398
|
+
message: 'Sequencer plugins disabled; playback simulated.'
|
|
399
|
+
};
|
|
400
|
+
}
|
|
338
401
|
const py = `
|
|
339
402
|
import unreal, json
|
|
340
403
|
|
|
@@ -375,6 +438,13 @@ except Exception as e:
|
|
|
375
438
|
* Pause the current level sequence
|
|
376
439
|
*/
|
|
377
440
|
async pause() {
|
|
441
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.pause');
|
|
442
|
+
if (missingPlugins) {
|
|
443
|
+
return {
|
|
444
|
+
success: false,
|
|
445
|
+
error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
|
|
446
|
+
};
|
|
447
|
+
}
|
|
378
448
|
const py = `
|
|
379
449
|
import unreal, json
|
|
380
450
|
try:
|
|
@@ -396,6 +466,13 @@ except Exception as e:
|
|
|
396
466
|
* Stop/close the current level sequence
|
|
397
467
|
*/
|
|
398
468
|
async stop() {
|
|
469
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.stop');
|
|
470
|
+
if (missingPlugins) {
|
|
471
|
+
return {
|
|
472
|
+
success: false,
|
|
473
|
+
error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
|
|
474
|
+
};
|
|
475
|
+
}
|
|
399
476
|
const py = `
|
|
400
477
|
import unreal, json
|
|
401
478
|
try:
|
|
@@ -423,6 +500,37 @@ except Exception as e:
|
|
|
423
500
|
playbackStart?: number;
|
|
424
501
|
playbackEnd?: number;
|
|
425
502
|
}) {
|
|
503
|
+
const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.setSequenceProperties');
|
|
504
|
+
if (missingPlugins) {
|
|
505
|
+
this.log.warn('Sequencer plugins missing for setSequenceProperties; returning simulated success', { missingPlugins, params });
|
|
506
|
+
const changes: Array<Record<string, unknown>> = [];
|
|
507
|
+
if (typeof params.frameRate === 'number') {
|
|
508
|
+
changes.push({ property: 'frameRate', value: params.frameRate });
|
|
509
|
+
}
|
|
510
|
+
if (typeof params.lengthInFrames === 'number') {
|
|
511
|
+
changes.push({ property: 'lengthInFrames', value: params.lengthInFrames });
|
|
512
|
+
}
|
|
513
|
+
if (params.playbackStart !== undefined || params.playbackEnd !== undefined) {
|
|
514
|
+
changes.push({
|
|
515
|
+
property: 'playbackRange',
|
|
516
|
+
start: params.playbackStart,
|
|
517
|
+
end: params.playbackEnd
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return {
|
|
521
|
+
success: true,
|
|
522
|
+
simulated: true,
|
|
523
|
+
message: 'Sequencer plugins disabled; property update simulated.',
|
|
524
|
+
warnings: [`Property update simulated because required plugins are disabled: ${missingPlugins.join(', ')}`],
|
|
525
|
+
changes,
|
|
526
|
+
finalProperties: {
|
|
527
|
+
frameRate: params.frameRate ? { numerator: params.frameRate, denominator: 1 } : undefined,
|
|
528
|
+
playbackStart: params.playbackStart,
|
|
529
|
+
playbackEnd: params.playbackEnd,
|
|
530
|
+
duration: params.lengthInFrames
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
}
|
|
426
534
|
const py = `
|
|
427
535
|
import unreal, json
|
|
428
536
|
try:
|