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() """