4

I load an image texture (of 3 frames) as a material node. I draw on all (3) frames in texture paint mode.

save

I want to save all modified images at once, preferably with python. How can I save all images of the image sequence at once?

Leander
  • 26,725
  • 2
  • 44
  • 105

1 Answers1

5

You can just call bpy.ops.image.save_sequence() within the Image Editor.

Override the context

If you would like to call the operator from another area, you can override the context:

Blender 3.2+

import bpy
from bpy import context

for area in context.screen.areas: if area.type == 'IMAGE_EDITOR': with context.temp_override(area=area): bpy.ops.image.save_sequence() break

Blender 2.8+

import bpy

C = bpy.context

for area in C.screen.areas: if area.type == 'IMAGE_EDITOR': override = C.copy() override['area'] = area bpy.ops.image.save_sequence(override) break

Console output:

Info: 3 image(s) will be saved in /tmp/
Info: Saved /tmp/untitled_0003.exr
Info: Saved /tmp/untitled_0002.exr
Info: Saved /tmp/untitled_0001.exr

Switch the area

If there is no Image Editor available, you can also switch the current area to the Image Editor, assign the sequence, call the operator and reset the area to its old type:

import bpy

C = bpy.context img_seq = bpy.data.images['untitled_0001.exr']

Store current area

area = C.area old_type = area.type

Set the area type and assign the sequence

area.type = 'IMAGE_EDITOR' area.spaces.active.image = img_seq

Call the operator and reset the area

bpy.ops.image.save_sequence() area.type = old_type

Console output:

Info: 3 image(s) will be saved in /tmp/
Info: Saved /tmp/untitled_0003.exr
Info: Saved /tmp/untitled_0002.exr
Info: Saved /tmp/untitled_0001.exr

Custom operator

Operator demo based on the operator_node.py template that comes with Blender in order to save the image sequence of the selected Image node in the Node Editor:

import bpy

class NodeOperator(bpy.types.Operator): """Tooltip""" bl_idname = "node.simple_operator" bl_label = "Simple Node Operator"

@classmethod
def poll(cls, context):
    space = context.space_data
    return space.type == 'NODE_EDITOR' and \
        context.active_node.type=='TEX_IMAGE'

def execute(self, context):
    image_sequence = context.active_node.image
    if image_sequence.source == 'SEQUENCE':
        # Store current area
        area = context.area
        old_type = area.type
        # Set the area type and assign the sequence
        area.type = 'IMAGE_EDITOR'
        area.spaces.active.image = image_sequence
        # Call the operator and reset the area
        bpy.ops.image.save_sequence()
        area.type = old_type
    return {'FINISHED'}

def register(): bpy.utils.register_class(NodeOperator) def unregister(): bpy.utils.unregister_class(NodeOperator) if name == "main": register()

p2or
  • 15,860
  • 10
  • 83
  • 143
brockmann
  • 12,613
  • 4
  • 50
  • 93
  • re 1: you'd have to switch the area temporarily see the link. re 2: Sure, layout.operator("image.save_sequence") (already part of the header menu, Image Editor > Image > Save Sequence) Does this answer your question(s)? @Leander – brockmann Sep 05 '21 at 16:28
  • Actually quite so. It seems I just completely overlooked that operator/button. Obligatory, but futile follow up: Can we access that functionality without ops? – Leander Sep 05 '21 at 21:58
  • Not sure, need to investigate. AFAICT there is no way to access each image (buffer) individually so I don't see any potential improvements of a low-level solution. We would have to change the frame using frame_set() and save the result which is quite expensive and slow, I guess. The operator on the other hand just saves the image buffer already in memory: https://github.com/blender/blender/blob/master/source/blender/editors/space_image/image_ops.c#L2201 @Leander – brockmann Sep 06 '21 at 08:52
  • I had tried your suggestion, but the image buffer was not replaced after frame_set(). Your argument about performance makes a lot of sense though. – Leander Sep 06 '21 at 09:31