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/materials.ts
CHANGED
|
@@ -1,133 +1,149 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
|
+
import { coerceBoolean, interpretStandardResult } from '../utils/result-helpers.js';
|
|
3
|
+
import { escapePythonString } from '../utils/python.js';
|
|
2
4
|
|
|
3
5
|
export class MaterialTools {
|
|
4
6
|
constructor(private bridge: UnrealBridge) {}
|
|
5
7
|
|
|
6
8
|
async createMaterial(name: string, path: string) {
|
|
7
9
|
try {
|
|
8
|
-
// Validate name
|
|
9
10
|
if (!name || name.trim() === '') {
|
|
10
11
|
return { success: false, error: 'Material name cannot be empty' };
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
// Check name length (Unreal has 260 char path limit)
|
|
13
|
+
|
|
14
14
|
if (name.length > 100) {
|
|
15
15
|
return { success: false, error: `Material name too long (${name.length} chars). Maximum is 100 characters.` };
|
|
16
16
|
}
|
|
17
|
-
|
|
18
|
-
// Validate name doesn't contain invalid characters
|
|
19
|
-
// Unreal Engine doesn't allow: spaces, dots, slashes, backslashes, pipes, angle brackets,
|
|
20
|
-
// curly braces, square brackets, parentheses, @, #, etc.
|
|
17
|
+
|
|
21
18
|
const invalidChars = /[\s./<>|{}[\]()@#\\]/;
|
|
22
19
|
if (invalidChars.test(name)) {
|
|
23
20
|
const foundChars = name.match(invalidChars);
|
|
24
21
|
return { success: false, error: `Material name contains invalid characters: '${foundChars?.[0]}'. Avoid spaces, dots, slashes, backslashes, brackets, and special symbols.` };
|
|
25
22
|
}
|
|
26
|
-
|
|
27
|
-
// Validate path type
|
|
23
|
+
|
|
28
24
|
if (typeof path !== 'string') {
|
|
29
25
|
return { success: false, error: `Invalid path type: expected string, got ${typeof path}` };
|
|
30
26
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
27
|
+
|
|
28
|
+
const trimmedPath = path.trim();
|
|
29
|
+
const effectivePath = trimmedPath.length === 0 ? '/Game' : trimmedPath;
|
|
30
|
+
const cleanPath = effectivePath.replace(/\/$/, '');
|
|
31
|
+
|
|
36
32
|
if (!cleanPath.startsWith('/Game') && !cleanPath.startsWith('/Engine')) {
|
|
37
33
|
return { success: false, error: `Invalid path: must start with /Game or /Engine, got ${cleanPath}` };
|
|
38
34
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
35
|
+
|
|
36
|
+
const normalizedPath = cleanPath.toLowerCase();
|
|
37
|
+
const restrictedPrefixes = ['/engine/restricted', '/engine/generated', '/engine/transient'];
|
|
38
|
+
if (restrictedPrefixes.some(prefix => normalizedPath.startsWith(prefix))) {
|
|
39
|
+
const errorMessage = `Destination path is read-only and cannot be used for material creation: ${cleanPath}`;
|
|
40
|
+
return { success: false, error: errorMessage, message: errorMessage };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const materialPath = `${cleanPath}/${name}`;
|
|
44
|
+
const payload = { name, cleanPath, materialPath };
|
|
45
|
+
const escapedName = escapePythonString(name);
|
|
43
46
|
const pythonCode = `
|
|
44
|
-
import unreal
|
|
45
|
-
|
|
47
|
+
import unreal, json
|
|
48
|
+
|
|
49
|
+
payload = json.loads(r'''${JSON.stringify(payload)}''')
|
|
50
|
+
result = {
|
|
51
|
+
'success': False,
|
|
52
|
+
'message': '',
|
|
53
|
+
'error': '',
|
|
54
|
+
'warnings': [],
|
|
55
|
+
'details': [],
|
|
56
|
+
'name': payload.get('name') or "${escapedName}",
|
|
57
|
+
'path': payload.get('materialPath')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
material_path = result['path']
|
|
61
|
+
clean_path = payload.get('cleanPath') or '/Game'
|
|
46
62
|
|
|
47
63
|
try:
|
|
48
|
-
# Check if material already exists
|
|
49
|
-
material_path = '${materialPath}'
|
|
50
64
|
if unreal.EditorAssetLibrary.does_asset_exist(material_path):
|
|
51
|
-
|
|
65
|
+
result['success'] = True
|
|
66
|
+
result['exists'] = True
|
|
67
|
+
result['message'] = f"Material already exists at {material_path}"
|
|
52
68
|
else:
|
|
53
|
-
# Get the AssetTools
|
|
54
69
|
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
55
|
-
|
|
56
|
-
# Create a MaterialFactoryNew
|
|
57
70
|
factory = unreal.MaterialFactoryNew()
|
|
58
|
-
|
|
59
|
-
# Clean up the path - remove trailing slashes
|
|
60
|
-
clean_path = '${cleanPath}'.rstrip('/')
|
|
61
|
-
|
|
62
|
-
# Create the material asset at the specified path
|
|
63
|
-
# The path should be: /Game/FolderName and asset name separately
|
|
64
71
|
asset = asset_tools.create_asset(
|
|
65
|
-
asset_name='
|
|
72
|
+
asset_name=payload.get('name'),
|
|
66
73
|
package_path=clean_path,
|
|
67
74
|
asset_class=unreal.Material,
|
|
68
75
|
factory=factory
|
|
69
76
|
)
|
|
70
|
-
|
|
71
77
|
if asset:
|
|
72
|
-
# Save the package
|
|
73
78
|
unreal.EditorAssetLibrary.save_asset(material_path)
|
|
74
|
-
|
|
79
|
+
result['success'] = True
|
|
80
|
+
result['created'] = True
|
|
81
|
+
result['message'] = f"Material created at {material_path}"
|
|
75
82
|
else:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
result['error'] = 'Failed to create material'
|
|
84
|
+
result['message'] = result['error']
|
|
85
|
+
except Exception as exc:
|
|
86
|
+
result['error'] = str(exc)
|
|
87
|
+
if not result['message']:
|
|
88
|
+
result['message'] = result['error']
|
|
89
|
+
|
|
90
|
+
print('RESULT:' + json.dumps(result))
|
|
79
91
|
`.trim();
|
|
80
|
-
|
|
92
|
+
|
|
81
93
|
const pyResult = await this.bridge.executePython(pythonCode);
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
const interpreted = interpretStandardResult(pyResult, {
|
|
95
|
+
successMessage: `Material ${name} processed`,
|
|
96
|
+
failureMessage: 'Failed to create material'
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (interpreted.success) {
|
|
100
|
+
const exists = coerceBoolean(interpreted.payload.exists, false) === true;
|
|
101
|
+
const created = coerceBoolean(interpreted.payload.created, false) === true;
|
|
102
|
+
if (exists) {
|
|
103
|
+
return { success: true, path: materialPath, message: `Material ${name} already exists at ${materialPath}` };
|
|
104
|
+
}
|
|
105
|
+
if (created) {
|
|
106
|
+
return { success: true, path: materialPath, message: `Material ${name} created at ${materialPath}` };
|
|
107
|
+
}
|
|
108
|
+
return { success: true, path: materialPath, message: interpreted.message };
|
|
91
109
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return { success: true, path: materialPath, message: `Material ${name} created at ${path}` };
|
|
104
|
-
}
|
|
105
|
-
} else {
|
|
106
|
-
return { success: false, error: result.error || 'Failed to create material' };
|
|
107
|
-
}
|
|
110
|
+
|
|
111
|
+
if (interpreted.error) {
|
|
112
|
+
const exists = await this.assetExists(materialPath);
|
|
113
|
+
if (exists) {
|
|
114
|
+
return {
|
|
115
|
+
success: true,
|
|
116
|
+
path: materialPath,
|
|
117
|
+
message: `Material ${name} created at ${materialPath}`,
|
|
118
|
+
warnings: interpreted.warnings,
|
|
119
|
+
details: interpreted.details
|
|
120
|
+
};
|
|
108
121
|
}
|
|
109
|
-
|
|
110
|
-
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: interpreted.error,
|
|
125
|
+
warnings: interpreted.warnings,
|
|
126
|
+
details: interpreted.details
|
|
127
|
+
};
|
|
111
128
|
}
|
|
112
129
|
|
|
113
|
-
|
|
114
|
-
let verify: any = {};
|
|
115
|
-
try {
|
|
116
|
-
verify = await this.bridge.call({
|
|
117
|
-
objectPath: '/Script/EditorScriptingUtilities.Default__EditorAssetLibrary',
|
|
118
|
-
functionName: 'DoesAssetExist',
|
|
119
|
-
parameters: {
|
|
120
|
-
AssetPath: materialPath
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
} catch {}
|
|
124
|
-
|
|
125
|
-
const exists = verify?.ReturnValue === true || verify?.Result === true;
|
|
130
|
+
const exists = await this.assetExists(materialPath);
|
|
126
131
|
if (exists) {
|
|
127
|
-
return {
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
path: materialPath,
|
|
135
|
+
message: `Material ${name} created at ${materialPath}`,
|
|
136
|
+
warnings: interpreted.warnings,
|
|
137
|
+
details: interpreted.details
|
|
138
|
+
};
|
|
130
139
|
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: interpreted.message,
|
|
144
|
+
warnings: interpreted.warnings,
|
|
145
|
+
details: interpreted.details
|
|
146
|
+
};
|
|
131
147
|
} catch (err) {
|
|
132
148
|
return { success: false, error: `Failed to create material: ${err}` };
|
|
133
149
|
}
|
|
@@ -145,4 +161,19 @@ except Exception as e:
|
|
|
145
161
|
return { success: false, error: `Failed to apply material: ${err}` };
|
|
146
162
|
}
|
|
147
163
|
}
|
|
164
|
+
|
|
165
|
+
private async assetExists(assetPath: string): Promise<boolean> {
|
|
166
|
+
try {
|
|
167
|
+
const response = await this.bridge.call({
|
|
168
|
+
objectPath: '/Script/EditorScriptingUtilities.Default__EditorAssetLibrary',
|
|
169
|
+
functionName: 'DoesAssetExist',
|
|
170
|
+
parameters: {
|
|
171
|
+
AssetPath: assetPath
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return coerceBoolean(response?.ReturnValue ?? response?.Result ?? response, false) === true;
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
148
179
|
}
|
package/src/tools/niagara.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
2
|
import { sanitizeAssetName, validateAssetParams } from '../utils/validation.js';
|
|
3
|
+
import { interpretStandardResult, coerceString } from '../utils/result-helpers.js';
|
|
3
4
|
|
|
4
5
|
export class NiagaraTools {
|
|
5
6
|
constructor(private bridge: UnrealBridge) {}
|
|
@@ -21,54 +22,50 @@ export class NiagaraTools {
|
|
|
21
22
|
}) {
|
|
22
23
|
try {
|
|
23
24
|
const path = params.savePath || '/Game/Effects/Niagara';
|
|
24
|
-
|
|
25
|
-
const python = `
|
|
25
|
+
const python = `
|
|
26
26
|
import unreal
|
|
27
|
+
import json
|
|
28
|
+
|
|
27
29
|
path = r"${path}"
|
|
28
30
|
name = r"${params.name}"
|
|
29
31
|
full_path = f"{path}/{name}"
|
|
30
|
-
|
|
32
|
+
|
|
31
33
|
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
32
|
-
print(
|
|
34
|
+
print('RESULT:' + json.dumps({'success': True, 'path': full_path, 'existing': True}))
|
|
33
35
|
else:
|
|
34
36
|
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
35
37
|
factory = None
|
|
36
38
|
try:
|
|
37
39
|
factory = unreal.NiagaraSystemFactoryNew()
|
|
38
|
-
except Exception
|
|
40
|
+
except Exception:
|
|
39
41
|
factory = None
|
|
42
|
+
|
|
40
43
|
if factory is None:
|
|
41
|
-
print(
|
|
44
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'NiagaraSystemFactoryNew unavailable'}))
|
|
42
45
|
else:
|
|
43
46
|
asset = asset_tools.create_asset(asset_name=name, package_path=path, asset_class=unreal.NiagaraSystem, factory=factory)
|
|
44
47
|
if asset:
|
|
45
48
|
unreal.EditorAssetLibrary.save_asset(full_path)
|
|
46
|
-
print(
|
|
49
|
+
print('RESULT:' + json.dumps({'success': True, 'path': full_path}))
|
|
47
50
|
else:
|
|
48
|
-
print(
|
|
51
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'AssetTools create_asset failed'}))
|
|
49
52
|
`.trim();
|
|
50
53
|
const resp = await this.bridge.executePython(python);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
const m = output.match(/RESULT:({.*})/);
|
|
60
|
-
if (m) {
|
|
61
|
-
try {
|
|
62
|
-
const parsed = JSON.parse(m[1].replace(/'/g, '"'));
|
|
63
|
-
if (parsed.success) {
|
|
64
|
-
return { success: true, path: parsed.path, message: `Niagara system ${params.name} created` };
|
|
65
|
-
}
|
|
66
|
-
return { success: false, error: parsed.error || 'Unknown error creating Niagara system' };
|
|
67
|
-
} catch {
|
|
68
|
-
// fallthrough
|
|
69
|
-
}
|
|
54
|
+
const interpreted = interpretStandardResult(resp, {
|
|
55
|
+
successMessage: `Niagara system ${params.name} created`,
|
|
56
|
+
failureMessage: `Failed to create Niagara system ${params.name}`
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (!interpreted.success) {
|
|
60
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
70
61
|
}
|
|
71
|
-
|
|
62
|
+
|
|
63
|
+
const pathFromPayload = coerceString(interpreted.payload.path) ?? `${path}/${params.name}`;
|
|
64
|
+
return {
|
|
65
|
+
success: true,
|
|
66
|
+
path: pathFromPayload,
|
|
67
|
+
message: interpreted.message
|
|
68
|
+
};
|
|
72
69
|
} catch (err) {
|
|
73
70
|
return { success: false, error: `Failed to create Niagara system: ${err}` };
|
|
74
71
|
}
|
|
@@ -122,9 +119,7 @@ else:
|
|
|
122
119
|
commands.push(`SetEmitterMesh ${params.systemName} ${params.emitterName} ${props.mesh}`);
|
|
123
120
|
}
|
|
124
121
|
}
|
|
125
|
-
|
|
126
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
127
|
-
}
|
|
122
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
128
123
|
return { success: true, message: `Emitter ${params.emitterName} added to ${params.systemName}` };
|
|
129
124
|
} catch (err) {
|
|
130
125
|
return { success: false, error: `Failed to add emitter: ${err}` };
|
|
@@ -192,21 +187,24 @@ else:
|
|
|
192
187
|
// Verify existence via Python to avoid RC EditorAssetLibrary issues
|
|
193
188
|
const verifyPy = `
|
|
194
189
|
import unreal
|
|
190
|
+
import json
|
|
191
|
+
|
|
195
192
|
p = r"${fullPath}"
|
|
196
|
-
|
|
193
|
+
exists = bool(unreal.EditorAssetLibrary.does_asset_exist(p))
|
|
194
|
+
print('RESULT:' + json.dumps({'success': True, 'exists': exists}))
|
|
197
195
|
`.trim();
|
|
198
196
|
const verifyResp = await this.bridge.executePython(verifyPy);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
197
|
+
const verifyResult = interpretStandardResult(verifyResp, {
|
|
198
|
+
successMessage: 'Niagara asset verification complete',
|
|
199
|
+
failureMessage: `Failed to verify Niagara asset at ${fullPath}`
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
if (!verifyResult.success) {
|
|
203
|
+
return { success: false, error: verifyResult.error ?? verifyResult.message };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (verifyResult.payload.exists === false) {
|
|
207
|
+
return { success: false, error: `Asset not found after creation: ${fullPath}` };
|
|
210
208
|
}
|
|
211
209
|
|
|
212
210
|
return { success: true, message: `${params.effectType} effect ${safeName} created`, path: fullPath };
|
|
@@ -237,7 +235,7 @@ print("RESULT:{'success': True, 'exists': %s}" % ('True' if unreal.EditorAssetLi
|
|
|
237
235
|
if (s.gridResolution) { const r = s.gridResolution; commands.push(`SetGPUGridResolution ${params.name} ${r[0]} ${r[1]} ${r[2]}`); }
|
|
238
236
|
if (s.iterations !== undefined) commands.push(`SetGPUIterations ${params.name} ${s.iterations}`);
|
|
239
237
|
}
|
|
240
|
-
|
|
238
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
241
239
|
return { success: true, message: `GPU simulation ${params.name} created`, path: `${path}/${params.name}` };
|
|
242
240
|
} catch (err) {
|
|
243
241
|
return { success: false, error: `Failed to create GPU simulation: ${err}` };
|
|
@@ -259,54 +257,62 @@ print("RESULT:{'success': True, 'exists': %s}" % ('True' if unreal.EditorAssetLi
|
|
|
259
257
|
const loc = Array.isArray(params.location) ? { x: params.location[0], y: params.location[1], z: params.location[2] } : params.location;
|
|
260
258
|
const rot = params.rotation || [0, 0, 0];
|
|
261
259
|
const scl = Array.isArray(params.scale) ? params.scale : (typeof params.scale === 'number' ? [params.scale, params.scale, params.scale] : [1, 1, 1]);
|
|
262
|
-
|
|
260
|
+
const py = `
|
|
263
261
|
import unreal
|
|
262
|
+
import json
|
|
263
|
+
|
|
264
264
|
loc = unreal.Vector(${loc.x || 0}, ${loc.y || 0}, ${loc.z || 0})
|
|
265
265
|
rot = unreal.Rotator(${rot[0]}, ${rot[1]}, ${rot[2]})
|
|
266
266
|
scale = unreal.Vector(${scl[0]}, ${scl[1]}, ${scl[2]})
|
|
267
267
|
sys_path = r"${params.systemPath}"
|
|
268
|
+
|
|
268
269
|
if unreal.EditorAssetLibrary.does_asset_exist(sys_path):
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
270
|
+
sys = unreal.EditorAssetLibrary.load_asset(sys_path)
|
|
271
|
+
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
272
|
+
actor = actor_subsystem.spawn_actor_from_class(unreal.NiagaraActor, loc, rot)
|
|
273
|
+
if actor:
|
|
274
|
+
comp = actor.get_niagara_component()
|
|
275
|
+
try:
|
|
276
|
+
comp.set_asset(sys)
|
|
277
|
+
except Exception:
|
|
278
|
+
try:
|
|
279
|
+
comp.set_editor_property('asset', sys)
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
comp.set_world_scale3d(scale)
|
|
283
|
+
comp.activate(True)
|
|
284
|
+
actor.set_actor_label(f"Niagara_{unreal.SystemLibrary.get_game_time_in_seconds(actor.get_world()):.0f}")
|
|
285
|
+
print('RESULT:' + json.dumps({'success': True, 'actor': actor.get_actor_label()}))
|
|
286
|
+
else:
|
|
287
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to spawn NiagaraActor'}))
|
|
287
288
|
else:
|
|
288
|
-
|
|
289
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'System asset not found'}))
|
|
289
290
|
`.trim();
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
291
|
+
const resp = await this.bridge.executePython(py);
|
|
292
|
+
const interpreted = interpretStandardResult(resp, {
|
|
293
|
+
successMessage: 'Niagara effect spawned',
|
|
294
|
+
failureMessage: 'Failed to spawn Niagara effect'
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const actorLabel = coerceString(interpreted.payload.actor);
|
|
298
|
+
|
|
299
|
+
if (!interpreted.success) {
|
|
300
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const outcome: { success: true; message: string; actor?: string } = {
|
|
304
|
+
success: true,
|
|
305
|
+
message: interpreted.message
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
if (actorLabel) {
|
|
309
|
+
outcome.actor = actorLabel;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return outcome;
|
|
299
313
|
} catch (err) {
|
|
300
314
|
return { success: false, error: `Failed to spawn effect: ${err}` };
|
|
301
315
|
}
|
|
302
316
|
}
|
|
303
317
|
|
|
304
|
-
private async _executeCommand(command: string) {
|
|
305
|
-
return this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
306
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
307
|
-
functionName: 'ExecuteConsoleCommand',
|
|
308
|
-
parameters: { WorldContextObject: null, Command: command, SpecificPlayer: null },
|
|
309
|
-
generateTransaction: false
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
318
|
}
|