From 576942ca8818c7e4b7dc764e7d6f9ccb7089c095 Mon Sep 17 00:00:00 2001 From: Mark Mine Date: Tue, 17 Oct 2000 18:31:39 +0000 Subject: [PATCH] *** empty log message *** --- direct/src/tkpanels/AnimPanel.py | 1 + direct/src/tkpanels/FSMInspector.py | 1 + direct/src/tkpanels/ParticlePanel.py | 350 ++++++++++++++++++++ direct/src/tkpanels/Placer.py | 389 ++++++++++++++++++++++ direct/src/tkpanels/animPanel.py | 345 +++++++++++++++++++ direct/src/tkwidgets/Dial.py | 458 ++++++++++++++++++++++++++ direct/src/tkwidgets/Floater.py | 209 ++++++++++++ direct/src/tkwidgets/VectorWidgets.py | 286 ++++++++++++++++ 8 files changed, 2039 insertions(+) create mode 100644 direct/src/tkpanels/AnimPanel.py create mode 100644 direct/src/tkpanels/FSMInspector.py create mode 100644 direct/src/tkpanels/ParticlePanel.py create mode 100644 direct/src/tkpanels/Placer.py create mode 100644 direct/src/tkpanels/animPanel.py create mode 100644 direct/src/tkwidgets/Dial.py create mode 100644 direct/src/tkwidgets/Floater.py create mode 100644 direct/src/tkwidgets/VectorWidgets.py diff --git a/direct/src/tkpanels/AnimPanel.py b/direct/src/tkpanels/AnimPanel.py new file mode 100644 index 0000000000..190a18037c --- /dev/null +++ b/direct/src/tkpanels/AnimPanel.py @@ -0,0 +1 @@ +123 diff --git a/direct/src/tkpanels/FSMInspector.py b/direct/src/tkpanels/FSMInspector.py new file mode 100644 index 0000000000..190a18037c --- /dev/null +++ b/direct/src/tkpanels/FSMInspector.py @@ -0,0 +1 @@ +123 diff --git a/direct/src/tkpanels/ParticlePanel.py b/direct/src/tkpanels/ParticlePanel.py new file mode 100644 index 0000000000..4f0e1eed41 --- /dev/null +++ b/direct/src/tkpanels/ParticlePanel.py @@ -0,0 +1,350 @@ +"PANDA3D Particle Panel" + +# Import Tkinter, Pmw, and the floater code from this directory tree. +from Tkinter import * +import Pmw +import dial +import floater +import vectorWidgets + +class ParticlePanel(Pmw.MegaToplevel): + def __init__(self, parent = None, **kw): + + INITOPT = Pmw.INITOPT + optiondefs = ( + ('title', 'Particle Panel', None), + ) + self.defineoptions(kw, optiondefs) + + Pmw.MegaToplevel.__init__(self, parent, title = self['title']) + + # Handle to the toplevels hull + hull = self.component('hull') + + balloon = self.balloon = Pmw.Balloon(hull) + # Start with balloon help disabled + self.balloon.configure(state = 'none') + + menuFrame = Frame(hull, relief = GROOVE, bd = 2) + menuFrame.pack(fill = X, expand = 1) + + menuBar = Pmw.MenuBar(menuFrame, hotkeys = 1, balloon = balloon) + menuBar.pack(side = LEFT, expand = 1, fill = X) + menuBar.addmenu('Particles', 'Particle Panel Operations') + menuBar.addmenuitem('Particles', 'command', + 'Exit Particles Panel', + label = 'Exit', + command = self.destroy) + + menuBar.addmenu('Help', 'Particle Panel Help Operations') + self.toggleBalloonVar = IntVar() + self.toggleBalloonVar.set(0) + menuBar.addmenuitem('Help', 'checkbutton', + 'Toggle balloon help', + label = 'Balloon Help', + variable = self.toggleBalloonVar, + command = self.toggleBalloon) + + self.systemSelector = Pmw.ComboBox(menuFrame, + labelpos = W, + label_text = 'Particle System:', + entry_width = 16, + selectioncommand = self.selectSystemNamed, + scrolledlist_items = ('system 0',)) + self.systemSelector.selectitem('system 0') + self.systemSelector.pack(side = 'left', expand = 0) + + # Create the notebook pages + notebook = Pmw.NoteBook(hull) + notebook.pack(fill = BOTH, expand = 1) + systemPage = notebook.add('System') + factoryPage = notebook.add('Factory') + emitterPage = notebook.add('Emitter') + rendererPage = notebook.add('Renderer') + + # System page + systemWidgets = ( + ('Pool size', 'Size of particle pool', 0.0, 1.0), + ('Birth rate', 'Seconds between particle births', 0.0, None), + ('Litter size', 'Number of particle created at each birth', 1.0, 1.0), + ('Litter spread', 'Variation in litter size', 0.0, 1.0), + ('System lifespan', 'Age in seconds at which system should die', 0.0, None) + ) + self.createFloaters(systemPage, systemWidgets) + Checkbutton(systemPage, text = 'Local velocity',anchor = W).pack( + fill = X) + Checkbutton(systemPage, text = 'System grows older',anchor = W).pack( + fill = X) + pos = self.createVector3Entry(systemPage, 'Pos', + 'Particle system position') + pos.addMenuItem('Popup 3DoF Panel') + hpr = self.createVector3Entry(systemPage, 'Hpr', + 'Particle system orientation', + floaterGroup_labels = ('H', 'P', 'R')) + hpr.addMenuItem('Popup 3DoF Panel') + + # FACTORY PAGE + self.createOptionMenu(factoryPage, 'Factory type:', + 'Select type of particle factory', + ('Point', 'Z Spin', 'Oriented'), + self.selectFactoryType) + factoryWidgets = ( + ('Life span', 'Average lifespan in seconds', 0.0, None), + ('Life span spread', 'Variation in lifespan', 0.0, None), + ('Mass', 'Average particle mass', 0.0, None), + ('Mass spread', 'Variation in particle mass', 0.0, None), + ('Terminal velocity', 'Average particle terminal velocity', 0.0, None), + ('Terminal vel. spread', 'Variation in terminal velocity', 0.0, None)) + self.createFloaters(factoryPage, factoryWidgets) + + self.factoryNotebook = Pmw.NoteBook(factoryPage, tabpos = None) + pointPage = self.factoryNotebook.add('Point') + zSpinPage = self.factoryNotebook.add('Z Spin') + self.createAngleDial(zSpinPage, 'Initial angle', + 'Starting angle in degrees') + self.createAngleDial(zSpinPage, 'Final angle', + 'Final angle in degrees') + self.createAngleDial(zSpinPage, 'Angle spread', + 'Spread of the final angle') + orientedPage = self.factoryNotebook.add('Oriented') + Label(orientedPage, text = 'Not implemented').pack(expand = 1, + fill = BOTH) + self.factoryNotebook.pack(expand = 1, fill = BOTH) + + # EMITTER PAGE + self.createOptionMenu(emitterPage, 'Emitter type:', + 'Select type of particle emitter', + ('Box', 'Disc', 'Line', 'Point', 'Rectangle', + 'Ring', 'Sphere Surface', 'Sphere Volume', + 'Tangent Ring'), + self.selectEmitterType) + self.emitterNotebook = Pmw.NoteBook(emitterPage, tabpos = None) + pointPage = self.emitterNotebook.add('Box') + self.createVector3Entry(pointPage, 'Point 1', + 'Point defining emitter box') + self.createVector3Entry(pointPage, 'Point 2', + 'Point defining emitter box', + initialValue = (1.0, 1.0, 1.0)) + self.createVector3Entry(pointPage, 'Launch vector', + 'Initial particle velocity vector') + + discPage = self.emitterNotebook.add('Disc') + self.createFloater(discPage, 'Radius', 'Radius of disc') + self.createAngleDial(discPage, 'Inner angle', + 'Particle launch angle at center of disc') + self.createFloater(discPage, 'Inner magnitude', + 'Launch velocity multiplier at center of disc') + self.createAngleDial(discPage, 'Outer angle', + 'Particle launch angle at outer edge of disc') + self.createFloater(discPage, 'Outer magnitude', + 'Launch velocity multiplier at edge of disc') + + Checkbutton(discPage, text = 'Cubic Lerping').pack( + side = LEFT, expand = 1, fill = X) + + linePage = self.emitterNotebook.add('Line') + self.createVector3Entry(linePage, 'Point 1', + 'Point defining emitter line') + self.createVector3Entry(linePage, 'Point 2', + 'Point defining emitter line', + initialValue = (1.0, 0.0, 0.0)) + self.createVector3Entry(linePage, 'Launch Vector', + 'Initial particle velocity vector', + initialValue = (0.0, 0.0, 1.0)) + + pointPage = self.emitterNotebook.add('Point') + self.createVector3Entry(pointPage, 'Location', + 'Location of emitter point') + self.createVector3Entry(pointPage, 'Launch vector', + 'Initial particle velocity vector', + initialValue = (0.0, 0.0, 1.0)) + + rectanglePage = self.emitterNotebook.add('Rectangle') + self.createVector3Entry(rectanglePage, 'Point 1', + 'Point defining rectangle') + self.createVector3Entry(rectanglePage, 'Point 2', + 'Point defining rectangle') + self.createVector3Entry(rectanglePage, 'Launch vector', + 'Initial particle velocity vector', + initialValue = (0.0, 0.0, 1.0)) + + ringPage = self.emitterNotebook.add('Ring') + self.createFloater(ringPage, 'Radius', 'Radius of ring') + self.createAngleDial(ringPage, 'Angle', 'Particle launch angle') + self.createFloater(ringPage, 'Magnitude', + 'Launch velocity multiplier at outer edge of ring') + + sphereSurfacePage = self.emitterNotebook.add('Sphere Surface') + self.createFloater(sphereSurfacePage, 'Radius', + 'Radius of sphere') + + sphereVolumePage = self.emitterNotebook.add('Sphere Volume') + self.createFloater(sphereVolumePage, 'Radius', + 'Radius of sphere') + + tangentRingPage = self.emitterNotebook.add('Tangent Ring') + self.createFloater(tangentRingPage, 'Radius', + 'Radius of ring') + + self.emitterNotebook.pack(fill = X) + + # RENDERER PAGE + self.createOptionMenu(rendererPage, 'Renderer type:', + 'Select type of particle renderer', + ('Geom', 'Point', 'Sparkle', 'Sprite'), + self.selectRendererType) + self.rendererNotebook = Pmw.NoteBook(rendererPage, tabpos = None) + geomPage = self.rendererNotebook.add('Geom') + f = Frame(geomPage) + f.pack(fill = X) + Label(f, width = 12, text = 'Geom node:').pack(side = LEFT) + Entry(f, width = 12).pack(side = LEFT, expand = 1, fill = X) + + pointPage = self.rendererNotebook.add('Point') + self.createFloater(pointPage, 'Point size', + 'Width and height of points in pixels') + self.createColorEntry(pointPage, 'Start color', + 'Starting color of point') + self.createColorEntry(pointPage, 'End color', + 'Ending color of point') + self.createOptionMenu(pointPage, 'Blend type:', + 'Type of color blending used for particle', + ('ONE_COLOR', 'BLEND_LIFE', 'BLEND_VEL'), + self.selectBlendType) + self.createOptionMenu(pointPage, 'Blend method:', + 'Interpolation method between colors', + ('LINEAR', 'CUBIC'), + self.selectBlendMethod) + + sparklePage = self.rendererNotebook.add('Sparkle') + self.createColorEntry(sparklePage, 'Center color', + 'Color of sparkle center') + self.createColorEntry(sparklePage, 'Edge color', + 'Color of sparkle line endpoints') + self.createFloater(sparklePage, 'Birth radius', + 'Initial sparkle radius') + self.createFloater(sparklePage, 'Death radius', + 'Final sparkle radius') + self.createOptionMenu(pointPage, 'Life scale:', + 'Does particle scale over its lifetime?', + ('NO_SCALE', 'SCALE'), + self.selectBlendMethod) + + spritePage = self.rendererNotebook.add('Sprite') + f = Frame(spritePage) + f.pack(fill = X) + Label(f, width = 12, text = 'Texture:').pack(side = LEFT) + Entry(f, width = 12).pack(side = LEFT, expand = 1, fill = X) + + Checkbutton(spritePage, text = 'xScale',anchor = W).pack(fill = X) + Checkbutton(spritePage, text = 'yScale',anchor = W).pack(fill = X) + Checkbutton(spritePage, text = 'animAngle',anchor = W).pack(fill = X) + self.createFloater(spritePage, 'Initial X Scale', + 'Initial X scaling factor') + self.createFloater(spritePage, 'Final X Scale', + 'Final X scaling factor') + self.createFloater(spritePage, 'Initial Y Scale', + 'Initial Y scaling factor') + self.createFloater(spritePage, 'Final Y Scale', + 'Final Y scaling factor') + self.createAngleDial(spritePage, 'Non Animated Theta', + 'Counter clockwise Z rotation of all sprites') + self.createOptionMenu(spritePage, 'Blend Type', + 'Interpolation blend type for X and Y scaling', + ('LINEAR', 'CUBIC'), + self.selectBlendMethod) + Checkbutton(spritePage, text = 'alphaDisable',anchor = W).pack(fill = X) + + + self.rendererNotebook.pack(fill = X) + + self.factoryNotebook.setnaturalsize() + self.emitterNotebook.setnaturalsize() + self.rendererNotebook.setnaturalsize() + notebook.setnaturalsize() + + # Make sure input variables processed + self.initialiseoptions(ParticlePanel) + + def createFloaters(self, parent, widgetDefinitions): + for label, balloonHelp, min, resolution in widgetDefinitions: + self.createFloater(parent, label, balloonHelp, min, resolution) + + def createFloater(self, parent, text, balloonHelp, + min = 0.0, resolution = None, **kw): + kw['text'] = text + kw['min'] = min + kw['initialValue'] = min + kw['resolution'] = resolution + widget = apply(floater.Floater, (parent,), kw) + widget.pack(fill = X) + self.balloon.bind(widget, balloonHelp) + return widget + + def createAngleDial(self, parent, text, balloonHelp, **kw): + kw['text'] = text + widget = apply(dial.AngleDial,(parent,), kw) + widget.pack(fill = X) + self.balloon.bind(widget, balloonHelp) + return widget + + def createVector3Entry(self, parent, text, balloonHelp, **kw): + # Set label's text + kw['text'] = text + widget = apply(vectorWidgets.Vector3Entry, (parent,), kw) + widget.pack(fill = X) + self.balloon.bind(widget, balloonHelp) + return widget + + def createColorEntry(self, parent, text, balloonHelp, **kw): + # Set label's text + kw['text'] = text + widget = apply(vectorWidgets.ColorEntry, (parent,) ,kw) + widget.pack(fill = X) + self.balloon.bind(widget, balloonHelp) + return widget + + def createOptionMenu(self, parent, text, balloonHelp, items, command): + optionVar = StringVar() + optionVar.set(items[0]) + widget = Pmw.OptionMenu(parent, labelpos = W, label_text = text, + label_width = 12, menu_tearoff = 1, + menubutton_textvariable = optionVar, + items = items, + command = command) + widget.pack(fill = X) + self.balloon.bind(widget.component('menubutton'), balloonHelp) + return optionVar + + def selectFactoryType(self, type): + self.factoryNotebook.selectpage(type) + + def selectEmitterType(self, type): + self.emitterNotebook.selectpage(type) + + def selectRendererType(self, type): + self.rendererNotebook.selectpage(type) + + def selectBlendType(self, type): + print type + + def selectBlendMethod(self, method): + print method + + def selectSystemNamed(self, name): + print name + + def toggleBalloon(self): + if self.toggleBalloonVar.get(): + self.balloon.configure(state = 'balloon') + else: + self.balloon.configure(state = 'none') + +###################################################################### + +# Create demo in root window for testing. +if __name__ == '__main__': + root = Pmw.initialise() + pp = ParticlePanel() + #ve = VectorEntry(Toplevel(), relief = GROOVE) + #ve.pack() diff --git a/direct/src/tkpanels/Placer.py b/direct/src/tkpanels/Placer.py new file mode 100644 index 0000000000..326922702e --- /dev/null +++ b/direct/src/tkpanels/Placer.py @@ -0,0 +1,389 @@ +"DIRECT Nine DoF Placer demonstration" + +# Import Tkinter, Pmw, and the floater code from this directory tree. +from Tkinter import * +import Pmw +import floater +import dial + +class Placer(Pmw.MegaToplevel): + def __init__(self, parent = None, **kw): + + INITOPT = Pmw.INITOPT + optiondefs = ( + ('title', 'Placer Panel', None), + ('nodePath', None, None), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the superclass + Pmw.MegaToplevel.__init__(self, parent) + + # Handle to the toplevels hull + hull = self.component('hull') + + menuFrame = Frame(hull, relief = GROOVE, bd = 2) + menuFrame.pack(fill = X, expand = 1) + + balloon = self.balloon = Pmw.Balloon() + # Start with balloon help disabled + self.balloon.configure(state = 'none') + + menuBar = Pmw.MenuBar(menuFrame, hotkeys = 1, balloon = balloon) + menuBar.pack(side = LEFT, expand = 1, fill = X) + menuBar.addmenu('Placer', 'Placer Panel Operations') + menuBar.addcascademenu('Placer', 'Axis', + 'Control axis visibility', + tearoff = 1) + self.axisViz = StringVar() + self.axisViz.set('Show Axis') + menuBar.component('Axis-menu').add_radiobutton( + label = 'Show Axis', + variable = self.axisViz, + value = 'Show Axis', + command = lambda s = self: s._updateAxisViz()) + menuBar.component('Axis-menu').add_radiobutton( + label = 'Hide Axis', + variable = self.axisViz, + value = 'Hide Axis', + command = lambda s = self: s._updateAxisViz()) + menuBar.component('Axis-menu').add_radiobutton( + label = 'Auto Axis', + variable = self.axisViz, + value = 'Auto Axis', + command = lambda s = self: s._updateAxisViz()) + + menuBar.addmenuitem('Placer', 'command', + 'Exit Placer Panel', + label = 'Exit', + command = self.destroy) + menuBar.addmenu('NodePath', 'Node Path Operations') + menuBar.addmenuitem('NodePath', 'command', + 'Undo Pos/Hpr/Scale', + label = 'Undo All', + command = self._undoAll) + menuBar.addmenuitem('NodePath', 'command', + 'Redo Pos/Hpr/Scale', + label = 'Redo All', + command = self._redoAll) + menuBar.addmenuitem('NodePath', 'command', + 'Reset Node Path', + label = 'Reset All', + command = self._resetAll) + menuBar.addmenuitem('NodePath', 'command', + 'Print Node Path Info', + label = 'Print Info', + command = self.printNodePathInfo) + + menuBar.addmenu('Help', 'Placer Panel Help Operations') + self.toggleBalloonVar = IntVar() + self.toggleBalloonVar.set(0) + menuBar.addmenuitem('Help', 'checkbutton', + 'Toggle balloon help', + label = 'Balloon Help', + variable = self.toggleBalloonVar, + command = self.toggleBalloon) + + nodePathMenu = Pmw.ComboBox(menuFrame, + labelpos = W, + label_text = 'Node Path:', + entry_width = 12, + selectioncommand = self.selectNodePathNamed, + scrolledlist_items = ('selected', + 'hot point', + 'camera')) + nodePathMenu.selectitem('selected') + nodePathMenu.pack(side = 'left', expand = 0) + + mode = StringVar() + mode.set('Drive') + modeMenu = Pmw.OptionMenu(menuFrame, + menubutton_textvariable=mode, + items = ('Drive', 'Orbit', + 'Absolute', 'Relative'), + command = self._updateFloaterLabels, + menubutton_width = 8) + modeMenu.pack(side = 'left', expand = 0) + + self.wrtMenu = Pmw.ComboBox(menuFrame, + labelpos = W, + label_text = 'WRT:', + entry_width = 12, + selectioncommand = self.selectNodePathNamed, + scrolledlist_items = ('render', + 'selected', + 'camera')) + self.wrtMenu.selectitem('render') + self.wrtMenu.pack(side = 'left', expand = 0) + + # The master frame for the dials + dialFrame = Frame(hull) + dialFrame.pack(fill = 'both', expand = 1) + + # Create and pack the Pos Controls + posGroup = Pmw.Group(dialFrame, + tag_pyclass = Menubutton, + tag_text = 'Position', + tag_font=('MSSansSerif', 14, 'bold'), + tag_activebackground = '#909090', + ring_relief = 'raised') + posMenubutton = posGroup.component('tag') + posMenu = Menu(posMenubutton) + posMenu.add_command(label = 'Undo', command = self._undoPos) + posMenu.add_command(label = 'Redo', command = self._redoPos) + posMenu.add_command(label = 'Set to zero', command = self._zeroPos) + posMenu.add_command(label = 'Restore initial', command = self._resetPos) + posMenubutton['menu'] = posMenu + posGroup.pack(side='left',fill = 'both', expand = 1) + posInterior = posGroup.interior() + + # Create the floaters + self.posX = self.createcomponent('posX', (), None, + dial.Dial, (posInterior,), + text = 'X', + label_foreground = 'Red') + self.posX['command'] = self.printCommand + self.posX.pack(expand=1,fill='x') + + self.posY = self.createcomponent('posY', (), None, + dial.Dial, (posInterior,), + text = 'Y', + label_foreground = '#00A000') + self.posY['command'] = self.printCommand + self.posY.pack(expand=1,fill='x') + + self.posZ = self.createcomponent('posZ', (), None, + dial.Dial, (posInterior,), + text = 'Z', + label_foreground = 'Blue') + self.posZ['command'] = self.printCommand + self.posZ.pack(expand=1,fill='x') + + # Create and pack the Hpr Controls + hprGroup = Pmw.Group(dialFrame, + tag_pyclass = Menubutton, + tag_text = 'Orientation', + tag_font=('MSSansSerif', 14, 'bold'), + tag_activebackground = '#909090', + ring_relief = 'raised') + hprMenubutton = hprGroup.component('tag') + hprMenu = Menu(hprMenubutton) + hprMenu.add_command(label = 'Undo', command = self._undoHpr) + hprMenu.add_command(label = 'Redo', command = self._redoHpr) + hprMenu.add_command(label = 'Set to zero', command = self._zeroHpr) + hprMenu.add_command(label = 'Restore initial', command = self._resetHpr) + hprMenubutton['menu'] = hprMenu + hprGroup.pack(side='left',fill = 'both', expand = 1) + hprInterior = hprGroup.interior() + + # Create the floaters + self.hprH = self.createcomponent('hprH', (), None, + dial.Dial, (hprInterior,), + text = 'H', fRollover = 0, + max = 360.0, numTicks = 12, + label_foreground = 'blue') + self.hprH['command'] = self.printCommand + self.hprH.pack(expand=1,fill='x') + + self.hprP = self.createcomponent('hprP', (), None, + dial.Dial, (hprInterior,), + text = 'P', fRollover = 0, + max = 360.0, numTicks = 12, + label_foreground = 'red') + self.hprP['command'] = self.printCommand + self.hprP.pack(expand=1,fill='x') + + self.hprR = self.createcomponent('hprR', (), None, + dial.Dial, (hprInterior,), + text = 'R', fRollover = 0, + max = 360.0, numTicks = 12, + label_foreground = '#00A000') + self.hprR['command'] = self.printCommand + self.hprR.pack(expand=1,fill='x') + + # Create and pack the Scale Controls + scaleGroup = Pmw.Group(dialFrame, + tag_text = 'Scale', + tag_pyclass = Menubutton, + tag_font=('MSSansSerif', 14, 'bold'), + tag_activebackground = '#909090', + ring_relief = 'raised') + scaleMenubutton = scaleGroup.component('tag') + scaleMenu = Menu(scaleMenubutton) + scaleModeMenu = Menu(scaleMenu) + # The available scaling modes + self.scalingMode = StringVar() + self.scalingMode.set('Free') + scaleModeMenu.add_radiobutton(label = 'Free', + variable = self.scalingMode) + scaleModeMenu.add_radiobutton(label = 'Uniform', + variable = self.scalingMode) + scaleModeMenu.add_radiobutton(label = 'Proportional', + variable = self.scalingMode) + + # First level scaling menu + scaleMenu.add_command(label = 'Undo', command = self._undoScale) + scaleMenu.add_command(label = 'Redo', command = self._redoScale) + scaleMenu.add_command(label = 'Set to unity', + command = self._unitScale) + scaleMenu.add_command(label = 'Restore initial', + command = self._resetScale) + scaleMenu.add_cascade(label = 'Scaling mode...', + menu = scaleModeMenu) + scaleMenubutton['menu'] = scaleMenu + scaleGroup.pack(side='left',fill = 'both', expand = 1) + scaleInterior = scaleGroup.interior() + + # Create the floaters + self.scaleX = self.createcomponent('scaleX', (), None, + dial.Dial, (scaleInterior,), + text = 'X Scale', + initialValue = 1.0, + label_foreground = 'Red') + self.scaleX['command'] = self.printCommand + self.scaleX.pack(expand=1,fill='x') + + self.scaleY = self.createcomponent('scaleY', (), None, + dial.Dial, (scaleInterior,), + text = 'Y Scale', + initialValue = 1.0, + label_foreground = '#00A000') + self.scaleY['command'] = self.printCommand + self.scaleY.pack(expand=1,fill='x') + + self.scaleZ = self.createcomponent('scaleZ', (), None, + dial.Dial, (scaleInterior,), + text = 'Z Scale', + initialValue = 1.0, + label_foreground = 'Blue') + self.scaleZ['command'] = self.printCommand + self.scaleZ.pack(expand=1,fill='x') + + # Make sure appropriate labels are showing + self._updateFloaterLabels('Drive') + + # Make sure input variables processed + self.initialiseoptions(Placer) + + + def printCommand(self, val): + print 'Current value: %s' % val + + def selectNodePathNamed(self, name): + print 'Selected Node Path: ' + name + + def printNodePathInfo(self): + print 'Print Node Path info here' + + def _updateAxisViz(self): + self.updateAxisViz(self.axisViz.get()) + + def updateAxisViz(self, mode): + print mode + + def _undoPos(self): + print 'undo pos' + + def _redoPos(self): + print 'redo pos' + + def _resetPos(self): + self.posX.reset() + self.posY.reset() + self.posZ.reset() + + def _zeroPos(self): + self.posX.set(0.0) + self.posY.set(0.0) + self.posZ.set(0.0) + + def _undoHpr(self): + print 'undo hpr' + + def _redoHpr(self): + print 'redo hpr' + + def _resetHpr(self): + self.hprH.reset() + self.hprP.reset() + self.hprR.reset() + + def _zeroHpr(self): + self.hprH.set(0.0) + self.hprP.set(0.0) + self.hprR.set(0.0) + + def _resetScale(self): + self.scaleX.reset() + self.scaleY.reset() + self.scaleZ.reset() + + def _undoScale(self): + print 'undo scale' + + def _redoScale(self): + print 'redo scale' + + def _unitScale(self): + self.scaleX.set(1.0) + self.scaleY.set(1.0) + self.scaleZ.set(1.0) + + def _undoAll(self): + self._undoPos() + self._undoHpr() + self._undoScale() + + def _redoAll(self): + self._redoPos() + self._redoHpr() + self._redoScale() + + def _resetAll(self): + self._resetPos() + self._resetHpr() + self._resetScale() + + def _updateFloaterLabels(self, movementMode): + namePrefix = '' + self.movementMode = movementMode + if (movementMode == 'Drive'): + namePrefix = 'Drive delta ' + elif (movementMode == 'Orbit'): + namePrefix = 'Orbit ' + elif (movementMode == 'Absolute'): + namePrefix = 'Absolute ' + elif (movementMode == 'Relative'): + namePrefix = 'Relative ' + + if(movementMode == 'Relative'): + self.wrtMenu.configure(entry_foreground = 'Black') + self.wrtMenu.configure(entry_background = 'SystemWindow') + else: + self.wrtMenu.configure(entry_foreground = 'gray50') + self.wrtMenu.configure(entry_background = '#E0E0E0') + + self.posX['text'] = namePrefix + 'X' + self.posY['text'] = namePrefix + 'Y' + self.posZ['text'] = namePrefix + 'Z' + + if (movementMode == 'Orbit'): + namePrefix = 'Orbit delta ' + + self.hprH['text'] = namePrefix + 'H' + self.hprP['text'] = namePrefix + 'P' + self.hprR['text'] = namePrefix + 'R' + + def toggleBalloon(self): + if self.toggleBalloonVar.get(): + self.balloon.configure(state = 'balloon') + else: + self.balloon.configure(state = 'none') + +###################################################################### + +# Create demo in root window for testing. +if __name__ == '__main__': + root = Pmw.initialise() + widget = Placer() + diff --git a/direct/src/tkpanels/animPanel.py b/direct/src/tkpanels/animPanel.py new file mode 100644 index 0000000000..5c5695975d --- /dev/null +++ b/direct/src/tkpanels/animPanel.py @@ -0,0 +1,345 @@ +"DIRECT Animation Control Panel" + +# Import Tkinter, Pmw, and the floater code from this directory tree. +from Tkinter import * +from tkSimpleDialog import askfloat +import Pmw +import string +import math + +FRAMES = 0 +SECONDS = 1 + +class AnimPanel(Pmw.MegaToplevel): + def __init__(self, parent = None, **kw): + + INITOPT = Pmw.INITOPT + optiondefs = ( + ('title', 'Anim Panel', None), + ('actorList', (), None), + ('Actor_label_width', 12, None), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the superclass + Pmw.MegaToplevel.__init__(self, parent) + + # Handle to the toplevels hull + hull = self.component('hull') + + # A handy little help balloon + balloon = self.balloon = Pmw.Balloon() + # Start with balloon help disabled + self.balloon.configure(state = 'none') + + menuFrame = Frame(hull, relief = GROOVE, bd = 2) + + menuBar = Pmw.MenuBar(menuFrame, hotkeys = 1, balloon = balloon) + menuBar.pack(side = LEFT, expand = 1, fill = X) + menuBar.addmenu('AnimPanel', 'Anim Panel Operations') + # Actor control status + menuBar.addcascademenu('AnimPanel', 'Control Status', + 'Enable/disable actor control panels') + menuBar.addmenuitem('Control Status', 'command', + 'Enable all actor controls', + label = 'Enable all', + command = self.enableActorControls) + menuBar.addmenuitem('Control Status', 'command', + 'Disable all actor controls', + label = 'Disable all', + command = self.disableActorControls) + # Frame Slider units + menuBar.addcascademenu('AnimPanel', 'Display Units', + 'Select display units') + menuBar.addmenuitem('Display Units', 'command', + 'Display frame counts', label = 'Frame count', + command = self.displayFrameCounts) + menuBar.addmenuitem('Display Units', 'command', + 'Display seconds', label = 'Seconds', + command = self.displaySeconds) + # Reset all actor controls + menuBar.addmenuitem('AnimPanel', 'command', + 'Reset Actor controls', + label = 'Reset all', + command = self.resetAll) + # Exit panel + menuBar.addmenuitem('AnimPanel', 'command', + 'Exit Anim Panel', + label = 'Exit', + command = self.destroy) + + menuBar.addmenu('Help', 'Anim Panel Help Operations') + self.toggleBalloonVar = IntVar() + self.toggleBalloonVar.set(0) + menuBar.addmenuitem('Help', 'checkbutton', + 'Toggle balloon help', + label = 'Balloon Help', + variable = self.toggleBalloonVar, + command = self.toggleBalloon) + menuFrame.pack(fill = X) + + # Create a frame to hold all the actor controls + actorFrame = Frame(hull) + + # Create a control for each actor + index = 0 + self.actorControlList = [] + for actor in self['actorList']: + ac = self.createcomponent( + 'actorControl%d' % index, (), 'Actor', + ActorControl, (actorFrame,)) + ac.pack(expand = 1, fill = X) + self.actorControlList.append(ac) + index = index + 1 + + # Now pack the actor frame + actorFrame.pack(expand = 1, fill = BOTH) + + # Create a frame to hold the playback controls + controlFrame = Frame(hull) + self.playPauseVar = IntVar() + self.playPauseVar.set(0) + self.playPauseButton = self.createcomponent( + 'playPause', (), None, + Checkbutton, (controlFrame,), + text = 'Play', width = 8, + variable = self.playPauseVar, + indicatoron = FALSE) + self.playPauseButton.pack(side = LEFT, expand = 1, fill = X) + + self.resetButton = self.createcomponent( + 'reset', (), None, + Button, (controlFrame,), + text = 'Reset All', + width = 8, + command = self.resetAll) + self.resetButton.pack(side = LEFT, expand = 1, fill = X) + + self.loopVar = IntVar() + self.loopVar.set(0) + self.loopButton = self.createcomponent( + 'loopButton', (), None, + Checkbutton, (controlFrame,), + text = 'Loop', width = 8, + variable = self.loopVar) + self.loopButton.pack(side = LEFT, expand = 1, fill = X) + + controlFrame.pack(fill = X) + + # Execute option callbacks + self.initialiseoptions(AnimPanel) + + def getActorControlAt(self, index): + return self.actorControlList[index] + + def enableActorControlAt(self,index): + self.getActorControlAt(index).enableControl() + + def enableActorControls(self): + for actorControl in self.actorControlList: + actorControl.enableControl() + + def disableActorControls(self): + for actorControl in self.actorControlList: + actorControl.disableControl() + + def disableActorControlAt(self,index): + self.getActorControlAt(index).disableControl() + + def displayFrameCounts(self): + for actorControl in self.actorControlList: + actorControl.displayFrameCounts() + + def displaySeconds(self): + for actorControl in self.actorControlList: + actorControl.displaySeconds() + + def resetAll(self): + for actorControl in self.actorControlList: + actorControl.reset() + + def toggleBalloon(self): + if self.toggleBalloonVar.get(): + self.balloon.configure(state = 'balloon') + else: + self.balloon.configure(state = 'none') + +class ActorControl(Pmw.MegaWidget): + def __init__(self, parent = None, **kw): + + INITOPT = Pmw.INITOPT + DEFAULT_FONT = (('MS', 'Sans', 'Serif'), 12, 'bold') + DEFAULT_ANIMS = ('neutral', 'run', 'walk') + optiondefs = ( + ('text', 'Actor', self._updateLabelText), + ('actor', None, None), + ('animList', DEFAULT_ANIMS, None), + ('sLabel_width', 5, None), + ('sLabel_font', DEFAULT_FONT, None), + ) + self.defineoptions(kw, optiondefs) + self.addoptions( + (('active', self['animList'][0], None),) + ) + + # Initialize the superclass + Pmw.MegaWidget.__init__(self, parent) + + # Handle to the toplevels hull + interior = self.interior() + interior.configure(relief = RAISED, bd = 2) + + # Instance variables + self.offset = 0.0 + self.fps = 24.0 + self.maxFrame = 120 + self.maxSeconds = self.maxFrame / self.fps + + # Create component widgets + self._label = self.createcomponent( + 'label', (), None, + Menubutton, (interior,), + font=('MSSansSerif', 14, 'bold'), + relief = RAISED, bd = 1, + activebackground = '#909090', + text = self['text']) + # Top level menu + labelMenu = Menu(self._label, tearoff = 0 ) + + # Menu to select display mode + self.unitsVar = IntVar() + self.unitsVar.set(FRAMES) + displayMenu = Menu(labelMenu, tearoff = 0 ) + displayMenu.add_radiobutton(label = 'Frame count', + value = FRAMES, + variable = self.unitsVar, + command = self.updateDisplay) + displayMenu.add_radiobutton(label = 'Seconds', + value = SECONDS, + variable = self.unitsVar, + command = self.updateDisplay) + # Items for top level menu + labelMenu.add_cascade(label = 'Display Units', menu = displayMenu) + labelMenu.add_command(label = 'Set Offset', command = self.setOffset) + labelMenu.add_command(label = 'Reset', command = self.reset) + # Now associate menu with menubutton + self._label['menu'] = labelMenu + self._label.pack(side = LEFT, fill = X) + + # Combo box to select current animation + animMenu = self.createcomponent( + 'animMenu', (), None, + Pmw.ComboBox, (interior,), + labelpos = W, label_text = 'Anim:', + entry_width = 12, selectioncommand = self.selectAnimNamed, + scrolledlist_items = self['animList']) + animMenu.selectitem(self['active']) + animMenu.pack(side = 'left', padx = 5, expand = 0) + + # Combo box to select frame rate + fpsList = (1,2,4,8,12,15,24,30) + fpsMenu = self.createcomponent( + 'fpsMenu', (), None, + Pmw.ComboBox, (interior,), + labelpos = W, label_text = 'at:', + entry_width = 4, selectioncommand = self.setFrameRate, + scrolledlist_items = fpsList) + fpsMenu.selectitem('24') + fpsMenu.pack(side = LEFT, padx = 5, expand = 0) + + # A label + fpsLabel = Label(interior, text = "fps") + fpsLabel.pack(side = LEFT) + + # Scale to control animation + frameFrame = Frame(interior, relief = SUNKEN, bd = 1) + self.minLabel = self.createcomponent( + 'minLabel', (), 'sLabel', + Label, (frameFrame,), + text = 0) + self.minLabel.pack(side = LEFT) + + self.frameControl = self.createcomponent( + 'scale', (), None, + Scale, (frameFrame,), + from_ = 0.0, to = self.maxFrame, resolution = 1.0, + orient = HORIZONTAL, showvalue = 1) + self.frameControl.pack(side = LEFT, expand = 1) + + self.maxLabel = self.createcomponent( + 'maxLabel', (), 'sLabel', + Label, (frameFrame,), + text = self.maxFrame) + self.maxLabel.pack(side = LEFT) + frameFrame.pack(side = LEFT, expand = 1, fill = X) + + # Checkbutton to enable/disable control + self.frameActiveVar = IntVar() + self.frameActiveVar.set(1) + frameActive = self.createcomponent( + 'checkbutton', (), None, + Checkbutton, (interior,), + variable = self.frameActiveVar) + frameActive.pack(side = LEFT, expand = 1) + + # Execute option callbacks + self.initialiseoptions(ActorControl) + + def _updateLabelText(self): + self._label['text'] = self['text'] + + def updateDisplay(self): + # Switch between showing frame counts and seconds + if self.unitsVar.get() == FRAMES: + newMin = int(math.floor(self.offset * self.fps)) + newMax = int(math.ceil(self.offset * self.fps)) + self.maxFrame + self.minLabel['text'] = newMin + self.maxLabel['text'] = newMax + self.frameControl.configure(to = newMax, resolution = 1.0) + else: + newMin = self.offset + newMax = self.offset + self.maxSeconds + self.minLabel['text'] = "%.1f" % newMin + self.maxLabel['text'] = "%.1f" % newMax + print newMin, newMax + self.frameControl.configure(to = newMax, resolution = 0.1) + + def selectAnimNamed(self, name): + print 'Selected Anim: ' + name + + def setFrameRate(self, rate): + self.fps = string.atof(rate) + self.maxSeconds = self.maxFrame / self.fps + self.updateDisplay() + + def setOffset(self): + newOffset = askfloat(title = self['text'], + prompt = 'Start offset (seconds):') + if newOffset: + self.offset = newOffset + self.updateDisplay() + + def enableControl(self): + self.frameActiveVar.set(1) + + def disableControl(self): + self.frameActiveVar.set(0) + + def displayFrameCounts(self): + self.unitsVar.set(FRAMES) + self.updateDisplay() + + def displaySeconds(self): + self.unitsVar.set(SECONDS) + self.updateDisplay() + + def reset(self): + self.offset = 0.0 + self.frameControl.set(0.0) + self.updateDisplay() +###################################################################### + +# Create demo in root window for testing. +if __name__ == '__main__': + widget = AnimPanel(actorList = (1,2,3)) + diff --git a/direct/src/tkwidgets/Dial.py b/direct/src/tkwidgets/Dial.py new file mode 100644 index 0000000000..b113cba812 --- /dev/null +++ b/direct/src/tkwidgets/Dial.py @@ -0,0 +1,458 @@ +from Tkinter import * +from tkSimpleDialog import askfloat +import Pmw +import math +import string +import operator + +# TODO: +# More standardized use of 'max' and 'min' +# Better floater style action +# New option? 'delta'? 'repeatVal'? 'modulus' + +TWO_PI = 2.0 * math.pi +ONEPOINTFIVE_PI = 1.5 * math.pi +POINTFIVE_PI = 0.5 * math.pi +INNER_SF = 0.175 +MAX_EXP = 5 + +class Dial(Pmw.MegaWidget): + def __init__(self, parent = None, **kw): + #define the megawidget options + INITOPT = Pmw.INITOPT + optiondefs = ( + # Widget relief + ('relief', GROOVE, INITOPT), + # Widget borderwidth + ('borderwidth', 2, INITOPT), + # Relief of dial inset + ('canvas_relief', GROOVE, INITOPT), + # Borderwidth of dial inset + ('canvas_bd', 2, INITOPT), + # Size of edge of dial inset + ('edgeLength', 50, INITOPT), + ('initialValue', 0.0, INITOPT), + # Snap to angle on/off + ('fSnap', 0, None), + # Do values rollover (i.e. accumulate) with multiple revolutions + ('fRollover', 1, None), + ('command', None, None), + ('text', 'Dial Widget', self.updateLabel), + ('numTicks', 10, self.createTicks), + ('numDigits', 2, self.updateEntryFormat), + ('min', 0.0, self.setScaleFactor), + ('max', 1.0, self.setScaleFactor), + ) + self.defineoptions(kw, optiondefs) + + # Initialize the superclass + Pmw.MegaWidget.__init__(self, parent) + + # Set up some local and instance variables + dim = self['edgeLength'] + self.sfGridDelta = dim / 10 + half = self.half = int(dim/2.0) + radius = self.radius = half - 2 + + # Running total which increments/decrements every time around dial + self.baseVal = 0.0 + # Determines value of one dial revolution + self.scaleFactor = 1.0 + self.dialAngle = None + # Current value + self.value = self['initialValue'] + + # Create the components + interior = self.interior() + interior.configure(relief = self['relief'], bd = self['borderwidth']) + + # The canvas + self._canvas = self.createcomponent('canvas', (), None, + Canvas, (interior,), + width = dim + 12, height = dim, + scrollregion = ((- half),(- half), + half, half)) + self._canvas.grid(rowspan = 2, columnspan = 2) + + # The dial face + self._canvas.create_oval(-radius, -radius, radius, radius, + fill = 'white', tags = ('dial',)) + self.createTicks() + + # The velocity knob + self._canvas.create_oval(-radius * INNER_SF, -radius * INNER_SF, + radius * INNER_SF, radius * INNER_SF, + fill = '#909090', tags = ('velocityKnob',)) + + # The indicator + self._canvas.create_line(0, 0, 0, (- radius), width = 2, + tags = ('indicator', 'dial')) + + # The Scale Factor marker + self._canvas.create_polygon( half + 4, - 4, half + 12, 0, + half + 4, + 4, fill = '#A0A0A0', + tags = ('sfMarker',)) + self.sfy = 0 + + # The Dial's label + self._label = self.createcomponent('label', (), None, + Label, (interior,), + text = self['text'], + font = ('MS Sans Serif', 12, 'bold'), + anchor = CENTER) + self._label.grid(row = 0, col = 2, sticky = EW) + + # The entry + self._entryVal = StringVar() + self._entry = self.createcomponent('entry', (), None, + Entry, (interior,), + justify = RIGHT, + textvariable = self._entryVal) + self._entry.grid(row = 1, col = 2, sticky = EW) + self._entry.bind('', self.validateEntryInput) + self._entryBackground = self._entry.cget('background') + interior.columnconfigure(2, weight = 1) + + # The popup menu + self._popupMenu = Menu(interior, tearoff = 0) + self._fAngleSnap = IntVar() + self._fAngleSnap.set(self['fSnap']) + self._popupMenu.add_checkbutton(label = 'Angle snap', + variable = self._fAngleSnap, + command = self.setAngleSnap) + self._fRollover = IntVar() + self._fRollover.set(self['fRollover']) + self._popupMenu.add_checkbutton(label = 'Rollover', + variable = self._fRollover, + command = self.setRollover) + + sfMenu = Menu(interior, tearoff = 1) + self.expVar = DoubleVar() + self.expVar.set(0) + for exp in range (MAX_EXP, -(MAX_EXP + 1), -1): + sf = "%g" % math.pow(10, exp) + sfMenu.add_radiobutton(label = sf, value = exp, + variable = self.expVar, + command = self.setScaleFactor) + sfMenu.add_command(label = 'Scale Factor...', + command = self.getScaleFactor) + self._popupMenu.add_cascade(label = 'Scale Factor', + menu = sfMenu) + self._popupMenu.add_command(label = 'Reset Dial', + command = self.reset) + + # Add event bindings + self._canvas.tag_bind('dial', '', self.mouseDown) + self._canvas.tag_bind('dial', '', self.mouseMotion) + self._canvas.tag_bind('dial', '', self.shiftMouseMotion) + self._canvas.tag_bind('sfMarker', '', self.highlightSFMarker) + self._canvas.tag_bind('sfMarker', '', self.restoreSFMarker) + self._canvas.tag_bind('velocityKnob', '', self.highlightKnob) + self._canvas.tag_bind('velocityKnob', '', self.restoreKnob) + self._canvas.tag_bind('sfMarker', '', self.sfMouseDown) + self._canvas.tag_bind('sfMarker', '', self.sfMouseMotion) + self._canvas.tag_bind('sfMarker', '', self.sfMouseUp) + self._canvas.tag_bind('velocityKnob', '', self.knobMouseDown) + self._canvas.tag_bind('velocityKnob', '', self.knobMouseMotion) + self._canvas.tag_bind('velocityKnob', '', self.knobMouseUp) + self._canvas.bind('', self.popupDialMenu) + self._canvas.bind('', self.mouseReset) + self._canvas.bind('', self.expUp) + self._canvas.bind('', self.expDown) + + # Make sure input variables processed + self.initialiseoptions(Dial) + + def updateLabel(self): + self._label['text'] = self['text'] + + def createTicks(self): + self._canvas.delete('ticks') + # Based upon input snap angle, how many ticks + numTicks = self['numTicks'] + # Compute snapAngle (radians) + self.snapAngle = snapAngle = TWO_PI / numTicks + # Create the ticks at the snap angles + for ticknum in range(numTicks): + angle = snapAngle * ticknum + # convert to canvas coords + angle = angle - POINTFIVE_PI + # Compute tick endpoints + startx = math.cos(angle) * self.radius + starty = math.sin(angle) * self.radius + # Elongate ticks at 90 degree points + if (angle % POINTFIVE_PI) == 0.0: + sf = 0.6 + else: + sf = 0.8 + endx = startx * sf + endy = starty * sf + self._canvas.create_line(startx, starty, endx, endy, + tags = ('ticks','dial')) + + def mouseDown(self,event): + self.lastAngle = dialAngle = self.computeDialAngle(event) + self.computeValueFromAngle(dialAngle) + + def shiftMouseMotion(self,event): + self.mouseMotion(event, 1) + + def mouseMotion(self, event, fShift = 0): + dialAngle = self.computeDialAngle(event, fShift) + self.computeValueFromAngle(dialAngle) + + def computeDialAngle(self,event, fShift = 0): + x = self._canvas.canvasx(event.x) + y = self._canvas.canvasy(event.y) + rawAngle = math.atan2(y,x) + # Snap to grid + # Convert to dial coords to do snapping + dialAngle = rawAngle + POINTFIVE_PI + if operator.xor(self['fSnap'], fShift): + dialAngle = round(dialAngle / self.snapAngle) * self.snapAngle + return dialAngle + + def computeValueFromAngle(self, dialAngle): + delta = self.delta + dialAngle = dialAngle % TWO_PI + # Check for rollover, if necessary + if (self.lastAngle > ONEPOINTFIVE_PI) & (dialAngle < POINTFIVE_PI): + self.baseVal = self.baseVal + delta + elif (self.lastAngle < POINTFIVE_PI) & (dialAngle > ONEPOINTFIVE_PI): + self.baseVal = self.baseVal - delta + self.lastAngle = dialAngle + # Update value and entry + newValue = self['min'] + self.baseVal + delta * (dialAngle / TWO_PI) + self.dialAngle = dialAngle + self.set(newValue) + + def get(self): + return self.value + + def set(self, value): + if not self['fRollover']: + if value > self['max']: + self.baseVal = 0.0 + value = ((value - self['min']) % + (self['max'] - self['min'])) + self['min'] + self.updateEntry(value) + if self.dialAngle: + self.updateIndicatorRadians(self.dialAngle) + self.dialAngle = None + else: + self.updateIndicator(value) + if self['command']: + self['command'](value) + + def updateIndicator(self, value): + # compute new indicator angle + delta = self.delta + factors = divmod(value - self['min'], delta) + self.baseVal = factors[0] * delta + self.updateIndicatorRadians( (factors[1]/delta) * TWO_PI ) + + def updateIndicatorDegrees(self, degAngle): + self.updateIndicatorRadians(degAngle * (math.pi/180.0)) + + def updateIndicatorRadians(self,dialAngle): + rawAngle = dialAngle - POINTFIVE_PI + # Compute end points + endx = math.cos(rawAngle) * self.radius + endy = math.sin(rawAngle) * self.radius + # Draw new indicator + self._canvas.coords('indicator', endx * INNER_SF, endy * INNER_SF, + endx, endy) + + def updateEntry(self, value): + self._entryVal.set(self.entryFormat % value) + + def updateEntryFormat(self): + self.entryFormat = "%." + "%df" % self['numDigits'] + self.updateEntry(self.value) + + def validateEntryInput(self, event): + input = self._entryVal.get() + try: + newValue = string.atof(input) + self.set(newValue) + self._entry.configure(background = self._entryBackground) + except ValueError: + self._entry.configure(background = 'Pink') + + def sfMouseDown(self, event): + # Record marker starting position + self.starty = self.sfy + # Record mouse starting position (convert to canvas coords) + self.lasty = self._canvas.canvasy(event.y) + + def sfMouseMotion(self, event): + # How far did the mouse move? + dy = self._canvas.canvasy(event.y) - self.lasty + # Apply this delta to the marker + newy = self.starty + dy + # Compute new exponent based upon current position + exp = self.sfComputeExp(newy) + # Set resulting scale factor + self.setScaleFactorExp(exp) + + def sfMouseUp(self, event): + self._canvas.delete('sfText') + + # Compute exponent based on current marker position + def sfComputeExp(self, y, fSnap = 1): + # Force marker to stay visible + newy = max( -self.half, min( self.half, y ) ) + # Snap it + gridDelta = self.sfGridDelta + if fSnap: + newy = round( newy / gridDelta ) * gridDelta + # Compute resulting exponent + return (-(newy / gridDelta)) + + def setScaleFactorExp(self, exp, showText = 1, fUpdateIndicator = 1): + self.exp = exp + # Update popup scale factor menu to nearest exponent + self.expVar.set(int(round(exp))) + # Compute new scale factor + self.scaleFactor = math.pow(10, exp) + # Compute resulting delta + self.delta = self.scaleFactor * (self['max'] - self['min']) + # Update indicator to reflect new scale factor + if fUpdateIndicator: + self.updateIndicator(self.value) + # Move marker to correct position + self.updateScaleFactorMarker(-exp*self.sfGridDelta, showText) + + def expUp(self,event): + self.setScaleFactorExp(min(MAX_EXP, self.exp + 1), 0) + + def expDown(self,event): + self.setScaleFactorExp(max(-MAX_EXP, self.exp - 1), 0) + + def knobMouseDown(self,event): + self.lasty = self._canvas.canvasy(event.y) + self.updateIndicatorRadians(0.0) + self.velocityTask = self.after(100, self.computeVelocity) + + def knobMouseMotion(self, event): + # How far is the mouse from the origin? + dx = self._canvas.canvasx(event.x) + self.lasty = self._canvas.canvasy(event.y) + exp = -5 + dx/20.0 + exp = max( -5, min( 5, exp ) ) + # Set resulting scale factor + self.setScaleFactorExp(exp, 0, fUpdateIndicator = 0) + + def knobMouseUp(self, event): + self.after_cancel(self.velocityTask) + # reset indicator + self.updateIndicator(self.value) + + def computeVelocity(self): + if self.lasty < 0: + sign = -1.0 + else: + sign = 1.0 + lasty = abs(self.lasty) + if lasty > 5: + lasty = lasty - 5 + sf = min(100, lasty)/100.0 + sf = pow(sf, 3.0) + newVal = self.value - sign * sf * self.delta + self.dialAngle = - sign * sf * POINTFIVE_PI + self.set(newVal) + self.velocityTask = self.after(100, self.computeVelocity) + + def updateScaleFactorMarker(self, newy, showText = 1): + # Move marker + self._canvas.move('sfMarker', 0, newy - self.sfy) + self.sfy = newy + + # Show current scaling factor + if showText: + sfText = '%g' % (self.delta / 10.0,) + self._canvas.delete('sfText') + self._canvas.create_rectangle( self.half - 40, newy - 6, + self.half, newy + 7, + fill = 'white', + tags = ('sfText',)) + self._canvas.create_text( self.half, newy, + justify = RIGHT, + anchor = E, + text = sfText, + fill = 'Red', + tags = ('sfText',)) + + # The following routines are used to handle the popup menu + def popupDialMenu(self,event): + self._popupMenu.post(event.widget.winfo_pointerx(), + event.widget.winfo_pointery()) + + # This is called by the scale factor popup menu and when the user + # changes the dial 'delta' value + def setScaleFactor(self): + exp = self.expVar.get() + self.setScaleFactorExp(exp, showText = 0) + + # This handles the popup scale factor dialog + def getScaleFactor(self): + sf = askfloat('Dial Scale Factor', 'Scale Factor:', + parent = self.interior()) + if sf: + self.setScaleFactorExp(math.log10(sf), showText = 0) + + # Turn angle snap on/off + def setAngleSnap(self): + self['fSnap'] = self._fAngleSnap.get() + + # Turn rollover (accumulation of a sum) on/off + def setRollover(self): + self['fRollover'] = self._fRollover.get() + + def highlightSFMarker(self, event): + self._canvas.itemconfigure('sfMarker', fill = '#252525') + + def restoreSFMarker(self, event): + self._canvas.itemconfigure('sfMarker', fill = '#A0A0A0') + + def highlightKnob(self, event): + self._canvas.itemconfigure('velocityKnob', fill = '#252525') + + def restoreKnob(self, event): + self._canvas.itemconfigure('velocityKnob', fill = '#A0A0A0') + + # Reset dial to zero + def mouseReset(self,event): + if not self._canvas.find_withtag(CURRENT): + self.reset() + + def reset(self): + self.set(self['initialValue']) + # Should we do this? + self.setScaleFactorExp(0, showText = 0) + +class AngleDial(Dial): + def __init__(self, parent = None, **kw): + # Set the typical defaults for a 360 degree angle dial + optiondefs = ( + ('fRollover', 0, None), + ('numTicks', 12, None), + ('max', 360.0, None), + ) + self.defineoptions(kw, optiondefs) + # Initialize the superclass + Dial.__init__(self, parent) + # Needed because this method checks if self.__class__ is myClass + # where myClass is the argument passed into inialiseoptions + self.initialiseoptions(AngleDial) + +if __name__ == '__main__': + tl = Toplevel() + d = Dial(tl) + d2 = Dial(tl, numTicks = 12, max = 360, fRollover = 0, initialValue = 180) + d3 = Dial(tl, numTicks = 12, max = 90, min = -90, fRollover = 0) + d4 = Dial(tl, numTicks = 16, max = 256, fRollover = 0) + d.pack(expand = 1, fill = X) + d2.pack(expand = 1, fill = X) + d3.pack(expand = 1, fill = X) + d4.pack(expand = 1, fill = X) diff --git a/direct/src/tkwidgets/Floater.py b/direct/src/tkwidgets/Floater.py new file mode 100644 index 0000000000..9941edd837 --- /dev/null +++ b/direct/src/tkwidgets/Floater.py @@ -0,0 +1,209 @@ +title = 'DIRECT Floater megawidget' + +import string +import Tkinter +import Pmw + +OK = 1 +ERROR = 0 + +class Floater(Pmw.MegaWidget): + """ Megawidget containing a label, an entry, and a scale. + Used as a velocity style controller for floating point values + """ + + def __init__(self, parent = None, **kw): + + # Define the megawidget options. + optiondefs = ( + ('command', None, None), + ('value', 0.0, self._updateValue), + ('text', '', self._updateLabelText), + ('min', None, None), + ('max', None, None), + ('resolution', None, None), + ('maxVelocity', 100.0, None), + ('errorbackground', 'pink', None), + ('significantDigits', 2, self._setSigDigits), + ) + self.defineoptions(kw, optiondefs) + + # Initialise base class (after defining options). + Pmw.MegaWidget.__init__(self, parent) + + # Initialize some variables + self.value = 0.0 + self.velocity = 0.0 + self.entryValue = Tkinter.StringVar() + self.entryFormat = '%.2f' + self.normalBackground = None + + # Create the components. + interior = self.interior() + interior['relief'] = 'groove' + interior['borderwidth'] = 2 + + self.infoFrame = self.createcomponent('frame', + (), None, + Tkinter.Frame, interior) + self.infoFrame.pack(expand = 1, fill = 'both') + + # Create the Floater's label + self.label = self.createcomponent('label', + (), None, + Tkinter.Label, self.infoFrame, + text = self['text'], + anchor = 'center', + width = 12, + font = "Arial 12 bold") + self.label.pack(side='left', expand = 1, fill = 'x') + + # Create an entry field to display and validate the floater's value + self.entry = self.createcomponent('entry', + (), None, + Tkinter.Entry, self.infoFrame, + width = 10, + justify = 'right', + textvar = self.entryValue) + self.entry.pack(side='left',padx = 4) + self.entry.bind('', self._entryCommand) + + # Create the scale component. + self.scale = self.createcomponent('scale', + (), None, + Tkinter.Scale, interior, + command = self._scaleToVelocity, + orient = 'horizontal', + length = 150, + from_ = -1.0, + to = 1.0, + resolution = 0.01, + showvalue = 0) + self.scale.pack(expand = 1, fill = 'x') + self.scale.set(0.0) + # When interacting with mouse: + self.scale.bind('', self._startFloaterTask) + self.scale.bind('', self._floaterReset) + # In case you wish to interact using keys + self.scale.bind('', self._floaterKeyCommand) + self.scale.bind('', self._floaterReset) + self.scale.bind('', self._floaterKeyCommand) + self.scale.bind('', self._floaterReset) + + # Check keywords and initialise options. + self.initialiseoptions(Floater) + + # Now that the widgets have been created, update significant digits + self._setSigDigits() + self._updateValue() + + def _scaleToVelocity(self, strVal): + # convert scale val to float + val = string.atof(strVal) + # retain sign of velocity by only calling abs once + self.velocity = self['maxVelocity'] * val * abs(val) + + def _startFloaterTask(self,event): + self._fFloaterTask = 1 + self._floaterTask() + + def _floaterTask(self): + if self.velocity != 0.0: + self.setValue( self.value + self.velocity ) + if self._fFloaterTask: + self.after(50, self._floaterTask) + + def _floaterReset(self, event): + self._fFloaterTask = 0 + self.velocity = 0.0 + self.scale.set(0.0) + + def _floaterKeyCommand(self, event): + if self.velocity != 0.0: + self.setValue( self.value + self.velocity ) + + def _entryCommand(self, event = None): + try: + val = string.atof( self.entryValue.get() ) + self.setValue( val ) + except ValueError: + # invalid entry, ring bell set background to warning color + self.entry.bell() + if self.normalBackground is None: + self.normalBackground = self.entry.cget('background') + self.entry.configure( background = self['errorbackground'] ) + + def _updateValue(self): + self.setValue(self['value']) + + def setValue(self, newVal): + if self['min'] is not None: + if newVal < self['min']: + newVal = self['min'] + if self['max'] is not None: + if newVal > self['max']: + newVal = self['max'] + if self['resolution'] is not None: + newVal = round(newVal / self['resolution']) * self['resolution'] + # Update floater's value + self.value = newVal + # Update entry to reflect formatted value + self.entryValue.set( self.entryFormat % self.value ) + # Reset background + if self.normalBackground is not None: + self.entry.configure(background = self.normalBackground) + self.normalBackground = None + # execute command + command = self['command'] + if command is not None: + command( newVal ) + + def _setSigDigits(self): + sd = self['significantDigits'] + self.entryFormat = '%.' + '%d' % sd + 'f' + self.scale['resolution'] = 10 ** (-1.0 * sd) + # And reset value to reflect change + self.entryValue.set( self.entryFormat % self.value ) + + def _updateLabelText(self): + self.label['text'] = self['text'] + +Pmw.forwardmethods(Floater, Tkinter.Scale, 'scale') + +## SAMPLE CODE +if __name__ == '__main__': + # Initialise Tkinter and Pmw. + root = Pmw.initialise() + root.title('Pmw Floater demonstration') + + # Dummy command + def printVal(val): + print `val` + + # Create and pack a Floater megawidget. + mega1 = Floater(root) + mega1.pack(side = 'left', expand = 1, fill = 'x') + + # These are things you can set/configure + mega1['command'] = printVal + mega1['text'] = 'Drive delta X' + mega1['min'] = 0.0 + #mega1['max'] = 1000.0 + mega1['resolution'] = 1.0 + + # UNCOMMENT THESE TO CUSTOMIZE THE FLOATER + # To change the color of the label: + # mega1.label['foreground'] = 'Red' + + # Max change/update, default is 100 + # To have really fine control, for example + # mega1['maxVelocity'] = 0.1 + + # Number of digits to the right of the decimal point, default = 2 + # mega1['significantDigits'] = 5 + + # Starting value for floater + # mega1['value'] = 123.4557 + + # Let's go. + #root.mainloop() diff --git a/direct/src/tkwidgets/VectorWidgets.py b/direct/src/tkwidgets/VectorWidgets.py new file mode 100644 index 0000000000..272247e5ae --- /dev/null +++ b/direct/src/tkwidgets/VectorWidgets.py @@ -0,0 +1,286 @@ +from Tkinter import * +import Pmw +import floater +import string +import tkColorChooser + +class VectorEntry(Pmw.MegaWidget): + def __init__(self, parent = None, **kw): + + # Default vector size + DEFAULT_DIM = 3 + # Default value depends on *actual* vector size, test for user input + DEFAULT_VALUE = [0.0] * kw.get('dim', DEFAULT_DIM) + DEFAULT_LABELS = map(lambda x: 'v[%d]' % x, + range(kw.get('dim', DEFAULT_DIM))) + + # Process options + INITOPT = Pmw.INITOPT + optiondefs = ( + ('dim', DEFAULT_DIM, INITOPT), + ('initialValue', DEFAULT_VALUE, INITOPT), + ('label_width', 12, None), + ('command', None, None), + ('entryWidth', 8, self._updateEntryWidth), + ('relief', GROOVE, self._updateRelief), + ('bd', 2, self._updateBorderWidth), + ('text', 'Vector:', self._updateText), + ('min', None, self._updateValidate), + ('max', None, self._updateValidate), + ('significantDigits', 2, self._setSigDigits), + ) + self.defineoptions(kw, optiondefs) + + # Initialize superclass + Pmw.MegaWidget.__init__(self, parent) + + # Initialize value + # Make sure its a list (and as a byproduct, make a distinct copy) + self._value = list(self['initialValue']) + self._floaters = None + self.entryFormat = '%.2f' + + # Get a handle on the parent container + interior = self.interior() + + # This does double duty as a menu button + self._label = self.createcomponent('label', (), None, + Menubutton, (interior,), + text = self['text'], + activebackground = '#909090') + self.menu = self._label['menu'] = Menu(self._label) + self.menu.add_command(label = 'Reset', command = self.reset) + self.menu.add_command(label = 'Popup sliders', command = self.popupSliders) + self._label.pack(side = LEFT, fill = X, ipadx = 2) + + self.variableList = [] + self.entryList = [] + for index in range(self['dim']): + var = StringVar() + self.variableList.append(var) + # To set the configuration of all entrys in a vector use: + # ve.configure(Entry_XXX = YYY) + # To configure an individual entryfield's entry use: + # ve.configure(entry0_XXX = YYY) + entry = self.createcomponent( + 'entryField%d' % index, + (('entry%d' % index, + 'entryField%d_entry' % index),), + 'Entry', + Pmw.EntryField, (interior,), + entry_justify = RIGHT, + entry_textvariable = var, + command = lambda s = self, i = index: s._entryUpdateAt(i)) + entry.pack(side = LEFT, expand = 1, fill = X) + self.entryList.append(entry) + + # To configure the floaterGroup use: + # ve.configure(floaterGroup_XXX = YYY) + # ve.configure(fGroup_XXX = YYY) or + # To set the configuration all floaters in a group use: + # ve.configure(Floater_XXX = YYY) + # To configure an individual floater in a group use: + # ve.configure(floaterGroup_floater0_XXX = YYY) or + # ve.configure(fGroup_floater0_XXX = YYY) + self._floaters = self.createcomponent( + 'floaterGroup', + (('fGroup', 'floaterGroup'), + ('Floater', 'floaterGroup_Floater'),), None, + floater.FloaterGroup, (self.interior(),), + dim = self['dim'], title = self['text'], + command = self.set) + # Note: This means the 'X' on the menu bar doesn't really destroy + # the panel, just withdraws it. This is to avoid problems which occur + # if the user kills the floaterGroup and then tries to pop it open again + self._floaters.userdeletefunc(self._floaters.withdraw) + self._floaters.withdraw() + + + # Make sure entries are updated + self.set(self['initialValue']) + + # Make sure input variables processed + self.initialiseoptions(VectorEntry) + + def menu(self): + return self.menu + + def label(self): + return self._label + + def entry(self, index): + return self.entryList[index] + + def entryList(self): + return self.entryList + + def floaters(self): + return self._floaters + + def _clearFloaters(self): + self._floaters.withdraw() + + def _updateText(self): + self._label['text'] = self['text'] + + def _updateRelief(self): + self.interior()['relief'] = self['relief'] + + def _updateBorderWidth(self): + self.interior()['bd'] = self['bd'] + + def _updateEntryWidth(self): + self['Entry_entry_width'] = self['entryWidth'] + + def _setSigDigits(self): + sd = self['significantDigits'] + self.entryFormat = '%.' + '%d' % sd + 'f' + self.configure(Floater_significantDigits = sd) + # And refresh value to reflect change + for index in range(self['dim']): + self._refreshEntry(index) + + def _updateValidate(self): + # Update entry field to respect new limits + self.configure(Entry_validate = { + 'validator' : 'real', + 'min' : self['min'], + 'max' : self['max'], + 'minstrict' : 0, + 'maxstrict' : 0}) + # Reflect changes in floaters + self.configure(Floater_min = self['min'], + Floater_max = self['max']) + + def get(self): + return self._value + + def getAt(self,index): + return self._value[index] + + def set(self, value): + for i in range(self['dim']): + self._value[i] = value[i] + self.variableList[i].set(self.entryFormat % value[i]) + self.action() + + def setAt(self, index, value): + self.variableList[index].set(self.entryFormat % value) + self._value[index] = value + self.action() + + def _entryUpdateAt(self, index): + entryVar = self.variableList[index] + # Did we get a valid float? + try: + newVal = string.atof(entryVar.get()) + except ValueError: + return + + # Clamp value + if self['min'] is not None: + if newVal < self['min']: + newVal = self['min'] + if self['max'] is not None: + if newVal > self['max']: + newVal = self['max'] + + # Update vector's value + self._value[index] = newVal + + # refresh entry to reflect formatted value + self._refreshEntry(index) + + # Update the floaters and call the command + self.action() + + def _refreshEntry(self,index): + self.variableList[index].set( self.entryFormat % self._value[index] ) + self.entryList[index].checkentry() + + def _refreshFloaters(self): + if self._floaters: + self._floaters.set(self._value, 0) + + def action(self): + self._refreshFloaters() + if self['command']: + self['command'](self._value) + + def reset(self): + self.set(self['initialValue']) + + def addMenuItem(self, label = '', command = None): + self.menu.add_command(label = label, command = command) + + def popupSliders(self): + self._floaters.set(self.get()[:]) + self._floaters.show() + +class Vector3Entry(VectorEntry): + def __init__(self, parent = None, **kw): + # Initialize options for the class + optiondefs = ( + ('dim', 3, Pmw.INITOPT), + ('fGroup_labels', ('X','Y','Z'), None), + ) + self.defineoptions(kw, optiondefs) + # Initialize the superclass, make sure dim makes it to superclass + VectorEntry.__init__(self, parent, dim = self['dim']) + # Needed because this method checks if self.__class__ is myClass + # where myClass is the argument passed into inialiseoptions + self.initialiseoptions(Vector3Entry) + +class Vector4Entry(VectorEntry): + def __init__(self, parent = None, **kw): + # Initialize options for the class + optiondefs = ( + ('dim', 4, Pmw.INITOPT), + ('fGroup_labels', ('X','Y','Z','W'), None), + ) + self.defineoptions(kw, optiondefs) + # Initialize the superclass, make sure dim makes it to superclass + VectorEntry.__init__(self, parent, dim = self['dim']) + # Needed because this method checks if self.__class__ is myClass + # where myClass is the argument passed into inialiseoptions + self.initialiseoptions(Vector4Entry) + +class ColorEntry(VectorEntry): + def __init__(self, parent = None, **kw): + # Initialize options for the class (overriding some superclass options) + optiondefs = ( + ('dim', 4, Pmw.INITOPT), + ('fGroup_labels', ('R','G','B','A'), None), + ('min', 0.0, None), + ('max', 255.0, None), + ('significantDigits', 0, None), + ('Floater_resolution', 1.0, None), + ) + self.defineoptions(kw, optiondefs) + # Initialize the superclass, make sure dim makes it to superclass + VectorEntry.__init__(self, parent, dim = self['dim']) + # Add menu item to popup color picker + self.addMenuItem( + 'Popup color picker', + command = lambda s = self: s.popupColorPicker()) + # Needed because this method checks if self.__class__ is myClass + # where myClass is the argument passed into inialiseoptions + self.initialiseoptions(ColorEntry) + + def popupColorPicker(self): + # Can pass in current color with: color = (255, 0, 0) + color = tkColorChooser.askcolor( + parent = self.interior(), + # Initialize it to current color + initialcolor = tuple(self.get()[:3]))[0] + if color: + self.set((color[0], color[1], color[2], self.getAt(3))) + +if __name__ == '__main__': + root = Toplevel() + root.title('Vector Widget demo') + + ve = VectorEntry(root); ve.pack() + v3e = Vector3Entry(root); v3e.pack() + v4e = Vector4Entry(root); v4e.pack() + ce = ColorEntry(root); ce.pack()