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:
parent
6ad75a664f
commit
37a9e0ca43
@ -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,
|
||||
|
146
fn.py
146
fn.py
@ -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
|
@ -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"}
|
||||
|
||||
|
@ -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
86
properties.py
Normal 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
49
ui.py
@ -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,18 +14,59 @@ 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")
|
||||
|
||||
layout.operator("rt.list_modifier_visibility", text="List Modifiers Visibility Conflicts", icon="MODIFIER")
|
||||
|
||||
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():
|
||||
|
Loading…
x
Reference in New Issue
Block a user