18
$\begingroup$

I would like to automate the proccess of rendering images (sprites). What I want to do is to render an image of the same model with different textures, ideally the texture filename would be passed as a command line parameter.

However, I am totally unfamiliar with blender scripting or blender command line. Can somebody please share some ideas of how this could be done?

$\endgroup$
23
$\begingroup$

For starter, there's already quite a bit of discussion about command-line rendering in this forum, like for this question. You do need scripting for such automation. Here's a script containing just the basics, elaborated below:

Script:

# Run as: blender -b <filename> -P <this_script> -- <image_path>
import bpy, sys, os

# Assume the last argument is image path
imagePath = sys.argv[-1]

if os.path.exists(imagePath):
    # Assume object, material and texture name (and settings) are valid
    charObj = bpy.data.objects['Char01']
    charMat = charObj.material_slots['Char01Mat'].material
    charTex = charMat.texture_slots['Char01Tex'].texture
    charTex.image.filepath = imagePath

    # Render to separate file, identified by texture file
    imageBaseName = bpy.path.basename(imagePath)
    bpy.context.scene.render.filepath += '-' + imageBaseName

    # Render still image, automatically write to output path
    bpy.ops.render.render(write_still=True)
else:
    print("Missing Image:", imagePath)

Invocation:

There are several arguments you need to pass, to make scripts like this work:

  • -b <fileName.blend> to load specified .blend file without UI,
  • -P <scriptName.py> to load and execute specified Python script,
  • -- prevents Blender from processing subsequent arguments,
  • <imagePath> will specify texture image file, to be read by the script.

For this discussion, let's say you save the script as render_script.py, and execute it like this:

blender -b char01.blend -P render_script.py -- tex01.png

Description:

The script itself just reads image file path, assigning it to appropriate texture, tweak some render settings, and then rendering the image.

Command-line arguments can be accessed in sys.argv, and by passing -1 index, we access the rightmost argument. In the example above, it's tex01.png.

After verifying that the path is valid, using os.path.exists(), the filename is passed as texture image path. This part assumes that the object exist, with material and texture already set, and names as specified.

The third step is to modify render output filepath, so that each render with different texture will be written to separate files. So if output is already set as //sprite, this particular render will be written as sprite-tex01.png.

Finally it just executes render() (making it automatically write still image), then exits quietly.

This script contains only the gist of what needs to be done, so I really suggest you to modify it to suit your need.

$\endgroup$
  • $\begingroup$ Suggest not to use try/except here, since it could hide real errors. Instead you could check the data exists and report an error if it doesn't, or - at the very least print a message when it fails. $\endgroup$ – ideasman42 Jun 17 '13 at 2:59
  • $\begingroup$ @ideasman42: You're right. Assumptions in this illustration code already spelled out, so it will do without catch-clause. That made it simpler, too. User can modify and add error-checking clause suitable for their needs. $\endgroup$ – Adhi Jun 17 '13 at 3:47
8
$\begingroup$

I have built a script that follows a set of instructions, getting lots of textures. The script is filling the sides of a cube with textures, with 2 methods, map4 cube faces and map1cubeface. It first looks for inside the directories of a folder, jpg images, and then allocate them to the faces, then unwrap, then render. Hope you understand.

Here you go:

import bpy
import os
from math import radians

bpy.ops.object.select_all(action='DESELECT')

# retrieve the active object
#bpy.ops.object.mode_set(mode = 'OBJECT')


def map_4_cube_faces():

    #call context into the object to be worked with
    bpy.context.scene.objects.active = bpy.data.objects["cube"]
    #attribute 4 images to 4 faces that were tracked
    for number in range(1, 5):
        #enter the context of activated material
        bpy.context.object.active_material_index = number 
        #attribute image to the material image_texture node

        bpy.data.materials[number-1].node_tree.nodes[1].image = bpy.data.images[str(number) + ".jpg"]
        #enter edit mode context
        bpy.ops.object.editmode_toggle()
        #select face that is assigned by the material
        bpy.ops.object.material_slot_select()
        #uv-unwrap image to the face
        bpy.ops.uv.unwrap(method='ANGLE_BASED', fill_holes=True, correct_aspect=True, use_subsurf_data=False, margin=0.001)
        #store last context to var last_area
        last_area = bpy.context.area.type
        #change context to UV Editing context
        bpy.context.area.type = 'IMAGE_EDITOR'
        bpy.ops.image.reload()
        #here it should rotate the texture by 180º, trial 1 o/ 
        bpy.ops.transform.rotate(value=(radians(180)), axis=(0, 0, -1), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)
        #returns to the last context stored on the var last area
        bpy.context.area.type = last_area
        #reset uv
        bpy.ops.uv.reset()
        #go back into object mode
        bpy.ops.object.editmode_toggle()

    bpy.ops.object.select_all(action='DESELECT')

