jonSS As a quick hack you can pluck the manager from one of the built in plugins currently present in the editor tree. This too shouldn't be used in a real project, only when playing with the stuff to learn how it works. This now shouldn't confuse the runtime compiler:

if Engine.is_editor_hint():
	undoredo = get_tree().get_root().find_children("*", "EditorPlugin", true, false)[0].get_undo_redo()

    xyz thanks,
    what about the "EditorPlugin" script ?
    Ive made it using "add_custom_type". But the only way i know of on how to get the @tool node is in the handle(Object)function

    Couldnt there be way to to get the node "add_custom_type" in the enter tree ? And pass the undoRendo there ?
    I mean... Iam not doing anything in the "EditorPlugin", all the code is being done in the @tool node ?

    • xyz replied to this.

      jonSS Yes, the most convenient way is to inject the manager reference into the edited object in EditorPlugin::_handles(). Or declare a static reference in the edited object class and assign it once from EditorPlugin::_enter_tree()

      Alternatively you can name the plugin node and then each edited node can get the manager from it using something similar to the "hack" I posted above.

        xyz great thanks
        ill leave it has it is then in the _handles() function

        there is another problem...
        it seems the undo rendo is lost or doesnt change places when using diferent nodes

        If i use undo rendo on an "EditorInspectorPlugin" scene,
        it goes to the undo rendo on the other side ?

        inspector.gd

        #----//--ADD-itemList-layer-//----
        func _on_btn_add_layer_pressed():
        	var listSize = itemList.get_item_count();
        	var itemName = "layer " + str(listSize)
        	#-----------------undo-rendo-add-layer------------------------------
        	undoredo.create_action("tileDraw - add layer");
        	undoredo.add_do_method(self, "_do_add_layer", itemName, listSize);
        	undoredo.add_undo_method(self, "_undo_remove_layer", listSize);
        	undoredo.commit_action();
        	#-----------------------------------------------------------
        
        #---------//--UNDO-RENDO-ADD-LAYER-//--------------
        func _do_add_layer(layer_name: String, index: int):
        	itemList.add_item(layer_name)
        	PlugTileDrawNode2.layerName.append(layer_name);
        	PlugTileDrawNode2.layers.Lnum.append({});
        	PlugTileDrawNode2.layers.Lnum.resize(index + 1);
        	notify_property_list_changed()
        	
        
        func _undo_remove_layer(index: int):
        	if index >= 0 and index < itemList.get_item_count():
        		itemList.select(index-1, true);
        		_on_item_list_multi_selected(index-1, true);
        		itemList.remove_item(index)
        		PlugTileDrawNode2.layerName.remove_at(index)
        		PlugTileDrawNode2.layers.Lnum.remove_at(index)
        	
        	notify_property_list_changed()
        	pass;

        This happens when draw on layer / add layer / draw on layer / add layer...etc... the undo_rendo always goes into the tileDraw script

        • xyz replied to this.

          jonSS Editor undo queue is scene specific. Each edited scene has its own queue. That's why undo/redo commands are in the Scene menu 🙂. Read the docs for EditorUndoRedoManager class.

          If you want a node specific or otherwise independent system, use UndoRedo object. However this will not be tied with the editor undo queue and you'll have to add your own gui for issuing undo commands. This is likely not something you actually need here.

          Btw it's re-do, not re-ndo.

            xyz i dont know...
            this is the amount of code i have to do, just to merge ( the selected layers ) together into 1...
            it uses previous functions on item select, and previous vars... and runs remove layers at the end.

            I dont think the undo redo is supossed to fit in here ?
            wouldnt it just be simpler to just save everything inside a var, tileDraw.gd... inspector.gd, and use the undo redo to restore everything ?

            I just dont think the way of how it is, the undo rendo is supossed to deal with this ?

            • xyz replied to this.

              jonSS Again, it has nothing to do with undo system per se. This thread is meant as a tutorial on how it actually works.
              In your case your whole data architecture is likely not properly done. That's why you have problems conforming the whole thing to engine's undo system. So if you want/need to use that system, reorganize your plugin to play nice with it.

              From what I've seen so far in your code, you're not fully understanding how arrays/dictionaries work. Best to first entrench your knowledge about that in a simpler project. This is fundamental, so cursory understanding is not enough if you wish to make something more ambitious like a plugin.

              Btw just posting random excerpts of code form your project is again not a very good communication of the problem. For umpteenth time, isolate the problem into a fresh project, make a new thread about it.

              jonSS The thing with undo/redo for a complex object/action is that you need to have a function that does the operation and a function that undoes the operation, both changing the state of the object. How exactly to do this is closely knit with your data architecture. You typically need to store only necessary parameters to (un)perform the action. That's precisely what Godot's UndoRedo class helps you with. This is how undo is implemented in almost every app, it's not a Godot specific thing.

              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()

              Did you add all the code ?
              For me the undo redo doesnt work, changing the color replaces the color of all circles on screen
              I also had to use "class_name Drawable extends Node2D" instead of "CanvasItem" or i cant get node to show in the list

              • xyz replied to this.

                jonSS That's all the code. I never said the class will appear on the list of nodes. It won't because it inherits an abstract class. You can just attach the Drawable script to any existing canvas item node and it will work. But that all is beside the point. You can inherit any other canvas item class. Makes no difference for what the example is about.

                The color is for the whole node. It's just to differentiate between the nodes so we can see how undo properly functions with multiple nodes. Not meant to work as some kind of brush color and it's not included in the undoable action. Although that'd be easy to implement. I'm leaving it to you as a homework.

                I'm here in the business of demonstrating the basic key concepts in the most compact form, not finetuning the user experience 🙂

                  xyz In the case of the "EditorInspectorPlugin" its diferent

                  @tool
                  extends EditorInspectorPlugin
                  
                  const inspect0 = preload("res://inspector/inspector.tscn");
                  
                  func _can_handle(object):
                  	if object is tileDrawNode:
                  		return true
                  	return false
                  
                  func _parse_begin(object):
                  	inspect1 = inspect0.instantiate();

                  everytime you press the editor screen it triggres the ready function in "inspect1"... i only found out about it now,
                  maybe the undoRedo history of that tscn gets reset

                  • xyz replied to this.

                    jonSS Inspector plugin has nothing to do with undo. Undo happens with the data, not with the interface that displays it.

                    For some reason its reseting it... I dont have anything in the code that does that

                    If i use this:

                    tileDrawNode.gd

                    @tool
                    extends Node2D
                    #class_name tileDrawX1;
                    
                    func _process(_delta):
                    	
                    	if ( Input.is_mouse_button_pressed(MOUSE_BUTTON_LEFT) && mouseJustPress == false ):
                    		mouseJustPress = true;
                    		
                    		if ( drawText == null ):
                    			return;
                    		
                    		mouseGroup.Fnum.clear();
                    		#-----------------------------------------------------------
                    		#-----------------undo-rendo-draw-------------------------------
                    		undoredo.create_action("tileDraw - drawing Tiles");
                    		undoredo.add_undo_method(self, "remove_element", layers.Lnum[ layerPOS ].duplicate(true) );
                    		undoredo.add_do_method(self, "add_element", layers.Lnum[ layerPOS ].duplicate(true) );
                    		undoredo.commit_action();

                    I get 2 prints on the other side in the tscn the EditorInspectorPlugin spawns

                    tileDrawInspector.gd

                    @tool
                    extends EditorInspectorPlugin
                    
                    const inspect0 = preload("res://inspector/inspector.tscn");
                    
                    func _parse_begin(object):
                    	inspect1 = inspect0.instantiate();

                    this prints out 2 times everytime i mouse click on the editor, "inspector Ready"

                    inspector.gd

                    @tool
                    extends Control
                    
                    func _ready():
                    	print("inspector Ready")

                    Its the undoRedo block of code for some reason is deleting or restarting the inspector, i dont have anything else if i remove that block of code the inspctor.tscn ready function no longer prints ?

                    • xyz replied to this.

                      jonSS For some reason its reseting it

                      Who resets what? Remember that this is not a thread about your project. Have you managed to implement a simple undo system on your own in a fresh project following the model shown in my example? Do that and it'll make things a bit clearer. Don't just copypaste. Make your own variations. Add stuff to it. Do "what if" experiments with it. Tinker until you gain full understanding of how it works.

                      Again, the inspector has nothing to do with undo. If you're storing undoable data in an inspector then your architecture is not properly done. All undoable data needs to be on edited node's side. An inspector should only read that data and display it. Akin to classical model-view-controller design pattern.

                      Typically you'd implement something like update() or refresh() method in your inspector and call it every time you want to refresh what the inspector displays. In that method you'd read all the relevant data from the edited node and set the inspector's state accordingly. Everything that lives in the inspector should be totally disposable.

                      Also, not directly related, but all viewport input concerning the plugin (e.g. editing plugin supported nodes...) is best handled in EditorPlugin::_forward_*_gui_input(). See my example.

                        xyz I dont know man... You want to force your its working stuff, and its the other s guys fault, you start looking like an idiot.
                        I havent added anything to the inspector, about redo undo etc.. its on the plugIN node.
                        I havent done the redoUndo, all i know is it reset "EditorInspectorPlugin" ( func _parse_begin(object) ) and everything along with it "inspect1 " etc...
                        Ive made examples and tested it, and cant do anything to prevent those scripts startUp functions from running...
                        If there are idiots that believe in what you say, good congralutations reason is on your side... but it doesnt work... shared believed doesnt make anything work..

                          jonSS I see. Good luck with your future coding endeavors.

                          For the record, here's an example undo project that contains only my above code.

                          Lo and behold - it works! 😃 Who would've thunk it? 🐱

                          undo-example.zip
                          3kB

                            xyz great !!! what iam supossed to do now ? create a new forum account and use the Tor Browser ?

                            you need to add "EditorInspectorPlugin" and see if it reset...
                            Also theres no reason, for to undoRedo to reset those scripts. I dont know if you added it to "EditorPlugin" or the node the plugIN handels ?

                            not working in here:
                            https://github.com/godotengine/godot/issues/97502