BaseDocument Manual

About

A BaseDocument represents a Cinema 4D scene with all its content. Cinema 4D can manage multiple open scenes simultaneously in a document list. It is possible to load, create, edit, render and save scenes in the background. A document contains:

  • Objects, materials and tags
  • Render settings
  • Document settings
  • Layers
  • Takes
  • Scene hooks
  • Undo actions
Warning
The active BaseDocument is the scene currently displayed in the editor. This active BaseDocument must not be edited from another thread than the main thread. Watch out for functions marked as "called in a thread context". If one needs to change the document from an asynchronous dialog, StopAllThreads() must be called before.

BaseDocument objects are an instance of Tbasedocument.

// This example shows how to create a virtual document, add a cube and render the document.
// create document
if (newDoc == nullptr)
return true;
// create and add a new cube
if (cube != nullptr)
newDoc->InsertObject(cube, nullptr, nullptr);
// prepare bitmap
if (!bitmap)
return false;
bitmap->Init(1280, 720);
// define render settings
RenderData* rdata = newDoc->GetActiveRenderData();
if (!rdata)
return false;
BaseContainer renderSettings = rdata->GetData();
renderSettings.SetFloat(RDATA_XRES, 1280);
renderSettings.SetFloat(RDATA_YRES, 720);
// render the document
const RENDERRESULT res = RenderDocument(newDoc, renderSettings,
nullptr, nullptr, bitmap, flags, nullptr);
// show result
if (res == RENDERRESULT_OK)
ShowBitmap(bitmap);

Access

Cinema 4D can handle multiple open scenes in a document list:

To navigate the document list GetNext() and GetPred() can be used:

// This example iterates through all BaseDocuments in the document list.
// The active BaseDocument is marked with a star.
BaseDocument* const activeDocument = GetActiveDocument();
if (activeDocument == nullptr)
return true;
while (document)
{
const GeMarker& docMarker = document->GetMarker();
const GeMarker& activeDocMarker = activeDocument->GetMarker();
// use GeMarker to compare identity of the BaseDocuments
if (docMarker.IsEqual(activeDocMarker))
{
GePrint("Document: " + document->GetDocumentName().GetString() + " *");
}
else
{
GePrint("Document: " + document->GetDocumentName().GetString());
}
document = document->GetNext();
}
Note
GetActiveDocument() and GetFirstDocument() only work in Cinema 4D versions that have an active document (not in Team Render Client, Command Line etc.). Use GetActiveDocument() only in combination with dialogs etc. but NEVER in NodeData based plugins or expressions.
GeListNode::GetDocument() returns the BaseDocument that hosts a given entity. Use this method to access the document in a NodeData based plugin and all functions that are part of the execution or drawing pipeline. But never forget to check for nullptr, as there are situations where elements are not part of any document at all.

Allocation/Deallocation

A BaseDocument can be created with the usual tools:

AutoAlloc can be used to handle a temporary document. A new document can be added to the document list:

if (newDocument != nullptr)
{
InsertBaseDocument(newDocument);
SetActiveDocument(newDocument);
newDocument->SetDocumentName("new_document.c4d");
}
Note
BaseDocuments can also be created by loading Cinema 4D files. See Disc I/O below.

Destroy

A dynamically created BaseDocument should be destroyed with BaseDocument::Free(). To remove a BaseDocument from the document list use KillDocument().

Copy

BaseDocument is based on C4DAtom and can be cloned using C4DAtom::GetClone():

// This example clones the given document add adds it to the document list.
BaseDocument* clone = static_cast<BaseDocument*>(doc->GetClone(COPYFLAGS_0, nullptr));
if (clone != nullptr)
{
clone->SetDocumentName("clone.c4d");
}

Preview Image

When a scene is saved the current editor image is saved with the BaseDocument to create a thumbnail preview image.

// This example shows the preview bitmap in the Picture Viewer.
BaseBitmap* bitmap = doc->GetDocPreviewBitmap();
if (bitmap != nullptr)
{
ShowBitmap(bitmap);
}

Properties

Data

Multiple settings are stored inside the BaseDocument. The different types of settings are defined in DOCUMENTSETTINGS, the parameter IDs in ddoc.h.

// This example switches the statue of the "Use Generators" setting.
const Bool useGenerators = data.GetBool(DOCUMENT_USEGENERATORS);
// set new settings
bc.SetBool(DOCUMENT_USEGENERATORS, !useGenerators);

and

// This example accesses the document settings to set the "Author" setting.
if (!settings)
return false;
settings->SetString(DOCUMENT_INFO_AUTHOR, "User");
Note
BaseDocument::GetSettingsInstance() won't work with DOCUMENTSETTINGS_GENERAL.

