import bpy from . import fn from time import time, strftime from pathlib import Path import sys from bpy.types import Panel, UIList, Operator, PropertyGroup, Menu from bpy.props import PointerProperty, IntProperty, BoolProperty, StringProperty, EnumProperty, FloatProperty class GPEXP_OT_render_all_scenes(bpy.types.Operator): bl_idname = "gp.render_all_scenes" bl_label = "Render all scenes" bl_description = "Render all scene except Render" bl_options = {"REGISTER"} @classmethod def poll(cls, context): return True def execute(self, context): start = time() ct = 0 for scn in bpy.data.scenes: # if scn.name == 'Scene': # continue if not scn.use_nodes: continue outfiles = [n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE'] if not outfiles: print(f'\n -!-> Skip {scn.name}, No output files') continue if all(x.mute for x in outfiles): print(f'\n -!-> Skip {scn.name}, All output file are muted') continue print(f'\n --> Rendering {scn.name}') # bpy.context.window.scene = scn bpy.ops.render.render(animation=True, scene=scn.name) ct += 1 print(f'\nDone. {ct} scenes rendered in {time()-start:.2f}s') return {"FINISHED"} class GPEXP_scene_select_prop(PropertyGroup): name : StringProperty() select: BoolProperty() def scene_render_popup_ui(self, context): layout = self.layout col = layout.column() for si in context.scene.scenes_list: row = col.row() row.prop(si, 'select',text='') row.label(text=si.name) ## Display warnings scn = bpy.data.scenes.get(si.name) # compare to existing Rlayers (overkill ?) # vls = [scn.view_layers.get(n.layer) for n in rlayers_nodes if scn.view_layers.get(n.layer)] vls = [vl for vl in scn.view_layers if vl.name != 'View Layer'] if vls: exclude_count = len([vl for vl in vls if not vl.use]) if exclude_count: row.label(text=f'{exclude_count}/{len(vls)} excluded viewlayers', icon='ERROR') if not scn.use_nodes: row.label(text='use_node deactivated', icon='ERROR') continue outfiles = [n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE'] if not outfiles: row.label(text='No output files nodes', icon='ERROR') continue outnum = len(outfiles) muted = len([x for x in outfiles if x.mute]) if muted == outnum: row.label(text='All output file are muted', icon='ERROR') continue elif muted: row.label(text=f'{muted}/{outnum} output file muted', icon='ERROR') continue class GPEXP_OT_render_selected_scene(bpy.types.Operator): bl_idname = "gp.render_selected_scenes" bl_label = "Render Selected Scenes" bl_description = "Launch render of selected scenes with a selection popup" bl_options = {"REGISTER"} @classmethod def poll(cls, context): return bpy.data.is_saved def invoke(self, context, event): # if not bpy.data.is_saved: # self.report({'ERROR'}, 'File needs to be saved') # return {'CANCELLED'} context.scene.scenes_list.clear() for s in bpy.data.scenes: scn_item = context.scene.scenes_list.add() scn_item.name = s.name scn_item.select = s.name != 'Scene' return context.window_manager.invoke_props_dialog(self, width=250) def draw(self, context): ## Basic (without hints) # layout = self.layout # col = layout.column() # for si in context.scene.scenes_list: # row = col.row() # row.label(text=si.name) # row.prop(si, 'select',text='') scene_render_popup_ui(self, context) def execute(self, context): d = fn.export_crop_to_json() if not d: print('No crop to export, border disabled in all scenes') scn_to_render = [si.name for si in context.scene.scenes_list if si.select] start = time() ct = 0 for scn_name in scn_to_render: scn = bpy.data.scenes.get(scn_name) if not scn.use_nodes: print(f'{scn.name} has use node deactivated') continue outfiles = [n for n in scn.node_tree.nodes if n.type == 'OUTPUT_FILE'] if not outfiles: print(f'\n -!-> Skip {scn.name}, No output files') continue if all(x.mute for x in outfiles): print(f'\n -!-> Skip {scn.name}, All output file are muted') continue print(f'\n --> Rendering {scn.name}') # bpy.context.window.scene = scn # no need bpy.ops.render.render(animation=True, scene=scn.name) ct += 1 print(f'\nDone. {ct} scenes rendered in {time()-start:.2f}s') return {"FINISHED"} class GPEXP_OT_bg_render_script_selected_scene(bpy.types.Operator): bl_idname = "gp.bg_render_script_selected_scenes" bl_label = "Create Selected Scene Render Batch " bl_description = "Create a batch script to render all selected scenes in a selection popup" bl_options = {"REGISTER"} @classmethod def poll(cls, context): return bpy.data.is_saved def invoke(self, context, event): context.scene.scenes_list.clear() for s in bpy.data.scenes: scn_item = context.scene.scenes_list.add() scn_item.name = s.name scn_item.select = s.name != 'Scene' return context.window_manager.invoke_props_dialog(self, width=500) def draw(self, context): scene_render_popup_ui(self, context) def execute(self, context): d = fn.export_crop_to_json() if not d: print('No crop to export, border disabled in all scenes') platform = sys.platform blend = Path(bpy.data.filepath) scn_to_render = [si.name for si in context.scene.scenes_list if si.select] batch_file = blend.parent / f'{blend.stem}--{len(scn_to_render)}batch_{strftime("%m-%d_%H-%M")}.sh' if platform.startswith('win'): script_text = ['@ECHO OFF'] batch_file = batch_file.with_suffix('.bat') else: script_text = ['#!/bin/bash'] print('batch_file: ', batch_file) bin_path = bpy.app.binary_path for scn_name in scn_to_render: if platform.startswith('win'): import re pattern = r'users[\/\\](.*?)[\/\\]softs' # or point to user dit with %UserProfile% re_user = re.search(pattern, bin_path, re.I) if not re_user: cmd = f'"{bin_path}" -b "{bpy.data.filepath}" -S "{scn_name}" -a' else: bin_path = bin_path.replace(re_user.group(1), '%USERNAME%') cmd = f'"{bin_path}" -b "{bpy.data.filepath}" -S "{scn_name}" -a' else: # Unix : point same for each user cmd = f'"{bin_path}" -b "{bpy.data.filepath}" -S "{scn_name}" -a' script_text.append(cmd) script_text.append('echo --- END BATCH ---') script_text.append('pause') with batch_file.open('w') as fd: fd.write('\n'.join(script_text)) print(f'Using following binary path: {bin_path}') self.report({'INFO'}, f'Batch script generated: {batch_file}') return {"FINISHED"} classes=( GPEXP_scene_select_prop, GPEXP_OT_render_selected_scene, GPEXP_OT_render_all_scenes, GPEXP_OT_bg_render_script_selected_scene, ) def register(): for cls in classes: bpy.utils.register_class(cls) bpy.types.Scene.scenes_list = bpy.props.CollectionProperty(type=GPEXP_scene_select_prop) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls) del bpy.types.Scene.scenes_list