fixes new merge lower name node format

0.3.0

- fix: viewlayer exclude attribution error
- fix: force PNG 8bit 15% compression for output settings
- change: GP dopesheet merge ops -> viewlayer merge instead of creating alphaover node
- feat: batch to_lower name
- feat: copy active output node format to selected
main
Pullusb 2021-09-21 18:23:25 +02:00
parent 9044134d79
commit 556612664d
9 changed files with 280 additions and 20 deletions

View File

@ -12,6 +12,14 @@ Activate / deactivate layer opaticty according to prefix
Activate / deactivate all masks using MA layers Activate / deactivate all masks using MA layers
--> -->
0.3.0
- fix: viewlayer exclude attribution error
- fix: force PNG 8bit 15% compression for output settings
- change: GP dopesheet merge ops -> viewlayer merge instead of creating alphaover node
- feat: batch to_lower name
- feat: copy active output node format to selected
0.2.8 0.2.8
- fix: added AA nodegroup - fix: added AA nodegroup

View File

@ -27,7 +27,7 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator):
if not l.select: if not l.select:
if not l.viewlayer_render: if not l.viewlayer_render:
# TODO : need to link, can reaise error if object is not linked in Render scene yet # TODO : need to link, can reaise error if object is not linked in Render scene yet
l.viewlayer_render == fn.get_view_layer('exclude') l.viewlayer_render == fn.get_view_layer('exclude').name
continue continue
gen_vlayer.get_set_viewlayer_from_gp(ob, l) gen_vlayer.get_set_viewlayer_from_gp(ob, l)
@ -54,7 +54,7 @@ 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') # assign "exclude" l.viewlayer_render = fn.get_view_layer('exclude').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=fn.get_render_scene())

View File

@ -22,8 +22,48 @@ class GPEXP_OT_mute_toggle_output_nodes(bpy.types.Operator):
self.report({"INFO"}, f'{ct} nodes {state}') self.report({"INFO"}, f'{ct} nodes {state}')
return {"FINISHED"} return {"FINISHED"}
class GPEXP_OT_set_output_node_format(bpy.types.Operator):
bl_idname = "gp.set_output_node_format"
bl_label = "Set output format from active"
bl_description = "Change all selected output node to match active output node format"
bl_options = {"REGISTER"}
mute : bpy.props.BoolProperty(default=True, options={'SKIP_SAVE'})
def execute(self, context):
# scene = bpy.data.scenes.get('Render')
nodes = context.scene.node_tree.nodes
if not nodes.active or nodes.active.type != 'OUTPUT_FILE':
self.report({"ERROR"}, f'Active node should be an output file to use as reference for output format')
return {"CANCELLED"}
ref = nodes.active
color_mode = ref.format.color_mode
file_format = ref.format.file_format
color_depth = ref.format.color_depth
compression = ref.format.compression
ct = 0
for n in context.scene.node_tree.nodes:
if n.type != 'OUTPUT_FILE' or n == ref:
continue
n.format.color_mode = color_mode
n.format.file_format = file_format
n.format.color_depth = color_depth
n.format.compression = compression
ct += 1
# state = 'muted' if self.mute else 'unmuted'
self.report({"INFO"}, f'{ct} output format copied from {ref.name}')
return {"FINISHED"}
classes=( classes=(
GPEXP_OT_mute_toggle_output_nodes, GPEXP_OT_mute_toggle_output_nodes,
GPEXP_OT_set_output_node_format,
) )
def register(): def register():

View File

