# How to get the usual render window behavior when using the render operator for multiple frames?

I have been trying to get a custom render button to work. The purpose is to render all markers in a timeline. There are examples that use handlers but I could not make it work properly:

import bpy
import os
import time
from bpy.types import Operator, Panel

class Marker_OT_Render(bpy.types.Operator):
bl_idname = "render.markers"
bl_label = "Render markers"

def execute(self, context):
# Variable to continue or stop rendering
complete = False
rendering = True

#output path
output_path = bpy.context.scene.render.filepath

#for filenaming
itemmarkers = bpy.context.scene.timeline_markers.items()
rendername = []
rendername.clear
for m in itemmarkers:
rendername.append(m[0])

#render limit verification
maxframe = bpy.context.scene.frame_end + 1

#position frame for the modal script
bpy.context.scene.frame_set(bpy.context.scene.frame_start - 1)

return {"RUNNING_MODAL"}

def modal(self, context, event):
if complete == False:
if rendering == False:
sc = bpy.context.scene
sc.frame_set(sc.frame_current + 1)
if sc.frame_current != maxframe:
sc.render.filepath = os.path.join(output_path, rendername[sc.frame_current] + ".png")
bpy.ops.render.render("INVOKE_DEFAULT", write_still=True)
else:
complete = True
return {"PASS_THROUGH"}
else:
time.sleep(5)
return {"PASS_THROUGH"}
else:
return {"FINISHED"}

class Render_PT_Buttontest(Panel):

bl_label = "Render Button Test"
bl_idname = "SCENE_PT_RENDERBUTTON"
bl_category = "Tool"
bl_space_type = "VIEW_3D"
bl_region_type = "UI"

def draw(self, context):
layout = self.layout

col = layout.column(align=True)
row = layout.row()
row.operator('render.markers', text="Render Button")
layout.separator()

def register():
bpy.utils.register_class(Marker_OT_Render)
bpy.utils.register_class(Render_PT_Buttontest)

def unregister():
bpy.utils.unregister_class(Marker_OT_Render)
bpy.utils.unregister_class(Render_PT_Buttontest)

if __name__ == "__main__":
register()


Q: Is there any way to get the render window when rendering these frames in the timeline?

• In general a tricky topic so: Why do you need that extra operator at all? What's your ultimate goal? Rendering all markers in the timeline? Might be helpful: blender.stackexchange.com/a/153254/31447 Also it would be nice answering our questions from yesterday... Apr 17 '20 at 10:22
• The end goal to get a script that works in a panel with other functions. And yes, the purpose is to render all markers in a timeline. The script you linked to has a error. It keeps looping the render of the same frame and I cannot stop it. Maybe am using it wrong. What question from yesterday are you referring too?? Apr 17 '20 at 10:41
• I don't know where it comes from. I was trying to see it there were additional render options to 2.82 in the python console. There it was.Have you have the function in your version? I haven't figured out if it is a plugin. Apr 17 '20 at 11:07
• Yes, I need the usual render window. Because other users will be using it. Since the rendering can take 10mins for each frame, people need to know that the render is in progress. The way blender freeze is not useful. Apr 17 '20 at 11:41

### The whole picture

Currently there is no way executing the render process in the background using bpy.ops.render.render() and even worse, the operator doesn't provide any arguments to pass a frame or the output path. It is like it is, and hopefully someone is going to implement that in the near future.

The limitations aside, we need to know whether the user started the rendering, if the user interrupted the process and if the rendering is finished to get the usual behavior of the render window, in any case.

In order to catch these 'states' we can wrap the render operator into a modal operator and use some of the available render handlers to get the information we need. The modal also keeps the UI responsive (non-blocking) and we can trigger a function in a very short (unnoticeable) interval by adding a timer to get the user interaction and handle all other events.

### Render camera markers in the timeline

To get the usual behavior and window for rendering the markers in the timeline, I would suggest the following changes to the operator of the linked answer:

Modal operator which allows to render all camera markers (base color of the shader is keyframed)

Assign the marker references to the shot variable in the execute method. That's simple enough and you still have access to all marker properties in the modal method:

def execute(self, context):
# Define the variables during execution. This allows
# to define when called from a button
self.stop = False
self.rendering = False

# Get all markers in a list and assign them to shot variable
self.shots = [m for m in context.scene.timeline_markers if m.camera is not None]
...


Read the first element of the shot-list to get TimeLineMarker.frame and TimeLineMarker.camera attribute in order to set the frame and assemble the output file path based on that:

    elif self.rendering is False: # Nothing is currently rendering.
# Proceed to render.
if self.shots:
# Get the first marker in the list
marker = self.shots[0]
frame_number = marker.frame
camera_name = marker.camera.name

# Assemble the file path
# -> output path + camera name + frame
scn.render.filepath = os.path.join(
self.path,
"{}_{}.jpg".format(camera_name, frame_number))

# Set the frame
scn.frame_set(frame_number)

# Call the render operator
bpy.ops.render.render("INVOKE_DEFAULT", write_still=True)

return {"PASS_THROUGH"}


Finally reset the output path in case the user interrupts the rendering:

if event.type == 'TIMER':
scn = context.scene

# No frames in the list or user hits ESC
if True in (not self.shots, self.stop is True):

...

# Reset the output path
scn.render.filepath = self.path
return {"FINISHED"}


Note: As of 2.81+ you'll have to add new depsgraph arguments to all handler methods:

# Set up the handler methods
def pre(self, scene, depsgraph):
self.rendering = True

def post(self, scene, depsgraph):
self.shots.pop(0)
self.rendering = False

def cancelled(self, scene, depsgraph):
self.stop = True


Otherwise the setup will cause an infinite loop for whatever reason.

