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