:mod:`spacedevice` --- Wrapper around the 3Dconnexion Developer's Kits
======================================================================

.. module:: cgkit.spacedevice
   :synopsis: Wrapper around the 3Dconnexion Developer's Kits


The :mod:`spacedevice` module allows applications to support 3D input devices
such as a SpaceMouse or a SpaceBall. The module wraps the 3DxWare SDK by
3Dconnexion. For a more detailed description of the SDK see the documentation
that is part of the `SDK <http://www.3dconnexion.com/sdk.htm>`_.

The module has to be used in conjunction with a GUI toolkit as there must be a
window that receives the 3D input device events. In principle, any GUI toolkit
can be used as long as it allows obtaining the native window handle and
accessing system events. The steps necessary to use a 3D input device are as
follows:

#. Create a :class:`SpaceDevice` object.

#. Open the device using the :meth:`open<SpaceDevice.open>` method. This requires a window handle
   of the window that will receive the SpaceDevice events.

#. For every system event received call the :meth:`translateWin32Event<SpaceDevice.translateWin32Event>` method
   and check if the event was generated by a SpaceDevice. If it was, check for the
   event type and process the event.

#. When the application terminates, call the :meth:`close` method to close the
   device.


.. function:: available()

   Returns ``True`` if the module functionality is available. Currently, this
   function will only return ``True`` under Windows and if the functionality was
   enabled during compilation.

   If this function returns ``False``, an exception will be raised whenever you try
   to instantiate a class from this module.


.. class:: SpaceDevice()

   The :class:`SpaceDevice` class provides the interface to the 3DxWare library
   that is used to communicate with the 3D input device. The constructor of this
   class calls the SDK function :cfunc:`SiInitialize`  to initialize the 3DxWare
   library. The destructor closes the device if it is open and calls
   :cfunc:`SiTerminate`.

.. method:: SpaceDevice.open(appname, hwnd, devID=SI_ANY_DEVICE)

   Establish a connection to a 3D input device. *appname* is a string containing
   the application name, *hwnd* is the native window handle  (as an integer) of the
   window that should receive the events. *devID* is the device id. An exception is
   thrown if a connection cannot be established.

   This method calls the SDK functions :cfunc:`SiOpenWinInit` and :cfunc:`SiOpen`.

.. method:: SpaceDevice.close()

   Close the connection to a device. The function returns immediately if there is
   no open connection.

   This method calls the SDK function :cfunc:`SiClose`.

.. method:: SpaceDevice.translateWin32Event(msgid, wparam, lparam)

   Translates a Win32 event into a SpaceDevice event. The return value is a tuple
   (*RetVal*, *EventType*, *Data*).

   *RetVal* is an object of type ``RetVal`` that represents an enumeration. It is
   ``RetVal.IS_EVENT`` if the event was generated by a 3D input device, otherwise
   it is ``RetVal.NOT_EVENT``.

   *EventType* is an object of type ``EventType`` that again represents an
   enumeration. It can take one of the following values:

   * ``EventType.BUTTON_EVENT``
   * ``EventType.MOTION_EVENT``
   * ``EventType.ZERO_EVENT``
   * ``EventType.EXCEPTION_EVENT``

   The contents of the third value, *Data*, depend on the event type:

     +---------------------+---------------------------------------+
     | Event type          | Data                                  |
     +=====================+=======================================+
     | ``BUTTON_EVENT``    | (*pressed*, *released*)               |
     +---------------------+---------------------------------------+
     | ``MOTION_EVENT``    | (*translation*, *rotation*, *period*) |
     +---------------------+---------------------------------------+
     | ``ZERO_EVENT``      | ``None``                              |
     +---------------------+---------------------------------------+
     | ``EXCEPTION_EVENT`` | ``None``                              |
     +---------------------+---------------------------------------+

   *pressed* and *released* are each lists that contain the numbers of the button
   that were either pressed or released. *translation* is a 3-tuple containing the
   translation vector and *rotation* is a 3-tuple containing the rotation vector.
   *period* contains the time in milliseconds since the last device event.

   This method calls the SDK functions :cfunc:`SiGetEventWinInit` and
   :cfunc:`SiGetEvent`.


.. method:: SpaceDevice.beep(s)

   Causes the device to emit a sequence of tones and pauses that is encoded in the
   string *s*. Lowercase letters represent a tone, uppercase letters represent a
   pause.  The closer the letter is to the beginning of the alphabet the shorter
   the pause or tone.

   This method calls the SDK function :cfunc:`SiBeep`.


.. method:: SpaceDevice.getDeviceID()

   Return the device id of the currently open device.

   This method calls the SDK function :cfunc:`SiGetDeviceID`.


.. method:: SpaceDevice.getDeviceInfo()

   Return information about the currently open device. The return value is a
   5-tuple (*device type*, *numButtons*, *numDegrees*, *canBeep*, *firmware*).

   This method calls the SDK function :cfunc:`SiGetDeviceInfo`.


.. method:: SpaceDevice.getDriverInfo()

   Return version information about the driver. The return value is a 5-tuple
   (*major*, *minor*, *build*, *versionstr*, *datestr*).

   This method calls the SDK function :cfunc:`SiGetDriverInfo`.


.. method:: SpaceDevice.getNumDevices()

   Return the number of input devices detected by the driver.

   This method calls the SDK function :cfunc:`SiGetNumDevices`.


.. method:: SpaceDevice.rezero()

   Causes the input device's current setting to be defined as the rest position.

   This method calls the SDK function :cfunc:`SiRezero`.

.. method:: SpaceDevice.setUIMode(show)

   Change the state of the driver menu window from within an application. The
   function has to be called before a call to :meth:`open`. *show* is a boolean
   that specifies if the driver menu should be displayed or not.

   This method calls the function :cfunc:`SiSetUiMode`.

.. note::

   The module uses the SDK by 3Dconnexion which can be found at
   `<http://www.3dconnexion.com/sdk.htm>`_. The following is the copyright
   information of the SDK:

   *(C) 1998-2001 3Dconnexion*

   *Permission to use, copy, modify, and distribute this software for all purposes
   and without fees is hereby granted provided that this copyright notice appears
   in all copies. Permission to modify this software is granted and 3Dconnexion
   will support such modifications only if said modifications are approved by
   3Dconnexion*


Example
-------

Here is a code example that uses pygame (you need at least version 1.7.1) as GUI
toolkit. It simply prints the input device events to the console. ::

   ######################################################################
   # SpaceDevice demo
   #
   # This demo demonstrates the usage of the SpaceDevice object in the
   # cgkit.spacedevice module which can be used to access events from
   # a SpaceMouse or SpaceBall. You can use this module to add support
   # for a SpaceDevice in your own Python application. The demo simply
   # prints the events generated from a SpaceDevice to the console.
   #
   # This demo uses pygame as GUI toolkit (v1.7.1 is required).
   # You can use any other GUI toolkit as long as it 1) lets you obtain
   # the native window handle of a window and 2) provides access to
   # system events.
   ######################################################################

   import sys
   import pygame
   from pygame.locals import *
   from cgkit import spacedevice

   # handleSystemEvent
   def handleSystemEvent(evt):
       """Handle a system event.

       evt is a pygame event object that contains a system event. The function
       first checks if the event was generated by a SpaceDevice and if it was,
       it prints the event data.
       """
       # sdev is the global SpaceDevice object
       global sdev

       # Translate the system event into a SpaceDevice event...
       res, evttype, data = sdev.translateWin32Event(evt.msg, evt.wparam, evt.lparam)
       # Check if the event actually was an event generated from
       # the SpaceMouse or SpaceBall...
       print res
       if res!=spacedevice.RetVal.IS_EVENT:
           return

       # Motion event?
       if evttype==spacedevice.EventType.MOTION_EVENT:
           t,r,period = data
           print "Motion: trans:%s rot:%s period:%d"%(t, r, period)
       # Button event?
       elif evttype==spacedevice.EventType.BUTTON_EVENT:
           pressed, released = data
           print "Button: pressed:%s released:%s"%(pressed, released)
       # Zero event?
       elif evttype==spacedevice.EventType.ZERO_EVENT:
           print "Zero"

   ######################################################################

   # Check if cgkit was compiled with SpaceDevice support...
   if not spacedevice.available():
       print "No SpaceDevice functionality available"
       sys.exit(1)

   # Initialize pygame...
   passed, failed = pygame.init()
   if failed>0:
       print "Error initializing pygame"
       sys.exit(1)

   # Open a window...
   pygame.display.set_caption("SpaceDevice demo")
   srf = pygame.display.set_mode((640,480))

   # Enable system events...
   pygame.event.set_allowed(SYSWMEVENT)

   # Initialize the Space Device...
   sdev = spacedevice.SpaceDevice()
   info = pygame.display.get_wm_info()
   hwnd = info["window"]
   sdev.open("Demo", hwnd)

   # Print some information about the driver and the device...
   major, minor, build, versionstr, datestr = sdev.getDriverInfo()
   print "Driver info:"
   print "------------"
   print "%s, v%d.%d.%d, %s\n"%(versionstr, major, minor, build, datestr)

   devtyp, numbuttons, numdegrees, canbeep, firmware = sdev.getDeviceInfo()
   print "Device info:"
   print "------------"
   print "Device ID:",sdev.getDeviceID()
   print "Type     :",devtyp
   print "#Buttons :",numbuttons
   print "#Degrees :",numdegrees
   print "Can beep :",canbeep
   print "Firmware :",firmware
   print ""

   # Event loop...
   running = True
   while running:

       # Get a list of events...
       events = pygame.event.get()

       # Process the events...
       for evt in events:

           # Close button?
           if evt.type==QUIT:
               running=False

           # Escape key?
           elif evt.type==KEYDOWN and evt.key==27:
               running=False

           # System event?
           elif evt.type==SYSWMEVENT:
               handleSystemEvent(evt)

   # Close the SpaceDevice
   sdev.close()