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,579 @@
1
+ import { validateAssetParams, concurrencyDelay } from '../utils/validation.js';
2
+ export class AnimationTools {
3
+ bridge;
4
+ constructor(bridge) {
5
+ this.bridge = bridge;
6
+ }
7
+ /**
8
+ * Create Animation Blueprint
9
+ */
10
+ async createAnimationBlueprint(params) {
11
+ try {
12
+ // Strong input validation with expected error messages
13
+ if (!params.name || params.name.trim() === '') {
14
+ return {
15
+ success: false,
16
+ message: 'Failed: Name cannot be empty',
17
+ error: 'Name cannot be empty'
18
+ };
19
+ }
20
+ // Check for whitespace issues
21
+ if (params.name.includes(' ') || params.name.startsWith(' ') || params.name.endsWith(' ')) {
22
+ return {
23
+ success: false,
24
+ message: 'Failed to create Animation Blueprint: Name contains invalid whitespace',
25
+ error: 'Name contains invalid whitespace'
26
+ };
27
+ }
28
+ // Check for SQL injection patterns
29
+ if (params.name.toLowerCase().includes('drop') || params.name.toLowerCase().includes('delete') ||
30
+ params.name.includes(';') || params.name.includes('--')) {
31
+ return {
32
+ success: false,
33
+ message: 'Failed to create Animation Blueprint: Name contains invalid characters',
34
+ error: 'Name contains invalid characters'
35
+ };
36
+ }
37
+ // Check save path starts with /
38
+ if (params.savePath && !params.savePath.startsWith('/')) {
39
+ return {
40
+ success: false,
41
+ message: 'Failed to create Animation Blueprint: Path must start with /',
42
+ error: 'Path must start with /'
43
+ };
44
+ }
45
+ // Now validate and sanitize for actual use
46
+ const validation = validateAssetParams({
47
+ name: params.name,
48
+ savePath: params.savePath || '/Game/Animations'
49
+ });
50
+ if (!validation.valid) {
51
+ return {
52
+ success: false,
53
+ message: `Failed to create Animation Blueprint: ${validation.error}`,
54
+ error: validation.error
55
+ };
56
+ }
57
+ const sanitizedParams = validation.sanitized;
58
+ const path = sanitizedParams.savePath || '/Game/Animations';
59
+ // Add concurrency delay to prevent race conditions
60
+ await concurrencyDelay();
61
+ // Enhanced Python script with proper persistence and error detection
62
+ const pythonScript = `
63
+ import unreal
64
+ import time
65
+
66
+ # Helper function to ensure asset persistence
67
+ def ensure_asset_persistence(asset_path):
68
+ """Ensure asset is properly saved and registered"""
69
+ try:
70
+ # Load the asset to ensure it's in memory
71
+ asset = unreal.EditorAssetLibrary.load_asset(asset_path)
72
+ if not asset:
73
+ return False
74
+
75
+ # Save the asset
76
+ saved = unreal.EditorAssetLibrary.save_asset(asset_path, only_if_is_dirty=False)
77
+ if saved:
78
+ print(f"Asset saved: {asset_path}")
79
+
80
+ # Refresh the asset registry for the asset's directory only
81
+ try:
82
+ asset_dir = asset_path.rsplit('/', 1)[0]
83
+ unreal.AssetRegistryHelpers.get_asset_registry().scan_paths_synchronous([asset_dir], True)
84
+ except Exception as _reg_e:
85
+ pass
86
+
87
+ # Small delay to ensure filesystem sync
88
+ time.sleep(0.1)
89
+
90
+ return saved
91
+ except Exception as e:
92
+ print(f"Error ensuring persistence: {e}")
93
+ return False
94
+
95
+ # Stop PIE if it's running
96
+ try:
97
+ if unreal.EditorLevelLibrary.is_playing_editor():
98
+ print("Stopping Play In Editor mode...")
99
+ unreal.EditorLevelLibrary.editor_end_play()
100
+ # Small delay to ensure editor fully exits play mode
101
+ import time as _t
102
+ _t.sleep(0.5)
103
+ except Exception as _e:
104
+ # Try alternative check
105
+ try:
106
+ play_world = unreal.EditorLevelLibrary.get_editor_world()
107
+ if play_world and play_world.is_play_in_editor():
108
+ print("Stopping PIE via alternative method...")
109
+ unreal.EditorLevelLibrary.editor_end_play()
110
+ import time as _t2
111
+ _t2.sleep(0.5)
112
+ except:
113
+ pass # Continue if we can't check/stop play mode
114
+
115
+ # Main execution
116
+ success = False
117
+ error_msg = ""
118
+
119
+ # Log the attempt
120
+ print("Creating animation blueprint: ${sanitizedParams.name}")
121
+
122
+ asset_path = "${path}"
123
+ asset_name = "${sanitizedParams.name}"
124
+ full_path = f"{asset_path}/{asset_name}"
125
+
126
+ try:
127
+ # Check if already exists
128
+ if unreal.EditorAssetLibrary.does_asset_exist(full_path):
129
+ print(f"Asset already exists at {full_path}")
130
+ # Load and return existing
131
+ existing = unreal.EditorAssetLibrary.load_asset(full_path)
132
+ if existing:
133
+ print(f"Loaded existing AnimBlueprint: {full_path}")
134
+ success = True
135
+ else:
136
+ error_msg = f"Could not load existing asset at {full_path}"
137
+ print(f"Warning: {error_msg}")
138
+ else:
139
+ # Try to create new animation blueprint
140
+ factory = unreal.AnimBlueprintFactory()
141
+
142
+ # Try to load skeleton if provided
143
+ skeleton_path = "${params.skeletonPath}"
144
+ skeleton = None
145
+ skeleton_set = False
146
+
147
+ if skeleton_path and skeleton_path != "None":
148
+ if unreal.EditorAssetLibrary.does_asset_exist(skeleton_path):
149
+ skeleton = unreal.EditorAssetLibrary.load_asset(skeleton_path)
150
+ if skeleton and isinstance(skeleton, unreal.Skeleton):
151
+ # Different Unreal versions use different attribute names
152
+ try:
153
+ factory.target_skeleton = skeleton
154
+ skeleton_set = True
155
+ print(f"Using skeleton: {skeleton_path}")
156
+ except AttributeError:
157
+ try:
158
+ factory.skeleton = skeleton
159
+ skeleton_set = True
160
+ print(f"Using skeleton (alternate): {skeleton_path}")
161
+ except AttributeError:
162
+ # In some versions, the skeleton is set differently
163
+ try:
164
+ factory.set_editor_property('target_skeleton', skeleton)
165
+ skeleton_set = True
166
+ print(f"Using skeleton (property): {skeleton_path}")
167
+ except:
168
+ print(f"Warning: Could not set skeleton on factory")
169
+ else:
170
+ error_msg = f"Invalid skeleton at {skeleton_path}"
171
+ print(f"Warning: {error_msg}")
172
+ else:
173
+ print(f"Warning: Skeleton not found at {skeleton_path}, creating without skeleton")
174
+
175
+ # Create the asset
176
+ asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
177
+ new_asset = asset_tools.create_asset(
178
+ asset_name=asset_name,
179
+ package_path=asset_path,
180
+ asset_class=unreal.AnimBlueprint,
181
+ factory=factory
182
+ )
183
+
184
+ if new_asset:
185
+ print(f"Successfully created AnimBlueprint at {full_path}")
186
+
187
+ # Ensure persistence
188
+ if ensure_asset_persistence(full_path):
189
+ # Verify it was saved
190
+ if unreal.EditorAssetLibrary.does_asset_exist(full_path):
191
+ print(f"Verified asset exists after save: {full_path}")
192
+ success = True
193
+ else:
194
+ error_msg = f"Asset not found after save: {full_path}"
195
+ print(f"Warning: {error_msg}")
196
+ else:
197
+ error_msg = "Failed to persist asset"
198
+ print(f"Warning: {error_msg}")
199
+ else:
200
+ error_msg = f"Failed to create AnimBlueprint {asset_name}"
201
+ print(error_msg)
202
+
203
+ except Exception as e:
204
+ error_msg = str(e)
205
+ print(f"Error: {error_msg}")
206
+ import traceback
207
+ traceback.print_exc()
208
+
209
+ # Output result markers for parsing
210
+ if success:
211
+ print("SUCCESS")
212
+ else:
213
+ print(f"FAILED: {error_msg}")
214
+
215
+ print("DONE")
216
+ `;
217
+ // Execute Python and parse the output
218
+ try {
219
+ const response = await this.bridge.executePython(pythonScript);
220
+ // Parse the response to detect actual success or failure
221
+ const responseStr = typeof response === 'string' ? response : JSON.stringify(response);
222
+ // Check for explicit success/failure markers
223
+ if (responseStr.includes('SUCCESS')) {
224
+ return {
225
+ success: true,
226
+ message: `Animation Blueprint ${sanitizedParams.name} created successfully`,
227
+ path: `${path}/${sanitizedParams.name}`
228
+ };
229
+ }
230
+ else if (responseStr.includes('FAILED:')) {
231
+ // Extract error message after FAILED:
232
+ const failMatch = responseStr.match(/FAILED:\s*(.+)/);
233
+ const errorMsg = failMatch ? failMatch[1] : 'Unknown error';
234
+ return {
235
+ success: false,
236
+ message: `Failed to create Animation Blueprint: ${errorMsg}`,
237
+ error: errorMsg
238
+ };
239
+ }
240
+ else {
241
+ // If no explicit markers, check for other error indicators
242
+ if (responseStr.includes('Error:') || responseStr.includes('error') ||
243
+ responseStr.includes('failed') || responseStr.includes('Failed')) {
244
+ return {
245
+ success: false,
246
+ message: 'Failed to create Animation Blueprint',
247
+ error: responseStr
248
+ };
249
+ }
250
+ // Assume success if no errors detected
251
+ return {
252
+ success: true,
253
+ message: `Animation Blueprint ${sanitizedParams.name} processed`,
254
+ path: `${path}/${sanitizedParams.name}`
255
+ };
256
+ }
257
+ }
258
+ catch (error) {
259
+ return {
260
+ success: false,
261
+ message: 'Failed to create Animation Blueprint',
262
+ error: String(error)
263
+ };
264
+ }
265
+ }
266
+ catch (err) {
267
+ return { success: false, error: `Failed to create AnimBlueprint: ${err}` };
268
+ }
269
+ }
270
+ /**
271
+ * Add State Machine to Animation Blueprint
272
+ */
273
+ async addStateMachine(params) {
274
+ try {
275
+ // State machines are complex - we'll use console commands for basic setup
276
+ const commands = [
277
+ `AddAnimStateMachine ${params.blueprintPath} ${params.machineName}`
278
+ ];
279
+ // Add states
280
+ for (const state of params.states) {
281
+ commands.push(`AddAnimState ${params.blueprintPath} ${params.machineName} ${state.name} ${state.animation || ''}`);
282
+ if (state.isEntry) {
283
+ commands.push(`SetAnimStateEntry ${params.blueprintPath} ${params.machineName} ${state.name}`);
284
+ }
285
+ }
286
+ // Add transitions
287
+ if (params.transitions) {
288
+ for (const transition of params.transitions) {
289
+ commands.push(`AddAnimTransition ${params.blueprintPath} ${params.machineName} ${transition.sourceState} ${transition.targetState}`);
290
+ }
291
+ }
292
+ for (const cmd of commands) {
293
+ await this.bridge.executeConsoleCommand(cmd);
294
+ }
295
+ return {
296
+ success: true,
297
+ message: `State machine ${params.machineName} added to ${params.blueprintPath}`
298
+ };
299
+ }
300
+ catch (err) {
301
+ return { success: false, error: `Failed to add state machine: ${err}` };
302
+ }
303
+ }
304
+ /**
305
+ * Create Animation Montage
306
+ */
307
+ async createMontage(params) {
308
+ try {
309
+ const path = params.savePath || '/Game/Animations/Montages';
310
+ const commands = [
311
+ `CreateAsset AnimMontage ${params.name} ${path}`,
312
+ `SetMontageAnimation ${params.name} ${params.animationSequence}`
313
+ ];
314
+ // Add sections
315
+ if (params.sections) {
316
+ for (const section of params.sections) {
317
+ commands.push(`AddMontageSection ${params.name} ${section.name} ${section.startTime} ${section.endTime}`);
318
+ }
319
+ }
320
+ // Add notifies
321
+ if (params.notifies) {
322
+ for (const notify of params.notifies) {
323
+ commands.push(`AddMontageNotify ${params.name} ${notify.name} ${notify.time}`);
324
+ }
325
+ }
326
+ for (const cmd of commands) {
327
+ await this.bridge.executeConsoleCommand(cmd);
328
+ }
329
+ return {
330
+ success: true,
331
+ message: `Animation Montage ${params.name} created`,
332
+ path: `${path}/${params.name}`
333
+ };
334
+ }
335
+ catch (err) {
336
+ return { success: false, error: `Failed to create montage: ${err}` };
337
+ }
338
+ }
339
+ /**
340
+ * Create Blend Space
341
+ */
342
+ async createBlendSpace(params) {
343
+ try {
344
+ const path = params.savePath || '/Game/Animations/BlendSpaces';
345
+ const blendSpaceType = params.dimensions === 1 ? 'BlendSpace1D' : 'BlendSpace';
346
+ // These commands don't exist, return a message about limitations
347
+ const commands = [
348
+ `echo Creating ${blendSpaceType} ${params.name} at ${path}`
349
+ ];
350
+ // Configure axes
351
+ if (params.horizontalAxis) {
352
+ commands.push(`SetBlendSpaceAxis ${params.name} Horizontal ${params.horizontalAxis.name} ${params.horizontalAxis.minValue} ${params.horizontalAxis.maxValue}`);
353
+ }
354
+ if (params.dimensions === 2 && params.verticalAxis) {
355
+ commands.push(`SetBlendSpaceAxis ${params.name} Vertical ${params.verticalAxis.name} ${params.verticalAxis.minValue} ${params.verticalAxis.maxValue}`);
356
+ }
357
+ // Add sample animations
358
+ if (params.samples) {
359
+ for (const sample of params.samples) {
360
+ const coords = params.dimensions === 1 ? `${sample.x}` : `${sample.x} ${sample.y || 0}`;
361
+ commands.push(`AddBlendSpaceSample ${params.name} ${sample.animation} ${coords}`);
362
+ }
363
+ }
364
+ for (const cmd of commands) {
365
+ await this.bridge.executeConsoleCommand(cmd);
366
+ }
367
+ return {
368
+ success: true,
369
+ message: `Blend Space ${params.name} created`,
370
+ path: `${path}/${params.name}`
371
+ };
372
+ }
373
+ catch (err) {
374
+ return { success: false, error: `Failed to create blend space: ${err}` };
375
+ }
376
+ }
377
+ /**
378
+ * Setup Control Rig
379
+ */
380
+ async setupControlRig(params) {
381
+ try {
382
+ const path = params.savePath || '/Game/Animations';
383
+ // Validate path length (Unreal has a 260 character limit)
384
+ const fullPath = `${path}/${params.name}`;
385
+ if (fullPath.length > 260) {
386
+ return {
387
+ success: false,
388
+ message: `Failed: Path too long (${fullPath.length} characters)`,
389
+ error: 'Unreal Engine paths must be less than 260 characters'
390
+ };
391
+ }
392
+ const commands = [
393
+ `CreateAsset ControlRig ${params.name} ${path}`,
394
+ `SetControlRigSkeleton ${params.name} ${params.skeletonPath}`
395
+ ];
396
+ // Add controls
397
+ if (params.controls) {
398
+ for (const control of params.controls) {
399
+ commands.push(`AddControlRigControl ${params.name} ${control.name} ${control.type} ${control.bone || ''}`);
400
+ if (control.defaultValue !== undefined) {
401
+ commands.push(`SetControlRigDefault ${params.name} ${control.name} ${JSON.stringify(control.defaultValue)}`);
402
+ }
403
+ }
404
+ }
405
+ for (const cmd of commands) {
406
+ await this.bridge.executeConsoleCommand(cmd);
407
+ }
408
+ return {
409
+ success: true,
410
+ message: `Control Rig ${params.name} created`,
411
+ path: `${path}/${params.name}`
412
+ };
413
+ }
414
+ catch (err) {
415
+ return { success: false, error: `Failed to setup control rig: ${err}` };
416
+ }
417
+ }
418
+ /**
419
+ * Create Level Sequence (for cinematics)
420
+ */
421
+ async createLevelSequence(params) {
422
+ try {
423
+ const path = params.savePath || '/Game/Cinematics';
424
+ const commands = [
425
+ `CreateAsset LevelSequence ${params.name} ${path}`,
426
+ `SetSequenceFrameRate ${params.name} ${params.frameRate || 30}`,
427
+ `SetSequenceDuration ${params.name} ${params.duration || 5}`
428
+ ];
429
+ // Add tracks
430
+ if (params.tracks) {
431
+ for (const track of params.tracks) {
432
+ commands.push(`AddSequenceTrack ${params.name} ${track.actorName} ${track.trackType}`);
433
+ // Add keyframes
434
+ if (track.keyframes) {
435
+ for (const keyframe of track.keyframes) {
436
+ commands.push(`AddSequenceKey ${params.name} ${track.actorName} ${track.trackType} ${keyframe.time} ${JSON.stringify(keyframe.value)}`);
437
+ }
438
+ }
439
+ }
440
+ }
441
+ for (const cmd of commands) {
442
+ await this.bridge.executeConsoleCommand(cmd);
443
+ }
444
+ return {
445
+ success: true,
446
+ message: `Level Sequence ${params.name} created`,
447
+ path: `${path}/${params.name}`
448
+ };
449
+ }
450
+ catch (err) {
451
+ return { success: false, error: `Failed to create level sequence: ${err}` };
452
+ }
453
+ }
454
+ /**
455
+ * Play Animation on Actor
456
+ */
457
+ async playAnimation(params) {
458
+ try {
459
+ // Implement via Python for UE 5.x compatibility instead of non-existent console commands
460
+ const playRate = params.playRate ?? 1.0;
461
+ const loopFlag = params.loop ? 'True' : 'False';
462
+ const python = `
463
+ import unreal
464
+ import json
465
+
466
+ result = {"success": False, "message": ""}
467
+
468
+ try:
469
+ actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
470
+ actors = actor_subsystem.get_all_level_actors()
471
+ target = None
472
+ search = "${params.actorName}"
473
+ for a in actors:
474
+ if not a:
475
+ continue
476
+ name = a.get_name()
477
+ label = a.get_actor_label()
478
+ if (search.lower() == name.lower()) or (search.lower() == label.lower()) or (search.lower() in label.lower()):
479
+ target = a
480
+ break
481
+
482
+ if not target:
483
+ result["message"] = f"Actor not found: {search}"
484
+ else:
485
+ # Try to get a SkeletalMeshComponent from the actor
486
+ sk = target.get_component_by_class(unreal.SkeletalMeshComponent)
487
+ if not sk:
488
+ # Try commonly named properties (e.g., Character mesh)
489
+ try:
490
+ sk = target.get_editor_property('mesh')
491
+ except Exception:
492
+ sk = None
493
+ if not sk:
494
+ result["message"] = "No SkeletalMeshComponent found on actor"
495
+ else:
496
+ anim_type = "${params.animationType}"
497
+ asset_path = r"${params.animationPath}"
498
+ if not unreal.EditorAssetLibrary.does_asset_exist(asset_path):
499
+ result["message"] = f"Animation asset not found: {asset_path}"
500
+ else:
501
+ asset = unreal.EditorAssetLibrary.load_asset(asset_path)
502
+ if anim_type == 'Montage':
503
+ # Use AnimInstance montage_play
504
+ inst = sk.get_anim_instance()
505
+ if not inst:
506
+ result["message"] = "AnimInstance not found on SkeletalMeshComponent"
507
+ else:
508
+ try:
509
+ # montage_play(montage, play_rate, return_value_type, time_to_start_montage_at, stop_all_montages)
510
+ inst.montage_play(asset, ${playRate})
511
+ result["success"] = True
512
+ result["message"] = f"Montage playing on {search}"
513
+ except Exception as e:
514
+ result["message"] = f"Failed to play montage: {e}"
515
+ elif anim_type == 'Sequence':
516
+ try:
517
+ sk.play_animation(asset, ${loopFlag})
518
+ # Adjust rate if supported via play rate on AnimInstance
519
+ try:
520
+ inst = sk.get_anim_instance()
521
+ if inst:
522
+ # Not all paths support direct play rate control here; best effort only
523
+ pass
524
+ except Exception:
525
+ pass
526
+ result["success"] = True
527
+ result["message"] = f"Sequence playing on {search}"
528
+ except Exception as e:
529
+ result["message"] = f"Failed to play sequence: {e}"
530
+ else:
531
+ result["message"] = "BlendSpace playback requires an Animation Blueprint; not supported via direct play."
532
+ except Exception as e:
533
+ result["message"] = f"Error: {e}"
534
+
535
+ print("RESULT:" + json.dumps(result))
536
+ `.trim();
537
+ const resp = await this.bridge.executePython(python);
538
+ // Parse Python result
539
+ let output = '';
540
+ if (resp && typeof resp === 'object' && Array.isArray(resp.LogOutput)) {
541
+ output = resp.LogOutput.map((l) => l.Output || '').join('');
542
+ }
543
+ else if (typeof resp === 'string') {
544
+ output = resp;
545
+ }
546
+ else {
547
+ output = JSON.stringify(resp);
548
+ }
549
+ const m = output.match(/RESULT:({.*})/);
550
+ if (m) {
551
+ try {
552
+ const parsed = JSON.parse(m[1]);
553
+ return parsed.success ? { success: true, message: parsed.message } : { success: false, error: parsed.message };
554
+ }
555
+ catch { }
556
+ }
557
+ return { success: true, message: `Animation ${params.animationType} processed for ${params.actorName}` };
558
+ }
559
+ catch (err) {
560
+ return { success: false, error: `Failed to play animation: ${err}` };
561
+ }
562
+ }
563
+ /**
564
+ * Helper function to execute console commands
565
+ */
566
+ async _executeCommand(command) {
567
+ return this.bridge.httpCall('/remote/object/call', 'PUT', {
568
+ objectPath: '/Script/Engine.Default__KismetSystemLibrary',
569
+ functionName: 'ExecuteConsoleCommand',
570
+ parameters: {
571
+ WorldContextObject: null,
572
+ Command: command,
573
+ SpecificPlayer: null
574
+ },
575
+ generateTransaction: false
576
+ });
577
+ }
578
+ }
579
+ //# sourceMappingURL=animation.js.map
@@ -0,0 +1,21 @@
1
+ import { UnrealBridge } from '../unreal-bridge.js';
2
+ export declare class AssetTools {
3
+ private bridge;
4
+ constructor(bridge: UnrealBridge);
5
+ importAsset(sourcePath: string, destinationPath: string): Promise<{
6
+ success: boolean;
7
+ message: string;
8
+ paths: any;
9
+ error?: undefined;
10
+ } | {
11
+ error: string;
12
+ success?: undefined;
13
+ message?: undefined;
14
+ paths?: undefined;
15
+ }>;
16
+ duplicateAsset(sourcePath: string, destinationPath: string): Promise<any>;
17
+ deleteAsset(assetPath: string): Promise<any>;
18
+ saveAsset(assetPath: string): Promise<any>;
19
+ private createTestFBX;
20
+ }
21
+ //# sourceMappingURL=assets.d.ts.map