3 # Python Input Module for NumptyPhysics
4 # Copyright (c) 2009 Thomas Perl <thpinfo.com>
6 # This program is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU General Public License as
8 # published by the Free Software Foundation; either version 3 of the
9 # License, or (at your option) any later version.
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # General Public License for more details.
25 # Create the file "use-ipod" if you want to use an iPod Touch
26 # and its OSCemote application for TUIO input (rm it if you don't)
27 use_ipod
= os
.path
.exists('use-ipod')
29 # Create the file "use-mtmini" if you want to show cursor previews
30 use_mtmini
= os
.path
.exists('use-mtmini')
32 CURSOR_ACTIVATE_DELAY
= .01
33 CURSOR_UPGRADE_DELAY
= .5
34 CURSOR_STOP_DELAY
= .5
36 CURSOR_NEIGHBOR_DISTANCE
= .2
40 except ImportError, ioe
:
41 print >>sys
.stderr
, """
42 This module can only be loaded from within NumptyPhysics.
48 except ImportError, ioe
:
49 print >>sys
.stderr
, """
50 You have to install PyTUIO and copy it into your $PYTHONPATH.
51 You can grab a tarball from: http://code.google.com/p/pytuio/
55 CURSOR_NEIGHBOR_DISTANCE
*= numptyphysics
.HEIGHT
57 class NumpytTuioCursor(object):
58 def __init__(self
, id, x
, y
):
62 self
.time
= time
.time()
64 class NumptyCursor(object):
65 DRAW
, DRAG
, DELETE
= range(3)
67 def __init__(self
, tuio_id
, x
, y
, on_activate
, on_deactivate
, on_move
, on_upgrade
):
72 self
.tuio_ids
= {tuio_id
: time
.time()}
74 self
.activated
= False
75 self
.deactivated
= False
79 self
.on_activate
= on_activate
80 self
.on_deactivate
= on_deactivate
81 self
.on_move
= on_move
82 self
.on_upgrade
= on_upgrade
84 self
.activate_at
= time
.time()+CURSOR_ACTIVATE_DELAY
85 self
.upgrade_at
= time
.time()+CURSOR_UPGRADE_DELAY
86 self
.deactivate_at
= 0
87 print 'new numpty cursor for', tuio_id
91 self
.deactivate_at
= time
.time()+CURSOR_STOP_DELAY
93 def is_near(self
, x
, y
):
94 return ((x
-self
.x
)**2 + (y
-self
.y
)**2)**.5 < CURSOR_NEIGHBOR_DISTANCE
97 if self
.activated
and not self
.deactivated
:
98 print 'moved', self
.id, 'to', x
, '/', y
, '(which is a', self
.mode
, ')'
99 if self
.x
!= x
or self
.y
!= y
:
104 def want_new_cursors(self
):
105 if self
.mode
== self
.DRAW
:
106 return len(self
.tuio_ids
) < 1
107 elif self
.mode
== self
.DRAG
:
108 return len(self
.tuio_ids
) < 2
109 elif self
.mode
== self
.DELETE
:
110 return len(self
.tuio_ids
) < 3
113 def seen_tuio_id(self
, id, x
, y
):
115 # If this cursor is gone, it's gone
118 if not self
.upgraded
and self
.is_near(x
, y
) and id not in self
.tuio_ids
:
119 # this cursor it not yet upgraded; the tuio id is near
120 if self
.mode
== self
.DRAW
:
121 print 'absorbing and setting to drag', id
122 self
.mode
= self
.DRAG
123 elif self
.mode
== self
.DRAG
:
124 print 'absorbing and setting to delete', id
125 self
.mode
= self
.DELETE
129 self
.tuio_ids
[id] = time
.time()
133 if self
.upgraded
and self
.want_new_cursors() and self
.is_near(x
, y
) and id not in self
.tuio_ids
:
134 # i can take more cursors, so absorb this
135 self
.tuio_ids
[id] = time
.time()
136 if id == min(self
.tuio_ids
) and self
.id:
140 elif id in self
.tuio_ids
:
141 self
.tuio_ids
[id] = time
.time()
142 if id == min(self
.tuio_ids
) and self
.id:
150 self
.activated
= True
151 self
.on_activate(self
)
155 self
.on_upgrade(self
)
156 if self
.mode
== self
.DELETE
:
159 def deactivate(self
):
160 self
.deactivated
= True
162 self
.on_deactivate(self
)
169 if time
.time() > self
.deactivate_at
and not self
.deactivated
:
171 elif time
.time() > self
.upgrade_at
and not self
.upgraded
and not self
.deactivated
:
173 elif time
.time() > self
.activate_at
and not self
.activated
and not self
.upgraded
and not self
.deactivated
:
175 elif self
.activated
and self
.moved
:
181 for id, t
in self
.tuio_ids
.items():
182 if t
+.5*CURSOR_STOP_DELAY
< time
.time():
183 del self
.tuio_ids
[id]
186 class CursorTracker(object):
189 self
._freeslots
= collections
.deque(range(numptyphysics
.MAX_CURSORS
))
190 self
._highest
_id
= -1
191 self
.events
= collections
.deque()
193 def activate_cursor(self
, cursor
):
194 cursor
.id = self
.grabslot()
195 if cursor
.mode
== cursor
.DRAW
:
196 self
.push_event(cursor
, numptyphysics
.START_STROKE
)
197 elif cursor
.mode
== cursor
.DRAG
:
198 self
.push_event(cursor
, numptyphysics
.START_DRAG
)
199 elif cursor
.mode
== cursor
.DELETE
:
200 self
.push_event(cursor
, numptyphysics
.DELETE
)
202 def deactivate_cursor(self
, cursor
):
203 if cursor
.mode
== cursor
.DRAW
:
204 self
.push_event(cursor
, numptyphysics
.FINISH_STROKE
)
205 elif cursor
.mode
== cursor
.DRAG
:
206 self
.push_event(cursor
, numptyphysics
.END_DRAG
)
207 self
.freeslot(cursor
.id)
209 def cursor_movement(self
, cursor
):
210 if cursor
.mode
== cursor
.DRAW
:
211 self
.push_event(cursor
, numptyphysics
.APPEND_STROKE
)
212 elif cursor
.mode
== cursor
.DRAG
:
213 self
.push_event(cursor
, numptyphysics
.DRAG
)
215 def upgrade_cursor(self
, cursor
):
216 if cursor
.mode
== cursor
.DRAW
:
217 # "draw" cursors do not need to be upgraded
220 # cancel the in-progress draw event + start "real" event
221 self
.push_event(cursor
, numptyphysics
.CANCEL_DRAW
)
222 if cursor
.mode
== cursor
.DRAG
:
223 self
.push_event(cursor
, numptyphysics
.START_DRAG
)
224 elif cursor
.mode
== cursor
.DELETE
:
225 self
.push_event(cursor
, numptyphysics
.DELETE
)
227 def update(self
, cursors
):
229 for cursor
in cursors
:
230 id, x
, y
= cursor
.sessionid
, cursor
.xpos
, cursor
.ypos
231 if x
== 0 and y
== 0:
235 x
, y
= self
.convert_coords(x
, y
)
237 self
.push_preview_event(id, x
, y
)
240 for c
in self
.cursors
:
241 if c
.seen_tuio_id(id, x
, y
):
245 if not absorbed
and id > self
._highest
_id
:
246 new_cursors
.append((id, x
, y
))
248 if id > self
._highest
_id
:
249 self
._highest
_id
= id
251 for id, x
, y
in new_cursors
:
252 self
.cursors
.append(NumptyCursor(id, x
, y
, self
.activate_cursor
, self
.deactivate_cursor
, self
.cursor_movement
, self
.upgrade_cursor
))
257 for cursor
in self
.cursors
:
260 self
.cursors
= [cursor
for cursor
in self
.cursors
if not cursor
.deactivated
]
264 return self
._freeslots
.pop()
268 def freeslot(self
, slot
):
269 self
._freeslots
.appendleft(slot
)
271 def push_event(self
, cursor
, type_
):
273 print 'pushing event type %d for cursor %s' % (type_
, repr(cursor
.id))
274 self
.events
.appendleft(InputEvent(cursor
.x
, cursor
.y
, type_
, cursor
.id))
276 def push_preview_event(self
, id, x
, y
):
277 self
.events
.appendleft(InputEvent(x
, y
, numptyphysics
.PREVIEW_CURSOR
, id))
279 def convert_coords(self
, x
, y
):
280 return (int(x
*numptyphysics
.WIDTH
), int(y
*numptyphysics
.HEIGHT
))
282 def has_events(self
):
283 return len(self
.events
) > 0
285 def get_events(self
):
288 yield self
.events
.pop()
289 except IndexError, ie
:
290 raise StopIteration()
292 class InputEvent(object):
293 def __init__(self
, x
, y
, event_type
, cursor_id
=0):
296 self
.event_type
= event_type
297 self
.cursor_id
= cursor_id
299 class C(threading
.Thread
):
301 tracking
= tuio
.Tracking('')
302 tracker
= CursorTracker()
306 while tracking
.update():
307 # read the socket empty
310 tracker
.update(tracking
.cursors())
312 for event
in tracker
.get_events():
313 numptyphysics
.post_event(event
)
315 while not tracking
.update() and not tracker
.has_events():
316 # wait for something to happen