Hello @lawzy,
Can you help in this part please? That would help a lot! Thanks again.
Broadly speaking, the answer is no, because we are not in the habit of writing solutions for users. Sometimes we make an exception, as I did here for you. But please understand that you must learn how to implement things yourself.
I think it goes something like this.. But I have no idea how to proceed.
if BatchRender.IsRendering() == True ...
Yes, that is roughly what you must do. You have to wrap this however into a loop as you are here only polling the renderer once if it is still running. You must instead poll until it is not running anymore. So, something like this:
while batchRender.IsRendering():
# The renderer is still rendering.
pass
# When this line is reached, the renderer has finished.
But there are also other hoops to jump through. I provided a draft for how you could implement this below.
Cheers,
Ferdinand
"""Demonstrates the to launch a render using the batch renderer and wait for it finish before
carrying out another task.
"""
import c4d
import typing
import os
import re
import time
from c4d.documents import BaseDocument, BatchRender, RenderData
doc: BaseDocument # The active document.
def RunBatchRenderJob(doc: BaseDocument) -> tuple[str, str]:
"""Renders the passed document with the render queue.
Returns the output path and file name pattern as defined in the render settings of the document.
This function is blocking, i.e., while it is rendering, you will not be able to interact with
Cinema 4D. This could be changed, but doing this would be up to you.
Args:
doc: The document to render.
Raises:
RuntimeError: On invalid arguments and failure to operate the render queue.
Returns:
The tuple (output directory, render_filename_pattern).
"""
# Get out when #doc is not a document, has not been saved yet, or the batch renderer is already
# running. You could also stop an ongoing rendering but I did not.
if not isinstance(doc, BaseDocument) or doc.GetDocumentPath() == "":
raise RuntimeError(f"Invalid document: {doc}")
batchRender: BatchRender = c4d.documents.GetBatchRender()
if batchRender.IsRendering():
raise RuntimeError(f"Batch renderer is running.")
# Disable all render jobs which are at the moment in the queue.
for i in range(batchRender.GetElementCount()):
batchRender.EnableElement(i, False)
# Get the path of the passed document file and render output path from the active render settings
# of the document.
filePath: str = os.path.join(doc.GetDocumentPath(), doc.GetDocumentName())
rdata: RenderData = doc.GetActiveRenderData()
renderPath: str = rdata[c4d.RDATA_PATH]
# A render path can come in three forms and we must figure out where the absolute path
# of the renders is without the file name with tokens.
#
# 1. An absolute path, e.g., c:\blah\blah\myRender_#camera_
# 2. A path relative to the document, e.g., output\myRender_#camera_
# 3. A file name to the document, e.g., myRender_#camera_
# Split the path and file name component of #renderPath, and make the renderPath absolute in
# relation to the document.
renderPath, renderName = os.path.split(renderPath)
if not os.path.isabs(renderPath):
renderPath = os.path.normpath(os.path.join(doc.GetDocumentPath(), renderPath))
# Add the job at the top, turn on the spin-indicator in the status bar, and start the rendering.
if not batchRender.AddFile(filePath, 0):
raise RuntimeError(f"Could not add render job for file: {filePath}")
c4d.StatusSetSpin()
batchRender.SetRendering(c4d.BR_START)
# Wait for the render to finish, this a blocking way to implement it, i.e., you will not be
# able to interact with Cinema 4D until the render has finished. There are more fancy ways to
# do this with threads which are not blocking, but doing this would be up to you. We wait for a
# second so that we do not bombard the renderer with IsRendering() polling.
while batchRender.IsRendering():
time.sleep(1)
# The rendering is done, clear the status bar and return the output path and filename pattern.
c4d.StatusClear()
return renderPath, renderName
def RenameFiles(path: str, namePattern: str) -> None:
"""Renames files #path by stripping away the frame counter postfix.
This could be done much more elaborately using the #namePattern argument. I did not do this here.
Args:
path: The path to inspect and rename files in. No recursion will happen, i.e., only files
in #path will be renamed, not files in #path/foo/ for example.
namePattern: The file name as defined in the render settings of the document, e.g.,
foo_#frame_1234. Not used here.
"""
if not os.path.exists(path):
raise RuntimeError(f"'{path}' does not exist.")
# A regular expression to match three or four digit frame counter at the end of a file name.
pattern: re.Pattern = re.compile(r"\d\d\d\d?$")
# Iterate over all files in #path and rename them.
for oldName in os.listdir(path):
oldPath: str = os.path.join(path, oldName)
if not os.path.isfile(oldPath):
continue
# Split the file name and extension and test if the file name matches #pattern.
name, extension = os.path.splitext(oldName)
if not pattern.search(name):
continue
# Strip away the frame counter part of the name and build a new full file path.
newName: str = pattern.sub("", name) + extension
newPath: str = os.path.join(path, newName)
# Rename the file or raise an error when lack access rights or such file does already exist.
try:
os.rename(oldPath, newPath)
except Exception as error:
print (f"Stepping over file access error: {error}")
print (f"Renamed '{oldName}' to '{newName}'.")
def main():
"""Runs the example.
"""
result: tuple[str, str] = RunBatchRenderJob(doc)
RenameFiles(*result)
if __name__ == "__main__":
main()