cleaning up blender

Recently I had to clean up a lot of my blendfiles. Especially when you append objects that share textures with your existing objects you wind up with a lot of duplicates.

So I came up with a little script to unlink every image in the file. You can manually link the images to your textures again, and they will not get deleted next time you open your blendfile.

  1. import bpy
  2. imgs = bpy.data.images
  3. for image in imgs:
  4.     image.user_clear()

bpy.data.images will store all images names that are used in any texture in an array.
The for-loop will be repeated as often as there are image names in imgs.
user_clear() is a built-in method, which will set the number of users of an image, texture, datablock etc. to 0.

Some people may ask: what if I want to keep some of my images? Of course this is a good question. You could "tag" the images you want to keep by hand, by inserting a string at the beginning, let's say "keep".
Then use an if statement, so we only delete those images whose names do not start with "keep".

  1. import bpy
  2.  
  3. imgs = bpy.data.images
  4. for image in imgs:
  5.     name = image.name
  6.     if name[:4] != "keep":
  7.         image.user_clear()

 The line if name[:4] != "keep": checks the first 4 characters in a string and whether they spell "keep". [:n] extracts the first n characters of a string [n:] the last n.
!= means not equal to so if the 4 characters of the name are not "keep" the image will be unlinked.

Still we have to do stuff by hand, which is not the goal when scripting. How about we write a script that removes all images that are not used in a texture slot?

  1. import bpy
  2.  
  3. img_names = []
  4. # if you want to use the method "append", you have to make sure, you're doing it with a variable that is in fact a list. = [] creates an empty array.
  5. textures = bpy.data.textures
  6. # Stores the names of all images that actually are used in a texture slot in an array (textures)
  7. for tex in textures:
  8.     if tex.type == 'IMAGE':
  9.         img_names.append(tex.image.name)
  10.  
  11. # The if statement will check if the image's name appears in the list. If not, it will be unlinked
  12. imgs = bpy.data.images
  13. for image in imgs:
  14.     name = image.name
  15.     if name not in img_names:
  16.         image.user_clear()

Careful: all reference and background images will be removed as well.

So there you have it: a script that will unlink every image that is not used in a texture, making the cleanup so much easier.

  1. import bpy
  2.  
  3. img_names = []
  4. textures = bpy.data.textures
  5.  
  6. for tex in textures:
  7.     if tex.type == 'IMAGE':
  8.         img_names.append(tex.image.name)
  9.  
  10. imgs = bpy.data.images
  11. for image in imgs:
  12.     name = image.name
  13.     if name not in img_names:
  14.         image.user_clear()

OK, that was for Blender Internal materials, since Cycles works with nodes and not texture slots, the script is a bit more complicated.

  1. import bpy
  2. print('new run')
  3. img_names = []
  4. materials = bpy.data.materials
  5.  
  6. for mat in materials:
  7.     nodes = mat.node_tree.nodes
  8.     for node in nodes:
  9.         if type(node) == bpy.types.ShaderNodeTexImage:
  10.             try:
  11.                 nam = node.image.name
  12.                 img_names.append(nam)
  13.             except:
  14.                 print('No texture assigned to this node, no problem though')
  15.  
  16. imgs = bpy.data.images
  17. for image in imgs:
  18.     name = image.name
  19.     if name not in img_names:
  20.         image.user_clear()
  21.  

So here is a little more explanation: nodes = mat.node_tree.nodes stores all nodes of the current material in a list. So the for loop will check every node whether it is a texture node or not. If it is a texture node, it's image name will be stored in the array. I used try, because nam = node.image.name raised an error in some cycles of the loop. If you are not sure if all texture nodes actually contain an image, you can use the try method. If your code causes an error, python will do whatever comes after the except, ignoring the error. In our case it will just notify us that something did not go according to plan, but the script will not be aborted.
Warning: This script will not work if there are materials present that do not have "use Nodes" enabled.