UV Image packing with the python console?

Hi all! I’ve been playing around with Blender for a few months, and never posted here before, so apologies if this is the wrong forum or a trivial question. What I’d like to know if its possible to pack an image to the .blend file using the python console. By hovering over the UV editor -> Image -> Pack Image button, I see that the code I’d need is

bpy.ops.image.pack(as_png=False)

However, if I try to run this in the console, it just spits out “CANCELLED”. I assume this is because that function call requires the context of the UV editor, and by pasting into the python console, you aren’t able to stay in the UV editor’s context. Is there anyway around this?

Thanks for any suggestions!

works for me:

img = bpy.data.images[0]
bpy.ops.image.pack({‘edit_image’: img})

Thanks for getting back to me! When I try the above I get:


>>> bpy.ops.image.pack({'edit_image': img}) 
Error: Cannot pack edited image from disk, only as internal PNG

Traceback (most recent call last):
  File "<blender_console>", line 1, in <module>
  File "/Applications/Blender/blender.app/Contents/MacOS/2.65/scripts/modules/bpy/ops.py", line 186, in __call__
    ret = op_call(self.idname_py(), C_dict, kw, C_exec, C_undo)
RuntimeError: Error: Cannot pack edited image from disk, only as internal PNG

Perhaps I am doing something wacky earlier in the code? I’ll paste the whole batch at the bottom if that is helpful to folks. Basically, I’m trying to script reading in a high resolution mesh from an OBJ file (with an extra parameter that gives the emission of each material, so I’ve modified the OBJ reader to except it), remesh it to a low resolution version, map its original colors as a texture which includes the colors, emissivity, and transparency of the object, and then export it in a way that Unity can read in all this information. All of that works lovely when I manually “pack” the mapped texture into the blend file with button pushes, but the failure is trying to do it in the same script.

Don’t know if all that info helps or not, but here is the code if it gives any insight.



# import an obj file (with added emissivity reader)
# ifile is path to obj, fname is file name
bpy.ops.import_scene.obj(filepath=ifile+fname+'.obj') # import an obj file

# define some things, high res model is named by "fname"
lowResname = 'LowRes' # name of our new mesh
meshname = "uvmesh" # name the new uv image 

# go into object mode
bpy.ops.object.mode_set(mode='OBJECT')

# duplicate the mesh
scene = bpy.data.scenes["Scene"]
highRes = bpy.data.objects[fname]
science.duplicateObject(scene,lowResname,highRes) # science is a lib I'm writing, this just duplicates a mesh
lowRes = bpy.data.objects[lowResname]

# need this to select only the one object
bpy.context.scene.objects.active = bpy.data.objects[lowResname]

# Add a modifier = remesh
bpy.ops.object.modifier_add(type='REMESH')
# select the smooth remesh
bpy.data.objects[lowResname].modifiers['Remesh'].mode = 'SMOOTH'
# scale the amount of blocks, seems like ~0.7 is ok?
bpy.data.objects[lowResname].modifiers['Remesh'].scale = 0.7
# apply the modifier
bpy.ops.object.modifier_apply(apply_as='DATA',modifier='Remesh')

# go to edit mode
bpy.ops.object.mode_set(mode='EDIT')

# select all verticies of our remeshed object
bpy.ops.mesh.select_all()

# unwrap into a UV map 
bpy.ops.uv.smart_project()
bpy.ops.image.new(name=meshname, width=1024, height=1024, color=(0, 0, 0, 1), alpha=True, generated_type='BLANK', float=False)

# link the new image with its name
bpy.data.screens['UV Editing'].areas[1].spaces[0].image = bpy.data.images[meshname]

# to make sure things are inside UV editor boarder
bpy.ops.uv.select_all() # select UVs
bpy.ops.uv.pack_islands()
bpy.ops.uv.select_all(action='TOGGLE') # unselect uvs

# make sure both high and low res meshes are at the same location
lowRes.location = [0., 0., 0.]
highRes.location = [0., 0., 0.]

