Xbox controller or joystick as input - mapping values to armature (NOT game engine)

I’m trying to figure out how to use a joystick controller as an input device to control an armature. The idea is to assign the joystick (perhaps as a driver?) to a joint or bone and create a kind of puppeteering system where the keyframes get recorded in real time.

So imagine the left joystick x direction maps to the x rotation of the bone, and the y direction controls the x rotation.

I found a python library called Inputs https://pypi.org/project/inputs/
but when I run it in Blender 2.9 it will show the results in the console, but it basically freezes Blender… and i don’t know how to grab the values and map them to my joint rotation position.

here is the code:

"""Simple example showing how to get gamepad events."""
#https://pypi.org/project/inputs/

from __future__ import print_function


from inputs import get_gamepad
myVal = 0

def main():
    """Just print out some event infomation when the gamepad is used."""
    while 1:
        events = get_gamepad()
        for event in events:
            if event.code == 'ABS_X':
                print('X position RAW: ')
                print(event.state)

                #need to add sume kind of conversion factor to map absolute values to degrees of rotation
                #need a way to store the values in a variable that Blender can access
            if event.code == 'ABS_Y':
                print('Y position RAW: ')
                print(event.state)

        #for event in events:
        #    print(event.ev_type, event.code, event.state)


if __name__ == "__main__":
    main()

your input system is running in the main thread, and since you have an infinite loop (while 1) it will stay in that loop forever- which means Blender will freeze. You need to run this code in a separate thread- but be careful not to do anything Blender related in the input thread, as bpy is not particularly thread-safe. A safe way to do it would be to have your input thread update an intermediate dataclass that can be accessed by the main thread to get the current (or last known) “state”.

1 Like

Thank you for the input! This is definitely new territory for me, I have a little, but limited python experience… most of my coding involves hacking other things together, modifying things till they break then figuring it out…

I’ll need to find a tutorial on the “intermediate dataclass” approach you mentioned… I don’t know how to do that.

you probably won’t find a tutorial for it, it’s more of a design concept than anything else. just create a class or even just some loose variables to store the current state. the input thread would write the state to those variables, and the main thread would do stuff with the input state. the key takeaway here is not to try and manipulate Blender from the second thread (inside your input loop, for example). Things can get very crashy.

1 Like

I did a little bit of searching and seams like what I want is a Modal Operator?

I found this bit for using a mouse input… but I’m still struggling to get this to work… I am now getting the joystick input but only when the mouse moves…

from __future__ import print_function
from inputs import get_gamepad
myVal = 0
import bpy


class ModalOperator(bpy.types.Operator):
    bl_idname = "object.modal_operator"
    bl_label = "Simple Modal Operator"

    def __init__(self):
        print("Start")

    def __del__(self):
        print("End")

    def execute(self, context):
        context.object.location.x = self.value / 100.0
        return {'FINISHED'}

    def modal(self, context, event):
        events = get_gamepad()
        for action in events:
            if action.code == 'ABS_X':
                print('X position RAW: ')
                #print(action.state)
                myVal = action.state/30000*360
                print(myVal)
            
        if event.type == 'MOUSEMOVE':  # Apply
            self.value = event.mouse_x
            self.execute(context)

            
        elif event.type == 'LEFTMOUSE':  # Confirm
            return {'FINISHED'}
        elif event.type in {'RIGHTMOUSE', 'ESC'}:  # Cancel
            context.object.location.x = self.init_loc_x
            return {'CANCELLED'}

        return {'RUNNING_MODAL'}

    def invoke(self, context, event):
        self.init_loc_x = context.object.location.x
        self.value = event.mouse_x
        self.execute(context)

        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


bpy.utils.register_class(ModalOperator)

# test call
bpy.ops.object.modal_operator('INVOKE_DEFAULT')

what kind of trouble are you running into? a specific error, or is nothing happening? IE) is your print('X position RAW: ') not happening? if it’s the former, post the error- if it’s the latter try dumping the contents of events to see if it even has anything in it. I’m not familiar with the input module you’re using, but it might have some way of querying a gamepad to see if it can even detect it as a valid input device, there might be some setup step that’s being missed somewhere.