• Thanks, the breakdown is very helpful. First question, why does the script render frame 1 only? Wouldn't it be best to render each frame then take the marker of that frame regardless of the camera name. Anyway, i'll learn from what you have post and try to adopt it to my needs. Many Thanks Apr 20 '20 at 11:52
• As you can see from the trusty gif the operator actually renders all frames which have a marker and a camera assigned to them. If the rendering is finished for that frame the post handler is called to remove that frame from the list and the next first item of the list is going to be rendered. What do you mean by "each frame of the camera name..."? The bare minimum is the frame number to save the image properly. However, for convenience I think it's nice to know which camera was used to render the frame (that's a common reason why using markers at all). Please explain what else @Sharl Apr 20 '20 at 12:20
• The script renders each marker yes, but the renders stays on frame 1. For example, we a cube that changes color on each frame, Red, Yellow, Green, Purple and Blue. With a marker for the corresponding color. The since the script renders only the Red cube. For the Camera, I only need one Camera. I'll post my modification. Should I post it here or as a new question/answer? Apr 20 '20 at 13:02
• I guess you're just missing a tiny detail. Suggest upload a basic scene, add that to your question: blend-exchange.giantcowfilms.com and I'll take a look @Sharl Apr 20 '20 at 13:04
• Tested the operator re the colors and it's working fine: i.stack.imgur.com/4xK9d.png Make sure the scene and material is set up properly by adding the keyframes to the shader (basecolor property in my case): i.stack.imgur.com/y6Fn7.png @Sharl Apr 20 '20 at 13:28

So this a response to the answer. My progress report :)

The main difference is that I am working with one camera and I am not be the end user.

Secondly, the user should be able to - render part of the timeline with the start and end frame - add additional text to the beginning and end of the filename

The start is pretty much the same.

class RenderMarkers_OT_LIST(bpy.types.Operator):
bl_idname = "render.colorways"
bl_label = "Render list"
bl_description = "Render colorways with marker in filename"

# Define some variables to register
_timer = None
shots = None
stop = None
rendering = None
path = "/tmp/"

# Set up the handler methods
def pre(self, scene, depsgraph):
self.rendering = True

def post(self, scene, depsgraph):
#self.shots.pop(0)
self.rendering = False

def cancelled(self, scene, depsgraph):
self.stop = True

def execute(self, context):
# Define the variables during execution. This allows
# to define when called from a button
self.stop = False
self.rendering = False


I changed the self.shots list to include only the string information of the marker

        # Get all markers in a list and assign them to shots variable
itemmarkers = bpy.context.scene.timeline_markers.items()
self.shots = []
self.shots.clear
for m in itemmarkers:
self.shots.append(m[0])

#don't know why but I need to begin 1 frame before the start
bpy.context.scene.frame_set(bpy.context.scene.frame_start - 1)

self.path = context.scene.render.filepath

bpy.app.handlers.render_pre.append(self.pre)
bpy.app.handlers.render_post.append(self.post)
bpy.app.handlers.render_cancel.append(self.cancelled)

# The timer gets created and the modal handler
# is added to the window manager

return {"RUNNING_MODAL"}


the main difference is the filenaming code

    def modal(self, context, event):
if event.type == 'TIMER':
scn = context.scene

# filename prefix ans suffix
firstpart = bpy.context.scene.TextInputProps.nameprefix
lastpart = bpy.context.scene.TextInputProps.namesuffix

# No frames in the list or user hits ESC
if True in (scn.frame_current >= scn.frame_end, self.stop is True):

# We remove the handlers and the modal timer to clean everything
bpy.app.handlers.render_pre.remove(self.pre)
bpy.app.handlers.render_post.remove(self.post)
bpy.app.handlers.render_cancel.remove(self.cancelled)
context.window_manager.event_timer_remove(self._timer)

# Reset the output path
scn.render.filepath = self.path
return {"FINISHED"}

elif self.rendering is False: # Nothing is currently rendering.
# Proceed to render.
# Get the marker from the list
marker_name  = self.shots[scn.frame_current]

#Create filename variable
if firstpart == "" and lastpart == "":
finalfilename = marker_name
elif firstpart == "" and lastpart != "":
finalfilename = marker_name +"_"+ lastpart
elif firstpart != "" and lastpart == "":
finalfilename = firstpart +"_"+ marker_name
else:
finalfilename = firstpart +"_"+ marker_name +"_"+ lastpart

# Assemble the file path and filename
scn.render.filepath = os.path.join(self.path, finalfilename + "." + scn.render.image_settings.file_format)

# Call the render operator
bpy.ops.render.render("INVOKE_DEFAULT", write_still=True)


Because I don't link the camera to the markers, the code needs to jump one frame

                # Set the frame
scn.frame_set(scn.frame_current + 1)

return {"PASS_THROUGH"}


I am sure I can simplify the code some more. But it is a good start for me.....

• Consider, that this is not a regular forum and you've posted your progress into the answer section of this site, I don't think that's useful for future visitors. A few beginner mistakes too: You already have a context reference in operator methods so you don't need bpy.context.scene*, just write context.scene*for all references instead. And again, the marker has a frame attribute, use it. You don't need scn.render.image_settings.file_format, use .jpg it's just a placeholder and will be overwritten. Next, to test a string you don't need ==, variable names should be lower_case. Apr 22 '20 at 9:07
• How to check if the string is empty? Apr 22 '20 at 9:09
• The error comes from using bpy.context.scene.frame_set(bpy.context.scene.frame_start - 1) - wrong references, see the comment above. Again to make my operator work for only one camera, get all markers self.shots = [m for m in context.scene.timeline_markers] and set the name for the camera to something like: camera_name = context.scene.camera.name (name of the scene camera) or just remove the variable, that's waaaayyy easier than your frame_current-approach. Apr 22 '20 at 9:12