Objects

A BaseDocument hosts multiple BaseObject based scene objects. These objects are organized in an object tree.

Note
Using BaseDocument::InsertObject() will trigger the message MSG_DOCUMENTINFO_TYPE_OBJECT_INSERT.

See also BaseObject Manual.

// This example searches for an object named "Cube".
// If it cannot be found a new cube with that name will be created.
BaseObject* const object = doc->SearchObject("Cube");
if (object == nullptr)
{
BaseObject* const cubeObject = BaseObject::Alloc(Ocube);
if (cubeObject)
{
cubeObject->SetName("Cube");
doc->InsertObject(cubeObject, nullptr, nullptr);
}
}
else
{
GePrint("found \"Cube\"");
}

In some cases multiple instances of a certain object class can be inserted into the scene but only one instance will be used (e.g. the Sky object). This is typically the "highest" instance.

// This example searches for the highest (and thus used) stage object
BaseObject* stageObject = doc->GetHighest(Ostage, false);
if (!stageObject)
return false;
GePrint("Found stage object \"" + stageObject->GetName() + "\"");

Materials

A BaseDocument hosts multiple BaseMaterial based materials. These materials are stored in a list and can be applied to objects in the scene.

See also BaseMaterial Manual

BaseMaterial* const material = doc->SearchMaterial("Green");
if (material == nullptr)
{
BaseMaterial* const greenMaterial = BaseMaterial::Alloc(Mmaterial);
greenMaterial->SetName("Green");
doc->InsertMaterial(greenMaterial);
}
else
{
GePrint("Found \"Green\"");
}

Selections

Inside a scene multiple objects, materials or tags can be selected.

// This example prints the name and type of all selected scene elements.
if (!selection)
return false;
doc->GetSelection(selection);
const Int32 cnt = selection->GetCount();
if (cnt == 0)
return true;
for (Int32 i = 0; i < cnt; ++i)
{
C4DAtom* element = selection->GetIndex(i);
// check if the given C4DAtom is a BaseList2D element
if (element && element->IsInstanceOf(Tbaselist2d))
{
BaseList2D* baseList2D = static_cast<BaseList2D*>(element);
GePrint(baseList2D->GetName() + " (" + baseList2D->GetTypeName() + ")");
}
}

Object selections can be managed separately:

Note
To get the position of the anchor of an object multi-selection one can use BaseDocument::GetHelperAxis().
To correctly add objects to the selection of active objects with BaseDocument::SetActiveObject() one must update internal caches by calling BaseDocument::GetActiveObject() after an object was added to the selection.
// This example gets all selected null objects.
// For each null object a cube is created at its position.
// The new cubes will be selected instead of the original nulls.
// get all selected null objects
if (!selection)
return false;
doc->GetActiveObjectsFilter(selection, true, Onull, NOTOK);
const Int32 cnt = selection->GetCount();
if (cnt == 0)
return true;
// clear object selection
doc->SetActiveObject(nullptr);
for (Int32 i = 0; i < cnt; ++i)
{
C4DAtom* element = selection->GetIndex(i);
BaseObject* nullobject = static_cast<BaseObject*>(element);
// create a cube
if (cube)
{
const Matrix nullMg = nullobject->GetMg();
cube->SetMg(nullMg);
doc->InsertObject(cube, nullptr, nullptr);
}
}

Material selections can be managed separately, too:

// This example gets the material that is assigned to the given object.
// If a material is found, it will be selected.
// get texture tag
BaseTag* tag = object->GetTag(Ttexture);
if (!tag)
return false;
// get material
TextureTag* ttag = static_cast<TextureTag*>(tag);
BaseMaterial* material = ttag->GetMaterial();
// set active material
if (material)

Finally tag selections can be managed:

// This example loops through all selected tags.
// If a tag is a Texture Tag, the given material is applied.
// get all selected tags
if (!tags)
return false;
doc->GetActiveTags(tags);
const Int32 cnt = tags->GetCount();
if (cnt == 0)
return true;
for (Int32 i = 0; i < cnt; i++)
{
C4DAtom* tag = tags->GetIndex(i);
// check if texture tag
if (tag->GetType() == Ttexture)
{
// set material
TextureTag* ttag = static_cast<TextureTag*>(tag);
ttag->SetMaterial(material);
}
}

RenderData

RenderData objects represent the render settings stored in a document. They allow access to the actual settings, multipass objects and post effects.

See also RenderData Manual.

