Sometimes, while waiting for a model to render, I think about my next project in Blender. One that I have thought about frequently is a render stats addon. An addon that provides information about the status and speed of a render. The code below is the initial foundation. In the future, I hope to expand this with further functionality.
The Code:
Below is the full code. In addition, you can find this in our Blender tools repository.
import bpy
import time
def render_start(scene):
global start_time
global num_frames
start_time = time.time()
num_frames = 0
print("++++++++++++++++++++++++++++++")
print("Render Started")
def render_end(scene):
end_time = time.time() - start_time
print("Render Finished: " + str(num_frames) + " frames in " + str(end_time) + " seconds")
if num_frames > 0:
print("Average: ", str(end_time / num_frames) + " seconds/frame")
print("++++++++++++++++++++++++++++++")
def frame_start(scene):
global frame_time
frame_time = time.time()
print("Frame Started")
def frame_end(scene):
global num_frames
num_frames += 1
frame_end_time = time.time() - frame_time
print("Frame Finished: ", str(frame_end_time) + " seconds")
bpy.app.handlers.render_init.append(render_start)
bpy.app.handlers.render_complete.append(render_end)
bpy.app.handlers.render_pre.append(frame_start)
bpy.app.handlers.render_write.append(frame_end)
bpy.app.handlers.render_cancel.append(render_end)
Blender Python Callbacks
The basic structure of the code is using something called a callback. A callback (also called a handler) is a function that is passed, usually as an argument, to another function. The passed function is intended to be called when some event happens in the future.
Blender has several built-in callbacks. We are going to use four:
- render_init: called when render starts. We will use to set up our variables.
- render_complete: called when the render ends. When this occurs, we will print our final stats.
- render_pre: called when the frame begins rendering.
- render_write: called after the image is written to disk.
Each of these is actually a Python list. To use a callback, we simply need to add a function to the respective callback list.
bpy.app.handlers.render_init.append(render_start)
bpy.app.handlers.render_complete.append(render_end)
bpy.app.handlers.render_pre.append(frame_start)
bpy.app.handlers.render_write.append(frame_end)
bpy.app.handlers.render_cancel.append(render_end)
Keep in mind that since you add the functions to a list every time you run the function, you need to clear the handlers or reload the file. Otherwise multiple functions will run every time you render.
Render Init
For the initialization callback, we will create a function, render_start, that uses the time module to save the start time of the render.
We first use the global keyword to create two global variables.
- start_time: We use time.time() to save the start time of the render. time.time() returns the total number of seconds that have occurred (including fractional seconds) since epoch, January 1, 1970.
- num_frames: We will use this to keep track of how many frames were rendered. We set this variable to 0 to start.
We need the ability to use the start time in other functions. Therefore, we use the global keyword to keep the two variables beyond of the scope of the render_start function. I initially tried to use Blender properties. However, I had a strange result. time.time() did not work consistently with properties. Sometimes the property would correctly store the current time, sometimes, the property would not change. I am still trying to figure out why. If you have ideas, please leave a comment below.
def render_start(scene):
global start_time
global num_frames
start_time = time.time()
num_frames = 0
print("++++++++++++++++++++++++++++++")
print("Render Started")
Render Complete
Once the render finishes, we call another callback function, render_end. This takes function calculates the total render time by taking the current time, time.time(), and subtracting the start_time.
We then print the information to the terminal. We also use the number of frames rendered to calculate the average time.
def render_end(scene):
end_time = time.time() - start_time
print("Render Finished: " + str(num_frames) + " frames in " + str(end_time) + " seconds")
if num_frames > 0:
print("Average: ", str(end_time / num_frames) + " seconds/frame")
print("++++++++++++++++++++++++++++++")
Frames
The frame code is almost identical to the render code (with the exception that we do not need to capture the number of frames or the average render time).
def frame_start(scene):
global frame_time
frame_time = time.time()
print("Frame Started")
def frame_end(scene):
global num_frames
num_frames += 1
frame_end_time = time.time() - frame_time
print("Frame Finished: ", str(frame_end_time) + " seconds")
How to See the Stats
Note that in order to see the results, you need to be able to view the terminal:
- Windows: Click Window->Toggle System Console
- Mac: More difficult, in Finder, go to Applications and right click on Blender and select “Show Package Contents”. Navigate to Contents->MacOS. Right click on blender and select Open With->Terminal.
Conclusion
This code is not complicated. It also doesn’t provide much more than the existing render stats. However, this code creates the foundation for future enhancements. I am interested in creating two enhancements in particular. First, I want to be able to save the render stats in a file. Second, I want the ability to check progress or get notifications about progress remotely. This will be very useful when rendering on a command line (e.g., a render server or render farm).