I am trying to script the motion blur shutter curve (image below) for each frame of a render. I want to control the "openness" of the shutter curve with a list of values between 0 and 1. Ideally I would also like to be able to change this from one frame to the next within a single animation. I believe the variable I need to change is bpy.context.scene.render.motion_blur_shutter_curve
. How do I convert a list of values to a curve and pass it to this variable?
-
2$\begingroup$ Learned a LOT from both answers. Super helpful y'all, thanks a bunch. $\endgroup$– onbCommented May 21, 2021 at 1:53
2 Answers
I'd suggest read the values and create a dictionary out of point positions per frame, which allows to structure as well as access the values in a reasonable way:
dict = {
frame_number : [
(LOC_X, LOC_Y),
(LOC_X, LOC_Y),
(LOC_X, LOC_Y),
...
]
frame_number : [
(LOC_X, LOC_Y),
(LOC_X, LOC_Y),
(LOC_X, LOC_Y),
...
]
}
One pitfall is that the minimum point count for a curve is 2, means that you can not remove all the points of any existing curve.
The following demo sets the shutter curve based on custom data. Notice that set_curve()
is a slightly modified version of the reset_curve()
function from this Q&A:
import bpy
frame_dict = {
1: [(0.0, 0.0), (0.07, 0.5), (0.25, 0.9), (0.5, 1.0), (0.75, 0.9), (0.92, 0.5), (1.0, 0.0)],
2: [(0.0, 0.0), (0.125, 0.44), (0.375, 0.95), (0.5, 1.0), (0.625, 0.95), (0.875, 0.44), (1.0, 0.0)]
}
# Based on 'reset_curve()' from https://blender.stackexchange.com/a/64461/
def set_curve(curvemap, custom_points):
curve = curvemap.curves[0] # Get the curve
# Remove redundant points
if len(curve.points) > len(custom_points):
n = 0
while len(curve.points) > len(custom_points):
curve.points.remove(curve.points[n])
n += 1
# Generate missing points
if len(curve.points) < len(custom_points):
n = 0
while len(curve.points) < len(custom_points):
curve.points.new(0.1, 0.5)
n +=1
# Set the values and update UI
for c, point in enumerate(curve.points):
point.location = custom_points[c]
curvemap.update() # Optional call
C = bpy.context
shutter_curve = C.scene.render.motion_blur_shutter_curve
# test call
set_curve(shutter_curve, frame_dict.get(2))
In order to set the curve and render each frame, go through the key-value pair of the dict
and call Scene.frame_set(your_frame)
per iteration to update the scene:
for frame, points in frame_dict.items():
# Set the curve
set_curve(shutter_curve, points)
# Set the frame
C.scene.frame_set(frame)
# Render the frame
bpy.ops.render.render(write_still=True)
Alternatively you can setup a frame_change_post
handler which allows to preview and set your values on the fly, when changing the frame in the timeline (before rendering):
def set_curve_frame(scene):
shutter_curve = scene.render.motion_blur_shutter_curve
if scene.frame_current in frame_dict:
set_curve(shutter_curve, frame_dict.get(scene.frame_current))
bpy.app.handlers.frame_change_post.append(set_curve_frame)
Related:
I think this works (after running, a new text block should appear with a script restoring the curves):
import bpy, json
save_name = "load_blur_curve.py"
D = bpy.data
curves = {}
for name, scene in D.scenes.items():
curve = scene.render.motion_blur_shutter_curve.curves[0]
curves[name] = [(p.location.x, p.location.y, p.handle_type) for p in curve.points]
out = "curves = " + json.dumps(curves, indent=4)
out += '''
import bpy
from itertools import zip_longest
for scene_name, source in curves.items():
curve_wrapper = bpy.data.scenes[scene_name].render.motion_blur_shutter_curve
target = curve_wrapper.curves[0].points
for i in range(len(target) - len(source)):
target.remove(target[-1])
for oldp, newp in zip_longest(target, source):
if not oldp:
oldp = target.new(*newp[:2]) # probably can init with just 0, 0
oldp.location.x, oldp.location.y, oldp.handle_type = newp
curve_wrapper.update()
'''
text = D.texts.get(save_name)
if not text:
text = D.texts.new(save_name)
text.from_string(out) # OVERWRITE!
-
$\begingroup$ Funny, I understood the question in a completely different way. We'll see, UV'd. $\endgroup$ Commented May 19, 2021 at 11:44
-
$\begingroup$ @brockmann when you run this script, it basically generates your script, though it seems your script uses old solutions that are battle-tested, while mine is fresh and perhaps buggy. $\endgroup$ Commented May 19, 2021 at 12:36
-
$\begingroup$ OH and I didn't put anything for the animation. Also I noticed there's an option to add drivers, will test later if it actually works. $\endgroup$ Commented May 19, 2021 at 12:58