Hey @tash,
you got the right idea and your code
rd = doc.GetFirstRenderData()
while True:
if rd.GetName() == "typeB":
break
rd = rd.GetNext()
is not a loop without an exit condition, it is only that it is a bit ugly one ;) At some point rd
will be None
, and then rd.GetName()
will raise an AttributeError because the type None has no such method. So, you could write instead:
rd = doc.GetFirstRenderData()
while rd: # or a bit more verbose as: while isinstance(rd, c4d.documents.RenderData):
if rd.GetName() == "typeB":
break
rd = rd.GetNext()
The problem would then however be, that this code cannot reach nested render data, e.g., RenderData_1.1 and RenderData_1.2 in the example below.
RenderData_1
RenderData_1.1
RenderData_1.2
RenderData_2
Your code also does not have an answer to what happens when there is no "typeB" to be found at all. Find an example of how I would write something like this below. Please keeo in mind that this is still an example that must be finished by yourself.
Cheers,
Ferdinand
"""Provides an example for discovering, loading, modifying, and then saving documents, to be loaded
by the batch renderer.
"""
import c4d
import os
import typing
def Iterate(node: c4d.GeListNode) -> c4d.GeListNode:
"""Walks #node depth first.
"""
if node is None:
return
while node:
yield node
for descendant in Iterate(node.GetDown()):
yield descendant
node = node.GetNext()
def ModifyDocument(doc: c4d.documents.BaseDocument, renderDataTargetName: str,
fallbackRenderData: typing.Optional[c4d.documents.RenderData] = None) -> None:
"""Modifies a document according to what you want to do.
This is just an example, the function could do anything.
Args:
doc: The document to modify.
renderDataTargetName: The name of the render data to load in the document.
fallbackRenderData: The render data to use when #renderDataTargetName is not present. Will
be ignored when None. Defaults to None.
"""
def _createNewRenderData():
"""Little helper function which inserts #fallbackRenderData into #doc.
"""
# Cloning is technically not necessary in this case, since we know #fallbackRenderData to
# be unique for each passed #doc, but if we weren't, we must clone the render data since a
# node can only be attached to one document (RenderData is also a BaseList2D, i.e., node).
clone: c4d.documents.RenderData = fallbackRenderData.GetClone(c4d.COPYFLAGS_0)
doc.InsertRenderDataLast(clone)
doc.SetActiveRenderData(clone)
if not isinstance(doc, c4d.documents.BaseDocument):
raise TypeError(f"Not a document: {doc}")
# Get the first render data in the document. If there is None, either create the default data
# and bail, or just bail when there is no default data.
rData: c4d.documents.RenderData = doc.GetFirstRenderData()
if rData is None:
if fallbackRenderData:
_createNewRenderData()
return
# Loop over all render data in the document.
for rData in Iterate(rData):
# We found a match, set the data as the active one and bail.
if rData.GetName() == renderDataTargetName:
doc.SetActiveRenderData(rData)
return
# When we reach this point it means that #renderDataTargetName was not contained in #doc.
if fallbackRenderData:
_createNewRenderData()
def main() -> None:
"""
"""
# Let the user select a directory to scan for documents to load.
directory: str = c4d.storage.LoadDialog(
title="Please select the scene directory", flags=c4d.FILESELECT_DIRECTORY)
if directory is None:
return
# Your render settings to look for and a list to put files into for the Batch render.
targetRenderSettings: str = "typeB"
batchRenderFiles: list[str] = []
# Walk that directory and find everything that is a Cinema 4D file.
for root, _, files in os.walk(directory):
for f in files:
if not f.endswith("c4d"):
continue
# Load the file into a Cinema 4D document and make up a new path for the modified
# document.
filePath: str = os.path.join(root, f)
doc: c4d.BaseDocument = c4d.documents.LoadDocument(filePath, c4d.SCENEFILTER_NONE)
if not isinstance(doc, c4d.documents.BaseDocument):
raise OSError(f"Failed loading into a c4d document: '{filePath}'")
name: str = os.path.splitext(doc.GetDocumentName())[0]
newPath: str = os.path.join(doc.GetDocumentPath(), f"modified_{name}.c4d")
# doc could not contain #targetRenderSettings, so we build some fallback data to use
# instead. They could also be loaded from another document, etc.
fallback: c4d.documents.RenderData = c4d.documents.RenderData()
fallback[c4d.RDATA_RENDERENGINE] = c4d.RDATA_RENDERENGINE_PREVIEWHARDWARE
fallback[c4d.RDATA_PATH] = f"render_{name}"
# Modify #doc to whatever you want to do.
ModifyDocument(doc, targetRenderSettings, fallback)
# Save the document and append the file to the to be rendered files.
if not c4d.documents.SaveDocument(doc, newPath, c4d.SAVEDOCUMENTFLAGS_NONE,
c4d.FORMAT_C4DEXPORT):
raise OSError(f"Could not save {doc} to '{newPath}'.")
batchRenderFiles.append(newPath)
# Get the batch renderer.
batchRender: c4d.documents.BatchRender = c4d.documents.GetBatchRender()
# Start adding the newly crated files as render jobs and then open the batch render dialog.
for i, file in enumerate(batchRenderFiles):
batchRender.AddFile(file, 0)
batchRender.Open()
if __name__ == '__main__':
main()