482 lines
16 KiB
Python
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() """ |