Files @ 69ca2b2fc133
Branch filter:

Location: light9/light8/Lightboard.py

drewp@bigasterisk.com
overcomplicated attempt at persisting the pane layout in the rdf graph

this was hard because we have to somehow wait for the graph to load before config'ing the panes
from __future__ import nested_scopes,division

from Tix import *
from signal import signal, SIGINT
from time import time
import sys, cPickle, random

from uihelpers import *
from panels import *
from Xfader import *
from subediting import Subediting
from Fader import Fader
from ExternalInput import ExternalSliders
import io, stage, Subs, Patch, ExtSliderMapper
import dmxclient

class Pickles:
    def __init__(self, scalelevels, subs=None, windowpos=None):
        self.scalelevels = dict([(name, lev.get()) 
            for name, lev in scalelevels.items()])
        self.substate = dict([(name, subobj.get_state())
            for name, subobj in subs])
        self.windowpos = windowpos

class Lightboard:
    def __init__(self, master, DUMMY):
        self.master = master

        self.DUMMY = DUMMY
        self.jostle_mode = 0
        self.lastline = None

        self.channel_levels = [] # these are actually the labels for the
                                 # channel levels, and should probably be moved
                                 # to panels.Leveldisplay
        self.scalelevels = {}

        self.lastsublevels = {}  # to determine which subs changed
        self.unchangedeffect = {} # dict of levels for lights that didn't 
                                  # change last time
        self.lastunchangedgroup = {}

        # doesn't draw any UI yet-- look for self.xfader.setupwidget()
        self.xfader = Xfader(self.scalelevels) 
        self.oldlevels = [None] * 68 # never replace this; just clear it
        self.subediting = Subediting(currentoutputlevels=self.oldlevels)

        self.windowpos = 0
        self.get_data()
        self.buildinterface()
        self.load()

        print "Light 8.8: Entering backgroundloop"
        self.backgroundloop()
        self.updatestagelevels()
        self.rec_file = open('light9.log', 'a')
        self.record_start()
        
    def buildinterface(self):
        print "Light 8.8: Constructing interface..."
        for w in self.master.winfo_children():
            w.destroy()

        print "\tstage"
        stage_tl = toplevelat('stage')
        s = stage.Stage(stage_tl)
        stage.createlights(s)
        s.setsubediting(self.subediting)
        s.pack()
        self.stage = s # save it

        sub_tl = toplevelat('sub')
        scene_tl = toplevelat('scenes')
        effect_tl = toplevelat('effect')

        print "\tslider patching -- It can't be turned off!"
        mapping_tl = toplevelat('mapping')
        self.slidermapper = ExtSliderMapper.ExtSliderMapper(mapping_tl, 
                                                            self.scalelevels, 
                                                            ExternalSliders(),
                                                            self)
        self.slidermapper.pack()

        print "\tsubmaster control"
        self.subpanels = Subpanels(sub_tl, effect_tl, scene_tl, self, 
                                   self.scalelevels, Subs, self.xfader, 
                                   self.changelevel, self.subediting, 
                                   Subs.longestsubname())

        for n, lev in self.scalelevels.items():
            self.lastsublevels[n] = lev.get()

        print "\tlevel display"
        leveldisplay_tl = toplevelat('leveldisplay')
        leveldisplay_tl.bind('<Escape>', sys.exit)

        self.leveldisplay = Leveldisplay(leveldisplay_tl, self.channel_levels)
        # I don't think we need this
        # for i in range(0,len(self.channel_levels)):
            # self.channel_levels[i].config(text=self.oldlevels[i])
            # colorlabel(self.channel_levels[i])

        print "\tconsole"
        Console(self)

        # root frame
        print "\tcontrol panel"
        self.master.configure(bg='black')
        controlpanel = Controlpanel(self.master, self.xfader, self.refresh, 
            self.quit, self.toggle_jostle, self.nonzerosubs)
        
        print "\tcrossfader"
        xf=Frame(self.master)
        xf.pack(side='right')

        self.master.bind('<q>', self.quit)
        self.master.bind('<r>', self.refresh)
        leveldisplay_tl.bind('<q>', self.quit)
        leveldisplay_tl.bind('<r>', self.refresh)

        self.xfader.setupwidget(xf)
        controlpanel.pack()

        print "\tcue fader (skipped)"
        # cuefader_tl = toplevelat('cuefader')
        # cuefader = Fader(cuefader_tl, Subs.cues, self.scalelevels)
        # cuefader.pack()
        print "Light 8.8: Everything's under control"


    def get_data(self,*args):
        Subs.reload_data(self.DUMMY)
        Patch.reload_data(self.DUMMY)
        print "Light 8.8:", len(Patch.patch), "dimmers patched"
        print "Light 8.8:", len(Subs.subs), "submasters loaded"

    def refresh(self, *args):
        'rebuild interface, reload data'
        print "Light 8.8: Refresh initiated.  Cross your fingers."
        self.get_data()
        print "Light 8.8: Subediting refreshed"
        self.subediting.refresh()
        print "Light 8.8: Rebuilding interface..."
        self.buildinterface()
        bindkeys(self.master,'<Escape>', self.quit)
        print "Light 8.8: Setting up slider patching..."
        self.slidermapper.setup()
        # self.master.tk_setPalette('gray40')
        print "Light 8.8: Now back to your regularly scheduled Light 8"

    def stageassub(self):
        """returns the current onstage lighting as a levels
        dictionary, skipping the zeros, and using names where
        possible"""
        levs=self.oldlevels
        
        return dict([(Patch.get_channel_name(i),l) for i,l
                     in zip(range(1,len(levs)+1),levs)
                     if l>0])
    def save_sub(self, name, levels, refresh=1):
        if not name:
            print "Enter sub name in console."
            return

        st = ''
        linebuf = 'subs["%s"] = {' % name
        for channame,lev in levels.items():
            if len(linebuf) > 60: 
                st += linebuf + '\n   '
                linebuf = ''

            linebuf += ' "%s" : %d,' % (channame, lev)
        st += linebuf + '}\n'
        if self.DUMMY:
            filename = 'ConfigDummy.py'
        else:
            filename = 'Config.py'
        f = open(filename, 'a')
        f.write(st)
        f.close()
        print 'Added sub:', st
        if refresh:
            self.refresh()

    # this is called on a loop, and ALSO by the Scales
    def changelevel(self, *args):
        'Amp trims slider'

        # load levels from external sliders
        extlevels = self.slidermapper.get_levels()
        for name, val in extlevels.items():
            if name in self.scalelevels:
                sl = self.scalelevels[name]
                sl.set(val)
        
        # newstart = time()

        # learn what changed
        unchangedgroup = {}
        changedgroup = {}
        for name, lastlevel in self.lastsublevels.items():
            newlevel = self.scalelevels[name].get()
            if lastlevel != newlevel:
                changedgroup[name] = newlevel
            else:
                unchangedgroup[name] = newlevel

        changedeffect = {}
        if not changedgroup:
            # should load levels from last time
            pass
        else:
            # calculate effect of new group.  this should take no time if 
            # nothing changed
            for name, level in changedgroup.items():
                newlevels = Subs.subs[name].get_levels(level=level)
                for (ch, fadelev) in newlevels.items():
                    changedeffect[ch-1] = \
                        max(changedeffect.get(ch-1, 0), fadelev)

        if unchangedgroup != self.lastunchangedgroup:
            # unchanged group changed! (confusing, huh?)
            # this means: the static subs from the last time are not the same 
            # as they are this time, so we recalculate effect of unchanged group
            self.unchangedeffect = {}
            for name, level in unchangedgroup.items():
                newlevels = Subs.subs[name].get_levels(level=level)
                for (ch, fadelev) in newlevels.items():
                    self.unchangedeffect[ch-1] = \
                        max(self.unchangedeffect.get(ch-1, 0), fadelev)
            self.lastunchangedgroup = unchangedgroup
                
        # record sublevels for future generations (iterations, that is)
        for name in self.lastsublevels:
            self.lastsublevels[name] = self.scalelevels[name]

        # merge effects together
        levels = [0] * 68
        if changedeffect:
            levels = [int(max(changedeffect.get(ch, 0), 
                              self.unchangedeffect.get(ch, 0)))
                        for ch in range(0, 68)]
        else:
            levels = [int(self.unchangedeffect.get(ch, 0))
                        for ch in range(0, 68)]

        '''
        newend = time()

        levels_opt = levels
        
        oldstart = time()
        # i tried to optimize this to a dictionary, but there was no speed
        # improvement
        levels = [0] * 68
        for name, s in Subs.subs.items():
            sublevel = self.scalelevels[name].get()
            newlevels = s.get_levels(level=sublevel)
            self.lastsublevels[name] = sublevel # XXX remove
            for (ch, fadelev) in newlevels.items():
                levels[ch-1] = max(levels[ch-1], fadelev)
        levels = [int(l) for l in levels]
        oldend = time()

        newtime = newend - newstart
        oldtime = oldend - oldstart
        print "new", newtime, 'old', (oldend - oldstart), 'sup', \
               oldtime / newtime
        
        if levels != levels_opt: 
            raise "not equal"
            # for l, lo in zip(levels, levels_opt):
                # print l, lo
        '''
        
        for lev,lab,oldlev,numlab in zip(levels, self.channel_levels, 
                                         self.oldlevels, 
                                         self.leveldisplay.number_labels):
            if lev != oldlev:
                lab.config(text="%d" % lev) # update labels in lev display
                colorlabel(lab)             # recolor labels
                if lev < oldlev:
                    numlab['bg'] = 'blue'
                else:
                    numlab['bg'] = 'red'
            else:
                numlab['bg'] = 'grey40'

        # replace the elements in oldlevels - don't make a new list 
        # (Subediting is watching it)
        self.oldlevels[:] = levels[:]
            
        if self.jostle_mode:
            delta = random.randrange(-1, 2, 1) # (-1, 0, or 1)
            # print "delta", delta
            levels = [min(100, max(x + delta, 0)) for x in levels]
            # print "jostled", levels

        dmxclient.outputlevels([l/100 for l in levels])
