Sunday, July 11, 2010

Dropdown menu for selecting an object.

The script I'm going to write about is a script I made to test out dropdown boxes and the selection of objects from a script.

You can find the complete script here. I based my script on a script found in this forum post by Crouch. Be sure to download the newest Blender build from GraphicAll, because I spend much time trying to run Crouch's script, only to come to the conclusion that my Blender version was a few days to old.

I will try to explain my script below:


class OBJECT_PT_SelectObjects(bpy.types.Panel):
    '''
    Class to represent a panel that allows you to browse and select objects.
    '''
    bl_space_type = "PROPERTIES" #window type where the panel will be displayed
    bl_region_type = "WINDOW"
    bl_context = "object" #where to show panel in space_type
    bl_label = "Select Object" #panel name displayed in header

    def draw(self, context):
        '''
        Function used by blender to draw the panel.
        '''
        update() #update obj_list
        scene = context.scene
        layout = self.layout
        layout.prop(scene, "obj_list", text="Objects") #draw dropdown box on panel
        layout.separator()
        row = layout.row()
        row.operator("selectObject_button") #draw select button on panel
        row = layout.row()
        row.label(text="Active object is: " + bpy.context.active_object.name) #display the name of the active object

This piece of code defines a class for our panel that allows us to select different objects.

  • The 10th line defines that function that is responsible for drawing our buttons and text on the panel.
  • The 14th line calls the update function, described below, that updates the list with all the objects in the scene.
  • Line 17 draws the dropdown box on the panel. layout.prop() displays a property. scene is the type that has the property, and "obj_list" is the name of the property that we want to display. This property will be displayed as a dropdown box since this property is an enumeration.
  • Line 20 draws the select object button on the panel.
  • Line 22 draws the name of the current active object on the panel.


def update():
    '''
    Function used to update the objects list (obj_list) used by the dropdown box.
    '''
    objects = [] #list containing tuples of each object
    for index, object in enumerate(bpy.context.scene.objects): #iterate over all objects
        objects.append((str(index), object.name, str(index))) #put each object in a tuple and add this to the objects list

    #create an EnumProperty wich can be used by the dropdown box to display the differnt objects
    bpy.types.Scene.EnumProperty( attr="obj_list", name="Objects", description="Choose an object", items=objects, default='0')

This function creates or updates our obj_list enumeration property when it is called.

  • Line 5 contains the list of the objects, this list will be appended in the next lines, and then converted into the EnumProperty.
  • Lines 6 and 7 append the object list with tuples that contain the index and name of the different objects in the current scene.
  • Line 10 adds a EnumProperty to the Scene type. This custom property is referenced by "obj_list", and is made from the list of tuples of objects. Check this link if you want to read more about properties.


class CUSTOM_OT_SelectObjectButton(bpy.types.Operator):
    '''
    Class to represent a button that can be used to select and make active the object selected by the dropdown box.
    '''
    bl_idname = "selectObject_button" #name used to refer to this operator
    bl_label = "select" #button label
    bl_description = "Select the chosen object" #tooltip
    
    def invoke(self, context, event):
        '''
        Function used to process a click on the deselect button.
        '''
        obj = bpy.context.scene.objects[int(bpy.context.scene.obj_list)] #get the object selected by the dropdown box
        for object in bpy.data.objects: # deselect all objects
            object.selected = False
        obj.selected = True #select the object selected by the dropdown box
        bpy.context.scene.objects.active = obj #make the selected object the active object
        return{'FINISHED'}

This code defines the class for the select button. The object selected in the dropdown box will be selected and made active in blender after pressing this button.

  • Line 9 is the start of the function that will be invoke by Blender when the user clicks on the button.
  • Line 13 gets the index of the object selected by the dropdown box and uses this index to get a reference of the selected object, this reference is hold in obj.
  • Lines 14 and 15 make sure all the objects are deselected.
  • Line 16 selects our selected object in Blender.
  • Line 17 makes our selected object the active object.

Button for appending RX130 Robot

