Add search and replace scene_viewlayers_socket name option

0.2.0
master
pullusb 2025-03-31 17:19:27 +02:00
parent d56d32ad57
commit 8f689af892
4 changed files with 169 additions and 33 deletions

View File

@ -2,7 +2,7 @@ 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, 1, 0), "version": (0, 2, 0),
"blender": (3, 0, 0), "blender": (3, 0, 0),
"location": "View3D", "location": "View3D",
"warning": "", "warning": "",

8
fn.py
View File

@ -106,9 +106,11 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
outs = [o for o in node.outputs if not o.is_unavailable and not 'crypto' in o.name.lower() and o.name not in exclusions] outs = [o for o in node.outputs if not o.is_unavailable and not 'crypto' in o.name.lower() and o.name not in exclusions]
cryptout = [o for o in node.outputs if not o.is_unavailable and 'crypto' in o.name.lower() and o.name not in exclusions] cryptout = [o for o in node.outputs if not o.is_unavailable and 'crypto' in o.name.lower() and o.name not in exclusions]
if node.type == 'R_LAYERS':
out_base = node.layer ## Using render layer can force connexion to an existing FO node when user may want a new one
elif node.label: # if node.type == 'R_LAYERS':
# out_base = node.layer
if node.label:
out_base = node.label out_base = node.label
else: else:
out_base = node.name out_base = node.name

View File

