Dynamic list of sliders

Hi, I’m trying to generate a GUI with a variable number of sliders. The amount of sliders depends on the number of bones on an armature that are named on a specific way. This is, if ANY bone starts with “SLI” a slider should be created for it.

Now I got something that seems to work partially…

import Blender 
from Blender import Draw, Scene, Window, Armature, Mathutils 
 
 g_slider = [] # slider objects to show up in GUI 
sliders = [] # bone names to create sliders after 

#Some constants:

EVENT = 400 
BASE_X = 10 
BASE_Y = 10 
SLI_width = 300 
SLI_height = 20 
DY = 0 
INIT = 0 
 
def DrawSlider(slider):
# returns a Slider Object (or that's what it is supposed to do) 
    global EVENT, BASE_X, DY, BASE_Y, SLI_width, SLI_height 
    Y = BASE_Y + DY * 40 
    newslider = Draw.Slider(slider, EVENT+1, BASE_X, Y, SLI_width,SLI_height,0.0,0.0,1.0) 
    return newslider 
 
def gui_event(evt, val):    # the function to handle input events 
  if evt == Draw.ESCKEY: 
    Draw.Exit()                 # exit when user presses ESC 
    return 

def button_event(evt):  # the function to handle Draw Button events
#crappy sample code... not really  important 
  if evt == 400: 
    print "You pressed one slider." 
  elif evt==401: 
    print "you pressed other slider" 

     
def gui_draw(): 
    global sliders, g_slider, INIT, DY 
    if INIT == 0: 
        # For each bone starting with SLI it creates a slider 
        for s in sliders: 
            DY = DY + 1 
            gui_slider = DrawSlider(s) 
            g_slider.append(gui_slider) 
        INIT = 1 
        print INIT 
        #Draw.Redraw(1) 
     
 
if INIT == 0: 
    scene = Scene.GetCurrent() 
    active = scene.objects.active     
    active_data = active.getData() 
    if type(active_data) == Blender.Types.ArmatureType: 
        if Window.PoseMode() == True: 
            armature_object = active 
            armature_data = armature_object.getData() 
            p = armature_object.getPose() 
            print p.bones 
            for key in p.bones.keys(): 
                if key[:3] == "SLI": #if 3 first characters... 
                    sliders.append(key) # appends bone name to a list
 
Draw.Register(gui_draw, gui_event, button_event) 

The problem with this code is that as soon as you click on a slider the GUI is cleaned and the sliders are gone. My question is how can I keep the sliders in the GUI ? I suppose that what is going on is that I’m failing at creating pointers to the slider objects… anybody knows ?

Thanks in advance !

Well, I already found out that I had it all wrong, will post the final version when I’m done. I missed the Draw.Create(0.0) for each element among other errors…

Good that you already figured it out yourself. I’m curious as to how you solved it, since I have the feeling that my solution is a bit dirty (exec isn’t really nice to use).

import Blender 
from Blender import Draw, Scene, Window, Armature, Mathutils 

g_slider = {} # slider objects to show up in GUI 
sliders = [] # bone names to create sliders after 

#Some constants:

EVENT = 400-1
BASE_X = 10 
BASE_Y = 10 
SLI_width = 300 
SLI_height = 20 
DY = 0 

def UpdateSlider(evt,val):
    global g_slider
    g_slider['newslider%s'%evt]=val
 
def DrawSlider(slider):
# returns a Slider Object (or that's what it is supposed to do) 
    global EVENT, BASE_X, DY, BASE_Y, SLI_width, SLI_height, g_slider
    Y = BASE_Y + DY * SLI_height 
    EVENT+=1
    if "newslider%s"%EVENT not in g_slider.keys(): 
        exec('newslider%s = Draw.Create(0.0)'%EVENT)
    else:
        value = g_slider['newslider%s'%EVENT]
        exec('newslider%s = Draw.Create(value)'%EVENT)
    exec('newslider%s = Draw.Slider(slider+": ", EVENT, BASE_X, Y, SLI_width,SLI_height, newslider%s.val,0.0,1.0,0,"Change slider",UpdateSlider)'%(EVENT,EVENT))
    exec('newslider_value = newslider%s.val'%EVENT)
    return "newslider%s"%EVENT, newslider_value
 
def gui_event(evt, val):    # the function to handle input events 
    if evt == Draw.ESCKEY: 
        Draw.Exit()              # exit when user presses ESC 
        return 

def button_event(evt):  # the function to handle Draw Button events
#crappy sample code... not really  important
    print "You moved",sliders[evt-400],"and set it to",g_slider['newslider%s'%evt]

     
