Adding Undos for CommandData Changes



  • Hello,
    I'd like to add undos for changes made in a CommandData plugin: changes to the GeDialog and changes made to Hyperfile data.

    Is this possible? My guess is not because these changes are not on the main thread, but it would be incredible to add this functionality to my plugin if so. I had to ask.

    I have seen the mention of undo messages from the Attribute Manager in the C++ documentation and posts on the forum which mention MSG_DESCRIPTION_INITUNDO but only saw mention of C4DAtom objects and ToolData/NodeData plugins.

    If this does work by some chance, how would I add interactions with my plugin as an undo?

    Thank you.



  • hi,

    sorry, the DoUndo function is broadcasting the message but not to GeDialog.
    But only to those Branches:

    70d0ea54-e354-4584-95f7-93fe7b21b99d-image.png

    So you can't catch the Undo Message in a GeDialog.

    About hiding the object, as the object will not be display anything, you can simply hide it in the object manager.
    The problem is that you have to find it in the hierarchy to retrieve the information (or store a baselink to it in the basecontainer's document)

    import c4d
    from c4d import gui
    
    # Main function
    def main():
        doc.StartUndo()
        doc.AddUndo(c4d.UNDO_CHANGE, op)
        op.ChangeNBit(c4d.NBIT_OHIDE, c4d.NBITCONTROL_SET)
        doc.EndUndo()
        
        c4d.EventAdd()
        
    # Execute main()
    if __name__=='__main__':
        main()
    

    To make it short, what you are asking is somehow possible, but not out of the box and not without taking care of a lot possible error to do it.

    Catching the "Undo" action from user isn't easy in a GeDialog, and storing its information and retrieve it, isn't easy.
    In c++ you could your life easier with a scenehook, but not that mush.

    Cheers,
    Manuel



  • Hi,

    Cinema's undo stack is limited to the content of documents, i.e. nodes, i.e. anything that is derived from C4DAtom. Neither GeDialog nor HyperFile do meet this requirement. To apply undos to a tool, that tool has to be a DescriptionToolData tool, something that is not available in Python (we do only have ToolData tools), which will effectively encapsulate the tool in a node.

    Personally I would just implement my own tool specific undo stack if I did need one in Python. An alternative route could be to implement your tool GUI as a NodeData node and then display that node via a DescriptionCustumGui in the dialog of the CommandData or ToolData plugin you want to write. The undos would then have to be applied to that dummy GUI node.

    I am not quite sure what you would expect an undo to do in the context of a HyperFile, but it will probably be rather hard to implement or even simply impossible on a logical/theoretical level.

    Cheers,
    zipit



  • hi,

    this is not possible out of the box.

    About the GeDialog, this could be possible if you create an object in the document, hide it and store your data there.
    In c++ you could create a sceneHook to store that NodeData.

    About the HyperFile, you have to create your own undo stack.

    I'm not sure MSG_DESCRIPTION_INITUNDO will help a lot in that case. But MSG_DOCUMENTINFO_TYPE_UNDO will.

    Cheers,
    Manuel



  • Thank you both for the replies.

    @m_magalhaes How would I hide it in a way that would be least noticeable to the user? In the past I've added things to layers and turned off their visibility in the Viewport and the Object Manager. Is there another way?

    How does the MSG_DOCUMENTINFO_TYPE_UNDO method work with GeDialog? This is for building my own undo stack, I assume? I found this snippet on the forum, but I believe it's for ToolData. When I tried listening to Messages from the GeDialog, MSG_DOCUMENTINFO and MSG_DOCUMENTINFO_TYPE_UNDO never came up.

      def Message(self, doc, data, type, t_data):
          if type==c4d.MSG_DOCUMENTINFO:
              info = t_data
              if info['type']==c4d.MSG_DOCUMENTINFO_TYPE_UNDO:
                  print "Undo Performed"
          return True
    

    Thank you!



  • Hi,

    you do not really have to hide the node. I would just attach a dummy document containing that node to your tool instance. It would also remove the necessity to clean up after yourself, because you do not want to leave (hidden) node bloat in your active document when the document is being closed. Another (dis-)advantage would be that this approach would be document agnostic (i.e. you could unfold the undo stack of your tool back to any point in any document that has been opened since Cinema started - within the bounds of Cinema's stack depth value).

    Cheers,
    zipit



  • hi,

    sorry, the DoUndo function is broadcasting the message but not to GeDialog.
    But only to those Branches:

    70d0ea54-e354-4584-95f7-93fe7b21b99d-image.png

    So you can't catch the Undo Message in a GeDialog.

    About hiding the object, as the object will not be display anything, you can simply hide it in the object manager.
    The problem is that you have to find it in the hierarchy to retrieve the information (or store a baselink to it in the basecontainer's document)

    import c4d
    from c4d import gui
    
    # Main function
    def main():
        doc.StartUndo()
        doc.AddUndo(c4d.UNDO_CHANGE, op)
        op.ChangeNBit(c4d.NBIT_OHIDE, c4d.NBITCONTROL_SET)
        doc.EndUndo()
        
        c4d.EventAdd()
        
    # Execute main()
    if __name__=='__main__':
        main()
    

    To make it short, what you are asking is somehow possible, but not out of the box and not without taking care of a lot possible error to do it.

    Catching the "Undo" action from user isn't easy in a GeDialog, and storing its information and retrieve it, isn't easy.
    In c++ you could your life easier with a scenehook, but not that mush.

    Cheers,
    Manuel



  • @m_magalhaes That's helpful, thank you, Manuel! 😄


Log in to reply