242 lines
8.0 KiB
Python
242 lines
8.0 KiB
Python
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 |