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
parent
d3871dcc88
commit
7b79542c6a
|
@ -14,6 +14,13 @@ Activate / deactivate layer opaticty according to prefix
|
|||
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
|
||||
|
||||
- feat: Select which scene to render
|
||||
|
|
|
@ -44,7 +44,7 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator):
|
|||
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
|
||||
if not isinstance(oblist, list):
|
||||
oblist = [oblist]
|
||||
|
@ -54,10 +54,10 @@ def export_gp_objects(oblist, exclude_list=[]):
|
|||
# if l.hide:
|
||||
# continue
|
||||
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
|
||||
|
||||
_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
|
||||
|
@ -75,17 +75,22 @@ class GPEXP_OT_add_objects_to_render(bpy.types.Operator):
|
|||
|
||||
def execute(self, context):
|
||||
# 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
|
||||
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':
|
||||
scn = bpy.data.scenes.get('Scene')
|
||||
if not scn:
|
||||
self.report({'ERROR'}, 'Could not found default scene')
|
||||
return {"CANCELLED"}
|
||||
export_gp_objects([o for o in scn.objects if o.type == 'GPENCIL' and not o.hide_get()], exclude_list=excludes) # excludes
|
||||
# scn = bpy.data.scenes.get('Scene')
|
||||
# if not scn:
|
||||
# self.report({'ERROR'}, 'Could not found default scene')
|
||||
# return {"CANCELLED"}
|
||||
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"}
|
||||
|
||||
|
|
|
@ -211,12 +211,48 @@ class GPEXP_OT_activate_only_selected_layers(bpy.types.Operator):
|
|||
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=(
|
||||
GPEXP_OT_mute_toggle_output_nodes,
|
||||
GPEXP_OT_set_output_node_format,
|
||||
GPEXP_OT_number_outputs,
|
||||
GPEXP_OT_enable_all_viewlayers,
|
||||
GPEXP_OT_activate_only_selected_layers,
|
||||
GPEXP_OT_reset_render_settings,
|
||||
# GPEXP_OT_normalize_outnames,
|
||||
)
|
||||
|
||||
|
|
|
@ -110,6 +110,8 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
|
|||
# fn.clean_nodegroup_inputs(dg)
|
||||
# # fn.clear_nodegroup_content_if_disconnected(dg.node_tree)
|
||||
|
||||
bpy.context.scene.use_aa = False # trigger fn.scene_aa(toggle=False)
|
||||
|
||||
return ng, out
|
||||
|
||||
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'})
|
||||
|
||||
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:
|
||||
self.report({'ERROR'}, 'No render scene')
|
||||
return {"CANCELLED"}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import bpy
|
||||
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.props import PointerProperty, IntProperty, BoolProperty, StringProperty, EnumProperty, FloatProperty
|
||||
|
@ -101,10 +103,98 @@ class GPEXP_OT_render_selected_scene(bpy.types.Operator):
|
|||
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=(
|
||||
GPEXP_scene_select_prop,
|
||||
GPEXP_OT_render_selected_scene,
|
||||
GPEXP_OT_render_all_scenes,
|
||||
GPEXP_OT_bg_render_script_selected_scene,
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
17
__init__.py
17
__init__.py
|
@ -2,7 +2,7 @@ bl_info = {
|
|||
"name": "GP Render",
|
||||
"description": "Organise export of gp layers through compositor output",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 5, 9),
|
||||
"version": (0, 6, 0),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "View3D",
|
||||
"warning": "",
|
||||
|
@ -25,6 +25,11 @@ from . import prefs
|
|||
from . import OP_setup_layers
|
||||
from . import ui
|
||||
|
||||
from .fn import scene_aa
|
||||
|
||||
def update_scene_aa(context, scene):
|
||||
scene_aa(toggle=bpy.context.scene.use_aa)
|
||||
|
||||
import bpy
|
||||
|
||||
def register():
|
||||
|
@ -46,6 +51,14 @@ def register():
|
|||
OP_setup_layers.register()
|
||||
ui.register()
|
||||
# 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():
|
||||
if bpy.app.background:
|
||||
|
@ -66,7 +79,7 @@ def unregister():
|
|||
OP_add_layer.unregister()
|
||||
prefs.unregister()
|
||||
|
||||
# del bpy.types.Scene.pgroup_name
|
||||
del bpy.types.Scene.use_aa
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
98
fn.py
98
fn.py
|
@ -107,12 +107,26 @@ def set_file_output_format(fo):
|
|||
# fo.format.color_depth = '8'
|
||||
# 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:
|
||||
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
|
||||
scene.eevee.taa_render_samples = 1
|
||||
scene.grease_pencil_settings.antialias_threshold = 0
|
||||
set_scene_aa_settings(scene=scene, aa=aa)
|
||||
|
||||
scene.render.film_transparent = True
|
||||
scene.render.use_compositing = True
|
||||
scene.render.use_sequencer = False
|
||||
|
@ -121,11 +135,25 @@ def set_settings(scene=None):
|
|||
scene.render.resolution_percentage = 100
|
||||
|
||||
# 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.color_mode = 'RGB'
|
||||
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):
|
||||
'''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.use_nodes = True
|
||||
# TODO Clear node tree (initial view layer stuff)
|
||||
# set adapted render settings (no AA)
|
||||
set_settings(render_scn)
|
||||
|
||||
# TODO Clear node tree (initial view layer stuff)
|
||||
|
||||
set_settings(render_scn, with_aa=False) # set adapted render settings (no AA by default)
|
||||
render_scn.use_aa = True
|
||||
return render_scn
|
||||
|
||||
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)
|
||||
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'
|
||||
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()
|
||||
|
||||
|
||||
|
@ -1106,56 +1135,3 @@ def clear_frame_out_of_range_all_object():
|
|||
ct += nct
|
||||
print(f'{ct} gp frames deleted')
|
||||
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
|
||||
"""
|
|
@ -40,7 +40,11 @@ def add_rlayer(layer_name, scene=None, location=None, color=None, node_name=None
|
|||
return comp
|
||||
|
||||
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
|
||||
links = scene.node_tree.links
|
||||
|
||||
|
@ -148,6 +152,9 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
|
|||
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
|
||||
|
||||
aa.mute = scene.use_aa # mute if native AA is used
|
||||
|
||||
|
||||
fn.reorganise_NG_nodegroup(ng) # decorative
|
||||
|
||||
# clean outputs
|
||||
|
@ -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):
|
||||
'''setup ouptut from passed gp obj > layer'''
|
||||
if not scene:
|
||||
# scene = bpy.context.scene
|
||||
scene = fn.get_render_scene() # create if necessary
|
||||
if bpy.context.scene.name != 'Scene':
|
||||
scene = bpy.context.scene
|
||||
else:
|
||||
scene = fn.get_render_scene()
|
||||
|
||||
node_tree = scene.node_tree
|
||||
nodes = node_tree.nodes
|
||||
|
|
6
ui.py
6
ui.py
|
@ -104,6 +104,7 @@ class GPEXP_PT_gp_node_ui(Panel):
|
|||
col.separator()
|
||||
|
||||
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()
|
||||
|
||||
|
@ -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"
|
||||
|
||||
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')
|
||||
|
||||
row = layout.row(align=True)
|
||||
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')
|
||||
|
||||
layout.label(text='Render:')
|
||||
row = layout.row(align=True)
|
||||
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')
|
||||
|
||||
layout.prop(context.scene, 'use_aa', text='Use Native AA Settings')
|
||||
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 = 'SELECTED'
|
||||
|
|
Loading…
Reference in New Issue