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,652 @@
|
|
|
1
|
+
import { validateAssetParams, concurrencyDelay } from '../utils/validation.js';
|
|
2
|
+
export class BlueprintTools {
|
|
3
|
+
bridge;
|
|
4
|
+
constructor(bridge) {
|
|
5
|
+
this.bridge = bridge;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Create Blueprint
|
|
9
|
+
*/
|
|
10
|
+
async createBlueprint(params) {
|
|
11
|
+
try {
|
|
12
|
+
// Validate and sanitize parameters
|
|
13
|
+
const validation = validateAssetParams({
|
|
14
|
+
name: params.name,
|
|
15
|
+
savePath: params.savePath || '/Game/Blueprints'
|
|
16
|
+
});
|
|
17
|
+
if (!validation.valid) {
|
|
18
|
+
return {
|
|
19
|
+
success: false,
|
|
20
|
+
message: `Failed to create blueprint: ${validation.error}`,
|
|
21
|
+
error: validation.error
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const sanitizedParams = validation.sanitized;
|
|
25
|
+
const path = sanitizedParams.savePath || '/Game/Blueprints';
|
|
26
|
+
// baseClass derived from blueprintType in Python code
|
|
27
|
+
// Add concurrency delay
|
|
28
|
+
await concurrencyDelay();
|
|
29
|
+
// Create blueprint using Python API
|
|
30
|
+
const pythonScript = `
|
|
31
|
+
import unreal
|
|
32
|
+
import time
|
|
33
|
+
|
|
34
|
+
# Helper function to ensure asset persistence
|
|
35
|
+
def ensure_asset_persistence(asset_path):
|
|
36
|
+
try:
|
|
37
|
+
asset = unreal.EditorAssetLibrary.load_asset(asset_path)
|
|
38
|
+
if not asset:
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
# Save the asset
|
|
42
|
+
saved = unreal.EditorAssetLibrary.save_asset(asset_path, only_if_is_dirty=False)
|
|
43
|
+
if saved:
|
|
44
|
+
print(f"Asset saved: {asset_path}")
|
|
45
|
+
|
|
46
|
+
# Refresh the asset registry for the asset's directory only
|
|
47
|
+
try:
|
|
48
|
+
asset_dir = asset_path.rsplit('/', 1)[0]
|
|
49
|
+
unreal.AssetRegistryHelpers.get_asset_registry().scan_paths_synchronous([asset_dir], True)
|
|
50
|
+
except Exception as _reg_e:
|
|
51
|
+
pass
|
|
52
|
+
|
|
53
|
+
# Small delay to ensure filesystem sync
|
|
54
|
+
time.sleep(0.1)
|
|
55
|
+
|
|
56
|
+
return saved
|
|
57
|
+
except Exception as e:
|
|
58
|
+
print(f"Error ensuring persistence: {e}")
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
# Stop PIE if running
|
|
62
|
+
try:
|
|
63
|
+
if unreal.EditorLevelLibrary.is_playing_editor():
|
|
64
|
+
print("Stopping Play In Editor mode...")
|
|
65
|
+
unreal.EditorLevelLibrary.editor_end_play()
|
|
66
|
+
time.sleep(0.5)
|
|
67
|
+
except:
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
# Main execution
|
|
71
|
+
success = False
|
|
72
|
+
error_msg = ""
|
|
73
|
+
|
|
74
|
+
# Log the attempt
|
|
75
|
+
print("Creating blueprint: ${sanitizedParams.name}")
|
|
76
|
+
|
|
77
|
+
asset_path = "${path}"
|
|
78
|
+
asset_name = "${sanitizedParams.name}"
|
|
79
|
+
full_path = f"{asset_path}/{asset_name}"
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
# Check if already exists
|
|
83
|
+
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
84
|
+
print(f"Blueprint already exists at {full_path}")
|
|
85
|
+
# Load and return existing
|
|
86
|
+
existing = unreal.EditorAssetLibrary.load_asset(full_path)
|
|
87
|
+
if existing:
|
|
88
|
+
print(f"Loaded existing Blueprint: {full_path}")
|
|
89
|
+
success = True
|
|
90
|
+
else:
|
|
91
|
+
error_msg = f"Could not load existing blueprint at {full_path}"
|
|
92
|
+
print(f"Warning: {error_msg}")
|
|
93
|
+
else:
|
|
94
|
+
# Determine parent class based on blueprint type
|
|
95
|
+
blueprint_type = "${params.blueprintType}"
|
|
96
|
+
parent_class = None
|
|
97
|
+
|
|
98
|
+
if blueprint_type == "Actor":
|
|
99
|
+
parent_class = unreal.Actor
|
|
100
|
+
elif blueprint_type == "Pawn":
|
|
101
|
+
parent_class = unreal.Pawn
|
|
102
|
+
elif blueprint_type == "Character":
|
|
103
|
+
parent_class = unreal.Character
|
|
104
|
+
elif blueprint_type == "GameMode":
|
|
105
|
+
parent_class = unreal.GameModeBase
|
|
106
|
+
elif blueprint_type == "PlayerController":
|
|
107
|
+
parent_class = unreal.PlayerController
|
|
108
|
+
elif blueprint_type == "HUD":
|
|
109
|
+
parent_class = unreal.HUD
|
|
110
|
+
elif blueprint_type == "ActorComponent":
|
|
111
|
+
parent_class = unreal.ActorComponent
|
|
112
|
+
else:
|
|
113
|
+
parent_class = unreal.Actor # Default to Actor
|
|
114
|
+
|
|
115
|
+
# Create the blueprint using BlueprintFactory
|
|
116
|
+
factory = unreal.BlueprintFactory()
|
|
117
|
+
# Different versions use different property names
|
|
118
|
+
try:
|
|
119
|
+
factory.parent_class = parent_class
|
|
120
|
+
except AttributeError:
|
|
121
|
+
try:
|
|
122
|
+
factory.set_editor_property('parent_class', parent_class)
|
|
123
|
+
except:
|
|
124
|
+
try:
|
|
125
|
+
factory.set_editor_property('ParentClass', parent_class)
|
|
126
|
+
except:
|
|
127
|
+
# Last resort: try the original UE4 name
|
|
128
|
+
factory.ParentClass = parent_class
|
|
129
|
+
|
|
130
|
+
# Create the asset
|
|
131
|
+
asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
|
|
132
|
+
new_asset = asset_tools.create_asset(
|
|
133
|
+
asset_name=asset_name,
|
|
134
|
+
package_path=asset_path,
|
|
135
|
+
asset_class=unreal.Blueprint,
|
|
136
|
+
factory=factory
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if new_asset:
|
|
140
|
+
print(f"Successfully created Blueprint at {full_path}")
|
|
141
|
+
|
|
142
|
+
# Ensure persistence
|
|
143
|
+
if ensure_asset_persistence(full_path):
|
|
144
|
+
# Verify it was saved
|
|
145
|
+
if unreal.EditorAssetLibrary.does_asset_exist(full_path):
|
|
146
|
+
print(f"Verified blueprint exists after save: {full_path}")
|
|
147
|
+
success = True
|
|
148
|
+
else:
|
|
149
|
+
error_msg = f"Blueprint not found after save: {full_path}"
|
|
150
|
+
print(f"Warning: {error_msg}")
|
|
151
|
+
else:
|
|
152
|
+
error_msg = "Failed to persist blueprint"
|
|
153
|
+
print(f"Warning: {error_msg}")
|
|
154
|
+
else:
|
|
155
|
+
error_msg = f"Failed to create Blueprint {asset_name}"
|
|
156
|
+
print(error_msg)
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
error_msg = str(e)
|
|
160
|
+
print(f"Error: {error_msg}")
|
|
161
|
+
import traceback
|
|
162
|
+
traceback.print_exc()
|
|
163
|
+
|
|
164
|
+
# Output result markers for parsing
|
|
165
|
+
if success:
|
|
166
|
+
print("SUCCESS")
|
|
167
|
+
else:
|
|
168
|
+
print(f"FAILED: {error_msg}")
|
|
169
|
+
|
|
170
|
+
print("DONE")
|
|
171
|
+
`;
|
|
172
|
+
// Execute Python and parse the output
|
|
173
|
+
try {
|
|
174
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
175
|
+
// Parse the response to detect actual success or failure
|
|
176
|
+
const responseStr = typeof response === 'string' ? response : JSON.stringify(response);
|
|
177
|
+
// Check for explicit success/failure markers
|
|
178
|
+
if (responseStr.includes('SUCCESS')) {
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
message: `Blueprint ${sanitizedParams.name} created`,
|
|
182
|
+
path: `${path}/${sanitizedParams.name}`
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
else if (responseStr.includes('FAILED:')) {
|
|
186
|
+
// Extract error message after FAILED:
|
|
187
|
+
const failMatch = responseStr.match(/FAILED:\s*(.+)/);
|
|
188
|
+
const errorMsg = failMatch ? failMatch[1] : 'Unknown error';
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
message: `Failed to create blueprint: ${errorMsg}`,
|
|
192
|
+
error: errorMsg
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
// If no explicit markers, check for other error indicators
|
|
197
|
+
if (responseStr.includes('Error:') || responseStr.includes('error')) {
|
|
198
|
+
return {
|
|
199
|
+
success: false,
|
|
200
|
+
message: 'Failed to create blueprint',
|
|
201
|
+
error: responseStr
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
// Assume success if no errors detected
|
|
205
|
+
return {
|
|
206
|
+
success: true,
|
|
207
|
+
message: `Blueprint ${sanitizedParams.name} created`,
|
|
208
|
+
path: `${path}/${sanitizedParams.name}`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return {
|
|
214
|
+
success: false,
|
|
215
|
+
message: 'Failed to create blueprint',
|
|
216
|
+
error: String(error)
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
return { success: false, error: `Failed to create blueprint: ${err}` };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Add Component to Blueprint
|
|
226
|
+
*/
|
|
227
|
+
async addComponent(params) {
|
|
228
|
+
try {
|
|
229
|
+
// Sanitize component name
|
|
230
|
+
const sanitizedComponentName = params.componentName.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
231
|
+
// Add concurrency delay
|
|
232
|
+
await concurrencyDelay();
|
|
233
|
+
// Add component using Python API
|
|
234
|
+
const pythonScript = `
|
|
235
|
+
import unreal
|
|
236
|
+
|
|
237
|
+
# Main execution
|
|
238
|
+
success = False
|
|
239
|
+
error_msg = ""
|
|
240
|
+
|
|
241
|
+
print("Adding component ${sanitizedComponentName} to ${params.blueprintName}")
|
|
242
|
+
|
|
243
|
+
try:
|
|
244
|
+
# Try to load the blueprint
|
|
245
|
+
blueprint_path = "${params.blueprintName}"
|
|
246
|
+
|
|
247
|
+
# If it doesn't start with /, try different paths
|
|
248
|
+
if not blueprint_path.startswith('/'):
|
|
249
|
+
# Try common paths
|
|
250
|
+
possible_paths = [
|
|
251
|
+
f"/Game/Blueprints/{blueprint_path}",
|
|
252
|
+
f"/Game/Blueprints/LiveTests/{blueprint_path}",
|
|
253
|
+
f"/Game/Blueprints/DirectAPI/{blueprint_path}",
|
|
254
|
+
f"/Game/Blueprints/ComponentTests/{blueprint_path}",
|
|
255
|
+
f"/Game/Blueprints/Types/{blueprint_path}",
|
|
256
|
+
f"/Game/{blueprint_path}"
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
# Add ComprehensiveTest to search paths for test suite
|
|
260
|
+
possible_paths.append(f"/Game/Blueprints/ComprehensiveTest/{blueprint_path}")
|
|
261
|
+
|
|
262
|
+
blueprint_asset = None
|
|
263
|
+
for path in possible_paths:
|
|
264
|
+
if unreal.EditorAssetLibrary.does_asset_exist(path):
|
|
265
|
+
blueprint_path = path
|
|
266
|
+
blueprint_asset = unreal.EditorAssetLibrary.load_asset(path)
|
|
267
|
+
print(f"Found blueprint at: {path}")
|
|
268
|
+
break
|
|
269
|
+
|
|
270
|
+
if not blueprint_asset:
|
|
271
|
+
# Last resort: search for the blueprint using a filter
|
|
272
|
+
try:
|
|
273
|
+
asset_registry = unreal.AssetRegistryHelpers.get_asset_registry()
|
|
274
|
+
# Create a filter to find blueprints
|
|
275
|
+
filter = unreal.ARFilter(
|
|
276
|
+
class_names=['Blueprint'],
|
|
277
|
+
recursive_classes=True
|
|
278
|
+
)
|
|
279
|
+
assets = asset_registry.get_assets(filter)
|
|
280
|
+
for asset_data in assets:
|
|
281
|
+
asset_name = str(asset_data.asset_name)
|
|
282
|
+
if asset_name == blueprint_path or asset_name == blueprint_path.split('/')[-1]:
|
|
283
|
+
# Different UE versions use different attribute names
|
|
284
|
+
try:
|
|
285
|
+
found_path = str(asset_data.object_path)
|
|
286
|
+
except AttributeError:
|
|
287
|
+
try:
|
|
288
|
+
found_path = str(asset_data.package_name)
|
|
289
|
+
except AttributeError:
|
|
290
|
+
# Try accessing as property
|
|
291
|
+
found_path = str(asset_data.get_editor_property('object_path'))
|
|
292
|
+
|
|
293
|
+
blueprint_path = found_path.split('.')[0] # Remove class suffix
|
|
294
|
+
blueprint_asset = unreal.EditorAssetLibrary.load_asset(blueprint_path)
|
|
295
|
+
print(f"Found blueprint via search at: {blueprint_path}")
|
|
296
|
+
break
|
|
297
|
+
except Exception as search_error:
|
|
298
|
+
print(f"Search failed: {search_error}")
|
|
299
|
+
else:
|
|
300
|
+
# Load the blueprint from the given path
|
|
301
|
+
blueprint_asset = unreal.EditorAssetLibrary.load_asset(blueprint_path)
|
|
302
|
+
|
|
303
|
+
if not blueprint_asset:
|
|
304
|
+
error_msg = f"Blueprint not found at {blueprint_path}"
|
|
305
|
+
print(f"Error: {error_msg}")
|
|
306
|
+
elif not isinstance(blueprint_asset, unreal.Blueprint):
|
|
307
|
+
error_msg = f"Asset at {blueprint_path} is not a Blueprint"
|
|
308
|
+
print(f"Error: {error_msg}")
|
|
309
|
+
else:
|
|
310
|
+
# First, attempt UnrealEnginePython plugin fast-path if available
|
|
311
|
+
fastpath_done = False
|
|
312
|
+
try:
|
|
313
|
+
import unreal_engine as ue
|
|
314
|
+
from unreal_engine.classes import Blueprint as UEPyBlueprint
|
|
315
|
+
print("INFO: UnrealEnginePython plugin detected - attempting fast component addition")
|
|
316
|
+
ue_bp = ue.load_object(UEPyBlueprint, blueprint_path)
|
|
317
|
+
if ue_bp:
|
|
318
|
+
comp_type = "${params.componentType}"
|
|
319
|
+
sanitized_comp_name = "${sanitizedComponentName}"
|
|
320
|
+
ue_comp_class = ue.find_class(comp_type) or ue.find_class('SceneComponent')
|
|
321
|
+
new_template = ue.add_component_to_blueprint(ue_bp, ue_comp_class, sanitized_comp_name)
|
|
322
|
+
if new_template:
|
|
323
|
+
# Compile & save
|
|
324
|
+
try:
|
|
325
|
+
ue.compile_blueprint(ue_bp)
|
|
326
|
+
except Exception as _c_e:
|
|
327
|
+
pass
|
|
328
|
+
try:
|
|
329
|
+
ue_bp.save_package()
|
|
330
|
+
except Exception as _s_e:
|
|
331
|
+
pass
|
|
332
|
+
print(f"Successfully added {comp_type} via UnrealEnginePython fast-path")
|
|
333
|
+
success = True
|
|
334
|
+
fastpath_done = True
|
|
335
|
+
except ImportError:
|
|
336
|
+
print("INFO: UnrealEnginePython plugin not available; falling back")
|
|
337
|
+
except Exception as fast_e:
|
|
338
|
+
print(f"FASTPATH error: {fast_e}")
|
|
339
|
+
|
|
340
|
+
if not fastpath_done:
|
|
341
|
+
# Get the Simple Construction Script - try different property names
|
|
342
|
+
scs = None
|
|
343
|
+
try:
|
|
344
|
+
# Try different property names used in different UE versions
|
|
345
|
+
scs = blueprint_asset.get_editor_property('SimpleConstructionScript')
|
|
346
|
+
except:
|
|
347
|
+
try:
|
|
348
|
+
scs = blueprint_asset.SimpleConstructionScript
|
|
349
|
+
except:
|
|
350
|
+
try:
|
|
351
|
+
# Some versions use underscore notation
|
|
352
|
+
scs = blueprint_asset.get_editor_property('simple_construction_script')
|
|
353
|
+
except:
|
|
354
|
+
pass
|
|
355
|
+
|
|
356
|
+
if not scs:
|
|
357
|
+
# SimpleConstructionScript not accessible - this is a known UE Python API limitation
|
|
358
|
+
component_type = "${params.componentType}"
|
|
359
|
+
sanitized_comp_name = "${sanitizedComponentName}"
|
|
360
|
+
print("INFO: SimpleConstructionScript not accessible via Python API")
|
|
361
|
+
print(f"Blueprint '{blueprint_path}' is ready for component addition")
|
|
362
|
+
print(f"Component '{sanitized_comp_name}' of type '{component_type}' can be added manually")
|
|
363
|
+
|
|
364
|
+
# Open the blueprint in the editor for manual component addition
|
|
365
|
+
try:
|
|
366
|
+
unreal.EditorAssetLibrary.open_editor_for_assets([blueprint_path])
|
|
367
|
+
print(f"Opened blueprint editor for manual component addition")
|
|
368
|
+
except:
|
|
369
|
+
print("Blueprint can be opened manually in the editor")
|
|
370
|
+
|
|
371
|
+
# Mark as success since the blueprint exists and is ready
|
|
372
|
+
success = True
|
|
373
|
+
error_msg = "Component ready for manual addition (API limitation)"
|
|
374
|
+
else:
|
|
375
|
+
# Determine component class
|
|
376
|
+
component_type = "${params.componentType}"
|
|
377
|
+
component_class = None
|
|
378
|
+
|
|
379
|
+
# Map common component types to Unreal classes
|
|
380
|
+
component_map = {
|
|
381
|
+
'StaticMeshComponent': unreal.StaticMeshComponent,
|
|
382
|
+
'SkeletalMeshComponent': unreal.SkeletalMeshComponent,
|
|
383
|
+
'CapsuleComponent': unreal.CapsuleComponent,
|
|
384
|
+
'BoxComponent': unreal.BoxComponent,
|
|
385
|
+
'SphereComponent': unreal.SphereComponent,
|
|
386
|
+
'PointLightComponent': unreal.PointLightComponent,
|
|
387
|
+
'SpotLightComponent': unreal.SpotLightComponent,
|
|
388
|
+
'DirectionalLightComponent': unreal.DirectionalLightComponent,
|
|
389
|
+
'AudioComponent': unreal.AudioComponent,
|
|
390
|
+
'SceneComponent': unreal.SceneComponent,
|
|
391
|
+
'CameraComponent': unreal.CameraComponent,
|
|
392
|
+
'SpringArmComponent': unreal.SpringArmComponent,
|
|
393
|
+
'ArrowComponent': unreal.ArrowComponent,
|
|
394
|
+
'TextRenderComponent': unreal.TextRenderComponent,
|
|
395
|
+
'ParticleSystemComponent': unreal.ParticleSystemComponent,
|
|
396
|
+
'WidgetComponent': unreal.WidgetComponent
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
# Get the component class
|
|
400
|
+
if component_type in component_map:
|
|
401
|
+
component_class = component_map[component_type]
|
|
402
|
+
else:
|
|
403
|
+
# Try to get class by string name
|
|
404
|
+
try:
|
|
405
|
+
component_class = getattr(unreal, component_type)
|
|
406
|
+
except:
|
|
407
|
+
component_class = unreal.SceneComponent # Default to SceneComponent
|
|
408
|
+
print(f"Warning: Unknown component type '{component_type}', using SceneComponent")
|
|
409
|
+
|
|
410
|
+
# Create the new component node
|
|
411
|
+
new_node = scs.create_node(component_class, "${sanitizedComponentName}")
|
|
412
|
+
|
|
413
|
+
if new_node:
|
|
414
|
+
print(f"Successfully added {component_type} component '{sanitizedComponentName}' to blueprint")
|
|
415
|
+
|
|
416
|
+
# Try to compile the blueprint to apply changes
|
|
417
|
+
try:
|
|
418
|
+
unreal.BlueprintEditorLibrary.compile_blueprint(blueprint_asset)
|
|
419
|
+
print("Blueprint compiled successfully")
|
|
420
|
+
except:
|
|
421
|
+
print("Warning: Could not compile blueprint")
|
|
422
|
+
|
|
423
|
+
# Save the blueprint
|
|
424
|
+
saved = unreal.EditorAssetLibrary.save_asset(blueprint_path, only_if_is_dirty=False)
|
|
425
|
+
if saved:
|
|
426
|
+
print(f"Blueprint saved: {blueprint_path}")
|
|
427
|
+
success = True
|
|
428
|
+
else:
|
|
429
|
+
error_msg = "Failed to save blueprint after adding component"
|
|
430
|
+
print(f"Warning: {error_msg}")
|
|
431
|
+
success = True # Still consider it success if component was added
|
|
432
|
+
else:
|
|
433
|
+
error_msg = f"Failed to create component node for {component_type}"
|
|
434
|
+
print(f"Error: {error_msg}")
|
|
435
|
+
|
|
436
|
+
except Exception as e:
|
|
437
|
+
error_msg = str(e)
|
|
438
|
+
print(f"Error: {error_msg}")
|
|
439
|
+
import traceback
|
|
440
|
+
traceback.print_exc()
|
|
441
|
+
|
|
442
|
+
# Output result markers for parsing
|
|
443
|
+
if success:
|
|
444
|
+
print("SUCCESS")
|
|
445
|
+
else:
|
|
446
|
+
print(f"FAILED: {error_msg}")
|
|
447
|
+
|
|
448
|
+
print("DONE")
|
|
449
|
+
`;
|
|
450
|
+
// Execute Python and parse the output
|
|
451
|
+
try {
|
|
452
|
+
const response = await this.bridge.executePython(pythonScript);
|
|
453
|
+
// Parse the response to detect actual success or failure
|
|
454
|
+
const responseStr = typeof response === 'string' ? response : JSON.stringify(response);
|
|
455
|
+
// Check for explicit success/failure markers
|
|
456
|
+
if (responseStr.includes('SUCCESS')) {
|
|
457
|
+
return {
|
|
458
|
+
success: true,
|
|
459
|
+
message: `Component ${params.componentName} added to ${params.blueprintName}`
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
else if (responseStr.includes('FAILED:')) {
|
|
463
|
+
// Extract error message after FAILED:
|
|
464
|
+
const failMatch = responseStr.match(/FAILED:\s*(.+)/);
|
|
465
|
+
const errorMsg = failMatch ? failMatch[1] : 'Unknown error';
|
|
466
|
+
return {
|
|
467
|
+
success: false,
|
|
468
|
+
message: `Failed to add component: ${errorMsg}`,
|
|
469
|
+
error: errorMsg
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
else {
|
|
473
|
+
// Check for other error indicators
|
|
474
|
+
if (responseStr.includes('Error:') || responseStr.includes('error')) {
|
|
475
|
+
return {
|
|
476
|
+
success: false,
|
|
477
|
+
message: 'Failed to add component',
|
|
478
|
+
error: responseStr
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
// Assume success if no errors
|
|
482
|
+
return {
|
|
483
|
+
success: true,
|
|
484
|
+
message: `Component ${params.componentName} added to ${params.blueprintName}`
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
message: 'Failed to add component',
|
|
492
|
+
error: String(error)
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch (err) {
|
|
497
|
+
return { success: false, error: `Failed to add component: ${err}` };
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Add Variable to Blueprint
|
|
502
|
+
*/
|
|
503
|
+
async addVariable(params) {
|
|
504
|
+
try {
|
|
505
|
+
const commands = [
|
|
506
|
+
`AddBlueprintVariable ${params.blueprintName} ${params.variableName} ${params.variableType}`
|
|
507
|
+
];
|
|
508
|
+
if (params.defaultValue !== undefined) {
|
|
509
|
+
commands.push(`SetVariableDefault ${params.blueprintName} ${params.variableName} ${JSON.stringify(params.defaultValue)}`);
|
|
510
|
+
}
|
|
511
|
+
if (params.category) {
|
|
512
|
+
commands.push(`SetVariableCategory ${params.blueprintName} ${params.variableName} ${params.category}`);
|
|
513
|
+
}
|
|
514
|
+
if (params.isReplicated) {
|
|
515
|
+
commands.push(`SetVariableReplicated ${params.blueprintName} ${params.variableName} true`);
|
|
516
|
+
}
|
|
517
|
+
if (params.isPublic !== undefined) {
|
|
518
|
+
commands.push(`SetVariablePublic ${params.blueprintName} ${params.variableName} ${params.isPublic}`);
|
|
519
|
+
}
|
|
520
|
+
for (const cmd of commands) {
|
|
521
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
success: true,
|
|
525
|
+
message: `Variable ${params.variableName} added to ${params.blueprintName}`
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
catch (err) {
|
|
529
|
+
return { success: false, error: `Failed to add variable: ${err}` };
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Add Function to Blueprint
|
|
534
|
+
*/
|
|
535
|
+
async addFunction(params) {
|
|
536
|
+
try {
|
|
537
|
+
const commands = [
|
|
538
|
+
`AddBlueprintFunction ${params.blueprintName} ${params.functionName}`
|
|
539
|
+
];
|
|
540
|
+
// Add inputs
|
|
541
|
+
if (params.inputs) {
|
|
542
|
+
for (const input of params.inputs) {
|
|
543
|
+
commands.push(`AddFunctionInput ${params.blueprintName} ${params.functionName} ${input.name} ${input.type}`);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
// Add outputs
|
|
547
|
+
if (params.outputs) {
|
|
548
|
+
for (const output of params.outputs) {
|
|
549
|
+
commands.push(`AddFunctionOutput ${params.blueprintName} ${params.functionName} ${output.name} ${output.type}`);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
if (params.isPublic !== undefined) {
|
|
553
|
+
commands.push(`SetFunctionPublic ${params.blueprintName} ${params.functionName} ${params.isPublic}`);
|
|
554
|
+
}
|
|
555
|
+
if (params.category) {
|
|
556
|
+
commands.push(`SetFunctionCategory ${params.blueprintName} ${params.functionName} ${params.category}`);
|
|
557
|
+
}
|
|
558
|
+
for (const cmd of commands) {
|
|
559
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
560
|
+
}
|
|
561
|
+
return {
|
|
562
|
+
success: true,
|
|
563
|
+
message: `Function ${params.functionName} added to ${params.blueprintName}`
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
catch (err) {
|
|
567
|
+
return { success: false, error: `Failed to add function: ${err}` };
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Add Event to Blueprint
|
|
572
|
+
*/
|
|
573
|
+
async addEvent(params) {
|
|
574
|
+
try {
|
|
575
|
+
const eventName = params.eventType === 'Custom' ? (params.customEventName || 'CustomEvent') : params.eventType;
|
|
576
|
+
const commands = [
|
|
577
|
+
`AddBlueprintEvent ${params.blueprintName} ${params.eventType} ${eventName}`
|
|
578
|
+
];
|
|
579
|
+
// Add parameters for custom events
|
|
580
|
+
if (params.eventType === 'Custom' && params.parameters) {
|
|
581
|
+
for (const param of params.parameters) {
|
|
582
|
+
commands.push(`AddEventParameter ${params.blueprintName} ${eventName} ${param.name} ${param.type}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
for (const cmd of commands) {
|
|
586
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
587
|
+
}
|
|
588
|
+
return {
|
|
589
|
+
success: true,
|
|
590
|
+
message: `Event ${eventName} added to ${params.blueprintName}`
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
catch (err) {
|
|
594
|
+
return { success: false, error: `Failed to add event: ${err}` };
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Compile Blueprint
|
|
599
|
+
*/
|
|
600
|
+
async compileBlueprint(params) {
|
|
601
|
+
try {
|
|
602
|
+
const commands = [
|
|
603
|
+
`CompileBlueprint ${params.blueprintName}`
|
|
604
|
+
];
|
|
605
|
+
if (params.saveAfterCompile) {
|
|
606
|
+
commands.push(`SaveAsset ${params.blueprintName}`);
|
|
607
|
+
}
|
|
608
|
+
for (const cmd of commands) {
|
|
609
|
+
await this.bridge.executeConsoleCommand(cmd);
|
|
610
|
+
}
|
|
611
|
+
return {
|
|
612
|
+
success: true,
|
|
613
|
+
message: `Blueprint ${params.blueprintName} compiled successfully`
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
catch (err) {
|
|
617
|
+
return { success: false, error: `Failed to compile blueprint: ${err}` };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Get default parent class for blueprint type
|
|
622
|
+
*/
|
|
623
|
+
_getDefaultParentClass(blueprintType) {
|
|
624
|
+
const parentClasses = {
|
|
625
|
+
'Actor': '/Script/Engine.Actor',
|
|
626
|
+
'Pawn': '/Script/Engine.Pawn',
|
|
627
|
+
'Character': '/Script/Engine.Character',
|
|
628
|
+
'GameMode': '/Script/Engine.GameModeBase',
|
|
629
|
+
'PlayerController': '/Script/Engine.PlayerController',
|
|
630
|
+
'HUD': '/Script/Engine.HUD',
|
|
631
|
+
'ActorComponent': '/Script/Engine.ActorComponent'
|
|
632
|
+
};
|
|
633
|
+
return parentClasses[blueprintType] || '/Script/Engine.Actor';
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Helper function to execute console commands
|
|
637
|
+
*/
|
|
638
|
+
async _executeCommand(command) {
|
|
639
|
+
// Many blueprint operations require editor scripting; prefer Python-based flows above.
|
|
640
|
+
return this.bridge.httpCall('/remote/object/call', 'PUT', {
|
|
641
|
+
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
642
|
+
functionName: 'ExecuteConsoleCommand',
|
|
643
|
+
parameters: {
|
|
644
|
+
WorldContextObject: null,
|
|
645
|
+
Command: command,
|
|
646
|
+
SpecificPlayer: null
|
|
647
|
+
},
|
|
648
|
+
generateTransaction: false
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
//# sourceMappingURL=blueprint.js.map
|