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()