@ -95,6 +95,7 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
# create dedicated fileout # create dedicated fileout
out = fn.create_node('CompositorNodeOutputFile', tree=node_tree, location=(ng.location[0]+450, ng.location[1]+50), width=600) out = fn.create_node('CompositorNodeOutputFile', tree=node_tree, location=(ng.location[0]+450, ng.location[1]+50), width=600)
fn.set_file_output_format(out)
out_name = f'merge_OUT_{vl_name}' # or get output from frame out_name = f'merge_OUT_{vl_name}' # or get output from frame
out.name = out_name out.name = out_name
out.base_path = base_path out.base_path = base_path
@ -111,9 +112,90 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
return ng, out return ng, out
class GPEXP_OT_merge_viewlayers_to_active(bpy.types.Operator):
bl_idname = "gp.merge_viewlayers_to_active"
bl_label = "Merge selected layers view_layers"
bl_description = "Merge view layers of selected gp layers to on the active one"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
def execute(self, context):
ob = bpy.context.object
# layers = [l for l in ob.data.layers if l.select and not l.hide]
act = ob.data.layers.active
layers = [l for l in ob.data.layers if l.select and l != act]
if not act.viewlayer_render:
self.report({'ERROR'}, f'Active layer {act.info} has no viewlayer assigned')
return {'CANCELLED'}
rd_scn = bpy.data.scenes.get('Render')
if not rd_scn:
self.report({'ERROR'}, 'Viewlayers needs to be generated first!')
return {'CANCELLED'}
# list layers and viewlayers
vls = [rd_scn.view_layers.get(l.viewlayer_render) for l in layers
if l.viewlayer_render and l.viewlayer_render != act.viewlayer_render and rd_scn.view_layers.get(l.viewlayer_render)]
vl_names = [v.name for v in vls]
for n in reversed(rd_scn.node_tree.nodes):
if n.type == 'R_LAYERS' and n.layer in vl_names:
for lnk in n.outputs[0].links:
grp = lnk.to_node
if grp.type != 'GROUP':
continue
if not grp.name.startswith('NG'):
continue
sockin = lnk.to_socket
sockout = grp.outputs.get(sockin.name)
if not sockout:
continue
for grplink in sockout.links:
if grplink.to_node.type != 'OUTPUT_FILE':
continue
fo_socket = grplink.to_socket
fo = grplink.to_node
fo.file_slots.remove(fo_socket)
# remove input and output from group
# grp.inputs.remove(sockin) # do not clear inside !!
# grp.outputs.remove(sockout) # do not clear inside !!
ngroup = grp.node_tree
for i in range(len(grp.inputs))[::-1]:
if grp.inputs[i].name == sockin.name:
ngroup.inputs.remove(ngroup.inputs[i])
break
for i in range(len(grp.outputs))[::-1]:
if grp.outputs[i].name == sockout.name:
ngroup.outputs.remove(ngroup.outputs[i])
break
# remove render_layer node
rd_scn.node_tree.nodes.remove(n)
# assign view layer from active to selected
for l in layers:
l.viewlayer_render = act.viewlayer_render
## delete unused_vl
# used_vl_name = [n.layer for n in rd_scn.node_tree.nodes if n.type == 'R_LAYERS' and n.layer]
for vl in vls:
rd_scn.view_layers.remove(vl)
# if not vl.name in used_vl_name:
# rd_scn.view_layers.remove(vl)
return {"FINISHED"}
class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator): class GPEXP_OT_merge_selected_dopesheet_layers(bpy.types.Operator):
bl_idname = "gp.merge_selected_dopesheet_layers" bl_idname = "gp.merge_selected_dopesheet_layers"
bl_label = "Merge selected layers view_layers " bl_label = "Merge selected layers nodes"
bl_description = "Merge view layers of selected gp layers to a new dedicated file output" bl_description = "Merge view layers of selected gp layers to a new dedicated file output"
bl_options = {"REGISTER"} bl_options = {"REGISTER"}
@ -124,10 +206,10 @@ class GPEXP_OT_merge_selected_dopesheet_layers(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):
# merge_selected_layers() # function to merge from GP dopesheet
ob = bpy.context.object ob = bpy.context.object
layers = [l for l in ob.data.layers if l.select and not l.hide] layers = [l for l in ob.data.layers if l.select and not l.hide]
act = ob.data.layers.active act = ob.data.layers.active
# merge_selected_layers() # function to merge from GP dopesheet
if not act: if not act:
self.report({'ERROR'}, f'An active layer is needed to set merge output name') self.report({'ERROR'}, f'An active layer is needed to set merge output name')
return {"CANCELLED"} return {"CANCELLED"}
@ -199,14 +281,16 @@ class GPEXP_OT_merge_selected_viewlayer_nodes(bpy.types.Operator):
if not all(selection[0].layer.split('.')[0] == n.layer.split('.')[0] for n in selection): if not all(selection[0].layer.split('.')[0] == n.layer.split('.')[0] for n in selection):
print('/!\ Merge -> Not every nodes start with the same object') print('/!\ Merge -> Not every nodes start with the same object')
# obname = selection[0].layer.split('.')[0] color = None
merge_layers(selection, active=nodes.active, disconnect=self.disconnect) if nodes.active.use_custom_color and nodes.active.color:
color = nodes.active.color
merge_layers(selection, active=nodes.active, disconnect=self.disconnect, color=color)
return {"FINISHED"} return {"FINISHED"}
classes=( classes=(
GPEXP_OT_merge_selected_dopesheet_layers, GPEXP_OT_merge_viewlayers_to_active,
GPEXP_OT_merge_selected_dopesheet_layers,# unused
GPEXP_OT_merge_selected_viewlayer_nodes, GPEXP_OT_merge_selected_viewlayer_nodes,
) )

