unreal-engine-mcp-server 0.4.0 → 0.4.4
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 +3 -2
- 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/dist/tools/lighting.d.ts
CHANGED
|
@@ -2,9 +2,8 @@ import { UnrealBridge } from '../unreal-bridge.js';
|
|
|
2
2
|
export declare class LightingTools {
|
|
3
3
|
private bridge;
|
|
4
4
|
constructor(bridge: UnrealBridge);
|
|
5
|
-
private escapePythonString;
|
|
6
5
|
private ensurePythonSpawnSucceeded;
|
|
7
|
-
private
|
|
6
|
+
private normalizeName;
|
|
8
7
|
createDirectionalLight(params: {
|
|
9
8
|
name: string;
|
|
10
9
|
intensity?: number;
|
|
@@ -62,12 +61,24 @@ export declare class LightingTools {
|
|
|
62
61
|
recapture?: boolean;
|
|
63
62
|
}): Promise<{
|
|
64
63
|
success: boolean;
|
|
64
|
+
error: string;
|
|
65
65
|
message: string;
|
|
66
|
+
warnings?: undefined;
|
|
67
|
+
} | {
|
|
68
|
+
success: boolean;
|
|
69
|
+
message: string;
|
|
70
|
+
warnings: any[] | undefined;
|
|
66
71
|
error?: undefined;
|
|
67
72
|
} | {
|
|
68
73
|
success: boolean;
|
|
69
|
-
error:
|
|
74
|
+
error: string;
|
|
75
|
+
warnings: any[] | undefined;
|
|
70
76
|
message?: undefined;
|
|
77
|
+
} | {
|
|
78
|
+
success: boolean;
|
|
79
|
+
message: string;
|
|
80
|
+
error?: undefined;
|
|
81
|
+
warnings?: undefined;
|
|
71
82
|
}>;
|
|
72
83
|
ensureSingleSkyLight(params?: {
|
|
73
84
|
name?: string;
|
package/dist/tools/lighting.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
|
+
import { parseStandardResult } from '../utils/python-output.js';
|
|
2
|
+
import { escapePythonString } from '../utils/python.js';
|
|
1
3
|
export class LightingTools {
|
|
2
4
|
bridge;
|
|
3
5
|
constructor(bridge) {
|
|
4
6
|
this.bridge = bridge;
|
|
5
7
|
}
|
|
6
|
-
// Helper to safely escape strings for Python
|
|
7
|
-
escapePythonString(str) {
|
|
8
|
-
// Escape backslashes first, then quotes
|
|
9
|
-
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
10
|
-
}
|
|
11
8
|
ensurePythonSpawnSucceeded(label, result) {
|
|
12
9
|
let logs = '';
|
|
13
10
|
if (Array.isArray(result?.LogOutput)) {
|
|
@@ -30,25 +27,25 @@ export class LightingTools {
|
|
|
30
27
|
// Otherwise, uncertain
|
|
31
28
|
throw new Error(`Uncertain spawn result for '${label}'. Engine logs:\n${logs}`);
|
|
32
29
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
30
|
+
normalizeName(value, fallback) {
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
if (trimmed.length > 0) {
|
|
34
|
+
return trimmed;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (typeof fallback === 'string') {
|
|
38
|
+
const trimmedFallback = fallback.trim();
|
|
39
|
+
if (trimmedFallback.length > 0) {
|
|
40
|
+
return trimmedFallback;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
throw new Error('Invalid name: must be a non-empty string');
|
|
45
44
|
}
|
|
46
45
|
// Create directional light
|
|
47
46
|
async createDirectionalLight(params) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
throw new Error('Invalid name: must be a non-empty string');
|
|
51
|
-
}
|
|
47
|
+
const name = this.normalizeName(params.name);
|
|
48
|
+
const escapedName = escapePythonString(name);
|
|
52
49
|
// Validate numeric parameters
|
|
53
50
|
if (params.intensity !== undefined) {
|
|
54
51
|
if (typeof params.intensity !== 'number' || !isFinite(params.intensity)) {
|
|
@@ -115,36 +112,34 @@ spawn_rotation = unreal.Rotator(${rot[0]}, ${rot[1]}, ${rot[2]})
|
|
|
115
112
|
|
|
116
113
|
# Spawn the actor
|
|
117
114
|
spawned_light = editor_actor_subsystem.spawn_actor_from_class(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
directional_light_class,
|
|
116
|
+
spawn_location,
|
|
117
|
+
spawn_rotation
|
|
121
118
|
)
|
|
122
119
|
|
|
123
120
|
if spawned_light:
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
# Set the label/name
|
|
122
|
+
spawned_light.set_actor_label("${escapedName}")
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
# Get the light component
|
|
125
|
+
light_component = spawned_light.get_component_by_class(unreal.DirectionalLightComponent)
|
|
129
126
|
|
|
130
|
-
|
|
127
|
+
if light_component:
|
|
131
128
|
${propertiesCode}
|
|
132
129
|
|
|
133
|
-
|
|
130
|
+
print("Directional light '${escapedName}' spawned")
|
|
134
131
|
else:
|
|
135
|
-
|
|
132
|
+
print("Failed to spawn directional light '${escapedName}'")
|
|
136
133
|
`;
|
|
137
134
|
// Execute the Python script via bridge (UE 5.6-compatible)
|
|
138
135
|
const result = await this.bridge.executePython(pythonScript);
|
|
139
|
-
this.ensurePythonSpawnSucceeded(
|
|
140
|
-
return { success: true, message: `Directional light '${
|
|
136
|
+
this.ensurePythonSpawnSucceeded(name, result);
|
|
137
|
+
return { success: true, message: `Directional light '${name}' spawned` };
|
|
141
138
|
}
|
|
142
139
|
// Create point light
|
|
143
140
|
async createPointLight(params) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
throw new Error('Invalid name: must be a non-empty string');
|
|
147
|
-
}
|
|
141
|
+
const name = this.normalizeName(params.name);
|
|
142
|
+
const escapedName = escapePythonString(name);
|
|
148
143
|
// Validate location array
|
|
149
144
|
if (params.location !== undefined) {
|
|
150
145
|
if (!Array.isArray(params.location) || params.location.length !== 3) {
|
|
@@ -224,36 +219,34 @@ spawn_rotation = unreal.Rotator(0, 0, 0)
|
|
|
224
219
|
|
|
225
220
|
# Spawn the actor
|
|
226
221
|
spawned_light = editor_actor_subsystem.spawn_actor_from_class(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
222
|
+
point_light_class,
|
|
223
|
+
spawn_location,
|
|
224
|
+
spawn_rotation
|
|
230
225
|
)
|
|
231
226
|
|
|
232
227
|
if spawned_light:
|
|
233
|
-
|
|
234
|
-
|
|
228
|
+
# Set the label/name
|
|
229
|
+
spawned_light.set_actor_label("${escapedName}")
|
|
235
230
|
|
|
236
|
-
|
|
237
|
-
|
|
231
|
+
# Get the light component
|
|
232
|
+
light_component = spawned_light.get_component_by_class(unreal.PointLightComponent)
|
|
238
233
|
|
|
239
|
-
|
|
234
|
+
if light_component:
|
|
240
235
|
${propertiesCode}
|
|
241
236
|
|
|
242
|
-
|
|
237
|
+
print(f"Point light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
|
|
243
238
|
else:
|
|
244
|
-
|
|
239
|
+
print("Failed to spawn point light '${escapedName}'")
|
|
245
240
|
`;
|
|
246
241
|
// Execute the Python script via bridge (UE 5.6-compatible)
|
|
247
242
|
const result = await this.bridge.executePython(pythonScript);
|
|
248
|
-
this.ensurePythonSpawnSucceeded(
|
|
249
|
-
return { success: true, message: `Point light '${
|
|
243
|
+
this.ensurePythonSpawnSucceeded(name, result);
|
|
244
|
+
return { success: true, message: `Point light '${name}' spawned at ${location.join(', ')}` };
|
|
250
245
|
}
|
|
251
246
|
// Create spot light
|
|
252
247
|
async createSpotLight(params) {
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
throw new Error('Invalid name: must be a non-empty string');
|
|
256
|
-
}
|
|
248
|
+
const name = this.normalizeName(params.name);
|
|
249
|
+
const escapedName = escapePythonString(name);
|
|
257
250
|
// Validate required location and rotation arrays
|
|
258
251
|
if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
|
|
259
252
|
throw new Error('Invalid location: must be an array [x,y,z]');
|
|
@@ -358,7 +351,7 @@ spawned_light = editor_actor_subsystem.spawn_actor_from_class(
|
|
|
358
351
|
|
|
359
352
|
if spawned_light:
|
|
360
353
|
# Set the label/name
|
|
361
|
-
spawned_light.set_actor_label("${
|
|
354
|
+
spawned_light.set_actor_label("${escapedName}")
|
|
362
355
|
|
|
363
356
|
# Get the light component
|
|
364
357
|
light_component = spawned_light.get_component_by_class(unreal.SpotLightComponent)
|
|
@@ -366,21 +359,19 @@ if spawned_light:
|
|
|
366
359
|
if light_component:
|
|
367
360
|
${propertiesCode}
|
|
368
361
|
|
|
369
|
-
print(f"Spot light '${
|
|
362
|
+
print(f"Spot light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
|
|
370
363
|
else:
|
|
371
|
-
print("Failed to spawn spot light '${
|
|
364
|
+
print("Failed to spawn spot light '${escapedName}'")
|
|
372
365
|
`;
|
|
373
366
|
// Execute the Python script via bridge (UE 5.6-compatible)
|
|
374
367
|
const result = await this.bridge.executePython(pythonScript);
|
|
375
|
-
this.ensurePythonSpawnSucceeded(
|
|
376
|
-
return { success: true, message: `Spot light '${
|
|
368
|
+
this.ensurePythonSpawnSucceeded(name, result);
|
|
369
|
+
return { success: true, message: `Spot light '${name}' spawned at ${params.location.join(', ')}` };
|
|
377
370
|
}
|
|
378
371
|
// Create rect light
|
|
379
372
|
async createRectLight(params) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
throw new Error('Invalid name: must be a non-empty string');
|
|
383
|
-
}
|
|
373
|
+
const name = this.normalizeName(params.name);
|
|
374
|
+
const escapedName = escapePythonString(name);
|
|
384
375
|
// Validate required location and rotation arrays
|
|
385
376
|
if (!params.location || !Array.isArray(params.location) || params.location.length !== 3) {
|
|
386
377
|
throw new Error('Invalid location: must be an array [x,y,z]');
|
|
@@ -470,87 +461,207 @@ spawned_light = editor_actor_subsystem.spawn_actor_from_class(
|
|
|
470
461
|
)
|
|
471
462
|
|
|
472
463
|
if spawned_light:
|
|
473
|
-
|
|
474
|
-
|
|
464
|
+
# Set the label/name
|
|
465
|
+
spawned_light.set_actor_label("${escapedName}")
|
|
475
466
|
|
|
476
|
-
|
|
477
|
-
|
|
467
|
+
# Get the light component
|
|
468
|
+
light_component = spawned_light.get_component_by_class(unreal.RectLightComponent)
|
|
478
469
|
|
|
479
|
-
|
|
470
|
+
if light_component:
|
|
480
471
|
${propertiesCode}
|
|
481
472
|
|
|
482
|
-
|
|
473
|
+
print(f"Rect light '${escapedName}' spawned at {spawn_location.x}, {spawn_location.y}, {spawn_location.z}")
|
|
483
474
|
else:
|
|
484
|
-
|
|
475
|
+
print("Failed to spawn rect light '${escapedName}'")
|
|
485
476
|
`;
|
|
486
477
|
// Execute the Python script via bridge (UE 5.6-compatible)
|
|
487
478
|
const result = await this.bridge.executePython(pythonScript);
|
|
488
|
-
this.ensurePythonSpawnSucceeded(
|
|
489
|
-
return { success: true, message: `Rect light '${
|
|
479
|
+
this.ensurePythonSpawnSucceeded(name, result);
|
|
480
|
+
return { success: true, message: `Rect light '${name}' spawned at ${params.location.join(', ')}` };
|
|
490
481
|
}
|
|
491
482
|
// Create sky light
|
|
492
483
|
async createSkyLight(params) {
|
|
493
|
-
const
|
|
484
|
+
const name = this.normalizeName(params.name);
|
|
485
|
+
const escapedName = escapePythonString(name);
|
|
486
|
+
const sourceTypeRaw = typeof params.sourceType === 'string' ? params.sourceType.trim() : undefined;
|
|
487
|
+
const normalizedSourceType = sourceTypeRaw
|
|
488
|
+
? sourceTypeRaw.toLowerCase() === 'specifiedcubemap'
|
|
489
|
+
? 'SpecifiedCubemap'
|
|
490
|
+
: sourceTypeRaw.toLowerCase() === 'capturedscene'
|
|
491
|
+
? 'CapturedScene'
|
|
492
|
+
: undefined
|
|
493
|
+
: undefined;
|
|
494
|
+
const cubemapPath = typeof params.cubemapPath === 'string' ? params.cubemapPath.trim() : undefined;
|
|
495
|
+
if (normalizedSourceType === 'SpecifiedCubemap' && (!cubemapPath || cubemapPath.length === 0)) {
|
|
496
|
+
const message = 'cubemapPath is required when sourceType is SpecifiedCubemap';
|
|
497
|
+
return { success: false, error: message, message };
|
|
498
|
+
}
|
|
499
|
+
const escapedCubemapPath = cubemapPath ? escapePythonString(cubemapPath) : '';
|
|
500
|
+
const python = `
|
|
494
501
|
import unreal
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
502
|
+
import json
|
|
503
|
+
|
|
504
|
+
result = {
|
|
505
|
+
"success": False,
|
|
506
|
+
"message": "",
|
|
507
|
+
"error": "",
|
|
508
|
+
"warnings": []
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
def add_warning(text):
|
|
512
|
+
if text:
|
|
513
|
+
result["warnings"].append(str(text))
|
|
514
|
+
|
|
515
|
+
def finish():
|
|
516
|
+
if result["success"]:
|
|
517
|
+
if not result["message"]:
|
|
518
|
+
result["message"] = "Sky light ensured"
|
|
519
|
+
result.pop("error", None)
|
|
520
|
+
else:
|
|
521
|
+
if not result["error"]:
|
|
522
|
+
result["error"] = result["message"] or "Failed to ensure sky light"
|
|
523
|
+
if not result["message"]:
|
|
524
|
+
result["message"] = result["error"]
|
|
525
|
+
if not result["warnings"]:
|
|
526
|
+
result.pop("warnings", None)
|
|
527
|
+
print('RESULT:' + json.dumps(result))
|
|
528
|
+
|
|
500
529
|
try:
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
530
|
+
actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
|
|
531
|
+
if not actor_sub:
|
|
532
|
+
result["error"] = "EditorActorSubsystem unavailable"
|
|
533
|
+
finish()
|
|
534
|
+
raise SystemExit(0)
|
|
535
|
+
|
|
536
|
+
spawn_location = unreal.Vector(0.0, 0.0, 500.0)
|
|
537
|
+
spawn_rotation = unreal.Rotator(0.0, 0.0, 0.0)
|
|
538
|
+
|
|
539
|
+
actor = None
|
|
540
|
+
try:
|
|
541
|
+
for candidate in actor_sub.get_all_level_actors():
|
|
542
|
+
try:
|
|
543
|
+
if candidate.get_class().get_name() == 'SkyLight':
|
|
544
|
+
actor = candidate
|
|
545
|
+
break
|
|
546
|
+
except Exception:
|
|
547
|
+
continue
|
|
548
|
+
except Exception:
|
|
549
|
+
pass
|
|
550
|
+
|
|
551
|
+
if actor is None:
|
|
552
|
+
actor = actor_sub.spawn_actor_from_class(unreal.SkyLight, spawn_location, spawn_rotation)
|
|
553
|
+
|
|
554
|
+
if not actor:
|
|
555
|
+
result["error"] = "Failed to spawn SkyLight actor"
|
|
556
|
+
finish()
|
|
557
|
+
raise SystemExit(0)
|
|
558
|
+
|
|
559
|
+
try:
|
|
560
|
+
actor.set_actor_label("${escapedName}")
|
|
561
|
+
except Exception:
|
|
562
|
+
pass
|
|
563
|
+
|
|
564
|
+
comp = actor.get_component_by_class(unreal.SkyLightComponent)
|
|
565
|
+
if not comp:
|
|
566
|
+
result["error"] = "SkyLight component missing"
|
|
567
|
+
finish()
|
|
568
|
+
raise SystemExit(0)
|
|
569
|
+
|
|
570
|
+
${params.intensity !== undefined ? `
|
|
571
|
+
try:
|
|
572
|
+
comp.set_intensity(${params.intensity})
|
|
573
|
+
except Exception:
|
|
513
574
|
try:
|
|
514
|
-
|
|
515
|
-
except Exception:
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
575
|
+
comp.set_editor_property('intensity', ${params.intensity})
|
|
576
|
+
except Exception:
|
|
577
|
+
add_warning('Unable to set intensity property')
|
|
578
|
+
` : ''}
|
|
579
|
+
|
|
580
|
+
source_type = ${normalizedSourceType ? `'${normalizedSourceType}'` : 'None'}
|
|
581
|
+
if source_type:
|
|
582
|
+
try:
|
|
583
|
+
comp.set_editor_property('source_type', getattr(unreal.SkyLightSourceType, source_type))
|
|
584
|
+
except Exception:
|
|
585
|
+
try:
|
|
586
|
+
comp.source_type = getattr(unreal.SkyLightSourceType, source_type)
|
|
587
|
+
except Exception:
|
|
588
|
+
add_warning(f"Unable to set source type {source_type}")
|
|
589
|
+
|
|
590
|
+
if source_type == 'SpecifiedCubemap':
|
|
591
|
+
path = "${escapedCubemapPath}"
|
|
592
|
+
if not path:
|
|
593
|
+
result["error"] = "cubemapPath is required when sourceType is SpecifiedCubemap"
|
|
594
|
+
finish()
|
|
595
|
+
raise SystemExit(0)
|
|
596
|
+
try:
|
|
597
|
+
exists = unreal.EditorAssetLibrary.does_asset_exist(path)
|
|
598
|
+
except Exception:
|
|
599
|
+
exists = False
|
|
600
|
+
if not exists:
|
|
601
|
+
result["error"] = f"Cubemap asset not found: {path}"
|
|
602
|
+
finish()
|
|
603
|
+
raise SystemExit(0)
|
|
604
|
+
try:
|
|
605
|
+
cube = unreal.EditorAssetLibrary.load_asset(path)
|
|
606
|
+
except Exception as load_err:
|
|
607
|
+
result["error"] = f"Failed to load cubemap asset: {load_err}"
|
|
608
|
+
finish()
|
|
609
|
+
raise SystemExit(0)
|
|
610
|
+
if not cube:
|
|
611
|
+
result["error"] = f"Cubemap asset could not be loaded: {path}"
|
|
612
|
+
finish()
|
|
613
|
+
raise SystemExit(0)
|
|
614
|
+
try:
|
|
615
|
+
if hasattr(comp, 'set_cubemap'):
|
|
616
|
+
comp.set_cubemap(cube)
|
|
617
|
+
else:
|
|
618
|
+
comp.set_editor_property('cubemap', cube)
|
|
619
|
+
except Exception as assign_err:
|
|
620
|
+
result["error"] = f"Failed to assign cubemap: {assign_err}"
|
|
621
|
+
finish()
|
|
622
|
+
raise SystemExit(0)
|
|
623
|
+
|
|
624
|
+
if ${params.recapture ? 'True' : 'False'}:
|
|
625
|
+
try:
|
|
626
|
+
comp.recapture_sky()
|
|
627
|
+
except Exception as recapture_err:
|
|
628
|
+
add_warning(f"Recapture failed: {recapture_err}")
|
|
629
|
+
|
|
630
|
+
result["success"] = True
|
|
631
|
+
result["message"] = "Sky light ensured"
|
|
632
|
+
finish()
|
|
633
|
+
|
|
634
|
+
except SystemExit:
|
|
635
|
+
pass
|
|
636
|
+
except Exception as run_err:
|
|
637
|
+
result["error"] = str(run_err)
|
|
638
|
+
finish()
|
|
536
639
|
`.trim();
|
|
537
|
-
const resp = await this.bridge.executePython(
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
640
|
+
const resp = await this.bridge.executePython(python);
|
|
641
|
+
const parsed = parseStandardResult(resp).data;
|
|
642
|
+
if (parsed) {
|
|
643
|
+
if (parsed.success) {
|
|
644
|
+
return {
|
|
645
|
+
success: true,
|
|
646
|
+
message: parsed.message ?? 'Sky light ensured',
|
|
647
|
+
warnings: Array.isArray(parsed.warnings) && parsed.warnings.length > 0 ? parsed.warnings : undefined
|
|
648
|
+
};
|
|
544
649
|
}
|
|
545
|
-
|
|
650
|
+
return {
|
|
651
|
+
success: false,
|
|
652
|
+
error: parsed.error ?? parsed.message ?? 'Failed to ensure sky light',
|
|
653
|
+
warnings: Array.isArray(parsed.warnings) && parsed.warnings.length > 0 ? parsed.warnings : undefined
|
|
654
|
+
};
|
|
546
655
|
}
|
|
547
656
|
return { success: true, message: 'Sky light ensured' };
|
|
548
657
|
}
|
|
549
658
|
// Remove duplicate SkyLights and keep only one (named target label)
|
|
550
659
|
async ensureSingleSkyLight(params) {
|
|
551
|
-
const
|
|
660
|
+
const fallbackName = 'MCP_Test_Sky';
|
|
661
|
+
const name = this.normalizeName(params?.name, fallbackName);
|
|
662
|
+
const escapedName = escapePythonString(name);
|
|
552
663
|
const recapture = !!params?.recapture;
|
|
553
|
-
const py = `\nimport unreal, json\nactor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)\nactors = actor_sub.get_all_level_actors() if actor_sub else []\nskies = []\nfor a in actors:\n try:\n if a.get_class().get_name() == 'SkyLight':\n skies.append(a)\n except Exception: pass\nkeep = None\n# Prefer one with matching label; otherwise keep the first\nfor a in skies:\n try:\n label = a.get_actor_label()\n if label ==
|
|
664
|
+
const py = `\nimport unreal, json\nactor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)\nactors = actor_sub.get_all_level_actors() if actor_sub else []\nskies = []\nfor a in actors:\n try:\n if a.get_class().get_name() == 'SkyLight':\n skies.append(a)\n except Exception: pass\nkeep = None\n# Prefer one with matching label; otherwise keep the first\nfor a in skies:\n try:\n label = a.get_actor_label()\n if label == "${escapedName}":\n keep = a\n break\n except Exception: pass\nif keep is None and len(skies) > 0:\n keep = skies[0]\n# Rename the kept one if needed\nif keep is not None:\n try: keep.set_actor_label("${escapedName}")\n except Exception: pass\n# Destroy all others using the correct non-deprecated API\nremoved = 0\nfor a in skies:\n if keep is not None and a == keep:\n continue\n try:\n # Use EditorActorSubsystem.destroy_actor instead of deprecated EditorLevelLibrary\n actor_sub.destroy_actor(a)\n removed += 1\n except Exception: pass\n# Optionally recapture\nif keep is not None and ${recapture ? 'True' : 'False'}:\n try:\n comp = keep.get_component_by_class(unreal.SkyLightComponent)\n if comp: comp.recapture_sky()\n except Exception: pass\nprint('RESULT:' + json.dumps({'success': True, 'removed': removed, 'kept': True if keep else False}))\n`.trim();
|
|
554
665
|
const resp = await this.bridge.executePython(py);
|
|
555
666
|
let out = '';
|
|
556
667
|
if (resp?.LogOutput && Array.isArray(resp.LogOutput)) {
|
|
@@ -1087,6 +1198,8 @@ print('RESULT:' + json.dumps(result))
|
|
|
1087
1198
|
}
|
|
1088
1199
|
// Create lightmass importance volume via Python
|
|
1089
1200
|
async createLightmassVolume(params) {
|
|
1201
|
+
const name = this.normalizeName(params.name);
|
|
1202
|
+
const escapedName = escapePythonString(name);
|
|
1090
1203
|
const [lx, ly, lz] = params.location;
|
|
1091
1204
|
const [sx, sy, sz] = params.size;
|
|
1092
1205
|
const py = `
|
|
@@ -1096,7 +1209,7 @@ loc = unreal.Vector(${lx}, ${ly}, ${lz})
|
|
|
1096
1209
|
rot = unreal.Rotator(0,0,0)
|
|
1097
1210
|
actor = editor_actor_subsystem.spawn_actor_from_class(unreal.LightmassImportanceVolume, loc, rot)
|
|
1098
1211
|
if actor:
|
|
1099
|
-
|
|
1212
|
+
try: actor.set_actor_label("${escapedName}")
|
|
1100
1213
|
except Exception: pass
|
|
1101
1214
|
# Best-effort: set actor scale to approximate size
|
|
1102
1215
|
try:
|
|
@@ -1112,7 +1225,7 @@ else:
|
|
|
1112
1225
|
if (m) {
|
|
1113
1226
|
try {
|
|
1114
1227
|
const parsed = JSON.parse(m[1].replace(/'/g, '"'));
|
|
1115
|
-
return parsed.success ? { success: true, message:
|
|
1228
|
+
return parsed.success ? { success: true, message: `LightmassImportanceVolume '${name}' created` } : { success: false, error: parsed.error };
|
|
1116
1229
|
}
|
|
1117
1230
|
catch { }
|
|
1118
1231
|
}
|
|
@@ -3,23 +3,40 @@ export declare class MaterialTools {
|
|
|
3
3
|
private bridge;
|
|
4
4
|
constructor(bridge: UnrealBridge);
|
|
5
5
|
createMaterial(name: string, path: string): Promise<{
|
|
6
|
+
success: boolean;
|
|
7
|
+
error: string;
|
|
8
|
+
message?: undefined;
|
|
9
|
+
path?: undefined;
|
|
10
|
+
warnings?: undefined;
|
|
11
|
+
details?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
success: boolean;
|
|
14
|
+
error: string;
|
|
15
|
+
message: string;
|
|
16
|
+
path?: undefined;
|
|
17
|
+
warnings?: undefined;
|
|
18
|
+
details?: undefined;
|
|
19
|
+
} | {
|
|
6
20
|
success: boolean;
|
|
7
21
|
path: string;
|
|
8
22
|
message: string;
|
|
9
23
|
error?: undefined;
|
|
10
|
-
|
|
24
|
+
warnings?: undefined;
|
|
25
|
+
details?: undefined;
|
|
11
26
|
} | {
|
|
12
27
|
success: boolean;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
28
|
+
path: string;
|
|
29
|
+
message: string;
|
|
30
|
+
warnings: string[] | undefined;
|
|
31
|
+
details: string[] | undefined;
|
|
32
|
+
error?: undefined;
|
|
17
33
|
} | {
|
|
18
34
|
success: boolean;
|
|
19
35
|
error: string;
|
|
20
|
-
|
|
21
|
-
|
|
36
|
+
warnings: string[] | undefined;
|
|
37
|
+
details: string[] | undefined;
|
|
22
38
|
message?: undefined;
|
|
39
|
+
path?: undefined;
|
|
23
40
|
}>;
|
|
24
41
|
applyMaterialToActor(actorPath: string, materialPath: string, slotIndex?: number): Promise<{
|
|
25
42
|
success: boolean;
|
|
@@ -30,5 +47,6 @@ export declare class MaterialTools {
|
|
|
30
47
|
error: string;
|
|
31
48
|
message?: undefined;
|
|
32
49
|
}>;
|
|
50
|
+
private assetExists;
|
|
33
51
|
}
|
|
34
52
|
//# sourceMappingURL=materials.d.ts.map
|