refactor: 拆分 claude-dev-stack 为 windows-dev-stack 和 wsl-dev-stack
将原 claude-dev-stack 目录拆分为独立的 Windows 和 WSL 部署栈,便于分别维护和使用。 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,706 @@
|
||||
@tool
|
||||
extends "res://addons/godot_mcp/commands/base_command.gd"
|
||||
|
||||
const NodeUtils := preload("res://addons/godot_mcp/utils/node_utils.gd")
|
||||
const PropertyParser := preload("res://addons/godot_mcp/utils/property_parser.gd")
|
||||
|
||||
|
||||
func get_commands() -> Dictionary:
|
||||
return {
|
||||
"add_node": _add_node,
|
||||
"delete_node": _delete_node,
|
||||
"duplicate_node": _duplicate_node,
|
||||
"move_node": _move_node,
|
||||
"update_property": _update_property,
|
||||
"get_node_properties": _get_node_properties,
|
||||
"add_resource": _add_resource,
|
||||
"set_anchor_preset": _set_anchor_preset,
|
||||
"rename_node": _rename_node,
|
||||
"connect_signal": _connect_signal,
|
||||
"disconnect_signal": _disconnect_signal,
|
||||
"get_node_groups": _get_node_groups,
|
||||
"set_node_groups": _set_node_groups,
|
||||
"find_nodes_in_group": _find_nodes_in_group,
|
||||
}
|
||||
|
||||
|
||||
func _find_script_by_class_name(class_name_str: String) -> Script:
|
||||
# Search project files for a script with matching class_name
|
||||
var global_classes: Array = ProjectSettings.get_global_class_list()
|
||||
for entry: Dictionary in global_classes:
|
||||
if entry.get("class", "") == class_name_str:
|
||||
var path: String = entry.get("path", "")
|
||||
if not path.is_empty():
|
||||
return load(path) as Script
|
||||
return null
|
||||
|
||||
|
||||
func _add_node(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "type")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var type: String = result[0]
|
||||
|
||||
var parent_path: String = optional_string(params, "parent_path", ".")
|
||||
var node_name: String = optional_string(params, "name", "")
|
||||
var properties: Dictionary = params.get("properties", {})
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var parent := find_node_by_path(parent_path)
|
||||
if parent == null:
|
||||
return error_not_found("Parent node '%s'" % parent_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
var node: Node
|
||||
var custom_script: Script = null
|
||||
|
||||
if ClassDB.class_exists(type):
|
||||
node = ClassDB.instantiate(type)
|
||||
else:
|
||||
# Try to find a script with matching class_name
|
||||
custom_script = _find_script_by_class_name(type)
|
||||
if custom_script == null:
|
||||
return error_invalid_params("Unknown node type: '%s'. Not found in ClassDB or as a script class_name. Use list_scripts to see available script classes." % type)
|
||||
var base_type: String = custom_script.get_instance_base_type()
|
||||
if not ClassDB.class_exists(base_type):
|
||||
return error_invalid_params("Script '%s' extends '%s' which is not a valid node type" % [type, base_type])
|
||||
node = ClassDB.instantiate(base_type)
|
||||
node.set_script(custom_script)
|
||||
if not node_name.is_empty():
|
||||
node.name = node_name
|
||||
|
||||
# Apply properties
|
||||
for prop_name: String in properties:
|
||||
var prop_exists := false
|
||||
for prop in node.get_property_list():
|
||||
if prop["name"] == prop_name:
|
||||
prop_exists = true
|
||||
break
|
||||
if prop_exists:
|
||||
var current: Variant = node.get(prop_name)
|
||||
var target_type := typeof(current)
|
||||
node.set(prop_name, PropertyParser.parse_value(properties[prop_name], target_type))
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Add %s" % type)
|
||||
undo_redo.add_do_method(parent, "add_child", node)
|
||||
undo_redo.add_do_method(node, "set_owner", root)
|
||||
undo_redo.add_do_reference(node)
|
||||
undo_redo.add_undo_method(parent, "remove_child", node)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({
|
||||
"node_path": str(root.get_path_to(node)),
|
||||
"type": type,
|
||||
"name": str(node.name),
|
||||
})
|
||||
|
||||
|
||||
func _delete_node(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
if node == root:
|
||||
return error_invalid_params("Cannot delete the root node")
|
||||
|
||||
var parent := node.get_parent()
|
||||
var node_name := str(node.name)
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Delete %s" % node_name)
|
||||
undo_redo.add_do_method(parent, "remove_child", node)
|
||||
undo_redo.add_undo_method(parent, "add_child", node)
|
||||
undo_redo.add_undo_method(node, "set_owner", root)
|
||||
undo_redo.add_undo_reference(node)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({"deleted": node_name})
|
||||
|
||||
|
||||
func _duplicate_node(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var new_name: String = optional_string(params, "name", "")
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
if new_name.is_empty():
|
||||
new_name = str(node.name) + "_copy"
|
||||
|
||||
var dup := node.duplicate()
|
||||
dup.name = new_name
|
||||
var parent := node.get_parent()
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Duplicate %s" % node.name)
|
||||
undo_redo.add_do_method(parent, "add_child", dup)
|
||||
undo_redo.add_do_method(dup, "set_owner", root)
|
||||
undo_redo.add_do_reference(dup)
|
||||
undo_redo.add_undo_method(parent, "remove_child", dup)
|
||||
undo_redo.commit_action()
|
||||
|
||||
NodeUtils.set_owner_recursive(dup, root)
|
||||
|
||||
return success({
|
||||
"original": str(root.get_path_to(node)),
|
||||
"duplicate": str(root.get_path_to(dup)),
|
||||
"name": str(dup.name),
|
||||
})
|
||||
|
||||
|
||||
func _move_node(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var result2 := require_string(params, "new_parent_path")
|
||||
if result2[1] != null:
|
||||
return result2[1]
|
||||
var new_parent_path: String = result2[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
if node == root:
|
||||
return error_invalid_params("Cannot move the root node")
|
||||
|
||||
var new_parent := find_node_by_path(new_parent_path)
|
||||
if new_parent == null:
|
||||
return error_not_found("Target parent '%s'" % new_parent_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
# Check we're not moving a node into its own subtree
|
||||
if new_parent == node or node.is_ancestor_of(new_parent):
|
||||
return error_invalid_params("Cannot move a node into its own subtree")
|
||||
|
||||
var old_parent := node.get_parent()
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Move %s" % node.name)
|
||||
undo_redo.add_do_method(old_parent, "remove_child", node)
|
||||
undo_redo.add_do_method(new_parent, "add_child", node)
|
||||
undo_redo.add_do_method(node, "set_owner", root)
|
||||
undo_redo.add_undo_method(new_parent, "remove_child", node)
|
||||
undo_redo.add_undo_method(old_parent, "add_child", node)
|
||||
undo_redo.add_undo_method(node, "set_owner", root)
|
||||
undo_redo.commit_action()
|
||||
|
||||
NodeUtils.set_owner_recursive(node, root)
|
||||
|
||||
return success({
|
||||
"node": str(node.name),
|
||||
"old_parent": str(root.get_path_to(old_parent)),
|
||||
"new_parent": str(root.get_path_to(new_parent)),
|
||||
"new_path": str(root.get_path_to(node)),
|
||||
})
|
||||
|
||||
|
||||
func _update_property(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var result2 := require_string(params, "property")
|
||||
if result2[1] != null:
|
||||
return result2[1]
|
||||
var property: String = result2[0]
|
||||
|
||||
if not params.has("value"):
|
||||
return error_invalid_params("Missing required parameter: value")
|
||||
var value: Variant = params["value"]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
# Check property exists
|
||||
if not property in node:
|
||||
var available: Array = []
|
||||
for prop in node.get_property_list():
|
||||
if prop["usage"] & PROPERTY_USAGE_EDITOR:
|
||||
available.append(prop["name"])
|
||||
return error_not_found("Property '%s' on %s" % [property, node.get_class()],
|
||||
"Available: %s" % str(available.slice(0, 20)))
|
||||
|
||||
var old_value: Variant = node.get(property)
|
||||
var target_type := typeof(old_value)
|
||||
var parsed_value: Variant = PropertyParser.parse_value(value, target_type)
|
||||
|
||||
# Handle @export node references (e.g. @export var hud: HUD)
|
||||
# typeof() returns TYPE_NIL when unset or TYPE_OBJECT when set,
|
||||
# neither resolves a string path to a node — check the property hint instead
|
||||
if value is String:
|
||||
for prop in node.get_property_list():
|
||||
if prop["name"] == property and prop["hint"] == PROPERTY_HINT_NODE_TYPE:
|
||||
var target_node: Node = node.get_node_or_null(NodePath(value))
|
||||
if target_node == null:
|
||||
target_node = root.get_node_or_null(NodePath(value))
|
||||
if target_node == null:
|
||||
return error_not_found("Node '%s'" % value, "Could not resolve node path for property '%s'" % property)
|
||||
parsed_value = target_node
|
||||
break
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Set %s.%s" % [node.name, property])
|
||||
undo_redo.add_do_property(node, property, parsed_value)
|
||||
undo_redo.add_undo_property(node, property, old_value)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({
|
||||
"node": str(root.get_path_to(node)),
|
||||
"property": property,
|
||||
"old_value": PropertyParser.serialize_value(old_value),
|
||||
"new_value": PropertyParser.serialize_value(node.get(property)),
|
||||
})
|
||||
|
||||
|
||||
func _get_node_properties(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
var category: String = optional_string(params, "category", "")
|
||||
var props := NodeUtils.get_node_properties_dict(node)
|
||||
|
||||
# Filter by category if specified
|
||||
if not category.is_empty():
|
||||
var filtered: Dictionary = {}
|
||||
for key: String in props:
|
||||
if key.begins_with(category):
|
||||
filtered[key] = props[key]
|
||||
props = filtered
|
||||
|
||||
return success({
|
||||
"node_path": str(root.get_path_to(node)),
|
||||
"type": node.get_class(),
|
||||
"properties": props,
|
||||
})
|
||||
|
||||
|
||||
func _add_resource(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var result2 := require_string(params, "property")
|
||||
if result2[1] != null:
|
||||
return result2[1]
|
||||
var property: String = result2[0]
|
||||
|
||||
var result3 := require_string(params, "resource_type")
|
||||
if result3[1] != null:
|
||||
return result3[1]
|
||||
var resource_type: String = result3[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
if not ClassDB.class_exists(resource_type):
|
||||
return error_invalid_params("Unknown resource type: %s" % resource_type)
|
||||
|
||||
if not ClassDB.is_parent_class(resource_type, "Resource"):
|
||||
return error_invalid_params("'%s' is not a Resource type" % resource_type)
|
||||
|
||||
var resource: Resource = ClassDB.instantiate(resource_type)
|
||||
if resource == null:
|
||||
return error_internal("Failed to create resource: %s" % resource_type)
|
||||
|
||||
# Apply resource properties if provided
|
||||
var resource_props: Dictionary = params.get("resource_properties", {})
|
||||
for prop_name: String in resource_props:
|
||||
if prop_name in resource:
|
||||
var current: Variant = resource.get(prop_name)
|
||||
resource.set(prop_name, PropertyParser.parse_value(resource_props[prop_name], typeof(current)))
|
||||
|
||||
var old_value: Variant = node.get(property) if property in node else null
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Add %s to %s" % [resource_type, node.name])
|
||||
undo_redo.add_do_property(node, property, resource)
|
||||
undo_redo.add_undo_property(node, property, old_value)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({
|
||||
"node_path": str(root.get_path_to(node)),
|
||||
"property": property,
|
||||
"resource_type": resource_type,
|
||||
})
|
||||
|
||||
|
||||
func _set_anchor_preset(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var result2 := require_string(params, "preset")
|
||||
if result2[1] != null:
|
||||
return result2[1]
|
||||
var preset_name: String = result2[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
if not node is Control:
|
||||
return error_invalid_params("Node '%s' is not a Control (is %s)" % [node_path, node.get_class()])
|
||||
|
||||
var control: Control = node
|
||||
var presets := {
|
||||
"top_left": Control.PRESET_TOP_LEFT,
|
||||
"top_right": Control.PRESET_TOP_RIGHT,
|
||||
"bottom_left": Control.PRESET_BOTTOM_LEFT,
|
||||
"bottom_right": Control.PRESET_BOTTOM_RIGHT,
|
||||
"center_left": Control.PRESET_CENTER_LEFT,
|
||||
"center_top": Control.PRESET_CENTER_TOP,
|
||||
"center_right": Control.PRESET_CENTER_RIGHT,
|
||||
"center_bottom": Control.PRESET_CENTER_BOTTOM,
|
||||
"center": Control.PRESET_CENTER,
|
||||
"left_wide": Control.PRESET_LEFT_WIDE,
|
||||
"top_wide": Control.PRESET_TOP_WIDE,
|
||||
"right_wide": Control.PRESET_RIGHT_WIDE,
|
||||
"bottom_wide": Control.PRESET_BOTTOM_WIDE,
|
||||
"vcenter_wide": Control.PRESET_VCENTER_WIDE,
|
||||
"hcenter_wide": Control.PRESET_HCENTER_WIDE,
|
||||
"full_rect": Control.PRESET_FULL_RECT,
|
||||
}
|
||||
|
||||
if not presets.has(preset_name):
|
||||
return error_invalid_params("Unknown preset: '%s'. Available: %s" % [preset_name, presets.keys()])
|
||||
|
||||
var keep_offsets: bool = optional_bool(params, "keep_offsets", false)
|
||||
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Set anchor preset on %s" % node.name)
|
||||
|
||||
# Store old values
|
||||
var old_anchors := [control.anchor_left, control.anchor_top, control.anchor_right, control.anchor_bottom]
|
||||
var old_offsets := [control.offset_left, control.offset_top, control.offset_right, control.offset_bottom]
|
||||
|
||||
var target: Control = control.duplicate() as Control
|
||||
target.set_anchors_and_offsets_preset(presets[preset_name],
|
||||
Control.PRESET_MODE_KEEP_SIZE if keep_offsets else Control.PRESET_MODE_MINSIZE)
|
||||
|
||||
undo_redo.add_do_property(control, "anchor_left", target.anchor_left)
|
||||
undo_redo.add_do_property(control, "anchor_top", target.anchor_top)
|
||||
undo_redo.add_do_property(control, "anchor_right", target.anchor_right)
|
||||
undo_redo.add_do_property(control, "anchor_bottom", target.anchor_bottom)
|
||||
undo_redo.add_do_property(control, "offset_left", target.offset_left)
|
||||
undo_redo.add_do_property(control, "offset_top", target.offset_top)
|
||||
undo_redo.add_do_property(control, "offset_right", target.offset_right)
|
||||
undo_redo.add_do_property(control, "offset_bottom", target.offset_bottom)
|
||||
|
||||
undo_redo.add_undo_property(control, "anchor_left", old_anchors[0])
|
||||
undo_redo.add_undo_property(control, "anchor_top", old_anchors[1])
|
||||
undo_redo.add_undo_property(control, "anchor_right", old_anchors[2])
|
||||
undo_redo.add_undo_property(control, "anchor_bottom", old_anchors[3])
|
||||
undo_redo.add_undo_property(control, "offset_left", old_offsets[0])
|
||||
undo_redo.add_undo_property(control, "offset_top", old_offsets[1])
|
||||
undo_redo.add_undo_property(control, "offset_right", old_offsets[2])
|
||||
undo_redo.add_undo_property(control, "offset_bottom", old_offsets[3])
|
||||
|
||||
target.free()
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({"node_path": str(root.get_path_to(control)), "preset": preset_name})
|
||||
|
||||
|
||||
func _rename_node(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var result2 := require_string(params, "new_name")
|
||||
if result2[1] != null:
|
||||
return result2[1]
|
||||
var new_name: String = result2[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
var old_name: String = node.name
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Rename %s to %s" % [old_name, new_name])
|
||||
undo_redo.add_do_property(node, "name", new_name)
|
||||
undo_redo.add_undo_property(node, "name", old_name)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({"old_name": old_name, "new_name": str(node.name), "node_path": str(root.get_path_to(node))})
|
||||
|
||||
|
||||
func _connect_signal(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "source_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var source_path: String = result[0]
|
||||
|
||||
var result2 := require_string(params, "signal_name")
|
||||
if result2[1] != null:
|
||||
return result2[1]
|
||||
var signal_name: String = result2[0]
|
||||
|
||||
var result3 := require_string(params, "target_path")
|
||||
if result3[1] != null:
|
||||
return result3[1]
|
||||
var target_path: String = result3[0]
|
||||
|
||||
var result4 := require_string(params, "method_name")
|
||||
if result4[1] != null:
|
||||
return result4[1]
|
||||
var method_name: String = result4[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var source := find_node_by_path(source_path)
|
||||
if source == null:
|
||||
return error_not_found("Source node '%s'" % source_path)
|
||||
|
||||
var target := find_node_by_path(target_path)
|
||||
if target == null:
|
||||
return error_not_found("Target node '%s'" % target_path)
|
||||
|
||||
if not source.has_signal(signal_name):
|
||||
return error_invalid_params("Signal '%s' not found on %s" % [signal_name, source.get_class()])
|
||||
|
||||
if source.is_connected(signal_name, Callable(target, method_name)):
|
||||
return success({"already_connected": true, "signal": signal_name})
|
||||
|
||||
var callable := Callable(target, method_name)
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Connect signal")
|
||||
undo_redo.add_do_method(source, "connect", signal_name, callable)
|
||||
undo_redo.add_undo_method(source, "disconnect", signal_name, callable)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({
|
||||
"source": str(root.get_path_to(source)),
|
||||
"signal": signal_name,
|
||||
"target": str(root.get_path_to(target)),
|
||||
"method": method_name,
|
||||
"connected": true,
|
||||
})
|
||||
|
||||
|
||||
func _disconnect_signal(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "source_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var source_path: String = result[0]
|
||||
|
||||
var result2 := require_string(params, "signal_name")
|
||||
if result2[1] != null:
|
||||
return result2[1]
|
||||
var signal_name: String = result2[0]
|
||||
|
||||
var result3 := require_string(params, "target_path")
|
||||
if result3[1] != null:
|
||||
return result3[1]
|
||||
var target_path: String = result3[0]
|
||||
|
||||
var result4 := require_string(params, "method_name")
|
||||
if result4[1] != null:
|
||||
return result4[1]
|
||||
var method_name: String = result4[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var source := find_node_by_path(source_path)
|
||||
if source == null:
|
||||
return error_not_found("Source node '%s'" % source_path)
|
||||
|
||||
var target := find_node_by_path(target_path)
|
||||
if target == null:
|
||||
return error_not_found("Target node '%s'" % target_path)
|
||||
|
||||
if not source.is_connected(signal_name, Callable(target, method_name)):
|
||||
return success({"was_connected": false})
|
||||
|
||||
var callable := Callable(target, method_name)
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Disconnect signal")
|
||||
undo_redo.add_do_method(source, "disconnect", signal_name, callable)
|
||||
undo_redo.add_undo_method(source, "connect", signal_name, callable)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({
|
||||
"source": str(root.get_path_to(source)),
|
||||
"signal": signal_name,
|
||||
"target": str(root.get_path_to(target)),
|
||||
"method": method_name,
|
||||
"disconnected": true,
|
||||
})
|
||||
|
||||
|
||||
func _get_node_groups(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
var groups: Array = []
|
||||
for group: StringName in node.get_groups():
|
||||
var g := str(group)
|
||||
# Filter out internal groups (start with _)
|
||||
if not g.begins_with("_"):
|
||||
groups.append(g)
|
||||
|
||||
return success({
|
||||
"node_path": str(root.get_path_to(node)),
|
||||
"groups": groups,
|
||||
"count": groups.size(),
|
||||
})
|
||||
|
||||
|
||||
func _set_node_groups(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "node_path")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var node_path: String = result[0]
|
||||
|
||||
if not params.has("groups") or not params["groups"] is Array:
|
||||
return error_invalid_params("'groups' array is required")
|
||||
var desired_groups: Array = params["groups"]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var node := find_node_by_path(node_path)
|
||||
if node == null:
|
||||
return error_not_found("Node '%s'" % node_path, "Use get_scene_tree to see available nodes")
|
||||
|
||||
# Get current non-internal groups
|
||||
var current_groups: Array = []
|
||||
for group: StringName in node.get_groups():
|
||||
var g := str(group)
|
||||
if not g.begins_with("_"):
|
||||
current_groups.append(g)
|
||||
|
||||
var added: Array = []
|
||||
var removed: Array = []
|
||||
|
||||
for group: String in current_groups:
|
||||
if group not in desired_groups:
|
||||
removed.append(group)
|
||||
|
||||
for group in desired_groups:
|
||||
var g: String = str(group)
|
||||
if g not in current_groups:
|
||||
added.append(g)
|
||||
|
||||
if not added.is_empty() or not removed.is_empty():
|
||||
var undo_redo := get_undo_redo()
|
||||
undo_redo.create_action("MCP: Set node groups")
|
||||
for group: String in removed:
|
||||
undo_redo.add_do_method(node, "remove_from_group", group)
|
||||
undo_redo.add_undo_method(node, "add_to_group", group, true)
|
||||
for group: String in added:
|
||||
undo_redo.add_do_method(node, "add_to_group", group, true)
|
||||
undo_redo.add_undo_method(node, "remove_from_group", group)
|
||||
undo_redo.commit_action()
|
||||
|
||||
return success({
|
||||
"node_path": str(root.get_path_to(node)),
|
||||
"groups": desired_groups,
|
||||
"added": added,
|
||||
"removed": removed,
|
||||
})
|
||||
|
||||
|
||||
func _find_nodes_in_group(params: Dictionary) -> Dictionary:
|
||||
var result := require_string(params, "group")
|
||||
if result[1] != null:
|
||||
return result[1]
|
||||
var group_name: String = result[0]
|
||||
|
||||
var root := get_edited_root()
|
||||
if root == null:
|
||||
return error_no_scene()
|
||||
|
||||
var matches: Array = []
|
||||
_find_in_group_recursive(root, root, group_name, matches)
|
||||
|
||||
return success({
|
||||
"group": group_name,
|
||||
"nodes": matches,
|
||||
"count": matches.size(),
|
||||
})
|
||||
|
||||
|
||||
func _find_in_group_recursive(node: Node, root: Node, group_name: String, matches: Array) -> void:
|
||||
if node.is_in_group(group_name):
|
||||
matches.append({
|
||||
"name": node.name,
|
||||
"path": str(root.get_path_to(node)),
|
||||
"type": node.get_class(),
|
||||
})
|
||||
for child in node.get_children():
|
||||
_find_in_group_recursive(child, root, group_name, matches)
|
||||
Reference in New Issue
Block a user