Posts Tagged ‘ wintab

Updated Pygame Tablet Pressure

This blog post has its own page:  Check there for latest info.

Updated to version 1.3:  Realized that I didn’t need to track mouse position and button-presses in the Tablet class:  Pygame does all this for you.  Solves a lot of problems.  Tablet object now only returns back pressure info, which is all we really want from it anyway.  The example ‘Pressure Test’ app has been updated as well to reflect this as well.

Tablet pressure sensitivity in Python and Pygame

This blog post has its own page:  Check there for latest info.

In Pygame, tablets (or at least my Wacom Bamboo) acts just like a mouse.  But it appears that Pygame, and the underlying SDL don’t detect tablet pressure. I’ve read that starting with SDL 1.3/2.0 it will detect for tablet pressure, but until then (currently v1.2)… what options are there?  It doesn’t seem that Python itself has any built-in modules for this either (that I can find…).

After asking the user groups, I tracked down an interface into wintab api through the “Python Computer Graphics Kit” (cgkit).   The API was developed by LCS/Telegraphics and maintained by Wacom:  cgkit has a wintab wrapper (docs) that appears to hook into Wacom standards.

But even with their docs, it took me a bit of troubleshooting to turn it into something usable, but I have:  ‘tablet.py’ which contains a ‘Tablet’ class, and a ‘pressureTest.py’ Pygame app showing it off:

  • cgkit download.  Required to run tablet.py.
  • tablet.py – Module containing the Tablet class.
  • pressureTest.py – Simple Pygame example using the Tablet class.
  • Built with Python 2.6.2, Pygame 1.9.1, cgkit 2.0.0 alpha9

Details:

tablet.py is a module containing a Tablet class, that will give you tablet querying functionality in your Pygame programs.  In a nutshell, you create a Tablet object, then inside the main loop you query your Tablet.getData() method instead of pinging pygame.mouse.  That method spits out this data:

  • The currently pressed pen ‘button’ as an int.
  • x,y tablet cursor position, relative to the current Pygame screen.
  • The tablet ‘pressure’ as an int, mapped from 0-1023

Expected behavior:

  • Given the example in pressureTest.py, on my machine, the tablet maps 100% of it’s surface region into the Pygame window:  It’s impossible for me to move the cursor outside of the Pygame window using the tablet (but the mouse still has external control).
  • However, based on other users, I’ve received reports of the tabletspace->pygame screen mapping not working, and their tablet still has free run of the whole computer screen.  This would cause problems, but unfortuntealy I have no way of testing this without other tablets\computers :(  If you run into this, you can try to troubleshoot:  Go to the wintab docs.  In the Tablet class, I’m querying these Context attrs for the tablet resolution, for mapping purposes back into pygame:  Context.inextx, Context.inexty.  There are many more attrs though, and those just happened to work for me.  If you try other attrs and they solve your problem, please let me know so I can update my code.

Here’s the few lines of code you’d use to put it in a Pygame app (illustrated in pressureTest.py):

from tablet import Tablet
tablet = Tablet(screen)
# Main loop:
while looping:
    button, x, y, pressure = tablet.getData()

This is the general implementation behind capturing the tablet data (crack open tablet.py to see what’s going on).  I should point out this was all by trial and error.  There could be better\more efficient\more eloquent ways of doing this.  But, this works :)

  • the wintab module needs to know the ‘window handle’ \ ‘window id’ for the current pygame app.  This isn’t something you query, but something you set.  The Tablet object makes up a window id, and sets it as an environment var:
    • os.environ[“SDL_WINDOWID”] = str(hwnd)
  • Create a new wintab Context object that will capture our tablet data:
    • self.context = wintab.Context()
  • Define what type of data our context will return via Packet objects.  These are based on wintab constants:
    • self.context.pktdata = ( PK_X | PK_Y | PK_BUTTONS | PK_NORMAL_PRESSURE )
    • It should be noted that the default values for pkdata (if you don’t define your own) are: xpos, ypos, buttons.
  • Open out context:
    • self.context.open(self.hwnd, True)
  • Then later we can query our Tablet object once per Pygame loop for Packet object data.  A LOT of packet data can be returned.  So we query the “extent” of the packets that were returned:
    • packetExtents = self.context.queuePacketsEx()
    • If the pen is away from the tablet, it will return None, so in that case, we don’t eval anything.
  • Presuming we found packet data, we the last packet list from the sub-list of packets that were returned:
    • packets = self.context.packetsGet(packetExtents[1])
  • And, we isolate the last packet in that sublist: (did I mention there were a lot of packets being returned?)
    • packet = packets[-1]
  • Based on that Packet object, we can now query it for which buttons were pressed, the x & y position, and the all important pressure data, via its attributes.
  • It’s important to note that Pygame has it’s (0,0) coordinate in the top-left corner of the screen, while the tablet has it in the bottom-left corner.  So the class does some hoop-jumping to convert the coordinates to Pygame.

The key takeaway is setting the Context.pktdata attribute:  This is the step that controls what is returned via the packets.  Depending on your tablet, a lot more info can be returned.