def map_1_cube_face():
    #call context into the object to be worked with
    bpy.context.scene.objects.active = bpy.data.objects["cube"]
    for number in range(1, 5):
        #enter the context of activated material
        bpy.context.object.active_material_index=number 
        #attribute image to the material image_texture node

        bpy.data.materials[number-1].node_tree.nodes[1].image = bpy.data.images["1.jpg"]

        #enter edit mode context
        bpy.ops.object.editmode_toggle()
        #select face that is assigned by the material
        bpy.ops.object.material_slot_select()
        #uv-unwrap image to the face
        bpy.ops.uv.unwrap(method='ANGLE_BASED', fill_holes=True, correct_aspect=True, use_subsurf_data=False, margin=0.001)
        #store last context to var last_area
        last_area = bpy.context.area.type
        #change context to UV Editing context
        bpy.context.area.type = 'IMAGE_EDITOR'
        bpy.ops.image.reload()
        #here it should rotate the texture by 180º, but it doesn't    
        bpy.ops.transform.rotate(value=(radians(180)), axis=(0, 0, -1), constraint_axis=(False, False, False), constraint_orientation='GLOBAL', mirror=False, proportional='DISABLED', proportional_edit_falloff='SMOOTH', proportional_size=1, snap=False, snap_target='CLOSEST', snap_point=(0, 0, 0), snap_align=False, snap_normal=(0, 0, 0), release_confirm=False)
        #returns to the last context stored on the var last area
        bpy.context.area.type = last_area
        #reset uv
        bpy.ops.uv.reset()
        #go back into object mode
        bpy.ops.object.editmode_toggle()

    bpy.ops.object.select_all(action='DESELECT')


def set_top_color_render(type,file_path):
    sceneKey = bpy.data.scenes.keys()[0] 

    for i in range (0,7):
        if i == 0:
            bpy.data.objects["top"].active_material = bpy.data.materials["yellow"]
        if i == 1:
            bpy.data.objects["top"].active_material = bpy.data.materials["blue"]
        if i == 2:
            bpy.data.objects["top"].active_material = bpy.data.materials["white"]
        if i == 3:
            bpy.data.objects["top"].active_material = bpy.data.materials["orange"]
        if i == 4:
            bpy.data.objects["top"].active_material = bpy.data.materials["black"]
        if i == 5:
            bpy.data.objects["top"].active_material = bpy.data.materials["purple"]
        if i == 6:
            bpy.data.objects["top"].active_material = bpy.data.materials["red"]



        c=0
        for obj in bpy.data.objects: 
            # Find cameras that match cameraNames 
            if ( obj.type =='CAMERA') and ( obj.name == 'cam1' or obj.name == 'cam2') and type=="0": 
              #print("Rendering scene["+sceneKey+"] with Camera["+obj.name+"]") 

              # Set Scenes camera and output filename 
              bpy.data.scenes[sceneKey].camera = obj 
              bpy.data.scenes[sceneKey].render.image_settings.file_format = 'JPEG' 
              bpy.data.scenes[sceneKey].render.filepath = file_path+'/renders/topcolor'+str(i)+'-'+'camera_' + str(c) 

              # Render Scene and store the scene 
              bpy.ops.render.render( write_still=True ) 
              c = c + 1

            if ( obj.type =='CAMERA') and ( obj.name == 'cam1') and type=="1": 
              #print("Rendering scene["+sceneKey+"] with Camera["+obj.name+"]") 

              # Set Scenes camera and output filename 
              bpy.data.scenes[sceneKey].camera = obj 
              bpy.data.scenes[sceneKey].render.image_settings.file_format = 'JPEG' 
              bpy.data.scenes[sceneKey].render.filepath = file_path+'/renders/topcolor'+str(i)

              # Render Scene and store the scene 
              bpy.ops.render.render( write_still=True ) 
              c = c + 1








path="fill here the full path of your project"
for root, dirs, files in os.walk(path):
    if (len(dirs)!=0):
         for directory in dirs:
             if not "renders" in directory:
                 if "tp1" in directory:
                     file_path = os.path.join(root,directory)

                     for r,d,f in os.walk(file_path):
                         for im in f:
                            if not ".DS_Store" in im and not "top" in im:
                                bpy.ops.image.open(filepath=format(file_path)+"/"+im)

                     #map faces
                     map_4_cube_faces()
                     #set top color and render
                     set_top_color_render("0",file_path)

                 else:
                     file_path = os.path.join(root,directory)
                     for r,d,f in os.walk(file_path):
                         for im in f:
                             if not ".DS_Store" in im and not "top" in im:

                                 bpy.ops.image.open(filepath=format(file_path)+"/"+im)
                     #map faces
                     map_1_cube_face()
                     #set top color and render
                     set_top_color_render("1", file_path)

                 for r,d,f in os.walk(file_path):
                     for im in f:
                         if not ".DS_Store" in im and not "top" in im:

                             try:
                                 bpy.data.images[im].user_clear()
                                 can_continue = True
                             except:
                                 print ("tried user_clear() for "+im+" with no success")
                                 can_continue = False

                             if can_continue == True:
                                 try:
                                    bpy.data.images.remove(bpy.data.images[im])

                                 except:
                                    print ("tried remove() for "+im+" with no success")
$\endgroup$
4
$\begingroup$

You can also make use of an Image Texture Node that uses an image sequence as the source. Save all the images that are part of the sprite as image.0001.png, image.0002.png, etc.. and then you can browse to the folder and select the first image..

For the Image Texture Node specify the number of frames in the sequence and the start frame (usually 1)

With this as a starting point, you can further automate using the techniques described in the other answers.

Using an image sequence

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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