Add search and replace for file outputs
This commit is contained in:
parent
2e4a9a3add
commit
9f9284853e
@ -2,8 +2,8 @@ bl_info = {
|
|||||||
"name": "Render Toolbox",
|
"name": "Render Toolbox",
|
||||||
"description": "Perform checks and setup outputs",
|
"description": "Perform checks and setup outputs",
|
||||||
"author": "Samuel Bernou",
|
"author": "Samuel Bernou",
|
||||||
"version": (0, 3, 1),
|
"version": (0, 4, 0),
|
||||||
"blender": (3, 0, 0),
|
"blender": (4, 0, 0),
|
||||||
"location": "View3D",
|
"location": "View3D",
|
||||||
"warning": "",
|
"warning": "",
|
||||||
"doc_url": "https://git.autourdeminuit.com/autour_de_minuit/render_toolbox",
|
"doc_url": "https://git.autourdeminuit.com/autour_de_minuit/render_toolbox",
|
||||||
@ -12,10 +12,12 @@ bl_info = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
from . import setup_outputs
|
from . import setup_outputs
|
||||||
|
from . import outputs_search_and_replace
|
||||||
from . import ui
|
from . import ui
|
||||||
|
|
||||||
bl_modules = (
|
bl_modules = (
|
||||||
setup_outputs,
|
setup_outputs,
|
||||||
|
outputs_search_and_replace,
|
||||||
ui,
|
ui,
|
||||||
# prefs,
|
# prefs,
|
||||||
)
|
)
|
||||||
|
165
outputs_search_and_replace.py
Normal file
165
outputs_search_and_replace.py
Normal file
@ -0,0 +1,165 @@
|
|||||||
|
import bpy
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
from . import fn
|
||||||
|
from bpy.props import (StringProperty,
|
||||||
|
BoolProperty,
|
||||||
|
EnumProperty,
|
||||||
|
CollectionProperty)
|
||||||
|
|
||||||
|
from .constant import TECH_PASS_KEYWORDS
|
||||||
|
|
||||||
|
|
||||||
|
class RT_OT_outputs_search_and_replace(bpy.types.Operator):
|
||||||
|
bl_idname = "rt.outputs_search_and_replace"
|
||||||
|
bl_label = "Search And Replace Outputs Paths"
|
||||||
|
bl_description = "Search/Replace texts in output path and slots"
|
||||||
|
bl_options = {'REGISTER', 'UNDO'}
|
||||||
|
|
||||||
|
## Search and replace options
|
||||||
|
find: StringProperty(name="Find", description="Name to replace", default="", maxlen=0, options={'HIDDEN'}, subtype='NONE')
|
||||||
|
replace: StringProperty(name="Repl", description="New name placed", default="", maxlen=0, options={'HIDDEN'}, subtype='NONE')
|
||||||
|
use_regex: BoolProperty(name="Regex", description="Use regular expression (advanced), equivalent to python re.sub()", default=False)
|
||||||
|
|
||||||
|
target : EnumProperty(
|
||||||
|
name="Target Fields",
|
||||||
|
description="Fields to search and replace in outputs",
|
||||||
|
items=(
|
||||||
|
('path', "Base Paths", "search and replace in output node paths"),
|
||||||
|
('slots', "File Slots", "search and replace in output node file slots (also Layer slots for multilayers outputs)"),
|
||||||
|
('all', "All", "search and replace in both paths and slots"),
|
||||||
|
),
|
||||||
|
default='all'
|
||||||
|
)
|
||||||
|
|
||||||
|
prefix: BoolProperty(name="Prefix Only", description="Affect only prefix of name (skipping names without separator)", default=False)
|
||||||
|
|
||||||
|
|
||||||
|
separator: StringProperty(name="Separator", description="Separator for prefix", default='_')
|
||||||
|
selected_node_only: BoolProperty(name="Selected Nodes Only", description="Affect only selected file output nodes", default=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.scene.node_tree and context.scene.node_tree.nodes
|
||||||
|
|
||||||
|
def rename(self, source):
|
||||||
|
if not self.find:
|
||||||
|
return
|
||||||
|
|
||||||
|
old = source
|
||||||
|
if self.use_regex:
|
||||||
|
new = re.sub(self.find, self.replace, source)
|
||||||
|
if old != new:
|
||||||
|
return new
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.prefix:
|
||||||
|
if not self.separator in source:
|
||||||
|
# Only if separator exists
|
||||||
|
return
|
||||||
|
splited = source.split(self.separator)
|
||||||
|
prefix = splited[0]
|
||||||
|
new_prefix = prefix.replace(self.find, self.replace)
|
||||||
|
if prefix != new_prefix:
|
||||||
|
splited[0] = new_prefix
|
||||||
|
return self.separator.join(splited)
|
||||||
|
|
||||||
|
else:
|
||||||
|
new = source.replace(self.find, self.replace)
|
||||||
|
if old != new:
|
||||||
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return context.window_manager.invoke_props_dialog(self, width=400)
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
# col = layout.column(align=False)
|
||||||
|
# col.prop(self, "separator")
|
||||||
|
# col.prop(self, "selected_node_only")
|
||||||
|
row = layout.row(align=False)
|
||||||
|
row.prop(self, "target", text='Fields', expand=False)
|
||||||
|
row.prop(self, "selected_node_only", text="Selected File Outputs Only")
|
||||||
|
|
||||||
|
row = layout.row()
|
||||||
|
row_c= row.row()
|
||||||
|
row_c.prop(self, "use_regex", text="Use Regex")
|
||||||
|
|
||||||
|
row_b= layout.row()
|
||||||
|
row_b.prop(self, "prefix")
|
||||||
|
row_b.active = not self.use_regex
|
||||||
|
|
||||||
|
subrow_b = row_b.row(align=True)
|
||||||
|
subrow_b.active = self.prefix
|
||||||
|
subrow_b.prop(self, "separator")
|
||||||
|
|
||||||
|
layout.prop(self, "find")
|
||||||
|
layout.prop(self, "replace")
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
## Get the collection prop from data path
|
||||||
|
## any node_tree type (here we specifically want compo nodes)
|
||||||
|
# node_tree = bpy.context.space_data.edit_tree
|
||||||
|
# if not node_tree or not node_tree.nodes:
|
||||||
|
# return
|
||||||
|
|
||||||
|
## Checked in poll
|
||||||
|
node_tree = context.scene.node_tree
|
||||||
|
|
||||||
|
file_outputs = [f for f in node_tree.nodes if f.type == 'OUTPUT_FILE']
|
||||||
|
if self.selected_node_only:
|
||||||
|
file_outputs = [f for f in file_outputs if f.select]
|
||||||
|
|
||||||
|
if not file_outputs:
|
||||||
|
self.report({'ERROR'}, 'No file output nodes found')
|
||||||
|
return {'CANCELLED'}
|
||||||
|
|
||||||
|
print()
|
||||||
|
count = 0
|
||||||
|
for file_out in file_outputs:
|
||||||
|
## Get the target prop
|
||||||
|
if self.target in ('path', 'all'):
|
||||||
|
current_base_path = file_out.base_path
|
||||||
|
new_base_path = self.rename(current_base_path)
|
||||||
|
if new_base_path is not None and current_base_path != new_base_path:
|
||||||
|
print(f"\n{file_out.name}: base path: {current_base_path} to {new_base_path}")
|
||||||
|
file_out.base_path = new_base_path
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if self.target in ('slots', 'all'):
|
||||||
|
for slot in file_out.file_slots:
|
||||||
|
current_slot_path = slot.path
|
||||||
|
new_slot_path = self.rename(current_slot_path)
|
||||||
|
if new_slot_path is not None and current_slot_path != new_slot_path:
|
||||||
|
print(f"{file_out.name}: file slot: {current_slot_path} to {new_slot_path}")
|
||||||
|
slot.path = new_slot_path
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
## Layers slots
|
||||||
|
for layer in file_out.layer_slots:
|
||||||
|
current_layer_path = layer.name
|
||||||
|
new_layer_path = self.rename(current_layer_path)
|
||||||
|
if new_layer_path is not None and current_layer_path != new_layer_path:
|
||||||
|
print(f"{file_out.name}: layer slot: {current_layer_path} to {new_layer_path}")
|
||||||
|
layer.name = new_layer_path
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
if count:
|
||||||
|
self.report({'INFO'}, f"{str(count)} field(s) renamed")
|
||||||
|
else:
|
||||||
|
self.report({'WARNING'}, 'Nothing changed')
|
||||||
|
return{'FINISHED'}
|
||||||
|
|
||||||
|
|
||||||
|
classes = (
|
||||||
|
RT_OT_outputs_search_and_replace,
|
||||||
|
)
|
||||||
|
|
||||||
|
def register():
|
||||||
|
for cls in classes:
|
||||||
|
bpy.utils.register_class(cls)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
for cls in reversed(classes):
|
||||||
|
bpy.utils.unregister_class(cls)
|
@ -9,12 +9,15 @@ from bpy.props import (StringProperty,
|
|||||||
|
|
||||||
from .constant import TECH_PASS_KEYWORDS
|
from .constant import TECH_PASS_KEYWORDS
|
||||||
|
|
||||||
## -- search and replace (WIP) to batch rename
|
|
||||||
|
|
||||||
class RT_OT_search_and_replace(bpy.types.Operator):
|
# region Search and replace
|
||||||
bl_idname = "rt.search_and_replace"
|
## -- Search and replace to batch rename item in collection property
|
||||||
|
|
||||||
|
class RT_OT_colprop_search_and_replace(bpy.types.Operator):
|
||||||
|
bl_idname = "rt.colprop_search_and_replace"
|
||||||
bl_label = "Search And Replace"
|
bl_label = "Search And Replace"
|
||||||
bl_description = "Search/Replace texts"
|
bl_description = "Search/Replace texts"
|
||||||
|
bl_options = {"REGISTER", "INTERNAL"}
|
||||||
|
|
||||||
## target to affect
|
## target to affect
|
||||||
data_path: StringProperty(name="Data Path", description="Path to collection prop to affect", default="")
|
data_path: StringProperty(name="Data Path", description="Path to collection prop to affect", default="")
|
||||||
@ -103,6 +106,9 @@ class RT_OT_search_and_replace(bpy.types.Operator):
|
|||||||
layout.prop(self, "find")
|
layout.prop(self, "find")
|
||||||
layout.prop(self, "replace")
|
layout.prop(self, "replace")
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Create file output
|
||||||
|
|
||||||
## -- properties and operator for file output connect
|
## -- properties and operator for file output connect
|
||||||
|
|
||||||
@ -279,7 +285,7 @@ class RT_OT_create_output_layers(bpy.types.Operator):
|
|||||||
col.prop(self, 'split_tech_passes')
|
col.prop(self, 'split_tech_passes')
|
||||||
|
|
||||||
search_row = layout.row()
|
search_row = layout.row()
|
||||||
op = search_row.operator("rt.search_and_replace", icon='BORDERMOVE')
|
op = search_row.operator("rt.colprop_search_and_replace", icon='BORDERMOVE')
|
||||||
op.data_path = 'bpy.context.window_manager.rt_socket_collection'
|
op.data_path = 'bpy.context.window_manager.rt_socket_collection'
|
||||||
if self.name_type == 'socket_name':
|
if self.name_type == 'socket_name':
|
||||||
op.target_prop = 'name'
|
op.target_prop = 'name'
|
||||||
@ -398,8 +404,10 @@ class RT_OT_create_output_layers(bpy.types.Operator):
|
|||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
classes=(
|
classes=(
|
||||||
RT_OT_search_and_replace,
|
RT_OT_colprop_search_and_replace,
|
||||||
RT_PG_selectable_prop,
|
RT_PG_selectable_prop,
|
||||||
RT_OT_create_output_layers,
|
RT_OT_create_output_layers,
|
||||||
)
|
)
|
||||||
|
1
ui.py
1
ui.py
@ -12,6 +12,7 @@ class RT_PT_gp_node_ui(Panel):
|
|||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.operator("rt.create_output_layers", icon="NODE")
|
layout.operator("rt.create_output_layers", icon="NODE")
|
||||||
|
layout.operator("rt.outputs_search_and_replace", text='Search and replace outputs', icon="BORDERMOVE")
|
||||||
|
|
||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user