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_START_DELAY
= .5
33 CURSOR_STOP_DELAY
= .5
35 CURSOR_NEIGHBOR_DISTANCE
= .35
39 except ImportError, ioe
:
40 print >>sys
.stderr
, """
41 This module can only be loaded from within NumptyPhysics.
47 except ImportError, ioe
:
48 print >>sys
.stderr
, """
49 You have to install PyTUIO and copy it into your $PYTHONPATH.
50 You can grab a tarball from: http://code.google.com/p/pytuio/
54 CURSOR_NEIGHBOR_DISTANCE
*= numptyphysics
.HEIGHT
56 class NumpytTuioCursor(object):
57 def __init__(self
, id, x
, y
):
61 self
.time
= time
.time()
63 class NumptyCursor(object):
64 DRAW
, DRAG
, DELETE
= range(3)
66 def __init__(self
, tuio_id
, x
, y
, on_activate
, on_deactivate
, on_move
):
71 self
.tuio_ids
= {tuio_id
: time
.time()}
73 self
.activated
= False
74 self
.deactivated
= False
77 self
.on_activate
= on_activate
78 self
.on_deactivate
= on_deactivate
79 self
.on_move
= on_move
81 self
.activate_at
= time
.time()+CURSOR_START_DELAY
82 self
.deactivate_at
= 0
83 print 'new numpty cursor for', tuio_id
87 self
.deactivate_at
= time
.time()+CURSOR_STOP_DELAY
89 def is_near(self
, x
, y
):
90 return ((x
-self
.x
)**2 + (y
-self
.y
)**2)**.5 < CURSOR_NEIGHBOR_DISTANCE
93 if self
.activated
and not self
.deactivated
:
94 print 'moved', self
.id, 'to', x
, '/', y
, '(which is a', self
.mode
, ')'
95 if self
.x
!= x
or self
.y
!= y
:
100 def want_new_cursors(self
):
101 if self
.mode
== self
.DRAW
:
102 return len(self
.tuio_ids
) < 1
103 elif self
.mode
== self
.DRAG
:
104 return len(self
.tuio_ids
) < 2
105 elif self
.mode
== self
.DELETE
:
106 return len(self
.tuio_ids
) < 3
109 def seen_tuio_id(self
, id, x
, y
):
111 # If this cursor is gone, it's gone
114 if not self
.activated
and self
.is_near(x
, y
) and id not in self
.tuio_ids
:
115 # this cursor it not yet activated; the tuio id is near
116 if self
.mode
== self
.DRAW
:
117 print 'absorbing and setting to drag', id
118 self
.mode
= self
.DRAG
119 elif self
.mode
== self
.DRAG
:
120 print 'absorbing and setting to delete', id
121 self
.mode
= self
.DELETE
125 self
.tuio_ids
[id] = time
.time()
129 if self
.activated
and self
.want_new_cursors() and self
.is_near(x
, y
) and id not in self
.tuio_ids
:
130 # i can take more cursors, so absorb this
131 self
.tuio_ids
[id] = time
.time()
132 if id == min(self
.tuio_ids
):
136 elif id in self
.tuio_ids
:
137 self
.tuio_ids
[id] = time
.time()
138 if id == min(self
.tuio_ids
):
146 self
.activated
= True
147 self
.on_activate(self
)
148 if self
.mode
== self
.DELETE
:
151 def deactivate(self
):
152 self
.deactivated
= True
154 self
.on_deactivate(self
)
161 if time
.time() > self
.deactivate_at
and not self
.deactivated
:
163 elif time
.time() > self
.activate_at
and not self
.activated
and not self
.deactivated
:
165 elif self
.activated
and self
.moved
:
171 for id, t
in self
.tuio_ids
.items():
172 if t
+.5*CURSOR_STOP_DELAY
< time
.time():
173 del self
.tuio_ids
[id]
176 class CursorTracker(object):
179 self
._freeslots
= collections
.deque(range(numptyphysics
.MAX_CURSORS
))
180 self
.events
= collections
.deque()
182 def activate_cursor(self
, cursor
):
183 cursor
.id = self
.grabslot()
184 if cursor
.mode
== cursor
.DRAW
:
185 self
.push_event(cursor
, numptyphysics
.START_STROKE
)
186 elif cursor
.mode
== cursor
.DRAG
:
187 self
.push_event(cursor
, numptyphysics
.START_DRAG
)
188 elif cursor
.mode
== cursor
.DELETE
:
189 self
.push_event(cursor
, numptyphysics
.DELETE
)
191 def deactivate_cursor(self
, cursor
):
192 if cursor
.mode
== cursor
.DRAW
:
193 self
.push_event(cursor
, numptyphysics
.FINISH_STROKE
)
194 elif cursor
.mode
== cursor
.DRAG
:
195 self
.push_event(cursor
, numptyphysics
.END_DRAG
)
196 self
.freeslot(cursor
.id)
198 def cursor_movement(self
, cursor
):
199 if cursor
.mode
== cursor
.DRAW
:
200 self
.push_event(cursor
, numptyphysics
.APPEND_STROKE
)
201 elif cursor
.mode
== cursor
.DRAG
:
202 self
.push_event(cursor
, numptyphysics
.DRAG
)
204 def update(self
, cursors
):
206 for cursor
in cursors
:
207 id, x
, y
= cursor
.sessionid
, cursor
.xpos
, cursor
.ypos
210 x
, y
= self
.convert_coords(x
, y
)
212 self
.push_preview_event(id, x
, y
)
215 for c
in self
.cursors
:
216 if c
.seen_tuio_id(id, x
, y
):
221 new_cursors
.append((id, x
, y
))
223 for id, x
, y
in new_cursors
:
224 self
.cursors
.append(NumptyCursor(id, x
, y
, self
.activate_cursor
, self
.deactivate_cursor
, self
.cursor_movement
))
229 for cursor
in self
.cursors
:
232 self
.cursors
= [cursor
for cursor
in self
.cursors
if not cursor
.deactivated
]
236 return self
._freeslots
.pop()
240 def freeslot(self
, slot
):
241 self
._freeslots
.appendleft(slot
)
243 def push_event(self
, cursor
, type_
):
244 self
.events
.appendleft(InputEvent(cursor
.x
, cursor
.y
, type_
, cursor
.id))
246 def push_preview_event(self
, id, x
, y
):
247 self
.events
.appendleft(InputEvent(x
, y
, numptyphysics
.PREVIEW_CURSOR
, id))
249 def convert_coords(self
, x
, y
):
250 return (int(x
*numptyphysics
.WIDTH
), int(y
*numptyphysics
.HEIGHT
))
252 def has_events(self
):
253 return len(self
.events
) > 0
255 def get_events(self
):
258 yield self
.events
.pop()
259 except IndexError, ie
:
260 raise StopIteration()
262 class InputEvent(object):
263 def __init__(self
, x
, y
, event_type
, cursor_id
=0):
266 self
.event_type
= event_type
267 self
.cursor_id
= cursor_id
269 class C(threading
.Thread
):
271 tracking
= tuio
.Tracking('')
272 tracker
= CursorTracker()
276 while tracking
.update():
277 # read the socket empty
280 tracker
.update(tracking
.cursors())
282 for event
in tracker
.get_events():
283 numptyphysics
.post_event(event
)
285 while not tracking
.update() and not tracker
.has_events():
286 # wait for something to happen