[Bf-blender-cvs] [a5b1231de79] master: WM: batch rename operator

Campbell Barton noreply at git.blender.org
Sat Aug 31 19:37:03 CEST 2019


Commit: a5b1231de790e4cbf0d4dd62e30afcbc5d8a95f5
Author: Campbell Barton
Date:   Sun Sep 1 03:16:58 2019 +1000
Branches: master
https://developer.blender.org/rBa5b1231de790e4cbf0d4dd62e30afcbc5d8a95f5

WM: batch rename operator

Matches rename active, supports objects, bones, sequence strips & nodes.

Support chaining actions, these can be extended, initially support:

- set/prefix/suffix
- search replace
- stripping characters
- capitalization.

===================================================================

M	release/scripts/presets/keyconfig/keymap_data/blender_default.py
M	release/scripts/startup/bl_operators/wm.py
M	release/scripts/startup/bl_ui/space_topbar.py

===================================================================

diff --git a/release/scripts/presets/keyconfig/keymap_data/blender_default.py b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
index f789da6f3fa..ae2d2952f85 100644
--- a/release/scripts/presets/keyconfig/keymap_data/blender_default.py
+++ b/release/scripts/presets/keyconfig/keymap_data/blender_default.py
@@ -409,6 +409,7 @@ def km_window(params):
         items.extend([
             ("wm.doc_view_manual_ui_context", {"type": 'F1', "value": 'PRESS'}, None),
             op_panel("TOPBAR_PT_name", {"type": 'F2', "value": 'PRESS'}, [("keep_open", False)]),
+            ("wm.batch_rename", {"type": 'F2', "value": 'PRESS', "alt": True}, None),
             ("wm.search_menu", {"type": 'F3', "value": 'PRESS'}, None),
             op_menu("TOPBAR_MT_file_context_menu", {"type": 'F4', "value": 'PRESS'}),
         ])