After I wrote the previous script, I tried to create a button to add a robot, from one of the pre-build robots I have access to, to a scene.

I experimented a bit with the link_append function to try adding multiple objects at once so any parent/child links would be maintained, but I couldn't find how to do this with link_append. After a suggestion by Pipeline, I created a new .blend file with the whole robot added to a single group and then I tried to append this group. This worked, and I noticed that now blender appended the different objects with parent/child links maintained. It imported the different objects, but it also imported the object as one large group which created duplicate meshes. So the only thing left was to unlink this group.

The code for this script is almost the same as my previous script, so I won't explain the whole script. You can find the script here and here. You can find the .blend file containing the rx130 robot in a single group here, notice that I didn't create this robot and that it is licensed through the LGPL license.

Below is the execute function that is called when a user presses the "Add rx130" button.


    def execute(self, context):
        '''
        Function to process a click on the "import Object" button
        '''
        bpy.ops.wm.link_append(directory="YOUR_DIRECTORY\\rx130.blend\\Group\\", link=False, filename="RX130") #append rx130 from blend file
        bpy.data.scenes['Scene'].objects.unlink(bpy.data.objects['RX130'])
        return{'FINISHED'}

  • The 5th line appends the group that contains all the meshes and armatures from the rx130 robot. Change "YOUR_DIRECTORY" to the directory where you downloaded the rx130.blend file.
  • The 6th line unlinks the duplicate group object.

I hope you like the script, and if anyone knows how to append/link objects with maintaining the parent/child links and without the needing of a group, contact me please. Or If you can explain why appending a group creates a duplicate object, I'm eager to know.

Friday, July 9, 2010

Toolbar button for appending an object.

The second script I wrote that might be interesting is a script that appends an object from a .blend file to the current scene. More specifically, this script adds a yellow submarine to the scene. You can find the code for the script here and here.

As example I used a .blend file with a yellow submarine that I made while I was learning blender with the help of this tutorial that I found on this webpage. You can find the .blend file that I used in this script here. (note: I joined all parts of the submarine (Submarine, Periscope, Propeller and Rudder) into 1 object that is called submarine, this was to simplify the script).

If you run the script, the Add Submarine button appears in the toolbar like this:

I based my script on the Monkify script by Crouch, which you can find in this forum post.

I will explain the code a bit below.


class OBJECT_OT_addSubmarine(bpy.types.Operator):
    '''
    Class representing an operator for adding a yellow submarine
    '''
    bl_idname = "addSubmarineButton" #name used to refer to this operator
    bl_label = "Add Submarine" #operator's label
    bl_description = "Add a yellow submarine" #tooltip

Like the first lines in the previous script, these lines define the new class for our button.

  • The 5th line defines the idname of the button which is used by Blender to refer to this button.
  • The 6th line defines the label of the button which will be shown as text on the button.
  • The 7th line defines a description for this button, this will be displayed as a tooltip when you hover with your mouse above the button.


def execute(self, context):
    '''
    Function to process a click on the "Add Submarine" button
    '''
        bpy.ops.wm.link_append(directory="YOUR_DIRECTORY\\yellowsubmarine.blend\\Object\\", filename="Submarine", link=False) #append submarine from .blend file
    return{'FINISHED'}

These lines define the the function that will be called by a click on the Add Submarine button

  • The 5th line calls the link_append function.
    • 'directory' is the directory of the object you want to append. Note that blend files contain subdirectories. You can get a view of this by going to File->Append or pressing SHIFT F1, and try to browse to the .blend file provided at the beginning of this post. To get your code working, substitute "YOUR_DIRECTORY" by the directory where the yellowsubmarine.blend file is saved. Note that windows uses backslashes to separate folders, this is an escape character, so you'll need to add an other backslash. On linux, use forward slashes.
    • 'filename' is the name of the object we are appending.
    • 'link' is a boolean that lets you append or link the object according to the True or False value. The difference of appending and linking by the blender wiki:
      Use Append if you want to make a local independent copy of the object inside your file.
      Select Link if you want a dynamic link made to the source file; if anyone changes the object in the source file, your current file will be updated the next time you open it.
  • The 6th line returns {'FINISHED'} to let Blender know that the click on the button is processed.


