output management

0.3.2

- code: grouped output management ops
- fix: name dash to underscore (normalize)
main
Pullusb 2021-09-22 12:06:40 +02:00
parent 3965a15922
commit d935cc0151
6 changed files with 171 additions and 122 deletions

View File

@ -1,5 +1,6 @@
import bpy import bpy
import re
from . import fn
class GPEXP_OT_mute_toggle_output_nodes(bpy.types.Operator): class GPEXP_OT_mute_toggle_output_nodes(bpy.types.Operator):
bl_idname = "gp.mute_toggle_output_nodes" bl_idname = "gp.mute_toggle_output_nodes"
@ -22,6 +23,51 @@ 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_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 == 'SELECTED' and not fo.select:
continue
# print(f'numbering {fo.name}')
ct += 1
if self.ctrl:
fn.delete_numbering(fo)
else:
fn.renumber_keep_existing(fo)
# 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"}
class GPEXP_OT_set_output_node_format(bpy.types.Operator): class GPEXP_OT_set_output_node_format(bpy.types.Operator):
bl_idname = "gp.set_output_node_format" bl_idname = "gp.set_output_node_format"
@ -45,8 +91,8 @@ class GPEXP_OT_set_output_node_format(bpy.types.Operator):
compression = ref.format.compression compression = ref.format.compression
ct = 0 ct = 0
for n in context.scene.node_tree.nodes: for n in nodes:
if n.type != 'OUTPUT_FILE' or n == ref: if n.type != 'OUTPUT_FILE' or n == ref or not n.select:
continue continue
n.format.color_mode = color_mode n.format.color_mode = color_mode
@ -61,9 +107,61 @@ class GPEXP_OT_set_output_node_format(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
def out_norm(x):
a = x.group(1) if x.group(1) else ''
b = x.group(2) if x.group(2) else ''
c = x.group(3) if x.group(3) else ''
d = x.group(4) if x.group(4) else ''
e = x.group(5) if x.group(5) else ''
return f'{a}{b}{fn.normalize(c)}{d}{e}'
## does not match the right thing yet
class GPEXP_OT_normalize_outnames(bpy.types.Operator):
bl_idname = "gp.normalize_outnames"
bl_label = "Normalize Output names"
bl_description = "Normalize output names with lowercase and replace dash to underscore"
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'')
# return {"CANCELLED"}
reslash = re.compile('\\/')
ct = 0
for n in nodes:
if n.type != 'OUTPUT_FILE' or not n.select:
continue
# Normalize last part of the file out names
base_path_l = reslash.split(n.base_path)
base_path_l[-1] = fn.normalize(base_path_l[-1])
n.base_path = '/'.join(base_path_l)
for fs in n.file_slots:
fp = fs.path
fp_l = reslash.split(fp)
for i, part in enumerate(fp_l):
fp_l[1] = re.sub(r'(^\d{3}_)?([A-Z]{2}_)?(.*?)(_[A-Z]{2})?(_)?', out_norm, part)
fs.path = '/'.join(fp_l)
ct += 1
# state = 'muted' if self.mute else 'unmuted'
self.report({"INFO"}, f'{ct} output nodes normalized')
return {"FINISHED"}
classes=( classes=(
GPEXP_OT_mute_toggle_output_nodes, GPEXP_OT_mute_toggle_output_nodes,
GPEXP_OT_set_output_node_format, GPEXP_OT_set_output_node_format,
GPEXP_OT_number_outputs,
# GPEXP_OT_normalize_outnames,
) )
def register(): def register():

View File

@ -1,61 +0,0 @@
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 == 'SELECTED' and not fo.select:
continue
# print(f'numbering {fo.name}')
ct += 1
if self.ctrl:
fn.delete_numbering(fo)
else:
fn.renumber_keep_existing(fo)
# 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)

View File

