0
$\begingroup$

I'm trying to make an addon for Blender 4.1 that adds current seconds to rendered file name. We need it for our workflow when rendering overnight, to check later which frames had the biggest gap between seconds. We've tried other solutions and settled for this one.

Addon works perfectly in the main output filepath, in the Output Properties.

  1. I specify output path - "//@second@_"
  2. When I hit render, addon stores the original filepath "//@second@_"
  3. Addon replaces @second@ with the current second
  4. Blender render out an image with a file name "41_0001.png"
  5. Addon restores the original filepath "//@second@_"

The problem is when I need to render other passes (alpha) in the Compositor's File Output node.

  1. I specify output path - "//@second@"
  2. When I hit render, I can see in Blender UI that the addon replaces @second@ with the current second
  3. Blender creates a folder named "@second@"
  4. Addon restores the original filepath "//@second@"

For the actual pass (alpha) files:

  1. In the Compositor's sidebar in the File Subpath I specify output path - "//@second@alpha"
  2. When I hit render, I can see in Blender UI that the addon replaces @second@ with the current second
  3. In the folder named "@second@" Blender creates a file named "@second@_alpha_0001.png"
  4. Addon restores the original filepath "//@second@alpha"

What I need the structure to look like:

Either:

folder named "41" with files "41_0001.png", "43_0002.png", "45_0003.png"

Or:

folder named "41" with file "41_0001.png"

folder named "43" with file "43_0002.png"

folder named "45" with file "45_0003.png"

Can someone help me please?

My code here:

    import bpy
from bpy.app.handlers import persistent
from datetime import datetime

# Store the original file paths
original_file_paths = {
    "render_file_path": "",
    "node_base_paths": {},
    "file_slot_paths": {}
}

@persistent
def replace_seconds(scene):
    global original_file_paths
    # Store the original render file path with @second@
    original_file_paths["render_file_path"] = scene.render.filepath

# Get the project name from the .blend file name
    project_name = bpy.path.basename(bpy.data.filepath).rpartition('.')[0]

    # Get the current date and time
    from datetime import datetime
    now = datetime.now()

    # Replace @second@ in the render file path
    scene.render.filepath = scene.render.filepath.replace("@second@", str(now.second).zfill(2))

    # Replace @second@ for Compositor File Output nodes
    for node in scene.node_tree.nodes:
        if node.type == 'OUTPUT_FILE':
            # Store the original node base path
            original_file_paths["node_base_paths"][node.name] = node.base_path
            # Replace @second@ in the node base path
            node.base_path = node.base_path.replace("@second@", str(now.second).zfill(2))
            for file_slot in node.file_slots:
                # Store the original file slot path with the modified path as the key
                original_file_paths["file_slot_paths"][file_slot.path.replace("@second@", str(now.second).zfill(2))] = file_slot.path
                # Replace @second@ in the file slot path
                file_slot.path = file_slot.path.replace("@second@", str(now.second).zfill(2))

@persistent
def restore_seconds(scene):
    global original_file_paths
    # Restore the original render file path with @second@
    scene.render.filepath = original_file_paths["render_file_path"]

# Restore the original node base paths with @second@
    for node in bpy.context.scene.node_tree.nodes:
        if node.type == 'OUTPUT_FILE':
            node.base_path = original_file_paths["node_base_paths"].get(node.name, "")
            # Restore the original file slot paths with @second@
            for file_slot in node.file_slots:
                # Use the modified path to get the original path
                file_slot.path = original_file_paths["file_slot_paths"].get(file_slot.path, "")

def register():
    # Make sure to clear any existing handlers to avoid duplicates
    bpy.app.handlers.render_pre.clear()
    bpy.app.handlers.render_post.clear()

    # Register the handlers to trigger before and after each frame
    bpy.app.handlers.render_pre.append(replace_seconds)
    bpy.app.handlers.render_post.append(restore_seconds)

def unregister():
    bpy.app.handlers.render_pre.remove(replace_seconds)
    bpy.app.handlers.render_post.remove(restore_seconds)

if __name__ == "__main__":
    register()
$\endgroup$
4
  • $\begingroup$ Hello, this seems awfully complicated, can't you just compare the file metadata which contains the date of creation of each file ? $\endgroup$
    – Gorgious
    Commented May 5 at 8:46
  • $\begingroup$ I need this functionality for further operations. Otherwise I’d use simpler solution. $\endgroup$
    – Matt
    Commented May 5 at 18:27
  • $\begingroup$ I just re-read your question and I could not pinpoint exactly where your solution fails, could you rephrase it so the actual problem stands out more ? It is a little hard to test your setup without your actual working file. Thank you $\endgroup$
    – Gorgious
    Commented May 6 at 7:05
  • $\begingroup$ The files that are rendered out of Compositor are named for example "@second@_alpha_0001.png" instead of "41_alpha_0001.png". The code is a working file, it just need 'bl_info' in the header. $\endgroup$
    – Matt
    Commented May 6 at 10:45

1 Answer 1

0
$\begingroup$

I don't really understand why your base script is so complicated, I suspect chatGPT had a hand in it :p. I don'treally see the point in having a cache system here, since the seconds will always take 2 characters.

Here's a proposal, if your node tree looks like this

enter image description here

import bpy
from bpy.app.handlers import persistent
from datetime import datetime


@persistent
def replace_seconds(scene):
    seconds = str(datetime.now().second).zfill(2)
    
    file_output_node = next(n for n in scene.node_tree.nodes if n.type == "OUTPUT_FILE")
    file_output_node.base_path = file_output_node.base_path[:-3] + seconds + "/"

    for file_slot in file_output_node.file_slots:
        file_slot.path = seconds + file_slot.path[2:]


def register():
    # Make sure to clear any existing handlers to avoid duplicates
    bpy.app.handlers.render_pre.clear()

    # Register the handlers to trigger before each frame
    bpy.app.handlers.render_pre.append(replace_seconds)

def unregister():
    bpy.app.handlers.render_pre.remove(replace_seconds)

if __name__ == "__main__":
    register()
$\endgroup$
3
  • $\begingroup$ This just adds seconds, but I need it to be able to react to the filepath I type in the UI. I need it to replace '@seconds@', save the file with for example '47' in the name and the restore @seconds@ so I can use it in the next render again. $\endgroup$
    – Matt
    Commented May 6 at 12:05
  • $\begingroup$ The filepath doesn't matter since the file paths are split by 3 characters depending on the filepath length. Or maybe I don't understand the problem ? $\endgroup$
    – Gorgious
    Commented May 6 at 12:18
  • $\begingroup$ It does to me, it's @seconds@ now, maybe something else in the future. The timing is the problem here I think, I need the correct functionality to replace @seconds@ and save the file with desired data (seconds) in the filename. And then restore @seconds@ in the filename, so I can reuse it all over. $\endgroup$
    – Matt
    Commented May 6 at 13:29

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .