Fix multiscene ops and batch script generation

0.6.0:

- feat: button to generate a background rendering script to batch multi-scene
- fix: exposed checkbox to change scene AA settings, should be on except if there are NG_merges. (auto-off when using merge nodes buttons)
- fix: default generated scene have native AA
- fix: adding layers from object in other scene use active scene (stop always rerouting to 'Render' scene)
main
Pullusb 2021-10-25 16:02:11 +02:00
parent d3871dcc88
commit 7b79542c6a
9 changed files with 224 additions and 78 deletions

View File

@ -14,6 +14,13 @@ Activate / deactivate layer opaticty according to prefix
Activate / deactivate all masks using MA layers Activate / deactivate all masks using MA layers
--> -->
0.6.0:
- feat: button to generate a background rendering script to batch multi-scene
- fix: exposed checkbox to change scene AA settings, should be on except if there are NG_merges. (auto-off when using merge nodes buttons)
- fix: default generated scene have native AA
- fix: adding layers from object in other scene use active scene (stop always rerouting to 'Render' scene)
0.5.9 0.5.9
- feat: Select which scene to render - feat: Select which scene to render

View File

@ -44,7 +44,7 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
def export_gp_objects(oblist, exclude_list=[]): def export_gp_objects(oblist, exclude_list=[], scene=None):
# Skip layer containing element in excluyde list # Skip layer containing element in excluyde list
if not isinstance(oblist, list): if not isinstance(oblist, list):
oblist = [oblist] oblist = [oblist]
@ -54,10 +54,10 @@ def export_gp_objects(oblist, exclude_list=[]):
# if l.hide: # if l.hide:
# continue # continue
if l.hide or any(x + '_' in l.info for x in exclude_list): # exclude hided ? if l.hide or any(x + '_' in l.info for x in exclude_list): # exclude hided ?
l.viewlayer_render = fn.get_view_layer('exclude').name # assign "exclude" l.viewlayer_render = fn.get_view_layer('exclude', scene=scene).name # assign "exclude"
continue continue
_vl, _cp = gen_vlayer.get_set_viewlayer_from_gp(ob, l) # scene=fn.get_render_scene()) _vl, _cp = gen_vlayer.get_set_viewlayer_from_gp(ob, l, scene=scene) # scene=fn.get_render_scene())
## send operator with mode ALL or SELECTED to batch build ## send operator with mode ALL or SELECTED to batch build
@ -75,17 +75,22 @@ class GPEXP_OT_add_objects_to_render(bpy.types.Operator):
def execute(self, context): def execute(self, context):
# create render scene # create render scene
fn.get_render_scene() if context.scene.name == 'Scene':
scn = fn.get_render_scene()
else:
scn = context.scene
excludes = [] # ['MA', 'IN'] # Get list dynamically excludes = [] # ['MA', 'IN'] # Get list dynamically
if self.mode == 'SELECTED': if self.mode == 'SELECTED':
export_gp_objects([o for o in context.selected_objects if o.type == 'GPENCIL'], exclude_list=excludes) # excludes export_gp_objects([o for o in context.selected_objects if o.type == 'GPENCIL'], exclude_list=excludes, scene=scn)
elif self.mode == 'ALL': elif self.mode == 'ALL':
scn = bpy.data.scenes.get('Scene') # scn = bpy.data.scenes.get('Scene')
if not scn: # if not scn:
self.report({'ERROR'}, 'Could not found default scene') # self.report({'ERROR'}, 'Could not found default scene')
return {"CANCELLED"} # return {"CANCELLED"}
export_gp_objects([o for o in scn.objects if o.type == 'GPENCIL' and not o.hide_get()], exclude_list=excludes) # excludes export_gp_objects([o for o in scn.objects if o.type == 'GPENCIL' and not o.hide_get()], exclude_list=excludes, scene=scn)
return {"FINISHED"} return {"FINISHED"}

View File

@ -211,12 +211,48 @@ class GPEXP_OT_activate_only_selected_layers(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
### TODO reset scene settings (set settings )
class GPEXP_OT_reset_render_settings(bpy.types.Operator):
bl_idname = "gp.reset_render_settings"
bl_label = "Reset Render Settings"
bl_description = "Reset render settings on all scene, disabling AA nodes when there is no Merge nodegroup"
bl_options = {"REGISTER"}
def execute(self, context):
for scn in bpy.data.scenes:
if scn.name == 'Scene':
# don't touch original scene
continue
# set a unique preview output
# - avoid possible write/sync overlap (point to tmp on linux ?)
# - allow to monitor output of a scene and possibly use Overwrite
if scn.render.filepath.startswith('//render/preview/'):
scn.render.filepath = f'//render/preview/{bpy.path.clean_nam(scn.name)}/preview_'
if not scn.use_nodes:
continue
# set the settings depending on merges node presences
use_native_aa = True
for n in scn.node_tree.nodes:
if n.name.startswith('merge_NG_'):
use_native_aa = False
break
fn.scene_aa(scene=scn, toggle=use_native_aa)
return {"FINISHED"}
classes=( classes=(
GPEXP_OT_mute_toggle_output_nodes, GPEXP_OT_mute_toggle_output_nodes,
GPEXP_OT_set_output_node_format, GPEXP_OT_set_output_node_format,
GPEXP_OT_number_outputs, GPEXP_OT_number_outputs,
GPEXP_OT_enable_all_viewlayers, GPEXP_OT_enable_all_viewlayers,
GPEXP_OT_activate_only_selected_layers, GPEXP_OT_activate_only_selected_layers,
GPEXP_OT_reset_render_settings,
# GPEXP_OT_normalize_outnames, # GPEXP_OT_normalize_outnames,
) )

View File

@ -110,6 +110,8 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
# fn.clean_nodegroup_inputs(dg) # fn.clean_nodegroup_inputs(dg)
# # fn.clear_nodegroup_content_if_disconnected(dg.node_tree) # # fn.clear_nodegroup_content_if_disconnected(dg.node_tree)
bpy.context.scene.use_aa = False # trigger fn.scene_aa(toggle=False)
return ng, out return ng, out
class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator): class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator):
@ -265,7 +267,11 @@ class GPEXP_OT_merge_selected_viewlayer_nodes(bpy.types.Operator):
disconnect : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'}) disconnect : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'})
def execute(self, context): def execute(self, context):
render = bpy.data.scenes.get('Render') if context.scene.name == 'Scene':
render = bpy.data.scenes.get('Render')
else:
render = context.scene
if not render: if not render:
self.report({'ERROR'}, 'No render scene') self.report({'ERROR'}, 'No render scene')
return {"CANCELLED"} return {"CANCELLED"}

View File

@ -1,6 +1,8 @@
import bpy import bpy
from . import fn from . import fn
from time import time from time import time, strftime
from pathlib import Path
import sys
from bpy.types import Panel, UIList, Operator, PropertyGroup, Menu from bpy.types import Panel, UIList, Operator, PropertyGroup, Menu
from bpy.props import PointerProperty, IntProperty, BoolProperty, StringProperty, EnumProperty, FloatProperty from bpy.props import PointerProperty, IntProperty, BoolProperty, StringProperty, EnumProperty, FloatProperty
@ -101,10 +103,98 @@ class GPEXP_OT_render_selected_scene(bpy.types.Operator):
return {"FINISHED"} 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 True
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):
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
def execute(self, context):
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")}.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)
for scn_name in scn_to_render:
cmd = f'"{bpy.app.binary_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))
self.report({'INFO'}, f'Batch script generated: {batch_file}')
return {"FINISHED"}
classes=( classes=(
GPEXP_scene_select_prop, GPEXP_scene_select_prop,
GPEXP_OT_render_selected_scene, GPEXP_OT_render_selected_scene,
GPEXP_OT_render_all_scenes, GPEXP_OT_render_all_scenes,
GPEXP_OT_bg_render_script_selected_scene,
) )
def register(): def register():

View File

