Mercurial > code > home > repos > light9
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/attic/tracker Sun May 12 19:02:10 2024 -0700 @@ -0,0 +1,311 @@ +#!/usr/bin/python + +import sys +sys.path.append("../../editor/pour") +sys.path.append("../light8") + +from Submaster import Submaster +from skim.zooming import Zooming, Pair +from math import sqrt, sin, cos +from pygame.rect import Rect +from xmlnodebase import xmlnodeclass, collectiveelement, xmldocfile +from dispatch import dispatcher + +import dmxclient + +import tkinter as tk + +defaultfont = "arial 8" + + +def pairdist(pair1, pair2): + return pair1.dist(pair2) + + +def canvashighlighter(canvas, obj, attribute, normalval, highlightval): + """creates bindings on a canvas obj that make attribute go + from normal to highlight when the mouse is over the obj""" + canvas.tag_bind( + obj, "<Enter>", lambda ev: canvas.itemconfig( + obj, **{attribute: highlightval})) + canvas.tag_bind( + obj, + "<Leave>", lambda ev: canvas.itemconfig(obj, **{attribute: normalval})) + + +class Field(xmlnodeclass): + """one light has a field of influence. for any point on the + canvas, you can ask this field how strong it is. """ + + def name(self, newval=None): + """light/sub name""" + return self._getorsetattr("name", newval) + + def center(self, x=None, y=None): + """x,y float coords for the center of this light in the field. returns + a Pair, although it accepts x,y""" + return Pair(self._getorsettypedattr("x", float, x), + self._getorsettypedattr("y", float, y)) + + def falloff(self, dist=None): + """linear falloff from 1 at center, to 0 at dist pixels away + from center""" + return self._getorsettypedattr("falloff", float, dist) + + def getdistforintensity(self, intens): + """returns the distance you'd have to be for the given intensity (0..1)""" + return (1 - intens) * self.falloff() + + def calc(self, x, y): + """returns field strength at point x,y""" + dist = pairdist(Pair(x, y), self.center()) + return max(0, (self.falloff() - dist) / self.falloff()) + + +class Fieldset(collectiveelement): + """group of fields. persistent.""" + + def childtype(self): + return Field + + def version(self): + """read-only version attribute on fieldset tag""" + return self._getorsetattr("version", None) + + def report(self, x, y): + """reports active fields and their intensities""" + active = 0 + for f in self.getall(): + name = f.name() + intens = f.calc(x, y) + if intens > 0: + print(name, intens, end=' ') + active += 1 + if active > 0: + print() + self.dmxsend(x, y) + + def dmxsend(self, x, y): + """output lights to dmx""" + levels = dict([(f.name(), f.calc(x, y)) for f in self.getall()]) + dmxlist = Submaster(None, levels).get_dmx_list() + dmxclient.outputlevels(dmxlist) + + def getbounds(self): + """returns xmin,xmax,ymin,ymax for the non-zero areas of this field""" + r = None + for f in self.getall(): + rad = f.getdistforintensity(0) + fx, fy = f.center() + fieldrect = Rect(fx - rad, fy - rad, rad * 2, rad * 2) + if r is None: + r = fieldrect + else: + r = r.union(fieldrect) + return r.left, r.right, r.top, r.bottom + + +class Fieldsetfile(xmldocfile): + + def __init__(self, filename): + self._openornew(filename, topleveltype=Fieldset) + + def fieldset(self): + return self._gettoplevel() + + +######################################################################## +######################################################################## + + +class FieldDisplay: + """the view for a Field.""" + + def __init__(self, canvas, field): + self.canvas = canvas + self.field = field + self.tags = [str(id(self))] # canvas tag to id our objects + + def setcoords(self): + """adjust canvas obj coords to match the field""" + # this uses the canvas object ids saved by makeobjs + f = self.field + c = self.canvas + w2c = self.canvas.world2canvas + + # rings + for intens, ring in list(self.rings.items()): + rad = f.getdistforintensity(intens) + p1 = w2c(*(f.center() - Pair(rad, rad))) + p2 = w2c(*(f.center() + Pair(rad, rad))) + c.coords(ring, p1[0], p1[1], p2[0], p2[1]) + + # text + p1 = w2c(*f.center()) + c.coords(self.txt, *p1) + + def makeobjs(self): + """(re)create the canvas objs (null coords) and make their bindings""" + c = self.canvas + f = self.field + c.delete(self.tags) + + w2c = self.canvas.world2canvas + + # make rings + self.rings = {} # rad,canvasobj + for intens, color in ( #(1,'white'), + (.8, 'gray90'), (.6, 'gray80'), (.4, 'gray60'), (.2, 'gray50'), + (0, '#000080')): + self.rings[intens] = c.create_oval(0, + 0, + 0, + 0, + outline=color, + width=2, + tags=self.tags, + outlinestipple='gray50') + + # make text + self.txt = c.create_text(0, + 0, + text=f.name(), + font=defaultfont + " bold", + fill='white', + anchor='c', + tags=self.tags) + + # highlight text bindings + canvashighlighter(c, + self.txt, + 'fill', + normalval='white', + highlightval='red') + + # position drag bindings + def press(ev): + self._lastmouse = ev.x, ev.y + + def motion(ev): + dcan = Pair(*[a - b for a, b in zip((ev.x, ev.y), self._lastmouse)]) + dworld = c.canvas2world_vector(*dcan) + self.field.center(*(self.field.center() + dworld)) + self._lastmouse = ev.x, ev.y + self.setcoords() # redraw + + def release(ev): + if hasattr(self, '_lastmouse'): + del self._lastmouse + dispatcher.send("field coord changed") # updates bounds + + c.tag_bind(self.txt, "<ButtonPress-1>", press) + c.tag_bind(self.txt, "<B1-Motion>", motion) + c.tag_bind(self.txt, "<B1-ButtonRelease>", release) + + # radius drag bindings + outerring = self.rings[0] + canvashighlighter(c, + outerring, + 'outline', + normalval='#000080', + highlightval='#4040ff') + + def motion(ev): + worldmouse = self.canvas.canvas2world(ev.x, ev.y) + currentdist = pairdist(worldmouse, self.field.center()) + self.field.falloff(currentdist) + self.setcoords() + + c.tag_bind(outerring, "<B1-Motion>", motion) + c.tag_bind(outerring, "<B1-ButtonRelease>", release) # from above + + self.setcoords() + + +class Tracker(tk.Frame): + """whole tracker widget, which is mostly a view for a + Fieldset. tracker makes its own fieldset""" + + # world coords of the visible canvas (preserved even in window resizes) + xmin = 0 + xmax = 100 + ymin = 0 + ymax = 100 + + fieldsetfile = None + displays = None # Field : FieldDisplay. we keep these in sync with the fieldset + + def __init__(self, master): + tk.Frame.__init__(self, master) + + self.displays = {} + + c = self.canvas = Zooming(self, bg='black', closeenough=5) + c.pack(fill='both', exp=1) + + # preserve edge coords over window resize + c.bind("<Configure>", self.configcoords) + + c.bind("<Motion>", lambda ev: self._fieldset().report(*c.canvas2world( + ev.x, ev.y))) + + def save(ev): + print("saving") + self.fieldsetfile.save() + + master.bind("<Key-s>", save) + dispatcher.connect(self.autobounds, "field coord changed") + + def _fieldset(self): + return self.fieldsetfile.fieldset() + + def load(self, filename): + self.fieldsetfile = Fieldsetfile(filename) + self.displays.clear() + for f in self.fieldsetfile.fieldset().getall(): + self.displays[f] = FieldDisplay(self.canvas, f) + self.displays[f].makeobjs() + self.autobounds() + + def configcoords(self, *args): + # force our canvas coords to stay at the edges of the window + c = self.canvas + cornerx, cornery = c.canvas2world(0, 0) + c.move(cornerx - self.xmin, cornery - self.ymin) + c.setscale(0, 0, + c.winfo_width() / (self.xmax - self.xmin), + c.winfo_height() / (self.ymax - self.ymin)) + + def autobounds(self): + """figure out our bounds from the fieldset, and adjust the display zooms. + writes the corner coords onto the canvas.""" + self.xmin, self.xmax, self.ymin, self.ymax = self._fieldset().getbounds( + ) + + self.configcoords() + + c = self.canvas + c.delete('cornercoords') + for x, anc2 in ((self.xmin, 'w'), (self.xmax, 'e')): + for y, anc1 in ((self.ymin, 'n'), (self.ymax, 's')): + pos = c.world2canvas(x, y) + c.create_text(pos[0], + pos[1], + text="%s,%s" % (x, y), + fill='white', + anchor=anc1 + anc2, + tags='cornercoords') + [d.setcoords() for d in list(self.displays.values())] + + +######################################################################## +######################################################################## + +root = tk.Tk() +root.wm_geometry('700x350') +tra = Tracker(root) +tra.pack(fill='both', exp=1) + +tra.load("fieldsets/demo") + +root.mainloop()