View File

@ -120,9 +120,119 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
# return {"CANCELLED"} # return {"CANCELLED"}
return {"FINISHED"} return {"FINISHED"}
PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\d{3})?$' # numering
def lower_layer_name(layer, prefix='', desc='', suffix=''):
'''GET a layer and argumen to build and assign name'''
import re
name = layer.info
pattern = PATTERN
sep = '_'
res = re.search(pattern, name.strip())
grp = '' if res.group('grp') is None else res.group('grp')
tag = '' if res.group('tag') is None else res.group('tag')
# tag2 = '' if res.group('tag2') is None else res.group('tag2')
name = '' if res.group('name') is None else res.group('name')
sfix = '' if res.group('sfix') is None else res.group('sfix')
inc = '' if res.group('inc') is None else res.group('inc')
if grp:
grp = ' ' + grp # name is strip(), so grp first spaces are gones.
if prefix:
if prefix == 'prefixkillcode':
tag = ''
else:
tag = prefix.upper().strip() + sep
# if prefix2:
# tag2 = prefix2.upper().strip() + sep
if desc:
name = desc
if suffix:
if suffix == 'suffixkillcode':
sfix = ''
else:
sfix = sep + suffix.upper().strip()
# check if name is available without the increment ending
new = f'{grp}{tag}{name.lower()}{sfix}' # lower suffix ?
if new != layer.info:
print(f'{layer.info} >> new')
layer.info = new
class GPEXP_OT_lower_layers_name(bpy.types.Operator):
bl_idname = "gp.lower_layers_name"
bl_label = "Lower Layers Name"
bl_description = "Make the layer name lowercase without touching prefix and suffix"
bl_options = {"REGISTER", "UNDO"}
@classmethod
def poll(cls, context):
return context.object and context.object.type == 'GPENCIL'
all_objects : BoolProperty(name='On All Object',
default=False, description='On All object, else use selected objects') # , options={'SKIP_SAVE'}
object_name : BoolProperty(name='Lower Object Name',
default=True, description='Make the object name lowercase') # , options={'SKIP_SAVE'}
layer_name : BoolProperty(name='Lower Layers Names',
default=True, description='Make the layers name lowercase') # , options={'SKIP_SAVE'}
def invoke(self, context, event):
# self.ctrl=event.ctrl
# self.alt=event.alt
if event.alt:
self.all_objects=True
# return self.execute(context)
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, 'all_objects')
if self.all_objects:
gp_ct = len([o for o in context.scene.objects if o.type == 'GPENCIL'])
else:
gp_ct = len([o for o in context.selected_objects if o.type == 'GPENCIL'])
layout.label(text=f'{gp_ct} to lower-case')
layout.separator()
layout.label(text=f'Choose what to rename:')
layout.prop(self, 'object_name')
layout.prop(self, 'layer_name')
if not self.object_name and not self.layer_name:
layout.label(text=f'At least one choice!', icon='ERROR')
def execute(self, context):
if self.all_objects:
pool = [o for o in context.scene.objects if o.type == 'GPENCIL']
else:
pool = [o for o in context.selected_objects if o.type == 'GPENCIL']
for ob in pool:
if self.object_name:
rename_data = ob.name == ob.data.name
ob.name = ob.name.lower()
if rename_data:
ob.data.name = ob.data.name.lower()
if self.layer_name:
for l in ob.data.layers:
lower_layer_name(l)
return {"FINISHED"}
classes=( classes=(
GPEXP_OT_layers_state, GPEXP_OT_layers_state,
GPEXP_OT_lower_layers_name
) )
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, 2, 8), "version": (0, 3, 0),
"blender": (2, 93, 0), "blender": (2, 93, 0),
"location": "View3D", "location": "View3D",
"warning": "", "warning": "",

