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

136
fn.py
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,6 +115,24 @@ def create_and_connect_file_output(node, outputs, file_out, out_name, base_path,
if node.parent:
fo.parent = node.parent
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:
# default behavior without template
if base_path:
fo.base_path = base_path
else:
@ -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

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,
@ -121,10 +124,42 @@ class RT_PG_selectable_prop(bpy.types.PropertyGroup):
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,20 +185,16 @@ 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='',
@ -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
box.row().prop(self, 'name_type', expand=False)
# 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)
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,6 +549,8 @@ 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, ...]}
@ -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"}

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
)

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
class RT_PT_gp_node_ui(Panel):
class RT_PT_render_toolbox_ui(Panel):
bl_space_type = "NODE_EDITOR"
bl_region_type = "UI"
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.outputs_search_and_replace", text='Search and replace outputs', icon="BORDERMOVE")
layout.separator()
layout.label(text="Visibily Checks:")
class RT_PT_visibility_check(Panel):
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_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.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 = (
RT_PT_gp_node_ui,
RT_PT_render_toolbox_ui,
# RT_PT_output_template,
RT_PT_visibility_check,
)
def register():