I am trying to write an addon that will give me an ability to set settings for multiple render sequences. I used to use bpy.ops.render.render(animation=True, write_still=True) function but it freezes the UI, then I used bpy.ops.render.render('INVOKE_DEFAULT', animation=True, write_still=True) but It does not work in a loop (cause it skips the order and renders the last ops call)
So I decided to use Modal Operator call to check if the render is active then do not call next render operator otherwise change the settings and call operator. It seems to work but I just need your expert advice on how to make it better, less buggy.
If you can test it in your scenario or let me know what might need some fix I will be more than glad to implement it!
import bpy
class RenderSeqOperator(bpy.types.Operator):
bl_idname = "object.render_seq_operator"
bl_label = "Render Sequence Operator"
index = 0
list_of_renders = []
def set_render_settings(self, rig_name, scene_name, cam_name, track_name, frame_start, frame_end, output_path):
# Get the armature object
if not rig_name:
print("No armature selected!")
return False
armature_obj = bpy.data.objects[rig_name]
if armature_obj.type != 'ARMATURE':
print("Selected object is not an armature!")
return False
# Get the scene by name
scene = bpy.data.scenes.get(scene_name)
if scene is None:
print(f"Scene '{scene_name}' not found!")
return False
# Set the current scene
bpy.context.window.scene = scene
# Mute all tracks
for track in armature_obj.animation_data.nla_tracks:
track.mute = True
# Activate the specified track
track_index = armature_obj.animation_data.nla_tracks.find(track_name)
if track_index == -1:
print(f"Track '{track_name}' not found!")
return False
armature_obj.animation_data.nla_tracks[track_index].mute = False
#set the camera active
cam_obj = bpy.data.objects[cam_name]
if cam_obj.type != 'CAMERA':
print("Selected object is not a camera!")
return False
bpy.context.scene.camera = cam_obj
# Set the render frame range
bpy.context.scene.frame_start = frame_start
bpy.context.scene.frame_end = frame_end
#bpy.context.scene.cycles.samples = samples
bpy.context.scene.render.fps = 12
bpy.context.scene.render.use_file_extension = True
bpy.context.scene.render.image_settings.color_mode = 'RGBA'
# Set the render output path
bpy.context.scene.render.filepath = output_path
return True
def modal(self, context, event):
if event.type in {'ESC'}:
return {'FINISHED'}
if not bpy.app.is_job_running('RENDER'):
if self.index < len(self.list_of_renders):
#set render settings
rig_name = self.list_of_renders[self.index][0]
scene_name = self.list_of_renders[self.index][1]
cam_name = self.list_of_renders[self.index][2]
track_name = self.list_of_renders[self.index][3]
frame_start = self.list_of_renders[self.index][4]
frame_end = self.list_of_renders[self.index][5]
output_path = self.list_of_renders[self.index][6]
if not self.set_render_settings(rig_name, scene_name, cam_name, track_name, frame_start, frame_end, output_path):
return {'FINISHED'}
# Call another instance of render with animation
bpy.ops.render.render('INVOKE_DEFAULT', animation=True, write_still=True)
RenderSeqOperator.index += 1
else:
print("Nothing to Render!")
return {'FINISHED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
#set list of renders
self.list_of_renders.append(["m2_rig", "m2","up", "walk_up", 0, 15, "C:/walk/up/"])
self.list_of_renders.append(["m2_rig", "m2","down", "walk_down", 0, 15, "C:/walk/down/"])
self.list_of_renders.append(["m2_rig", "m2","down", "idle_down", 0, 23, "C:/idle/down/"])
#add modal
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
Register the operator
def menu_func(self, context):
self.layout.operator(RenderSeqOperator.bl_idname)
def register():
bpy.utils.register_class(RenderSeqOperator)
bpy.types.VIEW3D_MT_mesh_add.append(menu_func)
def unregister():
bpy.utils.unregister_class(RenderSeqOperator)
bpy.types.VIEW3D_MT_mesh_add.remove(menu_func)
if name == "main":
register()
# test call
bpy.ops.object.render_seq_operator('INVOKE_DEFAULT')