31
fn.py
View File

@ -37,22 +37,27 @@ def create_aa_nodegroup(tree):
ng_in = create_node('NodeGroupInput', tree=ngroup, location=(-600,0)) ng_in = create_node('NodeGroupInput', tree=ngroup, location=(-600,0))
ng_out = create_node('NodeGroupOutput', tree=ngroup, location=(600,0)) ng_out = create_node('NodeGroupOutput', tree=ngroup, location=(600,0))
sep = create_node('CompositorNodeSepRGBA', tree=ngroup, location=(-300,0)) sep = create_node('CompositorNodeSepRGBA', tree=ngroup, location=(-150,0))
comb = create_node('CompositorNodeCombRGBA', tree=ngroup, location=(200,25)) comb = create_node('CompositorNodeCombRGBA', tree=ngroup, location=(350,25))
ngroup.links.new(ng_in.outputs[0], sep.inputs[0])
# in AA
# ngroup.links.new(comb.outputs[0], ng_out.inputs[0]) # <- connect without out AA
aa = new_aa_node(ngroup, location=(-400, 0))
# ngroup.links.new(ng_in.outputs[0], sep.inputs[0])
ngroup.links.new(ng_in.outputs[0], aa.inputs[0])
ngroup.links.new(aa.outputs[0], sep.inputs[0])
# ngroup.links.new(ng_in.outputs[0], sep.inputs[0])
for i in range(3): for i in range(3):
ngroup.links.new(sep.outputs[i], comb.inputs[i]) ngroup.links.new(sep.outputs[i], comb.inputs[i])
# alpha AA # alpha AA
alpha_aa = new_aa_node(ngroup, location=(-50,-150)) alpha_aa = new_aa_node(ngroup, location=(100,-150))
ngroup.links.new(sep.outputs[3], alpha_aa.inputs[0]) ngroup.links.new(sep.outputs[3], alpha_aa.inputs[0])
ngroup.links.new(alpha_aa.outputs[0], comb.inputs[3]) ngroup.links.new(alpha_aa.outputs[0], comb.inputs[3])
ngroup.links.new(comb.outputs[0], ng_out.inputs[0])
# outpout AA (maybe externalize ?)
# ngroup.links.new(comb.outputs[0], ng_out.inputs[0]) # <- connect without out AA
aa = new_aa_node(ngroup, location=(380, 0))
ngroup.links.new(comb.outputs[0], aa.inputs[0])
ngroup.links.new(aa.outputs[0], ng_out.inputs[0])
ng = create_node('CompositorNodeGroup', tree=tree) ng = create_node('CompositorNodeGroup', tree=tree)
@ -88,6 +93,12 @@ def copy_settings(obj_a, obj_b):
pass pass
def set_file_output_format(fo):
fo.format.color_mode = 'RGBA'
fo.format.file_format = 'PNG'
fo.format.color_depth = '8'
fo.format.compression = 15
def set_settings(scene=None): def set_settings(scene=None):
if not scene: if not scene:
scene = bpy.context.scene scene = bpy.context.scene
@ -408,7 +419,7 @@ def nodegroup_merge_inputs(ngroup):
prev = ao prev = ao
## create a merged name as output ?? ## create a merged name as output ??
aa = new_aa_node(ngroup) aa = create_aa_nodegroup(ngroup) # new_aa_node(ngroup)
aa.location = (ao.location.x + 200, ao.location.y) aa.location = (ao.location.x + 200, ao.location.y)
ngroup.links.new(ao.outputs[0], aa.inputs[0]) # node_tree ngroup.links.new(ao.outputs[0], aa.inputs[0]) # node_tree