def gui_draw(): 
    global sliders, g_slider, DY, EVENT
    # For each bone starting with SLI it creates a slider 
    for s in sliders: 
        DY = DY + 1 
        gui_slider, gui_slider_value = DrawSlider(s) 
        g_slider[gui_slider] = gui_slider_value
    DY = 0
    EVENT = 400-1
    #Draw.Redraw(1) 
     
 
scene = Scene.GetCurrent() 
active = scene.objects.active    
active_data = active.getData() 
if type(active_data) == Blender.Types.ArmatureType:
    if Window.PoseMode() == True: 
        armature_object = active 
        armature_data = armature_object.getData() 
        p = armature_object.getPose() 
        for key in p.bones.keys(): 
            if key[:3] == "SLI": #if 3 first characters... 
                sliders.append(key) # appends bone name to a list
 
Draw.Register(gui_draw, gui_event, button_event)

The assumption you made about the draw function is an easy one to make. Took a little trial and error on my part ot grok on to how the Registered (via Draw.Register) functions are used the first time I made a script with a UI.

It sounds like you have sorted this one out, but for anyone who does not already know what is (was) wrong…

The problem with the disappearing sliders is in the gui_draw function. As written once INIT is set to 1 (first call to gui_draw) the condition in the function stops the sliders “Draws” from being called.

The way Blender works is that the Registered function for gui drawing is called more than once. It’s actually called whenever an event is received (keyboard, mouse move, mouse click, …) or one of its Draw or Redraw functions are called. Blender creates an internal loop and does not exit the loop until Draw.Exit is called by the script.

I wrote simple version using bui. It’s a bit longer than versions shown above but should prove to be dynamic (handled renaming well in my tests). Note that it is still considered work in progress. :slight_smile:

You can find it at http://code.google.com/p/bui/source/browse/trunk/bones.py .

wow, thanks for the help guys ! I’m still struggling but closer…

Crouch: thanks ! your code works flawlessly though I don’t completely understand it, I’ll study it carefully…

FourMadMen: thanks that was really useful !

BeBraw: hey mate… what is THAT ?? what’s bui ? looks like really neat stuff…

malefico: It’s an abstraction layer that should make it a bit easier to write scripts. At the moment there are two other tiny test scripts available: filter_layers.py and simple.py. It will probably take a couple of weeks to get this in decent state (key mapping, tests, docs) but I don’t mind if you want to test it. :slight_smile:

OK this is my solution. I still like Crouch’s but can’t really understand all that exec stuff… maybe you could clarify it to me ?

I had horrible times trying to figure out this, and it’s probably the dumbest way of doing it…


import Blender 
from Blender import Draw, Scene, Window, Armature, Mathutils 
 
# Reads armature in Pose Mode and creates a slider for each bone with a name starting with SLI

# Variables
g_slider = [] # slider controls del GUI 
sliders = [] # names and other info for sliders (like X,Y) 
g_slider_vals=[] #  just the values that the sliders should pass 
 
#Some constants 
 
slider_EVENTS = 400  
BASE_X = 10 
BASE_Y = 10 
SLI_width = 300 
SLI_height = 20 
DY = 0 
X = 0 
 
 
def event(evt, val):    # the function to handle input events 
  if evt == Draw.ESCKEY: 
    Draw.Exit()                 # exit when user presses ESC 
    return 
 
def my_event(evt,val):  # callback function
# this function is the one that stores the values from the slider.
# shouldn't this be done automatically ???
  global g_slider 
  if evt == 400: 
     g_slider_vals[0] = val 
     print "Valor slider 1:", g_slider[0] 
  elif evt==401: 
     g_slider_vals[1] = val 
     print "Valor slider 2:", g_slider[1] 
  elif evt==402: 
    g_slider_vals[2] = val 
      print "Valor slider 3:", g_slider[2] 
  elif evt==403: 
    g_slider_vals[3] = val 
      print "Valor slider 4:", g_slider[3] 
  elif evt==404: 
    print "should not be printed" 
         
def button_event(evt):  # the function to handle Draw Button events
# you can happily ignore all this...
  global g_slider 
  if evt == 400: 
     print "Valor slider 1:", g_slider[0] 
  elif evt==401: 
     print "Valor slider 2:", g_slider[1] 
  elif evt==402: 
      print "Valor slider 3:", g_slider[2] 
  elif evt==403: 
      print "Valor slider 4:", g_slider[3] 
  elif evt==404: 
    print "should not be printed" 
 
         
def gui_draw(): 
    global g_slider, sliders, g_slider_vals 
    global SLI_width, SLI_height 
    
    # Draws the sliders...
 
    for i in range(len(g_slider_vals)): 
        g_slider[i] = Draw.Slider(sliders[i][0], sliders[i][1], sliders[i][2],sliders[i][3], SLI_width, SLI_height, g_slider_vals[i], 0.0, 1.0, 0,"A tooltip", my_event) 
    return 0 
 
