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,110 @@
|
|
|
1
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
export interface RCPreset {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
path: string;
|
|
6
|
+
description?: string;
|
|
7
|
+
exposedEntities?: RCExposedEntity[];
|
|
8
|
+
}
|
|
9
|
+
export interface RCExposedEntity {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
type: 'property' | 'function' | 'actor';
|
|
13
|
+
objectPath?: string;
|
|
14
|
+
propertyName?: string;
|
|
15
|
+
functionName?: string;
|
|
16
|
+
metadata?: Record<string, any>;
|
|
17
|
+
}
|
|
18
|
+
export declare class RcTools {
|
|
19
|
+
private bridge;
|
|
20
|
+
private log;
|
|
21
|
+
private presetCache;
|
|
22
|
+
private retryAttempts;
|
|
23
|
+
private retryDelay;
|
|
24
|
+
constructor(bridge: UnrealBridge);
|
|
25
|
+
/**
|
|
26
|
+
* Execute with retry logic for transient failures
|
|
27
|
+
*/
|
|
28
|
+
private executeWithRetry;
|
|
29
|
+
/**
|
|
30
|
+
* Parse Python execution result with better error handling
|
|
31
|
+
*/
|
|
32
|
+
private parsePythonResult;
|
|
33
|
+
createPreset(params: {
|
|
34
|
+
name: string;
|
|
35
|
+
path?: string;
|
|
36
|
+
}): Promise<any>;
|
|
37
|
+
exposeActor(params: {
|
|
38
|
+
presetPath: string;
|
|
39
|
+
actorName: string;
|
|
40
|
+
}): Promise<any>;
|
|
41
|
+
exposeProperty(params: {
|
|
42
|
+
presetPath: string;
|
|
43
|
+
objectPath: string;
|
|
44
|
+
propertyName: string;
|
|
45
|
+
}): Promise<any>;
|
|
46
|
+
listFields(params: {
|
|
47
|
+
presetPath: string;
|
|
48
|
+
}): Promise<any>;
|
|
49
|
+
setProperty(params: {
|
|
50
|
+
objectPath: string;
|
|
51
|
+
propertyName: string;
|
|
52
|
+
value: any;
|
|
53
|
+
}): Promise<{
|
|
54
|
+
success: boolean;
|
|
55
|
+
result: any;
|
|
56
|
+
error?: undefined;
|
|
57
|
+
} | {
|
|
58
|
+
success: boolean;
|
|
59
|
+
error: any;
|
|
60
|
+
result?: undefined;
|
|
61
|
+
}>;
|
|
62
|
+
getProperty(params: {
|
|
63
|
+
objectPath: string;
|
|
64
|
+
propertyName: string;
|
|
65
|
+
}): Promise<{
|
|
66
|
+
success: boolean;
|
|
67
|
+
value: any;
|
|
68
|
+
error?: undefined;
|
|
69
|
+
} | {
|
|
70
|
+
success: boolean;
|
|
71
|
+
error: any;
|
|
72
|
+
value?: undefined;
|
|
73
|
+
}>;
|
|
74
|
+
/**
|
|
75
|
+
* List all available Remote Control presets
|
|
76
|
+
*/
|
|
77
|
+
listPresets(): Promise<{
|
|
78
|
+
success: boolean;
|
|
79
|
+
presets?: RCPreset[];
|
|
80
|
+
error?: string;
|
|
81
|
+
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Delete a Remote Control preset
|
|
84
|
+
*/
|
|
85
|
+
deletePreset(presetId: string): Promise<{
|
|
86
|
+
success: boolean;
|
|
87
|
+
error?: string;
|
|
88
|
+
}>;
|
|
89
|
+
/**
|
|
90
|
+
* Call an exposed function through Remote Control
|
|
91
|
+
*/
|
|
92
|
+
callFunction(params: {
|
|
93
|
+
presetPath: string;
|
|
94
|
+
functionName: string;
|
|
95
|
+
parameters?: Record<string, any>;
|
|
96
|
+
}): Promise<{
|
|
97
|
+
success: boolean;
|
|
98
|
+
result?: any;
|
|
99
|
+
error?: string;
|
|
100
|
+
}>;
|
|
101
|
+
/**
|
|
102
|
+
* Validate connection to Remote Control
|
|
103
|
+
*/
|
|
104
|
+
validateConnection(): Promise<boolean>;
|
|
105
|
+
/**
|
|
106
|
+
* Clear preset cache
|
|
107
|
+
*/
|
|
108
|
+
clearCache(): void;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=rc.d.ts.map
|
package/dist/tools/rc.js
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { Logger } from '../utils/logger.js';
|
|
2
|
+
export class RcTools {
|
|
3
|
+
bridge;
|
|
4
|
+
log = new Logger('RcTools');
|
|
5
|
+
presetCache = new Map();
|
|
6
|
+
retryAttempts = 3;
|
|
7
|
+
retryDelay = 1000;
|
|
8
|
+
constructor(bridge) {
|
|
9
|
+
this.bridge = bridge;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Execute with retry logic for transient failures
|
|
13
|
+
*/
|
|
14
|
+
async executeWithRetry(operation, operationName) {
|
|
15
|
+
let lastError;
|
|
16
|
+
for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
|
|
17
|
+
try {
|
|
18
|
+
return await operation();
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
lastError = error;
|
|
22
|
+
this.log.warn(`${operationName} attempt ${attempt} failed: ${error.message || error}`);
|
|
23
|
+
if (attempt < this.retryAttempts) {
|
|
24
|
+
await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
throw lastError;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Parse Python execution result with better error handling
|
|
32
|
+
*/
|
|
33
|
+
parsePythonResult(resp, operationName) {
|
|
34
|
+
let out = '';
|
|
35
|
+
if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
|
|
36
|
+
out = resp.LogOutput.map((l) => l.Output || '').join('');
|
|
37
|
+
}
|
|
38
|
+
else if (typeof resp === 'string') {
|
|
39
|
+
out = resp;
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
out = JSON.stringify(resp);
|
|
43
|
+
}
|
|
44
|
+
const m = out.match(/RESULT:({.*})/);
|
|
45
|
+
if (m) {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(m[1]);
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
this.log.error(`Failed to parse ${operationName} result: ${e}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Check for common error patterns
|
|
54
|
+
if (out.includes('ModuleNotFoundError')) {
|
|
55
|
+
return { success: false, error: 'Remote Control module not available. Ensure Remote Control plugin is enabled.' };
|
|
56
|
+
}
|
|
57
|
+
if (out.includes('AttributeError')) {
|
|
58
|
+
return { success: false, error: 'Remote Control API method not found. Check Unreal Engine version compatibility.' };
|
|
59
|
+
}
|
|
60
|
+
return { success: false, error: `${operationName} did not return a valid result: ${out.substring(0, 200)}` };
|
|
61
|
+
}
|
|
62
|
+
// Create a Remote Control Preset asset
|
|
63
|
+
async createPreset(params) {
|
|
64
|
+
const name = params.name?.trim();
|
|
65
|
+
const path = (params.path || '/Game/RCPresets').replace(/\/$/, '');
|
|
66
|
+
if (!name)
|
|
67
|
+
return { success: false, error: 'Preset name is required' };
|
|
68
|
+
const python = `
|
|
69
|
+
import unreal, json
|
|
70
|
+
import time
|
|
71
|
+
name = r"${name}"
|
|
72
|
+
base_path = r"${path}"
|
|
73
|
+
full_path = f"{base_path}/{name}"
|
|
74
|
+
try:
|
|
75
|
+
# Check if asset already exists
|
|
76
|
+
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
77
|
+
# If it exists, add a timestamp suffix to create a unique name
|
|
78
|
+
timestamp = str(int(time.time() * 1000))
|
|
79
|
+
unique_name = f"{name}_{timestamp}"
|
|
80
|
+
full_path = f"{base_path}/{unique_name}"
|
|
81
|
+
# Check again to ensure uniqueness
|
|
82
|
+
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
83
|
+
print('RESULT:' + json.dumps({'success': True, 'presetPath': full_path, 'existing': True}))
|
|
84
|
+
else:
|
|
85
|
+
# Continue with creation using unique name
|
|
86
|
+
name = unique_name
|
|
87
|
+
# Now create the preset if it doesn't exist
|
|
88
|
+
if not unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
89
|
+
# Ensure directory exists
|
|
90
|
+
if not unreal.EditorAssetLibrary.does_directory_exist(base_path):
|
|
91
|
+
unreal.EditorAssetLibrary.make_directory(base_path)
|
|
92
|
+
|
|
93
|
+
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
94
|
+
factory = None
|
|
95
|
+
try:
|
|
96
|
+
factory = unreal.RemoteControlPresetFactory()
|
|
97
|
+
except Exception:
|
|
98
|
+
# Factory might not be available in older versions
|
|
99
|
+
factory = None
|
|
100
|
+
|
|
101
|
+
asset = None
|
|
102
|
+
try:
|
|
103
|
+
if factory is not None:
|
|
104
|
+
asset = asset_tools.create_asset(asset_name=name, package_path=base_path, asset_class=unreal.RemoteControlPreset, factory=factory)
|
|
105
|
+
else:
|
|
106
|
+
# Try alternative creation method
|
|
107
|
+
asset = asset_tools.create_asset(asset_name=name, package_path=base_path, asset_class=unreal.RemoteControlPreset, factory=None)
|
|
108
|
+
except Exception as e:
|
|
109
|
+
# If creation fails, try to provide helpful error
|
|
110
|
+
if "RemoteControlPreset" in str(e):
|
|
111
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'RemoteControlPreset class not available. Ensure Remote Control plugin is enabled.'}))
|
|
112
|
+
else:
|
|
113
|
+
print('RESULT:' + json.dumps({'success': False, 'error': f'Create asset failed: {str(e)}'}))
|
|
114
|
+
raise SystemExit(0)
|
|
115
|
+
|
|
116
|
+
if asset:
|
|
117
|
+
# Save with suppressed validation warnings
|
|
118
|
+
try:
|
|
119
|
+
unreal.EditorAssetLibrary.save_asset(full_path, only_if_is_dirty=False)
|
|
120
|
+
print('RESULT:' + json.dumps({'success': True, 'presetPath': full_path}))
|
|
121
|
+
except Exception as save_err:
|
|
122
|
+
# Asset was created but save had warnings - still consider success
|
|
123
|
+
print('RESULT:' + json.dumps({'success': True, 'presetPath': full_path, 'warning': 'Asset created with validation warnings'}))
|
|
124
|
+
else:
|
|
125
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Preset creation returned None'}))
|
|
126
|
+
except Exception as e:
|
|
127
|
+
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
128
|
+
`.trim();
|
|
129
|
+
const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'createPreset');
|
|
130
|
+
const result = this.parsePythonResult(resp, 'createPreset');
|
|
131
|
+
// Cache the preset if successful
|
|
132
|
+
if (result.success && result.presetPath) {
|
|
133
|
+
const preset = {
|
|
134
|
+
id: result.presetPath,
|
|
135
|
+
name: name,
|
|
136
|
+
path: result.presetPath,
|
|
137
|
+
description: `Created at ${new Date().toISOString()}`
|
|
138
|
+
};
|
|
139
|
+
this.presetCache.set(preset.id, preset);
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
// Expose an actor by label/name into a preset
|
|
144
|
+
async exposeActor(params) {
|
|
145
|
+
const python = `\nimport unreal, json\npreset_path = r"${params.presetPath}"\nactor_name = r"${params.actorName}"\ntry:\n preset = unreal.EditorAssetLibrary.load_asset(preset_path)\n if not preset:\n print('RESULT:' + json.dumps({'success': False, 'error': 'Preset not found'}))\n else:\n actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)\n target = None\n for a in actor_sub.get_all_level_actors():\n if not a: continue\n try:\n if a.get_actor_label() == actor_name or a.get_name() == actor_name:\n target = a; break\n except Exception: pass\n if not target:\n print('RESULT:' + json.dumps({'success': False, 'error': 'Actor not found'}))\n else:\n try:\n unreal.RemoteControlFunctionLibrary.expose_actor(preset, target, None)\n unreal.EditorAssetLibrary.save_asset(preset_path)\n print('RESULT:' + json.dumps({'success': True}))\n except Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\nexcept Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\n`.trim();
|
|
146
|
+
const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'exposeActor');
|
|
147
|
+
const result = this.parsePythonResult(resp, 'exposeActor');
|
|
148
|
+
// Clear cache for this preset to force refresh
|
|
149
|
+
if (result.success) {
|
|
150
|
+
this.presetCache.delete(params.presetPath);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
// Expose a property on an object into a preset
|
|
155
|
+
async exposeProperty(params) {
|
|
156
|
+
const python = `\nimport unreal, json\npreset_path = r"${params.presetPath}"\nobj_path = r"${params.objectPath}"\nprop_name = r"${params.propertyName}"\ntry:\n preset = unreal.EditorAssetLibrary.load_asset(preset_path)\n obj = unreal.load_object(None, obj_path)\n if not preset or not obj:\n print('RESULT:' + json.dumps({'success': False, 'error': 'Preset or object not found'}))\n else:\n try:\n unreal.RemoteControlFunctionLibrary.expose_property(preset, obj, prop_name, None)\n unreal.EditorAssetLibrary.save_asset(preset_path)\n print('RESULT:' + json.dumps({'success': True}))\n except Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\nexcept Exception as e:\n print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))\n`.trim();
|
|
157
|
+
const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'exposeProperty');
|
|
158
|
+
const result = this.parsePythonResult(resp, 'exposeProperty');
|
|
159
|
+
// Clear cache for this preset to force refresh
|
|
160
|
+
if (result.success) {
|
|
161
|
+
this.presetCache.delete(params.presetPath);
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
// List exposed fields (best-effort)
|
|
166
|
+
async listFields(params) {
|
|
167
|
+
const python = `
|
|
168
|
+
import unreal, json
|
|
169
|
+
preset_path = r"${params.presetPath}"
|
|
170
|
+
try:
|
|
171
|
+
# First check if the asset exists
|
|
172
|
+
if not preset_path or not preset_path.startswith('/Game/'):
|
|
173
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Invalid preset path. Must start with /Game/'}))
|
|
174
|
+
elif not unreal.EditorAssetLibrary.does_asset_exist(preset_path):
|
|
175
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Preset not found at path: ' + preset_path}))
|
|
176
|
+
else:
|
|
177
|
+
preset = unreal.EditorAssetLibrary.load_asset(preset_path)
|
|
178
|
+
if not preset:
|
|
179
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to load preset'}))
|
|
180
|
+
else:
|
|
181
|
+
fields = []
|
|
182
|
+
try:
|
|
183
|
+
# Try to get exposed entities
|
|
184
|
+
if hasattr(preset, 'get_exposed_entities'):
|
|
185
|
+
for entity in preset.get_exposed_entities():
|
|
186
|
+
try:
|
|
187
|
+
fields.append({
|
|
188
|
+
'id': str(entity.id) if hasattr(entity, 'id') else '',
|
|
189
|
+
'label': str(entity.label) if hasattr(entity, 'label') else '',
|
|
190
|
+
'path': str(getattr(entity, 'path', ''))
|
|
191
|
+
})
|
|
192
|
+
except Exception:
|
|
193
|
+
pass
|
|
194
|
+
except Exception as e:
|
|
195
|
+
# Method might not exist or be accessible
|
|
196
|
+
pass
|
|
197
|
+
print('RESULT:' + json.dumps({'success': True, 'fields': fields}))
|
|
198
|
+
except Exception as e:
|
|
199
|
+
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
200
|
+
`.trim();
|
|
201
|
+
const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'listFields');
|
|
202
|
+
return this.parsePythonResult(resp, 'listFields');
|
|
203
|
+
}
|
|
204
|
+
// Set a property value via Remote Control property endpoint
|
|
205
|
+
async setProperty(params) {
|
|
206
|
+
return this.executeWithRetry(async () => {
|
|
207
|
+
try {
|
|
208
|
+
// Validate value type and convert if needed
|
|
209
|
+
let processedValue = params.value;
|
|
210
|
+
// Handle special types
|
|
211
|
+
if (typeof params.value === 'object' && params.value !== null) {
|
|
212
|
+
// Check if it's a vector/rotator/transform
|
|
213
|
+
if ('x' in params.value || 'X' in params.value) {
|
|
214
|
+
processedValue = {
|
|
215
|
+
X: params.value.x || params.value.X || 0,
|
|
216
|
+
Y: params.value.y || params.value.Y || 0,
|
|
217
|
+
Z: params.value.z || params.value.Z || 0
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const res = await this.bridge.httpCall('/remote/object/property', 'PUT', {
|
|
222
|
+
objectPath: params.objectPath,
|
|
223
|
+
propertyName: params.propertyName,
|
|
224
|
+
propertyValue: processedValue
|
|
225
|
+
});
|
|
226
|
+
return { success: true, result: res };
|
|
227
|
+
}
|
|
228
|
+
catch (err) {
|
|
229
|
+
// Check for specific error types
|
|
230
|
+
const errorMsg = err?.message || String(err);
|
|
231
|
+
if (errorMsg.includes('404')) {
|
|
232
|
+
return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
|
|
233
|
+
}
|
|
234
|
+
if (errorMsg.includes('400')) {
|
|
235
|
+
return { success: false, error: `Invalid value type for property '${params.propertyName}'` };
|
|
236
|
+
}
|
|
237
|
+
return { success: false, error: errorMsg };
|
|
238
|
+
}
|
|
239
|
+
}, 'setProperty');
|
|
240
|
+
}
|
|
241
|
+
// Get a property value via Remote Control property endpoint
|
|
242
|
+
async getProperty(params) {
|
|
243
|
+
return this.executeWithRetry(async () => {
|
|
244
|
+
try {
|
|
245
|
+
const res = await this.bridge.httpCall('/remote/object/property', 'GET', {
|
|
246
|
+
objectPath: params.objectPath,
|
|
247
|
+
propertyName: params.propertyName
|
|
248
|
+
});
|
|
249
|
+
return { success: true, value: res };
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
const errorMsg = err?.message || String(err);
|
|
253
|
+
if (errorMsg.includes('404')) {
|
|
254
|
+
return { success: false, error: `Property '${params.propertyName}' not found on object '${params.objectPath}'` };
|
|
255
|
+
}
|
|
256
|
+
return { success: false, error: errorMsg };
|
|
257
|
+
}
|
|
258
|
+
}, 'getProperty');
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* List all available Remote Control presets
|
|
262
|
+
*/
|
|
263
|
+
async listPresets() {
|
|
264
|
+
const python = `
|
|
265
|
+
import unreal, json
|
|
266
|
+
try:
|
|
267
|
+
presets = []
|
|
268
|
+
# Try to list assets in common RC preset locations
|
|
269
|
+
for path in ["/Game/RCPresets", "/Game/RemoteControl", "/Game"]:
|
|
270
|
+
try:
|
|
271
|
+
assets = unreal.EditorAssetLibrary.list_assets(path, recursive=True)
|
|
272
|
+
for asset in assets:
|
|
273
|
+
if "RemoteControlPreset" in asset:
|
|
274
|
+
try:
|
|
275
|
+
preset = unreal.EditorAssetLibrary.load_asset(asset)
|
|
276
|
+
if preset:
|
|
277
|
+
presets.append({
|
|
278
|
+
"id": asset,
|
|
279
|
+
"name": preset.get_name(),
|
|
280
|
+
"path": asset,
|
|
281
|
+
"description": getattr(preset, 'description', '')
|
|
282
|
+
})
|
|
283
|
+
except Exception:
|
|
284
|
+
pass
|
|
285
|
+
except Exception:
|
|
286
|
+
pass
|
|
287
|
+
print('RESULT:' + json.dumps({'success': True, 'presets': presets}))
|
|
288
|
+
except Exception as e:
|
|
289
|
+
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
290
|
+
`.trim();
|
|
291
|
+
const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'listPresets');
|
|
292
|
+
const result = this.parsePythonResult(resp, 'listPresets');
|
|
293
|
+
// Update cache
|
|
294
|
+
if (result.success && result.presets) {
|
|
295
|
+
result.presets.forEach((p) => {
|
|
296
|
+
this.presetCache.set(p.id, p);
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
return result;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Delete a Remote Control preset
|
|
303
|
+
*/
|
|
304
|
+
async deletePreset(presetId) {
|
|
305
|
+
const python = `
|
|
306
|
+
import unreal, json
|
|
307
|
+
preset_id = r"${presetId}"
|
|
308
|
+
try:
|
|
309
|
+
if unreal.EditorAssetLibrary.does_asset_exist(preset_id):
|
|
310
|
+
success = unreal.EditorAssetLibrary.delete_asset(preset_id)
|
|
311
|
+
if success:
|
|
312
|
+
print('RESULT:' + json.dumps({'success': True}))
|
|
313
|
+
else:
|
|
314
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to delete preset'}))
|
|
315
|
+
else:
|
|
316
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Preset not found'}))
|
|
317
|
+
except Exception as e:
|
|
318
|
+
print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
|
|
319
|
+
`.trim();
|
|
320
|
+
const resp = await this.executeWithRetry(() => this.bridge.executePython(python), 'deletePreset');
|
|
321
|
+
const result = this.parsePythonResult(resp, 'deletePreset');
|
|
322
|
+
// Remove from cache if successful
|
|
323
|
+
if (result.success) {
|
|
324
|
+
this.presetCache.delete(presetId);
|
|
325
|
+
}
|
|
326
|
+
return result;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Call an exposed function through Remote Control
|
|
330
|
+
*/
|
|
331
|
+
async callFunction(params) {
|
|
332
|
+
try {
|
|
333
|
+
const res = await this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
334
|
+
objectPath: params.presetPath,
|
|
335
|
+
functionName: params.functionName,
|
|
336
|
+
parameters: params.parameters || {}
|
|
337
|
+
});
|
|
338
|
+
return { success: true, result: res };
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
return { success: false, error: String(err?.message || err) };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Validate connection to Remote Control
|
|
346
|
+
*/
|
|
347
|
+
async validateConnection() {
|
|
348
|
+
try {
|
|
349
|
+
await this.bridge.httpCall('/remote/info', 'GET', {});
|
|
350
|
+
return true;
|
|
351
|
+
}
|
|
352
|
+
catch {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Clear preset cache
|
|
358
|
+
*/
|
|
359
|
+
clearCache() {
|
|
360
|
+
this.presetCache.clear();
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
//# sourceMappingURL=rc.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
export interface LevelSequence {
|
|
3
|
+
path: string;
|
|
4
|
+
name: string;
|
|
5
|
+
duration?: number;
|
|
6
|
+
frameRate?: number;
|
|
7
|
+
bindings?: SequenceBinding[];
|
|
8
|
+
}
|
|
9
|
+
export interface SequenceBinding {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
type: 'actor' | 'camera' | 'spawnable';
|
|
13
|
+
tracks?: SequenceTrack[];
|
|
14
|
+
}
|
|
15
|
+
export interface SequenceTrack {
|
|
16
|
+
name: string;
|
|
17
|
+
type: string;
|
|
18
|
+
sections?: any[];
|
|
19
|
+
}
|
|
20
|
+
export declare class SequenceTools {
|
|
21
|
+
private bridge;
|
|
22
|
+
private log;
|
|
23
|
+
private sequenceCache;
|
|
24
|
+
private retryAttempts;
|
|
25
|
+
private retryDelay;
|
|
26
|
+
constructor(bridge: UnrealBridge);
|
|
27
|
+
/**
|
|
28
|
+
* Execute with retry logic for transient failures
|
|
29
|
+
*/
|
|
30
|
+
private executeWithRetry;
|
|
31
|
+
/**
|
|
32
|
+
* Parse Python execution result with better error handling
|
|
33
|
+
*/
|
|
34
|
+
private parsePythonResult;
|
|
35
|
+
create(params: {
|
|
36
|
+
name: string;
|
|
37
|
+
path?: string;
|
|
38
|
+
}): Promise<any>;
|
|
39
|
+
open(params: {
|
|
40
|
+
path: string;
|
|
41
|
+
}): Promise<any>;
|
|
42
|
+
addCamera(params: {
|
|
43
|
+
spawnable?: boolean;
|
|
44
|
+
}): Promise<any>;
|
|
45
|
+
addActor(params: {
|
|
46
|
+
actorName: string;
|
|
47
|
+
createBinding?: boolean;
|
|
48
|
+
}): Promise<any>;
|
|
49
|
+
/**
|
|
50
|
+
* Play the current level sequence
|
|
51
|
+
*/
|
|
52
|
+
play(params?: {
|
|
53
|
+
startTime?: number;
|
|
54
|
+
loopMode?: 'once' | 'loop' | 'pingpong';
|
|
55
|
+
}): Promise<any>;
|
|
56
|
+
/**
|
|
57
|
+
* Pause the current level sequence
|
|
58
|
+
*/
|
|
59
|
+
pause(): Promise<any>;
|
|
60
|
+
/**
|
|
61
|
+
* Stop/close the current level sequence
|
|
62
|
+
*/
|
|
63
|
+
stop(): Promise<any>;
|
|
64
|
+
/**
|
|
65
|
+
* Set sequence properties including frame rate and length
|
|
66
|
+
*/
|
|
67
|
+
setSequenceProperties(params: {
|
|
68
|
+
path?: string;
|
|
69
|
+
frameRate?: number;
|
|
70
|
+
lengthInFrames?: number;
|
|
71
|
+
playbackStart?: number;
|
|
72
|
+
playbackEnd?: number;
|
|
73
|
+
}): Promise<any>;
|
|
74
|
+
/**
|
|
75
|
+
* Get sequence properties
|
|
76
|
+
*/
|
|
77
|
+
getSequenceProperties(params: {
|
|
78
|
+
path?: string;
|
|
79
|
+
}): Promise<any>;
|
|
80
|
+
/**
|
|
81
|
+
* Set playback speed/rate
|
|
82
|
+
*/
|
|
83
|
+
setPlaybackSpeed(params: {
|
|
84
|
+
speed: number;
|
|
85
|
+
}): Promise<any>;
|
|
86
|
+
/**
|
|
87
|
+
* Get all bindings in the current sequence
|
|
88
|
+
*/
|
|
89
|
+
getBindings(params?: {
|
|
90
|
+
path?: string;
|
|
91
|
+
}): Promise<any>;
|
|
92
|
+
/**
|
|
93
|
+
* Add multiple actors to sequence at once
|
|
94
|
+
*/
|
|
95
|
+
addActors(params: {
|
|
96
|
+
actorNames: string[];
|
|
97
|
+
}): Promise<any>;
|
|
98
|
+
/**
|
|
99
|
+
* Remove actors from binding
|
|
100
|
+
*/
|
|
101
|
+
removeActors(params: {
|
|
102
|
+
actorNames: string[];
|
|
103
|
+
}): Promise<any>;
|
|
104
|
+
/**
|
|
105
|
+
* Create a spawnable from an actor class
|
|
106
|
+
*/
|
|
107
|
+
addSpawnableFromClass(params: {
|
|
108
|
+
className: string;
|
|
109
|
+
path?: string;
|
|
110
|
+
}): Promise<any>;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=sequence.d.ts.map
|