# HG changeset patch # User drewp # Date 2003-07-07 06:25:20 # Node ID e6ca7c1f0b1e11482a3c020e5ff5b6ce2b9f6ef3 # Parent 990a9474d0e7b2ca815f40f55cee2b884aa42dc7 loads and saves as xml now loads and saves as xml now draggable field centers and radii diff --git a/flax/tracker b/flax/tracker --- a/flax/tracker +++ b/flax/tracker @@ -1,80 +1,79 @@ #!/usr/bin/python -from __future__ import division +from __future__ import division,nested_scopes 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 Tkinter as tk -class Field: + +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,"", + lambda ev: canvas.itemconfig(obj,**{attribute:highlightval})) + canvas.tag_bind(obj,"", + 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. """ - center=Pair(0,0) - falloff=0 + 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 setcenter(self,x,y): - self.center=Pair(x,y) - - def setfalloff(self,dist): + def falloff(self,dist=None): """linear falloff from 1 at center, to 0 at dist pixels away from center""" - self.falloff=dist + 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 + return (1-intens)*self.falloff() def calc(self,x,y): """returns field strength at point x,y""" - dist=sqrt( (x-self.center.x)**2 + (y-self.center.y)**2 ) - return max(0,(self.falloff-dist)/self.falloff) + dist=pairdist(Pair(x,y),self.center()) + return max(0,(self.falloff()-dist)/self.falloff()) -class FieldSet: +class Fieldset(collectiveelement): """group of fields. persistent.""" - fields=None # lightname : Field - def __init__(self): - self.fields={} - def loadfromfile(self,f): - for line in f: - try: - col=line.index(':') - newfield=Field() - name=line[:col] - params={} - for param in line[col+1:].split(): - eq=param.index('=') - params[param[:eq]]=float(param[eq+1:]) - newfield.setcenter(params['x'],params['y']) - newfield.setfalloff(params['falloff']) - self.addfield(name,newfield) - newfield.name=name # fields have names? - except ValueError: - print "error on line: %s" % line - def savetofile(self,f): - for name,field in self.fields.items(): - print >>f,"%s: x=%s y=%s falloff=%s" % (name, - field.center.x,field.center.y, - field.falloff) - def addfield(self,name,f): - """consider the given field in this set with the given name""" - assert isinstance(f,Field) - self.fields[name]=f + 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""" - for name,f in self.fields.items(): + active=0 + for f in self.getall(): + name=f.name() intens=f.calc(x,y) if intens>0: print name,intens, - print + active+=1 + if active>0: + print def getbounds(self): """returns xmin,xmax,ymin,ymax for the non-zero areas of this field""" r=None - for f in self.fields.values(): + for f in self.getall(): rad=f.getdistforintensity(0) - fx,fy=f.center.x,f.center.y + fx,fy=f.center() fieldrect=Rect(fx-rad,fy-rad,rad*2,rad*2) if r is None: r=fieldrect @@ -82,70 +81,141 @@ class FieldSet: 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 draw(self): + + 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 - def oval(rad,color): - p1=w2c(*(f.center-Pair(rad,rad))) - p2=w2c(*(f.center+Pair(rad,rad))) - c.create_oval(p1[0],p1[1],p2[0],p2[1], - outline=color,width=2,tags=self.tags, - outlinestipple='gray50') - - oval(.01,'white') - for intens,color in ((1,'white'), + # make rings + self.rings={} # rad,canvasobj + for intens,color in (#(1,'white'), (.8,'gray90'),(.6,'gray80'),(.4,'gray60'),(.2,'gray50'), (0,'#000080')): - - oval(f.getdistforintensity(intens),color) - if hasattr(f,'name'): - p1=w2c(*f.center) - c.create_text(p1[0],p1[1],text=f.name, - fill='white',anchor='c') - + 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="arial 10 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 + dispatcher.send("field coord changed") + self.setcoords() # redraw + def release(ev): + del self._lastmouse + c.tag_bind(self.txt,"",press) + c.tag_bind(self.txt,"",motion) + c.tag_bind(self.txt,"",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) + dispatcher.send("field coord changed") + self.setcoords() + c.tag_bind(outerring,"",motion) + + self.setcoords() + class Tracker(tk.Frame): """whole tracker widget, which is mostly a view for a - FieldSet. tracker makes its own fieldset""" + 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.fieldset=FieldSet() - self.displays={} # Field : FieldDisplay + self.displays={} - c=Zooming(self,bg='black') + c=self.canvas=Zooming(self,bg='black',closeenough=5) c.pack(fill='both',exp=1) - for x,y in ((5,5),(5,95),(95,5),(95,95)): - c.create_text(x,y,text="%s,%s"%(x,y),fill='white') - + # preserve edge coords over window resize c.bind("",self.configcoords) - self.canvas=c + c.bind("", + lambda ev: self._fieldset().report(*c.canvas2world(ev.x,ev.y))) + def save(ev): + print "saving" + self.fieldsetfile.save() + master.bind("",save) + dispatcher.connect(self.autobounds,"field coord changed") - c.bind("", - lambda ev: self.fieldset.report(*c.canvas2world(ev.x,ev.y))) + 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 canvas coords to line up right + # 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, @@ -155,24 +225,30 @@ class Tracker(tk.Frame): c.winfo_height()/(self.ymax-self.ymin)) def autobounds(self): - """figure out our bounds from the fieldset, and adjust the display zooms""" - self.xmin,self.xmax,self.ymin,self.ymax=self.fieldset.getbounds() + """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() - def addfield(self,name,f): - """add the named Field to this display""" - self.fieldset.addfield(name,f) - self.displays[f]=FieldDisplay(self.canvas,f) - self.displays[f].draw() + 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() tra=Tracker(root) tra.pack(fill='both',exp=1) -tra.fieldset.loadfromfile(file("fieldsets/demo")) -for f in tra.fieldset.fields.values(): - tra.displays[f]=FieldDisplay(tra.canvas,f) - tra.displays[f].draw() -tra.autobounds() +tra.load("fieldsets/demo") root.mainloop()