0
|
1 #!/usr/bin/python
|
1858
|
2 from __future__ import division, nested_scopes
|
0
|
3
|
154
|
4 import sys
|
|
5 sys.path.append("../../editor/pour")
|
159
|
6 sys.path.append("../light8")
|
154
|
7
|
159
|
8 from Submaster import Submaster
|
1858
|
9 from skim.zooming import Zooming, Pair
|
|
10 from math import sqrt, sin, cos
|
0
|
11 from pygame.rect import Rect
|
1858
|
12 from xmlnodebase import xmlnodeclass, collectiveelement, xmldocfile
|
195
|
13 from dispatch import dispatcher
|
0
|
14
|
159
|
15 import dmxclient
|
|
16
|
0
|
17 import Tkinter as tk
|
152
|
18
|
1858
|
19 defaultfont = "arial 8"
|
156
|
20
|
1858
|
21
|
|
22 def pairdist(pair1, pair2):
|
193
|
23 return pair1.dist(pair2)
|
152
|
24
|
1858
|
25
|
|
26 def canvashighlighter(canvas, obj, attribute, normalval, highlightval):
|
152
|
27 """creates bindings on a canvas obj that make attribute go
|
|
28 from normal to highlight when the mouse is over the obj"""
|
1858
|
29 canvas.tag_bind(
|
|
30 obj, "<Enter>", lambda ev: canvas.itemconfig(
|
|
31 obj, **{attribute: highlightval}))
|
|
32 canvas.tag_bind(
|
|
33 obj,
|
|
34 "<Leave>", lambda ev: canvas.itemconfig(obj, **{attribute: normalval}))
|
|
35
|
152
|
36
|
|
37 class Field(xmlnodeclass):
|
0
|
38 """one light has a field of influence. for any point on the
|
|
39 canvas, you can ask this field how strong it is. """
|
|
40
|
1858
|
41 def name(self, newval=None):
|
152
|
42 """light/sub name"""
|
1858
|
43 return self._getorsetattr("name", newval)
|
|
44
|
|
45 def center(self, x=None, y=None):
|
152
|
46 """x,y float coords for the center of this light in the field. returns
|
|
47 a Pair, although it accepts x,y"""
|
1858
|
48 return Pair(self._getorsettypedattr("x", float, x),
|
|
49 self._getorsettypedattr("y", float, y))
|
0
|
50
|
1858
|
51 def falloff(self, dist=None):
|
0
|
52 """linear falloff from 1 at center, to 0 at dist pixels away
|
|
53 from center"""
|
1858
|
54 return self._getorsettypedattr("falloff", float, dist)
|
0
|
55
|
1858
|
56 def getdistforintensity(self, intens):
|
0
|
57 """returns the distance you'd have to be for the given intensity (0..1)"""
|
1858
|
58 return (1 - intens) * self.falloff()
|
0
|
59
|
1858
|
60 def calc(self, x, y):
|
0
|
61 """returns field strength at point x,y"""
|
1858
|
62 dist = pairdist(Pair(x, y), self.center())
|
|
63 return max(0, (self.falloff() - dist) / self.falloff())
|
|
64
|
0
|
65
|
152
|
66 class Fieldset(collectiveelement):
|
0
|
67 """group of fields. persistent."""
|
1858
|
68
|
|
69 def childtype(self):
|
|
70 return Field
|
152
|
71
|
|
72 def version(self):
|
|
73 """read-only version attribute on fieldset tag"""
|
1858
|
74 return self._getorsetattr("version", None)
|
152
|
75
|
1858
|
76 def report(self, x, y):
|
0
|
77 """reports active fields and their intensities"""
|
1858
|
78 active = 0
|
152
|
79 for f in self.getall():
|
1858
|
80 name = f.name()
|
|
81 intens = f.calc(x, y)
|
|
82 if intens > 0:
|
|
83 print name, intens,
|
|
84 active += 1
|
|
85 if active > 0:
|
152
|
86 print
|
1858
|
87 self.dmxsend(x, y)
|
|
88
|
|
89 def dmxsend(self, x, y):
|
159
|
90 """output lights to dmx"""
|
1858
|
91 levels = dict([(f.name(), f.calc(x, y)) for f in self.getall()])
|
|
92 dmxlist = Submaster(None, levels).get_dmx_list()
|
159
|
93 dmxclient.outputlevels(dmxlist)
|
1858
|
94
|
0
|
95 def getbounds(self):
|
|
96 """returns xmin,xmax,ymin,ymax for the non-zero areas of this field"""
|
1858
|
97 r = None
|
152
|
98 for f in self.getall():
|
1858
|
99 rad = f.getdistforintensity(0)
|
|
100 fx, fy = f.center()
|
|
101 fieldrect = Rect(fx - rad, fy - rad, rad * 2, rad * 2)
|
0
|
102 if r is None:
|
1858
|
103 r = fieldrect
|
0
|
104 else:
|
1858
|
105 r = r.union(fieldrect)
|
|
106 return r.left, r.right, r.top, r.bottom
|
|
107
|
0
|
108
|
152
|
109 class Fieldsetfile(xmldocfile):
|
1858
|
110
|
|
111 def __init__(self, filename):
|
|
112 self._openornew(filename, topleveltype=Fieldset)
|
|
113
|
152
|
114 def fieldset(self):
|
|
115 return self._gettoplevel()
|
|
116
|
1858
|
117
|
152
|
118 ########################################################################
|
|
119 ########################################################################
|
|
120
|
1858
|
121
|
0
|
122 class FieldDisplay:
|
|
123 """the view for a Field."""
|
1858
|
124
|
|
125 def __init__(self, canvas, field):
|
|
126 self.canvas = canvas
|
|
127 self.field = field
|
|
128 self.tags = [str(id(self))] # canvas tag to id our objects
|
|
129
|
152
|
130 def setcoords(self):
|
|
131 """adjust canvas obj coords to match the field"""
|
|
132 # this uses the canvas object ids saved by makeobjs
|
1858
|
133 f = self.field
|
|
134 c = self.canvas
|
|
135 w2c = self.canvas.world2canvas
|
152
|
136
|
|
137 # rings
|
1858
|
138 for intens, ring in self.rings.items():
|
|
139 rad = f.getdistforintensity(intens)
|
|
140 p1 = w2c(*(f.center() - Pair(rad, rad)))
|
|
141 p2 = w2c(*(f.center() + Pair(rad, rad)))
|
|
142 c.coords(ring, p1[0], p1[1], p2[0], p2[1])
|
152
|
143
|
|
144 # text
|
1858
|
145 p1 = w2c(*f.center())
|
|
146 c.coords(self.txt, *p1)
|
152
|
147
|
|
148 def makeobjs(self):
|
|
149 """(re)create the canvas objs (null coords) and make their bindings"""
|
1858
|
150 c = self.canvas
|
|
151 f = self.field
|
0
|
152 c.delete(self.tags)
|
|
153
|
1858
|
154 w2c = self.canvas.world2canvas
|
0
|
155
|
152
|
156 # make rings
|
1858
|
157 self.rings = {} # rad,canvasobj
|
|
158 for intens, color in ( #(1,'white'),
|
|
159 (.8, 'gray90'), (.6, 'gray80'), (.4, 'gray60'), (.2, 'gray50'),
|
|
160 (0, '#000080')):
|
|
161 self.rings[intens] = c.create_oval(0,
|
|
162 0,
|
|
163 0,
|
|
164 0,
|
|
165 outline=color,
|
|
166 width=2,
|
|
167 tags=self.tags,
|
|
168 outlinestipple='gray50')
|
152
|
169
|
|
170 # make text
|
1858
|
171 self.txt = c.create_text(0,
|
|
172 0,
|
|
173 text=f.name(),
|
|
174 font=defaultfont + " bold",
|
|
175 fill='white',
|
|
176 anchor='c',
|
|
177 tags=self.tags)
|
152
|
178
|
|
179 # highlight text bindings
|
1858
|
180 canvashighlighter(c,
|
|
181 self.txt,
|
|
182 'fill',
|
|
183 normalval='white',
|
|
184 highlightval='red')
|
152
|
185
|
|
186 # position drag bindings
|
|
187 def press(ev):
|
1858
|
188 self._lastmouse = ev.x, ev.y
|
|
189
|
152
|
190 def motion(ev):
|
1858
|
191 dcan = Pair(*[a - b for a, b in zip((ev.x, ev.y), self._lastmouse)])
|
|
192 dworld = c.canvas2world_vector(*dcan)
|
|
193 self.field.center(*(self.field.center() + dworld))
|
|
194 self._lastmouse = ev.x, ev.y
|
|
195 self.setcoords() # redraw
|
|
196
|
152
|
197 def release(ev):
|
1858
|
198 if hasattr(self, '_lastmouse'):
|
156
|
199 del self._lastmouse
|
1858
|
200 dispatcher.send("field coord changed") # updates bounds
|
|
201
|
|
202 c.tag_bind(self.txt, "<ButtonPress-1>", press)
|
|
203 c.tag_bind(self.txt, "<B1-Motion>", motion)
|
|
204 c.tag_bind(self.txt, "<B1-ButtonRelease>", release)
|
152
|
205
|
|
206 # radius drag bindings
|
1858
|
207 outerring = self.rings[0]
|
|
208 canvashighlighter(c,
|
|
209 outerring,
|
|
210 'outline',
|
|
211 normalval='#000080',
|
|
212 highlightval='#4040ff')
|
|
213
|
152
|
214 def motion(ev):
|
1858
|
215 worldmouse = self.canvas.canvas2world(ev.x, ev.y)
|
|
216 currentdist = pairdist(worldmouse, self.field.center())
|
152
|
217 self.field.falloff(currentdist)
|
|
218 self.setcoords()
|
1858
|
219
|
|
220 c.tag_bind(outerring, "<B1-Motion>", motion)
|
|
221 c.tag_bind(outerring, "<B1-ButtonRelease>", release) # from above
|
152
|
222
|
|
223 self.setcoords()
|
1858
|
224
|
|
225
|
0
|
226 class Tracker(tk.Frame):
|
|
227 """whole tracker widget, which is mostly a view for a
|
152
|
228 Fieldset. tracker makes its own fieldset"""
|
0
|
229
|
|
230 # world coords of the visible canvas (preserved even in window resizes)
|
1858
|
231 xmin = 0
|
|
232 xmax = 100
|
|
233 ymin = 0
|
|
234 ymax = 100
|
|
235
|
|
236 fieldsetfile = None
|
|
237 displays = None # Field : FieldDisplay. we keep these in sync with the fieldset
|
152
|
238
|
1858
|
239 def __init__(self, master):
|
|
240 tk.Frame.__init__(self, master)
|
0
|
241
|
1858
|
242 self.displays = {}
|
|
243
|
|
244 c = self.canvas = Zooming(self, bg='black', closeenough=5)
|
|
245 c.pack(fill='both', exp=1)
|
0
|
246
|
152
|
247 # preserve edge coords over window resize
|
1858
|
248 c.bind("<Configure>", self.configcoords)
|
|
249
|
|
250 c.bind("<Motion>", lambda ev: self._fieldset().report(*c.canvas2world(
|
|
251 ev.x, ev.y)))
|
|
252
|
152
|
253 def save(ev):
|
|
254 print "saving"
|
|
255 self.fieldsetfile.save()
|
1858
|
256
|
|
257 master.bind("<Key-s>", save)
|
|
258 dispatcher.connect(self.autobounds, "field coord changed")
|
0
|
259
|
152
|
260 def _fieldset(self):
|
|
261 return self.fieldsetfile.fieldset()
|
0
|
262
|
1858
|
263 def load(self, filename):
|
|
264 self.fieldsetfile = Fieldsetfile(filename)
|
152
|
265 self.displays.clear()
|
|
266 for f in self.fieldsetfile.fieldset().getall():
|
1858
|
267 self.displays[f] = FieldDisplay(self.canvas, f)
|
152
|
268 self.displays[f].makeobjs()
|
|
269 self.autobounds()
|
0
|
270
|
1858
|
271 def configcoords(self, *args):
|
152
|
272 # force our canvas coords to stay at the edges of the window
|
1858
|
273 c = self.canvas
|
|
274 cornerx, cornery = c.canvas2world(0, 0)
|
|
275 c.move(cornerx - self.xmin, cornery - self.ymin)
|
|
276 c.setscale(0, 0,
|
|
277 c.winfo_width() / (self.xmax - self.xmin),
|
|
278 c.winfo_height() / (self.ymax - self.ymin))
|
0
|
279
|
|
280 def autobounds(self):
|
152
|
281 """figure out our bounds from the fieldset, and adjust the display zooms.
|
|
282 writes the corner coords onto the canvas."""
|
1858
|
283 self.xmin, self.xmax, self.ymin, self.ymax = self._fieldset().getbounds(
|
|
284 )
|
0
|
285
|
152
|
286 self.configcoords()
|
1858
|
287
|
|
288 c = self.canvas
|
152
|
289 c.delete('cornercoords')
|
1858
|
290 for x, anc2 in ((self.xmin, 'w'), (self.xmax, 'e')):
|
|
291 for y, anc1 in ((self.ymin, 'n'), (self.ymax, 's')):
|
|
292 pos = c.world2canvas(x, y)
|
|
293 c.create_text(pos[0],
|
|
294 pos[1],
|
|
295 text="%s,%s" % (x, y),
|
|
296 fill='white',
|
|
297 anchor=anc1 + anc2,
|
152
|
298 tags='cornercoords')
|
|
299 [d.setcoords() for d in self.displays.values()]
|
0
|
300
|
1858
|
301
|
152
|
302 ########################################################################
|
|
303 ########################################################################
|
1858
|
304
|
|
305 root = tk.Tk()
|
156
|
306 root.wm_geometry('700x350')
|
1858
|
307 tra = Tracker(root)
|
|
308 tra.pack(fill='both', exp=1)
|
0
|
309
|
152
|
310 tra.load("fieldsets/demo")
|
0
|
311
|
|
312 root.mainloop()
|