diff --git a/release/scripts/startup/bl_operators/wm.py b/release/scripts/startup/bl_operators/wm.py
index bf1c79e766f..f4724ee9709 100644
--- a/release/scripts/startup/bl_operators/wm.py
+++ b/release/scripts/startup/bl_operators/wm.py
@@ -25,6 +25,7 @@ from bpy.types import (
 )
 from bpy.props import (
     BoolProperty,
+    CollectionProperty,
     EnumProperty,
     FloatProperty,
     IntProperty,
@@ -1174,6 +1175,7 @@ rna_vector_subtype_items = (
     ('QUATERNION', "Quaternion Rotation", "Quaternion rotation (affects NLA blending)"),
 )
 
+
 class WM_OT_properties_edit(Operator):
     bl_idname = "wm.properties_edit"
     bl_label = "Edit Property"
@@ -1763,6 +1765,294 @@ class WM_OT_toolbar(Operator):
         return {'FINISHED'}
 
 
+class BatchRenameAction(bpy.types.PropertyGroup):
+    # category: StringProperty()
+    type: EnumProperty(
+        name="Operation",
+        items=(
+            ('SET', "Set Name", "Set a new name or prefix/suffix the existing one"),
+            ('STRIP', "Strip Characters", "Strip leading/trailing text from the name"),
+            ('REPLACE', "Find/Replace", "Replace text in the name"),
+            ('CASE', "Change Case", "Change case of each name"),
+        ),
+    )
+
+    # We could split these into sub-properties, however it's not so important.
+
+    # type: 'SET'.
+    set_name: StringProperty(name="Name")
+    set_method: EnumProperty(
+        name="Method",
+        items=(
+            ('NEW', "New", ""),
+            ('PREFIX', "Prefix", ""),
+            ('SUFFIX', "Suffix", ""),
+        ),
+        default='SUFFIX',
+    )
+
+    # type: 'STRIP'.
+    strip_chars: EnumProperty(
+        name="Strip Characters",
+        options={'ENUM_FLAG'},
+        items=(
+            ('SPACE', "Spaces", ""),
+            ('DIGIT', "Digits", ""),
+            ('PUNCT', "Punctuation", ""),
+        ),
+    )
+
+    # type: 'STRIP'.
+    strip_part: EnumProperty(
+        name="Strip Part",
+        options={'ENUM_FLAG'},
+        items=(
+            ('START', "Start", ""),
+            ('END', "End", ""),
+        ),
+    )
+
+    # type: 'REPLACE'.
+    replace_src: StringProperty(name="Find")
+    replace_dst: StringProperty(name="Replace")
+    replace_match_case: BoolProperty(name="Match Case")
+
+    # type: 'CASE'.
+    case_method: EnumProperty(
+        name="Case",
+        items=(
+            ('UPPER', "Upper Case", ""),
+            ('LOWER', "Lower Case", ""),
+            ('TITLE', "Title Caps", ""),
+        ),
+    )
+
+    # Weak, add/remove as properties.
+    op_add: BoolProperty()
+    op_remove: BoolProperty()
+
+
+class WM_OT_batch_rename(Operator):
+    bl_idname = "wm.batch_rename"
+    bl_label = "Batch Rename"
+
+    bl_options = {'UNDO', 'INTERNAL'}
+
+    data_source: EnumProperty(
+        name="Source",
+        items=(
+            ('SELECT', "Selected", ""),
+            ('ALL', "All", ""),
+        ),
+    )
+
+    actions: CollectionProperty(type=BatchRenameAction)
+
+    @staticmethod
+    def _data_from_context(context, only_selected):
+
+        mode = context.mode
+        scene = context.scene
+        space = context.space_data
+        space_type = None if (space is None) else space.type
+
+        data = None
+        if space_type == 'SEQUENCE_EDITOR':
+            data = (
+                # TODO, we don't have access to seqbasep, this won't work when inside metas.
+                [seq for seq in context.scene.sequence_editor.sequences_all if seq.select]
+                if only_selected else
+                context.scene.sequence_editor.sequences_all,
+                "name",
+                "Strip(s)",
+            )
+        elif space_type == 'NODE_EDITOR':
+            data = (
+                context.selected_nodes
+                if only_selected else
+                list(space.node_tree.nodes),
+                "name",
+                "Node(s)",
+            )
+        else:
+            if mode == 'POSE' or (mode == 'WEIGHT_PAINT' and context.pose_object):
+                data = (
+                    [pchan.bone for pchan in context.selected_pose_bones]
+                    if only_selected else
+                    [pchan.bone for ob in context.objects_in_mode_unique_data for pbone in ob.pose.bones],
+                    "name",
+                    "Bone(s)",
+                )
+            elif mode == 'EDIT_ARMATURE':
+                data = (
+                    context.selected_editable_bones
+                    if only_selected else
+                    [ebone for ob in context.objects_in_mode_unique_data for ebone in ob.data.edit_bones],
+                    "name",
+                    "Edit Bone(s)",
+                )
+            else:
+                data = (
+                    context.selected_editable_objects
+                    if only_selected else
+                    [ob for ob in bpy.data.objects if ob.library is None],
+                    "name",
+                    "Object(s)",
+                )
+        return data
+
+    @staticmethod
+    def _apply_actions(actions, name):
+        import string
+        import re
+
+        for action in actions:
+            ty = action.type
+            if ty == 'SET':
+                text = action.set_name
+                method = action.set_method
+                if method == 'NEW':
+                    name = text
+                elif method == 'PREFIX':
+                    name = text + name
+                elif method == 'SUFFIX':
+                    name = name + text
+                else:
+                    assert(0)
+
+            elif ty == 'STRIP':
+                chars = action.strip_chars
+                chars_strip = (
+                    "{:s}{:s}{:s}"
+                ).format(
+                    string.punctuation if 'PUNCT' in chars else "",
+                    string.digits if 'DIGIT' in chars else "",
+                    " " if 'SPACE' in chars else "",
+                )
+                part = action.strip_part
+                if 'START' in part:
+                    name = name.lstrip(chars_strip)
+                if 'END' in part:
+                    name = name.rstrip(chars_strip)
+
+            elif ty == 'REPLACE':
+                if action.replace_match_case:
+                    name = name.replace(
+                        action.replace_src,
+                        action.replace_dst,
+                    )
+                else:
+                    name = re.sub(
+                        re.escape(action.replace_src),
+                        re.escape(action.replace_dst),
+                        name,
+                        flags=re.IGNORECASE,
+                    )
+            elif ty == 'CASE':
+                method = action.case_method
+                if method == 'UPPER':
+                    name = name.upper()
+                elif method == 'LOWER':
+                    name = name.lower()
+                elif method == 'TITLE':
+                    name = name.title()
+                else:
+                    assert(0)
+            else:
+                assert(0)
+        return name
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.label(text="Rename {:d} {:s}".format(len(self._data[0]), self._data[2]))
+        row.prop(self, "data_source", expand=True)
+
+        for action in self.actions:
+            box = layout.box()
+
+            row = box.row(align=True)
+            row.prop(action, "type", text="")
+            row.prop(action, "op_add", text="", icon='ADD')
+            row.prop(action, "op_remove", text="", icon='REMOVE')
+
+            ty = action.type
+            if ty == 'SET':
+                box.prop(action, "set_method")
+                box.prop(action, "set_name")
+            elif ty == 'STRIP':
+                box.row().prop(action, "strip_chars")
+                box.row().prop(action, "strip_part")
+            elif ty == 'REPLACE':
+                box.row().prop(action, "replace_src")
+                box.row().prop(action, "replace_dst")
+                box.row().prop(action, "replace_match_case")
+            elif ty == 'CASE':
+                box.row().prop(action, "case_method", expand=True)
+
+    def check(self, context):
+        changed = False
+        for i, action in enumerate(self.actions):
+            if action.op_add:
+                action.op_add = False
+                self.actions.add()
+                if i + 2 != len(self.actions):
+                    self.actions.move(len(self.actions) - 1, i + 1)
+                changed = True
+                break
+            if action.op_remove:
+                action.op_remove = False
+                if len(self.actions) > 1:
+                    self.actions.remove(i)
+                changed = True
+                break
+
+        if self._data_source_prev != self.data_source:
+            only_selected = self.data_source == 'SELECT'
+            self._data = self._data_from_context(context, only_selected)
+            self._data_source_prev = self.data_source
+            changed = True
+
+        return changed
+
+    def execute(self, context):
+        seq, attr, descr = self._data
+
+        actions = self.actions
+
+        total_len = 0
+        change_len = 0
+        for item in seq:
+            name_sr

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-blender-cvs mailing list