@ -121,56 +121,10 @@ class GPEXP_OT_layers_state(bpy.types.Operator):
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): class GPEXP_OT_lower_layers_name(bpy.types.Operator):
bl_idname = "gp.lower_layers_name" bl_idname = "gp.lower_layers_name"
bl_label = "Lower Layers Name" bl_label = "Normalize Layers Name"
bl_description = "Make the layer name lowercase without touching prefix and suffix" bl_description = "Make the object and layers name lowercase with dashed converted to underscore (without touching layer prefix and suffix)"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@classmethod @classmethod
@ -180,11 +134,14 @@ class GPEXP_OT_lower_layers_name(bpy.types.Operator):
all_objects : BoolProperty(name='On All Object', all_objects : BoolProperty(name='On All Object',
default=False, description='On All object, else use selected objects') # , options={'SKIP_SAVE'} default=False, description='On All object, else use selected objects') # , options={'SKIP_SAVE'}
object_name : BoolProperty(name='Lower Object Name', object_name : BoolProperty(name='Normalize Object Name',
default=True, description='Make the object name lowercase') # , options={'SKIP_SAVE'} default=True, description='Make the object name lowercase') # , options={'SKIP_SAVE'}
layer_name : BoolProperty(name='Lower Layers Names', layer_name : BoolProperty(name='Normalize Layers Names',
default=True, description='Make the layers name lowercase') # , options={'SKIP_SAVE'} default=True, description='Make the layers name lowercase') # , options={'SKIP_SAVE'}
# dash_to_undescore : BoolProperty(name='Dash To Underscore',
# default=True, description='Make the layers name lowercase') # , options={'SKIP_SAVE'}
def invoke(self, context, event): def invoke(self, context, event):
# self.ctrl=event.ctrl # self.ctrl=event.ctrl
@ -207,6 +164,10 @@ class GPEXP_OT_lower_layers_name(bpy.types.Operator):
layout.label(text=f'Choose what to rename:') layout.label(text=f'Choose what to rename:')
layout.prop(self, 'object_name') layout.prop(self, 'object_name')
layout.prop(self, 'layer_name') layout.prop(self, 'layer_name')
# if self.layer_name:
# box = layout.box()
# box.prop(self, 'dash_to_undescore')
if not self.object_name and not self.layer_name: if not self.object_name and not self.layer_name:
layout.label(text=f'At least one choice!', icon='ERROR') layout.label(text=f'At least one choice!', icon='ERROR')
@ -220,13 +181,13 @@ class GPEXP_OT_lower_layers_name(bpy.types.Operator):
if self.object_name: if self.object_name:
rename_data = ob.name == ob.data.name rename_data = ob.name == ob.data.name
ob.name = ob.name.lower() ob.name = ob.name.lower().replace('-', '_')
if rename_data: if rename_data:
ob.data.name = ob.data.name.lower() ob.data.name = ob.name
if self.layer_name: if self.layer_name:
for l in ob.data.layers: for l in ob.data.layers:
lower_layer_name(l) fn.normalize_layer_name(l) # default : lower=True, dash_to_underscore=self.dash_to_undescore
return {"FINISHED"} return {"FINISHED"}

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, 3, 1), "version": (0, 3, 2),
"blender": (2, 93, 0), "blender": (2, 93, 0),
"location": "View3D", "location": "View3D",
"warning": "", "warning": "",
@ -16,7 +16,6 @@ from . import OP_clear
from . import OP_clean from . import OP_clean
from . import OP_connect_toggle from . import OP_connect_toggle
from . import OP_manage_outputs from . import OP_manage_outputs
from . import OP_number_outputs
from . import OP_scene_switch from . import OP_scene_switch
# from . import OP_check_layer_status # from . import OP_check_layer_status
from . import OP_setup_layers from . import OP_setup_layers
@ -34,7 +33,6 @@ def register():
OP_connect_toggle.register() OP_connect_toggle.register()
OP_merge_layers.register() OP_merge_layers.register()
OP_manage_outputs.register() OP_manage_outputs.register()
OP_number_outputs.register()
OP_scene_switch.register() OP_scene_switch.register()
# OP_check_layer_status.register() # OP_check_layer_status.register()
OP_setup_layers.register() OP_setup_layers.register()
@ -49,7 +47,6 @@ def unregister():
OP_setup_layers.unregister() OP_setup_layers.unregister()
# OP_check_layer_status.unregister() # OP_check_layer_status.unregister()
OP_scene_switch.unregister() OP_scene_switch.unregister()
OP_number_outputs.unregister()
OP_manage_outputs.unregister() OP_manage_outputs.unregister()
OP_merge_layers.unregister() OP_merge_layers.unregister()
OP_connect_toggle.unregister() OP_connect_toggle.unregister()

53
fn.py
View File

@ -583,6 +583,59 @@ def has_channel_color(layer):
if not any(isclose(i, 0.2, abs_tol=0.001) for i in layer.channel_color): if not any(isclose(i, 0.2, abs_tol=0.001) for i in layer.channel_color):
return True return True
def normalize(text):
return text.lower().replace('-', '_')
PATTERN = r'^(?P<grp>-\s)?(?P<tag>[A-Z]{2}_)?(?P<name>.*?)(?P<sfix>_[A-Z]{2})?(?P<inc>\.\d{3})?$' # numering
def normalize_layer_name(layer, prefix='', desc='', suffix='', lower=True, dash_to_underscore=True):
'''GET a layer and argument 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
if lower:
name = name.lower()
if dash_to_underscore:
name = name.replace('-', '_')
new = f'{grp}{tag}{name}{sfix}' # lower suffix ?
if new != layer.info:
print(f'{layer.info} >> new')
layer.info = new
## confirm pop-up message: ## confirm pop-up message:
def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'): def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
def draw(self, context): def draw(self, context):

3
ui.py
View File

@ -54,10 +54,11 @@ class GPEXP_PT_gp_node_ui(Panel):
## (re)number exports ## (re)number exports
ct = len([n for n in context.scene.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.select]) ct = len([n for n in context.scene.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.select])
txt = f'Renumber {ct} Selected outputs' txt = f'Renumber {ct} Selected Outputs'
subcol = col.column() subcol = col.column()
subcol.enabled = bool(ct) subcol.enabled = bool(ct)
subcol.operator('gp.number_outputs', icon='LINENUMBERS_ON', text=txt).mode = 'SELECTED' subcol.operator('gp.number_outputs', icon='LINENUMBERS_ON', text=txt).mode = 'SELECTED'
# subcol.operator('gp.normalize_outnames', icon='SYNTAX_OFF', text=f'Normalize Paths {ct} Selected Ouptut') # not ready
# 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') subcol.operator('gp.set_output_node_format', icon='OUTPUT', text='Copy Active Output Format')