__all__ = ["instantiate"]
import threading
import math
import comtypes.client as client
from ctypes import *
from MessageInterpreter import MessageInterpreter
from MessageRegistry import theMessageRegistry
from RTFapi import Event, EventRef
from UserCustomization import UserCustomBase, CustomInfo
WantEventInformation = False
CUSTOM_NAV_KEY_EVENT_Doc = \
[(
"""
Keyboard navigation event.
"""
),
[[("upEvent"),("True if button up, False otherwise.")],
[("keyCode"),("Integer key code.")]
]
]
CUSTOM_NAV_SENSOR_EVENT_Doc = \
[(
"""
Sensor navigation event.
"""
),
[[("rotateX"),("rotateX")],
[("rotateY"),("rotateY")],
[("rotateZ"),("rotateZ")],
[("rotateAngle"),("rotateAngle")],
[("translateX"),("translateX")],
[("translateY"),("translateY")],
[("translateZ"),("translateZ")],
[("translateLength"),("translateLength")],
]
]
theMessageRegistry.register("CUSTOM_NAV_KEY_EVENT",
(bool,int), 0, CUSTOM_NAV_KEY_EVENT_Doc)
theMessageRegistry.register("CUSTOM_NAV_SENSOR_EVENT",
(float,float,float,float,float,float,float,float), 0, CUSTOM_NAV_SENSOR_EVENT_Doc)
class POINT(Structure):
_fields_ = [("x", c_long),
("y", c_long)]
class MSG(Structure):
_fields_ = [("hWnd" , c_ulong),
("message" , c_uint),
("wParam" , c_ulong),
("lParam" , c_ulong),
("time" , c_ulong),
("pt" , POINT)]
class CustomControllerThread(threading.Thread):
def __init__(self, listener):
threading.Thread.__init__(self)
self.__listener = listener
def GetMessage(self):
user32 = windll.user32
msg = MSG()
user32.GetMessageW(byref(msg), None, 0, 0)
return msg
def DispatchMessage(self,msg):
user32 = windll.user32
user32.DispatchMessageW(byref(msg))
def run(self):
while self.__listener.continueListening():
msg = self.GetMessage()
self.DispatchMessage(msg)
class SimpleSpaceNavigatorCustomInterpreter(MessageInterpreter):
kRotationX = 0
kRotationY = 1
kRotationZ = 2
kTranslationX = 3
kTranslationY = 4
kTranslationZ = 5
kRotationAngle = 6
kLength = 7
kNone = 8
kPanLeft = 0
kPanRight = 1
kPanUp = 2
kPanDown = 3
kZoomIn = 4
kZoomOut = 5
kTiltUp = 6
kTiltDown = 7
kSpinLeft = 8
kSpinRight = 9
kRollLeft = 10
kRollRight = 11
kTimerDelay = .25
def __init__(self,uiWindow,eventQueue):
MessageInterpreter.__init__(self)
self.__hasDocument = False
self.__uiWindow = uiWindow
self.__eventQueue = eventQueue
self.__transformInformation = [0,0,0, 0,0,0, 0,0]
self.__cameraButtonMap = {
self.kPanLeft : Event.BUTTON_KP_Left,
self.kPanRight : Event.BUTTON_KP_Right,
self.kPanUp : Event.BUTTON_KP_Page_Up,
self.kPanDown : Event.BUTTON_KP_Page_Down,
self.kZoomIn : Event.BUTTON_Up,
self.kZoomOut : Event.BUTTON_Down,
self.kTiltUp : Event.BUTTON_Page_Up,
self.kTiltDown : Event.BUTTON_Page_Down,
self.kSpinLeft : Event.BUTTON_Left,
self.kSpinRight: Event.BUTTON_Right,
self.kRollLeft : Event.BUTTON_Unknown,
self.kRollRight: Event.BUTTON_Unknown
}
self.__lastButtonKeycode = None
self.__threadTimer = None
self.__stickToLastMovement = True
def SET_DOCUMENT(self, message):
self.__hasDocument = True
def APPLICATION_CLOSE_SCENE( self, message ):
self.__hasDocument = False
def CUSTOM_NAV_KEY_EVENT(self,message):
(keyUp,keyCode) = message.data
if self.__documentOpen():
if keyUp and keyCode == Event.BUTTON_D:
self.sendMessage( 'FIT_TO_VIEW', ( True, ) )
def CUSTOM_NAV_SENSOR_EVENT(self,message):
( rotX, rotY, rotZ, rotAngle, X, Y, Z, Length ) = message.data
if WantEventInformation:
print "Rotation (%g,%g,%g) Angle %g Translation (%g,%g,%g) Length %g" %\
(rotX, rotY, rotZ, rotAngle, X, Y, Z, Length)
self.__handleSensorInput( rotX, rotY, rotZ, rotAngle, X, Y, Z, Length )
def __handleSensorInput( self, rotX, rotY, rotZ, rotAngle, X, Y, Z, Length ):
if self.__documentOpen():
self.__saveTransformationInformation(rotX, rotY, rotZ, rotAngle, X, Y, Z, Length)
(rotateIndex,rotateComponentValue,translateIndex,translateComponentValue) = self.__highPassFilterOnRotateTranslate()
inputHandled = False
if self.__stickToLastMovement:
if self.__lastButtonKeycode is not None:
self.__handleNewButtonDownMessage(self.__lastButtonKeycode )
inputHandled = True
if not inputHandled:
if translateIndex == self.kTranslationX:
self.__doPanRightOrLeft( translateComponentValue )
elif translateIndex == self.kTranslationY:
self.__doPanUpOrDown( translateComponentValue )
elif translateIndex == self.kTranslationZ:
self.__doZoomInOrOut( translateComponentValue )
else:
if rotateIndex == self.kRotationX:
self.__doTilt( rotateComponentValue )
elif rotateIndex == self.kRotationY:
self.__doSpin( rotateComponentValue )
elif rotateIndex == self.kRotationZ:
self.__doRoll( rotateComponentValue )
else:
self.__endPress()
if self.__threadTimer is not None:
self.__threadTimer.cancel()
del self.__threadTimer
self.__threadTimer = threading.Timer( self.kTimerDelay, self.__endPress )
self.__threadTimer.start()
def __endPress(self):
if self.__lastButtonKeycode is not None:
self.__dispatchEvent( Event.BUTTONUP,self.__lastButtonKeycode)
self.__lastButtonKeycode = None
def __handleNewButtonDownMessage( self, buttonAction ):
if self.__lastButtonKeycode != buttonAction:
self.__endPress()
self.__dispatchEvent( Event.BUTTONDOWN,buttonAction)
self.__lastButtonKeycode = buttonAction
def __doPanRightOrLeft(self, translateX ):
if WantEventInformation:
print "\tpan left/right: %g" % translateX
buttonAction = self.__cameraButtonMap[ self.kPanLeft ]
if translateX < 0:
buttonAction = self.__cameraButtonMap[ self.kPanRight ]
self.__handleNewButtonDownMessage( buttonAction )
def __doPanUpOrDown(self, translateY ):
if WantEventInformation:
print "\tpan up/down: %g" % translateY
buttonAction = self.__cameraButtonMap[ self.kPanUp ]
if translateY < 0:
buttonAction = self.__cameraButtonMap[self.kPanDown ]
self.__handleNewButtonDownMessage( buttonAction )
def __doZoomInOrOut(self, translateZ ):
if WantEventInformation:
print "\tzoom in/out: %g" % translateZ
buttonAction = self.__cameraButtonMap[ self.kZoomOut ]
if translateZ < 0:
buttonAction = self.__cameraButtonMap[ self.kZoomIn ]
self.__handleNewButtonDownMessage( buttonAction )
def __doTilt(self, rotateX ):
if WantEventInformation:
print "\ttilt: %g" % rotateX
buttonAction = self.__cameraButtonMap[ self.kTiltUp ]
if rotateX < 0:
buttonAction = self.__cameraButtonMap[ self.kTiltDown ]
self.__handleNewButtonDownMessage( buttonAction )
def __doSpin(self, rotateY ):
if WantEventInformation:
print "\tspin: %g" % rotateY
buttonAction = self.__cameraButtonMap[ self.kSpinLeft ]
if rotateY < 0:
buttonAction = self.__cameraButtonMap[ self.kSpinRight ]
self.__handleNewButtonDownMessage( buttonAction )
def __doRoll(self, rotateZ ):
if WantEventInformation:
print "\troll : %g" % rotateZ
def __documentOpen(self):
return self.__hasDocument
def __saveTransformationInformation(self, rotX, rotY, rotZ, rotAngle, X, Y, Z, Length):
self.__transformInformation[self.kRotationX] = rotX
self.__transformInformation[self.kRotationY] = rotY
self.__transformInformation[self.kRotationZ] = rotZ
self.__transformInformation[self.kTranslationX] = X
self.__transformInformation[self.kTranslationY] = Y
self.__transformInformation[self.kTranslationZ] = Z
self.__transformInformation[self.kRotationAngle] = rotAngle
self.__transformInformation[self.kLength] = Length
def __highPassFilterOnRotateTranslate(self):
def applyFilterToComponents(startPosition,endPosition):
for i in range(startPosition, endPosition+1):
if self.__transformInformation[i] != 0:
break
else:
return (self.kNone,0)
maxAbsPosition = startPosition
for i in range(startPosition+1, endPosition+1):
currentMaxAbsValue = math.fabs( self.__transformInformation[i] )
if currentMaxAbsValue > math.fabs( self.__transformInformation[maxAbsPosition] ):
maxAbsPosition = i
return (maxAbsPosition,self.__transformInformation[maxAbsPosition])
translateResult = applyFilterToComponents( self.kTranslationX, self.kTranslationZ )
rotateResult = applyFilterToComponents( self.kRotationX, self.kRotationZ )
return rotateResult + translateResult
def __dispatchEvent(self, direction, key):
if self.__eventQueue:
event = Event(direction, key)
eventRef = EventRef(event)
self.__eventQueue.queueEvent(event)
class SimpleSpaceNavigatorCustom(UserCustomBase):
def __init__(self):
self.__myMenu = None
self.__myMenuItemId = None
self.__keyboard = None
self.__sensor = None
self.__exit = False
self.__types = {
0 : "Unknown",
6 : "SpaceNavigator",
4 : "SpaceExplorer",
25 : "SpaceTraveller",
29 : "SpacePilot"
}
def getInterpreter(self, isInteractive):
if isInteractive:
return self.__myInterpreter
return None
def inputDevices( self, uiWindow, eventQueue ):
self.__myInterpreter = SimpleSpaceNavigatorCustomInterpreter( uiWindow, eventQueue )
self.__initializeInputDevice()
def __initializeInputDevice(self):
try:
device = client.CreateObject("TDxInput.Device")
except:
return
if device.Type in self.__types:
print self.__types[device.Type],
else:
print "Unknown Device",
if not device.Connect():
print ": Connected"
self.__keyboard = device.Keyboard
client.GetEvents(self.__keyboard, self)
self.__sensor = device.Sensor
client.GetEvents(self.__sensor, self)
deviceThread = CustomControllerThread(self)
deviceThread.start()
def continueListening(self):
return not self.__exit
def KeyDown(self, this, code):
if WantEventInformation:
print "SpaceNavigator.onKeyPress(%s)" % code
self.sendMessage( 'CUSTOM_NAV_KEY_EVENT', ( True,code ) )
def KeyUp(self, this, code):
if WantEventInformation:
print "SpaceNavigator.onKeyRelease(%s) " % code
self.sendMessage( 'CUSTOM_NAV_KEY_EVENT', ( True,code ) )
def SensorInput(self, this, inval=None):
rotX = self.__sensor.Rotation.X
rotY = self.__sensor.Rotation.Y
rotZ = self.__sensor.Rotation.Z
rotAngle = self.__sensor.Rotation.Angle
X = self.__sensor.Translation.X
Y = self.__sensor.Translation.Y
Z = self.__sensor.Translation.Z
Length = self.__sensor.Translation.Length
self.sendMessage( "CUSTOM_NAV_SENSOR_EVENT", (rotX, rotY, rotZ, rotAngle, X, Y, Z, Length) )
def instantiate():
return SimpleSpaceNavigatorCustom()
def info():
customInfo = CustomInfo()
customInfo.vendor = 'Autodesk'
customInfo.version = '3.0'
customInfo.api = '2013'
customInfo.shortInfo = "Add-in of a device controller interfacing with 3Dconnexion Space Navigator."
customInfo.longInfo = \
"""The sensor output is converted to Showcase mouse keys and then button down and button up events \
are created and dispatched into the Showcase event queue.
Supported functionality:
- Left button will Fit To View (3Dconnexion Panel button setting must be 'Fit')
- Pan left/right
- Pan up/down
- Zoom up/down
- Tilt up/down
- Spin left/right
"""
return customInfo