View File

@ -181,6 +181,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
if not out: if not out:
# color = (0.2,0.3,0.5) # color = (0.2,0.3,0.5)
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 = 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)
fn.set_file_output_format(out)
out.name = out_name out.name = out_name
out.parent = frame out.parent = frame
out.base_path = f'//render/{bpy.path.clean_name(obname)}' out.base_path = f'//render/{bpy.path.clean_name(obname)}'

8
ui.py
View File

@ -60,6 +60,9 @@ class GPEXP_PT_gp_node_ui(Panel):
subcol.operator('gp.number_outputs', icon='LINENUMBERS_ON', text=txt).mode = 'SELECTED' subcol.operator('gp.number_outputs', icon='LINENUMBERS_ON', text=txt).mode = 'SELECTED'
# col.operator('gp.number_outputs', icon='LINENUMBERS_ON', text='Renumber all outputs').mode = 'ALL' # col.operator('gp.number_outputs', icon='LINENUMBERS_ON', text='Renumber all outputs').mode = 'ALL'
subcol.operator('gp.set_output_node_format', icon='OUTPUT', text='Copy Active Output Format')
layout.separator() layout.separator()
col=layout.column() col=layout.column()
@ -91,6 +94,8 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
row = layout.row() row = layout.row()
row.label(text=f'Multiple users ({context.object.data.users})', icon='ERROR') row.label(text=f'Multiple users ({context.object.data.users})', icon='ERROR')
row.operator("wm.call_menu", text="", icon='QUESTION').name = "GPEXP_MT_multi_user_doc" row.operator("wm.call_menu", text="", icon='QUESTION').name = "GPEXP_MT_multi_user_doc"
layout.label(text=f'viewlayer: {context.object.data.layers.active.viewlayer_render}')
## On layers ## On layers
if context.object and context.object.type == 'GPENCIL': if context.object and context.object.type == 'GPENCIL':
@ -105,7 +110,7 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
ct = len([l for l in context.object.data.layers if l.select]) ct = len([l for l in context.object.data.layers if l.select])
txt = f'Merge {ct} layers' txt = f'Merge {ct} layers'
# merge layers from dopesheet # merge layers from dopesheet
row.operator('gp.merge_selected_dopesheet_layers', text=txt, icon='SELECT_EXTEND') row.operator('gp.merge_viewlayers_to_active', text=txt, icon='SELECT_EXTEND')
row.enabled= ct > 1 row.enabled= ct > 1
@ -121,6 +126,7 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
layout.separator() layout.separator()
layout.operator('gp.layers_state', icon='CHECKMARK', text='Check layers') layout.operator('gp.layers_state', icon='CHECKMARK', text='Check layers')
layout.operator('gp.lower_layers_name', icon='SYNTAX_OFF', text='Rename Lowercase')
# row = layout.row() # row = layout.row()
layout.prop(bpy.context.preferences.edit, 'use_anim_channel_group_colors') layout.prop(bpy.context.preferences.edit, 'use_anim_channel_group_colors')