I’ve made some changes to the script, but I don’t have the means to test it myself. Essentially moved the pre-render visibility changes into own macro defines using the same modal operator (but called with different arguments), which should execute before each render.
Do report back if there’s any issues.
bl_info = {
"name": "EXOLIFE Character Renderer",
"author": "MikeMS",
"version": (0,9),
"blender": (2,90,0),
"category": "Render",
"location": "View3D > EXOLIFE Panel",
"description": "Automation render of character parts",
"warning": "",
"doc_url": "",
"tracker_url": "",
}
import bpy
def get_macro():
class RENDER_OT_render_macro(bpy.types.Macro):
bl_idname = "render.render_macro"
bl_label = "Render Macro"
@classmethod
def define(cls, idname, **kwargs):
cls._is_set = True
op = super().define(idname).properties
for key, val in kwargs.items():
setattr(op, key, val)
@classmethod
def is_set(cls):
return getattr(cls, "_is_set", False)
old = getattr(bpy.types, "RENDER_OT_render_macro", False)
if old:
bpy.utils.unregister_class(old)
bpy.utils.register_class(RENDER_OT_render_macro)
return RENDER_OT_render_macro
class WM_OT_refresh(bpy.types.Operator):
bl_idname = "wm.refresh"
bl_label = "Refresh"
tag_finish: bpy.props.BoolProperty(options={'SKIP_SAVE'})
bp_settings_set: bpy.props.StringProperty(options={'SKIP_SAVE'})
use_blur: bpy.props.IntProperty(default=-1, options={'SKIP_SAVE'})
def modal(self, context, event):
if getattr(type(self), "finished", False):
self.report({'INFO'}, "Done")
type(self).finished = False
context.window_manager.event_timer_remove(self.t)
return {'FINISHED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
if self.tag_finish:
type(self).finished = True
return {'FINISHED'}
elif self.bp_settings_set:
return self.execute(context)
elif self.use_blur != -1:
context.scene.render.use_motion_blur = bool(self.use_blur)
return {'FINISHED'}
bpy.ops.render.render_macro('INVOKE_DEFAULT')
wm = context.window_manager
self.t = wm.event_timer_add(0.1, window=context.window)
wm.modal_handler_add(self)
return {'RUNNING_MODAL'}
def execute(self, context):
exolife_render_set_only(self.bp_settings_set)
return {'FINISHED'}
# Define a macro chain for a specific body part:
# Sets body part visibility and settings.
# Sets a render job.
def macro_define_by_bp(bp, **kwargs):
render_settings = {"animation":True, "write_still":True,
"use_viewport":False, "layer":'', "scene":''}
if kwargs:
render_settings.update(kwargs)
macro.define("RENDER_OT_ExolifeCharVisibility").bp_to_show = bp
macro.define("WM_OT_refresh").bp_settings_set = bp
render = macro.define("RENDER_OT_render")
for key, val in render_settings.items():
setattr(render, key, val)
#import EXOLIFE_CharRenderer
class RENDER_OT_ExolifeCharRenderer_reInit(bpy.types.Operator):
bl_idname = "render.exolifecharrenderer_reinit"
bl_label = "EXOLIFE ExolifeCharRenderer - reInit"
bl_description = "Create 'exolife_properties' Empy Object to store setting and make it all works!"
def execute(self, context):
try:
bpy.data.objects['exolife_properties']
except:
bpy.ops.object.empty_add(type='SPHERE', align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
bpy.context.active_object.name = 'exolife_properties'
# bpy.ops.script.python_file_run(filepath=EXOLIFE_CharRenderer.__file__)
return {'FINISHED'}
#def exolife_prop_object():
# try:
# bpy.data.objects['exolife_properties']
# except:
# bpy.ops.object.empty_add(type='SPHERE', align='WORLD', location=(0, 0, 0), scale=(1, 1, 1))
# bpy.context.active_object.name = 'exolife_properties'
#
# return {'FINISHED'}
class RENDER_OT_ExolifeCharVisibility(bpy.types.Operator):
"""Let's set character visibility!"""
bl_idname = "render.exolifecharvisibility"
bl_label = "EXOLIFE CharVisibility"
bl_options = {'REGISTER','UNDO'}
bp_to_show: bpy.props.StringProperty()
def execute(self, context):
bpy.data.node_groups["V-Selection"].nodes["Vertex Color"].layer_name = self.bp_to_show
if self.bp_to_show == "" or self.bp_to_show == "Head": bpy.data.node_groups["V-Selection_HeadHairs"].nodes["Vertex Color"].layer_name = "Head"
else: bpy.data.node_groups["V-Selection_HeadHairs"].nodes["Vertex Color"].layer_name = ""
bpy.data.objects['exolife_properties'].Exolife_Settings.bp_active = self.bp_to_show
return {'FINISHED'}
class RENDER_OT_ExolifeCharRenderer(bpy.types.Operator):
"""Let's render character!"""
bl_idname = "render.exolifecharrenderer"
bl_label = "EXOLIFE CharRenderer"
bl_options = {'REGISTER','UNDO'}
# Replaced eval statements with getattr.
def execute(data, context):
macro = get_macro()
settings = bpy.data.objects['exolife_properties'].Exolife_Settings
for i in range(settings.anims_count):
i += 1
if getattr(settings, f"anim{i}", False):
print(f"\n!!!!!### RenderProcess - ANIMATION: {getattr(settings, f'anim{i}_name', '')}\n")
macro.define("RENDER_OT_ExolifeAnimSet").anim = i
use_blur = int(getattr(settings, f"anim{i}_mb"))
macro.define("WM_OT_refresh").set_render_motion_blur = use_blur
if settings.bp_hand_l:
macro_define_by_bp("HandL")
if settings.bp_legs:
macro_define_by_bp("Legs")
if settings.bp_torso:
macro_define_by_bp("Torso")
if settings.bp_head:
macro_define_by_bp("Head")
if settings.bp_hand_r:
macro_define_by_bp("HandR")
# Macro has definitions. Start rendering.
if macro.is_set():
macro.define("WM_OT_refresh", tag_finish=True)
bpy.ops.wm.refresh('INVOKE_DEFAULT')
return {'FINISHED'}
def exolife_render(bp):
print("\n!!!!! RenderProcess - BodyPart: "+bp+"\n")
bpy.context.scene.render.filepath = bpy.data.objects['exolife_properties'].Exolife_Settings.file_path + "\\" + bpy.data.objects['exolife_properties'].Exolife_Settings.bp_active + bpy.data.objects['exolife_properties'].Exolife_Settings.bp_part_suffix + "\\" + bpy.context.object.animation_data.action.name + "\\###"
bpy.ops.render.render(animation=True, write_still=True, use_viewport=False, layer='', scene='')
# bpy.ops.render.render('INVOKE_DEFAULT', animation=True, write_still=True, use_viewport=False, layer='', scene='')
def exolife_render_set_only(bp):
print("\n!!!!! RenderProcess - BodyPart: "+bp+"\n")
bpy.context.scene.render.filepath = bpy.data.objects['exolife_properties'].Exolife_Settings.file_path + "\\" + bpy.data.objects['exolife_properties'].Exolife_Settings.bp_active + bpy.data.objects['exolife_properties'].Exolife_Settings.bp_part_suffix + "\\" + bpy.context.object.animation_data.action.name + "\\###"
class VIEW3D_PT_ExolifeCharVisibility(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "EXOLIFE"
bl_label = "Set BodyPart Visible:"
def draw(self, context):
bp_to_show = ""
props = self.layout.operator('render.exolifecharvisibility', text = "All Visible")
props.bp_to_show = bp_to_show
bp_to_show = "HandL"
props = self.layout.operator('render.exolifecharvisibility', text = bp_to_show)
props.bp_to_show = bp_to_show
bp_to_show = "Legs"
props = self.layout.operator('render.exolifecharvisibility', text = bp_to_show)
props.bp_to_show = bp_to_show
bp_to_show = "Torso"
props = self.layout.operator('render.exolifecharvisibility', text = bp_to_show)
props.bp_to_show = bp_to_show
bp_to_show = "Head"
props = self.layout.operator('render.exolifecharvisibility', text = bp_to_show)
props.bp_to_show = bp_to_show
bp_to_show = "HandR"
props = self.layout.operator('render.exolifecharvisibility', text = bp_to_show)
props.bp_to_show = bp_to_show
class RENDER_OT_ExolifeAnimSet(bpy.types.Operator):
"""Set this Animation (Action) and it's frame ranges"""
bl_idname = "render.exolifecharanimset"
bl_label = "EXOLIFE AnimSet"
bl_options = {'REGISTER','UNDO'}
anim: bpy.props.IntProperty()
def execute(scene, context):
bpy.ops.object.select_all(action='DESELECT')
bpy.data.objects[bpy.data.objects['exolife_properties'].Exolife_Settings.rig_name].select_set(True)
bpy.context.view_layer.objects.active = bpy.context.scene.objects[bpy.data.objects['exolife_properties'].Exolife_Settings.rig_name]
bpy.context.object.animation_data.action = bpy.data.actions[eval("bpy.data.objects['exolife_properties'].Exolife_Settings.anim"+str(scene.anim)+"_name")]
bpy.context.scene.frame_start = eval("bpy.data.objects['exolife_properties'].Exolife_Settings."+"anim"+str(scene.anim)+"_start")
bpy.context.scene.frame_end = eval("bpy.data.objects['exolife_properties'].Exolife_Settings."+"anim"+str(scene.anim)+"_end")
return {'FINISHED'}
class ExolifeSettings(bpy.types.PropertyGroup):
file_path: bpy.props.StringProperty(name="", description="Render to \ bodypart folder \ animation folder", default="", maxlen=1024, subtype="DIR_PATH")
rig_name: bpy.props.StringProperty(name="Rig name (necessarily)", default="", description="for example: JordanRig")
bp_active: bpy.props.StringProperty()
anims_max = 100
anims_count: bpy.props.IntProperty(name="",default = 1, min=1, max = anims_max, description="Amount of animations to work with")
try:
bpy.data.objects['exolife_properties'].Exolife_Settings.anims_count
anims_count = bpy.data.objects['exolife_properties'].Exolife_Settings.anims_count
except: anims_count = 1
for i in range(anims_max):
i = str(i+1)
exec('anim'+i+': bpy.props.BoolProperty(name="", default=True, description="Render - On/Off")')
exec('anim'+i+'_name: bpy.props.StringProperty(name="", default="", description="Animation name (Action name)")')
exec('anim'+i+'_start: bpy.props.IntProperty(name="",default = 1, description="Start frame")')
exec('anim'+i+'_end: bpy.props.IntProperty(name="",default = 10, description="End frame")')
exec('anim'+i+'_mb: bpy.props.BoolProperty(name="MB", default=True, description="Motion Blur - On/Off")')
bp_hand_l: bpy.props.BoolProperty(name="HandL", default=True, description="Enable/Disable rendering of this BodyPart")
bp_legs: bpy.props.BoolProperty(name="Legs", default=True, description="Enable/Disable rendering of this BodyPart")
bp_torso: bpy.props.BoolProperty(name="Torso", default=True, description="Enable/Disable rendering of this BodyPart")
bp_head: bpy.props.BoolProperty(name="Head", default=True, description="Enable/Disable rendering of this BodyPart")
bp_hand_r: bpy.props.BoolProperty(name="HandR", default=True, description="Enable/Disable rendering of this BodyPart")
bp_part_suffix: bpy.props.StringProperty(name="BodyPart folder suffix", default="_1")
class VIEW3D_PT_ExolifeCharRenderer(bpy.types.Panel):
bl_space_type = 'VIEW_3D'
bl_region_type = 'UI'
bl_category = "EXOLIFE"
bl_label = "Set Render Settings:"
def draw(self, context):
try:
bpy.data.objects['exolife_properties']
except:
self.layout.operator('render.exolifecharrenderer_reinit', text = "INITIALIZE!")
Exolife_Settings = bpy.data.objects['exolife_properties'].Exolife_Settings
self.layout.label(text="Render Path:")
row = self.layout.row()
row.prop(Exolife_Settings, "file_path")
col = self.layout.column()
col.alignment = 'RIGHT'
col.prop(Exolife_Settings, "rig_name")
row = self.layout.row()
row.label(text="Animations to render:")
row.alignment = 'RIGHT'
row.prop(Exolife_Settings, "anims_count")
# row.operator('render.exolifecharrenderer_reinit', text = "ReInit!")
row = self.layout.row(); row = self.layout.row()
for i in range(bpy.data.objects['exolife_properties'].Exolife_Settings.anims_count):
row.alignment = 'LEFT'
props = row.operator('render.exolifecharanimset', text = "Set"); props.anim = i+1;
row.prop(Exolife_Settings, "anim"+str(props.anim))
row.prop(Exolife_Settings, "anim"+str(props.anim)+"_name")
row.alignment = 'RIGHT'
row.prop(Exolife_Settings, "anim"+str(props.anim)+"_start")
row.prop(Exolife_Settings, "anim"+str(props.anim)+"_end")
row.prop(Exolife_Settings, "anim"+str(props.anim)+"_mb")
row = self.layout.row()
self.layout.label(text="BodyParts to render:")
row = self.layout.row()
row.prop(Exolife_Settings, "bp_hand_l")
row = self.layout.row()
row.prop(Exolife_Settings, "bp_legs")
row = self.layout.row()
row.prop(Exolife_Settings, "bp_torso")
row = self.layout.row()
row.prop(Exolife_Settings, "bp_head")
row = self.layout.row()
row.prop(Exolife_Settings, "bp_hand_r")
col = self.layout.column()
col.alignment = 'RIGHT'
col.prop(Exolife_Settings, "bp_part_suffix")
row = self.layout.row()
row.operator('render.exolifecharrenderer', text = "Render!")
classes = (
RENDER_OT_ExolifeCharRenderer_reInit,
RENDER_OT_ExolifeCharVisibility,
RENDER_OT_ExolifeCharRenderer,
RENDER_OT_ExolifeAnimSet,
VIEW3D_PT_ExolifeCharVisibility,
ExolifeSettings,
VIEW3D_PT_ExolifeCharRenderer
)
def register():
bpy.utils.register_class(WM_OT_refresh)
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Object.Exolife_Settings = bpy.props.PointerProperty(type=ExolifeSettings)
def unregister():
for cls in classes:
bpy.utils.unregister_class(cls)
# del bpy.types.Scene.Exolife_Settings
if __name__ == "__main__":
register()