Remove render sequence automatic numbering

Hello!

I have an issue where I need to render my animation sequence but without the automatic numbering (_000 or _0000). I am renaming the camera for this which means that each frame has a unique name and I want to replace the automatic numbering with this.

Is there a way to do so please ? I know some python for c4d but I'm certainly not an expert.

Please help!

Thank you!

@lawzy

Have you used the token available in render settings?
For example $camera.
It will be renamed with the current camera used for every frame, which I assume changes for every frame on your scene.

@bentraje Yes, that's exactly what I am using.. but instead of ONLY having the camera, it also adds the automatic numbering..

Anyone available to help with this please ? 🙂

Hello @lawzy,

Thank you for reaching out to us. What you want to do is not possible without some trickery because it is intrinsic to Cinema 4D to bake the frame number into file names and you cannot do anything about it, even from the API.

You have two options:

  • Use c4d.documents.RenderDocument: Here you do have full control over the file name as you retrieve the render result as a BaseBitmap and can therefore save it however you want. But especially for image sequences and baking in camera names into frames, this could be a lot of extra work.
  • Modify the file names after they have been created. In the end, you just want to remove a simple regular pattern. With Python you can easily do this as long as you have write access rights to the path where the image files lie. There are different flavors in which you could do this:
    • Stay in Cinema 4D and use c4d.documents.BatchRender: This is the interface for the Render Queue manager of Cinema 4D. The idea is simple, clean out any existing jobs, add your job, get the output path of the job, start its rendering, wait for it to be finished, modify the file names to your liking.
    • The same thing in green, but here we use a batch/shell script: Write a terminal script which first launches command line Cinema 4D executable to render a file. After that, launch a CPython script that cleans up the file names. The disadvantage is here that it will be not possible to use the Cinema 4D API and it therefore not being possible to retrieve the output paths of documents, they must either be passed to the terminal script, or be inferred by other means.

You could also just write yourself a CPython script which you can throw into any directory, and then rename files in the directory after a regular pattern with the script.

Cheers,
Ferdinand

  • ...The idea is simple, clean out any existing jobs, add your job, get the output path of the job, start its rendering, wait for it to be finished, modify the file names to your liking.

Hello @ferdinand, Thank you for your detailed answer, I appreciate it!

I am not an expert python dev, I just started like a month ago. I know how to clean, add, get output path and start rendering, however for the '' wait for it to be finished '' part, and change the naming, I have no idea how to do that because it's around 240 frames, and they have a different naming.

e.g :

frame-00-00
frame-00-01
frame-01-00
frame01-01
and so on..

I think it goes something like this.. But I have no idea how to proceed..

def main():
    
    doc = c4d.documents.GetActiveDocument()
    
    if BatchRender.IsRendering() == True:
        # I want to say wait until rendering is finished, but I am not sure how I can do that ?
        
    else : 
        #check if the dir has 240 images and if they are .png type
        # Apply the renaming
    
    
    
    c4d.EventAdd()
    
main()

Can you help in this part please ? That would help a lot! Thanks again.

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()

Hello @ferdinand

Thanks a lot for the code above, I really appreciate your help.

I changed a few things for match what I was looking for and now it's working perfectly. Thanks again!