2026-10-09 12:49:07 +02:00

161 lines
5.7 KiB
GDScript

@tool
extends RefCounted
## path2d.* command handlers — edit Path2D Curve2D points.
const Modules := preload("res://addons/godot_mcp_toolkit/core/modules.gd")
const Helpers = Modules.CommandHelpers
static func register(registry: MCPToolkitCommandRegistry, _server: Node) -> void:
registry.add("path2d.edit_curve", func(parameters: Dictionary) -> Dictionary:
return _cmd_edit_curve2d(parameters)
, MCPToolkitCommandOptions.new())
# -- Commands -----------------------------------------------------------------
## Edits the **Curve2D** on a Path2D scene node (tool `path2d.edit_curve`). NOT
## `procedural.edit_curve`, which authors a 1D **Curve** .tres resource —
## different engine type, different aggregate. The private name carries the
## `2d` suffix so even internal symbols distinguish Curve from Curve2D.
static func _cmd_edit_curve2d(parameters: Dictionary) -> Dictionary:
var err = MCPToolkitError.require(parameters, ["node_path", "action"])
if err != null:
return err
var node_path := str(parameters.get("node_path", ""))
var action := str(parameters.get("action", ""))
var points_raw = parameters.get("points", null)
var index_raw = parameters.get("index", null)
if action not in ["set", "add", "remove", "clear"]:
return MCPToolkitError.fail("INVALID_PARAMS",
"action must be one of: set, add, remove, clear (got '%s')" % action)
var node = Helpers.resolve_scene_node(node_path)
if node == null:
return MCPToolkitError.fail("NODE_NOT_FOUND",
"no node at path '%s' in edited scene" % node_path)
if not (node is Path2D):
return MCPToolkitError.fail("INVALID_CLASS",
"node at '%s' is not a Path2D (got %s)" % [node_path, node.get_class()])
var curve: Curve2D = node.curve
if curve == null:
curve = Curve2D.new()
node.curve = curve
match action:
"set":
if points_raw == null or typeof(points_raw) != TYPE_ARRAY:
return MCPToolkitError.fail("INVALID_PARAMS",
"action 'set' requires a 'points' array")
return _action_set(node, curve, points_raw as Array)
"add":
if points_raw == null or typeof(points_raw) != TYPE_ARRAY:
return MCPToolkitError.fail("INVALID_PARAMS",
"action 'add' requires a 'points' array")
var index: int = int(index_raw) if index_raw != null else -1
return _action_add(node, curve, points_raw as Array, index)
"remove":
if index_raw == null:
return MCPToolkitError.fail("INVALID_PARAMS",
"action 'remove' requires an 'index'")
return _action_remove(node, curve, int(index_raw))
"clear":
return _action_clear(node, curve)
return MCPToolkitError.fail("INVALID_PARAMS", "unknown action '%s'" % action)
# -- Action helpers -----------------------------------------------------------
static func _action_set(node: Path2D, curve: Curve2D, points: Array) -> Dictionary:
# Capture old state for undo.
var old_curve := curve.duplicate() as Curve2D
curve.clear_points()
for i in range(points.size()):
var pt = points[i]
if typeof(pt) != TYPE_DICTIONARY or not pt.has("position"):
return MCPToolkitError.fail("INVALID_PARAMS",
"points[%d] must have a 'position' key with {x, y}" % i)
var pos := _to_vec2(pt["position"])
var in_h := _to_vec2(pt.get("in_handle")) if pt.has("in_handle") else Vector2.ZERO
var out_h := _to_vec2(pt.get("out_handle")) if pt.has("out_handle") else Vector2.ZERO
curve.add_point(pos, in_h, out_h)
_wrap_undo_redo(node, curve, old_curve, "MCP: path2d.edit_curve set")
return _ok(curve)
static func _action_add(node: Path2D, curve: Curve2D, points: Array, index: int) -> Dictionary:
var old_curve := curve.duplicate() as Curve2D
for i in range(points.size()):
var pt = points[i]
if typeof(pt) != TYPE_DICTIONARY or not pt.has("position"):
return MCPToolkitError.fail("INVALID_PARAMS",
"points[%d] must have a 'position' key with {x, y}" % i)
var pos := _to_vec2(pt["position"])
var in_h := _to_vec2(pt.get("in_handle")) if pt.has("in_handle") else Vector2.ZERO
var out_h := _to_vec2(pt.get("out_handle")) if pt.has("out_handle") else Vector2.ZERO
if index >= 0:
curve.add_point(pos, in_h, out_h, index + i)
else:
curve.add_point(pos, in_h, out_h)
_wrap_undo_redo(node, curve, old_curve, "MCP: path2d.edit_curve add")
return _ok(curve)
static func _action_remove(node: Path2D, curve: Curve2D, index: int) -> Dictionary:
if index < 0 or index >= curve.point_count:
return MCPToolkitError.fail("INVALID_PARAMS",
"index %d out of range (point_count=%d)" % [index, curve.point_count])
var old_curve := curve.duplicate() as Curve2D
curve.remove_point(index)
_wrap_undo_redo(node, curve, old_curve, "MCP: path2d.edit_curve remove")
return _ok(curve)
static func _action_clear(node: Path2D, curve: Curve2D) -> Dictionary:
var old_curve := curve.duplicate() as Curve2D
curve.clear_points()
_wrap_undo_redo(node, curve, old_curve, "MCP: path2d.edit_curve clear")
return _ok(curve)
# -- Undo/Redo ---------------------------------------------------------------
static func _wrap_undo_redo(node: Path2D, curve: Curve2D, old_curve: Curve2D, action_name: String) -> void:
MCPToolkitUndoRedoAction.begin(action_name.trim_prefix("MCP: "), node) \
.do_property(node, &"curve", curve) \
.undo_property(node, &"curve", old_curve) \
.do_reference(curve) \
.undo_reference(old_curve) \
.commit_recorded()
# -- Helpers ------------------------------------------------------------------
static func _to_vec2(val) -> Vector2:
if val == null:
return Vector2.ZERO
if val is Vector2:
return val
if typeof(val) == TYPE_DICTIONARY:
return Vector2(float(val.get("x", 0.0)), float(val.get("y", 0.0)))
return Vector2.ZERO
static func _ok(curve: Curve2D) -> Dictionary:
return MCPToolkitSuccess.ok({
"point_count": curve.point_count,
"baked_length": curve.get_baked_length(),
})