add template in create output path base node

templates for slot are in code but disabled, not useful in the end
This commit is contained in:
pullusb
2025-07-21 16:24:27 +02:00
parent 6ad75a664f
commit 37a9e0ca43
6 changed files with 506 additions and 29 deletions
+134 -12
View File
@@ -4,6 +4,7 @@ import re
import json
from .constant import TECH_PASS_KEYWORDS
from pathlib import Path
# region Manage nodes
@@ -62,9 +63,11 @@ def recursive_node_connect_check(l, target_node):
return False
# region create and connect file output
def create_and_connect_file_output(node, outputs, file_out, out_name, base_path, out_base,
scene, remap_names, file_format, suffix='', color_depth=None,
location_offset=(500, 50)):
location_offset=(500, 50), base_path_template=None, use_slot_template=False):
"""Helper function to create file output node and connect it to outputs
Args:
@@ -112,13 +115,31 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path,
if node.parent:
fo.parent = node.parent
if base_path:
fo.base_path = base_path
if base_path_template is not None:
fo.base_path = build_path_from_template(base_path_template,
node_name=node.name.strip(),
node_label=node.label.strip(),
scene_name=node.scene.name if node.type == 'R_LAYERS' else None,
viewlayer_name=node.layer if node.type == 'R_LAYERS' else None,
)
if suffix:
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
## insert suffix right after last slash as folder
path_list = re.split('(\\/)', fo.base_path)
path_list.insert(-1, suffix)
fo.base_path = ''.join(path_list)
else:
## Ensure slash separator then add suffix
fo.base_path = fo.base_path.rstrip('\\/') + '/' + suffix
else:
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
fo.base_path = f'//render/{out_base}/{suffix}{out_base}_'
# default behavior without template
if base_path:
fo.base_path = base_path
else:
fo.base_path = f'//render/{out_base}/{suffix}'
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
fo.base_path = f'//render/{out_base}/{suffix}{out_base}_'
else:
fo.base_path = f'//render/{out_base}/{suffix}'
for o in outputs:
if next((l for l in o.links if recursive_node_connect_check(l, fo)), None):
@@ -134,9 +155,11 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path,
ls = fo.layer_slots[-1]
ls.name = slot_name
fs = fo.file_slots[-1]
fs.path = f'{slot_name}/{slot_name}_'
if use_slot_template:
## Always False (currently never use slot template)
fs.path = slot_name
out_input = fo.inputs[-1]
links.new(o, out_input)
@@ -147,7 +170,7 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path,
def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None, remap_names=None,
file_format=None, split_tech_passes=False):
file_format=None, split_tech_passes=False, template=None):
"""Connect selected nodes output to file output(s)
if a file output is selected, add intputs on it
@@ -234,7 +257,8 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
fo_regular = create_and_connect_file_output(
node, regular_outputs, file_out, out_name, base_path,
out_base, scene, remap_names, file_format,
location_offset=(500, y_offset)
location_offset=(500, y_offset),
base_path_template=template
)
if fo_regular and fo_regular not in created_nodes:
created_nodes.append(fo_regular)
@@ -249,7 +273,8 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
node, tech_outputs, None, out_name, base_path,
out_base, scene, remap_names, tech_file_format,
suffix='tech/', color_depth='32',
location_offset=(500, y_offset)
location_offset=(500, y_offset),
base_path_template=template
)
if fo_tech and fo_tech not in created_nodes:
@@ -266,17 +291,19 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
node, crypto_outputs, None, out_name, base_path,
out_base, scene, remap_names, tech_file_format,
suffix='cryptos/', color_depth='32',
location_offset=(500, y_offset)
location_offset=(500, y_offset),
base_path_template=template
)
if fo_crypto and fo_crypto not in created_nodes:
created_nodes.append(fo_crypto)
return created_nodes
# endregion
# endregion
# region Utilities
@@ -365,3 +392,98 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
if isinstance(_message, str):
_message = [_message]
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon)
def get_rightmost_number_in_string(string) -> str:
"""Get the rightmost number passed string
return the number as string, empty string if no number found
"""
res = re.search(r'(\d+)(?!.*\d)', string)
if not res:
return ''
return res.group(1)
def build_path_from_template(template: str,
node_name: str=None,
node_label: str=None,
scene_name: str=None,
viewlayer_name: str=None,
socket_name: str=None,
socket_index: str=None,
) -> str:
"""Get a name from a template string
Possible keyword:
{node_name} : name of the node
{node_label} : label of the node (fallback to node_name if not set)
{scene_name} : name of the source render layer node scene
{viewlayer_name} : name of the source render layer viewlayer
{socket_name} : name of source the node socket
{socket_index} : index of the node in the node tree (010, 020, etc.)
## Info from Blend
{blend_name} : name of the blend file
{blend_version} : version of the blend file
{date} : current date (YYYYMMDD format)
{time} : current time (HHMMSS format)
"""
## note: socket index is a bad idea because the final order is not decided before execute
if not template:
return ''
# Get blend file info
blend_path = bpy.data.filepath
blend_name = ''
blend_version = ''
if blend_path:
blend_name = Path(blend_path).stem
blend_version = get_rightmost_number_in_string(blend_name)
# Get current date and time
import datetime
now = datetime.datetime.now()
current_date = now.strftime("%Y%m%d")
current_time = now.strftime("%H%M%S")
# Handle node_label to name fallback
if "{node_label}" in template:
if node_label is None or not node_label:
node_label = node_name
if socket_index:
# Ensure socket_index is formatted as a zero-padded string and increment on base 10 (allow to insert inbetween renders)
socket_index = f"{int(socket_index)*10:03d}"
def clean_name(name):
# Clean names to be file-system safe
if name is None:
return ''
return bpy.path.clean_name(name)
# Create a dictionary of all available variables
template_vars = {
'node_name': clean_name(node_name),
'node_label': clean_name(node_label),
'socket_name': clean_name(socket_name),
'scene_name': clean_name(scene_name),
'viewlayer_name': clean_name(viewlayer_name),
'socket_index': socket_index or '',
'blend_name': clean_name(blend_name),
'blend_version': blend_version,
'date': current_date,
'time': current_time,
}
# Apply template using format_map to handle missing keys gracefully
try:
applied_template = template.format_map(template_vars)
except KeyError as e:
# If a key is missing, return template with error message
# print(f"Template error: Unknown variable {e}")
return f"Template error: Unknown {e}"
return applied_template
# endregion