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 selectedmain
parent
9044134d79
commit
556612664d
|
@ -12,6 +12,14 @@ Activate / deactivate layer opaticty according to prefix
|
|||
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
|
||||
|
||||
- fix: added AA nodegroup
|
||||
|
|
|
@ -27,7 +27,7 @@ class GPEXP_OT_add_layer_to_render(bpy.types.Operator):
|
|||
if not l.select:
|
||||
if not l.viewlayer_render:
|
||||
# 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
|
||||
gen_vlayer.get_set_viewlayer_from_gp(ob, l)
|
||||
|
||||
|
@ -54,7 +54,7 @@ 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') # assign "exclude"
|
||||
l.viewlayer_render = fn.get_view_layer('exclude').name # assign "exclude"
|
||||
continue
|
||||
|
||||
_vl, _cp = gen_vlayer.get_set_viewlayer_from_gp(ob, l) # scene=fn.get_render_scene())
|
||||
|
|
|
@ -22,8 +22,48 @@ class GPEXP_OT_mute_toggle_output_nodes(bpy.types.Operator):
|
|||
self.report({"INFO"}, f'{ct} nodes {state}')
|
||||
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=(
|
||||
GPEXP_OT_mute_toggle_output_nodes,
|
||||
GPEXP_OT_set_output_node_format,
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
|
@ -95,6 +95,7 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
|
|||
# create dedicated fileout
|
||||
|
||||
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 = out_name
|
||||
out.base_path = base_path
|
||||
|
@ -111,9 +112,90 @@ def merge_layers(rlayers, obname=None, active=None, disconnect=True, color=None)
|
|||
|
||||
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):
|
||||
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_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'})
|
||||
|
||||
def execute(self, context):
|
||||
# merge_selected_layers() # function to merge from GP dopesheet
|
||||
ob = bpy.context.object
|
||||
layers = [l for l in ob.data.layers if l.select and not l.hide]
|
||||
act = ob.data.layers.active
|
||||
# merge_selected_layers() # function to merge from GP dopesheet
|
||||
if not act:
|
||||
self.report({'ERROR'}, f'An active layer is needed to set merge output name')
|
||||
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):
|
||||
print('/!\ Merge -> Not every nodes start with the same object')
|
||||
|
||||
# obname = selection[0].layer.split('.')[0]
|
||||
merge_layers(selection, active=nodes.active, disconnect=self.disconnect)
|
||||
color = None
|
||||
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"}
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
|
||||
|
|
|
@ -120,9 +120,119 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
|
|||
# return {"CANCELLED"}
|
||||
|
||||
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=(
|
||||
GPEXP_OT_layers_state,
|
||||
GPEXP_OT_lower_layers_name
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
|
@ -2,7 +2,7 @@ bl_info = {
|
|||
"name": "GP Render",
|
||||
"description": "Organise export of gp layers through compositor output",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 2, 8),
|
||||
"version": (0, 3, 0),
|
||||
"blender": (2, 93, 0),
|
||||
"location": "View3D",
|
||||
"warning": "",
|
||||
|
|
31
fn.py
31
fn.py
|
@ -37,22 +37,27 @@ def create_aa_nodegroup(tree):
|
|||
ng_in = create_node('NodeGroupInput', tree=ngroup, location=(-600,0))
|
||||
ng_out = create_node('NodeGroupOutput', tree=ngroup, location=(600,0))
|
||||
|
||||
sep = create_node('CompositorNodeSepRGBA', tree=ngroup, location=(-300,0))
|
||||
comb = create_node('CompositorNodeCombRGBA', tree=ngroup, location=(200,25))
|
||||
ngroup.links.new(ng_in.outputs[0], sep.inputs[0])
|
||||
sep = create_node('CompositorNodeSepRGBA', tree=ngroup, location=(-150,0))
|
||||
comb = create_node('CompositorNodeCombRGBA', tree=ngroup, location=(350,25))
|
||||
|
||||
# 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):
|
||||
ngroup.links.new(sep.outputs[i], comb.inputs[i])
|
||||
|
||||
# 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(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)
|
||||
|
@ -88,6 +93,12 @@ def copy_settings(obj_a, obj_b):
|
|||
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):
|
||||
if not scene:
|
||||
scene = bpy.context.scene
|
||||
|
@ -408,7 +419,7 @@ def nodegroup_merge_inputs(ngroup):
|
|||
prev = ao
|
||||
|
||||
## 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)
|
||||
ngroup.links.new(ao.outputs[0], aa.inputs[0]) # node_tree
|
||||
|
||||
|
|
|
@ -181,6 +181,7 @@ def connect_render_layer(rlayer, ng=None, out=None, frame=None):
|
|||
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]+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.parent = frame
|
||||
out.base_path = f'//render/{bpy.path.clean_name(obname)}'
|
||||
|
|
8
ui.py
8
ui.py
|
@ -60,6 +60,9 @@ class GPEXP_PT_gp_node_ui(Panel):
|
|||
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'
|
||||
|
||||
subcol.operator('gp.set_output_node_format', icon='OUTPUT', text='Copy Active Output Format')
|
||||
|
||||
|
||||
layout.separator()
|
||||
|
||||
col=layout.column()
|
||||
|
@ -91,6 +94,8 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
|
|||
row = layout.row()
|
||||
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"
|
||||
layout.label(text=f'viewlayer: {context.object.data.layers.active.viewlayer_render}')
|
||||
|
||||
|
||||
## On layers
|
||||
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])
|
||||
txt = f'Merge {ct} layers'
|
||||
# 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
|
||||
|
||||
|
||||
|
@ -121,6 +126,7 @@ class GPEXP_PT_gp_dopesheet_ui(Panel):
|
|||
layout.separator()
|
||||
|
||||
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()
|
||||
layout.prop(bpy.context.preferences.edit, 'use_anim_channel_group_colors')
|
||||
|
|
Loading…
Reference in New Issue