Update for Blender 5.2

- use new api compositor access
- Fix file output nodes connexion/renumber/rename/slots handlibng
- consider the new output types
- fixes node position with new API
- fix errors with handler

LLM assisted update
This commit is contained in:
Pullusb
2026-09-29 18:57:26 +02:00
parent 02aa67fc26
commit d650ec8c98
12 changed files with 295 additions and 179 deletions
+65 -62
View File
@@ -11,24 +11,16 @@ from .. import fn
# region renumber outputs
def delete_numbering(fo): # padding=3
'''Delete prefix numbering on all slots on passed file output'''
'''Delete prefix numbering on all items on passed file output'''
if fo.type != 'OUTPUT_FILE': return
if fo.format.file_format == 'OPEN_EXR_MULTILAYER':
slots = fo.layer_slots
field_attr = 'name'
else:
slots = fo.file_slots
field_attr = 'path'
for fs in slots:
elems = getattr(fs, field_attr).split('/')
for fs in fo.file_output_items:
elems = fs.name.split('/')
for i, e in enumerate(elems):
elems[i] = re.sub(r'^\d{3}_', '', e)
new = '/'.join(elems)
setattr(fs, field_attr, new)
fs.name = '/'.join(elems)
## Add - Insert prefix incrementation in fileoutput
@@ -43,9 +35,7 @@ def get_numbered_output(out, slot_name):
def add_fileslot_number(fs, number):
field_attr = 'name' if hasattr(fs, 'name') else 'path'
elems = getattr(fs, field_attr).split('/')
elems = fs.name.split('/')
for i, e in enumerate(elems):
if re.match(r'^\d{3}_', e):
elems[i] = re.sub(r'^(\d{3})', lambda x: str(number).zfill(3), e)
@@ -53,37 +43,34 @@ def add_fileslot_number(fs, number):
elems[i] = f'{str(number).zfill(3)}_{e}'
new = '/'.join(elems)
setattr(fs, field_attr, new)
fs.name = new
return new
def renumber(fo, offset=10):
'''Force renumber all the slots with a 3'''
'''Force renumber all the items with a 3'''
if fo.type != 'OUTPUT_FILE': return
ct = 10 # start at 10
slots = fo.layer_slots if fo.format.file_format == 'OPEN_EXR_MULTILAYER' else fo.file_slots
for fs in slots:
for fs in fo.file_output_items:
add_fileslot_number(fs, ct)
ct += offset
def get_num(string) -> int:
'''get a tring or a file_slot object
'''get a string or a file output item object
return leading number or None
'''
if not isinstance(string, str):
if hasattr(string, 'path'):
string = string.path
else:
string = string.name
string = string.name
num = re.search(r'^(\d{3})_', string)
if num:
return int(num.group(1))
def reverse_fileout_inputs(fo):
count = len(fo.inputs)
items = fo.file_output_items
count = len(items)
for i in range(count):
fo.inputs.move(count-1, i)
items.move(count-1, i)
def renumber_keep_existing(fo, offset=10, invert=True):
'''Renumber by keeping existing numbers and inserting new one whenever possible
@@ -96,7 +83,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
if invert:
reverse_fileout_inputs(fo)
fsl = fo.layer_slots if fo.format.file_format == 'OPEN_EXR_MULTILAYER' else fo.file_slots
fsl = fo.file_output_items
last_idx = len(fsl) - 1
prev = None
@@ -140,7 +127,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
if next_num is None:
add_fileslot_number(fs, 0)
elif next_num == 0:
print(f'Cannot insert value before 0 to {fsl.path}')
print(f'Cannot insert value before 0 to {fs.name}')
continue
else:
add_fileslot_number(fs, int(next_num / 2))
@@ -161,7 +148,7 @@ def renumber_keep_existing(fo, offset=10, invert=True):
# iterate rename
gap_inc = int((next_num - prev_num) / divider)
if gap_inc < 1: # same values !
print(f'cannot insert a median value at {fs.path} between {prev_num} and {next_num}')
print(f'cannot insert a median value at {fs.name} between {prev_num} and {next_num}')
continue
ct = prev_num
@@ -187,7 +174,7 @@ class RT_OT_number_outputs(Operator):
@classmethod
def poll(cls, context):
return True
return fn.get_compositor_tree(context.scene) is not None
mode : StringProperty(default='SELECTED', options={'SKIP_SAVE'})
clear : BoolProperty(default=False, options={'SKIP_SAVE'})
@@ -199,10 +186,8 @@ class RT_OT_number_outputs(Operator):
return self.execute(context)
def execute(self, context):
scn = context.scene
ct = 0
nodes = scn.node_tree.nodes
nodes = fn.get_compositor_tree(context.scene).nodes
for fo in nodes:
if fo.type != 'OUTPUT_FILE':
continue
@@ -234,9 +219,13 @@ class RT_OT_mute_toggle_output_nodes(Operator):
mute : BoolProperty(default=True, options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return fn.get_compositor_tree(context.scene) is not None
def execute(self, context):
ct = 0
for n in context.scene.node_tree.nodes:
for n in fn.get_compositor_tree(context.scene).nodes:
if n.type != 'OUTPUT_FILE':
continue
n.mute = self.mute
@@ -255,8 +244,12 @@ class RT_OT_set_output_node_format(Operator):
mute : BoolProperty(default=True, options={'SKIP_SAVE'})
@classmethod
def poll(cls, context):
return fn.get_compositor_tree(context.scene) is not None
def execute(self, context):
nodes = context.scene.node_tree.nodes
nodes = fn.get_compositor_tree(context.scene).nodes
if not nodes.active or nodes.active.type != 'OUTPUT_FILE':
self.report({"ERROR"}, f'Active node should be an output file to use as reference for output format')
return {"CANCELLED"}
@@ -273,8 +266,14 @@ class RT_OT_set_output_node_format(Operator):
if n.type != 'OUTPUT_FILE' or n == ref or not n.select:
continue
## media type and file format first, available values of other attributes depend on them
n.format.media_type = ref.format.media_type
n.format.file_format = ref.format.file_format
for attr in dir(ref.format):
if attr.startswith('__') or attr in {'rna_type','bl_rna', 'view_settings', 'display_settings','stereo_3d_format'}: # views_format
if attr.startswith('__') or attr in {'rna_type','bl_rna', 'media_type', 'file_format',
'view_settings', 'display_settings', 'linear_colorspace_settings',
'has_linear_colorspace', 'stereo_3d_format'}: # views_format
continue
try:
setattr(n.format, attr, getattr(ref.format, attr))
@@ -295,36 +294,38 @@ class RT_OT_set_output_node_format(Operator):
class RT_OT_set_active_file_output_slot_to_composite(bpy.types.Operator):
bl_idname = "rt.set_active_file_output_slot_to_composite"
bl_label = "Set Active File Output Slot To Composite"
bl_description = "Use active slot of active file output node to set scene output settings (swap connection)"
bl_label = "Set Active File Output Item To Composite"
bl_description = "Use active item of active file output node to set scene output settings\
\nand swap connection to compositor Group Output node"
bl_options = {"REGISTER"}
@classmethod
def poll(cls, context):
return context.scene.use_nodes\
and context.scene.node_tree\
and context.scene.node_tree.nodes.active\
and context.scene.node_tree.nodes.active.type == 'OUTPUT_FILE'
tree = fn.get_compositor_tree(context.scene)
return bool(tree\
and tree.nodes.active\
and tree.nodes.active.type == 'OUTPUT_FILE')
relink_composite : bpy.props.BoolProperty(
name='Relink Composite',
default=True,
description='In case file slot is linked, swap link to Composite file',
description='In case file output item is linked, swap link to compositor Group Output',
options={'SKIP_SAVE'},
)
def invoke(self, context, event):
self.fo = context.scene.node_tree.nodes.active
if not len(self.fo.file_slots):
self.report({'ERROR'}, 'no slots in active file output')
tree = fn.get_compositor_tree(context.scene)
self.fo = tree.nodes.active
if not len(self.fo.file_output_items):
self.report({'ERROR'}, 'no items in active file output')
return {'CANCELLED'}
# check if active slot has a source
if not self.fo.inputs[self.fo.active_input_index].is_linked:
# check if active item has a source
if not self.fo.inputs[self.fo.active_item_index].is_linked:
return self.execute(context)
# check if composite linked
out = context.scene.node_tree.nodes.get('Composite')
out = fn.get_group_output_node(tree)
if not out or not out.inputs[0].is_linked:
self.compo_out_from_link = ''
return self.execute(context)
@@ -337,34 +338,36 @@ class RT_OT_set_active_file_output_slot_to_composite(bpy.types.Operator):
def draw(self, context):
layout = self.layout
col = layout.column()
col.label(text=f'Composite node connected to: {self.compo_out_from_link}')
col.label(text=f'Would you like to replace by file output slot source ?')
col.label(text=f'Group Output node connected to: {self.compo_out_from_link}')
col.label(text=f'Would you like to replace by file output item source ?')
layout.prop(self, 'relink_composite')
def execute(self, context):
self.fo = fn.get_compositor_tree(context.scene).nodes.active
if not len(self.fo.file_output_items):
self.report({'ERROR'}, 'no items in active file output')
return {'CANCELLED'}
# if comp
fn.set_scene_output_from_active_fileout_item()
idx = self.fo.active_input_index
sl = self.fo.file_slots[idx]
idx = self.fo.active_item_index
item = self.fo.file_output_items[idx]
sk = self.fo.inputs[idx]
if not sk.is_linked:
self.report({'INFO'}, f'Outut changed to match {sl.path} (slot was not linked)')
self.report({'INFO'}, f'Outut changed to match {item.name} (item was not linked)')
return {'FINISHED'}
## If linked replace links to Composite node
## If linked replace links to Group Output node
if not self.relink_composite:
return {'FINISHED'}
ntree = context.scene.node_tree
links = context.scene.node_tree.links
nodes = context.scene.node_tree.nodes
ntree = fn.get_compositor_tree(context.scene)
links = ntree.links
out = nodes.get('Composite')
if not out:
out = fn.create_node('COMPOSITE', tree=ntree)
out = fn.get_group_output_node(ntree, create=True)
fo_loc = fn.real_loc(self.fo)
out.location = (fo_loc.x, fo_loc.y + 160)
out.location_absolute = (fo_loc.x, fo_loc.y + 160)
# if out.inputs[0].is_linked:
# self.report({'WARNING'}, f'Outut changed to match {sl.path} (Composite node already linked)')