@ -1,57 +1,162 @@
import bpy import bpy
import os import os
import re
from . import fn from . import fn
from bpy.props import (StringProperty,
BoolProperty,
EnumProperty,
CollectionProperty)
## -- search and replace (WIP) to batch rename
class RT_OT_search_and_replace(bpy.types.Operator):
bl_idname = "rt.search_and_replace"
bl_label = "Search And Replace"
bl_description = "Search/Replace texts"
## target to affect
data_path: StringProperty(name="Data Path", description="Path to collection prop to affect", default="")
target_prop: StringProperty(name="Target Prop", description="Name of the property to affect in whole collection", default="")
## 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')
prefix: BoolProperty(name="Prefix Only", description="Affect only prefix of name (skipping names without separator)", default=False)
use_regex: BoolProperty(name="Regex", description="Use regular expression (advanced), equivalent to python re.sub()", default=False)
separator: StringProperty(name="Separator", description="Separator to get prefix", default='_')
# selected: BoolProperty(name="Selected Only", description="Affect only selection", default=False)
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 execute(self, context):
## Get the collection prop from data path
collection_prop = eval(self.data_path)
count = 0
for item in collection_prop:
## Get the target prop
name = getattr(item, self.target_prop)
# prop = prop.replace(self.find, self.replace)
new = self.rename(name)
if new is None or name == new:
continue
## Rename in case of difference
print(f'rename: {name} --> {new}')
setattr(item, self.target_prop, new)
count += 1
if count:
mess = str(count) + ' rename(s)'
self.report({'INFO'}, mess)
else:
self.report({'WARNING'}, 'Nothing changed')
return{'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
row = layout.row()
# row_a= row.row()
# row_a.prop(self, "separator")
# # row_a.prop(self, "selected")
row_b= row.row()
row_b.prop(self, "prefix")
row_c= row.row()
row_c.prop(self, "use_regex")
# row_a.active = not self.use_regex
row_b.active = not self.use_regex
layout.prop(self, "find")
layout.prop(self, "replace")
## -- properties and operator for file output connect
class RT_PG_selectable_prop(bpy.types.PropertyGroup): class RT_PG_selectable_prop(bpy.types.PropertyGroup):
node_name: bpy.props.StringProperty(name="Node Name") node_name: StringProperty(name="Node Name")
node_label: bpy.props.StringProperty(name="Node Label") node_label: StringProperty(name="Node Label")
name: bpy.props.StringProperty(name="Name or Path") name: StringProperty(name="Name or Path")
socket_name: bpy.props.StringProperty(name="Source socket Name") # Source socket name as reference socket_name: StringProperty(name="Source socket Name") # Source socket name as reference
select: bpy.props.BoolProperty(name="Selected", default=True) select: BoolProperty(name="Selected", default=True)
is_linked: bpy.props.BoolProperty(name="Linked", default=False) is_linked: BoolProperty(name="Linked", default=False)
# is_valid: bpy.props.BoolProperty(name="Valid", default=True) # is_valid: BoolProperty(name="Valid", default=True)
## extra output naming options ## extra output naming options
name_from_node: bpy.props.StringProperty(name="Name From Node") name_from_node: StringProperty(name="Name From Node")
name_from_node_and_socket: bpy.props.StringProperty(name="Name From Node And Socket") name_from_node_and_socket: StringProperty(name="Name From Node And Socket")
name_from_node_and_socket_with_scene: StringProperty(name="Name From Node And Socket With Scene prefixed")
class RT_OT_connect_selected_to_file_out(bpy.types.Operator): class RT_OT_create_output_layers(bpy.types.Operator):
bl_idname = "rt.connect_selected_to_file_out" bl_idname = "rt.create_output_layers"
bl_label = "Connect Selected To File Output" bl_label = "Create Output Layers"
bl_description = "Connect Selected Nodes to a new file output node\ bl_description = "Connect Selected Nodes to a new file output node\
\nIf a fileoutput node is selected, socket are added to it" \nIf an existing file output node is selected, socket are added to it"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
socket_collection : bpy.props.CollectionProperty(type=RT_PG_selectable_prop) ## ! collection prop -> Now stored on window manager at invoke
# socket_collection : CollectionProperty(type=RT_PG_selectable_prop)
show_custom_settings : bpy.props.BoolProperty( show_custom_settings : BoolProperty(
name='Settings', name='Settings',
default=False) default=False)
## enum choice for naming: socket_name, node_name, node_and_socket_name, ## enum choice for naming: socket_name, node_name, node_and_socket_name,
name_type : bpy.props.EnumProperty( name_type : EnumProperty(
name='Output Name From', name='Output Name From',
description='Choose the output name\ description='Choose the output name\
\nNode name use Label (Use node name when there is no Label)', \nNode name use Label (Use node name when there is no Label)',
default='node_and_socket_name', default='node_and_socket_name',
items=( items=(
('node_and_socket_name', 'Node_Socket Name', 'Use the node name prefix and socket name', 0), ('node_and_socket_name', 'Node Socket Name', 'Use the node name prefix and socket name', 0),
('socket_name', 'Socket Name', 'Use the socket name as output name', 1), ('node_and_socket_name_with_scene', 'Scene + Node Socket Name', 'Use the node name prefix and socket name, prefix scene with render layer nodes', 1),
('node_name', 'Node Name', 'Use the node name as output name', 2), ('socket_name', 'Socket Name', 'Use the socket name as output name', 2),
('node_name', 'Node Name', 'Use the node name as output name', 3),
) )
) )
# prefix_with_node_name : bpy.props.BoolProperty( # prefix_with_node_name : BoolProperty(
# name='Prefix With Node Name', # name='Prefix With Node Name',
# description='Add the node name as prefix to the output name', # description='Add the node name as prefix to the output name',
# default=False) # default=False)
base_path : bpy.props.StringProperty( base_path : StringProperty(
name='Custom base path', name='Custom base path',
default='', default='',
description='Set the base path of created file_output (not if already exists)') description='Set the base path of created file_output (not if already exists)')
file_format : bpy.props.EnumProperty( file_format : EnumProperty(
name='Output Format', name='Output Format',
default='NONE', default='NONE',
items=( items=(
@ -61,7 +166,7 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
) )
) )
exr_codec : bpy.props.EnumProperty( exr_codec : EnumProperty(
name='Codec', name='Codec',
default='PIZ', default='PIZ',
description='Codec settings for OpenEXR', description='Codec settings for OpenEXR',
@ -78,7 +183,7 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
), ),
) )
color_depth : bpy.props.EnumProperty( color_depth : EnumProperty(
name='Color Depth', name='Color Depth',
default='16', default='16',
description='Bit depth per channel', description='Bit depth per channel',
@ -92,6 +197,12 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
) )
def invoke(self, context, event): def invoke(self, context, event):
if not hasattr(context.window_manager, 'rt_socket_collection'):
## Create collection prop on window manager if not existing
## (not stored in self anymore, so that other operators can access it, ex: search and replace)
bpy.types.WindowManager.rt_socket_collection = CollectionProperty(type=RT_PG_selectable_prop)
self.socket_collection = context.window_manager.rt_socket_collection
self.socket_collection.clear() self.socket_collection.clear()
if event.ctrl: if event.ctrl:
# Direct connect, do not use any options # Direct connect, do not use any options
@ -116,19 +227,25 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
## Store other naming options (cleaned at exec with bpy.path.clean_name) ## Store other naming options (cleaned at exec with bpy.path.clean_name)
node_name = n.label.strip() if n.label.strip() else n.name node_name = n.label.strip() if n.label.strip() else n.name
scene_node_name = node_name # same by default
if n.type == 'R_LAYERS':
scene_node_name = f'{n.scene.name}_{n.layer}'
## Change node_name for render layers: scene_viewlayer_name ## Change node_name for render layers: scene_viewlayer_name
if n.type == 'R_LAYERS' and node_name != n.label: # skip if a label is set if n.type == 'R_LAYERS' and node_name != n.label: # skip if a label is set
node_name = f'{n.layer}' node_name = f'{n.layer}'
# node_name = f'{n.scene.name}_{n.layer}'
item.name_from_node = node_name item.name_from_node = node_name
if len(n.outputs) == 1: if len(n.outputs) == 1:
## Only one output, just pick node name, no need to add socket name ## Only one output, just pick node name, no need to add socket name
item.name_from_node_and_socket = node_name item.name_from_node_and_socket = node_name
item.name_from_node_and_socket_with_scene = scene_node_name
else: else:
print(f'node_name: {node_name} VS {scene_node_name}')
item.name_from_node_and_socket = f'{node_name}_{o.name}' item.name_from_node_and_socket = f'{node_name}_{o.name}'
item.name_from_node_and_socket_with_scene = f'{scene_node_name}_{o.name}'
## TODO: rename item.name according to template pairs in preferences (to add later) ## TODO: rename item.name according to template pairs in preferences (to add later)
if o.is_linked: if o.is_linked:
@ -152,6 +269,18 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
col.prop(self, 'exr_codec') col.prop(self, 'exr_codec')
col.row().prop(self, 'color_depth', expand=True) col.row().prop(self, 'color_depth', expand=True)
search_row = layout.row()
op = search_row.operator("rt.search_and_replace", icon='BORDERMOVE')
op.data_path = 'bpy.context.window_manager.rt_socket_collection'
if self.name_type == 'socket_name':
op.target_prop = 'name'
elif self.name_type == 'node_name':
op.target_prop = 'name_from_node'
elif self.name_type == 'node_and_socket_name':
op.target_prop = 'name_from_node_and_socket'
elif self.name_type == 'node_and_socket_name_with_scene':
op.target_prop = 'name_from_node_and_socket_with_scene'
## Node Sockets ## Node Sockets
layout.use_property_split = False layout.use_property_split = False
col = layout.column() col = layout.column()
@ -190,6 +319,8 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
row.prop(item, 'name_from_node', text='') row.prop(item, 'name_from_node', text='')
elif self.name_type == 'node_and_socket_name': elif self.name_type == 'node_and_socket_name':
row.prop(item, 'name_from_node_and_socket', text='') row.prop(item, 'name_from_node_and_socket', text='')
elif self.name_type == 'node_and_socket_name_with_scene':
row.prop(item, 'name_from_node_and_socket_with_scene', text='')
def execute(self, context): def execute(self, context):
@ -213,6 +344,8 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
final_name = item.name_from_node final_name = item.name_from_node
elif self.name_type == 'node_and_socket_name': elif self.name_type == 'node_and_socket_name':
final_name = item.name_from_node_and_socket final_name = item.name_from_node_and_socket
elif self.name_type == 'node_and_socket_name_with_scene':
final_name = item.name_from_node_and_socket_with_scene
if not item.select: if not item.select:
# All deselected goes to exclude with {node_name: [socket1, ...]} # All deselected goes to exclude with {node_name: [socket1, ...]}
@ -246,8 +379,9 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
return {"FINISHED"} return {"FINISHED"}
classes=( classes=(
RT_OT_search_and_replace,
RT_PG_selectable_prop, RT_PG_selectable_prop,
RT_OT_connect_selected_to_file_out, RT_OT_create_output_layers,
) )
def register(): def register():

2
ui.py
View File

@ -11,7 +11,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.connect_selected_to_file_out", icon="NODE") layout.operator("rt.create_output_layers", icon="NODE")
classes = ( classes = (