documents

Demonstrates basic document operations.

documents.py

"""Demonstrates basic document operations.

Topics:
    - Accessing or instantiating documents
    - Loading, saving, and merging documents
    - Document Modes
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__license__ = "Apache-2.0 License"
__version__ = "2027.0.0"
__date__ = "07/09/2026"

import c4d
import mxutils


doc: c4d.documents.BaseDocument # The active Cinema 4D document.
op: c4d.BaseObject | None # The currently selected object in the Cinema 4D document, can be None.

def main() -> None:
    """Runs the example.
    """
    print(f"\n--- Accessing or Instantiating Documents -----------------------------------------\n")

    # In many contexts, such as this script, you will either get a predefined module attribute #doc,
    # or a document argument to a function or method. If that is the a case, you should always use 
    # this attribute or argument.

    # Get the active document, i.e., the document that is currently in view in Cinema 4D. This is 
    # the same as the predefined module attribute #doc. Will never be None.
    activeDoc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument()
    print(f"{doc = }")
    print(f"{activeDoc = }")

    # Get the first document in the list of documents. Will never be None.
    firstDoc: c4d.documents.BaseDocument = c4d.documents.GetFirstDocument()

    # Since documents are nodes, just like any other scene elements, we can iterate over the list
    # of all opened documents with mxutils.IterateTree.
    for docNode in mxutils.IterateTree(firstDoc):
        print(f"{docNode = }")

    # Documents can also be instantiated like any other scene element. This can fail, we therefore
    # shield ourself against allocation failures by using mxutils.CheckType.
    newDoc: c4d.documents.BaseDocument = mxutils.CheckType(c4d.documents.BaseDocument())
    print(f"{newDoc = }")

    # Add the new document to the list of opened documents and make it the active document.
    c4d.documents.InsertBaseDocument(newDoc)
    c4d.documents.SetActiveDocument(newDoc)

    # Another method to get documents is from node via BaseList2D.GetDocument().

    # Add a cube to #newDoc and then get its document (which will be #newDoc).
    obj: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Ocube))
    newDoc.InsertObject(obj)

    objDoc: c4d.documents.BaseDocument = obj.GetDocument()
    print(f"{objDoc = }")

    print(f"\n--- Loading, Saving, and Merging Documents ---------------------------------------\n")

    # Create a document with a cube, and a document with a sphere.
    docA: c4d.documents.BaseDocument = mxutils.CheckType(c4d.documents.BaseDocument())
    docA.SetDocumentName("Document A")
    cube: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Ocube))
    docA.InsertObject(cube)

    docB: c4d.documents.BaseDocument = mxutils.CheckType(c4d.documents.BaseDocument())
    docB.SetDocumentName("Document B")
    sphere: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Osphere))
    docB.InsertObject(sphere)

    # Insert docA and make it the active document.
    c4d.documents.InsertBaseDocument(docA)
    c4d.documents.SetActiveDocument(docA)
    print(f"{c4d.documents.GetActiveDocument() = }")

    # Save docB to the desktop.
    path: str = c4d.storage.GeGetC4DPath(c4d.C4D_PATH_DESKTOP) + "/scene.c4d"
    c4d.documents.SaveDocument(docB, path, c4d.SAVEDOCUMENTFLAGS_NONE, c4d.FORMAT_C4DEXPORT)

    # Merge the file on the desktop (docB) into the active document (docA). Our scene should now
    # contain both the cube from docA and the sphere from docB.
    c4d.documents.MergeDocument(c4d.documents.GetActiveDocument(), path, 
                                c4d.SCENEFILTER_OBJECTS | c4d.SCENEFILTER_MATERIALS)

    # And now also load the document from the desktop into a new BaseDocument. This will be just an
    # object in memory, not an inserted document. Vut before we do that we close the original
    # document, so that Cinema will not complain about having the same document open twice.
    c4d.documents.KillDocument(docB)
    loadedDoc: c4d.documents.BaseDocument | None = c4d.documents.LoadDocument(
        path, c4d.SCENEFILTER_OBJECTS | c4d.SCENEFILTER_MATERIALS)
    print(f"{loadedDoc = }")

    # Load the document from the desktop directly as the active document, adding a new document to the
    # opened documents.
    c4d.documents.LoadFile(path)

    # Close the active document again, so that the example is less confusing to follow.
    c4d.documents.KillDocument(c4d.documents.GetActiveDocument())

    print(f"\n--- Get/Set Document Name and Path -----------------------------------------------\n")

    # Print the name and path of docA (which we did not save yet, but we gave it a name)
    print(f"{docA.GetDocumentName() = }")
    print(f"{docA.GetDocumentPath() = }")

    docA.SetDocumentPath(c4d.storage.GeGetC4DPath(c4d.C4D_PATH_DESKTOP))
    print(f"{docA.GetDocumentPath() = }")

    print(f"\n--- Modes ------------------------------------------------------------------------\n")

    # Get the mode (as in model, polygons, edges, points, uv, etc.) the document is in, and 
    # translate it to a human-readable symbol.
    mode: int = docA.GetMode()
    symbol: str = mxutils.g_c4d_symbol_translation_cache.Get(mode, "^M[a-z]")
    print(f"{mode = } ({symbol = })")

    # Set the document to polygon mode.
    docA.SetMode(c4d.Mpolygons)


if __name__ == "__main__":
    c4d.ClearPythonConsole()
    main()