# deselect all not-mesh things
bpy.data.objects['Camera'].select = False
bpy.data.objects['Empty'].select = False

# select low res model & high res model with low active
lowRes.select = True
highRes.select = True
bpy.context.scene.objects.active = bpy.data.objects[lowResname]

# bake...
# first, select to active to bake from high res to low res
scene.render.use_bake_selected_to_active = True
# also don't clear???? <- not sure, but have had it unselected
scene.render.use_bake_clear = False
# bake mode, we'll use full render, options are:
#'FULL', 'AO', 'SHADOW', 'NORMALS', 'TEXTURE', 'DISPLACEMENT', 'EMIT', 'ALPHA', 'MIRROR_INTENSITY', 'MIRROR_COLOR', 'SPEC_INTENSITY', 'SPEC_COLOR'
scene.render.bake_type = 'FULL'

# for packing, save full as rbga?  but if we are packing to
#   the blender file, then don't need it?
#bpy.context.scene.render.image_settings.color_mode = 'RGBA'

# bake it!
bpy.ops.object.bake_image()

# now, pack to blend file
#  NO WORK HERE!  GRRRRR
bpy.ops.uv.select_all() # select all
img = bpy.data.images[meshname]
bpy.ops.image.pack({'edit_image': img}) 
#bpy.ops.image.pack(as_png=False)
#img.pack(as_png = True)


Thanks again for any ideas!

In case anybody needs to do the same, I fixed it by adding it as a texture instead… which to me seems like the same thing, but hey, it works!


    lowRes.select = True
    bpy.context.scene.objects.active = bpy.data.objects[lowResname]
    # remove all zee origional materials!
    delete_unused_materials(lowRes)
    # now, add a new texture over this material
    matLR = makeMaterial('matLR', (0,0,1), (1,1,1), 0.0, 0.0) # blue, bright, invisible, non emissive
    tex = bpy.data.textures.new('UVtex',type='IMAGE')
    img = bpy.data.images['uvmesh'+lowResname]
    tex.image = img
    mtex = matLR.texture_slots.add()
    mtex.texture = tex
    mtex.texture_coords = 'UV'
    mtex.use_map_color_diffuse = True 
    mtex.use_map_color_emission = True 
    mtex.use_map_emit = True ##?
    mtex.use_map_diffuse = True
    mtex.use_map_density = True 
    mtex.mapping = 'FLAT'
    ob = bpy.context.object
    me = ob.data
    me.materials.append(matLR)
    # remind the user that GLSL shading needs to be selected to display
    print('Done Decimating Mesh... NOTE!!!  To see colors w/o rendering, must have GLSL shading selected')
    img = bpy.data.images['uvmesh' + lowResname]
    img.pack(as_png = True) # pack into blend file for exporting as a standalone player

Here, I’ve used the helper functions:


def delete_unused_materials(name):
    for mat in bpy.data.materials:
        if (mat.name.find(name) != -1):
            mat.user_clear()
            bpy.data.materials.remove(mat)
    # delete material slots too
    for i in range(0,len(bpy.data.objects[name].material_slots)):
        bpy.context.object.active_material_index = i
        bpy.ops.object.material_slot_remove()

and


def makeMaterial(name, diffuse, specular, alpha, emiss, mat_type=None, halo_size=None):
    mat = bpy.data.materials.new(name)
    mat.emit = emiss # emissivity is up to 2.0!
    mat.diffuse_color = diffuse
    mat.diffuse_shader = 'LAMBERT' 
    mat.diffuse_intensity = 1.0 
    mat.specular_color = specular
    mat.specular_shader = 'COOKTORR'
    mat.specular_intensity = 0.5
    mat.alpha = alpha  # sets transparency (0.0 = invisable)
    mat.use_transparency = True # assumes Z-transparency
#    mat.ambient = 1use  # can't have for transparency
    if mat_type is not None:
        mat.type = mat_type
    if halo_size is not None:
        mat.halo.size = halo_size
    return mat