menu_func = (lambda self, context: self.layout.operator('addSubmarineButton'))
bpy.types.VIEW3D_PT_tools_objectmode.prepend(menu_func)
bpy.types.register(OBJECT_OT_addSubmarine)

  • The 1th line defines a new operator for the Add Submarine button.
  • The 2th line adds the Add Submarine button to the toolbar in the 3D View
  • The 3th line registers our Add Submarine button in Blender so it can refer to it

Wednesday, July 7, 2010

First script

The first Blender 2.5 Python script I wrote was to try out some simple scripting basics. You can find the code here or here.

If you load the script into the Blender text window and run the script, the panel will display under Object in the Properties window like this (note: the new panel can be at the bottom):

I used this and this forum post by Crouch and the panel_simple.py script, which you can find in the /scripts/templates folder in your Blender folder, as a basis for this script.

I will try to explain the code a bit below.


class OBJECT_PT_test1(bpy.types.Panel):
    '''
    Class to represent a testpanel in Blender.
    '''
    bl_space_type = "PROPERTIES" #Window type where the panel will be shown
    bl_region_type = "WINDOW"
    bl_context = "object" #Where to show panel in space_type
    bl_label = "Test Panel 1" #Panel name displayed in header

This piece of code defines our new testpanel class.

  • The 1th line defines a new class for representing our testpanel in Blender. It inherits from bpy.types.Panel.
  • The 2,3 and 4th lines give a little description about the class.
  • The 5th line defines the space_type where our panel will be shown. Other types are:
    ('EMPTY', 'VIEW_3D', 'GRAPH_EDITOR', 'OUTLINER', 'PROPERTIES', 'FILE_BROWSER', 'IMAGE_EDITOR', 'INFO', 'SEQUENCE_EDITOR', 'TEXT_EDITOR', 'AUDIO_WINDOW', 'DOPESHEET_EDITOR', 'NLA_EDITOR', 'SCRIPTS_WINDOW', 'TIMELINE', 'NODE_EDITOR', 'LOGIC_EDITOR', 'CONSOLE', 'USER_PREFERENCES')
  • The 6th line defines the region_type of the testpanel. Other types are:
    ('WINDOW', 'HEADER', 'CHANNELS', 'TEMPORARY', 'UI', 'TOOLS', 'TOOL_PROPS', 'PREVIEW')
    But not all of them work, you have to experiment with them yourself.
  • The 7th line defines the context where our panel will be shown, I guess these are all the different subpanels like 'Render', 'Scene', etc. where the panel will be shown.
  • The 8th line is the panel's label which will be shown in the panel's header.


def draw_header(self, context):
    '''
    Function used by blender to draw the header of the panel.
    '''
    layout = self.layout #Panel layout to draw on
    layout.label(text="Icon text", icon='RADIO') #Shows a icon and extra text before the panel title (bl_label)

This piece of code defines the function that draws the header of the testpanel.

  • The 1th line is used to define the draw_header method. Blender uses this predefined method to draw the header of the testpanel. This function has 2 parameters: self and context. 'Self' refers to object that called the this function, so this will be an object of our OBJECT_PT_test1 class. 'context' refers to the context in which the header is drawn.
  • The 2,3 and 4th are a little description about the function.
  • The 5th line gets the layout from the self parameter (which represents our panel object).
  • The 6th line show an icon and extra text in the header. To check out all the different default icons in Blender check out this forum post by Crouch.


