Please, help me to make auto-save textures by timer

Hello developers! Please help to artist who trying to automate little things.
I found this magical timer that I want to use for autosave my textures in Texture Paint mode. So I found that my operator is bpy.ops.image.save_all_modified(). But how i can to call it by timer? I tried hard but no result. Call operator once - so simple, but do it every N seconds - I rip my brain))

import bpy


def every_2_seconds():
    print("Hello World")
    return 2.0


bpy.app.timers.register(every_2_seconds)

Did you try substituting the line:

print("Hello World")

with:

bpy.ops.image.save_all_modified()

and run that? the timer will execute any line between the def statement and the return statement.

Yes. But no luck… Context error. What It wants?

Traceback (most recent call last):
  File "/alfa/TMP/Test.blend/Text", line 5, in every_2_seconds
  File "/ToshibaHDD/Soft/blender-git/build_linux/bin/2.83/scripts/modules/bpy/ops.py", line 201, in __call__
    ret = op_call(self.idname_py(), None, kw)
RuntimeError: Operator bpy.ops.image.save_all_modified.poll() failed, context is incorrect

My script

import bpy


def every_2_seconds():
    bpy.ops.image.save_all_modified()
    return 2.0


bpy.app.timers.register(every_2_seconds)

The context must be coming from the wrong area, you can change context area like this:

context.area.type == "VIEW_3D"

For example, I guess you need the context to be “IMAGE_EDITOR” (check that…)

You may need to add bpy. in front if you have not passed context to the function. Are you running this in a Text Editor? If so, the context area will not be correct for the Image Editor.

Try this and see how it goes.

See this link and this link.

Cheers, Clock.

EDIT:

Here is a code snippet I use quite a lot:

        if context.area.type == "VIEW_3D":
            if context.window_manager.pdt_run_opengl is False:
                self.handle_add(self, context)
                context.area.tag_redraw()
            else:
                self.handle_remove(self, context)
                context.area.tag_redraw()

            return {"FINISHED"}

        self.report({"ERROR"}, PDT_ERR_NO3DVIEW)
        return {"CANCELLED"}

Here is another that switches context area in a loop:

            for i in range(0, len(freq_l) - 1):
                bpy.context.area.type = "VIEW_3D"
                # Do stuff in 3D View

                bpy.context.area.type = "GRAPH_EDITOR"
                # Do stuff in Graph Editor
                bpy.ops.graph.sound_bake(
                    filepath=path,
                    low=low_f,
                    high=freq_l[i + 1],
                    attack=cm_node.attack,
                    release=cm_node.release,
                    threshold=cm_node.threshold,
                    use_accumulate=cm_node.use_accumulate,
                    use_additive=cm_node.use_additive,
                    use_square=cm_node.use_square,
                    sthreshold=cm_node.sthreshold
                )
                obj.select_set(state = False)

                x_loc = x_loc + 0.1

            bpy.context.area.type = "NODE_EDITOR"
            # Do stuff in Node Editor
            self.report({"INFO"}, f"{len(freq_l) - 1} Controls Created")
            return {"FINISHED"}

It’s deeper. Only this changed error to another:

import bpy

def every_2_seconds():
    if bpy.context.mode == "TEXTURE_PAINT":
        bpy.ops.image.save_all_modified()
    return 2.0

bpy.app.timers.register(every_2_seconds)

Error

location: :-1
Error: File “/alfa/TMP/Test.blend/Text”, line 9
return 2.0
^
IndentationError: unexpected indent

location: :-1

Post the blend file here please. It sounds like it doesn’t like the indent, but unless I can run the code as you wrote it, I can’t debug it… Why have you used context.mode == “TEXTURE_PAINT”? The correct enumerator is “PAINT_TEXTURE”. see this. How are you going to get that mode if the context is in the wrong Editor?

I’ll wait for your file…

My file

I really mixed up commands. But I tried again with ‘PAINT_TEXTURE’ and doesn’t work :frowning:


I do not allow any advertising cookies on my machine…

Can you just post the blend file here, just drag it into a reply, there is no need to use websites that infest our machines with Cookies… Or use https://pasteall.org/blend/

Devtalk doesn’t allow this type of files…

https://pasteall.org/blend/4a149a20c4ac429eb84fd5c410880531

OK, this is working for me:

You don’t need the print statement, that is just so I see it working in my Terminal.

Hint always run Blender from a terminal when writing code, it shows all the error message (if you are not already doing this).

The enumerator is “PAINT_TEXTURE” not “TEXTURE_PAINT”.

EDIT:

I know it says “Texture Paint” in the mode selector in 3D View, but the enumerator is t’other way around for some obscure reason… Ha! just to fool us.

It’s not working) Please check state of textures (create they at first and save) - they doesn’t saving. Print working for me as well, but originally trouble in bpy.ops.image.save_all_modified()

this worked for me, I added “try and except” because you can call save_all_modified only when there is something changed, I don’t know if it’s right I’m only artist myself but hey- it’s working :wink:

 import bpy
    def every_2_seconds():
        try:
            bpy.ops.image.save_all_modified()
            print("saved")
        except:
            pass
        return 2.0

    bpy.app.timers.register(every_2_seconds)
2 Likes

Thank you! It’s really clever. It’s working nice)
I trying to find how to start timer when bpy.ops.image.save_all_modified() is able - now timer working permanent and textures saving in different time due timer and button not sync…

I think you can even check if save_all_modified is available with bpy.ops.image.save_all_modified.poll() so script could be edited like

import bpy

def every_2_seconds():
    if bpy.ops.image.save_all_modified.poll():
        bpy.ops.image.save_all_modified()
    return 2.0

bpy.app.timers.register(every_2_seconds)
2 Likes