import bpy import os from pathlib import Path import re from time import time def renumber_sequence_on_disk_from_file_slots(apply=True, active_scene_only=False): '''renumber sequence on disk from scenes file slots''' scn = bpy.context.scene blend = Path(bpy.data.filepath) render = blend.parent / 'render' prenum = re.compile(r'\d{3}_') print('-- starting rename sequences numbers from fileslots number') if not apply: print('-- Dry run') t0 = time() ct = 0 if active_scene_only: # Only on currrent scene file_outs = [n for n in bpy.context.scene.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.name.startswith('OUT_') and not n.mute] else: # multi scene check: file_outs = [] for S in bpy.data.scenes: if not S.node_tree or not S.use_nodes: # S.name == 'Scene' or continue file_outs += [n for n in S.node_tree.nodes if n.type == 'OUTPUT_FILE' and n.name.startswith('OUT_') and not n.mute] if not file_outs: return 'No file output found (should be unmuted nodes with name starting with OUT_)', '_' for fo in file_outs: obj_full = fo.base_path.split('/')[-1] obj = prenum.sub('', obj_full) obj_num = prenum.search(obj_full) if obj_num: obj_num = obj_num.group(0) ## check if folder exists folder_path = None for d in os.scandir(render): if d.is_dir() and prenum.sub('', d.name) == obj: folder_path = render / d.name break if not folder_path: print(f'Could not find obj folder for: {obj}') continue # rename inside folder dirst so that root path isn't changed while iterating for fs in fo.file_slots: img_full = fs.path.split('/')[0] img = prenum.sub('', img_full) img_num = prenum.search(img_full) if img_num: img_num = img_num.group(0) else: print(f'! no num : {fo.base_path} : {img_full}') continue # If no img_num no point in renaming sequences img_dir_path = None for img_dir in os.scandir(folder_path): if img_dir.is_dir() and prenum.sub('', img_dir.name) == img: img_dir_path = folder_path / img_dir.name break if not img_dir_path: print(f'Could not find img folder for: {img}') continue # if folder exists check if full name is ok if img_full == img_dir_path.name: continue # name already (maybe not in sequence but should be good) # rename sequence and image folder for frame in os.scandir(img_dir_path): good = img_num + prenum.sub('', frame.name) if frame.name != good: print(f' img: {frame.name} > {good}') ct += 1 if apply: fp = Path(frame.path) fp.rename(fp.parent / good) # rename image folder if img_dir_path.name != img_full: print(f' dir:{img_dir_path.name} > {img_full}') ct += 1 if apply: img_dir_path.rename(img_dir_path.parent / img_full) # rename object folder if obj_num and folder_path.name != obj_full: print(f'obj: {folder_path.name} > {obj_full}') ct += 1 if apply: folder_path.rename(folder_path.parent / obj_full) elapsed = f'{time() - t0:.2f}s' print(f'Eslapsed time: {elapsed}') return ct, elapsed class GPEXP_OT_renumber_files_on_disk(bpy.types.Operator): bl_idname = "gp.renumber_files_on_disk" bl_label = "Renumber Files On Disk" bl_description = "Rename folder/files in render folder on disk according to unmuted file output numbering" bl_options = {"REGISTER"} def invoke(self, context, event): # return self.execute(context) return context.window_manager.invoke_props_dialog(self) dry_run: bpy.props.BoolProperty(name='Dry-run (no actions, prints in console only)', default=False, description='Test mode. If checked, no action is actually performed') active_scene_only: bpy.props.BoolProperty(name='Only Active Scene', default=False, description='use only file output of active scene instead of all scenes (skipping "Scene")') def draw(self, context): layout = self.layout layout.prop(self, 'dry_run') layout.prop(self, 'active_scene_only') def execute(self, context): ct, timing = renumber_sequence_on_disk_from_file_slots(apply = not self.dry_run, active_scene_only=self.active_scene_only) if isinstance(ct, str): self.report({'ERROR'}, ct) return {"CANCELLED"} if not ct: self.report({'WARNING'}, 'Already good or nothing to rename') return {"CANCELLED"} if self.dry_run: mess = f'Dry run : {ct} items would have been renamed, see console' else: mess = f'{ct} items renamed in {timing}' self.report({'INFO'}, mess) return {"FINISHED"} classes=( GPEXP_OT_renumber_files_on_disk, ) def register(): for cls in classes: bpy.utils.register_class(cls) def unregister(): for cls in reversed(classes): bpy.utils.unregister_class(cls)