Blender Asked on December 12, 2021
I am working on a Python-driven scientific animation using Blender 2.83.1. The animation includes four watt meters which are visually represented by Text
objects. Each text object has a custom property named "kw" which stands for kilowatts and is the value I wish to display. The text objects are driven to show the corresponding value using a handler and animated with keyframes. The color of the text is also changed to red or green depending on the sign of the number.
When I scrub through the timeline it works just fine in preview. However, when I attempt to render it, the hander does not seem to do anything and the rendered frames are all identical.
After studying Execute Python script between rendering animation frames and Handler frame_change_pre doesn't work in render which seem to be describing the same problem, and finding that the proposed solutions there don’t work for me, I came up with an ugly workaround shown in the code below as a Python routine called slow_render
. For each frame, it sets the frame, temporarily reduces the animation length to a single frame and renders that animation. This is ugly, slow, and only works if output is a series of still images. Also, I found that I had to set the frame twice to get the intended effect. I don’t know why.
frame_change_pre
seem to work during renders?The code I’m using to drive this is relatively simple. It has a handler called my_handler
which uses the value of the custom kw
property to alter the text body. It also has a main routine which installs the handler as a frame_change_pre
handler and then sets up key frames for the significant events in this simulation. The meters are named meter.load
, meter.solar
, meter.batt
and meter.net
. The first three are driven from data in the script and the last is calculated as the negative net value of the other three. In other words meter.load + meter.solar + meter.batt = -meter.net
.
import bpy
def my_handler(scene, depsgraph):
for meter in bpy.data.objects.data.collections['meters'].objects:
#meter = meter.evaluated_get(depsgraph)
kw = meter.get('kw')
color = 'delivered' if kw < 0 else 'received'
meter.data.body = "{:+.1f} {}".format(kw, "kW")
meter.active_material_index = 0
meter.active_material = bpy.data.materials[color]
print(scene.frame_current, meter.name, kw, color)
def slow_render():
scene = bpy.context.scene
lo, hi = scene.frame_start, scene.frame_end
for f in range(lo, hi):
bpy.context.scene.frame_set(f)
bpy.context.scene.frame_set(f)
scene.frame_start = f
scene.frame_end = f+1
bpy.ops.render.render(animation=True)
scene.frame_start, scene.frame_end = lo, hi
if __name__ == "__main__":
alreadyInitialized = len(bpy.app.handlers.frame_change_pre)
if not alreadyInitialized:
bpy.app.handlers.frame_change_pre.append(my_handler)
meter_name = ['meter.net', 'meter.load', 'meter.solar', 'meter.batt']
# define the power for (load, solar, batt) at each point
# predawn morning noon afternoon evening night
kws = [0.2, 0, 0], [4.1, 0, 0], [2.7, -5.5, 2.8], [3.2, -0.9, -2.3], [5.9, 0, 0], [12.1, 0, 0]
# calculate net value using list comprehension
[m.insert(0, -sum(m)) for m in kws]
# now assign values
framenum = 1
for pwr in kws:
print("Frame: {}".format(framenum))
for i in range(len(pwr)):
bpy.context.scene.frame_set(framenum)
meter = bpy.data.collections['meters'].objects[meter_name[i]]
meter['kw'] = float(pwr[i])
print("t{} = {}".format(meter.name, pwr[i]))
meter.keyframe_insert(data_path='["kw"]')
framenum += 30
else:
slow_render()
These are probably not relevant to the solution to this, but here are some additional details that may help reproduce exactly what I have here:
Based on the helpful feedback I received here, the video is complete.
Use frame_change_post
, and evaluated object
Test script.
Have added a collection to scene collection containing font objects with a "kw" custom property.
This property has been animated using keyframes.
To get the animated custom property value use the evaluated font object.
import bpy
def handler(scene, depsgraph):
meters = scene.collection.children.get("meters")
if meters:
for m in meters.objects:
print(m.name)
em = m.evaluated_get(depsgraph)
m.data.body = f"{em.get('kw', 0):4.2f} {m.get('kw', 0):4.2f}"
#bpy.app.handlers.frame_change_post.clear()
bpy.app.handlers.frame_change_post.append(handler)
Answered by batFINGER on December 12, 2021
Get help from others!
Recent Questions
Recent Answers
© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP