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?

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

Your Answer

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.