Undo/Redo

3DS Max Plug-In SDK

Undo/Redo

See Also: Class RestoreObj, Class Hold.

Overview

3ds max has a built-in system for handling undo and redo. This allows users to undo their previous modifications to the database. The user may undo several operations, and also redo several undo operations.

The undo/redo system uses a global object call theHold. This is an instance of the class Hold. The developer calls methods of theHold to participate in undo/redo. Another class involved is RestoreObj. The developer creates instances of a class derived from RestoreObj to save the data needed to undo and redo the operation of the plug-in and to implement several methods required by MAX. The process works as follows:

Any operation that will modify the database checks to see if theHold is 'holding'. This means the 3ds max undo system has had the Begin() method called. If theHold is not 'holding' the developer should call theHold.Begin(). This signals the start of a potential undo operation. If theHold is holding, any operation that modifies the database must register a restore object with the system before it modifies the database. A restore object is an instance of the class RestoreObj. The restore object saves just enough data to restore the database to the state it was in when theHold.Begin() was called. It also saves data to allow a Redo operation to occur.

As a simple example consider a utility plug-in that allows the user to change the wireframe color of a node in the scene. The restore object would need to save the previous color and a pointer to the node being changed. It would also need to have storage to save the current color prior to an undo. This would allow it to undo the undo, or redo the operation.

In order to register the restore object with the system the developer calls theHold.Put(). This methods passes a pointer to the restore object. For example:

 if ( theHold.Holding() ) {

  theHold.Put(new ColorRestoreObj(currentColor,this));

 }

In the example above the ColorRestoreObj is a developer defined class derived from RestoreObj.

Once the restore object has been registered with theHold there are two potential cases to terminate the Begin(). The user can complete the operation or they may cancel it.

If the user completes the operation, the developer calls theHold.Accept(). This registers an undo object with the undo system and leaves the database in its modified state.

If the user cancels the operation, the developer calls theHold.Cancel(). This restores the database to its previous state and throws out the restore object.

In addition to saving the data it needs to restore the database, the restore object has three main methods it must implement:

Restore(int isUndo)

This is called to restore the database to the state it was in when theHold.Begin() was called. In the earlier example, the Restore() method would reset the wireframe color to the previous color. A flag, isUndo, is passed into this method to indicate if it was called in response to the Undo command. In the above example, if the flag is TRUE the developer must save the current color before restoring the previous color so if the user selects the Redo command the color could be reset to the state before the undo.

Redo()

This is called by the system if the user has selected the Redo command. This means the user wants to undo the undo. The developer restores the database to the state it was in before the last undo was called on the restore object.

Size()

This is called by the system to retrieve the size in bytes of the restore object. This is used to make sure all the accumulated restore objects to not grow beyond a manageable size.

Sometimes the Restore() method of the restore object may be called but not in response to the user selecting the Undo command. The undo system also allows values to be held in a buffer and then restored programmatically. For example, consider the operation of moving a node in the scene using the mouse. When the user first clicks the mouse button down to begin the operation, the state of the node's = 4) BSPSPopupOnMouseOver(event);;">transform controller is saved as part of a restore object. As the user moves the mouse, the plug-in tracks its position and updates the position of the node in the scene. Over and over the plug-in receives a message that the mouse has moved and the node in the scene must be re-positioned. Rather than storing incremental moves from the previously calculated position (which may accumulate error) it is more accurate to restore the mouse position to where it started and recalculate the transformation based on the distance from the original mouse point to the new mouse point. In order to reset the original position, the system calls Restore(). This puts the node back into its original position. Then when the new position is calculated this is applied to the node. This happens over and over until the user releases the mouse. When the mouse is released theHold.Accept() is called to register an undo object with the system.

In iterative operations such as this it is often useful to set one of the flags of Animatable to indicate that a restore object is being held. In the example above, when the user first clicks down on the mouse the developer checks if theHold is holding and if it is calls theHold.Put() to register a restore object. Then the developer calls a method of Animatable SetAFlag(A_HELD). This sets the A_HELD bit of the Animatable aflag data member to indicate the restore object is held. Then on each iteration the bit is tested to see if it is set and if so another restore object is not registered. A single restore object can be restored over and other again.

When theHold.Accept() or theHold.Cancel() is called, the system calls a method of the restore object called EndHold(). The developer may then clear the A_HELD bit to indicate the restore object is no longer being held.

Database Changes that are not Undoable

When 3ds max is exited, reset, etc., the save requester is only brought up if there is something on the undo stack. If a plug-in makes a change that is not undoable, the 'save dirty bit' must be set. This will indicate to the system that the save requester needs to be brought up. There are three APIs related to this:

Function:

BOOL GetSaveRequiredFlag();

Remarks:

Implemented by the System.

Returns TRUE if the 'save required' flag is set; otherwise FALSE. Note: this method does not tell you if saving is required, it just returns the value of the 'save required' flag. To really know if saving is required, you have to check the undo buffer as well as this flag. This is the purpose IsSaveRequired() below.

Function:

void SetSaveRequiredFlag(BOOL b=TRUE);

Remarks:

Implemented by the System.

Sets the 'save required flag'. Note that calling SetSaveRequiredFlag(TRUE) will cause the 'Save Changes' prompt to appear, but SetSaveRequiredFlag(FALSE) will not prevent it from coming up unless you also reset the undo buffer.

Parameters:

BOOL b=TRUE

TRUE to set the save dirty bit; FALSE to clear it.

Function:

BOOL IsSaveRequired();

Remarks:

Implemented by the System.

Returns TRUE if a change was made to the 3ds max database that would require the file to be saved. In other words, it returns TRUE if the save requester will be brought up when the user exits, resets, etc.; otherwise FALSE.

Flushing the Undo Buffer

If a plug-in developer needs to clear the undo buffer, two APIs are available to do so. A method of class Interface will flush the undo buffer:

void FlushUndoBuffer();

Developers may also use:

int GetSystemSetting(int id);

If the ID SYSSET_CLEAR_UNDO is passed to this method, the undo buffer is flushed. This function will return 0. Note that this will only work with version 1.1 of 3ds max or later.

An example of when to do this is when a creation object is deleted after some undo items have been put on the stack that refer to that object, for instance when a plug-in is doing a custom creation process. See the sample code in \MAXSDK\SAMPLES\OBJECTS\LIGHT.CPP.

If the creation object is deleted without being attached to a node, or if the creation is canceled, and if some undo objects have been logged since the creation object was created, then one should flush the entire undo stack. This brute force way of doing it is, unfortunately, the only safe way to ensure that the 3ds max undo stack is not corrupt with restore objects that point to deleted objects.

Summary

In summary, plug-In developers are encouraged to have their plug-ins fully participate in the undo redo system of MAX. The developer works with two classes. RestoreObj is used to store data for undoing and redoing modifications to the database. Hold is used to call methods of the system and the restore object to manage undo and redo.

For reference information see Class Hold and Class RestoreObj.