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)
            return op

        @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(macro, bp, **kwargs):
    render_settings = {"animation":True, "write_still":True,
                       "use_viewport":False, "layer":'', "scene":''}
    if kwargs:
        render_settings.update(kwargs)
    macro.define("EXOLIFE_OT_char_visibility").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 EXOLIFE_OT_char_renderer_reinit(bpy.types.Operator):
    bl_idname = "exolife.char_renderer_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 EXOLIFE_OT_char_visibility(bpy.types.Operator):
    """Let's set character visibility!"""
    bl_idname = "exolife.char_visibility"
    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 EXOLIFE_OT_char_renderer(bpy.types.Operator):
    """Let's render character!"""
    bl_idname = "exolife.char_renderer"
    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(1, settings.anims_count):
            if getattr(settings, f"anim{i}", False):
                print(f"\n!!!!!### RenderProcess - ANIMATION: {getattr(settings, f'anim{i}_name', '')}\n")
                macro.define("EXOLIFE_OT_char_anim_set").anim = i
                use_blur = int(getattr(settings, f"anim{i}_mb"))
                macro.define("WM_OT_refresh").use_blur = use_blur

                if settings.bp_hand_l:
                    macro_define_by_bp(macro, "HandL")
                if settings.bp_legs:
                    macro_define_by_bp(macro, "Legs")
                if settings.bp_torso:
                    macro_define_by_bp(macro, "Torso")
                if settings.bp_head:
                    macro_define_by_bp(macro, "Head")
                if settings.bp_hand_r:
                    macro_define_by_bp(macro, "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 EXOLIFE_PT_char_visibility(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('exolife.char_visibility', text = "All Visible")
        props.bp_to_show = bp_to_show
        
        bp_to_show = "HandL"
        props = self.layout.operator('exolife.char_visibility', text = bp_to_show)
        props.bp_to_show = bp_to_show
        
        bp_to_show = "Legs"
        props = self.layout.operator('exolife.char_visibility', text = bp_to_show)
        props.bp_to_show = bp_to_show
        
        bp_to_show = "Torso"
        props = self.layout.operator('exolife.char_visibility', text = bp_to_show)
        props.bp_to_show = bp_to_show
        
        bp_to_show = "Head"
        props = self.layout.operator('exolife.char_visibility', text = bp_to_show)
        props.bp_to_show = bp_to_show
        
        bp_to_show = "HandR"
        props = self.layout.operator('exolife.char_visibility', text = bp_to_show)
        props.bp_to_show = bp_to_show
        


class EXOLIFE_OT_char_anim_set(bpy.types.Operator):
    """Set this Animation (Action) and it's frame ranges"""
    bl_idname = "exolife.char_anim_set"
    bl_label = "EXOLIFE AnimSet"
    bl_options = {'REGISTER','UNDO'}
    
    anim: bpy.props.IntProperty()
    
    def execute(self, 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]
        assert self.anim > 0
        bpy.context.object.animation_data.action = bpy.data.actions[eval("bpy.data.objects['exolife_properties'].Exolife_Settings.anim"+str(self.anim)+"_name")]
        
        bpy.context.scene.frame_start = eval("bpy.data.objects['exolife_properties'].Exolife_Settings."+"anim"+str(self.anim)+"_start")
        bpy.context.scene.frame_end = eval("bpy.data.objects['exolife_properties'].Exolife_Settings."+"anim"+str(self.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 EXOLIFE_PT_char_renderer(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('exolife.char_renderer_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('exolife.char_renderer_reinit', text = "ReInit!")
        row = self.layout.row(); row = self.layout.row()
        

        for i in range(1, bpy.data.objects['exolife_properties'].Exolife_Settings.anims_count):
            row.alignment = 'LEFT'
            props = row.operator('exolife.char_anim_set', text = "Set"); props.anim = i
            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('exolife.char_renderer', text = "Render!")
        

        
classes = (
    EXOLIFE_OT_char_renderer_reinit,
    EXOLIFE_OT_char_visibility,
    EXOLIFE_OT_char_renderer,
    EXOLIFE_OT_char_anim_set,
    EXOLIFE_PT_char_visibility,
    ExolifeSettings,
    EXOLIFE_PT_char_renderer
)


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()
