Split tech passes in separate 32 bits

hardcoded tech passes names : ['uv', 'normal', 'depth', 'position', 'vector', 'ao']
This commit is contained in:
pullusb
2025-04-02 11:53:11 +02:00
parent 79406ca0a2
commit 7fd7f9ab27
5 changed files with 180 additions and 135 deletions
+154 -131
View File
@@ -3,6 +3,8 @@ import os
import re
import json
from .constant import TECH_PASS_KEYWORDS
### --- Manage nodes --- ###
def real_loc(n):
@@ -60,7 +62,92 @@ def recursive_node_connect_check(l, target_node):
return False
def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None, remap_names=None, file_format=None):
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)):
"""Helper function to create file output node and connect it to outputs
Args:
node: Source node
outputs: List of outputs to connect
file_out: Existing file output node or None
out_name: Name for the file output node
base_path: Base path for output files
out_base: Base name for output files
scene: Blender scene
remap_names: Dictionary for remapping output names
file_format: Format settings
suffix: Optional suffix for paths
color_depth: Optional override for color depth
location_offset: Offset for the node position
Returns:
bpy.types.CompositorNode: Created or used file output node
"""
if not outputs:
return None
nodes = scene.node_tree.nodes
links = scene.node_tree.links
fo = file_out
if not fo:
fo = nodes.get(out_name)
if not fo:
fo = create_node('CompositorNodeOutputFile', tree=scene.node_tree,
location=(real_loc(node)[0] + location_offset[0],
real_loc(node)[1] + location_offset[1]), width=600)
fo.inputs.remove(fo.inputs[0]) # Remove default image input
if file_format:
for k, v in file_format.items():
setattr(fo.format, k, v)
else:
set_file_output_format(fo)
if color_depth:
fo.format.color_depth = color_depth
fo.name = out_name
if node.parent:
fo.parent = node.parent
if base_path:
fo.base_path = base_path
else:
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):
continue
if (socket_remaps := remap_names.get(node.name)) and (custom_name := socket_remaps.get(o.name)):
slot_name = bpy.path.clean_name(custom_name)
else:
slot_name = bpy.path.clean_name(o.name)
fs = fo.file_slots.new('tmp')
ls = fo.layer_slots.new('tmp')
ls = fo.layer_slots[-1]
ls.name = slot_name
fs = fo.file_slots[-1]
fs.path = f'{slot_name}/{slot_name}_'
out_input = fo.inputs[-1]
links.new(o, out_input)
clear_disconnected(fo)
fo.update()
return fo
def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None, remap_names=None,
file_format=None, split_tech_passes=False):
"""Connect selected nodes output to file output(s)
if a file output is selected, add intputs on it
@@ -81,159 +168,95 @@ def connect_to_file_output(node_list, file_out=None, base_path='', excludes=None
Defaults toNone.
remap_names (dict, optionnal): List of output names to remap {node_name: {output_name: new_name}}.
frame (bpy.types.CompositorNode, optional): If given, create nodes into a frame.
Defaults to None.
split_tech_passes (bool, optional): When True, create a separate file output for technical passes
Defaults to False.
Returns:
list[bpy.types.CompositorNode]: All nodes created.
"""
scene = bpy.context.scene
nodes = scene.node_tree.nodes
links = scene.node_tree.links
if not isinstance(node_list, list):
node_list = [node_list]
node_list = [n for n in node_list if n.type != 'OUTPUT_FILE']
if not node_list:
return
return []
created_nodes = []
excludes = excludes or {}
remap_names = remap_names or {}
for node in node_list:
exclusions = excludes.get(node.name) or []
## create one output facing node and connect all
outs = [o for o in node.outputs if not o.is_unavailable and not 'crypto' in o.name.lower() and o.name not in exclusions]
cryptout = [o for o in node.outputs if not o.is_unavailable and 'crypto' in o.name.lower() and o.name not in exclusions]
## Using render layer can force connexion to an existing FO node when user may want a new one
# if node.type == 'R_LAYERS':
# out_base = node.layer
# Get all available outputs excluding those in exclusions
all_outputs = [o for o in node.outputs if not o.is_unavailable and o.name not in exclusions]
# Base name for output nodes
if node.label:
out_base = node.label
else:
out_base = node.name
out_base = bpy.path.clean_name(out_base)
out_name = f'OUT_{out_base}'
# Categorize outputs
crypto_outputs = [o for o in all_outputs if 'crypto' in o.name.lower()]
if split_tech_passes:
# 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)]
# 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:
# If not splitting tech passes, include them with regular outputs
regular_outputs = [o for o in all_outputs if o not in crypto_outputs]
tech_outputs = []
if outs:
fo = file_out
if not fo:
fo = nodes.get(out_name)
if not fo:
# color = (0.2,0.3,0.5)
fo = create_node('CompositorNodeOutputFile', tree=scene.node_tree, location=(real_loc(node)[0]+500, real_loc(node)[1]+50), width=600)
fo.inputs.remove(fo.inputs[0]) # Remove default image input
if file_format:
for k, v in file_format.items():
setattr(fo.format, k, v)
else:
set_file_output_format(fo)
y_offset = 50
node_margin = 100
# Create and connect regular outputs
if regular_outputs:
out_name = f'OUT_{out_base}'
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)
)
if fo_regular and fo_regular not in created_nodes:
created_nodes.append(fo_regular)
fo.name = out_name
if node.parent:
fo.parent = node.parent
y_offset += -22 * len(regular_outputs) - node_margin
# Create and connect tech outputs with 32-bit depth if split_tech_passes is True
if tech_outputs:
out_name = f'OUT_{out_base}_tech'
if base_path:
fo.base_path = base_path
else:
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
fo.base_path = f'//render/{out_base}/{out_base}_'
else:
fo.base_path = f'//render/{out_base}'
fo_tech = create_and_connect_file_output(
node, tech_outputs, None, out_name, base_path,
out_base, scene, remap_names, file_format,
suffix='tech/', color_depth='32',
location_offset=(500, y_offset)
)
for o in outs:
if next((l for l in o.links if recursive_node_connect_check(l, fo)), None):
continue
if (socket_remaps := remap_names.get(node.name)) and (custom_name := socket_remaps.get(o.name)):
slot_name = bpy.path.clean_name(custom_name) # clean name ?
else:
slot_name = bpy.path.clean_name(o.name)
if fo_tech and fo_tech not in created_nodes:
created_nodes.append(fo_tech)
y_offset -= node_margin
# if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
# slot_name = slot_name
# else:
# slot_name = f'{slot_name}/{slot_name}_'
# fo.file_slots.new(slot_name)
fs = fo.file_slots.new('tmp') # slot_name)
ls = fo.layer_slots.new('tmp') # slot_name + 'layer')
y_offset += -22 * len(tech_outputs)
# Create and connect crypto outputs with 32-bit depth
if crypto_outputs:
out_name = f'OUT_{out_base}_cryptos'
fo_crypto = create_and_connect_file_output(
node, crypto_outputs, None, out_name, base_path,
out_base, scene, remap_names, file_format,
suffix='cryptos/', color_depth='32',
location_offset=(500, y_offset)
)
ls = fo.layer_slots[-1]
ls.name = slot_name
fs = fo.file_slots[-1]
fs.path = f'{slot_name}/{slot_name}_' # Error 'NodeSocketColor' object has no attribute 'path'
out_input = fo.inputs[-1]
links.new(o, out_input)
clear_disconnected(fo)
fo.update()
## Create separate file out for cryptos
if cryptout:
out_name += '_cryptos'
fo = file_out
if not fo:
fo = nodes.get(out_name)
if not fo:
# color = (0.2,0.3,0.5)
fo = create_node('CompositorNodeOutputFile', tree=scene.node_tree, location=(real_loc(node)[0]+400, real_loc(node)[1]-200), width=220)
fo.inputs.remove(fo.inputs[0]) # Remove default image input
if file_format:
for k, v in file_format.items():
setattr(fo.format, k, v)
else:
set_file_output_format(fo) # OPEN_EXR_MULTILAYER, RGBA, ZIP
fo.format.color_depth = '32' # For crypto force 32bit
fo.name = out_name
if node.parent:
fo.parent = node.parent
if base_path:
fo.base_path = base_path
else:
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
## FIXME: find a better organization for separated crypto pass
fo.base_path = f'//render/{out_base}/cryptos/cryptos_'
else:
fo.base_path = f'//render/{out_base}'
for o in cryptout:
## Skip already connected
## TODO Test recusively to find fo (some have interconnected sockets)
# if next((l for l in o.links if l.to_node == fo), None):
if next((l for l in o.links if recursive_node_connect_check(l, fo)), None):
continue
# if remap_names and (custom_name := remap_names.get(o.name)):
if (socket_remaps := remap_names.get(node.name)) and (custom_name := socket_remaps.get(o.name)):
slot_name = bpy.path.clean_name(custom_name) # clean name ?
else:
slot_name = bpy.path.clean_name(o.name) # directly use name in multi layer exr
# if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
# slot_name = slot_name
# else:
# slot_name = f'{slot_name}/{slot_name}_'
# fo.file_slots.new(slot_name)
# Setting both file_slots and layer_slots...
fs = fo.file_slots.new('tmp')
ls = fo.layer_slots.new('tmp')
ls = fo.layer_slots[-1]
ls.name = slot_name
fs = fo.file_slots[-1]
fs.path = f'{slot_name}/{slot_name}_' # Error 'NodeSocketColor' object has no attribute 'path'
out_input = fo.inputs[-1]
links.new(o, out_input)
clear_disconnected(fo)
fo.update()
if fo_crypto and fo_crypto not in created_nodes:
created_nodes.append(fo_crypto)
return created_nodes