2021-01-10 16:47:17 +01:00
import bpy
import os
from os import listdir , scandir
from os . path import join , dirname , basename , exists , isfile , isdir , splitext
import re , fnmatch , glob
from pathlib import Path
from time import strftime
C = bpy . context
D = bpy . data
from . utils import open_file , open_folder , get_addon_prefs
exclude = (
### add lines here to exclude specific attribute
' bl_rna ' , ' identifier ' , ' name_property ' , ' rna_type ' , ' properties ' , ' compare ' , ' to_string ' , #basic
)
"""
rd_keep = [
" resolution_percentage " ,
" resolution_x " ,
" resolution_y " ,
" filepath " ,
" use_stamp " ,
" stamp_font_size " ,
]
im_keep = [
' file_format ' ,
' color_mode ' ,
' quality ' ,
' compression ' ,
]
ff_keep = [
' codec ' ,
' format ' ,
' constant_rate_factor ' ,
' ffmpeg_preset ' ,
' gopsize ' ,
' audio_codec ' ,
' audio_bitrate ' ,
]
"""
2022-03-24 14:41:32 +01:00
'''
2021-01-10 16:47:17 +01:00
def render_with_restore ( ) :
class RenderFileRestorer :
rd = bpy . context . scene . render
im = rd . image_settings
ff = rd . ffmpeg
# ffmpeg (ff) need to be before image_settings(im) in list
# otherwise __exit__ may try to restore settings of image mode in video mode !
# ex : "RGBA" not found in ('BW', 'RGB') (will still not stop thx to try block)
zones = [ rd , ff , im ]
val_dic = { }
cam = bpy . context . scene . camera
def __enter__ ( self ) :
## store attribute of data_path in self.zones list.
for data_path in self . zones :
self . val_dic [ data_path ] = { }
for attr in dir ( data_path ) : #iterate in attribute of given datapath
if attr not in exclude and not attr . startswith ( ' __ ' ) and not callable ( getattr ( data_path , attr ) ) and not data_path . is_property_readonly ( attr ) :
self . val_dic [ data_path ] [ attr ] = getattr ( data_path , attr )
if self . cam and self . cam . name == ' draw_cam ' :
if self . cam . parent :
bpy . context . scene . camera = self . cam . parent
def __exit__ ( self , type , value , traceback ) :
## restore attribute from self.zones list
for data_path , prop_dic in self . val_dic . items ( ) :
for attr , val in prop_dic . items ( ) :
try :
setattr ( data_path , attr , val )
except Exception as e :
print ( f " /! \ Impossible to re-assign: { attr } = { val } " )
print ( e )
if self . cam :
bpy . context . scene . camera = self . cam
return RenderFileRestorer ( )
2022-03-24 14:41:32 +01:00
'''
2021-01-10 16:47:17 +01:00
2022-03-24 14:41:32 +01:00
class render_with_restore :
def __init__ ( self ) :
rd = bpy . context . scene . render
im = rd . image_settings
ff = rd . ffmpeg
# ffmpeg (ff) need to be before image_settings(im) in list
# otherwise __exit__ may try to restore settings of image mode in video mode !
# ex : "RGBA" not found in ('BW', 'RGB') (will still not stop thx to try block)
self . zones = [ rd , ff , im ]
self . val_dic = { }
self . cam = bpy . context . scene . camera
def __enter__ ( self ) :
## store attribute of data_path in self.zones list.
for data_path in self . zones :
self . val_dic [ data_path ] = { }
for attr in dir ( data_path ) : #iterate in attribute of given datapath
if attr not in exclude and not attr . startswith ( ' __ ' ) and not callable ( getattr ( data_path , attr ) ) and not data_path . is_property_readonly ( attr ) :
self . val_dic [ data_path ] [ attr ] = getattr ( data_path , attr )
if self . cam and self . cam . name == ' draw_cam ' :
if self . cam . parent :
bpy . context . scene . camera = self . cam . parent
def __exit__ ( self , type , value , traceback ) :
## restore attribute from self.zones list
for data_path , prop_dic in self . val_dic . items ( ) :
for attr , val in prop_dic . items ( ) :
try :
setattr ( data_path , attr , val )
except Exception as e :
print ( f " /! \ Impossible to re-assign: { attr } = { val } " )
print ( e )
if self . cam :
bpy . context . scene . camera = self . cam
2021-01-10 16:47:17 +01:00
def playblast ( viewport = False , stamping = True ) :
scn = bpy . context . scene
res_factor = scn . gptoolprops . resolution_percentage
2022-03-24 18:25:56 +01:00
playblast_path = get_addon_prefs ( ) . playblast_path
2021-01-10 16:47:17 +01:00
rd = scn . render
ff = rd . ffmpeg
with render_with_restore ( ) :
### can add propeties for personalisation as toolsetting props
rd . resolution_percentage = res_factor
2022-03-24 14:41:32 +01:00
while ( rd . resolution_x * res_factor / 100 ) % 2 != 0 : # rd.resolution_percentage
2021-01-10 16:47:17 +01:00
rd . resolution_x = rd . resolution_x + 1
2022-03-24 14:41:32 +01:00
while ( rd . resolution_y * res_factor / 100 ) % 2 != 0 : # rd.resolution_percentage
rd . resolution_y = rd . resolution_y + 1
2021-01-10 16:47:17 +01:00
rd . image_settings . file_format = ' FFMPEG '
ff . format = ' MPEG4 '
ff . codec = ' H264 '
2022-03-24 14:41:32 +01:00
ff . constant_rate_factor = ' HIGH ' # MEDIUM
2021-01-10 16:47:17 +01:00
ff . ffmpeg_preset = ' REALTIME '
ff . gopsize = 10
ff . audio_codec = ' AAC '
ff . audio_bitrate = 128
rd . use_sequencer = False
rd . stamp_background = ( 0.0 , 0.0 , 0.0 , 0.75 ) # blacker notes BG (default 0.25)
# rd.use_compositing
# rd.filepath = join(dirname(bpy.data.filepath), basename(bpy.data.filepath))
# rd.frame_path(frame=0, preview=0, view="_sauce")## give absolute render filepath with some suffix
# rd.is_movie_format# check if its movie mode
## set filepath
# mode incermental or just use fulldate (cannot create conflict and filter OK but long name)
blend = Path ( bpy . data . filepath )
date_format = " % Y- % m- %d _ % H- % M- % S "
2022-03-24 18:25:56 +01:00
## old direct place
# fp = join(blend.parent, "playblast", f'playblast_{blend.stem}_{strftime(date_format)}.mp4')
fp = Path ( bpy . path . abspath ( playblast_path ) ) . resolve ( ) / f ' playblast_ { blend . stem } _ { strftime ( date_format ) } .mp4 '
fp = str ( fp )
2021-01-10 16:47:17 +01:00
#may need a properties for choosing location : bpy.types.Scene.qrd_savepath = bpy.props.StringProperty(subtype='DIR_PATH', description="Export location, if not specify, create a 'quick_render' directory aside blend location")#(change defaut name in user_prefernece)
rd . filepath = fp
rd . use_stamp = stamping # toolsetting.use_stamp# True for playblast
#stamp options
2022-03-24 14:41:32 +01:00
rd . stamp_font_size = int ( rd . stamp_font_size * res_factor / 100 ) # rd.resolution_percentage
2021-01-10 16:47:17 +01:00
# bpy.ops.render.render_wrap(use_view=viewport)
### render
if viewport : ## openGL
bpy . ops . render . opengl ( animation = True , view_context = True ) # 'INVOKE_DEFAULT',
else : ## normal render
bpy . ops . render . render ( animation = True ) # 'INVOKE_DEFAULT',
# print("Playblast Done :", fp)#Dbg
return fp
class PBLAST_OT_playblast_anim ( bpy . types . Operator ) :
bl_idname = " render.playblast_anim "
bl_label = " Playblast anim "
bl_description = " Launch animation playblast, use resolution percentage (Lock blender during process) "
bl_options = { " REGISTER " }
use_view : bpy . props . BoolProperty ( name = ' use_view ' , default = False )
def execute ( self , context ) :
if not bpy . data . is_saved :
self . report ( { ' ERROR ' } , ' File is not saved, Playblast cancelled ' )
return { " CANCELLED " }
fp = playblast ( viewport = self . use_view , stamping = True )
if fp :
self . report ( { ' INFO ' } , f ' File saved at: { fp } ' )
addon_prefs = get_addon_prefs ( )
if addon_prefs :
if addon_prefs . playblast_auto_play :
open_file ( fp )
if addon_prefs . playblast_auto_open_folder :
open_folder ( dirname ( fp ) )
return { " FINISHED " }
def register ( ) :
bpy . utils . register_class ( PBLAST_OT_playblast_anim )
def unregister ( ) :
bpy . utils . unregister_class ( PBLAST_OT_playblast_anim )
'''
## Potential cancelling method for image sequence rendering.
for cfra in range ( start , end + 1 ) :
print ( " Baking frame " + str ( cfra ) )
# update scene to new frame and bake to template image
scene . frame_set ( cfra )
ret = bpy . ops . object . bake_image ( )
if ' CANCELLED ' in ret :
return { ' CANCELLED ' }
'''
"""
class PBLAST_OT_render_wrap ( bpy . types . Operator ) :
bl_idname = " render.render_wrap "
bl_label = " Render wraped "
bl_description = " render "
bl_options = { " REGISTER " } ## need hide
use_view : bpy . props . BoolProperty ( name = ' use_view ' , default = False )
def execute ( self , context ) :
if self . use_view : ## openGL
ret = bpy . ops . render . opengl ( ' INVOKE_DEFAULT ' , animation = True , view_context = True )
else : ## normal render
ret = bpy . ops . render . render ( ' INVOKE_DEFAULT ' , animation = True )
return { " FINISHED " }
"""
""" if __name__ == " __main__ " :
register ( ) """