Files @ ccd635df77a9
Branch filter:

Location: light9/flax/tracker

dmcc
- commit latest cue and sub changes
- commit latest cue and sub changes
- CueFaders: show sub levels everywhere, update TODO section
#!/usr/bin/python
from __future__ import division,nested_scopes

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
import dispatcher

import dmxclient

import Tkinter as tk

defaultfont="arial 8"

def pairdist(pair1,pair2):
    return sqrt((pair1[0]-pair2[0])**2+(pair1[1]-pair2[1])**2)

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