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 / denumberingmain
parent
fac73fd4c1
commit
191f667b75
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
|
@ -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"}
|
||||
|
||||
|
|
|
@ -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
188
fn.py
|
@ -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])
|
|
@ -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()
|
||||
|
|
|
@ -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
32
ui.py
|
@ -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"
|
||||
|
||||
|
|
Loading…
Reference in New Issue