To conclude, here's a bit more elaborate example that implements undo in a plugin that draws grided circles on a canvas item node. It's somewhat similar to your situation @jonSS only simpler, for clarity. the action is implemented in a proper way so the undo works between any number of drawable nodes in the scene. I won't go into any explanations as I think the code is self explanatory enough. If not, just study it harder 😃

@tool
class_name Drawable extends CanvasItem
@export var color = Color.YELLOW
var draw_list = {}
func _draw():
for pos in draw_list.keys():
draw_circle(pos, 32, color, true)
func _get_property_list():
return [{"name": "draw_list", "type" : TYPE_DICTIONARY, "usage": PROPERTY_USAGE_STORAGE}]
@tool
extends EditorPlugin
var drawable = null # current selected node we draw on
var stroke_draw_list = {} # current stroke draw list, used for undo
func _handles(object):
drawable = object if object is Drawable else null
return object is Drawable
func _forward_canvas_gui_input(e):
if not drawable or e is InputEventKey:
return false
if e is InputEventMouseMotion and e.button_mask == MOUSE_BUTTON_MASK_LEFT:
add_to_current_stroke()
if e is InputEventMouseButton and e.button_index == MOUSE_BUTTON_LEFT and not e.is_pressed():
end_stroke()
return true
func add_to_current_stroke():
var position = snapped(Vector2i(drawable.get_local_mouse_position()), Vector2i(64, 64))
if not drawable.draw_list.has(position):
drawable.draw_list[position] = 1 # some dummy value, when drawing textures can be a texture reference
stroke_draw_list[position] = 1
drawable.queue_redraw()
func end_stroke():
remove_from_draw_list(drawable, stroke_draw_list)
var undoredo = get_undo_redo()
undoredo.create_action("draw")
undoredo.add_do_method(self, "add_to_draw_list", drawable, stroke_draw_list.duplicate())
undoredo.add_undo_method(self, "remove_from_draw_list", drawable, stroke_draw_list.duplicate())
undoredo.commit_action()
stroke_draw_list.clear()
func add_to_draw_list(object, stroke):
object.draw_list.merge(stroke)
object.queue_redraw()
func remove_from_draw_list(object, stroke):
for pos in stroke.keys():
object.draw_list.erase(pos)
object.queue_redraw()