// This example adds a new RenderData to the document and set this RenderData as the active one.
RenderData* const renderData = RenderData::Alloc();
if (renderData != nullptr)
{
renderData->SetName("Preview Render Settings");
doc->InsertRenderDataLast(renderData);
doc->SetActiveRenderData(renderData);
}

Scene Hooks

Scene hooks are used to add additional data to the document or to influence the execution pipeline. These scene hooks are stored in the document:

// This example accesses the Dynamics scene hook to disable Dynamics globally.
// gets the dynamics scene hook
BaseSceneHook* dynamicshook = doc->FindSceneHook(180000100);
if (!dynamicshook)
return false;
BaseContainer* bc = dynamicshook->GetDataInstance();
if (!bc)
return false;
bc->SetBool(WORLD_ENABLED, false);

Layers

Layers are used to organize a document. A layer is represented by a LayerObject and these LayerObject objects are organized in a tree:

See also Layer Manual.

// This example gets the layer root object to access the first layer.
GeListHead* layers = doc->GetLayerObjectRoot();
if (layers != nullptr)
{
LayerObject* layer = static_cast<LayerObject*>(layers->GetFirst());
if (layer != nullptr)
GePrint("First Layer: " + layer->GetName());
}

Takes

Takes allow to handle multiple versions of a scene within the same document. The takes of a document are stored in a TakeData object:

// This example simply creates a new take and makes it the current one.
TakeData* takeData = doc->GetTakeData();
if (!takeData)
return false;
BaseTake* newTake = takeData->AddTake("this is a new take", nullptr, nullptr);
takeData->SetCurrentTake(newTake);

See Take System Overview

Changed Mode

The change status of a document indicates that the scene was changed after the last time it was saved. The status is displayed with a little star (*) in the application title bar.

// This example closes and frees the currently active document only if it is not "dirty".
if (!activeDoc)
return false;
// check dirty state of the document
if (activeDoc->GetChanged() == false)
KillDocument(activeDoc);

Document Name and Path

A document is presented in the application using its filename. Because of this the name of the document cannot be obtained with BaseList2D::GetName() that returns a String and not a Filename.

// This example checks the suffix of the current document name.
const Filename docName = doc->GetDocumentName();
const String suffix = docName.GetSuffix();
// check if the suffix is defined
if (suffix.Content())
{
if (suffix == "c4d")
GePrint("Cinema 4D File");
else
GePrint("Filetype: " + suffix);
}
else
{
GePrint("unsaved document");
}

Level of Detail

The level of detail defines how detailed certain objects are displayed in the editor. This is typically used in custom ObjectData based generators.

// This example sets the LOD to the level set in the current render settings.
// check if the document uses render LOD in the editor
if (doc->GetRenderLod() == false)
{
RenderData* renderData = doc->GetActiveRenderData();
BaseContainer* bc = renderData->GetDataInstance();
const Float lod = bc->GetFloat(RDATA_LOD);
// set new LOD
doc->SetLOD(lod);
}
Note
The LOD buttons in the editor represent the values 25%, 50% and 100%.

Time

The current timeline position of a document and the timeline size is defined using BaseTime objects.

Note
Setting the current time will not animate the document. See paragraph Animate below.

Current time:

// This example jumps one second ahead and updates the document.
const BaseTime now = doc->GetTime();
const BaseTime target = now + BaseTime(1.0);
doc->SetTime(target);
// animate document
doc->ExecutePasses(nullptr, true, true, true, BUILDFLAGS_0);

Framerate:

Note
The framerate defined in the render settings is independent from the document framerate.
// This example synchronizes the document framerate with the render framerate.
RenderData* renderData = doc->GetActiveRenderData();
BaseContainer* bc = renderData->GetDataInstance();
const Int32 fps = bc->GetInt32(RDATA_FRAMERATE);
doc->SetFps(fps);

Timeline dimensions:

Used Time:

Loop Preview:

// This example moves the timeline loop to the start of the timeline.
const BaseTime minTime = doc->GetMinTime();
// current loop duration
const BaseTime loopDuration = doc->GetLoopMaxTime() - doc->GetLoopMinTime();
// move loop to the start
doc->SetLoopMinTime(minTime);
doc->SetLoopMaxTime(minTime + loopDuration);

Mode

A document can be in different modes defining which elements should be edited (objects, points, edges, etc.).

// This example sets the document mode based on the currently active selection tag.
BaseTag* tag = doc->GetActiveTag();
if (!tag)
return false;
// check tag type
switch (tag->GetType())
{
case Tpointselection: doc->SetMode(Mpoints); break;
case Tpolygonselection: doc->SetMode(Mpolygons); break;
case Tedgeselection: doc->SetMode(Medges); break;
}

Active Tool

The currently active tool is stored in the document.

