From d935cc0151551d67625816ef2bff2894e7b5c7fc Mon Sep 17 00:00:00 2001 From: Pullusb Date: Wed, 22 Sep 2021 12:06:40 +0200 Subject: [PATCH] output management 0.3.2 - code: grouped output management ops - fix: name dash to underscore (normalize) --- OP_manage_outputs.py | 104 +++++++++++++++++++++++++++++++++++++++++-- OP_number_outputs.py | 61 ------------------------- OP_setup_layers.py | 67 ++++++---------------------- __init__.py | 5 +-- fn.py | 53 ++++++++++++++++++++++ ui.py | 3 +- 6 files changed, 171 insertions(+), 122 deletions(-) delete mode 100644 OP_number_outputs.py diff --git a/OP_manage_outputs.py b/OP_manage_outputs.py index 9df1d8e..0a826c1 100644 --- a/OP_manage_outputs.py +++ b/OP_manage_outputs.py @@ -1,5 +1,6 @@ import bpy - +import re +from . import fn class GPEXP_OT_mute_toggle_output_nodes(bpy.types.Operator): 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}') 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): 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 ct = 0 - for n in context.scene.node_tree.nodes: - if n.type != 'OUTPUT_FILE' or n == ref: + for n in nodes: + if n.type != 'OUTPUT_FILE' or n == ref or not n.select: continue n.format.color_mode = color_mode @@ -61,9 +107,61 @@ class GPEXP_OT_set_output_node_format(bpy.types.Operator): 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=( GPEXP_OT_mute_toggle_output_nodes, GPEXP_OT_set_output_node_format, +GPEXP_OT_number_outputs, +# GPEXP_OT_normalize_outnames, ) def register(): diff --git a/OP_number_outputs.py b/OP_number_outputs.py deleted file mode 100644 index 4632b78..0000000 --- a/OP_number_outputs.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/OP_setup_layers.py b/OP_setup_layers.py index 242ee33..ac36611 100644 --- a/OP_setup_layers.py +++ b/OP_setup_layers.py @@ -121,56 +121,10 @@ class GPEXP_OT_layers_state(bpy.types.Operator): return {"FINISHED"} - -PATTERN = r'^(?P-\s)?(?P[A-Z]{2}_)?(?P.*?)(?P_[A-Z]{2})?(?P\.\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_label = "Normalize Layers Name" + bl_description = "Make the object and layers name lowercase with dashed converted to underscore (without touching layer prefix and suffix)" bl_options = {"REGISTER", "UNDO"} @classmethod @@ -180,11 +134,14 @@ class GPEXP_OT_lower_layers_name(bpy.types.Operator): 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', + object_name : BoolProperty(name='Normalize Object Name', 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'} + + # dash_to_undescore : BoolProperty(name='Dash To Underscore', + # default=True, description='Make the layers name lowercase') # , options={'SKIP_SAVE'} def invoke(self, context, event): # 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.prop(self, 'object_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: 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: rename_data = ob.name == ob.data.name - ob.name = ob.name.lower() + ob.name = ob.name.lower().replace('-', '_') if rename_data: - ob.data.name = ob.data.name.lower() + ob.data.name = ob.name if self.layer_name: 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"} diff --git a/__init__.py b/__init__.py index 98fc507..dfa0e05 100644 --- a/__init__.py +++ b/__init__.py @@ -2,7 +2,7 @@ bl_info = { "name": "GP Render", "description": "Organise export of gp layers through compositor output", "author": "Samuel Bernou", - "version": (0, 3, 1), + "version": (0, 3, 2), "blender": (2, 93, 0), "location": "View3D", "warning": "", @@ -16,7 +16,6 @@ from . import OP_clear from . import OP_clean from . import OP_connect_toggle from . import OP_manage_outputs -from . import OP_number_outputs from . import OP_scene_switch # from . import OP_check_layer_status from . import OP_setup_layers @@ -34,7 +33,6 @@ def register(): OP_connect_toggle.register() OP_merge_layers.register() OP_manage_outputs.register() - OP_number_outputs.register() OP_scene_switch.register() # OP_check_layer_status.register() OP_setup_layers.register() @@ -49,7 +47,6 @@ def unregister(): OP_setup_layers.unregister() # OP_check_layer_status.unregister() OP_scene_switch.unregister() - OP_number_outputs.unregister() OP_manage_outputs.unregister() OP_merge_layers.unregister() OP_connect_toggle.unregister() diff --git a/fn.py b/fn.py index a59145d..8252b26 100644 --- a/fn.py +++ b/fn.py @@ -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): return True +def normalize(text): + return text.lower().replace('-', '_') + +PATTERN = r'^(?P-\s)?(?P[A-Z]{2}_)?(?P.*?)(?P_[A-Z]{2})?(?P\.\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: def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'): def draw(self, context): diff --git a/ui.py b/ui.py index a8f6a0d..368267b 100644 --- a/ui.py +++ b/ui.py @@ -54,10 +54,11 @@ class GPEXP_PT_gp_node_ui(Panel): ## (re)number exports 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.enabled = bool(ct) 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' subcol.operator('gp.set_output_node_format', icon='OUTPUT', text='Copy Active Output Format')