gp_toolbox/OP_playblast.py

248 lines
8.9 KiB
Python
Raw Permalink Normal View History

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',
]
"""
'''
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()
'''
2021-01-10 16:47:17 +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
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
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
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'
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"
## 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
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() """