def draw(self, context):
    '''
    Function used by blender to draw the panel.
    '''
    obj = bpy.context.active_object #Get the active object
    layout = self.layout #Panel layout to draw on

    row = layout.row() #Create new row
    row.label(text="The currently active object is: " + obj.name) #show name of the active object as text

    row = layout.row() #Create new row
    if obj.type == 'MESH':
        #show the number of vertices as text if the object is a Mesh structure
        row.label(text="It is a mesh containing " + str(len(obj.data.verts)) + " vertices.")
    else:
        #show the object type as text if the object isn't a Mesh structure
        row.label(text="it is a " + obj.type.capitalize() + ".")

    row = layout.row() #Create new row
    row.alignment = 'RIGHT' #Align the row to the right
    if bpy.context.mode == 'OBJECT':
        #Show a (de)select button if the object is in object mode
        if obj.selected:
            #If the object is selected show a deselect button
            row.operator("slctbtn", text="Deselect")
        else:
            #If the object is deselected show a select button
            row.operator("slctbtn", text="Select")

This code defines a function that lets Blender draw the panel.

  • The 5th line puts the active object in the variable 'obj'
  • The 8 and 9th lines create a new row on the panel, and draws text and the name of the active object on this line.
  • The 11-17th lines create a new row and if the active object is a Mesh type it draws the number of vertices on this row, otherwise it draws the type on the row.
  • Lines 19-28 create a new row and set the row alignment to the right. If Blender is in object mode it draws a (de)select button (which is defined at the bottom of the script) on this row. Line 25 draws an operator with idname "slctbtn" on the row. "Deselect" is the text that will be displayed on the button.


class OBJECT_OT_selectbtn(bpy.types.Operator):
    '''
    Class to represent a button that can be used to deselect an object in blender.
    '''
    bl_label = "slctbtn" #Button label
    bl_idname = "slctbtn" #Name used to refer to this operator
    bl_description = "(De)select the active object" # tooltip

    def invoke(self,context,event):
        '''
        Function used to process a click on the deselect button.
        '''
        context.object.selected = not context.object.selected #(de)select the object on a click on the button
        return{'FINISHED'}

This code defines a custom button operator which can be drawn onto a panel.

  • The 1th line defines the class for the custom button, it inherits from the operator class.
  • The 5th line is the label for the button.
  • The 6th line is the idname for the button. This idname is used to refer to the button.
  • The 7th line gives a description about the button. This is displayed as a tool tip when you hover with your mouse above the button.
  • Lines 9-14 define a function that is used by Blender to process a click on the button. The function changes the text from "select" to "deselect" when the active object is deselected or selected. The last line returns {'FINISHED'} to let Blender know that the click on the button is processed and done


bpy.types.register(OBJECT_PT_test1) #Register OBJECT_PT_test1 in blender
bpy.types.register(OBJECT_OT_selectbtn) #Register OBJECT_OT_selectbtn in blender

These lines are used to register our custom object so Blender can refer to them.

Starting with Blender 2.5 Python scripting.

I started Blender 2.5 Python scripting with these great video tutorials by Ira Krakow, and this introduction on the Blender wiki. Some great examples of Python scrips can be found in this forum post by Crouch, and some more info can be found here. The Blender 2.52 Python API, which is unstable and badly documented can be found here.

Getting to know Blender and Python

I had some previous experience in Python as it was the language which got me into programming two years ago. I learned Python through a great free online book called 'How to Think Like a Computer Scientist', if you're interested in learning how to program, be sure to check it out.
Blender 2.5 uses Python 3.0 for scripting, so in order to familiarize myself with Python 3.0 I went through the great, and also free, book 'Dive Into Python 3'.
As I had never used Blender before I spend the last few weeks trying to get to know the program. I watched some tutorials and followed some lessons from this website.

Welcome

Hi, and welcome to this blog.
My name is Peter, I'm a first year bachelor student in computer science, and the next month I will be doing an internship at the Catholic University of Leuven. Here Dr. Herman Bruyninckx gave me the opportunity to work at the Blender For Robotics project. This project is aimed at developing tools within Blender for simulating robots.
I created this blog to follow my progress at learning Python scripting in the new Blender 2.5. It's main purpose is to give me a 'diary' where I can look up different things I've discovered and learned while improving my English. I also hope that this blog can help other people who are also learning Python scripting in Blender.