add env variable and prefs for tech passes split, and allow customization ofr tech passes split in popup

This commit is contained in:
pullusb 2025-07-31 17:24:38 +02:00
parent aa4293c7d7
commit f95fe2eff7
6 changed files with 106 additions and 25 deletions

View File

@ -2,7 +2,7 @@ bl_info = {
"name": "Render Toolbox",
"description": "Setup outputs and perform cheks for rendering",
"author": "Samuel Bernou",
"version": (0, 8, 0),
"version": (1, 0, 0),
"blender": (4, 0, 0),
"location": "View3D",
"warning": "",

17
fn.py
View File

@ -7,6 +7,9 @@ from .constant import TECH_PASS_KEYWORDS
from pathlib import Path
def get_addon_prefs():
return bpy.context.preferences.addons[__package__].preferences
# region Manage nodes
def real_loc(n):
@ -208,7 +211,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, tech_file_format=None, template=None):
file_format=None, tech_pass_names=None, tech_file_format=None, template=None):
"""Connect selected nodes output to file output(s)
if a file output is selected, add intputs on it
@ -230,8 +233,12 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
remap_names (dict, optionnal): List of output names to remap {node_name: {output_name: new_name}}.
split_tech_passes (bool, optional): When True, create a separate file output for technical passes
Defaults to False.
tech_pass_names (list[str], optional): list of of socket names to set as separate file output for technical passes
Defaults to None
tech_file_format (dict, optionnal): converts each dictionary key into a file output format for tech passes
if not passed, will use file_format.
Defaults to None.
Returns:
list[bpy.types.CompositorNode]: All nodes created.
@ -278,9 +285,9 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
# Categorize outputs
crypto_outputs = [o for o in all_outputs if 'crypto' in o.name.lower()]
if split_tech_passes:
if tech_pass_names:
# Filter tech passes
tech_outputs = [o for o in all_outputs if o.name.lower() in TECH_PASS_KEYWORDS] # any(keyword in o.name.lower() for keyword in TECH_PASS_KEYWORDS)]
tech_outputs = [o for o in all_outputs if o.name.lower() in tech_pass_names] # TECH_PASS_KEYWORDS
# Regular outputs (excluding crypto and tech passes)
regular_outputs = [o for o in all_outputs if o not in crypto_outputs and o not in tech_outputs]
else:

View File

@ -282,8 +282,8 @@ class RT_OT_conform_collection_hierarchy(Operator):
for _, v in affected_items_dict.items():
for item, attr_list in v.items():
line = f'{item.name} : {", ".join(attr_list)}'
print(line)
message.append(line)
print(line)
message.append(line)
fn.show_message_box(
message=message,

View File

@ -7,9 +7,6 @@ from bpy.props import (StringProperty,
EnumProperty,
CollectionProperty)
from ..constant import TECH_PASS_KEYWORDS
class RT_OT_outputs_search_and_replace(bpy.types.Operator):
bl_idname = "rt.outputs_search_and_replace"
bl_label = "Search And Replace Outputs Paths"

View File

@ -122,6 +122,7 @@ class RT_PG_selectable_prop(bpy.types.PropertyGroup):
socket_name: StringProperty(name="Source socket Name") # Source socket name as reference
select: BoolProperty(name="Selected", default=True)
is_linked: BoolProperty(name="Linked", default=False)
is_tech_pass: BoolProperty(name="Tech Pass", default=False)
# is_valid: BoolProperty(name="Valid", default=True)
## Specific to render layer nodes
@ -337,6 +338,14 @@ class RT_OT_create_output_layers(bpy.types.Operator):
# self.blend_name = Path(self.blend_name).stem
# self.version = fn.get_rightmost_number_in_string(self.blend_name)
## tech_passes
prefs = fn.get_addon_prefs()
tech_passes_names = prefs.tech_passes_names
if prefs.use_env_technical_passes:
tech_passes_names = os.getenv('RENDERTOOLBOX_TECH_PASSES', prefs.tech_passes_names)
tech_passes_names = [tp.strip().lower() for tp in tech_passes_names.split(',') if tp.strip()]
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')
@ -381,6 +390,10 @@ class RT_OT_create_output_layers(bpy.types.Operator):
if o.is_linked:
item.is_linked = True
item.select = False
if item.socket_name.lower() in tech_passes_names:
item.is_tech_pass = True
## Assign default template values (Disabled for now)
## TRIGGER update to firstly fill template slot/names
@ -401,7 +414,7 @@ class RT_OT_create_output_layers(bpy.types.Operator):
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=530)
def draw(self, context):
layout = self.layout
@ -483,8 +496,12 @@ Possible variables:
col.prop(self, 'exr_codec')
col.row().prop(self, 'color_depth', expand=True)
col.prop(self, 'split_tech_passes')
if self.split_tech_passes:
col.prop(self, 'tech_exr_codec', text='Tech Passes Codec')
row = col.row()
row.prop(self, 'tech_exr_codec', text='Tech Passes Codec')
row.active = self.split_tech_passes
# if self.split_tech_passes:
# col.prop(self, 'tech_exr_codec', text='Tech Passes Codec')
search_row = layout.row()
op = search_row.operator("rt.colprop_search_and_replace", icon='BORDERMOVE')
@ -520,6 +537,7 @@ Possible variables:
col.label(text=display_node_name, icon='NODE_SEL')
row = col.row()
row.active = item.select
if item.is_linked:
row.label(text='', icon='LINKED') # NODETREE
else:
@ -529,7 +547,7 @@ Possible variables:
display_name = item.socket_name
if 'crypto' in display_name.lower():
display_name = f'{display_name} -> 32bit output node'
elif self.split_tech_passes and display_name.lower() in TECH_PASS_KEYWORDS:
elif self.split_tech_passes and item.is_tech_pass:
display_name = f'{display_name} -> Tech pass'
row.label(text=display_name)
@ -546,6 +564,9 @@ Possible variables:
elif self.name_type == 'template':
row.prop(item, 'name_from_template', text='')
if self.split_tech_passes:
row.prop(item, 'is_tech_pass', text='')
def execute(self, context):
# Build exclude dict from selection
@ -560,6 +581,8 @@ Possible variables:
# elif item.socket_name != item.name:
# remap_names[item.socket_name] = item.name
tech_pass_names = []
if len(self.socket_collection):
for item in self.socket_collection:
final_name = item.name
@ -580,6 +603,9 @@ Possible variables:
remap_names.setdefault(item.node_name, {})[item.socket_name] = final_name
# remap_names[item.socket_name] = final_name
if self.split_tech_passes and item.is_tech_pass:
tech_pass_names.append(item.socket_name.lower())
## Handle default file format
file_ext = self.file_format
if self.file_format == 'NONE':
@ -598,14 +624,14 @@ Possible variables:
'exr_codec' : self.tech_exr_codec,
'color_depth' : '32',
}
scn = context.scene
nodes = scn.node_tree.nodes
selected = [n for n in nodes if n.select]
outfile = next((n for n in selected if n.type == 'OUTPUT_FILE'), None)
# Exclude output file from
selected = [n for n in selected if n.type != 'OUTPUT_FILE']
# fn.connect_to_file_output(selected, outfile)
for n in selected:
fn.connect_to_file_output(
@ -615,7 +641,7 @@ Possible variables:
excludes=excludes,
remap_names=remap_names,
file_format=file_format,
split_tech_passes=self.split_tech_passes,
tech_pass_names=tech_pass_names,
tech_file_format=tech_file_format,
template=self.final_base_path_template)

View File

@ -18,11 +18,12 @@ from bpy.app.handlers import persistent
class RT_prefs(bpy.types.AddonPreferences):
bl_idname = __package__
## path templates
use_env_base_path_templates : BoolProperty(
name="Use Environment Base Path Templates",
description="Use environmenet variables to set base path for file output templates\
\nFollowing env variable are available:\
\nRENDERTOOLBOX_EXR_PATH_TEMPLATE fo single base path file output\
\nRENDERTOOLBOX_EXR_PATH_TEMPLATE for single base path file output\
\nRENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE for multilayer base path file output",
default=True,
)
@ -39,24 +40,74 @@ class RT_prefs(bpy.types.AddonPreferences):
default="//render/{node_label}/{node_label}_",
)
## Tech passes
use_env_technical_passes : BoolProperty(
name="Use Environment Technical Passes",
description="Use environmenet variables to set the technical passes name:\
\nRENDERTOOLBOX_TECH_PASSES",
default=True,
)
tech_passes_names : StringProperty(
name="Tech Passes Names",
description="Comma separated list of tech passes names to use for file output creation (Use lossless EXR 32bit)",
default="uv, normal, depth, position, vector, ao",
)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.use_property_decorate = False
layout.prop(self, "use_env_base_path_templates", text="Use Environment Base Path Templates")
col = layout.column()
col.label(text="Output files path templates:")
col.prop(self, "use_env_base_path_templates", text="Use Environment Base Path Templates")
# col.active = not self.use_env_base_path_templates
col.prop(self, "base_path_template", text="Base Path Template")
col.prop(self, "base_path_multilayer_template", text="Base Path Multilayer Template")
if self.use_env_base_path_templates:
col.separator()
col.label(text="Environment variables will override above templates if set", icon='INFO')
# col.label(text="RENDERTOOLBOX_EXR_PATH_TEMPLATE for single base path file output")
# col.label(text="RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE for multilayer base path file output")
col.label(text='If environment variables are not found, preferences will be used instead')
box=col.box()
boxcol = box.column()
env_path_template = os.getenv('RENDERTOOLBOX_EXR_PATH_TEMPLATE')
env_multi_template = os.getenv('RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE')
if not env_path_template or not env_multi_template:
boxcol.label(text="Environment variables override above templates if set", icon='INFO')
if env_path_template or env_multi_template:
boxcol.label(text="Environment variables found:", icon='CHECKMARK')
if env_path_template:
boxcol.label(text=f"RENDERTOOLBOX_EXR_PATH_TEMPLATE:")
boxcol.label(text=env_path_template)
else:
boxcol.label(text="RENDERTOOLBOX_EXR_PATH_TEMPLATE env not found, using preferences ") # (single base path file output)
if env_multi_template:
boxcol.label(text=f"RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE:")
boxcol.label(text=env_multi_template)
else:
boxcol.label(text="RENDERTOOLBOX_MULTILAYER_PATH_TEMPLATE env not found, using preferences") # (multilayer base path file output)
layout.separator()
col = layout.column()
col.label(text="Default Separated Technical Passes:")
col.prop(self, "use_env_technical_passes", text="Use Environment Technical Passes")
col.prop(self, "tech_passes_names", text="Technical Passes Names", placeholder="e.g. uv, normal, depth, vector, ...")
if self.use_env_technical_passes:
box=col.box()
boxcol = box.column()
env_tech_pass = os.getenv('RENDERTOOLBOX_TECH_PASSES')
if env_tech_pass:
boxcol.label(text="Environment variable found:", icon='CHECKMARK')
boxcol.label(text=f"RENDERTOOLBOX_TECH_PASSES:")
boxcol.label(text=env_tech_pass)
else:
boxcol.label(text="Environment variable override tech passes names if set", icon='INFO')
boxcol.label(text='RENDERTOOLBOX_TECH_PASSES env not found, using preferences')
# region Handlers