Undo in Plugin



  • On 26/02/2013 at 12:57, xxxxxxxx wrote:

    I´m trying to make proper use of undo, but it´s very confusing, SDK not being a big help here...

    I´ve got a dialogue with a number field (IDC_POS)  controlling the position of an object.

    The GeDialog.Command looks basically like this:

    def Command(self, id, msg) :  
          doc = c4d.documents.GetActiveDocument()  
          obj = doc.GetActiveObject()  
          doc.StartUndo()  
          doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)  
      
          # Rightclick Reset X to 0  
          if id == IDC_POS and "Rightclick" == True:  
                ......  
      
          # Value Change to Object Position  
          if id == IDC_POS:  
              if obj:  
                  x_value = self.GetReal(IDC_POS)  
                  pos = obj.GetAbsPos()  
                  pos.x = x_value  
                  obj.SetAbsPos(pos)  
                  c4d.DrawViews()  
      
          return True  
          doc.EndUndo()
    

    Undo works for rightclick on the number arrows, setting pos.x to 0.

    But if I drag the value with the mouse-button pressed, undo is repeating every little step, instead of the initial value. Using AddUndo within the if methods doesn´t help. Also it takes two undos to get going, same when I type an value in here.

    If somebody would have a solution, or maybe a better explanation on the undo stuff, you´d make me very happy ;-)

    I don´t really get the definition of Start and Add undo:

    StartUndo seems like a general toggle for undo support, but it still matters where exactly you put it? So it starts storing everything from here on? End Undo stops that storing...

    AddUndo basically tells you which object and undotype you want to perform, while an Undo does recover the objects state within the code at that AddUndo Command?

    Are there cases you use multiple Start/End Undos?



  • On 26/02/2013 at 13:08, xxxxxxxx wrote:

    Command() is called for every step of dragging the widget. Any AddUndo() calls encapsulated in StartUndo() and
    EndUndo() will result in a single undo-step. So you create an undo-step for each call to Command().



  • On 26/02/2013 at 13:40, xxxxxxxx wrote:

    Thanks Niklas, I figured that out too, but I just don´t know where else to put it.
    It needs to get the obj when transformation starts, but how?



  • On 26/02/2013 at 14:18, xxxxxxxx wrote:

    Hello schnupps,

    if you set Undo as follows,

    if id == IDC_POS:  
        
        
          if obj:  
            doc.StartUndo()  
          doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)  
          x_value = self.GetReal(IDC_POS)  
          pos = obj.GetAbsPos()  
          pos.x = x_value  
          obj.SetAbsPos(pos)  
            doc.EndUndo()  
          c4d.DrawViews()  
        
    

    it is just called if button is clicked and obj is not none.

    Greetings  
    rown  
    


  • On 26/02/2013 at 15:01, xxxxxxxx wrote:

    Hi Rown, that´s the first I tried but the same problem :/
    Undo repeats all substeps and it takes two undo to get going...



  • On 26/02/2013 at 16:17, xxxxxxxx wrote:

    Im not sure understanding the context properly. Is IDC_POS a button or a mouseclick? If its a mouseclick it may be the case that the id is called two times: click down and click end.



  • On 27/02/2013 at 03:03, xxxxxxxx wrote:

    IDC_POS is in this case a mouseclick -or drag, on a number field arrow.

    The two undos it takes seem to come, as you said, from the double input I get with a single left mouseclick.
    If I just have:

    def Command(self, id, msg) :  
          print "Event"
    

    I get two prints from a single left mouscelick, but just one from a rightclick, is this normal behaviour?

    And on the dragging the value-arrows side,  I´m still comletely lost. Tried hundreds of things like reacting to input message Data like BFM_ACTION_INDRAGbut no luck.
    Spent the whole yesterday trying to work that out 🤢



  • On 27/02/2013 at 08:47, xxxxxxxx wrote:

    Puhhh🤢 this is working by storing the first mouse drag event in a basecontainer (bc_md) and deleting it´s data by mouse release.

    But I´ve got still two undo steps for a keyboard entry. If I hit Enter the command functions two times, without an release info I could use.

    I still got the feeling I´m doing something overly complicated, which could be done in a much easier way ;-)
    And could it be that GetInputEvent isn´t doing anything? I never gotten any info out of it...

    class MainDialog(c4d.gui.GeDialog) :  
      MOUSE_CHECK_ID = 10099  
      doc = c4d.documents.GetActiveDocument()  
      obj = c4d.documents.GetActiveDocument().GetActiveObject()  
      
      # Container for mouse down check  
      bc_md = c4d.BaseContainer()  
      
      def CreateLayout(self) :  
          .......  
      
      def InitValues(self) :  
          .......  
      
      def Command(self, id, msg) :  
          c4d.StopAllThreads()  
          doc = c4d.documents.GetActiveDocument()  
          obj = c4d.documents.GetActiveDocument().GetActiveObject()  
            
          # Leftclick container  
          lc = c4d.BaseContainer()  
          # Rightclick container  
          rc = c4d.BaseContainer()  
          # Keyboard container  
          bc_keyboard = c4d.BaseContainer()  
      
          self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSERIGHT, rc)  
          self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSELEFT, lc)  
          self.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.KEY_ENTER, bc_keyboard)  
      
          state = lc[c4d.BFM_INPUT_VALUE]  
      
          # use basecontainer bc_md to get the "mouse-down"  
          if self.bc_md.GetData(self.MOUSE_CHECK_ID) == None and (state == 1 or bc_keyboard[c4d.BFM_INPUT_VALUE] == 1) :  
              doc.StartUndo()  
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)  
              self.bc_md.InsData(self.MOUSE_CHECK_ID, 1)  
      
          # If mouse is released, empty bc_md data  
          if self.bc_md.GetData(self.MOUSE_CHECK_ID) == 1 and (state == 0 or bc_keyboard[c4d.BFM_INPUT_VALUE] == 1) :  
              self.bc_md.RemoveData(self.MOUSE_CHECK_ID)  
      
          # Rightclick resets Edit Field to 0  
          if id == IDC_POS and rc[c4d.BFM_INPUT_VALUE] == True:  
              doc.StartUndo()  
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)  
              x_value = 0  
              pos = obj.GetAbsPos()  
              pos.x = x_value  
              obj.SetAbsPos(pos)  
              self.SetReal(OFFSET, 0.0, -10000000, 10000000, 1, c4d.FORMAT_METER)  
              c4d.EventAdd()  
              return True  
      
          # Value Change to Object  
          if id == IDC_POS:  
              if obj:  
                  x_value = self.GetReal(IDC_POS)  
                  pos = obj.GetAbsPos()  
                  pos.x = x_value  
                  obj.SetAbsPos(pos)  
                  c4d.DrawViews()  
      
          doc.EndUndo()  
          return True
    

    EDIT: Replaced the bc_md container with a variable, don´t aks me why I used a container 😠
    The problem is still there of course...



  • On 27/02/2013 at 09:44, xxxxxxxx wrote:

    Thanks for listening to my private blog ;-) I hope I don´t get annoying...

    Doing it like that with an self.keyb/mouse-_checker = 0 from CoreMessage did the job!
    Now onto Autokeying...

      
          # Mouse Checker  
          if self.mouse_checker == 0 and state == 1:  
              doc.StartUndo()  
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)  
              self.mouse_checker = 1  
      
          #Keyboard Checker  
          if self.keyb_checker == 0 and bc_keyboard[c4d.BFM_INPUT_VALUE] == 1:  
              doc.StartUndo()  
              doc.AddUndo(c4d.UNDOTYPE_CHANGE, obj)  
              self.keyb_checker = 1  
    ......  
      def CoreMessage(self, id, msg) :  
          if id != c4d.EVMSG_UPDATEHIGHLIGHT:  
              if obj != None:  
                  self.keyb_checker = 0  
                  self.mouse_checker = 0
    

Log in to reply