0

I am currently developing a drawing interface that interacts with an XYZ machine (plotter) that uses a Wacom tablet. X and Y are related to pen coordinates on the screen, and Z should be the pen pressure. So I am trying to access the data sent by the wacom tablet while holding the pen.

My program is running python with pygame on a windows machine.

I found a way to access tablet raw data with pywinusb.hid, like so:

import pywinusb.hid as hid
# detect tablet
device = None
my_tablet = "PTK-440"
for dev in hid.find_all_hid_devices():
 if my_tablet == dev.product_name:
 device = dev
 break
# get data
def on_data(data):
 log.info(f"tablet data: {data}")
if device:
 try:
 self.device.open()
 self.device.set_raw_data_handler(on_data)
 except Exception as err:
 log.error(err)

This is what I get at each frame:

[213, 32, 124, 92, 152, 81, 134, 3, 0, 0, 2, 8, 16, 0, 253, 32, 128, 156, 2, 8, 16, 0, 0, 0, 0, 0, 0, 0, 96, 9, 20, 5, 156, 24, 180, 45, 0, 0]

I did not find any documentation related to the raw data.

So I tried different approaches

  • Wacom gives access to an SDK but not for python users
  • using Wintab and ctypes seems a good idea, but it is way too complicated for me.
  • I am also aware of the cgkit/cgkit2 module combined with pygame but the module is deprecated for a while now.
  • I know Qt has an event handler for such purpose (QtTabletEvent), but it requires implementing another threaded loop, which is overcomplicated as well.

My try with Wintab:

import ctypes
from ctypes import wintypes
import pygame
from typing import List
from livegcode import log, GUI, PATH
from livegcode.utils import TimeOut
class LOGCONTEXT(ctypes.Structure):
 _fields_ = [
 ("lcName", ctypes.c_char * 40),
 ("lcOptions", ctypes.c_uint),
 ("lcStatus", ctypes.c_uint),
 ("lcLocks", ctypes.c_uint),
 ("lcMsgBase", ctypes.c_uint),
 ("lcDevice", ctypes.c_uint),
 ("lcPktRate", ctypes.c_uint),
 ("lcPktData", ctypes.c_uint),
 ("lcPktMode", ctypes.c_uint),
 ("lcMoveMask", ctypes.c_uint),
 ("lcBtnDnMask", ctypes.c_uint),
 ("lcBtnUpMask", ctypes.c_uint),
 ("lcInOrgX", ctypes.c_long),
 ("lcInOrgY", ctypes.c_long),
 ("lcInOrgZ", ctypes.c_long),
 ("lcInExtX", ctypes.c_long),
 ("lcInExtY", ctypes.c_long),
 ("lcInExtZ", ctypes.c_long),
 ("lcOutOrgX", ctypes.c_long),
 ("lcOutOrgY", ctypes.c_long),
 ("lcOutOrgZ", ctypes.c_long),
 ("lcOutExtX", ctypes.c_long),
 ("lcOutExtY", ctypes.c_long),
 ("lcOutExtZ", ctypes.c_long),
 ("lcSensX", ctypes.c_long),
 ("lcSensY", ctypes.c_long),
 ("lcSensZ", ctypes.c_long),
 ("lcSysMode", ctypes.c_bool),
 ("lcSysOrgX", ctypes.c_int),
 ("lcSysOrgY", ctypes.c_int),
 ("lcSysExtX", ctypes.c_int),
 ("lcSysExtY", ctypes.c_int),
 ("lcSysSensX", ctypes.c_long),
 ("lcSysSensY", ctypes.c_long)
 ]
class WintabDefinitions:
 def __init__(self):
 self.wintab = ctypes.WinDLL('Wintab32.dll') # load wintab dll
 self.window = self._get_pygame_handle_window()
 self.context = self._init_wintab_context()
 def _get_pygame_handle_window(self):
 """ Get the window handle from pygame """
 return pygame.display.get_wm_info()['window']
 def close_wintab_context(self):
 self.wintab.WTClose(self.context)
 def _init_wintab_context(self):
 self._define_functions_prototypes()
 log_context = LOGCONTEXT()
 log_context.lcPktData = 0x0004 | 0x0008 | 0x0010 # Include X, Y, and pressure in packets 
 context = self.wintab.WTOpenA(self.window, ctypes.byref(log_context), True)
 if context == 0:
 log.error("Failed to initialize Wintab context.")
 return None
 return context
 
 def _define_functions_prototypes(self):
 HCTX = wintypes.HANDLE
 self.wintab.WTOpenA.argtypes = [wintypes.HWND, ctypes.POINTER(LOGCONTEXT), wintypes.BOOL]
 self.wintab.WTOpenA.restype = HCTX
 self.wintab.WTClose.argtypes = [HCTX]
 self.wintab.WTClose.restype = wintypes.BOOL
 self.wintab.WTPacket.argtypes = [HCTX, wintypes.UINT, ctypes.POINTER(ctypes.c_void_p)]
 self.wintab.WTPacket.restype = wintypes.BOOL
class TabletWacom(WintabDefinitions):
 
 def __init__(self):
 super().__init__()
 
 def get_tablet_data(self):
 log.info(self.context)
 if self.context:
 packet = self.wintab.WTPacketsGet(self.context, 1)
 if packet:
 x = packet.pkX
 y = packet.pkY
 pressure = packet.pkNormalPressure
 print(f"Tablet Data: X={x}, Y={y}, Pressure={pressure}")
 def close(self):
 self.close_wintab_context()

Which results with a bad context.

Does someone have an idea for resolving this problem?

TylerH
21.3k85 gold badges84 silver badges122 bronze badges
asked Aug 31, 2024 at 11:58
4
  • It is not possible to provide anything more than an opinion-based response, given your question lacks sufficient sample data and a proper definition of what an acceptable answer might be. Questions that ask for general guidance regarding a problem approach are typically are not a good fit for this site. Split your task into smaller ones and start working on them. And ask particular questions related to particular issues you encounter. Here's how: how-to-ask Commented Aug 31, 2024 at 13:15
  • 1
    @itprorh66 I don't see how this is an opinion-based question. If the question lacks necessary data to answer, then it's possible "in need of details/clarity" or "lacking an MCVE/MRE". But that doesn't make it opinion-based. Commented Sep 3, 2024 at 21:32
  • Maybe it's Wacom-specific, but what do you mean by "results in a bad context"? What does "context" refer to here? Commented Sep 3, 2024 at 21:32
  • Thanks! The context is the structure that defines the parameters and environment in which the tablet interacts with my program. It delivers me a packet with the data I asked for. Mine is not configured properly, which is not surprising, it seems too 'low' for me. I would expect it to be already handled in a module, like cgkit2 did but not a deprecated one. Note, I didn't find any! I understood itprorh66 comment since my question is a bit of a call for suggestions. At some point I just felt I was trying to reinvent the wheel. Commented Sep 4, 2024 at 13:58

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.