uasble version pack of feature and fixes

0.2.0

- feat: merge selected viewlayer
- feat: mute/unmute all output nodes
- feat: cleaning options
- feat: renumbering / denumbering
main
Pullusb 2021-09-10 18:32:50 +02:00
parent fac73fd4c1
commit 191f667b75
10 changed files with 599 additions and 69 deletions

View File

@ -9,6 +9,13 @@ OR always duplicate (safe but heavy scenes...)
if duplicate, need to "connect" with namespace ('_duprender') or something
-->
0.2.0
- feat: merge selected viewlayer
- feat: mute/unmute all output nodes
- feat: cleaning options
- feat: renumbering / denumbering
0.1.1
- ui: show number of selected obj

157
OP_clean.py Normal file
View File

@ -0,0 +1,157 @@
import bpy
from . import fn
## direct use (Pop up menu version below)
"""
class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
bl_idname = "gp.clean_compo_tree"
bl_label = "Clean Compo Tree"
bl_description = "Reorder inputs/outputs and clear unused viewlayers"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return True
# mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'})
def execute(self, context):
render = bpy.data.scenes.get('Render')
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
print('re-arranging frames')
fn.rearrange_frames(render.node_tree)
for n in render.node_tree.nodes:
if n.name.startswith('NG_'):
fn.reorder_inputs(n)
fn.reorder_outputs(n)
# get output node to reorder output
out = None
for s in n.outputs:
if not s.is_linked:
continue
out = s.links[0].to_node
if out.type == 'OUTPUT_FILE':
break
if out:
fn.reorder_fileout(out, ng=n)
## clear disconnected fileout ??...
# for fo in render.node_tree.nodes:
# if fo.type != 'OUTPUT_FILE':
# continue
# fn.clear_disconnected(fo)
return {"FINISHED"}
"""
class GPEXP_OT_clean_compo_tree(bpy.types.Operator):
bl_idname = "gp.clean_compo_tree"
bl_label = "Clean Compo Tree"
bl_description = "Pop up menu with cleaning options"
bl_options = {"REGISTER", "UNDO"}
clear_unused_view_layers : bpy.props.BoolProperty(name="Clear unused view layers",
description="Delete view layer that aren't used in the nodetree anymore",
default=True)
arrange_frames : bpy.props.BoolProperty(name="Arrange Frames",
description="Re-arrange all frames Y positions" ,
default=True)
reorder_inputs : bpy.props.BoolProperty(name="Reorder I/O Sockets",
description="Reorder inputs/outputs of all 'NG_' nodegroup and their connected file output",
default=True)
fo_clear_disconnected : bpy.props.BoolProperty(name="Remove Disconnected Export Inputs",
description="Clear any disconnected intput of every 'file output' node",
default=False)
@classmethod
def poll(cls, context):
return True
def invoke(self, context, event):
# self.nodes = context.object
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, 'clear_unused_view_layers')
layout.prop(self, 'arrange_frames')
layout.prop(self, 'reorder_inputs')
layout.separator()
layout.prop(self, 'fo_clear_disconnected')
if self.fo_clear_disconnected:
layout.label(text='Disconnected inputs are not exported', icon='INFO')
# box = layout.box()
# box.prop(self, 'arrange_frames')
# box.prop(self, 'reorder_inputs')
# box.prop(self, 'fo_clear_disconnected')
def execute(self, context):
render = bpy.data.scenes.get('Render')
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
nodes = render.node_tree.nodes
if self.clear_unused_view_layers:
used_rlayer_names = [n.layer for n in nodes if n.type == 'R_LAYERS']
for rl in reversed(render.view_layers):
if rl.name in used_rlayer_names or rl.name == 'View Layer':
continue
render.view_layers.remove(rl)
if self.arrange_frames:
print('re-arranging frames')
fn.rearrange_frames(render.node_tree)
if self.reorder_inputs:
for n in nodes:
if n.name.startswith('NG_'):
fn.reorder_inputs(n)
fn.reorder_outputs(n)
# get output node to reorder output
out = None
for s in n.outputs:
if not s.is_linked:
continue
out = s.links[0].to_node
if out.type == 'OUTPUT_FILE':
break
if out:
fn.reorder_fileout(out, ng=n)
if self.fo_clear_disconnected:
for fo in nodes:
if fo.type != 'OUTPUT_FILE':
continue
fn.clear_disconnected(fo)
return {"FINISHED"}
classes=(
GPEXP_OT_clean_compo_tree,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View File

@ -18,6 +18,7 @@ class GPEXP_OT_clear_render_tree(bpy.types.Operator):
render = bpy.data.scenes.get('Render')
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
# clear all nodes in frames
if render.use_nodes:

69
OP_connect_toggle.py Normal file
View File

@ -0,0 +1,69 @@
import bpy
from . import fn
class GPEXP_OT_reconnect_render_layer(bpy.types.Operator):
bl_idname = "gp.reconnect_render_layer"
bl_label = "Reconnect Render Layer"
bl_description = "Reconnect selected render layers"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return True
# mode : bpy.props.StringProperty(default='NORMAL', options={'SKIP_SAVE'})
def execute(self, context):
node_tree = context.scene.node_tree
nodes = node_tree.nodes
changed = []
for n in nodes:
if not n.select or not n.type == 'R_LAYERS':
continue
if not ' / ' in n.layer:
continue
if n.outputs[0].is_linked: # already connected
continue
# get namme
obname = n.layer.split()[0]
grp_name = f'NG_{obname}'
# get nodegroup
grp = nodes.get(grp_name)
if not grp:
print(f'{n.name} Node group not found : {n.layer} !-> {grp_name}')
continue
inp = grp.inputs.get(n.layer)
if not inp:
print(f'{n.name} no inputs name "{n.layer}" in group {grp_name}')
continue
# reconnect
node_tree.links.new(n.outputs[0], inp)
changed.append(f'{n.name} ({n.layer}) to {grp_name}')
if changed:
self.report({'INFO'}, f'{len(changed)} nodes reconnected')
else:
self.report({'WARNING'}, f'Could not reconnect, see console')
return {"FINISHED"}
classes=(
GPEXP_OT_reconnect_render_layer,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

View File

@ -1,7 +1,8 @@
import bpy
import re
from . import fn
def merge_layers(rlayers, obname=None, active=None):
def merge_layers(rlayers, obname=None, active=None, disconnect=True):
print(f'Merging {len(rlayers)} layers')
print('->', [r.layer for r in rlayers])
@ -16,79 +17,93 @@ def merge_layers(rlayers, obname=None, active=None):
# sort RL descending
rlayers.sort(key=lambda n: fn.real_loc(n).y, reverse=True)
node_tree = rlayers[0].id_data
nodes = node_tree.nodes
links = node_tree.links
if active:
vl_name = active.layer
if not vl_name:
vl_name = rlayers[0].layer
else:
vl_name = rlayers[-1].layer # -1 : bottom node == upper layer
if ' / ' in vl_name:
obname, lname = vl_name.split(' / ')
lname = bpy.path.clean_name(lname)
base_path = f'//render/{bpy.path.clean_name(obname)}'
slot_name = f'{lname}/{lname}_'
else:
# directly use full vlname for both base output and subfolder ?? (or return error)
obname = lname = bpy.path.clean_name(vl_name)
base_path = f'//render/'
slot_name = f'{lname}/{lname}_'
# change colors of those nodes
disconnected_groups = []
color = fn.random_color()
for n in rlayers:
n.use_custom_color = True
n.color = color
obname, lname = vl_name.split(' / ')
lname = bpy.path.clean_name(lname)
if disconnect:
if n.outputs[0].is_linked:
for lnk in reversed(n.outputs[0].links):
if lnk.to_node.name.startswith('NG_'):
disconnected_groups.append(lnk.to_node)
links.remove(lnk)
disconnected_groups = list(set(disconnected_groups))
ng_name = f'merge_NG_{obname}' # only object name
## clear nodes groups duplication (.00?)
## clear unused nodes groups duplication
fn.clear_nodegroup(ng_name, full_clear=False)
# get set nodegroup from vlayer name
if not ng:
ng = nodes.get(ng_name)
### always create a new nodegroup (nerve call an existing one)
if not ng:
ngroup = bpy.data.node_groups.get(ng_name)
# full clear True if exists but not used
if ngroup and ngroup.users == 0:
ngroup = None
fn.clear_nodegroup(ng_name, full_clear=True)
# need a unique nodegroup name
# increment name while nodegroup exists
while bpy.data.node_groups.get(ng_name): # nodes.get(ng_name)
if not re.search(r'(\d+)$', ng_name):
ng_name += '_02' # if not ending with a number add _02
ng_name = re.sub(r'(\d+)(?!.*\d)', lambda x: str(int(x.group(1))+1).zfill(len(x.group(1))), ng_name)
if not ngroup:
# delete and recreate ?
print(f'create nodegroup {ng_name}')
print(f'create merge nodegroup {ng_name}')
ngroup = bpy.data.node_groups.new(ng_name, 'CompositorNodeTree')
ng = fn.create_node('CompositorNodeGroup', tree=scene.node_tree, location=(fn.real_loc(rlayer)[0] + 600, fn.real_loc(rlayer)[1]), width=400) # (rlayer.location[0] + 600, rlayer.location[1])
if frame:
ng.parent= frame
ng = fn.create_node('CompositorNodeGroup', tree=node_tree, location=(fn.real_loc(rlayers[0]).x + 1900, fn.real_loc(rlayers[0]).y - 200), width=400)
ng.node_tree = ngroup
ng.name = ngroup.name
ng_in = fn.create_node('NodeGroupInput', tree=ngroup, location=(-600,0))
ng_out = fn.create_node('NodeGroupOutput', tree=ngroup, location=(600,0))
_ng_in = fn.create_node('NodeGroupInput', tree=ngroup, location=(-600,0))
_ng_out = fn.create_node('NodeGroupOutput', tree=ngroup, location=(600,0))
# Create inputs and links to node_group
for rln in rlayers:
rln.outputs['Image']
sockin = ng.inputs.new('NodeSocketColor', rln.layer)
sockin = ng.inputs[-1]
links.new(rln.outputs['Image'], sockin)
fn.nodegroup_merge_inputs(ng.node_tree)
ng.update()
# create dedicated fileout
"""
socket_list = []
grp_sockets = []
out = fn.create_node('CompositorNodeOutputFile', tree=node_tree, location=(ng.location[0]+450, ng.location[1]+50), width=600)
out_name = f'merge_OUT_{vl_name}' # or get output from frame
out.name = out_name
out.base_path = base_path
out.file_slots.new(slot_name)
links.new(ng.outputs[0], out.inputs[-1])
for n in rlayers:
if n.outputs[0].links[0].to_node != ng:
print(f'Skip {n.layer}, connect to {n.outputs[0].links[0].to_node} instead of {ng.name}')
continue
fn.clear_disconnected(out)
out.update()
sock_in = n.outputs[0].links[0].to_socket
for i, s in enumerate(ng.inputs):
if s == sock_in:
print(i, s.name)
socket_list.append(s)
grp_sockets.append(ng.node_tree.nodes['Group Input'].outputs[i])
break
## Clear node_group after disconnect
# for dg in disconnected_groups:
# fn.clean_nodegroup_inputs(dg)
# # fn.clear_nodegroup_content_if_disconnected(dg.node_tree)
# debug
for inp, grps in zip(socket_list, grp_sockets):
if inp.name != grps.name:
print(f'\n! Problem ! : {inp.name}, {grps.name}')
return
"""
##
# JUST CREATE ANOTHER GROUP NODE FOR THE MERGE !
##
return ng, out
class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
bl_idname = "gp.merge_selected_dopesheet_layers"
@ -100,6 +115,8 @@ class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
disconnect : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'})
def execute(self, context):
# merge_selected_layers() # function to merge from GP dopesheet
ob = bpy.context.object
@ -130,7 +147,7 @@ class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
rlayers.append(rlayer)
merge_layers(rlayers, obname=clean_ob_name)
merge_layers(rlayers, disconnect=self.disconnect)
return {"FINISHED"}
@ -138,9 +155,11 @@ class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
class GPEXP_OT_merge_selected_viewlayer_nodes(bpy.types.Operator):
bl_idname = "gp.merge_selected_viewlayer_nodes"
bl_label = "Merge selected view_layers "
bl_description = "Merge selected view layers to a new dedicated file output"
bl_description = "Merge selected view layers to a new dedicated file output\nDisconnect single output unless using 'keep connect'"
bl_options = {"REGISTER"}
disconnect : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'})
def execute(self, context):
render = bpy.data.scenes.get('Render')
if not render:
@ -159,7 +178,7 @@ class GPEXP_OT_merge_selected_viewlayer_nodes(bpy.types.Operator):
print('Merge -> Not every nodes start with the same object')
# obname = selection[0].layer.split('.')[0]
merge_layers(selection, nodes.active)
merge_layers(selection, active=nodes.active, disconnect=self.disconnect)
return {"FINISHED"}

60
OP_number_outputs.py Normal file
View File

@ -0,0 +1,60 @@
import bpy
from . import fn
class GPEXP_OT_number_outputs(bpy.types.Operator):
bl_idname = "gp.number_outputs"
bl_label = "Number Outputs"
bl_description = "(Re)Number the outputs to have ordered file by name in export directories\nCtrl+Clic : Delete numbering"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return True
mode : bpy.props.StringProperty(default='SELECTED', options={'SKIP_SAVE'})
# ctrl : bpy.props.StringProperty(default=False, options={'SKIP_SAVE'}) # no need
def invoke(self, context, event):
self.ctrl = event.ctrl
return self.execute(context)
def execute(self, context):
render = bpy.data.scenes.get('Render')
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
ct = 0
nodes = render.node_tree.nodes
for fo in nodes:
if fo.type != 'OUTPUT_FILE':
continue
if self.mode == 'SELECT' and not fo.select:
continue
print(f'numbering {fo.name}')
ct += 1
if self.ctrl:
fn.delete_numbering(fo)
else:
fn.renumber(fo)
txt = 'de-numbered' if self.ctrl else 're-numbered'
if ct:
self.report({'INFO'}, f'{ct} output nodes {txt}')
else:
self.report({'ERROR'}, f'No output nodes {txt}')
return {"FINISHED"}
classes=(
GPEXP_OT_number_outputs,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
def unregister():
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

188
fn.py
View File

@ -35,7 +35,9 @@ def set_settings(scene=None):
scene = bpy.context.scene
scene.eevee.taa_render_samples = 1
scene.grease_pencil_settings.antialias_threshold = 0
# add transparent toggle on ?
scene.render.film_transparent = True
scene.view_settings.view_transform = 'Standard'
def get_render_scene():
'''Get / Create a scene named Render'''
@ -169,6 +171,10 @@ def clear_nodegroup(name, full_clear=False):
for ng in reversed(bpy.data.node_groups):
pattern = name + r'\.\d{3}'
if not full_clear and ng.users:
continue
if re.search(pattern, ng.name):
bpy.data.node_groups.remove(ng)
@ -242,6 +248,17 @@ def reorder_fileout(fo, ng=None):
all_outnames = [s.name for s in fo.inputs] # same as [fs.path for fs in fo.file_slots]
fo.inputs.move(all_outnames.index(s_name), ordered.index(s_name))
def reorganise_NG_nodegroup(ng):
'''refit node content to avoid overlap'''
ngroup = ng.node_tree
ng_in = ngroup.nodes.get('Group Input')
offset = 35
y = 0
for s in ng_in.outputs:
if s.is_linked:
s.links[0].to_node.location.y = y
y -= offset
def connect_to_group_output(n):
for o in n.outputs:
if o.is_linked:
@ -262,8 +279,177 @@ def connect_to_group_input(n):
return val
return False
def clear_nodegroup_content_if_disconnected(ngroup):
'''Get a nodegroup.node_tree
delete orphan nodes that are not connected from group input node
'''
for n in reversed(ngroup.nodes):
if n.type in ('GROUP_INPUT', 'GROUP_OUTPUT'):
continue
if not connect_to_group_input(n) and not connect_to_group_output(n): # is disconnected from both side
ngroup.nodes.remove(n)
def clean_nodegroup_inputs(ng):
'''Clear inputs to output of passed nodegroup if not connected'''
ngroup = ng.node_tree
for i in range(len(ng.inputs))[::-1]:
print(i)
if not ng.inputs[i].is_linked: # and not any(x.layer == s.name for x in rl_nodes)
ngroup.inputs.remove(ngroup.inputs[i])
# clear_nodegroup_content_if_disconnected(ngroup)
def random_color(alpha=False):
import random
if alpha:
return (random.uniform(0,1), random.uniform(0,1), random.uniform(0,1), 1)
return (random.uniform(0,1), random.uniform(0,1), random.uniform(0,1))
def get_numbered_output(out, slot_name):
'''Return output slot name without looking for numbering ???_
'''
pattern = r'^(?:\d{3}_)?' # optional non capture group of 3 digits + _
pattern = f'{pattern}{slot_name}'
for inp in out.inputs:
if re.match(pattern, inp.name):
return inp
def add_fileslot_number(fs, number):
elems = fs.path.split('/')
for i, e in enumerate(elems):
if re.match(r'^\d{3}_', e):
elems[i] = re.sub(r'^(\d{3})', lambda x: str(number).zfill(3), e)
else:
elems[i] = f'{str(number).zfill(3)}_{e}'
new = '/'.join(elems)
fs.path = new
return new
def renumber(fo, offset=10):
'''Force renumber all the slots with a 3'''
if fo.type != 'OUTPUT_FILE': return
ct = 0
for fs in fo.file_slots:
add_fileslot_number(fs, ct)
ct += offset
def get_num(string):
num = re.search(r'^(\d{3})_', string)
if num:
return int(num.group(1))
"""
def renumber_keep(fo, offset=10):
'''Force renumber all the slots with a 3'''
if fo.type != 'OUTPUT_FILE': return
renum = re.compile(r'^(\d{3})_')
ct = 0
last_idx = len(fo.file_slots) -1
prev = None
prev_num = None
for idx, fs in enumerate(fo.file_slots):
# if idx!=last_idx:
# fs_next = fo.file_slots[idx+1]
if idx == last_idx: # handle last
if idx > 0:
prev = fo.file_slots[idx-1]
num = renum.search(prev)
if not num:
add_fileslot_number(fs, ct)
else:
add_fileslot_number(fs, int(num.group(1)) + offset)
else:
add_fileslot_number(fs, 0) # there is only one slot (maybe don't number ?)
break
# update the ct with the current taken number if any
number = renum.search(fs.path)
if number:
prev = fs
ct = number + offset
continue # skip already numbered
# analyse all next slots until there is numbered
divider = None
for i in range(idx + 1, len(fo.file_slots) - idx):
next_num = renum.search(fo.file_slots[idx + i])
if next_num:
divider = i-1
break
if idx == 0:
prev_num = 0
prev = None
else:
prev = fo.file_slots[idx-1]
prev_num = renum.search(prev.path)
if prev_num:
prev_num = prev_num.group(1)
if not divider: # just use prev and next if prev
if not prev:
add_fileslot_number(fs, ct)
# first check if it has a number (if not bas)
prev = fs
ct += offset
"""
def delete_numbering(fo): # padding=3
'''Delete prefix numbering on all slots on passed file output'''
if fo.type != 'OUTPUT_FILE': return
for fs in fo.file_slots:
elems = fs.path.split('/')
for i, e in enumerate(elems):
elems[i] = re.sub(r'^\d{3}_', '', e)
new = '/'.join(elems)
fs.path = new
def nodegroup_merge_inputs(ngroup):
'''Get a nodegroup
merge every group inputs with alpha over
then connect to antialias and a new output
'''
ng_in = ngroup.nodes.get('Group Input')
ng_out = ngroup.nodes.get('Group Output')
x, y = ng_in.location.x + 200, 0
offset_x, offset_y = 150, -100
# merge all inputs in alphaover nodes
prev = None
for i in range(len(ng_in.outputs)-1): # skip waiting point
inp = ng_in.outputs[i]
if not prev:
prev = ng_in
continue
# live connect
ao = create_node('CompositorNodeAlphaOver', tree=ngroup, location=(x,y), hide=True)
ngroup.links.new(prev.outputs[0], ao.inputs[1])
ngroup.links.new(inp, ao.inputs[2])
x += offset_x
y += offset_y
prev = ao
## create a merged name as output ??
aa = new_aa_node(ngroup)
aa.location = (ao.location.x + 200, ao.location.y)
ngroup.links.new(ao.outputs[0], aa.inputs[0]) # node_tree
# create one input and link
out = ngroup.outputs.new('NodeSocketColor', ngroup.inputs[0].name)
ngroup.links.new(aa.outputs[0], ng_out.inputs[0])

View File

@ -124,8 +124,8 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
## reorder
fn.reorder_inputs(ng)
ng.update()
# CREATE NG outsocket (individual, without taking merge)
# CREATE NG outsocket (individual, without taking merge)
connected = False
if ng_in.outputs[vl_name].is_linked:
@ -152,6 +152,8 @@ 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
fn.reorganise_NG_nodegroup(ng) # decorative
# clean outputs
for o in reversed(ngroup.outputs):
if not o.name in [o.name for o in ngroup.inputs]:
@ -182,12 +184,16 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
out = nodes.get(out_name)
if not out:
# color = (0.2,0.3,0.5)
out = fn.create_node('CompositorNodeOutputFile', tree=scene.node_tree, location=(fn.real_loc(ng)[0]+600, fn.real_loc(ng)[1]+50), width=600) # =(ng.location[0]+600, ng.location[1]+50)
out = fn.create_node('CompositorNodeOutputFile', tree=scene.node_tree, location=(fn.real_loc(ng)[0]+500, fn.real_loc(ng)[1]+50), width=600) # =(ng.location[0]+600, ng.location[1]+50)
out.name = out_name
out.parent = frame
out.base_path = f'//render/{bpy.path.clean_name(obname)}'
out_input = out.inputs.get(slot_name)
## out_input = out.inputs.get(slot_name) # ok for non-numbered outputs
out_input = None
out_input = fn.get_numbered_output(out, slot_name)
if not out_input:
out.file_slots.new(slot_name)
out_input = out.inputs[-1] # assigning directly above doesn't link afterwards
@ -197,7 +203,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
links.new(groupout, out_input)
# clean fileout
fn.clear_disconnected(out)
fn.clear_disconnected(out) # maybe not disconnect ?
fn.reorder_fileout(out, ng=ng)
out.update()

View File

@ -10,6 +10,7 @@ def clear():
render = bpy.data.scenes.get('Render')
if not render:
print('SKIP, no Render scene')
return {"CANCELLED"}
# # clear passes
# for i in range(len(render.view_layers))[::-1]:

32
ui.py
View File

@ -11,16 +11,22 @@ class GPEXP_PT_gp_node_ui(Panel):
bl_label = "Gpencil Render Manager"
def draw(self, context):
layout = self.layout
if not context.scene.use_nodes or not context.scene.node_tree:
return
row = layout.row()
# TODO : add advanced bool checkbox to hide some options from the user
layout = self.layout
col = layout.column(align=True)
ct = len([n for n in context.scene.node_tree.nodes if n.type == 'R_LAYERS' and n.select])
txt = f'Merge {ct} Layer Nodes'
row.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text=txt)
row.enabled = ct > 1
col.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text=txt).disconnect = True
col.operator('gp.merge_selected_viewlayer_nodes', icon='NODETREE', text='Merge (keep connect)').disconnect = False
col.enabled = ct > 1
layout.separator()
layout.label(text='All Outputs:')
row=layout.row()
row.operator('gp.mute_toggle_output_nodes', icon='NODE_INSERT_ON', text='Mute').mute = True
row.operator('gp.mute_toggle_output_nodes', icon='NODE_INSERT_OFF', text='Unmute').mute = False
@ -28,6 +34,24 @@ class GPEXP_PT_gp_node_ui(Panel):
layout.separator()
col=layout.column()
col.label(text='Clean and updates:')
row = col.row()
n = context.scene.node_tree.nodes.active
row.enabled = n and n.type == 'R_LAYERS' and not n.outputs[0].is_linked
row.operator('gp.reconnect_render_layer', icon='ANIM', text='Reconnect')
col.operator('gp.clean_compo_tree', icon='BRUSHES_ALL', text='Clean Nodes') # NODE_CORNER
col.separator()
col.operator('gp.number_outputs', icon='LINENUMBERS_ON', text='Renumber outputs')
## maybe get only this one...
col.operator('gp.number_outputs', icon='LINENUMBERS_ON', text='Renumber Selected outputs').mode = 'SELECTED'
layout.separator()
col=layout.column()
col.label(text='Delete Options:')
col.operator('gp.clear_render_tree', icon='X', text='Clear Render Tree')
col.operator('gp.clear_render_tree', icon='X', text='Clear Delete Render Scene').mode = "COMPLETE"