#        self.parportdmx.sendlevels(levels)

    def updatestagelevels(self):
        self.master.after(100, self.updatestagelevels)
        for lev, idx in zip(self.oldlevels, xrange(0, 68 + 1)):
            self.stage.updatelightlevel(Patch.get_channel_name(idx + 1), lev)

    def load(self):
        try:
            filename = '/tmp/light9.prefs'
            if self.DUMMY:
                filename += '.dummy'
            print "Light 8.8: Loading from", filename
            file = open(filename, 'r')
            p = cPickle.load(file)
            for s, v in p.scalelevels.items():
                try:
                    self.scalelevels[s].set(v)
                except Exception,e:
                    print "Couldn't set %s -> %s: %s" % (s, v,e)
            for name, substate in p.substate.items():
                try:
                    Subs.subs[name].set_state(substate)
                except Exception, e:
                    print "Couldn't set sub %s state: %s" % (name,e)
        except IOError, e:
            print "IOError: Couldn't load prefs (%s): %s" % (filename,e)
        except EOFError, e:
            print "EOFrror: Couldn't load prefs (%s): %s" % (filename,e)
        except Exception,e:
            print "Couldn't load prefs (%s): %s" % (filename,e)
        self.slidermapper.setup()

    def backgroundloop(self, *args):
        self.master.after(150, self.backgroundloop, ())
        self.changelevel()
    def quit(self, *args):
        print "Light 8.8: And that's my cue to exit..."
        self.save()
        self.record_end()
        self.master.destroy()
        sys.exit()
    def save(self, *args):
        filename = '/tmp/light9.prefs'
        if self.DUMMY:
            filename += '.dummy'
        print "Light 8.8: Saving to", filename
        file = open(filename, 'w')

        try:
            cPickle.dump(Pickles(self.scalelevels, Subs.subs.items()), file)
        except cPickle.UnpickleableError:
            print "UnpickleableError!  There's yer problem."
    def toggle_jostle(self, *args):
        self.jostle_mode = not self.jostle_mode
        if self.jostle_mode:
            print 'Light 8.8: Perhaps we can jost-le your memory?'
        else:
            print 'Light 8.8: He remembers! (jostle off)'
    def nonzerosubs(self, *args):
        print "Light 8.8: Active subs:"
        for n, dv in self.scalelevels.items():
            if dv.get() > 0:
                print "%-.4f: %s" % (dv.get(), n)
    def record_start(self):
        print "Light 8.8: Recorder started"
        self.rec_file.write("%s:\t%s\n" % (time(), "--- Start ---"))
        self.record_stamp()
    def record_end(self):
        print "Light 8.8: Recorder shutdown"
        self.rec_file.write("%s:\t%s\n" % (time(), "--- End ---"))
    def record_stamp(self):
        'Record the current submaster levels, continue this loop'
        levels = []
        for n, v in self.scalelevels.items():
            lev = v.get()
            if lev:
                levels.append('%s\t%s' % (n, lev))


        newdata = '\t'.join(levels) 
        if newdata!=self.lastline:
            template = "%s:\t%s\n" % (time(), newdata)
            self.rec_file.write(template)
            self.lastline = newdata
        self.master.after(100, self.record_stamp)
    def highlight_sub(self, name, color):
        self.subediting.colorsub(name, color)