def main(): 
    global g_slider_vals, sliders, g_slider 
    scene = Scene.GetCurrent() 
    active = scene.objects.active     
    active_data = active.getData() 
    if type(active_data) == Blender.Types.ArmatureType: 
        if Window.PoseMode() == True: 
            armature_object = active 
            armature_data = armature_object.getData() 
            p = armature_object.getPose() 
            index = 0 
            for key in p.bones.keys(): 
                if key[:3] == "SLI": #if 3 first characters... 
                    g_slider.append(Draw.Create(0.5))  # prepares memory for control
                    g_slider_vals.append(0.0)  # assign default values
                    Y = BASE_Y + index * 40 
                    EVENT = slider_EVENTS + index # generates event numbers
                    sliders.append((key, EVENT, BASE_X, Y)) #the name and coordinates of the slider are stored in a list 
                    index += 1 
 
main()     
 
Draw.Register(gui_draw, event, button_event)

If you remove the per button callbacks in Draw.Slider you can remove the function “my_event(evt,val)”.

Without the per button callbacks the slider events will be handled by the registered function “def button_event(evt)”. Subsequently, the sliders know their own value so storing them separately in “g_slider_vals” is unnecessary.

And finally, since you are using a base number for the slider events you can derive the slider index by subtracting the base number from the event number:

slider_idx = evt - slider_EVENTS
print “Valor slider”, slider_idx+1, “:”, g_slider[slider_idx].val

Actually I tried that first, but somehow I couldn’t retain the values from the sliders, this was the only way that worked for that. Of course, I must have done something wrong but couldn’t figure out what.:o

That’s interesting, I ran into the exact same problem as malefico did. Normally the buttons should retain their own values and the callback function (my_event in malefico’s version and Update_slider in mine) shouldn’t be needed. However leaving it out will reset the sliders to 0 when you move your cursor out of the script window.
So I had another look at the script malefico posted and I finally got it working correctly.

Some explanation of the things I changed:

  • I removed the callback function
  • All information for the sliders is now stored in one variable (sliders), with the following format: [ Button (the Draw.Create thing), name of the button, event, x, y ]
  • Changed some calls in the main function to use bpy instead of Blender (isn’t really necessary, but bpy is the newer module)
  • Added checks so the scripts will not run if the object isn’t an armature or isn’t in posemode
  • Turned the button_event into something that can handle any number of sliders. sliders[slider_EVENTS-evt] gives access to the list with all data concerning the changed slider, so sliders[slider_EVENTS-evt][0].val is the value of the slider and sliders[slider_EVENTS-evt][1] the name of the bone represented by the slider that is changed.
import bpy
from Blender import Draw, Window

# Reads armature in Pose Mode and creates a slider for each bone with a name starting with SLI

# Variables
sliders = []            # button, names and other info for sliders (like X,Y)

# Constants
slider_EVENTS = 400
BASE_X = 10
BASE_Y = 10
SLI_width = 300
SLI_height = 20

def event(evt, val):    # the function to handle input events
    if evt == Draw.ESCKEY:
        Draw.Exit()        # exit when user presses ESC

def button_event(evt):    # the function to handle Draw Button events
    global sliders
    print "Valor slider %s:"%(evt-slider_EVENTS+1), sliders[slider_EVENTS-evt][0].val

def gui_draw():
    global sliders, SLI_width, SLI_height
    # Draws the sliders...
    for i in range(len(sliders)):
        sliders[i][0] = Draw.Slider(sliders[i][1], sliders[i][2], sliders[i][3],sliders[i][4], SLI_width, SLI_height, sliders[i][0].val, 0.0, 1.0, 0,"A tooltip")

def main():
    global sliders
    scene = bpy.data.scenes.active
    active_object = scene.objects.active
    if active_object.type != "Armature":    # error checking, abort if necessary
        return "Active object isn't an armature"
    if Window.PoseMode() != True:            # error checking, abort if necessary
        return "Not in posemode"
    index = 0
    p = active_object.getPose()
    for key in p.bones.keys():
        if key[:3] == "SLI":                # if 3 first characters...
            Y = BASE_Y + index * 40
            EVENT = slider_EVENTS + index    # generates event numbers
            sliders.append([Draw.Create(0.0), key+": ", EVENT, BASE_X, Y]) #the name and coordinates of the slider are stored in a list
            index += 1
    return False

error = main()
if error:
    Draw.PupMenu("Error%t|"+error)
else:
    Draw.Register(gui_draw, event, button_event)

Thanks Crouch, this looks a lot better ! :slight_smile: