godot-mcp-runtime 2.2.3 → 3.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.
- package/README.md +30 -93
- package/dist/dispatch.d.ts +1 -11
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +7 -8
- package/dist/dispatch.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -10
- package/dist/index.js.map +1 -1
- package/dist/scripts/godot_operations.gd +134 -283
- package/dist/scripts/mcp_bridge.gd +210 -43
- package/dist/tools/autoload-tools.d.ts +51 -0
- package/dist/tools/autoload-tools.d.ts.map +1 -0
- package/dist/tools/autoload-tools.js +187 -0
- package/dist/tools/autoload-tools.js.map +1 -0
- package/dist/tools/node-tools.d.ts +9 -78
- package/dist/tools/node-tools.d.ts.map +1 -1
- package/dist/tools/node-tools.js +114 -310
- package/dist/tools/node-tools.js.map +1 -1
- package/dist/tools/project-tools.d.ts +0 -168
- package/dist/tools/project-tools.d.ts.map +1 -1
- package/dist/tools/project-tools.js +120 -1192
- package/dist/tools/project-tools.js.map +1 -1
- package/dist/tools/runtime-tools.d.ts +108 -0
- package/dist/tools/runtime-tools.d.ts.map +1 -0
- package/dist/tools/runtime-tools.js +808 -0
- package/dist/tools/runtime-tools.js.map +1 -0
- package/dist/tools/scene-tools.d.ts +6 -48
- package/dist/tools/scene-tools.d.ts.map +1 -1
- package/dist/tools/scene-tools.js +67 -211
- package/dist/tools/scene-tools.js.map +1 -1
- package/dist/tools/validate-tools.d.ts.map +1 -1
- package/dist/tools/validate-tools.js +35 -29
- package/dist/tools/validate-tools.js.map +1 -1
- package/dist/utils/autoload-ini.d.ts +32 -0
- package/dist/utils/autoload-ini.d.ts.map +1 -0
- package/dist/utils/autoload-ini.js +111 -0
- package/dist/utils/autoload-ini.js.map +1 -0
- package/dist/utils/bridge-manager.d.ts +29 -0
- package/dist/utils/bridge-manager.d.ts.map +1 -0
- package/dist/utils/bridge-manager.js +136 -0
- package/dist/utils/bridge-manager.js.map +1 -0
- package/dist/utils/bridge-protocol.d.ts +34 -0
- package/dist/utils/bridge-protocol.d.ts.map +1 -0
- package/dist/utils/bridge-protocol.js +65 -0
- package/dist/utils/bridge-protocol.js.map +1 -0
- package/dist/utils/godot-runner.d.ts +70 -15
- package/dist/utils/godot-runner.d.ts.map +1 -1
- package/dist/utils/godot-runner.js +309 -277
- package/dist/utils/godot-runner.js.map +1 -1
- package/dist/utils/handler-helpers.d.ts +34 -0
- package/dist/utils/handler-helpers.d.ts.map +1 -0
- package/dist/utils/handler-helpers.js +55 -0
- package/dist/utils/handler-helpers.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +11 -0
- package/dist/utils/logger.js.map +1 -0
- package/package.json +7 -4
|
@@ -61,19 +61,13 @@ func _init():
|
|
|
61
61
|
export_mesh_library(params)
|
|
62
62
|
"save_scene":
|
|
63
63
|
save_scene(params)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
"delete_node":
|
|
70
|
-
delete_node(params)
|
|
71
|
-
"set_node_property":
|
|
72
|
-
set_node_property(params)
|
|
64
|
+
# Node operations (always-array)
|
|
65
|
+
"delete_nodes":
|
|
66
|
+
delete_nodes(params)
|
|
67
|
+
"set_node_properties":
|
|
68
|
+
set_node_properties(params)
|
|
73
69
|
"get_node_properties":
|
|
74
70
|
get_node_properties(params)
|
|
75
|
-
"list_nodes":
|
|
76
|
-
list_nodes(params)
|
|
77
71
|
"get_scene_tree":
|
|
78
72
|
get_scene_tree(params)
|
|
79
73
|
"attach_script":
|
|
@@ -93,10 +87,6 @@ func _init():
|
|
|
93
87
|
validate_batch(params)
|
|
94
88
|
"batch_scene_operations":
|
|
95
89
|
batch_scene_operations(params)
|
|
96
|
-
"batch_set_node_properties":
|
|
97
|
-
batch_set_node_properties(params)
|
|
98
|
-
"batch_get_node_properties":
|
|
99
|
-
batch_get_node_properties(params)
|
|
100
90
|
_:
|
|
101
91
|
log_error("Unknown operation: " + operation)
|
|
102
92
|
quit(1)
|
|
@@ -220,14 +210,22 @@ func load_scene_instance(scene_path: String):
|
|
|
220
210
|
|
|
221
211
|
return instance
|
|
222
212
|
|
|
223
|
-
# Helper to find a node by path
|
|
213
|
+
# Helper to find a node by path. Accepts "root", ".", "" (all → scene_root),
|
|
214
|
+
# the actual scene root's name (e.g. "Main"), or a path with either as the first
|
|
215
|
+
# segment (e.g. "root/Button" or "Main/Button"). Bare paths ("Button") resolve
|
|
216
|
+
# normally via get_node_or_null.
|
|
224
217
|
func find_node_by_path(scene_root: Node, node_path: String) -> Node:
|
|
225
|
-
if node_path == "
|
|
218
|
+
if node_path == "" or node_path == "." or node_path == "root":
|
|
219
|
+
return scene_root
|
|
220
|
+
if node_path == String(scene_root.name):
|
|
226
221
|
return scene_root
|
|
227
222
|
|
|
228
223
|
var path = node_path
|
|
229
|
-
|
|
230
|
-
|
|
224
|
+
var first_slash = path.find("/")
|
|
225
|
+
if first_slash != -1:
|
|
226
|
+
var first_segment = path.substr(0, first_slash)
|
|
227
|
+
if first_segment == "root" or first_segment == String(scene_root.name):
|
|
228
|
+
path = path.substr(first_slash + 1)
|
|
231
229
|
|
|
232
230
|
if path.is_empty():
|
|
233
231
|
return scene_root
|
|
@@ -252,6 +250,20 @@ func save_scene_to_path(scene_root: Node, save_path: String) -> bool:
|
|
|
252
250
|
|
|
253
251
|
return true
|
|
254
252
|
|
|
253
|
+
# Ensure the parent directory of a res:// path exists, creating it recursively
|
|
254
|
+
# if needed. Returns true on success or when the directory already exists.
|
|
255
|
+
func _ensure_res_dir(full_res_path: String) -> bool:
|
|
256
|
+
var dir_path = full_res_path.get_base_dir()
|
|
257
|
+
if dir_path == "res://" or dir_path.is_empty():
|
|
258
|
+
return true
|
|
259
|
+
var dir = DirAccess.open("res://")
|
|
260
|
+
if not dir:
|
|
261
|
+
return false
|
|
262
|
+
var relative_dir = dir_path.substr(6) if dir_path.begins_with("res://") else dir_path
|
|
263
|
+
if relative_dir.is_empty() or dir.dir_exists(relative_dir):
|
|
264
|
+
return true
|
|
265
|
+
return dir.make_dir_recursive(relative_dir) == OK
|
|
266
|
+
|
|
255
267
|
# Create a new scene with a specified root node type
|
|
256
268
|
func create_scene(params):
|
|
257
269
|
printerr("Creating scene: " + params.scene_path)
|
|
@@ -272,17 +284,9 @@ func create_scene(params):
|
|
|
272
284
|
scene_root.name = "root"
|
|
273
285
|
scene_root.owner = scene_root
|
|
274
286
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
var dir = DirAccess.open("res://")
|
|
279
|
-
if dir:
|
|
280
|
-
var relative_dir = scene_dir.substr(6) if scene_dir.begins_with("res://") else scene_dir
|
|
281
|
-
if not relative_dir.is_empty() and not dir.dir_exists(relative_dir):
|
|
282
|
-
var make_error = dir.make_dir_recursive(relative_dir)
|
|
283
|
-
if make_error != OK:
|
|
284
|
-
log_error("Failed to create directory: " + relative_dir)
|
|
285
|
-
quit(1)
|
|
287
|
+
if not _ensure_res_dir(full_scene_path):
|
|
288
|
+
log_error("Failed to create directory for scene: " + full_scene_path)
|
|
289
|
+
quit(1)
|
|
286
290
|
|
|
287
291
|
if save_scene_to_path(scene_root, full_scene_path):
|
|
288
292
|
print("Scene created successfully at: " + params.scene_path)
|
|
@@ -318,7 +322,7 @@ func add_node(params):
|
|
|
318
322
|
var properties = params.properties
|
|
319
323
|
for property in properties:
|
|
320
324
|
log_debug("Setting property: " + property + " = " + str(properties[property]))
|
|
321
|
-
new_node.set(property, properties[property])
|
|
325
|
+
new_node.set(property, _coerce_property_value(properties[property]))
|
|
322
326
|
|
|
323
327
|
parent.add_child(new_node)
|
|
324
328
|
new_node.owner = scene_root
|
|
@@ -351,6 +355,14 @@ func load_sprite(params):
|
|
|
351
355
|
if not texture:
|
|
352
356
|
log_error("Failed to load texture: " + full_texture_path)
|
|
353
357
|
quit(1)
|
|
358
|
+
if not (texture is Texture2D):
|
|
359
|
+
log_error("Loaded resource is not a Texture2D: " + full_texture_path)
|
|
360
|
+
quit(1)
|
|
361
|
+
# A texture without a resource_path is a runtime-only object — PackedScene.pack()
|
|
362
|
+
# cannot serialize it, so the assignment would silently vanish on save.
|
|
363
|
+
if texture.resource_path == "":
|
|
364
|
+
log_error("Texture has no resource_path — likely not imported. Open project in Godot editor once, or run 'godot --headless --editor --quit' to import assets.")
|
|
365
|
+
quit(1)
|
|
354
366
|
|
|
355
367
|
sprite_node.texture = texture
|
|
356
368
|
|
|
@@ -398,22 +410,16 @@ func export_mesh_library(params):
|
|
|
398
410
|
mesh_library.set_item_shapes(item_id, [collision_child.shape])
|
|
399
411
|
break
|
|
400
412
|
|
|
401
|
-
|
|
402
|
-
mesh_library.set_item_preview(item_id, mesh_instance.mesh)
|
|
413
|
+
mesh_library.set_item_preview(item_id, mesh_instance.mesh)
|
|
403
414
|
|
|
404
415
|
item_id += 1
|
|
405
416
|
|
|
406
417
|
if item_id > 0:
|
|
407
418
|
var full_output_path = normalize_scene_path(params.output_path)
|
|
408
419
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
var dir = DirAccess.open("res://")
|
|
413
|
-
if dir:
|
|
414
|
-
var relative_dir = output_dir.substr(6) if output_dir.begins_with("res://") else output_dir
|
|
415
|
-
if not relative_dir.is_empty() and not dir.dir_exists(relative_dir):
|
|
416
|
-
dir.make_dir_recursive(relative_dir)
|
|
420
|
+
if not _ensure_res_dir(full_output_path):
|
|
421
|
+
log_error("Failed to create directory for MeshLibrary: " + full_output_path)
|
|
422
|
+
quit(1)
|
|
417
423
|
|
|
418
424
|
var error = ResourceSaver.save(mesh_library, full_output_path)
|
|
419
425
|
if error == OK:
|
|
@@ -441,216 +447,97 @@ func save_scene(params):
|
|
|
441
447
|
log_error("Failed to save scene")
|
|
442
448
|
quit(1)
|
|
443
449
|
|
|
444
|
-
# Find files with a specific extension recursively
|
|
445
|
-
func find_files(path, extension):
|
|
446
|
-
var files = []
|
|
447
|
-
var dir = DirAccess.open(path)
|
|
448
|
-
|
|
449
|
-
if dir:
|
|
450
|
-
dir.list_dir_begin()
|
|
451
|
-
var file_name = dir.get_next()
|
|
452
|
-
|
|
453
|
-
while file_name != "":
|
|
454
|
-
if dir.current_is_dir() and not file_name.begins_with("."):
|
|
455
|
-
files.append_array(find_files(path + file_name + "/", extension))
|
|
456
|
-
elif file_name.ends_with(extension):
|
|
457
|
-
files.append(path + file_name)
|
|
458
|
-
|
|
459
|
-
file_name = dir.get_next()
|
|
460
|
-
|
|
461
|
-
return files
|
|
462
|
-
|
|
463
|
-
# Get UID for a specific file
|
|
464
|
-
func get_uid(params):
|
|
465
|
-
if not params.has("file_path"):
|
|
466
|
-
log_error("File path is required")
|
|
467
|
-
quit(1)
|
|
468
|
-
|
|
469
|
-
var file_path = normalize_scene_path(params.file_path)
|
|
470
|
-
printerr("Getting UID for file: " + file_path)
|
|
471
|
-
|
|
472
|
-
if not FileAccess.file_exists(file_path):
|
|
473
|
-
log_error("File does not exist: " + file_path)
|
|
474
|
-
quit(1)
|
|
475
|
-
|
|
476
|
-
var uid_path = file_path + ".uid"
|
|
477
|
-
var f = FileAccess.open(uid_path, FileAccess.READ)
|
|
478
|
-
|
|
479
|
-
if f:
|
|
480
|
-
var uid_content = f.get_as_text()
|
|
481
|
-
f.close()
|
|
482
|
-
|
|
483
|
-
var result = {
|
|
484
|
-
"file": file_path,
|
|
485
|
-
"uid": uid_content.strip_edges(),
|
|
486
|
-
"exists": true
|
|
487
|
-
}
|
|
488
|
-
print(JSON.stringify(result))
|
|
489
|
-
else:
|
|
490
|
-
var result = {
|
|
491
|
-
"file": file_path,
|
|
492
|
-
"exists": false,
|
|
493
|
-
"message": "UID file does not exist for this file. Use resave_resources to generate UIDs."
|
|
494
|
-
}
|
|
495
|
-
print(JSON.stringify(result))
|
|
496
|
-
|
|
497
|
-
# Resave all resources to update UID references
|
|
498
|
-
func resave_resources(params):
|
|
499
|
-
printerr("Resaving all resources to update UID references...")
|
|
500
|
-
|
|
501
|
-
var project_path = "res://"
|
|
502
|
-
if params.has("project_path"):
|
|
503
|
-
project_path = params.project_path
|
|
504
|
-
if not project_path.begins_with("res://"):
|
|
505
|
-
project_path = "res://" + project_path
|
|
506
|
-
if not project_path.ends_with("/"):
|
|
507
|
-
project_path += "/"
|
|
508
|
-
|
|
509
|
-
var scenes = find_files(project_path, ".tscn")
|
|
510
|
-
var success_count = 0
|
|
511
|
-
var error_count = 0
|
|
512
|
-
|
|
513
|
-
for scene_path in scenes:
|
|
514
|
-
var scene = load(scene_path)
|
|
515
|
-
if scene:
|
|
516
|
-
var error = ResourceSaver.save(scene, scene_path)
|
|
517
|
-
if error == OK:
|
|
518
|
-
success_count += 1
|
|
519
|
-
else:
|
|
520
|
-
error_count += 1
|
|
521
|
-
log_error("Failed to save: " + scene_path)
|
|
522
|
-
else:
|
|
523
|
-
error_count += 1
|
|
524
|
-
log_error("Failed to load: " + scene_path)
|
|
525
|
-
|
|
526
|
-
var scripts = find_files(project_path, ".gd") + find_files(project_path, ".shader") + find_files(project_path, ".gdshader")
|
|
527
|
-
var generated_uids = 0
|
|
528
|
-
|
|
529
|
-
for script_path in scripts:
|
|
530
|
-
var uid_path = script_path + ".uid"
|
|
531
|
-
var f = FileAccess.open(uid_path, FileAccess.READ)
|
|
532
|
-
if not f:
|
|
533
|
-
var res = load(script_path)
|
|
534
|
-
if res:
|
|
535
|
-
var error = ResourceSaver.save(res, script_path)
|
|
536
|
-
if error == OK:
|
|
537
|
-
generated_uids += 1
|
|
538
|
-
|
|
539
|
-
print("Resave operation complete. Scenes: " + str(success_count) + " saved, " + str(error_count) + " errors. UIDs generated: " + str(generated_uids))
|
|
540
|
-
|
|
541
450
|
# ============================================
|
|
542
|
-
#
|
|
451
|
+
# NODE OPERATIONS
|
|
543
452
|
# ============================================
|
|
544
453
|
|
|
545
|
-
# Delete
|
|
546
|
-
func
|
|
547
|
-
printerr("Deleting
|
|
454
|
+
# Delete one or more nodes from a scene (saves once)
|
|
455
|
+
func delete_nodes(params):
|
|
456
|
+
printerr("Deleting nodes from scene: " + params.scene_path)
|
|
548
457
|
|
|
549
458
|
var scene_root = load_scene_instance(params.scene_path)
|
|
550
459
|
if not scene_root:
|
|
551
460
|
quit(1)
|
|
552
461
|
|
|
553
|
-
var
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
quit(1)
|
|
557
|
-
|
|
558
|
-
if node == scene_root:
|
|
559
|
-
log_error("Cannot delete the root node")
|
|
560
|
-
quit(1)
|
|
561
|
-
|
|
562
|
-
var parent = node.get_parent()
|
|
563
|
-
parent.remove_child(node)
|
|
564
|
-
node.queue_free()
|
|
565
|
-
|
|
566
|
-
if save_scene_to_path(scene_root, params.scene_path):
|
|
567
|
-
print("Node '" + params.node_path + "' deleted successfully")
|
|
568
|
-
else:
|
|
569
|
-
log_error("Failed to save scene after deleting node")
|
|
570
|
-
quit(1)
|
|
571
|
-
|
|
572
|
-
# Update a single property on a node
|
|
573
|
-
func set_node_property(params):
|
|
574
|
-
printerr("Updating node property in scene: " + params.scene_path)
|
|
575
|
-
|
|
576
|
-
var scene_root = load_scene_instance(params.scene_path)
|
|
577
|
-
if not scene_root:
|
|
578
|
-
quit(1)
|
|
579
|
-
|
|
580
|
-
var node = find_node_by_path(scene_root, params.node_path)
|
|
581
|
-
if not node:
|
|
582
|
-
log_error("Node not found: " + params.node_path)
|
|
583
|
-
quit(1)
|
|
584
|
-
|
|
585
|
-
var property_name = params.property
|
|
586
|
-
var property_value = _coerce_property_value(params.value)
|
|
587
|
-
|
|
588
|
-
log_debug("Setting property '" + property_name + "' to: " + str(property_value))
|
|
589
|
-
|
|
590
|
-
node.set(property_name, property_value)
|
|
462
|
+
var node_paths: Array = params.node_paths
|
|
463
|
+
var results: Array = []
|
|
464
|
+
var any_deleted := false
|
|
591
465
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
466
|
+
for node_path in node_paths:
|
|
467
|
+
var entry = {"nodePath": node_path}
|
|
468
|
+
var node = find_node_by_path(scene_root, node_path)
|
|
469
|
+
if not node:
|
|
470
|
+
entry["error"] = "Node not found: " + node_path
|
|
471
|
+
elif node == scene_root:
|
|
472
|
+
entry["error"] = "Cannot delete the root node"
|
|
473
|
+
else:
|
|
474
|
+
var parent = node.get_parent()
|
|
475
|
+
parent.remove_child(node)
|
|
476
|
+
node.queue_free()
|
|
477
|
+
entry["success"] = true
|
|
478
|
+
any_deleted = true
|
|
479
|
+
results.append(entry)
|
|
480
|
+
|
|
481
|
+
if any_deleted:
|
|
482
|
+
if not save_scene_to_path(scene_root, params.scene_path):
|
|
483
|
+
print(JSON.stringify({"error": "Failed to save scene after deleting nodes", "results": results}))
|
|
484
|
+
return
|
|
597
485
|
|
|
598
|
-
|
|
599
|
-
func get_node_properties(params):
|
|
600
|
-
printerr("Getting node properties from scene: " + params.scene_path)
|
|
486
|
+
print(JSON.stringify({"results": results}))
|
|
601
487
|
|
|
488
|
+
# Update one or more node properties in a single headless process (saves once)
|
|
489
|
+
func set_node_properties(params: Dictionary) -> void:
|
|
602
490
|
var scene_root = load_scene_instance(params.scene_path)
|
|
603
491
|
if not scene_root:
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
var node = find_node_by_path(scene_root, params.node_path)
|
|
607
|
-
if not node:
|
|
608
|
-
log_error("Node not found: " + params.node_path)
|
|
609
|
-
quit(1)
|
|
492
|
+
print(JSON.stringify({"error": "Failed to load scene: " + params.scene_path, "results": []}))
|
|
493
|
+
return
|
|
610
494
|
|
|
611
|
-
var
|
|
612
|
-
var
|
|
495
|
+
var abort_on_error = params.get("abort_on_error", false)
|
|
496
|
+
var results: Array = []
|
|
497
|
+
var any_set := false
|
|
613
498
|
|
|
614
|
-
|
|
615
|
-
"nodePath":
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
499
|
+
for update in params.updates:
|
|
500
|
+
var result = {"nodePath": update.node_path, "property": update.property}
|
|
501
|
+
var node = find_node_by_path(scene_root, update.node_path)
|
|
502
|
+
if node == null:
|
|
503
|
+
result["error"] = "Node not found: " + update.node_path
|
|
504
|
+
elif not (update.property in node):
|
|
505
|
+
result["error"] = "Property '%s' does not exist on node of type '%s'" % [update.property, node.get_class()]
|
|
506
|
+
else:
|
|
507
|
+
node.set(update.property, _coerce_property_value(update.value))
|
|
508
|
+
result["success"] = true
|
|
509
|
+
any_set = true
|
|
510
|
+
results.append(result)
|
|
511
|
+
if abort_on_error and result.has("error"):
|
|
512
|
+
break
|
|
619
513
|
|
|
620
|
-
|
|
514
|
+
if any_set:
|
|
515
|
+
if not save_scene_to_path(scene_root, params.scene_path):
|
|
516
|
+
print(JSON.stringify({"error": "Failed to save scene after updates", "results": results}))
|
|
517
|
+
return
|
|
621
518
|
|
|
622
|
-
|
|
623
|
-
func list_nodes(params):
|
|
624
|
-
printerr("Listing nodes in scene: " + params.scene_path)
|
|
519
|
+
print(JSON.stringify({"results": results}))
|
|
625
520
|
|
|
521
|
+
# Get properties from one or more nodes in a single headless process (loads scene once)
|
|
522
|
+
func get_node_properties(params: Dictionary) -> void:
|
|
626
523
|
var scene_root = load_scene_instance(params.scene_path)
|
|
627
524
|
if not scene_root:
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
var parent_path = "root"
|
|
631
|
-
if params.has("parent_path"):
|
|
632
|
-
parent_path = params.parent_path
|
|
633
|
-
|
|
634
|
-
var parent = find_node_by_path(scene_root, parent_path)
|
|
635
|
-
if not parent:
|
|
636
|
-
log_error("Parent node not found: " + parent_path)
|
|
637
|
-
quit(1)
|
|
525
|
+
print(JSON.stringify({"error": "Failed to load scene: " + params.scene_path, "results": []}))
|
|
526
|
+
return
|
|
638
527
|
|
|
639
|
-
var
|
|
640
|
-
for child in parent.get_children():
|
|
641
|
-
children.append({
|
|
642
|
-
"name": child.name,
|
|
643
|
-
"type": child.get_class(),
|
|
644
|
-
"childCount": child.get_child_count()
|
|
645
|
-
})
|
|
528
|
+
var results: Array = []
|
|
646
529
|
|
|
647
|
-
|
|
648
|
-
"
|
|
649
|
-
"
|
|
650
|
-
|
|
651
|
-
|
|
530
|
+
for node_spec in params.nodes:
|
|
531
|
+
var node_path = node_spec.get("node_path", "")
|
|
532
|
+
var changed_only = node_spec.get("changed_only", false)
|
|
533
|
+
var node = find_node_by_path(scene_root, node_path)
|
|
534
|
+
if node == null:
|
|
535
|
+
results.append({"nodePath": node_path, "error": "Node not found"})
|
|
536
|
+
else:
|
|
537
|
+
var props = _collect_node_properties(node, changed_only)
|
|
538
|
+
results.append({"nodePath": node_path, "nodeType": node.get_class(), "properties": props})
|
|
652
539
|
|
|
653
|
-
print(JSON.stringify(
|
|
540
|
+
print(JSON.stringify({"results": results}))
|
|
654
541
|
|
|
655
542
|
# Get full hierarchical tree structure of a scene
|
|
656
543
|
func get_scene_tree(params):
|
|
@@ -660,11 +547,18 @@ func get_scene_tree(params):
|
|
|
660
547
|
if not scene_root:
|
|
661
548
|
quit(1)
|
|
662
549
|
|
|
550
|
+
var tree_root = scene_root
|
|
551
|
+
if params.has("parent_path") and params.parent_path:
|
|
552
|
+
tree_root = find_node_by_path(scene_root, params.parent_path)
|
|
553
|
+
if not tree_root:
|
|
554
|
+
log_error("Parent node not found: " + str(params.parent_path))
|
|
555
|
+
quit(1)
|
|
556
|
+
|
|
663
557
|
var max_depth = -1
|
|
664
558
|
if params.has("max_depth"):
|
|
665
559
|
max_depth = int(params.max_depth)
|
|
666
560
|
|
|
667
|
-
var tree = build_tree_recursive(
|
|
561
|
+
var tree = build_tree_recursive(tree_root, "", 0, max_depth)
|
|
668
562
|
print(JSON.stringify(tree))
|
|
669
563
|
|
|
670
564
|
func build_tree_recursive(node: Node, path: String, depth: int = 0, max_depth: int = -1) -> Dictionary:
|
|
@@ -676,10 +570,9 @@ func build_tree_recursive(node: Node, path: String, depth: int = 0, max_depth: i
|
|
|
676
570
|
children.append(build_tree_recursive(child, node_path, depth + 1, max_depth))
|
|
677
571
|
|
|
678
572
|
var script_path = ""
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
script_path = script.resource_path
|
|
573
|
+
var script = node.get_script()
|
|
574
|
+
if script and script.resource_path:
|
|
575
|
+
script_path = script.resource_path
|
|
683
576
|
|
|
684
577
|
return {
|
|
685
578
|
"name": node.name,
|
|
@@ -822,7 +715,9 @@ func connect_signal(params):
|
|
|
822
715
|
log_error("Method does not exist: " + params.method + " on " + target.get_class())
|
|
823
716
|
quit(1)
|
|
824
717
|
|
|
825
|
-
|
|
718
|
+
# CONNECT_PERSIST is required for the connection to be serialized into the
|
|
719
|
+
# packed scene; without it the connection is runtime-only and disappears on save.
|
|
720
|
+
var err = source.connect(params.signal, Callable(target, params.method), CONNECT_PERSIST)
|
|
826
721
|
if err != OK:
|
|
827
722
|
log_error("Failed to connect signal: " + str(err))
|
|
828
723
|
quit(1)
|
|
@@ -976,7 +871,7 @@ func _batch_add_node(scene_root: Node, op: Dictionary) -> String:
|
|
|
976
871
|
new_node.name = op.node_name
|
|
977
872
|
if op.has("properties"):
|
|
978
873
|
for property in op.properties:
|
|
979
|
-
new_node.set(property, op.properties[property])
|
|
874
|
+
new_node.set(property, _coerce_property_value(op.properties[property]))
|
|
980
875
|
parent.add_child(new_node)
|
|
981
876
|
new_node.owner = scene_root
|
|
982
877
|
return ""
|
|
@@ -995,6 +890,10 @@ func _batch_load_sprite(scene_root: Node, op: Dictionary) -> String:
|
|
|
995
890
|
var texture = load(normalize_scene_path(op.texture_path))
|
|
996
891
|
if not texture:
|
|
997
892
|
return "Failed to load texture: " + op.texture_path
|
|
893
|
+
if not (texture is Texture2D):
|
|
894
|
+
return "Loaded resource is not a Texture2D: " + op.texture_path
|
|
895
|
+
if texture.resource_path == "":
|
|
896
|
+
return "Texture has no resource_path — likely not imported. Open project in Godot editor once, or run 'godot --headless --editor --quit' to import assets."
|
|
998
897
|
sprite_node.texture = texture
|
|
999
898
|
return ""
|
|
1000
899
|
|
|
@@ -1067,51 +966,3 @@ func batch_scene_operations(params: Dictionary) -> void:
|
|
|
1067
966
|
save_scene_to_path(scene_cache[scene_path], scene_path)
|
|
1068
967
|
|
|
1069
968
|
print(JSON.stringify({"results": results}))
|
|
1070
|
-
|
|
1071
|
-
# Update multiple node properties in a single headless process (loads and saves scene once)
|
|
1072
|
-
func batch_set_node_properties(params: Dictionary) -> void:
|
|
1073
|
-
var scene_root = load_scene_instance(params.scene_path)
|
|
1074
|
-
if not scene_root:
|
|
1075
|
-
print(JSON.stringify({"error": "Failed to load scene: " + params.scene_path, "results": []}))
|
|
1076
|
-
return
|
|
1077
|
-
|
|
1078
|
-
var abort_on_error = params.get("abort_on_error", false)
|
|
1079
|
-
var results: Array = []
|
|
1080
|
-
|
|
1081
|
-
for update in params.updates:
|
|
1082
|
-
var result = {"nodePath": update.node_path, "property": update.property}
|
|
1083
|
-
var node = find_node_by_path(scene_root, update.node_path)
|
|
1084
|
-
if node == null:
|
|
1085
|
-
result["error"] = "Node not found: " + update.node_path
|
|
1086
|
-
else:
|
|
1087
|
-
node.set(update.property, _coerce_property_value(update.value))
|
|
1088
|
-
result["success"] = true
|
|
1089
|
-
results.append(result)
|
|
1090
|
-
if abort_on_error and result.has("error"):
|
|
1091
|
-
break
|
|
1092
|
-
|
|
1093
|
-
if save_scene_to_path(scene_root, params.scene_path):
|
|
1094
|
-
print(JSON.stringify({"results": results}))
|
|
1095
|
-
else:
|
|
1096
|
-
print(JSON.stringify({"error": "Failed to save scene after batch updates", "partial_results": results}))
|
|
1097
|
-
|
|
1098
|
-
# Get properties from multiple nodes in a single headless process (loads scene once)
|
|
1099
|
-
func batch_get_node_properties(params: Dictionary) -> void:
|
|
1100
|
-
var scene_root = load_scene_instance(params.scene_path)
|
|
1101
|
-
if not scene_root:
|
|
1102
|
-
print(JSON.stringify({"error": "Failed to load scene: " + params.scene_path, "results": []}))
|
|
1103
|
-
return
|
|
1104
|
-
|
|
1105
|
-
var results: Array = []
|
|
1106
|
-
|
|
1107
|
-
for node_spec in params.nodes:
|
|
1108
|
-
var node_path = node_spec.get("node_path", "")
|
|
1109
|
-
var changed_only = node_spec.get("changed_only", false)
|
|
1110
|
-
var node = find_node_by_path(scene_root, node_path)
|
|
1111
|
-
if node == null:
|
|
1112
|
-
results.append({"nodePath": node_path, "error": "Node not found"})
|
|
1113
|
-
else:
|
|
1114
|
-
var props = _collect_node_properties(node, changed_only)
|
|
1115
|
-
results.append({"nodePath": node_path, "nodeType": node.get_class(), "properties": props})
|
|
1116
|
-
|
|
1117
|
-
print(JSON.stringify({"results": results}))
|