// This example switches the mode and enables and configures the "Knife" tool.
if (tool)
Note
To edit the settings of the Move/Scale/Rotate tools the actual plugin instance has to be accessed with FindPlugin().

Axis and Plane

The helper axis represents the position of the handles used to move a multi-selection.

// This example creates a null object at the position of the helper axis.
// The currently selected objects (stored in "selection") are placed under that null object.
// check if model mode
if (doc->GetMode() != Mmodel)
return false;
// get helper object and matrix
const BaseObject* helperAxis = doc->GetHelperAxis();
if (!helperAxis)
return false;
const Matrix matrix = helperAxis->GetMg();
// create null object
if (!nullObject)
return false;
doc->InsertObject(nullObject, nullptr, nullptr);
nullObject->SetMg(matrix);
// make selected object child of the new null
for (Int32 i = 0; i < cnt; ++i)
{
C4DAtom* element = selection->GetIndex(i);
BaseObject* baseObject = static_cast<BaseObject*>(element);
const Matrix position = baseObject->GetMg();
baseObject->Remove();
doc->InsertObject(baseObject, nullObject, nullptr);
baseObject->SetMg(position);
}

Editor Windows

An editor window is represented by a BaseDraw object. Such a BaseDraw object allows access to the currently used camera. The render view is the editor window used to define the camera that will be used while rendering the final image.

See also BaseView / BaseDraw Manual.

// This example accesses the active BaseDraw to get its camera.
// Then it moves the active object into the view of that camera.
BaseDraw* baseDraw = doc->GetActiveBaseDraw();
if (!baseDraw)
return false;
BaseObject* cameraObject = baseDraw->GetEditorCamera();
// move active object into the camera view
const Matrix cameraMatrix = cameraObject->GetMg();
Matrix targetPosition = activeObject->GetMg();
targetPosition.off = cameraMatrix.off + (900.0 * cameraMatrix.v3);
activeObject->SetMg(targetPosition);

Net Context

If the document is currently handled inside a Team Render context can be checked using the net render context:

// This example checks if the current context is a Team Render context.
// If so, the error message will be printed just to the console, not displayed as a message box.
const BaseDocument* doc = op->GetDocument();
if (context)
GePrint("Some Error occured!");
else
MessageDialog("Some Error occured!");

Functionality

Animate

Scene elements can be animated by setting keyframes with specific values at a specific time. The BaseDocument class offers functions to create such keyframes:

// This example creates automatically keys for the given object if "Auto Key" is enabled.
BaseObject* undoObject = static_cast<BaseObject*>(cubeObject->GetClone(COPYFLAGS_0, nullptr));
if (!undoObject)
return false;
// edit the object
// create auto keys if "Auto Key" is enabled.
doc->AutoKey(undoObject, cubeObject, true, true, true, true, true, true);
BaseObject::Free(undoObject);

See also Animation Tracks.

To apply keyframe animation, expressions, generators and deformers the document has to be animated.

// This example executes the document at different frames
// and prints the position of the "Cube" object.
// save current frame
const BaseTime originalTime = doc->GetTime();
for (Int32 i = 0; i < 30; ++i)
{
BaseTime time(i, doc->GetFps());
doc->SetTime(time);
doc->ExecutePasses(nullptr, true, true, true, BUILDFLAGS_INTERNALRENDERER);
BaseObject* cube = doc->SearchObject("Cube");
if (cube)
{
const Vector position = cube->GetMg().off;
GePrint("Cube Position: " + String::VectorToString(position));
}
}
// reset frame
doc->SetTime(originalTime);

When a new keyframe is created a default template is used. This template can be edited:

// This example changes the default key of the document.
if (key != nullptr)
{
Bool overdub;
doc->GetDefaultKey(key, overdub);
// new keys should use linear interpolation
doc->SetDefaultKey(key, overdub);
}

The result of simulations is often iterative: The current frame depends on the results of previous calculations. To calculate the state of a particle simulation at a certain frame all frames before that have to be calculated:

// This example reads the emitter end time from the given particle object.
// The document time is set to this frame and SetRewind() is called to animate
// all frame before that frame.
GeData data;
// access PARTICLEOBJECT_STOP parameter
if (particleEmitter->GetParameter(DescID(PARTICLEOBJECT_STOP), data, DESCFLAGS_GET_0))
{
const BaseTime endTime = data.GetTime();
doc->SetTime(endTime);
doc->SetRewind();
}

Undo

The undo system of Cinema 4D allows to store operations in undo actions that can be executed to undo and redo user interaction.

See also Undo System Manual.

