662 lines
27 KiB
Python
Executable File
662 lines
27 KiB
Python
Executable File
import bpy
|
|
import os
|
|
import re
|
|
|
|
from pathlib import Path
|
|
|
|
from .. import fn
|
|
from bpy.props import (StringProperty,
|
|
BoolProperty,
|
|
EnumProperty,
|
|
CollectionProperty)
|
|
|
|
|
|
# region 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_description = "Search/Replace texts"
|
|
bl_options = {"REGISTER", "INTERNAL"}
|
|
|
|
## 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")
|
|
|
|
# endregion
|
|
|
|
# region Create file output
|
|
|
|
## -- properties and operator for file output connect
|
|
|
|
class RT_PG_selectable_prop(bpy.types.PropertyGroup):
|
|
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_tech_pass: BoolProperty(name="Tech Pass", default=False)
|
|
# is_valid: BoolProperty(name="Valid", default=True)
|
|
|
|
## Specific to render layer nodes
|
|
scene_name : StringProperty(name="Scene Name", default='')
|
|
viewlayer_name : StringProperty(name="ViewLayer Name", default='')
|
|
|
|
|
|
## extra output naming options
|
|
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")
|
|
name_from_node_and_socket_with_scene: StringProperty(name="Name From Node And Socket With Scene prefixed")
|
|
name_from_template: StringProperty(name="Name From Templates")
|
|
## Individual ? (not transmitted to assignation function currently, only used to build remap name)
|
|
# name_from_template: StringProperty(name="Layer Name From Templates")
|
|
# slot_from_template: StringProperty(name="File Slot From Templates")
|
|
|
|
def update_template_file_slot(self, context):
|
|
for item in context.window_manager.rt_socket_collection:
|
|
item.name_from_template = fn.build_path_from_template(
|
|
self.template_file_slot,
|
|
node_name=item.node_name,
|
|
node_label=item.node_label,
|
|
scene_name=item.scene_name,
|
|
viewlayer_name=item.viewlayer_name,
|
|
socket_name=item.socket_name,
|
|
)
|
|
|
|
def update_template_multilayer_name(self, context):
|
|
for item in context.window_manager.rt_socket_collection:
|
|
item.name_from_template = fn.build_path_from_template(
|
|
self.template_multilayer_name,
|
|
node_name=item.node_name,
|
|
node_label=item.node_label,
|
|
scene_name=item.scene_name,
|
|
viewlayer_name=item.viewlayer_name,
|
|
socket_name=item.socket_name,
|
|
)
|
|
|
|
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"}
|
|
|
|
## ! collection prop -> Now stored on window manager at invoke
|
|
# socket_collection : CollectionProperty(type=RT_PG_selectable_prop)
|
|
|
|
show_custom_settings : BoolProperty(
|
|
name='Settings',
|
|
default=False)
|
|
|
|
split_tech_passes : BoolProperty(
|
|
name='Separate Tech Passes',
|
|
default=True,
|
|
description='Create a separate file output for technical passes (32 bit)')
|
|
|
|
## enum choice for naming: socket_name, node_name, node_and_socket_name,
|
|
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='socket_name',
|
|
items=(
|
|
('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),
|
|
# ('template', 'From Template', 'Use custom template for naming', 4), # Disable slot template for now
|
|
)
|
|
)
|
|
|
|
base_path : StringProperty(
|
|
name='Custom base path',
|
|
default='',
|
|
description='Set the base path of created file_output (not if already exists)')
|
|
|
|
file_format : EnumProperty(
|
|
name='Output Format',
|
|
default='NONE',
|
|
items=(
|
|
('NONE', 'Default', 'Use default settings'),
|
|
('OPEN_EXR_MULTILAYER', 'OpenEXR MultiLayer', 'Output image in multilayer OpenEXR format'),
|
|
('OPEN_EXR', 'OpenEXR', 'Output image in OpenEXR format'),
|
|
)
|
|
)
|
|
|
|
exr_codec : EnumProperty(
|
|
name='Codec',
|
|
default='DWAB',
|
|
description='Codec settings for OpenEXR',
|
|
items=(
|
|
('PIZ', 'PIZ (lossless)', ''),
|
|
('ZIP', 'ZIP (lossless)', ''),
|
|
('RLE', 'RLE (lossless)', ''),
|
|
('ZIPS', 'ZIPS (lossless)', ''),
|
|
('PXR24', 'Pxr24 (lossy)', ''),
|
|
('B44', 'B44 (lossy)', ''),
|
|
('B44A', 'B44A (lossy)', ''),
|
|
('DWAA', 'DWAA (lossy)', ''),
|
|
('DWAB', 'DWAB (lossy)', ''),
|
|
),
|
|
)
|
|
|
|
tech_exr_codec : EnumProperty(
|
|
name='Tech Passes Codec',
|
|
default='ZIP',
|
|
description='Codec settings for Techpasses OpenEXR (passes are set to 32bit)',
|
|
items=(
|
|
('PIZ', 'PIZ (lossless)', ''),
|
|
('ZIP', 'ZIP (lossless)', ''),
|
|
('RLE', 'RLE (lossless)', ''),
|
|
('ZIPS', 'ZIPS (lossless)', ''),
|
|
# ('PXR24', 'Pxr24 (lossy)', ''),
|
|
# ('B44', 'B44 (lossy)', ''),
|
|
# ('B44A', 'B44A (lossy)', ''),
|
|
# ('DWAA', 'DWAA (lossy)', ''),
|
|
# ('DWAB', 'DWAB (lossy)', ''),
|
|
),
|
|
)
|
|
|
|
color_depth : EnumProperty(
|
|
name='Color Depth',
|
|
default='16',
|
|
description='Bit depth per channel',
|
|
items=(
|
|
# ('8', '8', '8-bit color channels'),
|
|
# ('10', '10', '10-bit color channels'),
|
|
# ('12', '12', '12-bit color channels'),
|
|
('16', '16 (Half)', '16-bit color channels'),
|
|
('32', '32 (Full)', '32-bit color channels'),
|
|
),
|
|
)
|
|
|
|
# --- templates
|
|
|
|
use_base_path_templates : BoolProperty(
|
|
name='Use Base Path Template',
|
|
default=True,
|
|
description='Use template strings for base path formatting')
|
|
|
|
## -- base path
|
|
template_base_path : StringProperty(
|
|
name="Base Path Template",
|
|
description="Template for file output base path\
|
|
\nFolder containing subsequent filepath",
|
|
default="//render/{node_name}/",
|
|
# options={'SKIP_SAVE'}
|
|
)
|
|
|
|
## Multilayer base path : output on file instead of folder
|
|
template_multilayer_base_path : StringProperty(
|
|
name="Base Path Template",
|
|
description="Template for multilayer file output base paths\
|
|
\nOutput of the file sequence",
|
|
default="//render/{node_name}/{node_name}_",
|
|
# options={'SKIP_SAVE'}
|
|
)
|
|
|
|
|
|
## -- slots/layer_names (UNUSED for now)
|
|
template_file_slot : StringProperty(
|
|
name="File Slot Template",
|
|
description="Template for file output file slots\
|
|
\nSubpath added to base path for file sequence",
|
|
default="{socket_name}/{socket_name}_",
|
|
# update=update_template_file_slot,
|
|
)
|
|
|
|
## Multilayer's layer: layer name in EXR instead of subpath to file
|
|
template_multilayer_name : StringProperty(
|
|
name="Layer Name Template",
|
|
description="Template for multilayer file output layer names\
|
|
\nLayer name in EXR multilayer file",
|
|
default="{socket_name}",
|
|
# update=update_template_multilayer_name,
|
|
)
|
|
|
|
## extra suffix passes (unused)
|
|
# template_tech_pass_name : StringProperty(
|
|
# name="Tech Pass Name Template",
|
|
# description="Template for tech pass file output layer names\
|
|
# \nLayer name in EXR multilayer file",
|
|
# default="tech",
|
|
# )
|
|
|
|
# template_crypto_name : StringProperty(
|
|
# name="Crypto Pass Name Template",
|
|
# description="Template for crypto pass file output layer names\
|
|
# \nLayer name in EXR multilayer file",
|
|
# default="crypto",
|
|
# )
|
|
|
|
|
|
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
|
|
self.base_path = ''
|
|
return self.execute(context)
|
|
|
|
## Store variable usable in templates
|
|
# self.blend_name = bpy.data.filepath
|
|
# self.version = ''
|
|
# if self.blend_name:
|
|
# self.blend_name = Path(self.blend_name).stem
|
|
# self.version = fn.get_rightmost_number_in_string(self.blend_name)
|
|
|
|
## tech_passes
|
|
prefs = fn.get_addon_prefs()
|
|
tech_passes_names = prefs.tech_passes_names
|
|
if prefs.use_env_technical_passes:
|
|
tech_passes_names = os.getenv('RENDERTOOLBOX_TECH_PASSES', prefs.tech_passes_names)
|
|
|
|
tech_passes_names = [tp.strip().lower() for tp in tech_passes_names.split(',') if tp.strip()]
|
|
|
|
selected = [n for n in context.scene.node_tree.nodes if n.select and n.type != 'OUTPUT_FILE']
|
|
if not selected:
|
|
self.report({'ERROR'}, 'No render layer nodes selected')
|
|
return {'CANCELLED'}
|
|
for n in selected:
|
|
for o in n.outputs:
|
|
if o.is_unavailable:
|
|
continue
|
|
item = self.socket_collection.add()
|
|
item.node_name = n.name
|
|
item.node_label = n.label.strip()
|
|
item.socket_name = o.name
|
|
|
|
## Set editable names
|
|
item.name = o.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
|
|
scene_node_name = node_name # same by default
|
|
|
|
if n.type == 'R_LAYERS':
|
|
scene_node_name = f'{n.scene.name}_{n.layer}'
|
|
item.scene_name = n.scene.name
|
|
item.viewlayer_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}'
|
|
|
|
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:
|
|
item.is_linked = True
|
|
item.select = False
|
|
|
|
if item.socket_name.lower() in tech_passes_names:
|
|
item.is_tech_pass = True
|
|
|
|
|
|
## Assign default template values (Disabled for now)
|
|
## TRIGGER update to firstly fill template slot/names
|
|
# self.template_file_slot = context.scene.render_toolbox.default_file_slot
|
|
# self.template_multilayer_name = context.scene.render_toolbox.default_multilayer_name
|
|
|
|
## Replace self base path
|
|
# self.template_base_path = context.scene.render_toolbox.default_base_path
|
|
# self.template_multilayer_base_path = context.scene.render_toolbox.default_multilayer_base_path
|
|
|
|
## For template preview display, Store some infos
|
|
ref = selected[0]
|
|
self.node_name = ref.name.strip()
|
|
self.node_label = ref.label.strip()
|
|
self.scene_name = ''
|
|
self.view_layer_name = ''
|
|
if ref.type == 'R_LAYERS':
|
|
self.scene_name = ref.scene.name
|
|
self.view_layer_name = ref.layer
|
|
self.final_base_path_template = ''
|
|
return context.window_manager.invoke_props_dialog(self, width=530)
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
box = layout.box()
|
|
expand_icon = 'DISCLOSURE_TRI_DOWN' if self.show_custom_settings else 'DISCLOSURE_TRI_RIGHT'
|
|
box.prop(self, 'show_custom_settings', emboss=False, icon=expand_icon)
|
|
|
|
## Settings
|
|
if self.show_custom_settings:
|
|
box.use_property_split = True
|
|
|
|
# Template settings
|
|
col = box.column()
|
|
row = col.row(align=True)
|
|
row.prop(self, 'use_base_path_templates')
|
|
|
|
if self.use_base_path_templates:
|
|
row.operator("rt.reset_path_templates", text='', icon='FILE_REFRESH')
|
|
row.separator()
|
|
|
|
row.operator("rt.info_note", text='', icon='INFO').text = """Format the base path using templates.
|
|
Possible variables:
|
|
{node_name} : name of the node
|
|
{node_label} : label of the node, fallback to node_name when nothing
|
|
{scene_name} : name of the scene from render layer node
|
|
{viewlayer_name} : name of the viewlayer from render layer node
|
|
{blend_name} : stem of the blend
|
|
{blend_version} : version (rightmost number in blend name)
|
|
{date} : (YYYYMMDD format)
|
|
{time} : (HHMMSS format)""" # {socket_name} : name of the socket (only availabe in slots)
|
|
|
|
final_format = self.file_format
|
|
if final_format == 'NONE':
|
|
final_format = os.environ.get('FILE_FORMAT', 'OPEN_EXR_MULTILAYER')
|
|
|
|
if self.use_base_path_templates:
|
|
|
|
if final_format == 'OPEN_EXR_MULTILAYER':
|
|
## self local template
|
|
# box.prop(self, 'template_multilayer_base_path')
|
|
# self.final_base_path_template = self.template_multilayer_base_path
|
|
|
|
## scene prop
|
|
box.prop(context.scene.render_toolbox, 'default_multilayer_base_path')
|
|
self.final_base_path_template = context.scene.render_toolbox.default_multilayer_base_path
|
|
else:
|
|
## self local template
|
|
# box.prop(self, 'template_base_path')
|
|
# self.final_base_path_template = self.template_base_path
|
|
|
|
## scene prop
|
|
box.prop(context.scene.render_toolbox, 'default_base_path')
|
|
self.final_base_path_template = context.scene.render_toolbox.default_base_path
|
|
## display the applied template
|
|
box.label(text=fn.build_path_from_template(self.final_base_path_template,
|
|
node_name = self.node_name,
|
|
node_label=self.node_label,
|
|
scene_name=self.scene_name,
|
|
viewlayer_name=self.view_layer_name,
|
|
),
|
|
icon='INFO'
|
|
)
|
|
|
|
else:
|
|
box.prop(self, 'base_path')
|
|
self.final_base_path_template = None
|
|
|
|
if self.name_type == 'template':
|
|
row_single = box.row()
|
|
row_single.prop(self, 'template_file_slot')
|
|
row_single.active = final_format != 'OPEN_EXR_MULTILAYER'
|
|
row_multi = box.row()
|
|
row_multi.prop(self, 'template_multilayer_name')
|
|
row_multi.active = final_format == 'OPEN_EXR_MULTILAYER'
|
|
|
|
box.row().prop(self, 'name_type', expand=False)
|
|
col = box.column()
|
|
col.prop(self, 'file_format')
|
|
col.prop(self, 'exr_codec')
|
|
col.row().prop(self, 'color_depth', expand=True)
|
|
col.prop(self, 'split_tech_passes')
|
|
|
|
row = col.row()
|
|
row.prop(self, 'tech_exr_codec', text='Tech Passes Codec')
|
|
row.active = self.split_tech_passes
|
|
# if self.split_tech_passes:
|
|
# col.prop(self, 'tech_exr_codec', text='Tech Passes Codec')
|
|
|
|
search_row = layout.row()
|
|
op = search_row.operator("rt.colprop_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'
|
|
elif self.name_type == 'template':
|
|
op.target_prop = 'name_from_template'
|
|
|
|
## Node Sockets
|
|
layout.use_property_split = False
|
|
col = layout.column()
|
|
current_node_name = ''
|
|
for item in self.socket_collection:
|
|
if item.node_name != current_node_name:
|
|
current_node_name = item.node_name
|
|
col.separator()
|
|
## Display node label + name or name only
|
|
if item.node_label:
|
|
display_node_name = f'{item.node_label} ({item.node_name})'
|
|
else:
|
|
display_node_name = item.node_name
|
|
## A bit dirty: For render layer node, show render layer-node name.
|
|
if display_node_name.startswith('Render Layer'):
|
|
rln = context.scene.node_tree.nodes.get(item.node_name)
|
|
display_node_name = f'{rln.scene.name}/{rln.layer} ({display_node_name})'
|
|
col.label(text=display_node_name, icon='NODE_SEL')
|
|
|
|
row = col.row()
|
|
row.active = item.select
|
|
if item.is_linked:
|
|
row.label(text='', icon='LINKED') # NODETREE
|
|
else:
|
|
row.label(text='', icon='BLANK1')
|
|
row.prop(item, 'select', text='')
|
|
|
|
display_name = item.socket_name
|
|
if 'crypto' in display_name.lower():
|
|
display_name = f'{display_name} -> 32bit output node'
|
|
elif self.split_tech_passes and item.is_tech_pass:
|
|
display_name = f'{display_name} -> Tech pass'
|
|
|
|
row.label(text=display_name)
|
|
row.label(text='', icon='RIGHTARROW')
|
|
|
|
if self.name_type == 'socket_name':
|
|
row.prop(item, 'name', text='')
|
|
elif self.name_type == 'node_name':
|
|
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='')
|
|
elif self.name_type == 'template':
|
|
row.prop(item, 'name_from_template', text='')
|
|
|
|
if self.split_tech_passes:
|
|
row.prop(item, 'is_tech_pass', text='')
|
|
|
|
def execute(self, context):
|
|
|
|
# Build exclude dict from selection
|
|
excludes = {}
|
|
remap_names = {}
|
|
## Old system
|
|
# if len(self.socket_collection):
|
|
# for item in self.socket_collection:
|
|
# if not item.select:
|
|
# # All deselected goes to exclude with {node_name: [socket1, ...]}
|
|
# excludes.setdefault(item.node_name, []).append(item.name)
|
|
# elif item.socket_name != item.name:
|
|
# remap_names[item.socket_name] = item.name
|
|
|
|
tech_pass_names = []
|
|
|
|
if len(self.socket_collection):
|
|
for item in self.socket_collection:
|
|
final_name = item.name
|
|
## change name if other options were used
|
|
if self.name_type == 'node_name':
|
|
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
|
|
elif self.name_type == 'name_from_template':
|
|
final_name = item.name_from_template
|
|
|
|
if not item.select:
|
|
# All deselected goes to exclude with {node_name: [socket1, ...]}
|
|
excludes.setdefault(item.node_name, []).append(item.socket_name)
|
|
elif item.socket_name != final_name:
|
|
remap_names.setdefault(item.node_name, {})[item.socket_name] = final_name
|
|
# remap_names[item.socket_name] = final_name
|
|
|
|
if self.split_tech_passes and item.is_tech_pass:
|
|
tech_pass_names.append(item.socket_name.lower())
|
|
|
|
## Handle default file format
|
|
file_ext = self.file_format
|
|
if self.file_format == 'NONE':
|
|
env_file_format = os.environ.get('FILE_FORMAT')
|
|
file_ext = env_file_format if env_file_format else 'OPEN_EXR_MULTILAYER'
|
|
|
|
file_format = {
|
|
'file_format' : file_ext,
|
|
'exr_codec' : self.exr_codec,
|
|
'color_depth' : self.color_depth,
|
|
}
|
|
|
|
tech_file_format = {
|
|
## Force the use of OpenEXR for tech passes if normal passes uses PNG or the likes
|
|
'file_format' : file_ext if file_ext in ('OPEN_EXR_MULTILAYER', 'OPEN_EXR') else 'OPEN_EXR',
|
|
'exr_codec' : self.tech_exr_codec,
|
|
'color_depth' : '32',
|
|
}
|
|
|
|
scn = context.scene
|
|
nodes = scn.node_tree.nodes
|
|
selected = [n for n in nodes if n.select]
|
|
outfile = next((n for n in selected if n.type == 'OUTPUT_FILE'), None)
|
|
# Exclude output file from
|
|
selected = [n for n in selected if n.type != 'OUTPUT_FILE']
|
|
|
|
# fn.connect_to_file_output(selected, outfile)
|
|
for n in selected:
|
|
fn.connect_to_file_output(
|
|
n,
|
|
outfile,
|
|
base_path=self.base_path,
|
|
excludes=excludes,
|
|
remap_names=remap_names,
|
|
file_format=file_format,
|
|
tech_pass_names=tech_pass_names,
|
|
tech_file_format=tech_file_format,
|
|
template=self.final_base_path_template)
|
|
|
|
return {"FINISHED"}
|
|
|
|
# endregion
|
|
|
|
classes=(
|
|
RT_OT_colprop_search_and_replace,
|
|
RT_PG_selectable_prop,
|
|
RT_OT_create_output_layers,
|
|
)
|
|
|
|
def register():
|
|
for cls in classes:
|
|
bpy.utils.register_class(cls)
|
|
|
|
def unregister():
|
|
for cls in reversed(classes):
|
|
bpy.utils.unregister_class(cls) |