161 lines
5.7 KiB
GDScript
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(),
|
|
})
|