// This example creates a cube object and adds it to the document with an undo step.
if (!cube)
return false;
doc->StartUndo();
doc->InsertObject(cube, nullptr, nullptr);
doc->AddUndo(UNDOTYPE_NEW, cube);
doc->EndUndo();

Undo actions can be executed:

Pick Session

A pick session can be used to select multiple objects.

See PickSessionDataStruct.

Send Messages

Convert and Export

The objects of a BaseDocument can be converted to polygon objects and associated sounds and textures can be collected:

// This example prints the file paths of all textures of the given material selection.
BaseContainer textures = doc->GetAllTextures(materials);
BrowseContainer browse(&textures);
Int32 id;
GeData* data;
// loop through elements of the BaseContainer
while (browse.GetNext(&id, &data))
{
// check if the found element is a Filename
if (data && (data->GetType() == DA_FILENAME))
{
GePrint("Texture: " + data->GetFilename().GetString());
}
}
  • IsolateObjects(): Creates a new document that contains only copies of the given objects and their materials.
// This example creates a new BaseDocument containing the objects from the given selection.
// The new document is added to the document list and set at the active document.
BaseDocument* newDoc = IsolateObjects(doc, selection);
if (newDoc)
{
newDoc->SetDocumentName("isolated_objects.c4d");
}

Disc I/O

A BaseDocument can be created by reading if from a file on disc. It is possible to load *.c4d files and supported import file formats.

// This example loads the given c4d file and prints its min. and max. frame to the console.
// check if the Filename references a "c4d" file
if (filename.CheckSuffix("c4d"))
{
BaseDocument* loadedDoc = LoadDocument(filename, SCENEFILTER_0, nullptr);
if (loadedDoc)
{
const Int32 fps = loadedDoc->GetFps();
const Int32 startFrame = loadedDoc->GetMinTime().GetFrame(fps);
const Int32 endFrame = loadedDoc->GetMaxTime().GetFrame(fps);
// convert to String to print the data to the console
const String fileStr = filename.GetString();
const String startFrameStr = String::IntToString(startFrame);
const String endFrameStr = String::IntToString(endFrame);
// print to console
GePrint("Document " + fileStr + ": " + startFrameStr + " - " + endFrameStr);
BaseDocument::Free(loadedDoc);
}
}
  • SaveDocument(): Saves the document to a Cinema 4D file or to a supported export file format.
  • SaveProject(): Saves the document like the "Save Project with Assets" command.
// This example creates a new BaseDocument, adds an cube to it to save it to file.
if (!tempDoc)
return false;
if (!cube)
return false;
tempDoc->InsertObject(cube, nullptr, nullptr);

Render

A BaseDocument can be rendered independently from the GUI and the Picture Viewer into a BaseBitmap (see BaseBitmap Manual).

// This example loads the given c4d file.
// One frame of the loaded document is rendered and displayed in the Picture Viewer.
BaseDocument* loadedDoc = LoadDocument(filename, flags, nullptr);
if (loadedDoc)
{
RenderData* rdata = loadedDoc->GetActiveRenderData();
BaseContainer renderSettings = rdata->GetData();
// just render one frame
const BaseTime startFrame = renderSettings.GetTime(RDATA_FRAMEFROM, BaseTime());
renderSettings.SetTime(RDATA_FRAMETO, startFrame);
if (bitmap)
{
// prepare target bitmap
const Int32 width = renderSettings.GetInt32(RDATA_XRES);
const Int32 height = renderSettings.GetInt32(RDATA_YRES);
const IMAGERESULT imageRes = bitmap->Init(width, height);
if (imageRes == IMAGERESULT_OK)
{
// render the image
const RENDERRESULT res = RenderDocument(loadedDoc, renderSettings,
nullptr, nullptr, bitmap, renderFlags, nullptr);
// show result
if (res == RENDERRESULT_OK)
ShowBitmap(bitmap);
}
}
BaseDocument::Free(loadedDoc);
}

Messages

On certain events (load, save, render,...) a MSG_DOCUMENTINFO message is sent to elements contained in a document.

// This example catches MSG_DOCUMENTINFO in a ObjectData::Message() function.
// When the document is loaded the value of a old (legacy) parameter is copied
// into a new parameter.
{
DocumentInfoData* msg = static_cast<DocumentInfoData*>(data);
if (!msg)
return false;
// switch message sub-type
switch (msg->type)
{
{
BaseObject* op = static_cast<BaseObject*>(node);
if (!op)
return false;
if (!bc)
return false;
bc->SetInt32(NEW_PARAMETER, bc->GetInt32(OLD_PARAMETER));
GePrint("document is loaded");
break;
}
}
break;
}

Futher document related messages:

Further Reading