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

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, 4, 0), "version": (0, 5, 0),
"blender": (4, 0, 0), "blender": (4, 0, 0),
"location": "View3D", "location": "View3D",
"warning": "", "warning": "",
@ -11,10 +11,12 @@ bl_info = {
"category": "Render" "category": "Render"
} }
from . import properties
from . import operators from . import operators
from . import ui from . import ui
modules = ( modules = (
properties,
operators, operators,
ui, ui,
# prefs, # prefs,

146
fn.py
View File

@ -4,6 +4,7 @@ import re
import json import json
from .constant import TECH_PASS_KEYWORDS from .constant import TECH_PASS_KEYWORDS
from pathlib import Path
# region Manage nodes # region Manage nodes
@ -62,9 +63,11 @@ def recursive_node_connect_check(l, target_node):
return False return False
# region create and connect file output
def create_and_connect_file_output(node, outputs, file_out, out_name, base_path, out_base, 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, 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 """Helper function to create file output node and connect it to outputs
Args: Args:
@ -112,13 +115,31 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path,
if node.parent: if node.parent:
fo.parent = node.parent fo.parent = node.parent
if base_path: if base_path_template is not None:
fo.base_path = base_path 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: else:
if fo.format.file_format == 'OPEN_EXR_MULTILAYER': # default behavior without template
fo.base_path = f'//render/{out_base}/{suffix}{out_base}_' if base_path:
fo.base_path = base_path
else: 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: for o in outputs:
if next((l for l in o.links if recursive_node_connect_check(l, fo)), None): 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 = fo.layer_slots[-1]
ls.name = slot_name ls.name = slot_name
fs = fo.file_slots[-1] fs = fo.file_slots[-1]
fs.path = f'{slot_name}/{slot_name}_' 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] out_input = fo.inputs[-1]
links.new(o, out_input) 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, 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) """Connect selected nodes output to file output(s)
if a file output is selected, add intputs on it 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( fo_regular = create_and_connect_file_output(
node, regular_outputs, file_out, out_name, base_path, node, regular_outputs, file_out, out_name, base_path,
out_base, scene, remap_names, file_format, 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: if fo_regular and fo_regular not in created_nodes:
created_nodes.append(fo_regular) 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, node, tech_outputs, None, out_name, base_path,
out_base, scene, remap_names, tech_file_format, out_base, scene, remap_names, tech_file_format,
suffix='tech/', color_depth='32', 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: 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, node, crypto_outputs, None, out_name, base_path,
out_base, scene, remap_names, tech_file_format, out_base, scene, remap_names, tech_file_format,
suffix='cryptos/', color_depth='32', 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: if fo_crypto and fo_crypto not in created_nodes:
created_nodes.append(fo_crypto) created_nodes.append(fo_crypto)
return created_nodes return created_nodes
# endregion # endregion
# endregion
# region Utilities # region Utilities
@ -365,3 +392,98 @@ def show_message_box(_message = "", _title = "Message Box", _icon = 'INFO'):
if isinstance(_message, str): if isinstance(_message, str):
_message = [_message] _message = [_message]
bpy.context.window_manager.popup_menu(draw, title = _title, icon = _icon) 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

View File

@ -1,6 +1,9 @@
import bpy import bpy
import os import os
import re import re
from pathlib import Path
from .. import fn from .. import fn
from bpy.props import (StringProperty, from bpy.props import (StringProperty,
BoolProperty, BoolProperty,
@ -121,10 +124,42 @@ class RT_PG_selectable_prop(bpy.types.PropertyGroup):
is_linked: BoolProperty(name="Linked", default=False) is_linked: BoolProperty(name="Linked", default=False)
# is_valid: BoolProperty(name="Valid", default=True) # 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 ## extra output naming options
name_from_node: StringProperty(name="Name From Node") 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: 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_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): class RT_OT_create_output_layers(bpy.types.Operator):
bl_idname = "rt.create_output_layers" bl_idname = "rt.create_output_layers"
@ -150,20 +185,16 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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='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),
('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_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), ('socket_name', 'Socket Name', 'Use the socket name as output name', 2),
('node_name', 'Node Name', 'Use the node name as output name', 3), ('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( base_path : StringProperty(
name='Custom base path', name='Custom base path',
default='', default='',
@ -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): def invoke(self, context, event):
if not hasattr(context.window_manager, 'rt_socket_collection'): if not hasattr(context.window_manager, 'rt_socket_collection'):
## Create collection prop on window manager if not existing ## 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 = '' self.base_path = ''
return self.execute(context) 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'] selected = [n for n in context.scene.node_tree.nodes if n.select and n.type != 'OUTPUT_FILE']
if not selected: if not selected:
self.report({'ERROR'}, 'No render layer nodes 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': if n.type == 'R_LAYERS':
scene_node_name = f'{n.scene.name}_{n.layer}' 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 ## 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
@ -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 = node_name
item.name_from_node_and_socket_with_scene = scene_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}') # 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}' 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.is_linked = True
item.select = False 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) return context.window_manager.invoke_props_dialog(self, width=500)
def draw(self, context): def draw(self, context):
@ -276,8 +397,68 @@ class RT_OT_create_output_layers(bpy.types.Operator):
## Settings ## Settings
if self.show_custom_settings: if self.show_custom_settings:
box.use_property_split = True 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.row().prop(self, 'name_type', expand=False)
box.prop(self, 'base_path')
col = box.column() col = box.column()
col.prop(self, 'file_format') col.prop(self, 'file_format')
col.prop(self, 'exr_codec') 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' op.target_prop = 'name_from_node_and_socket'
elif self.name_type == 'node_and_socket_name_with_scene': elif self.name_type == 'node_and_socket_name_with_scene':
op.target_prop = 'name_from_node_and_socket_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 ## Node Sockets
layout.use_property_split = False 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='') row.prop(item, 'name_from_node_and_socket', text='')
elif self.name_type == 'node_and_socket_name_with_scene': elif self.name_type == 'node_and_socket_name_with_scene':
row.prop(item, 'name_from_node_and_socket_with_scene', text='') 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): def execute(self, context):
@ -364,6 +549,8 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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': elif self.name_type == 'node_and_socket_name_with_scene':
final_name = item.name_from_node_and_socket_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: if not item.select:
# All deselected goes to exclude with {node_name: [socket1, ...]} # All deselected goes to exclude with {node_name: [socket1, ...]}
@ -400,7 +587,8 @@ class RT_OT_create_output_layers(bpy.types.Operator):
excludes=excludes, excludes=excludes,
remap_names=remap_names, remap_names=remap_names,
file_format=file_format, 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"} return {"FINISHED"}

View File

@ -1,5 +1,6 @@
import bpy import bpy
from bpy.types import Operator
from bpy.props import (BoolProperty, from bpy.props import (BoolProperty,
EnumProperty, EnumProperty,
PointerProperty, PointerProperty,
@ -8,7 +9,7 @@ from bpy.props import (BoolProperty,
from .. import fn 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_idname = "rt.select_object_by_name"
bl_label = "Select Object By name" bl_label = "Select Object By name"
bl_description = "Select object and modifier" bl_description = "Select object and modifier"
@ -41,6 +42,7 @@ class RT_OT_select_object_by_name(bpy.types.Operator):
return {'CANCELLED'} return {'CANCELLED'}
# Make it the active object # Make it the active object
## function to make active and selected
# fn.show_and_active_object(context, target_object, make_active=True, ) # fn.show_and_active_object(context, target_object, make_active=True, )
context.view_layer.objects.active = target_object context.view_layer.objects.active = target_object
@ -64,8 +66,44 @@ class RT_OT_select_object_by_name(bpy.types.Operator):
return {'FINISHED'} 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 = ( classes = (
RT_OT_select_object_by_name, RT_OT_select_object_by_name,
RT_OT_info_note
) )

86
properties.py Normal file
View File

@ -0,0 +1,86 @@
import bpy
import os
from bpy.types import PropertyGroup
from bpy.props import StringProperty
class RT_PG_render_toolbox_props(PropertyGroup):
default_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
default_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'}
)
### file slots (not used currently)
default_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}_",
# options={'SKIP_SAVE'}
)
## Multilayer's layer: layer name in EXR instead of subpath to file
default_multilayer_name : StringProperty(
name="Layer Name Template",
description="Template for multilayer file output layer names\
\nLayer name in EXR multilayer file",
default="{socket_name}",
# options={'SKIP_SAVE'}
)
# default_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",
# # options={'SKIP_SAVE'}
# )
# default_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",
# # options={'SKIP_SAVE'}
# )
def set_env_properties():
## set default template from environment variable if available
exr_base_path_template = os.getenv('RENDERTOOLBOX_EXR_PATH_TEMPLATE')
if exr_base_path_template:
bpy.context.scene.render_toolbox.default_base_path = exr_base_path_template
multilayer_base_path_template = os.getenv('RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE')
if multilayer_base_path_template:
bpy.context.scene.render_toolbox.default_multilayer_base_path = multilayer_base_path_template
classes = (
RT_PG_render_toolbox_props,
)
def register():
for cls in classes:
bpy.utils.register_class(cls)
bpy.types.Scene.render_toolbox = bpy.props.PointerProperty(type=RT_PG_render_toolbox_props)
set_env_properties()
def unregister():
del bpy.types.Scene.render_toolbox
for cls in reversed(classes):
bpy.utils.unregister_class(cls)

49
ui.py
View File

@ -3,7 +3,7 @@ import bpy
from bpy.types import Panel from bpy.types import Panel
class RT_PT_gp_node_ui(Panel): class RT_PT_render_toolbox_ui(Panel):
bl_space_type = "NODE_EDITOR" bl_space_type = "NODE_EDITOR"
bl_region_type = "UI" bl_region_type = "UI"
bl_category = "Render" # Wrangler bl_category = "Render" # Wrangler
@ -14,8 +14,17 @@ class RT_PT_gp_node_ui(Panel):
layout.operator("rt.create_output_layers", icon="NODE") layout.operator("rt.create_output_layers", icon="NODE")
layout.operator("rt.outputs_search_and_replace", text='Search and replace outputs', icon="BORDERMOVE") layout.operator("rt.outputs_search_and_replace", text='Search and replace outputs', icon="BORDERMOVE")
layout.separator() class RT_PT_visibility_check(Panel):
layout.label(text="Visibily Checks:") bl_space_type = "NODE_EDITOR"
bl_region_type = "UI"
bl_category = "Render" # Wrangler
bl_label = "Visibility Checks"
# bl_parent_id = "RT_PT_render_toolbox_ui"
# bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
# layout.label(text="Visibily Checks:")
layout.operator("rt.list_object_visibility_conflicts", icon="OBJECT_DATAMODE") layout.operator("rt.list_object_visibility_conflicts", icon="OBJECT_DATAMODE")
layout.operator("rt.list_viewport_render_visibility", text="List Viewport Vs Render Visibility", icon="OBJECT_DATAMODE") layout.operator("rt.list_viewport_render_visibility", text="List Viewport Vs Render Visibility", icon="OBJECT_DATAMODE")
@ -23,9 +32,41 @@ class RT_PT_gp_node_ui(Panel):
layout.operator("rt.list_collection_visibility_conflicts", text="List Collections Visibility Conflicts", icon="OUTLINER_COLLECTION") layout.operator("rt.list_collection_visibility_conflicts", text="List Collections Visibility Conflicts", icon="OUTLINER_COLLECTION")
# layout.separator()
# layout.operator("rt.list_object_affected_by_simplify", text="List Object Affected By Simplify", icon="MOD_SIMPLIFY")
## Unused, only exposed in Create output panel
class RT_PT_output_template(Panel):
bl_space_type = "NODE_EDITOR"
bl_region_type = "UI"
bl_category = "Render" # Wrangler
bl_label = "File Output Templates"
bl_parent_id = "RT_PT_render_toolbox_ui"
# bl_options = {'DEFAULT_CLOSED'}
def draw(self, context):
layout = self.layout
settings = context.scene.render_toolbox
col = layout.column(align=True)
col.label(text='Single file:')
col.prop(settings, "default_base_path", text="Base Path")
col.prop(settings, "default_file_slot", text="File Slot")
col.separator()
col = layout.column(align=True)
col.label(text='Multilayers:')
col.prop(settings, "default_multilayer_base_path", text="Base Path")
col.prop(settings, "default_multilayer_name", text="Layer Name")
## Handle separate tech passes names ?
classes = ( classes = (
RT_PT_gp_node_ui, RT_PT_render_toolbox_ui,
# RT_PT_output_template,
RT_PT_visibility_check,
) )
def register(): def register():