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
+199 -11
View File
@@ -1,6 +1,9 @@
import bpy
import os
import re
from pathlib import Path
from .. import fn
from bpy.props import (StringProperty,
BoolProperty,
@@ -120,11 +123,43 @@ class RT_PG_selectable_prop(bpy.types.PropertyGroup):
select: BoolProperty(name="Selected", default=True)
is_linked: BoolProperty(name="Linked", 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"
@@ -150,25 +185,21 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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',
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
)
)
# 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 : 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',
@@ -209,6 +240,68 @@ class RT_OT_create_output_layers(bpy.types.Operator):
),
)
# --- 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,
# options={'SKIP_SAVE'}
)
## 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,
# options={'SKIP_SAVE'}
)
## 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
@@ -222,6 +315,13 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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)
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')
@@ -244,6 +344,8 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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
@@ -256,7 +358,7 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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}')
# 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}'
@@ -265,6 +367,25 @@ class RT_OT_create_output_layers(bpy.types.Operator):
item.is_linked = True
item.select = False
## 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=500)
def draw(self, context):
@@ -276,8 +397,68 @@ class RT_OT_create_output_layers(bpy.types.Operator):
## 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')
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'
## Todo: Add rt.info_note to explain templates
box.row().prop(self, 'name_type', expand=False)
box.prop(self, 'base_path')
col = box.column()
col.prop(self, 'file_format')
col.prop(self, 'exr_codec')
@@ -295,6 +476,8 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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
@@ -339,6 +522,8 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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='')
def execute(self, context):
@@ -364,7 +549,9 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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)
@@ -400,7 +587,8 @@ class RT_OT_create_output_layers(bpy.types.Operator):
excludes=excludes,
remap_names=remap_names,
file_format=file_format,
split_tech_passes=self.split_tech_passes)
split_tech_passes=self.split_tech_passes,
template=self.final_base_path_template)
return {"FINISHED"}
+39 -1
View File
@@ -1,5 +1,6 @@
import bpy
from bpy.types import Operator
from bpy.props import (BoolProperty,
EnumProperty,
PointerProperty,
@@ -8,7 +9,7 @@ from bpy.props import (BoolProperty,
from .. import fn
class RT_OT_select_object_by_name(bpy.types.Operator):
class RT_OT_select_object_by_name(Operator):
bl_idname = "rt.select_object_by_name"
bl_label = "Select Object By name"
bl_description = "Select object and modifier"
@@ -41,6 +42,7 @@ class RT_OT_select_object_by_name(bpy.types.Operator):
return {'CANCELLED'}
# Make it the active object
## function to make active and selected
# fn.show_and_active_object(context, target_object, make_active=True, )
context.view_layer.objects.active = target_object
@@ -64,8 +66,44 @@ class RT_OT_select_object_by_name(bpy.types.Operator):
return {'FINISHED'}
class RT_OT_info_note(Operator):
bl_idname = "rt.info_note"
bl_label = "Info Note"
bl_description = "Info Note"
bl_options = {"REGISTER", "INTERNAL"}
text : bpy.props.StringProperty(default='', options={'SKIP_SAVE'})
title : bpy.props.StringProperty(default='Help', options={'SKIP_SAVE'})
icon : bpy.props.StringProperty(default='INFO', options={'SKIP_SAVE'})
@classmethod
def description(self, context, properties):
return properties.text
def execute(self, context):
## Split text in list of lines to display
lines = self.text.split('\n')
fn.show_message_box(_message=lines, _title=self.title, _icon=self.icon)
return {"FINISHED"}
# Not registered yet
class RT_set_env_settings(bpy.types.Operator):
"""manually reset environnement settings"""
bl_idname = "rt.set_env_settings"
bl_label = "Reset Templates From Env"
bl_description = "Reset Render Toolbox templates from environment variables"
bl_options = {"REGISTER", "UNDO", "INTERNAL"}
# mode : bpy.props.StringProperty(default='ALL', options={'SKIP_SAVE'}) # 'HIDDEN',
def execute(self, context):
from ..properties import set_env_properties
set_env_properties()
return {'FINISHED'}
classes = (
RT_OT_select_object_by_name,
RT_OT_info_note
)