Undo System Manual

About

The undo system of Cinema 4D allows to store changes of elements so that these changes can be undone and redone. An undo action can contain multiple changes to multiple elements - all these changes will be undone in a single step when the undo action is applied. These undo actions are stored in an undo buffer and are created in a BaseDocument.

  • The undo system will create copies of the changed elements. This means that NodeData::Init(), NodeData::CopyTo() etc. of plugin classes will be called multiple times.
  • No undos should be created in expressions or generators etc. Undos should be created when the user interacts with the scene.
  • Using CallCommand() will create its own undo step (since it behaves like pressing the associated button in the GUI).
  • To start and end an undo based on some interaction in a GeDialog the messages ::BFM_INTERACTSTART and ::BFM_INTERACTEND can be used.

The Undo System

Creating Undos

Undo actions are added to the BaseDocument using these functions:

  • BaseDocument::StartUndo(): Starts an undo action. Must be paired with BaseDocument::EndUndo().
  • BaseDocument::AddUndo(): Adds an undo operation to the current action. Make sure to use the proper call sequence depending on undo type. Call only once for multiple changes on the same element.
  • BaseDocument::GetUndoPtr(): Returns the pointer to the element of the last operation.
  • BaseDocument::FindUndoPtr(): Returns the pointer to the element of the last operation of the given undo type.
  • BaseDocument::EndUndo(): Ends an undo action.
// This example creates a cube object and adds it to the document with an undo step.
BaseObject* const cube = BaseObject::Alloc(Ocube);
if (cube == nullptr)
return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION);
doc->StartUndo();
doc->InsertObject(cube, nullptr, nullptr);
doc->AddUndo(UNDOTYPE::NEWOBJ, cube);
doc->EndUndo();
NEWOBJ
A new object, material, tag, or other Cinema API node instance has been inserted into the document....
Definition: ge_prepass.h:7
#define Ocube
Cube.
Definition: ge_prepass.h:1118
#define MAXON_SOURCE_LOCATION
Definition: memoryallocationbase.h:69
const char * doc
Definition: pyerrors.h:226

The undo types are:

  • UNDOTYPE::CHANGE : Any change to an object, including hierarchy modifications; modification in positioning (e.g. object has been moved from A to B), substructures etc. Needs to be called BEFORE the change.
  • UNDOTYPE::CHANGE_NOCHILDREN : Same as UNDOTYPE::CHANGE, but without child modifications. Needs to be called BEFORE the change.
  • UNDOTYPE::CHANGE_SMALL : Change to local data only (e.g. data container). No substructures (e.g. no tags on an object) and no children. Needs to be called BEFORE the change.
// This example changes the radius of the given sphere object.
doc->AddUndo(UNDOTYPE::CHANGE_SMALL, sphere);
sphere->SetParameter(ConstDescID(DescLevel(PRIM_SPHERE_RAD)), 200.0, DESCFLAGS_SET::NONE);
NONE
Definition: asset_browser.h:1
CHANGE_SMALL
Change to the local data of the node as its data container. Does not apply for changes on substructur...
Definition: ge_prepass.h:4
#define ConstDescID(...)
Definition: lib_description.h:592
@ PRIM_SPHERE_RAD
Definition: osphere.h:6

UNDOTYPE::CHANGE_SELECTION : Change to point/poly/edge selection only. Needs to be called BEFORE the change.

// This example clears the point selection of the given PolygonObject.
PolygonObject* polygonObject = ToPoly(polyObject);
doc->AddUndo(UNDOTYPE::CHANGE_SELECTION, polygonObject);
BaseSelect* pSelect = polygonObject->GetWritablePointS();
pSelect->DeselectAll();
CHANGE_SELECTION
Change to the point, polygon, or edge selection of the node. Must be called before the change.
Definition: ge_prepass.h:5
MAXON_ATTRIBUTE_FORCE_INLINE const PolygonObject * ToPoly(const T *op)
Casts a BaseObject* to a PolygonObject*.
Definition: c4d_baseobject.h:2392

UNDOTYPE::NEWOBJ : New object/material/tag etc. was created. Needs to be called AFTER the insertion.

// This example adds a new object to the scene.
BaseObject* const cube = BaseObject::Alloc(Ocube);
if (cube)
{
doc->InsertObject(cube, nullptr, nullptr);
doc->AddUndo(UNDOTYPE::NEWOBJ, cube);
}

UNDOTYPE::DELETEOBJ : Object/node/tag etc. to be deleted. Needs to be called BEFORE removal.

// This example removes the given BaseObject.
doc->AddUndo(UNDOTYPE::DELETEOBJ, torus);
torus->Remove();
BaseObject::Free(torus);
DELETEOBJ
An object, node, tag, or other Cinema API node instance is about to be deleted. Must to be called bef...
Definition: ge_prepass.h:8

UNDOTYPE::BITS : Change to object bits, e.g. selection status. Needs to be called BEFORE the change.

// This example disables a video post effect.
doc->AddUndo(UNDOTYPE::BITS, videoPost);
videoPost->SetBit(BIT_VPDISABLED);
BITS
The Bits (BaseList2D::GetBit) of the object will be accessed.
Definition: c4d_accessedobjects.h:9
#define BIT_VPDISABLED
Videopost is disabled.
Definition: ge_prepass.h:926

UNDOTYPE::HIERARCHY_PSR : Change in hierarchical placement and PSR values. Needs to be called BEFORE the change.

// This example moves the object "child" under the object "parent".
doc->AddUndo(UNDOTYPE::HIERARCHY_PSR, child);
child->Remove();
doc->InsertObject(child, parent, nullptr);
HIERARCHY_PSR
Change in hierarchical placement and PSR values. Needs to be called before the change.
Definition: ge_prepass.h:14

Handle Undos

Undo actions can be applied using these functions:

  • BaseDocument::DoUndo(): Performs the undo action.
  • BaseDocument::DoRedo(): Performs the redo action.
  • BaseDocument::FlushUndoBuffer(): Flushes the undo buffer. This is only when needed when an operation that is not undoable is performed.
// This example adds a new undo action.
// If something went wrong during the operation the last actions are undone.
Bool success = true;
doc->StartUndo();
// do something
doc->AddUndo(UNDOTYPE::CHANGE_SMALL, sphereObject);
sphereObject->SetParameter(ConstDescID(DescLevel(PRIM_SPHERE_RAD)), 200.0, DESCFLAGS_SET::NONE);
// let's assume something went wrong, could not be done or the user aborted the action
success = false;
const Bool hasundo = doc->GetUndoPtr() != nullptr;
// end the undo step and check for success
if (doc->EndUndo())
{
// if something went wrong, undo all actions
if (!success && hasundo)
doc->DoUndo();
}
maxon::Bool Bool
Definition: ge_sys_math.h:46

User Interaction

If the user changes a parameter value in the Attribute Manager, Cinema 4D will create the proper undos. NodeData based plugins will receive a message to add custom operations to the automatically created undo action.

// This example catches MSG_DESCRIPTION_INITUNDO in a Message() function.
{
DescriptionInitUndo* const uData = static_cast<DescriptionInitUndo*>(data);
// check if data and BaseDocument can be accessed
if (!uData || !uData->doc)
break;
// add undo for dependent entities
BaseDocument* const doc = uData->doc;
return true;
}
#define MSG_DESCRIPTION_INITUNDO
Allows elements to create undo actions for the following parameter changes in the attributes manager....
Definition: c4d_baselist.h:390
Definition: node.h:10

Further Reading