Correct Undoing



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 19/05/2008 at 06:00, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   10.5 
    Platform:   Windows  ;   
    Language(s) :     C++  ;

    ---------
    Hi,

    like already posted in former threads, I've got several problems with undoing. Is there somewhere a complete explanation of how undoing is handled by Cinema?

    Here's one example I'm not able to solve:

    > \>           BaseObject \*origin = BaseObject::Alloc(Onull); \>           origin->SetName ("Joint Hierarchy"); \> \>           doc->AddUndo (UNDO_CHANGE, jointOrigin); \>           jointOrigin = origin; \>           doc->InsertObject (origin, NULL, NULL); \> \>           doc->AddUndo(UNDO_NEW, origin); \> \>           BaseTag \*oTag = PluginTag::Alloc(10005); \> \>           BaseContainer \*originContainer = oTag->GetDataInstance(); \>           originContainer->SetBool (DESC_ORIGIN, TRUE); \>           originContainer->SetLink (DESC_TAGLINK, tag); \>           origin->InsertTag(oTag); \>           doc->AddUndo (UNDO_CHANGE_SMALL, tag); \>           data->SetBool (DESC_NO_ORIGIN, FALSE); \> \>

    UNDO_NEW seems to work but the UNDO_CHANGE_SMALL and UNDO_CHANGE do not. jointOrigin is set to NULL before but after undoing it still points (at the now non-existing object). Any ideas? Thx



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 22/05/2008 at 10:08, xxxxxxxx wrote:

    There seems to be several problems with the (incomplete?) example code you posted...

    • first and foremost, you should not be doing any AddUndo() on any object that is not yet attached to the document.
    • ...you are doing AddUndo() to 'jointOrigin' before assigning it to 'origin', which is not yet attached to the document (is 'jointOrigin' an object already existing in the scene/doc ahead of this sample code? Are you trying to actually _replace_ the pre-existing 'jointOrigin' object in the scene with a new 'origin' object you are creating with that code? - the code is not really clear enough for us to divine your intent :) ).
    • you don't show where 'tag' comes from.
    • you don't show where 'data' comes from.
    • etc.

    ...it would help if you could show (or comment on) how each variable is assigned and describe a little more detail about what you're trying to do.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 23/05/2008 at 02:37, xxxxxxxx wrote:

    Thx for the answer. This is just a bit of the code, the whole code is here: http://www.plugincafe.com/forum/display_topic_threads.asp?ForumID=4&TopicID;=3325&PagePosition;=2, but I think it could be confusing. So I try to explain this one.

    I'm trying to set the class variable BaseObject *jointOrigin, which represents a top level Null Object of a hierarchy. This code bit is inside an if-statement which asks if jointOrigin == NULL. In this case the top level object must be allocated.

    > <code>
    >
    >
    >           BaseObject *origin = BaseObject::Alloc(Onull); //allocate new top level object
    >           origin->SetName ("Joint Hierarchy");
    >
    >           doc->AddUndo (UNDO_CHANGE, jointOrigin); //as written in the SDK: UNDO_CHANGE BEFORE action is done
    >           jointOrigin = origin; //class variable is set
    >           doc->InsertObject (origin, NULL, NULL); //origin attached to the document
    >
    >           doc->AddUndo(UNDO_NEW, origin); // UNDO_NEW AFTER action is done
    >
    >           doc->AddUndo (UNDO_CHANGE_SMALL, tag); //UNDO_CHANGE_SMALL BEFORE action is done. tag represents my PluginTag itself
    >           data->SetBool (DESC_NO_ORIGIN, FALSE); // data represents my PluginTags container
    >
    > </code>

    Does this give any hints? And what exactly do you mean by "you should not be doing any AddUndo() on any object that is not yet attached to the document"? Does this mean, undoing is only possible with objects that are visible in the manager?

    Greetz



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 23/05/2008 at 07:39, xxxxxxxx wrote:

    That helps a little...
    Based on what you are showing above, take a look at this:
    (btw, use square brackets for CODE keyword)

        
        
          
        if( jointOrigin == NULL )  
        {  
             //allocate new top level object  
            BaseObject *origin = BaseObject::Alloc(Onull);
        
        
         
        
        
            // note that the following line is:  
            //  a) accessing a NULL 'jointOrigin' pointer (generally a BAD idea)  
            //  b) essentially trying to tell the document to make a backup copy of  
            //      an object (well, a NULL pointer, in this case) that is not yet attached  
            //      to it (it doesn't know about it yet)  
            //  c) not necessary to start with  
            doc->AddUndo(UNDO_CHANGE, jointOrigin);
        
        
         
        
        
            // as per your code, it now gets set to point to the newly allocated object  
            jointOrigin = origin;
        
        
         
        
        
            // ...and then added to the scene/doc  
            doc->InsertObject (origin, NULL, NULL);  
            doc->AddUndo(UNDO_NEW, origin);
        
        
         
        
        
            // do changes to your tag...  
            doc->AddUndo (UNDO_CHANGE_SMALL, tag);   
            data->SetBool (DESC_NO_ORIGIN, FALSE);   
        }
    // etc...  
    

    ...let's back up a bit and talk about what AddUndo() is actually doing internally.  A user activated 'Undo' operation should: "restore the document to the state it was in, prior to the last user-initiated action".  I don't have access to the code, but my understanding is that what AddUndo() is doing is essentially causing backup copies of objects to be created, so that if/when the user does an Undo, it reverts to the backup copy.
    With that in mind, let's assume that your plugin is a Tag Plugin (which it seems to be) and that the user activates some menu option that adds your tag to some mesh in the Object Manager ( and/or a Null object to the scene ).  From the user's perspective, his "last user-initiated action" was to activate the menu that adds your tag to some mesh.  In which case, the "doc->AddUndo(UNDO_NEW, tag)" ( and/or doc->AddUndo(UNDO_NEW, origin) ) should be the only AddUndo() you need ( unless you make other changes to the mesh object, in which case you'd be doing AddUndo() to on the mesh object itself ).
    But your code seems to indicate that the tag (or the Null object that your plugin adds to the scene) might or might not already exist when it's called so the more correct code would be:

        
        
          
        if( jointOrigin == NULL )  
        {  
             //allocate new top level object  
            jointOrigin = BaseObject::Alloc(Onull);  
           
            // add to the scene/doc  
            doc->InsertObject (jointOrigin, NULL, NULL);  
            doc->AddUndo(UNDO_NEW, jointOrigin);  // tell doc about new object
        
        
        
        
            // going to modify tag, so AddUndo() on the pre-existing tag is appropriate here  
            doc->AddUndo (UNDO_CHANGE_SMALL, tag);   
            data->SetBool (DESC_NO_ORIGIN, FALSE);  
        }
        
        
        
        
        // etc...  
        
    

    ...note that if 'jointOrigin' didn't exist yet, there's no need to do any AddUndo() for changes to that variable... just allocate it, add it to the doc, then do the UNDO_NEW to tell the doc about it being added.   Once it exists in the document, if code below there modifies it, then additional AddUndo()'s can/should be added (again, a lot of if/when AddUndo() is needed would depend on knowing more details about what your code is doing and how it's structured).
    I hope the above makes some sense ;).



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 23/05/2008 at 08:14, xxxxxxxx wrote:

    ..."(again, a lot of if/when AddUndo() is needed would depend on knowing more details about what your code is doing and how it's structured)."
    What I meant by that is (for example)...
    Note that you only need one AddUndo() for any/all changes applied to some object/tag per 'user-initiated action'.  So, let's say that you're going to do multiple changes to your tag data...

        
        
          
            if( x )       data->SetBool (DESC_X, TRUE);  
            if( y )       data->SetBool (DESC_Y, TRUE);  
            if( z )       data->SetBool (DESC_Z, TRUE);  
            // etc.  
        
    

    ...when you want to implement Undo functionality for the above, you might be tempted to do something like:

        
        
          
            if( x )  
            {  
               doc->AddUndo (UNDO_CHANGE_SMALL, tag);  
               data->SetBool (DESC_X, TRUE);  
            }  
          
            if( y )  
            {  
               doc->AddUndo (UNDO_CHANGE_SMALL, tag);  
               data->SetBool (DESC_Y, TRUE);  
            }  
          
            if( z )  
            {  
               doc->AddUndo (UNDO_CHANGE_SMALL, tag);  
               data->SetBool (DESC_Z, TRUE);  
            }  
            // etc.  
        
    

    ...but that could potentially be overkill (depending on which x/y/z were true at the time), because each of those AddUndo() calls makes a new copy of your tag (or at least some portion of your tag) and since all of these changes occur for a single "user-initiated action", you only need one copy of your tag - the one that existed before you made any/all changes for that action.
    A better approach would be to either keep track of whether you'd done an AddUndo() yet or not, or - if your code pretty much always changes 'something' - just always do one near the top of your code, then do whatever changes you need to...

        
        
          
            // do an AddUndo() here, to handle any changes below  
            doc->AddUndo (UNDO_CHANGE_SMALL, tag);
        
        
        
        
            if( x )       data->SetBool (DESC_X, TRUE);  
            if( y )       data->SetBool (DESC_Y, TRUE);  
            if( z )       data->SetBool (DESC_Z, TRUE);  
            // etc.  
        
    

    ...again, this depends a lot on what your code needs to do and how it's structured.  In cases where it's more complex, with multiple routines that may or may not ultimately end up doing any changes, another approach is to write a simple routine that keeps track of whether AddUndo() has been called yet for some object/tag and then always call that routine instead of AddUndo() directly.
    Regardless of how you structure things, you need to make sure that you're calling doc->StartUndo() before doing any AddUndo() calls, and doc->EndUndo() after you've done all of your AddUndo()'s.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 23/05/2008 at 08:21, xxxxxxxx wrote:

    Hi, thx for your time and patience:). The problem is that the jointOrigin-pointer, which is a static class variable, should also be set back with undo. This does not happen, it still points somewhere and Cinema crashes. That's why I divided the undos for jointOrigin and origin. Also, the undo for the existing tag does not work. :( Have you got any more ideas? Maybe my whole undo chain does not work properly, I had several problems.

    For better explanation: The calls for startUndo() and endUndo() are placed in my Message()-method: startUndo() is called in case MSG_DESCRIPTION_INITUNDO, endUndo() in case MSG_DESCRIPTION_USERINTERACTION_END. Is this the proper way?

    Worst of all, in certain situations my tag-instance gets deleted and replaced by a new instance when calling undo. This apparently is the result of wrong placement of the undo-calls. Is there no detailed documentation on how undoing is done correctly?

    Greetz



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 23/05/2008 at 08:56, xxxxxxxx wrote:

    Hmm.. it's really tough to say without either seeing all your code or at least knowing more about what your code/plugin 'does'.  For example, it's a little odd to me that you've apparently got a Tag Plugin (tags, by nature are added to some object), that's also adding new objects to the scene and trying to keep track of and/or modifying those as well.
    I have also not used MSG_DESCRIPTION_INITUNDO / MSG_DESCRIPTION_USERINTERACTION_END before, so someone else may need to help you with whether that's appropriate usage of those messages (related to Start/EndUndo()).
    However, in any case, none of the Undo features are going to reset your jointOrigin pointer - you'll have to parse the doc to find  that Null object (or not), each time you want to do anything with it.  In other words, the Undo features will restore old objects in the scene, but they don't have direct access to the variables in your code, only your plugin does.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 23/05/2008 at 09:13, xxxxxxxx wrote:

    ...on that last point... the Undo features (both SDK calls and user keyboard/menu actions) of C4D are not there to fix/maintain your code variables, they only take care of what's in the scene/ document at any given time.
    I just went and looked at my various plugins.. most of which are CommandData plugins (activate, do thier thing, exit), that do the Start/EndUndo() from the Execute() method.  I do have a few TagData plugins, but the ones I have either don't have Undo methods in them, or they have a button in the AM that 'activate' them, so they behaive more like the CommandData plugins with a on-shot (synchronous) Execute() type function.
    I guess I haven't yet tried to do any asynchronous (Message() driven) Undo implementations.



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 23/05/2008 at 09:44, xxxxxxxx wrote:

    As Giblet notes, undos are a Cinema 4D database operation - working on the document element lists only. They will not change any of your internal data back to the way it was upon an undo operation. To be precise here, if you AddUndo() for an instance of one of your plugin objects (object/tag/material/etc.), a copy of the plugin object is placed onto the undo stack. If an undo operation is then issued, the data of the plugin object will be restored from the copy stored on the undo stack. But it will not revert pointers and data in other parts of your code. Realize that an undo operation creates a new instance which replaces the current one in the document - replete with class constructor, Init(), and so forth.

    I think that you are expecting jointOrigin to be restored to pointing to what it previously pointed before making the reassignment. This won't happen. If you really need for jointOrigin to be restored effectively, one way would be to store a BaseLink in the BaseContainer of the object for the code above and AddUndo() for it as well. An undo operation would then restore the link after it has been changed.


Log in to reply