Mercurial > code > home > repos > light9
annotate bin/tracker @ 1859:f066d6e874db
2to3 with these fixers: all idioms set_literal
Ignore-this: cbd28518218c2f0ddce8c4f92d3b8b33
author | drewp@bigasterisk.com |
---|---|
date | Wed, 22 May 2019 00:08:22 +0000 |
parents | 7772cc48e016 |
children | 5bcb950024af |
rev | line source |
---|---|
0 | 1 #!/usr/bin/python |
1859
f066d6e874db
2to3 with these fixers: all idioms set_literal
drewp@bigasterisk.com
parents:
1858
diff
changeset
|
2 |
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 | |
1859
f066d6e874db
2to3 with these fixers: all idioms set_literal
drewp@bigasterisk.com
parents:
1858
diff
changeset
|
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: | |
1859
f066d6e874db
2to3 with these fixers: all idioms set_literal
drewp@bigasterisk.com
parents:
1858
diff
changeset
|
83 print(name, intens, end=' ') |
1858 | 84 active += 1 |
85 if active > 0: | |
1859
f066d6e874db
2to3 with these fixers: all idioms set_literal
drewp@bigasterisk.com
parents:
1858
diff
changeset
|
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 | |
1859
f066d6e874db
2to3 with these fixers: all idioms set_literal
drewp@bigasterisk.com
parents:
1858
diff
changeset
|
138 for intens, ring in list(self.rings.items()): |
1858 | 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): |
1859
f066d6e874db
2to3 with these fixers: all idioms set_literal
drewp@bigasterisk.com
parents:
1858
diff
changeset
|
254 print("saving") |
152 | 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') |
1859
f066d6e874db
2to3 with these fixers: all idioms set_literal
drewp@bigasterisk.com
parents:
1858
diff
changeset
|
299 [d.setcoords() for d in list(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() |