gp_toolbox/OP_render.py

482 lines
16 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
### render the png sequences
def initial_render_checks(context=None):
if not context:
context=bpy.context
if not bpy.data.is_saved:
return "File is not saved, render cancelled"
cam = context.scene.camera
if not cam:
return "No active Camera"
if cam.name == 'draw_cam':
if not cam.parent:
return "Camera is draw_cam but has no parent cam to render from..."
context.scene.camera = cam.parent
if cam.name == 'obj_cam':
if not cam.get('maincam_name'):
return "Cannot found main camera from obj_cam. Set main camera manually"
main_cam = context.scene.objects.get(cam['maincam_name'])
if not main_cam:
return f"Main camera not found with name: {cam['main_cam']}"
context.scene.camera = main_cam
return
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]
obviz = {}
# layviz = []
# matviz = []
closeline = False
val_dic = {}
cam = bpy.context.scene.camera
enter_context = None
def __enter__(self):
self.enter_context = bpy.context
## 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)
# cam
if self.cam and self.cam.name == 'draw_cam':
if self.cam.parent:
bpy.context.scene.camera = self.cam.parent
#case of obj cam
if self.cam.name == 'obj_cam':
bpy.context.scene.camera = bpy.context.scene.objects.get(self.cam['main_cam'])
for ob in bpy.context.scene.objects:
self.obviz[ob.name] = ob.hide_render
close_mat = bpy.data.materials.get('closeline')
if close_mat and not close_mat.grease_pencil.hide:
close_mat.grease_pencil.hide = True
self.closeline = True
# for gpo in bpy.context.scene.objects:
# if gpo.type != 'GPENCIL':
# continue
# if not gpo.materials.get('closeline'):
# continue
# self.closelines[gpo] = gpo.materials['closeline'].hide_render
def __exit__(self, type, value, traceback):
## reset header text
# self.enter_context.area.header_text_set(None)
### maybe keep render settings for custom output with right mode
"""
## restore attribute from self.zones list
for data_path, prop_dic in self.val_dic.items():
for attr, val in prop_dic.ietms():
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
for obname, val in self.obviz.items():
bpy.context.scene.objects[obname].hide_render = val
if self.closeline:
close_mat = bpy.data.materials.get('closeline')
if close_mat:
close_mat.grease_pencil.hide = False
return RenderFileRestorer()
def set_render_settings():
prefs = get_addon_prefs()
rd = bpy.context.scene.render
rd.use_sequencer = False
rd.use_compositing = False
rd.use_overwrite = True
rd.image_settings.file_format = 'PNG'
rd.image_settings.color_mode = 'RGBA'
rd.image_settings.color_depth = '16'
rd.image_settings.compression = 80 #maybe up the compression a bit...
rd.resolution_percentage = 100
rd.resolution_x, rd.resolution_y = prefs.render_res_x, prefs.render_res_y
rd.use_stamp = False
rd.film_transparent = True
def render_invididually(context, render_list):
'''Receive a list of object to render individually isolated'''
prefs = get_addon_prefs()
scn = context.scene
rd = scn.render
error_list = []
with render_with_restore():
set_render_settings()
# 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
## set filepath
blend = Path(bpy.data.filepath)
### render by object in list
for obname in render_list:
the_obj = scn.objects.get(obname)
if not the_obj:
error_list.append(f'! Could not found {obname} in scene, skipped !')
continue
## Kill renderability of all
for o in scn.objects:
o.hide_render = True
the_obj.hide_render = False
# f'{blend.stem}_'
# fp = blend.parents[1] / "compo" / "base" / obname / (obname+'_')
fp = (blend.parent / prefs.output_path.lstrip(r'\/')).resolve() / obname / (obname+'_')
rd.filepath = str(fp)
# Freeze so impossible to display advance
# context.area.header_text_set(f'rendering > {obname} ...')
### render
# bpy.ops.render.render_wrap(use_view=viewport)
bpy.ops.render.render(animation=True)
# print("render Done :", fp)#Dbg
return error_list
def render_grouped(context, render_list):
'''Receive a list of object to render grouped'''
scn = context.scene
rd = scn.render
error_list = []
with render_with_restore():
set_render_settings()
## Kill renderability of all
for o in scn.objects:
o.hide_render = True
### show all object of the list
for obname in render_list:
the_obj = scn.objects.get(obname)
if not the_obj:
error_list.append(f'! Could not found {obname} in scene, skipped !')
continue
the_obj.hide_render = False
## Use current file path of setup output path else following :
blend = Path(bpy.data.filepath)
outname = context.scene.gptoolprops.name_for_current_render
# fp = blend.parents[1] / "compo" / "base" / outname / (outname+'_')
fp = (blend.parent / prefs.output_path.lstrip(r'\/')).resolve() / outname / (outname+'_')
rd.filepath = str(fp)
### render
# bpy.ops.render.render_wrap(use_view=viewport)
bpy.ops.render.render(animation=True)
# print("render Done :", fp)#Dbg
return error_list
class GPTRD_OT_render_anim(bpy.types.Operator):
bl_idname = "render.render_anim"
bl_label = "render anim"
bl_description = "Launch animation render"
bl_options = {"REGISTER"}
# use_view : bpy.props.BoolProperty(name='use_view', default=False)
to_render = []
mode : bpy.props.StringProperty(name="render mode",
description="change render mode for list rendering", default="INDIVIDUAL")
render_bool : bpy.props.BoolVectorProperty(name="render bools",
description="", default=tuple([True]*32), size=32, subtype='NONE')
def invoke(self, context, event):
# prefs = get_addons_prefs_and_set()
# if not prefs.local_folder:
# self.report({'ERROR'}, f'Project local folder is not specified in addon preferences')
# return {'CANCELLED'}
if self.mode == 'GROUP' and not context.scene.gptoolprops.name_for_current_render:
self.report({'ERROR'}, 'Need to set ouput name')
return {'CANCELLED'}
prefs = get_addon_prefs()
print('exclusions list ->', prefs.render_obj_exclusion)
exclusion_obj = [name.strip() for name in prefs.render_obj_exclusion.split(',')]
print('object exclusion list: ', exclusion_obj)
print('initial self.to_render: ', self.to_render)
self.to_render = []#reset
## check object to render with basic filter
for ob in context.scene.objects:
if ob.type != 'GPENCIL':
continue
if any(x in ob.name.lower() for x in exclusion_obj): #('old', 'rough', 'trash', 'test')
print('Skip', ob.name)
continue
self.to_render.append(ob.name)
if not self.to_render:
self.report({'ERROR'}, 'No GP to render')
return {'CANCELLED'}
## Reset at each render
# self.render_bool = tuple([True]*32)# reset all True
## disable for some name (ex: BG)
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.label(text='Tick objects to render')
for i, name in enumerate(self.to_render):
row = layout.row()
row.prop(self, 'render_bool', index = i, text = name)
# for i, set in enumerate(SETS):
# column.row().prop(context.scene.spritesheet, 'sets', index=i, text=set)
def execute(self, context):
prefs = get_addon_prefs()
err = initial_render_checks(context)
if err:
self.report({'ERROR'}, err)
return {"CANCELLED"}
render_list = []
for i, name in enumerate(self.to_render):
if self.render_bool[i]:
render_list.append(name)
if not render_list:
self.report({'ERROR'}, 'Nothing to render')
return {"CANCELLED"}
# self.report({'INFO'}, f'rendering {render_list}')#Dgb
# return {"FINISHED"}#Dgb
if self.mode == 'INDIVIDUAL':
errlist = render_invididually(context, render_list)
elif self.mode == 'GROUP':
errlist = render_grouped(context, render_list)
blend = Path(bpy.data.filepath)
# out = blend.parents[1] / "compo" / "base"
out = (blend.parent / prefs.output_path.lstrip(r'\/')).resolve()
if out.exists():
open_folder(str(out))
else:
errlist.append('No compo/base folder created')
if errlist:
self.report({'ERROR'}, '\n'.join(errlist))
return {"FINISHED"}
### ---- Setup render path
class GPTRD_OT_setup_render_path(bpy.types.Operator):
bl_idname = "render.setup_render_path"
bl_label = "Setup render"
bl_description = "Setup render settings for normal render of the current state\nHint: F12 to check one frame, ctrl+F12 to render animation"
bl_options = {"REGISTER"}
def execute(self, context):
#get name and check
prefs = get_addon_prefs()
outname = context.scene.gptoolprops.name_for_current_render
if not outname:
self.report({'ERROR'}, 'No output name has been set')
return {"CANCELLED"}
err = initial_render_checks(context)
if err:
self.report({'ERROR'}, err)
return {"CANCELLED"}
set_render_settings()
blend = Path(bpy.data.filepath)
# out = blend.parents[1] / "compo" / "base"
out = (blend.parent / prefs.output_path.lstrip(r'\/')).resolve()
fp = out / outname / (outname+'_')
context.scene.render.filepath = str(fp)
self.report({'INFO'}, f'output setup for "{outname}"')
return {"FINISHED"}
class GPTRD_OT_use_active_object_infos(bpy.types.Operator):
bl_idname = "render.use_active_object_name"
bl_label = "Use object Name"
bl_description = "Write active object name (active layer name with shift click on the button)"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return context.object
def invoke(self, context, event):
# wm = context.window_manager
# return wm.invoke_props_dialog(self)
self.shift = event.shift
return self.execute(context)
def execute(self, context):
ob = context.object
#get name and check
if self.shift:
if ob.type != "GPENCIL":
self.report({'ERROR'}, 'Not a GP, no access to layers')
return {"CANCELLED"}
lay = ob.data.layers.active
if not lay:
self.report({'ERROR'}, 'No active layer found')
return {"CANCELLED"}
context.scene.gptoolprops.name_for_current_render = lay.info
else:
context.scene.gptoolprops.name_for_current_render = ob.name
# self.report({'INFO'}, 'Output Name changed')
return {"FINISHED"}
""" class GPTRD_OT_render_as_is(bpy.types.Operator):
bl_idname = "render.render_as_is"
bl_label = "render current"
bl_description = "Launch animation render with current setup"
bl_options = {"REGISTER"}
def execute(self, context):
err = initial_render_checks(context)
if err:
self.report({'ERROR'}, err)
return {"CANCELLED"}
return {"FINISHED"} """
### --- REGISTER
classes = (
GPTRD_OT_render_anim,
GPTRD_OT_setup_render_path,
GPTRD_OT_use_active_object_infos,
)
def register():
for cl in classes:
bpy.utils.register_class(cl)
def unregister():
for cl in classes:
bpy.utils.unregister_class(cl)
'''
## 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() """