10
$\begingroup$

My Blender daily life is :-

  1. Create a texture
  2. Pack it
  3. Save (Ctrl+S)
  4. Edit the texture
  5. Save (Ctrl+S)
  6. Blender crashes ===> I lose all work in Step 4.
    (This can also be tested by terminating the Blender process.)

How to make Ctrl+S saves BOTH the dot-blend file AND all of the modified packed images?
In preference, the command to do so is image.save_all_modified.

My current workarounds

  1. Create another hotkey for image.save_all_modified. To save properly, press both Ctrl+S and another one. I have to press the new hotkey with the mouse hover within the "Image Editor" workpane before Ctrl+S.

  2. Enabling File > External Data > Automatically Pack Into .blend will pack all images , including unwanted big external image.

  3. (best so far) Whenever I want to save properly, I close the program. Blender will popup whether I want to save modified image(s). I answer yes. After that, I re-open my file again.

Related issue : https://developer.blender.org/T45636

After get brockmann's awesome answer : If anyone want to extend the script and check whether an image is packed, it can be read at Detect with python script if a blend file has packed images. Adding these code in save_mod_images() can be useful. It will reload all external files, so now the script will save & sync (reload external) in one go :-

for i in bpy.data.images:
    if i.packed_file is None :
        i.reload()
$\endgroup$
2
  • $\begingroup$ Reloading the images before closing the file doesn't make any sense to me (at a first glance) because the images are going to be reloaded when opening the file anyway, aren't they? $\endgroup$ – brockmann Mar 23 at 12:06
  • 1
    $\begingroup$ @brockmann :: My workflow is :: edit internal texture OR external texture OR model --> Ctrl+S to backup & sync --> continue editing . I don't close the file yet. Is it weird? ^^' $\endgroup$ – cppBeginner Mar 23 at 12:09
11
$\begingroup$

You can use application handlers, specifically save_pre handler. When you're saving the file, the handler kicks in and allows to execute whatever you want before saving the file.

The following demo saves all modified images when you are hitting save, automatically:

import bpy
from bpy.app.handlers import persistent

@persistent
def save_mod_images(dummy):
    """ Save all modified images """
    if any(i.is_dirty for i in bpy.data.images):
        bpy.ops.image.save_all_modified()

@persistent
def pack_dirty_images(dummy):
    """ Pack all modified images """ 
    for i in bpy.data.images:
        if i.is_dirty:
            i.pack()
            print("Packed:", i.name)
            
bpy.app.handlers.save_pre.append(save_mod_images)
#bpy.app.handlers.save_pre.append(pack_dirty_images)

Copy-paste the script into the text editor and run it using the play button, this is going to add the handler to the current Blender session. If you'd like to pack all unsaved images when saving the file, just un-comment bpy.app.handlers.save_pre.append(pack_dirty_images).


image-data-linter.py

For simplicity, this can be easily turned into an Add-on. Add the usual bl_info dict, register and unregister functions and make sure to remove the handler when the add-on gets un-registered, further reading: How to create a custom UI?

enter image description here Added both options to the Add-on preferences so they can be easily turned on or off.

# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

bl_info = {
    "name": "Image Data Linter",
    "description": "Save or pack all modified images when saving the file",
    "author": "brockmann",
    "version": (0, 0, 1),
    "blender": (2, 80, 0),
    "location": "",
    "category": "Development"
}

import bpy
from bpy.app.handlers import persistent


class ImageDataLinterPrefs(bpy.types.AddonPreferences):

    bl_idname = __name__

    save: bpy.props.BoolProperty(
        name="Save modified Images",
        description="Save modified images when saving the blend",
        default=True)

    pack: bpy.props.BoolProperty(
        name="Pack un-saved Images",
        description="Pack un-saved images when saving the blend",
        default=False)

    def draw(self, context):
        layout = self.layout
        layout.prop(self, "save", toggle=True, icon="DISK_DRIVE")
        layout.prop(self, "pack", toggle=True, icon="PACKAGE")
        layout.separator()


@persistent
def pack_dirty_images(dummy):
    if bpy.context.preferences.addons[__name__].preferences.pack:
        for i in bpy.data.images:
            if i.is_dirty:
                i.pack()
                print("Packed:", i.name)

@persistent
def save_mod_images(dummy):
    if bpy.context.preferences.addons[__name__].preferences.save:
        """ Save all modified images """
        if any(i.is_dirty for i in bpy.data.images):
            bpy.ops.image.save_all_modified()


def register():
    bpy.utils.register_class(ImageDataLinterPrefs)
    bpy.app.handlers.save_pre.append(save_mod_images)
    bpy.app.handlers.save_pre.append(pack_dirty_images)

def unregister():
    bpy.app.handlers.save_pre.remove(pack_dirty_images)
    bpy.app.handlers.save_pre.remove(save_mod_images)
    bpy.utils.unregister_class(ImageDataLinterPrefs)

if __name__ == "__main__":
    register()

Save the script as image-data-linter.py, go to the User Preferences > Add-ons > Install. Further reading: How to install an Add-on

$\endgroup$
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.