@ -2,7 +2,7 @@ bl_info = {
"name": "GP Render", "name": "GP Render",
"description": "Organise export of gp layers through compositor output", "description": "Organise export of gp layers through compositor output",
"author": "Samuel Bernou", "author": "Samuel Bernou",
"version": (0, 5, 9), "version": (0, 6, 0),
"blender": (2, 93, 0), "blender": (2, 93, 0),
"location": "View3D", "location": "View3D",
"warning": "", "warning": "",
@ -25,6 +25,11 @@ from . import prefs
from . import OP_setup_layers from . import OP_setup_layers
from . import ui from . import ui
from .fn import scene_aa
def update_scene_aa(context, scene):
scene_aa(toggle=bpy.context.scene.use_aa)
import bpy import bpy
def register(): def register():
@ -46,6 +51,14 @@ def register():
OP_setup_layers.register() OP_setup_layers.register()
ui.register() ui.register()
# bpy.types.Scene.pgroup_name = bpy.props.PointerProperty(type = PROJ_PGT_settings) # bpy.types.Scene.pgroup_name = bpy.props.PointerProperty(type = PROJ_PGT_settings)
bpy.types.Scene.use_aa = bpy.props.BoolProperty(
name='Use Native Anti Aliasing',
default=True,
description='\
Should be Off only if tree contains a merge_NG or alpha-over-combined renderlayers.\n\
Auto-set to Off when using node merge button\n\
Toggle: AA settings of and muting AA nested-nodegroup',
update=update_scene_aa)
def unregister(): def unregister():
if bpy.app.background: if bpy.app.background:
@ -66,7 +79,7 @@ def unregister():
OP_add_layer.unregister() OP_add_layer.unregister()
prefs.unregister() prefs.unregister()
# del bpy.types.Scene.pgroup_name del bpy.types.Scene.use_aa
if __name__ == "__main__": if __name__ == "__main__":
register() register()

96
fn.py
View File

@ -107,12 +107,26 @@ def set_file_output_format(fo):
# fo.format.color_depth = '8' # fo.format.color_depth = '8'
# fo.format.compression = 15 # fo.format.compression = 15
def set_settings(scene=None):
def set_scene_aa_settings(scene=None, aa=True):
'''aa == using native AA, else disable scene AA'''
if not scene: if not scene:
scene = bpy.context.scene scene = bpy.context.scene
if aa:
scene.eevee.taa_render_samples = 32
scene.grease_pencil_settings.antialias_threshold = 1
else:
scene.eevee.taa_render_samples = 1
scene.grease_pencil_settings.antialias_threshold = 0
def set_settings(scene=None, aa=True):
'''aa == using native AA, else disable scene AA'''
if not scene:
scene = bpy.context.scene
# specify scene settings for these kind of render # specify scene settings for these kind of render
scene.eevee.taa_render_samples = 1 set_scene_aa_settings(scene=scene, aa=aa)
scene.grease_pencil_settings.antialias_threshold = 0
scene.render.film_transparent = True scene.render.film_transparent = True
scene.render.use_compositing = True scene.render.use_compositing = True
scene.render.use_sequencer = False scene.render.use_sequencer = False
@ -121,11 +135,25 @@ def set_settings(scene=None):
scene.render.resolution_percentage = 100 scene.render.resolution_percentage = 100
# output (fast write settings since this is just to delete afterwards...) # output (fast write settings since this is just to delete afterwards...)
scene.render.filepath = '//render/preview/preview_' scene.render.filepath = f'//render/preview/{scene.name}/preview_'
scene.render.image_settings.file_format = 'JPEG' scene.render.image_settings.file_format = 'JPEG'
scene.render.image_settings.color_mode = 'RGB' scene.render.image_settings.color_mode = 'RGB'
scene.render.image_settings.quality = 0 scene.render.image_settings.quality = 0
def scene_aa(scene=None, toggle=True):
'''Change scene AA settings and commute AA nodes according to toggle'''
if not scene:
scene=bpy.context.scene
# enable/disable native anti-alias on active scene
set_scene_aa_settings(scene=scene, aa=toggle)
# mute/unmute AA nodegroups
for n in scene.node_tree.nodes:
if n.type == 'GROUP' and n.name.startswith('NG_'):
# n.mute = False # mute whole nodegroup ?
for gn in n.node_tree.nodes:
if gn.type == 'GROUP' and gn.node_tree.name == 'AA':
gn.mute = toggle
def new_scene_from(name, src_scn=None, regen=True, crop=True, link_cam=True, link_light=True): def new_scene_from(name, src_scn=None, regen=True, crop=True, link_cam=True, link_light=True):
'''Get / Create a scene from name and source scene to get settings from''' '''Get / Create a scene from name and source scene to get settings from'''
@ -179,10 +207,11 @@ def get_render_scene():
render_scn.collection.objects.link(ob) render_scn.collection.objects.link(ob)
render_scn.use_nodes = True render_scn.use_nodes = True
# TODO Clear node tree (initial view layer stuff) # TODO Clear node tree (initial view layer stuff)
# set adapted render settings (no AA)
set_settings(render_scn)
set_settings(render_scn, with_aa=False) # set adapted render settings (no AA by default)
render_scn.use_aa = True
return render_scn return render_scn
def get_view_layer(name, scene=None): def get_view_layer(name, scene=None):
@ -1054,8 +1083,8 @@ def split_object_to_scene():
coords = get_gp_box_all_frame_selection(oblist=gp_objs, scn=new, cam=new.camera) coords = get_gp_box_all_frame_selection(oblist=gp_objs, scn=new, cam=new.camera)
if not coords: if not coords:
return f'Scene "{scene_name}" created. But Border was not set (Timeout during GP analysis), should be done by hand if needed then use export crop to json' return f'Scene "{scene_name}" created. But Border was not set (Timeout during GP analysis), should be done by hand if needed then use export crop to json'
set_border_region_from_coord(coords, margin=30, scn=new, export_json=True) set_border_region_from_coord(coords, margin=30, scn=new, export_json=True)
export_crop_to_json() export_crop_to_json()
@ -1106,56 +1135,3 @@ def clear_frame_out_of_range_all_object():
ct += nct ct += nct
print(f'{ct} gp frames deleted') print(f'{ct} gp frames deleted')
return ct return ct
"""
def split_object_to_scene():
'''Create a new scene from selection'''
# send objects in a new render scene
## define new scene name with active object names
active = bpy.context.object
scene_name = active.name
objs = [o for o in bpy.context.selected_objects]
rd_scn = bpy.data.scenes.get('Render')
## create scene and copy settings from render scene or current
# src_scn = bpy.data.scenes.get('Render')
# src_scn = src_scn or bpy.context.scene
# if src_scn.name == scene_name:
# print('! Problem ! Trying to to create new render scene without source')
# return
## From current scene (might be Render OR Scene)
src_scn = bpy.context.scene
new = new_scene_from(scene_name, src_scn=src_scn, regen=True) # crop=True, link_cam=True, link_light=True
for ob in objs:
new.collection.objects.link(ob)
if ob.type == 'GPENCIL':
# recreate VL
vl_names = [l.viewlayer_render for l in ob.data.layers if l.viewlayer_render]
for names in vl_names:
new.view_layers.new(names)
# get_set_viewlayer_from_gp(ob, l, scene=new)
def set_crop_bbox_2d(ob, cam=None):
'''Basic crop using bouding box on current frame'''
from bpy_extras.object_utils import world_to_camera_view
scn = bpy.context.scene
cam = cam or scn.camera
# bbox = [ob.matrix_world @ Vector(b) for b in bbox_coords]
coords2d = [world_to_camera_view(scn, cam, p) for p in get_bbox_3d(ob)]
coords2d_x = sorted([c[0] for c in coords2d])
coords2d_y = sorted([c[1] for c in coords2d])
scn.render.border_min_x = coords2d_x[0]
scn.render.border_max_x = coords2d_x[-1]
scn.render.border_min_y = coords2d_y[0]
scn.render.border_max_y = coords2d_y[-1]
return
"""

View File

@ -40,7 +40,11 @@ def add_rlayer(layer_name, scene=None, location=None, color=None, node_name=None
return comp return comp
def connect_render_layer(rlayer, ng=None, out=None, frame=None): def connect_render_layer(rlayer, ng=None, out=None, frame=None):
scene = fn.get_render_scene() if bpy.context.scene.name == 'Scene':
scene = fn.get_render_scene()
else:
scene = bpy.context.scene
nodes = scene.node_tree.nodes nodes = scene.node_tree.nodes
links = scene.node_tree.links links = scene.node_tree.links
@ -147,6 +151,9 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
# ng_in.outputs[vl_name] # ng_in.outputs[vl_name]
ngroup.links.new(ng_in.outputs[vl_name], aa.inputs[0]) # node_tree ngroup.links.new(ng_in.outputs[vl_name], aa.inputs[0]) # node_tree
ngroup.links.new(aa.outputs[0], ng_out.inputs[vl_name]) # node_tree ngroup.links.new(aa.outputs[0], ng_out.inputs[vl_name]) # node_tree
aa.mute = scene.use_aa # mute if native AA is used
fn.reorganise_NG_nodegroup(ng) # decorative fn.reorganise_NG_nodegroup(ng) # decorative
@ -210,8 +217,10 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
def get_set_viewlayer_from_gp(ob, l, scene=None): def get_set_viewlayer_from_gp(ob, l, scene=None):
'''setup ouptut from passed gp obj > layer''' '''setup ouptut from passed gp obj > layer'''
if not scene: if not scene:
# scene = bpy.context.scene if bpy.context.scene.name != 'Scene':
scene = fn.get_render_scene() # create if necessary scene = bpy.context.scene
else:
scene = fn.get_render_scene()
node_tree = scene.node_tree node_tree = scene.node_tree
nodes = node_tree.nodes nodes = node_tree.nodes

6
ui.py
View File

@ -104,6 +104,7 @@ class GPEXP_PT_gp_node_ui(Panel):
col.separator() col.separator()
col.operator('gp.clean_compo_tree', icon='BRUSHES_ALL', text='Clean Nodes') # NODE_CORNER col.operator('gp.clean_compo_tree', icon='BRUSHES_ALL', text='Clean Nodes') # NODE_CORNER
col.operator('gp.reset_render_settings', icon='SCENE', text='Reset All Scenes Render Settings')
col.separator() col.separator()
@ -129,17 +130,20 @@ class GPEXP_PT_gp_node_ui(Panel):
col.operator('gp.clear_render_tree', icon='X', text='Clear & Delete Render Scene').mode = "COMPLETE" col.operator('gp.clear_render_tree', icon='X', text='Clear & Delete Render Scene').mode = "COMPLETE"
layout.separator() layout.separator()
layout.label(text='Sub Scenes:') layout.label(text='Scenes:')
layout.operator('gp.split_to_scene', icon='DUPLICATE', text='Split Selected Obj To Scene') layout.operator('gp.split_to_scene', icon='DUPLICATE', text='Split Selected Obj To Scene')
row = layout.row(align=True) row = layout.row(align=True)
row.operator('gp.set_crop_from_selection', icon='CON_OBJECTSOLVER', text='Autoset Crop') row.operator('gp.set_crop_from_selection', icon='CON_OBJECTSOLVER', text='Autoset Crop')
row.operator('gp.export_crop_coord_to_json', icon='FILE', text='Export json') row.operator('gp.export_crop_coord_to_json', icon='FILE', text='Export json')
layout.label(text='Render:')
row = layout.row(align=True) row = layout.row(align=True)
row.operator('gp.render_selected_scenes', icon='RENDER_ANIMATION', text='Render Selected Scene') row.operator('gp.render_selected_scenes', icon='RENDER_ANIMATION', text='Render Selected Scene')
row.operator('gp.bg_render_script_selected_scenes', icon='TEXT', text='Gen Batch')
# row.operator('gp.render_all_scenes', icon='RENDER_ANIMATION', text='Render All') # row.operator('gp.render_all_scenes', icon='RENDER_ANIMATION', text='Render All')
layout.prop(context.scene, 'use_aa', text='Use Native AA Settings')
layout.prop(prefs, 'advanced', text='Show Advanced Options') layout.prop(prefs, 'advanced', text='Show Advanced Options')
# layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'ALL' # layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'ALL'
# layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'SELECTED' # layout.operator('gp.add_object_to_render', icon='RENDERLAYERS', text='Layer To Render').mode = 'SELECTED'