parent
d56d32ad57
commit
8f689af892
|
@ -2,7 +2,7 @@ bl_info = {
|
|||
"name": "Render Toolbox",
|
||||
"description": "Perform checks and setup outputs",
|
||||
"author": "Samuel Bernou",
|
||||
"version": (0, 1, 0),
|
||||
"version": (0, 2, 0),
|
||||
"blender": (3, 0, 0),
|
||||
"location": "View3D",
|
||||
"warning": "",
|
||||
|
|
8
fn.py
8
fn.py
|
@ -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]
|
||||
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
|
||||
elif node.label:
|
||||
|
||||
## Using render layer can force connexion to an existing FO node when user may want a new one
|
||||
# if node.type == 'R_LAYERS':
|
||||
# out_base = node.layer
|
||||
if node.label:
|
||||
out_base = node.label
|
||||
else:
|
||||
out_base = node.name
|
||||
|
|
188
setup_outputs.py
188
setup_outputs.py
|
@ -1,57 +1,162 @@
|
|||
import bpy
|
||||
import os
|
||||
import re
|
||||
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):
|
||||
node_name: bpy.props.StringProperty(name="Node Name")
|
||||
node_label: bpy.props.StringProperty(name="Node Label")
|
||||
name: bpy.props.StringProperty(name="Name or Path")
|
||||
socket_name: bpy.props.StringProperty(name="Source socket Name") # Source socket name as reference
|
||||
select: bpy.props.BoolProperty(name="Selected", default=True)
|
||||
is_linked: bpy.props.BoolProperty(name="Linked", default=False)
|
||||
# is_valid: bpy.props.BoolProperty(name="Valid", default=True)
|
||||
node_name: StringProperty(name="Node Name")
|
||||
node_label: StringProperty(name="Node Label")
|
||||
name: StringProperty(name="Name or Path")
|
||||
socket_name: StringProperty(name="Source socket Name") # Source socket name as reference
|
||||
select: BoolProperty(name="Selected", default=True)
|
||||
is_linked: BoolProperty(name="Linked", default=False)
|
||||
# is_valid: BoolProperty(name="Valid", default=True)
|
||||
|
||||
## extra output naming options
|
||||
name_from_node: bpy.props.StringProperty(name="Name From Node")
|
||||
name_from_node_and_socket: bpy.props.StringProperty(name="Name From Node And Socket")
|
||||
name_from_node: StringProperty(name="Name From Node")
|
||||
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):
|
||||
bl_idname = "rt.connect_selected_to_file_out"
|
||||
bl_label = "Connect Selected To File Output"
|
||||
bl_description = "Connect Selected Nodes to a new fileoutput node\
|
||||
\nIf a fileoutput node is selected, socket are added to it"
|
||||
class RT_OT_create_output_layers(bpy.types.Operator):
|
||||
bl_idname = "rt.create_output_layers"
|
||||
bl_label = "Create Output Layers"
|
||||
bl_description = "Connect Selected Nodes to a new file output node\
|
||||
\nIf an existing file output node is selected, socket are added to it"
|
||||
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',
|
||||
default=False)
|
||||
|
||||
## enum choice for naming: socket_name, node_name, node_and_socket_name,
|
||||
name_type : bpy.props.EnumProperty(
|
||||
name_type : EnumProperty(
|
||||
name='Output Name From',
|
||||
description='Choose the output name\
|
||||
\nNode name use Label (Use node name when there is no Label)',
|
||||
default='node_and_socket_name',
|
||||
items=(
|
||||
('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_name', 'Node Name', 'Use the node name as output name', 2),
|
||||
('node_and_socket_name', 'Node Socket Name', 'Use the node name prefix and socket name', 0),
|
||||
('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),
|
||||
('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',
|
||||
# description='Add the node name as prefix to the output name',
|
||||
# default=False)
|
||||
|
||||
base_path : bpy.props.StringProperty(
|
||||
base_path : StringProperty(
|
||||
name='Custom base path',
|
||||
default='',
|
||||
description='Set the base path of created file_output (not if already exists)')
|
||||
|
||||
file_format : bpy.props.EnumProperty(
|
||||
file_format : EnumProperty(
|
||||
name='Output Format',
|
||||
default='NONE',
|
||||
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',
|
||||
default='PIZ',
|
||||
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',
|
||||
default='16',
|
||||
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):
|
||||
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()
|
||||
if event.ctrl:
|
||||
# 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)
|
||||
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
|
||||
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.scene.name}_{n.layer}'
|
||||
|
||||
item.name_from_node = node_name
|
||||
|
||||
if len(n.outputs) == 1:
|
||||
## 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_with_scene = scene_node_name
|
||||
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_with_scene = f'{scene_node_name}_{o.name}'
|
||||
|
||||
## TODO: rename item.name according to template pairs in preferences (to add later)
|
||||
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.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
|
||||
layout.use_property_split = False
|
||||
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='')
|
||||
elif self.name_type == 'node_and_socket_name':
|
||||
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):
|
||||
|
||||
|
@ -213,6 +344,8 @@ class RT_OT_connect_selected_to_file_out(bpy.types.Operator):
|
|||
final_name = item.name_from_node
|
||||
elif self.name_type == 'node_and_socket_name':
|
||||
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:
|
||||
# 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"}
|
||||
|
||||
classes=(
|
||||
RT_OT_search_and_replace,
|
||||
RT_PG_selectable_prop,
|
||||
RT_OT_connect_selected_to_file_out,
|
||||
RT_OT_create_output_layers,
|
||||
)
|
||||
|
||||
def register():
|
||||
|
|
Loading…
Reference in New Issue