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