248 lines
8.9 KiB
Python
248 lines
8.9 KiB
Python
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',
|
|
]
|
|
"""
|
|
|
|
'''
|
|
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()
|
|
'''
|
|
|
|
|
|
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
|
|
|
|
def playblast(viewport = False, stamping = True):
|
|
scn = bpy.context.scene
|
|
res_factor = scn.gptoolprops.resolution_percentage
|
|
playblast_path = get_addon_prefs().playblast_path
|
|
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
|
|
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
|
|
|
|
rd.image_settings.file_format = 'FFMPEG'
|
|
ff.format = 'MPEG4'
|
|
ff.codec = 'H264'
|
|
ff.constant_rate_factor = 'HIGH' # MEDIUM
|
|
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)
|
|
|
|
#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
|
|
|
|
# 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() """ |