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 # Create the file "use-justdraw" if you don't want dragging+deleting
33 use_justdraw
= os
.path
.exists('use-justdraw')
35 CURSOR_ACTIVATE_DELAY
= .01
36 CURSOR_UPGRADE_DELAY
= .5
37 CURSOR_STOP_DELAY
= .5
39 CURSOR_NEIGHBOR_DISTANCE
= .1
43 except ImportError, ioe
:
44 print >>sys
.stderr
, """
45 This module can only be loaded from within NumptyPhysics.
51 except ImportError, ioe
:
52 print >>sys
.stderr
, """
53 You have to install PyTUIO and copy it into your $PYTHONPATH.
54 You can grab a tarball from: http://code.google.com/p/pytuio/
58 CURSOR_NEIGHBOR_DISTANCE
*= numptyphysics
.HEIGHT
60 class NumpytTuioCursor(object):
61 def __init__(self
, id, x
, y
):
65 self
.time
= time
.time()
67 class NumptyCursor(object):
68 DRAW
, DRAG
, DELETE
= range(3)
70 def __init__(self
, tuio_id
, x
, y
, on_activate
, on_deactivate
, on_move
, on_upgrade
):
75 self
.tuio_ids
= {tuio_id
: time
.time()}
77 self
.activated
= False
78 self
.deactivated
= False
82 self
.on_activate
= on_activate
83 self
.on_deactivate
= on_deactivate
84 self
.on_move
= on_move
85 self
.on_upgrade
= on_upgrade
87 self
.activate_at
= time
.time()+CURSOR_ACTIVATE_DELAY
88 self
.upgrade_at
= time
.time()+CURSOR_UPGRADE_DELAY
89 self
.deactivate_at
= 0
90 print 'new numpty cursor for', tuio_id
94 self
.deactivate_at
= time
.time()+CURSOR_STOP_DELAY
96 def is_near(self
, x
, y
):
97 return ((x
-self
.x
)**2 + (y
-self
.y
)**2) < CURSOR_NEIGHBOR_DISTANCE
**2
100 if self
.activated
and not self
.deactivated
:
101 print 'moved', self
.id, 'to', x
, '/', y
, '(which is a', self
.mode
, ')'
102 if self
.x
!= x
or self
.y
!= y
:
107 def want_new_cursors(self
):
108 if self
.mode
== self
.DRAW
:
109 return len(self
.tuio_ids
) < 1
110 elif self
.mode
== self
.DRAG
:
111 return len(self
.tuio_ids
) < 2
112 elif self
.mode
== self
.DELETE
:
113 return len(self
.tuio_ids
) < 3
116 def seen_tuio_id(self
, id, x
, y
):
118 # If this cursor is gone, it's gone
121 if not self
.upgraded
and self
.is_near(x
, y
) and id not in self
.tuio_ids
and not use_justdraw
:
122 # this cursor it not yet upgraded; the tuio id is near
123 if self
.mode
== self
.DRAW
:
124 print 'absorbing and setting to drag', id
125 self
.mode
= self
.DRAG
126 elif self
.mode
== self
.DRAG
:
127 print 'absorbing and setting to delete', id
128 self
.mode
= self
.DELETE
132 self
.tuio_ids
[id] = time
.time()
136 if self
.upgraded
and self
.want_new_cursors() and self
.is_near(x
, y
) and id not in self
.tuio_ids
:
137 # i can take more cursors, so absorb this
138 self
.tuio_ids
[id] = time
.time()
139 if id == min(self
.tuio_ids
) and self
.id:
143 elif id in self
.tuio_ids
:
144 self
.tuio_ids
[id] = time
.time()
145 if id == min(self
.tuio_ids
) and self
.id:
153 self
.activated
= True
154 self
.on_activate(self
)
158 self
.on_upgrade(self
)
159 if self
.mode
== self
.DELETE
:
162 def deactivate(self
):
163 self
.deactivated
= True
165 self
.on_deactivate(self
)
172 if time
.time() > self
.deactivate_at
and not self
.deactivated
:
174 elif time
.time() > self
.upgrade_at
and not self
.upgraded
and not self
.deactivated
:
176 elif time
.time() > self
.activate_at
and not self
.activated
and not self
.upgraded
and not self
.deactivated
:
178 elif self
.activated
and self
.moved
:
184 for id, t
in self
.tuio_ids
.items():
185 if t
+.5*CURSOR_STOP_DELAY
< time
.time():
186 del self
.tuio_ids
[id]
189 class CursorTracker(object):
192 self
._freeslots
= collections
.deque(range(numptyphysics
.MAX_CURSORS
))
193 self
._highest
_id
= -1
194 self
.events
= collections
.deque()
196 def activate_cursor(self
, cursor
):
197 cursor
.id = self
.grabslot()
198 if cursor
.mode
== cursor
.DRAW
:
199 self
.push_event(cursor
, numptyphysics
.START_STROKE
)
200 elif cursor
.mode
== cursor
.DRAG
:
201 self
.push_event(cursor
, numptyphysics
.START_DRAG
)
202 elif cursor
.mode
== cursor
.DELETE
:
203 self
.push_event(cursor
, numptyphysics
.DELETE
)
205 def deactivate_cursor(self
, cursor
):
206 if cursor
.mode
== cursor
.DRAW
:
207 self
.push_event(cursor
, numptyphysics
.FINISH_STROKE
)
208 elif cursor
.mode
== cursor
.DRAG
:
209 self
.push_event(cursor
, numptyphysics
.END_DRAG
)
210 self
.freeslot(cursor
.id)
212 def cursor_movement(self
, cursor
):
213 if cursor
.mode
== cursor
.DRAW
:
214 self
.push_event(cursor
, numptyphysics
.APPEND_STROKE
)
215 elif cursor
.mode
== cursor
.DRAG
:
216 self
.push_event(cursor
, numptyphysics
.DRAG
)
218 def upgrade_cursor(self
, cursor
):
219 if cursor
.mode
== cursor
.DRAW
:
220 # "draw" cursors do not need to be upgraded
223 # cancel the in-progress draw event + start "real" event
224 self
.push_event(cursor
, numptyphysics
.CANCEL_DRAW
)
225 if cursor
.mode
== cursor
.DRAG
:
226 self
.push_event(cursor
, numptyphysics
.START_DRAG
)
227 elif cursor
.mode
== cursor
.DELETE
:
228 self
.push_event(cursor
, numptyphysics
.DELETE
)
230 def update(self
, cursors
):
232 for cursor
in cursors
:
233 id, x
, y
= cursor
.sessionid
, cursor
.xpos
, cursor
.ypos
234 if x
== 0 and y
== 0:
238 x
, y
= self
.convert_coords(x
, y
)
240 self
.push_preview_event(id, x
, y
)
243 for c
in self
.cursors
:
244 if c
.seen_tuio_id(id, x
, y
):
248 if not absorbed
and id > self
._highest
_id
:
249 new_cursors
.append((id, x
, y
))
251 if id > self
._highest
_id
:
252 self
._highest
_id
= id
254 for id, x
, y
in new_cursors
:
255 self
.cursors
.append(NumptyCursor(id, x
, y
, self
.activate_cursor
, self
.deactivate_cursor
, self
.cursor_movement
, self
.upgrade_cursor
))
260 for cursor
in self
.cursors
:
263 self
.cursors
= [cursor
for cursor
in self
.cursors
if not cursor
.deactivated
]
267 return self
._freeslots
.pop()
271 def freeslot(self
, slot
):
272 self
._freeslots
.appendleft(slot
)
274 def push_event(self
, cursor
, type_
):
276 print 'pushing event type %d for cursor %s' % (type_
, repr(cursor
.id))
277 self
.events
.appendleft(InputEvent(cursor
.x
, cursor
.y
, type_
, cursor
.id))
279 def push_preview_event(self
, id, x
, y
):
280 self
.events
.appendleft(InputEvent(x
, y
, numptyphysics
.PREVIEW_CURSOR
, id))
282 def convert_coords(self
, x
, y
):
283 return (int(x
*numptyphysics
.WIDTH
), int(y
*numptyphysics
.HEIGHT
))
285 def has_events(self
):
286 return len(self
.events
) > 0
288 def get_events(self
):
291 yield self
.events
.pop()
292 except IndexError, ie
:
293 raise StopIteration()
295 class InputEvent(object):
296 def __init__(self
, x
, y
, event_type
, cursor_id
=0):
299 self
.event_type
= event_type
300 self
.cursor_id
= cursor_id
302 class C(threading
.Thread
):
304 pointlog
= open('pointlog-'+str(int(time
.time()))+'.np', 'w')
305 tracking
= tuio
.Tracking('')
306 tracker
= CursorTracker()
310 while tracking
.update():
311 # read the socket empty
314 tracker
.update(tracking
.cursors())
316 for event
in tracker
.get_events():
317 pointlog
.write('/'.join((str(event
.x
), str(event
.y
), str(int(time
.time())))))
319 numptyphysics
.post_event(event
)
321 while not tracking
.update() and not tracker
.has_events():
322 # wait for something to happen