Hi @beezle,
unfortunately, we cannot sign any NDAs for sent in files. We merely do offer the option to reach out to us with more sensitive information in a more private manner. I am sorry if I somehow led you to believe otherwise.
However, I just spotted something which might be have been your issue for you, could please try the adjusted code below? Otherwise I would mostly suspect you more having a write permission error than actually something being wrong with render queue. Which is not out of the question, but would be my last guess. I would suggest carefully looking for any console error messages while the script is running. Also check if the render path of the document itself is valid, as otherwise BatchRender
will fail.
Cheers,
Ferdinand
"""Demonstrates how to create batch render jobs for multiple cameras in a
scene file as discussed in:
https://plugincafe.maxon.net/topic/13059
Note:
* This script is designed for Cinema's native camera objects, to include
other camera types, either modify CAMERA_TYPES or adopt
get_scene_cameras to accommodate render engines which use tags to mark
"their" camera types.
* This script has been written for R23, i.e., relies on Python 3 syntax.
* This is not by any means production ready code, but rather a code
example. The script for example does not handle preventing overwriting
existing files, some proper batch render status feedback, etc.
"""
import c4d
import os
import time
# The node types which we consider to be a camera.
CAMERA_TYPES = (c4d.Ocamera,)
def get_scene_cameras(doc):
"""Returns all relevant camera objects in the passed document.
Args:
doc (c4d.documents.BaseDocument): The document to get the camera
objects for.
Returns:
list[c4d.BaseObject]: The retrieved camera objects. Can be an empty
list.
Raises:
RuntimeError: When the document does not contain any objects.
"""
def get_next(op):
"""Returns the "next" node in a graph in a depth-first traversal.
Args:
op (c4d.GeListNode): The node from which to traverse.
stop_node (c4d.GeListNode): A node not to exceed in the horizontal
hierarchical traversal.
Returns:
c4d.GeListNode or None: A child or grandchild of op. Or
`None` when the graph has been exhausted.
"""
if op is None:
return None
elif op.GetDown():
return op.GetDown()
while op.GetUp() and not op.GetNext():
op = op.GetUp()
return op.GetNext()
# Get the first object and raise if there is none.
node = doc.GetFirstObject()
if not isinstance(node, c4d.BaseObject):
raise RuntimeError("Could not find any objects.")
# Return all nodes which are of type CAMERA_TYPES
result = []
while node:
if node.GetType() in CAMERA_TYPES:
result.append(node)
node = get_next(node)
return result
def save_camera_variations(doc, cameras):
"""Saves a variant of `doc` for each camera in `cameras`.
Args:
doc (c4d.documents.BaseDocument): The document to save the variations
for.
cameras (list[c4d.BaseObject]): The list of nodes in `doc` to use as
cameras.
Returns:
list[str]: The file paths of the generated variations.
Raises:
IOError: When a file could not be written.
RuntimeError: When `doc` has no file path, i.e. is an unsaved file.
"""
# Raise when the master document has not been saved yet.
if doc.GetDocumentPath() in (None, ""):
raise RuntimeError("Cannot be run on unsaved documents.")
# The render BaseDraw, the initial render camera and render data.
bd = doc.GetRenderBaseDraw()
initial_camera = bd.GetSceneCamera(doc)
rd = doc.GetActiveRenderData()
# The render path and the document name.
render_path = rd[c4d.RDATA_PATH]
render_path, render_ext = os.path.splitext(render_path)
document_name = os.path.splitext(doc.GetDocumentName())[0]
# Go over all cameras and store a document for each of them.
document_paths = []
cameras = sorted(cameras, key=lambda cam: cam.GetName())
SaveDocument = c4d.documents.SaveDocument
for i, camera in enumerate(cameras):
# Set the next camera as the render camera and modify the render data
# to set a new render path.
bd.SetSceneCamera(camera)
rd[c4d.RDATA_PATH] = f"{render_path}_{i}{render_ext}"
print (rd[c4d.RDATA_PATH])
doc.SetActiveRenderData(rd)
c4d.EventAdd()
# Save the file and store the save path.
path = os.path.join(doc.GetDocumentPath(),
f"{document_name}_{i}.c4d")
result = SaveDocument(doc=doc, name=path,
saveflags=c4d.SAVEDOCUMENTFLAGS_NONE,
format=c4d.FORMAT_C4DEXPORT)
if result is False:
raise IOError(f"Could not write file to: {path}")
print (f"Saved camera variation to: {path}")
document_paths.append(path)
# Set the render path of the document back to its initial value and set
# the camera back.
rd[c4d.RDATA_PATH] = f"{render_path}{render_ext}"
doc.SetActiveRenderData(rd)
bd.SetSceneCamera(initial_camera)
c4d.EventAdd()
return document_paths
def run_batch_jobs(paths):
"""Runs a batch renderer job with the `paths` generated by the rest of the
script.
Args:
paths (list[str]): The file paths for the Cinema 4D documents to
render.
Raises:
IOError: When a file path in `paths` is not accessible.
"""
batch = c4d.documents.GetBatchRender()
# Populate the batch renderer.
for i, path in enumerate(paths):
if not os.path.exists(path):
raise IOError(f"Could not access path: {path}")
batch.AddFile(path, i)
print (f"Added job for: {path}.")
# Run the renders.
batch.SetRendering(c4d.BR_START)
def main():
"""Entry point.
"""
# Get all cameras.
cameras = get_scene_cameras(doc)
# Generate new documents for each camera.
paths = save_camera_variations(doc, cameras)
# Render these new documents.
run_batch_jobs(paths)
if __name__ == "__main__":
main()