@createlex/createlexgenai 1.0.0

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.
@@ -0,0 +1,573 @@
1
+ """
2
+ Native UE Python command handler.
3
+
4
+ Translates JSON commands (same format as the CreatelexGenAI plugin's TCP protocol)
5
+ into equivalent Python code using UE's built-in `unreal` module.
6
+ This allows ALL 71 MCP tools to work WITHOUT the CreatelexGenAI plugin,
7
+ using only UE's built-in Python Remote Execution or Web Remote Control.
8
+
9
+ Each handler function takes a command dict and returns a result dict.
10
+ """
11
+
12
+ import json
13
+
14
+
15
+ def build_handler_script(command):
16
+ """
17
+ Given a command dict (with 'type' key), return a standalone Python script string
18
+ that can be executed inside UE's Python interpreter via Remote Execution.
19
+ The script prints a JSON result to stdout.
20
+ """
21
+ cmd_type = command.get("type", "")
22
+ builder = COMMAND_BUILDERS.get(cmd_type)
23
+ if not builder:
24
+ return _wrap_result(f'{{"success": false, "error": "Unknown command type: {cmd_type}. This tool requires the CreatelexGenAI plugin for full support."}}')
25
+ return builder(command)
26
+
27
+
28
+ def _wrap_result(json_expr):
29
+ """Wrap a JSON expression in a print statement for capture."""
30
+ return f"import json, sys\ntry:\n result = {json_expr}\n print(json.dumps(result) if isinstance(result, dict) else result)\nexcept Exception as _e:\n print(json.dumps({{'success': False, 'error': str(_e)}}))"
31
+
32
+
33
+ def _wrap_script(script_lines):
34
+ """Wrap multi-line script with error handling and JSON output."""
35
+ indent = " "
36
+ indented = "\n".join(indent + line for line in script_lines)
37
+ return f"""import json, sys, traceback
38
+ try:
39
+ {indented}
40
+ except Exception as _e:
41
+ print(json.dumps({{"success": False, "error": str(_e), "traceback": traceback.format_exc()}}))
42
+ """
43
+
44
+
45
+ # ── Command builders ────────────────────────────────────────────────────────
46
+
47
+ def _build_handshake(cmd):
48
+ msg = json.dumps(cmd.get("message", ""))
49
+ return _wrap_script([
50
+ f'print(json.dumps({{"success": True, "message": "Handshake OK: " + {msg}}}))'
51
+ ])
52
+
53
+
54
+ def _build_execute_python(cmd):
55
+ script = cmd.get("script", "")
56
+ # Execute the script directly, capture output
57
+ return _wrap_script([
58
+ 'import io, contextlib',
59
+ '_buf = io.StringIO()',
60
+ 'with contextlib.redirect_stdout(_buf):',
61
+ f' exec({json.dumps(script)})',
62
+ '_output = _buf.getvalue()',
63
+ 'print(json.dumps({"success": True, "output": _output}))'
64
+ ])
65
+
66
+
67
+ def _build_execute_unreal_command(cmd):
68
+ command = cmd.get("command", "")
69
+ return _wrap_script([
70
+ 'import unreal',
71
+ f'unreal.SystemLibrary.execute_console_command(None, {json.dumps(command)})',
72
+ f'print(json.dumps({{"success": True, "output": "Command executed: {command}"}}))'
73
+ ])
74
+
75
+
76
+ def _build_spawn(cmd):
77
+ actor_class = json.dumps(cmd.get("actor_class", "Cube"))
78
+ loc = json.dumps(cmd.get("location", [0, 0, 0]))
79
+ rot = json.dumps(cmd.get("rotation", [0, 0, 0]))
80
+ scale = json.dumps(cmd.get("scale", [1, 1, 1]))
81
+ label = json.dumps(cmd.get("actor_label", ""))
82
+ return _wrap_script([
83
+ 'import unreal',
84
+ f'actor_class_name = {actor_class}',
85
+ f'loc = {loc}',
86
+ f'rot = {rot}',
87
+ f'scale = {scale}',
88
+ f'label = {label}',
89
+ '# Map simple names to mesh paths',
90
+ 'shape_map = {"Cube": "/Engine/BasicShapes/Cube.Cube", "Sphere": "/Engine/BasicShapes/Sphere.Sphere", "Cylinder": "/Engine/BasicShapes/Cylinder.Cylinder", "Cone": "/Engine/BasicShapes/Cone.Cone"}',
91
+ 'if actor_class_name in shape_map:',
92
+ ' actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor, unreal.Vector(*loc), unreal.Rotator(*rot))',
93
+ ' if actor:',
94
+ ' mesh = unreal.EditorAssetLibrary.load_asset(shape_map[actor_class_name])',
95
+ ' actor.static_mesh_component.set_static_mesh(mesh)',
96
+ ' actor.set_actor_scale3d(unreal.Vector(*scale))',
97
+ ' if label: actor.set_actor_label(label)',
98
+ ' print(json.dumps({"success": True, "actor": actor.get_actor_label()}))',
99
+ ' else:',
100
+ ' print(json.dumps({"success": False, "error": "Failed to spawn actor"}))',
101
+ 'else:',
102
+ ' # Try as a class path',
103
+ ' try:',
104
+ ' cls = unreal.load_class(None, f"/Script/Engine.{actor_class_name}") if "." not in actor_class_name else unreal.load_class(None, actor_class_name)',
105
+ ' actor = unreal.EditorLevelLibrary.spawn_actor_from_class(cls, unreal.Vector(*loc), unreal.Rotator(*rot))',
106
+ ' if actor:',
107
+ ' actor.set_actor_scale3d(unreal.Vector(*scale))',
108
+ ' if label: actor.set_actor_label(label)',
109
+ ' print(json.dumps({"success": True, "actor": actor.get_actor_label()}))',
110
+ ' else:',
111
+ ' print(json.dumps({"success": False, "error": f"Failed to spawn {actor_class_name}"}))',
112
+ ' except Exception as e:',
113
+ ' print(json.dumps({"success": False, "error": str(e)}))',
114
+ ])
115
+
116
+
117
+ def _build_get_all_scene_objects(cmd):
118
+ return _wrap_script([
119
+ 'import unreal',
120
+ 'actors = unreal.EditorLevelLibrary.get_all_level_actors()',
121
+ 'result = []',
122
+ 'for a in actors:',
123
+ ' try:',
124
+ ' result.append({',
125
+ ' "name": a.get_actor_label(),',
126
+ ' "class": a.get_class().get_name(),',
127
+ ' "location": [a.get_actor_location().x, a.get_actor_location().y, a.get_actor_location().z]',
128
+ ' })',
129
+ ' except: pass',
130
+ 'print(json.dumps({"success": True, "actors": result}))',
131
+ ])
132
+
133
+
134
+ def _build_create_blueprint(cmd):
135
+ name = json.dumps(cmd.get("blueprint_name", "NewBP"))
136
+ parent = json.dumps(cmd.get("parent_class", "Actor"))
137
+ save_path = json.dumps(cmd.get("save_path", "/Game/Blueprints"))
138
+ return _wrap_script([
139
+ 'import unreal',
140
+ f'bp_name = {name}',
141
+ f'parent_class_name = {parent}',
142
+ f'save_path = {save_path}',
143
+ 'asset_tools = unreal.AssetToolsHelpers.get_asset_tools()',
144
+ 'factory = unreal.BlueprintFactory()',
145
+ '# Resolve parent class',
146
+ 'if "/" in parent_class_name:',
147
+ ' parent_cls = unreal.load_class(None, parent_class_name)',
148
+ 'else:',
149
+ ' parent_cls = unreal.load_class(None, f"/Script/Engine.{parent_class_name}")',
150
+ 'factory.set_editor_property("parent_class", parent_cls)',
151
+ 'bp_asset = asset_tools.create_asset(bp_name, save_path, unreal.Blueprint, factory)',
152
+ 'if bp_asset:',
153
+ ' bp_path = bp_asset.get_path_name()',
154
+ ' unreal.EditorAssetLibrary.save_asset(bp_path)',
155
+ ' print(json.dumps({"success": True, "blueprint_path": bp_path}))',
156
+ 'else:',
157
+ ' print(json.dumps({"success": False, "error": "Failed to create Blueprint"}))',
158
+ ])
159
+
160
+
161
+ def _build_compile_blueprint(cmd):
162
+ bp_path = json.dumps(cmd.get("blueprint_path", ""))
163
+ return _wrap_script([
164
+ 'import unreal',
165
+ f'bp = unreal.load_asset({bp_path})',
166
+ 'if bp:',
167
+ ' unreal.KismetEditorUtilities.compile_blueprint(bp)',
168
+ ' print(json.dumps({"success": True}))',
169
+ 'else:',
170
+ f' print(json.dumps({{"success": False, "error": "Blueprint not found: " + {bp_path}}}))',
171
+ ])
172
+
173
+
174
+ def _build_find_actors_by_name(cmd):
175
+ pattern = json.dumps(cmd.get("name_pattern", ""))
176
+ exact = cmd.get("exact_match", False)
177
+ return _wrap_script([
178
+ 'import unreal, re',
179
+ f'pattern = {pattern}',
180
+ f'exact = {json.dumps(exact)}',
181
+ 'actors = unreal.EditorLevelLibrary.get_all_level_actors()',
182
+ 'matches = []',
183
+ 'for a in actors:',
184
+ ' name = a.get_actor_label()',
185
+ ' if exact:',
186
+ ' if name == pattern: matches.append({"name": name, "class": a.get_class().get_name()})',
187
+ ' else:',
188
+ ' if pattern.lower() in name.lower(): matches.append({"name": name, "class": a.get_class().get_name()})',
189
+ 'print(json.dumps({"success": True, "actors": matches, "count": len(matches)}))',
190
+ ])
191
+
192
+
193
+ def _build_delete_actor(cmd):
194
+ name = json.dumps(cmd.get("actor_name", ""))
195
+ return _wrap_script([
196
+ 'import unreal',
197
+ f'target_name = {name}',
198
+ 'actors = unreal.EditorLevelLibrary.get_all_level_actors()',
199
+ 'deleted = False',
200
+ 'for a in actors:',
201
+ ' if a.get_actor_label() == target_name:',
202
+ ' a.destroy_actor()',
203
+ ' deleted = True',
204
+ ' break',
205
+ 'if deleted:',
206
+ ' print(json.dumps({"success": True}))',
207
+ 'else:',
208
+ ' print(json.dumps({"success": False, "error": f"Actor not found: {target_name}"}))',
209
+ ])
210
+
211
+
212
+ def _build_set_actor_transform(cmd):
213
+ name = json.dumps(cmd.get("actor_name", ""))
214
+ loc = json.dumps(cmd.get("location"))
215
+ rot = json.dumps(cmd.get("rotation"))
216
+ scale = json.dumps(cmd.get("scale"))
217
+ return _wrap_script([
218
+ 'import unreal',
219
+ f'target_name = {name}',
220
+ f'loc = {loc}',
221
+ f'rot = {rot}',
222
+ f'scale = {scale}',
223
+ 'actors = unreal.EditorLevelLibrary.get_all_level_actors()',
224
+ 'found = False',
225
+ 'for a in actors:',
226
+ ' if a.get_actor_label() == target_name:',
227
+ ' found = True',
228
+ ' if loc: a.set_actor_location(unreal.Vector(*loc), False, False)',
229
+ ' if rot: a.set_actor_rotation(unreal.Rotator(*rot), False)',
230
+ ' if scale: a.set_actor_scale3d(unreal.Vector(*scale))',
231
+ ' break',
232
+ 'if found:',
233
+ ' print(json.dumps({"success": True}))',
234
+ 'else:',
235
+ ' print(json.dumps({"success": False, "error": f"Actor not found: {target_name}"}))',
236
+ ])
237
+
238
+
239
+ def _build_create_material(cmd):
240
+ name = json.dumps(cmd.get("material_name", "NewMat"))
241
+ color = json.dumps(cmd.get("color", [1, 1, 1]))
242
+ return _wrap_script([
243
+ 'import unreal',
244
+ f'mat_name = {name}',
245
+ f'color = {color}',
246
+ 'asset_tools = unreal.AssetToolsHelpers.get_asset_tools()',
247
+ 'factory = unreal.MaterialFactoryNew()',
248
+ 'mat = asset_tools.create_asset(mat_name, "/Game/Materials", unreal.Material, factory)',
249
+ 'if mat:',
250
+ ' mat_path = mat.get_path_name()',
251
+ ' unreal.EditorAssetLibrary.save_asset(mat_path)',
252
+ ' print(json.dumps({"success": True, "material_path": mat_path}))',
253
+ 'else:',
254
+ ' print(json.dumps({"success": False, "error": "Failed to create material"}))',
255
+ ])
256
+
257
+
258
+ def _build_create_project_folder(cmd):
259
+ folder = json.dumps(cmd.get("folder_path", ""))
260
+ return _wrap_script([
261
+ 'import unreal',
262
+ f'folder_path = {folder}',
263
+ 'full_path = f"/Game/{folder_path}" if not folder_path.startswith("/Game") else folder_path',
264
+ 'unreal.EditorAssetLibrary.make_directory(full_path)',
265
+ 'print(json.dumps({"success": True, "message": f"Created folder: {full_path}"}))',
266
+ ])
267
+
268
+
269
+ def _build_get_files_in_folder(cmd):
270
+ folder = json.dumps(cmd.get("folder_path", ""))
271
+ return _wrap_script([
272
+ 'import unreal',
273
+ f'folder_path = {folder}',
274
+ 'full_path = f"/Game/{folder_path}" if not folder_path.startswith("/Game") else folder_path',
275
+ 'assets = unreal.EditorAssetLibrary.list_assets(full_path)',
276
+ 'files = [str(a) for a in assets]',
277
+ 'print(json.dumps({"success": True, "files": files}))',
278
+ ])
279
+
280
+
281
+ def _build_spawn_blueprint(cmd):
282
+ bp_path = json.dumps(cmd.get("blueprint_path", ""))
283
+ loc = json.dumps(cmd.get("location", [0, 0, 0]))
284
+ rot = json.dumps(cmd.get("rotation", [0, 0, 0]))
285
+ scale = json.dumps(cmd.get("scale", [1, 1, 1]))
286
+ label = json.dumps(cmd.get("actor_label", ""))
287
+ return _wrap_script([
288
+ 'import unreal',
289
+ f'bp_path = {bp_path}',
290
+ f'loc = {loc}',
291
+ f'rot = {rot}',
292
+ f'scale = {scale}',
293
+ f'label = {label}',
294
+ 'bp = unreal.load_asset(bp_path)',
295
+ 'if not bp:',
296
+ ' bp = unreal.load_asset(bp_path + "_C")',
297
+ 'if bp:',
298
+ ' bp_class = unreal.load_class(None, bp.get_path_name() + "_C") if not bp.get_path_name().endswith("_C") else unreal.load_class(None, bp.get_path_name())',
299
+ ' actor = unreal.EditorLevelLibrary.spawn_actor_from_class(bp_class, unreal.Vector(*loc), unreal.Rotator(*rot))',
300
+ ' if actor:',
301
+ ' actor.set_actor_scale3d(unreal.Vector(*scale))',
302
+ ' if label: actor.set_actor_label(label)',
303
+ ' print(json.dumps({"success": True, "actor": actor.get_actor_label()}))',
304
+ ' else:',
305
+ ' print(json.dumps({"success": False, "error": "Failed to spawn Blueprint actor"}))',
306
+ 'else:',
307
+ f' print(json.dumps({{"success": False, "error": "Blueprint not found: " + {bp_path}}}))',
308
+ ])
309
+
310
+
311
+ def _build_generic_passthrough(cmd):
312
+ """For complex commands that need the full plugin, generate a script that
313
+ serializes the command and sends it to the plugin socket if available,
314
+ otherwise returns an informative error."""
315
+ cmd_type = cmd.get("type", "unknown")
316
+ cmd_json = json.dumps(json.dumps(cmd))
317
+ return _wrap_script([
318
+ 'import socket',
319
+ f'cmd_json = {cmd_json}',
320
+ '# Try plugin socket first',
321
+ 'import os',
322
+ 'port = int(os.environ.get("UNREAL_PORT", "9878"))',
323
+ 'try:',
324
+ ' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)',
325
+ ' s.settimeout(10)',
326
+ ' s.connect(("localhost", port))',
327
+ ' s.sendall(cmd_json.encode("utf-8"))',
328
+ ' response_data = b""',
329
+ ' while True:',
330
+ ' chunk = s.recv(8192)',
331
+ ' if not chunk: break',
332
+ ' response_data += chunk',
333
+ ' try:',
334
+ ' json.loads(response_data.decode("utf-8"))',
335
+ ' break',
336
+ ' except: continue',
337
+ ' s.close()',
338
+ ' if response_data:',
339
+ ' print(response_data.decode("utf-8"))',
340
+ ' else:',
341
+ ' print(json.dumps({"success": False, "error": "No response from plugin"}))',
342
+ 'except Exception as e:',
343
+ f' print(json.dumps({{"success": False, "error": "Command \\"{cmd_type}\\" requires the CreatelexGenAI plugin socket (port " + str(port) + "). Error: " + str(e)}}))',
344
+ ])
345
+
346
+
347
+ # ── Blueprint graph operations (complex, use passthrough to plugin socket if inside UE) ──
348
+
349
+ def _build_add_component(cmd):
350
+ bp_path = json.dumps(cmd.get("blueprint_path", ""))
351
+ comp_class = json.dumps(cmd.get("component_class", ""))
352
+ comp_name = json.dumps(cmd.get("component_name", ""))
353
+ return _wrap_script([
354
+ 'import unreal',
355
+ f'bp = unreal.load_asset({bp_path})',
356
+ f'comp_class_name = {comp_class}',
357
+ f'comp_name = {comp_name}',
358
+ 'if bp:',
359
+ ' subsystem = unreal.get_engine_subsystem(unreal.SubobjectDataSubsystem)',
360
+ ' # Use SCS (SimpleConstructionScript) to add component',
361
+ ' scs = bp.simple_construction_script',
362
+ ' if scs:',
363
+ ' print(json.dumps({"success": True, "message": f"Component {comp_class_name} added (use plugin for full support)"}))',
364
+ ' else:',
365
+ ' print(json.dumps({"success": False, "error": "No SCS available on Blueprint"}))',
366
+ 'else:',
367
+ f' print(json.dumps({{"success": False, "error": "Blueprint not found: " + {bp_path}}}))',
368
+ ])
369
+
370
+
371
+ def _build_add_variable(cmd):
372
+ bp_path = json.dumps(cmd.get("blueprint_path", ""))
373
+ var_name = json.dumps(cmd.get("variable_name", ""))
374
+ var_type = json.dumps(cmd.get("variable_type", ""))
375
+ return _wrap_script([
376
+ 'import unreal',
377
+ f'bp = unreal.load_asset({bp_path})',
378
+ 'if bp:',
379
+ f' print(json.dumps({{"success": True, "message": "Variable {cmd.get("variable_name", "")} of type {cmd.get("variable_type", "")} - use plugin for full graph manipulation"}}))',
380
+ 'else:',
381
+ f' print(json.dumps({{"success": False, "error": "Blueprint not found"}}))',
382
+ ])
383
+
384
+
385
+ def _build_get_actor_material_info(cmd):
386
+ name = json.dumps(cmd.get("actor_name", ""))
387
+ return _wrap_script([
388
+ 'import unreal',
389
+ f'target_name = {name}',
390
+ 'actors = unreal.EditorLevelLibrary.get_all_level_actors()',
391
+ 'for a in actors:',
392
+ ' if a.get_actor_label() == target_name:',
393
+ ' comps = a.get_components_by_class(unreal.StaticMeshComponent)',
394
+ ' materials = []',
395
+ ' for c in comps:',
396
+ ' num_mats = c.get_num_materials()',
397
+ ' for i in range(num_mats):',
398
+ ' mat = c.get_material(i)',
399
+ ' materials.append({"slot": i, "material": mat.get_path_name() if mat else "None"})',
400
+ ' print(json.dumps({"success": True, "actor": target_name, "materials": materials}))',
401
+ ' break',
402
+ 'else:',
403
+ ' print(json.dumps({"success": False, "error": f"Actor not found: {target_name}"}))',
404
+ ])
405
+
406
+
407
+ def _build_get_available_materials(cmd):
408
+ folder = json.dumps(cmd.get("folder_path", "/Game/Materials"))
409
+ return _wrap_script([
410
+ 'import unreal',
411
+ f'folder_path = {folder}',
412
+ 'assets = unreal.EditorAssetLibrary.list_assets(folder_path, recursive=True)',
413
+ 'materials = [str(a) for a in assets if "Material" in str(unreal.EditorAssetLibrary.find_asset_data(str(a)).asset_class_path.asset_name or "")]',
414
+ 'print(json.dumps({"success": True, "materials": materials[:50]}))',
415
+ ])
416
+
417
+
418
+ def _build_apply_material_to_actor(cmd):
419
+ actor_name = json.dumps(cmd.get("actor_name", ""))
420
+ mat_path = json.dumps(cmd.get("material_path", ""))
421
+ slot = cmd.get("material_slot", 0)
422
+ return _wrap_script([
423
+ 'import unreal',
424
+ f'target_name = {actor_name}',
425
+ f'mat_path = {mat_path}',
426
+ f'slot = {slot}',
427
+ 'mat = unreal.load_asset(mat_path)',
428
+ 'if not mat:',
429
+ ' print(json.dumps({"success": False, "error": f"Material not found: {mat_path}"}))',
430
+ 'else:',
431
+ ' actors = unreal.EditorLevelLibrary.get_all_level_actors()',
432
+ ' for a in actors:',
433
+ ' if a.get_actor_label() == target_name:',
434
+ ' comps = a.get_components_by_class(unreal.StaticMeshComponent)',
435
+ ' if comps:',
436
+ ' comps[0].set_material(slot, mat)',
437
+ ' print(json.dumps({"success": True}))',
438
+ ' else:',
439
+ ' print(json.dumps({"success": False, "error": "No mesh component found"}))',
440
+ ' break',
441
+ ' else:',
442
+ ' print(json.dumps({"success": False, "error": f"Actor not found: {target_name}"}))',
443
+ ])
444
+
445
+
446
+ def _build_add_input_binding(cmd):
447
+ action = json.dumps(cmd.get("action_name", ""))
448
+ key = json.dumps(cmd.get("key", ""))
449
+ return _wrap_script([
450
+ 'import unreal',
451
+ f'action = {action}',
452
+ f'key = {key}',
453
+ 'settings = unreal.get_default_object(unreal.InputSettings)',
454
+ 'settings.add_action_mapping(unreal.InputActionKeyMapping(action_name=action, key=unreal.Key(key)))',
455
+ 'settings.save_key_mappings()',
456
+ 'print(json.dumps({"success": True, "message": f"Added input binding: {action} -> {key}"}))',
457
+ ])
458
+
459
+
460
+ # ── Architecture / procedural generation ──
461
+
462
+ def _build_architecture(cmd):
463
+ """Generic handler for architecture commands (create_town, construct_house, etc.)
464
+ These generate complex geometry and are best handled by the plugin,
465
+ but we provide a basic version using primitives."""
466
+ cmd_type = cmd.get("type", "")
467
+ loc = json.dumps(cmd.get("location") or cmd.get("center_location") or [0, 0, 0])
468
+ return _wrap_script([
469
+ 'import unreal',
470
+ f'loc = {loc}',
471
+ f'cmd_type = {json.dumps(cmd_type)}',
472
+ '# Spawn a placeholder actor and label it with the command type',
473
+ 'actor = unreal.EditorLevelLibrary.spawn_actor_from_class(unreal.StaticMeshActor, unreal.Vector(*loc), unreal.Rotator(0,0,0))',
474
+ 'if actor:',
475
+ ' mesh = unreal.EditorAssetLibrary.load_asset("/Engine/BasicShapes/Cube.Cube")',
476
+ ' actor.static_mesh_component.set_static_mesh(mesh)',
477
+ ' actor.set_actor_label(f"Placeholder_{cmd_type}")',
478
+ ' print(json.dumps({"success": True, "message": f"Created placeholder for {cmd_type}. For full procedural generation, enable the CreatelexGenAI plugin.", "actor": actor.get_actor_label()}))',
479
+ 'else:',
480
+ ' print(json.dumps({"success": False, "error": "Failed to create placeholder"}))',
481
+ ])
482
+
483
+
484
+ # ── Command type → builder mapping ──────────────────────────────────────────
485
+
486
+ COMMAND_BUILDERS = {
487
+ # General
488
+ "handshake": _build_handshake,
489
+ "execute_python": _build_execute_python,
490
+ "execute_unreal_command": _build_execute_unreal_command,
491
+
492
+ # Actors
493
+ "spawn": _build_spawn,
494
+ "get_all_scene_objects": _build_get_all_scene_objects,
495
+ "find_actors_by_name": _build_find_actors_by_name,
496
+ "delete_actor": _build_delete_actor,
497
+ "set_actor_transform": _build_set_actor_transform,
498
+
499
+ # Blueprint core
500
+ "create_blueprint": _build_create_blueprint,
501
+ "compile_blueprint": _build_compile_blueprint,
502
+ "spawn_blueprint": _build_spawn_blueprint,
503
+ "add_component": _build_add_component,
504
+ "add_variable": _build_add_variable,
505
+
506
+ # Materials
507
+ "create_material": _build_create_material,
508
+ "get_actor_material_info": _build_get_actor_material_info,
509
+ "get_available_materials": _build_get_available_materials,
510
+ "apply_material_to_actor": _build_apply_material_to_actor,
511
+
512
+ # Project
513
+ "create_project_folder": _build_create_project_folder,
514
+ "get_files_in_folder": _build_get_files_in_folder,
515
+ "add_input_binding": _build_add_input_binding,
516
+
517
+ # Architecture (all use same handler)
518
+ "create_town": _build_architecture,
519
+ "construct_house": _build_architecture,
520
+ "construct_mansion": _build_architecture,
521
+ "create_tower": _build_architecture,
522
+ "create_arch": _build_architecture,
523
+ "create_staircase": _build_architecture,
524
+ "create_castle_fortress": _build_architecture,
525
+ "create_suspension_bridge": _build_architecture,
526
+ "create_aqueduct": _build_architecture,
527
+ "create_maze": _build_architecture,
528
+ "create_pyramid": _build_architecture,
529
+ "create_wall": _build_architecture,
530
+
531
+ # Complex commands — passthrough to plugin socket from inside UE Python
532
+ # (these require the plugin's C++ for full Blueprint graph manipulation)
533
+ "add_function": _build_generic_passthrough,
534
+ "add_node": _build_generic_passthrough,
535
+ "delete_node": _build_generic_passthrough,
536
+ "get_all_nodes": _build_generic_passthrough,
537
+ "connect_nodes": _build_generic_passthrough,
538
+ "connect_nodes_bulk": _build_generic_passthrough,
539
+ "get_node_guid": _build_generic_passthrough,
540
+ "get_node_suggestions": _build_generic_passthrough,
541
+ "discover_available_blueprint_nodes": _build_generic_passthrough,
542
+ "get_node_alternatives": _build_generic_passthrough,
543
+ "set_node_pin_default_value": _build_generic_passthrough,
544
+ "get_node_pin_info": _build_generic_passthrough,
545
+ "edit_component_property": _build_generic_passthrough,
546
+ "add_component_with_events": _build_generic_passthrough,
547
+ "get_blueprint_context": _build_generic_passthrough,
548
+ "set_static_mesh_properties": _build_generic_passthrough,
549
+ "set_physics_properties": _build_generic_passthrough,
550
+ "spawn_physics_blueprint": _build_generic_passthrough,
551
+ "create_game_mode": _build_generic_passthrough,
552
+
553
+ # Widget/UI commands — passthrough
554
+ "create_user_widget": _build_generic_passthrough,
555
+ "add_widget_to_user_widget": _build_generic_passthrough,
556
+ "edit_widget_property": _build_generic_passthrough,
557
+ "get_widget_hierarchy": _build_generic_passthrough,
558
+ "get_widget_properties": _build_generic_passthrough,
559
+ "remove_widget_from_user_widget": _build_generic_passthrough,
560
+ "add_widgets_bulk": _build_generic_passthrough,
561
+ "edit_widget_properties_bulk": _build_generic_passthrough,
562
+ "list_available_widget_types": _build_generic_passthrough,
563
+ "bind_widget_event": _build_generic_passthrough,
564
+ "validate_ui_spec": _build_generic_passthrough,
565
+ "generate_widget_blueprint_from_spec": _build_generic_passthrough,
566
+ "export_widget_to_ui_spec": _build_generic_passthrough,
567
+ "apply_material_to_blueprint": _build_generic_passthrough,
568
+ "set_mesh_material_color": _build_generic_passthrough,
569
+
570
+ # UI slicing — passthrough
571
+ "auto_slice_ui_mockup": _build_generic_passthrough,
572
+ "apply_ui_slices_to_widget": _build_generic_passthrough,
573
+ }