unreal-engine-mcp-server 0.3.1 → 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 +22 -7
- package/dist/index.js +137 -46
- 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.d.ts +3 -2
- package/dist/resources/assets.js +117 -109
- package/dist/resources/levels.d.ts +21 -3
- package/dist/resources/levels.js +31 -56
- 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 +58 -46
- 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 +236 -87
- package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
- package/dist/tools/consolidated-tool-definitions.js +124 -255
- package/dist/tools/consolidated-tool-handlers.js +749 -766
- package/dist/tools/debug.d.ts +72 -10
- package/dist/tools/debug.js +170 -36
- 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 +98 -24
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +146 -24
- 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.d.ts +6 -1
- package/dist/utils/response-validator.js +66 -13
- 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 +11 -10
- package/server.json +37 -14
- package/src/index.ts +146 -50
- package/src/prompts/index.ts +211 -13
- package/src/resources/actors.ts +59 -44
- package/src/resources/assets.ts +123 -102
- package/src/resources/levels.ts +37 -47
- package/src/tools/actors.ts +269 -313
- package/src/tools/animation.ts +556 -539
- package/src/tools/assets.ts +59 -45
- package/src/tools/audio.ts +507 -113
- package/src/tools/blueprint.ts +778 -462
- package/src/tools/build_environment_advanced.ts +312 -106
- package/src/tools/consolidated-tool-definitions.ts +136 -267
- package/src/tools/consolidated-tool-handlers.ts +871 -795
- package/src/tools/debug.ts +179 -38
- 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 +103 -25
- package/src/tools/sequence.ts +157 -30
- 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 +68 -17
- 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/tools/tool-definitions.d.ts +0 -4919
- package/dist/tools/tool-definitions.js +0 -1065
- package/dist/tools/tool-handlers.d.ts +0 -47
- package/dist/tools/tool-handlers.js +0 -863
- 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/tools/tool-definitions.ts +0 -1081
- package/src/tools/tool-handlers.ts +0 -973
- package/src/types/index.ts +0 -414
- package/src/utils/cache-manager.ts +0 -213
- package/src/utils/errors.ts +0 -312
package/dist/tools/materials.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { coerceBoolean, interpretStandardResult } from '../utils/result-helpers.js';
|
|
2
|
+
import { escapePythonString } from '../utils/python.js';
|
|
1
3
|
export class MaterialTools {
|
|
2
4
|
bridge;
|
|
3
5
|
constructor(bridge) {
|
|
@@ -5,125 +7,131 @@ export class MaterialTools {
|
|
|
5
7
|
}
|
|
6
8
|
async createMaterial(name, path) {
|
|
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
|
-
// Check name length (Unreal has 260 char path limit)
|
|
13
13
|
if (name.length > 100) {
|
|
14
14
|
return { success: false, error: `Material name too long (${name.length} chars). Maximum is 100 characters.` };
|
|
15
15
|
}
|
|
16
|
-
// Validate name doesn't contain invalid characters
|
|
17
|
-
// Unreal Engine doesn't allow: spaces, dots, slashes, backslashes, pipes, angle brackets,
|
|
18
|
-
// curly braces, square brackets, parentheses, @, #, etc.
|
|
19
16
|
const invalidChars = /[\s./<>|{}[\]()@#\\]/;
|
|
20
17
|
if (invalidChars.test(name)) {
|
|
21
18
|
const foundChars = name.match(invalidChars);
|
|
22
19
|
return { success: false, error: `Material name contains invalid characters: '${foundChars?.[0]}'. Avoid spaces, dots, slashes, backslashes, brackets, and special symbols.` };
|
|
23
20
|
}
|
|
24
|
-
// Validate path type
|
|
25
21
|
if (typeof path !== 'string') {
|
|
26
22
|
return { success: false, error: `Invalid path type: expected string, got ${typeof path}` };
|
|
27
23
|
}
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
24
|
+
const trimmedPath = path.trim();
|
|
25
|
+
const effectivePath = trimmedPath.length === 0 ? '/Game' : trimmedPath;
|
|
26
|
+
const cleanPath = effectivePath.replace(/\/$/, '');
|
|
31
27
|
if (!cleanPath.startsWith('/Game') && !cleanPath.startsWith('/Engine')) {
|
|
32
28
|
return { success: false, error: `Invalid path: must start with /Game or /Engine, got ${cleanPath}` };
|
|
33
29
|
}
|
|
34
|
-
|
|
30
|
+
const normalizedPath = cleanPath.toLowerCase();
|
|
31
|
+
const restrictedPrefixes = ['/engine/restricted', '/engine/generated', '/engine/transient'];
|
|
32
|
+
if (restrictedPrefixes.some(prefix => normalizedPath.startsWith(prefix))) {
|
|
33
|
+
const errorMessage = `Destination path is read-only and cannot be used for material creation: ${cleanPath}`;
|
|
34
|
+
return { success: false, error: errorMessage, message: errorMessage };
|
|
35
|
+
}
|
|
35
36
|
const materialPath = `${cleanPath}/${name}`;
|
|
36
|
-
|
|
37
|
+
const payload = { name, cleanPath, materialPath };
|
|
38
|
+
const escapedName = escapePythonString(name);
|
|
37
39
|
const pythonCode = `
|
|
38
|
-
import unreal
|
|
39
|
-
|
|
40
|
+
import unreal, json
|
|
41
|
+
|
|
42
|
+
payload = json.loads(r'''${JSON.stringify(payload)}''')
|
|
43
|
+
result = {
|
|
44
|
+
'success': False,
|
|
45
|
+
'message': '',
|
|
46
|
+
'error': '',
|
|
47
|
+
'warnings': [],
|
|
48
|
+
'details': [],
|
|
49
|
+
'name': payload.get('name') or "${escapedName}",
|
|
50
|
+
'path': payload.get('materialPath')
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
material_path = result['path']
|
|
54
|
+
clean_path = payload.get('cleanPath') or '/Game'
|
|
40
55
|
|
|
41
56
|
try:
|
|
42
|
-
# Check if material already exists
|
|
43
|
-
material_path = '${materialPath}'
|
|
44
57
|
if unreal.EditorAssetLibrary.does_asset_exist(material_path):
|
|
45
|
-
|
|
58
|
+
result['success'] = True
|
|
59
|
+
result['exists'] = True
|
|
60
|
+
result['message'] = f"Material already exists at {material_path}"
|
|
46
61
|
else:
|
|
47
|
-
# Get the AssetTools
|
|
48
62
|
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
49
|
-
|
|
50
|
-
# Create a MaterialFactoryNew
|
|
51
63
|
factory = unreal.MaterialFactoryNew()
|
|
52
|
-
|
|
53
|
-
# Clean up the path - remove trailing slashes
|
|
54
|
-
clean_path = '${cleanPath}'.rstrip('/')
|
|
55
|
-
|
|
56
|
-
# Create the material asset at the specified path
|
|
57
|
-
# The path should be: /Game/FolderName and asset name separately
|
|
58
64
|
asset = asset_tools.create_asset(
|
|
59
|
-
asset_name='
|
|
65
|
+
asset_name=payload.get('name'),
|
|
60
66
|
package_path=clean_path,
|
|
61
67
|
asset_class=unreal.Material,
|
|
62
68
|
factory=factory
|
|
63
69
|
)
|
|
64
|
-
|
|
65
70
|
if asset:
|
|
66
|
-
# Save the package
|
|
67
71
|
unreal.EditorAssetLibrary.save_asset(material_path)
|
|
68
|
-
|
|
72
|
+
result['success'] = True
|
|
73
|
+
result['created'] = True
|
|
74
|
+
result['message'] = f"Material created at {material_path}"
|
|
69
75
|
else:
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
result['error'] = 'Failed to create material'
|
|
77
|
+
result['message'] = result['error']
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
result['error'] = str(exc)
|
|
80
|
+
if not result['message']:
|
|
81
|
+
result['message'] = result['error']
|
|
82
|
+
|
|
83
|
+
print('RESULT:' + json.dumps(result))
|
|
73
84
|
`.trim();
|
|
74
85
|
const pyResult = await this.bridge.executePython(pythonCode);
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
responseStr = JSON.stringify(pyResult);
|
|
85
|
-
}
|
|
86
|
-
// Try to extract JSON response
|
|
87
|
-
try {
|
|
88
|
-
// Look for JSON in the output
|
|
89
|
-
const jsonMatch = responseStr.match(/\{.*\}/s);
|
|
90
|
-
if (jsonMatch) {
|
|
91
|
-
const result = JSON.parse(jsonMatch[0]);
|
|
92
|
-
if (result.success) {
|
|
93
|
-
if (result.exists) {
|
|
94
|
-
return { success: true, path: materialPath, message: `Material ${name} already exists at ${path}` };
|
|
95
|
-
}
|
|
96
|
-
else if (result.created) {
|
|
97
|
-
return { success: true, path: materialPath, message: `Material ${name} created at ${path}` };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
return { success: false, error: result.error || 'Failed to create material' };
|
|
102
|
-
}
|
|
86
|
+
const interpreted = interpretStandardResult(pyResult, {
|
|
87
|
+
successMessage: `Material ${name} processed`,
|
|
88
|
+
failureMessage: 'Failed to create material'
|
|
89
|
+
});
|
|
90
|
+
if (interpreted.success) {
|
|
91
|
+
const exists = coerceBoolean(interpreted.payload.exists, false) === true;
|
|
92
|
+
const created = coerceBoolean(interpreted.payload.created, false) === true;
|
|
93
|
+
if (exists) {
|
|
94
|
+
return { success: true, path: materialPath, message: `Material ${name} already exists at ${materialPath}` };
|
|
103
95
|
}
|
|
96
|
+
if (created) {
|
|
97
|
+
return { success: true, path: materialPath, message: `Material ${name} created at ${materialPath}` };
|
|
98
|
+
}
|
|
99
|
+
return { success: true, path: materialPath, message: interpreted.message };
|
|
104
100
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
101
|
+
if (interpreted.error) {
|
|
102
|
+
const exists = await this.assetExists(materialPath);
|
|
103
|
+
if (exists) {
|
|
104
|
+
return {
|
|
105
|
+
success: true,
|
|
106
|
+
path: materialPath,
|
|
107
|
+
message: `Material ${name} created at ${materialPath}`,
|
|
108
|
+
warnings: interpreted.warnings,
|
|
109
|
+
details: interpreted.details
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: interpreted.error,
|
|
115
|
+
warnings: interpreted.warnings,
|
|
116
|
+
details: interpreted.details
|
|
117
|
+
};
|
|
118
118
|
}
|
|
119
|
-
|
|
120
|
-
const exists = verify?.ReturnValue === true || verify?.Result === true;
|
|
119
|
+
const exists = await this.assetExists(materialPath);
|
|
121
120
|
if (exists) {
|
|
122
|
-
return {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
path: materialPath,
|
|
124
|
+
message: `Material ${name} created at ${materialPath}`,
|
|
125
|
+
warnings: interpreted.warnings,
|
|
126
|
+
details: interpreted.details
|
|
127
|
+
};
|
|
126
128
|
}
|
|
129
|
+
return {
|
|
130
|
+
success: false,
|
|
131
|
+
error: interpreted.message,
|
|
132
|
+
warnings: interpreted.warnings,
|
|
133
|
+
details: interpreted.details
|
|
134
|
+
};
|
|
127
135
|
}
|
|
128
136
|
catch (err) {
|
|
129
137
|
return { success: false, error: `Failed to create material: ${err}` };
|
|
@@ -142,5 +150,20 @@ except Exception as e:
|
|
|
142
150
|
return { success: false, error: `Failed to apply material: ${err}` };
|
|
143
151
|
}
|
|
144
152
|
}
|
|
153
|
+
async assetExists(assetPath) {
|
|
154
|
+
try {
|
|
155
|
+
const response = await this.bridge.call({
|
|
156
|
+
objectPath: '/Script/EditorScriptingUtilities.Default__EditorAssetLibrary',
|
|
157
|
+
functionName: 'DoesAssetExist',
|
|
158
|
+
parameters: {
|
|
159
|
+
AssetPath: assetPath
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return coerceBoolean(response?.ReturnValue ?? response?.Result ?? response, false) === true;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
145
168
|
}
|
|
146
169
|
//# sourceMappingURL=materials.js.map
|
package/dist/tools/niagara.d.ts
CHANGED
|
@@ -18,14 +18,14 @@ export declare class NiagaraTools {
|
|
|
18
18
|
}>;
|
|
19
19
|
}): Promise<{
|
|
20
20
|
success: boolean;
|
|
21
|
-
|
|
22
|
-
message: string;
|
|
23
|
-
error?: undefined;
|
|
24
|
-
} | {
|
|
25
|
-
success: boolean;
|
|
26
|
-
error: any;
|
|
21
|
+
error: string;
|
|
27
22
|
path?: undefined;
|
|
28
23
|
message?: undefined;
|
|
24
|
+
} | {
|
|
25
|
+
success: boolean;
|
|
26
|
+
path: string;
|
|
27
|
+
message: string;
|
|
28
|
+
error?: undefined;
|
|
29
29
|
}>;
|
|
30
30
|
/**
|
|
31
31
|
* Add Emitter to System (left as-is; console commands may be placeholders)
|
|
@@ -86,7 +86,7 @@ export declare class NiagaraTools {
|
|
|
86
86
|
};
|
|
87
87
|
}): Promise<{
|
|
88
88
|
success: boolean;
|
|
89
|
-
error:
|
|
89
|
+
error: string;
|
|
90
90
|
message?: undefined;
|
|
91
91
|
path?: undefined;
|
|
92
92
|
} | {
|
|
@@ -132,14 +132,12 @@ export declare class NiagaraTools {
|
|
|
132
132
|
autoDestroy?: boolean;
|
|
133
133
|
attachToActor?: string;
|
|
134
134
|
}): Promise<{
|
|
135
|
-
success:
|
|
135
|
+
success: true;
|
|
136
136
|
message: string;
|
|
137
|
-
|
|
137
|
+
actor?: string;
|
|
138
138
|
} | {
|
|
139
139
|
success: boolean;
|
|
140
|
-
error:
|
|
141
|
-
message?: undefined;
|
|
140
|
+
error: string;
|
|
142
141
|
}>;
|
|
143
|
-
private _executeCommand;
|
|
144
142
|
}
|
|
145
143
|
//# sourceMappingURL=niagara.d.ts.map
|
package/dist/tools/niagara.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { sanitizeAssetName, validateAssetParams } from '../utils/validation.js';
|
|
2
|
+
import { interpretStandardResult, coerceString } from '../utils/result-helpers.js';
|
|
2
3
|
export class NiagaraTools {
|
|
3
4
|
bridge;
|
|
4
5
|
constructor(bridge) {
|
|
@@ -10,57 +11,48 @@ export class NiagaraTools {
|
|
|
10
11
|
async createSystem(params) {
|
|
11
12
|
try {
|
|
12
13
|
const path = params.savePath || '/Game/Effects/Niagara';
|
|
13
|
-
// const fullPath = `${path}/${params.name}`; // Currently unused
|
|
14
14
|
const python = `
|
|
15
15
|
import unreal
|
|
16
|
+
import json
|
|
17
|
+
|
|
16
18
|
path = r"${path}"
|
|
17
19
|
name = r"${params.name}"
|
|
18
20
|
full_path = f"{path}/{name}"
|
|
19
|
-
|
|
21
|
+
|
|
20
22
|
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
21
|
-
print(
|
|
23
|
+
print('RESULT:' + json.dumps({'success': True, 'path': full_path, 'existing': True}))
|
|
22
24
|
else:
|
|
23
25
|
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
24
26
|
factory = None
|
|
25
27
|
try:
|
|
26
28
|
factory = unreal.NiagaraSystemFactoryNew()
|
|
27
|
-
except Exception
|
|
29
|
+
except Exception:
|
|
28
30
|
factory = None
|
|
31
|
+
|
|
29
32
|
if factory is None:
|
|
30
|
-
print(
|
|
33
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'NiagaraSystemFactoryNew unavailable'}))
|
|
31
34
|
else:
|
|
32
35
|
asset = asset_tools.create_asset(asset_name=name, package_path=path, asset_class=unreal.NiagaraSystem, factory=factory)
|
|
33
36
|
if asset:
|
|
34
37
|
unreal.EditorAssetLibrary.save_asset(full_path)
|
|
35
|
-
print(
|
|
38
|
+
print('RESULT:' + json.dumps({'success': True, 'path': full_path}))
|
|
36
39
|
else:
|
|
37
|
-
print(
|
|
40
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'AssetTools create_asset failed'}))
|
|
38
41
|
`.trim();
|
|
39
42
|
const resp = await this.bridge.executePython(python);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
const interpreted = interpretStandardResult(resp, {
|
|
44
|
+
successMessage: `Niagara system ${params.name} created`,
|
|
45
|
+
failureMessage: `Failed to create Niagara system ${params.name}`
|
|
46
|
+
});
|
|
47
|
+
if (!interpreted.success) {
|
|
48
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
43
49
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
const m = output.match(/RESULT:({.*})/);
|
|
51
|
-
if (m) {
|
|
52
|
-
try {
|
|
53
|
-
const parsed = JSON.parse(m[1].replace(/'/g, '"'));
|
|
54
|
-
if (parsed.success) {
|
|
55
|
-
return { success: true, path: parsed.path, message: `Niagara system ${params.name} created` };
|
|
56
|
-
}
|
|
57
|
-
return { success: false, error: parsed.error || 'Unknown error creating Niagara system' };
|
|
58
|
-
}
|
|
59
|
-
catch {
|
|
60
|
-
// fallthrough
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return { success: false, error: 'No RESULT from Python when creating Niagara system' };
|
|
50
|
+
const pathFromPayload = coerceString(interpreted.payload.path) ?? `${path}/${params.name}`;
|
|
51
|
+
return {
|
|
52
|
+
success: true,
|
|
53
|
+
path: pathFromPayload,
|
|
54
|
+
message: interpreted.message
|
|
55
|
+
};
|
|
64
56
|
}
|
|
65
57
|
catch (err) {
|
|
66
58
|
return { success: false, error: `Failed to create Niagara system: ${err}` };
|
|
@@ -101,9 +93,7 @@ else:
|
|
|
101
93
|
commands.push(`SetEmitterMesh ${params.systemName} ${params.emitterName} ${props.mesh}`);
|
|
102
94
|
}
|
|
103
95
|
}
|
|
104
|
-
|
|
105
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
106
|
-
}
|
|
96
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
107
97
|
return { success: true, message: `Emitter ${params.emitterName} added to ${params.systemName}` };
|
|
108
98
|
}
|
|
109
99
|
catch (err) {
|
|
@@ -167,26 +157,22 @@ else:
|
|
|
167
157
|
// Verify existence via Python to avoid RC EditorAssetLibrary issues
|
|
168
158
|
const verifyPy = `
|
|
169
159
|
import unreal
|
|
160
|
+
import json
|
|
161
|
+
|
|
170
162
|
p = r"${fullPath}"
|
|
171
|
-
|
|
163
|
+
exists = bool(unreal.EditorAssetLibrary.does_asset_exist(p))
|
|
164
|
+
print('RESULT:' + json.dumps({'success': True, 'exists': exists}))
|
|
172
165
|
`.trim();
|
|
173
166
|
const verifyResp = await this.bridge.executePython(verifyPy);
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
const parsed = JSON.parse(m[1].replace(/'/g, '"'));
|
|
185
|
-
if (!parsed.exists) {
|
|
186
|
-
return { success: false, error: `Asset not found after creation: ${fullPath}` };
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
catch { }
|
|
167
|
+
const verifyResult = interpretStandardResult(verifyResp, {
|
|
168
|
+
successMessage: 'Niagara asset verification complete',
|
|
169
|
+
failureMessage: `Failed to verify Niagara asset at ${fullPath}`
|
|
170
|
+
});
|
|
171
|
+
if (!verifyResult.success) {
|
|
172
|
+
return { success: false, error: verifyResult.error ?? verifyResult.message };
|
|
173
|
+
}
|
|
174
|
+
if (verifyResult.payload.exists === false) {
|
|
175
|
+
return { success: false, error: `Asset not found after creation: ${fullPath}` };
|
|
190
176
|
}
|
|
191
177
|
return { success: true, message: `${params.effectType} effect ${safeName} created`, path: fullPath };
|
|
192
178
|
}
|
|
@@ -211,8 +197,7 @@ print("RESULT:{'success': True, 'exists': %s}" % ('True' if unreal.EditorAssetLi
|
|
|
211
197
|
if (s.iterations !== undefined)
|
|
212
198
|
commands.push(`SetGPUIterations ${params.name} ${s.iterations}`);
|
|
213
199
|
}
|
|
214
|
-
|
|
215
|
-
await this.bridge.executeConsoleCommand(cmd);
|
|
200
|
+
await this.bridge.executeConsoleCommands(commands);
|
|
216
201
|
return { success: true, message: `GPU simulation ${params.name} created`, path: `${path}/${params.name}` };
|
|
217
202
|
}
|
|
218
203
|
catch (err) {
|
|
@@ -229,61 +214,56 @@ print("RESULT:{'success': True, 'exists': %s}" % ('True' if unreal.EditorAssetLi
|
|
|
229
214
|
const scl = Array.isArray(params.scale) ? params.scale : (typeof params.scale === 'number' ? [params.scale, params.scale, params.scale] : [1, 1, 1]);
|
|
230
215
|
const py = `
|
|
231
216
|
import unreal
|
|
217
|
+
import json
|
|
218
|
+
|
|
232
219
|
loc = unreal.Vector(${loc.x || 0}, ${loc.y || 0}, ${loc.z || 0})
|
|
233
220
|
rot = unreal.Rotator(${rot[0]}, ${rot[1]}, ${rot[2]})
|
|
234
221
|
scale = unreal.Vector(${scl[0]}, ${scl[1]}, ${scl[2]})
|
|
235
222
|
sys_path = r"${params.systemPath}"
|
|
223
|
+
|
|
236
224
|
if unreal.EditorAssetLibrary.does_asset_exist(sys_path):
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
225
|
+
sys = unreal.EditorAssetLibrary.load_asset(sys_path)
|
|
226
|
+
actor_subsystem = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
227
|
+
actor = actor_subsystem.spawn_actor_from_class(unreal.NiagaraActor, loc, rot)
|
|
228
|
+
if actor:
|
|
229
|
+
comp = actor.get_niagara_component()
|
|
230
|
+
try:
|
|
231
|
+
comp.set_asset(sys)
|
|
232
|
+
except Exception:
|
|
233
|
+
try:
|
|
234
|
+
comp.set_editor_property('asset', sys)
|
|
235
|
+
except Exception:
|
|
236
|
+
pass
|
|
237
|
+
comp.set_world_scale3d(scale)
|
|
238
|
+
comp.activate(True)
|
|
239
|
+
actor.set_actor_label(f"Niagara_{unreal.SystemLibrary.get_game_time_in_seconds(actor.get_world()):.0f}")
|
|
240
|
+
print('RESULT:' + json.dumps({'success': True, 'actor': actor.get_actor_label()}))
|
|
241
|
+
else:
|
|
242
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to spawn NiagaraActor'}))
|
|
255
243
|
else:
|
|
256
|
-
|
|
244
|
+
print('RESULT:' + json.dumps({'success': False, 'error': 'System asset not found'}))
|
|
257
245
|
`.trim();
|
|
258
246
|
const resp = await this.bridge.executePython(py);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
247
|
+
const interpreted = interpretStandardResult(resp, {
|
|
248
|
+
successMessage: 'Niagara effect spawned',
|
|
249
|
+
failureMessage: 'Failed to spawn Niagara effect'
|
|
250
|
+
});
|
|
251
|
+
const actorLabel = coerceString(interpreted.payload.actor);
|
|
252
|
+
if (!interpreted.success) {
|
|
253
|
+
return { success: false, error: interpreted.error ?? interpreted.message };
|
|
254
|
+
}
|
|
255
|
+
const outcome = {
|
|
256
|
+
success: true,
|
|
257
|
+
message: interpreted.message
|
|
258
|
+
};
|
|
259
|
+
if (actorLabel) {
|
|
260
|
+
outcome.actor = actorLabel;
|
|
273
261
|
}
|
|
274
|
-
return
|
|
262
|
+
return outcome;
|
|
275
263
|
}
|
|
276
264
|
catch (err) {
|
|
277
265
|
return { success: false, error: `Failed to spawn effect: ${err}` };
|
|
278
266
|
}
|
|
279
267
|
}
|
|
280
|
-
async _executeCommand(command) {
|
|
281
|
-
return this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
282
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
283
|
-
functionName: 'ExecuteConsoleCommand',
|
|
284
|
-
parameters: { WorldContextObject: null, Command: command, SpecificPlayer: null },
|
|
285
|
-
generateTransaction: false
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
268
|
}
|
|
289
269
|
//# sourceMappingURL=niagara.js.map
|
|
@@ -2,7 +2,6 @@ import { UnrealBridge } from '../unreal-bridge.js';
|
|
|
2
2
|
export declare class PerformanceTools {
|
|
3
3
|
private bridge;
|
|
4
4
|
constructor(bridge: UnrealBridge);
|
|
5
|
-
private _executeCommand;
|
|
6
5
|
startProfiling(params: {
|
|
7
6
|
type: 'CPU' | 'GPU' | 'Memory' | 'RenderThread' | 'GameThread' | 'All';
|
|
8
7
|
duration?: number;
|
|
@@ -38,15 +37,24 @@ export declare class PerformanceTools {
|
|
|
38
37
|
category: 'ViewDistance' | 'AntiAliasing' | 'PostProcessing' | 'PostProcess' | 'Shadows' | 'GlobalIllumination' | 'Reflections' | 'Textures' | 'Effects' | 'Foliage' | 'Shading';
|
|
39
38
|
level: 0 | 1 | 2 | 3 | 4;
|
|
40
39
|
}): Promise<{
|
|
40
|
+
success: boolean;
|
|
41
|
+
error: string;
|
|
42
|
+
message?: undefined;
|
|
43
|
+
verified?: undefined;
|
|
44
|
+
readback?: undefined;
|
|
45
|
+
method?: undefined;
|
|
46
|
+
} | {
|
|
41
47
|
success: boolean;
|
|
42
48
|
message: string;
|
|
43
|
-
verified:
|
|
44
|
-
readback:
|
|
45
|
-
method:
|
|
49
|
+
verified: boolean;
|
|
50
|
+
readback: number;
|
|
51
|
+
method: string;
|
|
52
|
+
error?: undefined;
|
|
46
53
|
} | {
|
|
47
54
|
success: boolean;
|
|
48
55
|
message: string;
|
|
49
56
|
method: string;
|
|
57
|
+
error?: undefined;
|
|
50
58
|
verified?: undefined;
|
|
51
59
|
readback?: undefined;
|
|
52
60
|
}>;
|