Browse Source

Populate contrib tree (with SceneEditor)

rdb 16 years ago
parent
commit
0b29357059
33 changed files with 20240 additions and 0 deletions
  1. 4 0
      contrib/src/Sources.pp
  2. 188 0
      contrib/src/sceneeditor/AlignTool.py
  3. 59 0
      contrib/src/sceneeditor/MetadataPanel.py
  4. 458 0
      contrib/src/sceneeditor/SideWindow.py
  5. 2 0
      contrib/src/sceneeditor/Sources.pp
  6. 0 0
      contrib/src/sceneeditor/__init__.py
  7. 302 0
      contrib/src/sceneeditor/collisionWindow.py
  8. 695 0
      contrib/src/sceneeditor/controllerWindow.py
  9. 1071 0
      contrib/src/sceneeditor/dataHolder.py
  10. 172 0
      contrib/src/sceneeditor/duplicateWindow.py
  11. 493 0
      contrib/src/sceneeditor/lightingPanel.py
  12. 1456 0
      contrib/src/sceneeditor/propertyWindow.py
  13. 676 0
      contrib/src/sceneeditor/quad.py
  14. 1709 0
      contrib/src/sceneeditor/sceneEditor.py
  15. 608 0
      contrib/src/sceneeditor/seAnimPanel.py
  16. 666 0
      contrib/src/sceneeditor/seBlendAnimPanel.py
  17. 670 0
      contrib/src/sceneeditor/seCameraControl.py
  18. 49 0
      contrib/src/sceneeditor/seColorEntry.py
  19. 915 0
      contrib/src/sceneeditor/seFileSaver.py
  20. 134 0
      contrib/src/sceneeditor/seForceGroup.py
  21. 256 0
      contrib/src/sceneeditor/seGeometry.py
  22. 170 0
      contrib/src/sceneeditor/seGrid.py
  23. 633 0
      contrib/src/sceneeditor/seLights.py
  24. 956 0
      contrib/src/sceneeditor/seManipulation.py
  25. 2073 0
      contrib/src/sceneeditor/seMopathRecorder.py
  26. 267 0
      contrib/src/sceneeditor/seParticleEffect.py
  27. 1976 0
      contrib/src/sceneeditor/seParticlePanel.py
  28. 449 0
      contrib/src/sceneeditor/seParticles.py
  29. 800 0
      contrib/src/sceneeditor/sePlacer.py
  30. 209 0
      contrib/src/sceneeditor/seSceneGraphExplorer.py
  31. 715 0
      contrib/src/sceneeditor/seSelection.py
  32. 991 0
      contrib/src/sceneeditor/seSession.py
  33. 418 0
      contrib/src/sceneeditor/seTree.py

+ 4 - 0
contrib/src/Sources.pp

@@ -0,0 +1,4 @@
+// This is a group directory: a directory level above a number of
+// source subdirectories.
+
+#define DIR_TYPE group

+ 188 - 0
contrib/src/sceneeditor/AlignTool.py

@@ -0,0 +1,188 @@
+#################################################################
+# AlignTool.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+from direct.tkwidgets.AppShell import *
+from direct.showbase.TkGlobal import *
+
+
+class AlignTool(AppShell):
+    #################################################################
+    # AlignTool(AppShell)
+    #################################################################
+    appversion      = '1.0'
+    appname         = 'Align Tool'
+    frameWidth      = 220
+    frameHeight     = 330
+    frameIniPosX    = 250
+    frameIniPosY    = 250
+    padx            = 0
+    pady            = 0
+
+    
+    def __init__(self, list = [], parent = None, nodePath = None, **kw):
+        # Keep nodePath Data
+        self.nodePath = nodePath
+        self.targetList = list
+        self.targetName = None
+        # Rename App
+        self.appname += (' '+self.nodePath.getName())
+        # Define the megawidget options.
+        optiondefs = (
+            ('title',       self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+        if parent == None:
+            self.parent = Toplevel()
+        AppShell.__init__(self, self.parent)
+        self.parent.geometry('%dx%d+%d+%d' % (self.frameWidth, self.frameHeight,self.frameIniPosX,self.frameIniPosY))
+        
+        self.initialiseoptions(AlignTool)
+        
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+        
+    def appInit(self):
+        return
+        
+    def createInterface(self):
+        # The interior of the toplevel panel
+        interior = self.interior()
+        mainFrame = Frame(interior)
+        frame = Frame(mainFrame)
+        self.nameBox = self.createcomponent(
+            'Align Target', (), None,
+            Pmw.ComboBox, (frame,),
+            labelpos = W, label_text='Target Node:', entry_width = 20, entry_state = DISABLED,
+            selectioncommand = self.setTargetNode,
+            scrolledlist_items = self.targetList)
+        self.nameBox.pack(side=LEFT)
+        frame.pack(side=TOP, fill = X, expand = 1,pady=5)
+        group = Pmw.Group(mainFrame, tag_text = 'Setting')
+        group.pack(side=TOP, fill = 'both', expand = 1,pady=5)
+        groupFrame = group.interior()
+        # X and H checkbox
+        frame = Frame(groupFrame)
+        self.alignXVar = IntVar()
+        self.alignXVar.set(False)
+        self.alignXButton = Checkbutton(
+            frame,
+            text = ': Align X',
+            variable = self.alignXVar)
+        self.alignXButton.pack(side=LEFT, expand=False)
+        self.alignHVar = IntVar()
+        self.alignHVar.set(False)
+        self.alignHButton = Checkbutton(
+            frame,
+            text = ': Align H',
+            variable = self.alignHVar)
+        self.alignHButton.pack(side=RIGHT, expand=False)
+        frame.pack(side=TOP, fill = X, expand = 1,pady=5)
+        
+        groupFrame.pack(side=TOP, fill = 'both', expand = 1,padx=5,pady=5)
+        
+        frame = Frame(mainFrame)
+        Button(frame, text='Align', width = 13, command=self.Align_press).pack(side=LEFT)
+        Button(frame, text='OK', width = 13, command=self.ok_press).pack(side=RIGHT)
+        frame.pack(side=BOTTOM, fill = X, expand = 1,pady=5)
+        
+        # Y and P checkbox
+        frame = Frame(groupFrame)
+        self.alignYVar = IntVar()
+        self.alignYVar.set(False)
+        self.alignYButton = Checkbutton(
+            frame,
+            text = ': Align Y',
+            variable = self.alignYVar)
+        self.alignYButton.pack(side=LEFT, expand=False)
+        self.alignPVar = IntVar()
+        self.alignPVar.set(False)
+        self.alignPButton = Checkbutton(
+            frame,
+            text = ': Align P',
+            variable = self.alignPVar)
+        self.alignPButton.pack(side=RIGHT, expand=False)
+        frame.pack(side=TOP, fill = X, expand = 1,pady=5)
+
+        # Z and R checkbox
+        frame = Frame(groupFrame)
+        self.alignZVar = IntVar()
+        self.alignZVar.set(False)
+        self.alignZButton = Checkbutton(
+            frame,
+            text = ': Align Z',
+            variable = self.alignZVar)
+        self.alignZButton.pack(side=LEFT, expand=False)
+        self.alignRVar = IntVar()
+        self.alignRVar.set(False)
+        self.alignRButton = Checkbutton(
+            frame,
+            text = ': Align R',
+            variable = self.alignRVar)
+        self.alignRButton.pack(side=RIGHT, expand=False)
+        frame.pack(side=TOP, fill = X, expand = 1,pady=5)
+
+        # Scale
+        frame = Frame(groupFrame)
+        Label(frame,text='Align Scale:').pack(side=LEFT)
+        frame.pack(side=TOP, fill = X, expand = 1,pady=5)
+        frame = Frame(groupFrame)
+        self.alignSXVar = IntVar()
+        self.alignSXVar.set(False)
+        self.alignSXButton = Checkbutton(
+            frame,
+            text = ': X',
+            variable = self.alignSXVar)
+        self.alignSXButton.pack(side=LEFT, expand=False)
+        self.alignSYVar = IntVar()
+        self.alignSYVar.set(False)
+        self.alignSYButton = Checkbutton(
+            frame,
+            text = ': Y',
+            variable = self.alignSYVar)
+        self.alignSYButton.pack(side=LEFT, expand=False)
+        frame.pack(side=TOP, fill = X, expand = 1,pady=5)
+        self.alignSZVar = IntVar()
+        self.alignSZVar.set(False)
+        self.alignSZButton = Checkbutton(
+            frame,
+            text = ': Z',
+            variable = self.alignSZVar)
+        self.alignSZButton.pack(side=LEFT, expand=False)
+        frame.pack(side=TOP, fill = X, expand = 1,pady=5)
+        
+        
+        mainFrame.pack(fill = 'both', expand = 1,padx=7,pady=7)
+
+    
+    def createMenuBar(self):
+        self.menuBar.destroy()
+        
+    def onDestroy(self, event):
+        messenger.send('ALW_close', [self.nodePath.getName()])
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    ###############################
+    
+    def ok_press(self):
+        #################################################################
+        # ok_press(self)
+        # Callback function
+        # This function will be called when user click on the "OK" button on the window.
+        #################################################################
+        self.quit()
+
+    def Align_press(self):
+        list = [self.alignXVar.get(), self.alignYVar.get(), self.alignZVar.get(),
+                self.alignHVar.get(), self.alignPVar.get(), self.alignRVar.get(),
+                self.alignSXVar.get(), self.alignSYVar.get(), self.alignSZVar.get()]
+        if self.targetName != None:
+            messenger.send('ALW_align', [self.nodePath, self.targetName, list])
+        return
+
+    def setTargetNode(self,name=None):
+        self.targetName = name
+        return
+

+ 59 - 0
contrib/src/sceneeditor/MetadataPanel.py

@@ -0,0 +1,59 @@
+from direct.tkwidgets.AppShell import *
+from direct.showbase.TkGlobal import *
+import Pmw
+
+class MetadataPanel(AppShell,Pmw.MegaWidget):
+    appversion      = '1.0'
+    appname         = 'Metadata Panel'
+    frameWidth      = 400
+    frameHeight     = 400
+    padx            = 0
+    pady            = 0
+    usecommandarea  = 0
+    usestatusarea   = 0
+    Metatag=""
+    Metanode=None 
+    tag_text=None
+    def __init__(self,nodePath,parent=None,**kw):
+
+        # Initialise superclass
+        Pmw.MegaWidget.__init__(self, parent)
+        
+        # Define the megawidget options.
+        optiondefs = (
+            ('title',       self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+        self.Metanode=nodePath
+        if(nodePath.hasTag("Metadata")):
+            self.Metatag=self.Metanode.getTag("Metadata")
+
+        if parent == None:
+            self.parent = Toplevel()
+        AppShell.__init__(self, self.parent)
+        
+        self.parent.resizable(False,False)
+
+    
+    def appInit(self):
+        print "Metadata Panel"
+
+    def createInterface(self):
+        interior = self.interior()
+        mainFrame = Frame(interior)
+        tag_label=Label (mainFrame,text="Enter Metadata",font=('MSSansSerif', 15),
+                           relief = RIDGE, borderwidth=5)
+        tag_label.pack()
+        source=StringVar()
+        source.set(self.Metatag)
+        self.tag_text=Entry(mainFrame, width=10,textvariable=source)
+        self.tag_text.pack()
+        set_button=Button(mainFrame, text='Set Metadata',font=('MSSansSerif', 15),
+                           relief = RIDGE, borderwidth=5, command= lambda:self.SetIt())
+        set_button.pack()
+        mainFrame.pack(fill = 'both', expand = 1)
+
+    def SetIt(self):
+        self.Metanode.setTag("Metadata",self.tag_text.get())
+        
+        

+ 458 - 0
contrib/src/sceneeditor/SideWindow.py

@@ -0,0 +1,458 @@
+#################################################################
+# sideWindow.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+from direct.tkwidgets.AppShell import AppShell
+from direct.tkwidgets.VectorWidgets import ColorEntry
+from direct.showbase.TkGlobal import spawnTkLoop, Toplevel
+import seSceneGraphExplorer
+from Tkinter import Frame, IntVar, Checkbutton
+import Pmw, Tkinter
+
+class sideWindow(AppShell):
+    #################################################################
+    # sideWindow(AppShell)
+    # This class will open a side window wich contains a scene graph and
+    # a world setting page.
+    #################################################################
+    appversion      = '1.0'
+    appname         = 'Navigation Window'
+    frameWidth      = 325
+    frameHeight     = 580
+    frameIniPosX    = 0
+    frameIniPosY    = 110
+    padx            = 0
+    pady            = 0
+
+    lightEnable = 0
+    ParticleEnable = 0
+    basedriveEnable = 0
+    collision = 0
+    backface = 0
+    texture = 1
+    wireframe = 0
+    
+    enableBaseUseDrive = 0
+    
+    def __init__(self, worldColor,lightEnable,ParticleEnable, basedriveEnable,collision,
+                 backface, texture, wireframe, grid, widgetVis, enableAutoCamera, parent = None, nodePath = render, **kw):
+        self.worldColor = worldColor
+        self.lightEnable = lightEnable
+        self.ParticleEnable = ParticleEnable
+        self.basedriveEnable = basedriveEnable
+        self.collision = collision
+        self.backface = backface
+        self.texture = texture
+        self.wireframe = wireframe
+        self.grid = grid
+        self.enableAutoCamera = enableAutoCamera
+        self.widgetVis = widgetVis
+
+        # Define the megawidget options.
+        optiondefs = (
+            ('title',       self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        if parent == None:
+            self.parent = Toplevel()
+        else:
+            self.parent = parent
+        
+        AppShell.__init__(self, self.parent)
+        self.parent.geometry('%dx%d+%d+%d' % (self.frameWidth, self.frameHeight,self.frameIniPosX,self.frameIniPosY))
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+                
+    def appInit(self):
+        print '----SideWindow is Initialized!!'
+        
+    def createInterface(self):
+        # The interior of the toplevel panel
+        interior = self.interior()
+        mainFrame = Frame(interior)
+        ## Creat NoteBook
+        self.notebookFrame = Pmw.NoteBook(mainFrame)
+        self.notebookFrame.pack(fill=Tkinter.BOTH,expand=1)
+        sgePage = self.notebookFrame.add('Tree Graph')
+        envPage = self.notebookFrame.add('World Setting')
+        self.notebookFrame['raisecommand'] = self.updateInfo
+
+        ## Tree Grapgh Page
+        self.SGE = seSceneGraphExplorer.seSceneGraphExplorer(
+            sgePage, nodePath = render,
+            scrolledCanvas_hull_width = 270,
+            scrolledCanvas_hull_height = 570)
+        self.SGE.pack(fill = Tkinter.BOTH, expand = 0)
+
+        ## World Setting Page
+        envPage = Frame(envPage)
+        pageFrame = Frame(envPage)
+        self.LightingVar = IntVar()
+        self.LightingVar.set(self.lightEnable)
+        self.LightingButton = Checkbutton(
+            pageFrame,
+            text = 'Enable Lighting',
+            variable = self.LightingVar,
+            command = self.toggleLights)
+        self.LightingButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.CollisionVar = IntVar()
+        self.CollisionVar.set(self.collision)
+        self.CollisionButton = Checkbutton(
+            pageFrame,
+            text = 'Show Collision Object',
+            variable = self.CollisionVar,
+            command = self.showCollision)
+        self.CollisionButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.ParticleVar = IntVar()
+        self.ParticleVar.set(self.ParticleEnable)
+        self.ParticleButton = Checkbutton(
+            pageFrame,
+            text = 'Show Particle Dummy',
+            variable = self.ParticleVar,
+            command = self.enableParticle)
+        self.ParticleButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.baseUseDriveVar = IntVar()
+        self.baseUseDriveVar.set(self.basedriveEnable)
+        self.baseUseDriveButton = Checkbutton(
+            pageFrame,
+            text = 'Enable base.usedrive',
+            variable = self.baseUseDriveVar,
+            command = self.enablebaseUseDrive)
+        self.baseUseDriveButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.backfaceVar = IntVar()
+        self.backfaceVar.set(self.backface)
+        self.backfaceButton = Checkbutton(
+            pageFrame,
+            text = 'Enable BackFace',
+            variable = self.backfaceVar,
+            command = self.toggleBackface)
+        self.backfaceButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.textureVar = IntVar()
+        self.textureVar.set(self.texture)
+        self.textureButton = Checkbutton(
+            pageFrame,
+            text = 'Enable Texture',
+            variable = self.textureVar,
+            command = self.toggleTexture)
+        self.textureButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.wireframeVar = IntVar()
+        self.wireframeVar.set(self.wireframe)
+        self.wireframeButton = Checkbutton(
+            pageFrame,
+            text = 'Enable Wireframe',
+            variable = self.wireframeVar,
+            command = self.toggleWireframe)
+        self.wireframeButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.gridVar = IntVar()
+        self.gridVar.set(self.grid)
+        self.gridButton = Checkbutton(
+            pageFrame,
+            text = 'Enable Grid',
+            variable = self.gridVar,
+            command = self.toggleGrid)
+        self.gridButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.widgetVisVar = IntVar()
+        self.widgetVisVar.set(self.widgetVis)
+        self.widgetVisButton = Checkbutton(
+            pageFrame,
+            text = 'Enable WidgetVisible',
+            variable = self.widgetVisVar,
+            command = self.togglewidgetVis)
+        self.widgetVisButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.enableAutoCameraVar = IntVar()
+        self.enableAutoCameraVar.set(self.enableAutoCamera)
+        self.enableAutoCameraButton = Checkbutton(
+            pageFrame,
+            text = 'Enable Auto Camera Movement for Loading Objects',
+            variable = self.enableAutoCameraVar,
+            command = self.toggleAutoCamera)
+        self.enableAutoCameraButton.pack(side=Tkinter.LEFT, expand=False)
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        pageFrame = Frame(envPage)
+        self.backgroundColor = ColorEntry(
+            pageFrame, text = 'BG Color', value=self.worldColor)
+        self.backgroundColor['command'] = self.setBackgroundColorVec
+        self.backgroundColor['resetValue'] = [0,0,0,0]
+        self.backgroundColor.pack(side=Tkinter.LEFT, expand=False)
+        self.bind(self.backgroundColor, 'Set background color')
+        pageFrame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True)
+
+        envPage.pack(expand=False)
+
+        ## Set all stuff done
+        self.notebookFrame.setnaturalsize()
+        mainFrame.pack(fill = 'both', expand = 1)
+
+    
+    def createMenuBar(self):
+        # We don't need menu bar here.
+        self.menuBar.destroy()
+        
+    def onDestroy(self, event):
+        #################################################################
+        # onDestroy(self, event)
+        # This function will be called when user closed the side window.
+        # Here we will send out a message with whole data set we will need
+        # for the next time user open the side window.
+        #################################################################
+        messenger.send('SW_close',[self.worldColor,
+                                   self.lightEnable,
+                                   self.ParticleEnable,
+                                   self.basedriveEnable,
+                                   self.collision,
+                                   self.backface,
+                                   self.texture,
+                                   self.wireframe,
+                                   self.grid,
+                                   self.widgetVis,
+                                   self.enableAutoCamera])
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    ###############################
+    def updateInfo(self, page = 'Tree Graph'):
+        #################################################################
+        # updateInfo(self, page = 'Tree Graph')
+        # This function will be called when each time user change the main
+        # page of the window.
+        # What it dose is to call right function to restore the data for current selected page.
+        #################################################################
+        if page=='Tree Graph':
+            self.updateTreeGraph()
+        elif page == 'World Setting':
+            self.updateWorldSetting()
+
+    def updateTreeGraph(self):
+        #################################################################
+        # updateTreeGraph(self)
+        # When scene graoh page has been opend, call sceneGraphExplorer to
+        # updata the tree.
+        #################################################################
+        self.SGE.update()
+        pass
+
+    def updateWorldSetting(self):
+        #################################################################
+        # updateWorldSetting(self)
+        # When world setting page has been selected, this function will
+        # reset those check box in the page to reflect the current world setting.
+        #################################################################
+        self.LightingVar.set(self.lightEnable)
+
+        self.CollisionVar.set(self.collision)
+        self.ParticleVar.set(self.ParticleEnable)
+        self.baseUseDriveVar.set(self.basedriveEnable)
+        self.backgroundColor.set(value = self.worldColor)
+        pass
+
+    def toggleLights(self):
+        #################################################################
+        # toggleLights(self)
+        # send out a message to let sceneEditor know we need to toggle the light.
+        # Then, sceneEditor will pass the message to dataHolder to disable/enable
+        # the lights. (lightManager is inside the dataHolder)
+        #################################################################
+        self.lightEnable = (self.lightEnable+1)%2
+        messenger.send('SW_lightToggle')
+        pass
+
+    def showCollision(self):
+        #################################################################
+        # showCollision(self)
+        # This function will send out a message to sceneEditor to toggle
+        # the visibility of collision objects.
+        #################################################################
+        self.collision = (self.collision+1)%2
+        messenger.send('SW_collisionToggle', [self.collision])
+        pass
+
+    def enableParticle(self):
+        #################################################################
+        # enableParticle(self)
+        # This function will send out a message to sceneEditor to toggle
+        # the visibility of particle objects.
+        #################################################################
+        self.ParticleEnable = (self.ParticleEnable+1)%2
+        messenger.send('SW_particleToggle', [self.ParticleEnable])
+        pass
+
+    def enablebaseUseDrive(self):
+        #################################################################
+        # enablebaseUseDrive(self)
+        # This function will toggle the usage of base.useDrive.
+        # Well, it may not usefull at all.
+        #
+        # We won't send out any message in this time to notice
+        # the sceneEditor this event happend.
+        # In the other hand, we will restore it back when
+        # the side window has been closed.
+        #
+        #################################################################
+        if self.enableBaseUseDrive==0:
+            print 'Enabled'
+            base.useDrive()
+            self.enableBaseUseDrive = 1
+        else:
+            print 'disabled'
+            #base.useTrackball()
+            base.disableMouse()
+            self.enableBaseUseDrive = 0
+        self.basedriveEnable = (self.basedriveEnable+1)%2
+        pass
+
+    def toggleBackface(self):
+        #################################################################
+        # toggleBackface(self)
+        # This function will toggle the back face setting. so it will
+        # render the polygon with two sides.
+        #################################################################
+        base.toggleBackface()
+        self.backface = (self.backface+1)%2
+        return
+
+    def toggleBackfaceFromMainW(self):
+        #################################################################
+        # toggleBackfaceFromMainW(self)
+        # This function is called by sceneEditor when user used hot key
+        # to toggle the back face setting in the main panda window.
+        # In here we will only reset the flag and reset the state of
+        # check box
+        #################################################################
+        self.backface = (self.backface+1)%2
+        self.backfaceButton.toggle()
+        return
+    
+    def toggleTexture(self):
+        #################################################################
+        # toggleTexture(self)
+        # This function will toggle the txture using option for the whole scene.
+        #################################################################
+        base.toggleTexture()
+        self.texture = (self.texture+1)%2
+        return
+    
+    def toggleTextureFromMainW(self):
+        #################################################################
+        # toggleTextureFromMainW(self)
+        # This function is called by sceneEditor when user used hot key
+        # to toggle the texture usage from the main panda window.
+        # In here we will only reset the flag and reset the state of
+        # check box
+        #################################################################
+        self.texture = (self.texture+1)%2
+        self.textureButton.toggle()
+        return
+    
+    def toggleWireframe(self):
+        #################################################################
+        # toggleWireframe(self)
+        # This function will toggle the wire frame mode.
+        #################################################################
+        base.toggleWireframe()
+        self.wireframe = (self.wireframe+1)%2
+        return
+
+    def toggleWireframeFromMainW(self):
+        #################################################################
+        # toggleWireframeFromMainW(self)
+        # This function is called by sceneEditor when user used hot key
+        # to toggle the wire frame mode in the main panda window.
+        # In here we will only reset the flag and reset the state of
+        # check box
+        #################################################################
+        self.wireframe = (self.wireframe+1)%2
+        self.wireframeButton.toggle()
+        return
+
+    def toggleGrid(self):
+        #################################################################
+        # toggleGrid(self)
+        # This function will toggle the usage of the grid.
+        #################################################################
+        self.grid = (self.grid+1)%2
+        if self.grid==1:
+            SEditor.grid.enable()
+        else:
+            SEditor.grid.disable()
+
+    def togglewidgetVis(self):
+        #################################################################
+        # togglewidgetVis(self)
+        # This function will toggle the visibility of the widget of the grid.
+        #################################################################
+        self.widgetVis = (self.widgetVis+1)%2
+        SEditor.toggleWidgetVis()
+        if SEditor.widget.fActive:
+                messenger.send('shift-f')
+        return
+
+    def toggleWidgetVisFromMainW(self):
+        #################################################################
+        # toggleWidgetVisFromMainW(self)
+        # This function is called by sceneEditor when user used hot key
+        # to toggle the visibility of widgets ('v') from the main panda window.
+        # In here we will only reset the flag and reset the state of
+        # check box
+        #################################################################
+        self.widgetVis = (self.widgetVis+1)%2
+        self.widgetVisButton.toggle()
+        return
+        
+    def setBackgroundColorVec(self,color):
+        #################################################################
+        # setBackgroundColorVec(self,color)
+        # Call back function
+        # This will be called from the colorEntry on the world setting page.
+        # The "color" here is a list containing three integer data, R, G and B.
+        #################################################################
+        base.setBackgroundColor(color[0]/255.0,
+                                color[1]/255.0,
+                                color[2]/255.0)
+        self.worldColor = [color[0],color[1],color[2],0]
+
+    def toggleAutoCamera(self):
+        #################################################################
+        # toggleAutoCamera(self)
+        # This function will toggle the usage of the auto-camera movement
+        # when user loaded model or actor into the scene.
+        #################################################################
+        self.enableAutoCamera = (self.enableAutoCamera+1)%2
+        SEditor.toggleAutoCamera()
+        return
+
+    def selectPage(self,page='Tree Graph'):
+        #################################################################
+        #################################################################
+        self.notebookFrame.selectpage(page)
+

+ 2 - 0
contrib/src/sceneeditor/Sources.pp

@@ -0,0 +1,2 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.

+ 0 - 0
contrib/src/sceneeditor/__init__.py


+ 302 - 0
contrib/src/sceneeditor/collisionWindow.py

@@ -0,0 +1,302 @@
+#################################################################
+# collisionWindow.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+# Import Tkinter, Pmw, and the floater code from this directory tree.
+from direct.tkwidgets.AppShell import *
+from direct.showbase.TkGlobal import *
+from seColorEntry import *
+from direct.tkwidgets import VectorWidgets
+from direct.tkwidgets import Floater
+from direct.tkwidgets import Slider
+from Tkinter import *
+import string, math, types
+from pandac.PandaModules import *
+
+
+class collisionWindow(AppShell):
+    #################################################################
+    # This will open a talk window for user to set the collision object
+    # In here, we won't finish the whole process to generate the
+    # collision object, half of process will be finished by dataHolder.
+    #################################################################
+    # Override class variables
+    appname = 'Creating Collision Object'
+    frameWidth  = 600
+    frameHeight = 300
+
+    widgetsDict = {}
+
+    # Define the types of collision we take care here
+    collisionType = ['collisionPolygon',
+                     'collisionSphere',
+                     'collisionSegment',
+                     'collisionRay']
+    
+    def __init__(self, nodePath, parent = None, **kw):
+        
+        self.nodePath = nodePath
+        self.objType = 'collisionSphere' # set default type to Collision Sphere
+        
+        INITOPT = Pmw.INITOPT
+        optiondefs = (
+            ('title',               self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize the superclass
+        AppShell.__init__(self)
+
+        # Execute option callbacks
+        self.initialiseoptions(collisionWindow)
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+
+    def createInterface(self):
+        # Handle to the toplevels interior
+        interior = self.interior()
+        menuBar = self.menuBar
+        self.menuBar.destroy()
+
+        # Create a frame to hold all stuff
+        mainFrame = Frame(interior)
+
+        frame = Frame(mainFrame)
+
+        self.collisionTypeEntry = self.createcomponent(
+            'Collision Type', (), None,
+            Pmw.ComboBox, (frame,),
+            labelpos = W, label_text='Collision Object Type:', entry_width = 20,
+            selectioncommand = self.setObjectType,
+            scrolledlist_items = self.collisionType)
+        self.collisionTypeEntry.pack(side=LEFT, padx=3)
+
+        label = Label(frame, text='Parent NodePath: '+ self.nodePath.getName(), font=('MSSansSerif', 12),
+                      relief = RIDGE)
+        label.pack(side=LEFT,expand=0,fill=X, padx=20)
+
+        frame.pack(side=TOP, fill=X, expand=True, padx=3)
+        self.collisionTypeEntry.selectitem('collisionSphere', setentry=True)
+        
+        self.inputZone = Pmw.Group(mainFrame, tag_pyclass = None)
+        self.inputZone.pack(fill='both',expand=1)
+        settingFrame = self.inputZone.interior()
+
+        ############################################
+        # Notebook pages for specific object setting
+        ############################################
+        self.objNotebook = Pmw.NoteBook(settingFrame, tabpos = None,
+                                        borderwidth = 0)
+        PolygonPage = self.objNotebook.add('Polygon')
+        SpherePage = self.objNotebook.add('Sphere')
+        SegmentPage = self.objNotebook.add('Segment')
+        RayPage = self.objNotebook.add('Ray')
+        self.objNotebook.selectpage('Sphere')
+        # Put this here so it isn't called right away
+        self.objNotebook['raisecommand'] = self.updateObjInfo
+
+        # Polygon object setting
+
+        Interior = Frame(PolygonPage)
+        label = Label(Interior, text='Attention! All Coordinates Are Related To Its Parent Node!')
+        label.pack(side=LEFT,expand=0,fill=X, padx=1)
+        Interior.pack(side=TOP, expand=0,fill=X)
+        
+        self.createPosEntry(PolygonPage, catagory='Polygon', id='Point A')
+        self.createPosEntry(PolygonPage, catagory='Polygon', id='Point B')
+        self.createPosEntry(PolygonPage, catagory='Polygon', id='Point C')
+
+        # Sphere object setting
+
+        Interior = Frame(SpherePage)
+        label = Label(Interior, text='Attention! All Coordinates Are Related To Its Parent Node!')
+        label.pack(side=LEFT,expand=0,fill=X, padx=1)
+        Interior.pack(side=TOP, expand=0,fill=X)
+        
+        self.createPosEntry(SpherePage, catagory='Sphere', id='Center Point')
+
+        self.createEntryField(SpherePage,catagory='Sphere', id='Size',
+                              value = 1.0,
+                              command = None,
+                              initialState='normal',
+                              side = 'top')
+
+        # Segment object setting
+
+        Interior = Frame(SegmentPage)
+        label = Label(Interior, text='Attention! All Coordinates Are Related To Its Parent Node!')
+        label.pack(side=LEFT,expand=0,fill=X, padx=1)
+        Interior.pack(side=TOP, expand=0,fill=X)
+        
+        self.createPosEntry(SegmentPage, catagory='Segment', id='Point A')
+        self.createPosEntry(SegmentPage, catagory='Segment', id='Point B')
+
+        # Ray object setting
+
+        Interior = Frame(RayPage)
+        label = Label(Interior, text='Attention! All Coordinates Are Related To Its Parent Node!')
+        label.pack(side=LEFT,expand=0,fill=X, padx=1)
+        Interior.pack(side=TOP, expand=0,fill=X)
+        
+        self.createPosEntry(RayPage, catagory='Ray', id='Origin')
+        self.createPosEntry(RayPage, catagory='Ray', id='Direction')
+
+
+           
+        self.objNotebook.setnaturalsize()
+        self.objNotebook.pack(expand = 1, fill = BOTH)
+
+        self.okButton = Button(mainFrame, text="OK", command=self.okPress,width=10)
+        self.okButton.pack(fill=BOTH,expand=0,side=RIGHT)
+
+        mainFrame.pack(expand=1, fill = BOTH)
+
+    def onDestroy(self, event):
+        messenger.send('CW_close')
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    def setObjectType(self, typeName = 'collisionSphere'):
+        #################################################################
+        # setObjectType(self, typeName = 'collisionSphere')
+        # Call back function
+        # This function will be called when user select target collision
+        # type on the combo box on the panel.
+        # Basically, this function's job is to switch the notebook page to right one.
+        #################################################################
+        self.objType = typeName
+        if self.objType=='collisionPolygon':
+            self.objNotebook.selectpage('Polygon')
+        elif self.objType=='collisionSphere':
+            self.objNotebook.selectpage('Sphere')
+        elif self.objType=='collisionSegment':
+            self.objNotebook.selectpage('Segment')
+        elif self.objType=='collisionRay':
+            self.objNotebook.selectpage('Ray')
+            
+        return
+
+    def updateObjInfo(self, page=None):
+        #################################################################
+        # Nothing. Unlike in the lighting panel, we don't have to keep data
+        # once user switch the page.
+        #################################################################
+        return
+
+    def okPress(self):
+        #################################################################
+        # okPress(self)
+        # This function will be called when user click on the Ok button.
+        # Then this function will collect all parameters that we need to create
+        # a collision Object from the panel and generate the colision Object.
+        # In the last, it will send the object out with a message to dataHolder to
+        # put the object into a CollisionNode and attach it to the target nodePath
+        #################################################################
+        collisionObject = None
+        print self.objType
+        if self.objType=='collisionPolygon':
+            pointA =  Point3(float(self.widgetDict['PolygonPoint A'][0]._entry.get()),
+                             float(self.widgetDict['PolygonPoint A'][1]._entry.get()),
+                             float(self.widgetDict['PolygonPoint A'][2]._entry.get()))
+            pointB =  Point3(float(self.widgetDict['PolygonPoint B'][0]._entry.get()),
+                             float(self.widgetDict['PolygonPoint B'][1]._entry.get()),
+                             float(self.widgetDict['PolygonPoint B'][2]._entry.get()))
+            pointC =  Point3(float(self.widgetDict['PolygonPoint C'][0]._entry.get()),
+                             float(self.widgetDict['PolygonPoint C'][1]._entry.get()),
+                             float(self.widgetDict['PolygonPoint C'][2]._entry.get()))
+            collisionObject = CollisionPolygon(pointA, pointB, pointC)
+            
+        elif self.objType=='collisionSphere':
+            collisionObject = CollisionSphere(float(self.widgetDict['SphereCenter Point'][0]._entry.get()),
+                                              float(self.widgetDict['SphereCenter Point'][1]._entry.get()),
+                                              float(self.widgetDict['SphereCenter Point'][2]._entry.get()),
+                                              float(self.widgetDict['SphereSize'].getvalue()))
+        
+        elif self.objType=='collisionSegment':
+            pointA =  Point3(float(self.widgetDict['SegmentPoint A'][0]._entry.get()),
+                             float(self.widgetDict['SegmentPoint A'][1]._entry.get()),
+                             float(self.widgetDict['SegmentPoint A'][2]._entry.get()))
+            pointB =  Point3(float(self.widgetDict['SegmentPoint B'][0]._entry.get()),
+                             float(self.widgetDict['SegmentPoint B'][1]._entry.get()),
+                             float(self.widgetDict['SegmentPoint B'][2]._entry.get()))
+            
+            collisionObject = CollisionSegment()
+            collisionObject.setPointA(pointA)
+            collisionObject.setFromLens(base.cam.node(), Point2( -1, 1 ))  ## You must set up the camera lensNode before you set point B....
+            collisionObject.setPointB(pointB)
+        
+        elif self.objType=='collisionRay':
+            point =  Point3(float(self.widgetDict['RayOrigin'][0]._entry.get()),
+                            float(self.widgetDict['RayOrigin'][1]._entry.get()),
+                            float(self.widgetDict['RayOrigin'][2]._entry.get()))
+            
+            vector =  Vec3(float(self.widgetDict['RayDirection'][0]._entry.get()),
+                           float(self.widgetDict['RayDirection'][1]._entry.get()),
+                           float(self.widgetDict['RayDirection'][2]._entry.get()))
+
+            print vector, point
+            
+            collisionObject = CollisionRay()
+            collisionObject.setOrigin(point)
+            collisionObject.setDirection(vector)
+            #collisionObject.setFromLens(base.cam.node(), Point2( -1, 1 ))  ## You must set up the camera lensNode before you set up others...
+            
+        if self.objType=='collisionPolygon':
+            messenger.send('CW_addCollisionObj', [collisionObject, self.nodePath, pointA, pointB, pointC])
+        else:
+            messenger.send('CW_addCollisionObj', [collisionObject, self.nodePath])
+
+        self.quit()
+        
+        return
+
+    def createPosEntry(self, contentFrame, catagory, id):
+        posInterior = Frame(contentFrame)
+        label = Label(posInterior, text=id+':')
+        label.pack(side=LEFT,expand=0,fill=X, padx=1)
+        self.posX = self.createcomponent('posX'+catagory+id, (), None,
+                                         Floater.Floater, (posInterior,),
+                                         text = 'X', relief = FLAT,
+                                         value = 0.0,
+                                         entry_width = 6)
+        
+        self.posX.pack(side=LEFT,expand=0,fill=X, padx=1)
+        
+        self.posY = self.createcomponent('posY'+catagory+id, (), None,
+                                         Floater.Floater, (posInterior,),
+                                         text = 'Y', relief = FLAT,
+                                         value = 0.0,
+                                         entry_width = 6)
+        self.posY.pack(side=LEFT, expand=0,fill=X, padx=1)
+        
+        self.posZ = self.createcomponent('posZ'+catagory+id, (), None,
+                                         Floater.Floater, (posInterior,),
+                                         text = 'Z', relief = FLAT,
+                                         value = 0.0,
+                                         entry_width = 6)
+        self.posZ.pack(side=LEFT, expand=0,fill=X, padx=1)
+        self.widgetDict[catagory+id]=[self.posX, self.posY, self.posZ]
+        posInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+        return
+
+    def createEntryField(self, parent, catagory, id, value,
+                         command, initialState, labelWidth = 6,
+                         side = 'left', fill = X, expand = 0,
+                         validate = None,
+                         defaultButton = False, buttonText = 'Default',defaultFunction = None ):
+        frame = Frame(parent)
+        widget = Pmw.EntryField(frame, labelpos='w', label_text = id+':',
+                                value = value, entry_font=('MSSansSerif', 10),label_font=('MSSansSerif', 10),
+                                modifiedcommand=command, validate = validate,
+                                label_width = labelWidth)
+        widget.configure(entry_state = initialState)
+        widget.pack(side=LEFT)
+        self.widgetDict[catagory+id] = widget
+        if defaultButton and (defaultFunction!=None):
+            widget = Button(frame, text=buttonText, font=('MSSansSerif', 10), command = defaultFunction)
+            widget.pack(side=LEFT, padx=3)
+            self.widgetDict[catagory+id+'-'+'DefaultButton']=widget
+            
+        frame.pack(side = side, fill = fill, expand = expand,pady=3)

+ 695 - 0
contrib/src/sceneeditor/controllerWindow.py

@@ -0,0 +1,695 @@
+#################################################################
+# controllerWindow.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+
+from direct.tkwidgets.AppShell import AppShell
+from Tkinter import Frame, Label, Button
+import string, Pmw, Tkinter
+
+# Define the Category
+KEYBOARD = 'Keyboard-'
+TRACKER = 'Tarcker-'
+
+class controllerWindow(AppShell):
+    #################################################################
+    # This will open a talk window for user to set the control mechanism
+    # In here, user can choose to control what object by keyboard or other inputs.
+    #################################################################
+    
+    # Override class variables
+    appname = 'Controller Panel'
+    frameWidth  = 500
+    frameHeight = 500
+
+    widgetsDict = {} # category-id : widget obj
+
+    # setup the type of controller we handle here.
+    controllerList = ['Keyboard',
+                      'Tracker']
+    
+    # Default Keyboard setting
+    keyboardMapDict = {}
+    keyboardSpeedDict = {}
+    
+    def __init__(self, listOfObj, controlType , dataList, parent = None, **kw):
+        if controlType == 'Keyboard':
+            self.nodePath = dataList[0] # Default setting -> mainly used for Keyboard control now.
+            self.nameOfNode = self.nodePath.getName()
+            self.controllType = 'Keyboard'
+            self.keyboardMapDict.clear()
+            self.keyboardMapDict = dataList[1]
+            self.keyboardSpeedDict.clear()
+            self.keyboardSpeedDict = dataList[2]
+            
+        self.listOfObj = listOfObj
+        self.keepControl = False
+        
+        INITOPT = Pmw.INITOPT
+        optiondefs = (
+            ('title',               self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize the superclass
+        AppShell.__init__(self)
+
+        # Execute option callbacks
+        self.initialiseoptions(controllerWindow)
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+
+    def createInterface(self):
+        # Handle to the toplevels interior
+        interior = self.interior()
+        menuBar = self.menuBar
+        
+        # We don't need menu bar here
+        self.menuBar.destroy() 
+
+        # Create a frame to hold all stuff
+        mainFrame = Frame(interior)
+
+        # A comboBox to switch the controller
+        frame = Frame(mainFrame)
+        self.cotrollerTypeEntry = self.createcomponent(
+            'Controller Type', (), None,
+            Pmw.ComboBox, (frame,),
+            labelpos = Tkinter.W, label_text='Controller Type:', entry_width = 20,entry_state = Tkinter.DISABLED,
+            selectioncommand = self.setControllerType,
+            scrolledlist_items = self.controllerList)
+        self.cotrollerTypeEntry.pack(side=Tkinter.LEFT)
+        frame.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=False, pady = 3)
+        self.cotrollerTypeEntry.selectitem('Keyboard', setentry=True)
+        
+        self.inputZone = Pmw.Group(mainFrame, tag_pyclass = None)
+        self.inputZone.pack(fill='both',expand=1)
+        settingFrame = self.inputZone.interior()
+
+        ###################################################
+        # Notebook pages for specific controller setting  #
+        ###################################################
+        self.contentWidge = self.createcomponent(
+            'scrolledFrame',
+            (), None,
+            Pmw.ScrolledFrame, (settingFrame,),
+            hull_width = 200, hull_height = 300,
+            usehullsize = 1)
+        self.contentFrame = self.contentWidge.component('frame')
+        self.contentWidge.pack(fill = 'both', expand = 1,padx = 3, pady = 5)
+        self.objNotebook = Pmw.NoteBook(self.contentFrame, tabpos = None,
+                                        borderwidth = 0)
+        keyboardPage = self.objNotebook.add('Keyboard')
+        tarckerPage = self.objNotebook.add('Tracker')
+        self.objNotebook.selectpage('Keyboard')
+        self.objNotebook.pack(side = Tkinter.TOP, fill='both',expand=False)
+        # Put this here so it isn't called right away
+        self.objNotebook['raisecommand'] = self.updateControlInfo
+
+        # Keyboard  setting
+        assignFrame = Frame(keyboardPage)
+
+        Interior = Frame(assignFrame)
+        widget = self.createcomponent(
+            'Target Type', (), None,
+            Pmw.ComboBox, (Interior,),
+            labelpos = Tkinter.W, label_text='Target Object:', entry_width = 20, entry_state = Tkinter.DISABLED,
+            selectioncommand = self.setTargetObj,
+            scrolledlist_items = self.listOfObj)
+        widget.pack(side=Tkinter.LEFT, padx=3)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 5)
+        widget.selectitem(self.nameOfNode, setentry=True)
+        self.widgetsDict[KEYBOARD+'ObjList'] = widget
+
+        inputZone = Pmw.Group(assignFrame, tag_pyclass = None)
+        inputZone.pack(fill='both',expand=1)
+        settingFrame = inputZone.interior()
+        
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Assign a Key For:').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True,pady = 6 )
+        
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Forward   :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Forward key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyForward'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyForward'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Forward Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedForward'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedForward'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Backward  :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Backward key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyBackward'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyBackward'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Backward Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedBackward'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedBackward'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Right     :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Right key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyRight'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyRight'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Right Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedRight'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedRight'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Left      :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Left key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyLeft'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyLeft'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Left Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedLeft'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedLeft'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Up        :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Up key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyUp'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyUp'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Up Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedUp'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedUp'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Down      :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Down key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyDown'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyDown'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Down Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedDown'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedDown'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Turn Right:', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn Right key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyTurnRight'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyTurnRight'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn Right Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedTurnRight'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedTurnRight'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Turn Left :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn Left key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyTurnLeft'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyTurnLeft'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn Left Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedTurnLeft'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedTurnLeft'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Turn UP   :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn UP key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyTurnUp'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyTurnUp'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn UP Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedTurnUp'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedTurnUp'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Turn Down :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn Down key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyTurnDown'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyTurnDown'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Turn Down Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedTurnDown'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedTurnDown'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Roll Right:', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Roll Right key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyRollRight'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyRollRight'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Roll Right Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedRollRight'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedRollRight'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Roll Left :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Roll Left key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyRollLeft'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyRollLeft'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Roll Left Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedRollLeft'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedRollLeft'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale UP :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale UP key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleUp'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleUp'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale UP Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleUp'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleUp'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale Down:', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Down key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleDown'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleDown'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Down Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleDown'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleDown'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale X UP :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale X UP key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleXUp'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleXUp'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale X UP Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleXUp'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleXUp'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale X Down:', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale X Down key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleXDown'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleXDown'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Down X Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleXDown'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleXDown'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale Y UP :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Y UP key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleYUp'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleYUp'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Y UP Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleYUp'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleYUp'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale Y Down:', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Y Down key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleYDown'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleYDown'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Down XY Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleYDown'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleYDown'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale Z UP :', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Z UP key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleZUp'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleZUp'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Z UP Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleZUp'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleZUp'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        Interior = Frame(settingFrame)
+        widget = Label(Interior, text = 'Scale Z Down:', width = 20, anchor = Tkinter.W).pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Z Down key', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardMapDict['KeyScaleZDown'],
+            labelpos = Tkinter.W, label_text='Key :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'KeyScaleZDown'] = widget
+        widget = Label(Interior, text = '   ').pack(side=Tkinter.LEFT, expand = False)
+        widget = self.createcomponent(
+            'Scale Down Z Speed', (), None,
+            Pmw.EntryField, (Interior,),
+            value = self.keyboardSpeedDict['SpeedScaleZDown'],
+            labelpos = Tkinter.W, label_text='Speed :', entry_width = 10)
+        widget.pack(side=Tkinter.LEFT, expand = False)
+        self.widgetsDict[KEYBOARD+'SpeedScaleZDown'] = widget
+        widget = Label(Interior, text = 'Per Second').pack(side=Tkinter.LEFT, expand = False)
+        Interior.pack(side=Tkinter.TOP, fill=Tkinter.X, expand=True, pady = 4 )
+
+        assignFrame.pack(side=Tkinter.TOP, expand=True, fill = Tkinter.X)
+        keyboardPage.pack(side=Tkinter.TOP, expand=True, fill = Tkinter.X)
+        
+        ####################################################################
+        ####################################################################
+        # End of Keyboard control page
+        ####################################################################
+        ####################################################################
+        # Pack the mainFrame
+        frame = Frame(mainFrame)
+        widget = Button(frame, text='OK', width = 13, command=self.ok_press).pack(side=Tkinter.RIGHT)
+        widget = Button(frame, text='Enable Control', width = 13, command=self.enableControl).pack(side=Tkinter.LEFT)
+        widget = Button(frame, text='Disable Control', width = 13, command=self.disableControl).pack(side=Tkinter.LEFT)
+        widget = Button(frame, text='Save & Keep', width = 13, command=self.saveKeepControl).pack(side=Tkinter.LEFT)
+        frame.pack(side = Tkinter.BOTTOM, expand=1, fill = Tkinter.X)
+        mainFrame.pack(expand=1, fill = Tkinter.BOTH)
+
+    def onDestroy(self, event):
+        # Check if user wish to keep the control after the window closed.
+        if not self.keepControl:
+            messenger.send('ControlW_controlDisable',[self.controllType])
+        messenger.send('ControlW_close')
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    def ok_press(self):
+        ####################################################################
+        # ok_press(self)
+        # After user clicked on "OK" button, this function will be called.
+        # This function will collect data from the panel and send it back to
+        # sceneEditor and close the window. It won't activate control at all.
+        ####################################################################
+        if self.controllType=='Keyboard':
+            for index in self.keyboardMapDict:
+                self.keyboardMapDict[index] = self.widgetsDict['Keyboard-'+index].getvalue()
+            for index in self.keyboardSpeedDict:
+                self.keyboardSpeedDict[index] = float(self.widgetsDict['Keyboard-'+index].getvalue())
+            messenger.send('ControlW_controlSetting', ['Keyboard', [self.nodePath, self.keyboardMapDict, self.keyboardSpeedDict]])
+        self.quit()
+        return
+
+    def enableControl(self):
+        ####################################################################
+        # enableControl(self)
+        # Call back function.
+        # THis function will be called each time when user clicks on the
+        # "Enable Control" button. This function will do pretty much
+        # the same thing with ok_press function, except that this function
+        # will activate the control process in sceneEditor.
+        # However, if user didn't click on the button "Keep ANd Save,"
+        # the control process will be terminated when user closed the panel.
+        ####################################################################
+        if self.controllType=='Keyboard':
+            for index in self.keyboardMapDict:
+                self.keyboardMapDict[index] = self.widgetsDict['Keyboard-'+index].getvalue()
+            for index in self.keyboardSpeedDict:
+                self.keyboardSpeedDict[index] = float(self.widgetsDict['Keyboard-'+index].getvalue())
+            messenger.send('ControlW_controlEnable', ['Keyboard', [self.nodePath, self.keyboardMapDict, self.keyboardSpeedDict]])
+        return
+
+    def disableControl(self):
+        ####################################################################
+        # disableControl(self)
+        # This function will send out a message to sceneEditor to stop the
+        # control task.
+        ####################################################################
+        messenger.send('ControlW_controlDisable',[self.controllType])
+        return
+
+    def setControllerType(self, typeName = 'Keyboard'):
+        #################################################################
+        # setControllerType(self, typeName = 'Keyboard')
+        # Call back function
+        # This function will be called when user select the type of
+        # controller they want on the combo box on the panel.
+        # Basically, this function's job is to switch the notebook page to right one.
+        #################################################################
+        self.controllType = typeName
+        if self.controllType=='Keyboard':
+            self.objNotebook.selectpage('Keyboard')
+        elif self.controllType=='Tracker':
+            self.objNotebook.selectpage('Tracker')
+            
+        return
+
+    def updateControlInfo(self, page=None):
+        #################################################################
+        # Nothing. Unlike in the lighting panel, we don't have to keep data
+        # once user switch the page.
+        #################################################################
+        return
+
+    def setTargetObj(self, name = None, tracker = None):
+        #################################################################
+        # setTargetObj(self, name = None)
+        # setup the target object we want to control
+        #################################################################
+        if tracker == None: # Call from Keyboard page.
+            if name=='camera':
+                self.nodePath = camera
+            else:
+                messenger.send('ControlW_require',[name])
+        return
+
+    def resetNameList(self, list, name = None, nodePath = None):
+        ####################################################################
+        # resetNameList(self, list, name = None, nodePath = None)
+        # This function will be called be sceneEditor in order to reset the
+        # object list inside the panel.
+        # list here is a name list for all objects that can be set on control
+        # in the scene. If user has specify a name and a nodePath in, it will
+        # check if the name is equal to the name of current control target.
+        # If so, it will change the name showed on panel.
+        ####################################################################
+        self.widgetsDict['Keyboard-ObjList'].setlist(list)
+        if name != None:
+            if self.nameOfNode == name:
+                self.nameOfNode = self.nodePath.getName()
+                self.widgetsDict['Keyboard-ObjList'].selectitem(self.nameOfNode, setentry=True)
+        return
+
+    def setNodePathIn(self, nodePath):
+        ####################################################################
+        # setNodePathIn(self, nodePath)
+        # THis function will be called by sceneEditor.
+        # After we send out a message to sceneEditor in setTargetObj function,
+        # This function will be called by sceneEditor after we get the reference
+        # of target object from dataHolder.
+        # This function will keep the reference.
+        ####################################################################
+        self.nodePath = nodePath
+        self.nameOfNode = self.nodePath.getName()
+        return
+
+    def saveKeepControl(self):
+        #################################################################
+        # saveKeepControl(self)
+        # When any time user has click on the "Save & Keep" button,
+        # This function will be called.
+        # This function will send out the message to sceneEditor to process
+        # the saving. Also, this function will change the "self.keepControl" flag.
+        # So, when user closes the window with control enabled, it will keep
+        # the control process runnning. otherwise program will disable the
+        # control automatically when panel has been closed.
+        #
+        # It doesn't mean that this function will automatically enable the
+        # control when user closed the window!!
+        # This flag will only decide that we will send out a "stopControl"
+        # message or not.
+        #
+        #################################################################
+        self.keepControl = True
+        if self.controllType=='Keyboard':
+            for index in self.keyboardMapDict:
+                self.keyboardMapDict[index] = self.widgetsDict['Keyboard-'+index].getvalue()
+            for index in self.keyboardSpeedDict:
+                self.keyboardSpeedDict[index] = float(self.widgetsDict['Keyboard-'+index].getvalue())
+            print self.nodePath
+            messenger.send('ControlW_saveSetting', ['Keyboard', [self.nodePath, self.keyboardMapDict, self.keyboardSpeedDict]])
+        return
+
+    

+ 1071 - 0
contrib/src/sceneeditor/dataHolder.py

@@ -0,0 +1,1071 @@
+###############################
+# TK and PMW INTERFACE MODULES#
+###############################
+from direct.showbase.TkGlobal import*
+from tkFileDialog import *
+import Pmw
+import tkFileDialog
+import tkMessageBox
+from direct.tkwidgets import Dial
+from direct.tkwidgets import Floater
+
+
+#############################
+# Scene Editor Python Files #
+#############################
+from seLights import * # All the scene editor lighting
+from seFileSaver import * # The actual File Saving Module which generates Python code
+
+################################
+#Panda Modules                 #
+################################
+from direct.actor import Actor
+
+###############################
+# Core Python Modules         #
+###############################
+import os
+import string
+import sys
+
+import seParticleEffect
+import seParticles
+
+######################################################################################################
+# Data Holder
+# This class will actually hold all data in the form of dictionaries
+# This is essentially the entire state of the scene
+# When saving a scene, the dictionaries will be accessed and code generated for the loaded content
+# Dictionaries are used to access the objects in the scene and their properties so code can be generated
+# Write/Use Accessor Methods to interface with these dictionaries
+######################################################################################################
+
+class dataHolder:
+
+    ModelDic = {} # Name: Model Nodepath; ModelRoot (whatever loader.loadModel() returns)
+    ModelRefDic = {} # Name:File Path; (whatever the Open File Dialog Returns)
+    ActorDic = {} # Name:Actor Actor; Nodepath, ModelRoot (whatever Actor.Actor() returns)
+    ActorRefDic = {} # Name:File Path; (whatever the Open File Dialog Returns)
+    curveDict = {}  # Node Name: CurveCollection; The actual curve collection data
+    collisionDict = {} # Node Name: collisionNode; collisionNode in which contains a collision object
+    blendAnimDict = {} # Node Name: Dictionary of blended animation; User blened animation will be saved in here.
+                       # the data structure in the inner dictionary is
+                       # {"name of blended animation" : [Animation Name A, Animation Name B, Effect(Float)]}
+    collisionVisable = True # A flag used to record that collision objects are visable or not
+    dummyDict = {}  # Node Name: Dummy Obj; All Object created as a dummy will be save here.
+    particleDict={} # "Effect Name": Effect Object
+    particleNodes={} # "Effect Name": Node which is a parent to the effect used to move it aruond easily
+    ModelNum = 0 # Count of number of models loaded
+    ActorNum = 0 # Count of number of animations loaded
+    theScene=None # Global variable to hold a loaded scene
+    CollisionHandler = CollisionHandlerEvent() # This object shows what happend when collision appeared
+                                               # Now, the default is CollisionHandlerEvent Type, which will just send out message when collision happend
+
+    controlType = 'Keyboard' # default control input setting
+    # Default Control setting for keyboard.
+    controlTarget = camera
+    # This two dictionary set the basic setting for the keyboard contorl
+    # Those dictionaries will be passed into controller panel each time it has been opend
+    # Do NOT change anything about INDEX in the dictionary!! (But it's OK to change the values)
+    keyboardMapDict = {'KeyForward':'arrow_up',
+                       'KeyBackward':'arrow_down',
+                       'KeyLeft':'arrow_left',
+                       'KeyRight':'arrow_right',
+                       'KeyUp':'',
+                       'KeyDown':'',
+                       'KeyTurnRight':'',
+                       'KeyTurnLeft':'',
+                       'KeyTurnUp':'',
+                       'KeyTurnDown':'',
+                       'KeyRollRight':'',
+                       'KeyRollLeft':'',
+                       'KeyScaleUp':'',
+                       'KeyScaleDown':'',
+                       'KeyScaleXUp':'',
+                       'KeyScaleXDown':'',
+                       'KeyScaleYUp':'',
+                       'KeyScaleYDown':'',
+                       'KeyScaleZUp':'',
+                       'KeyScaleZDown':''}
+    keyboardSpeedDict = {'SpeedForward': 0,
+                         'SpeedBackward': 0,
+                         'SpeedLeft': 0,
+                         'SpeedRight': 0,
+                         'SpeedUp': 0,
+                         'SpeedDown': 0,
+                         'SpeedTurnRight': 0,
+                         'SpeedTurnLeft': 0,
+                         'SpeedTurnUp': 0,
+                         'SpeedTurnDown': 0,
+                         'SpeedRollRight':0,
+                         'SpeedRollLeft':0,
+                         'SpeedScaleUp':0,
+                         'SpeedScaleDown':0,
+                         'SpeedScaleXUp':0,
+                         'SpeedScaleXDown':0,
+                         'SpeedScaleYUp':0,
+                         'SpeedScaleYDown':0,
+                         'SpeedScaleZUp':0,
+                         'SpeedScaleZDown':0}
+
+    def __init__(self):
+
+        # Creat light manager to contorl the lighting
+        self.lightManager = seLightManager()
+        self.lightManager.allOn()
+
+        # Initialize the basic message formate from CollisionHandler
+        self.CollisionHandler.setInPattern("%fnenter%in")
+        self.CollisionHandler.setOutPattern("%fnexit%in")
+ 
+        pass
+
+    
+    def resetAll(self):
+        #################################################################
+        # resetAll(self)
+        # This function will reset the whole scene
+        #################################################################
+        # Delete Everything in the Scene Graph
+        for index in self.ModelDic:
+            self.ModelDic[index].removeNode()
+        for index in self.ActorDic:
+            self.ActorDic[index].removeNode()
+        for index in self.dummyDict:
+            self.dummyDict[index].removeNode()
+        for index in self.collisionDict:
+            self.collisionDict[index].removeNode()
+        for index in self.particleNodes:
+            self.particleDict[index].cleanup()
+            self.particleNodes[index].removeNode()
+
+        # Clear all data containers in the dataHolder
+        self.ModelDic.clear()
+        self.ModelRefDic.clear()
+        self.ActorDic.clear()
+        self.ActorRefDic.clear()
+        self.dummyDict.clear()
+        self.lightManager.deleteAll()
+        self.blendAnimDict.clear()
+        self.particleDict.clear()
+        self.particleNodes.clear()
+        
+        self.ModelNum=0
+        self.ActorNum=0
+        self.theScene=None
+        messenger.send('SGE_Update Explorer',[render])
+        print 'Scene should be cleaned up!'
+
+    def removeObj(self, nodePath):
+        #################################################################
+        # removeObj(self, nodePath)
+        # This function will take one nodePath obj as a input
+        # and will remove this node from scene if it is legal to be removed.
+        # Also, this function will remove all children nodes belong to this specific nodePath by recursive call.
+        #################################################################
+        name = nodePath.getName()
+
+        ## Check if there is any child node, if so, remove it.
+        childrenList = nodePath.getChildren()
+        
+                
+        if self.ModelDic.has_key(name):
+            del self.ModelDic[name]
+            del self.ModelRefDic[name]
+            if len(childrenList) != 0:
+                for node in childrenList:
+                    self.removeObj(node)
+            nodePath.removeNode()
+            self.ModelNum -= 1
+            pass
+        elif self.ActorDic.has_key(name):
+            del self.ActorDic[name]
+            del self.ActorRefDic[name]
+            if len(childrenList) != 0:
+                for node in childrenList:
+                    self.removeObj(node)
+            nodePath.removeNode()
+            self.ActorNum -= 1
+            pass
+        elif self.collisionDict.has_key(name):
+            del self.collisionDict[name]
+            if len(childrenList) != 0:
+                for node in childrenList:
+                    self.removeObj(node)
+            nodePath.removeNode()
+            pass
+        elif self.dummyDict.has_key(name):
+            del self.dummyDict[name]
+            if len(childrenList) != 0:
+                for node in childrenList:
+                    self.removeObj(node)
+            nodePath.removeNode()
+            pass
+        elif self.lightManager.isLight(name):
+            if len(childrenList) != 0:
+                for node in childrenList:
+                    self.removeObj(node)
+            list = self.lightManager.delete(name)
+            return list
+        elif self.particleNodes.has_key(name):
+            self.particleNodes[name].removeNode()
+            del self.particleNodes[name]
+            del self.particleDict[name]
+        else:
+            print 'You cannot remove this NodePath'
+            return
+        
+        messenger.send('SGE_Update Explorer',[render])
+        return
+
+    def duplicateObj(self, nodePath, pos, hpr, scale, num):
+        #############################################################################
+        # duplicateObj(self, nodePath, pos, hpr, scale, num)
+        # This function now only worked for either Actor or Model type node.
+        # It won't duplicate lights or others!!!
+        #
+        # This function will duplicate the input nodePath "num" times.
+        # Each time it will use "pos", "hpr" and "scale" as a offset to change the properties of copy.
+        # Then, reparent copies to the same parent of origin
+        #
+        # To Do:
+        # Make it work for all kinds of objects....
+        #############################################################################
+        name = nodePath.getName()
+        isModel = True
+        cPos = pos
+        cHpr = hpr
+        cScale = scale
+        parent = nodePath.getParent()
+        if self.ActorDic.has_key(name):
+            holder = self.ActorDic
+            holderRef = self.ActorRefDic
+            isModel = False
+        elif self.ModelDic.has_key(name):
+            holder = self.ModelDic
+            holderRef = self.ModelRefDic
+        else:
+            print '---- DataHolder: Target Obj is not a legal object could be duplicate!'
+            return
+            
+        FilePath = holderRef[name]
+        oPos = holder[name].getPos()+cPos
+        oHpr = holder[name].getHpr()+cHpr
+        
+        for i in range(num):
+            if isModel:
+                ### copy model node from modelpool
+                newName = name+'_copy_%d'%i
+                while self.isInScene(newName):
+                    newName = newName + '_1'
+                holder[newName] = loader.loadModel(FilePath.getFullpath())
+                holderRef[newName] = FilePath
+                self.ModelNum += 1
+                holder[newName].reparentTo(parent)
+                holder[newName].setPos(oPos)
+                holder[newName].setHpr(oHpr)
+                holder[newName].setScale(cScale)
+                holder[newName].setName(newName)
+                oPos = oPos + cPos
+                oHpr = oHpr + cHpr
+                
+            else:
+                ### copy the actor- not includ its animations
+                '''
+                Yeah, Yeah, Yeah, I know I should not reload the Actor but get it from modelpool too.
+                I tried, but it caused some error.
+                I 'might' be back to fix this problem.
+                '''
+                newName = name+'_copy_%d'%i
+                while self.isInScene(newName):
+                    newName = newName + '_1'
+                holder[newName] = Actor.Actor(FilePath.getFullpath())
+                holderRef[newName] = FilePath
+                self.ActorNum += 1
+                holder[newName].reparentTo(parent)
+                holder[newName].setPos(oPos)
+                holder[newName].setHpr(oHpr)
+                holder[newName].setScale(cScale)
+                holder[newName].setName(newName)
+                oPos = oPos + cPos
+                oHpr = oHpr + cHpr
+            
+        messenger.send('SGE_Update Explorer',[render])
+        return
+
+    def loadModel(self, lFilePath, FilePath, Name='Model_'):
+        ###########################################################################
+        # loadModel(self, lFilePath, FilePath, Name='Model_')
+        # This funciton will load a model node into the scene
+        # and will keep its reference in the ModelDic dictionary. {"NameOfModel":ModelRoot}
+        # Also it will keep the file path in ModelRefDic dictionary.
+        #
+        # The "lFilePath" parameter now is completely useless,
+        # but I still keep it here because maybe some day we will need it...
+        # (NOT because I am laze to change the funtion call in the sceneEditor...)
+        #
+        ###########################################################################
+        self.ModelNum += 1
+        defaultName = Name + '%d'%self.ModelNum
+        while self.isInScene(defaultName):
+            defaultName = defaultName + '_1'
+        self.ModelDic[defaultName] = loader.loadModel(FilePath)
+        if self.ModelDic[defaultName]==None:
+            del self.ModelDic[defaultName]
+            self.ModelNum -= 1
+            return False
+        self.ModelRefDic[defaultName] = FilePath
+        self.ModelDic[defaultName].setName(defaultName)
+        self.ModelDic[defaultName].reparentTo(render)
+        messenger.send('SGE_Update Explorer',[render])
+        messenger.send('DH_LoadingComplete',[self.ModelDic[defaultName]])
+        return True
+
+    def loadActor(self, lFilePath, FilePath, Name='Actor_'):
+        ###########################################################################
+        # loadActor(self, lFilePath, FilePath, Name='Actor_')
+        # This funciton will load an actor node into the scene
+        # and will keep its reference in the ActorDic dictionary.{"NameOfActor":Actor}
+        # Also it will keep the file path in ActorRefDic dictionary.
+        #
+        # The "lFilePath" parameter now is completely useless,
+        # but I still keep it here because maybe some day we will need it...
+        # (NOT because I am laze to change the funtion call in the sceneEditor...)
+        #
+        ###########################################################################
+        self.ActorNum += 1
+        defaultName = Name + '%d'%self.ActorNum
+        while self.isInScene(defaultName):
+            defaultName = defaultName + '_1'
+        self.ActorDic[defaultName] = Actor.Actor(FilePath.getFullpath())
+        if self.ActorDic[defaultName]==None:
+            del self.ActorDic[defaultName]
+            self.ActorNum -= 1
+            return False
+        self.ActorRefDic[defaultName] = FilePath
+        self.ActorDic[defaultName].setName(defaultName)
+        self.ActorDic[defaultName].reparentTo(render)
+        messenger.send('SGE_Update Explorer',[render])
+        messenger.send('DH_LoadingComplete',[self.ActorDic[defaultName]])
+        return True
+
+
+    def isActor(self, name):
+        ###########################################################################
+        # isActor(self, name)
+        # This funciton will return True if there is an Actor in the scene named "name"
+        # and will return False if not.
+        ###########################################################################
+        return self.ActorDic.has_key(name)
+
+    def getActor(self, name):
+        ###########################################################################
+        # getActor(self, name)
+        # This funciton will return an Actor node named "name"
+        ###########################################################################
+        if self.isActor(name):
+            return self.ActorDic[name]
+        else:
+            print '----No Actor named: ', name
+            return None
+
+    def getModel(self, name):
+        ###########################################################################
+        # getModel(self, name)
+        # This funciton will return a model node named "name"
+        ###########################################################################
+        if self.isModel(name):
+            return self.ModelDic[name]
+        else:
+            print '----No Model named: ', name
+            return None
+
+    def isModel(self, name):
+        ###########################################################################
+        # isModel(self, name)
+        # This funciton will return True if there is a Model in the scene named "name"
+        # and will return False if not.
+        ###########################################################################
+        return self.ModelDic.has_key(name)
+
+    def loadAnimation(self,name, Dic):
+        ###########################################################################
+        # loadAnimation(self,name, Dic)
+        # This funciton will load animation into an Actor NOde named "name."
+        # All animation data needs to be put into a dictionary "Dic".
+        # The formate of this dictionary is {"Name of Animation" : Path to the animation Egg file}
+        #
+        # Also, it will send out a message after the loading complete.
+        # 'DataH_loadFinish'+"the name of actor",
+        # this message will be catched by the sub window in the animation penal.
+        ###########################################################################
+        if self.isActor(name):
+            self.ActorDic[name].loadAnims(Dic)
+            for anim in Dic:
+                self.ActorDic[name].bindAnim(anim)
+            messenger.send('DataH_loadFinish'+name)
+            return
+        else:
+            print '------ Error when loading animation for Actor: ', name
+
+    def removeAnimation(self, name, anim):
+        ###########################################################################
+        # removeAnimation(self, name, anim)
+        # This function will remove the specific animation "anim" from the actor named "name."
+        #
+        # After remove compelete, it will send out two messages.
+        # One is 'DataH_removeAnimFinish'+"name of the actor." It will be caught by Animation Panel of this actor.
+        # The other is 'animRemovedFromNode,' this will be caught by property window of this actor.
+        ###########################################################################
+        if self.isActor(name):
+            self.ActorDic[name].unloadAnims([anim])
+            AnimDict = self.ActorDic[name].getAnimControlDict()
+            del AnimDict['lodRoot']['modelRoot'][anim]
+            messenger.send('DataH_removeAnimFinish'+name)
+            messenger.send('animRemovedFromNode',[self.ActorDic[name],self.getAnimationDictFromActor(name)])
+        return
+
+    def toggleLight(self):
+        ###########################################################################
+        # toggleLight(self)
+        # This function do noting but call a function inside the lightManger to toggle the lighting.
+        # If it is on, then it will turn it to off.
+        # If it is off, it will turn it on.
+        ###########################################################################
+        self.lightManager.toggle()
+        return
+        
+    def isLight(self,name):
+        ###########################################################################
+        # isLight(self, name)
+        # This function will check that there is a light named "name" of not.
+        # If it dose have, then return True.
+        # If it doesn't, then return False
+        ###########################################################################
+        return self.lightManager.isLight(name)
+
+    def createLight(self, type = 'ambient',
+                   lightcolor=VBase4(0.3,0.3,0.3,1),
+                   specularColor = VBase4(1),
+                   position = Point3(0,0,0),
+                   orientation = Vec3(1,0,0),
+                   constant = 1.0,
+                   linear = 0.0,
+                   quadratic = 0.0,
+                   exponent = 0.0):
+        ###########################################################################
+        # createLight(self, type = 'ambient',
+        #            lightcolor=VBase4(0.3,0.3,0.3,1),
+        #            specularColor = VBase4(1),
+        #            position = Point3(0,0,0),
+        #            orientation = Vec3(1,0,0),
+        #            constant = 1.0,
+        #            linear = 0.0,
+        #            quadratic = 0.0,
+        #            exponent = 0.0)
+        # It will create a light(seLight) into the scene.
+        #
+        # For more detail about creating light, please look the seLight.py.
+        #
+        ###########################################################################
+        list,lightNode = self.lightManager.create(type, lightcolor, specularColor, position,
+                                                  orientation, constant, linear, quadratic, exponent)
+        messenger.send('SGE_Update Explorer',[render])
+        return list, lightNode
+
+    def getLightList(self):
+        ###########################################################################
+        # getLightList(self)
+        # This function will return the lights(seLight) as a list.
+        #
+        # For more detail about creating light, please look the seLight.py.
+        #
+        ###########################################################################
+        return self.lightManager.getLightList()
+
+    def getLightNode(self,lightName):
+        ###########################################################################
+        # getLightNode(self,lightName)
+        # This function will return the lightNode(seLigth) named 'lightName' back.
+        #
+        # For more detail about creating light, please look the seLight.py.
+        #
+        ###########################################################################
+        return self.lightManager.getLightNode(lightName)
+
+    def toggleLightNode(self, lightNode):
+        ###########################################################################
+        # toggleLightNode(self, lightNode)
+        # This function will enable of disable the lightNode user put in.
+        #
+        # For more detail about creating light, please look the seLight.py.
+        #
+        ###########################################################################
+        if lightNode.active:
+            self.lightManager.setOff(lightNode)
+        else:
+            self.lightManager.setOn(lightNode)
+        return
+
+    def rename(self,nodePath,nName):
+        ###########################################################################
+        # Rename(self,nodePath,nName)
+        # First, it will check the target object is legal to rename or not.
+        # this function now doesn't support user to rename everything on the scene gragh.
+        # If there already has object hase the same name with the target object,
+        # the new name will be changed.
+        ###########################################################################
+        oName = nodePath.getName()
+        if oName == nName:
+            # If the new name is the same with old name, do nothing.
+            return
+        
+        while self.isInScene(nName):
+            nName = nName + '_1'
+       
+        if self.isActor(oName):
+            self.ActorDic[nName]= self.ActorDic[oName]
+            self.ActorRefDic[nName]= self.ActorRefDic[oName]
+            self.ActorDic[nName].setName(nName)
+            if self.blendAnimDict.has_key(oName):
+                self.blendAnimDict[nName] = self.blendAnimDict[oName]
+                del self.blendAnimDict[oName]
+            del self.ActorDic[oName]
+            del self.ActorRefDic[oName]
+        elif self.isModel(oName):
+            self.ModelDic[nName]= self.ModelDic[oName]
+            self.ModelRefDic[nName]= self.ModelRefDic[oName]
+            self.ModelDic[nName].setName(nName)
+            del self.ModelDic[oName]
+            del self.ModelRefDic[oName]
+        elif self.lightManager.isLight(oName):
+            list, lightNode = self.lightManager.rename(oName, nName)
+        elif self.dummyDict.has_key(oName):
+            self.dummyDict[nName]= self.dummyDict[oName]
+            self.dummyDict[nName].setName(nName)
+            del self.dummyDict[oName]
+        elif self.collisionDict.has_key(oName):
+            self.collisionDict[nName]= self.collisionDict[oName]
+            self.collisionDict[nName].setName(nName)
+            del self.collisionDict[oName]
+            
+        elif self.particleNodes.has_key(oName):
+            self.particleNodes[nName]= self.particleNodes[oName]
+            self.particleDict[nName]= self.particleDict[oName]
+            self.particleDict[nName].setName(nName)
+            self.particleNodes[nName].setName(nName)
+            del self.particleNodes[oName]
+            del self.particleDict[oName]
+        else:
+            print '----Error: This Object is not allowed to this function!'
+
+        if self.curveDict.has_key(oName):
+            self.curveDict[nName] = self.curveDict[oName]
+            del self.curveDict[oName]
+
+        if self.lightManager.isLight(nName):
+            return list, lightNode
+
+    def isInScene(self,name):
+        ###########################################################################
+        # isInScene(self,name)
+        # Return True if there is a Node named "name" inside the scene.
+        # This will check the whole scene, including model, actor, dummy, collisionObj...
+        ###########################################################################
+        if self.isActor(name):
+            return True
+        elif self.isModel(name):
+            return True
+        elif self.lightManager.isLight(name):
+            return True
+        elif self.dummyDict.has_key(name):
+            return True
+        elif self.collisionDict.has_key(name):
+            return True
+        elif self.particleNodes.has_key(name):
+            return True
+        elif (name == 'render')or(name == 'SEditor')or(name == 'Lights')or(name == 'camera'):
+            return True
+
+        return False
+        
+    def bindCurveToNode(self,node,curveCollection):
+        ###########################################################################
+        # bindCurveToNode(self,node,curveCollection)
+        # This function will maintain the curvesDict
+        # using the node name as a reference to assosiate a list which contains all curves related to that node.
+        ###########################################################################
+        name = node.getName()
+        if self.curveDict.has_key(name):
+            self.curveDict[name].append(curveCollection)
+            return
+        else:
+            self.curveDict[name] = [curveCollection]
+            return
+        return
+
+    def getCurveList(self, nodePath):
+        ###########################################################################
+        # getCureveList(self, nodePath)
+        # This function will return a list
+        # which contains all curves taht have been binded with the inout node
+        # If the input node has not been bindedwith any curve, it will return None.
+        ###########################################################################
+        name = nodePath.getName()
+        if self.curveDict.has_key(name):
+            return self.curveDict[name]
+        else:
+            return None
+
+    def removeCurveFromNode(self, nodePath, curveName):
+        ###########################################################################
+        # removeCurveFromNode(self, nodePath, curveName)
+        # This function will remove the "curveName" curve(Motion path data) from the nodaPath.
+        # After remove, it will send out a message.
+        # 'curveRemovedFromNode'
+        # This message will be caught by Property Window for this node.
+        ###########################################################################
+        name =nodePath.getName()
+        if self.curveDict.has_key(name):
+            index = None
+            for curve in self.curveDict[name]:
+                if curve.getCurve(0).getName() == curveName:
+                    index = self.curveDict[name].index(curve)
+                    break
+            del self.curveDict[name][index]
+            if len(self.curveDict[name])!=0:
+                messenger.send('curveRemovedFromNode',[nodePath, self.curveDict[name]])
+            else:
+                del self.curveDict[name]
+                messenger.send('curveRemovedFromNode',[nodePath, None])
+        return
+
+    def getInfoOfThisNode(self, nodePath):
+        ###########################################################################
+        # getInfoOfThisNode(self, nodePath)
+        # This function will return a list which contains all object properies
+        # that will be used in property window.
+        ###########################################################################
+        type = ''
+        info = {}
+        name = nodePath.getName()
+        if name == 'render':
+            type = 'render'
+        elif name == 'camera':
+            type = 'camera'
+            cameraNode = base.cam.node()
+            lens = cameraNode.getLens()
+            info['lensType'] = lens.getClassType().getName()
+            info['far'] = lens.getFar()
+            info['near'] = lens.getNear()
+            info['FilmSize'] = lens.getFilmSize()
+            info['fov'] = lens.getFov()
+            info['hFov'] = lens.getHfov()
+            info['vFov'] = lens.getVfov()
+            info['focalLength'] = lens.getFocalLength()
+
+            
+        elif name == 'SEditor':
+            type = 'Special'
+        elif self.isActor(name):
+            type = 'Actor'
+            info['filePath'] = self.ActorRefDic[name]
+            info['animDict'] = self.getAnimationDictFromActor(name)
+        elif self.isModel(name):
+            type = 'Model'
+            info['filePath'] = self.ModelRefDic[name]
+        elif self.isLight(name):
+            type = 'Light'
+            info['lightNode'] = self.lightManager.getLightNode(name)
+        elif self.dummyDict.has_key(name):
+            type = 'dummy'
+        elif self.collisionDict.has_key(name):
+            type = 'collisionNode'
+            info['collisionNode'] = self.collisionDict[name]
+        if self.curveDict.has_key(name):
+            info['curveList'] = self.getCurveList(nodePath)
+        
+        return type, info
+
+    def getAnimationDictFromActor(self, actorName):
+        ###########################################################################
+        # getAnimationDictFromActor(self, actorName)
+        # This function will return a Dictionary which contains the animation data in the actor "actorName".
+        # The data inside is get from the actor, so, it can't be wrong...
+        ###########################################################################
+        animContorlDict = self.ActorDic[actorName].getAnimControlDict()
+        animNameList = self.ActorDic[actorName].getAnimNames()
+        if len(animNameList)==0:
+            return {}
+        animDict = {}
+        for anim in animNameList:
+            animDict[anim] = animContorlDict['lodRoot']['modelRoot'][anim][0]
+        return animDict
+
+    def addDummyNode(self,nodePath):
+        ###########################################################################
+        # addDummyNode(self,nodePath)
+        # This function will add a dummy node into the scane and reparent it to nodePath which user put in.
+        #
+        # This dummy actually is just a default sphere model.
+        #
+        ###########################################################################
+        number = len(self.dummyDict)
+        number += 1
+        name = 'Dummy%d'%number
+        self.dummyModel = loader.loadModel( "models/misc/sphere" )
+        self.dummyModel.reparentTo(nodePath)
+        while self.isInScene(name):
+            name = name + '_1'
+        self.dummyModel.setName(name)
+        self.dummyDict[name] = self.dummyModel
+        messenger.send('SGE_Update Explorer',[render])
+        return
+
+    def addCollisionObject(self, collisionObj, nodePath, pointA=None, pointB=None, pointC=None, name = None):
+        ###########################################################################
+        # addCollisionObject(self, collisionObj, nodePath, pointA=None, pointB=None, pointC=None, name = None)
+        # This function will add a collision object into a "CollisionNode" object and put it into scene.
+        # The collision object will be reparent to "nodePath" and
+        # will be show on the screen if user has enable the "show collision objects" option.
+        ###########################################################################
+        if name == None:
+            name = 'CollisionNode_%d'%len(self.collisionDict)
+        while self.isInScene(name):
+            name=name + '_1'
+        node = CollisionNode(name)
+        node.addSolid(collisionObj)
+        self.collisionDict[name] = nodePath.attachNewNode(node)
+
+        if pointA!=None:
+            self.collisionDict[name].setTag('A_X','%f'%pointA.getX())
+            self.collisionDict[name].setTag('A_Y','%f'%pointA.getY())
+            self.collisionDict[name].setTag('A_Z','%f'%pointA.getZ())
+            self.collisionDict[name].setTag('B_X','%f'%pointB.getX())
+            self.collisionDict[name].setTag('B_Y','%f'%pointB.getY())
+            self.collisionDict[name].setTag('B_Z','%f'%pointB.getZ())
+            self.collisionDict[name].setTag('C_X','%f'%pointC.getX())
+            self.collisionDict[name].setTag('C_Y','%f'%pointC.getY())
+            self.collisionDict[name].setTag('C_Z','%f'%pointC.getZ())
+
+        if self.collisionVisable:
+            self.collisionDict[name].show()
+        #Manakel 2/12/2005: replace node by its nodepath
+        base.cTrav.addCollider( self.collisionDict[name], self.CollisionHandler)
+
+        messenger.send('SGE_Update Explorer',[render])
+        
+        return
+
+    def toggleCollisionVisable(self, visable):
+        ###########################################################################
+        # toggleCollisionVisable(self, visable)
+        # This fucntion will toggle the visibility of all collision node in the scene.
+        ###########################################################################
+        if visable == 1:
+            self.collisionVisable = True
+            for name in self.collisionDict:
+                if self.collisionDict[name].isHidden():
+                    self.collisionDict[name].show()
+        else:
+            self.collisionVisable = False
+            for name in self.collisionDict:
+                if not self.collisionDict[name].isHidden():
+                    self.collisionDict[name].hide()
+
+    def toggleParticleVisable(self, visable):
+        if not visable:
+            for name in self.particleNodes:
+                self.particleNodes[name].setTransparency(True)
+                self.particleNodes[name].setAlphaScale(0)
+                self.particleNodes[name].setBin("fixed", 1)
+        else:
+            for name in self.particleNodes:
+                self.particleNodes[name].setTransparency(False)
+                self.particleNodes[name].setAlphaScale(1)
+                self.particleNodes[name].setBin("default", 1)
+        return
+
+    def getBlendAnimAsDict(self, name):
+        ###########################################################################
+        # getBlendAnimAsDict(self, name)
+        # This function will return a dictionry
+        # which contains user blended animation data for actor named "name."
+        # The formate of thsi dictionary is
+        # {"name of Blend Animation" : ["Animation A, Animation B, Effect(Float, 0~1)"]}
+        ###########################################################################
+        if self.blendAnimDict.has_key(name):
+            return self.blendAnimDict[name]
+        else:
+            return {}
+
+    def saveBlendAnim(self, actorName, blendName, animNameA, animNameB, effect):
+        ###########################################################################
+        # saveBlendAnim(self, actorName, blendName, animNameA, animNameB, effect)
+        # This function will save the blended Animation "blendname" for actor "actorNane"
+        # and keep the data in the blendAnimDict.
+        #
+        # Also, if this blend is the first blend animation that the target actor has,
+        # this function will add a "Blending" tag on this actor which is "True".
+        ###########################################################################
+        if self.blendAnimDict.has_key(actorName):
+            if self.blendAnimDict[actorName].has_key(blendName):
+                ### replace the original setting
+                self.blendAnimDict[actorName][blendName][0] = animNameA
+                self.blendAnimDict[actorName][blendName][1] = animNameB
+                self.blendAnimDict[actorName][blendName][2] = effect
+            else:
+                ### create new blend animation in the dictionary
+                self.blendAnimDict[actorName][blendName] = [animNameA, animNameB, effect]
+        else:
+            self.getActor(actorName).setTag('Blending','True')
+            self.blendAnimDict[actorName] = {blendName:[animNameA, animNameB, effect]}
+        return self.blendAnimDict[actorName]
+
+    def renameBlendAnim(self, actorName, nName, oName, animNameA, animNameB, effect):
+        ###########################################################################
+        # renameBlendAnim(self, actorName, nName, oName, animNameA, animNameB, effect)
+        # This function is used to rename a exist blended animation named "oName" to "nName."
+        # The way it doing this is first remove the original blend fomr the actor
+        # and then re-create a new one named "nName" in.
+        # Because it is not just simply rename the animation,
+        # it will also rewrite the data to the newest one.
+        ###########################################################################
+        self.removeBlendAnim(actorName,oName)
+        print self.blendAnimDict
+        return self.saveBlendAnim(actorName, nName, animNameA, animNameB, effect)
+
+    def removeBlendAnim(self, actorName, blendName):
+        ###########################################################################
+        # removeBlendAnim(self, actorName, blendName)
+        # This fucntion will remove the record of blended animation named "blendName"
+        # from the actor named "actorName".
+        #
+        # Also, it will check that there is any blended animation remained for this actor,
+        # If none, this function will clear the "Blending" tag of this object.
+        ###########################################################################
+        if self.blendAnimDict.has_key(actorName):
+            if self.blendAnimDict[actorName].has_key(blendName):
+                ### replace the original setting
+                del self.blendAnimDict[actorName][blendName]
+            if len(self.blendAnimDict[actorName])==0:
+                del self.blendAnimDict[actorName]
+                self.getActor(actorName).clearTag('Blending')
+                return {}
+            return self.blendAnimDict[actorName]
+        else:
+            return {}
+
+    def getAllObjNameAsList(self):
+        ###########################################################################
+        # getAllObjNameAsList(self)
+        # This function will return a list which contains all objects' names in the scene.
+        # It means which won't have any kinds of animation, blend animation or Mopath data inside.
+        ###########################################################################
+        list = ['camera'] # Default object you can select camera
+        list = list + self.ModelDic.keys() \
+               + self.ActorDic.keys() + self.collisionDict.keys() \
+               + self.dummyDict.keys() + self.particleNodes.keys() \
+               + self.lightManager.getLightList()
+        return list
+
+    def getObjFromSceneByName(self, name):
+        ###########################################################################
+        # getObjFromSceneByName(self, name)
+        # return a reference to the nodePath named "name"
+        ###########################################################################
+        if name == 'camera':
+            return camera
+        elif self.ModelDic.has_key(name):
+            return self.ModelDic[name]
+        elif self.ActorDic.has_key(name):
+            return self.ActorDic[name]
+        elif self.collisionDict.has_key(name):
+            return self.collisionDict[name]
+        elif self.dummyDict.has_key(name):
+            return self.dummyDict[name]
+        elif self.particleNodes.has_key(name):
+            return self.particleNodes[name]
+        elif self.lightManager.isLight(name):
+            return self.lightManager.getLightNode(name)
+        
+        return None
+
+    def getControlSetting(self):
+        ###########################################################################
+        # getControlSetting(self)
+        # return tqwo things.
+        # One is the type of the control. The other is the data about that control.
+        # Now we only support keyboard control, so it will return a list.
+        # The first object in the list is a reference to the target we want to control.
+        # The second object in the list is a dictionary which contains a map about
+        # which keyboard message should be accepted.
+        # The third and the last object here is a dictionary which contains the data
+        # indicating that the changing value for each keyboard control event.
+        ###########################################################################
+        if self.controlType == 'Keyboard':
+            return self.controlType, [self.controlTarget, self.keyboardMapDict.copy(), self.keyboardSpeedDict.copy()]
+        elif self.controlType == 'Tracker':
+            return self.controlType, []
+        return
+
+    def saveControlSetting(self, controlType, data):
+        ###########################################################################
+        # saveControlSetting(self, controlType, data)
+        # copy the current control setting into dataHolder
+        # Mainly called by sceneEditor.
+        ###########################################################################
+        if controlType == 'Keyboard':
+            self.controlType = controlType
+            self.controlTarget = data[0]
+            self.keyboardMapDict.clear()
+            self.keyboardMapDict = data[1].copy()
+            self.keyboardSpeedDict.clear()
+            self.keyboardSpeedDict = data[2].copy()
+            return
+  
+    def loadScene(self):
+        ###########################################################################
+        # loadScene(self)
+        # Opens a dialog box asking for a scene file to load.  It then removes
+        # the current scene and opens the new one.
+        # It basically proceeds by executig the python file containing the scene
+        # and then re-populating the various dictionaries based on the dictionaries
+        # in the scene file hence reviving the state for the scene
+        ###########################################################################
+
+        ### Ask for a filename  
+        OpenFilename = tkFileDialog.askopenfilename(filetypes = [("PY","py")],title = "Load Scene")
+        if(not OpenFilename):   
+            return None
+        f=Filename.fromOsSpecific(OpenFilename)
+        fileName=f.getBasenameWoExtension()
+        dirName=f.getFullpathWoExtension()
+        print "DATAHOLDER::" + dirName
+        ############################################################################
+        # Append the path to this file to our sys path where python looks for modules
+        # We do this so that we can use "import"  on our saved scene code and execute it
+        ############################################################################
+        sys.path.append(os.path.dirname(f.toOsSpecific()))
+
+        ############################################################################
+        # Actually import the scene... this executes the code in the scene
+        ############################################################################
+        self.theScene=__import__(fileName)
+        self.Scene=self.theScene.SavedScene(0,seParticleEffect,seParticles,dirName) # Specify load mode of 0 which will allow us to pass seParticle and seParticleEffect
+        messenger.send('SGE_Update Explorer',[render])    
+
+
+        # Lets call some important initialization methods on our scene:
+        #self.Scene.starteffects(0,seParticleEffect,seParticles,dirName)    # This special calling of start effect with mode 0 is to use seParticleEffect and seParticles
+
+
+        ############################################################################
+        # Populate Model related Dictionaries
+        ############################################################################
+        for model in self.Scene.ModelDic:
+            self.ModelDic[model]=self.Scene.ModelDic[model]
+            #self.ModelRefDic[model]=self.Scene.ModelRefDic[model] # The Old absolute paths way
+            self.ModelRefDic[model]=Filename(dirName + "/" + self.Scene.ModelRefDic[model]) # Relative Paths
+            self.ModelNum=self.ModelNum+1
+
+        ############################################################################
+        # Populate Actor related Dictionaries
+        ############################################################################
+        for actor in self.Scene.ActorDic:
+            self.ActorDic[actor]=self.Scene.ActorDic[actor]
+            #self.ActorRefDic[actor]=self.Scene.ActorRefDic[actor] # Old way of doing absolute paths
+            self.ActorRefDic[actor]=Filename(dirName + "/" + self.Scene.ActorRefDic[actor]) # Relative Paths
+            if(self.Scene.blendAnimDict.has_key(str(actor))):
+                self.blendAnimDict[actor]=self.Scene.blendAnimDict[actor]
+            self.ActorNum=self.ActorNum+1
+
+
+
+        ############################################################################
+        # Populate Light related Dictionaries
+        ############################################################################
+        #print self.Scene.LightDict
+        for light in self.Scene.LightDict:
+            #print light
+            alight=self.Scene.LightDict[light]
+            type=self.Scene.LightTypes[light]   
+            thenode=self.Scene.LightNodes[light]
+            #print type
+            if type == 'ambient':
+                self.lightManager.create('ambient',alight.getColor(),name=alight.getName(),tag=thenode.getTag("Metadata"))
+            elif type == 'directional':
+                #print alight.getPoint()
+                #print alight.getDirection()
+                self.lightManager.create('directional',alight.getColor(),alight.getSpecularColor(),thenode.getPos(),thenode.getHpr(),name=alight.getName(),tag=thenode.getTag("Metadata"))
+            elif type == 'point':
+                atten=alight.getAttenuation()
+                #print alight.getPoint()
+                self.lightManager.create('point',alight.getColor(),alight.getSpecularColor(),thenode.getPos(),Vec3(1,0,0),atten.getX(),atten.getY(),atten.getZ(),name=alight.getName(),tag=thenode.getTag("Metadata"))
+            elif type == 'spot':
+                atten=alight.getAttenuation()
+                self.lightManager.create('spot',alight.getColor(),alight.getSpecularColor(),thenode.getPos(),thenode.getHpr(),atten.getX(),atten.getY(),atten.getZ(),alight.getExponent(),name=alight.getName(),tag=thenode.getTag("Metadata"))
+            else:
+                print 'Invalid light type'
+
+        ############################################################################
+        # Populate Dummy related Dictionaries
+        ############################################################################
+        for dummy in self.Scene.dummyDict:
+            self.dummyDict[dummy] = self.Scene.dummyDict[dummy]
+
+        ############################################################################
+        # Populate Collision related Dictionaries
+        ############################################################################
+        for collnode in self.Scene.collisionDict:
+            self.collisionDict[collnode]=self.Scene.collisionDict[collnode]
+
+        ############################################################################
+        # Populate Mopath related Dictionaries
+        ############################################################################
+        for node in self.Scene.curveDict:
+            curveCollection=self.Scene.curveDict[node]
+            for curve in curveCollection:
+                curveColl=ParametricCurveCollection()
+                nodeP=loader.loadModel(curve)
+                curveColl.addCurves(nodeP.node())
+                nodeP.removeNode()
+                thenode=render.find("**/"+str(node))
+                self.bindCurveToNode(thenode,curveColl)
+        
+        ############################################################################
+        # Populate Particle related Dictionaries
+        ############################################################################
+        for effect in self.Scene.particleDict:
+            theeffect=self.Scene.particleDict[effect]
+            emitter=loader.loadModel("sphere")
+            emitter.setPosHprScale(theeffect.getX(),theeffect.getY(),theeffect.getZ(),theeffect.getH(),theeffect.getP(),theeffect.getR(),theeffect.getSx(),theeffect.getSy(),theeffect.getSz())
+            theeffect.setPos(0,0,0)
+            theeffect.setName(str(effect))
+            tempparent=theeffect.getParent()
+            theeffect.reparentTo(emitter)
+            emitter.setName(str(effect))
+            emitter.reparentTo(tempparent)
+            theeffect.enable()
+            self.particleDict[effect]=theeffect
+            self.particleNodes[effect]=emitter
+            
+        
+ 
+        # Clean up things added to scene graph by saved file's code execution
+        for light in self.Scene.LightDict:
+            vestige=render.find('**/'+light)
+            if(vestige != None):
+                vestige.removeNode()
+
+        ############################################################################
+        # return the filename and update the scenegraph explorer window
+        ############################################################################
+        messenger.send('SGE_Update Explorer',[render])  
+        if(OpenFilename):
+            return OpenFilename
+        else:
+            return None
+
+    def getList(self):
+        return self.lightManager.getList()     

+ 172 - 0
contrib/src/sceneeditor/duplicateWindow.py

@@ -0,0 +1,172 @@
+#################################################################
+# duplicateWindow.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+from direct.tkwidgets.AppShell import *
+from direct.showbase.TkGlobal import *
+
+import seSceneGraphExplorer
+
+
+class duplicateWindow(AppShell):
+    #################################################################
+    # duplicateWindow(AppShell)
+    # This class is used to create a dialog window
+    # for handling the "dupicate" command from the sceneEditor
+    #
+    # Notice!
+    # The actual duplicating process is not happending here!
+    # The one handle the process is dataHolder...
+    #################################################################
+    appversion      = '1.0'
+    appname         = 'Duplication'
+    frameWidth      = 450
+    frameHeight     = 320
+    frameIniPosX    = 250
+    frameIniPosY    = 250
+    padx            = 0
+    pady            = 0
+
+    
+    def __init__(self, parent = None, nodePath = None, **kw):
+        # Define the megawidget options.
+        optiondefs = (
+            ('title',       self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        if parent == None:
+            self.parent = Toplevel()
+        AppShell.__init__(self, self.parent)
+        self.parent.geometry('%dx%d+%d+%d' % (self.frameWidth, self.frameHeight,self.frameIniPosX,self.frameIniPosY))
+
+        self.nodePath = nodePath
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+        
+    def appInit(self):
+        print '----SideWindow is Initialized!!'
+        
+    def createInterface(self):
+        # The interior of the toplevel panel
+        interior = self.interior()
+        mainFrame = Frame(interior)
+        self.inputZone = Pmw.Group(mainFrame, tag_text='Offset setting')
+        self.inputZone.pack(fill='both',expand=1)
+        settingFrame = self.inputZone.interior()
+        Label(settingFrame,text='  X  ').place(anchor=NW,x=110,y=15)
+        Label(settingFrame,text='  Y  ').place(anchor=NW,x=205,y=15)
+        Label(settingFrame,text='  Z  ').place(anchor=NW,x=295,y=15)
+        self.move_x = Pmw.EntryField(settingFrame,label_text='Move  :',labelpos='w',value='0.0', validate=Pmw.realvalidator)
+        self.move_x.component('entry').config(width=10)
+        self.move_y = Pmw.EntryField(settingFrame,value='0.0', validate=Pmw.realvalidator)
+        self.move_y.component('entry').config(width=10)
+        self.move_z = Pmw.EntryField(settingFrame, value='0.0', validate=Pmw.realvalidator)
+        self.move_z.component('entry').config(width=10)
+        self.move_x.place(anchor=NW,x=50,y=40)
+        self.move_y.place(anchor=NW,x=185,y=40)
+        self.move_z.place(anchor=NW,x=275,y=40)
+
+        self.rotate_x = Pmw.EntryField(settingFrame,label_text='Rotate:',labelpos='w',value='0.0', validate=Pmw.realvalidator)
+        self.rotate_x.component('entry').config(width=10)
+        self.rotate_y = Pmw.EntryField(settingFrame,value='0.0', validate=Pmw.realvalidator)
+        self.rotate_y.component('entry').config(width=10)
+        self.rotate_z = Pmw.EntryField(settingFrame, value='0.0', validate=Pmw.realvalidator)
+        self.rotate_z.component('entry').config(width=10)
+        self.rotate_x.place(anchor=NW,x=50,y=70)
+        self.rotate_y.place(anchor=NW,x=185,y=70)
+        self.rotate_z.place(anchor=NW,x=275,y=70)
+
+        self.scale_x = Pmw.EntryField(settingFrame,label_text='Scale :',labelpos='w',value='1.0', validate=Pmw.realvalidator)
+        self.scale_x.component('entry').config(width=10)
+        self.scale_y = Pmw.EntryField(settingFrame,value='1.0', validate=Pmw.realvalidator)
+        self.scale_y.component('entry').config(width=10)
+        self.scale_z = Pmw.EntryField(settingFrame, value='1.0', validate=Pmw.realvalidator)
+        self.scale_z.component('entry').config(width=10)
+        self.scale_x.place(anchor=NW,x=52,y=100)
+        self.scale_y.place(anchor=NW,x=185,y=100)
+        self.scale_z.place(anchor=NW,x=275,y=100)
+
+        self.numberOfCopy = Pmw.EntryField(settingFrame,label_text='Number of Copy :',labelpos='w',value='1', validate=Pmw.integervalidator)
+        self.numberOfCopy.component('entry').config(width=15)
+        self.numberOfCopy.place(anchor=NW,x=52,y=150)
+        
+        settingFrame.pack(fill=BOTH,expand=1,padx=7,pady=7)
+
+        self.button_ok = Button(mainFrame, text="OK", command=self.ok_press,width=10)
+        self.button_ok.pack(fill=BOTH,expand=0,side=RIGHT)
+        
+        mainFrame.pack(fill = 'both', expand = 1,padx=7,pady=7)
+
+    
+    def createMenuBar(self):
+        self.menuBar.destroy()
+        
+    def onDestroy(self, event):
+        messenger.send('DW_close')
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    ###############################
+    
+    def ok_press(self):
+        #################################################################
+        # ok_press(self)
+        # Callback function
+        # This function will be called when user click on the "OK" button on the window.
+        # After collect all data we need for the duplication process,
+        # this function will send out a message with all data.
+        # 'DW_duplicating'
+        # This message will be caught by sceneEditor.
+        #################################################################
+        if not self.allEntryValid():
+            print '---- Duplication Window: Invalid value!!'
+            return
+        x = self.move_x.getvalue()
+        y = self.move_y.getvalue()
+        z = self.move_z.getvalue()
+        pos=Vec3(FloatType(x),FloatType(y),FloatType(z))
+        x = self.rotate_x.getvalue()
+        y = self.rotate_y.getvalue()
+        z = self.rotate_z.getvalue()
+        hpr=Vec3(FloatType(x),FloatType(y),FloatType(z))
+        x = self.scale_x.getvalue()
+        y = self.scale_y.getvalue()
+        z = self.scale_z.getvalue()
+        scale=Vec3(FloatType(x),FloatType(y),FloatType(z))
+        num = int(self.numberOfCopy.getvalue())
+        messenger.send('DW_duplicating',[self.nodePath,pos,hpr,scale,num])
+        self.quit()
+
+    def allEntryValid(self):
+        #################################################################
+        # allEntryValid(self)
+        # This function is used to check all data in the input entries are valid.
+        #
+        # For example, none of entries contains blank data.
+        #
+        #################################################################
+        if not self.move_x.valid():
+            return False
+        elif not self.move_y.valid():
+            return False
+        elif not self.move_z.valid():
+            return False
+        elif not self.rotate_x.valid():
+            return False
+        elif not self.rotate_y.valid():
+            return False
+        elif not self.rotate_z.valid():
+            return False
+        elif not self.scale_x.valid():
+            return False
+        elif not self.scale_y.valid():
+            return False
+        elif not self.scale_z.valid():
+            return False
+        elif not self.numberOfCopy.valid():
+            return False
+
+        return True

+ 493 - 0
contrib/src/sceneeditor/lightingPanel.py

@@ -0,0 +1,493 @@
+#################################################################
+# lightingPanel.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+# Import Tkinter, Pmw, and the floater code from this directory tree.
+from direct.tkwidgets.AppShell import AppShell
+from seColorEntry import *
+from direct.tkwidgets.VectorWidgets import Vector3Entry
+from direct.tkwidgets.Slider import Slider
+from Tkinter import Frame, Button, Menubutton, Menu
+import string, math, types, Pmw, Tkinter
+from pandac.PandaModules import *
+
+class lightingPanel(AppShell):
+    #################################################################
+    # lightingPanel(AppShell)
+    # This will create a window to let user
+    # create any kinds of lighting into the scene
+    #################################################################
+    # Override class variables
+    appname = 'Lighting Panel'
+    frameWidth  = 400
+    frameHeight = 400
+    currentLight = None
+    
+    def __init__(self, lightList, parent = None, **kw):
+        self.lightList = lightList
+        self.lightColor = [0.3*255,0.3*255,0.3*255]
+        self.type = ''
+        INITOPT = Pmw.INITOPT
+        optiondefs = (
+            ('title',               self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize the superclass
+        AppShell.__init__(self)
+
+        # Execute option callbacks
+        self.initialiseoptions(lightingPanel)
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+
+    def createInterface(self):
+        # Handle to the toplevels interior
+        interior = self.interior()
+        menuBar = self.menuBar
+        self.menuBar.destroy()
+
+        # Create a frame to hold all stuff
+        mainFrame = Frame(interior)
+
+        self.listZone = Pmw.Group(mainFrame,tag_pyclass = None)
+        self.listZone.pack(expand=0, fill=Tkinter.X,padx=3,pady=3)
+        listFrame = self.listZone.interior()
+        
+        self.lightEntry = self.createcomponent(
+            'Lights List', (), None,
+            Pmw.ComboBox, (listFrame,),label_text='Light :',
+            labelpos = Tkinter.W, entry_width = 25, selectioncommand = self.selectLight,
+            scrolledlist_items = self.lightList)
+        self.lightEntry.pack(side=Tkinter.LEFT)
+        
+        self.renameButton = self.createcomponent(
+            'Rename Light', (), None,
+            Button, (listFrame,),
+            text = ' Rename ',
+            command = self.renameLight)
+        self.renameButton.pack(side=Tkinter.LEFT)
+
+        self.addLighZone = Pmw.Group(listFrame,tag_pyclass = None)
+        self.addLighZone.pack(side=Tkinter.LEFT)
+        insideFrame = self.addLighZone.interior()
+        self.lightsButton = Menubutton(insideFrame, text = 'Add light',borderwidth = 3,
+                                       activebackground = '#909090')
+        lightsMenu = Menu(self.lightsButton)
+        lightsMenu.add_command(label = 'Add Ambient Light',
+                            command = self.addAmbient)
+        lightsMenu.add_command(label = 'Add Directional Light',
+                            command = self.addDirectional)
+        lightsMenu.add_command(label = 'Add Point Light',
+                            command = self.addPoint)
+        lightsMenu.add_command(label = 'Add Spotlight',
+                            command = self.addSpot)
+            
+        self.lightsButton.pack(expand=0)
+        self.lightsButton['menu'] = lightsMenu
+
+        self.deleteButton = self.createcomponent(
+            'delete Light', (), None,
+            Button, (listFrame,),
+            text = '  Delete  ',
+            command = self.deleteLight)
+        self.deleteButton.pack(side=Tkinter.LEFT)
+
+        self.lightColor = seColorEntry(
+            mainFrame, text = 'Light Color', value=self.lightColor)
+        self.lightColor['command'] = self.setLightingColorVec
+        self.lightColor['resetValue'] = [0.3*255,0.3*255,0.3*255,0]
+        self.lightColor.pack(fill=Tkinter.X,expand=0)
+        self.bind(self.lightColor, 'Set light color')
+
+        # Notebook pages for light specific controls
+        self.lightNotebook = Pmw.NoteBook(mainFrame, tabpos = None,
+                                          borderwidth = 0)
+        ambientPage = self.lightNotebook.add('Ambient')
+        directionalPage = self.lightNotebook.add('Directional')
+        pointPage = self.lightNotebook.add('Point')
+        spotPage = self.lightNotebook.add('Spot')
+        # Put this here so it isn't called right away
+        self.lightNotebook['raisecommand'] = self.updateLightInfo
+
+        # Directional light controls
+        self.dSpecularColor = seColorEntry(
+            directionalPage, text = 'Specular Color')
+        self.dSpecularColor['command'] = self.setSpecularColor
+        self.dSpecularColor.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.dSpecularColor,
+                  'Set directional light specular color')
+        self.dPosition = Vector3Entry(
+            directionalPage, text = 'Position')
+        self.dPosition['command'] = self.setPosition
+        self.dPosition['resetValue'] = [0,0,0,0]
+        self.dPosition.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.dPosition, 'Set directional light position')
+        self.dOrientation = Vector3Entry(
+            directionalPage, text = 'Orientation')
+        self.dOrientation['command'] = self.setOrientation
+        self.dOrientation['resetValue'] = [0,0,0,0]
+        self.dOrientation.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.dOrientation, 'Set directional light orientation')
+
+        # Point light controls
+        self.pSpecularColor = seColorEntry(
+            pointPage, text = 'Specular Color')
+        self.pSpecularColor['command'] = self.setSpecularColor
+        self.pSpecularColor.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.pSpecularColor,
+                  'Set point light specular color')
+
+        self.pPosition = Vector3Entry(
+            pointPage, text = 'Position')
+        self.pPosition['command'] = self.setPosition
+        self.pPosition['resetValue'] = [0,0,0,0]
+        self.pPosition.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.pPosition, 'Set point light position')
+
+        self.pConstantAttenuation = Slider(
+            pointPage,
+            text = 'Constant Attenuation',
+            max = 1.0,
+            resolution = 0.01,
+            value = 1.0)
+        self.pConstantAttenuation['command'] = self.setConstantAttenuation
+        self.pConstantAttenuation.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.pConstantAttenuation,
+                  'Set point light constant attenuation')
+           
+        self.pLinearAttenuation = Slider(
+            pointPage,
+            text = 'Linear Attenuation',
+            max = 1.0,
+            resolution = 0.01,
+            value = 0.0)
+        self.pLinearAttenuation['command'] = self.setLinearAttenuation
+        self.pLinearAttenuation.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.pLinearAttenuation,
+                  'Set point light linear attenuation')
+           
+        self.pQuadraticAttenuation = Slider(
+            pointPage,
+            text = 'Quadratic Attenuation',
+            max = 1.0,
+            resolution = 0.01,
+            value = 0.0)
+        self.pQuadraticAttenuation['command'] = self.setQuadraticAttenuation
+        self.pQuadraticAttenuation.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.pQuadraticAttenuation,
+                  'Set point light quadratic attenuation')
+           
+        # Spot light controls
+        self.sSpecularColor = seColorEntry(
+            spotPage, text = 'Specular Color')
+        self.sSpecularColor['command'] = self.setSpecularColor
+        self.sSpecularColor.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.sSpecularColor,
+                  'Set spot light specular color')
+
+        self.sConstantAttenuation = Slider(
+            spotPage,
+            text = 'Constant Attenuation',
+            max = 1.0,
+            resolution = 0.01,
+            value = 1.0)
+        self.sConstantAttenuation['command'] = self.setConstantAttenuation
+        self.sConstantAttenuation.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.sConstantAttenuation,
+                  'Set spot light constant attenuation')
+           
+        self.sLinearAttenuation = Slider(
+            spotPage,
+            text = 'Linear Attenuation',
+            max = 1.0,
+            resolution = 0.01,
+            value = 0.0)
+        self.sLinearAttenuation['command'] = self.setLinearAttenuation
+        self.sLinearAttenuation.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.sLinearAttenuation,
+                  'Set spot light linear attenuation')
+           
+        self.sQuadraticAttenuation = Slider(
+            spotPage,
+            text = 'Quadratic Attenuation',
+            max = 1.0,
+            resolution = 0.01,
+            value = 0.0)
+        self.sQuadraticAttenuation['command'] = self.setQuadraticAttenuation
+        self.sQuadraticAttenuation.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.sQuadraticAttenuation,
+                  'Set spot light quadratic attenuation')
+           
+        self.sExponent = Slider(
+            spotPage,
+            text = 'Exponent',
+            max = 1.0,
+            resolution = 0.01,
+            value = 0.0)
+        self.sExponent['command'] = self.setExponent
+        self.sExponent.pack(fill = Tkinter.X, expand = 0)
+        self.bind(self.sExponent,
+                  'Set spot light exponent')
+
+        # MRM: Add frustum controls
+           
+        self.lightNotebook.setnaturalsize()
+        self.lightNotebook.pack(expand = 1, fill = Tkinter.BOTH)
+
+        mainFrame.pack(expand=1, fill = Tkinter.BOTH)
+
+    def onDestroy(self, event):
+        messenger.send('LP_close')
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    def renameLight(self):
+        #################################################################
+        # renameLight(self)
+        # Call Back function
+        # This function will be called when user push
+        # the "Rename" button on the panel.
+        #
+        # Then, this function will collect data and send out them with a message
+        # "LP_rename"
+        # Which will be caught by sceneEditor and pass to dataHolder to
+        # complete the renaming.
+        #
+        #################################################################
+        oName = self.currentLight
+        nName = self.lightEntry.get()
+        messenger.send('LP_rename',[oName,nName])
+        return
+
+    def deleteLight(self):
+        #################################################################
+        # deleteLight(self)
+        # Call Back Function.
+        # This function will be called when user click on
+        # the "Delete" button on the panel.
+        #
+        # Then, this function will send out a message with current seleted light
+        # "LP_removeLight"
+        # Which will be caught by sceneEditor and pass to dataHolder to
+        # complete the delete process.
+        #
+        #################################################################
+        messenger.send('LP_removeLight',[self.currentLight])
+        return
+
+    def updateList(self, list, node=None):
+        #################################################################
+        # updataList(self, list, node = None)
+        # This function will take a list object which contains names of lights in the scene.
+        # Also, if user has put node as a parameter,
+        # this function will automatically select that node as the current working target.
+        #################################################################
+        self.lightList = list
+        self.lightEntry.setlist(list)
+        if node!=None:
+            self.lightEntry.selectitem(index=node.getName(), setentry=True )
+            self.updateDisplay(node)
+        elif len(list)>0:
+            self.lightEntry.selectitem(index=0, setentry=True )
+            self.selectLight(list[0])
+        else:
+            self.lightEntry.clear()
+        return
+
+    def selectLight(self, lightName):
+        #################################################################
+        # selectLight(self, lightName)
+        # This function will be called each time when
+        # user select a light from the list on the panel.
+        # Then, this function will send out the message,
+        # 'LP_selectLight' to sceneEditorand get the current light information from dataHolder.
+        #################################################################
+        if lightName in self.lightList:
+            messenger.send('LP_selectLight',[lightName])
+        return
+
+    def updateDisplay(self, lightNode):
+        #################################################################
+        # updateDisplay(self, lightNode)
+        # This function will update the information showing on the panel.
+        # For example, give a lightNode which is a Point Light.
+        # This function will switch the page to specify the type.
+        # Also, new node is the same type with the previous one,
+        # then this is function won't do the page switching,
+        # but will call other function to refresh the data to target node.
+        #################################################################
+        self.currentLight = lightNode
+        if self.currentLight != None:
+            color = lightNode.getLightColor()
+            self.lightColor.set([255*color.getX(),255*color.getY(),255*color.getZ()])
+            oldType = self.type
+            self.type = lightNode.getType()
+        else:
+            self.lightColor.set([255*0.3,255*0.3,255*0.3])
+            oldType = self.type
+            self.type = 'ambient'
+            
+        if self.type=='ambient':
+            self.lightNotebook.selectpage('Ambient')
+        elif self.type =='directional':
+            self.lightNotebook.selectpage('Directional')
+        elif self.type =='point':
+            self.lightNotebook.selectpage('Point')
+        elif self.type =='spot':
+            self.lightNotebook.selectpage('Spot')
+        if oldType == self.type:
+            # The same type with previous one, call updateLightInfo to refresh the values.
+            self.updateLightInfo()
+        return
+
+    def updateLightInfo(self, page=None):
+        #################################################################
+        # updateLightInfo(self, page=None)
+        # This function will refresh the data we user have done any selection.
+        #################################################################
+        if self.currentLight != None:
+            light = self.currentLight.getLight()
+        if self.type != 'ambient':
+            specColor = light.getSpecularColor()
+        if self.type =='directional':
+            point = self.currentLight.getPosition()
+            dir = self.currentLight.getOrientation()
+            self.dSpecularColor.set([specColor.getX()*255,specColor.getY()*255,specColor.getZ()*255])
+            self.dPosition.set([point.getX(),point.getY(),point.getZ()])
+            self.dOrientation.set([dir.getX(),dir.getY(),dir.getZ()])
+        elif self.type =='point':
+            point = self.currentLight.getPosition()
+            attenuation = light.getAttenuation()
+            self.pSpecularColor.set([specColor.getX()*255,specColor.getY()*255,specColor.getZ()*255])
+            self.pPosition.set([point.getX(),point.getY(),point.getZ()])
+            self.pConstantAttenuation.set(attenuation.getX())
+            self.pLinearAttenuation.set(attenuation.getY())
+            self.pQuadraticAttenuation.set(attenuation.getZ())
+        elif self.type =='spot':
+            attenuation = light.getAttenuation()
+            expo = light.getExponent()
+            self.sSpecularColor.set([specColor.getX()*255,specColor.getY()*255,specColor.getZ()*255])
+            self.sConstantAttenuation.set(attenuation.getX())
+            self.sLinearAttenuation.set(attenuation.getY())
+            self.sQuadraticAttenuation.set(attenuation.getZ())
+            self.sExponent.set(expo)
+        return
+
+    def addAmbient(self):
+        #################################################################
+        # addAmbient(self)
+        # This function will send out a message to
+        # ask dataHolder to create a default ambient light
+        #################################################################
+        messenger.send('LP_addLight',['ambient'])
+        return
+    
+    def addDirectional(self):
+        #################################################################
+        # addDirectional(self)
+        # This function will send out a message to
+        # sk dataHolder to create a default Directional light
+        #################################################################
+        messenger.send('LP_addLight',['directional'])
+        return
+    
+    def addPoint(self):
+        #################################################################
+        # addPoint(self)
+        # This function will send out a message to
+        # ask dataHolder to create a default Point light
+        #################################################################
+        messenger.send('LP_addLight',['point'])
+        return
+    
+    def addSpot(self):
+        #################################################################
+        # addSpot(self)
+        # This function will send out a message to
+        # ask dataHolder to create a default Spot light
+        #################################################################
+        messenger.send('LP_addLight',['spot'])
+        return
+
+    def setLightingColorVec(self,color):
+        #################################################################
+        # setLightingColorVec(self,color)
+        # Call Back function. This will be called
+        # when user try to change the color of light.
+        #################################################################
+        if self.currentLight==None:
+            return
+        self.currentLight.setColor(VBase4((color[0]/255),(color[1]/255),(color[2]/255),1))
+        return
+
+    def setSpecularColor(self,color):
+        #################################################################
+        # setSpecularColor(self,color)
+        # Call Back function. This will be called
+        # when user try to change the Specular color of light.
+        #################################################################
+        if self.currentLight==None:
+            return
+        self.currentLight.setSpecColor(VBase4((color[0]/255),(color[1]/255),(color[2]/255),1))
+        return
+
+    def setPosition(self,position):
+        #################################################################
+        # setPosition(self,position)
+        # Call Back function. This will be called
+        # when user try to change the position of light.
+        #################################################################
+        if self.currentLight==None:
+            return
+        self.currentLight.setPosition(Point3(position[0],position[1],position[2]))
+        return
+
+    def setOrientation(self, orient):
+        #################################################################
+        # setOrientation(self, orient)
+        # Call Back function. This will be called
+        # when user try to change the orientation of light.
+        #################################################################
+        if self.currentLight==None:
+            return
+        self.currentLight.setOrientation(Vec3(orient[0],orient[1],orient[2]))
+        return
+    
+    def setConstantAttenuation(self, value):
+        #################################################################
+        # setConstantAttenuation(self, value)
+        # Call Back function. This will be called
+        # when user try to change the Constant Attenuation of light.
+        #################################################################
+        self.currentLight.setConstantAttenuation(value)
+        return
+    
+    def setLinearAttenuation(self, value):
+        #################################################################
+        # setLinearAttenuation(self, value)
+        # Call Back function. This will be called
+        # when user try to change the Linear Attenuation of light.
+        #################################################################
+        self.currentLight.setLinearAttenuation(value)
+        return
+    
+    def setQuadraticAttenuation(self, value):
+        #################################################################
+        # setQuadraticAttenuation(self, value)
+        # Call Back function. This will be called
+        # when user try to change the Quadratic Attenuation of light.
+        #################################################################
+        self.currentLight.setQuadraticAttenuation(value)
+        return
+    
+    def setExponent(self, value):
+        #################################################################
+        # setExponent(self, value)
+        # Call Back function. This will be called
+        # when user try to change Exponent value of light.
+        #################################################################
+        self.currentLight.setExponent(value)
+        return

+ 1456 - 0
contrib/src/sceneeditor/propertyWindow.py

@@ -0,0 +1,1456 @@
+#################################################################
+# propertyWindow.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+from direct.tkwidgets.AppShell import *
+from direct.showbase.TkGlobal import *
+
+from seColorEntry import *
+
+from direct.tkwidgets import Floater
+from direct.tkwidgets import Dial
+from direct.tkwidgets import Slider
+from direct.tkwidgets import VectorWidgets
+from pandac.PandaModules import *
+from Tkinter import *
+import Pmw
+
+class propertyWindow(AppShell,Pmw.MegaWidget):
+    #################################################################
+    # propertyWindow(AppShell,Pmw.MegaWidget)
+    # This class will create a widow to show the object property and
+    # let user can change shoe of them.
+    #################################################################
+    appversion      = '1.0'
+    appname         = 'Property Window'
+    frameWidth      = 400
+    frameHeight     = 400
+    padx            = 0
+    pady            = 0
+    usecommandarea  = 0
+    usestatusarea   = 0
+    widgetsDict = {}
+
+    
+    def __init__(self, target, type, info, parent = None, nodePath = render, **kw):
+        self.nodePath = target
+        self.name = target.getName()
+        self.type = type
+        self.info = info
+        
+
+        # Initialise superclass
+        Pmw.MegaWidget.__init__(self, parent)
+        
+        # Define the megawidget options.
+        optiondefs = (
+            ('title',       self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        if parent == None:
+            self.parent = Toplevel()
+        AppShell.__init__(self, self.parent)
+        
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+        
+    def appInit(self):
+        return
+        
+    def createInterface(self):
+        # The interior of the toplevel panel
+        interior = self.interior()
+        mainFrame = Frame(interior)
+        name_label = Label(mainFrame, text= self.name,font=('MSSansSerif', 15),
+                           relief = RIDGE, borderwidth=5)
+        name_label.pack()
+        outFrame = Frame(mainFrame, relief = RIDGE, borderwidth=3)
+        self.contentWidge = self.createcomponent(
+            'scrolledFrame',
+            (), None,
+            Pmw.ScrolledFrame, (outFrame,),
+            hull_width = 200, hull_height = 300,
+            usehullsize = 1)
+        self.contentFrame = self.contentWidge.component('frame')
+        self.contentWidge.pack(fill = 'both', expand = 1,padx = 3, pady = 5)
+        outFrame.pack(fill = 'both', expand = 1)
+
+        # Creating different interface depands on object's type
+        if self.type == 'camera':
+            self.cameraInterface(self.contentFrame)
+            self.accept('forPorpertyWindow'+self.name, self.trackDataFromSceneCamera)
+        elif self.type == 'Model':
+            self.modelInterface(self.contentFrame)
+            self.accept('forPorpertyWindow'+self.name, self.trackDataFromSceneModel)
+        elif self.type == 'Actor':
+            self.modelInterface(self.contentFrame)
+            self.actorInterface(self.contentFrame)
+            self.accept('forPorpertyWindow'+self.name, self.trackDataFromSceneActor)
+            pass
+        elif self.type == 'Light':
+            self.lightInterface(self.contentFrame)
+            self.accept('forPorpertyWindow'+self.name, self.trackDataFromSceneLight)
+            pass
+        elif self.type == 'dummy':
+            self.dummyInterface(self.contentFrame)
+            self.accept('forPorpertyWindow'+self.name, self.trackDataFromSceneDummy)
+            pass
+        elif self.type == 'collisionNode':
+            self.collisionInterface(self.contentFrame)
+            self.accept('forPorpertyWindow'+self.name, self.trackDataFromSceneCollision)
+            pass
+        elif self.type == 'Special':
+            # If user try to open the property window for node "SEditor"
+            # It will show the grid property.
+            self.gridInterface(self.contentFrame)
+            self.accept('forPorpertyWindow'+self.name, None)
+            pass
+
+        self.curveFrame = None
+        #### If nodePath has been binded with any curves
+        if self.info.has_key('curveList'):
+            self.createCurveFrame(self.contentFrame)
+       
+        ## Set all stuff done
+        mainFrame.pack(fill = 'both', expand = 1)
+
+    
+    def createMenuBar(self):
+        # we don't need menu bar here.
+        self.menuBar.destroy()
+        
+    def onDestroy(self, event):
+        self.ignore('forPorpertyWindow'+self.name)
+        messenger.send('PW_close', [self.name])
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    def createEntryField(self, parent,text, value,
+                         command, initialState, labelWidth = 12,
+                         side = 'left', fill = X, expand = 0,
+                         validate = None,
+                         defaultButton = False, buttonText = 'Default',defaultFunction = None ):
+        #################################################################
+        # createEntryField(self, parent,text, value,
+        #                  command, initialState, labelWidth = 12,
+        #                  side = 'left', fill = X, expand = 0,
+        #                  validate = None,
+        #                  defaultButton = False, buttonText = 'Default',defaultFunction = None ):
+        # This function will create a Entry on the frame "parent"
+        # Also, if user has enabled the "defaultButton," it will create a button right after the entry.
+        #################################################################
+        frame = Frame(parent)
+        widget = Pmw.EntryField(frame, labelpos='w', label_text = text,
+                                value = value, entry_font=('MSSansSerif', 10),label_font=('MSSansSerif', 10),
+                                modifiedcommand=command, validate = validate,
+                                label_width = labelWidth)
+        widget.configure(entry_state = initialState)
+        widget.pack(side=LEFT)
+        self.widgetsDict[text] = widget
+        if defaultButton and (defaultFunction!=None):
+            # create a button if they need.
+            widget = Button(frame, text=buttonText, font=('MSSansSerif', 10), command = defaultFunction)
+            widget.pack(side=LEFT, padx=3)
+            self.widgetsDict[text+'-'+'DefaultButton']=widget
+            
+        frame.pack(side = side, fill = fill, expand = expand,pady=3)
+
+
+    def createPosEntry(self, contentFrame):
+        #################################################################
+        # createPosEntry(self, contentFrame)
+        # This function will create three entries for setting position for the objects.
+        # the entry type is Floater.
+        # And, it will set the call back function to setNodePathPosHprScale()
+        #################################################################
+        posInterior = Frame(contentFrame)
+        self.posX = self.createcomponent('posX', (), None,
+                                         Floater.Floater, (posInterior,),
+                                         text = 'X', relief = FLAT,
+                                         value = self.nodePath.getX(),
+                                         label_foreground = 'Red',
+                                         entry_width = 9)
+        self.posX['commandData'] = ['x']
+        self.posX['command'] = self.setNodePathPosHprScale
+        self.posX.pack(side=LEFT,expand=0,fill=X, padx=1)
+        
+        self.posY = self.createcomponent('posY', (), None,
+                                         Floater.Floater, (posInterior,),
+                                         text = 'Y', relief = FLAT,
+                                         value = self.nodePath.getY(),
+                                         label_foreground = '#00A000',
+                                         entry_width = 9)
+        self.posY['commandData'] = ['y']
+        self.posY['command'] = self.setNodePathPosHprScale
+        self.posY.pack(side=LEFT, expand=0,fill=X, padx=1)
+        
+        self.posZ = self.createcomponent('posZ', (), None,
+                                         Floater.Floater, (posInterior,),
+                                         text = 'Z', relief = FLAT,
+                                         value = self.nodePath.getZ(),
+                                         label_foreground = 'Blue',
+                                         entry_width = 9)
+        self.posZ['commandData'] = ['z']
+        self.posZ['command'] = self.setNodePathPosHprScale
+        self.posZ.pack(side=LEFT, expand=0,fill=X, padx=1)
+        posInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+
+    def createHprEntry(self, contentFrame):
+        #################################################################
+        # createHprEntry(self, contentFrame)
+        # This function will create three entries for setting orientation for the objects.
+        # the entry type is Floater.
+        # And, it will set the call back function to setNodePathPosHprScale()
+        #################################################################
+        hprInterior = Frame(contentFrame)
+        self.hprH = self.createcomponent('hprH', (), None,
+                                         Dial.AngleDial, (hprInterior,),
+                                         style = 'mini',
+                                         text = 'H', value = self.nodePath.getH(),
+                                         relief = FLAT,
+                                         label_foreground = 'blue',
+                                         entry_width = 9)
+        self.hprH['commandData'] = ['h']
+        self.hprH['command'] = self.setNodePathPosHprScale
+        self.hprH.pack(side = LEFT, expand=0,fill=X)
+        
+        self.hprP = self.createcomponent('hprP', (), None,
+                                         Dial.AngleDial, (hprInterior,),
+                                         style = 'mini',
+                                         text = 'P', value = self.nodePath.getP(),
+                                         relief = FLAT,
+                                         label_foreground = 'red',
+                                         entry_width = 9)
+        self.hprP['commandData'] = ['p']
+        self.hprP['command'] = self.setNodePathPosHprScale
+        self.hprP.pack(side = LEFT, expand=0,fill=X)
+        
+        self.hprR = self.createcomponent('hprR', (), None,
+                                         Dial.AngleDial, (hprInterior,),
+                                         style = 'mini',
+                                         text = 'R', value = self.nodePath.getR(),
+                                         relief = FLAT,
+                                         label_foreground = '#00A000',
+                                         entry_width = 9)
+        self.hprR['commandData'] = ['r']
+        self.hprR['command'] = self.setNodePathPosHprScale
+        self.hprR.pack(side = LEFT, expand=0,fill=X)
+
+        hprInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+
+
+    def createScaleEntry(self, contentFrame):
+        #################################################################
+        # createScaleEntry(self, contentFrame)
+        # This function will create three entries for setting scale for the objects.
+        # the entry type is Floater.
+        # And, it will set the call back function to setNodePathPosHprScale()
+        #################################################################
+        scaleInterior = Frame(contentFrame)
+        
+        self.scale = self.createcomponent('scale', (), None,
+                                           Floater.Floater, (scaleInterior,),
+                                           text = 'Scale',
+                                           relief = FLAT,
+                                           min = 0.0001, value = self.nodePath.getScale().getX(),
+                                           resetValue = 1.0,
+                                           label_foreground = 'Blue')
+        self.scale['commandData'] = ['s']
+        self.scale['command'] = self.setNodePathPosHprScale
+        self.scale.pack(side=LEFT,expand=0,fill=X)
+
+        scaleInterior.pack(side=TOP,expand=0,fill=X, padx=3, pady=3)
+
+    def createColorEntry(self, contentFrame):
+        #################################################################
+        # createColorEntry(self, contentFrame)
+        # This function will create three entries for setting color for the objects.
+        # the entry type is Floater.
+        # And, it will set the call back function to setNodeColorVec()
+        #################################################################
+        color = self.nodePath.getColor()
+        print color
+        self.nodeColor = VectorWidgets.ColorEntry(
+            contentFrame, text = 'Node Color', value=[color.getX()*255,
+                                                      color.getY()*255,
+                                                      color.getZ()*255,
+                                                      color.getW()*255])
+        self.nodeColor['command'] = self.setNodeColorVec
+        self.nodeColor['resetValue'] = [255,255,255,255]
+        self.nodeColor.place(anchor=NW,y=235)
+        self.bind(self.nodeColor, 'Set nodePath color')        
+        self.nodeColor.pack(side=TOP,expand=0,fill=X, padx=3, pady=3)
+        return
+    
+    def setNodeColorVec(self, color):
+        #################################################################
+        # setNodeColorVec(self, color)
+        # This function will set the color of the object
+        #################################################################
+        self.nodePath.setColor(color[0]/255.0,
+                               color[1]/255.0,
+                               color[2]/255.0,
+                               color[3]/255.0)
+        return
+
+    
+    def setNodePathPosHprScale(self, data, axis):
+        #################################################################
+        # setNodePathPosHprScale(self, data, axis)
+        # This function will set the postion, orientation or scale of the object
+        # use the "axis" parameter to decide which property should be set.
+        #################################################################
+        if axis == 'x':
+            self.nodePath.setX(data)
+        elif axis == 'y':
+            self.nodePath.setY(data)
+        elif axis == 'z':
+            self.nodePath.setZ(data)
+        elif axis == 'h':
+            self.nodePath.setH(data)
+        elif axis == 'p':
+            self.nodePath.setP(data)
+        elif axis == 'r':
+            self.nodePath.setR(data)
+        elif axis == 's':
+            self.nodePath.setScale(data)
+
+
+    #### Curve property
+    def createCurveFrame(self, contentFrame):
+        #################################################################
+        # createCurveFrame(self, contentFrame)
+        # Draw the curve property frame
+        # This function will draw the property frame and content of curves
+        # pass the target frame as a variable
+        #################################################################
+        if self.curveFrame==None:
+            self.curveFrame = Frame(contentFrame)
+            group = Pmw.Group(self.curveFrame,
+                              tag_text='Motion Path List for this Node',
+                              tag_font=('MSSansSerif', 10))
+            innerFrame = group.interior()
+            n = 0
+            for curve in self.info['curveList']:
+                n += 1
+                self.createEntryField(innerFrame,'Curve %d:' %n,
+                                      value = curve.getCurve(0).getName(),
+                                      command = None,
+                                      initialState='disabled',
+                                      side = 'top',
+                                      defaultButton = True,
+                                      buttonText = 'delete',
+                                      defaultFunction = lambda a = n, b = self : b.deleteCurve(a))
+            group.pack(side = TOP, fill = X, expand = 0,pady=3, padx=3)
+            self.curveFrame.pack(side = TOP, fill = X, expand = 0,pady=3, padx=3)
+            
+        return
+    
+    def deleteCurve(self, number = 0):
+        #################################################################
+        # deleteCurve(self, number = 0)
+        # Call back function, will be called when user click on the "delete" button beside the curve name.
+        # This function will send the message to sceneEditor to remove the target curve
+        # and will set a callback function waitting the result.
+        #################################################################
+        widget = self.widgetsDict['Curve %d:' %number]
+        curveName = widget.getvalue()
+        self.accept('curveRemovedFromNode',self.redrawCurveProperty)
+        messenger.send('PW_removeCurveFromNode',[self.nodePath, curveName])
+        return
+
+    def redrawCurveProperty(self, nodePath, curveList):
+        #################################################################
+        # redrawCurveProperty(self, nodePath, curveList)
+        # Callback function, will be called once get the result from dataHolder.
+        # It will check the target nodePath first, then check the curve list is empty or not.
+        # If yes, then delete whole curve frame. If not, then renew the data and redraw the curve frame again.
+        #################################################################
+        self.name = self.nodePath.getName()
+        if self.name != nodePath.getName():
+            messenger.send('curveRemovedFromNode',[nodePath, curveList])
+            return
+        else:
+            self.ignore('curveRemovedFromNode')
+            
+        if curveList!= None:
+            del self.info['curveList']
+            self.info['curveList'] = curveList
+            self.curveFrame.destroy()
+            del self.curveFrame
+            self.curveFrame = None
+            self.createCurveFrame(self.contentFrame)
+        else:
+            del self.info['curveList']
+            self.curveFrame.destroy()
+            del self.curveFrame
+            self.curveFrame = None
+        return
+
+    ####
+    ####  Anything about Camera will be here!
+    ####
+    def cameraInterface(self, contentFrame):
+        #################################################################
+        # cameraInterface(self, interior, mainFrame)
+        # Create the interface for camera node.
+        #################################################################
+        
+        ## Type entry : unchageable
+        widget = self.createEntryField(contentFrame,'Type:',
+                                       value = self.type,
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top')
+
+        ## lens Type entry
+        widget = self.createEntryField(contentFrame, 'Lens Type:',
+                                       value = self.info['lensType'],
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top')
+
+        ## Pos
+        group = Pmw.Group(contentFrame,tag_text='Position',
+                          tag_font=('MSSansSerif', 10))
+        self.createPosEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+
+        ## Orientation
+        group = Pmw.Group(contentFrame,tag_text='Orientation',
+                          tag_font=('MSSansSerif', 10))
+        self.createHprEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+        
+        ## near entry
+        group = Pmw.Group(contentFrame,tag_text='Lens Property',
+                          tag_font=('MSSansSerif', 10))
+        lensFrame = group.interior()
+        widget = self.createEntryField(lensFrame, 'Near:',value = self.info['near'],
+                                       command = self.setCameraNear,
+                                       initialState='normal',
+                                       validate = Pmw.realvalidator,
+                                       side = 'top',
+                                       defaultButton = True,
+                                       defaultFunction = self.defaultCameraNear)
+
+        ## far entry
+        widget = self.createEntryField(lensFrame, 'Far:',
+                                       value = self.info['far'],
+                                       command = self.setCameraFar,
+                                       initialState='normal',
+                                       side = 'top',
+                                       validate = Pmw.realvalidator,
+                                       defaultButton = True,
+                                       defaultFunction = self.defaultCameraFar)
+
+        ## Hfov entry
+        widget = self.createEntryField(lensFrame, 'H.F.O.V.:',
+                                           value = self.info['hFov'],
+                                           command = self.setCameraFov,
+                                           validate = Pmw.realvalidator,
+                                           initialState='normal',
+                                           side = 'top',
+                                           defaultButton = True,
+                                           defaultFunction = self.defaultCameraHfov)
+
+        ## Vfov entry
+        widget = self.createEntryField(lensFrame, 'V.F.O.V.:',
+                                       value = self.info['vFov'],
+                                       command = self.setCameraFov,
+                                       validate = Pmw.realvalidator,
+                                       initialState='normal',
+                                       side = 'top',
+                                       defaultButton = True,
+                                       defaultFunction = self.defaultCameraVfov)
+
+        ## Film Size entry
+        frame = Frame(lensFrame)
+        widget = Label(frame, text = "Film Size:", font=('MSSansSerif', 10),width=12)
+        widget.pack(side=LEFT)
+        frame.pack(side = TOP, fill = X, expand = 0, pady=3)
+            
+        frame = Frame(lensFrame)
+        widget = Pmw.EntryField(frame, labelpos='w', label_text = '                        ',
+                                value = self.info['FilmSize'].getX(),
+                                entry_font=('MSSansSerif', 10),
+                                label_font=('MSSansSerif', 10),
+                                modifiedcommand=self.setCameraFilmSize, validate = Pmw.realvalidator,
+                                entry_width = 8)
+        self.widgetsDict['FilmSizeX']=widget
+        widget.pack(side=LEFT, padx=3)
+        widget = Pmw.EntryField(frame, labelpos='w', label_text = ': ', value = self.info['FilmSize'].getY() ,
+                                label_font=('MSSansSerif', 10),
+                                entry_font=('MSSansSerif', 10),
+                                modifiedcommand=self.setCameraFilmSize, validate = Pmw.realvalidator,
+                                entry_width = 8)
+        self.widgetsDict['FilmSizeY']=widget
+        widget.pack(side=LEFT, padx=3)
+        widget = Button(frame, text='Default', font=('MSSansSerif', 10), command = self.defaultCameraFilmSize)
+        widget.pack(side=LEFT, padx=3)
+        self.widgetsDict['FilmSize'+'-'+'DefaultButton']=widget            
+        frame.pack(side = TOP, fill = X, expand = 0,pady=0)
+
+        ## Focal Length entry
+        widget = self.createEntryField(lensFrame, 'Focal Length:',
+                                       value = self.info['focalLength'],
+                                       command = self.setCameraFocalLength,
+                                       validate = Pmw.realvalidator,
+                                       initialState='normal',
+                                       side = 'top',
+                                       defaultButton = True,
+                                       defaultFunction = self.defaultCameraFocalLength)
+        group.pack(side = TOP, fill = X, expand = 0,pady=2)
+
+        
+    def defaultCameraFar(self):
+        #################################################################
+        # defaultCameraFar(self)
+        # set the camera "Far" value back to default.
+        #################################################################
+        widget = self.widgetsDict['Far:']
+        widget.setvalue(base.cam.node().getLens().getDefaultFar())
+        return
+
+    def setCameraFar(self):
+        #################################################################
+        # setCameraFar(self)
+        # set the camera "Far" value to what now user has typed in the entry
+        #################################################################
+        if self.widgetsDict['Far:'].getvalue() != '':
+            value = float(self.widgetsDict['Far:'].getvalue())
+        else:
+            value = 0
+        camera.getChild(0).node().getLens().setFar(value)
+        return
+
+    def defaultCameraNear(self):
+        #################################################################
+        # defaultCameraNear(self)
+        # set the camera "Near" value back to default.
+        #################################################################
+        widget = self.widgetsDict['Near:']
+        widget.setvalue(base.cam.node().getLens().getDefaultNear())
+        return
+
+    def setCameraNear(self):
+        #################################################################
+        # setCameraNear(self)
+        # set the camera "Near" value to what now user has typed in the entry
+        #################################################################
+        if self.widgetsDict['Near:'].getvalue() != '':
+            value = float(self.widgetsDict['Near:'].getvalue())
+        else:
+            value = 0
+        camera.getChild(0).node().getLens().setNear(value)
+        return
+
+    def defaultCameraHfov(self):
+        #################################################################
+        # defaultCameraHfov(self)
+        # set the camera "Hfov" value back to default.
+        #################################################################
+        widget = self.widgetsDict['H.F.O.V.:']
+        widget.setvalue(45.0)
+        return
+
+    def setCameraFov(self):
+        #################################################################
+        # setCameraFov(self)
+        # set the camera "Fov" value to what now user has typed in the entry
+        #################################################################
+        if self.widgetsDict['H.F.O.V.:'].getvalue() != '':
+            value1 = float(self.widgetsDict['H.F.O.V.:'].getvalue())
+        else:
+            value1 = 0
+        if self.widgetsDict['V.F.O.V.:'].getvalue() != '':
+            value2 = float(self.widgetsDict['V.F.O.V.:'].getvalue())
+        else:
+            value2 = 0
+        camera.getChild(0).node().getLens().setFov(VBase2(value1,value2))
+        return
+
+    def defaultCameraVfov(self):
+        #################################################################
+        # defaultCameraVfov(self)
+        # set the camera "Vfov" value back to default.
+        #################################################################
+        widget = self.widgetsDict['V.F.O.V.:']
+        widget.setvalue(34.51587677)
+        return
+
+    def defaultCameraFocalLength(self):
+        #################################################################
+        # defaultCameraFocalLength(self)
+        # set the camera "Focal Length" value back to default.
+        #################################################################
+        widget = self.widgetsDict['Focal Length:']
+        widget.setvalue(1.20710682869)
+        return
+
+    def setCameraFocalLength(self):
+        #################################################################
+        # setCameraFocalLength(self)
+        # set the camera "Focal Length" value to what now user has typed in the entry
+        #################################################################
+        if self.widgetsDict['Focal Length:'].getvalue() != '':
+            value = float(self.widgetsDict['Focal Length:'].getvalue())
+        else:
+            value = 0
+        camera.getChild(0).node().getLens().setFocalLength(value)
+        camera.getChild(0).node().getLens().setFilmSize(VBase2(float(self.widgetsDict['FilmSizeX'].getvalue()),float(self.widgetsDict['FilmSizeY'].getvalue())))
+        return
+
+    def defaultCameraFilmSize(self):
+        #################################################################
+        # defaultCameraFilmSize(self)
+        # set the camera "Film Size" value back to default.
+        #################################################################
+        widget = self.widgetsDict['FilmSizeX']
+        widget.setvalue(1)
+        widget = self.widgetsDict['FilmSizeY']
+        widget.setvalue(0.75)
+        return
+
+    def setCameraFilmSize(self):
+        #################################################################
+        # setCameraFilmSize(self)
+        # set the camera "Film Size" value to what now user has typed in the entry
+        #################################################################
+        if self.widgetsDict['FilmSizeX'].getvalue() != '':
+            value1 = float(self.widgetsDict['FilmSizeX'].getvalue())
+        else:
+            value1 = 0
+        if self.widgetsDict['FilmSizeY'].getvalue() != '':
+            value2 = float(self.widgetsDict['FilmSizeY'].getvalue())
+        else:
+            value2 = 0
+        camera.getChild(0).node().getLens().setFilmSize(VBase2(value1,value2))
+        return
+
+    ####
+    ####  Anything about Model & Actor will be here!
+    ####
+    def modelInterface(self, contentFrame):
+        #################################################################
+        # modelInterface(self, contentFrame)
+        # Create the basic interface for ModelRoot Type Node
+        #################################################################
+        widget = self.createEntryField(contentFrame,'Type:',
+                                       value = self.type,
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top')
+        widget = self.createEntryField(contentFrame,'Model File:',
+                                       value = self.info['filePath'].getFullpath(),
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top',
+                                       defaultButton = False,
+                                       buttonText = 'Change',
+                                       defaultFunction = None)
+        group = Pmw.Group(contentFrame,tag_text='Position',
+                          tag_font=('MSSansSerif', 10))
+        self.createPosEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+        
+        group = Pmw.Group(contentFrame,tag_text='Orientation',
+                          tag_font=('MSSansSerif', 10))
+        self.createHprEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+
+        self.createScaleEntry(contentFrame)
+
+        group = Pmw.Group(contentFrame,tag_text='Color',
+                          tag_font=('MSSansSerif', 10))
+        frame = group.interior()
+        self.createColorEntry(frame)
+        self.varAlpha = IntVar()
+        self.varAlpha.set(self.nodePath.hasTransparency())
+        checkButton = Checkbutton(frame, text='Enable Alpha',
+                                  variable=self.varAlpha, command=self.toggleAlpha)
+        checkButton.pack(side=RIGHT,pady=3)
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+        return
+
+    def toggleAlpha(self):
+        #################################################################
+        # toggleAlpha(self)
+        # This funtion will toggle the objects alpha value
+        # And, it will also reset the "Bin" to
+        # "fixed" if user enable the alpha for this object.
+        #################################################################
+        if self.nodePath.hasTransparency():
+            self.nodePath.clearTransparency()
+            self.nodePath.setBin("default", 0)
+        else:
+            self.nodePath.setTransparency(True)
+            self.nodePath.setBin("fixed", 1)
+        return
+    
+    def actorInterface(self, contentFrame):
+        #################################################################
+        # actorInterface(self, contentFrame)
+        # Create the basic interface for Actor Type Node
+        #################################################################
+        self.animFrame = None
+        animeDict = self.info['animDict']
+        if len(animeDict)==0:
+            return
+
+        self.animFrame = Frame(contentFrame)
+        group = Pmw.Group(self.animFrame,tag_text='Animations',
+                                   tag_font=('MSSansSerif', 10))
+        innerFrame = group.interior()
+        for name in animeDict:
+            self.createEntryField(innerFrame, name,
+                                  value = animeDict[name],
+                                  command = None,
+                                  initialState='disabled',
+                                  side = 'top',
+                                  defaultButton = True,
+                                  buttonText = 'Remove',
+                                  defaultFunction = lambda a = name, b = self : b.deleteAnimation(a))
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+        self.animFrame.pack(side=TOP,fill = X, expand = 0, pady=3)
+        return
+
+
+    def deleteAnimation(self, anim):
+        #################################################################
+        # deleteAnimation(self, anim)
+        # This function will delete the animation named "anim" in this actor
+        # But, not directly removed be this function.
+        # This function will send out a message to notice dataHolder to remove this animation
+        #################################################################
+        print anim
+        widget = self.widgetsDict[anim]
+        self.accept('animRemovedFromNode',self.redrawAnimProperty)
+        messenger.send('PW_removeAnimFromNode',[self.name, anim])
+        return
+
+    def redrawAnimProperty(self, nodePath, animDict):
+        #################################################################
+        # redrawCurveProperty(self, nodePath, curveList)
+        # Callback function, will be called once get the result from dataHolder.
+        # It will check the target nodePath first, then check the curve list is empty or not.
+        # If yes, then delete whole curve frame. If not, then renew the data and redraw the curve frame again.
+        #################################################################
+        self.name = self.nodePath.getName()
+        if self.name != nodePath.getName():
+            messenger.send('animRemovedFromNode',[nodePath, animDict])
+            return
+        else:
+            self.ignore('animRemovedFromNode')
+            
+        if len(animDict)!= 0:
+            del self.info['animDict']
+            self.info['animDict'] = animDict
+            self.animFrame.destroy()
+            del self.animFrame
+            self.animFrame = None
+            self.actorInterface(self.contentFrame)
+        else:
+            del self.info['animDict']
+            self.animFrame.destroy()
+            del self.animFrame
+            self.animFrame = None
+        return
+
+    ####
+    ####  Anything about Light will be here!
+    ####
+    def lightInterface(self, contentFrame):
+        #################################################################
+        # lightInterface(self, contentFrame)
+        # Create the basic interface for light Type Node
+        #################################################################
+        widget = self.createEntryField(contentFrame,'Type:',
+                                       value = self.nodePath.node().getType().getName(),
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top')
+
+        self.lightNode = self.info['lightNode']
+
+        lightingGroup = Pmw.Group(contentFrame,tag_pyclass=None)
+        frame = lightingGroup.interior()
+        self.lightColor = seColorEntry(
+            frame, text = 'Light Color', label_font=('MSSansSerif', 10),
+            value=[self.lightNode.lightcolor.getX()*255, self.lightNode.lightcolor.getY()*255,self.lightNode.lightcolor.getZ()*255,0])
+        self.lightColor['command'] = self.setLightingColorVec
+        self.lightColor['resetValue'] = [0.3*255,0.3*255,0.3*255,0]
+        self.lightColor.pack(side=TOP, fill=X,expand=1, padx = 2, pady =2)
+        self.bind(self.lightColor, 'Set light color')
+        
+        self.varActive = IntVar()
+        self.varActive.set(self.lightNode.active)
+        checkButton = Checkbutton(frame, text='Enable This Light',
+                                  variable=self.varActive, command=self.toggleLight)
+        checkButton.pack(side=RIGHT,pady=3)
+        lightingGroup.pack(side=TOP, fill = X, expand =1)
+        
+        # Directional light controls
+        if self.lightNode.type == 'directional':
+            lightingGroup = Pmw.Group(contentFrame,tag_pyclass=None)
+            directionalPage = lightingGroup.interior()
+            self.dSpecularColor = seColorEntry(
+                directionalPage, text = 'Specular Color',  label_font=('MSSansSerif', 10),value = [self.lightNode.specularColor.getX()*255,self.lightNode.specularColor.getY()*255,self.lightNode.specularColor.getZ()*255,0])
+            self.dSpecularColor['command'] = self.setSpecularColor
+            self.dSpecularColor.pack(fill = X, expand = 1)
+            self.bind(self.dSpecularColor,
+                      'Set directional light specular color')
+            self.dPosition = VectorWidgets.Vector3Entry(
+                directionalPage, text = 'Position',  label_font=('MSSansSerif', 10),value = [self.lightNode.getPosition().getX(),self.lightNode.getPosition().getY(),self.lightNode.getPosition().getZ()])
+            self.dPosition['command'] = self.setPosition
+            self.dPosition['resetValue'] = [0,0,0,0]
+            self.dPosition.pack(fill = X, expand = 1)
+            self.bind(self.dPosition, 'Set directional light position')
+            self.dOrientation = VectorWidgets.Vector3Entry(
+                directionalPage, text = 'Orientation', label_font=('MSSansSerif', 10),
+                value = [self.lightNode.getOrientation().getX(),self.lightNode.getOrientation().getY(),self.lightNode.getOrientation().getZ(),0])
+            self.dOrientation['command'] = self.setOrientation
+            self.dOrientation['resetValue'] = [0,0,0,0]
+            self.dOrientation.pack(fill = X, expand = 1)
+            self.bind(self.dOrientation, 'Set directional light orientation')
+            
+            lightingGroup.pack(side=TOP, fill = X, expand =1)
+
+        
+        elif self.lightNode.type == 'point':
+            # Point light controls
+            lightingGroup = Pmw.Group(contentFrame,tag_pyclass=None)
+            pointPage = lightingGroup.interior()
+            self.pSpecularColor = seColorEntry(
+                pointPage, text = 'Specular Color', label_font=('MSSansSerif', 10),
+                value = [self.lightNode.specularColor.getX(),self.lightNode.specularColor.getY(),self.lightNode.specularColor.getZ(),0])
+            self.pSpecularColor['command'] = self.setSpecularColor
+            self.pSpecularColor.pack(fill = X, expand = 1)
+            self.bind(self.pSpecularColor,
+                      'Set point light specular color')
+    
+            self.pPosition = VectorWidgets.Vector3Entry(
+                pointPage, text = 'Position',  label_font=('MSSansSerif', 10),
+                value = [self.lightNode.getPosition().getX(),self.lightNode.getPosition().getY(),self.lightNode.getPosition().getZ(),0])
+            self.pPosition['command'] = self.setPosition
+            self.pPosition['resetValue'] = [0,0,0,0]
+            self.pPosition.pack(fill = X, expand = 1)
+            self.bind(self.pPosition, 'Set point light position')
+
+            self.pConstantAttenuation = Slider.Slider(
+                pointPage,
+                text = 'Constant Attenuation', label_font=('MSSansSerif', 10),
+                max = 1.0,
+                value = self.lightNode.constant)
+            self.pConstantAttenuation['command'] = self.setConstantAttenuation
+            self.pConstantAttenuation.pack(fill = X, expand = 1)
+            self.bind(self.pConstantAttenuation,
+                      'Set point light constant attenuation')
+           
+            self.pLinearAttenuation = Slider.Slider(
+                pointPage,
+                text = 'Linear Attenuation', label_font=('MSSansSerif', 10),
+                max = 1.0,
+                value = self.lightNode.linear)
+            self.pLinearAttenuation['command'] = self.setLinearAttenuation
+            self.pLinearAttenuation.pack(fill = X, expand = 1)
+            self.bind(self.pLinearAttenuation,
+                      'Set point light linear attenuation')
+           
+            self.pQuadraticAttenuation = Slider.Slider(
+                pointPage,
+                text = 'Quadratic Attenuation', label_font=('MSSansSerif', 10),
+                max = 1.0,
+                value = self.lightNode.quadratic)
+            self.pQuadraticAttenuation['command'] = self.setQuadraticAttenuation
+            self.pQuadraticAttenuation.pack(fill = X, expand = 1)
+            self.bind(self.pQuadraticAttenuation,
+                      'Set point light quadratic attenuation')
+
+            lightingGroup.pack(side=TOP, fill = X, expand =1)
+
+           
+        elif self.lightNode.type == 'spot':     
+            # Spot light controls
+            lightingGroup = Pmw.Group(contentFrame,tag_pyclass=None)
+            spotPage = lightingGroup.interior()
+            self.sSpecularColor = seColorEntry(
+                spotPage, text = 'Specular Color', label_font=('MSSansSerif', 10),
+                value = [self.lightNode.specularColor.getX()*255,self.lightNode.specularColor.getY()*255,self.lightNode.specularColor.getZ()*255,0])
+            self.sSpecularColor['command'] = self.setSpecularColor
+            self.sSpecularColor.pack(fill = X, expand = 1)
+            self.bind(self.sSpecularColor,
+                      'Set spot light specular color')
+
+            self.sConstantAttenuation = Slider.Slider(
+                spotPage,
+                text = 'Constant Attenuation', label_font=('MSSansSerif', 10),
+                max = 1.0,
+                value = self.lightNode.constant)
+            self.sConstantAttenuation['command'] = self.setConstantAttenuation
+            self.sConstantAttenuation.pack(fill = X, expand = 1)
+            self.bind(self.sConstantAttenuation,
+                      'Set spot light constant attenuation')
+           
+            self.sLinearAttenuation = Slider.Slider(
+                spotPage,
+                text = 'Linear Attenuation', label_font=('MSSansSerif', 10),
+                max = 1.0,
+                value = self.lightNode.linear)
+            self.sLinearAttenuation['command'] = self.setLinearAttenuation
+            self.sLinearAttenuation.pack(fill = X, expand = 1)
+            self.bind(self.sLinearAttenuation,
+                      'Set spot light linear attenuation')
+           
+            self.sQuadraticAttenuation = Slider.Slider(
+                spotPage,
+                text = 'Quadratic Attenuation', label_font=('MSSansSerif', 10),
+                max = 1.0,
+                value = self.lightNode.quadratic)
+            self.sQuadraticAttenuation['command'] = self.setQuadraticAttenuation
+            self.sQuadraticAttenuation.pack(fill = X, expand = 1)
+            self.bind(self.sQuadraticAttenuation,
+                      'Set spot light quadratic attenuation')
+           
+            self.sExponent = Slider.Slider(
+                spotPage,
+                text = 'Exponent', label_font=('MSSansSerif', 10),
+                max = 1.0,
+                value = self.lightNode.exponent)
+            self.sExponent['command'] = self.setExponent
+            self.sExponent.pack(fill = X, expand = 1)
+            self.bind(self.sExponent,
+                      'Set spot light exponent')
+            lightingGroup.pack(side=TOP, fill = X, expand =1)
+            
+        return
+
+    def setLightingColorVec(self,color):
+        if self.lightNode==None:
+            return
+        self.lightNode.setColor(VBase4((color[0]/255),(color[1]/255),(color[2]/255),1))
+        return
+
+    def setSpecularColor(self,color):
+        if self.lightNode==None:
+            return
+        self.lightNode.setSpecColor(VBase4((color[0]/255),(color[1]/255),(color[2]/255),1))
+        return
+
+    def setPosition(self,position):
+        if self.lightNode==None:
+            return
+        self.lightNode.setPosition(Point3(position[0],position[1],position[2]))
+        return
+
+    def setOrientation(self, orient):
+        if self.lightNode==None:
+            return
+        self.lightNode.setOrientation(Vec3(orient[0],orient[1],orient[2]))
+        return
+    
+    def setConstantAttenuation(self, value):
+        self.lightNode.setConstantAttenuation(value)
+        return
+    
+    def setLinearAttenuation(self, value):
+        self.lightNode.setLinearAttenuation(value)
+        return
+    
+    def setQuadraticAttenuation(self, value):
+        self.lightNode.setQuadraticAttenuation(value)
+        return
+    
+    def setExponent(self, value):
+        self.lightNode.setExponent(value)
+        return
+
+    def toggleLight(self):
+        messenger.send('PW_toggleLight',[self.lightNode])
+        return
+
+
+    ####
+    ####  Anything about Dummy will be here!
+    ####
+    def dummyInterface(self, contentFrame):
+        #################################################################
+        # dummyInterface(self, contentFrame)
+        # Create the basic interface for dummy Type Node
+        #################################################################
+        '''dummyInterface(self, contentFrame)
+        Create the basic interface for dummy Node
+        '''
+        widget = self.createEntryField(contentFrame,'Type:',
+                                       value = 'Dummy Nodepath',
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top')
+        
+        group = Pmw.Group(contentFrame,tag_text='Position',
+                          tag_font=('MSSansSerif', 10))
+        self.createPosEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+        
+        group = Pmw.Group(contentFrame,tag_text='Orientation',
+                          tag_font=('MSSansSerif', 10))
+        self.createHprEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+
+        self.createScaleEntry(contentFrame)
+
+        group = Pmw.Group(contentFrame,tag_text='Color',
+                          tag_font=('MSSansSerif', 10))
+        frame = group.interior()
+        self.createColorEntry(frame)
+        self.varAlpha = IntVar()
+        self.varAlpha.set(self.nodePath.hasTransparency())
+        checkButton = Checkbutton(frame, text='Enable Alpha',
+                                  variable=self.varAlpha, command=self.toggleAlpha)
+        checkButton.pack(side=RIGHT,pady=3)
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+        return
+
+
+    #########
+    #######  This will be called when user try to open property window for SEditor Node
+    #########
+    def gridInterface(self, contentFrame):
+        #################################################################
+        # gridInterface(self, contentFrame)
+        # Create the basic interface for grid (Which is stolen from directGrid)
+        #################################################################
+        group = Pmw.Group(contentFrame,tag_text='Grid Property',
+                          tag_font=('MSSansSerif', 10))
+        group.pack(side=TOP,fill = X, expand = 0, padx = 3, pady=3)
+
+        gridPage = group.interior()
+        
+        self.xyzSnap = BooleanVar()
+        self.xyzSnapButton = Checkbutton(
+            gridPage,
+            text = 'XYZ Snap',
+            anchor = 'w', justify = LEFT,
+            variable = self.xyzSnap,
+            command = self.toggleXyzSnap)
+        self.xyzSnapButton.pack(fill = X, expand = 0, pady=3)
+
+        self.hprSnap = BooleanVar()
+        self.hprSnapButton = Checkbutton(
+            gridPage,
+            text = 'HPR Snap',
+            anchor = 'w', justify = LEFT,
+            variable = self.hprSnap,
+            command = self.toggleHprSnap)
+        self.hprSnapButton.pack(fill = X, expand = 0, pady=3)
+
+        self.xyzSnap.set(SEditor.grid.getXyzSnap())
+        self.hprSnap.set(SEditor.grid.getHprSnap())
+
+        self.gridSpacing = Floater.Floater(
+            gridPage,
+            text = 'Grid Spacing',
+            min = 0.1,
+            value = SEditor.grid.getGridSpacing())
+        self.gridSpacing['command'] = SEditor.grid.setGridSpacing
+        self.gridSpacing.pack(fill = X, expand = 0, pady=3)
+        
+        self.gridSize = Floater.Floater(
+            gridPage,
+            text = 'Grid Size',
+            min = 1.0,
+            value = SEditor.grid.getGridSize())
+        self.gridSize['command'] = SEditor.grid.setGridSize
+        self.gridSize.pack(fill = X, expand = 0, pady=3)
+
+        self.gridSnapAngle = Dial.AngleDial(
+            gridPage,
+            text = 'Snap Angle',
+            style = 'mini',
+            value = SEditor.grid.getSnapAngle())
+        self.gridSnapAngle['command'] = SEditor.grid.setSnapAngle
+        self.gridSnapAngle.pack(fill = X, expand = 0, pady=3)
+        
+        return
+
+    def toggleXyzSnap(self):
+        SEditor.grid.setXyzSnap(self.xyzSnap.get())
+        return
+
+    def toggleHprSnap(self):
+        SEditor.grid.setHprSnap(self.hprSnap.get())
+        return
+
+
+
+    ###### Collision Section!!!!
+    def collisionInterface(self, contentFrame):
+        #################################################################
+        # collisionInterface(self, contentFrame)
+        # Create the basic interface for CollisionNode Type Node
+        #################################################################
+        collisionNode = self.info['collisionNode']
+        self.collisionObj = collisionNode.node().getSolid(0)
+        widget = self.createEntryField(contentFrame,'Node Type:',
+                                       value = self.type,
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top')
+        cType = self.collisionObj.getType().getName()
+        widget = self.createEntryField(contentFrame,'Object Type:',
+                                       value = cType,
+                                       command = None,
+                                       initialState='disabled',
+                                       side = 'top')
+        group = Pmw.Group(contentFrame,tag_text='Position',
+                          tag_font=('MSSansSerif', 10))
+        self.createPosEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+        
+        group = Pmw.Group(contentFrame,tag_text='Orientation',
+                          tag_font=('MSSansSerif', 10))
+        self.createHprEntry(group.interior())
+        group.pack(side=TOP,fill = X, expand = 0, pady=3)
+
+        self.createScaleEntry(contentFrame)
+        
+        collisionGroup = Pmw.Group(contentFrame,tag_text='Collision Object Properties',
+                          tag_font=('MSSansSerif', 10))
+        cObjFrame = collisionGroup.interior()
+
+        ### Generate different Interface for each different kinds of Collision Objects
+        ### Yeah, yeah. I know this part of code looks so ugly...
+        if cType == 'CollisionSphere':
+            centerPos = self.collisionObj.getCenter()
+            radius = self.collisionObj.getRadius()
+            group = Pmw.Group(cObjFrame,tag_text='Origin',
+                              tag_font=('MSSansSerif', 10))
+
+
+            posInterior = Frame(group.interior())
+            self.cPosX = self.createcomponent('originX', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'X', relief = FLAT,
+                                             value = centerPos.getX(),
+                                             label_foreground = 'Red',
+                                             entry_width = 9)
+            self.cPosX['commandData'] = ['sphere-o']
+            self.cPosX['command'] = self.setCollisionPosHprScale
+            self.cPosX.pack(side=LEFT,expand=0,fill=X, padx=1)
+        
+            self.cPosY = self.createcomponent('originY', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Y', relief = FLAT,
+                                             value = centerPos.getY(),
+                                             label_foreground = '#00A000',
+                                             entry_width = 9)
+            self.cPosY['commandData'] = ['sphere-o']
+            self.cPosY['command'] = self.setCollisionPosHprScale
+            self.cPosY.pack(side=LEFT, expand=0,fill=X, padx=1)
+        
+            self.cPosZ = self.createcomponent('originZ', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Z', relief = FLAT,
+                                             value = centerPos.getZ(),
+                                             label_foreground = 'Blue',
+                                             entry_width = 9)
+            self.cPosZ['commandData'] = ['sphere-o']
+            self.cPosZ['command'] = self.setCollisionPosHprScale
+            self.cPosZ.pack(side=LEFT, expand=0,fill=X, padx=1)
+            posInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+
+            group.pack(side=TOP,fill = X, expand = 0, pady=3)
+
+            scaleInterior = Frame(cObjFrame)
+        
+            self.scaleS = self.createcomponent('radius', (), None,
+                                               Floater.Floater, (scaleInterior,),
+                                               text = 'Radius',
+                                               relief = FLAT,
+                                               min = 0.0001, value = radius,
+                                               resetValue = 1.0,
+                                               label_foreground = 'Blue')
+            self.scaleS['commandData'] = ['sphere-radius']
+            self.scaleS['command'] = self.setCollisionPosHprScale
+            self.scaleS.pack(side=LEFT,expand=0,fill=X)
+
+            scaleInterior.pack(side=TOP,expand=0,fill=X, padx=3, pady=3)
+            pass
+        
+        elif cType == 'CollisionPolygon':
+            frame = Frame(cObjFrame)
+            label = Label(frame, text= "Sorry!",font=('MSSansSerif', 10),
+                          borderwidth=5)
+            label.pack(side=LEFT)
+            frame.pack(side=TOP, fill=X, expand=True)
+            frame = Frame(cObjFrame)
+            label = Label(frame, text= "There is no way to change",font=('MSSansSerif', 10),
+                          borderwidth=5)
+            label.pack(side=LEFT)
+            frame.pack(side=TOP, fill=X, expand=True)
+            frame = Frame(cObjFrame)
+            label = Label(frame, text= "the basic properties of Collision Polygon!",font=('MSSansSerif', 10),
+                          borderwidth=5)
+            label.pack(side=LEFT)
+            frame.pack(side=TOP, fill=X, expand=True)
+            frame = Frame(cObjFrame)
+            label = Label(frame, text= "If you really need to change, recreate one...",font=('MSSansSerif', 10),
+                          borderwidth=5)
+            label.pack(side=LEFT)
+            frame.pack(side=TOP, fill=X, expand=True)        
+            pass
+        
+        elif cType == 'CollisionSegment':
+            pointA = self.collisionObj.getPointA()
+            pointB = self.collisionObj.getPointB()
+            group = Pmw.Group(cObjFrame,tag_text='Point A',
+                              tag_font=('MSSansSerif', 10))
+            posInterior = Frame(group.interior())
+            self.cPosX = self.createcomponent('pointA-X', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'X', relief = FLAT,
+                                             value = pointA.getX(),
+                                             label_foreground = 'Red',
+                                             entry_width = 9)
+            self.cPosX['commandData'] = ['segment-A']
+            self.cPosX['command'] = self.setCollisionPosHprScale
+            self.cPosX.pack(side=LEFT,expand=0,fill=X, padx=1)
+        
+            self.cPosY = self.createcomponent('pointA-Y', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Y', relief = FLAT,
+                                             value = pointA.getY(),
+                                             label_foreground = '#00A000',
+                                             entry_width = 9)
+            self.cPosY['commandData'] = ['segment-A']
+            self.cPosY['command'] = self.setCollisionPosHprScale
+            self.cPosY.pack(side=LEFT, expand=0,fill=X, padx=1)
+        
+            self.cPosZ = self.createcomponent('pointA-Z', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Z', relief = FLAT,
+                                             value = pointA.getZ(),
+                                             label_foreground = 'Blue',
+                                             entry_width = 9)
+            self.cPosZ['commandData'] = ['segment-A']
+            self.cPosZ['command'] = self.setCollisionPosHprScale
+            self.cPosZ.pack(side=LEFT, expand=0,fill=X, padx=1)
+            posInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+            group.pack(side=TOP,fill = X, expand = 0, pady=3)
+            group = Pmw.Group(cObjFrame,tag_text='Point B',
+                              tag_font=('MSSansSerif', 10))
+            posInterior = Frame(group.interior())
+            self.cPosXB = self.createcomponent('pointB-X', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'X', relief = FLAT,
+                                             value = pointB.getX(),
+                                             label_foreground = 'Red',
+                                             entry_width = 9)
+            self.cPosXB['commandData'] = ['segment-B']
+            self.cPosXB['command'] = self.setCollisionPosHprScale
+            self.cPosXB.pack(side=LEFT,expand=0,fill=X, padx=1)
+        
+            self.cPosYB = self.createcomponent('pointB-Y', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Y', relief = FLAT,
+                                             value = pointB.getY(),
+                                             label_foreground = '#00A000',
+                                             entry_width = 9)
+            self.cPosYB['commandData'] = ['segment-B']
+            self.cPosYB['command'] = self.setCollisionPosHprScale
+            self.cPosYB.pack(side=LEFT, expand=0,fill=X, padx=1)
+        
+            self.cPosZB = self.createcomponent('pointB-Z', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Z', relief = FLAT,
+                                             value = pointB.getZ(),
+                                             label_foreground = 'Blue',
+                                             entry_width = 9)
+            self.cPosZB['commandData'] = ['segment-B']
+            self.cPosZB['command'] = self.setCollisionPosHprScale
+            self.cPosZB.pack(side=LEFT, expand=0,fill=X, padx=1)
+            posInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+            group.pack(side=TOP,fill = X, expand = 0, pady=3)
+            pass
+        elif cType == 'CollisionRay':
+            origin = self.collisionObj.getOrigin()
+            direction = self.collisionObj.getDirection()
+            group = Pmw.Group(cObjFrame,tag_text='Origin Point',
+                              tag_font=('MSSansSerif', 10))
+            posInterior = Frame(group.interior())
+            self.cPosX = self.createcomponent('origin-X', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'X', relief = FLAT,
+                                             value = origin.getX(),
+                                             label_foreground = 'Red',
+                                             entry_width = 9)
+            self.cPosX['commandData'] = ['ray-A']
+            self.cPosX['command'] = self.setCollisionPosHprScale
+            self.cPosX.pack(side=LEFT,expand=0,fill=X, padx=1)
+        
+            self.cPosY = self.createcomponent('origin-Y', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Y', relief = FLAT,
+                                             value = origin.getY(),
+                                             label_foreground = '#00A000',
+                                             entry_width = 9)
+            self.cPosY['commandData'] = ['ray-A']
+            self.cPosY['command'] = self.setCollisionPosHprScale
+            self.cPosY.pack(side=LEFT, expand=0,fill=X, padx=1)
+        
+            self.cPosZ = self.createcomponent('origin-Z', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Z', relief = FLAT,
+                                             value = origin.getZ(),
+                                             label_foreground = 'Blue',
+                                             entry_width = 9)
+            self.cPosZ['commandData'] = ['ray-A']
+            self.cPosZ['command'] = self.setCollisionPosHprScale
+            self.cPosZ.pack(side=LEFT, expand=0,fill=X, padx=1)
+            posInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+            group.pack(side=TOP,fill = X, expand = 0, pady=3)
+            group = Pmw.Group(cObjFrame,tag_text='Direction',
+                              tag_font=('MSSansSerif', 10))
+            posInterior = Frame(group.interior())
+            self.cPosXB = self.createcomponent('direction-X', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'X', relief = FLAT,
+                                             value = direction.getX(),
+                                             label_foreground = 'Red',
+                                             entry_width = 9)
+            self.cPosXB['commandData'] = ['ray-B']
+            self.cPosXB['command'] = self.setCollisionPosHprScale
+            self.cPosXB.pack(side=LEFT,expand=0,fill=X, padx=1)
+        
+            self.cPosYB = self.createcomponent('direction-Y', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Y', relief = FLAT,
+                                             value = direction.getY(),
+                                             label_foreground = '#00A000',
+                                             entry_width = 9)
+            self.cPosYB['commandData'] = ['ray-B']
+            self.cPosYB['command'] = self.setCollisionPosHprScale
+            self.cPosYB.pack(side=LEFT, expand=0,fill=X, padx=1)
+        
+            self.cPosZB = self.createcomponent('direction-Z', (), None,
+                                             Floater.Floater, (posInterior,),
+                                             text = 'Z', relief = FLAT,
+                                             value = direction.getZ(),
+                                             label_foreground = 'Blue',
+                                             entry_width = 9)
+            self.cPosZB['commandData'] = ['ray-B']
+            self.cPosZB['command'] = self.setCollisionPosHprScale
+            self.cPosZB.pack(side=LEFT, expand=0,fill=X, padx=1)
+            posInterior.pack(side=TOP, expand=0,fill=X, padx=3, pady=3)
+            group.pack(side=TOP,fill = X, expand = 0, pady=3)
+            pass
+            
+        collisionGroup.pack(side=TOP,fill = X, expand = 0, pady=3)
+
+        return
+
+    def setCollisionPosHprScale(self, data, dataType):
+        #################################################################
+        # setCollisionPosHprScale(self, data, dataType)
+        # Well, the reason that we didn't use the same one with other nodePath
+        # is that each tyoe of collsion objects has its unique properties and way to set value.
+        # So, they have to be separated from other nodePath
+        #################################################################
+        if dataType == 'sphere-o':
+            origin = Point3(float(self.cPosX._entry.get()),
+                            float(self.cPosY._entry.get()),
+                            float(self.cPosZ._entry.get()))
+            self.collisionObj.setCenter(origin)
+        elif dataType == 'sphere-radius':
+            self.collisionObj.setRadius(data)
+        elif dataType == 'segment-A':
+            pointA = Point3(float(self.cPosX._entry.get()),
+                            float(self.cPosY._entry.get()),
+                            float(self.cPosZ._entry.get()))
+            self.collisionObj.setPointA(pointA)
+        elif dataType == 'segment-B':
+            pointB = Point3(float(self.cPosXB._entry.get()),
+                            float(self.cPosYB._entry.get()),
+                            float(self.cPosZB._entry.get()))
+            self.collisionObj.setPointB(pointB)
+        elif dataType == 'ray-A':
+            pointA = Point3(float(self.cPosX._entry.get()),
+                            float(self.cPosY._entry.get()),
+                            float(self.cPosZ._entry.get()))
+            self.collisionObj.setOrigin(pointA)
+        elif dataType == 'ray-B':
+            pointB = Vec3(float(self.cPosXB._entry.get()),
+                          float(self.cPosYB._entry.get()),
+                          float(self.cPosZB._entry.get()))
+            self.collisionObj.setDirection(pointB)
+        return
+
+    #################################################################
+    #################################################################
+    # Functions below are all call back function
+    # They will be called when user has manipulated its node on the screen
+    # The message itself is sent by a task called monitorSelectedNode in the sceneEditor.
+    #################################################################
+    #################################################################
+    def trackDataFromSceneCamera(self, pos=Point3(0,0,0), hpr=Vec3(0,0,0), scale=Point3(0,0,0)):
+        self.posX.set(pos.getX())
+        self.posY.set(pos.getY())
+        self.posZ.set(pos.getZ())
+        self.hprH.set(hpr.getX())
+        self.hprP.set(hpr.getY())
+        self.hprR.set(hpr.getZ())
+        return
+
+    def trackDataFromSceneModel(self, pos=Point3(0,0,0), hpr=Vec3(0,0,0), scale=Point3(0,0,0)):
+        self.posX.set(pos.getX())
+        self.posY.set(pos.getY())
+        self.posZ.set(pos.getZ())
+        self.hprH.set(hpr.getX())
+        self.hprP.set(hpr.getY())
+        self.hprR.set(hpr.getZ())
+        self.scale.set(scale.getX())
+        return
+
+    def trackDataFromSceneActor(self, pos=Point3(0,0,0), hpr=Vec3(0,0,0), scale=Point3(0,0,0)):
+        self.posX.set(pos.getX())
+        self.posY.set(pos.getY())
+        self.posZ.set(pos.getZ())
+        self.hprH.set(hpr.getX())
+        self.hprP.set(hpr.getY())
+        self.hprR.set(hpr.getZ())
+        self.scale.set(scale.getX())
+        return
+    
+    def trackDataFromSceneLight(self, pos=Point3(0,0,0), hpr=Vec3(0,0,0), scale=Point3(0,0,0)):
+        if self.lightNode.type == 'directional':
+            self.dPosition.set([pos.getX(),pos.getY(),pos.getZ()])
+            self.dOrientation.set([hpr.getX(),hpr.getY(),hpr.getZ()])
+            pass
+        
+        elif self.lightNode.type == 'point':
+            self.pPosition.set([pos.getX(),pos.getY(),pos.getZ()])
+            pass       
+        return
+    
+    def trackDataFromSceneDummy(self, pos=Point3(0,0,0), hpr=Vec3(0,0,0), scale=Point3(0,0,0)):
+        self.posX.set(pos.getX())
+        self.posY.set(pos.getY())
+        self.posZ.set(pos.getZ())
+        self.hprH.set(hpr.getX())
+        self.hprP.set(hpr.getY())
+        self.hprR.set(hpr.getZ())
+        self.scale.set(scale.getX())
+        return
+    
+    def trackDataFromSceneCollision(self, pos=Point3(0,0,0), hpr=Vec3(0,0,0), scale=Point3(0,0,0)):
+        self.posX.set(pos.getX())
+        self.posY.set(pos.getY())
+        self.posZ.set(pos.getZ())
+        self.hprH.set(hpr.getX())
+        self.hprP.set(hpr.getY())
+        self.hprR.set(hpr.getZ())
+        self.scale.set(scale.getX())
+        return
+        

+ 676 - 0
contrib/src/sceneeditor/quad.py

@@ -0,0 +1,676 @@
+#########################################################################################################################################
+# This file implements a Quad View for the level editor
+# This feature is not yet enabled in the level editor because picking objects in quad view doesnt work
+# I have tried to send the picking function in seSelection.py the correct camera and mouse coordinates but something seems to be wron
+# There are two classes in this file..the QuadView and the viewPort...there are four instances of viewport, one for each view in QuadView
+#########################################################################################################################################
+
+
+from direct.showbase.ShowBaseGlobal import *
+from direct.interval.IntervalGlobal import *
+from direct.showbase.DirectObject import DirectObject
+from pandac.PandaModules import *
+import math
+#Manakel 2/12/2005: replace from pandac import by from pandac.PandaModules import
+from pandac.PandaModules import MouseWatcher 
+
+
+class ViewPort:
+#########################################################################################################################################
+# The ViewPort class has the camera and associated display region set up for actually rendering the four sub-views
+# The constructor needs the bounds, window layer, camera, color, projection type, name and scene for the view
+#########################################################################################################################################           
+    
+    def __init__(self,X1,X2,Y1,Y2,layer,cam,background=Vec4(0.3,0.3,0.3,1),projection="perspective",type="top",scene=render):
+        self.VPType=type
+        self.VP_X1=X1
+        self.VP_Y1=Y1
+        self.VP_X2=X2
+        self.VP_Y2=Y2
+        self.VP_width=self.VP_X2 - self.VP_X1
+        self.VP_height=self.VP_Y2 - self.VP_Y1
+        
+        self.the_viewport=layer.makeDisplayRegion(self.VP_X1, self.VP_X2,self.VP_Y1, self.VP_Y2)
+        self.the_viewport.setCamera(cam)
+        self.the_viewport.setClearDepthActive(1)
+        self.the_viewport.setClearColorActive(1)
+        self.the_viewport.setClearColor(background)
+        self.cam=cam   
+     
+
+        # Set up the cameras to look in the right place.
+        if(type=="top"):
+            self.cam.setP(-90)
+            self.cam.setZ(-40)
+        elif(type=="left"):
+            self.cam.setH(-90)
+            self.cam.setX(10)
+        elif(type=="front"):
+            self.cam.setY(-10) 
+        elif(type=="perspective"):
+            cam.setY(-100)
+            #cam.setX(10)
+            #cam.setZ(-10)
+            #cam.setH(45)
+            #cam.setP(-45)   
+            #print "aa"
+
+
+        if(projection=="ortho"):
+            self.lens=OrthographicLens()
+            self.lens.setAspectRatio((self.VP_X2-self.VP_X1)/(self.VP_Y2-self.VP_Y1))
+            self.lens.setFilmSize(self.VP_width*200,self.VP_height*200)
+            #lens.setFilmOffset((self.VP_X2 + self.VP_X1) * 0.5, (self.VP_Y2 + self.VP_Y1) * 0.5)
+            self.lens.setNearFar(-1000, 1000)
+            self.cam.node().setLens(self.lens)
+            self.cam.node().setScene(scene)
+        elif(projection=="perspective"):
+            self.lens=base.cam.node().getLens()
+            self.lens.setAspectRatio((self.VP_X2-self.VP_X1)/(self.VP_Y2-self.VP_Y1))
+            self.cam.node().setLens(self.lens)
+            self.cam.node().setScene(scene)
+
+        self.the_viewport.setCamera(self.cam)
+
+    def resizeX(self,width_increment):
+        if(self.VPType=="top" or self.VPType=="left"):
+            self.the_viewport.setDimensions(self.VP_X1,self.VP_X2+width_increment,self.VP_Y1,self.VP_Y2)
+        elif(self.VPType=="perspective" or self.VPType=="front"):
+            self.the_viewport.setDimensions(self.VP_X1+width_increment,self.VP_X2,self.VP_Y1,self.VP_Y2)
+
+    def resizeY(self,height_increment,direction):
+        if(self.VPType=="left" or self.type=="perspective"):
+            self.the_viewport.setDimensions(self.VP_X1,self.VP_X2,self.VP_Y1,self.VP_Y2+height_increment)
+        else:
+            self.the_viewport.setDimensions(self.VP_X1,self.VP_X2,self.VP_Y1+height_increment,self.VP_Y2)
+
+    def AdjustAspect(self,x,y):
+        if (y==0):
+            y=1
+        self.lens.setAspectRatio(x/y)
+        self.cam.node().setLens(self.lens)
+   
+    def resize(self,x,y):
+        
+        if(self.VPType=="left"):
+            self.the_viewport.setDimensions(0,x,0,y)
+            w=abs(x-self.VP_X1)
+            h=abs(y-self.VP_Y1)
+            if(h==0):
+                h=1
+            self.lens.setAspectRatio(w/h)
+            self.cam.node().setLens(self.lens)
+        if(self.VPType=="top"):
+            self.the_viewport.setDimensions(0,x,y,1)
+            w=abs(x-self.VP_X1)
+            h=abs(self.VP_Y2-y)
+            if(h==0):
+                h=1
+            self.lens.setAspectRatio(w/h)
+            self.cam.node().setLens(self.lens)
+        if(self.VPType=="front"):
+            self.the_viewport.setDimensions(x,1,y,1)
+            w=abs(self.VP_X2-x)
+            h=abs(self.VP_Y2-y)
+            if(h==0):
+                h=1
+            self.lens.setAspectRatio(w/h)
+            self.cam.node().setLens(self.lens)
+        if(self.VPType=="perspective"):
+            self.the_viewport.setDimensions(x,1,0,y)
+            w=abs(self.VP_X2-x)
+            h=abs(y-self.VP_Y1)
+            if(h==0):
+                h=1
+            self.lens.setAspectRatio(w/h)
+            self.cam.node().setLens(self.lens)
+
+    def setScene(self,scene):
+        self.cam.node().setScene(scene)
+    
+    def setDR(self,mouseWatcher):
+        #mouseWatcher.setDisplayRegion(self.the_viewport)
+        pass
+
+    def setCam(self):
+        #base.cam=self.cam
+        #base.cam.node().setLens(self.cam.node().getLens())
+        base.camNode=self.cam.node()
+        #base.camNode.setLens(self.cam.node().getLens())
+        #base.camLens=self.cam.node().getLens()
+
+    def getCam(self):
+        return self.cam
+        
+
+class QuadView(DirectObject):
+#########################################################################################################################################
+# This class sets up four cameras for the scene (ideally we want four instances of render too)
+# and then instatiates a ViewPort class for each of them
+#
+#########################################################################################################################################
+
+    def __init__(self):
+
+        self.PTracker=1
+        self.ControlPressed=0
+        self.AltPressed=0
+        self.PanConstantX=50
+        self.PanConstantY=50
+        self.ZoomConstant=1     
+        self.FrontWidth=100
+        self.FrontHeight=100
+        self.TopWidth=100
+        self.TopHeight=100
+        self.LeftWidth=100
+        self.LeftHeight=100
+
+        self.MouseButton=0
+        self.CurrentQuad=4
+        self.HorizontalAxis=0.0
+        self.VerticalAxis=0.0
+        #base.disableMouse()
+        self.MouseDragging=0
+        self.currX= 0
+        self.oldX=self.currX
+        self.currY= 0
+        self.oldY=self.currY
+
+        self.FrontTexture=1
+        self.LeftTexture=1
+        self.PerspectiveTexture=1
+        self.TopTexture=1
+
+        self.FrontWire=0
+        self.LeftWire=0
+        self.PerspectiveWire=0
+        self.TopWire=0
+
+        # Keep track of the currently selected window... values are 1-4 for four quadrants of a standard 
+        # Cartesian coordinate system
+        
+        # These are the orthographic cameras
+        # They will be restricted to panning and zooming i.e. no rotation
+        # Top could be flipped to back, left to right and front to back
+        self.topCam= render.attachNewNode(Camera('topCam'))
+        self.frontCam = render.attachNewNode(Camera('frontCam'))
+        self.leftCam= render.attachNewNode(Camera('leftCam'))
+
+        # This camera will have a trackball control since its perspective
+        self.perspectiveCam = render.attachNewNode(Camera('perspectiveCam'))
+        
+        #self.toplens=OrthographicLens()
+        #self.leftLens=OrthographicLens()
+        #self.frontLens=OrthographicLens()
+        #self.perspectiveLens=base.cam.node().getLens()
+
+        # For now all lenses are same as that of base.cam
+        #self.topCamLens=OrthographicLens()
+        #self.frontCamLens= base.cam.node().getLens()
+        #self.leftCamLens= base.cam.node().getLens()
+        #self.perspectiveCamLens= base.cam.node().getLens()
+
+        # Manipulate lenses here if need be     
+        #self.topCamLens.setFilmSize(250)
+
+        # Set the Lenses
+        #self.topCam.node().setLens(self.topCamLens)
+        #self.frontCam.node().setLens(self.frontCamLens)
+        #self.leftCam.node().setLens(self.leftCamLens)
+        #self.perspectiveCam.node().setLens(self.perspectiveCamLens)
+
+        #self.badwiz = loader.loadModel('badwizard1')
+        #self.badwiz.reparentTo(render)
+
+        # Create four separate display regions for the quad view.
+        # These will overlap the main display region
+        # To stack these overlapping DisplayRegions, we need a new layer.  If
+        # they didn't overlap, we could put them in the same layer.
+
+        self.newLayer = base.win.getChannel(0).makeLayer()
+
+        self.PerspectiveScene=NodePath('PerspectiveScene')
+        self.FrontScene=NodePath('FrontScene')
+        self.TopScene=NodePath('TopScene')
+        self.LeftScene=NodePath('LeftScene')
+        self.SceneParent=NodePath('SceneParent')
+
+        #self.PerspectiveScene=render.copyTo(self.SceneParent)
+        #self.FrontScene=render.copyTo(self.SceneParent)
+        #self.TopScene=render.copyTo(self.SceneParent)
+        #self.LeftScene=render.copyTo(self.SceneParent)
+                
+        self.PerspectiveScene=render
+        self.FrontScene=render
+        self.TopScene=render
+        self.LeftScene=render
+        
+        #self.PerspectiveScene.reparentTo(self.SceneParent)
+        #self.FrontScene.reparentTo(self.SceneParent)
+        #self.TopScene.reparentTo(self.SceneParent)
+        #self.LeftScene.reparentTo(self.SceneParent)
+        
+        self.Perspective=ViewPort(0.5,1.0,0.0,0.5,self.newLayer,self.perspectiveCam,Vec4(0.75,0.75,0.75,1),"perspective","perspective",self.PerspectiveScene)
+        self.Top=ViewPort(0.0,0.5,0.5,1.0,self.newLayer,self.topCam,Vec4(0.80,0.80,0.80,1),"ortho","top",self.TopScene)
+        self.Left=ViewPort(0.0,0.5,0.0,0.5,self.newLayer,self.leftCam,Vec4(0.85,0.85,0.85,1),"ortho","left",self.LeftScene)
+        self.Front=ViewPort(0.5,1.0,0.5,1.0,self.newLayer,self.frontCam,Vec4(0.85,0.85,0.85,1),"ortho","front",self.FrontScene)
+        #self.Perspective=None
+        #self.Top=None
+        #self.Front=None
+        #self.Left=None
+
+        #self.raycaster = RayCaster( camera )
+        #self.lastPickPoint = None
+
+        #base.useTrackball()
+        
+        #self.dataRoot = NodePath('dataRoot')
+        # Cache the node so we do not ask for it every frame
+        #self.dataRootNode = self.dataRoot.node()
+        #self.dataUnused = NodePath('dataUnused')
+        #self.mak=None
+        #self.mak = self.dataRoot.attachNewNode(MouseAndKeyboard(base.win, 0, 'mak'))
+        #self.mak.node().setSource(base.win, 0)
+        self.mouseWatcherNode = MouseWatcher('mouseWatcher')
+        
+        self.mouseWatcher = base.mak.attachNewNode(self.mouseWatcherNode)
+        #self.Perspective.setDR(self.mouseWatcherNode)
+
+        self.buttonThrower = self.mouseWatcher.attachNewNode(ButtonThrower('buttons'))
+
+
+        #ddr=DisplayRegionContext(self.Perspective.getCam())
+        #base.setMouseOnNode(self.smiley.node()) # Let Mouse Control Perspective View for now
+        #base.enableSoftwareMousePointer()
+        
+        # Message Handlers
+        self.accept("a",self.setLeft)
+        self.accept("q",self.setTop)
+        self.accept("w",self.setFront)
+        self.accept("s",self.setPerspective)
+        self.accept("mouse1",self.MouseTell,[1])
+        self.accept("mouse2",self.MouseTell,[2])
+        self.accept("mouse3",self.MouseTell,[3])
+        self.accept("mouse1-up",self.MouseTellUp,[4])
+        self.accept("mouse2-up",self.MouseTellUp,[5])
+        self.accept("mouse3-up",self.MouseTellUp,[6])
+        self.accept("mouse2-scroll",self.resizedr)
+        self.accept("r",self.resizedr)
+        self.accept("alt",self.AltHandler)
+        self.accept("alt-up",self.AltUpHandler)
+        self.accept("alt-mouse1",self.AltDown)
+        self.accept("alt-mouse1-up",self.AltUp)
+        self.accept("control-mouse1",self.CtlDown)
+        self.accept("control-mouse1-up",self.CtlUp)
+        
+    # Methods
+
+    #def setLastPickPoint( self ):
+    #    mouseX, mouseY = self.mouseWatcherNode.getMouseX(), self.mouseWatcherNode.getMouseY()
+    #    self.lastPickPoint = self.raycaster.pick( mouseX, mouseY )
+    #    print self.lastPickPoint
+
+    def AltDown(self):
+        self.AltPressed=1
+
+
+    def AltUp(self):
+        self.AltPressed=0
+
+
+    def CtlDown(self):
+        self.ControlPressed=1
+
+
+    def CtlUp(self):
+        self.ControlPressed=0
+
+
+    def ToggleWire(self):
+        if (self.CurrentQuad==1): # Front View
+            if(self.FrontWire): # Wireframe is On so turn it off
+                self.FrontScene.setRenderModeWireframe(100)
+                self.FrontScene.setTwoSided(1)
+                self.FrontScene.setTextureOff(100)
+                self.FrontWire=0
+            else:
+                self.FrontScene.clearRenderMode()
+                #self.FrontScene.setTwoSided(not self.backfaceCullingEnabled)
+                if(self.FrontTexture):
+                    self.FrontScene.clearTexture()
+                self.FrontWire=1
+        elif (self.CurrentQuad==2): # Front View
+            if(self.TopWire): # Wireframe is On so turn it off
+                self.TopScene.setRenderModeWireframe(100)
+                self.TopScene.setTwoSided(1)
+                self.TopScene.setTextureOff(100)
+                self.TopWire=0
+            else:
+                self.TopScene.clearRenderMode()
+                #self.TopScene.setTwoSided(not self.backfaceCullingEnabled)
+                if(self.TopTexture):
+                    self.TopScene.clearTexture()
+                self.TopWire=1
+        elif (self.CurrentQuad==3): # Front View
+            if(self.LeftWire): # Wireframe is On so turn it off
+                self.LeftScene.setRenderModeWireframe(100)
+                self.LeftScene.setTwoSided(1)
+                self.LeftScene.setTextureOff(100)
+                self.LeftWire=0
+            else:
+                self.LeftScene.clearRenderMode()
+                #self.LeftScene.setTwoSided(not self.backfaceCullingEnabled)
+                if(self.LeftTexture):
+                    self.LeftScene.clearTexture()
+                self.LeftWire=1
+        elif (self.CurrentQuad==4): # Front View
+            if(self.PerspectiveWire): # Wireframe is On so turn it off
+                self.PerspectiveScene.setRenderModeWireframe(100)
+                self.PerspectiveScene.setTwoSided(1)
+                self.PerspectiveScene.setTextureOff(100)
+                self.PerspectiveWire=0
+            else:
+                self.PerspectiveScene.clearRenderMode()
+                #self.PerspectiveScene.setTwoSided(not self.backfaceCullingEnabled)
+                if(self.PerspectiveTexture):
+                         self.PerspectiveScene.clearTexture()
+                self.PerspectiveWire=1
+
+
+    def ToggleTexture(self):
+        if (self.CurrentQuad==1): # Front View
+            if(self.FrontTexture): # Texture is on so turn it off
+                self.FrontScene.setTextureOff(100)
+                self.FrontTexture=0
+            else:
+                self.FrontScene.clearTexture()
+                self.FrontTexture=1
+        elif (self.CurrentQuad==2): # Top View
+            if(self.TopTexture): # Texture is on so turn it off
+                self.TopScene.setTextureOff(100)
+                self.TopTexture=0
+            else:
+                self.TopScene.clearTexture()
+                self.TopTexture=1
+        elif (self.CurrentQuad==3): # Left View
+            if(self.LeftTexture): # Texture is on so turn it off
+                self.LeftScene.setTextureOff(100)
+                self.LeftTexture=0
+            else:
+                self.LeftScene.clearTexture()
+                self.LeftTexture=1
+        elif (self.CurrentQuad==4): # Perspective View
+            if(self.PerspectiveTexture): # Texture is on so turn it off
+                self.PerspectiveScene.setTextureOff(100)
+                self.PerspectiveTexture=0
+            else:
+                self.PerspectiveScene.clearTexture()
+                self.PerspectiveTexture=1
+
+   
+
+    def reparenter(self):
+        #self.FrontScene.reparentTo(render)
+        #self.Front.setScene(render)
+        #self.Top.setScene(render)
+        #self.Left.setScene(render)
+        #self.Perspective.setScene(render)
+        pass
+
+    def unparenter(self):
+        #self.PerspectiveScene=render.copyTo(render)
+        #self.FrontScene=render.copyTo(render)
+        #self.TopScene=render.copyTo(render)
+        #self.LeftScene=render.copyTo(render)
+        #self.SceneParent.reparentTo(render)
+        #self.PerspectiveScene.reparentTo(self.SceneParent)
+        #self.FrontScene.reparentTo(self.SceneParent)
+        #self.TopScene.reparentTo(self.SceneParent)
+        #self.LeftScene.reparentTo(self.SceneParent)
+        pass
+
+    def AltHandler(self):
+        self.oldX=self.mouseWatcherNode.getMouseX()
+        if(self.oldX<-1 or self.oldX>1):
+            return
+        self.oldY=self.mouseWatcherNode.getMouseY()
+        if(self.oldY<-1 or self.oldY>1):
+            return
+        taskMgr.add(self.DragAction,'DragAction')
+            
+    def AltUpHandler(self):
+        taskMgr.remove('DragAction')    
+        
+    def gridtoggle(self):
+        #grid=DirectGrid()
+        #grid.enable()
+        pass
+
+    def resizedr(self,x,y):
+        #print "X: " + str(x) + " Y: " + str(y)
+        x=(x+1)/2.0
+        y=(y+1)/2.0
+        self.Perspective.resize(x,y)
+        self.Top.resize(x,y)
+        self.Front.resize(x,y)
+        self.Left.resize(x,y)
+
+    def setAppropriateViewPort(self,x,y):
+        #print "SET APPROPRIATE:" + str(x) + " " + str(y)
+        if(x<self.VerticalAxis):
+            if(y<self.HorizontalAxis):
+                self.setLeft()
+            else:
+                self.setTop()
+        else:
+            if(y<self.HorizontalAxis):
+                self.setPerspective()
+            else:
+                self.setFront()
+        
+    def MouseTell(self,buttonCode):
+        self.MouseButton=buttonCode
+        self.setAppropriateViewPort(self.mouseWatcherNode.getMouseX(),self.mouseWatcherNode.getMouseY())
+        
+        x=base.mouseWatcherNode.getMouseX()
+        y=base.mouseWatcherNode.getMouseY()
+
+        #Perspective and Front
+        if(self.CurrentQuad==4 or self.CurrentQuad==1):
+            x1=abs(x-self.VerticalAxis)
+            w1=abs(1-self.VerticalAxis)
+            x2=x1*2.0/w1
+            ansX=-1+x2
+
+
+        #Left and top
+        if(self.CurrentQuad==2 or self.CurrentQuad==3):
+            x1=abs(x-(-1.0))
+            w1=abs(self.VerticalAxis-(-1.0))
+            x2=x1*2.0/w1
+            ansX=-1.0+x2
+
+        #Left and Perspective
+        if(self.CurrentQuad==4 or self.CurrentQuad==3):
+            y1=abs(y-(-1.0))
+            h1=abs(self.HorizontalAxis-(-1.0))
+            y2=y1*2.0/h1
+            ansY=-1.0+y2
+
+
+        #Front and top
+        if(self.CurrentQuad==1 or self.CurrentQuad==2):
+            y1=abs(y-self.HorizontalAxis)
+            h1=abs(1.0-self.HorizontalAxis)
+            y2=y1*2.0/h1
+            ansY=-1.0+y2
+
+        self.xy=[ansX,ansY]
+        print "Sent X:%f Sent Y:%f"%(ansX,ansY)
+        #SEditor.iRay.pick(render,self.xy)
+        SEditor.manipulationControl.manipulationStop(self.xy)
+        #print "MouseX " + str(base.mouseWatcherNode.getMouseX()) + "MouseY " + str(base.mouseWatcherNode.getMouseY()) + "\n" 
+        #print "MouseX " + str(self.mouseWatcherNode.getMouseX()) + "MouseY " + str(self.mouseWatcherNode.getMouseY()) + "\n" 
+        
+
+        base.mouseWatcherNode=self.mouseWatcherNode
+        
+        self.oldX=self.mouseWatcherNode.getMouseX()
+        if(self.oldX<-1 or self.oldX>1):
+            return
+
+        self.oldY=self.mouseWatcherNode.getMouseY()
+        if(self.oldY<-1 or self.oldY>1):
+            return
+        self.Mouse_Dragging=1
+        taskMgr.add(self.DragAction,'DragAction')
+
+    def MouseTellUp(self,buttoncode):
+        #self.MouseButton=0
+        self.PanConstantX= 50
+        self.PanConstantY= 50
+        self.ZoomConstant=1
+        taskMgr.remove('DragAction')
+        self.Mouse_Draggin=0
+        #print "Mouse Up"
+        
+
+    def Max_Style_Mouse_View(self,buttoncode):
+        pass
+        
+    def ChangeBaseDR(self):
+        dr=base.win.getDisplayRegion(0)
+        if(self.CurrentQuad==1): #Front
+            dr.setDimensions(0.5,1,0.5,1)
+        elif(self.CurrentQuad==2): #Top
+            dr.setDimensions(0,0.5,0.5,1)
+        elif(self.CurrentQuad==3): #Left
+            dr.setDimensions(0,0.5,0,0.5)
+        elif(self.CurrentQuad==4): #Perspective
+            dr.setDimensions(0.5,1,0,0.5)
+
+    def setLeft(self):
+        print "LEFT"
+        self.CurrentQuad=3
+        self.ChangeBaseDR()
+        self.Left.setCam()
+        #self.Left.setDR(self.mouseWatcherNode)
+
+    def setTop(self):
+        print "TOP"
+        self.CurrentQuad=2
+        self.ChangeBaseDR()
+        self.Top.setCam()
+        #self.Top.setDR(self.mouseWatcherNode)
+
+    def setPerspective(self):
+        print "PERSPECTIVE"
+        self.CurrentQuad=4
+        self.ChangeBaseDR()
+        self.Perspective.setCam()
+        #self.Perspective.setDR(self.mouseWatcherNode)
+        
+    def setFront(self):
+        print "FRONT"
+        self.CurrentQuad=1
+        self.ChangeBaseDR()
+        self.Front.setCam()
+        #self.Front.setDR(self.mouseWatcherNode)
+
+    
+    def DragAction(self,task):
+        #if(self.MouseDragging==1):
+        self.currX= self.mouseWatcherNode.getMouseX()
+        if(self.currX<-1 or self.currX>1):
+            return
+        self.currY= self.mouseWatcherNode.getMouseY()
+        if(self.currY<-1 or self.currY>1):
+            return
+
+
+        self.diffX=self.currX-self.oldX
+        self.diffY=self.currY-self.oldY
+
+        if(self.ControlPressed): # Change Size of the ViewPorts
+        #if(base.getControl()):
+             self.VerticalAxis=self.currX
+             self.HorizontalAxis=self.currY
+             if(self.HorizontalAxis<-1 or self.HorizontalAxis>1 or self.VerticalAxis<-1 or self.VerticalAxis>1):
+                 return
+             self.resizedr(self.VerticalAxis,self.HorizontalAxis)
+        
+        #if(self.AltPressed): # View Camera Transforms -> Maya style
+        elif(1):
+            #print "ALTPRESSED"
+            if(self.PanConstantX<4096):
+                self.PanConstantX= self.PanConstantX * 2
+                self.PanConstantY= self.PanConstantY * 2
+            self.ZoomConstant= self.ZoomConstant + 50
+            if(self.MouseButton==1): # TrackBall rotation only for Perspective View
+                if(self.CurrentQuad==4):
+                    pass
+            elif(self.MouseButton==2): # Do Panning
+                if(self.CurrentQuad==1): # Y and Z values change meanings for different cameras
+                    self.MoveCamera(-self.diffX*self.PanConstantX,0,-self.diffY*self.PanConstantY,self.CurrentQuad)
+                elif(self.CurrentQuad==2):
+                    self.MoveCamera(-self.diffX*self.PanConstantX,-self.diffY*self.PanConstantY,0,self.CurrentQuad)
+                elif(self.CurrentQuad==3):
+                    self.MoveCamera(0,self.diffX*self.PanConstantX,-self.diffY*self.PanConstantY,self.CurrentQuad)
+                elif(self.CurrentQuad==4):
+                    pass
+            elif(self.MouseButton==3): # Do Zoom 
+                if(self.CurrentQuad==1): # Y and Z values change meanings for different cameras
+                    #lens = OrthographicLens()
+                    #lens.setFilmSize(l,self.VP_height*200)
+                    #lens.setFilmOffset((self.VP_X2 + self.VP_X1) * 0.5, (self.VP_Y2 + self.VP_Y1) * 0.5)
+                    #lens.setNearFar(-1000, 1000)
+                    self.FrontWidth= self.FrontWidth + self.diffX
+                    self.FrontHeight= self.FrontHeight + self.diffX
+                    self.FrontWidth= self.FrontWidth + self.diffY
+                    self.FrontHeight= self.FrontHeight + self.diffY
+
+                    if(self.FrontWidth<=0):
+                        Frontwidth=1
+                    if(self.FrontHeight<=0):
+                        FrontHeight=1
+                    self.frontCam.node().getLens().setFilmSize(self.FrontWidth,self.FrontHeight)
+                    self.resizedr(self.VerticalAxis,self.HorizontalAxis)        
+                elif(self.CurrentQuad==2):
+                    self.TopWidth= self.TopWidth + self.diffX
+                    self.TopHeight= self.TopHeight + self.diffX
+                    self.TopWidth= self.TopWidth + self.diffY
+                    self.TopHeight= self.TopHeight + self.diffY
+                    self.topCam.node().getLens().setFilmSize(self.TopWidth,self.TopHeight)
+                    self.resizedr(self.VerticalAxis,self.HorizontalAxis)        
+                elif(self.CurrentQuad==3):
+                    self.LeftWidth= self.LeftWidth + self.diffX
+                    self.LeftHeight= self.LeftHeight + self.diffX
+                    self.LeftWidth= self.LeftWidth + self.diffY
+                    self.LeftHeight= self.LeftHeight + self.diffY
+                    self.leftCam.node().getLens().setFilmSize(self.LeftWidth,self.LeftHeight)
+                    self.resizedr(self.VerticalAxis,self.HorizontalAxis)        
+                elif(self.CurrentQuad==4):
+                    pass
+        else:
+             pass
+                
+        self.oldX=self.currX
+        self.oldY=self.currY
+        return Task.cont
+
+
+
+    def MoveCamera(self,X_amt,Y_amt,Z_amt,quad):
+        if(quad==1):
+            self.frontCam.setPos(self.frontCam.getX()+X_amt,self.frontCam.getY()+Y_amt,self.frontCam.getZ()+Z_amt)
+        elif(quad==2):
+            self.topCam.setPos(self.topCam.getX()+X_amt,self.topCam.getY()+Y_amt,self.topCam.getZ()+Z_amt)
+        elif(quad==3):
+            self.leftCam.setPos(self.leftCam.getX()+X_amt,self.leftCam.getY()+Y_amt,self.leftCam.getZ()+Z_amt)
+        elif(quad==4):
+            self.perspectiveCam.setPos(self.perspectiveCam.getX()+X_amt,self.perspectiveCam.getY()+Y_amt,self.perspectiveCam.getZ()+Z_amt)
+        
+    
+
+#View=QuadView()
+#run()

+ 1709 - 0
contrib/src/sceneeditor/sceneEditor.py

@@ -0,0 +1,1709 @@
+
+import sys
+try: import _tkinter
+except: sys.exit("Please install python module 'Tkinter'")
+
+import direct
+from direct.directbase.DirectStart import*
+from direct.showbase.TkGlobal import *
+from direct.showbase.TkGlobal import spawnTkLoop, Toplevel
+from tkFileDialog import *
+from direct.directtools.DirectGlobals import *
+from direct.tkwidgets.AppShell import*
+
+from SideWindow import*
+from duplicateWindow import*
+from lightingPanel import *
+from seMopathRecorder import *
+from seSession import *
+from quad import *
+from sePlacer import *
+from seFileSaver import *
+from propertyWindow import *
+import seParticlePanel
+from collisionWindow import *
+from direct.gui.DirectGui import *
+from MetadataPanel import *
+from seBlendAnimPanel import *
+from controllerWindow import *
+from AlignTool import *
+
+
+
+import os
+import string
+from direct.tkwidgets import Dial
+from direct.tkwidgets import Floater
+from direct.tkwidgets import Slider
+from direct.actor import Actor
+import seAnimPanel
+from direct.task import Task
+import math
+
+
+#################################################################
+# All scene and windows object will be stored in here.
+# So, any event which will or need to change contents
+# should be wirtten in here or imported into here!
+#################################################################
+from dataHolder import*  ## Use this thing to Save/load data.
+AllScene = dataHolder()
+
+
+
+class myLevelEditor(AppShell):
+    ## overridden the basic app info ##
+    appname = 'Scene Editor - New Scene'
+    appversion      = '1.0'
+    copyright       = ('Copyright 2004 E.T.C. Carnegie Mellon U.' +
+                       ' All Rights Reserved')
+    contactname     = 'Jesse Schell, Shalin Shodhan & YiHong Lin'
+    contactphone    = '(412) 268-5791'
+    contactemail    = '[email protected]'
+    frameWidth      = 1024
+    frameHeight     = 80
+    frameIniPosX    = 0
+    frameIniPosY    = 0
+    usecommandarea = 0
+    usestatusarea  = 0
+    padx            = 5
+    pady            = 5
+
+    sideWindowCount = 0
+
+    ## Basic World default setting (For side window)
+    worldColor = [0,0,0,0]
+    lightEnable = 1
+    ParticleEnable = 1
+    basedriveEnable = 0
+    collision = 1
+    backface = 0
+    texture = 1
+    wireframe = 0
+    grid = 0
+    widgetVis = 0
+    enableAutoCamera = 1
+
+    enableControl = False
+    controlType = 'Keyboard'
+    keyboardMapDict = {}
+    keyboardSpeedDict = {}
+    
+    Scene=None
+    isSelect = False
+    nodeSelected = None
+    undoDic = {}
+    redoDic = {}
+    animPanel = {}
+    animBlendPanel = {}
+    propertyWindow = {}
+    CurrentFileName=None #Holds the current scene file name
+    CurrentDirName=None # Holds the current file name without extension which is the path where file's data gets saved
+    Dirty=0 # Keeps track of whether there are any modifications that should be saved
+  
+    
+    
+    def __init__(self, parent = None, **kw):
+
+        base.setBackgroundColor(0,0,0)
+        self.parent = parent
+        ## Check TkTool is activated! ##
+        self.wantTK = config.GetBool('want-tk', 0)
+        if self.wantTK:
+            pass
+        else:
+            taskMgr.remove('tkloop')
+            spawnTkLoop()
+        ## Set up window frame
+        INITOPT = Pmw.INITOPT
+        optiondefs = (
+            ('title',       self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        AppShell.__init__(self, parent)
+        self.parent.geometry('%dx%d+%d+%d' % (self.frameWidth, self.frameHeight,self.frameIniPosX,self.frameIniPosY))
+
+
+        ###### Put th directLabel on the screen to show the selected object Data
+        self.posLabel = DirectLabel(
+            relief = None,
+            pos = (-1.3, 0, 0.90),
+            text = "Position   : X: 00.00 Y: 00.00 Z: 00.00",
+            color = Vec4(1, 1, 1, 1),
+            text_scale = 0.05,
+            text_align = TextNode.ALeft
+            )
+        self.hprLabel = DirectLabel(
+            relief = None,
+            pos = (-1.3 , 0, 0.80),
+            text = "Orientation: H: 00.00 P: 00.00 R: 00.00",
+            color = Vec4(1, 1, 1, 1),
+            text_scale = 0.05,
+            text_align = TextNode.ALeft
+            )
+        self.scaleLabel = DirectLabel(
+            relief = None,
+            pos = (-1.3, 0, 0.70),
+            text = "Scale      : X: 00.00 Y: 00.00 Z: 00.00",
+            color = Vec4(1, 1, 1, 1),
+            text_scale = 0.05,
+            text_align = TextNode.ALeft
+            )
+        
+
+        self.initialiseoptions(myLevelEditor)
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+        
+        ######### Set the event handler ##########
+        self.dataFlowEvents = [
+            ## Event from Side Window
+            ['SW_lightToggle',self.lightToggle],
+            ['SW_collisionToggle',AllScene.toggleCollisionVisable],
+            ['SW_particleToggle',self.toggleParticleVisable],
+            ['SW_close',self.sideWindowClose],
+            ## From Duplication Window
+            ['DW_duplicating',self.duplicationObj],
+            ## From Animation Panel
+            ['AW_AnimationLoad',self.animationLoader],
+            ['AW_removeAnim',self.animationRemove],
+            ['AW_close',self.animPanelClose],
+            ## From Blending Animation Window
+            ['BAW_saveBlendAnim',self.animBlendPanelSave],
+            ['BAW_removeBlendAnim',self.animBlendPanelRemove],
+            ['BAW_renameBlendAnim',self.animBlendPanelRename],
+            ['BAW_close',self.animBlendPanelClose],
+            ## From Lighting Panel
+            ['LP_selectLight', self.lightSelect],
+            ['LP_addLight',self.addLight],
+            ['LP_rename',self.lightRename],
+            ['LP_removeLight',self.removeLight],
+            ['LP_close',self.lightingPanelClose],
+            ## From MotionPath Panel
+            ['mPath_bindPathToNode',AllScene.bindCurveToNode],
+            ['mPath_requestCurveList', self.requestCurveList],
+            ['mPath_close', self.mopathClosed],
+            ## From Property Window
+            ['PW_removeCurveFromNode', AllScene.removeCurveFromNode],
+            ['PW_removeAnimFromNode', AllScene.removeAnimation],
+            ['PW_toggleLight', AllScene.toggleLightNode],
+            ['PW_close', self.closePropertyWindow],
+            ## From collisionWindow
+            ['CW_addCollisionObj', AllScene.addCollisionObject],
+            ## From AlignWindow
+            ['ALW_close', self.closeAlignPanel],
+            ['ALW_align', self.alignObject],
+            ## From controllerWindow
+            ['ControlW_close', self.closeInputPanel],
+            ['ControlW_require', self.requestObjFromControlW],
+            ['ControlW_controlSetting', self.setControlSet],
+            ['ControlW_controlEnable', self.startControl],
+            ['ControlW_controlDisable', self.stopControl],
+            ['ControlW_saveSetting', AllScene.saveControlSetting],
+            ## From Placer
+            ['Placer_close', self.closePlacerPanel],
+            ## From Particle Panel
+            ['ParticlePanle_close', self.closeParticlePanel],
+            ## From SEditor object which is a altered DirectSession
+            ['SEditor-ToggleWidgetVis',self.toggleWidgetVis],
+            ['SEditor-ToggleBackface',self.toggleBackface],
+            ['SEditor-ToggleTexture',self.toggleTexture],
+            ['SEditor-ToggleWireframe',self.toggleWireframe],
+            ['ParticlePanel_Added_Effect',self.addParticleEffect],
+            ['f11',self.loadFromBam],
+            ['f12',self.saveAsBam],
+            ]
+        
+
+        #################################
+        ###  Collision detection
+        #################################
+        self.cTrav = CollisionTraverser()
+        base.cTrav = self.cTrav
+
+        for event in self.dataFlowEvents:
+            self.accept(event[0], event[1], extraArgs = event[2:])
+        
+        self.actionEvents = [
+            # Scene graph explorer functions
+            ['SGE_changeName', self.changeName],
+            ['SGE_Properties', self.openPropertyPanel],
+            ['SGE_Duplicate', self.duplicate],
+            ['SGE_Remove', self.remove],
+            ['SGE_Add Dummy', self.addDummyNode],
+            ['SGE_Add Collision Object', self.addCollisionObj],
+            ['SGE_Metadata', self.openMetadataPanel],
+            ['SGE_Set as Reparent Target', self.setAsReparentTarget],
+            ['SGE_Reparent to Target', self.reparentToNode],
+            ['SGE_Animation Panel', self.openAnimPanel],
+            ['SGE_Blend Animation Panel', self.openBlendAnimPanel],
+            ['SGE_MoPath Panel', self.openMoPathPanel],
+            ['SGE_Align Tool', self.openAlignPanel],
+            ['SGE_Flash', self.flash],
+            ['SGE_madeSelection', self.selectNode],
+            ['select',self.selectNode],
+            ['deselect', self.deSelectNode],
+            ['se_selectedNodePath',self.selectFromScene],
+            ['se_deselectedAll',self.deselectFromScene],
+            ]
+        ''' All messages starting with "SGE_" are generated in seSceneGraphExplorer'''
+        
+        for event in self.actionEvents:
+            self.accept(event[0], event[1], extraArgs = event[2:])
+
+        camera.toggleVis()
+        self.selectNode(base.camera) ## Initially, we select camera as the first node...
+
+    def appInit(self):
+        #################################################################
+        # appInit(self)
+        # Initialize the application.
+        # This function will be called when you call AppShell's constructor
+        #################################################################
+        
+        ### Create SceneEditor Ver. DirectSession
+        self.seSession = SeSession()
+        self.seSession.enable()
+        SEditor.camera.setPos(0,-50,10)
+
+        self.placer=None
+        self.MopathPanel = None
+        self.alignPanelDict = {}
+        #self.quadview=QuadView()
+        
+
+        self.lightingPanel = None
+        self.controllerPanel = None
+        self.particlePanel = None
+
+        ### Create Side Window
+        self.sideWindow = sideWindow(worldColor = self.worldColor,
+                                     lightEnable = self.lightEnable,
+                                     ParticleEnable = self.ParticleEnable,
+                                     basedriveEnable = self.basedriveEnable,
+                                     collision = self.collision,
+                                     backface = self.backface,
+                                     texture = self.texture,
+                                     wireframe = self.wireframe,
+                                     grid = self.grid,
+                                     widgetVis = self.widgetVis,
+                                     enableAutoCamera = self.enableAutoCamera)
+        self.sideWindowCount = 1
+        self.sideWindow.selectPage()
+        messenger.send('SGE_Update Explorer',[render]) ## Update the Scene Graph
+        
+        pass
+        
+    def getPhotoImage(self,name):
+        modpath = ConfigVariableSearchPath("model-path")
+        path = modpath.findFile(Filename(name))
+        return PhotoImage(file=path.toOsSpecific())
+
+    def createInterface(self):
+        # The interior of the toplevel panel
+        interior = self.interior()
+
+        #######################################################
+        ### Creating the Buttons in the window frame
+        #######################################################
+        buttonFrame = Frame(interior)
+        self.image=[]
+
+        self.image.append(self.getPhotoImage('models/icons/new.gif'))#0
+        self.image.append(self.getPhotoImage('models/icons/open.gif'))#1
+        self.image.append(self.getPhotoImage('models/icons/save.gif'))#2
+        self.image.append(self.getPhotoImage('models/icons/model.gif'))#3
+        self.image.append(self.getPhotoImage('models/icons/actor.gif'))#4
+        self.image.append(self.getPhotoImage('models/icons/placer.gif'))#5
+        self.image.append(self.getPhotoImage('models/icons/mopath.gif'))#6
+        self.image.append(self.getPhotoImage('models/icons/lights.gif'))#7
+        self.image.append(self.getPhotoImage('models/icons/particles.gif'))#8
+        self.image.append(self.getPhotoImage('models/icons/control.gif'))
+        self.image.append(self.getPhotoImage('models/icons/help.gif'))#9
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+        self.image.append(self.getPhotoImage('models/icons/blank.gif'))
+
+        i = 0
+        for element in self.image:
+            i += 1
+            button = Button(buttonFrame, image = element, command=lambda n=i : self.buttonPushed(n))
+            button.pack(fill=X, side = LEFT)
+            
+
+        buttonFrame.pack(fill=X, side=LEFT,expand=True)
+        
+        
+    def buttonPushed(self, buttonIndex):
+        #################################################################
+        # buttonPushed(self, buttonNum)
+        # This function will handle all button events from top level window
+        # Take the button index as a reference to sence which button has been pushed.
+        #################################################################
+        ####
+        ####  Change here to process the button event further.
+        ####
+        if buttonIndex==1: # New Scene
+            self.newScene()
+            return
+        elif buttonIndex==2: # Open Scene
+            self.openScene()
+            return
+        elif buttonIndex==3: # Save Scene
+            self.saveScene()
+            return
+        elif buttonIndex==4: # Load Model
+            self.loadModel()
+            return
+        elif buttonIndex==5: # Load Actor
+            self.loadActor()
+            return
+        elif buttonIndex==6: # Open Placer
+            self.openPlacerPanel()
+            return
+        elif buttonIndex==7: # Open Mopath Panel
+            self.openMoPathPanel()
+            return
+        elif buttonIndex==8: # Open Lighting Panel
+            self.openLightingPanel()
+            return
+        elif buttonIndex==9: # Open Particle Panel
+            self.openParticlePanel()
+            return
+        elif buttonIndex==10: 
+            self.openInputPanel()
+            return
+        elif buttonIndex==11: # Help
+            self.showAbout()
+            return
+        elif buttonIndex==12:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        elif buttonIndex==13:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        elif buttonIndex==14:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        elif buttonIndex==15:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        elif buttonIndex==16:
+            print "Your scene will be eliminated within five seconds, Save your world!!!, Number %d."%buttonIndex
+            return
+        elif buttonIndex==17:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        elif buttonIndex==18:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        elif buttonIndex==19:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        elif buttonIndex==20:
+            print "You haven't defined the function for this Button, Number %d."%buttonIndex
+            return
+        
+        return
+
+    def createMenuBar(self):
+        # Creates default menus.  Can be overridden or simply augmented
+        # Using button Add below
+        self.menuBar.addmenuitem('Help', 'command',
+                                 'Get information on application', 
+                                 label='About...', command=self.showAbout)
+        ## Creat stuff inside the "File"
+        self.menuBar.addmenuitem('File', 'command', 'Creat New Scene',
+                                label='New Scene',
+                                command=self.newScene)
+
+        self.menuBar.addmenuitem('File', 'command', 'Open a Scene',
+                                label='Open Scene',
+                                command=self.openScene)
+
+        self.menuBar.addmenuitem('File', 'command', 'Save a Scene',
+                                label='Save Scene',
+                                command=self.saveScene)
+
+        self.menuBar.addmenuitem('File', 'command', 'Save Scene as...',
+                                label='Save as...',
+                                command=self.saveAsScene)
+
+        self.menuBar.addmenuitem('File', 'separator')
+
+        self.menuBar.addmenuitem('File', 'command', 'Load Model',
+                                label='Load Model',
+                                command=self.loadModel)
+
+        self.menuBar.addmenuitem('File', 'command', 'Load Actor',
+                                label='Load Actor',
+                                command=self.loadActor)
+        
+        self.menuBar.addmenuitem('File', 'separator')
+
+        self.menuBar.addmenuitem('File', 'command', 'Import a Scene',
+                                label='Import...',
+                                command=self.importScene)
+
+        self.menuBar.addmenuitem('File', 'separator')
+        
+        self.menuBar.addmenuitem('File', 'command', 'Quit this application',
+                                label='Exit',
+                                command=self.quit)
+
+        ## Creat "Edit" on the menu and its stuff
+        self.menuBar.addmenu('Edit', 'Editting tools')
+        self.menuBar.addmenuitem('Edit', 'command', 'Un-do',
+                                label='Undo...',
+                                command=self.unDo)
+        self.menuBar.addmenuitem('Edit', 'command', 'Re-do',
+                                label='Redo...',
+                                command=self.reDo)
+        self.menuBar.addmenuitem('Edit', 'separator')
+        self.menuBar.addmenuitem('Edit', 'command', 'Deselect nodepath',
+                                label='Deselect',
+                                command=self.deSelectNode)
+        self.menuBar.addmenuitem('Edit', 'separator')
+        self.menuBar.addmenuitem('Edit', 'command', 'Add a Dummy',
+                                label='Add Dummy',
+                                command=self.addDummy)
+        self.menuBar.addmenuitem('Edit', 'command', 'Duplicate nodepath',
+                                label='Duplicate',
+                                command=self.duplicateNode)
+        self.menuBar.addmenuitem('Edit', 'command', 'Remove the nodepath',
+                                label='Remove',
+                                command=self.removeNode)
+        self.menuBar.addmenuitem('Edit', 'command', 'Show the object properties',
+                                label='Object Properties',
+                                command=self.showObjProp)
+        self.menuBar.addmenuitem('Edit', 'separator')
+        self.menuBar.addmenuitem('Edit', 'command', 'Show the Camera setting',
+                                label='Camera Setting',
+                                command=self.showCameraSetting)
+        self.menuBar.addmenuitem('Edit', 'command', 'Render setting',
+                                label='Render Setting',
+                                command=self.showRenderSetting)
+
+        ## Creat "Panel" on the menu and its stuff
+        self.menuBar.addmenu('Panel', 'Panel tools')
+        self.menuBar.addmenuitem('Panel', 'command', 'Open Side Window',
+                                label='Side Window',
+                                command=self.openSideWindow)
+        self.menuBar.addmenuitem('Panel', 'command', 'Placer Panel',
+                                label='Placer Panel',
+                                command=self.openPlacerPanel)
+        self.menuBar.addmenuitem('Panel', 'command', 'Animation Panel',
+                                label='Animation Panel',
+                                command=self.openAnimationPanel)
+        self.menuBar.addmenuitem('Panel', 'command', 'Motion Path Panel',
+                                label='Mopath Panel',
+                                command=self.openMopathPanel)
+        self.menuBar.addmenuitem('Panel', 'command', 'Lighting Panel',
+                                label='Lighting Panel',
+                                command=self.openLightingPanel)
+        self.menuBar.addmenuitem('Panel', 'command', 'Particle Panel',
+                                label='Particle Panel',
+                                command=self.openParticlePanel)
+        self.menuBar.addmenuitem('Panel', 'separator')
+        self.menuBar.addmenuitem('Panel', 'command', 'Input control Panel',
+                                label='Input device panel',
+                                command=self.openInputPanel)
+
+        self.menuBar.pack(fill=X, side = LEFT)
+
+        ## get "Menu" items in order to control the entry status
+        self.menuFile = self.menuBar.component('File-menu')
+        self.menuEdit = self.menuBar.component('Edit-menu')
+        self.menuPanel = self.menuBar.component('Panel-menu')
+
+        ## Disable entries when user doesn't select anything
+        if not self.isSelect:
+            self.menuEdit.entryconfig('Deselect', state=DISABLED)
+            self.menuEdit.entryconfig('Add Dummy', state=DISABLED)
+            self.menuEdit.entryconfig('Duplicate', state=DISABLED)
+            self.menuEdit.entryconfig('Remove', state=DISABLED)
+            self.menuEdit.entryconfig('Object Properties', state=DISABLED)
+            self.menuPanel.entryconfig('Animation Panel', state=DISABLED)
+            self.menuPanel.entryconfig('Side Window', state=DISABLED)
+
+        
+    def onDestroy(self, event):
+        #################################################################
+        # If you have open any thing, please rewrite here!
+        #################################################################
+        if taskMgr.hasTaskNamed('seMonitorSelectedNode'):
+                taskMgr.remove('seMonitorSelectedNode')
+        pass
+
+    def closeAllSubWindows(self):
+        #################################################################
+        # closeAllSubWindows(self)
+        # except side window. this function will close all sub window if there is any.
+        #################################################################
+        if self.lightingPanel != None:
+            self.lightingPanel.quit()
+        if self.placer != None:
+            self.placer.quit()
+        if self.MopathPanel != None:
+            self.MopathPanel.quit()
+
+        if self.particlePanel != None:
+            self.particlePanel.quit()
+
+        if self.controllerPanel != None:
+            self.controllerPanel.quit()
+
+        list = self.animPanel.keys()
+        for index in list:
+            self.animPanel[index].quit()
+        list = self.animBlendPanel.keys()
+        for index in list:
+            self.animBlendPanel[index].quit()
+        list = self.propertyWindow.keys()
+        for index in list:
+            self.propertyWindow[index].quit()
+        list = self.alignPanelDict.keys()
+        for index in list:
+            self.alignPanelDict[index].quit()
+        self.animPanel.clear()
+        self.animBlendPanel.clear()
+        self.propertyWindow.clear()
+        self.alignPanelDict.clear()
+
+        return
+
+    ## Processing message events
+
+    def makeDirty(self):
+        self.Dirty=1
+
+    def removeLight(self, lightNode):
+        #################################################################
+        # removeLight(self, lightNode)
+        # This function will be called when user try to remove the light from lightingPanel
+        # (by sending out the message)
+        # So, in here we will call dataHolder(AllScene) to remove the light
+        # and return a list contains the newest data of lights in he scene.
+        # Then, this function will reset the lighting list in the lightingPanel
+        #################################################################
+        list = AllScene.removeObj(lightNode)
+        if self.lightingPanel != None:
+            self.lightingPanel.updateList(list)
+        return
+        
+    def lightRename(self,oName, nName):
+        #################################################################
+        # lightRename(self,oName, nName)
+        # This function will be called when user try to rename the light from lightingPanel
+        # (by sending out the message)
+        # So, in here we will call dataHolder(AllScene) to rename the light
+        # and return a list contains the newest data of lights in he scene.
+        # Then, this function will reset the lighting list in the lightingPanel
+        #################################################################
+        list, lightNode = AllScene.rename(oName, nName)
+        if self.lightingPanel != None:
+            self.lightingPanel.updateList(list,lightNode)
+        return
+    
+    def lightSelect(self,lightName):
+        #################################################################
+        # lightSelect(self,lightName)
+        # This function will be called when user try to select the light from lightingPanel
+        # (by sending out the message)
+        # So, in here we will call dataHolder(AllScene) to get the target light node
+        # Then, this function will put this light node back into lighting
+        # panel and update the data on the panel.
+        #################################################################
+        lightNode = AllScene.getLightNode(lightName)
+        if self.lightingPanel != None:
+            self.lightingPanel.updateDisplay(lightNode)
+        return
+    
+    def addLight(self, type):
+        #################################################################
+        # addLight(self, type)
+        # This function will be called when user try to add a light from lightingPanel
+        # (by sending out the message)
+        # So, in here we will call dataHolder(AllScene) to create a default light node
+        # by the type that user assigned.
+        # Then, this function will put this light node back into lighting
+        # panel with the newest lighting list and update the data on the panel.
+        #################################################################
+        list, lightNode = AllScene.createLight(type = type)
+        if self.lightingPanel != None:
+            self.lightingPanel.updateList(list,lightNode)
+        self.makeDirty()
+        return
+    
+    def lightingPanelClose(self):
+        #################################################################
+        # lightingPanelClose(self)
+        # This function will be called when user try to close the lighting panel
+        # This function will re-config the state of the lighting panel button on the top screen
+        # And it will set the self.lightingPanel to None
+        #################################################################
+        self.menuPanel.entryconfig('Lighting Panel', state=NORMAL)
+        self.lightingPanel = None
+        return
+    
+    def openPropertyPanel(self, nodePath = None):
+        #################################################################
+        # openPropertyPanel(self, nodePath = None)
+        # This function will be called when user try to open a property window
+        # for one specific node in the scene.
+        # Here we will call dataHolder to get the basic properties
+        # we would like to let user to see and cange.
+        # And then we pass those information into propertyWindow
+        #################################################################
+        type, info = AllScene.getInfoOfThisNode(nodePath)
+        name = nodePath.getName()
+        if not self.propertyWindow.has_key(name):
+            self.propertyWindow[name] = propertyWindow(nodePath, type,info )
+        pass
+
+    def closePropertyWindow(self, name):
+        if self.propertyWindow.has_key(name):
+            del self.propertyWindow[name]
+        return
+
+    def openMetadataPanel(self,nodePath=None):
+        print nodePath
+        self.MetadataPanel=MetadataPanel(nodePath)
+        pass
+
+    def duplicate(self, nodePath = None):
+        #################################################################
+        # duplicate(self, nodePath = None)
+        # This function will be called when user try to open the duplication window
+        #################################################################
+        print '----Duplication!!'
+        if nodePath != None:
+            self.duplicateWindow = duplicateWindow(nodePath = nodePath)
+        pass
+
+    def remove(self, nodePath = None):
+        #################################################################
+        # remove(self, nodePath = None)
+        # This function will be called when user try to delete a node from scene
+        #
+        # For safty issue,
+        # we will do deselect first then remove the certain node.
+        #
+        #################################################################
+        if nodePath==None:
+            if self.nodeSelected == None:
+                return
+            nodePath = self.nodeSelected
+        self.deSelectNode()
+        if AllScene.isLight(nodePath.getName()):
+            self.removeLight(nodePath)
+        else:
+            AllScene.removeObj(nodePath)
+        pass
+
+    def addDummyNode(self, nodepath = None):
+        #################################################################
+        # addDummyNode(self, nodepath = None)
+        # This function will be called when user try to create a dummy node into scene
+        # 
+        # Here we will call dataHolder to create a dummy node
+        # and reparent it to the nodePath that user has assigned.
+        #
+        #################################################################
+        AllScene.addDummyNode(nodepath)
+        self.makeDirty()
+        pass
+
+    def addCollisionObj(self, nodepath = None):
+        #################################################################
+        # addCollisionObj(self, nodepath = None)
+        # This function will be called when user try to create a collision object into the scene
+        # 
+        # Here we will call collisionWindow to ask user what kind of collision objects they want to have.
+        # Then, send the information and generated collision object to dataHolder to finish the whole process
+        # and reparent it to the nodePath that user has assigned.
+        #
+        #################################################################
+        self.collisionWindow = collisionWindow(nodepath)
+        pass
+
+    def setAsReparentTarget(self, nodepath = None):
+        #################################################################
+        # setAsReparentTarget(self, nodepath = None)
+        # This function will be called when user select a nodePaht
+        # and want to reparent other node under it. (Drom side window pop-up nemu)
+        #################################################################
+        SEditor.setActiveParent(nodepath)
+        return
+
+    def reparentToNode(self, nodepath = None):
+        #################################################################
+        # reparentToNode(self, nodepath = None)
+        # This function will be call when user try to reparent a node to
+        # that node he selected as a reparent target before.
+        #
+        # The whole reparent process is handled by seSession,
+        # which is tunned from DirectSession
+        #
+        #################################################################
+        SEditor.reparent(nodepath, fWrt = 1)
+        return
+    
+    def openPlacerPanel(self, nodePath = None):
+        #################################################################
+        # openPlacerPanel(self, nodePath = None)
+        # This function will be call when user try to open a placer panel.
+        # This call will only success if there is no other placer panel been activated
+        #################################################################
+        if(self.placer==None):
+            self.placer = Placer()
+            self.menuPanel.entryconfig('Placer Panel', state=DISABLED)          
+        return
+
+    def closePlacerPanel(self):
+        #################################################################
+        # closePlacerPanel(self)
+        # This function will be called when user close the placer panel.
+        # Here we will reset the self.placer back to None.
+        # (You can think this is just like a reference count)
+        #################################################################
+        self.placer = None
+        self.menuPanel.entryconfig('Placer Panel', state=NORMAL)
+        return
+        
+    def openAnimPanel(self, nodePath = None):
+        #################################################################
+        # openAnimPanel(self, nodePath = None)
+        # This function will be called when user tries to open an Animation Panel
+        # This will generated a panel and put it
+        # into a dictionary using the actor's name as an index.
+        # So, if there already has an animation panel for the target actor,
+        # it won't allow user to open another one.
+        #################################################################
+        name = nodePath.getName()
+        if AllScene.isActor(name):
+            if self.animPanel.has_key(name):
+                print '---- You already have an animation panel for this Actor!'
+                return
+            else:
+                Actor = AllScene.getActor(name)
+                self.animPanel[name] = seAnimPanel.AnimPanel(aNode=Actor)
+                pass
+
+    def openMoPathPanel(self, nodepath = None):
+        #################################################################
+        # openMoPathPanel(self, nodepath = None)
+        # This function will open a Motion Path Recorder for you.
+        #################################################################
+        if self.MopathPanel == None:
+            self.MopathPanel = MopathRecorder()
+        pass
+
+    def mopathClosed(self):
+        self.MopathPanel = None
+        return
+
+    def changeName(self, nodePath, nName):
+        #################################################################
+        # changeName(self, nodePath, nName)
+        # This function will be called when user tries to change the name of the node
+        #################################################################
+        oName = nodePath.getName() # I need this line in order to check the obj name in the control panel.
+        AllScene.rename(nodePath,nName)
+        
+        # reset the list in the controller panel if it has been opened.
+        if (self.controllerPanel) != None:
+            list = AllScene.getAllObjNameAsList()
+            self.controllerPanel.resetNameList(list = list, name = oName, nodePath = nodePath)
+        return
+
+    # Take care things under File menu
+    def newScene(self):
+        #################################################################
+        # newScene(self)
+        # This function will clear whole stuff in the scene
+        # and will reset the application title to "New Scene"
+        #################################################################
+        self.closeAllSubWindows() ## Close all sub window
+        if(self.CurrentFileName):
+            currentF=Filename(self.CurrentFileName)
+            self.CurrentFileName=None
+            AllScene.resetAll()
+            currentModName=currentF.getBasenameWoExtension()
+            # Let us actually remove the scene from sys modules... this is done because every scene is loaded as a module
+            # And if we reload a scene python wont reload since its already in sys.modules... and hence we delete it
+            # If there is ever a garbage colleciton bug..this might be a point to look at 
+            if sys.modules.has_key(currentModName):
+                del sys.modules[currentModName]
+                print sys.getrefcount(AllScene.theScene)
+                del AllScene.theScene
+        else:
+            AllScene.resetAll()
+        self.parent.title('Scene Editor - New Scene')
+        pass
+
+    def openScene(self):
+        #################################################################
+        # openScene(self)
+        #################################################################
+        # In the future try and provide merging of two scenes
+        
+        if(self.CurrentFileName or self.Dirty):
+            saveScene = tkMessageBox._show("Load scene","Save the current scene?",icon = tkMessageBox.QUESTION,type = tkMessageBox.YESNOCANCEL)
+            if (saveScene == "yes"):
+                self.saveScene()
+            elif (saveScene == "cancel"):
+                return
+        
+        self.closeAllSubWindows() ## Close all sub window
+        if(self.CurrentFileName):
+            currentF=Filename(self.CurrentFileName)
+            AllScene.resetAll()
+            currentModName=currentF.getBasenameWoExtension()
+            # Let us actually remove the scene from sys modules... this is done because every scene is loaded as a module
+            # And if we reload a scene python wont reload since its already in sys.modules... and hence we delete it
+            # If there is ever a garbage colleciton bug..this might be a point to look at 
+            if sys.modules.has_key(currentModName):
+                del sys.modules[currentModName]
+                print sys.getrefcount(AllScene.theScene)
+                del AllScene.theScene
+        else:
+            AllScene.resetAll()
+
+        self.CurrentFileName = AllScene.loadScene()
+        
+        if(self.CurrentFileName==None):
+            return
+
+        thefile=Filename(self.CurrentFileName)
+        thedir=thefile.getFullpathWoExtension()
+        print "SCENE EDITOR::" + thedir
+        self.CurrentDirName=thedir
+        if self.CurrentFileName != None:
+            self.parent.title('Scene Editor - '+ Filename.fromOsSpecific(self.CurrentFileName).getBasenameWoExtension())
+        if self.lightingPanel !=None:
+            lightList=AllScene.getList()
+            self.lightingPanel.updateList(lightList)
+        messenger.send('SGE_Update Explorer',[render])
+        
+
+        # Close the side window in order to reset all world settings to fit the scene we have loaded.
+        self.sideWindow.quit()
+
+        # Try to re-open the side window again
+        while self.sideWindow == None:
+            wColor = base.getBackgroundColor()
+            self.worldColor[0] = wColor.getX()
+            self.worldColor[1] = wColor.getY()
+            self.worldColor[2] = wColor.getZ()
+            self.worldColor[3] = wColor.getW()
+            self.lightEnable = 1
+            self.ParticleEnable = 1
+            self.collision = 1
+            self.openSideWindow()
+
+    def saveScene(self):
+        #################################################################
+        # saveScene(self)
+        # If this is an open file call saveAsScene
+        # or else instantiate FileSaver from seFileSaver.py and pass it the filename
+        # If this filename exists in sys.modules you cannot use it
+        #################################################################
+
+        if(self.CurrentFileName):
+            f=FileSaver()       
+            f.SaveFile(AllScene,self.CurrentFileName,self.CurrentDirName,1)
+            self.Dirty=0
+        else:
+            self.saveAsScene()
+        pass
+
+    def saveAsBam(self):
+        fileName = tkFileDialog.asksaveasfilename(filetypes = [("BAM",".bam")],title = "Save Scenegraph as Bam file")
+        theScene=render.find("**/Scene")
+        if not theScene is None:
+            theScene.writeBamFile(fileName)
+        else:
+            render.writeBamFile(fileName+".bad")
+        print " Scenegraph saved as :" +str(fileName)
+
+    def loadFromBam(self):
+        fileName = tkFileDialog.askopenfilename(filetypes = [("BAM",".bam")],title = "Load Scenegraph from Bam file")
+        if not fileName is None:
+            d=path(fileName)
+            scene=loader.loadModel(d.relpath())
+            scene.reparentTo(render)
+
+    def saveAsScene(self):
+        #################################################################
+        # saveAsScene(self)
+        # Ask for filename using a file save dialog
+        # If this filename exists in sys.modules you cannot use it
+        # Instantiate FileSaver from seFileSaver.py and pass it the filename
+        #################################################################
+
+        fileName = tkFileDialog.asksaveasfilename(filetypes = [("PY","py")],title = "Save Scene")
+        if(not fileName):
+            return
+        fCheck=Filename(fileName)
+        #print fCheck.getBasenameWoExtension()
+        ###############################################################################
+        # !!!!! See if a module exists by this name... if it does you cannot use this filename !!!!!
+        ###############################################################################
+        if(sys.modules.has_key(fCheck.getBasenameWoExtension())):
+            tkMessageBox.showwarning(
+            "Save file",
+            "Cannot save with this name because there is a system module with the same name. Please resave as something else."
+                )
+
+            return
+        self.CurrentDirName=fileName
+        fileName=fileName+".py"
+        f=FileSaver()
+        self.CurrentFileName=fileName
+        f.SaveFile(AllScene,fileName,self.CurrentDirName,0)
+        self.Dirty=0
+        self.parent.title('Scene Editor - '+ Filename.fromOsSpecific(self.CurrentFileName).getBasenameWoExtension())
+        pass
+
+    def loadModel(self):
+        #################################################################
+        # loadModel(self)
+        # This function will be called when user tries to load a model into the scene.
+        # Here we will pop-up a dialog to ask user which model file should be loaded in.
+        # Then, pass the path to dataHolder to load the model in.
+        #################################################################
+        modelFilename = askopenfilename(
+            defaultextension = '.egg',
+            filetypes = (('Egg Files', '*.egg'),
+                         ('Bam Files', '*.bam'),
+                         ('All files', '*')),
+            initialdir = '.',
+            title = 'Load New Model',
+            parent = self.parent)
+        if modelFilename:
+            self.makeDirty()
+            if not AllScene.loadModel(modelFilename, Filename.fromOsSpecific(modelFilename)):
+                print '----Error! No Such Model File!'
+        pass
+
+    def loadActor(self):
+        #################################################################
+        # loadActor(self)
+        # This function will be called when user tries to load an Actor into the scene.
+        # Here we will pop-up a dialog to ask user which Actor file should be loaded in.
+        # Then, pass the path to dataHolder to load the Actor in.
+        #################################################################
+        ActorFilename = askopenfilename(
+            defaultextension = '.egg',
+            filetypes = (('Egg Files', '*.egg'),
+                         ('Bam Files', '*.bam'),
+                         ('All files', '*')),
+            initialdir = '.',
+            title = 'Load New Actor',
+            parent = self.parent)
+
+        
+        if ActorFilename:
+            self.makeDirty()
+            if not AllScene.loadActor(ActorFilename, Filename.fromOsSpecific(ActorFilename)):
+                print '----Error! No Such Model File!'
+        pass
+
+    def importScene(self):
+        self.makeDirty()
+        print '----God bless you Please Import!'
+        pass
+
+
+    ## Take care those things under Edit nemu
+    def unDo(self):
+        pass
+    
+    def reDo(self):
+        pass
+
+    def deSelectNode(self, nodePath=None):
+        #################################################################
+        # deSelectNode(self, nodePath=None)
+        # This function will deselect the node which we have selected currently.
+        # This will also remove the monitor task which monitor selected object's
+        # position, orientation and scale each frame.
+        #################################################################
+        if nodePath != None:
+            self.seSession.deselect(nodePath)
+        if self.isSelect:
+            self.isSelect = False
+            #if self.nodeSelected != None:
+            #    self.nodeSelected.hideBounds()
+            self.nodeSelected =None
+            self.menuEdit.entryconfig('Deselect', state=DISABLED)
+            self.menuEdit.entryconfig('Add Dummy', state=DISABLED)
+            self.menuEdit.entryconfig('Duplicate', state=DISABLED)
+            self.menuEdit.entryconfig('Remove', state=DISABLED)
+            self.menuEdit.entryconfig('Object Properties', state=DISABLED)
+            if self.sideWindowCount==1:
+                self.sideWindow.SGE.deSelectTree()
+            if taskMgr.hasTaskNamed('seMonitorSelectedNode'):
+                taskMgr.remove('seMonitorSelectedNode')
+            return
+        pass
+
+    def addDummy(self):
+        #################################################################
+        # addDummy(self)
+        # This function will do nothing but call other function
+        # to add a dummy into the scene.
+        #
+        # Ok... this is really redundancy... 
+        #
+        #################################################################
+        self.addDummyNode(self.nodeSelected)
+        pass
+
+    def duplicateNode(self):
+        #################################################################
+        # duplicateNode(self)
+        # This function will do nothing but call other function
+        # to open the duplication window.
+        #
+        # Ok... this is really redundancy... 
+        #
+        #################################################################
+        if self.nodeSelected!=None:
+            self.duplicate(self.nodeSelected)
+        pass
+
+    def removeNode(self):
+        #################################################################
+        # removeNode(self)
+        # This function will do nothing but call other function
+        # to remove the current selected node..
+        #
+        # Ok... this is really redundancy... 
+        #
+        ################################################################
+        self.remove(self.nodeSelected)
+        pass
+
+    def showObjProp(self):
+        ################################################################
+        # showObjProp(self)
+        # This function will do nothing but call other function
+        # to open the property window of current selected node..
+        #
+        # Ok... this is really redundancy... 
+        #
+        ################################################################
+        self.openPropertyPanel(self.nodeSelected)
+        pass 
+
+    def showCameraSetting(self):
+        ################################################################
+        # showCameraSetting(self)
+        # This function will do nothing but call other function
+        # to open the property window of camera..
+        #
+        # Ok... this is really redundancy... 
+        #
+        ################################################################
+        self.openPropertyPanel(camera)
+        pass
+
+    def showRenderSetting(self):
+        '''Currently, no idea what gonna pop-out here...'''
+        pass
+
+    ## Take care those thins under Edit nemu
+    def openSideWindow(self):
+        ################################################################
+        # openSideWindow(self)
+        # This function will open the side window and set the reference number
+        # so that we can make sure there won't have two or more side windows in the same time.
+        ################################################################
+        if self.sideWindowCount==0:
+            self.sideWindow = sideWindow(worldColor = self.worldColor,
+                                         lightEnable = self.lightEnable,
+                                         ParticleEnable = self.ParticleEnable,
+                                         basedriveEnable = self.basedriveEnable,
+                                         collision = self.collision,
+                                         backface = self.backface,
+                                         texture = self.texture,
+                                         wireframe = self.wireframe,
+                                         grid = self.grid,
+                                         widgetVis = self.widgetVis,
+                                         enableAutoCamera = self.enableAutoCamera)
+            self.sideWindowCount = 1
+            self.menuPanel.entryconfig('Side Window', state=DISABLED)
+        return
+    
+    def openAnimationPanel(self):
+        ################################################################
+        # openAnimationPanel(self)
+        # This function will do nothing but call other function
+        # to open the animation window for selected node(if it is an Actor)..
+        #
+        # Ok... this is really redundancy... 
+        #
+        ################################################################
+        if AllScene.isActor(self.nodeSelected):
+            self.openAnimPanel(self.nodeSelected)
+        pass
+    
+    def openMopathPanel(self):
+        ################################################################
+        # openMopathPanel(self)
+        # This function will create a Motion Path Recorder
+        ################################################################
+        MopathPanel = MopathRecorder()
+        pass
+
+    def toggleParticleVisable(self, visable):
+        ################################################################
+        # toggleParticleVisable(self, visable)
+        # This function will be called each time user has toggled
+        # the check box of Particle visibility in the side window.
+        # The reason we keep track this is because
+        # we have to know we should show/hide the model on the new-created particle
+        ################################################################
+        self.ParticleEnable = visable
+        AllScene.toggleParticleVisable(visable)
+        return
+    
+    def openLightingPanel(self):
+        ################################################################
+        # openLightingPanel(self)
+        # open the lighting panel here.
+        # If there is already exist a lighting panel, then do nothing
+        ################################################################
+        if self.lightingPanel==None:
+            self.lightingPanel = lightingPanel(AllScene.getLightList())
+            self.menuPanel.entryconfig('Lighting Panel', state=DISABLED)
+        return
+    
+    def addParticleEffect(self,effect_name,effect,node):
+        AllScene.particleDict[effect_name]=effect
+        AllScene.particleNodes[effect_name]=node
+        if not self.ParticleEnable:
+            AllScene.particleNodes[effect_name].setTransparency(True)
+            AllScene.particleNodes[effect_name].setAlphaScale(0)
+            AllScene.particleNodes[effect_name].setBin("fixed",1)
+        return
+
+    def openParticlePanel(self):
+        if self.particlePanel != None:
+            ## There already has a Particle panel!
+            return
+        if(len(AllScene.particleDict)==0):
+            self.particlePanel=seParticlePanel.ParticlePanel()
+        else:
+            for effect in AllScene.particleDict:
+                theeffect=AllScene.particleDict[effect]
+            self.particlePanel=seParticlePanel.ParticlePanel(particleEffect=theeffect,effectsDict=AllScene.particleDict)
+
+        pass
+
+    def closeParticlePanel(self):
+        self.particlePanel = None
+        return
+    
+    def openInputPanel(self):
+        if self.controllerPanel==None:
+            list = AllScene.getAllObjNameAsList()
+            type, dataList = AllScene.getControlSetting()
+            self.controllerPanel = controllerWindow(listOfObj = list, controlType = type, dataList = dataList)
+        pass
+
+    def closeInputPanel(self):
+        self.controllerPanel = None
+        return
+
+    def requestObjFromControlW(self, name):
+        ################################################################
+        # requestObjFromControlW(self, name)
+        # Call back function
+        # Each time when user selects a node from Control Panel,
+        # this function will be called.
+        # This function will get the actual nodePath from dataHolder and then
+        # set it back into controller panel
+        ################################################################
+        node = AllScene.getObjFromSceneByName(name)
+        
+        if (self.controllerPanel) != None and (node!=None):
+            self.controllerPanel.setNodePathIn(node)
+            
+        return
+
+    def setControlSet(self, controlType, dataList):
+        if controlType == 'Keyboard':
+            self.controlTarget = dataList[0]
+            self.keyboardMapDict.clear()
+            self.keyboardMapDict = dataList[1].copy()
+            self.keyboardSpeedDict.clear()
+            self.keyboardSpeedDict = dataList[2].copy()
+        return
+
+    def startControl(self, controlType, dataList):
+        if not self.enableControl:
+            self.enableControl = True
+        else:
+            # Stop the current control setting first
+            # Also this will make sure we won't catch wrong keyboard message
+            self.stopControl(controlType)
+            self.enableControl = True
+        
+        self.setControlSet(controlType, dataList)
+        self.lastContorlTimer = globalClock.getFrameTime()
+        if controlType == 'Keyboard':
+            self.controlType = 'Keyboard'
+            self.keyControlEventDict = {}
+            self.transNodeKeyboard = self.controlTarget.attachNewNode('transformNode')
+            self.transNodeKeyboard.hide()
+            for index in self.keyboardMapDict:
+                self.keyControlEventDict[index] = 0
+                self.accept(self.keyboardMapDict[index], lambda a = index:self.keyboardPushed(a))
+                self.accept(self.keyboardMapDict[index]+'-up', lambda a = index:self.keyboardReleased(a))
+        return
+
+    def stopControl(self, controlType):
+        if not self.enableControl:
+            return
+        if controlType == 'Keyboard':
+            self.enableControl = False
+            for index in self.keyboardMapDict:
+                self.ignore(self.keyboardMapDict[index])
+                self.ignore(self.keyboardMapDict[index]+'-up')
+            taskMgr.remove("KeyboardControlTask")
+            self.transNodeKeyboard.removeNode()
+        return
+
+    def keyboardPushed(self, key):
+        self.keyControlEventDict[key] = 1
+        if not taskMgr.hasTaskNamed("KeyboardControlTask"):
+            self.keyboardLastTimer = globalClock.getFrameTime()
+            taskMgr.add(self.keyboardControlTask, "KeyboardControlTask")
+        return
+
+    def keyboardReleased(self, key):
+        self.keyControlEventDict[key] = 0
+        for index in self.keyControlEventDict:
+            if self.keyControlEventDict[index] == 1:
+                return
+        if taskMgr.hasTaskNamed("KeyboardControlTask"):
+            taskMgr.remove("KeyboardControlTask")
+        return
+
+    def keyboardControlTask(self, task):
+        newTimer = globalClock.getFrameTime()
+        delta = newTimer - self.keyboardLastTimer
+        self.keyboardLastTimer = newTimer
+        pos = self.controlTarget.getPos()
+        hpr = self.controlTarget.getHpr()
+        scale = self.controlTarget.getScale()
+        self.transNodeKeyboard.setPosHpr((self.keyControlEventDict['KeyRight']*self.keyboardSpeedDict['SpeedRight']-self.keyControlEventDict['KeyLeft']*self.keyboardSpeedDict['SpeedLeft'])*delta,
+                                         (self.keyControlEventDict['KeyForward']*self.keyboardSpeedDict['SpeedForward']-self.keyControlEventDict['KeyBackward']*self.keyboardSpeedDict['SpeedBackward'])*delta,
+                                         (self.keyControlEventDict['KeyUp']*self.keyboardSpeedDict['SpeedUp']-self.keyControlEventDict['KeyDown']*self.keyboardSpeedDict['SpeedDown'])*delta,
+                                         (self.keyControlEventDict['KeyTurnLeft']*self.keyboardSpeedDict['SpeedTurnLeft']-self.keyControlEventDict['KeyTurnRight']*self.keyboardSpeedDict['SpeedTurnRight'])*delta,
+                                         (self.keyControlEventDict['KeyTurnUp']*self.keyboardSpeedDict['SpeedTurnUp']-self.keyControlEventDict['KeyTurnDown']*self.keyboardSpeedDict['SpeedTurnDown'])*delta,
+                                         (self.keyControlEventDict['KeyRollLeft']*self.keyboardSpeedDict['SpeedRollLeft']-self.keyControlEventDict['KeyRollRight']*self.keyboardSpeedDict['SpeedRollRight'])*delta)
+        newPos = self.transNodeKeyboard.getPos(self.controlTarget.getParent())
+        newHpr = self.transNodeKeyboard.getHpr(self.controlTarget.getParent())
+        overAllScale = self.keyControlEventDict['KeyScaleUp']*self.keyboardSpeedDict['SpeedScaleUp']-self.keyControlEventDict['KeyScaleDown']*self.keyboardSpeedDict['SpeedScaleDown']
+        newScale = Point3(scale.getX() + (overAllScale + self.keyControlEventDict['KeyScaleXUp']*self.keyboardSpeedDict['SpeedScaleXUp'] - self.keyControlEventDict['KeyScaleXDown']*self.keyboardSpeedDict['SpeedScaleXDown'])*delta,
+                          scale.getY() + (overAllScale + self.keyControlEventDict['KeyScaleYUp']*self.keyboardSpeedDict['SpeedScaleYUp'] - self.keyControlEventDict['KeyScaleYDown']*self.keyboardSpeedDict['SpeedScaleYDown'])*delta,
+                          scale.getZ() + (overAllScale + self.keyControlEventDict['KeyScaleZUp']*self.keyboardSpeedDict['SpeedScaleZUp'] - self.keyControlEventDict['KeyScaleZDown']*self.keyboardSpeedDict['SpeedScaleZDown'])*delta
+                          )
+        self.controlTarget.setPos(newPos.getX(), newPos.getY() , newPos.getZ())
+        self.controlTarget.setHpr(newHpr.getX(), newHpr.getY() , newHpr.getZ())
+        self.controlTarget.setScale(newScale.getX(),newScale.getY(),newScale.getZ())
+        self.transNodeKeyboard.setPosHpr(0,0,0,0,0,0)
+        return Task.cont
+    
+    ## Misc
+    ##### This one get the event from SGE (Scene Graph Explorer) and Picking
+    def selectNode(self, nodePath=None, callBack = True):
+        ################################################################
+        # selectNode(self, nodePath=None, callBack = True)
+        # This will be called when user try to select nodes from the
+        # side window.
+        # It will also call seSession to select this node in order to keep data's consistency
+        ################################################################
+        if nodePath==None:
+            self.isSelect = False
+            self.nodeSelected =None
+            if taskMgr.hasTaskNamed('seMonitorSelectedNode'):
+                taskMgr.remove('seMonitorSelectedNode')
+            return
+        else:
+            self.isSelect = True
+            #if self.nodeSelected != None:
+            #    self.nodeSelected.hideBounds()
+            self.nodeSelected = nodePath
+            #self.nodeSelected.showBounds()
+            self.menuEdit.entryconfig('Deselect', state=NORMAL)
+            self.menuEdit.entryconfig('Add Dummy', state=NORMAL)
+            self.menuEdit.entryconfig('Duplicate', state=NORMAL)
+            self.menuEdit.entryconfig('Remove', state=NORMAL)
+            self.menuEdit.entryconfig('Object Properties', state=NORMAL)
+            if callBack:
+                self.seSession.select(nodePath,fResetAncestry=1)
+            messenger.send('SGE_Update Explorer',[render])
+            if not taskMgr.hasTaskNamed('seMonitorSelectedNode'):
+                self.oPos = self.nodeSelected.getPos()
+                self.oHpr = self.nodeSelected.getHpr()
+                self.oScale = self.nodeSelected.getScale()
+                taskMgr.add(self.monitorSelectedNodeTask, 'seMonitorSelectedNode')
+            return
+        pass
+
+    def selectFromScene(self, nodePath=None, callBack=True):
+        ################################################################
+        # selectFromScene(self, nodePath=None, callBack = True)
+        # This will be called when user try to select nodes from the
+        # scene. (By picking)
+        # Actually this will be called by seSession
+        # The reason we make two selections is we don't want they call each other and never stop...
+        ################################################################
+        if nodePath==None:
+            self.isSelect = False
+            self.nodeSelected =None
+            if taskMgr.hasTaskNamed('seMonitorSelectedNode'):
+                taskMgr.remove('seMonitorSelectedNode')
+            return
+        else:
+            self.isSelect = True
+            #if self.nodeSelected != None:
+            #    self.nodeSelected.hideBounds()
+            self.nodeSelected = nodePath
+            #self.nodeSelected.showBounds()
+            self.menuEdit.entryconfig('Deselect', state=NORMAL)
+            self.menuEdit.entryconfig('Add Dummy', state=NORMAL)
+            self.menuEdit.entryconfig('Duplicate', state=NORMAL)
+            self.menuEdit.entryconfig('Remove', state=NORMAL)
+            self.menuEdit.entryconfig('Object Properties', state=NORMAL)
+            self.sideWindow.SGE.selectNodePath(nodePath,callBack)
+            messenger.send('SGE_Update Explorer',[render])
+            if not taskMgr.hasTaskNamed('seMonitorSelectedNode'):
+                self.oPos = self.nodeSelected.getPos()
+                self.oHpr = self.nodeSelected.getHpr()
+                self.oScale = self.nodeSelected.getScale()
+                taskMgr.add(self.monitorSelectedNodeTask, 'seMonitorSelectedNode')
+            return
+        pass
+
+    def monitorSelectedNodeTask(self, task):
+        ################################################################
+        # monitorSelectedNodeTask(self, task)
+        # This is a function which will keep tracking
+        # the position, orientation and scale data of selected node and update the display on the screen.
+        # Alos, it will send out message to sychronize the data in the placer and property window.
+        ################################################################
+        if self.nodeSelected != None:
+            pos = self.nodeSelected.getPos()
+            hpr = self.nodeSelected.getHpr()
+            scale = self.nodeSelected.getScale()
+            if ((self.oPos != pos )or(self.oScale != scale)or(self.oHpr != hpr)):
+                messenger.send('forPorpertyWindow'+self.nodeSelected.getName(),[pos, hpr, scale])
+                messenger.send('placerUpdate')
+                self.oPos = pos
+                self.oScale = scale
+                self.oHpr = hpr
+                self.posLabel['text'] = "Position   : X: %2.2f Y: %2.2f Z: %2.2f"%(pos.getX(), pos.getY(),pos.getZ())
+                self.hprLabel['text'] = "Orientation: H: %2.2f P: %2.2f R: %2.2f"%(hpr.getX(), hpr.getY(),hpr.getZ())
+                self.scaleLabel['text'] = "Scale      : X: %2.2f Y: %2.2f Z: %2.2f"%(scale.getX(), scale.getY(),scale.getZ())
+        return Task.cont
+
+    def deselectFromScene(self):
+        ################################################################
+        # deselectFromScene(self)
+        # This function will do nothing but call other function
+        # to delete selected node...
+        #
+        # Ok... this is really redundancy... 
+        #
+        ################################################################
+        self.deSelectNode(self.nodeSelected)
+        messenger.send('SGE_Update Explorer',[render])
+
+    ##### Take care the even quest from Side Window
+    def lightToggle(self):
+        ################################################################
+        # lightToggle(self)
+        # This function will do nothing but call other function
+        # to toggle the light...
+        ################################################################
+        self.makeDirty()
+        AllScene.toggleLight()
+        return
+    
+    def sideWindowClose(self,worldColor,lightEnable,ParticleEnable, basedriveEnable,collision,
+                        backface, texture, wireframe, grid, widgetVis, enableAutoCamera):
+        ################################################################
+        # sideWindowClose(self,worldColor,lightEnable,ParticleEnable, basedriveEnable,collision,
+        #                 backface, texture, wireframe, grid, widgetVis, enableAutoCamera):
+        # This function will be called when user close the side window.
+        # Here we will restore all parameters about world setting back in the sceneEditor.
+        # So, when next time people recall the side window, it will still keep the same world setting.
+        ################################################################
+        if self.sideWindowCount==1:
+            self.worldColor = worldColor
+            self.lightEnable = lightEnable
+            self.ParticleEnable = ParticleEnable
+            self.basedriveEnable = basedriveEnable
+            self.collision = collision
+            self.backface = backface
+            self.texture = texture
+            self.wireframe = wireframe
+            self.grid = grid
+            self.enableAutoCamera = enableAutoCamera
+            self.widgetVis = widgetVis
+            self.sideWindowCount=0
+            self.sideWindow = None
+            self.menuPanel.entryconfig('Side Window', state=NORMAL)
+            return
+
+    ## Process message from Duplication Window
+    def duplicationObj(self, nodePath, pos, hpr, scale, num):
+        ################################################################
+        # duplicationObj(self, nodePath, pos, hpr, scale, num)
+        # This function will do nothing but call other function
+        # to duplicate selected node...
+        #
+        # Ok... this is really redundancy... 
+        #
+        ################################################################
+        AllScene.duplicateObj(nodePath, pos, hpr, scale, num)
+        return
+
+    ## Process message from Animation Panel
+    def animationLoader(self, nodePath, Dic):
+        name = nodePath.getName()
+        AllScene.loadAnimation(name, Dic)
+        return
+
+    def animationRemove(self, nodePath, name):
+        AllScene.removeAnimation(nodePath.getName(),name)
+        return
+
+    def animPanelClose(self, name):
+        if self.animPanel.has_key(name):
+            del self.animPanel[name]
+        return
+
+    ### Blend Animation Panel
+    def openBlendAnimPanel(self, nodePath=None):
+        ################################################################
+        # openBlendAnimPanel(self, nodePath=None)
+        # This function will get the user defined blending animation data from dataHolder.
+        # And then open a blendAnimPanel by passing those data in.
+        ################################################################
+        name = nodePath.getName()
+        if AllScene.isActor(name):
+            if self.animBlendPanel.has_key(name):
+                print '---- You already have an Blend Animation Panel for this Actor!'
+                return
+            else:
+                Actor = AllScene.getActor(name)
+                Dict = AllScene.getBlendAnimAsDict(name)
+                self.animBlendPanel[name] = BlendAnimPanel(aNode=Actor, blendDict=Dict)
+                pass
+        return
+
+    def animBlendPanelSave(self, actorName, blendName, animNameA, animNameB, effect):
+        ################################################################
+        # animBlendPanelSave(self, actorName, blendName, animNameA, animNameB, effect)
+        # This function will call dataHolder to save the blended animation.
+        # Then, it will reset the newest blended animation list back to animBlendPanel
+        ################################################################
+        dict = AllScene.saveBlendAnim(actorName, blendName, animNameA, animNameB, effect)
+        self.animBlendPanel[actorName].setBlendAnimList(dict)
+        return
+
+    def animBlendPanelRemove(self, actorName, blendName):
+        ################################################################
+        # animBlendPanelRemove(self, actorName, blendName)
+        # This function will call dataHolder to remove the blended animation.
+        # Then, it will reset the newest blended animation list back to animBlendPanel
+        ################################################################
+        dict = AllScene.removeBlendAnim(actorName, blendName)
+        self.animBlendPanel[actorName].setBlendAnimList(dict, True)
+        return
+
+    def animBlendPanelRename(self, actorName, nName, oName, animNameA, animNameB, effect):
+        ################################################################
+        # animBlendPanelRename(self, actorName, nName, oName, animNameA, animNameB, effect)
+        # This function will call dataHolder to rename the blended animation.
+        # Then, it will reset the newest blended animation list back to animBlendPanel
+        ################################################################
+        dict = AllScene.renameBlendAnim(actorName, nName, oName, animNameA, animNameB, effect)
+        self.animBlendPanel[actorName].setBlendAnimList(dict)
+        return
+
+    def animBlendPanelClose(self, name):
+        ################################################################
+        # animBlendPanelClose(self, name)
+        # This function will be called when Blend panel has been closed.
+        # Here we will reset the reference dictionary so it can be open again.
+        ################################################################
+        if self.animBlendPanel.has_key(name):
+            del self.animBlendPanel[name]
+        return
+
+    ## Process message from SEditor object
+    def toggleWidgetVis(self):
+        ################################################################
+        # toggleWidgetVis(self)
+        # This function will be called when user use the hot-key to change the
+        # world setting. (From seSession)
+        # In this function we will restore the change and let side window know
+        # the hot-key ahs been pushed.
+        ################################################################
+        if self.sideWindow != None:
+            self.sideWindow.toggleWidgetVisFromMainW()
+        else:
+            self.widgetVis = (self.widgetVis+1)%2
+
+    def toggleBackface(self):
+        ################################################################
+        # toggleBackface(self)
+        # This function will be called when user use the hot-key to change the
+        # world setting. (From seSession)
+        # In this function we will restore the change and let side window know
+        # the hot-key ahs been pushed.
+        ################################################################
+        if self.sideWindow != None:
+            self.sideWindow.toggleBackfaceFromMainW()
+        else:
+            self.backface = (self.backface+1)%2
+
+    def toggleTexture(self):
+        ################################################################
+        # toggleTexture(self)
+        # This function will be called when user use the hot-key to change the
+        # world setting. (From seSession)
+        # In this function we will restore the change and let side window know
+        # the hot-key ahs been pushed.
+        ################################################################
+        if self.sideWindow != None:
+            self.sideWindow.toggleTextureFromMainW()
+        else:
+            self.texture = (self.texture+1)%2
+
+    def toggleWireframe(self):
+        ################################################################
+        # toggleWireframe(self)
+        # This function will be called when user use the hot-key to change the
+        # world setting. (From seSession)
+        # In this function we will restore the change and let side window know
+        # the hot-key ahs been pushed.
+        ################################################################
+        if self.sideWindow != None:
+            self.sideWindow.toggleWireframeFromMainW()
+        else:
+            self.wireframe = (self.wireframe+1)%2
+
+    def openAlignPanel(self, nodePath=None):
+        name = nodePath.getName()
+        if not self.alignPanelDict.has_key(name):
+            list = AllScene.getAllObjNameAsList()
+            if name in list:
+                list.remove(name)
+            else:
+                return
+            self.alignPanelDict[name] = AlignTool(nodePath = nodePath, list = list)
+        return
+
+    def closeAlignPanel(self, name=None):
+        if self.alignPanelDict.has_key(name):
+            del self.alignPanelDict[name]
+
+    def alignObject(self, nodePath, name, list):
+        target = AllScene.getObjFromSceneByName(name)
+        pos = target.getPos()
+        hpr = target.getHpr()
+        scale = target.getScale()
+        if list[0]: # Align X
+            nodePath.setX(pos.getX())
+        if list[1]: # Align Y
+            nodePath.setY(pos.getY())
+        if list[2]: # Align Z
+            nodePath.setZ(pos.getZ())
+        if list[3]: # Align H
+            nodePath.setH(hpr.getX())
+        if list[4]: # Align P
+            nodePath.setP(hpr.getY())
+        if list[5]: # Align R
+            nodePath.setR(hpr.getZ())
+        if list[6]: # Scale X
+            nodePath.setSx(scale.getX())
+        if list[7]: # Scale Y
+            nodePath.setSy(scale.getY())
+        if list[8]: # Scale Z
+            nodePath.setSz(scale.getZ())
+        return
+
+    ### Event from Motion Path Panel
+    def requestCurveList(self, nodePath,name):
+        curveList = AllScene.getCurveList(nodePath)
+        messenger.send('curveListFor'+name, [curveList])
+
+
+    ## Steal from DirectSession...
+    def flash(self, nodePath = 'None Given'):
+        """ Highlight an object by setting it red for a few seconds """
+        # Clean up any existing task
+        taskMgr.remove('flashNodePath')
+        # Spawn new task if appropriate
+        if nodePath == 'None Given':
+            # If nothing specified, try selected node path
+            nodePath = self.selected.last
+        if nodePath:
+            if nodePath.hasColor():
+                doneColor = nodePath.getColor()
+                flashColor = VBase4(1) - doneColor
+                flashColor.setW(1)
+            else:
+                doneColor = None
+                flashColor = VBase4(1,0,0,1)
+            # Temporarily set node path color
+            nodePath.setColor(flashColor)
+            # Clean up color in a few seconds
+            t = taskMgr.doMethodLater(1.5,
+                                      # This is just a dummy task
+                                      self.flashDummy,
+                                      'flashNodePath')
+            t.nodePath = nodePath
+            t.doneColor = doneColor
+            # This really does all the work
+            t.uponDeath = self.flashDone
+            
+    def flashDummy(self, state):
+        # Real work is done in upon death function
+        return Task.done
+        
+    def flashDone(self,state):
+        # Return node Path to original state
+        if state.nodePath.isEmpty():
+            # Node path doesn't exist anymore, bail
+            return
+        if state.doneColor:
+            state.nodePath.setColor(state.doneColor)
+        else:
+            state.nodePath.clearColor()
+
+
+    
+
+editor = myLevelEditor(parent = tkroot)
+
+run()

+ 608 - 0
contrib/src/sceneeditor/seAnimPanel.py

@@ -0,0 +1,608 @@
+#################################################################
+# collisionWindow.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+# Import Tkinter, Pmw, and the floater code from this directory tree.
+from direct.tkwidgets.AppShell import *
+from direct.showbase.TkGlobal import *
+from tkSimpleDialog import askfloat
+import string
+import math
+import types
+from direct.task import Task
+
+FRAMES = 0
+SECONDS = 1
+
+class AnimPanel(AppShell):
+    #################################################################
+    # This class will generate a animation panel for an actor
+    # which user assigned. Inside this panel, instead of using actorInterval
+    # or just simply calling the play function in Actor, we create a task to
+    # set animation frame by frame using setPose.
+    #################################################################
+    # Override class variables
+    appname = 'Anim Panel'
+    frameWidth  = 575
+    frameHeight = 250
+    usecommandarea = 0
+    usestatusarea  = 0
+    index = 0
+    dragMode = False
+    rateList= ['1/24.0', '0.1', '0.5', '1.0', '2.0', '5.0' , '10.0']
+
+    
+    def __init__(self, aNode =  None, parent = None, **kw):
+        INITOPT = Pmw.INITOPT
+        self.id = 'AnimPanel '+ aNode.getName()
+        self.appname = self.id
+        optiondefs = (
+            ('title',               self.appname,       None),
+            ('actor',           aNode,               None),
+            ('animList',        [],      None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        self.frameHeight = 300
+        self.id = 'AnimPanel '+ aNode.getName()
+        self.nodeName = aNode.getName()
+        # Initialize the superclass
+        AppShell.__init__(self)
+
+        # Execute option callbacks
+        self.initialiseoptions(AnimPanel)
+
+        self.currTime = 0.0 # Initialize the start time
+        self.animName = None
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+
+    def createInterface(self):
+        # Handle to the toplevels interior
+        interior = self.interior()
+        menuBar = self.menuBar
+
+        menuBar.addmenu('Anim', 'Anim Panel Operations')
+        
+        # Reset all actor controls
+        menuBar.addmenuitem('File', 'command',
+                            'Load Animation',
+                            label = 'Load Animation',
+                            command = self.loadAnimation)
+        menuBar.addmenuitem('Anim', 'command',
+                            'Set actor controls to t = 0.0',
+                            label = 'Jump all to zero',
+                            command = self.resetAllToZero)
+        menuBar.addmenuitem('Anim', 'command',
+                            'Set Actor controls to end time',
+                            label = 'Jump all to end time',
+                            command = self.resetAllToEnd)
+        menuBar.addmenuitem('Anim', 'separator')
+        menuBar.addmenuitem('Anim', 'command',
+                            'Play Current Animation',
+                            label = 'Play',
+                            command = self.play)
+        menuBar.addmenuitem('Anim', 'command',
+                            'Stop Current Animation',
+                            label = 'stop',
+                            command = self.stop)
+
+        # Create a frame to hold all the actor controls
+        actorFrame = Frame(interior)
+        name_label = Label(actorFrame, text= self.nodeName,font=('MSSansSerif', 16),
+                           relief = SUNKEN, borderwidth=3)
+        name_label.place(x=5,y=5,anchor=NW)
+        Label(actorFrame, text= "Animation:", font=('MSSansSerif', 12)).place(x=140,y=5,anchor=NW)
+        Label(actorFrame, text= "Play Rate:", font=('MSSansSerif', 12)).place(x=140,y=35,anchor=NW)
+        self['animList'] = self['actor'].getAnimNames()
+        self.AnimEntry = self.createcomponent(
+            'AnimationMenu', (), None,
+            Pmw.ComboBox, (actorFrame,),
+            labelpos = W, entry_width = 20, selectioncommand = self.setAnimation,
+            scrolledlist_items = self['animList'])
+        self.AnimEntry.place(x=240,y=10,anchor=NW)
+
+        
+        self.playRateEntry = self.createcomponent(
+            'playRateMenu', (), None,
+            Pmw.ComboBox, (actorFrame,),
+            labelpos = W, entry_width = 20, selectioncommand = self.setPlayRate,
+            scrolledlist_items = self.rateList)
+        self.playRateEntry.place(x=240,y=40,anchor=NW)
+        self.playRateEntry.selectitem('1.0')
+
+        ### Loop checkbox
+        Label(actorFrame, text= "Loop:", font=('MSSansSerif', 12)).place(x=420,y=05,anchor=NW)
+
+        self.loopVar = IntVar()
+        self.loopVar.set(0)
+        self.loopButton = self.createcomponent(
+            'loopButton', (), None,
+            Checkbutton, (actorFrame,),
+            variable = self.loopVar)
+        self.loopButton.place(x=470,y=7,anchor=NW)
+
+        ### Display Frames/Seconds
+        Label(actorFrame, text= "Frame/Second:", font=('MSSansSerif', 11)).place(x=5,y=75,anchor=NW)
+
+        self.unitsVar = IntVar()
+        self.unitsVar.set(FRAMES)
+        self.displayButton = self.createcomponent(
+            'displayButton', (), None,
+            Checkbutton, (actorFrame,),
+            command = self.updateDisplay,
+            variable = self.unitsVar)
+        self.displayButton.place(x=120,y=77,anchor=NW)
+        
+        ## scale control
+        frameFrame = Frame(actorFrame, 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, to = 24, resolution = 1.0,
+            command = self.goTo, length = 500,
+            orient = HORIZONTAL, showvalue = 1)
+        self.frameControl.pack(side = LEFT, expand = 1)
+        self.frameControl.bind('<Button-1>', self.onPress)
+        self.frameControl.bind('<ButtonRelease-1>', self.onRelease)
+
+        self.maxLabel = self.createcomponent(
+            'maxLabel', (), 'sLabel',
+            Label, (frameFrame,),
+            text = 24)
+        self.maxLabel.pack(side = LEFT)
+        frameFrame.pack(side = LEFT, expand = 1, fill = X)
+        
+        ## button contorl
+        ButtomFrame = Frame(actorFrame, relief = SUNKEN, bd = 1,borderwidth=5)
+        self.toStartButton = self.createcomponent(
+            'toStart', (), None,
+            Button, (ButtomFrame,),
+            text = '<<',
+            width = 8,
+            command = self.resetAllToZero)
+        self.toStartButton.pack(side = LEFT, expand = 1, fill = X)
+        
+        self.playButton = self.createcomponent(
+            'playButton', (), None,
+            Button, (ButtomFrame,),
+            text = 'Play', width = 8,
+            command = self.play)
+        self.playButton.pack(side = LEFT, expand = 1, fill = X)
+
+        self.stopButton = self.createcomponent(
+            'stopButton', (), None,
+            Button, (ButtomFrame,),
+            text = 'Stop', width = 8, state=DISABLED,
+            command = self.stop)
+        self.stopButton.pack(side = LEFT, expand = 1, fill = X)
+
+        self.toEndButton = self.createcomponent(
+            'toEnd', (), None,
+            Button, (ButtomFrame,),
+            text = '>>',
+            width = 8,
+            command = self.resetAllToEnd)
+        self.toEndButton.pack(side = LEFT, expand = 1, fill = X)
+
+        ButtomFrame.place(anchor=NW,x=5,y=165)
+
+        self.removeButton = self.createcomponent(
+            'Remove Animation', (), None,
+            Button, (actorFrame,),
+            text = 'Remove This Animation', width = 20,
+            command = self.removeAnim)
+        self.removeButton.place(anchor=NW,x=5,y=220)
+
+        self.loadButton = self.createcomponent(
+            'Load Animation', (), None,
+            Button, (actorFrame,),
+            text = 'Load New Animation', width = 20,
+            command = self.loadAnimation)
+        self.loadButton.place(anchor=NW,x=180,y=220)
+
+        # Now pack the actor frame
+        actorFrame.pack(expand = 1, fill = BOTH)
+
+    def updateList(self):
+        #################################################################
+        # updateList(self)
+        # This function will update the list of animations that the Actor
+        # currently has into the combo box widget. 
+        #################################################################
+        self.ignore('DataH_loadFinish'+self.nodeName)
+        del self.loaderWindow
+        self['animList'] = self['actor'].getAnimNames()
+        animL = self['actor'].getAnimNames()
+        self.AnimEntry.setlist(animL)
+            
+
+    def removeAnim(self):
+        #################################################################
+        # removeAnim(self)
+        # This function will stop the animation and get the name of animation
+        # which user wish to remove from the panel. Then, it will send out
+        # a message to dataHolder to remove the target animation.
+        # And in the same time, it will start waiting a return message to
+        # make sure that target animation has been removed.
+        #################################################################
+        name = self.AnimEntry.get()
+        if taskMgr.hasTaskNamed(self.id + '_UpdateTask'):
+            self.stop()
+        self.accept('DataH_removeAnimFinish'+self.nodeName,self.afterRemove)
+        messenger.send('AW_removeAnim',[self['actor'],name])
+        return
+
+    def afterRemove(self):
+        #################################################################
+        # afterRemove(self)
+        # This function will be called once panel has received the return
+        # message from dataHolder. This function will call setList to
+        # reset the list of Animations
+        #################################################################
+        self.ignore('DataH_removeAnimFinish'+self.nodeName)
+        self['animList'] = self['actor'].getAnimNames()
+        animL = self['actor'].getAnimNames()
+        self.AnimEntry.setlist(animL)
+        print '-----',animL
+        return
+
+    def loadAnimation(self):
+        #################################################################
+        # loadAnimation(self)
+        # This function will open a dialog window to require user to input
+        # the animation he wants to load in for this actor.
+        #################################################################
+        self.loaderWindow = LoadAnimPanel(aNode=self['actor'])
+        self.accept('DataH_loadFinish'+self.nodeName,self.updateList)
+        return
+
+    def play(self):
+        #################################################################
+        # play(self)
+        # This function will be called when user click on the "play" button.
+        # First, this function will initialize all parameter that the actual
+        # play task need to run and add the play task into the taskMgr.
+        #################################################################
+        self.animName = self.AnimEntry.get()
+        if self.animName in self['animList']:
+            animName = self.AnimEntry.get()
+            self.playButton.config(state=DISABLED)
+            self.lastT = globalClock.getFrameTime()
+            taskMgr.add(self.playTask, self.id + '_UpdateTask')
+            self.stopButton.config(state=NORMAL)
+        else:
+            print '----Illegal Animaion name!!', self.animName
+        return
+
+    def playTask(self, task):
+        #################################################################
+        # playTask(self, task)
+        # This task will record time by each frame
+        # In fact it is just a clock keeper.
+        # If the current frame time over the max long of the animation,
+        # it will reset the timer.
+        # Anyway, this function will call gotoT by each frame.
+        #################################################################
+        fLoop = self.loopVar.get()
+        currT = globalClock.getFrameTime()
+        deltaT = currT - self.lastT
+        self.lastT = currT
+        if self.dragMode:
+            return Task.cont
+        self.currTime = self.currTime + deltaT
+        if (self.currTime > self.maxSeconds):
+            if fLoop:
+                self.currTime = self.currTime%self.duration
+                self.gotoT(self.currTime)
+            else:
+                self.currTime = 0.0
+                self.gotoT(0.0)
+                self.playButton.config(state=NORMAL)
+                self.stopButton.config(state=DISABLED)
+                return Task.done
+        else:
+            self.gotoT(self.currTime)    
+        return Task.cont
+
+    def stop(self):
+        #################################################################
+        # stop(self)
+        # This function will remove the play task from taskMgr when user
+        # click on the "Stop" button
+        #################################################################
+        taskMgr.remove(self.id + '_UpdateTask')
+        self.playButton.config(state=NORMAL)
+        self.stopButton.config(state=DISABLED)
+        return
+
+    def setAnimation(self, animation):
+        #################################################################
+        # setAnimation(self, animation)
+        # This function will be called each time when user change
+        # the current animation. Most important thing this function do is
+        # to recalculate all variables to fit the selected animation
+        #################################################################
+        self.animName = self.AnimEntry.get()        
+        playRate = '%0.1f' % self['actor'].getPlayRate(self.animName)
+        if playRate not in self.rateList:
+            def strCmp(a, b):
+                return cmp(eval(a), eval(b))
+            self.rateList.append(playRate)
+            self.rateList.sort(strCmp)
+            self.playRateEntry.reset(self.rateList)
+            self.playRateEntry.selectitem(playRate)
+        self.currTime = 0.0
+        self.frameControl.set(0)
+        self.updateDisplay()
+        return
+
+    def setPlayRate(self,rate):
+        #################################################################
+        # setPlayRate(self, rate)
+        # This function will be called each time when user changes the current play rate.
+        #################################################################
+        self.animName = self.AnimEntry.get()
+        if self.animName in self['animList']:
+            self['actor'].setPlayRate(eval(rate), self.animName)
+            self.updateDisplay()
+        return
+
+    def updateDisplay(self):
+        #################################################################
+        # updateDisplay(self)
+        # This function will be called whenever something has been changed
+        # on the panel. In here we will re-new all widgets on the panel to
+        # correct value.
+        #################################################################
+        self.fps = self['actor'].getFrameRate(self.animName)
+        self.duration = self['actor'].getDuration(self.animName)
+        self.maxFrame = self['actor'].getNumFrames(self.animName) - 1
+        self.maxSeconds = self.duration
+        if self.unitsVar.get() == FRAMES:
+            fromFrame = 0
+            toFrame = self.maxFrame
+            self.minLabel['text'] = fromFrame
+            self.maxLabel['text'] = toFrame
+            self.frameControl.configure(from_ = fromFrame,
+                                        to = toFrame,
+                                        resolution = 1.0)
+        else:
+            self.minLabel['text'] = '0.0'
+            self.maxLabel['text'] = "%.2f" % self.duration
+            self.frameControl.configure(from_ = 0.0, 
+                                        to = self.duration,
+                                        resolution = 0.01)
+
+    def gotoT(self,time):
+        #################################################################
+        # gotoT(self, time)
+        # calculate the right parameter which will be send to set Frame
+        # Control slider, which is the real place we play the animation.
+        #################################################################
+        if self.unitsVar.get() == FRAMES:
+            self.frameControl.set(time * self.fps)
+        else:
+            self.frameControl.set(time)
+        return
+
+    def goTo(self,frame):
+        #################################################################
+        # goto(self, frame)
+        # Call back function for the frame control slider.
+        # This function will set the animation by the value on the slider.
+        #
+        # This function is the real function we "play" the animation.
+        #
+        #################################################################
+        if self.animName in self['animList']:
+            # Convert scale value to float
+            frame = string.atof(frame)
+            # Now convert t to seconds for offset calculations
+            if self.unitsVar.get() == FRAMES:
+                frame = frame / self.fps
+            if self.dragMode:
+                # If user is clicking on the slider and is draging the bar, reset the global timer.
+                self.currTime = frame
+            self['actor'].pose(self.animName,
+                            min(self.maxFrame, int(frame * self.fps)))
+        return
+
+    def onRelease(self,frame):
+        #################################################################
+        # onRelease(self, frame)
+        # disable the dragMode when user releases the bar on the slider.
+        #################################################################
+        self.dragMode = False
+        return
+
+    def onPress(self,frame):
+        #################################################################
+        # onPress(self, frame)
+        # enable the dragMode when user press the bar on the slider.
+        #################################################################
+        self.dragMode = True
+        return
+    
+    def resetAllToZero(self):
+        #################################################################
+        # resetAllToZero(self)
+        # reset the global timer to zero and also move the slider to zero.
+        # This will also reset the actor to the zero frame of current animation
+        #################################################################
+        self.currTime = 0.0
+        self.gotoT(0)
+        return
+
+    def resetAllToEnd(self):
+        #################################################################
+        # resetAllToEnd(self)
+        # set the global timer to the end of current animation and
+        # also move the slider to the end.
+        #################################################################
+        self.currTime = self.maxSeconds
+        self.gotoT(self.duration)
+        return
+
+    def onDestroy(self, event):
+        if taskMgr.hasTaskNamed(self.id + '_UpdateTask'):
+            taskMgr.remove(self.id + '_UpdateTask')
+        self.ignore('DataH_loadFinish')
+        messenger.send('AW_close',[self.nodeName])
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+class LoadAnimPanel(AppShell):
+    #################################################################
+    # LoadAnimPanel(AppShell)
+    # This class will open a dialog to ask user to input names and
+    # file paths of animations
+    #################################################################
+    # Override class variables
+    appname = 'Load Animation'
+    frameWidth  = 575
+    frameHeight = 200
+    usecommandarea = 0
+    usestatusarea  = 0
+    index = 0
+    ## Anim name : File Path
+
+    def __init__(self, aNode =  None, parent = None, **kw):
+        INITOPT = Pmw.INITOPT
+        self.id = 'Load Animation '+ aNode.getName()
+        self.appname = self.id
+        self.animDic = {}
+        self.animList = []
+        optiondefs = (
+            ('title',               self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        self.frameHeight = 300
+        self.nodeName = aNode.getName()
+        self.Actor = aNode
+        # Initialize the superclass
+        AppShell.__init__(self)
+
+        # Execute option callbacks
+        self.initialiseoptions(LoadAnimPanel)
+
+    def createInterface(self):
+        self.menuBar.destroy()
+        interior = self.interior()
+        mainFrame = Frame(interior)
+        self.inputZone = Pmw.Group(mainFrame, tag_text='File Setting')
+        self.inputZone.pack(fill='both',expand=1)
+        settingFrame = self.inputZone.interior()
+        Label(settingFrame,text='Anim Name').place(anchor=NW,x=60,y=5)
+        Label(settingFrame,text='File Path').place(anchor=NW,x=205,y=5)
+        self.AnimName_1 = self.createcomponent(
+            'Anim Name List', (), None,
+            Pmw.ComboBox, (settingFrame,),label_text='Anim   :',
+            labelpos = W, entry_width = 10, selectioncommand = self.selectAnim,
+            scrolledlist_items = self.animList)
+        self.AnimFile_1 = Pmw.EntryField(settingFrame,value='')
+        self.AnimFile_1.component('entry').config(width=20)
+        self.AnimName_1.place(anchor=NW,x=10,y=25)
+        self.AnimFile_1.place(anchor=NW,x=140,y=25)
+        self.Browse_1 = self.createcomponent(
+            'File Browser1', (), None,
+            Button, (mainFrame,),
+            text = 'Browse...',
+            command = self.Browse_1)
+        self.Browse_1.place(anchor=NW,x=270,y=38)
+
+        self.addIntoButton = self.createcomponent(
+            'Load Add', (), None,
+            Button, (mainFrame,),
+            text = 'Add to Load',
+            command = self.addIntoList)
+        self.addIntoButton.place(anchor=NW,x=345,y=38)
+
+        att_label = Label(mainFrame, font=('MSSansSerif', 10),
+                          text= "Attention! Animations won't be loaded in before you press the 'OK' button below!")
+        att_label.place(anchor=NW,x=10,y=80)
+
+        self.button_ok = Button(mainFrame, text="OK", command=self.ok_press,width=10)
+        self.button_ok.pack(fill=BOTH,expand=0,side=RIGHT)
+
+        mainFrame.pack(expand = 1, fill = BOTH)
+
+        
+        
+    def onDestroy(self, event):
+        messenger.send('AWL_close',[self.nodeName])
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    def selectAnim(self,name):
+        #################################################################
+        # selectAnim(self, name)
+        # This function will be called if user select an animation on the list.
+        #################################################################
+        if name in self.animDic:
+            self.AnimFile_1.setvalue = self.animDic[name]
+        return
+
+    def Browse_1(self):
+        #################################################################
+        # Browse_1(self)
+        # when the browse button pused, this function will be called.
+        # Do nothing but open a file dialog for user to set the path to target file
+        # Then, set the path back to the entry on the panel.
+        #################################################################
+        AnimFilename = askopenfilename(
+            defaultextension = '.egg',
+            filetypes = (('Egg Files', '*.egg'),
+                         ('Bam Files', '*.bam'),
+                         ('All files', '*')),
+            initialdir = '.',
+            title = 'File Path for Anim 1',
+            parent = self.parent)
+        if AnimFilename:
+            self.AnimFile_1.setvalue(AnimFilename)
+        return
+
+    def addIntoList(self):
+        #################################################################
+        # addIntoList(self)
+        # This function will be called each time when user click on the
+        # "Add to Load" button. This function will read the current data
+        # on the panel into a dictionary. then reset the list of the animation
+        # name list on this panel.(not the one in the animation panel...)
+        #
+        # This function won't load any animation....
+        #
+        #################################################################
+        name = self.AnimName_1.get()
+        self.animDic[name] = Filename.fromOsSpecific(self.AnimFile_1.getvalue()).getFullpath()
+        if name in self.animList:
+            pass
+        else:
+            self.animList.append(name)
+        self.AnimName_1.setlist(self.animList)
+        print self.animDic
+        return
+
+    def ok_press(self):
+        #################################################################
+        # ok_press(Self)
+        # This functiion will be called when user click on the "OK"
+        # button. This function will send a message along with the animation
+        # file we wish to load for this actor.
+        # Then, it will close the panel itself.
+        #################################################################
+        messenger.send('AW_AnimationLoad',[self.Actor,self.animDic])
+        #print self.animDic
+        self.quit()
+        return

+ 666 - 0
contrib/src/sceneeditor/seBlendAnimPanel.py

@@ -0,0 +1,666 @@
+#################################################################
+# collisionWindow.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+# Import Tkinter, Pmw, and the floater code from this directory tree.
+from direct.tkwidgets.AppShell import *
+from direct.showbase.TkGlobal import *
+from tkSimpleDialog import askfloat
+import string
+import math
+import types
+from direct.task import Task
+
+FRAMES = 0
+SECONDS = 1
+
+
+#####################################################################################
+# BlendAnimPanel(AppShell)
+# This Panel will allow user to blend tow animations
+# that have already been loaded for this actor.
+# user can play and manipulate this blended animation
+# just like in the animation panel. And, they can save this blended animation.
+#####################################################################################
+class BlendAnimPanel(AppShell):
+    # Override class variables
+    appname = 'Blend Anim Panel'
+    frameWidth  = 575
+    frameHeight = 450
+    usecommandarea = 0
+    usestatusarea  = 0
+    index = 0
+    dragMode = False
+    blendRatio = 0
+    rateList= ['1/24.0', '0.1', '0.5', '1.0', '2.0', '5.0' , '10.0']
+    enableBlend = False
+    currentBlendName = None
+
+    
+    def __init__(self, aNode =  None, blendDict={}, parent = None, **kw):
+        INITOPT = Pmw.INITOPT
+        self.id = 'BlendAnimPanel '+ aNode.getName()
+        self.appname = self.id
+        self.actorNode = aNode
+        self.blendDict = blendDict.copy()
+        if len(blendDict)>0:
+            self.blendList = blendDict.keys()
+        else:
+            self.blendList = []
+        optiondefs = (
+            ('title',               self.appname,       None),
+            ('actor',               aNode,              None),
+            ('animList',            [],                 None),
+            ('blendAnimList',       self.blendList,          None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        self.id = 'Blend AnimPanel '+ aNode.getName()
+        self.nodeName = aNode.getName()
+        # Initialize the superclass
+        AppShell.__init__(self)
+
+        # Execute option callbacks
+        self.initialiseoptions(BlendAnimPanel)
+
+        self.currTime = 0.0
+        self.animNameA = None
+        self.animNameB = None
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+
+    def createInterface(self):
+        # Handle to the toplevels interior
+        interior = self.interior()
+        self.menuBar.destroy()
+
+        # show the actor's name
+        actorFrame = Frame(interior)
+        name_label = Label(actorFrame, text= self.nodeName,font=('MSSansSerif', 14),
+                           relief = SUNKEN, borderwidth=3)
+        name_label.pack(side = TOP, expand = False)
+        actorFrame.pack(side = TOP, expand = False, fill = X)
+
+        # Create a frame to show is there any ore-blended animation and save, edit, rename button.
+        group = Pmw.Group(interior, tag_pyclass=None)
+        actorFrame = group.interior()
+        group.pack(side = TOP, expand = False, fill = X)
+
+        Label(actorFrame, text= "Blended:", font=('MSSansSerif', 10)).pack(side=LEFT)
+        self.blendAnimEntry = self.createcomponent(
+            'Blended Animation', (), None,
+            Pmw.ComboBox, (actorFrame,),
+            labelpos = W, entry_width = 20, selectioncommand = self.setBlendAnim,
+            scrolledlist_items = self['blendAnimList'])
+        self.blendAnimEntry.pack(side=LEFT)
+
+        Label(actorFrame, text= "   ", font=('MSSansSerif', 10)).pack(side=LEFT)
+        
+        button = Button(actorFrame, text="Save", font=('MSSansSerif', 10),width = 12,
+                        command = self.saveButtonPushed).pack(side=LEFT)
+        button = Button(actorFrame, text="Remove", font=('MSSansSerif', 10),width = 12,
+                        command = self.removeButtonPushed).pack(side=LEFT)
+        button = Button(actorFrame, text="Rename", font=('MSSansSerif', 10),width = 12,
+                        command = self.renameButtonPushed).pack(side=LEFT)
+
+        actorFrame.pack(side = TOP, expand = False, fill = X)
+
+        # Create a frame to hold all the animation setting
+        group = Pmw.Group(interior, tag_pyclass=None)
+        actorFrame = group.interior()
+        group.pack(side = TOP, expand = False, fill = X)
+        Label(actorFrame, text= "Animation A:", font=('MSSansSerif', 10)).pack(side=LEFT)
+        self['animList'] = self['actor'].getAnimNames()
+        self.AnimEntryA = self.createcomponent(
+            'AnimationMenuA', (), None,
+            Pmw.ComboBox, (actorFrame,),
+            labelpos = W, entry_width = 20, entry_state = DISABLED,
+            selectioncommand = lambda name, a = 'a' : self.setAnimation(name, AB=a),
+            scrolledlist_items = self['animList'])
+        self.AnimEntryA.pack(side=LEFT)
+
+        Label(actorFrame, text= "   ", font=('MSSansSerif', 10)).pack(side=LEFT,)
+        Label(actorFrame, text= "Animation B:", font=('MSSansSerif', 10)).pack(side=LEFT)
+        self['animList'] = self['actor'].getAnimNames()
+        self.AnimEntryB = self.createcomponent(
+            'AnimationMenuB', (), None,
+            Pmw.ComboBox, (actorFrame,),
+            labelpos = W, entry_width = 20, entry_state = DISABLED,
+            selectioncommand = lambda name, a = 'b' : self.setAnimation(name, AB=a),
+            scrolledlist_items = self['animList'])
+        self.AnimEntryB.pack(side=LEFT)
+        actorFrame.pack(side = TOP, expand = False, fill = X)
+        
+        ### Blend Enable checkbox
+        actorFrame = Frame(interior, relief = SUNKEN, bd = 1)
+        Label(actorFrame, text= "Enable Blending:", font=('MSSansSerif', 10)).pack(side=LEFT,)
+        self.blendVar = IntVar()
+        self.blendVar.set(0)
+        self.blendButton = self.createcomponent(
+            'blendButton', (), None,
+            Checkbutton, (actorFrame,),
+            variable = self.blendVar,
+            command = self.toggleBlend)
+        self.blendButton.pack(side=LEFT)
+        actorFrame.pack(side = TOP, expand = False, fill = X)
+
+        ## Ratio control
+        actorFrame = Frame(interior)
+        frameFrame = Frame(actorFrame, relief = SUNKEN, bd = 1)
+        minRatioLabel = self.createcomponent(
+            'minRatioLabel', (), 'sLabel',
+            Label, (frameFrame,),
+            text = 0.00)
+        minRatioLabel.pack(side = LEFT)
+
+        self.ratioControl = self.createcomponent(
+            'ratio', (), None,
+            Scale, (frameFrame,),
+            from_ = 0.0, to = 1.0, resolution = 0.01,
+            command = self.setRatio, length = 500,
+            orient = HORIZONTAL, showvalue = 1)
+        self.ratioControl.pack(side = LEFT, expand = 1)
+        self.ratioControl.set(1.0)
+
+        self.maxRatioLabel = self.createcomponent(
+            'maxRatioLabel', (), 'sLabel',
+            Label, (frameFrame,),
+            text = 1.00)
+        self.maxRatioLabel.pack(side = LEFT)
+        frameFrame.pack(side = LEFT, expand = 1, fill = X)
+        actorFrame.pack(side = TOP, expand = True, fill = X)
+
+        ###################################################################################
+        ###################################################################################
+        actorFrame = Frame(interior)
+        Label(actorFrame, text= "Play Rate:", font=('MSSansSerif', 10)).pack(side=LEFT)
+        self.playRateEntry = self.createcomponent(
+            'playRateMenu', (), None,
+            Pmw.ComboBox, (actorFrame,),
+            labelpos = W, entry_width = 20, selectioncommand = self.setPlayRate,
+            scrolledlist_items = self.rateList)
+        self.playRateEntry.pack(side=LEFT)
+        self.playRateEntry.selectitem('1.0')
+
+        ### Loop checkbox        
+        Label(actorFrame, text= "   ", font=('MSSansSerif', 10)).pack(side=LEFT,)
+        Label(actorFrame, text= "Loop:", font=('MSSansSerif', 10)).pack(side=LEFT,)
+
+        self.loopVar = IntVar()
+        self.loopVar.set(0)
+        self.loopButton = self.createcomponent(
+            'loopButton', (), None,
+            Checkbutton, (actorFrame,),
+            variable = self.loopVar)
+        self.loopButton.pack(side=LEFT)
+        
+        actorFrame.pack(side = TOP, expand = True, fill = X)
+
+        
+        
+        ### Display Frames/Seconds
+        actorFrame = Frame(interior)
+        
+        Label(actorFrame, text= "Frame/Second:", font=('MSSansSerif', 10)).pack(side=LEFT)
+
+        self.unitsVar = IntVar()
+        self.unitsVar.set(FRAMES)
+        self.displayButton = self.createcomponent(
+            'displayButton', (), None,
+            Checkbutton, (actorFrame,),
+            command = self.updateDisplay,
+            variable = self.unitsVar)
+        self.displayButton.pack(side=LEFT)
+
+        actorFrame.pack(side = TOP, expand = True, fill = X)
+        
+        ## scale control
+        actorFrame = Frame(interior)
+        frameFrame = Frame(actorFrame, 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, to = 24, resolution = 1.0,
+            command = self.goTo, length = 500,
+            orient = HORIZONTAL, showvalue = 1)
+        self.frameControl.pack(side = LEFT, expand = 1)
+        self.frameControl.bind('<Button-1>', self.onPress)
+        self.frameControl.bind('<ButtonRelease-1>', self.onRelease)
+
+        self.maxLabel = self.createcomponent(
+            'maxLabel', (), 'sLabel',
+            Label, (frameFrame,),
+            text = 24)
+        self.maxLabel.pack(side = LEFT)
+        frameFrame.pack(side = LEFT, expand = 1, fill = X)
+        actorFrame.pack(side = TOP, expand = True, fill = X)
+        
+        ## button contorl
+        actorFrame = Frame(interior)
+        ButtomFrame = Frame(actorFrame, relief = SUNKEN, bd = 1,borderwidth=5)
+        self.toStartButton = self.createcomponent(
+            'toStart', (), None,
+            Button, (ButtomFrame,),
+            text = '<<',
+            width = 8,
+            command = self.resetAllToZero)
+        self.toStartButton.pack(side = LEFT, expand = 1, fill = X)
+        
+        self.playButton = self.createcomponent(
+            'playButton', (), None,
+            Button, (ButtomFrame,),
+            text = 'Play', width = 8,
+            command = self.play)
+        self.playButton.pack(side = LEFT, expand = 1, fill = X)
+
+        self.stopButton = self.createcomponent(
+            'stopButton', (), None,
+            Button, (ButtomFrame,),
+            text = 'Stop', width = 8, state=DISABLED,
+            command = self.stop)
+        self.stopButton.pack(side = LEFT, expand = 1, fill = X)
+
+        self.toEndButton = self.createcomponent(
+            'toEnd', (), None,
+            Button, (ButtomFrame,),
+            text = '>>',
+            width = 8,
+            command = self.resetAllToEnd)
+        self.toEndButton.pack(side = LEFT, expand = 1, fill = X)
+
+        ButtomFrame.pack(side = TOP, expand = True, fill = X)
+        actorFrame.pack(expand = 1, fill = BOTH)
+
+    def updateList(self):
+        #################################################################
+        # updateList(self)
+        # This will reset the list of all animations that this actor has
+        # to the animation entry A and B.
+        #################################################################
+        self['animList'] = self['actor'].getAnimNames()
+        animL = self['actor'].getAnimNames()
+        self.AnimEntryA.setlist(animL)
+        self.AnimEntryB.setlist(animL)
+            
+    def play(self):
+        #################################################################
+        # play(self)
+        # It works pretty much like what we have in the Animation Panel.
+        # The only different now is that we set two "pose" here.
+        # When you do the blending animation by setPose, you don't have
+        # to set them simultaneously.
+        #################################################################
+        self.animNameA = self.AnimEntryA.get()
+        self.animNameB = self.AnimEntryB.get()
+        if (self.animNameA in self['animList'])and(self.animNameB in self['animList']):
+            self.playButton.config(state=DISABLED)
+            self.lastT = globalClock.getFrameTime()
+            taskMgr.add(self.playTask, self.id + '_UpdateTask')
+            self.stopButton.config(state=NORMAL)
+        else:
+            print '----Illegal Animaion name!!', self.animNameA +  ',  '+ self.animNameB
+        return
+
+    def playTask(self, task):
+        #################################################################
+        # playTask(self, task)
+        # see play(self)
+        #################################################################
+        fLoop = self.loopVar.get()
+        currT = globalClock.getFrameTime()
+        deltaT = currT - self.lastT
+        self.lastT = currT
+        if self.dragMode:
+            return Task.cont
+        self.currTime = self.currTime + deltaT
+        if (self.currTime > self.maxSeconds):
+            if fLoop:
+                self.currTime = self.currTime%self.duration
+                self.gotoT(self.currTime)
+            else:
+                self.currTime = 0.0
+                self.gotoT(0.0)
+                self.playButton.config(state=NORMAL)
+                self.stopButton.config(state=DISABLED)
+                return Task.done
+        else:
+            self.gotoT(self.currTime)    
+        return Task.cont
+
+    def stop(self):
+        #################################################################
+        # stop(self)
+        # see play(self)
+        #################################################################
+        taskMgr.remove(self.id + '_UpdateTask')
+        self.playButton.config(state=NORMAL)
+        self.stopButton.config(state=DISABLED)
+        return
+
+    def setAnimation(self, animation, AB = 'a'):
+        #################################################################
+        # setAnimation(self, animation, AB = 'a')
+        # see play(self)
+        #################################################################
+        print 'OK!!!'
+        if AB == 'a':
+            if self.animNameA != None:
+                self['actor'].setControlEffect(self.animNameA, 1.0, 'modelRoot','lodRoot')
+            self.animNameA = self.AnimEntryA.get()
+        else:
+            if self.animNameB != None:
+                self['actor'].setControlEffect(self.animNameB, 1.0, 'modelRoot','lodRoot')
+            self.animNameB = self.AnimEntryB.get()
+        self.currTime = 0.0
+        self.frameControl.set(0)
+        self.updateDisplay()
+        self.setRatio(self.blendRatio)
+        return
+
+    def setPlayRate(self,rate):
+        #################################################################
+        # setPlayRate(self,rate)
+        # see play(self)
+        #################################################################
+        self.animNameA = self.AnimEntryA.get()
+        if self.animNameA in self['animList']:
+            self['actor'].setPlayRate(eval(rate), self.animNameA)
+            self.updateDisplay()
+        if self.animNameB in self['animList']:
+            self['actor'].setPlayRate(eval(rate), self.animNameB)
+            self.updateDisplay()
+        return
+
+    def updateDisplay(self):
+        #################################################################
+        # updateDisplay(self)
+        # see play(self)
+        #################################################################
+        if not (self.animNameA in self['animList']):
+            return
+        self.fps = self['actor'].getFrameRate(self.animNameA)
+        self.duration = self['actor'].getDuration(self.animNameA)
+        self.maxFrame = self['actor'].getNumFrames(self.animNameA) - 1
+        if not (self.animNameB in self['animList']):
+            return
+        if self.duration > self['actor'].getDuration(self.animNameB):
+            self.duration = self['actor'].getDuration(self.animNameB)
+        if self.maxFrame > self['actor'].getNumFrames(self.animNameB) - 1:
+            self.maxFrame = self['actor'].getNumFrames(self.animNameB) - 1
+        self.maxSeconds = self.duration
+        if self.unitsVar.get() == FRAMES:
+            fromFrame = 0
+            toFrame = self.maxFrame
+            self.minLabel['text'] = fromFrame
+            self.maxLabel['text'] = toFrame
+            self.frameControl.configure(from_ = fromFrame,
+                                        to = toFrame,
+                                        resolution = 1.0)
+        else:
+            self.minLabel['text'] = '0.0'
+            self.maxLabel['text'] = "%.2f" % self.duration
+            self.frameControl.configure(from_ = 0.0, 
+                                        to = self.duration,
+                                        resolution = 0.01)
+
+    def gotoT(self,time):
+        #################################################################
+        # gotoT(self,time)
+        # see play(self)
+        #################################################################
+        if self.unitsVar.get() == FRAMES:
+            self.frameControl.set(time * self.fps)
+        else:
+            self.frameControl.set(time)
+        return
+
+    def goTo(self,frame):
+        #################################################################
+        # goTo(self,frame)
+        # see play(self)
+        #################################################################
+        if (self.animNameA in self['animList'])and(self.animNameB in self['animList']):
+            # Convert scale value to float
+            frame = string.atof(frame)
+            # Now convert t to seconds for offset calculations
+            if self.unitsVar.get() == FRAMES:
+                frame = frame / self.fps
+            if self.dragMode:
+                self.currTime = frame
+            self['actor'].pose(self.animNameA,
+                            min(self.maxFrame, int(frame * self.fps)))
+            self['actor'].pose(self.animNameB,
+                            min(self.maxFrame, int(frame * self.fps)))
+        return
+
+    def onRelease(self,frame):
+        #################################################################
+        # onRelease(self,frame)
+        # see play(self)
+        #################################################################
+        self.dragMode = False
+        return
+
+    def onPress(self,frame):
+        #################################################################
+        # onPress(self,frame)
+        # see play(self)
+        #################################################################
+        self.dragMode = True
+        return
+    
+    def resetAllToZero(self):
+        #################################################################
+        # resetAllToZero(self)
+        # see play(self)
+        #################################################################
+        self.currTime = 0.0
+        self.gotoT(0)
+        return
+
+    def resetAllToEnd(self):
+        #################################################################
+        # resetAllToEnd(self)
+        # see play(self)
+        #################################################################
+        self.currTime = self.maxSeconds
+        self.gotoT(self.duration)
+        return
+
+    def toggleBlend(self):
+        #################################################################
+        # toggleBlend(self)
+        # This function will enable the blending option for the actor.
+        # and call set ratio function to set the blending animation mixing in
+        # current ratio.
+        #
+        # This blending enable will not be keep when you close the window!
+        #
+        #################################################################
+        if self.blendVar.get():
+            self.enableBlend = True
+            self['actor'].enableBlend()
+            self.setRatio(self.blendRatio)
+        else:
+            self.enableBlend = False
+            self['actor'].disableBlend()
+        return
+
+    def setRatio(self, ratio):
+        #################################################################
+        # setRatio(self, ratio)
+        # callback funtion
+        # This one will be called each time when user drag the blend ratio
+        # slider on the panel. This will set the blening ratio to both animation.
+        # (Which is "setControlEffect")
+        #################################################################
+        self.blendRatio = float(ratio)
+        if self.enableBlend:
+            if self.animNameA in self['animList']:
+                self['actor'].setControlEffect(self.animNameA, self.blendRatio, 'modelRoot','lodRoot')
+            if self.animNameB in self['animList']:
+                self['actor'].setControlEffect(self.animNameB, 1-self.blendRatio, 'modelRoot','lodRoot')
+            return
+
+    def setBlendAnim(self, name):
+        #################################################################
+        # setBlendAnim(self, name)
+        # This function will be called each time when user try to select
+        # a existing blending animation from the comboBox on the panel
+        # This function will re-set every varaibles on the panel to what
+        # it should be. For example, when user choose blending anim "R,"
+        # which was blended by anim "a" and "b" with ratio "c,"
+        # then this function will set Animation A to "a" and animation B
+        # to "b" and set the ratio slider to "c" position.
+        #################################################################
+        if self.blendDict.has_key(name):
+            self.currentBlendName = name
+            animA = self.blendDict[name][0]
+            animB = self.blendDict[name][1]
+            ratio = self.blendDict[name][2]
+            self.AnimEntryA.selectitem(animA)
+            self.AnimEntryB.selectitem(animB)
+            self.setAnimation(animA, AB = 'a')
+            self.setAnimation(animB, AB = 'b')
+            self.ratioControl.set(ratio)
+        return
+
+    def setBlendAnimList(self, dict, select=False):
+        #################################################################
+        # setBlendAnimList(self, dict, select=False)
+        # This function will be called when we need to reset the dropdown list
+        # of "Blend Anim."
+        # About "selec" option, this now is mainly used when we remove
+        # a blended animation from the actor. When it has been specified to True,
+        # the function will not only reset the list, but will also automatically
+        # select one from the top of list, if it is not empty.
+        #################################################################
+        self.blendDict.clear()
+        del self.blendDict
+        self.blendDict = dict.copy()
+        print self.blendDict
+        if len(self.blendDict)>0:
+            self.blendList = self.blendDict.keys()
+        else:
+            self.blendList = []
+        self.blendAnimEntry.setlist(self.blendList)
+        if select:
+            if len(self.blendList)>0:
+                self.blendAnimEntry.selectitem(self.blendList[0])
+                self.setBlendAnim(self.blendList[0])
+                self.currentBlendName = self.blendList[0]
+            else:
+                self.blendAnimEntry.clear()
+                self.currentBlendName = None
+        return
+    
+    def saveButtonPushed(self):
+        #################################################################
+        # saveButtonPushed(self)
+        # This function will be called when user clicked on the "Save" button
+        # This functiont will collect all data on the panel and send them with
+        # a message to sceneEditor to save the current blending animation
+        # into the dataHolder.
+        #################################################################
+        name = self.blendAnimEntry.get()
+        if name=='':
+            Pmw.MessageDialog(None, title='Caution!',
+                              message_text = 'You have to give the blending animation a name first!',
+                              iconpos='s',
+                              defaultbutton = 'Close'
+                              )
+            return
+        elif (not(self.animNameA in self['animList']))or(not(self.animNameB in self['animList'])):
+            Pmw.MessageDialog(None, title='Caution!',
+                              message_text = 'The Animations you have selected are not exist!',
+                              iconpos='s',
+                              defaultbutton = 'Close'
+                              )
+            return
+        else:
+            messenger.send('BAW_saveBlendAnim', [self['actor'].getName(),
+                                                 name,
+                                                 self.animNameA,
+                                                 self.animNameB,
+                                                 self.blendRatio])
+            self.currentBlendName = name
+        return
+
+    def removeButtonPushed(self):
+        #################################################################
+        # removeButtonPushed(self)
+        # remove the current seleted blended animation from the actor.
+        # This will send out a message to sceneEditor to delete the data inside
+        # the dataHolder and then reset the list of here.
+        #################################################################
+        name = self.blendAnimEntry.get()
+        messenger.send('BAW_removeBlendAnim', [self['actor'].getName(),name])
+        return
+
+    def renameButtonPushed(self):
+        #################################################################
+        # renameButtonPushed(self)
+        # this function will be called when user click on the "Rename" button.
+        # This function will collect all data on the panel and send them out
+        # with a message to sceneEditor to rename and re-save all setting about
+        # current animation.
+        #################################################################
+        oName = self.currentBlendName
+        name = self.blendAnimEntry.get()
+        if self.currentBlendName == None:
+            Pmw.MessageDialog(None, title='Caution!',
+                              message_text = "You haven't select any blended animation!!",
+                              iconpos='s',
+                              defaultbutton = 'Close'
+                              )
+            return
+        elif name=='':
+            Pmw.MessageDialog(None, title='Caution!',
+                              message_text = 'You have to give the blending animation a name first!',
+                              iconpos='s',
+                              defaultbutton = 'Close'
+                              )
+            return
+        elif (not(self.animNameA in self['animList']))or(not(self.animNameB in self['animList'])):
+            Pmw.MessageDialog(None, title='Caution!',
+                              message_text = 'The Animations you have selected are not exist!',
+                              iconpos='s',
+                              defaultbutton = 'Close'
+                              )
+            return
+        else:
+            messenger.send('BAW_renameBlendAnim', [self['actor'].getName(),
+                                                   name,
+                                                   oName,
+                                                   self.animNameA,
+                                                   self.animNameB,
+                                                   self.blendRatio]
+                           )
+            self.currentBlendName = name
+        return
+
+    def onDestroy(self, event):
+        #################################################################
+        # onDestroy(self, event)
+        # This function will be call when user try to close the window.
+        # In here we will stop all tasks we have opend and disable the
+        # blend setting of actor.
+        # If we didn't disable the blend option, the next time you play
+        # the animation via animation panel will cause some error.
+        #################################################################
+        if taskMgr.hasTaskNamed(self.id + '_UpdateTask'):
+            taskMgr.remove(self.id + '_UpdateTask')
+        messenger.send('BAW_close',[self.nodeName])
+        self.actorNode.setControlEffect(self.animNameA, 1.0, 'modelRoot','lodRoot')
+        self.actorNode.setControlEffect(self.animNameB, 1.0, 'modelRoot','lodRoot')
+        self.actorNode.disableBlend()
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass

+ 670 - 0
contrib/src/sceneeditor/seCameraControl.py

@@ -0,0 +1,670 @@
+#################################################################
+# seCameraControl.py
+# Originally from DirectCameraControl.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# We didn't change anything essential.
+# Just because we customized the seSession from DirectSession,
+# So we need related files can follow the change.
+# However, we don't want to change anything inside the original directool
+# to let them can work with our scene editor.
+# (If we do change original directools, it will force user has to install the latest version of OUR Panda)
+#
+#################################################################
+from direct.showbase.DirectObject import DirectObject
+from direct.directtools.DirectUtil import *
+from seGeometry import *
+from direct.directtools.DirectGlobals import *
+from direct.task import Task
+
+CAM_MOVE_DURATION = 1.2
+COA_MARKER_SF = 0.0075
+Y_AXIS = Vec3(0,1,0)
+
+class DirectCameraControl(DirectObject):
+    def __init__(self):
+        # Create the grid
+        self.startT = 0.0
+        self.startF = 0
+        self.orthoViewRoll = 0.0
+        self.lastView = 0
+        self.coa = Point3(0,100,0)
+        self.coaMarker = loader.loadModel('models/misc/sphere')
+        self.coaMarker.setName('DirectCameraCOAMarker')
+        self.coaMarker.setTransparency(1)
+        self.coaMarker.setColor(1,0,0,0)
+        self.coaMarker.setPos(0,100,0)
+        useDirectRenderStyle(self.coaMarker)
+        self.coaMarkerPos = Point3(0)
+        self.fLockCOA = 0
+        self.nullHitPointCount = 0
+        self.cqEntries = []
+        self.coaMarkerRef = SEditor.group.attachNewNode('coaMarkerRef')
+        self.camManipRef = SEditor.group.attachNewNode('camManipRef')
+        t = CAM_MOVE_DURATION
+        self.actionEvents = [
+            ['DIRECT-mouse2', self.mouseFlyStart],
+            ['DIRECT-mouse2Up', self.mouseFlyStop],
+            ]
+        self.keyEvents = [
+            ['c', self.centerCamIn, 0.5],
+            ['f', self.fitOnWidget],
+            ['h', self.homeCam],
+            ['shift-v', self.toggleMarkerVis],
+            ['m', self.moveToFit],
+            ['n', self.pickNextCOA],
+            ['u', self.orbitUprightCam],
+            ['shift-u', self.uprightCam],
+            [`1`, self.spawnMoveToView, 1],
+            [`2`, self.spawnMoveToView, 2],
+            [`3`, self.spawnMoveToView, 3],
+            [`4`, self.spawnMoveToView, 4],
+            [`5`, self.spawnMoveToView, 5],
+            [`6`, self.spawnMoveToView, 6],
+            [`7`, self.spawnMoveToView, 7],
+            [`8`, self.spawnMoveToView, 8],
+            ['9', self.swingCamAboutWidget, -90.0, t],
+            ['0', self.swingCamAboutWidget,  90.0, t],
+            ['`', self.removeManipulateCameraTask],
+            ['=', self.zoomCam, 0.5, t],
+            ['+', self.zoomCam, 0.5, t],
+            ['-', self.zoomCam, -2.0, t],
+            ['_', self.zoomCam, -2.0, t],
+            ]
+
+    def toggleMarkerVis(self):
+        if SEditor.cameraControl.coaMarker.isHidden():
+            SEditor.cameraControl.coaMarker.show()
+        else:
+            SEditor.cameraControl.coaMarker.hide()
+
+    def mouseFlyStart(self, modifiers):
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        # Where are we in the display region?
+        if ((abs(SEditor.dr.mouseX) < 0.9) and (abs(SEditor.dr.mouseY) < 0.9)):
+            # MOUSE IS IN CENTRAL REGION
+            # Hide the marker for this kind of motion
+            self.coaMarker.hide()
+            # Record time of start of mouse interaction
+            self.startT= globalClock.getFrameTime()
+            self.startF = globalClock.getFrameCount()
+            # Start manipulation
+            self.spawnXZTranslateOrHPanYZoom()
+            # END MOUSE IN CENTRAL REGION
+        else:
+            if ((abs(SEditor.dr.mouseX) > 0.9) and
+                (abs(SEditor.dr.mouseY) > 0.9)):
+                # Mouse is in corners, spawn roll task
+                self.spawnMouseRollTask()
+            else:
+                # Mouse is in outer frame, spawn mouseRotateTask
+                self.spawnMouseRotateTask()
+
+    def mouseFlyStop(self):
+        taskMgr.remove('manipulateCamera')
+        stopT = globalClock.getFrameTime()
+        deltaT = stopT - self.startT
+        stopF = globalClock.getFrameCount()
+        deltaF = stopF - self.startF
+        if (deltaT <= 0.25) or (deltaF <= 1):
+            # Check for a hit point based on
+            # current mouse position
+            # Allow intersection with unpickable objects
+            # And then spawn task to determine mouse mode
+            # Don't intersect with hidden or backfacing objects
+            skipFlags = SKIP_HIDDEN | SKIP_BACKFACE
+            # Skip camera (and its children), unless control key is pressed
+            skipFlags |= SKIP_CAMERA * (1 - base.getControl())
+            self.computeCOA(SEditor.iRay.pickGeom(skipFlags = skipFlags))
+            # Record reference point
+            self.coaMarkerRef.iPosHprScale(base.cam)
+            # Record entries
+            self.cqEntries = []
+            for i in range(SEditor.iRay.getNumEntries()):
+                self.cqEntries.append(SEditor.iRay.getEntry(i))
+        # Show the marker
+        self.coaMarker.show()
+        # Resize it
+        self.updateCoaMarkerSize()
+
+    def spawnXZTranslateOrHPanYZoom(self):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Spawn the new task
+        t = Task.Task(self.XZTranslateOrHPanYZoomTask)
+        # For HPanYZoom
+        t.zoomSF = Vec3(self.coaMarker.getPos(SEditor.camera)).length()
+        taskMgr.add(t, 'manipulateCamera')
+
+    def spawnXZTranslateOrHPPan(self):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Spawn new task
+        taskMgr.add(self.XZTranslateOrHPPanTask,
+                    'manipulateCamera')
+
+    def spawnXZTranslate(self):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Spawn new task
+        taskMgr.add(self.XZTranslateTask, 'manipulateCamera')
+
+    def spawnHPanYZoom(self):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Spawn new task
+        t = Task.Task(self.HPanYZoomTask)
+        t.zoomSF = Vec3(self.coaMarker.getPos(SEditor.camera)).length()
+        taskMgr.add(t, 'manipulateCamera')
+
+    def spawnHPPan(self):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Spawn new task
+        taskMgr.add(self.HPPanTask, 'manipulateCamera')
+
+    def XZTranslateOrHPanYZoomTask(self, state):
+        if SEditor.fShift:
+            return self.XZTranslateTask(state)
+        else:
+            return self.HPanYZoomTask(state)
+
+    def XZTranslateOrHPPanTask(self, state):
+        if SEditor.fShift:
+            # Panning action
+            return self.HPPanTask(state)
+        else:
+            # Translation action
+            return self.XZTranslateTask(state)
+
+    def XZTranslateTask(self,state):
+        coaDist = Vec3(self.coaMarker.getPos(SEditor.camera)).length()
+        xlateSF = (coaDist / SEditor.dr.near)
+        SEditor.camera.setPos(SEditor.camera,
+                             (-0.5 * SEditor.dr.mouseDeltaX *
+                              SEditor.dr.nearWidth *
+                              xlateSF),
+                             0.0,
+                             (-0.5 * SEditor.dr.mouseDeltaY *
+                              SEditor.dr.nearHeight *
+                              xlateSF))
+        return Task.cont
+
+    def HPanYZoomTask(self,state):
+        if SEditor.fControl:
+            moveDir = Vec3(self.coaMarker.getPos(SEditor.camera))
+            # If marker is behind camera invert vector
+            if moveDir[1] < 0.0:
+                moveDir.assign(moveDir * -1)
+            moveDir.normalize()
+        else:
+            moveDir = Vec3(Y_AXIS)
+        moveDir.assign(moveDir * (-1.0 * SEditor.dr.mouseDeltaY *
+                                        state.zoomSF))
+        if SEditor.dr.mouseDeltaY > 0.0:
+            moveDir.setY(moveDir[1] * 1.0)
+        SEditor.camera.setPosHpr(SEditor.camera,
+                                moveDir[0],
+                                moveDir[1],
+                                moveDir[2],
+                                (0.5 * SEditor.dr.mouseDeltaX *
+                                 SEditor.dr.fovH),
+                                0.0, 0.0)
+        return Task.cont
+
+    def HPPanTask(self, state):
+        SEditor.camera.setHpr(SEditor.camera,
+                             (0.5 * SEditor.dr.mouseDeltaX *
+                              SEditor.dr.fovH),
+                             (-0.5 * SEditor.dr.mouseDeltaY *
+                              SEditor.dr.fovV),
+                             0.0)
+        return Task.cont
+
+    def spawnMouseRotateTask(self):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Set at markers position in render coordinates
+        self.camManipRef.setPos(self.coaMarkerPos)
+        self.camManipRef.setHpr(SEditor.camera, ZERO_POINT)
+        t = Task.Task(self.mouseRotateTask)
+        if abs(SEditor.dr.mouseX) > 0.9:
+            t.constrainedDir = 'y'
+        else:
+            t.constrainedDir = 'x'
+        taskMgr.add(t, 'manipulateCamera')
+
+    def mouseRotateTask(self, state):
+        # If moving outside of center, ignore motion perpendicular to edge
+        if ((state.constrainedDir == 'y') and (abs(SEditor.dr.mouseX) > 0.9)):
+            deltaX = 0
+            deltaY = SEditor.dr.mouseDeltaY
+        elif ((state.constrainedDir == 'x') and (abs(SEditor.dr.mouseY) > 0.9)):
+            deltaX = SEditor.dr.mouseDeltaX
+            deltaY = 0
+        else:
+            deltaX = SEditor.dr.mouseDeltaX
+            deltaY = SEditor.dr.mouseDeltaY
+        if SEditor.fShift:
+            SEditor.camera.setHpr(SEditor.camera,
+                                 (deltaX * SEditor.dr.fovH),
+                                 (-deltaY * SEditor.dr.fovV),
+                                 0.0)
+            self.camManipRef.setPos(self.coaMarkerPos)
+            self.camManipRef.setHpr(SEditor.camera, ZERO_POINT)
+        else:
+            wrt = SEditor.camera.getTransform( self.camManipRef )
+            self.camManipRef.setHpr(self.camManipRef,
+                                    (-1 * deltaX * 180.0),
+                                    (deltaY * 180.0),
+                                    0.0)
+            SEditor.camera.setTransform(self.camManipRef, wrt)
+        return Task.cont
+
+    def spawnMouseRollTask(self):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Set at markers position in render coordinates
+        self.camManipRef.setPos(self.coaMarkerPos)
+        self.camManipRef.setHpr(SEditor.camera, ZERO_POINT)
+        t = Task.Task(self.mouseRollTask)
+        t.coaCenter = getScreenXY(self.coaMarker)
+        t.lastAngle = getCrankAngle(t.coaCenter)
+        # Store the camera/manipRef offset transform
+        t.wrt = SEditor.camera.getTransform( self.camManipRef )
+        taskMgr.add(t, 'manipulateCamera')
+
+    def mouseRollTask(self, state):
+        wrt = state.wrt
+        angle = getCrankAngle(state.coaCenter)
+        deltaAngle = angle - state.lastAngle
+        state.lastAngle = angle
+        if base.config.GetBool('temp-hpr-fix',0):
+            self.camManipRef.setHpr(self.camManipRef, 0, 0, deltaAngle)
+        else:
+            self.camManipRef.setHpr(self.camManipRef, 0, 0, -deltaAngle)
+        SEditor.camera.setTransform(self.camManipRef, wrt)
+        return Task.cont
+
+    def lockCOA(self):
+        self.fLockCOA = 1
+        SEditor.message('COA Lock On')
+            
+    def unlockCOA(self):
+        self.fLockCOA = 0
+        SEditor.message('COA Lock Off')
+
+    def toggleCOALock(self):
+        self.fLockCOA = 1 - self.fLockCOA
+        if self.fLockCOA:
+            SEditor.message('COA Lock On')
+        else:
+            SEditor.message('COA Lock Off')
+
+    def pickNextCOA(self):
+        """ Cycle through collision handler entries """
+        if self.cqEntries:
+            # Get next entry and rotate entries
+            entry = self.cqEntries[0]
+            self.cqEntries = self.cqEntries[1:] + self.cqEntries[:1]
+            # Filter out object's under camera
+            nodePath = entry.getIntoNodePath()
+            if SEditor.camera not in nodePath.getAncestors():
+                # Compute new hit point
+                hitPt = entry.getSurfacePoint(entry.getFromNodePath())
+                # Move coa marker to new point
+                self.updateCoa(hitPt, ref = self.coaMarkerRef)
+            else:
+                # Remove offending entry
+                self.cqEntries = self.cqEntries[:-1]
+                self.pickNextCOA()
+
+    def computeCOA(self, entry):
+        coa = Point3(0)
+        dr = SEditor.drList.getCurrentDr()
+        if self.fLockCOA:
+            # COA is locked, use existing point
+            # Use existing point
+            coa.assign(self.coaMarker.getPos(SEditor.camera))
+            # Reset hit point count
+            self.nullHitPointCount = 0
+        elif entry:
+            # Got a hit point (hit point is in camera coordinates)
+            # Set center of action
+            hitPt = entry.getSurfacePoint(entry.getFromNodePath())
+            hitPtDist = Vec3(hitPt).length()
+            coa.assign(hitPt)
+            # Handle case of bad coa point (too close or too far)
+            if ((hitPtDist < (1.1 * dr.near)) or
+                (hitPtDist > dr.far)):
+                # Just use existing point
+                coa.assign(self.coaMarker.getPos(SEditor.camera))
+            # Reset hit point count
+            self.nullHitPointCount = 0
+        else:
+            # Increment null hit point count
+            self.nullHitPointCount = (self.nullHitPointCount + 1) % 7
+            # No COA lock and no intersection point
+            # Use a point out in front of camera
+            # Distance to point increases on multiple null hit points
+            # MRM: Would be nice to be able to control this
+            # At least display it
+            dist = pow(10.0, self.nullHitPointCount)
+            SEditor.message('COA Distance: ' + `dist`)
+            coa.set(0,dist,0)
+        # Compute COA Dist
+        coaDist = Vec3(coa - ZERO_POINT).length()
+        if coaDist < (1.1 * dr.near):
+            coa.set(0,100,0)
+            coaDist = 100
+        # Update coa and marker
+        self.updateCoa(coa, coaDist = coaDist)
+
+    def updateCoa(self, ref2point, coaDist = None, ref = None):
+        self.coa.set(ref2point[0], ref2point[1], ref2point[2])
+        if not coaDist:
+            coaDist = Vec3(self.coa - ZERO_POINT).length()
+        # Place the marker in render space
+        if ref == None:
+            # KEH: use the current display region
+            # ref = base.cam
+            ref = SEditor.drList.getCurrentDr().cam
+        self.coaMarker.setPos(ref, self.coa)
+        pos = self.coaMarker.getPos()
+        self.coaMarker.setPosHprScale(pos, Vec3(0), Vec3(1))
+        # Resize it
+        self.updateCoaMarkerSize(coaDist)
+        # Record marker pos in render space
+        self.coaMarkerPos.assign(self.coaMarker.getPos())
+
+    def updateCoaMarkerSizeOnDeath(self, state):
+        # Needed because tasks pass in state as first arg
+        self.updateCoaMarkerSize()
+
+    def updateCoaMarkerSize(self, coaDist = None):
+        if not coaDist:
+            coaDist = Vec3(self.coaMarker.getPos( SEditor.camera )).length()
+        # KEH: use current display region for fov
+        # sf = COA_MARKER_SF * coaDist * math.tan(deg2Rad(SEditor.dr.fovV))
+        sf = COA_MARKER_SF * coaDist * math.tan(deg2Rad(SEditor.drList.getCurrentDr().fovV))
+        if sf == 0.0:
+            sf = 0.1
+        self.coaMarker.setScale(sf)
+        # Lerp color to fade out
+        self.coaMarker.lerpColor(VBase4(1,0,0,1), VBase4(1,0,0,0), 3.0,
+                                 task = 'fadeAway')
+
+    def homeCam(self):
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        SEditor.camera.reparentTo(render)
+        SEditor.camera.clearMat()
+        # Resize coa marker
+        self.updateCoaMarkerSize()
+
+    def uprightCam(self):
+        taskMgr.remove('manipulateCamera')
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        # Pitch camera till upright
+        currH = SEditor.camera.getH()
+        SEditor.camera.lerpHpr(currH, 0, 0,
+                              CAM_MOVE_DURATION,
+                              other = render,
+                              blendType = 'easeInOut',
+                              task = 'manipulateCamera')
+
+    def orbitUprightCam(self):
+        taskMgr.remove('manipulateCamera')
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        # Transform camera z axis to render space
+        mCam2Render = Mat4()
+        mCam2Render.assign(SEditor.camera.getMat(render))
+        zAxis = Vec3(mCam2Render.xformVec(Z_AXIS))
+        zAxis.normalize()
+        # Compute rotation angle needed to upright cam
+        orbitAngle = rad2Deg(math.acos(CLAMP(zAxis.dot(Z_AXIS),-1,1)))
+        # Check angle
+        if orbitAngle < 0.1:
+            # Already upright
+            return
+        # Compute orthogonal axis of rotation
+        rotAxis = Vec3(zAxis.cross(Z_AXIS))
+        rotAxis.normalize()
+        # Find angle between rot Axis and render X_AXIS
+        rotAngle = rad2Deg(math.acos(CLAMP(rotAxis.dot(X_AXIS),-1,1)))
+        # Determine sign or rotation angle
+        if rotAxis[1] < 0:
+            rotAngle *= -1
+        # Position ref CS at coa marker with xaxis aligned with rot axis
+        self.camManipRef.setPos(self.coaMarker, Vec3(0))
+        self.camManipRef.setHpr(render, rotAngle, 0, 0)
+        # Reparent Cam to ref Coordinate system
+        parent = SEditor.camera.getParent()
+        SEditor.camera.wrtReparentTo(self.camManipRef)
+        # Rotate ref CS to final orientation
+        t = self.camManipRef.lerpHpr(rotAngle, orbitAngle, 0,
+                                     CAM_MOVE_DURATION,
+                                     other = render,
+                                     blendType = 'easeInOut',
+                                     task = 'manipulateCamera')
+        # Upon death, reparent Cam to parent
+        t.parent = parent
+        t.uponDeath = self.reparentCam
+
+    def centerCam(self):
+        self.centerCamIn(1.0)
+        
+    def centerCamNow(self):
+        self.centerCamIn(0.)
+
+    def centerCamIn(self, t):
+        taskMgr.remove('manipulateCamera')
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        # Determine marker location
+        markerToCam = self.coaMarker.getPos( SEditor.camera )
+        dist = Vec3(markerToCam - ZERO_POINT).length()
+        scaledCenterVec = Y_AXIS * dist
+        delta = markerToCam - scaledCenterVec
+        self.camManipRef.setPosHpr(SEditor.camera, Point3(0), Point3(0))
+        t = SEditor.camera.lerpPos(Point3(delta),
+                                  CAM_MOVE_DURATION,
+                                  other = self.camManipRef,
+                                  blendType = 'easeInOut',
+                                  task = 'manipulateCamera')
+        t.uponDeath = self.updateCoaMarkerSizeOnDeath
+
+    def zoomCam(self, zoomFactor, t):
+        taskMgr.remove('manipulateCamera')
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        # Find a point zoom factor times the current separation
+        # of the widget and cam
+        zoomPtToCam = self.coaMarker.getPos(SEditor.camera) * zoomFactor
+        # Put a target nodePath there
+        self.camManipRef.setPos(SEditor.camera, zoomPtToCam)
+        # Move to that point
+        t = SEditor.camera.lerpPos(ZERO_POINT,
+                                  CAM_MOVE_DURATION,
+                                  other = self.camManipRef,
+                                  blendType = 'easeInOut',
+                                  task = 'manipulateCamera')
+        t.uponDeath = self.updateCoaMarkerSizeOnDeath
+        
+    def spawnMoveToView(self, view):
+        # Kill any existing tasks
+        taskMgr.remove('manipulateCamera')
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        # Calc hprOffset
+        hprOffset = VBase3()
+        if view == 8:
+            # Try the next roll angle
+            self.orthoViewRoll = (self.orthoViewRoll + 90.0) % 360.0
+            # but use the last view
+            view = self.lastView
+        else:
+            self.orthoViewRoll = 0.0
+        # Adjust offset based on specified view
+        if view == 1:
+            hprOffset.set(180., 0., 0.)
+        elif view == 2:
+            hprOffset.set(0., 0., 0.)
+        elif view == 3:
+            hprOffset.set(90., 0., 0.)
+        elif view == 4:
+            hprOffset.set(-90., 0., 0.)
+        elif view == 5:
+            hprOffset.set(0., -90., 0.)
+        elif view == 6:
+            hprOffset.set(0., 90., 0.)
+        elif view == 7:
+            hprOffset.set(135., -35.264, 0.)
+        # Position target
+        self.camManipRef.setPosHpr(self.coaMarker, ZERO_VEC,
+                                   hprOffset)
+        # Scale center vec by current distance to target
+        offsetDistance = Vec3(SEditor.camera.getPos(self.camManipRef) - 
+                              ZERO_POINT).length()
+        scaledCenterVec = Y_AXIS * (-1.0 * offsetDistance)
+        # Now put the camManipRef at that point
+        self.camManipRef.setPosHpr(self.camManipRef,
+                                   scaledCenterVec,
+                                   ZERO_VEC)
+        # Record view for next time around
+        self.lastView = view
+        t = SEditor.camera.lerpPosHpr(ZERO_POINT,
+                                     VBase3(0,0,self.orthoViewRoll),
+                                     CAM_MOVE_DURATION,
+                                     other = self.camManipRef,
+                                     blendType = 'easeInOut',
+                                     task = 'manipulateCamera')
+        t.uponDeath = self.updateCoaMarkerSizeOnDeath
+        
+        
+    def swingCamAboutWidget(self, degrees, t):
+        # Remove existing camera manipulation task
+        taskMgr.remove('manipulateCamera')
+
+        # Record undo point
+        SEditor.pushUndo([SEditor.camera])
+        
+        # Coincident with widget
+        self.camManipRef.setPos(self.coaMarker, ZERO_POINT)
+        # But aligned with render space
+        self.camManipRef.setHpr(ZERO_POINT)
+
+        parent = SEditor.camera.getParent()
+        SEditor.camera.wrtReparentTo(self.camManipRef)
+
+        manipTask = self.camManipRef.lerpHpr(VBase3(degrees,0,0),
+                                             CAM_MOVE_DURATION,
+                                             blendType = 'easeInOut',
+                                             task = 'manipulateCamera')
+        # Upon death, reparent Cam to parent
+        manipTask.parent = parent
+        manipTask.uponDeath = self.reparentCam
+
+    def reparentCam(self, state):
+        SEditor.camera.wrtReparentTo(state.parent)
+        self.updateCoaMarkerSize()
+
+    def fitOnWidget(self, nodePath = 'None Given'):
+        # Fit the node on the screen
+        # stop any ongoing tasks
+        taskMgr.remove('manipulateCamera')
+        # How big is the node?
+        nodeScale = SEditor.widget.scalingNode.getScale(render)
+        maxScale = max(nodeScale[0],nodeScale[1],nodeScale[2])
+        maxDim = min(SEditor.dr.nearWidth, SEditor.dr.nearHeight)
+
+        # At what distance does the object fill 30% of the screen?
+        # Assuming radius of 1 on widget
+        camY = SEditor.dr.near * (2.0 * maxScale)/(0.3 * maxDim)
+    
+        # What is the vector through the center of the screen?
+        centerVec = Y_AXIS * camY
+    
+        # Where is the node relative to the viewpoint
+        vWidget2Camera = SEditor.widget.getPos(SEditor.camera)
+    
+        # How far do you move the camera to be this distance from the node?
+        deltaMove = vWidget2Camera - centerVec
+    
+        # Move a target there
+        self.camManipRef.setPos(SEditor.camera, deltaMove)
+
+        parent = SEditor.camera.getParent()
+        SEditor.camera.wrtReparentTo(self.camManipRef)
+        fitTask = SEditor.camera.lerpPos(Point3(0,0,0),
+                                        CAM_MOVE_DURATION,
+                                        blendType = 'easeInOut',
+                                        task = 'manipulateCamera')
+        # Upon death, reparent Cam to parent
+        fitTask.parent = parent
+        fitTask.uponDeath = self.reparentCam                                
+
+    def moveToFit(self):
+        # How bit is the active widget?
+        widgetScale = SEditor.widget.scalingNode.getScale(render)
+        maxScale = max(widgetScale[0], widgetScale[1], widgetScale[2])
+        # At what distance does the widget fill 50% of the screen?
+        camY = ((2 * SEditor.dr.near * (1.5 * maxScale)) /
+                min(SEditor.dr.nearWidth, SEditor.dr.nearHeight))
+        # Find a point this distance along the Y axis
+        # MRM: This needs to be generalized to support non uniform frusta
+        centerVec = Y_AXIS * camY
+        # Before moving, record the relationship between the selected nodes
+        # and the widget, so that this can be maintained
+        SEditor.selected.getWrtAll()
+        # Push state onto undo stack
+        SEditor.pushUndo(SEditor.selected)
+        # Remove the task to keep the widget attached to the object
+        taskMgr.remove('followSelectedNodePath')
+        # Spawn a task to keep the selected objects with the widget
+        taskMgr.add(self.stickToWidgetTask, 'stickToWidget')
+        # Spawn a task to move the widget
+        t = SEditor.widget.lerpPos(Point3(centerVec),
+                                  CAM_MOVE_DURATION,
+                                  other = SEditor.camera, 
+                                  blendType = 'easeInOut',
+                                  task = 'moveToFitTask')
+        t.uponDeath = lambda state: taskMgr.remove('stickToWidget')
+
+    def stickToWidgetTask(self, state):
+        # Move the objects with the widget
+        SEditor.selected.moveWrtWidgetAll()
+        # Continue
+        return Task.cont
+
+    def enableMouseFly(self, fKeyEvents = 1):
+        # disable C++ fly interface
+        base.disableMouse()
+        # Enable events
+        for event in self.actionEvents:
+            self.accept(event[0], event[1], extraArgs = event[2:])
+        if fKeyEvents:
+            for event in self.keyEvents:
+                self.accept(event[0], event[1], extraArgs = event[2:])
+        # Show marker
+        self.coaMarker.reparentTo(SEditor.group)
+
+    def disableMouseFly(self):
+        # Hide the marker
+        self.coaMarker.reparentTo(hidden)
+        # Ignore events
+        for event in self.actionEvents:
+            self.ignore(event[0])
+        for event in self.keyEvents:
+            self.ignore(event[0])
+        # Kill tasks
+        self.removeManipulateCameraTask()
+        taskMgr.remove('stickToWidget')
+        base.enableMouse()
+
+    def removeManipulateCameraTask(self):
+        taskMgr.remove('manipulateCamera')
+

+ 49 - 0
contrib/src/sceneeditor/seColorEntry.py

@@ -0,0 +1,49 @@
+#################################################################
+# seColorEntry.py
+# Originally from VectorWidgets.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# Here we need some widget to handle some color input.
+# There is some colorEntry in the VectorWidgets.py, but they didn't
+# work as we need (we don't need alpha here. so the dim option should be set to 3).
+# So we make one from them.
+#
+#################################################################
+from direct.tkwidgets import Valuator
+from direct.tkwidgets import Floater
+from direct.tkwidgets import Slider
+import string, Pmw, Tkinter, tkColorChooser
+from direct.tkwidgets.VectorWidgets import VectorEntry
+
+class seColorEntry(VectorEntry):
+    def __init__(self, parent = None, **kw):
+        # Initialize options for the class (overriding some superclass options)
+        optiondefs = (
+            ('dim',                     3,                  Pmw.INITOPT),
+            ('type',                    'slider',           Pmw.INITOPT),
+            ('fGroup_labels',           ('R','G','B'),      None),
+            ('min',                     0.0,                None),
+            ('max',                     255.0,              None),
+            ('nuDigits',                0,                  None),
+            ('valuator_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(seColorEntry)
+
+    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]))

+ 915 - 0
contrib/src/sceneeditor/seFileSaver.py

@@ -0,0 +1,915 @@
+####################################################################################################################################################    
+# File Saving 
+# This code saves the scene out as python code... the scene is stored in the various dictionaries in "dataHolder.py" ...the class "AllScene"
+# 
+####################################################################################################################################################
+from pandac.PandaModules import *
+
+from direct.showbase.ShowBaseGlobal import *
+import os
+import shutil
+import string
+
+####################################################################################################################################################
+#### These modules are modified versions of Disney's equivalent modules
+#### We need to figure out a way to inherit their modules and overwrite what we need changed
+import seParticlePanel
+import seParticles
+import seParticleEffect
+import seForceGroup
+####################################################################################################################################################
+
+class FileSaver:
+
+    ####################################################################################################################################################    
+    # This class saves out the scene built with the scene editor as python code
+    # There are dictionaries saved out to save the state of the scene for reloading it with the editor
+    # Currently saving is supported for Models, Animations, Lights, Dummy Nodes
+    # Attributes like parenting are also saved out
+    # This class is actually instantiated in sceneEditor.py in the saveScene() method 
+    ####################################################################################################################################################
+
+    def __init(self):
+        pass
+
+    def SaveFile(self,AllScene,filename,dirname,reSaveFlag=0):
+
+      ################################################################################################################################################  
+        # This function takes the "dataHolder" instance "AllScene" which has dictionaries containing scene information
+        # The filename is where the scene will be written to
+        ################################################################################################################################################
+        
+        i1="    " # indentation
+        i2=i1+i1  # double indentation
+        out_file = open(filename,"w")
+        print "dirname:" + dirname
+        if( not os.path.isdir(dirname)):
+            os.mkdir(dirname)
+        savepathname=Filename(filename)
+        self.savepath=savepathname.getBasenameWoExtension()
+        out_file.write("##########################################################################################################\n")
+        out_file.write("# Auto Generated Code by Scene Editor\n")
+        out_file.write("# Edit with caution\n")
+        out_file.write("# Using this file in your code:\n")
+        out_file.write("# For example, if you have named this file as \"myscene.py\"\n")
+        out_file.write("# Do the following:\n")
+        out_file.write("# from myscene import * \n")
+        out_file.write("# theScene=SavedScene() #instantiate the class\n")
+        out_file.write("# IMPORTANT: All the documentation below refers to \"theScene\" as the instance of SavedScene()\n")
+        out_file.write("##########################################################################################################\n\n")
+        
+        out_file.write("##########################################################################################################\n")
+        out_file.write("# Import Panda Modules\n")
+        out_file.write("##########################################################################################################\n")
+        out_file.write("from direct.directbase.DirectStart import * # Core functionality for running the \"show\"\n")
+        out_file.write("from direct.actor import Actor # Importing models with animations\n")
+        out_file.write("from direct.directutil import Mopath # Motion Paths\n")
+        out_file.write("from direct.interval import MopathInterval # Motion Paths\n")
+        out_file.write("from direct.interval.IntervalGlobal import * # Intervals for interpolation, sequencing and parallelization\n")
+        out_file.write("from direct.particles import ParticleEffect # Particle Systems\n")
+        out_file.write("from direct.particles import ForceGroup # Forces acting on Particles\n")
+        out_file.write("from direct.particles import Particles\n\n")
+        out_file.write("##########################################################################################################\n")
+        out_file.write("# This class stores the entire scene\n")
+        out_file.write("##########################################################################################################\n\n")
+        out_file.write("class SavedScene(DirectObject): # We inherit from DirectObject so that we can use self.accept method to catch messages\n")
+        out_file.write("\n")
+        out_file.write(i1+"# These dictionaries are required for re-loading a scene in the editor\n")
+        out_file.write(i1+"# They can be used to access the objects as well\n\n")
+        out_file.write(i1+"ModelDic={}# Stores all the models and static geometry\n")
+        out_file.write(i1+"ModelRefDic={}# Stores the paths to the models\n")
+        out_file.write("\n")
+        out_file.write(i1+"ActorDic={}# Stores all the actors\n")
+        out_file.write(i1+"ActorRefDic={}# Stores the paths to the actors\n")
+        out_file.write(i1+"ActorAnimsDic={}# Stores the animations for each actor\n")
+        out_file.write(i1+"blendAnimDict={}# Stores all the blended animations\n")
+        out_file.write("\n")
+        out_file.write(i1+"LightDict={}# Stores all the lights\n")
+        out_file.write(i1+"LightTypes={}# Stores types for the lights\n")
+        out_file.write(i1+"LightNodes={}# Stores the actual nodes for the lights\n")
+        out_file.write("\n")
+        out_file.write(i1+"dummyDict={}# Stores dummies\n")
+        out_file.write("\n")
+        out_file.write(i1+"collisionDict={}# Stores Collision information\n")
+        out_file.write("\n")
+        out_file.write(i1+"curveDict={}# Stores Mopath information\n")
+        out_file.write(i1+"curveIntervals=[]# Stores list of mopath intervals\n")
+        out_file.write(i1+"curveRefColl=[]# Stores paths to mopaths\n")
+        out_file.write(i1+"curveIntervalsDict={}# Stores mopath intervals\n")
+        out_file.write("\n")
+        out_file.write(i1+"particleDict={}# Stores particles\n")
+        out_file.write(i1+"particleNodes={}# Stores particle nodes\n")
+        out_file.write("\n")
+        out_file.write(i1+"#Light Count\n")
+        out_file.write(i1+"ambientCount=0\n")
+        out_file.write(i1+"directionalCount=0\n")
+        out_file.write(i1+"pointCount=0\n")
+        out_file.write(i1+"spotCount=0\n")
+        out_file.write("\n")
+        out_file.write(i1+"#Lighting Attribute\n")
+        out_file.write(i1+"lightAttrib = LightAttrib.makeAllOff()# Initialize lighting\n")
+        out_file.write("\n")
+        out_file.write(i1+"CollisionHandler=CollisionHandlerEvent()# Setup a Collision Handler\n")
+
+        out_file.write(i1+"##########################################################################################################\n")
+        out_file.write(i1+"# Constructor: this is run first when you instantiate the SavedScene class\n")
+        out_file.write(i1+"##########################################################################################################\n")
+        out_file.write(i1+"def __init__(self,loadmode=1,seParticleEffect=None,seParticles=None,executionpath=None):# loadmode 0 specifies that this file is being loaded by the scene editor and it passes its own versions of the particle fx modules\n")
+        out_file.write("\n")
+        out_file.write(i2+"self.loadmode=loadmode\n")
+        out_file.write(i2+"self.seParticleEffect=seParticleEffect\n")
+        out_file.write(i2+"self.seParticles=seParticles\n")
+        out_file.write(i2+"self.executionpath=executionpath\n")
+        out_file.write("\n")
+        out_file.write(i2+"base.enableParticles()# Enable Particle effects\n")
+        out_file.write("\n")
+        out_file.write(i2+"self.cTrav = CollisionTraverser() # Setup a traverser for collisions\n")
+        out_file.write(i2+"base.cTrav = self.cTrav\n")
+        out_file.write(i2+"self.CollisionHandler.setInPattern(\"enter%in\")# The message to be raised when something enters a collision node\n")
+        out_file.write(i2+"self.CollisionHandler.setOutPattern(\"exit%in\")# The message to be raised when something exits a collision node\n")
+        out_file.write("\n")
+        ####################################################################################################################################################
+        # Save Models
+        ####################################################################################################################################################
+
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Code for all the models\n")
+        out_file.write(i2+"# To access these models:\n")
+        out_file.write(i2+"# theScene.ModelDic[\"'Model_Name\"']\n")
+        out_file.write(i2+"# where theScene is the SavedScene class instance\n")
+        out_file.write(i2+"# Properties saved include:\n")
+        out_file.write(i2+"# Transformations\n")
+        out_file.write(i2+"# Alpha and color\n")
+        out_file.write(i2+"# Parent and child information\n")
+        out_file.write(i2+"##########################################################################################################\n")
+
+        for model in AllScene.ModelDic:
+            out_file.write("\n")
+            modelS=str(model)
+            
+            if(1): # This is kept for now... perhaps later some sort of check might have to be enforced based on loadMode
+                #Loading Code
+                out_file.write(i2+"# Loading model's egg file\n")
+                #out_file.write(i2+ "self."+ modelS + "=loader.loadModel(\'" + AllScene.ModelRefDic[model].getFullpath() + "\')\n")#Absolute Paths
+
+                newpath = dirname + "/" + AllScene.ModelRefDic[model].getBasename()
+                newpathF=Filename(newpath)
+                newpathSpecific=newpathF.toOsSpecific()
+
+                # Copy all the textures referenced by this file over to the relative directory
+                fnamelist=[]
+                modelData=EggData()
+                modelData.read(AllScene.ModelRefDic[model])
+                textures=EggTextureCollection()
+                textures.findUsedTextures(modelData)
+                for index in range(textures.getNumTextures()):
+                    texture=textures.getTexture(index)
+                    texfilename=texture.getFilename()
+                    fnamelist.append(texfilename.getFullpath())   
+                    oldFilename=Filename(Filename(AllScene.ModelRefDic[model].getDirname()),texfilename)
+                    if(not oldFilename.isRegularFile()):
+                        if(texfilename.resolveFilename(getTexturePath(),"")):
+                            oldFilename=texfilename
+                    oldtexpath=oldFilename.toOsSpecific()
+                
+                    newtexpath=dirname + "/" + texfilename.getBasename()
+                    newtexpathF=Filename(newtexpath)
+                    newtexpathSpecific=newtexpathF.toOsSpecific()
+                    
+                    print "TEXTURE SAVER:: copying" + oldtexpath + " to " + newtexpathSpecific
+                    if(oldtexpath != newtexpathSpecific):
+                        shutil.copyfile(oldtexpath,newtexpathSpecific)
+
+                    
+                
+                    
+
+                
+                # Copy the file over to the relative directory
+                oldModelpath=AllScene.ModelRefDic[model].toOsSpecific()
+                print "FILESAVER:: copying from " + AllScene.ModelRefDic[model].toOsSpecific() + "to" + newpathSpecific 
+                if(oldModelpath!=newpathSpecific):
+                    shutil.copyfile(oldModelpath,newpathSpecific)
+
+                
+                e=EggData()
+                e.read(AllScene.ModelRefDic[model])
+                etc=EggTextureCollection()
+                etc.extractTextures(e)
+                for index in range(len(fnamelist)):
+                    print fnamelist[index]
+                    tex=etc.findFilename(Filename(fnamelist[index]))
+                    fn=Filename(tex.getFilename())
+                    fn.setDirname("")
+                    tex.setFilename(fn)
+                    e.writeEgg(Filename.fromOsSpecific(newpathSpecific))
+                
+
+
+                out_file.write(i2+"if(self.loadmode==1):\n")    
+                out_file.write(i2+i1+ "self."+ modelS + "=loader.loadModel(\'" + self.savepath + "/" +  AllScene.ModelRefDic[model].getBasename() + "')\n")#Relative Path
+                out_file.write(i2+"else:\n")
+                out_file.write(i2+i1+ "self."+ modelS + "=loader.loadModel(self.executionpath + \'/" +  AllScene.ModelRefDic[model].getBasename() + "')\n")#Relative Path with execution point specified by the invoking-level-editor
+
+                #Transformation Code
+                out_file.write("\n")
+                out_file.write(i2+"# Transforming the model\n")
+                out_file.write(i2+ "self."+ modelS + ".setPosHprScale(%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f)\n"% (AllScene.ModelDic[model].getX(),AllScene.ModelDic[model].getY(),AllScene.ModelDic[model].getZ(),AllScene.ModelDic[model].getH(),AllScene.ModelDic[model].getP(),AllScene.ModelDic[model].getR(),AllScene.ModelDic[model].getSx(),AllScene.ModelDic[model].getSy(),AllScene.ModelDic[model].getSz()))
+
+                if(AllScene.ModelDic[model].hasTransparency()):
+                    out_file.write("\n")
+                    out_file.write(i2+"# Alpha\n")
+                    out_file.write(i2+ "self."+ modelS + ".setTransparency(1)\n")
+                    clr=AllScene.ModelDic[model].getColor()
+                    out_file.write(i2+ "self."+ modelS + ".setColor(%.4f,%.4f,%.4f,%.4f)\n"%(clr.getX(),clr.getY(),clr.getZ(),clr.getW()))                  
+
+                out_file.write("\n")
+                out_file.write(i2+ "# Reparent To Render for now and later we update all the parentings\n")
+                out_file.write(i2+ "self."+ modelS + ".reparentTo(render)\n")
+                out_file.write("\n")
+                out_file.write(i2+ "# Save Metadata...can be retrieved by doing theScene.ModelDic[\"Model_Name\"].getTag(\"Metadata\")\n")
+                out_file.write(i2+ "self."+ modelS + ".setTag(\"Metadata\",\"" + AllScene.ModelDic[model].getTag("Metadata") + "\")\n")
+                out_file.write("\n")
+                out_file.write(i2+ "# Fill in the dictionaries which are used by level Ed to reload state\n")
+                out_file.write(i2+ "self.ModelDic[\'" + modelS + "\']=self." + AllScene.ModelDic[model].getName()+"\n")
+                #out_file.write(i2+ "self.ModelRefDic[\'" + modelS + "\']=Filename(\'"+ AllScene.ModelRefDic[model].getFullpath() +"\')\n")# The old Absolute Path way
+                out_file.write(i2+ "self.ModelRefDic[\'" + modelS + "\']=\'"+ AllScene.ModelRefDic[model].getBasename() +"\'\n")# Relative paths
+                out_file.write(i2+ "self.ModelDic[\'"+ modelS + "\'].setName(\'"+ modelS +"\')\n")              
+                out_file.write("\n")
+
+        ####################################################################################################################################################
+        # Save Dummies
+        ####################################################################################################################################################
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Code for all the Dummy Objects\n")
+        out_file.write(i2+"# To access the dummies\n")
+        out_file.write(i2+"# theScene.dummyDict['Dummy_Name']\n")
+        out_file.write(i2+"##########################################################################################################\n")
+        for dummy in AllScene.dummyDict:
+            out_file.write("\n")
+            dummyS=str(dummy)
+            
+            if(1): # This is kept for now... perhaps later some sort of check might have to be enforced based on loadMode
+                out_file.write(i2+ "self."+ dummyS + "=loader.loadModel(\"models/misc/sphere\")\n")
+                #Transformation Code
+                out_file.write(i2+"# Transforming the Dummy\n")
+                out_file.write(i2+ "self."+ dummyS + ".setPosHprScale(%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f)\n"% (AllScene.dummyDict[dummy].getX(),AllScene.dummyDict[dummy].getY(),AllScene.dummyDict[dummy].getZ(),AllScene.dummyDict[dummy].getH(),AllScene.dummyDict[dummy].getP(),AllScene.dummyDict[dummy].getR(),AllScene.dummyDict[dummy].getSx(),AllScene.dummyDict[dummy].getSy(),AllScene.dummyDict[dummy].getSz()))
+                out_file.write("\n")
+                out_file.write(i2+ "# Fill in the dictionaries which are used by level Ed to reload state\n")
+                out_file.write(i2+ "self.dummyDict[\'" + dummyS + "\']=self." + AllScene.dummyDict[dummy].getName()+"\n")
+                out_file.write(i2+ "self.dummyDict[\'"+ dummyS + "\'].setName(\'"+ dummyS +"\')\n")
+                out_file.write("\n")
+                out_file.write(i2+ "# Save Metadata...can be retrieved by doing theScene.dummyDict[\"Dummy_Name\"].getTag(\"Metadata\")\n")             
+                out_file.write(i2+ "self."+ dummyS + ".setTag(\"Metadata\",\"" + AllScene.dummyDict[dummy].getTag("Metadata") + "\")\n")
+                out_file.write("\n")
+
+        ####################################################################################################################################################
+        # Saving Actors and their animations
+        ####################################################################################################################################################                    
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Code for all the Actors and animations\n")
+        out_file.write(i2+"# To access the Actors\n")
+        out_file.write(i2+"# theScene.ActorDic[\'Actor_Name\']\n")
+        out_file.write(i2+"# theScene.ActorDic[\'Actor_Name\'].play(\'Animation_Name\')\n")
+        out_file.write(i2+"##########################################################################################################\n")
+        for actor in AllScene.ActorDic:
+            out_file.write("\n")
+            actorS=str(actor)
+            
+            if(1): # This is kept for now... perhaps later some sort of check might have to be enforced based on loadMode
+                #out_file.write(i2+ "self."+ actorS + "=Actor.Actor(\'"+ AllScene.ActorRefDic[actor].getFullpath() + "\')\n")# The old way with absolute paths
+
+
+                newpath = dirname + "/" + AllScene.ActorRefDic[actor].getBasename()
+                newpathF=Filename(newpath)
+                newpathSpecific=newpathF.toOsSpecific()
+
+                # Copy all the textures referenced by this file over to the relative directory
+                actorfnamelist=[]
+                actorData=EggData()
+                actorData.read(AllScene.ActorRefDic[actor])
+                textures=EggTextureCollection()
+                textures.findUsedTextures(actorData)
+                for index in range(textures.getNumTextures()):
+                    texture=textures.getTexture(index)
+                    texfilename=texture.getFilename()
+                    actorfnamelist.append(texfilename.getFullpath()) 
+
+                    oldFilename=Filename(Filename(AllScene.ActorRefDic[actor].getDirname()),texfilename)
+                    if(not oldFilename.isRegularFile()):
+                        if(texfilename.resolveFilename(getTexturePath(),"")):
+                            oldFilename=texfilename
+                    oldtexpath=oldFilename.toOsSpecific()
+
+
+                    newtexpath=dirname + "/" + texfilename.getBasename()
+                    newtexpathF=Filename(newtexpath)
+                    newtexpathSpecific=newtexpathF.toOsSpecific()
+                    print "TEXTURE SAVER:: copying" + oldtexpath + " to " + newtexpathSpecific
+                    if(oldtexpath != newtexpathSpecific):
+                        shutil.copyfile(oldtexpath,newtexpathSpecific)
+
+
+                # Copy the file over to the relative directory
+                oldActorpath=AllScene.ActorRefDic[actor].toOsSpecific()
+                print "FILESAVER:: copying from " + AllScene.ActorRefDic[actor].toOsSpecific() + "to" + newpathSpecific 
+                if(oldActorpath!=newpathSpecific):
+                    shutil.copyfile(oldActorpath,newpathSpecific)
+                
+
+                e=EggData()
+                e.read(AllScene.ActorRefDic[actor])
+                etc=EggTextureCollection()
+                etc.extractTextures(e)
+                for index in range(len(actorfnamelist)):
+                    print actorfnamelist[index]
+                    tex=etc.findFilename(Filename(actorfnamelist[index]))
+                    fn=Filename(tex.getFilename())
+                    fn.setDirname("")
+                    tex.setFilename(fn)
+                    e.writeEgg(Filename.fromOsSpecific(newpathSpecific))
+                
+
+
+                out_file.write(i2+"if(self.loadmode==1):\n")    
+                out_file.write(i2+i1+ "self."+ actorS + "=Actor.Actor(\'" + self.savepath + "/" +  AllScene.ActorRefDic[actor].getBasename() + "')\n")#Relative Path
+                out_file.write(i2+"else:\n")
+                out_file.write(i2+i1+ "self."+ actorS + "=Actor.Actor(self.executionpath + \'/" +  AllScene.ActorRefDic[actor].getBasename() + "')\n")#Relative Path with execution point specified by the invoking-level-editor
+
+                #Transformation Code
+                out_file.write(i2+"# Transforming the Actor\n")
+                out_file.write(i2+ "self."+ actorS + ".setPosHprScale(%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f)\n"% (AllScene.ActorDic[actor].getX(),AllScene.ActorDic[actor].getY(),AllScene.ActorDic[actor].getZ(),AllScene.ActorDic[actor].getH(),AllScene.ActorDic[actor].getP(),AllScene.ActorDic[actor].getR(),AllScene.ActorDic[actor].getSx(),AllScene.ActorDic[actor].getSy(),AllScene.ActorDic[actor].getSz()))
+
+                if(AllScene.ActorDic[actor].hasTransparency()):
+                    out_file.write(i2+"# Alpha\n")
+                    out_file.write(i2+ "self."+ actorS + ".setTransparency(1)\n")
+                    clr=AllScene.ActorDic[actor].getColor()
+                    out_file.write(i2+ "self."+ actorS + ".setColor(%.4f,%.4f,%.4f,%.4f)\n"%(clr.getX(),clr.getY(),clr.getZ(),clr.getW()))
+
+                out_file.write(i2+ "self."+ actorS + ".reparentTo(render)\n")
+                
+                out_file.write("\n")
+                out_file.write(i2+ "# Save Metadata...can be retrieved by doing theScene.ActorDic[\"Actor_Name\"].getTag(\"Metadata\")\n")
+                out_file.write(i2+ "self."+ actorS + ".setTag(\"Metadata\",\"" + AllScene.ActorDic[actor].getTag("Metadata") + "\")\n")
+
+                out_file.write("\n")
+                out_file.write(i2+ "# Fill in the dictionaries which are used by level Ed to reload state\n")
+                ActorAnimations=AllScene.getAnimationDictFromActor(actor)
+                ActorAnimationsInvoke={}
+
+                if(ActorAnimations!={}):  #Check if a dictionary of animations exists for this actor
+                    for animation in ActorAnimations:
+                        #out_file.write(i2+ "self."+ actorS + ".loadAnims(" + str(ActorAnimations) +")\n") # Old way with absolute paths
+                        #Manakel 2/12/2004: solve the not empty but not defined animation case
+                        if not animation is None:
+                            print "ACTOR ANIMATIONS:" + ActorAnimations[animation]   
+                            oldAnimPath=Filename(ActorAnimations[animation])
+                            oldAnim=oldAnimPath.toOsSpecific()
+                            dirOS=Filename(dirname)
+                            newAnim=dirOS.toOsSpecific() + "\\" + oldAnimPath.getBasename()
+                            print "ACTOR ANIM SAVER:: Comparing" + oldAnim +"and" + newAnim
+                            if(oldAnim!=newAnim):
+                                shutil.copyfile(oldAnim,newAnim)
+                            newAnimF=Filename.fromOsSpecific(newAnim)
+                            ActorAnimationsInvoke[animation]="self.executionpath +" + "/" +newAnimF.getBasename()
+                            ActorAnimations[animation]= self.savepath + "/" + newAnimF.getBasename()
+
+
+                out_file.write(i2+"if(self.loadmode==1):\n")
+                out_file.write(i2+ i1+"self."+ actorS + ".loadAnims(" + str(ActorAnimations) +")\n") # Now with new relative paths
+                out_file.write(i2+"else:\n")
+                theloadAnimString=str(ActorAnimationsInvoke)# We hack the "self.executionpath" part into the dictionary as a variable using string replace
+                print "LOAD ANIM STRING BEFORE" + theloadAnimString
+                theloadAnimString=theloadAnimString.replace('\'self.executionpath +','self.executionpath + \'')         
+                print "LOAD ANIM STRING AFTER" + theloadAnimString
+                out_file.write(i2+ i1+"self."+ actorS + ".loadAnims(" + theloadAnimString +")\n") # Now with new relative paths based on editor invocation
+
+                out_file.write(i2+ "self.ActorDic[\'" + actorS + "\']=self." + AllScene.ActorDic[actor].getName()+"\n")
+                #out_file.write(i2+ "self.ActorRefDic[\'" + actorS + "\']=Filename(\'"+AllScene.ActorRefDic[actor].getFullpath() +"\')\n") # Old way with absolute paths
+                out_file.write(i2+ "self.ActorRefDic[\'" + actorS + "\']=\'"+ AllScene.ActorRefDic[actor].getBasename() +"\'\n")# Relative paths
+                out_file.write(i2+ "self.ActorDic[\'"+ actorS + "\'].setName(\'"+ actorS +"\')\n")              
+                if(AllScene.blendAnimDict.has_key(actor)): # Check if a dictionary of blended animations exists
+                    out_file.write(i2+ "self.blendAnimDict[\"" + actorS +"\"]=" + str(AllScene.blendAnimDict[actor]) + "\n")
+                
+                
+                out_file.write("\n")
+        
+        ####################################################################################################################################################
+        # Collsion Node Saving
+        ####################################################################################################################################################
+        
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Code for setting up Collision Nodes\n")
+        out_file.write(i2+"# To use collision detection:\n")
+        out_file.write(i2+"# You must set up your own bitmasking and event handlers, the traverser \"cTrav\" is created for you at the top\n")
+        out_file.write(i2+"# The collision nodes are stored in collisionDict\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+        for collnode in AllScene.collisionDict:
+            collnodeS=str(collnode)
+            solid=AllScene.collisionDict[collnode].node().getSolid(0)
+            nodetype=solid.getType().getName()
+
+            if(nodetype=="CollisionSphere"): #Save Collison Sphere
+                out_file.write(i2+"collSolid=CollisionSphere(%.3f,%.3f,%.3f,%.3f)\n"%(solid.getCenter().getX(),solid.getCenter().getY(),solid.getCenter().getZ(),solid.getRadius()))            
+                pass
+            elif(nodetype=="CollisionPolygon"): #Save Collison Polygon
+
+                ax=AllScene.collisionDict[collnode].getTag("A_X")
+                ay=AllScene.collisionDict[collnode].getTag("A_Y")
+                az=AllScene.collisionDict[collnode].getTag("A_Z")
+
+                bx=AllScene.collisionDict[collnode].getTag("B_X")
+                by=AllScene.collisionDict[collnode].getTag("B_Y")
+                bz=AllScene.collisionDict[collnode].getTag("B_Z")
+
+                cx=AllScene.collisionDict[collnode].getTag("C_X")
+                cy=AllScene.collisionDict[collnode].getTag("C_Y")
+                cz=AllScene.collisionDict[collnode].getTag("C_Z")
+
+                out_file.write(i2+"pointA =  Point3(" + ax + "," + ay + "," + az + ")\n")
+                out_file.write(i2+"pointB =  Point3(" + bx + "," + by + "," + bz + ")\n")
+                out_file.write(i2+"pointC =  Point3(" + cx + "," + cy + "," + cz + ")\n")
+                out_file.write(i2+"collSolid=CollisionPolygon(pointA, pointB, pointC)\n")
+                
+                pass
+
+            elif(nodetype=="CollisionSegment"): #Save Collison Segment
+                A=AllScene.collisionDict[collnode].node().getSolid(0).getPointA()
+                B=AllScene.collisionDict[collnode].node().getSolid(0).getPointB()
+                
+                out_file.write(i2+"pointA =  Point3(%.3f,%.3f,%.3f)\n"%(A.getX(),A.getY(),A.getZ()))
+                out_file.write(i2+"pointB =  Point3(%.3f,%.3f,%.3f)\n"%(B.getX(),B.getY(),B.getZ()))
+                out_file.write(i2+"collSolid=CollisionSegment()\n")
+                out_file.write(i2+"collSolid.setPointA(pointA)\n")
+                out_file.write(i2+"collSolid.setFromLens(base.cam.node(),Point2(-1,1))\n")
+                out_file.write(i2+"collSolid.setPointB(pointB)\n")
+
+                pass
+
+            elif(nodetype=="CollisionRay"): #Save Collison Ray
+                P =  AllScene.collisionDict[collnode].node().getSolid(0).getOrigin()
+                V =  AllScene.collisionDict[collnode].node().getSolid(0).getDirection()
+
+                out_file.write(i2+"point=Point3(%.3f,%.3f,%.3f)\n"%(P.getX(),P.getY(),P.getZ()))
+                out_file.write(i2+"vector=Vec3(%.3f,%.3f,%.3f)\n"%(V.getX(),V.getY(),V.getZ()))
+                out_file.write(i2+"collSolid=CollisionRay()\n")
+                out_file.write(i2+"collSolid.setOrigin(point)\n")
+                out_file.write(i2+"collSolid.setDirection(vector)\n")
+            
+                pass
+            else:
+                 print "Invalid Collision Node: " + nodetype
+            out_file.write("\n")
+
+
+            out_file.write(i2+"self." + collnodeS + "_Node" + "=CollisionNode(\""+collnodeS+"\")\n")
+            out_file.write(i2+"self." + collnodeS + "_Node" + ".addSolid(collSolid)\n")
+            out_file.write(i2+"base.cTrav.addCollider(self." + collnodeS + "_Node,self.CollisionHandler)\n")
+            out_file.write("\n")
+           
+
+
+
+        ####################################################################################################################################################
+        # Light Saving
+        ####################################################################################################################################################
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Code for Lighting\n")
+        out_file.write(i2+"# To manipulated lights:\n")
+        out_file.write(i2+"# Manipulate the light node in theScene.LightNodes[\'Light_Name\']\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+
+        LightList=AllScene.lightManager.getLightNodeList()
+        for light in LightList:
+                 type = light.getType()
+                 if type == 'ambient':
+                     out_file.write(i2+"# Ambient Light\n")
+                     out_file.write (i2+ "self.ambientCount += 1\n")
+                     out_file.write (i2+ "alight = AmbientLight(\'"+ light.getName() +"\')\n")
+                     out_file.write (i2+ "alight.setColor(VBase4("+ str(light.getLightColor().getX())+ "," + str(light.getLightColor().getY())+ "," + str(light.getLightColor().getZ()) + "," + str(light.getLightColor().getW()) + "))\n")
+                     out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
+                     out_file.write (i2+ "self."+light.getName()+"= render.attachNewNode(alight.upcastToPandaNode())\n") 
+                     out_file.write (i2+ "self."+light.getName()+".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")        
+                     out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
+                     out_file.write (i2+ "self.LightTypes[\'" + light.getName() + "\']=\'" + type + "\'\n")
+                     out_file.write (i2+ "self.LightNodes[\'" + light.getName() + "\']=self." + light.getName() + "\n")
+                     out_file.write ("\n")
+                 elif type == 'directional':
+                     out_file.write(i2+"# Directional Light\n")
+                     out_file.write (i2+ "self.directionalCount += 1\n")
+                     out_file.write (i2+ "alight = DirectionalLight(\'"+ light.getName() + "\')\n")
+                     out_file.write (i2+ "alight.setColor(VBase4("+ str(light.getLightColor().getX())+ "," + str(light.getLightColor().getY())+ "," + str(light.getLightColor().getZ()) + "," + str(light.getLightColor().getW()) + "))\n")
+                     #out_file.write (i2+ "alight.setDirection(Vec3("+ str(light.getH())+ "," + str(light.getP())+ "," + str(light.getR()) + "))\n")
+                     #out_file.write (i2+ "alight.setPoint(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
+                     out_file.write (i2+ "alight.setSpecularColor(Vec4(" + str(light.getSpecColor().getX()) + "," + str(light.getSpecColor().getY()) + "," + str(light.getSpecColor().getZ()) + "," + str(light.getSpecColor().getW()) + "))\n")
+                     out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
+                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight.upcastToPandaNode())\n") 
+                     out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
+                     out_file.write (i2+ "self."+light.getName()+ ".setHpr(Vec3("+ str(light.getH())+ "," + str(light.getP())+ "," + str(light.getR()) + "))\n")
+                     out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")       
+                     #out_file.write (i2+ "alight.setPos
+                     out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
+                     out_file.write (i2+ "self.LightTypes[\'" + light.getName() + "\']=\'" + type + "\'\n")
+                     out_file.write (i2+ "self.LightNodes[\'" + light.getName() + "\']=self." + light.getName()  + "\n")
+                     out_file.write ("\n")
+                 elif type == 'point':
+                     out_file.write(i2+"# Point Light\n")
+                     out_file.write (i2+ "self.pointCount += 1\n")
+                     out_file.write (i2+ "alight = PointLight(\'"+ light.getName() +"\')\n")
+                     out_file.write (i2+ "alight.setColor(VBase4("+ str(light.getLightColor().getX())+ "," + str(light.getLightColor().getY())+ "," + str(light.getLightColor().getZ()) + "," + str(light.getLightColor().getW()) + "))\n")
+                     #out_file.write (i2+ "alight.setPoint(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
+                     out_file.write (i2+ "alight.setSpecularColor(Vec4(" + str(light.getSpecColor().getX()) + "," + str(light.getSpecColor().getY()) + "," + str(light.getSpecColor().getZ()) + "," + str(light.getSpecColor().getW()) + "))\n")
+                     out_file.write (i2+ "alight.setAttenuation(Vec3("+ str(light.getAttenuation().getX()) + "," + str(light.getAttenuation().getY()) + "," + str(light.getAttenuation().getZ()) + "))\n")
+                     out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
+                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight.upcastToPandaNode())\n") 
+                     out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")       
+                     out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
+                     out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
+                     out_file.write (i2+ "self.LightTypes[\'" + light.getName() + "\']=\'" + type + "\'\n")
+                     out_file.write (i2+ "self.LightNodes[\'" + light.getName() + "\']=self." + light.getName() +  "\n")
+                     out_file.write ("\n")
+                 elif type == 'spot':
+                     out_file.write(i2+"# Spot Light\n")
+                     out_file.write (i2+ "self.spotCount += 1\n")
+                     out_file.write (i2+ "alight = Spotlight(\'"+ light.getName() + "\')\n")
+                     out_file.write (i2+ "alight.setColor(VBase4("+ str(light.getLightColor().getX())+ "," + str(light.getLightColor().getY())+ "," + str(light.getLightColor().getZ()) + "," + str(light.getLightColor().getW()) + "))\n")
+                     out_file.write (i2+ "alens = PerspectiveLens()\n")
+                     out_file.write (i2+ "alight.setLens(alens)\n")
+                     out_file.write (i2+ "alight.setSpecularColor(Vec4(" + str(light.getSpecColor().getX()) + "," + str(light.getSpecColor().getY()) + "," + str(light.getSpecColor().getZ()) + "," + str(light.getSpecColor().getW()) + "))\n")
+                     out_file.write (i2+ "alight.setAttenuation(Vec3("+ str(light.getAttenuation().getX()) + "," + str(light.getAttenuation().getY()) + "," + str(light.getAttenuation().getZ()) + "))\n")
+                     out_file.write (i2+ "alight.setExponent(" +str(light.getExponent()) +")\n")
+                     out_file.write (i2+ "self.lightAttrib=self.lightAttrib.addLight(alight)\n")
+                     out_file.write (i2+ "self."+light.getName()+ "= render.attachNewNode(alight.upcastToLensNode())\n") 
+                     out_file.write (i2+ "self."+light.getName()+ ".setTag(\"Metadata\",\"" + light.getTag("Metadata") + "\")\n")       
+                     out_file.write (i2+ "self."+light.getName()+ ".setPos(Point3(" + str(light.getX()) + "," + str(light.getY()) + "," + str(light.getZ()) + "))\n")
+                     out_file.write (i2+ "self."+light.getName()+ ".setHpr(Vec3("+ str(light.getH())+ "," + str(light.getP())+ "," + str(light.getR()) + "))\n")
+                     out_file.write (i2+ "self.LightDict[\'" + light.getName() + "\']=alight\n")
+                     out_file.write (i2+ "self.LightTypes[\'" + light.getName() + "\']=\'" + type + "\'\n")
+                     out_file.write (i2+ "self.LightNodes[\'" + light.getName() + "\']=self." + light.getName() + "\n")
+                     out_file.write ("\n")
+                 else:
+                     out_file.write (i2+ "print \'Invalid light type\'")
+                     out_file.write (i2+ "return None")
+                 out_file.write("\n")
+
+        
+
+        ####################################################################################################################################################
+        # Enable Lighting
+        ####################################################################################################################################################
+
+        out_file.write(i2+ "# Enable Ligthing\n")
+        out_file.write(i2+ "render.node().setAttrib(self.lightAttrib)\n")
+        out_file.write("\n")
+
+
+
+        ####################################################################################################################################################
+        # Initialize Particles for non scene editor mode
+        ####################################################################################################################################################
+
+        out_file.write(i2+"# Load Particle Effects. The parameters to this function are to allow us to use our modified versions of the Particle Effects modules when loading this file with the level editor\n")
+        out_file.write(i2+"self.starteffects(self.loadmode,self.seParticleEffect,self.seParticles)\n")
+        out_file.write("\n")
+                
+        ####################################################################################################################################################
+        # Save Camera Settings
+        ####################################################################################################################################################
+
+        out_file.write("\n")
+        out_file.write(i2+ "# Save Camera Settings\n")
+        out_file.write(i2+ "camera.setX(" + str(camera.getX()) + ")\n")         
+        out_file.write(i2+ "camera.setY(" + str(camera.getY()) + ")\n")         
+        out_file.write(i2+ "camera.setZ(" + str(camera.getZ()) + ")\n")         
+        out_file.write(i2+ "camera.setH(" + str(camera.getH()) + ")\n")         
+        out_file.write(i2+ "camera.setP(" + str(camera.getP()) + ")\n")         
+        out_file.write(i2+ "camera.setR(" + str(camera.getR()) + ")\n") 
+        out_file.write(i2+ "camera.getChild(0).node().getLens().setNear(" + str(camera.getChild(0).node().getLens().getNear()) + ")\n") 
+        out_file.write(i2+ "camera.getChild(0).node().getLens().setFar(" + str(camera.getChild(0).node().getLens().getFar()) + ")\n") 
+        out_file.write(i2+ "camera.getChild(0).node().getLens().setFov(VBase2(%.5f,%.5f))\n"% (camera.getChild(0).node().getLens().getHfov(),camera.getChild(0).node().getLens().getVfov())) 
+        FilmSize=camera.getChild(0).node().getLens().getFilmSize()
+        out_file.write(i2+ "camera.getChild(0).node().getLens().setFilmSize(%.3f,%.3f)\n"%(FilmSize.getX(),FilmSize.getY())) 
+        out_file.write(i2+ "camera.getChild(0).node().getLens().setFocalLength(" + str(camera.getChild(0).node().getLens().getFocalLength()) + ")\n") 
+        out_file.write(i2+ "camera.setTag(\"Metadata\",\"" + camera.getTag("Metadata") + "\")\n")       
+        out_file.write(i2+ "camera.reparentTo(render)\n")
+        out_file.write(i2+ "base.disableMouse()\n")
+        self.bgColor=base.getBackgroundColor()
+        out_file.write(i2+ "base.setBackgroundColor(%.3f,%.3f,%.3f)\n"%(self.bgColor.getX(),self.bgColor.getY(),self.bgColor.getZ()))
+        out_file.write("\n")
+        
+
+        ####################################################################################################################################################
+        # Mopath Saving
+        ####################################################################################################################################################
+
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Motion Paths\n")
+        out_file.write(i2+"# Using Mopaths:\n")
+        out_file.write(i2+"# theScene.curveIntervals[0].start() or .loop() will play curve with index 0\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+
+        for node in AllScene.curveDict:
+            curveCollection=AllScene.curveDict[node]
+            curvenumber=0
+            for curve in curveCollection:
+                filestring=dirname+ "\\" + str(node)+"_curve_"+str(curvenumber)+".egg"
+                f=Filename.fromOsSpecific(filestring)
+                #filestring=f.getFullpath()# The old absolute path way
+                filestring=f.getBasename() # The new relative path way
+                curve.writeEgg(f)
+                out_file.write(i2+"m=Mopath.Mopath()\n")
+
+                out_file.write(i2+"if(self.loadmode==1):\n")
+                out_file.write(i2+i1+"m.loadFile(\"" + self.savepath +"/"+ filestring + "\")\n") # If just normally executed
+                out_file.write(i2+"else:\n")
+                out_file.write(i2+i1+"m.loadFile(self.executionpath + \"/"+ filestring + "\")\n") # If being invoked by level Ed
+
+                out_file.write(i2+"mp=MopathInterval(m,self." + str(node) + ")\n")
+                out_file.write(i2+"self.curveIntervals.append(mp)\n")
+                
+                out_file.write(i2+"if(self.loadmode==1):\n")
+                out_file.write(i2+i1+"self.curveRefColl.append(\"" + self.savepath +"/"+ filestring +"\")\n")
+                out_file.write(i2+"else:\n")
+                out_file.write(i2+i1+"self.curveRefColl.append(self.executionpath + \"/"+ filestring +"\")\n")
+
+                curvenumber=curvenumber+1
+            out_file.write(i2+"self.curveIntervalsDict[\"" + str(node) + "\"]=self.curveIntervals\n")        
+            out_file.write(i2+"self.curveDict[\"" + str(node) + "\"]=self.curveRefColl\n")
+
+    
+        ####################################################################################################################################################
+        # Lets do all the reparenting here so as to make sure everything that needed to load was loaded
+        ####################################################################################################################################################
+
+
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Reparenting\n")
+        out_file.write(i2+"# A final pass is done on setting all the scenegraph hierarchy after all objects are laoded\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+
+        for model in AllScene.ModelDic:
+            modelS=str(model)
+            parent=AllScene.ModelDic[model].getParent().getName()
+            if(parent=="render" or parent=="camera"):
+                out_file.write(i2+ "self."+ modelS + ".reparentTo(" + parent + ")\n")
+            else:
+                if(AllScene.particleDict.has_key(parent)):
+                    out_file.write(i2+ "self."+ modelS + ".reparentTo(self." + parent + ".getEffect())\n")
+                else:
+                    out_file.write(i2+ "self."+ modelS + ".reparentTo(self." + parent + ")\n")
+            out_file.write(i2+ "self.ModelDic[\'" + modelS + "\']=self." + AllScene.ModelDic[model].getName()+"\n")
+            out_file.write(i2+"\n")
+
+        for dummy in AllScene.dummyDict:
+            dummyS=str(dummy)
+            parent=AllScene.dummyDict[dummy].getParent().getName()
+            if(parent=="render" or parent=="camera"):
+                out_file.write(i2+ "self."+ dummyS + ".reparentTo(" + parent + ")\n")
+            else:  
+                if(AllScene.particleDict.has_key(parent)):
+                    out_file.write(i2+ "self."+ dummyS + ".reparentTo(self." + parent + ".getEffect())\n")
+                else:
+                    out_file.write(i2+ "self."+ dummyS + ".reparentTo(self." + parent + ")\n")
+
+            out_file.write(i2+ "self.dummyDict[\'" + dummyS + "\']=self." + AllScene.dummyDict[dummy].getName()+"\n")
+            out_file.write(i2+"\n")
+
+        for actor in AllScene.ActorDic:
+            actorS=str(actor)   
+            parent=AllScene.ActorDic[actor].getParent().getName()
+            if(parent=="render" or parent=="camera"):
+                out_file.write(i2+ "self."+ actorS + ".reparentTo(" + parent + ")\n")
+            else:
+                if(AllScene.particleDict.has_key(parent)):
+                    out_file.write(i2+ "self."+ actorS + ".reparentTo(self." + parent + ".getEffect())\n")
+                else:
+                    out_file.write(i2+ "self."+ actorS + ".reparentTo(self." + parent + ")\n")
+
+            out_file.write(i2+ "self.ActorDic[\'" + actorS + "\']=self." + AllScene.ActorDic[actor].getName()+"\n")
+            out_file.write(i2+"\n")
+        
+
+        for collnode in AllScene.collisionDict:
+            collnodeS=str(collnode)
+            solid=AllScene.collisionDict[collnode].node().getSolid(0)
+            nodetype=solid.getType().getName()
+            parentname=AllScene.collisionDict[collnode].getParent().getName()
+            if(parentname=="render" or parentname =="camera"):
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"]="+ parentname + ".attachNewNode(self." + collnodeS + "_Node)\n")
+            else:
+                #Manakel 2/12/2005: parent replaced by parent Name but why Parent name in partice and parent for other objects?
+                if(AllScene.particleDict.has_key(parentname)):
+                    out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"]=self."+ parentname + "getEffect().attachNewNode(self." + collnodeS + "_Node)\n")
+                else:
+                    out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"]=self."+ parentname + ".attachNewNode(self." + collnodeS + "_Node)\n")
+            dictelem=AllScene.collisionDict[collnode]
+            out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setPosHprScale(%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f,%.3f)\n"%(dictelem.getX(),dictelem.getY(),dictelem.getZ(),dictelem.getH(),dictelem.getP(),dictelem.getR(),dictelem.getSx(),dictelem.getSy(),dictelem.getSz()))
+            out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag(\"Metadata\",\"" + AllScene.collisionDict[collnode].getTag("Metadata") + "\")\n")
+            out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].show()\n")
+            if(nodetype=="CollisionPolygon"): #Save Collison Polygon... the reason we need to use setTag here is because there is no inbuilt way of saving transforms for collision polys
+
+                ax=float(AllScene.collisionDict[collnode].getTag("A_X"))
+                ay=float(AllScene.collisionDict[collnode].getTag("A_Y"))
+                az=float(AllScene.collisionDict[collnode].getTag("A_Z"))
+
+                bx=float(AllScene.collisionDict[collnode].getTag("B_X"))
+                by=float(AllScene.collisionDict[collnode].getTag("B_Y"))
+                bz=float(AllScene.collisionDict[collnode].getTag("B_Z"))
+
+                cx=float(AllScene.collisionDict[collnode].getTag("C_X"))
+                cy=float(AllScene.collisionDict[collnode].getTag("C_Y"))
+                cz=float(AllScene.collisionDict[collnode].getTag("C_Z"))
+
+                out_file.write(i2+"pointA=Point3(%.3f,%.3f,%.3f)\n"%(ax,ay,az))
+                out_file.write(i2+"pointB=Point3(%.3f,%.3f,%.3f)\n"%(bx,by,bz))
+                out_file.write(i2+"pointC=Point3(%.3f,%.3f,%.3f)\n"%(cx,cy,cz))
+
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('A_X','%f'%pointA.getX())\n")
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('A_Y','%f'%pointA.getY())\n")
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('A_Z','%f'%pointA.getZ())\n")
+
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('B_X','%f'%pointB.getX())\n")
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('B_Y','%f'%pointB.getY())\n")
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('B_Z','%f'%pointB.getZ())\n")
+
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('C_X','%f'%pointC.getX())\n")
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('C_Y','%f'%pointC.getY())\n")
+                out_file.write(i2+"self.collisionDict[\"" + collnodeS + "\"].setTag('C_Z','%f'%pointC.getZ())\n")
+            out_file.write(i2+"\n")
+
+
+        for effect in AllScene.particleDict:
+            parent=AllScene.particleNodes[effect].getParent().getName()
+            if(parent=="render" or parent=="camera"):
+                out_file.write(i2+"self.particleDict[\""+ str(effect) +"\"].reparentTo("  + parent + ")\n")             
+            else:
+                out_file.write(i2+"self.particleDict[\""+ str(effect) +"\"].reparentTo(self."  + parent + ")\n")
+            out_file.write(i2+"\n")
+
+
+        ####################################################################################################################################################
+        # Particle Saving
+        ####################################################################################################################################################
+        out_file.write("\n")
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Particle Effects\n")
+        out_file.write(i2+"# Using Particles:\n")
+        out_file.write(i2+"# theScene.enableeffect(\"Effect_Name\")\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+        out_file.write(i1+"def starteffects(self,mode,seParticleEffect=None,seParticles=None):\n")
+        for effect in AllScene.particleDict:
+            effectS=str(effect)
+            out_file.write(i2+ "self." + effectS + "=" + effectS + "(mode,seParticleEffect,seParticles)\n")
+            out_file.write(i2+ "effect=self."+ effectS + ".getEffect()\n")
+            out_file.write(i2+ "self.particleDict[\"" + effectS + "\"]=effect\n")
+            out_file.write(i2+ "effect.reparentTo(render)\n")
+            thenode=AllScene.particleNodes[effect]
+            out_file.write(i2+ "self.particleDict[\"" + effectS + "\"].setPosHprScale(%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f,%.2f)\n"%(thenode.getX(),thenode.getY(),thenode.getZ(),thenode.getH(),thenode.getP(),thenode.getR(),thenode.getSx(),thenode.getSy(),thenode.getSz()))
+            out_file.write("\n")
+        out_file.write(i2+"return\n")
+        out_file.write("\n")
+        out_file.write(i1+"def enableeffect(self,effect_name):\n")
+        out_file.write(i2+"self.particleDict[effect_name].enable()\n")
+        out_file.write(i2+"return\n")
+        out_file.write("\n")
+        out_file.write(i1+"def disableeffect(self,effect_name):\n")
+        out_file.write(i2+"self.particleDict[effect_name].disable()\n")
+        out_file.write(i2+"return\n")
+        out_file.write("\n")
+
+
+        ####################################################################################################################################################
+        # Animation Blending Methods
+        ####################################################################################################################################################
+        out_file.write("\n")
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Animation Blending\n")
+        out_file.write(i2+"# Using blending:\n")
+        out_file.write(i2+"# theScene.playBlendAnim(actor,blendname)\n")
+        out_file.write(i2+"# theScene.stopBlendAnim(actor,blendname)\n")
+        out_file.write(i2+"# theScene.changeBlendAnim(actor,blendname,blend_amount)\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+        out_file.write(i1+"def playBlendAnim(self,actor,blendName,loop=0):\n")
+        out_file.write(i2+"actor.enableBlend()\n")
+        out_file.write(i2+"blendDicts=self.blendAnimDict[actor.getName()]\n")
+        out_file.write(i2+"blendList=blendDicts[blendName]\n")
+        out_file.write(i2+"actor.setControlEffect(blendList[0],blendList[2])\n")
+        out_file.write(i2+"actor.setControlEffect(blendList[1],1.0-blendList[2])\n")
+        out_file.write(i2+"if(loop):\n")
+        out_file.write(i2+i1+"actor.loop(blendList[0])\n")
+        out_file.write(i2+i1+"actor.loop(blendList[1])\n")
+        out_file.write(i2+"else:\n")
+        out_file.write(i2+i1+"actor.start(blendList[0])\n")
+        out_file.write(i2+i1+"actor.start(blendList[1])\n")
+        out_file.write("\n")
+
+
+        out_file.write(i1+"def stopBlendAnim(self,actor,blendName):\n")
+        out_file.write(i2+"blendDicts=self.blendAnimDict[actor.getName()]\n")
+        out_file.write(i2+"blendList=blendDicts[blendName]\n")
+        out_file.write(i2+"actor.stop(blendList[0])\n")
+        out_file.write(i2+"actor.stop(blendList[1])\n")
+        out_file.write("\n")    
+
+        out_file.write(i1+"def changeBlending(self,actor,blendName,blending):\n")
+        out_file.write(i2+"blendDicts=self.blendAnimDict[actor.getName()]\n")
+        out_file.write(i2+"blendList=blendDicts[blendName]\n")  
+        out_file.write(i2+"blendList[2]=blending\n")
+        out_file.write(i2+"self.blendAnimDict[actor.getName()]={blendName:[blendList[0],blendList[1],blending]}\n")
+        out_file.write("\n")
+
+
+
+        ####################################################################################################################################################
+        # Hide and Show Methods
+        ####################################################################################################################################################
+     
+        out_file.write("\n")
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Hide and Show Methods\n")
+        out_file.write(i2+"# These will help you hide/show dummies, collision solids, effect nodes etc.\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+  
+     
+        out_file.write("\n")
+        out_file.write(i1+"def hideDummies(self):\n")
+        out_file.write("\n")
+        out_file.write(i2+"for dummy in self.dummyDict:\n")
+        out_file.write(i2+i1+"self.dummyDict[dummy].reparentTo(hidden)\n")
+        
+       
+        out_file.write("\n")
+        out_file.write(i1+"def hideCollSolids(self):\n")
+        out_file.write("\n")
+        out_file.write(i2+"for collSolid in self.collisionDict:\n")
+        out_file.write(i2+i1+"self.collisionDict[collSolid].hide()\n")
+
+
+        out_file.write("\n")
+        out_file.write(i1+"def hideEffectNodes(self):\n")
+        out_file.write("\n")
+        out_file.write(i2+"for effectnode in self.particleNodes:\n")
+        out_file.write(i2+i1+"self.particleNodes[effectnode].hide()\n")
+
+
+        out_file.write("\n")
+        out_file.write(i1+"def showDummies(self):\n")
+        out_file.write("\n")
+        out_file.write(i2+"for dummy in self.dummyDict:\n")
+        out_file.write(i2+i1+"self.dummyDict[dummy].reparentTo(hidden)\n")
+        
+       
+        out_file.write("\n")
+        out_file.write(i1+"def showCollSolids(self):\n")
+        out_file.write("\n")
+        out_file.write(i2+"for collSolid in self.collisionDict:\n")
+        out_file.write(i2+i1+"self.collisionDict[collSolid].show()\n")
+
+
+        out_file.write("\n")
+        out_file.write(i1+"def showEffectNodes(self):\n")
+        out_file.write("\n")
+        out_file.write(i2+"for effectnode in self.particleNodes:\n")
+        out_file.write(i2+i1+"self.particleNodes[effectnode].show()\n\n")
+
+
+        ##########################################################################################################
+        # Saving Particle Parameters as a Class
+        ##########################################################################################################
+
+        out_file.write("\n")
+        out_file.write(i2+"##########################################################################################################\n")
+        out_file.write(i2+"# Particle Effects\n")
+        out_file.write(i2+"# This is where effect parameters are saved in a class\n")
+        out_file.write(i2+"# The class is then instantiated in the starteffects method and appended to the dictionaries\n")
+        out_file.write(i2+"##########################################################################################################\n\n")
+
+        for effect in AllScene.particleDict:
+        
+            out_file.write("\n\n")
+            out_file.write("class " + str(effect) + ":\n")
+            out_file.write(i1+"def __init__(self,mode=1,seParticleEffect=None,seParticles=None):\n")
+            out_file.write(i2+"if(mode==0):\n")
+            out_file.write(i2+i1+"self.effect=seParticleEffect.ParticleEffect()\n")
+            out_file.write(i2+"else:\n")
+            out_file.write(i2+i1+"self.effect=ParticleEffect.ParticleEffect()\n")
+            AllScene.particleDict[effect].AppendConfig(out_file)
+            #out_file.write(i2+"return self.effect\n")
+            out_file.write("\n\n")
+            out_file.write(i1+"def starteffect(self):\n")
+            out_file.write(i2+"pass\n")
+            out_file.write("\n\n")
+            out_file.write(i1+"def stopeffect(self):\n")
+            out_file.write(i2+"pass\n\n")
+            out_file.write(i1+"def getEffect(self):\n")
+            out_file.write(i2+"return self.effect\n\n")
+
+
+
+        #Un-comment the lines below to make this a stand-alone file
+        #out_file.write("Scene=SavedScene()\n")
+        #out_file.write("run()\n")
+
+        out_file.close()
+                
+                

+ 134 - 0
contrib/src/sceneeditor/seForceGroup.py

@@ -0,0 +1,134 @@
+from pandac.PandaModules import *
+from direct.showbase.DirectObject import DirectObject
+from direct.showbase.PhysicsManagerGlobal import *
+#Manakel 2/12/2005: replace from pandac import by from pandac.PandaModules import
+from pandac.PandaModules import ForceNode
+from direct.directnotify import DirectNotifyGlobal
+import sys
+
+class ForceGroup(DirectObject):
+
+    notify = DirectNotifyGlobal.directNotify.newCategory('ForceGroup')
+    id = 1
+
+    def __init__(self, name=None):
+        """__init__(self)"""
+
+        if (name == None):
+            self.name = 'ForceGroup-%d' % ForceGroup.id 
+            ForceGroup.id += 1
+        else:
+            self.name = name
+
+        self.node = ForceNode.ForceNode(self.name)
+        self.nodePath = NodePath(self.node)
+        self.fEnabled = 0
+
+        self.particleEffect = None
+
+    def cleanup(self):
+        self.node.clear()
+        self.nodePath.removeNode()
+        del self.nodePath
+        del self.node
+        del self.particleEffect
+
+    def enable(self):
+        """
+        Convenience function to enable all forces in force group
+        """
+        for i in range(self.node.getNumForces()):
+            f = self.node.getForce(i)
+            f.setActive(1)
+        self.fEnabled = 1
+
+    def disable(self):
+        """
+        Convenience function to disable all forces in force group
+        """
+        for i in range(self.node.getNumForces()):
+            f = self.node.getForce(i)
+            f.setActive(0)
+        self.fEnabled = 0
+
+    def isEnabled(self):
+        return self.fEnabled
+
+    def addForce(self, force):
+        """addForce(self, force)"""
+        self.node.addForce(force)
+        if (self.particleEffect):
+            self.particleEffect.addForce(force)
+
+    def removeForce(self, force):
+        """removeForce(self, force)"""
+        self.node.removeForce(force)
+        if (self.particleEffect != None):
+            self.particleEffect.removeForce(force)
+
+    # Get/set
+    def getName(self):
+        return self.name
+    def getNode(self):
+        return self.node
+    def getNodePath(self):
+        return self.nodePath
+
+    # Utility functions 
+    def __getitem__(self, index):
+        numForces = self.node.getNumForces()
+        if ((index < 0) or (index >= numForces)):
+            raise IndexError
+        return self.node.getForce(index)
+
+    def __len__(self):
+        return self.node.getNumForces()
+
+    def asList(self):
+        l = []
+        for i in range(self.node.getNumForces()):
+            l.append(self.node.getForce(i))
+        return l
+
+    def printParams(self, file = sys.stdout, targ = 'self'):
+        i1="    "
+        i2=i1+i1
+        file.write(i2+'# Force parameters\n')
+        for i in range(self.node.getNumForces()):
+            f = self.node.getForce(i)
+            fname = 'force%d' % i
+            if isinstance(f, LinearForce):
+                amplitude = f.getAmplitude()
+                massDependent = f.getMassDependent()
+                if isinstance(f, LinearCylinderVortexForce):
+                    file.write(i2+fname + ' = LinearCylinderVortexForce(%.4f, %.4f, %.4f, %.4f, %d)\n' % (f.getRadius(), f.getLength(), f.getCoef(), amplitude, massDependent))
+                elif isinstance(f, LinearDistanceForce):
+                    radius = f.getRadius()
+                    falloffType = f.getFalloffType()
+                    ftype = 'FTONEOVERR'
+                    if (falloffType == LinearDistanceForce.FTONEOVERR):
+                        ftype = 'FTONEOVERR'
+                    elif (falloffType == LinearDistanceForce.FTONEOVERRSQUARED):
+                        ftype = 'FTONEOVERRSQUARED'
+                    elif (falloffType == LinearDistanceForce.FTONEOVERRCUBED):
+                        ftype = 'FTONEOVERRCUBED'
+                    forceCenter = f.getForceCenter()
+                    if isinstance(f, LinearSinkForce):
+                        file.write(i2+fname + ' = LinearSinkForce(Point3(%.4f, %.4f, %.4f), LinearDistanceForce.%s, %.4f, %.4f, %d)\n' % (forceCenter[0], forceCenter[1], forceCenter[2], ftype, radius, amplitude, massDependent))
+                    elif isinstance(f, LinearSourceForce):
+                        file.write(i2+fname + ' = LinearSourceForce(Point3(%.4f, %.4f, %.4f), LinearDistanceForce.%s, %.4f, %.4f, %d)\n' % (forceCenter[0], forceCenter[1], forceCenter[2], ftype, radius, amplitude, massDependent))
+                elif isinstance(f, LinearFrictionForce):
+                    file.write(i2+fname + ' = LinearFrictionForce(%.4f, %.4f, %d)\n' % (f.getCoef(), amplitude, massDependent))
+                elif isinstance(f, LinearJitterForce):
+                    file.write(i2+fname + ' = LinearJitterForce(%.4f, %d)\n' % (amplitude, massDependent))
+                elif isinstance(f, LinearNoiseForce):
+                    file.write(i2+fname + ' = LinearNoiseForce(%.4f, %d)\n' % (amplitude, massDependent))
+                elif isinstance(f, LinearVectorForce):
+                    vec = f.getLocalVector()
+                    file.write(i2+fname + ' = LinearVectorForce(Vec3(%.4f, %.4f, %.4f), %.4f, %d)\n' % (vec[0], vec[1], vec[2], amplitude, massDependent))
+            elif isinstance(f, AngularForce):
+                if isinstance(f, AngularVectorForce):
+                    vec = f.getQuat()
+                    file.write(i2+fname + ' = AngularVectorForce(Quat(%.4f, %.4f, %.4f))\n' % (vec[0], vec[1], vec[2], vec[3]))
+            file.write(i2+fname + '.setActive(%d)\n' % f.getActive())
+            file.write(i2+targ + '.addForce(%s)\n' % fname)

+ 256 - 0
contrib/src/sceneeditor/seGeometry.py

@@ -0,0 +1,256 @@
+#################################################################
+# seGeometry.py
+# Originally from DirectGeometry.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# We didn't change anything essential.
+# Just because we customized the seSession from DirectSession,
+# So we need related files can follow the change.
+# However, we don't want to change anything inside the original directool
+# to let them can work with our scene editor.
+# (If we do change original directools, it will force user has to install the latest version of OUR Panda)
+#
+#################################################################
+
+from pandac.PandaModules import *
+from direct.directtools.DirectGlobals import *
+from direct.directtools.DirectUtil import *
+import math
+
+class LineNodePath(NodePath):
+    def __init__(self, parent = None, name = None,
+                 thickness = 1.0, colorVec = VBase4(1)):
+
+        # Initialize the superclass
+        NodePath.__init__(self)
+
+        if parent is None:
+            parent = hidden
+
+        # Attach a geomNode to the parent and set self to be
+        # the resulting node path
+        self.lineNode = GeomNode("lineNode")
+        self.assign(parent.attachNewNode( self.lineNode ))
+        if name:
+            self.setName(name)
+
+        # Create a lineSegs object to hold the line
+        ls = self.lineSegs = LineSegs()
+        # Initialize the lineSegs parameters
+        ls.setThickness(thickness)
+        ls.setColor(colorVec)
+
+    def moveTo( self, *_args ):
+        apply( self.lineSegs.moveTo, _args )
+
+    def drawTo( self, *_args ):
+        apply( self.lineSegs.drawTo, _args )
+
+    def create( self, frameAccurate = 0 ):
+        self.lineSegs.create( self.lineNode, frameAccurate )
+
+    def reset( self ):
+        self.lineSegs.reset()
+        self.lineNode.removeAllGeoms()
+
+    def isEmpty( self ):
+        return self.lineSegs.isEmpty()
+
+    def setThickness( self, thickness ):
+        self.lineSegs.setThickness( thickness )
+
+    def setColor( self, *_args ):
+        apply( self.lineSegs.setColor, _args )
+
+    def setVertex( self, *_args):
+        apply( self.lineSegs.setVertex, _args )
+
+    def setVertexColor( self, vertex, *_args ):
+        apply( self.lineSegs.setVertexColor, (vertex,) + _args )
+
+    def getCurrentPosition( self ):
+        return self.lineSegs.getCurrentPosition()
+
+    def getNumVertices( self ):
+        return self.lineSegs.getNumVertices()
+
+    def getVertex( self, index ):
+        return self.lineSegs.getVertex(index)
+
+    def getVertexColor( self ):
+        return self.lineSegs.getVertexColor()
+    
+    def drawArrow(self, sv, ev, arrowAngle, arrowLength):
+        """
+        Do the work of moving the cursor around to draw an arrow from
+        sv to ev. Hack: the arrows take the z value of the end point
+        """
+        self.moveTo(sv)
+        self.drawTo(ev)
+        v = sv - ev
+        # Find the angle of the line
+        angle = math.atan2(v[1], v[0])
+        # Get the arrow angles
+        a1 = angle + deg2Rad(arrowAngle)
+        a2 = angle - deg2Rad(arrowAngle)
+        # Get the arrow points
+        a1x = arrowLength * math.cos(a1)
+        a1y = arrowLength * math.sin(a1)
+        a2x = arrowLength * math.cos(a2)
+        a2y = arrowLength * math.sin(a2)
+        z = ev[2]
+        self.moveTo(ev)
+        self.drawTo(Point3(ev + Point3(a1x, a1y, z)))
+        self.moveTo(ev)
+        self.drawTo(Point3(ev + Point3(a2x, a2y, z)))
+
+    def drawArrow2d(self, sv, ev, arrowAngle, arrowLength):
+        """
+        Do the work of moving the cursor around to draw an arrow from
+        sv to ev. Hack: the arrows take the z value of the end point
+        """
+        self.moveTo(sv)
+        self.drawTo(ev)
+        v = sv - ev
+        # Find the angle of the line
+        angle = math.atan2(v[2], v[0])
+        # Get the arrow angles
+        a1 = angle + deg2Rad(arrowAngle)
+        a2 = angle - deg2Rad(arrowAngle)
+        # Get the arrow points
+        a1x = arrowLength * math.cos(a1)
+        a1y = arrowLength * math.sin(a1)
+        a2x = arrowLength * math.cos(a2)
+        a2y = arrowLength * math.sin(a2)
+        self.moveTo(ev)
+        self.drawTo(Point3(ev + Point3(a1x, 0.0, a1y)))
+        self.moveTo(ev)
+        self.drawTo(Point3(ev + Point3(a2x, 0.0, a2y)))
+
+    def drawLines(self, lineList):
+        """
+        Given a list of lists of points, draw a separate line for each list
+        """
+        for pointList in lineList:
+            apply(self.moveTo, pointList[0])
+            for point in pointList[1:]:
+                apply(self.drawTo, point)
+
+##
+## Given a point in space, and a direction, find the point of intersection
+## of that ray with a plane at the specified origin, with the specified normal
+def planeIntersect (lineOrigin, lineDir, planeOrigin, normal):
+    t = 0
+    offset = planeOrigin - lineOrigin
+    t = offset.dot(normal) / lineDir.dot(normal)
+    hitPt = lineDir * t
+    return hitPt + lineOrigin
+
+def getNearProjectionPoint(nodePath):
+    # Find the position of the projection of the specified node path
+    # on the near plane
+    origin = nodePath.getPos(SEditor.camera)
+    # project this onto near plane
+    if origin[1] != 0.0:
+        return origin * (SEditor.dr.near / origin[1])
+    else:
+        # Object is coplaner with camera, just return something reasonable
+        return Point3(0, SEditor.dr.near, 0)
+
+def getScreenXY(nodePath):
+    # Where does the node path's projection fall on the near plane
+    nearVec = getNearProjectionPoint(nodePath)
+    # Clamp these coordinates to visible screen
+    nearX = CLAMP(nearVec[0], SEditor.dr.left, SEditor.dr.right)
+    nearY = CLAMP(nearVec[2], SEditor.dr.bottom, SEditor.dr.top)
+    # What percentage of the distance across the screen is this?
+    percentX = (nearX - SEditor.dr.left)/SEditor.dr.nearWidth
+    percentY = (nearY - SEditor.dr.bottom)/SEditor.dr.nearHeight
+    # Map this percentage to the same -1 to 1 space as the mouse
+    screenXY = Vec3((2 * percentX) - 1.0,nearVec[1],(2 * percentY) - 1.0)
+    # Return the resulting value
+    return screenXY
+
+def getCrankAngle(center):
+    # Used to compute current angle of mouse (relative to the coa's
+    # origin) in screen space
+    x = SEditor.dr.mouseX - center[0]
+    y = SEditor.dr.mouseY - center[2]
+    return (180 + rad2Deg(math.atan2(y,x)))
+
+def relHpr(nodePath, base, h, p, r):
+    # Compute nodePath2newNodePath relative to base coordinate system
+    # nodePath2base
+    mNodePath2Base = nodePath.getMat(base)
+    # delta scale, orientation, and position matrix
+    mBase2NewBase = Mat4()
+    composeMatrix(mBase2NewBase, UNIT_VEC, VBase3(h,p,r), ZERO_VEC,
+                  CSDefault)
+    # base2nodePath
+    mBase2NodePath = base.getMat(nodePath)
+    # nodePath2 Parent
+    mNodePath2Parent = nodePath.getMat()
+    # Compose the result
+    resultMat = mNodePath2Base * mBase2NewBase
+    resultMat = resultMat * mBase2NodePath
+    resultMat = resultMat * mNodePath2Parent
+    # Extract and apply the hpr
+    hpr = Vec3(0)
+    decomposeMatrix(resultMat, VBase3(), hpr, VBase3(),
+                    CSDefault)
+    nodePath.setHpr(hpr)
+
+# Quaternion interpolation
+def qSlerp(startQuat, endQuat, t):
+    startQ = Quat(startQuat)
+    destQuat = Quat.identQuat()
+    # Calc dot product
+    cosOmega = (startQ.getI() * endQuat.getI() +
+                startQ.getJ() * endQuat.getJ() + 
+                startQ.getK() * endQuat.getK() +
+                startQ.getR() * endQuat.getR())
+    # If the above dot product is negative, it would be better to
+    # go between the negative of the initial and the final, so that
+    # we take the shorter path.  
+    if ( cosOmega < 0.0 ):
+        cosOmega *= -1
+        startQ.setI(-1 * startQ.getI())
+        startQ.setJ(-1 * startQ.getJ())
+        startQ.setK(-1 * startQ.getK())
+        startQ.setR(-1 * startQ.getR())
+    if ((1.0 + cosOmega) > Q_EPSILON):
+        # usual case
+        if ((1.0 - cosOmega) > Q_EPSILON):
+            # usual case
+            omega = math.acos(cosOmega)
+            sinOmega = math.sin(omega)
+            startScale = math.sin((1.0 - t) * omega)/sinOmega
+            endScale = math.sin(t * omega)/sinOmega
+        else:
+            # ends very close 
+            startScale = 1.0 - t
+            endScale = t
+        destQuat.setI(startScale * startQ.getI() +
+                      endScale * endQuat.getI())
+        destQuat.setJ(startScale * startQ.getJ() +
+                      endScale * endQuat.getJ())
+        destQuat.setK(startScale * startQ.getK() +
+                      endScale * endQuat.getK())
+        destQuat.setR(startScale * startQ.getR() +
+                      endScale * endQuat.getR())
+    else:
+        # ends nearly opposite
+        destQuat.setI(-startQ.getJ())
+        destQuat.setJ(startQ.getI())
+        destQuat.setK(-startQ.getR())
+        destQuat.setR(startQ.getK())
+        startScale = math.sin((0.5 - t) * math.pi)
+        endScale = math.sin(t * math.pi)
+        destQuat.setI(startScale * startQ.getI() +
+                      endScale * endQuat.getI())
+        destQuat.setJ(startScale * startQ.getJ() +
+                      endScale * endQuat.getJ())
+        destQuat.setK(startScale * startQ.getK() +
+                      endScale * endQuat.getK())
+    return destQuat
+

+ 170 - 0
contrib/src/sceneeditor/seGrid.py

@@ -0,0 +1,170 @@
+#################################################################
+# seGrid.py
+# Originally from DirectGrid.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# We didn't change anything essential.
+# Just because we customized the seSession from DirectSession,
+# So we need related files can follow the change.
+# However, we don't want to change anything inside the original directool
+# to let them can work with our scene editor.
+# (If we do change original directools, it will force user has to install the latest version of OUR Panda)
+#
+#################################################################
+from direct.showbase.DirectObject import *
+from direct.directtools.DirectUtil import *
+from seGeometry import *
+
+class DirectGrid(NodePath,DirectObject):
+    def __init__(self):
+        # Initialize superclass
+        NodePath.__init__(self)
+        self.assign(hidden.attachNewNode('DirectGrid'))
+        # Don't wireframe or light
+        useDirectRenderStyle(self)
+
+        # Load up grid parts to initialize grid object
+        # Polygon used to mark grid plane
+        self.gridBack = loader.loadModel('models/misc/gridBack')
+        self.gridBack.reparentTo(self)
+        self.gridBack.setColor(0.5,0.5,0.5,0.5)
+
+        # Grid Lines
+        self.lines = self.attachNewNode('gridLines')
+        self.minorLines = LineNodePath(self.lines)
+        self.minorLines.lineNode.setName('minorLines')
+        self.minorLines.setColor(VBase4(0.3,0.55,1,1))
+        self.minorLines.setThickness(1)
+
+        self.majorLines = LineNodePath(self.lines)
+        self.majorLines.lineNode.setName('majorLines')
+        self.majorLines.setColor(VBase4(0.3,0.55,1,1))
+        self.majorLines.setThickness(5)
+
+        self.centerLines = LineNodePath(self.lines)
+        self.centerLines.lineNode.setName('centerLines')
+        self.centerLines.setColor(VBase4(1,0,0,0))
+        self.centerLines.setThickness(3)
+
+        # Small marker to hilight snap-to-grid point
+        self.snapMarker = loader.loadModel('models/misc/sphere')
+        self.snapMarker.node().setName('gridSnapMarker')
+        self.snapMarker.reparentTo(self)
+        self.snapMarker.setColor(1,0,0,1)
+        self.snapMarker.setScale(0.3)
+        self.snapPos = Point3(0)
+
+        # Initialize Grid characteristics
+        self.fXyzSnap = 1
+        self.fHprSnap = 1
+        self.gridSize = 100.0
+        self.gridSpacing = 5.0
+        self.snapAngle = 15.0
+        self.enable()
+
+    def enable(self):
+        self.reparentTo(SEditor.group)
+        self.updateGrid()
+        self.fEnabled = 1
+
+    def disable(self):
+        self.reparentTo(hidden)
+        self.fEnabled = 0
+
+    def toggleGrid(self):
+        if self.fEnabled:
+            self.disable()
+        else:
+            self.enable()
+
+    def isEnabled(self):
+        return self.fEnabled
+
+    def updateGrid(self):
+        # Update grid lines based upon current grid spacing and grid size
+        # First reset existing grid lines
+        self.minorLines.reset()
+        self.majorLines.reset()
+        self.centerLines.reset()
+
+        # Now redraw lines
+        numLines = int(math.ceil(self.gridSize/self.gridSpacing))
+        scaledSize = numLines * self.gridSpacing
+ 
+        center = self.centerLines
+        minor = self.minorLines
+        major = self.majorLines
+        for i in range(-numLines,numLines + 1):
+            if i == 0:
+                center.moveTo(i * self.gridSpacing, -scaledSize, 0)
+                center.drawTo(i * self.gridSpacing, scaledSize, 0)
+                center.moveTo(-scaledSize, i * self.gridSpacing, 0)
+                center.drawTo(scaledSize, i * self.gridSpacing, 0)
+            else:
+                if (i % 5) == 0:
+                    major.moveTo(i * self.gridSpacing, -scaledSize, 0)
+                    major.drawTo(i * self.gridSpacing, scaledSize, 0)
+                    major.moveTo(-scaledSize, i * self.gridSpacing, 0)
+                    major.drawTo(scaledSize, i * self.gridSpacing, 0)
+                else:
+                    minor.moveTo(i * self.gridSpacing, -scaledSize, 0)
+                    minor.drawTo(i * self.gridSpacing, scaledSize, 0)
+                    minor.moveTo(-scaledSize, i * self.gridSpacing, 0)
+                    minor.drawTo(scaledSize, i * self.gridSpacing, 0)
+
+        center.create()
+        minor.create()
+        major.create()
+        self.gridBack.setScale(scaledSize)
+        
+    def setXyzSnap(self, fSnap):
+        self.fXyzSnap = fSnap
+
+    def getXyzSnap(self):
+        return self.fXyzSnap
+
+    def setHprSnap(self, fSnap):
+        self.fHprSnap = fSnap
+
+    def getHprSnap(self):
+        return self.fHprSnap
+
+    def computeSnapPoint(self, point):
+        # Start of with current point
+        self.snapPos.assign(point)
+        # Snap if necessary
+        if self.fXyzSnap:
+            self.snapPos.set(
+                ROUND_TO(self.snapPos[0], self.gridSpacing),
+                ROUND_TO(self.snapPos[1], self.gridSpacing),
+                ROUND_TO(self.snapPos[2], self.gridSpacing))
+            
+        # Move snap marker to this point
+        self.snapMarker.setPos(self.snapPos)
+
+        # Return the hit point
+        return self.snapPos
+
+    def computeSnapAngle(self, angle):
+        return ROUND_TO(angle, self.snapAngle)
+
+    def setSnapAngle(self, angle):
+        self.snapAngle = angle
+
+    def getSnapAngle(self):
+        return self.snapAngle
+
+    def setGridSpacing(self, spacing):
+        self.gridSpacing = spacing
+        self.updateGrid()
+        
+    def getGridSpacing(self):
+        return self.gridSpacing
+
+    def setGridSize(self, size):
+        # Set size of grid back and redraw lines
+        self.gridSize = size
+        self.updateGrid()
+
+    def getGridSize(self):
+        return self.gridSize

+ 633 - 0
contrib/src/sceneeditor/seLights.py

@@ -0,0 +1,633 @@
+#################################################################
+# seLights.py
+# Written by Yi-Hong Lin, [email protected], 2004
+#################################################################
+from direct.showbase.DirectObject import *
+from string import lower
+from direct.directtools import DirectUtil
+from pandac.PandaModules import *
+import string
+
+
+class seLight(NodePath):
+    #################################################################
+    # seLight(NodePath)
+    # This is an object for keeping light data and let we can manipulate
+    # lights as otherNopaths.
+    # This idea basically is from directLight.
+    # But the way directLight object worked is not we want in our
+    # sceneEditor. So, we wrote one by ourself.
+    #################################################################
+    def __init__(self, light, parent, type,
+                 lightcolor=VBase4(0.3,0.3,0.3,1),
+                 specularColor = VBase4(1),
+                 position = Point3(0,0,0),
+                 orientation = Vec3(1,0,0),
+                 constant = 1.0,
+                 linear = 0.0,
+                 quadratic = 0.0,
+                 exponent = 0.0,
+                 tag="",
+                 lence = None):
+        #################################################################
+        # __init__(self, light, parent, type,
+        #          lightcolor=VBase4(0.3,0.3,0.3,1),
+        #          specularColor = VBase4(1),
+        #          position = Point3(0,0,0),
+        #          orientation = Vec3(1,0,0),
+        #          constant = 1.0,
+        #          linear = 0.0,
+        #          quadratic = 0.0,
+        #          exponent = 0.0,
+        #          tag="",
+        #          lence = None):
+        # This constructor will create a light node inside it and upcast
+        # this light node to itself as a nodePath.
+        # Also, it will load a model as a reference to itself on the secene so that
+        # user can easily recognize where is the light and can easily manipulate
+        # by mouse picking and widget control.
+        #################################################################
+        # Initialize the super class
+        NodePath.__init__(self)
+        # Initialize and save values
+        self.light = light
+        self.type = type
+        self.lightcolor=lightcolor
+        self.specularColor = specularColor
+        self.position = position
+        self.orientation = orientation
+        self.constant = constant
+        self.linear = linear
+        self.quadratic = quadratic
+        self.exponent = exponent
+        self.lence = lence
+        self.active = True
+           
+        if isinstance(light, Spotlight):
+            node = light.upcastToLensNode()
+        else:
+            node = light.upcastToPandaNode()
+
+        # Attach node to self
+        self.LightNode=parent.attachNewNode(node)
+        self.LightNode.setTag("Metadata",tag)
+        if(self.type=='spot'):
+            self.LightNode.setHpr(self.orientation)
+            self.LightNode.setPos(self.position)
+        else:
+            self.LightNode.setHpr(self.orientation)     
+            self.LightNode.setPos(self.position)
+            
+        
+        self.assign(self.LightNode)
+        if(self.type=='spot'):
+            self.helpModel = loader.loadModel( "models/misc/Spotlight" )
+        elif(self.type=='point'):
+            self.helpModel = loader.loadModel( "models/misc/Pointlight" )
+        elif(self.type=='directional'):
+            self.helpModel = loader.loadModel( "models/misc/Dirlight" )
+        else:
+            self.helpModel = loader.loadModel( "models/misc/sphere" )
+        self.helpModel.setColor(self.lightcolor)
+        self.helpModel.reparentTo(self)
+        DirectUtil.useDirectRenderStyle(self.helpModel)
+        if not ((self.type == 'directional')or(self.type == 'point')or(self.type == 'spot')):
+            self.helpModel.hide()
+
+    def getLight(self):
+        #################################################################
+        # getLight(self)
+        # This function will return the light object it contains.
+        #################################################################
+        
+        return self.light
+
+    def getLightColor(self):
+        #################################################################
+        # getLightColor(self)
+        # This function will return the color of the light color of this light node.
+        #################################################################
+        return self.lightcolor
+    
+    def getName(self):
+        #################################################################
+        # getName(self)
+        # This function will return the name of this light.
+        #################################################################
+        return self.light.getName()
+    
+    def rename(self,name):
+        #################################################################
+        # rename(self, name)
+        # User should specify a string object, name, as the new name of this
+        # light node. This function will rename itself and light object
+        # to this new name.
+        #################################################################
+        self.light.setName(name)
+        self.setName(name)
+
+    def getType(self):
+        #################################################################
+        # getType(self)
+        # This function will return a string which is the type of this
+        # light node. (We only have four types of light)
+        #################################################################
+        return self.type
+
+    def setColor(self,color):
+        #################################################################
+        # setColor(self, color)
+        # This function takes one input parameter, color, which is a
+        # VBase4 object. This function will simply set this object into
+        # light node it has to change the property of light.
+        # Also, if the light type is either "directional" or "point", which
+        # means it has a reference model with it, this function will also
+        # change the reference model's color to input value.
+        #################################################################
+        self.light.setColor(color)
+        self.lightcolor = color
+        if (self.type == 'directional')or(self.type == 'point'):
+            self.helpModel.setColor(self.lightcolor)
+        return
+
+    def getSpecColor(self):
+        #################################################################
+        # getSpecColor(self)
+        # This function will return the specular color of the light.
+        # Although you can call this function for all kinds of light,
+        # it will only meanful if this light is not a ambient light.
+        #################################################################
+        return self.specularColor
+
+    def setSpecColor(self,color):
+        #################################################################
+        # setSpecColor(self, color)
+        # This function can be used to set the specular color of the light.
+        # Although you can call this function for all kinds of light,
+        # it will only meanful if the light type is not "ambient"
+        #################################################################
+        self.light.setSpecularColor(color)
+        self.specularcolor = color
+        return
+
+    def getPosition(self):
+        #################################################################
+        # getPosition(self)
+        # getPosition(self)
+        # This functioln will return a Point3 object which contains
+        # the x, y, z position data of this light node.
+        # It only has meaning for "point Light" and "Directional light"
+        #################################################################
+        self.position = self.LightNode.getPos()
+        return self.position
+
+    def setPosition(self, pos):
+        #################################################################
+        # setPosition(self, pos)
+        # This function will take a Point3 object as a input.
+        # Then, this function will set the itself andd light node to the
+        # target point.
+        #################################################################
+        self.LightNode.setPos(pos)
+        self.position = pos
+        return
+
+    def getOrientation(self):
+        #################################################################
+        # getOrientation(self)
+        # This function will return a Vec3-type object which contains the
+        # orientation data of this light node
+        #
+        # This function will only have meaning for point light and directional light.
+        #
+        #################################################################
+        self.orientation = self.LightNode.getHpr()
+        return self.orientation
+        
+    def setOrientation(self,orient):
+        #################################################################
+        # setOrientation(self, orient)
+        # This funtction will take a Vec3-type object as an input.
+        # Then this function will set itself nad light node to face
+        # the target orientation.
+        #
+        # This function only has meaning for point light and directional light
+        # type of lights.
+        #
+        #################################################################
+        self.LightNode.setHpr(orient)
+        self.orientation = orient
+        return
+
+    def getAttenuation(self):
+        #################################################################
+        # getAttenuation(self)
+        # This function will return a Vec3 type of object which contains
+        # the constant, linear and quadratic attenuation for this light node.
+        #
+        # This function will only have meaning for point light and spot light
+        # tyoe of lights.
+        #
+        #################################################################
+        return Vec3(self.constant,self.linear,self.quadratic)
+
+    def setConstantAttenuation(self, value):
+        #################################################################
+        # setConstantAttenuation(self, value)
+        # This function will take a float number as an input.
+        # Then, this function will set the Constant Atenuation value
+        # to this number.
+        #################################################################
+        self.light.setAttenuation(Vec3(value, self.linear, self.quadratic))
+        self.constant = value
+        return
+    
+    def setLinearAttenuation(self, value):
+        #################################################################
+        # setLinearAttenuation(self, value)
+        # This function will take a float number as an input.
+        # Then, this function will set the Linear Atenuation value
+        # to this number.
+        #################################################################
+        self.light.setAttenuation(Vec3(self.constant, value, self.quadratic))
+        self.linear = value
+        return
+    
+    def setQuadraticAttenuation(self, value):
+        #################################################################
+        # setQuadraticAttenuation(self, value)
+        # This function will take a float number as an input.
+        # Then, this function will set the Quadratic Atenuation value
+        # to this number.
+        #################################################################
+        self.light.setAttenuation(Vec3(self.constant, self.linear, value))
+        self.quadratic = value
+        return
+
+    def getExponent(self):
+        #################################################################
+        # getExponent(self)
+        # This function will return the value of the Exponent Attenuation
+        # of this light node. (float)
+        #################################################################
+        return self.exponent    
+
+    def setExponent(self, value):
+        #################################################################
+        # setExponent(self, value)
+        # This function will take a float number as an input.
+        # Then, this function will set the Exponent Atenuation value
+        # to this number.
+        #################################################################
+        self.light.setExponent(value)
+        self.exponent = value
+        return
+        
+class seLightManager(NodePath):
+    #################################################################
+    # seLightManager(NodePath)
+    # This is the class we used to control al lightings in our sceneEditor.
+    #################################################################
+    def __init__(self):
+        # Initialize the superclass
+        NodePath.__init__(self)
+        # Create a node for the lights
+        self.lnode=render.attachNewNode('Lights')
+        self.assign(self.lnode)
+        # Create a light attrib
+        self.lightAttrib = LightAttrib.makeAllOff()
+        self.lightDict = {}
+        self.ambientCount = 0
+        self.directionalCount = 0
+        self.pointCount = 0
+        self.spotCount = 0
+        # Originally, we don't do this load model thing.
+        # But the problem is, if we don't, then it will cause some
+        # Bounding calculation error...
+        self.helpModel = loader.loadModel( "models/misc/sphere" )
+        self.helpModel.reparentTo(self)
+        self.helpModel.hide()
+   
+
+
+
+    def create(self, type = 'ambient',
+                 lightcolor=VBase4(0.3,0.3,0.3,1),
+                 specularColor = VBase4(1),
+                 position = Point3(0,0,0),
+                 orientation = Vec3(1,0,0),
+                 constant = 1.0,
+                 linear = 0.0,
+                 quadratic = 0.0,
+                 exponent = 0.0,
+                 tag= "",
+                 name='DEFAULT_NAME'):
+        #################################################################
+        # create(self, type = 'ambient',
+        #        lightcolor=VBase4(0.3,0.3,0.3,1),
+        #        specularColor = VBase4(1),
+        #        position = Point3(0,0,0),
+        #        orientation = Vec3(1,0,0),
+        #        constant = 1.0,
+        #        linear = 0.0,
+        #        quadratic = 0.0,
+        #        exponent = 0.0,
+        #        tag= "",
+        #        name='DEFAULT_NAME')
+        # As you can see, once user call this function and specify those
+        # variables, this function will create a seLight node.
+        # In the default, the light which just has been created will be
+        # set to off.
+        #################################################################
+        ### create the light
+
+        lence = None
+        
+        if type == 'ambient':
+            self.ambientCount += 1
+            if(name=='DEFAULT_NAME'):
+                light = AmbientLight('ambient_' + `self.ambientCount`)
+            else:
+                light = AmbientLight(name)
+
+            light.setColor(lightcolor)
+
+        elif type == 'directional':
+            self.directionalCount += 1
+            if(name=='DEFAULT_NAME'):
+                light = DirectionalLight('directional_' + `self.directionalCount`)
+            else:
+                light = DirectionalLight(name)
+
+            light.setColor(lightcolor)
+            light.setSpecularColor(specularColor)
+
+        elif type == 'point':
+            self.pointCount += 1
+            if(name=='DEFAULT_NAME'):
+                light = PointLight('point_' + `self.pointCount`)
+            else:
+                light = PointLight(name)
+            
+            light.setColor(lightcolor)
+            light.setSpecularColor(specularColor)
+            light.setAttenuation(Vec3(constant, linear, quadratic))
+
+        elif type == 'spot':
+            self.spotCount += 1
+            if(name=='DEFAULT_NAME'):
+                light = Spotlight('spot_' + `self.spotCount`)
+            else:
+                light = Spotlight(name)
+
+            light.setColor(lightcolor)
+            lence = PerspectiveLens()
+            light.setLens(lence)
+            light.setSpecularColor(specularColor)
+            light.setAttenuation(Vec3(constant, linear, quadratic))
+            light.setExponent(exponent)
+        else:
+            print 'Invalid light type'
+            return None
+
+        # Create the seLight objects and put the light object we just created into it.
+        lightNode = seLight(light,self,type,
+                            lightcolor=lightcolor,
+                            specularColor = specularColor,
+                            position = position,
+                            orientation = orientation,
+                            constant = constant,
+                            linear = linear,
+                            quadratic = quadratic,
+                            exponent = exponent,
+                            tag=tag,
+                            lence = lence
+                                )
+        self.lightDict[light.getName()] = lightNode
+        self.setOn(lightNode)
+
+        
+        return self.lightDict.keys(),lightNode
+
+    def addLight(self, light):
+        #################################################################
+        # addLight(self, light)
+        # This function will put light in and save its properties in to a seLight Node
+        # Attention!!
+        # only Spotlight obj nneds to be specified a lens node first. i.e. setLens() first!
+        #################################################################
+        type = lower(light.getType().getName())
+
+        light.upcastToNamable()
+
+        specularColor = VBase4(1)
+        position = Point3(0,0,0)
+        orientation = Vec3(1,0,0)
+        constant = 1.0
+        linear = 0.0
+        quadratic = 0.0
+        exponent = 0.0
+        lence = None
+
+        lightcolor = light.getColor()
+        if type == 'ambientlight':
+            type = 'ambient'
+            self.ambientCount += 1
+        elif type == 'directionallight':
+            type = 'directional'
+            self.directionalCount += 1
+            orientation = light.getDirection()
+            position = light.getPoint()
+            specularColor = light.getSpecularColor()
+        elif type == 'pointlight':
+            type = 'point'
+            self.pointCount += 1
+            position = light.getPoint()
+            specularColor = light.getSpecularColor()
+            Attenuation = light.getAttenuation()
+            constant = Attenuation.getX()
+            linear = Attenuation.getY()
+            quadratic = Attenuation.getZ()
+        elif type == 'spotlight':
+            type = 'spot'
+            self.spotCount += 1
+            specularColor = light.getSpecularColor()
+            Attenuation = light.getAttenuation()
+            constant = Attenuation.getX()
+            linear = Attenuation.getY()
+            quadratic = Attenuation.getZ()
+            exponent = light.getExponent()
+        else:
+            print 'Invalid light type'
+            return None
+
+        lightNode = seLight(light,self,type,
+                            lightcolor=lightcolor,
+                            specularColor = specularColor,
+                            position = position,
+                            orientation = orientation,
+                            constant = constant,
+                            linear = linear,
+                            quadratic = quadratic,
+                            exponent = exponent,
+                            lence = lence)
+        self.lightDict[light.getName()] = lightNode
+        self.setOn(lightNode)
+
+        return self.lightDict.keys(),lightNode
+
+    def delete(self, name, removeEntry = True):
+        #################################################################
+        # delete(self, name, removeEntry = True)
+        # This function will remove light node had the same name with user input.
+        # Also, you can specify the removeEntry to decide to remove the entry from the lightDict or not.
+        # Normaly, you alway want to remove the entry from the dictionary. Thsi is only used for "deleteAll" function.
+        #################################################################
+        type = self.lightDict[name].getType()
+        if type == 'ambient':
+            self.ambientCount -= 1
+        elif type == 'directional':
+            self.directionalCount -= 1
+        elif type == 'point':
+            self.pointCount -= 1
+        elif type == 'spot':
+            self.spotCount -= 1
+        self.setOff(self.lightDict[name])
+        self.lightDict[name].removeChildren()
+        self.lightDict[name].removeNode()
+        if removeEntry:
+            del self.lightDict[name]
+        return self.lightDict.keys()
+
+    def deleteAll(self):
+        #################################################################
+        # deleteAll(self)
+        # This function will keep calling delete and put exist lights in
+        # until all lights have been eliminated.
+        #################################################################
+        for name in self.lightDict:
+            self.delete(name, removeEntry = False)
+        
+        self.lightDict.clear()
+
+    def isLight(self,name):
+        #################################################################
+        # isLight(self.name)
+        # Use a string as a index to check if there existing a light named "name"
+        #################################################################
+        return self.lightDict.has_key(name)
+
+    def rename(self,oName,nName):
+        #################################################################
+        # rename(self, oName, nName)
+        # This function will reanem the light named "oName(String)" to
+        # nName(String)
+        #################################################################
+        if self.isLight(oName):
+            lightNode = self.lightDict[oName]
+            self.lightDict[nName] = lightNode
+            lightNode.rename(nName)
+            del self.lightDict[oName]
+            return self.lightDict.keys(),lightNode
+        else:
+            print '----Light Mnager: No such Light!'
+            
+    def getLightNodeList(self):
+        #################################################################
+        # getLightNodeList(self)
+        # Return a list which contains all seLight nodes
+        #################################################################
+        list = []
+        for name in self.lightDict:
+            list.append(self.lightDict[name])
+        return list
+
+    def getLightNodeDict(self):
+        #################################################################
+        # getLightNodeDict(self)
+        # Return the light dictionary itself.
+        #
+        # Attention!
+        # Because it doesn't make a copy when you return a dictionary, it
+        # means when you can change the value from outside and that change
+        # will directly reflect back to here.
+        #
+        #################################################################
+        return self.lightDict
+
+    def getLightList(self):
+        #################################################################
+        # getLightList(self)
+        # Return a list which contains names of all lights.
+        #################################################################
+        list = []
+        for name in self.lightDict:
+            list.append(name)
+        return list
+
+    def getLightNode(self,lightName):
+        #################################################################
+        # getLightNode(self, lightName)
+        # This function will return a seLight Node using a string, lightName. as a index.
+        #################################################################
+        if lightName in self.lightDict:
+            return self.lightDict[lightName]
+
+    def allOn(self):
+        #################################################################
+        # allOb(self)
+        # Enable the lighting system.
+        #################################################################
+        # Turn on all lighting
+        render.node().setAttrib(self.lightAttrib)
+        # Make sure there is a default material
+        render.setMaterial(Material())
+
+    def allOff(self):
+        #################################################################
+        # allOff(self)
+        # Disable whole lighting system
+        #################################################################
+        # Turn off all lighting
+        render.node().clearAttrib(LightAttrib.getClassType())
+
+    def toggle(self):
+        #################################################################
+        # toggle(self)
+        # Toggles light attribute, but doesn't toggle individual lights
+        #################################################################
+        if render.node().hasAttrib(LightAttrib.getClassType()):
+            self.allOff()
+        else:
+            self.allOn()
+
+    def setOn(self, lightNode):
+        #################################################################
+        # setOn(lightNode)
+        # This function will enable the input seLight node.
+        # If the light system itself is down, activate it.
+        #################################################################
+        self.lightAttrib = self.lightAttrib.addLight(lightNode.getLight())
+        lightNode.active = True
+        if render.node().hasAttrib(LightAttrib.getClassType()):
+            render.node().setAttrib(self.lightAttrib)
+
+            
+    def setOff(self, lightNode):
+        #################################################################
+        # setOff(self, lightNode)
+        # This function will disable the input seLight node
+        # If the light system itself is down, activate it.
+        #################################################################
+        lightNode.active = False
+        self.lightAttrib = self.lightAttrib.removeLight(lightNode.getLight())
+        if render.node().hasAttrib(LightAttrib.getClassType()):
+            render.node().setAttrib(self.lightAttrib)
+
+    def getList(self):
+        #################################################################
+        # getList(self)
+        # This function actually has the same functionality with getLightList(),
+        # but this one should be more efficient.
+        #################################################################
+        return self.lightDict.keys()

+ 956 - 0
contrib/src/sceneeditor/seManipulation.py

@@ -0,0 +1,956 @@
+#################################################################
+# seManipulation.py
+# Originally from DirectManipulation.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# We didn't change anything essential.
+# Just because we customized the seSession from DirectSession,
+# So we need related files can follow the change.
+# However, we don't want to change anything inside the original directool
+# to let them can work with our scene editor.
+# (If we do change original directools, it will force user has to install the latest version of OUR Panda)
+#
+#################################################################
+
+from direct.showbase.DirectObject import *
+from direct.directtools.DirectGlobals import *
+from direct.directtools.DirectUtil import *
+from seGeometry import *
+from direct.task import Task
+
+class DirectManipulationControl(DirectObject):
+    def __init__(self):
+        # Create the grid
+        self.objectHandles = ObjectHandles()
+        self.hitPt = Point3(0)
+        self.prevHit = Vec3(0)
+        self.rotationCenter = Point3(0)
+        self.initScaleMag = 1
+        self.manipRef = SEditor.group.attachNewNode('manipRef')
+        self.hitPtDist = 0
+        self.constraint = None
+        self.rotateAxis = 'x'
+        self.lastCrankAngle = 0
+        self.fSetCoa = 0
+        self.fHitInit = 1
+        self.fScaleInit = 1
+        self.fWidgetTop = 0
+        self.fFreeManip = 1
+        self.fScaling = 0
+        self.mode = None
+        self.actionEvents = [
+            ['DIRECT-mouse1', self.manipulationStart],
+            ['DIRECT-mouse1Up', self.manipulationStop],
+            ['tab', self.toggleObjectHandlesMode],
+            ['.', self.objectHandles.multiplyScalingFactorBy, 2.0],
+            ['>', self.objectHandles.multiplyScalingFactorBy, 2.0],
+            [',', self.objectHandles.multiplyScalingFactorBy, 0.5],
+            ['<', self.objectHandles.multiplyScalingFactorBy, 0.5],
+            ['shift-f', self.objectHandles.growToFit],
+            ['i', self.plantSelectedNodePath],
+            ]
+
+    def manipulationStart(self, modifiers):
+        # Start out in select mode
+        self.mode = 'select'
+        # Check for a widget hit point
+        entry = SEditor.iRay.pickWidget()
+        # Did we hit a widget?
+        if entry:
+            # Yes!
+            self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
+            self.hitPtDist = Vec3(self.hitPt).length()
+            # Constraint determined by nodes name
+            self.constraint = entry.getIntoNodePath().getName()
+        else:
+            # Nope, off the widget, no constraint
+            self.constraint = None
+        # Check to see if we are moving the object
+        # We are moving the object if we either wait long enough
+        taskMgr.doMethodLater(MANIPULATION_MOVE_DELAY,
+                              self.switchToMoveMode,
+                              'manip-move-wait')
+        # Or we move far enough
+        self.moveDir = None
+        watchMouseTask = Task.Task(self.watchMouseTask)
+        watchMouseTask.initX = SEditor.dr.mouseX
+        watchMouseTask.initY = SEditor.dr.mouseY
+        taskMgr.add(watchMouseTask, 'manip-watch-mouse')
+
+    def switchToMoveMode(self, state):
+        taskMgr.remove('manip-watch-mouse')
+        self.mode = 'move'
+        self.manipulateObject()
+        return Task.done
+
+    def watchMouseTask(self, state):
+        if (((abs (state.initX - SEditor.dr.mouseX)) > 0.01) or
+            ((abs (state.initY - SEditor.dr.mouseY)) > 0.01)):
+            taskMgr.remove('manip-move-wait')
+            self.mode = 'move'
+            self.manipulateObject()
+            return Task.done
+        else:
+            return Task.cont
+
+    def manipulationStop(self,xy=[]):
+        taskMgr.remove('manipulateObject')
+        taskMgr.remove('manip-move-wait')
+        taskMgr.remove('manip-watch-mouse')
+        # depending on flag.....
+        if self.mode == 'select':
+            # Check for object under mouse
+            # Don't intersect with hidden or backfacing objects
+            skipFlags = SKIP_HIDDEN | SKIP_BACKFACE
+            # Skip camera (and its children), unless control key is pressed
+            skipFlags |= SKIP_CAMERA * (1 - base.getControl())
+            entry = SEditor.iRay.pickGeom(skipFlags = skipFlags)
+            if entry:
+                # Record hit point information
+                self.hitPt.assign(entry.getSurfacePoint(entry.getFromNodePath()))
+                self.hitPtDist = Vec3(self.hitPt).length()
+                # Select it
+                SEditor.select(entry.getIntoNodePath(), SEditor.fShift)
+            else:
+                SEditor.deselectAll()
+        else:
+            self.manipulateObjectCleanup()
+
+    def manipulateObjectCleanup(self):
+        if self.fScaling:
+            # We had been scaling, need to reset object handles
+            self.objectHandles.transferObjectHandlesScale()
+            self.fScaling = 0
+        SEditor.selected.highlightAll()
+        self.objectHandles.showAllHandles()
+        self.objectHandles.hideGuides()
+        # Restart followSelectedNodePath task
+        self.spawnFollowSelectedNodePathTask()
+        messenger.send('DIRECT_manipulateObjectCleanup')
+
+    def spawnFollowSelectedNodePathTask(self):
+        # If nothing selected, just return
+        if not SEditor.selected.last:
+            return
+        # Clear out old task to make sure
+        taskMgr.remove('followSelectedNodePath')
+        # Where are the object handles relative to the selected object
+        pos = VBase3(0)
+        hpr = VBase3(0)
+        decomposeMatrix(SEditor.selected.last.mCoa2Dnp,
+                        VBase3(0), hpr, pos, CSDefault)
+        # Create the task
+        t = Task.Task(self.followSelectedNodePathTask)
+        # Update state variables
+        t.pos = pos
+        t.hpr = hpr
+        t.base = SEditor.selected.last
+        # Spawn the task
+        taskMgr.add(t, 'followSelectedNodePath')
+
+    def followSelectedNodePathTask(self, state):
+        SEditor.widget.setPosHpr(state.base, state.pos, state.hpr)
+        return Task.cont
+
+    def enableManipulation(self):
+        # Accept events
+        for event in self.actionEvents:
+            self.accept(event[0], event[1], extraArgs = event[2:])
+
+    def disableManipulation(self):
+        # Ignore events
+        for event in self.actionEvents:
+            self.ignore(event[0])
+        self.removeManipulateObjectTask()
+        taskMgr.remove('manipulateObject')
+        taskMgr.remove('manip-move-wait')
+        taskMgr.remove('manip-watch-mouse')
+        taskMgr.remove('highlightWidgetTask')
+        taskMgr.remove('resizeObjectHandles')
+
+    def toggleObjectHandlesMode(self):
+        self.fSetCoa = 1 - self.fSetCoa
+        if self.fSetCoa:
+            self.objectHandles.coaModeColor()
+        else:
+            self.objectHandles.manipModeColor()
+
+    def removeManipulateObjectTask(self):
+        taskMgr.remove('manipulateObject')
+
+    def manipulateObject(self):
+        # Only do this if something is selected
+        if SEditor.selected:
+            # Remove the task to keep the widget attached to the object
+            taskMgr.remove('followSelectedNodePath')
+            # and the task to highlight the widget
+            taskMgr.remove('highlightWidgetTask')
+            # Set manipulation flag
+            self.fManip = 1
+            # Record undo point
+            SEditor.pushUndo(SEditor.selected)
+            # Update object handles visibility
+            self.objectHandles.showGuides()
+            self.objectHandles.hideAllHandles()
+            self.objectHandles.showHandle(self.constraint)
+            # Record relationship between selected nodes and widget
+            SEditor.selected.getWrtAll()
+            # hide the bbox of the selected objects during interaction
+            SEditor.selected.dehighlightAll()
+            # Send event to signal start of manipulation
+            messenger.send('DIRECT_manipulateObjectStart')
+            # Manipulate the real object with the constraint
+            # The constraint is passed as the name of the node 
+            self.spawnManipulateObjectTask()
+
+    def spawnManipulateObjectTask(self):
+        # reset hit-pt flag
+        self.fHitInit = 1
+        self.fScaleInit = 1
+        # record initial offset between widget and camera
+        t = Task.Task(self.manipulateObjectTask)
+        t.fMouseX = abs(SEditor.dr.mouseX) > 0.9
+        t.fMouseY = abs(SEditor.dr.mouseY) > 0.9
+        if t.fMouseX:
+            t.constrainedDir = 'y'
+        else:
+            t.constrainedDir = 'x'
+        # Compute widget's xy coords in screen space
+        t.coaCenter = getScreenXY(SEditor.widget)
+        # These are used to rotate about view vector
+        if t.fMouseX and t.fMouseY:
+            t.lastAngle = getCrankAngle(t.coaCenter)
+        taskMgr.add(t, 'manipulateObject')
+
+    def manipulateObjectTask(self, state):
+        # Widget takes precedence
+        if self.constraint:
+            type = self.constraint[2:]
+            if type == 'post':
+                self.xlate1D(state)
+            elif type == 'disc':
+                self.xlate2D(state)
+            elif type == 'ring':
+                self.rotate1D(state)
+        # No widget interaction, determine free manip mode
+        elif self.fFreeManip:
+            # If we've been scaling and changed modes, reset object handles
+            if 0 and self.fScaling and (not SEditor.fAlt):
+                self.objectHandles.transferObjectHandlesScale()
+                self.fScaling = 0
+            # Alt key switches to a scaling mode
+            if SEditor.fControl:
+                self.fScaling = 1
+                self.scale3D(state)
+            # Otherwise, manip mode depends on where you started
+            elif state.fMouseX and state.fMouseY:
+                # In the corner, spin around camera's axis
+                self.rotateAboutViewVector(state)
+            elif state.fMouseX or state.fMouseY:
+                # Mouse started elsewhere in the outer frame, rotate
+                self.rotate2D(state)
+            else:
+                # Mouse started in central region, xlate
+                # Mode depends on shift key
+                if SEditor.fShift or SEditor.fControl:
+                    self.xlateCamXY(state)
+                else:
+                    self.xlateCamXZ(state)
+        if self.fSetCoa:
+            # Update coa based on current widget position
+            SEditor.selected.last.mCoa2Dnp.assign(
+                SEditor.widget.getMat(SEditor.selected.last))
+        else:
+            # Move the objects with the widget
+            SEditor.selected.moveWrtWidgetAll()
+        # Continue
+        return Task.cont
+
+    ### WIDGET MANIPULATION METHODS ###
+    def xlate1D(self, state):
+        # Constrained 1D Translation along widget axis
+        # Compute nearest hit point along axis and try to keep
+        # that point as close to the current mouse position as possible
+        # what point on the axis is the mouse pointing at?
+        self.hitPt.assign(self.objectHandles.getAxisIntersectPt(
+            self.constraint[:1]))
+        # use it to see how far to move the widget
+        if self.fHitInit:
+            # First time through, just record that point
+            self.fHitInit = 0
+            self.prevHit.assign(self.hitPt)
+        else:
+            # Move widget to keep hit point as close to mouse as possible
+            offset = self.hitPt - self.prevHit
+            SEditor.widget.setPos(SEditor.widget, offset)
+
+    def xlate2D(self, state):
+        # Constrained 2D (planar) translation
+        # Compute point of intersection of ray from eyepoint through cursor
+        # to one of the three orthogonal planes on the widget.
+        # This point tracks all subsequent mouse movements
+        self.hitPt.assign(self.objectHandles.getWidgetIntersectPt(
+            SEditor.widget, self.constraint[:1]))
+        # use it to see how far to move the widget
+        if self.fHitInit:
+            # First time through just record hit point
+            self.fHitInit = 0
+            self.prevHit.assign(self.hitPt)
+        else:
+            offset = self.hitPt - self.prevHit
+            SEditor.widget.setPos(SEditor.widget, offset)
+
+    def rotate1D(self, state):
+        # Constrained 1D rotation about the widget's main axis (X,Y, or Z)
+        # Rotation depends upon circular motion of the mouse about the
+        # projection of the widget's origin on the image plane
+        # A complete circle about the widget results in a change in
+        # orientation of 360 degrees.
+
+        # First initialize hit point/rotation angle
+        if self.fHitInit:
+            self.fHitInit = 0
+            self.rotateAxis = self.constraint[:1]
+            self.fWidgetTop = self.widgetCheck('top?')
+            self.rotationCenter = getScreenXY(SEditor.widget)
+            self.lastCrankAngle = getCrankAngle(self.rotationCenter)
+            
+        # Rotate widget based on how far cursor has swung around origin
+        newAngle = getCrankAngle(self.rotationCenter)
+        deltaAngle = self.lastCrankAngle - newAngle
+        if self.fWidgetTop:
+            deltaAngle = -1 * deltaAngle
+        if self.rotateAxis == 'x':
+            SEditor.widget.setP(SEditor.widget, deltaAngle)
+        elif self.rotateAxis == 'y':
+            if base.config.GetBool('temp-hpr-fix',0):
+                SEditor.widget.setR(SEditor.widget, deltaAngle)
+            else:
+                SEditor.widget.setR(SEditor.widget, -deltaAngle)
+        elif self.rotateAxis == 'z':
+            SEditor.widget.setH(SEditor.widget, deltaAngle)
+        # Record crank angle for next time around
+        self.lastCrankAngle = newAngle
+
+    def widgetCheck(self,type):
+        # Utility to see if we are looking at the top or bottom of
+        # a 2D planar widget or if we are looking at a 2D planar widget
+        # edge on
+        # Based upon angle between view vector from eye through the
+        # widget's origin and one of the three principle axes
+        axis = self.constraint[:1]
+        # First compute vector from eye through widget origin
+        mWidget2Cam = SEditor.widget.getMat(SEditor.camera)
+        # And determine where the viewpoint is relative to widget
+        pos = VBase3(0)
+        decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos,
+                        CSDefault)
+        widgetDir = Vec3(pos)
+        widgetDir.normalize()
+        # Convert specified widget axis to view space
+        if axis == 'x':
+            widgetAxis = Vec3(mWidget2Cam.xformVec(X_AXIS))
+        elif axis == 'y':
+            widgetAxis = Vec3(mWidget2Cam.xformVec(Y_AXIS))
+        elif axis == 'z':
+            widgetAxis = Vec3(mWidget2Cam.xformVec(Z_AXIS))
+        widgetAxis.normalize()
+        if type == 'top?':
+            # Check sign of angle between two vectors
+            return (widgetDir.dot(widgetAxis) < 0.)
+        elif type == 'edge?':
+            # Checking to see if we are viewing edge-on
+            # Check angle between two vectors
+            return(abs(widgetDir.dot(widgetAxis)) < .2)
+
+    ### FREE MANIPULATION METHODS ###
+    def xlateCamXZ(self, state):
+        """Constrained 2D motion parallel to the camera's image plane
+        This moves the object in the camera's XZ plane"""
+        # reset fHitInit in case we later switch to manip mode
+        self.fHitInit = 1
+        # Reset scaling init flag
+        self.fScaleInit = 1
+        # Where is the widget relative to current camera view
+        vWidget2Camera = SEditor.widget.getPos(SEditor.camera)
+        x = vWidget2Camera[0]
+        y = vWidget2Camera[1]
+        z = vWidget2Camera[2]
+        # Move widget (and objects) based upon mouse motion
+        # Scaled up accordingly based upon widget distance
+        dr = SEditor.dr
+        SEditor.widget.setX(
+            SEditor.camera,
+            x + 0.5 * dr.mouseDeltaX * dr.nearWidth * (y/dr.near))
+        SEditor.widget.setZ(
+            SEditor.camera,
+            z + 0.5 * dr.mouseDeltaY * dr.nearHeight * (y/dr.near))
+
+    def xlateCamXY(self, state):
+        """Constrained 2D motion perpendicular to camera's image plane
+        This moves the object in the camera's XY plane if shift is held
+        Moves object toward camera if control is held
+        """
+        # Reset scaling init flag
+        self.fScaleInit = 1
+        # Now, where is the widget relative to current camera view
+        vWidget2Camera = SEditor.widget.getPos(SEditor.camera)
+        # If this is first time around, record initial y distance
+        if self.fHitInit:
+            self.fHitInit = 0
+            # Use distance to widget to scale motion along Y
+            self.xlateSF = Vec3(vWidget2Camera).length()
+            # Get widget's current xy coords in screen space
+            coaCenter = getNearProjectionPoint(SEditor.widget)
+            self.deltaNearX = coaCenter[0] - SEditor.dr.nearVec[0]
+        # Which way do we move the object?
+        if SEditor.fControl:
+            moveDir = Vec3(vWidget2Camera)
+            # If widget is behind camera invert vector
+            if moveDir[1] < 0.0:
+                moveDir.assign(moveDir * -1)
+            moveDir.normalize()
+        else:
+            moveDir = Vec3(Y_AXIS)
+        # Move selected objects
+        dr = SEditor.dr
+        # Scale move dir
+        moveDir.assign(moveDir * (2.0 * dr.mouseDeltaY * self.xlateSF))
+        # Add it to current widget offset
+        vWidget2Camera += moveDir
+        # The object, however, stays at the same relative point to mouse in X
+        vWidget2Camera.setX((dr.nearVec[0] + self.deltaNearX) *
+                            (vWidget2Camera[1]/dr.near))
+        # Move widget
+        SEditor.widget.setPos(SEditor.camera, vWidget2Camera)
+
+    def rotate2D(self, state):
+        """ Virtual trackball rotation of widget """
+        # Reset init flag in case we switch to another mode
+        self.fHitInit = 1
+        # Reset scaling init flag
+        self.fScaleInit = 1
+        tumbleRate = 360
+        # If moving outside of center, ignore motion perpendicular to edge
+        if ((state.constrainedDir == 'y') and (abs(SEditor.dr.mouseX) > 0.9)):
+            deltaX = 0
+            deltaY = SEditor.dr.mouseDeltaY
+        elif ((state.constrainedDir == 'x') and (abs(SEditor.dr.mouseY) > 0.9)):
+            deltaX = SEditor.dr.mouseDeltaX
+            deltaY = 0
+        else:
+            deltaX = SEditor.dr.mouseDeltaX
+            deltaY = SEditor.dr.mouseDeltaY
+        # Mouse motion edge to edge of display region results in one full turn
+        relHpr(SEditor.widget, SEditor.camera, deltaX * tumbleRate,
+               -deltaY * tumbleRate, 0)
+
+    def rotateAboutViewVector(self, state):
+        # Reset init flag in case we switch to another mode
+        self.fHitInit = 1
+        # Reset scaling init flag
+        self.fScaleInit = 1
+        # Compute current angle
+        angle = getCrankAngle(state.coaCenter)
+        deltaAngle = angle - state.lastAngle
+        state.lastAngle = angle
+        # Mouse motion edge to edge of display region results in one full turn
+        if base.config.GetBool('temp-hpr-fix',0):
+            relHpr(SEditor.widget, SEditor.camera, 0, 0, -deltaAngle)
+        else:
+            relHpr(SEditor.widget, SEditor.camera, 0, 0, deltaAngle)
+
+    def scale3D(self, state):
+        # Scale the selected node based upon up down mouse motion
+        # Mouse motion from edge to edge results in a factor of 4 scaling
+        # From midpoint to edge doubles or halves objects scale
+        if self.fScaleInit:
+            self.fScaleInit = 0
+            self.manipRef.setPos(SEditor.widget, 0, 0, 0)
+            self.manipRef.setHpr(SEditor.camera, 0, 0, 0)
+            self.initScaleMag = Vec3(
+                self.objectHandles.getWidgetIntersectPt(
+                self.manipRef, 'y')).length()
+            # record initial scale
+            self.initScale = SEditor.widget.getScale()
+        # Reset fHitInitFlag
+        self.fHitInit = 1
+        # Begin
+        # Scale factor is ratio current mag with init mag
+        currScale = (
+            self.initScale *
+            (self.objectHandles.getWidgetIntersectPt(
+            self.manipRef, 'y').length() /
+             self.initScaleMag)
+            )
+        SEditor.widget.setScale(currScale)
+
+    ## Utility functions ##
+    def plantSelectedNodePath(self):
+        """ Move selected object to intersection point of cursor on scene """
+        # Check for intersection
+        entry = SEditor.iRay.pickGeom(
+            skipFlags = SKIP_HIDDEN | SKIP_BACKFACE | SKIP_CAMERA)
+        # MRM: Need to handle moving COA
+        if (entry != None) and (SEditor.selected.last != None):
+            # Record undo point
+            SEditor.pushUndo(SEditor.selected)
+            # Record wrt matrix
+            SEditor.selected.getWrtAll()
+            # Move selected
+            SEditor.widget.setPos(
+                SEditor.camera,entry.getSurfacePoint(entry.getFromNodePath()))
+            # Move all the selected objects with widget
+            # Move the objects with the widget
+            SEditor.selected.moveWrtWidgetAll()
+            # Let everyone know that something was moved
+            messenger.send('DIRECT_manipulateObjectCleanup')
+
+class ObjectHandles(NodePath,DirectObject):
+    def __init__(self):
+        # Initialize the superclass
+        NodePath.__init__(self)
+
+        # Load up object handles model and assign it to self
+        self.assign(loader.loadModel('models/misc/objectHandles'))
+        self.setName('objectHandles')
+        self.scalingNode = self.getChild(0)
+        self.scalingNode.setName('ohScalingNode')
+        self.ohScalingFactor = 1.0
+        # To avoid recreating a vec every frame
+        self.hitPt = Vec3(0)
+        # Get a handle on the components
+        self.xHandles = self.find('**/X')
+        self.xPostGroup = self.xHandles.find('**/x-post-group')
+        self.xPostCollision = self.xHandles.find('**/x-post')
+        self.xRingGroup = self.xHandles.find('**/x-ring-group')
+        self.xRingCollision = self.xHandles.find('**/x-ring')
+        self.xDiscGroup = self.xHandles.find('**/x-disc-group')
+        self.xDisc = self.xHandles.find('**/x-disc-visible')
+        self.xDiscCollision = self.xHandles.find('**/x-disc')
+        
+        self.yHandles = self.find('**/Y')
+        self.yPostGroup = self.yHandles.find('**/y-post-group')
+        self.yPostCollision = self.yHandles.find('**/y-post')
+        self.yRingGroup = self.yHandles.find('**/y-ring-group')
+        self.yRingCollision = self.yHandles.find('**/y-ring')
+        self.yDiscGroup = self.yHandles.find('**/y-disc-group')
+        self.yDisc = self.yHandles.find('**/y-disc-visible')
+        self.yDiscCollision = self.yHandles.find('**/y-disc')
+        
+        self.zHandles = self.find('**/Z')
+        self.zPostGroup = self.zHandles.find('**/z-post-group')
+        self.zPostCollision = self.zHandles.find('**/z-post')
+        self.zRingGroup = self.zHandles.find('**/z-ring-group')
+        self.zRingCollision = self.zHandles.find('**/z-ring')
+        self.zDiscGroup = self.zHandles.find('**/z-disc-group')
+        self.zDisc = self.zHandles.find('**/z-disc-visible')
+        self.zDiscCollision = self.zHandles.find('**/z-disc')
+
+        # Adjust visiblity, colors, and transparency
+        self.xPostCollision.hide()
+        self.xRingCollision.hide()
+        self.xDisc.setColor(1,0,0,.2)
+        self.yPostCollision.hide()
+        self.yRingCollision.hide()
+        self.yDisc.setColor(0,1,0,.2)
+        self.zPostCollision.hide()
+        self.zRingCollision.hide()
+        self.zDisc.setColor(0,0,1,.2)
+        # Augment geometry with lines
+        self.createObjectHandleLines()
+        # Create long markers to help line up in world
+        self.createGuideLines()
+        self.hideGuides()
+
+        # Start with widget handles hidden
+        self.fActive = 1
+        self.toggleWidget()
+
+        # Make sure object handles are never lit or drawn in wireframe
+        useDirectRenderStyle(self)
+
+    def coaModeColor(self):
+        self.setColor(.5,.5,.5,1)
+
+    def manipModeColor(self):
+        self.clearColor()
+
+    def toggleWidget(self):
+        if self.fActive:
+            self.deactivate()
+        else:
+            self.activate()
+
+    def activate(self):
+        self.scalingNode.reparentTo(self)
+        self.fActive = 1
+
+    def deactivate(self):
+        self.scalingNode.reparentTo(hidden)
+        self.fActive = 0
+
+    def showWidgetIfActive(self):
+        if self.fActive:
+            self.reparentTo(SEditor.group)
+
+    def showWidget(self):
+        self.reparentTo(SEditor.group)
+
+    def hideWidget(self):
+        self.reparentTo(hidden)
+
+    def enableHandles(self, handles):
+        if type(handles) == types.ListType:
+            for handle in handles:
+                self.enableHandle(handle)
+        elif handles == 'x':
+            self.enableHandles(['x-post','x-ring','x-disc'])
+        elif handles == 'y':
+            self.enableHandles(['y-post','y-ring','y-disc'])
+        elif handles == 'z':
+            self.enableHandles(['z-post','z-ring','z-disc'])
+        elif handles == 'post':
+            self.enableHandles(['x-post','y-post','z-post'])
+        elif handles == 'ring':
+            self.enableHandles(['x-ring','y-ring','z-ring'])
+        elif handles == 'disc':
+            self.enableHandles(['x-disc','y-disc','z-disc'])
+        elif handles == 'all':
+            self.enableHandles(['x-post','x-ring','x-disc',
+                                'y-post','y-ring','y-disc',
+                                'z-post','z-ring','z-disc'])
+
+    def enableHandle(self, handle):
+        if handle == 'x-post':
+            self.xPostGroup.reparentTo(self.xHandles)
+        elif handle == 'x-ring':
+            self.xRingGroup.reparentTo(self.xHandles)
+        elif handle == 'x-disc':
+            self.xDiscGroup.reparentTo(self.xHandles)
+        if handle == 'y-post':
+            self.yPostGroup.reparentTo(self.yHandles)
+        elif handle == 'y-ring':
+            self.yRingGroup.reparentTo(self.yHandles)
+        elif handle == 'y-disc':
+            self.yDiscGroup.reparentTo(self.yHandles)
+        if handle == 'z-post':
+            self.zPostGroup.reparentTo(self.zHandles)
+        elif handle == 'z-ring':
+            self.zRingGroup.reparentTo(self.zHandles)
+        elif handle == 'z-disc':
+            self.zDiscGroup.reparentTo(self.zHandles)
+
+    def disableHandles(self, handles):
+        if type(handles) == types.ListType:
+            for handle in handles:
+                self.disableHandle(handle)
+        elif handles == 'x':
+            self.disableHandles(['x-post','x-ring','x-disc'])
+        elif handles == 'y':
+            self.disableHandles(['y-post','y-ring','y-disc'])
+        elif handles == 'z':
+            self.disableHandles(['z-post','z-ring','z-disc'])
+        elif handles == 'post':
+            self.disableHandles(['x-post','y-post','z-post'])
+        elif handles == 'ring':
+            self.disableHandles(['x-ring','y-ring','z-ring'])
+        elif handles == 'disc':
+            self.disableHandles(['x-disc','y-disc','z-disc'])
+        elif handles == 'all':
+            self.disableHandles(['x-post','x-ring','x-disc',
+                                 'y-post','y-ring','y-disc',
+                                 'z-post','z-ring','z-disc'])
+
+    def disableHandle(self, handle):
+        if handle == 'x-post':
+            self.xPostGroup.reparentTo(hidden)
+        elif handle == 'x-ring':
+            self.xRingGroup.reparentTo(hidden)
+        elif handle == 'x-disc':
+            self.xDiscGroup.reparentTo(hidden)
+        if handle == 'y-post':
+            self.yPostGroup.reparentTo(hidden)
+        elif handle == 'y-ring':
+            self.yRingGroup.reparentTo(hidden)
+        elif handle == 'y-disc':
+            self.yDiscGroup.reparentTo(hidden)
+        if handle == 'z-post':
+            self.zPostGroup.reparentTo(hidden)
+        elif handle == 'z-ring':
+            self.zRingGroup.reparentTo(hidden)
+        elif handle == 'z-disc':
+            self.zDiscGroup.reparentTo(hidden)
+
+    def showAllHandles(self):
+        self.xPost.show()
+        self.xRing.show()
+        self.xDisc.show()
+        self.yPost.show()
+        self.yRing.show()
+        self.yDisc.show()
+        self.zPost.show()
+        self.zRing.show()
+        self.zDisc.show()
+
+    def hideAllHandles(self):
+        self.xPost.hide()
+        self.xRing.hide()
+        self.xDisc.hide()
+        self.yPost.hide()
+        self.yRing.hide()
+        self.yDisc.hide()
+        self.zPost.hide()
+        self.zRing.hide()
+        self.zDisc.hide()
+
+    def showHandle(self, handle):
+        if handle == 'x-post':
+            self.xPost.show()
+        elif handle == 'x-ring':
+            self.xRing.show()
+        elif handle == 'x-disc':
+            self.xDisc.show()
+        elif handle == 'y-post':
+            self.yPost.show()
+        elif handle == 'y-ring':
+            self.yRing.show()
+        elif handle == 'y-disc':
+            self.yDisc.show()
+        elif handle == 'z-post':
+            self.zPost.show()
+        elif handle == 'z-ring':
+            self.zRing.show()
+        elif handle == 'z-disc':
+            self.zDisc.show()
+
+    def showGuides(self):
+        self.guideLines.show()
+
+    def hideGuides(self):
+        self.guideLines.hide()
+
+    def setScalingFactor(self, scaleFactor):
+        self.ohScalingFactor = scaleFactor
+        self.scalingNode.setScale(self.ohScalingFactor)
+
+    def getScalingFactor(self):
+        return self.scalingNode.getScale()
+
+    def transferObjectHandlesScale(self):
+        # see how much object handles have been scaled
+        ohs = self.getScale()
+        sns = self.scalingNode.getScale()
+        # Transfer this to the scaling node
+        self.scalingNode.setScale(
+            ohs[0] * sns[0],
+            ohs[1] * sns[1],
+            ohs[2] * sns[2])
+        self.setScale(1)
+
+    def multiplyScalingFactorBy(self, factor):
+        taskMgr.remove('resizeObjectHandles')
+        sf = self.ohScalingFactor = self.ohScalingFactor * factor
+        self.scalingNode.lerpScale(sf,sf,sf, 0.5,
+                                   blendType = 'easeInOut',
+                                   task = 'resizeObjectHandles')
+
+    def growToFit(self):
+        taskMgr.remove('resizeObjectHandles')
+        # Increase handles scale until they cover 80% of the min dimension
+        # Originally, here is "cover 30% of the min dimension", we changed.
+        pos = SEditor.widget.getPos(SEditor.camera)
+        minDim = min(SEditor.dr.nearWidth, SEditor.dr.nearHeight)
+        sf = 0.4 * minDim * (pos[1]/SEditor.dr.near)
+        self.ohScalingFactor = sf
+        self.scalingNode.lerpScale(sf,sf,sf, 0.5,
+                                   blendType = 'easeInOut',
+                                   task = 'resizeObjectHandles')
+
+    def createObjectHandleLines(self):
+        # X post
+        self.xPost = self.xPostGroup.attachNewNode('x-post-visible')
+        lines = LineNodePath(self.xPost)
+        lines.setColor(VBase4(1,0,0,1))
+        lines.setThickness(5)
+        lines.moveTo(0,0,0)
+        lines.drawTo(1.5,0,0)
+        lines.create()
+        lines = LineNodePath(self.xPost)
+        lines.setColor(VBase4(1,0,0,1))
+        lines.setThickness(1.5)
+        lines.moveTo(0,0,0)
+        lines.drawTo(-1.5,0,0)
+        lines.create()
+        
+        # X ring
+        self.xRing = self.xRingGroup.attachNewNode('x-ring-visible')
+        lines = LineNodePath(self.xRing)
+        lines.setColor(VBase4(1,0,0,1))
+        lines.setThickness(3)
+        lines.moveTo(0,1,0)
+        for ang in range(15, 370, 15):
+            lines.drawTo(0,
+                          math.cos(deg2Rad(ang)),
+                          math.sin(deg2Rad(ang)))
+        lines.create()
+        
+        # Y post
+        self.yPost = self.yPostGroup.attachNewNode('y-post-visible')
+        lines = LineNodePath(self.yPost)
+        lines.setColor(VBase4(0,1,0,1))
+        lines.setThickness(5)
+        lines.moveTo(0,0,0)
+        lines.drawTo(0,1.5,0)
+        lines.create()
+        lines = LineNodePath(self.yPost)
+        lines.setColor(VBase4(0,1,0,1))
+        lines.setThickness(1.5)
+        lines.moveTo(0,0,0)
+        lines.drawTo(0,-1.5,0)
+        lines.create()
+        
+        # Y ring
+        self.yRing = self.yRingGroup.attachNewNode('y-ring-visible')
+        lines = LineNodePath(self.yRing)
+        lines.setColor(VBase4(0,1,0,1))
+        lines.setThickness(3)
+        lines.moveTo(1,0,0)
+        for ang in range(15, 370, 15):
+            lines.drawTo(math.cos(deg2Rad(ang)),
+                          0,
+                          math.sin(deg2Rad(ang)))
+        lines.create()
+
+        # Z post
+        self.zPost = self.zPostGroup.attachNewNode('z-post-visible')
+        lines = LineNodePath(self.zPost)
+        lines.setColor(VBase4(0,0,1,1))
+        lines.setThickness(5)
+        lines.moveTo(0,0,0)
+        lines.drawTo(0,0,1.5)
+        lines.create()
+        lines = LineNodePath(self.zPost)
+        lines.setColor(VBase4(0,0,1,1))
+        lines.setThickness(1.5)
+        lines.moveTo(0,0,0)
+        lines.drawTo(0,0,-1.5)
+        lines.create()
+        
+        # Z ring
+        self.zRing = self.zRingGroup.attachNewNode('z-ring-visible')
+        lines = LineNodePath(self.zRing)
+        lines.setColor(VBase4(0,0,1,1))
+        lines.setThickness(3)
+        lines.moveTo(1,0,0)
+        for ang in range(15, 370, 15):
+            lines.drawTo(math.cos(deg2Rad(ang)),
+                          math.sin(deg2Rad(ang)),
+                          0)
+        lines.create()
+
+    def createGuideLines(self):
+        self.guideLines = self.attachNewNode('guideLines')
+        # X guide lines
+        lines = LineNodePath(self.guideLines)
+        lines.setColor(VBase4(1,0,0,1))
+        lines.setThickness(0.5)
+        lines.moveTo(-500,0,0)
+        lines.drawTo(500,0,0)
+        lines.create()
+        lines.setName('x-guide')
+
+        # Y guide lines
+        lines = LineNodePath(self.guideLines)
+        lines.setColor(VBase4(0,1,0,1))
+        lines.setThickness(0.5)
+        lines.moveTo(0,-500,0)
+        lines.drawTo(0,500,0)
+        lines.create()
+        lines.setName('y-guide')
+
+        # Z guide lines
+        lines = LineNodePath(self.guideLines)
+        lines.setColor(VBase4(0,0,1,1))
+        lines.setThickness(0.5)
+        lines.moveTo(0,0,-500)
+        lines.drawTo(0,0,500)
+        lines.create()
+        lines.setName('z-guide')
+
+    def getAxisIntersectPt(self, axis):
+        # Calc the xfrom from camera to widget
+        mCam2Widget = SEditor.camera.getMat(SEditor.widget)
+        lineDir = Vec3(mCam2Widget.xformVec(SEditor.dr.nearVec))
+        lineDir.normalize()
+        # And determine where the viewpoint is relative to widget
+        lineOrigin = VBase3(0)
+        decomposeMatrix(mCam2Widget, VBase3(0), VBase3(0), lineOrigin,
+                        CSDefault)
+        # Now see where this hits the plane containing the 1D motion axis.
+        # Pick the intersection plane most normal to the intersection ray
+        # by comparing lineDir with plane normals.  The plane with the
+        # largest dotProduct is most "normal"
+        if axis == 'x':
+            if (abs(lineDir.dot(Y_AXIS)) > abs(lineDir.dot(Z_AXIS))):
+                self.hitPt.assign(
+                    planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
+            else:
+                self.hitPt.assign(
+                    planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
+            # We really only care about the nearest point on the axis
+            self.hitPt.setY(0)
+            self.hitPt.setZ(0)
+        elif axis == 'y':
+            if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Z_AXIS))):
+                self.hitPt.assign(
+                    planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
+            else:
+                self.hitPt.assign(
+                    planeIntersect(lineOrigin, lineDir, ORIGIN, Z_AXIS))
+            # We really only care about the nearest point on the axis
+            self.hitPt.setX(0)
+            self.hitPt.setZ(0)
+        elif axis == 'z':
+            if (abs(lineDir.dot(X_AXIS)) > abs(lineDir.dot(Y_AXIS))):
+                self.hitPt.assign(
+                    planeIntersect(lineOrigin, lineDir, ORIGIN, X_AXIS))
+            else:
+                self.hitPt.assign(
+                    planeIntersect(lineOrigin, lineDir, ORIGIN, Y_AXIS))
+            # We really only care about the nearest point on the axis
+            self.hitPt.setX(0)
+            self.hitPt.setY(0)
+        return self.hitPt
+
+    def getWidgetIntersectPt(self, nodePath, plane):
+        # Find out the point of interection of the ray passing though the mouse
+        # with the plane containing the 2D xlation or 1D rotation widgets
+
+        # Calc the xfrom from camera to the nodePath
+        mCam2NodePath = SEditor.camera.getMat(nodePath)
+    
+        # And determine where the viewpoint is relative to widget
+        lineOrigin = VBase3(0)
+        decomposeMatrix(mCam2NodePath, VBase3(0), VBase3(0), lineOrigin,
+                        CSDefault)
+        
+        # Next we find the vector from viewpoint to the widget through
+        # the mouse's position on near plane.
+        # This defines the intersection ray
+        lineDir = Vec3(mCam2NodePath.xformVec(SEditor.dr.nearVec))
+        lineDir.normalize()
+        # Find the hit point
+        if plane == 'x':
+            self.hitPt.assign(planeIntersect(
+                lineOrigin, lineDir, ORIGIN, X_AXIS))
+        elif plane == 'y':
+            self.hitPt.assign(planeIntersect(
+                lineOrigin, lineDir, ORIGIN, Y_AXIS))
+        elif plane == 'z':
+            self.hitPt.assign(planeIntersect(
+                lineOrigin, lineDir, ORIGIN, Z_AXIS))
+        return self.hitPt
+
+
+

+ 2073 - 0
contrib/src/sceneeditor/seMopathRecorder.py

@@ -0,0 +1,2073 @@
+#################################################################
+# seMopathRecorder.py
+# Originally from MopathRecorder.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# We have to change something about data flow and UI
+# so the curves data can be saved into our dataHolder.
+# Other things we have changed here is that we have added a copy model
+# of target nodePath under the render when the recording begins.
+# And, of course, we have removed it right after the recording ends.
+# You can find it in toggleRecord function.
+#
+#################################################################
+
+from direct.showbase.DirectObject import DirectObject
+from direct.tkwidgets.AppShell import AppShell
+#from direct.directtools.DirectGlobals import *
+#from direct.directtools.DirectUtil import *
+from seGeometry import *
+from seSelection import *
+from direct.task.Task import Task
+from direct.tkwidgets.Dial import AngleDial
+from direct.tkwidgets.Floater import Floater
+from direct.tkwidgets.Slider import Slider
+from direct.tkwidgets.EntryScale import EntryScale
+from direct.tkwidgets.VectorWidgets import Vector2Entry, Vector3Entry
+from direct.tkwidgets.VectorWidgets import ColorEntry
+from Tkinter import Button, Frame, Radiobutton, Checkbutton, Label
+from Tkinter import StringVar, BooleanVar, Entry, Scale
+import os, string, Tkinter, Pmw
+import __builtin__
+
+PRF_UTILITIES = [
+    'lambda: camera.lookAt(render)',
+    'lambda: camera.setZ(render, 0.0)',
+    'lambda s = self: s.playbackMarker.lookAt(render)',
+    'lambda s = self: s.playbackMarker.setZ(render, 0.0)',
+    'lambda s = self: s.followTerrain(10.0)']
+
+class MopathRecorder(AppShell, DirectObject):
+    # Override class variables here
+    appname = 'Mopath Recorder Panel'
+    frameWidth      = 450
+    frameHeight     = 550
+    usecommandarea = 0
+    usestatusarea  = 0
+    count = 0
+
+    def __init__(self, parent = None, **kw):
+        INITOPT = Pmw.INITOPT
+        name = 'recorder-%d' % MopathRecorder.count
+        MopathRecorder.count += 1
+        optiondefs = (
+            ('title',       self.appname,         None),
+            ('nodePath',    None,                 None),
+            ('name',        name,                 None)
+            )
+        self.defineoptions(kw, optiondefs)
+
+        # Call superclass initialization function
+        AppShell.__init__(self)
+        
+        self.initialiseoptions(MopathRecorder)
+
+        self.selectNodePathNamed('camera')
+
+        self.parent.resizable(False,False) ## Disable the ability to resize for this Window.
+
+    def appInit(self):
+        self.mopathRecorderNode = render.attachNewNode("MopathRecorder")
+        self.name = self['name']
+        # Dictionary of widgets
+        self.widgetDict = {}
+        self.variableDict = {}
+        # Initialize state
+        # The active node path
+        self.nodePath = self['nodePath']
+        self.playbackNodePath = self.nodePath
+        # The active node path's parent
+        self.nodePathParent = render
+        # Top level node path
+        self.recorderNodePath = self.mopathRecorderNode.attachNewNode(self.name)
+        # Temp CS for use in refinement/path extension
+        self.tempCS = self.recorderNodePath.attachNewNode(
+            'mopathRecorderTempCS')
+        # Marker for use in playback
+        self.playbackMarker = loader.loadModel('models/misc/sphere')  ###
+        self.playbackMarker.setName('Playback Marker')
+        self.playbackMarker.reparentTo(self.recorderNodePath)
+        self.playbackMarkerIds = self.getChildIds(
+            self.playbackMarker.getChild(0))
+        self.playbackMarker.hide()
+        # Tangent marker
+        self.tangentGroup = self.playbackMarker.attachNewNode('Tangent Group')
+        self.tangentGroup.hide()
+        self.tangentMarker = loader.loadModel('models/misc/sphere')
+        self.tangentMarker.reparentTo(self.tangentGroup)
+        self.tangentMarker.setScale(0.5)
+        self.tangentMarker.setColor(1,0,1,1)
+        self.tangentMarker.setName('Tangent Marker')
+        self.tangentMarkerIds = self.getChildIds(
+            self.tangentMarker.getChild(0))
+        self.tangentLines = LineNodePath(self.tangentGroup)
+        self.tangentLines.setColor(VBase4(1,0,1,1))
+        self.tangentLines.setThickness(1)
+        self.tangentLines.moveTo(0,0,0)
+        self.tangentLines.drawTo(0,0,0)
+        self.tangentLines.create()
+        # Active node path dictionary
+        self.nodePathDict = {}
+        self.nodePathDict['marker'] = self.playbackMarker
+        self.nodePathDict['camera'] = camera
+        self.nodePathDict['widget'] = SEditor.widget
+        self.nodePathDict['mopathRecorderTempCS'] = self.tempCS
+        self.nodePathNames = ['marker', 'camera', 'selected']
+        # ID of selected object
+        self.manipulandumId = None
+        self.trace = LineNodePath(self.recorderNodePath)
+        self.oldPlaybackNodePath = None
+        # Count of point sets recorded
+        self.pointSet = []
+        self.prePoints = []
+        self.postPoints = []
+        self.pointSetDict = {}
+        self.pointSetCount = 0
+        self.pointSetName = self.name + '-ps-' + `self.pointSetCount`
+        # User callback to call before recording point
+        self.samplingMode = 'Continuous'
+        self.preRecordFunc = None
+        # Hook to start/stop recording
+        self.startStopHook = 'f6'
+        self.keyframeHook = 'f10'
+        # Curve fitter object
+        self.lastPos = Point3(0)
+        self.curveFitter = CurveFitter()
+        # Curve variables
+        # Number of ticks per parametric unit
+        self.numTicks = 1
+        # Number of segments to represent each parametric unit
+        # This just affects the visual appearance of the curve
+        self.numSegs = 40
+        # The nurbs curves
+        self.curveCollection = None
+        # Curve drawers
+        self.nurbsCurveDrawer = NurbsCurveDrawer()
+        self.nurbsCurveDrawer.setCurves(ParametricCurveCollection())
+        self.nurbsCurveDrawer.setNumSegs(self.numSegs)
+        self.nurbsCurveDrawer.setShowHull(0)
+        self.nurbsCurveDrawer.setShowCvs(0)
+        self.nurbsCurveDrawer.setNumTicks(0)
+        self.nurbsCurveDrawer.setTickScale(5.0)
+        self.curveNodePath = self.recorderNodePath.attachNewNode(
+            self.nurbsCurveDrawer.getGeomNode())
+        useDirectRenderStyle(self.curveNodePath)
+        # Playback variables
+        self.maxT = 0.0
+        self.playbackTime = 0.0
+        self.loopPlayback = 1
+        self.playbackSF = 1.0
+        # Sample variables
+        self.desampleFrequency = 1
+        self.numSamples = 100
+        self.recordStart = 0.0
+        self.deltaTime = 0.0
+        self.controlStart = 0.0
+        self.controlStop = 0.0
+        self.recordStop = 0.0
+        self.cropFrom = 0.0
+        self.cropTo = 0.0
+        self.fAdjustingValues = 0
+        # For terrain following
+        self.iRayCS = self.recorderNodePath.attachNewNode(
+            'mopathRecorderIRayCS')
+        self.iRay = SelectionRay(self.iRayCS)
+        # Set up event hooks
+        self.actionEvents = [
+            ('DIRECT_undo', self.undoHook),
+            ('DIRECT_pushUndo', self.pushUndoHook),
+            ('DIRECT_undoListEmpty', self.undoListEmptyHook),
+            ('DIRECT_redo', self.redoHook),
+            ('DIRECT_pushRedo', self.pushRedoHook),
+            ('DIRECT_redoListEmpty', self.redoListEmptyHook),
+            ('DIRECT_selectedNodePath', self.selectedNodePathHook),
+            ('DIRECT_deselectedNodePath', self.deselectedNodePathHook),
+            ('DIRECT_manipulateObjectStart', self.manipulateObjectStartHook),
+            ('DIRECT_manipulateObjectCleanup',
+             self.manipulateObjectCleanupHook),
+            ]
+        for event, method in self.actionEvents:
+            self.accept(event, method)
+
+    def createInterface(self):
+        interior = self.interior()
+        # FILE MENU
+        # Get a handle on the file menu so commands can be inserted
+        # before quit item
+        fileMenu = self.menuBar.component('File-menu')
+        fileMenu.insert_command(
+            fileMenu.index('Quit'),
+            label = 'Load Curve',
+            command = self.loadCurveFromFile)
+        fileMenu.insert_command(
+            fileMenu.index('Quit'),
+            label = 'Save Curve',
+            command = self.saveCurveToFile)
+
+        # Add mopath recorder commands to menubar
+        self.menuBar.addmenu('Recorder', 'Mopath Recorder Panel Operations')
+        self.menuBar.addmenuitem(
+            'Recorder', 'command',
+            'Save current curve as a new point set',
+            label = 'Save Point Set',
+            command = self.extractPointSetFromCurveCollection)
+        self.menuBar.addmenuitem(
+            'Recorder', 'command',
+            'Toggle widget visability',
+            label = 'Toggle Widget Vis',
+            command = self.toggleWidgetVis)
+        self.menuBar.addmenuitem(
+            'Recorder', 'command',
+            'Toggle widget manipulation mode',
+            label = 'Toggle Widget Mode',
+            command = SEditor.manipulationControl.toggleObjectHandlesMode)
+
+        self.historyWidget = self.createComboBox(self.menuFrame, 'Mopath', 'Path:',
+                                                 'Select input points to fit curve to', '',
+                                                 self.selectPointSetNamed, expand = 1)
+
+
+        self.undoButton = Button(self.menuFrame, text = 'Undo',
+                                 command = SEditor.undo)
+        if SEditor.undoList:
+            self.undoButton['state'] = 'normal'
+        else:
+            self.undoButton['state'] = 'disabled'
+        self.undoButton.pack(side = Tkinter.LEFT, expand = 0)
+        self.bind(self.undoButton, 'Undo last operation')
+
+        self.redoButton = Button(self.menuFrame, text = 'Redo',
+                                 command = SEditor.redo)
+        if SEditor.redoList:
+            self.redoButton['state'] = 'normal'
+        else:
+            self.redoButton['state'] = 'disabled'
+        self.redoButton.pack(side = Tkinter.LEFT, expand = 0)
+        self.bind(self.redoButton, 'Redo last operation')
+
+        # Record button
+        mainFrame = Frame(interior, relief = Tkinter.SUNKEN, borderwidth = 2)
+        frame = Frame(mainFrame)
+        # Active node path
+        # Button to select active node path
+        widget = self.createButton(frame, 'Recording', 'Node Path:',
+                                   'Select Active Mopath Node Path',
+                                   lambda s = self: SEditor.select(s.nodePath),
+                                   side = Tkinter.LEFT, expand = 0)
+        widget['relief'] = Tkinter.FLAT
+        self.nodePathMenu = Pmw.ComboBox(
+            frame, entry_width = 20,
+            selectioncommand = self.selectNodePathNamed,
+            scrolledlist_items = self.nodePathNames)
+        self.nodePathMenu.selectitem('camera')
+        self.nodePathMenuEntry = (
+            self.nodePathMenu.component('entryfield_entry'))
+        self.nodePathMenuBG = (
+            self.nodePathMenuEntry.configure('background')[3])
+        self.nodePathMenu.pack(side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        self.bind(self.nodePathMenu,
+                  'Select active node path used for recording and playback')
+        # Recording type
+        self.recordingType = StringVar()
+        self.recordingType.set('New Curve')
+        widget = self.createRadiobutton(
+            frame, 'left',
+            'Recording', 'New Curve',
+            ('Next record session records a new path'),
+            self.recordingType, 'New Curve',expand = 0)
+        widget = self.createRadiobutton(
+            frame, 'left',
+            'Recording', 'Refine',
+            ('Next record session refines existing path'),
+            self.recordingType, 'Refine', expand = 0)
+        widget = self.createRadiobutton(
+            frame, 'left',
+            'Recording', 'Extend',
+            ('Next record session extends existing path'),
+            self.recordingType, 'Extend', expand = 0)
+        frame.pack(fill = Tkinter.X, expand = 1)
+
+        frame = Frame(mainFrame)
+        widget = self.createCheckbutton(
+            frame, 'Recording', 'Record',
+            'On: path is being recorded', self.toggleRecord, 0,
+            side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 1)
+        widget.configure(foreground = 'Red', relief = Tkinter.RAISED, borderwidth = 2,
+                         anchor = Tkinter.CENTER, width = 16)
+        widget = self.createButton(frame, 'Recording', 'Add Keyframe',
+                                   'Add Keyframe To Current Path',
+                                   self.addKeyframe,
+                                   side = Tkinter.LEFT, expand = 1)
+
+        widget = self.createButton(frame, 'Recording', 'Bind Path to Node',
+                                   'Bind Motion Path to selected Object',
+                                   self.bindMotionPathToNode,
+                                   side = Tkinter.LEFT, expand = 1)
+
+
+        frame.pack(fill = Tkinter.X, expand = 1)
+        
+        mainFrame.pack(expand = 1, fill = Tkinter.X, pady = 3)
+        
+        # Playback controls
+        playbackFrame = Frame(interior, relief = Tkinter.SUNKEN,
+                              borderwidth = 2)
+        Label(playbackFrame, text = 'PLAYBACK CONTROLS',
+              font=('MSSansSerif', 12, 'bold')).pack(fill = Tkinter.X)
+        # Main playback control slider
+        widget = self.createEntryScale(
+            playbackFrame, 'Playback', 'Time', 'Set current playback time',
+            resolution = 0.01, command = self.playbackGoTo, side = Tkinter.TOP)
+        widget.component('hull')['relief'] = Tkinter.RIDGE
+        # Kill playback task if drag slider
+        widget['preCallback'] = self.stopPlayback
+        # Jam duration entry into entry scale
+        self.createLabeledEntry(widget.labelFrame, 'Resample', 'Path Duration',
+                                'Set total curve duration',
+                                command = self.setPathDuration,
+                                side = Tkinter.LEFT, expand = 0)
+        # Start stop buttons
+        frame = Frame(playbackFrame)
+        widget = self.createButton(frame, 'Playback', '<<',
+                                   'Jump to start of playback',
+                                   self.jumpToStartOfPlayback,
+                                   side = Tkinter.LEFT, expand = 1)
+        widget['font'] = (('MSSansSerif', 12, 'bold'))
+        widget = self.createCheckbutton(frame, 'Playback', 'Play',
+                                        'Start/Stop playback',
+                                        self.startStopPlayback, 0,
+                                        side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 1)
+        widget.configure(anchor = 'center', justify = 'center',
+                         relief = Tkinter.RAISED, font = ('MSSansSerif', 12, 'bold'))
+        widget = self.createButton(frame, 'Playback', '>>',
+                                   'Jump to end of playback',
+                                   self.jumpToEndOfPlayback,
+                                   side = Tkinter.LEFT, expand = 1)
+        widget['font'] = (('MSSansSerif', 12, 'bold'))
+        self.createCheckbutton(frame, 'Playback', 'Loop',
+                               'On: loop playback',
+                               self.setLoopPlayback, self.loopPlayback,
+                               side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 0)
+        frame.pack(fill = Tkinter.X, expand = 1)
+
+        # Speed control
+        frame = Frame(playbackFrame)
+        widget = Button(frame, text = 'PB Speed Vernier', relief = Tkinter.FLAT,
+                        command = lambda s = self: s.setSpeedScale(1.0))
+        widget.pack(side = Tkinter.LEFT, expand = 0)
+        self.speedScale = Scale(frame, from_ = -1, to = 1,
+                                resolution = 0.01, showvalue = 0,
+                                width = 10, orient = 'horizontal',
+                                command = self.setPlaybackSF)
+        self.speedScale.pack(side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        self.speedVar = StringVar()
+        self.speedVar.set("0.00")
+        self.speedEntry = Entry(frame, textvariable = self.speedVar,
+                                width = 8)
+        self.speedEntry.bind(
+            '<Return>',
+            lambda e = None, s = self: s.setSpeedScale(
+            string.atof(s.speedVar.get())))
+        self.speedEntry.pack(side = Tkinter.LEFT, expand = 0)
+        frame.pack(fill = Tkinter.X, expand = 1)
+        
+        playbackFrame.pack(fill = Tkinter.X, pady = 2)
+
+        # Create notebook pages
+        self.mainNotebook = Pmw.NoteBook(interior)
+        self.mainNotebook.pack(fill = Tkinter.BOTH, expand = 1)
+        self.resamplePage = self.mainNotebook.add('Resample')
+        self.refinePage = self.mainNotebook.add('Refine')
+        self.extendPage = self.mainNotebook.add('Extend')
+        self.cropPage = self.mainNotebook.add('Crop')
+        self.drawPage = self.mainNotebook.add('Draw')
+        self.optionsPage = self.mainNotebook.add('Options')
+
+        ## RESAMPLE PAGE
+        label = Label(self.resamplePage, text = 'RESAMPLE CURVE',
+                      font=('MSSansSerif', 12, 'bold'))
+        label.pack(fill = Tkinter.X)
+        
+        # Resample
+        resampleFrame = Frame(
+            self.resamplePage, relief = Tkinter.SUNKEN, borderwidth = 2)
+        label = Label(resampleFrame, text = 'RESAMPLE CURVE',
+                      font=('MSSansSerif', 12, 'bold')).pack()
+        widget = self.createSlider(
+            resampleFrame, 'Resample', 'Num. Samples',
+            'Number of samples in resampled curve',
+            resolution = 1, min = 2, max = 1000, command = self.setNumSamples)
+        widget.component('hull')['relief'] = Tkinter.RIDGE
+        widget['postCallback'] = self.sampleCurve
+
+        frame = Frame(resampleFrame)
+        self.createButton(
+            frame, 'Resample', 'Make Even',
+            'Apply timewarp so resulting path has constant velocity',
+            self.makeEven, side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        self.createButton(
+            frame, 'Resample', 'Face Forward',
+            'Compute HPR so resulting hpr curve faces along xyz tangent',
+            self.faceForward, side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        frame.pack(fill = Tkinter.X, expand = 0)
+        resampleFrame.pack(fill = Tkinter.X, expand = 0, pady = 2)
+        
+        # Desample
+        desampleFrame = Frame(
+            self.resamplePage, relief = Tkinter.SUNKEN, borderwidth = 2)
+        Label(desampleFrame, text = 'DESAMPLE CURVE',
+              font=('MSSansSerif', 12, 'bold')).pack()
+        widget = self.createSlider(
+            desampleFrame, 'Resample', 'Points Between Samples',
+            'Specify number of points to skip between samples',
+            min = 1, max = 100, resolution = 1,
+            command = self.setDesampleFrequency)
+        widget.component('hull')['relief'] = Tkinter.RIDGE
+        widget['postCallback'] = self.desampleCurve
+        desampleFrame.pack(fill = Tkinter.X, expand = 0, pady = 2)
+
+        ## REFINE PAGE ##
+        refineFrame = Frame(self.refinePage, relief = Tkinter.SUNKEN,
+                            borderwidth = 2)
+        label = Label(refineFrame, text = 'REFINE CURVE',
+                      font=('MSSansSerif', 12, 'bold'))
+        label.pack(fill = Tkinter.X)
+
+        widget = self.createSlider(refineFrame,
+                                       'Refine Page', 'Refine From',
+                                       'Begin time of refine pass',
+                                       resolution = 0.01,
+                                       command = self.setRecordStart)
+        widget['preCallback'] = self.setRefineMode
+        widget['postCallback'] = lambda s = self: s.getPrePoints('Refine')
+        widget = self.createSlider(
+            refineFrame, 'Refine Page',
+            'Control Start',
+            'Time when full control of node path is given during refine pass',
+            resolution = 0.01,
+            command = self.setControlStart)
+        widget['preCallback'] = self.setRefineMode
+        widget = self.createSlider(
+            refineFrame, 'Refine Page',
+            'Control Stop',
+            'Time when node path begins transition back to original curve',
+            resolution = 0.01,
+            command = self.setControlStop)
+        widget['preCallback'] = self.setRefineMode
+        widget = self.createSlider(refineFrame, 'Refine Page', 'Refine To',
+                                       'Stop time of refine pass',
+                                       resolution = 0.01,
+                                       command = self.setRefineStop)
+        widget['preCallback'] = self.setRefineMode
+        widget['postCallback'] = self.getPostPoints
+        refineFrame.pack(fill = Tkinter.X)
+
+        ## EXTEND PAGE ##
+        extendFrame = Frame(self.extendPage, relief = Tkinter.SUNKEN,
+                            borderwidth = 2)
+        label = Label(extendFrame, text = 'EXTEND CURVE',
+                      font=('MSSansSerif', 12, 'bold'))
+        label.pack(fill = Tkinter.X)
+
+        widget = self.createSlider(extendFrame,
+                                       'Extend Page', 'Extend From',
+                                       'Begin time of extend pass',
+                                       resolution = 0.01,
+                                       command = self.setRecordStart)
+        widget['preCallback'] = self.setExtendMode
+        widget['postCallback'] = lambda s = self: s.getPrePoints('Extend')
+        widget = self.createSlider(
+            extendFrame, 'Extend Page',
+            'Control Start',
+            'Time when full control of node path is given during extend pass',
+            resolution = 0.01,
+            command = self.setControlStart)
+        widget['preCallback'] = self.setExtendMode
+        extendFrame.pack(fill = Tkinter.X)
+
+        ## CROP PAGE ##
+        cropFrame = Frame(self.cropPage, relief = Tkinter.SUNKEN,
+                            borderwidth = 2)
+        label = Label(cropFrame, text = 'CROP CURVE',
+                      font=('MSSansSerif', 12, 'bold'))
+        label.pack(fill = Tkinter.X)
+
+        widget = self.createSlider(
+            cropFrame,
+            'Crop Page', 'Crop From',
+            'Delete all curve points before this time',
+            resolution = 0.01,
+            command = self.setCropFrom)
+
+        widget = self.createSlider(
+            cropFrame,
+            'Crop Page', 'Crop To',
+            'Delete all curve points after this time',
+            resolution = 0.01,
+            command = self.setCropTo)
+
+        self.createButton(cropFrame, 'Crop Page', 'Crop Curve',
+                          'Crop curve to specified from to times',
+                          self.cropCurve, fill = Tkinter.NONE)
+        cropFrame.pack(fill = Tkinter.X)
+
+        ## DRAW PAGE ##
+        drawFrame = Frame(self.drawPage, relief = Tkinter.SUNKEN,
+                           borderwidth = 2)
+
+        self.sf = Pmw.ScrolledFrame(self.drawPage, horizflex = 'elastic')
+        self.sf.pack(fill = 'both', expand = 1)
+        sfFrame = self.sf.interior()
+
+        label = Label(sfFrame, text = 'CURVE RENDERING STYLE',
+                      font=('MSSansSerif', 12, 'bold'))
+        label.pack(fill = Tkinter.X)
+
+        frame = Frame(sfFrame)
+        Label(frame, text = 'SHOW:').pack(side = Tkinter.LEFT, expand = 0)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Path',
+            'On: path is visible', self.setPathVis, 1,
+            side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Knots',
+            'On: path knots are visible', self.setKnotVis, 1,
+            side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'CVs',
+            'On: path CVs are visible', self.setCvVis, 0,
+            side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Hull',
+            'On: path hull is visible', self.setHullVis, 0,
+            side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Trace',
+            'On: record is visible', self.setTraceVis, 0,
+            side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Marker',
+            'On: playback marker is visible', self.setMarkerVis, 0,
+            side = Tkinter.LEFT, fill = Tkinter.X, expand = 1)
+        frame.pack(fill = Tkinter.X, expand = 1)
+        # Sliders
+        widget = self.createSlider(
+            sfFrame, 'Style', 'Num Segs',
+            'Set number of segments used to approximate each parametric unit',
+            min = 1.0, max = 400, resolution = 1.0,
+            value = 40, 
+            command = self.setNumSegs, side = Tkinter.TOP)
+        widget.component('hull')['relief'] = Tkinter.RIDGE
+        widget = self.createSlider(
+            sfFrame, 'Style', 'Num Ticks',
+            'Set number of tick marks drawn for each unit of time',
+            min = 0.0, max = 10.0, resolution = 1.0,
+            value = 0.0,
+            command = self.setNumTicks, side = Tkinter.TOP)
+        widget.component('hull')['relief'] = Tkinter.RIDGE
+        widget = self.createSlider(
+            sfFrame, 'Style', 'Tick Scale',
+            'Set visible size of time tick marks',
+            min = 0.01, max = 100.0, resolution = 0.01,
+            value = 5.0,
+            command = self.setTickScale, side = Tkinter.TOP)
+        widget.component('hull')['relief'] = Tkinter.RIDGE
+        self.createColorEntry(
+            sfFrame, 'Style', 'Path Color',
+            'Color of curve',
+            command = self.setPathColor,
+            value = [255.0,255.0,255.0,255.0])
+        self.createColorEntry(
+            sfFrame, 'Style', 'Knot Color',
+            'Color of knots',
+            command = self.setKnotColor,
+            value = [0,0,255.0,255.0])
+        self.createColorEntry(
+            sfFrame, 'Style', 'CV Color',
+            'Color of CVs',
+            command = self.setCvColor,
+            value = [255.0,0,0,255.0])
+        self.createColorEntry(
+            sfFrame, 'Style', 'Tick Color',
+            'Color of Ticks',
+            command = self.setTickColor,
+            value = [255.0,0,0,255.0])
+        self.createColorEntry(
+            sfFrame, 'Style', 'Hull Color',
+            'Color of Hull',
+            command = self.setHullColor,
+            value = [255.0,128.0,128.0,255.0])
+
+        #drawFrame.pack(fill = Tkinter.X)
+
+        ## OPTIONS PAGE ##
+        optionsFrame = Frame(self.optionsPage, relief = Tkinter.SUNKEN,
+                            borderwidth = 2)
+        label = Label(optionsFrame, text = 'RECORDING OPTIONS',
+                      font=('MSSansSerif', 12, 'bold'))
+        label.pack(fill = Tkinter.X)
+        # Hooks
+        frame = Frame(optionsFrame)
+        widget = self.createLabeledEntry(
+            frame, 'Recording', 'Record Hook',
+            'Hook used to start/stop recording',
+            value = self.startStopHook,
+            command = self.setStartStopHook)[0]
+        label = self.getWidget('Recording', 'Record Hook-Label')
+        label.configure(width = 16, anchor = Tkinter.W)
+        self.setStartStopHook()
+        widget = self.createLabeledEntry(
+            frame, 'Recording', 'Keyframe Hook',
+            'Hook used to add a new keyframe',
+            value = self.keyframeHook,
+            command = self.setKeyframeHook)[0]
+        label = self.getWidget('Recording', 'Keyframe Hook-Label')
+        label.configure(width = 16, anchor = Tkinter.W)
+        self.setKeyframeHook()
+        frame.pack(expand = 1, fill = Tkinter.X)
+        # PreRecordFunc
+        frame = Frame(optionsFrame)
+        widget = self.createComboBox(
+            frame, 'Recording', 'Pre-Record Func',
+            'Function called before sampling each point',
+            PRF_UTILITIES, self.setPreRecordFunc,
+            history = 1, expand = 1)
+        widget.configure(label_width = 16, label_anchor = Tkinter.W)
+        widget.configure(entryfield_entry_state = 'normal')
+        # Initialize preRecordFunc
+        self.preRecordFunc = eval(PRF_UTILITIES[0])
+        self.createCheckbutton(frame, 'Recording', 'PRF Active',
+                               'On: Pre Record Func enabled',
+                               None, 0,
+                               side = Tkinter.LEFT, fill = Tkinter.BOTH, expand = 0)
+        frame.pack(expand = 1, fill = Tkinter.X)
+        # Pack record frame
+        optionsFrame.pack(fill = Tkinter.X, pady = 2)
+
+        self.mainNotebook.setnaturalsize()        
+        
+    def pushUndo(self, fResetRedo = 1):
+        SEditor.pushUndo([self.nodePath])
+
+    def undoHook(self):
+        # Reflect new changes
+        pass
+
+    def pushUndoHook(self):
+        # Make sure button is reactivated
+        self.undoButton.configure(state = 'normal')
+
+    def undoListEmptyHook(self):
+        # Make sure button is deactivated
+        self.undoButton.configure(state = 'disabled')
+
+    def pushRedo(self):
+        SEditor.pushRedo([self.nodePath])
+        
+    def redoHook(self):
+        # Reflect new changes
+        pass
+
+    def pushRedoHook(self):
+        # Make sure button is reactivated
+        self.redoButton.configure(state = 'normal')
+
+    def redoListEmptyHook(self):
+        # Make sure button is deactivated
+        self.redoButton.configure(state = 'disabled')
+        
+    def selectedNodePathHook(self, nodePath):
+        """
+        Hook called upon selection of a node path used to select playback
+        marker if subnode selected
+        """
+        taskMgr.remove(self.name + '-curveEditTask')
+        print nodePath.id()
+        if nodePath.id() in self.playbackMarkerIds:
+            SEditor.select(self.playbackMarker)
+        elif nodePath.id() in self.tangentMarkerIds:
+            SEditor.select(self.tangentMarker)
+        elif nodePath.id() == self.playbackMarker.id():
+            self.tangentGroup.show()
+            taskMgr.add(self.curveEditTask,
+                                     self.name + '-curveEditTask')
+        elif nodePath.id() == self.tangentMarker.id():
+            self.tangentGroup.show()
+            taskMgr.add(self.curveEditTask,
+                                     self.name + '-curveEditTask')
+        else:
+            self.tangentGroup.hide()
+
+    def getChildIds(self, nodePath):
+        ids = [nodePath.id()]
+        kids = nodePath.getChildren()
+        for kid in kids:
+            ids += self.getChildIds(kid)
+        return ids
+
+    def deselectedNodePathHook(self, nodePath):
+        """
+        Hook called upon deselection of a node path used to select playback
+        marker if subnode selected
+        """
+        if ((nodePath.id() == self.playbackMarker.id()) or
+            (nodePath.id() == self.tangentMarker.id())):
+            self.tangentGroup.hide()
+
+    def curveEditTask(self,state):
+        if self.curveCollection != None:
+            # Update curve position
+            if self.manipulandumId == self.playbackMarker.id():
+                # Show playback marker
+                self.playbackMarker.getChild(0).show()
+                pos = Point3(0)
+                hpr = Point3(0)
+                pos = self.playbackMarker.getPos(self.nodePathParent)
+                hpr = self.playbackMarker.getHpr(self.nodePathParent)
+                self.curveCollection.adjustXyz(
+                    self.playbackTime, VBase3(pos[0], pos[1], pos[2]))
+                self.curveCollection.adjustHpr(
+                    self.playbackTime, VBase3(hpr[0], hpr[1], hpr[2]))
+                # Note: this calls recompute on the curves
+                self.nurbsCurveDrawer.draw()
+            # Update tangent
+            if self.manipulandumId == self.tangentMarker.id():
+                # If manipulating marker, update tangent
+                # Hide playback marker
+                self.playbackMarker.getChild(0).hide()
+                # Where is tangent marker relative to playback marker
+                tan = self.tangentMarker.getPos()
+                # Transform this vector to curve space
+                tan2Curve = Vec3(
+                    self.playbackMarker.getMat(
+                    self.nodePathParent).xformVec(tan))
+                # Update nurbs curve
+                self.curveCollection.getXyzCurve().adjustTangent(
+                    self.playbackTime,
+                    tan2Curve[0], tan2Curve[1], tan2Curve[2])
+                # Note: this calls recompute on the curves
+                self.nurbsCurveDrawer.draw()
+            else:
+                # Show playback marker
+                self.playbackMarker.getChild(0).show()
+                # Update tangent marker line
+                tan = Point3(0)
+                self.curveCollection.getXyzCurve().getTangent(
+                    self.playbackTime, tan)
+                # Transform this point to playback marker space
+                tan.assign(
+                    self.nodePathParent.getMat(
+                    self.playbackMarker).xformVec(tan))
+                self.tangentMarker.setPos(tan)
+            # In either case update tangent line
+            self.tangentLines.setVertex(1, tan[0], tan[1], tan[2])
+        return Task.cont
+
+    def manipulateObjectStartHook(self):
+        self.manipulandumId = None
+        if SEditor.selected.last:
+            if SEditor.selected.last.id() == self.playbackMarker.id():
+                self.manipulandumId = self.playbackMarker.id()
+            elif SEditor.selected.last.id() == self.tangentMarker.id():
+                self.manipulandumId = self.tangentMarker.id()
+              
+    def manipulateObjectCleanupHook(self):
+        # Clear flag
+        self.manipulandumId = None
+            
+    def onDestroy(self, event):
+        # Remove hooks
+        for event, method in self.actionEvents:
+            self.ignore(event)
+        # remove start stop hook
+        self.ignore(self.startStopHook)
+        self.ignore(self.keyframeHook)
+        self.curveNodePath.reparentTo(self.recorderNodePath)
+        self.trace.reparentTo(self.recorderNodePath)
+        self.recorderNodePath.removeNode()
+        # Make sure markers are deselected
+        SEditor.deselect(self.playbackMarker)
+        SEditor.deselect(self.tangentMarker)
+        # Remove tasks
+        taskMgr.remove(self.name + '-recordTask')
+        taskMgr.remove(self.name + '-playbackTask')
+        taskMgr.remove(self.name + '-curveEditTask')
+        self.mopathRecorderNode.removeChildren()
+        self.mopathRecorderNode.removeNode()
+        messenger.send('mPath_close')
+        messenger.send('SGE_Update Explorer',[render])
+
+    def createNewPointSet(self, curveName = None):
+        if curveName == None:
+            self.pointSetName = self.name + '-ps-' + `self.pointSetCount`
+        else:
+            self.pointSetName = curveName
+        # Update dictionary and record pointer to new point set
+        self.pointSet = self.pointSetDict[self.pointSetName] = []
+        # Update combo box
+        comboBox = self.getWidget('Mopath', 'Path:')
+        scrolledList = comboBox.component('scrolledlist')
+        listbox = scrolledList.component('listbox')
+        names = list(listbox.get(0,'end'))
+        names.append(self.pointSetName)
+        scrolledList.setlist(names)
+        comboBox.selectitem(self.pointSetName)
+        # Update count
+        self.pointSetCount += 1
+
+    def extractPointSetFromCurveFitter(self, curveName = None):
+        # Get new point set based on newly created curve
+        self.createNewPointSet(curveName)
+        for i in range(self.curveFitter.getNumSamples()):
+            time = self.curveFitter.getSampleT(i)
+            pos = Point3(self.curveFitter.getSampleXyz(i))
+            hpr = Point3(self.curveFitter.getSampleHpr(i))
+            self.pointSet.append([time, pos, hpr])
+
+    def extractPointSetFromCurveCollection(self, curveName=None):
+        # Use curve to compute new point set
+        # Record maxT
+        self.maxT = self.curveCollection.getMaxT()
+        # Determine num samples
+        # Limit point set to 1000 points and samples per second to 30
+        samplesPerSegment = min(30.0, 1000.0/self.curveCollection.getMaxT())
+        self.setNumSamples(self.maxT * samplesPerSegment)
+        # Sample the curve but don't create a new curve collection
+        self.sampleCurve(fCompute = 0, curveName = curveName)
+        # Update widgets based on new data
+        self.updateWidgets()
+
+    def selectPointSetNamed(self, name):
+        self.pointSet = self.pointSetDict.get(name, None)
+        # Reload points into curve fitter
+        # Reset curve fitters
+        self.curveFitter.reset()
+        for time, pos, hpr in self.pointSet:
+            # Add it to the curve fitters
+            self.curveFitter.addXyzHpr(time, pos, hpr)
+        # Compute curve
+        self.computeCurves()
+
+    def setPathVis(self):
+        if self.getVariable('Style', 'Path').get():
+            self.curveNodePath.show()
+        else:
+            self.curveNodePath.hide()
+        
+    def setKnotVis(self):
+        self.nurbsCurveDrawer.setShowKnots(
+            self.getVariable('Style', 'Knots').get())
+
+    def setCvVis(self):
+        self.nurbsCurveDrawer.setShowCvs(
+            self.getVariable('Style', 'CVs').get())
+        
+    def setHullVis(self):
+        self.nurbsCurveDrawer.setShowHull(
+            self.getVariable('Style', 'Hull').get())
+        
+    def setTraceVis(self):
+        if self.getVariable('Style', 'Trace').get():
+            self.trace.show()
+        else:
+            self.trace.hide()
+
+    def setMarkerVis(self):
+        if self.getVariable('Style', 'Marker').get():
+            self.playbackMarker.reparentTo(self.recorderNodePath)
+        else:
+            self.playbackMarker.reparentTo(hidden)
+
+    def setNumSegs(self, value):
+        self.numSegs = int(value)
+        self.nurbsCurveDrawer.setNumSegs(self.numSegs)
+        
+    def setNumTicks(self, value):
+        self.nurbsCurveDrawer.setNumTicks(float(value))
+        
+    def setTickScale(self, value):
+        self.nurbsCurveDrawer.setTickScale(float(value))
+
+    def setPathColor(self, color):
+        self.nurbsCurveDrawer.setColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+        self.nurbsCurveDrawer.draw()
+
+    def setKnotColor(self, color):
+        self.nurbsCurveDrawer.setKnotColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
+    def setCvColor(self, color):
+        self.nurbsCurveDrawer.setCvColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
+    def setTickColor(self, color):
+        self.nurbsCurveDrawer.setTickColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
+    def setHullColor(self, color):
+        self.nurbsCurveDrawer.setHullColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
+    def setStartStopHook(self, event = None):
+        # Clear out old hook
+        self.ignore(self.startStopHook)
+        # Record new one
+        hook = self.getVariable('Recording', 'Record Hook').get()
+        self.startStopHook = hook
+        # Add new one
+        self.accept(self.startStopHook, self.toggleRecordVar)
+
+    def setKeyframeHook(self, event = None):
+        # Clear out old hook
+        self.ignore(self.keyframeHook)
+        # Record new one
+        hook = self.getVariable('Recording', 'Keyframe Hook').get()
+        self.keyframeHook = hook
+        # Add new one
+        self.accept(self.keyframeHook, self.addKeyframe)
+
+    def reset(self):
+        self.pointSet = []
+        self.hasPoints = 0
+        self.curveCollection = None
+        self.curveFitter.reset()
+        self.nurbsCurveDrawer.hide()
+        
+    def setSamplingMode(self, mode):
+        self.samplingMode = mode
+
+    def disableKeyframeButton(self):
+        self.getWidget('Recording', 'Add Keyframe')['state'] = 'disabled'
+    def enableKeyframeButton(self):
+        self.getWidget('Recording', 'Add Keyframe')['state'] = 'normal'
+
+    def setRecordingType(self, type):
+        self.recordingType.set(type)
+
+    def setNewCurveMode(self):
+        self.setRecordingType('New Curve')
+
+    def setRefineMode(self):
+        self.setRecordingType('Refine')
+        
+    def setExtendMode(self):
+        self.setRecordingType('Extend')
+
+    def toggleRecordVar(self):
+        # Get recording variable
+        v = self.getVariable('Recording', 'Record')
+        # Toggle it
+        v.set(1 - v.get())
+        # Call the command
+        self.toggleRecord()
+
+    def toggleRecord(self):
+        if self.getVariable('Recording', 'Record').get():
+            # Reparent a Marker to target nodePath to show where the recording start
+            self.markingNode = self.nodePath.getParent().attachNewNode('MopthMarkerNode')
+            self.nodePath.copyTo(self.markingNode)
+            self.markingNode.wrtReparentTo(render)
+            # Kill old tasks
+            taskMgr.remove(self.name + '-recordTask')
+            taskMgr.remove(self.name + '-curveEditTask')
+            # Remove old curve
+            self.nurbsCurveDrawer.hide()
+            # Reset curve fitters
+            self.curveFitter.reset()
+            # Update sampling mode button if necessary
+            if self.samplingMode == 'Continuous':
+                self.disableKeyframeButton()
+            # Create a new point set to hold raw data
+            self.createNewPointSet()
+            # Clear out old trace, get ready to draw new
+            self.initTrace()
+            # Keyframe mode?
+            if (self.samplingMode == 'Keyframe'):
+                # Record first point
+                self.lastPos.assign(Point3(
+                    self.nodePath.getPos(self.nodePathParent)))
+                # Init delta time
+                self.deltaTime = 0.0
+                # Record first point
+                self.recordPoint(self.recordStart)
+            # Everything else
+            else:
+                if ((self.recordingType.get() == 'Refine') or
+                    (self.recordingType.get() == 'Extend')):
+                    # Turn off looping playback
+                    self.loopPlayback = 0
+                    # Update widget to reflect new value
+                    self.getVariable('Playback', 'Loop').set(0)
+                    # Select tempCS as playback nodepath
+                    self.oldPlaybackNodePath = self.playbackNodePath
+                    self.setPlaybackNodePath(self.tempCS)
+                    # Parent record node path to temp
+                    self.nodePath.reparentTo(self.playbackNodePath)
+                    # Align with temp
+                    self.nodePath.setPosHpr(0,0,0,0,0,0)
+                    # Set playback start to self.recordStart
+                    self.playbackGoTo(self.recordStart)
+                    # start flying nodePath along path
+                    self.startPlayback()
+                # Start new task
+                t = taskMgr.add(
+                    self.recordTask, self.name + '-recordTask')
+                t.startTime = globalClock.getFrameTime()
+        else:
+            self.markingNode.removeNode() # Hide the marker in the end of recording 
+            if self.samplingMode == 'Continuous':
+                # Kill old task
+                taskMgr.remove(self.name + '-recordTask')
+                if ((self.recordingType.get() == 'Refine') or
+                    (self.recordingType.get() == 'Extend')):
+                    # Reparent node path back to parent
+                    self.nodePath.wrtReparentTo(self.nodePathParent)
+                    # Restore playback Node Path
+                    self.setPlaybackNodePath(self.oldPlaybackNodePath)
+            else:
+                # Add last point
+                self.addKeyframe(0)
+            # Reset sampling mode
+            self.setSamplingMode('Continuous')
+            self.enableKeyframeButton()
+            # Clean up after refine or extend
+            if ((self.recordingType.get() == 'Refine') or
+                (self.recordingType.get() == 'Extend')):
+                # Merge prePoints, pointSet, postPoints
+                self.mergePoints()
+                # Clear out pre and post list
+                self.prePoints = []
+                self.postPoints = []
+                # Reset recording mode
+                self.setNewCurveMode()
+            # Compute curve
+            self.computeCurves()
+            
+    def recordTask(self, state):
+        # Record raw data point
+        time = self.recordStart + (
+            globalClock.getFrameTime() - state.startTime)
+        self.recordPoint(time)
+        return Task.cont
+
+    def addKeyframe(self, fToggleRecord = 1):
+        # Make sure we're in a recording mode!
+        if (fToggleRecord and
+            (not self.getVariable('Recording', 'Record').get())):
+            # Set sampling mode
+            self.setSamplingMode('Keyframe')
+            # This will automatically add the first point
+            self.toggleRecordVar()
+        else:
+            # Use distance as a time
+            pos = self.nodePath.getPos(self.nodePathParent)
+            deltaPos = Vec3(pos - self.lastPos).length()
+            if deltaPos != 0:
+                # If we've moved at all, use delta Pos as time
+                self.deltaTime = self.deltaTime + deltaPos
+            else:
+                # Otherwise add one second
+                self.deltaTime = self.deltaTime + 1.0
+            # Record point at new time
+            self.recordPoint(self.recordStart + self.deltaTime)
+            # Update last pos
+            self.lastPos.assign(pos)
+
+    def easeInOut(self, t):
+        x = t * t
+        return (3 * x) - (2 * t * x)
+
+    def setPreRecordFunc(self, func):
+        # Note: If func is one defined at command prompt, need to set
+        # __builtins__.func = func at command line
+        self.preRecordFunc = eval(func)
+        # Update widget to reflect new value
+        self.getVariable('Recording', 'PRF Active').set(1)
+
+    def recordPoint(self, time):
+        # Call user define callback before recording point
+        if (self.getVariable('Recording', 'PRF Active').get() and
+            (self.preRecordFunc != None)):
+            self.preRecordFunc()
+        # Get point
+        pos = self.nodePath.getPos(self.nodePathParent)
+        hpr = self.nodePath.getHpr(self.nodePathParent)
+        qNP = Quat()
+        qNP.setHpr(hpr)
+        # Blend between recordNodePath and self.nodePath
+        if ((self.recordingType.get() == 'Refine') or
+            (self.recordingType.get() == 'Extend')):
+            if ((time < self.controlStart) and
+                ((self.controlStart - self.recordStart) != 0.0)):
+                rPos = self.playbackNodePath.getPos(self.nodePathParent)
+                rHpr = self.playbackNodePath.getHpr(self.nodePathParent)
+                qR = Quat()
+                qR.setHpr(rHpr)
+                t = self.easeInOut(((time - self.recordStart)/
+                                    (self.controlStart - self.recordStart)))
+                # Transition between the recorded node path and the driven one
+                pos = (rPos * (1 - t)) + (pos * t)
+                q = qSlerp(qR, qNP, t)
+                hpr.assign(q.getHpr())
+            elif ((self.recordingType.get() == 'Refine') and
+                  (time > self.controlStop) and
+                  ((self.recordStop - self.controlStop) != 0.0)):
+                rPos = self.playbackNodePath.getPos(self.nodePathParent)
+                rHpr = self.playbackNodePath.getHpr(self.nodePathParent)
+                qR = Quat()
+                qR.setHpr(rHpr)
+                t = self.easeInOut(((time - self.controlStop)/
+                                    (self.recordStop - self.controlStop)))
+                # Transition between the recorded node path and the driven one
+                pos = (pos * (1 - t)) + (rPos * t)
+                q = qSlerp(qNP, qR, t)
+                hpr.assign(q.getHpr())
+        # Add it to the point set
+        self.pointSet.append([time, pos, hpr])
+        # Add it to the curve fitters
+        self.curveFitter.addXyzHpr(time, pos, hpr)
+        # Update trace now if recording keyframes
+        if (self.samplingMode == 'Keyframe'):
+            self.trace.reset()
+            for t, p, h in self.pointSet:
+                self.trace.drawTo(p[0], p[1], p[2])
+            self.trace.create()
+
+    def computeCurves(self):
+        # Check to make sure curve fitters have points
+        if (self.curveFitter.getNumSamples() == 0):
+            print 'MopathRecorder.computeCurves: Must define curve first'
+            return
+        # Create curves
+        # XYZ
+        self.curveFitter.sortPoints()
+        self.curveFitter.wrapHpr()
+        self.curveFitter.computeTangents(1)
+        # This is really a collection
+        self.curveCollection = self.curveFitter.makeNurbs()
+        self.nurbsCurveDrawer.setCurves(self.curveCollection)
+        self.nurbsCurveDrawer.draw()
+        # Update widget based on new curve
+        self.updateWidgets()
+
+    def initTrace(self):
+        self.trace.reset()
+        # Put trace line segs under node path's parent
+        self.trace.reparentTo(self.nodePathParent)
+        # Show it
+        self.trace.show()
+
+    def updateWidgets(self):
+        if not self.curveCollection:
+            return
+        self.fAdjustingValues = 1
+        # Widgets depending on max T
+        maxT = self.curveCollection.getMaxT()
+        maxT_text = '%0.2f' % maxT
+        # Playback controls
+        self.getWidget('Playback', 'Time').configure(max = maxT_text)
+        self.getVariable('Resample', 'Path Duration').set(maxT_text)
+        # Refine widgets
+        widget = self.getWidget('Refine Page', 'Refine From')
+        widget.configure(max = maxT)
+        widget.set(0.0)
+        widget = self.getWidget('Refine Page', 'Control Start')
+        widget.configure(max = maxT)
+        widget.set(0.0)
+        widget = self.getWidget('Refine Page', 'Control Stop')
+        widget.configure(max = maxT)
+        widget.set(float(maxT))
+        widget = self.getWidget('Refine Page', 'Refine To')
+        widget.configure(max = maxT)
+        widget.set(float(maxT))
+        # Extend widgets
+        widget = self.getWidget('Extend Page', 'Extend From')
+        widget.configure(max = maxT)
+        widget.set(float(0.0))
+        widget = self.getWidget('Extend Page', 'Control Start')
+        widget.configure(max = maxT)
+        widget.set(float(0.0))
+        # Crop widgets
+        widget = self.getWidget('Crop Page', 'Crop From')
+        widget.configure(max = maxT)
+        widget.set(float(0.0))
+        widget = self.getWidget('Crop Page', 'Crop To')
+        widget.configure(max = maxT)
+        widget.set(float(maxT))
+        self.maxT = float(maxT)
+        # Widgets depending on number of samples
+        numSamples = self.curveFitter.getNumSamples()
+        widget = self.getWidget('Resample', 'Points Between Samples')
+        widget.configure(max=numSamples)
+        widget = self.getWidget('Resample', 'Num. Samples')
+        widget.configure(max = 4 * numSamples)
+        widget.set(numSamples, 0)
+        self.fAdjustingValues = 0
+
+    def selectNodePathNamed(self, name):
+        nodePath = None
+        if name == 'init':
+            nodePath = self.nodePath
+            # Add Combo box entry for the initial node path
+            self.addNodePath(nodePath)
+        elif name == 'selected':
+            nodePath = SEditor.selected.last
+            # Add Combo box entry for this selected object
+            self.addNodePath(nodePath)
+        else:
+            nodePath = self.nodePathDict.get(name, None)
+            if (nodePath == None):
+                # See if this evaluates into a node path
+                try:
+                    nodePath = eval(name)
+                    if isinstance(nodePath, NodePath):
+                        self.addNodePath(nodePath)
+                    else:
+                        # Good eval but not a node path, give up
+                        nodePath = None
+                except:
+                    # Bogus eval
+                    nodePath = None
+                    # Clear bogus entry from listbox
+                    listbox = self.nodePathMenu.component('scrolledlist')
+                    listbox.setlist(self.nodePathNames)
+            else:
+                if name == 'widget':
+                    # Record relationship between selected nodes and widget
+                    SEditor.selected.getWrtAll()
+                if name == 'marker':
+                    self.playbackMarker.show()
+                    # Initialize tangent marker position
+                    tan = Point3(0)
+                    if self.curveCollection != None:
+                        self.curveCollection.getXyzCurve().getTangent(
+                            self.playbackTime, tan)
+                    self.tangentMarker.setPos(tan)
+                else:
+                    self.playbackMarker.hide()
+        # Update active node path
+        self.setNodePath(nodePath)
+        messenger.send('mPath_requestCurveList',[nodePath,self.name])
+        self.accept('curveListFor'+self.name, self.addCurvesFromNodepath)
+
+
+    def setNodePath(self, nodePath):
+        self.playbackNodePath = self.nodePath = nodePath
+        if self.nodePath:
+            # Record nopath's parent
+            self.nodePathParent = self.nodePath.getParent()
+            # Put curve drawer under record node path's parent
+            self.curveNodePath.reparentTo(self.nodePathParent)
+            # Set entry color
+            self.nodePathMenuEntry.configure(
+                background = self.nodePathMenuBG)
+        else:
+            # Flash entry
+            self.nodePathMenuEntry.configure(background = 'Pink')
+
+    def setPlaybackNodePath(self, nodePath):
+        self.playbackNodePath = nodePath
+
+    def addNodePath(self, nodePath):
+        self.addNodePathToDict(nodePath, self.nodePathNames,
+                               self.nodePathMenu, self.nodePathDict)
+
+    def addNodePathToDict(self, nodePath, names, menu, dict):
+        if not nodePath:
+            return
+        # Get node path's name
+        name = nodePath.getName()
+        if name in ['mopathRecorderTempCS', 'widget', 'camera', 'marker']:
+            dictName = name
+        else:
+            # Generate a unique name for the dict
+            dictName = name # + '-' + `nodePath.id()`
+        if not dict.has_key(dictName):
+            # Update combo box to include new item
+            names.append(dictName)
+            listbox = menu.component('scrolledlist')
+            listbox.setlist(names)
+            # Add new item to dictionary
+            dict[dictName] = nodePath
+        menu.selectitem(dictName)
+
+    def setLoopPlayback(self):
+        self.loopPlayback = self.getVariable('Playback', 'Loop').get()
+
+    def playbackGoTo(self, time):
+        if self.curveCollection == None:
+            return
+        self.playbackTime = CLAMP(time, 0.0, self.maxT)
+        if self.curveCollection != None:
+            pos = Point3(0)
+            hpr = Point3(0)
+            self.curveCollection.evaluate(self.playbackTime, pos, hpr)
+            self.playbackNodePath.setPosHpr(self.nodePathParent, pos, hpr)
+
+    def startPlayback(self):
+        if self.curveCollection == None:
+            return
+        # Kill any existing tasks
+        self.stopPlayback()
+        # Make sure checkbutton is set
+        self.getVariable('Playback', 'Play').set(1)
+        # Start new playback task
+        t = taskMgr.add(
+            self.playbackTask, self.name + '-playbackTask')
+        t.currentTime = self.playbackTime
+        t.lastTime = globalClock.getFrameTime()
+
+    def setSpeedScale(self, value):
+        self.speedScale.set(math.log10(value))
+
+    def setPlaybackSF(self, value):
+        self.playbackSF = pow(10.0, float(value))
+        self.speedVar.set('%0.2f' % self.playbackSF)
+        
+    def playbackTask(self, state):
+        time = globalClock.getFrameTime()
+        dTime = self.playbackSF * (time - state.lastTime)
+        state.lastTime = time
+        if self.loopPlayback:
+            cTime = (state.currentTime + dTime) % self.maxT
+        else:
+            cTime = state.currentTime + dTime
+        # Stop task if not looping and at end of curve
+        # Or if refining curve and past recordStop
+        if ((self.recordingType.get() == 'Refine') and
+              (cTime > self.recordStop)):
+            # Go to recordStop
+            self.getWidget('Playback', 'Time').set(self.recordStop)
+            # Then stop playback
+            self.stopPlayback()
+            # Also kill record task
+            self.toggleRecordVar()
+            return Task.done
+        elif ((self.loopPlayback == 0) and (cTime > self.maxT)):
+            # Go to maxT
+            self.getWidget('Playback', 'Time').set(self.maxT)
+            # Then stop playback
+            self.stopPlayback()
+            return Task.done
+        elif ((self.recordingType.get() == 'Extend') and
+              (cTime > self.controlStart)):
+            # Go to final point
+            self.getWidget('Playback', 'Time').set(self.controlStart)
+            # Stop playback
+            self.stopPlayback()
+            return Task.done
+        # Otherwise go to specified time and continue
+        self.getWidget('Playback', 'Time').set(cTime)
+        state.currentTime = cTime
+        return Task.cont
+
+    def stopPlayback(self):
+        self.getVariable('Playback', 'Play').set(0)
+        taskMgr.remove(self.name + '-playbackTask')
+
+    def jumpToStartOfPlayback(self):
+        self.stopPlayback()
+        self.getWidget('Playback', 'Time').set(0.0)
+
+    def jumpToEndOfPlayback(self):
+        self.stopPlayback()
+        if self.curveCollection != None:
+            self.getWidget('Playback', 'Time').set(self.maxT)
+
+    def startStopPlayback(self):
+        if self.getVariable('Playback', 'Play').get():
+            self.startPlayback()
+        else:
+            self.stopPlayback()
+
+    def setDesampleFrequency(self, frequency):
+        self.desampleFrequency = frequency
+        
+    def desampleCurve(self):
+        if (self.curveFitter.getNumSamples() == 0):
+            print 'MopathRecorder.desampleCurve: Must define curve first'
+            return
+        # NOTE: This is destructive, points will be deleted from curve fitter
+        self.curveFitter.desample(self.desampleFrequency)
+        # Compute new curve based on desampled data
+        self.computeCurves()
+        # Get point set from the curve fitter
+        self.extractPointSetFromCurveFitter()
+
+    def setNumSamples(self, numSamples):
+        self.numSamples = int(numSamples)
+        
+    def sampleCurve(self, fCompute = 1, curveName = None):
+        if self.curveCollection == None:
+            print 'MopathRecorder.sampleCurve: Must define curve first'
+            return
+        # Reset curve fitters
+        self.curveFitter.reset()
+        # Sample curve using specified number of samples
+        self.curveFitter.sample(self.curveCollection, self.numSamples)
+        if fCompute:
+            # Now recompute curves
+            self.computeCurves()
+        # Get point set from the curve fitter
+        self.extractPointSetFromCurveFitter(curveName)
+
+
+
+    def makeEven(self):
+        # Note: segments_per_unit = 2 seems to give a good fit
+        self.curveCollection.makeEven(self.maxT, 2)
+        # Get point set from curve
+        self.extractPointSetFromCurveCollection()
+
+    def faceForward(self):
+        # Note: segments_per_unit = 2 seems to give a good fit
+        self.curveCollection.faceForward(2)
+        # Get point set from curve
+        self.extractPointSetFromCurveCollection()
+
+    def setPathDuration(self, event):
+        newMaxT = float(self.getWidget('Resample', 'Path Duration').get())
+        self.setPathDurationTo(newMaxT)
+        
+    def setPathDurationTo(self, newMaxT):
+        # Compute scale factor
+        sf = newMaxT/self.maxT
+        # Scale curve collection
+        self.curveCollection.resetMaxT(newMaxT)
+        # Scale point set
+        # Save handle to old point set
+        oldPointSet = self.pointSet
+        # Create new point set
+        self.createNewPointSet()
+        # Reset curve fitters
+        self.curveFitter.reset()
+        # Now scale values
+        for time, pos, hpr in oldPointSet:
+            newTime = time * sf
+            # Update point set
+            self.pointSet.append([newTime, Point3(pos), Point3(hpr)])
+            # Add it to the curve fitters
+            self.curveFitter.addXyzHpr(newTime, pos, hpr)
+        # Update widgets
+        self.updateWidgets()
+        # Compute curve
+        #self.computeCurves()
+
+    def setRecordStart(self,value):
+        self.recordStart = value
+        # Someone else is adjusting values, let them take care of it
+        if self.fAdjustingValues:
+            return
+        self.fAdjustingValues = 1
+        # Adjust refine widgets
+        # Make sure we're in sync
+        self.getWidget('Refine Page', 'Refine From').set(
+            self.recordStart)
+        self.getWidget('Extend Page', 'Extend From').set(
+            self.recordStart)
+        # Check bounds
+        if self.recordStart > self.controlStart:
+            self.getWidget('Refine Page', 'Control Start').set(
+                self.recordStart)
+            self.getWidget('Extend Page', 'Control Start').set(
+                self.recordStart)
+        if self.recordStart > self.controlStop:
+            self.getWidget('Refine Page', 'Control Stop').set(
+                self.recordStart)
+        if self.recordStart > self.recordStop:
+            self.getWidget('Refine Page', 'Refine To').set(self.recordStart)
+        # Move playback node path to specified time
+        self.getWidget('Playback', 'Time').set(value)
+        self.fAdjustingValues = 0
+
+    def getPrePoints(self, type = 'Refine'):
+        # Switch to appropriate recording type
+        self.setRecordingType(type)
+        # Reset prePoints
+        self.prePoints = []
+        # See if we need to save any points before recordStart
+        for i in range(len(self.pointSet)):
+            # Have we passed recordStart?
+            if self.recordStart < self.pointSet[i][0]:
+                # Get a copy of the points prior to recordStart
+                self.prePoints = self.pointSet[:i-1]
+                break
+
+    def setControlStart(self, value):
+        self.controlStart = value
+        # Someone else is adjusting values, let them take care of it
+        if self.fAdjustingValues:
+            return
+        self.fAdjustingValues = 1
+        # Adjust refine widgets
+        # Make sure both pages are in sync
+        self.getWidget('Refine Page', 'Control Start').set(
+            self.controlStart)
+        self.getWidget('Extend Page', 'Control Start').set(
+            self.controlStart)
+        # Check bounds on other widgets
+        if self.controlStart < self.recordStart:
+            self.getWidget('Refine Page', 'Refine From').set(
+                self.controlStart)
+            self.getWidget('Extend Page', 'Extend From').set(
+                self.controlStart)
+        if self.controlStart > self.controlStop:
+            self.getWidget('Refine Page', 'Control Stop').set(
+                self.controlStart)
+        if self.controlStart > self.recordStop:
+            self.getWidget('Refine Page', 'Refine To').set(
+                self.controlStart)
+        # Move playback node path to specified time
+        self.getWidget('Playback', 'Time').set(value)
+        self.fAdjustingValues = 0
+
+    def setControlStop(self, value):
+        self.controlStop = value
+        # Someone else is adjusting values, let them take care of it
+        if self.fAdjustingValues:
+            return
+        self.fAdjustingValues = 1
+        if self.controlStop < self.recordStart:
+            self.getWidget('Refine Page', 'Refine From').set(
+                self.controlStop)
+        if self.controlStop < self.controlStart:
+            self.getWidget('Refine Page', 'Control Start').set(
+                self.controlStop)
+        if self.controlStop > self.recordStop:
+            self.getWidget('Refine Page', 'Refine To').set(
+                self.controlStop)
+        # Move playback node path to specified time
+        self.getWidget('Playback', 'Time').set(value)
+        self.fAdjustingValues = 0
+
+    def setRefineStop(self, value):
+        self.recordStop = value
+        # Someone else is adjusting values, let them take care of it
+        if self.fAdjustingValues:
+            return
+        self.fAdjustingValues = 1
+        if self.recordStop < self.recordStart:
+            self.getWidget('Refine Page', 'Refine From').set(
+                self.recordStop)
+        if self.recordStop < self.controlStart:
+            self.getWidget('Refine Page', 'Control Start').set(
+                self.recordStop)
+        if self.recordStop < self.controlStop:
+            self.getWidget('Refine Page', 'Control Stop').set(
+                self.recordStop)
+        # Move playback node path to specified time
+        self.getWidget('Playback', 'Time').set(value)
+        self.fAdjustingValues = 0
+
+    def getPostPoints(self):
+        # Set flag so we know to do a refine pass
+        self.setRefineMode()
+        # Reset postPoints
+        self.postPoints = []
+        # See if we need to save any points after recordStop
+        for i in range(len(self.pointSet)):
+            # Have we reached recordStop?
+            if self.recordStop < self.pointSet[i][0]:
+                # Get a copy of the points after recordStop
+                self.postPoints = self.pointSet[i:]
+                break
+
+    def mergePoints(self):
+        # prepend pre points
+        self.pointSet[0:0] = self.prePoints
+        for time, pos, hpr in self.prePoints:
+            # Add it to the curve fitters
+            self.curveFitter.addXyzHpr(time, pos, hpr)
+        # And post points
+        # What is end time of pointSet?
+        endTime = self.pointSet[-1][0]
+        for time, pos, hpr in self.postPoints:
+            adjustedTime = endTime + (time - self.recordStop)
+            # Add it to point set
+            self.pointSet.append([adjustedTime, pos, hpr])
+            # Add it to the curve fitters
+            self.curveFitter.addXyzHpr(adjustedTime, pos, hpr)
+
+    def setCropFrom(self,value):
+        self.cropFrom = value
+        # Someone else is adjusting values, let them take care of it
+        if self.fAdjustingValues:
+            return
+        self.fAdjustingValues = 1
+        if self.cropFrom > self.cropTo:
+            self.getWidget('Crop Page', 'Crop To').set(
+                self.cropFrom)
+        # Move playback node path to specified time
+        self.getWidget('Playback', 'Time').set(value)
+        self.fAdjustingValues = 0
+
+    def setCropTo(self,value):
+        self.cropTo = value
+        # Someone else is adjusting values, let them take care of it
+        if self.fAdjustingValues:
+            return
+        self.fAdjustingValues = 1
+        if self.cropTo < self.cropFrom:
+            self.getWidget('Crop Page', 'Crop From').set(
+                self.cropTo)
+        # Move playback node path to specified time
+        self.getWidget('Playback', 'Time').set(value)
+        self.fAdjustingValues = 0
+
+    def cropCurve(self):
+        if self.pointSet == None:
+            print 'Empty Point Set'
+            return
+        # Keep handle on old points
+        oldPoints = self.pointSet
+        # Create new point set
+        self.createNewPointSet()
+        # Copy over points between from/to
+        # Reset curve fitters
+        self.curveFitter.reset()
+        # Add start point
+        pos = Point3(0)
+        hpr = Point3(0)
+        self.curveCollection.evaluate(self.cropFrom, pos, hpr)
+        self.curveFitter.addXyzHpr(0.0, pos, hpr)
+        # Get points within bounds
+        for time, pos, hpr in oldPoints:
+            # Is it within the time?
+            if ((time > self.cropFrom) and
+                (time < self.cropTo)):
+                # Add it to the curve fitters
+                t = time - self.cropFrom
+                self.curveFitter.addXyzHpr(t, pos, hpr)
+                # And the point set
+                self.pointSet.append([t, pos, hpr])
+        # Add last point
+        pos = Vec3(0)
+        hpr = Vec3(0)
+        self.curveCollection.evaluate(self.cropTo, pos, hpr)
+        self.curveFitter.addXyzHpr(self.cropTo - self.cropFrom, pos, hpr)
+        # Compute curve
+        self.computeCurves()
+
+    def loadCurveFromFile(self):
+        # Use first directory in model path
+        mPath = getModelPath()
+        if mPath.getNumDirectories() > 0:
+            if `mPath.getDirectory(0)` == '.':
+                path = '.'
+            else:
+                path = mPath.getDirectory(0).toOsSpecific()
+        else:
+            path = '.'
+        if not os.path.isdir(path):
+            print 'MopathRecorder Info: Empty Model Path!'
+            print 'Using current directory'
+            path = '.'
+        mopathFilename = askopenfilename(
+            defaultextension = '.egg',
+            filetypes = (('Egg Files', '*.egg'),
+                         ('Bam Files', '*.bam'),
+                         ('All files', '*')),
+            initialdir = path,
+            title = 'Load Nurbs Curve',
+            parent = self.parent)
+        if mopathFilename:
+            self.reset()
+            nodePath = loader.loadModel(
+                Filename.fromOsSpecific(mopathFilename))
+            self.curveCollection = ParametricCurveCollection()
+            # MRM: Add error check
+            self.curveCollection.addCurves(nodePath.node())
+            nodePath.removeNode()
+            if self.curveCollection:
+                # Draw the curve
+                self.nurbsCurveDrawer.setCurves(self.curveCollection)
+                self.nurbsCurveDrawer.draw()
+                # Save a pointset for this curve
+                self.extractPointSetFromCurveCollection()
+            else:
+                self.reset()
+
+    def saveCurveToFile(self):
+        # Use first directory in model path
+        mPath = getModelPath()
+        if mPath.getNumDirectories() > 0:
+            if `mPath.getDirectory(0)` == '.':
+                path = '.'
+            else:
+                path = mPath.getDirectory(0).toOsSpecific()
+        else:
+            path = '.'
+        if not os.path.isdir(path):
+            print 'MopathRecorder Info: Empty Model Path!'
+            print 'Using current directory'
+            path = '.'
+        mopathFilename = asksaveasfilename(
+            defaultextension = '.egg',
+            filetypes = (('Egg Files', '*.egg'),
+                         ('Bam Files', '*.bam'),
+                         ('All files', '*')),
+            initialdir = path,
+            title = 'Save Nurbs Curve as',
+            parent = self.parent)
+        if mopathFilename:
+            self.curveCollection.writeEgg(Filename(mopathFilename))
+
+    def followTerrain(self, height = 1.0):
+        self.iRay.rayCollisionNodePath.reparentTo(self.nodePath)
+        entry = self.iRay.pickGeom3D()
+        if entry:
+            fromNodePath = entry.getFromNodePath()
+            hitPtDist = Vec3(entry.getSurfacePoint(fromNodePath))
+            self.nodePath.setZ(self.nodePath, height - hitPtDist)
+        self.iRay.rayCollisionNodePath.reparentTo(self.recorderNodePath)
+
+    ## WIDGET UTILITY FUNCTIONS ##
+    def addWidget(self, widget, category, text):
+        self.widgetDict[category + '-' + text] = widget
+        
+    def getWidget(self, category, text):
+        return self.widgetDict[category + '-' + text]
+
+    def getVariable(self, category, text):
+        return self.variableDict[category + '-' + text]
+
+    def createLabeledEntry(self, parent, category, text, balloonHelp,
+                           value = '', command = None,
+                           relief = 'sunken', side = Tkinter.LEFT,
+                           expand = 1, width = 12):
+        frame = Frame(parent)
+        variable = StringVar()
+        variable.set(value)
+        label = Label(frame, text = text)
+        label.pack(side = Tkinter.LEFT, fill = Tkinter.X)
+        self.bind(label, balloonHelp)
+        self.widgetDict[category + '-' + text + '-Label'] = label
+        entry = Entry(frame, width = width, relief = relief,
+                      textvariable = variable)
+        entry.pack(side = Tkinter.LEFT, fill = Tkinter.X, expand = expand)
+        self.bind(entry, balloonHelp)
+        self.widgetDict[category + '-' + text] = entry
+        self.variableDict[category + '-' + text] = variable
+        if command:
+            entry.bind('<Return>', command)
+        frame.pack(side = side, fill = Tkinter.X, expand = expand)
+        return (frame, label, entry)
+
+    def createButton(self, parent, category, text, balloonHelp, command,
+                     side = 'top', expand = 0, fill = Tkinter.X):
+        widget = Button(parent, text = text)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(side = side, fill = fill, expand = expand)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+        
+    def createCheckbutton(self, parent, category, text,
+                          balloonHelp, command, initialState,
+                          side = 'top', fill = Tkinter.X, expand = 0):
+        bool = BooleanVar()
+        bool.set(initialState)
+        widget = Checkbutton(parent, text = text, anchor = Tkinter.W,
+                         variable = bool)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(side = side, fill = fill, expand = expand)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        self.variableDict[category + '-' + text] = bool
+        return widget
+        
+    def createRadiobutton(self, parent, side, category, text,
+                          balloonHelp, variable, value,
+                          command = None, fill = Tkinter.X, expand = 0):
+        widget = Radiobutton(parent, text = text, anchor = Tkinter.W,
+                             variable = variable, value = value)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(side = side, fill = fill, expand = expand)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+        
+    def createFloater(self, parent, category, text, balloonHelp,
+                      command = None, min = 0.0, resolution = None,
+                      maxVelocity = 10.0, **kw):
+        kw['text'] = text
+        kw['min'] = min
+        kw['maxVelocity'] = maxVelocity
+        kw['resolution'] = resolution
+        widget = apply(Floater, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = Tkinter.X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createAngleDial(self, parent, category, text, balloonHelp,
+                        command = None, **kw):
+        kw['text'] = text
+        widget = apply(AngleDial,(parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = Tkinter.X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createSlider(self, parent, category, text, balloonHelp,
+                         command = None, min = 0.0, max = 1.0,
+                         resolution = None,
+                         side = Tkinter.TOP, fill = Tkinter.X, expand = 1, **kw):
+        kw['text'] = text
+        kw['min'] = min
+        kw['max'] = max
+        kw['resolution'] = resolution
+        #widget = apply(EntryScale, (parent,), kw)
+        widget = apply(Slider, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(side = side, fill = fill, expand = expand)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createEntryScale(self, parent, category, text, balloonHelp,
+                         command = None, min = 0.0, max = 1.0,
+                         resolution = None,
+                         side = Tkinter.TOP, fill = Tkinter.X, expand = 1, **kw):
+        kw['text'] = text
+        kw['min'] = min
+        kw['max'] = max
+        kw['resolution'] = resolution
+        widget = apply(EntryScale, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(side = side, fill = fill, expand = expand)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createVector2Entry(self, parent, category, text, balloonHelp,
+                           command = None, **kw):
+        # Set label's text
+        kw['text'] = text
+        widget = apply(Vector2Entry, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = Tkinter.X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createVector3Entry(self, parent, category, text, balloonHelp,
+                           command = None, **kw):
+        # Set label's text
+        kw['text'] = text
+        widget = apply(Vector3Entry, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = Tkinter.X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createColorEntry(self, parent, category, text, balloonHelp,
+                         command = None, **kw):
+        # Set label's text
+        kw['text'] = text
+        widget = apply(ColorEntry, (parent,) ,kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = Tkinter.X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createOptionMenu(self, parent, category, text, balloonHelp,
+                         items, command):
+        optionVar = StringVar()
+        if len(items) > 0:
+            optionVar.set(items[0])
+        widget = Pmw.OptionMenu(parent, labelpos = Tkinter.W, label_text = text,
+                                label_width = 12, menu_tearoff = 1,
+                                menubutton_textvariable = optionVar,
+                                items = items)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = Tkinter.X)
+        self.bind(widget.component('menubutton'), balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        self.variableDict[category + '-' + text] = optionVar
+        return optionVar
+
+    def createComboBox(self, parent, category, text, balloonHelp,
+                       items, command, history = 0,
+                       side = Tkinter.LEFT, expand = 0, fill = Tkinter.X):
+        widget = Pmw.ComboBox(parent,
+                              labelpos = Tkinter.W,
+                              label_text = text,
+                              label_anchor = 'e',
+                              label_width = 12,
+                              entry_width = 16,
+                              history = history,
+                              scrolledlist_items = items)
+        # Don't allow user to edit entryfield
+        widget.configure(entryfield_entry_state = 'disabled')
+        # Select first item if it exists
+        if len(items) > 0:
+            widget.selectitem(items[0])
+        # Bind selection command
+        widget['selectioncommand'] = command
+        widget.pack(side = side, fill = fill, expand = expand)
+        # Bind help
+        self.bind(widget, balloonHelp)
+        # Record widget
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def makeCameraWindow(self):
+        # First, we need to make a new layer on the window.
+        chan = base.win.getChannel(0)
+        self.cLayer = chan.makeLayer(1)
+        self.layerIndex = 1
+        self.cDr = self.cLayer.makeDisplayRegion(0.6, 1.0, 0, 0.4)
+        self.cDr.setClearDepthActive(1)
+        self.cDr.setClearColorActive(1)
+        self.cDr.setClearColor(Vec4(0))
+
+        # It gets its own camera
+        self.cCamera = render.attachNewNode('cCamera')
+        self.cCamNode = Camera('cCam')
+        self.cLens = PerspectiveLens()
+        self.cLens.setFov(40,40)
+        self.cLens.setNear(0.1)
+        self.cLens.setFar(100.0)
+        self.cCamNode.setLens(self.cLens)
+        self.cCamNode.setScene(render)
+        self.cCam = self.cCamera.attachNewNode(self.cCamNode)
+        
+        self.cDr.setCamera(self.cCam)
+
+    def toggleWidgetVis(self):
+        ## In order to make sure everything is going on right way...
+        messenger.send('SEditor-ToggleWidgetVis')
+        SEditor.toggleWidgetVis()
+
+
+    def bindMotionPathToNode(self):
+        if self.curveCollection == None:
+            print '----Error: you need to select or create a curve first!'
+            return
+        self.accept('MP_checkName', self.bindMotionPath)
+        self.askName = namePathPanel(MopathRecorder.count)
+        return
+
+    def bindMotionPath(self,name=None,test=None):
+        print test
+        self.ignore('MP_checkName')
+        del self.askName
+        self.curveCollection.getCurve(0).setName(name)
+        comboBox = self.getWidget('Mopath', 'Path:')
+        oldName = comboBox.get()
+        self.pointSetDict[name] = self.pointSetDict[oldName]
+        del self.pointSetDict[oldName]
+        scrolledList = comboBox.component('scrolledlist')
+        listbox = scrolledList.component('listbox')
+        names = list(listbox.get(0,'end'))
+        num = names.index(oldName)
+        names.pop(num)
+        names.append(name)
+        scrolledList.setlist(names)
+        comboBox.selectitem(name)
+        messenger.send('mPath_bindPathToNode',[self.playbackNodePath, self.curveCollection])
+        return
+        
+        
+
+    def addCurvesFromNodepath(self,curveList):
+        '''addCurvesFromNodepath(self,curveList)
+        This function will take a curveCollection list as a input.
+        If the list is not None, it will put the vurve back into the curve list.
+        else, do nothing.
+        '''
+        print curveList
+        self.ignore('curveListFor'+self.name)
+        if curveList != None:
+            for collection in curveList:
+                self.curveCollection = collection
+                self.extractPointSetFromCurveCollection(curveName=self.curveCollection.getCurve(0).getName())
+        else:
+            pass
+        return
+
+
+class namePathPanel(AppShell):
+    # Override class variables
+    appname = 'Name the Path'
+    frameWidth  = 575
+    frameHeight = 200
+    usecommandarea = 0
+    usestatusarea  = 0
+    index = 0
+
+    def __init__(self, count, parent = None, **kw):
+        INITOPT = Pmw.INITOPT
+        self.id = 'Name the Path'
+        self.appname = self.id
+        optiondefs = (
+            ('title',               self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        # Initialize the superclass
+        AppShell.__init__(self)
+
+        self.parent.resizable(False,False)
+
+        # Execute option callbacks
+        self.initialiseoptions(namePathPanel)
+
+    def createInterface(self):
+        self.menuBar.destroy()
+        interior = self.interior()
+        mainFrame = Frame(interior)
+
+        dataFrame = Frame(mainFrame)
+        label = Label(dataFrame, text='This name will be used as a reference for this Path.',font=('MSSansSerif', 10))
+        label.pack(side = Tkinter.TOP, expand = 0, fill = Tkinter.X)
+        dataFrame.pack(side = Tkinter.TOP, expand = 0, fill = Tkinter.X, padx=5, pady=10)
+        
+        dataFrame = Frame(mainFrame)
+        self.inputZone = Pmw.EntryField(dataFrame, labelpos='w', label_text = 'Name Selected Path: ',
+                                        entry_font=('MSSansSerif', 10),
+                                        label_font=('MSSansSerif', 10),
+                                        validate = None,
+                                        entry_width = 20)
+        self.inputZone.pack(side = Tkinter.LEFT, fill=Tkinter.X,expand=0)
+
+        self.button_ok = Button(dataFrame, text="OK", command=self.ok_press,width=10)
+        self.button_ok.pack(fill=Tkinter.X,expand=0,side=Tkinter.LEFT, padx = 3)
+
+        dataFrame.pack(side = Tkinter.TOP, expand = 0, fill = Tkinter.X, padx=10, pady=10)
+
+        mainFrame.pack(expand = 1, fill = Tkinter.BOTH)
+
+        
+        
+    def onDestroy(self, event):
+        '''
+        If you have open any thing, please rewrite here!
+        '''
+        pass
+
+    
+    def ok_press(self):
+        name = self.inputZone.getvalue()
+        messenger.send('MP_checkName',[name])
+        self.quit()
+        return
+
+

+ 267 - 0
contrib/src/sceneeditor/seParticleEffect.py

@@ -0,0 +1,267 @@
+from pandac.PandaModules import *
+import seParticles
+import seForceGroup
+from direct.directnotify import DirectNotifyGlobal
+
+class ParticleEffect(NodePath):
+
+    notify = DirectNotifyGlobal.directNotify.newCategory('ParticleEffect')
+    pid = 1 
+
+    def __init__(self, name=None, particles=None):
+        """__init__()"""
+        if (name == None):
+            name = 'particle-effect-%d' % ParticleEffect.pid
+            ParticleEffect.pid += 1
+        NodePath.__init__(self, name)
+        # Record particle effect name
+        self.name = name
+        # Enabled flag
+        self.fEnabled = 0
+        # Dictionary of particles and forceGroups
+        self.particlesDict = {}
+        self.forceGroupDict = {}
+        # The effect's particle system
+        if (particles != None):
+            self.addParticles(particles)
+        self.renderParent = None
+
+    def start(self, parent=None, renderParent=None):
+        assert(self.notify.debug('start() - name: %s' % self.name))
+        self.renderParent = renderParent
+        self.enable()
+        if (parent != None):
+            self.reparentTo(parent)
+
+    def cleanup(self):
+        self.removeNode()
+        self.disable()
+        for f in self.forceGroupDict.values():
+            f.cleanup()
+        for p in self.particlesDict.values():
+            p.cleanup()
+        del self.renderParent
+        del self.particlesDict
+        del self.forceGroupDict
+
+    def reset(self):
+        self.removeAllForces()
+        self.removeAllParticles()
+        self.forceGroupDict = {}
+        self.particlesDict = {}
+
+    def enable(self):
+        """enable()"""
+        if (self.renderParent != None):
+            for p in self.particlesDict.values():
+                p.setRenderParent(self.renderParent.node())
+        for f in self.forceGroupDict.values():
+            f.enable()
+        for p in self.particlesDict.values():
+            p.enable()
+        self.fEnabled = 1
+
+    def disable(self):
+        """disable()"""
+        self.detachNode()
+        for p in self.particlesDict.values():
+            p.setRenderParent(p.node)
+        for f in self.forceGroupDict.values():
+            f.disable()
+        for p in self.particlesDict.values():
+            p.disable()
+        self.fEnabled = 0
+
+    def isEnabled(self):
+        """
+        isEnabled()
+        Note: this may be misleading if enable(),disable() not used
+        """
+        return self.fEnabled
+
+    def addForceGroup(self, forceGroup):
+        """addForceGroup(forceGroup)"""
+        forceGroup.nodePath.reparentTo(self)
+        forceGroup.particleEffect = self
+        self.forceGroupDict[forceGroup.getName()] = forceGroup
+
+        # Associate the force group with all particles
+        for i in range(len(forceGroup)):
+            self.addForce(forceGroup[i])
+
+    def addForce(self, force):
+        """addForce(force)"""
+        for p in self.particlesDict.values():
+            p.addForce(force)
+
+    def removeForceGroup(self, forceGroup):
+        """removeForceGroup(forceGroup)"""
+        # Remove forces from all particles
+        for i in range(len(forceGroup)):
+            self.removeForce(forceGroup[i])
+
+        forceGroup.nodePath.removeNode()
+        forceGroup.particleEffect = None
+        del self.forceGroupDict[forceGroup.getName()]
+
+    def removeForce(self, force):
+        """removeForce(force)"""
+        for p in self.particlesDict.values():
+            p.removeForce(force)
+
+    def removeAllForces(self):
+        for fg in self.forceGroupDict.values():
+            self.removeForceGroup(fg)
+
+    def addParticles(self, particles):
+        """addParticles(particles)"""
+        particles.nodePath.reparentTo(self)
+        self.particlesDict[particles.getName()] = particles
+
+        # Associate all forces in all force groups with the particles
+        for fg in self.forceGroupDict.values():
+            for i in range(len(fg)):
+                particles.addForce(fg[i])
+
+    def removeParticles(self, particles):
+        """removeParticles(particles)"""
+        if (particles == None):
+            self.notify.warning('removeParticles() - particles == None!')
+            return
+        particles.nodePath.detachNode()
+        del self.particlesDict[particles.getName()]
+
+        # Remove all forces from the particles
+        for fg in self.forceGroupDict.values():
+            for f in fg.asList():
+                particles.removeForce(f)
+
+    def removeAllParticles(self):
+        for p in self.particlesDict.values():
+            self.removeParticles(p)
+
+    def getParticlesList(self):
+        """getParticles()"""
+        return self.particlesDict.values()
+    
+    def getParticlesNamed(self, name):
+        """getParticlesNamed(name)"""
+        return self.particlesDict.get(name, None)
+
+    def getParticlesDict(self):
+        """getParticlesDict()"""
+        return self.particlesDict
+
+    def getForceGroupList(self):
+        """getForceGroup()"""
+        return self.forceGroupDict.values()
+
+    def getForceGroupNamed(self, name):
+        """getForceGroupNamed(name)"""
+        return self.forceGroupDict.get(name, None)
+
+    def getForceGroupDict(self):
+        """getForceGroup()"""
+        return self.forceGroupDict
+
+    def saveConfig(self, filename):
+        """saveFileData(filename)"""
+        f = open(filename.toOsSpecific(), 'wb')
+        # Add a blank line
+        f.write('\n')
+
+        # Make sure we start with a clean slate
+        f.write('self.reset()\n')
+
+        pos = self.getPos()
+        hpr = self.getHpr()
+        scale = self.getScale()
+        f.write('self.setPos(%0.3f, %0.3f, %0.3f)\n' %
+                (pos[0], pos[1], pos[2]))
+        f.write('self.setHpr(%0.3f, %0.3f, %0.3f)\n' %
+                (hpr[0], hpr[1], hpr[2]))
+        f.write('self.setScale(%0.3f, %0.3f, %0.3f)\n' %
+                (scale[0], scale[1], scale[2]))
+
+        # Save all the particles to file
+        num = 0
+        for p in self.particlesDict.values():
+            target = 'p%d' % num 
+            num = num + 1
+            f.write(target + ' = Particles.Particles(\'%s\')\n' % p.getName())
+            p.printParams(f, target)
+            f.write('self.addParticles(%s)\n' % target)
+
+        # Save all the forces to file
+        num = 0
+        for fg in self.forceGroupDict.values():
+            target = 'f%d' % num
+            num = num + 1
+            f.write(target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
+                                                fg.getName())
+            fg.printParams(f, target)
+            f.write('self.addForceGroup(%s)\n' % target)
+
+        # Close the file
+        f.close()
+
+    def loadConfig(self, filename):
+        """loadConfig(filename)"""
+        #try:
+        #    if vfs:
+        print vfs.readFile(filename)
+        exec vfs.readFile(filename)
+        print "Particle Effect Reading using VFS"
+        #    else:
+        #       execfile(filename.toOsSpecific())
+        #       print "Shouldnt be wrong"
+        #except:
+        #    self.notify.error('loadConfig: failed to load particle file: '+ repr(filename))
+
+
+
+    def AppendConfig(self, f):
+        f.write('\n')
+        i1="    "
+        i2=i1+i1
+        # Make sure we start with a clean slate
+        f.write(i2+'self.effect.reset()\n')
+
+        pos = self.getPos()
+        hpr = self.getHpr()
+        scale = self.getScale()
+        f.write(i2+'self.effect.setPos(%0.3f, %0.3f, %0.3f)\n' %
+                (pos[0], pos[1], pos[2]))
+        f.write(i2+'self.effect.setHpr(%0.3f, %0.3f, %0.3f)\n' %
+                (hpr[0], hpr[1], hpr[2]))
+        f.write(i2+'self.effect.setScale(%0.3f, %0.3f, %0.3f)\n' %
+                (scale[0], scale[1], scale[2]))
+
+        # Save all the particles to file
+        num = 0
+        for p in self.particlesDict.values():
+            target = 'p%d' % num 
+            num = num + 1
+            f.write(i2+"if(mode==0):\n")
+            f.write(i2+i1+target + ' = seParticles.Particles(\'%s\')\n' % p.getName())
+            f.write(i2+"else:\n")
+            f.write(i2+i1+target + ' = Particles.Particles(\'%s\')\n' % p.getName())
+            p.printParams(f, target)
+            f.write(i2+'self.effect.addParticles(%s)\n' % target)
+
+        # Save all the forces to file
+        num = 0
+        for fg in self.forceGroupDict.values():
+            target = 'f%d' % num
+            num = num + 1
+            f.write(i2+target + ' = ForceGroup.ForceGroup(\'%s\')\n' % \
+                                                fg.getName())
+            fg.printParams(f, target)
+            f.write(i2+'self.effect.addForceGroup(%s)\n' % target)
+
+
+
+
+
+
+

+ 1976 - 0
contrib/src/sceneeditor/seParticlePanel.py

@@ -0,0 +1,1976 @@
+"""PANDA3D Particle Panel"""
+
+# Import Tkinter, Pmw, and the floater code from this directory tree.
+from direct.tkwidgets.AppShell import AppShell
+from tkFileDialog import *
+from tkSimpleDialog import askstring
+import os, Pmw, Tkinter
+from direct.tkwidgets.Dial import AngleDial
+from direct.tkwidgets.Floater import Floater
+from direct.tkwidgets.Slider import Slider
+from direct.tkwidgets.VectorWidgets import Vector2Entry, Vector3Entry
+from direct.tkwidgets.VectorWidgets import ColorEntry
+import sePlacer
+import seForceGroup
+import seParticles
+import seParticleEffect
+
+class ParticlePanel(AppShell):
+    # Override class variables
+    appname = 'Particle Panel'
+    frameWidth  = 375
+    frameHeight = 575
+    usecommandarea = 0
+    usestatusarea  = 0
+    balloonState = 'both'
+    effectsDict={}
+    def __init__(self, particleEffect = None, effectsDict={}, **kw):
+        INITOPT = Pmw.INITOPT
+        optiondefs = (
+            ('title',     self.appname,       None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        # Record particle effect
+        if particleEffect != None:
+            self.particleEffect = particleEffect
+            self.effectsDict = effectsDict
+        else:
+            # Or create a new one if none given
+            particles = seParticles.Particles()
+            particles.setBirthRate(0.02)
+            particles.setLitterSize(10)
+            particles.setLitterSpread(0)
+            particles.setFactory("PointParticleFactory")
+            particles.setRenderer("PointParticleRenderer")
+            particles.setEmitter("SphereVolumeEmitter")
+            particles.enable()
+            pe = seParticleEffect.ParticleEffect('effect1', particles)
+            self.particleEffect = pe
+            self.emitter=loader.loadModel("sphere")
+            pe.reparentTo(self.emitter) 
+            self.emitter.setName("effect1")
+            self.emitter.reparentTo(render)
+            pe.enable()
+            messenger.send('ParticlePanel_Added_Effect',['effect1',pe,self.emitter])
+            self.effectsDict[self.particleEffect.getName()]=self.particleEffect
+        
+
+        messenger.send('SGE_Update Explorer',[render])
+        
+        # Initialize application specific info
+        AppShell.__init__(self)
+
+        # Initialize panel Pmw options
+        self.initialiseoptions(ParticlePanel)
+        # Update panel values to reflect particle effect's state
+        self.selectEffectNamed(self.effectsDict.keys()[0])
+        # Make sure labels/menus reflect current state
+        self.updateMenusAndLabels()
+        # Make sure there is a page for each forceGroup objects
+        for forceGroup in self.particleEffect.getForceGroupList():
+            self.addForceGroupNotebookPage(self.particleEffect, forceGroup)
+
+    def appInit(self):
+        # Create dictionaries to keep track of panel objects
+        self.widgetDict = {}
+        self.variableDict = {}
+        self.forcePagesDict = {}
+        # Make sure particles are enabled
+        base.enableParticles()
+
+    def onDestroy(self, event):
+        messenger.send('ParticlePanle_close')
+        return
+
+    def createInterface(self):
+        # Handle to the toplevels hull
+        interior = self.interior()
+
+        # Create particle panel menu items
+
+        ## MENUBAR ENTRIES ##
+        # FILE MENU
+        # Get a handle on the file menu so commands can be inserted
+        # before quit item
+        fileMenu = self.menuBar.component('File-menu')
+        # MRM: Need to add load and save effects methods
+        fileMenu.insert_command(
+            fileMenu.index('Quit'),
+            label = 'Load Params',
+            command = self.loadParticleEffectFromFile)
+        fileMenu.insert_command(
+            fileMenu.index('Quit'),
+            label = 'Save Params',
+            command = self.saveParticleEffectToFile)
+        fileMenu.insert_command(
+            fileMenu.index('Quit'),
+            label = 'Print Params',
+            command = lambda s = self: s.particles.printParams())
+
+        # PARTICLE MANAGER MENU
+        self.menuBar.addmenu('ParticleMgr', 'ParticleMgr Operations')
+        self.particleMgrActive = IntVar()
+        self.particleMgrActive.set(base.isParticleMgrEnabled())
+        self.menuBar.addmenuitem(
+            'ParticleMgr', 'checkbutton',
+            'Enable/Disable ParticleMgr',
+            label = 'Active',
+            variable = self.particleMgrActive,
+            command = self.toggleParticleMgr)
+
+        ## MENUBUTTON LABELS ##
+        # Menubutton/label to identify the current objects being configured
+        labelFrame = Frame(interior)
+        # Current effect
+        self.effectsLabel = Menubutton(labelFrame, width = 10,
+                                       relief = RAISED,
+                                       borderwidth = 2,
+                                       font=('MSSansSerif', 12, 'bold'),
+                                       activebackground = '#909090')
+        self.effectsLabelMenu = Menu(self.effectsLabel, tearoff = 0)
+        self.effectsLabel['menu'] = self.effectsLabelMenu
+        self.effectsLabel.pack(side = LEFT, fill = 'x', expand = 1)
+        self.bind(self.effectsLabel,
+                  'Select effect to configure or create new effect')
+        self.effectsLabelMenu.add_command(label = 'Create New Effect',
+                                          command = self.createNewEffect)
+        self.effectsLabelMenu.add_command(
+            label = 'Select Particle Effect',
+            command = lambda s = self: SEditor.select(s.particleEffect))
+        self.effectsLabelMenu.add_command(
+            label = 'Place Particle Effect',
+            command = lambda s = self: sePlacer.place(s.particleEffect))
+        def togglePEVis(s = self):
+            if s.particleEffect.isHidden():
+                s.particleEffect.show()
+            else:
+                s.particleEffect.hide()
+        self.effectsLabelMenu.add_command(
+            label = 'Toggle Effect Vis',
+            command = togglePEVis)
+        self.effectsEnableMenu = Menu(self.effectsLabelMenu, tearoff = 0)
+        self.effectsLabelMenu.add_cascade(label = 'Enable/Disable',
+                                          menu = self.effectsEnableMenu)
+        self.effectsLabelMenu.add_separator()
+        # Current particles
+        self.particlesLabel = Menubutton(labelFrame, width = 10,
+                                         relief = RAISED,
+                                         borderwidth = 2,
+                                         font=('MSSansSerif', 12, 'bold'),
+                                         activebackground = '#909090')
+        self.particlesLabelMenu = Menu(self.particlesLabel, tearoff = 0)
+        self.particlesLabel['menu'] = self.particlesLabelMenu
+        self.particlesLabel.pack(side = LEFT, fill = 'x', expand = 1)
+        self.bind(self.particlesLabel,
+                  ('Select particles object to configure ' +
+                   'or add new particles object to current effect' ))
+        self.particlesLabelMenu.add_command(label = 'Create New Particles',
+                                            command = self.createNewParticles)
+        self.particlesEnableMenu = Menu(self.particlesLabelMenu, tearoff = 0)
+        self.particlesLabelMenu.add_cascade(label = 'Enable/Disable',
+                                            menu = self.particlesEnableMenu)
+        self.particlesLabelMenu.add_separator()
+        # Current force
+        self.forceGroupLabel = Menubutton(labelFrame, width = 10,
+                                      relief = RAISED,
+                                      borderwidth = 2,
+                                      font=('MSSansSerif', 12, 'bold'),
+                                      activebackground = '#909090')
+        self.forceGroupLabelMenu = Menu(self.forceGroupLabel, tearoff = 0)
+        self.forceGroupLabel['menu'] = self.forceGroupLabelMenu
+        self.forceGroupLabel.pack(side = LEFT, fill = 'x', expand = 1)
+        self.bind(self.forceGroupLabel,
+                  ('Select force group to configure ' +
+                   'or add a new force group to current effect'))
+        self.forceGroupLabelMenu.add_command(
+            label = 'Create New ForceGroup',
+            command = self.createNewForceGroup)
+        self.forceGroupEnableMenu = Menu(self.forceGroupLabelMenu, tearoff = 0)
+        self.forceGroupLabelMenu.add_cascade(label = 'Enable/Disable',
+                                             menu = self.forceGroupEnableMenu)
+        self.forceGroupLabelMenu.add_separator()
+        # Pack labels
+        labelFrame.pack(fill = 'x', expand = 0)
+
+        # Create the toplevel notebook pages
+        self.mainNotebook = Pmw.NoteBook(interior)
+        self.mainNotebook.pack(fill = BOTH, expand = 1)
+        systemPage = self.mainNotebook.add('System')
+        factoryPage = self.mainNotebook.add('Factory')
+        emitterPage = self.mainNotebook.add('Emitter')
+        rendererPage = self.mainNotebook.add('Renderer')
+        forcePage = self.mainNotebook.add('Force')
+        # Put this here so it isn't called right away
+        self.mainNotebook['raisecommand'] = self.updateInfo
+
+        ## SYSTEM PAGE WIDGETS ##
+        # Create system floaters
+        systemFloaterDefs = (
+            ('System', 'Pool Size',
+             'Max number of simultaneous particles',
+             self.setSystemPoolSize,
+             1.0, 1.0),
+            ('System', 'Birth Rate',
+             'Seconds between particle births',
+             self.setSystemBirthRate,
+             0.0, None),
+            ('System', 'Litter Size',
+             'Number of particle created at each birth',
+             self.setSystemLitterSize,
+             1.0, 1.0),
+            ('System', 'Litter Spread',
+             'Variation in litter size',
+             self.setSystemLitterSpread,
+             0.0, 1.0),
+            ('System', 'Lifespan',
+             'Age in seconds at which the system (vs. particles) should die',
+             self.setSystemLifespan,
+             0.0, None)
+            )
+        self.createFloaters(systemPage, systemFloaterDefs)
+
+        # Checkboxes
+        self.createCheckbutton(
+            systemPage, 'System', 'Render Space Velocities',
+            ('On: velocities are in render space; ' +
+             'Off: velocities are in particle local space'),
+            self.toggleSystemLocalVelocity, 0)
+        self.createCheckbutton(
+            systemPage, 'System', 'System Grows Older',
+            'On: system has a lifespan',
+            self.toggleSystemGrowsOlder, 0)
+
+        # Vector widgets
+        pos = self.createVector3Entry(systemPage, 'System', 'Pos',
+                                      'Particle system position',
+                                      command = self.setSystemPos)
+        pos.addMenuItem('Popup Placer Panel', sePlacer.Placer)
+        hpr = self.createVector3Entry(systemPage, 'System', 'Hpr',
+                                     'Particle system orientation',
+                                      fGroup_labels = ('H', 'P', 'R'),
+                                      command = self.setSystemHpr)
+        hpr.addMenuItem('Popup Placer Panel', sePlacer.Placer)
+
+        ## FACTORY PAGE WIDGETS ##
+        self.createOptionMenu(
+            factoryPage,
+            'Factory', 'Factory Type',
+            'Select type of particle factory',
+            ('PointParticleFactory', 'ZSpinParticleFactory',
+             'OrientedParticleFactory'),
+            self.selectFactoryType)
+        factoryWidgets = (
+            ('Factory', 'Life Span',
+             'Average particle lifespan in seconds',
+             self.setFactoryLifeSpan,
+             0.0, None),
+            ('Factory', 'Life Span Spread',
+             'Variation in lifespan',
+             self.setFactoryLifeSpanSpread,
+             0.0, None),
+            ('Factory', 'Mass',
+             'Average particle mass',
+             self.setFactoryParticleMass,
+             0.001, None),
+            ('Factory', 'Mass Spread',
+             'Variation in particle mass',
+             self.setFactoryParticleMassSpread,
+             0.0, None),
+            ('Factory', 'Terminal Velocity',
+             'Cap on average particle velocity',
+             self.setFactoryTerminalVelocity,
+             0.0, None),
+            ('Factory', 'Terminal Vel. Spread',
+             'Variation in terminal velocity',
+             self.setFactoryTerminalVelocitySpread,
+             0.0, None),
+        )
+        self.createFloaters(factoryPage, factoryWidgets)
+
+        self.factoryNotebook = Pmw.NoteBook(factoryPage, tabpos = None)
+        # Point page #
+        factoryPointPage = self.factoryNotebook.add('PointParticleFactory')
+        # Z spin page #
+        zSpinPage = self.factoryNotebook.add('ZSpinParticleFactory')
+
+        self.createCheckbutton(
+            zSpinPage, 'Z Spin Factory', 'Enable Angular Velocity',
+            ("On: angular velocity is used; " +
+             "Off: final angle is used"),
+            self.toggleAngularVelocity, 0, side = TOP),
+
+        self.createFloater(
+            zSpinPage, 'Z Spin Factory', 'Angular Velocity',
+             'How fast sprites rotate',
+             command = self.setFactoryZSpinAngularVelocity)
+
+        self.createFloater(
+            zSpinPage, 'Z Spin Factory', 'Angular Velocity Spread',
+             'Variation in how fast sprites rotate',
+             command = self.setFactoryZSpinAngularVelocitySpread)
+
+        self.createAngleDial(zSpinPage, 'Z Spin Factory', 'Initial Angle',
+                             'Starting angle in degrees',
+                             fRollover = 1,
+                             command = self.setFactoryZSpinInitialAngle)
+        self.createAngleDial(
+            zSpinPage, 'Z Spin Factory',
+            'Initial Angle Spread',
+            'Spread of the initial angle',
+            fRollover = 1,
+            command = self.setFactoryZSpinInitialAngleSpread)
+        self.createAngleDial(zSpinPage, 'Z Spin Factory', 'Final Angle',
+                             'Final angle in degrees',
+                             fRollover = 1,
+                             command = self.setFactoryZSpinFinalAngle)
+        self.createAngleDial(
+            zSpinPage, 'Z Spin Factory',
+            'Final Angle Spread',
+            'Spread of the final angle',
+            fRollover = 1,
+            command = self.setFactoryZSpinFinalAngleSpread)
+        # Oriented page #
+        orientedPage = self.factoryNotebook.add('OrientedParticleFactory')
+        Label(orientedPage, text = 'Not implemented').pack(expand = 1,
+                                                           fill = BOTH)
+        self.factoryNotebook.pack(expand = 1, fill = BOTH)
+
+        ## EMITTER PAGE WIDGETS ##
+        self.createOptionMenu(
+            emitterPage, 'Emitter',
+            'Emitter Type',
+            'Select type of particle emitter',
+            ('BoxEmitter', 'DiscEmitter', 'LineEmitter', 'PointEmitter',
+             'RectangleEmitter', 'RingEmitter', 'SphereVolumeEmitter',
+             'SphereSurfaceEmitter', 'TangentRingEmitter'),
+            self.selectEmitterType)
+
+        # Emitter modes
+        self.emissionType = IntVar()
+        self.emissionType.set(BaseParticleEmitter.ETRADIATE)
+        emissionFrame = Frame(emitterPage)
+        self.createRadiobutton(
+            emissionFrame, 'left',
+            'Emitter', 'Explicit Emission',
+            ('particles are all emitted in parallel, direction is based ' +
+             'on explicit velocity vector'),
+            self.emissionType, BaseParticleEmitter.ETEXPLICIT,
+            self.setEmissionType)
+        self.createRadiobutton(
+            emissionFrame, 'left',
+            'Emitter', 'Radiate Emission',
+            'particles are emitted away from a specific point',
+            self.emissionType, BaseParticleEmitter.ETRADIATE,
+            self.setEmissionType)
+        self.createRadiobutton(
+            emissionFrame, 'left',
+            'Emitter', 'Custom Emission',
+            ('particles are emitted with a velocity that ' +
+             'is determined by the particular emitter'),
+            self.emissionType, BaseParticleEmitter.ETCUSTOM,
+            self.setEmissionType)
+        emissionFrame.pack(fill = 'x', expand = 0)
+
+        self.createFloater(
+            emitterPage, 'Emitter', 'Velocity Multiplier',
+            'launch velocity multiplier (all emission modes)',
+            command = self.setEmitterAmplitude,
+            min = None)
+
+        self.createFloater(
+            emitterPage, 'Emitter', 'Velocity Multiplier Spread',
+            'spread for launch velocity multiplier (all emission modes)',
+            command = self.setEmitterAmplitudeSpread)
+
+        self.createVector3Entry(
+            emitterPage, 'Emitter', 'Offset Velocity',
+            'Velocity vector applied to all particles',
+            command = self.setEmitterOffsetForce)
+
+        self.createVector3Entry(
+            emitterPage, 'Emitter', 'Explicit Velocity',
+            'all particles launch with this velocity in Explicit mode',
+            command = self.setEmitterExplicitLaunchVector)
+
+        self.createVector3Entry(
+            emitterPage, 'Emitter', 'Radiate Origin',
+            'particles launch away from this point in Radiate mode',
+            command = self.setEmitterRadiateOrigin)
+
+        self.emitterNotebook = Pmw.NoteBook(emitterPage, tabpos = None)
+        # Box page #
+        boxPage = self.emitterNotebook.add('BoxEmitter')
+        self.createVector3Entry(boxPage, 'Box Emitter', 'Min',
+                                'Min point defining emitter box',
+                                command = self.setEmitterBoxPoint1)
+        self.createVector3Entry(boxPage, 'Box Emitter', 'Max',
+                                'Max point defining emitter box',
+                                command = self.setEmitterBoxPoint2,
+                                value = (1.0, 1.0, 1.0))
+        # Disc page #
+        discPage = self.emitterNotebook.add('DiscEmitter')
+        self.createFloater(discPage, 'Disc Emitter', 'Radius',
+                           'Radius of disc',
+                           command = self.setEmitterDiscRadius,
+                           min = 0.01)
+        customPage = self.discCustomFrame = Frame(discPage)
+        self.createAngleDial(customPage, 'Disc Emitter', 'Inner Angle',
+                             'Particle launch angle at center of disc',
+                             command = self.setEmitterDiscInnerAngle)
+        self.createFloater(customPage, 'Disc Emitter', 'Inner Velocity',
+                           'Launch velocity multiplier at center of disc',
+                           command = self.setEmitterDiscInnerVelocity)
+        self.createAngleDial(customPage, 'Disc Emitter', 'Outer Angle',
+                             'Particle launch angle at outer edge of disc',
+                             command = self.setEmitterDiscOuterAngle)
+        self.createFloater(customPage, 'Disc Emitter', 'Outer Velocity',
+                           'Launch velocity multiplier at edge of disc',
+                           command = self.setEmitterDiscOuterVelocity)
+        self.createCheckbutton(
+            customPage, 'Disc Emitter', 'Cubic Lerping',
+            'On: magnitude/angle interpolation from center',
+            self.toggleEmitterDiscCubicLerping, 0)
+        customPage.pack(fill = BOTH, expand = 1)
+
+        # Line page #
+        linePage = self.emitterNotebook.add('LineEmitter')
+        self.createVector3Entry(linePage, 'Line Emitter', 'Min',
+                                'Min point defining emitter line',
+                                command = self.setEmitterLinePoint1)
+        self.createVector3Entry(linePage, 'Line Emitter', 'Max',
+                                'Max point defining emitter line',
+                                command = self.setEmitterLinePoint2,
+                                value = (1.0, 0.0, 0.0))
+        # Point page #
+        emitterPointPage = self.emitterNotebook.add('PointEmitter')
+        self.createVector3Entry(emitterPointPage, 'Point Emitter', 'Position',
+                               'Position of emitter point',
+                                command = self.setEmitterPointPosition)
+        # Rectangle #
+        rectanglePage = self.emitterNotebook.add('RectangleEmitter')
+        self.createVector2Entry(rectanglePage,
+                                'Rectangle Emitter', 'Min',
+                               'Point defining rectangle',
+                                command = self.setEmitterRectanglePoint1)
+        self.createVector2Entry(rectanglePage,
+                                'Rectangle Emitter', 'Max',
+                               'Point defining rectangle',
+                                command = self.setEmitterRectanglePoint2)
+        # Ring #
+        ringPage = self.emitterNotebook.add('RingEmitter')
+        self.createFloater(ringPage, 'Ring Emitter', 'Radius',
+                           'Radius of ring',
+                           command = self.setEmitterRingRadius,
+                           min = 0.01)
+        self.ringCustomFrame = Frame(ringPage)
+        self.createAngleDial(self.ringCustomFrame, 'Ring Emitter', 'Angle',
+                             'Particle launch angle',
+                             command = self.setEmitterRingLaunchAngle)
+        self.ringCustomFrame.pack(fill = BOTH, expand = 1)
+
+        # Sphere volume #
+        sphereVolumePage = self.emitterNotebook.add('SphereVolumeEmitter')
+        self.createFloater(sphereVolumePage, 'Sphere Volume Emitter', 'Radius',
+                           'Radius of sphere',
+                           command = self.setEmitterSphereVolumeRadius,
+                           min = 0.01)
+        # Sphere surface #
+        sphereSurfacePage = self.emitterNotebook.add('SphereSurfaceEmitter')
+        self.createFloater(sphereSurfacePage, 'Sphere Surface Emitter',
+                           'Radius',
+                           'Radius of sphere',
+                           command = self.setEmitterSphereSurfaceRadius,
+                           min = 0.01)
+        # Tangent ring #
+        tangentRingPage = self.emitterNotebook.add('TangentRingEmitter')
+        self.createFloater(tangentRingPage, 'Tangent Ring Emitter', 'Radius',
+                           'Radius of ring',
+                           command = self.setEmitterTangentRingRadius,
+                           min = 0.01)
+        self.emitterNotebook.pack(fill = X)
+
+        ## RENDERER PAGE WIDGETS ##
+        self.createOptionMenu(
+            rendererPage, 'Renderer', 'Renderer Type',
+            'Select type of particle renderer',
+            ('LineParticleRenderer', 'GeomParticleRenderer',
+             'PointParticleRenderer', 'SparkleParticleRenderer',
+             'SpriteParticleRenderer'),
+            self.selectRendererType)
+
+        self.createOptionMenu(rendererPage,
+                              'Renderer', 'Alpha Mode',
+                              "alpha setting over particles' lifetime",
+                              ('NO_ALPHA','ALPHA_OUT','ALPHA_IN','ALPHA_USER'),
+                              self.setRendererAlphaMode)
+
+        self.createSlider(
+            rendererPage, 'Renderer', 'User Alpha',
+            'alpha value for ALPHA_USER alpha mode',
+            command = self.setRendererUserAlpha)
+
+        self.rendererNotebook = Pmw.NoteBook(rendererPage, tabpos = None)
+        # Line page #
+        linePage = self.rendererNotebook.add('LineParticleRenderer')
+        self.createColorEntry(linePage, 'Line Renderer', 'Head Color',
+                                'Head color of line',
+                                command = self.setRendererLineHeadColor)
+        self.createColorEntry(linePage, 'Line Renderer', 'Tail Color',
+                                'Tail color of line',
+                                command = self.setRendererLineTailColor)
+        # Geom page #
+        geomPage = self.rendererNotebook.add('GeomParticleRenderer')
+        f = Frame(geomPage)
+        f.pack(fill = X)
+        Label(f, width = 12, text = 'Geom Node').pack(side = LEFT)
+        self.rendererGeomNode = StringVar()
+        self.rendererGeomNodeEntry = Entry(
+            f, width = 12,
+            textvariable = self.rendererGeomNode)
+        self.rendererGeomNodeEntry.bind('<Return>', self.setRendererGeomNode)
+        self.rendererGeomNodeEntry.pack(side = LEFT, expand = 1, fill = X)
+        # Point #
+        rendererPointPage = self.rendererNotebook.add('PointParticleRenderer')
+        self.createFloater(rendererPointPage, 'Point Renderer', 'Point Size',
+                           'Width and height of points in pixels',
+                           command = self.setRendererPointSize)
+        self.createColorEntry(rendererPointPage, 'Point Renderer',
+                              'Start Color',
+                               'Starting color of point',
+                              command = self.setRendererPointStartColor)
+        self.createColorEntry(rendererPointPage, 'Point Renderer',
+                              'End Color',
+                               'Ending color of point',
+                              command = self.setRendererPointEndColor)
+        self.createOptionMenu(rendererPointPage, 'Point Renderer',
+                              'Blend Type',
+                              'Type of color blending used for particle',
+                              ('PP_ONE_COLOR', 'PP_BLEND_LIFE',
+                               'PP_BLEND_VEL'),
+                              self.rendererPointSelectBlendType)
+        self.createOptionMenu(rendererPointPage, 'Point Renderer',
+                              'Blend Method',
+                              'Interpolation method between colors',
+                              ('PP_NO_BLEND', 'PP_BLEND_LINEAR',
+                               'PP_BLEND_CUBIC'),
+                              self.rendererPointSelectBlendMethod)
+        # Sparkle #
+        sparklePage = self.rendererNotebook.add('SparkleParticleRenderer')
+        self.createColorEntry(sparklePage, 'Sparkle Renderer',
+                              'Center Color',
+                              'Color of sparkle center',
+                              command = self.setRendererSparkleCenterColor)
+        self.createColorEntry(sparklePage, 'Sparkle Renderer',
+                              'Edge Color',
+                              'Color of sparkle line endpoints',
+                              command = self.setRendererSparkleEdgeColor)
+        self.createFloater(sparklePage, 'Sparkle Renderer',
+                           'Birth Radius',
+                           'Initial sparkle radius',
+                           command = self.setRendererSparkleBirthRadius)
+        self.createFloater(sparklePage, 'Sparkle Renderer',
+                           'Death Radius',
+                           'Final sparkle radius',
+                           command = self.setRendererSparkleDeathRadius)
+        self.createOptionMenu(sparklePage,
+                              'Sparkle Renderer', 'Life Scale',
+                              'Does particle scale over its lifetime?',
+                              ('SP_NO_SCALE', 'SP_SCALE'),
+                              self.setRendererSparkleLifeScale)
+        # Sprite #
+        spritePage = self.rendererNotebook.add('SpriteParticleRenderer')
+        f = Frame(spritePage)
+        Label(f, width = 12, text = 'Texture Type:').pack(side = LEFT)
+        self.rendererSpriteSourceType = IntVar()
+        self.rendererSpriteSourceType.set(SpriteParticleRenderer.STTexture)
+        self.rendererSpriteSTTexture = self.createRadiobutton(
+            f, 'left',
+            'Sprite Renderer', 'Texture Type',
+            'Sprite particle renderer created from texture file',
+            self.rendererSpriteSourceType, SpriteParticleRenderer.STTexture,
+            self.setSpriteSourceType)
+        self.rendererSpriteSTTexture = self.createRadiobutton(
+            f, 'left',
+            'Sprite Renderer', 'NodePath Type',
+            'Sprite particle renderer created from node path',
+            self.rendererSpriteSourceType, SpriteParticleRenderer.STFromNode,
+            self.setSpriteSourceType)
+        f.pack(fill = X)
+        f = Frame(spritePage)
+        Label(f, width = 6, text = 'Texture:').pack(side = LEFT)
+        self.rendererSpriteTexture = StringVar()
+        self.rendererSpriteTexture.set(SpriteParticleRenderer.sourceTextureName)
+        self.rendererSpriteTextureEntry = Entry(
+            f, width = 12,
+            textvariable = self.rendererSpriteTexture)
+        self.rendererSpriteTextureEntry.pack(side = LEFT, expand = 1, fill = X)
+        f.pack(fill = X)
+        f = Frame(spritePage)
+        Label(f, width = 6, text = 'File:').pack(side = LEFT)
+        self.rendererSpriteFile = StringVar()
+        self.rendererSpriteFile.set(SpriteParticleRenderer.sourceFileName)
+        self.rendererSpriteFileEntry = Entry(
+            f, width = 12,
+            textvariable = self.rendererSpriteFile)
+        self.rendererSpriteFileEntry.pack(side = LEFT, expand = 1, fill = X)
+        Label(f, width = 6, text = 'Node:').pack(side = LEFT)
+        self.rendererSpriteNode = StringVar()
+        self.rendererSpriteNode.set(SpriteParticleRenderer.sourceNodeName)
+        self.rendererSpriteNodeEntry = Entry(
+            f, width = 6,
+            textvariable = self.rendererSpriteNode)
+        self.rendererSpriteNodeEntry.pack(side = LEFT, expand = 1, fill = X)
+        f.pack(fill = X)
+        # Init entries
+        self.setSpriteSourceType()
+        self.setTextureButton = Button(spritePage, text = 'Set Texture',
+                                       command = self.setRendererSpriteTexture)
+        self.setTextureButton.pack(fill = X)
+        f = Frame(spritePage)
+        self.createCheckbutton(
+            f, 'Sprite Renderer', 'X Scale',
+            ("On: x scale is interpolated over particle's life; " +
+             "Off: stays as start_X_Scale"),
+            self.toggleRendererSpriteXScale, 0, side = LEFT)
+        self.createCheckbutton(
+            f, 'Sprite Renderer', 'Y Scale',
+            ("On: y scale is interpolated over particle's life; " +
+             "Off: stays as start_Y_Scale"),
+            self.toggleRendererSpriteYScale, 0, side = LEFT)
+        self.createCheckbutton(
+            f, 'Sprite Renderer', 'Anim Angle',
+            ("On: particles that are set to spin on the Z axis will " +
+             "spin appropriately"),
+            self.toggleRendererSpriteAnimAngle, 0, side = LEFT)
+        f.pack(fill = X)
+        self.createFloater(spritePage, 'Sprite Renderer',
+                           'Initial X Scale',
+                           'Initial X scaling factor',
+                           command = self.setRendererSpriteInitialXScale)
+        self.createFloater(spritePage, 'Sprite Renderer',
+                           'Final X Scale',
+                           'Final X scaling factor, if xScale enabled',
+                           command = self.setRendererSpriteFinalXScale)
+        self.createFloater(spritePage, 'Sprite Renderer',
+                           'Initial Y Scale',
+                           'Initial Y scaling factor',
+                           command = self.setRendererSpriteInitialYScale)
+        self.createFloater(spritePage, 'Sprite Renderer',
+                           'Final Y Scale',
+                           'Final Y scaling factor, if yScale enabled',
+                           command = self.setRendererSpriteFinalYScale)
+        self.createAngleDial(spritePage, 'Sprite Renderer',
+                             'Non Animated Theta',
+                             ('If animAngle is false: counter clockwise ' +
+                              'Z rotation of all sprites'),
+                             command = self.setRendererSpriteNonAnimatedTheta)
+        self.createOptionMenu(spritePage, 'Sprite Renderer',
+                              'Blend Type',
+                              'Interpolation blend type for X and Y scaling',
+                              ('PP_NO_BLEND', 'PP_LINEAR', 'PP_CUBIC'),
+                              self.setRendererSpriteBlendMethod)
+        self.createCheckbutton(
+            spritePage, 'Sprite Renderer', 'Alpha Disable',
+            'On: alpha blending is disabled',
+            self.toggleRendererSpriteAlphaDisable, 0)
+        self.rendererNotebook.pack(fill = X)
+
+        ## FORCE PAGE WIDGETS ##
+        self.addForceButton = Menubutton(forcePage, text = 'Add Force',
+                                          relief = RAISED,
+                                          borderwidth = 2,
+                                          font=('MSSansSerif', 14, 'bold'),
+                                          activebackground = '#909090')
+        forceMenu = Menu(self.addForceButton)
+        self.addForceButton['menu'] = forceMenu
+        # DERIVED FROM LINEAR FORCE
+        # This also has: setVector
+        forceMenu.add_command(label = 'Add Linear Vector Force',
+                            command = self.addLinearVectorForce)
+        # Parameters: setAmplitude, setMassDependent, setVectorMasks
+        forceMenu.add_command(label = 'Add Linear Noise Force',
+                            command = self.addLinearNoiseForce)
+        forceMenu.add_command(label = 'Add Linear Jitter Force',
+                            command = self.addLinearJitterForce)
+        # This also has setCoef
+        forceMenu.add_command(label = 'Add Linear Friction Force',
+                            command = self.addLinearFrictionForce)
+        # This also has: setCoef, setLength, setRadius,
+        forceMenu.add_command(label = 'Add Linear Cylinder Vortex Force',
+                            command = self.addLinearCylinderVortexForce)
+
+        # DERIVED FROM LINEAR DISTANCE FORCE
+        # Parameters: setFalloffType, setForceCenter, setRadius
+        forceMenu.add_command(label = 'Add Linear Sink Force',
+                            command = self.addLinearSinkForce)
+        forceMenu.add_command(label = 'Add Linear Source Force',
+                            command = self.addLinearSourceForce)
+        """
+        # Avoid for now
+        forceMenu.add_command(label = 'Add Linear User Defined Force',
+                            command = self.addLinearUserDefinedForce)
+        """
+
+        self.addForceButton.pack(expand = 0)
+
+        # Scrolled frame to hold force widgets
+        self.sf = Pmw.ScrolledFrame(forcePage, horizflex = 'elastic')
+        self.sf.pack(fill = 'both', expand = 1)
+        self.forceFrame = self.sf.interior()
+        # Notebook to hold force widgets as the are added
+        self.forceGroupNotebook = Pmw.NoteBook(self.forceFrame, tabpos = None)
+        self.forceGroupNotebook.pack(fill = X)
+
+        self.factoryNotebook.setnaturalsize()
+        self.emitterNotebook.setnaturalsize()
+        self.rendererNotebook.setnaturalsize()
+        self.forceGroupNotebook.setnaturalsize()
+        self.mainNotebook.setnaturalsize()
+
+        # Make sure input variables processed
+        self.initialiseoptions(ParticlePanel)
+
+    ### WIDGET UTILITY FUNCTIONS ###
+    def createCheckbutton(self, parent, category, text,
+                          balloonHelp, command, initialState, side = 'top'):
+        bool = BooleanVar()
+        bool.set(initialState)
+        widget = Checkbutton(parent, text = text, anchor = W,
+                         variable = bool)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X, side = side)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        self.variableDict[category + '-' + text] = bool
+        return widget
+
+    def createRadiobutton(self, parent, side, category, text,
+                          balloonHelp, variable, value,
+                          command):
+        widget = Radiobutton(parent, text = text, anchor = W,
+                             variable = variable, value = value)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(side = side, fill = X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createFloaters(self, parent, widgetDefinitions):
+        widgets = []
+        for category, label, balloonHelp, command, min, resolution in widgetDefinitions:
+            widgets.append(
+                self.createFloater(parent, category, label, balloonHelp,
+                                   command, min, resolution)
+                )
+        return widgets
+
+    def createFloater(self, parent, category, text, balloonHelp,
+                      command = None, min = 0.0, resolution = None,
+                      numDigits = 3, **kw):
+        kw['text'] = text
+        kw['min'] = min
+        kw['resolution'] = resolution
+        kw['numDigits'] = numDigits
+        widget = apply(Floater, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createAngleDial(self, parent, category, text, balloonHelp,
+                        command = None, **kw):
+        kw['text'] = text
+        kw['style'] = 'mini'
+        widget = apply(AngleDial,(parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createSlider(self, parent, category, text, balloonHelp,
+                     command = None, min = 0.0, max = 1.0,
+                     resolution = 0.001, **kw):
+        kw['text'] = text
+        kw['min'] = min
+        kw['max'] = max
+        kw['resolution'] = resolution
+        widget = apply(Slider, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createVector2Entry(self, parent, category, text, balloonHelp,
+                           command = None, **kw):
+        # Set label's text
+        kw['text'] = text
+        widget = apply(Vector2Entry, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createVector3Entry(self, parent, category, text, balloonHelp,
+                           command = None, **kw):
+        # Set label's text
+        kw['text'] = text
+        widget = apply(Vector3Entry, (parent,), kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createColorEntry(self, parent, category, text, balloonHelp,
+                         command = None, **kw):
+        # Set label's text
+        kw['text'] = text
+        widget = apply(ColorEntry, (parent,) ,kw)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X)
+        self.bind(widget, balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def createOptionMenu(self, parent, category, text, balloonHelp,
+                         items, command):
+        optionVar = StringVar()
+        if len(items) > 0:
+            optionVar.set(items[0])
+        widget = Pmw.OptionMenu(parent, labelpos = W, label_text = text,
+                                label_width = 12, menu_tearoff = 1,
+                                menubutton_textvariable = optionVar,
+                                items = items)
+        # Do this after the widget so command isn't called on creation
+        widget['command'] = command
+        widget.pack(fill = X)
+        self.bind(widget.component('menubutton'), balloonHelp)
+        self.widgetDict[category + '-' + text] = widget
+        self.variableDict[category + '-' + text] = optionVar
+        return optionVar
+
+    def createComboBox(self, parent, category, text, balloonHelp,
+                         items, command, history = 0):
+        widget = Pmw.ComboBox(parent,
+                              labelpos = W,
+                              label_text = text,
+                              label_anchor = 'w',
+                              label_width = 12,
+                              entry_width = 16,
+                              history = history,
+                              scrolledlist_items = items)
+        # Don't allow user to edit entryfield
+        widget.configure(entryfield_entry_state = 'disabled')
+        # Select first item if it exists
+        if len(items) > 0:
+            widget.selectitem(items[0])
+        # Bind selection command
+        widget['selectioncommand'] = command
+        widget.pack(side = 'left', expand = 0)
+        # Bind help
+        self.bind(widget, balloonHelp)
+        # Record widget
+        self.widgetDict[category + '-' + text] = widget
+        return widget
+
+    def updateMenusAndLabels(self):
+        self.updateMenus()
+        self.updateLabels()
+
+    def updateLabels(self):
+        self.effectsLabel['text'] = self.particleEffect.getName()
+        self.particlesLabel['text'] = self.particles.getName()
+        if self.forceGroup != None:
+            self.forceGroupLabel['text'] = self.forceGroup.getName()
+        else:
+            self.forceGroupLabel['text'] = 'Force Group'
+
+    def updateMenus(self):
+        self.updateEffectsMenus()
+        self.updateParticlesMenus()
+        self.updateForceGroupMenus()
+
+    def updateEffectsMenus(self):
+        # Get rid of old effects entries if any
+        self.effectsEnableMenu.delete(0, 'end')
+        self.effectsLabelMenu.delete(5, 'end')
+        self.effectsLabelMenu.add_separator()
+        # Add in a checkbutton for each effect (to toggle on/off)
+        keys = self.effectsDict.keys()
+        keys.sort()
+        for name in keys:
+            effect = self.effectsDict[name]
+            self.effectsLabelMenu.add_command(
+                label = effect.getName(),
+                command = (lambda s = self,
+                           e = effect: s.selectEffectNamed(e.getName()))
+                )
+            effectActive = IntVar()
+            effectActive.set(effect.isEnabled())
+            self.effectsEnableMenu.add_checkbutton(
+                label = effect.getName(),
+                variable = effectActive,
+                command = (lambda s = self,
+                           e = effect,
+                           v = effectActive: s.toggleEffect(e, v)))
+
+    def updateParticlesMenus(self):
+        # Get rid of old particles entries if any
+        self.particlesEnableMenu.delete(0, 'end')
+        self.particlesLabelMenu.delete(2, 'end')
+        self.particlesLabelMenu.add_separator()
+        # Add in a checkbutton for each effect (to toggle on/off)
+        particles = self.particleEffect.getParticlesList()
+        names = map(lambda x: x.getName(), particles)
+        names.sort()
+        for name in names:
+            particle = self.particleEffect.getParticlesNamed(name)
+            self.particlesLabelMenu.add_command(
+                label = name,
+                command = (lambda s = self,
+                           n = name: s.selectParticlesNamed(n))
+                )
+            particleActive = IntVar()
+            particleActive.set(particle.isEnabled())
+            self.particlesEnableMenu.add_checkbutton(
+                label = name,
+                variable = particleActive,
+                command = (lambda s = self,
+                           p = particle,
+                           v = particleActive: s.toggleParticles(p, v)))
+
+    def updateForceGroupMenus(self):
+        # Get rid of old forceGroup entries if any
+        self.forceGroupEnableMenu.delete(0, 'end')
+        self.forceGroupLabelMenu.delete(2, 'end')
+        self.forceGroupLabelMenu.add_separator()
+        # Add in a checkbutton for each effect (to toggle on/off)
+        forceGroupList = self.particleEffect.getForceGroupList()
+        names = map(lambda x: x.getName(), forceGroupList)
+        names.sort()
+        for name in names:
+            force = self.particleEffect.getForceGroupNamed(name)
+            self.forceGroupLabelMenu.add_command(
+                label = name,
+                command = (lambda s = self,
+                           n = name: s.selectForceGroupNamed(n))
+                )
+            forceActive = IntVar()
+            forceActive.set(force.isEnabled())
+            self.forceGroupEnableMenu.add_checkbutton(
+                label = name,
+                variable = forceActive,
+                command = (lambda s = self,
+                           f = force,
+                           v = forceActive: s.toggleForceGroup(f, v)))
+
+    def selectEffectNamed(self, name):
+        effect = self.effectsDict.get(name, None)
+        if effect != None:
+            self.particleEffect = effect
+            # Default to first particle in particlesDict
+            self.particles = self.particleEffect.getParticlesList()[0]
+            # See if particle effect has any forceGroup
+            forceGroupList = self.particleEffect.getForceGroupList()
+            if len(forceGroupList) > 0:
+                self.forceGroup = forceGroupList[0]
+            else:
+                self.forceGroup = None
+            self.mainNotebook.selectpage('System')
+            self.updateInfo('System')
+        else:
+            print 'ParticlePanel: No effect named ' + name
+
+    def toggleEffect(self, effect, var):
+        if var.get():
+            effect.enable()
+        else:
+            effect.disable()
+
+    def selectParticlesNamed(self, name):
+        particles = self.particleEffect.getParticlesNamed(name)
+        if particles != None:
+            self.particles = particles
+            self.updateInfo()
+
+    def toggleParticles(self, particles, var):
+        if var.get():
+            particles.enable()
+        else:
+            particles.disable()
+
+    def selectForceGroupNamed(self, name):
+        forceGroup = self.particleEffect.getForceGroupNamed(name)
+        if forceGroup != None:
+            self.forceGroup = forceGroup
+            self.updateInfo('Force')
+
+    def toggleForceGroup(self, forceGroup, var):
+        if var.get():
+            forceGroup.enable()
+        else:
+            forceGroup.disable()
+
+    def toggleForce(self, force, pageName, variableName):
+        v = self.getVariable(pageName, variableName)
+        if v.get():
+            force.setActive(1)
+        else:
+            force.setActive(0)
+
+    def getWidget(self, category, text):
+        return self.widgetDict[category + '-' + text]
+
+    def getVariable(self, category, text):
+        return self.variableDict[category + '-' + text]
+
+    def loadParticleEffectFromFile(self):
+        # Find path to particle directory
+        pPath = getParticlePath()
+        if pPath.getNumDirectories() > 0:
+            if `pPath.getDirectory(0)` == '.':
+                path = '.'
+            else:
+                path = pPath.getDirectory(0).toOsSpecific()
+        else:
+            path = '.'
+        if not os.path.isdir(path):
+            print 'ParticlePanel Warning: Invalid default DNA directory!'
+            print 'Using current directory'
+            path = '.'
+        particleFilename = askopenfilename(
+            defaultextension = '.ptf',
+            filetypes = (('Particle Files', '*.ptf'),('All files', '*')),
+            initialdir = path,
+            title = 'Load Particle Effect',
+            parent = self.parent)
+        if particleFilename:
+            # Delete existing particles and forces
+            self.particleEffect.loadConfig(
+                Filename.fromOsSpecific(particleFilename))
+            self.selectEffectNamed(self.particleEffect.getName())
+            # Enable effect
+            self.particleEffect.enable()
+        messenger.send('SGE_Update Explorer',[render])
+
+    def saveParticleEffectToFile(self):
+        # Find path to particle directory
+        pPath = getParticlePath()
+        if pPath.getNumDirectories() > 0:
+            if `pPath.getDirectory(0)` == '.':
+                path = '.'
+            else:
+                path = pPath.getDirectory(0).toOsSpecific()
+        else:
+            path = '.'
+        if not os.path.isdir(path):
+            print 'ParticlePanel Warning: Invalid default DNA directory!'
+            print 'Using current directory'
+            path = '.'
+        particleFilename = asksaveasfilename(
+            defaultextension = '.ptf',
+            filetypes = (('Particle Files', '*.ptf'),('All files', '*')),
+            initialdir = path,
+            title = 'Save Particle Effect as',
+            parent = self.parent)
+        if particleFilename:
+            self.particleEffect.saveConfig(Filename(particleFilename))
+
+    ### PARTICLE EFFECTS COMMANDS ###
+    def toggleParticleMgr(self):
+        if self.particleMgrActive.get():
+            base.enableParticles()
+        else:
+            base.disableParticles()
+
+    ### PARTICLE SYSTEM COMMANDS ###
+    def updateInfo(self, page = 'System'):
+        self.updateMenusAndLabels()
+        if page == 'System':
+            self.updateSystemWidgets()
+        elif page == 'Factory':
+            self.selectFactoryPage()
+            self.updateFactoryWidgets()
+        elif page == 'Emitter':
+            self.selectEmitterPage()
+            self.updateEmitterWidgets()
+        elif page == 'Renderer':
+            self.selectRendererPage()
+            self.updateRendererWidgets()
+        elif page == 'Force':
+            self.updateForceWidgets()
+
+    def toggleParticleEffect(self):
+        if self.getVariable('Effect', 'Active').get():
+            self.particleEffect.enable()
+        else:
+            self.particleEffect.disable()
+
+    ## SYSTEM PAGE ##
+    def updateSystemWidgets(self):
+        poolSize = self.particles.getPoolSize()
+        self.getWidget('System', 'Pool Size').set(int(poolSize), 0)
+        birthRate = self.particles.getBirthRate()
+        self.getWidget('System', 'Birth Rate').set(birthRate, 0)
+        litterSize = self.particles.getLitterSize()
+        self.getWidget('System', 'Litter Size').set(int(litterSize), 0)
+        litterSpread = self.particles.getLitterSpread()
+        self.getWidget('System', 'Litter Spread').set(litterSpread, 0)
+        systemLifespan = self.particles.getSystemLifespan()
+        self.getWidget('System', 'Lifespan').set(systemLifespan, 0)
+        pos = self.particles.nodePath.getPos()
+        self.getWidget('System', 'Pos').set([pos[0], pos[1], pos[2]], 0)
+        hpr = self.particles.nodePath.getHpr()
+        self.getWidget('System', 'Hpr').set([hpr[0], hpr[1], hpr[2]], 0)
+        self.getVariable('System', 'Render Space Velocities').set(
+            self.particles.getLocalVelocityFlag())
+        self.getVariable('System', 'System Grows Older').set(
+            self.particles.getSystemGrowsOlderFlag())
+    def setSystemPoolSize(self, value):
+        self.particles.setPoolSize(int(value))
+    def setSystemBirthRate(self, value):
+        self.particles.setBirthRate(value)
+    def setSystemLitterSize(self, value):
+        self.particles.setLitterSize(int(value))
+    def setSystemLitterSpread(self, value):
+        self.particles.setLitterSpread(int(value))
+    def setSystemLifespan(self, value):
+        self.particles.setSystemLifespan(value)
+    def toggleSystemLocalVelocity(self):
+        self.particles.setLocalVelocityFlag(
+            self.getVariable('System', 'Render Space Velocities').get())
+    def toggleSystemGrowsOlder(self):
+        self.particles.setSystemGrowsOlderFlag(
+            self.getVariable('System', 'System Grows Older').get())
+    def setSystemPos(self, pos):
+        self.particles.nodePath.setPos(Vec3(pos[0], pos[1], pos[2]))
+    def setSystemHpr(self, pos):
+        self.particles.nodePath.setHpr(Vec3(pos[0], pos[1], pos[2]))
+
+    ## FACTORY PAGE ##
+    def selectFactoryType(self, type):
+        self.factoryNotebook.selectpage(type)
+        self.particles.setFactory(type)
+        self.updateFactoryWidgets()
+
+    def selectFactoryPage(self):
+        pass
+
+    def updateFactoryWidgets(self):
+        factory = self.particles.factory
+        lifespan = factory.getLifespanBase()
+        self.getWidget('Factory', 'Life Span').set(lifespan, 0)
+        lifespanSpread = factory.getLifespanSpread()
+        self.getWidget('Factory', 'Life Span Spread').set(lifespanSpread, 0)
+        mass = factory.getMassBase()
+        self.getWidget('Factory', 'Mass').set(mass, 0)
+        massSpread = factory.getMassSpread()
+        self.getWidget('Factory', 'Mass Spread').set(massSpread, 0)
+        terminalVelocity = factory.getTerminalVelocityBase()
+        self.getWidget('Factory', 'Terminal Velocity').set(terminalVelocity, 0)
+        terminalVelocitySpread = factory.getTerminalVelocitySpread()
+        self.getWidget('Factory', 'Terminal Vel. Spread').set(
+            terminalVelocitySpread, 0)
+
+    def setFactoryLifeSpan(self, value):
+        self.particles.factory.setLifespanBase(value)
+    def setFactoryLifeSpanSpread(self, value):
+        self.particles.factory.setLifespanSpread(value)
+    def setFactoryParticleMass(self, value):
+        self.particles.factory.setMassBase(value)
+    def setFactoryParticleMassSpread(self, value):
+        self.particles.factory.setMassSpread(value)
+    def setFactoryTerminalVelocity(self, value):
+        self.particles.factory.setTerminalVelocityBase(value)
+    def setFactoryTerminalVelocitySpread(self, value):
+        self.particles.factory.setTerminalVelocitySpread(value)
+    # Point Page #
+    # Z Spin Page #
+    def setFactoryZSpinInitialAngle(self, angle):
+        self.particles.factory.setInitialAngle(angle)
+    def setFactoryZSpinInitialAngleSpread(self, spread):
+        self.particles.factory.setInitialAngleSpread(spread)
+    def setFactoryZSpinFinalAngle(self, angle):
+        self.particles.factory.setFinalAngle(angle)
+    def setFactoryZSpinFinalAngleSpread(self, spread):
+        self.particles.factory.setFinalAngleSpread(spread)
+    def setFactoryZSpinAngularVelocity(self, vel):
+        self.particles.factory.setAngularVelocity(vel)
+    def setFactoryZSpinAngularVelocitySpread(self, spread):
+        self.particles.factory.setAngularVelocitySpread(spread)
+
+    ## EMITTER PAGE ##
+    def selectEmitterType(self, type):
+        self.emitterNotebook.selectpage(type)
+        self.particles.setEmitter(type)
+        self.updateEmitterWidgets()
+
+    def selectEmitterPage(self):
+        type = self.particles.emitter.__class__.__name__
+        self.emitterNotebook.selectpage(type)
+        self.getVariable('Emitter', 'Emitter Type').set(type)
+
+    def updateEmitterWidgets(self):
+        emitter = self.particles.emitter
+        self.setEmissionType(self.particles.emitter.getEmissionType())
+        amp = emitter.getAmplitude()
+        self.getWidget('Emitter', 'Velocity Multiplier').set(amp)
+        spread = emitter.getAmplitudeSpread()
+        self.getWidget('Emitter', 'Velocity Multiplier Spread').set(spread)
+        vec = emitter.getOffsetForce()
+        self.getWidget('Emitter', 'Offset Velocity').set(
+            [vec[0], vec[1], vec[2]], 0)
+        vec = emitter.getRadiateOrigin()
+        self.getWidget('Emitter', 'Radiate Origin').set(
+            [vec[0], vec[1], vec[2]], 0)
+        vec = emitter.getExplicitLaunchVector()
+        self.getWidget('Emitter', 'Explicit Velocity').set(
+            [vec[0], vec[1], vec[2]], 0)
+        if isinstance(emitter, BoxEmitter):
+            min = emitter.getMinBound()
+            self.getWidget('Box Emitter', 'Min').set(
+                [min[0], min[1], min[2]], 0)
+            max = emitter.getMaxBound()
+            self.getWidget('Box Emitter', 'Max').set(
+                [max[0], max[1], max[2]], 0)
+        elif isinstance(emitter, DiscEmitter):
+            radius = emitter.getRadius()
+            self.getWidget('Disc Emitter', 'Radius').set(radius, 0)
+            innerAngle = emitter.getInnerAngle()
+            self.getWidget('Disc Emitter', 'Inner Angle').set(innerAngle, 0)
+            innerMagnitude = emitter.getInnerMagnitude()
+            self.getWidget('Disc Emitter', 'Inner Velocity').set(
+                innerMagnitude, 0)
+            outerAngle = emitter.getOuterAngle()
+            self.getWidget('Disc Emitter', 'Outer Angle').set(outerAngle, 0)
+            outerMagnitude = emitter.getOuterMagnitude()
+            self.getWidget('Disc Emitter', 'Inner Velocity').set(
+                outerMagnitude, 0)
+            cubicLerping = emitter.getCubicLerping()
+            self.getVariable('Disc Emitter', 'Cubic Lerping').set(cubicLerping)
+        elif isinstance(emitter, LineEmitter):
+            min = emitter.getEndpoint1()
+            self.getWidget('Line Emitter', 'Min').set(
+                [min[0], min[1], min[2]], 0)
+            max = emitter.getEndpoint2()
+            self.getWidget('Line Emitter', 'Max').set(
+                [max[0], max[1], max[2]], 0)
+        elif isinstance(emitter, PointEmitter):
+            location = emitter.getLocation()
+            self.getWidget('Point Emitter', 'Position').set(
+                [location[0], location[1], location[2]], 0)
+        elif isinstance(emitter, RectangleEmitter):
+            min = emitter.getMinBound()
+            self.getWidget('Rectangle Emitter', 'Min').set(
+                [min[0], min[1]], 0)
+            max = emitter.getMaxBound()
+            self.getWidget('Rectangle Emitter', 'Max').set(
+                [max[0], max[1]], 0)
+        elif isinstance(emitter, RingEmitter):
+            radius = emitter.getRadius()
+            self.getWidget('Ring Emitter', 'Radius').set(radius, 0)
+            angle = emitter.getAngle()
+            self.getWidget('Ring Emitter', 'Angle').set(angle, 0)
+        elif isinstance(emitter, SphereVolumeEmitter):
+            radius = emitter.getRadius()
+            self.getWidget('Sphere Volume Emitter', 'Radius').set(radius, 0)
+        elif isinstance(emitter, SphereSurfaceEmitter):
+            radius = emitter.getRadius()
+            self.getWidget('Sphere Surface Emitter', 'Radius').set(radius, 0)
+        elif isinstance(emitter, TangentRingEmitter):
+            radius = emitter.getRadius()
+            self.getWidget('Tangent Ring Emitter', 'Radius').set(radius, 0)
+    # All #
+    def setEmissionType(self, newType = None):
+        if newType:
+            type = newType
+            self.emissionType.set(type)
+        else:
+            type = self.emissionType.get()
+        self.particles.emitter.setEmissionType(type)
+        if type == BaseParticleEmitter.ETEXPLICIT:
+            self.getWidget(
+                'Emitter', 'Radiate Origin')['state'] = 'disabled'
+            self.getWidget(
+                'Emitter', 'Explicit Velocity')['state'] = 'normal'
+            # Hide custom widgets
+            if isinstance(self.particles.emitter, DiscEmitter):
+                self.discCustomFrame.pack_forget()
+            elif isinstance(self.particles.emitter, RingEmitter):
+                self.ringCustomFrame.pack_forget()
+        elif type == BaseParticleEmitter.ETRADIATE:
+            self.getWidget(
+                'Emitter', 'Radiate Origin')['state'] = 'normal'
+            self.getWidget(
+                'Emitter', 'Explicit Velocity')['state'] = 'disabled'
+            # Hide custom widgets
+            if isinstance(self.particles.emitter, DiscEmitter):
+                self.discCustomFrame.pack_forget()
+            elif isinstance(self.particles.emitter, RingEmitter):
+                self.ringCustomFrame.pack_forget()
+        elif type == BaseParticleEmitter.ETCUSTOM:
+            self.getWidget(
+                'Emitter', 'Radiate Origin')['state'] = 'disabled'
+            self.getWidget(
+                'Emitter', 'Explicit Velocity')['state'] = 'disabled'
+            # Show custom widgets
+            if isinstance(self.particles.emitter, DiscEmitter):
+                self.discCustomFrame.pack(fill = BOTH, expand = 1)
+            elif isinstance(self.particles.emitter, RingEmitter):
+                self.ringCustomFrame.pack(fill = BOTH, expand = 1)
+
+    def setEmitterAmplitude(self, value):
+        self.particles.emitter.setAmplitude(value)
+
+    def setEmitterAmplitudeSpread(self, value):
+        self.particles.emitter.setAmplitudeSpread(value)
+
+    def setEmitterOffsetForce(self, vec):
+        self.particles.emitter.setOffsetForce(
+            Vec3(vec[0], vec[1], vec[2]))
+
+    def setEmitterRadiateOrigin(self, origin):
+        self.particles.emitter.setRadiateOrigin(
+            Point3(origin[0], origin[1], origin[2]))
+
+    def setEmitterExplicitLaunchVector(self, vec):
+        self.particles.emitter.setExplicitLaunchVector(
+            Vec3(vec[0], vec[1], vec[2]))
+
+    # Box #
+    def setEmitterBoxPoint1(self, point):
+        self.particles.emitter.setMinBound(Point3(point[0],
+                                                  point[1],
+                                                  point[2]))
+    def setEmitterBoxPoint2(self, point):
+        self.particles.emitter.setMaxBound(Point3(point[0],
+                                                  point[1],
+                                                  point[2]))
+    # Disc #
+    def setEmitterDiscRadius(self, radius):
+        self.particles.emitter.setRadius(radius)
+    def setEmitterDiscInnerAngle(self, angle):
+        self.particles.emitter.setInnerAngle(angle)
+    def setEmitterDiscInnerVelocity(self, velocity):
+        self.particles.emitter.setInnerMagnitude(velocity)
+    def setEmitterDiscOuterAngle(self, angle):
+        self.particles.emitter.setOuterAngle(angle)
+    def setEmitterDiscOuterVelocity(self, velocity):
+        self.particles.emitter.setOuterMagnitude(velocity)
+    def toggleEmitterDiscCubicLerping(self):
+        self.particles.emitter.setCubicLerping(
+            self.getVariable('Disc Emitter', 'Cubic Lerping').get())
+    # Line #
+    def setEmitterLinePoint1(self, point):
+        self.particles.emitter.setEndpoint1(Point3(point[0],
+                                                   point[1],
+                                                   point[2]))
+    def setEmitterLinePoint2(self, point):
+        self.particles.emitter.setEndpoint2(Point3(point[0],
+                                                   point[1],
+                                                   point[2]))
+    # Point #
+    def setEmitterPointPosition(self, pos):
+        self.particles.emitter.setLocation(Point3(pos[0], pos[1], pos[2]))
+    # Rectangle #
+    def setEmitterRectanglePoint1(self, point):
+        self.particles.emitter.setMinBound(Point2(point[0], point[1]))
+    def setEmitterRectanglePoint2(self, point):
+        self.particles.emitter.setMaxBound(Point2(point[0], point[1]))
+    # Ring #
+    def setEmitterRingRadius(self, radius):
+        self.particles.emitter.setRadius(radius)
+    def setEmitterRingLaunchAngle(self, angle):
+        self.particles.emitter.setAngle(angle)
+    # Sphere surface #
+    def setEmitterSphereSurfaceRadius(self, radius):
+        self.particles.emitter.setRadius(radius)
+    # Sphere volume #
+    def setEmitterSphereVolumeRadius(self, radius):
+        self.particles.emitter.setRadius(radius)
+    # Tangent ring #
+    def setEmitterTangentRingRadius(self, radius):
+        self.particles.emitter.setRadius(radius)
+
+    ## RENDERER PAGE ##
+    def selectRendererType(self, type):
+        self.rendererNotebook.selectpage(type)
+        self.particles.setRenderer(type)
+        self.updateRendererWidgets()
+
+    def updateRendererWidgets(self):
+        renderer = self.particles.renderer
+        alphaMode = renderer.getAlphaMode()
+        if alphaMode == BaseParticleRenderer.PRALPHANONE:
+            aMode = 'NO_ALPHA'
+        elif alphaMode == BaseParticleRenderer.PRALPHAOUT:
+            aMode = 'ALPHA_OUT'
+        elif alphaMode == BaseParticleRenderer.PRALPHAIN:
+            aMode = 'ALPHA_IN'
+        elif alphaMode == BaseParticleRenderer.PRALPHAUSER:
+            aMode = 'ALPHA_USER'
+        self.getVariable('Renderer', 'Alpha Mode').set(aMode)
+        userAlpha = renderer.getUserAlpha()
+        self.getWidget('Renderer', 'User Alpha').set(userAlpha)
+        if isinstance(renderer, LineParticleRenderer):
+            headColor = renderer.getHeadColor() * 255.0
+            self.getWidget('Line Renderer', 'Head Color').set(
+                [headColor[0], headColor[1], headColor[2], headColor[3]])
+            tailColor = renderer.getTailColor() * 255.0
+            self.getWidget('Line Renderer', 'Tail Color').set(
+                [tailColor[0], tailColor[1], tailColor[2], tailColor[3]])
+        elif isinstance(renderer, GeomParticleRenderer):
+            pass
+        elif isinstance(renderer, PointParticleRenderer):
+            pointSize = renderer.getPointSize()
+            self.getWidget('Point Renderer', 'Point Size').set(pointSize)
+            startColor = renderer.getStartColor() * 255.0
+            self.getWidget('Point Renderer', 'Start Color').set(
+                [startColor[0], startColor[1], startColor[2], startColor[3]])
+            endColor = renderer.getEndColor() * 255.0
+            self.getWidget('Point Renderer', 'End Color').set(
+                [endColor[0], endColor[1], endColor[2], endColor[3]])
+            blendType = renderer.getBlendType()
+            if (blendType == PointParticleRenderer.PPONECOLOR):
+                bType = "PP_ONE_COLOR"
+            elif (blendType == PointParticleRenderer.PPBLENDLIFE):
+                bType = "PP_BLEND_LIFE"
+            elif (blendType == PointParticleRenderer.PPBLENDVEL):
+                bType = "PP_BLEND_VEL"
+            self.getVariable('Point Renderer', 'Blend Type').set(bType)
+            blendMethod = renderer.getBlendMethod()
+            bMethod = "PP_NO_BLEND"
+            if (blendMethod == BaseParticleRenderer.PPNOBLEND):
+                bMethod = "PP_NO_BLEND"
+            elif (blendMethod == BaseParticleRenderer.PPBLENDLINEAR):
+                bMethod = "PP_BLEND_LINEAR"
+            elif (blendMethod == BaseParticleRenderer.PPBLENDCUBIC):
+                bMethod = "PP_BLEND_CUBIC"
+            self.getVariable('Point Renderer', 'Blend Method').set(bMethod)
+        elif isinstance(renderer, SparkleParticleRenderer):
+            centerColor = renderer.getCenterColor() * 255.0
+            self.getWidget('Sparkle Renderer', 'Center Color').set(
+                [centerColor[0], centerColor[1],
+                 centerColor[2], centerColor[3]])
+            edgeColor = renderer.getEdgeColor() * 255.0
+            self.getWidget('Sparkle Renderer', 'Edge Color').set(
+                [edgeColor[0], edgeColor[1], edgeColor[2], edgeColor[3]])
+            birthRadius = renderer.getBirthRadius()
+            self.getWidget('Sparkle Renderer', 'Birth Radius').set(birthRadius)
+            deathRadius = renderer.getDeathRadius()
+            self.getWidget('Sparkle Renderer', 'Death Radius').set(deathRadius)
+            lifeScale = renderer.getLifeScale()
+            lScale = "SP_NO_SCALE"
+            if (lifeScale == SparkleParticleRenderer.SPSCALE):
+                lScale = "SP_SCALE"
+            self.getVariable('Sparkle Renderer', 'Life Scale').set(lScale)
+        elif isinstance(renderer, SpriteParticleRenderer):
+            color = renderer.getColor() * 255.0
+            # Update widgets to reflect current default values
+            # Texture
+            textureName = renderer.getSourceTextureName()
+            if textureName != None:
+                self.rendererSpriteTexture.set(textureName)
+            # File
+            fileName = renderer.getSourceFileName()
+            if fileName != None:
+                self.rendererSpriteFile.set(fileName)
+            # Node
+            nodeName = renderer.getSourceNodeName()
+            if nodeName != None:
+                self.rendererSpriteNode.set(nodeName)
+            self.getVariable('Sprite Renderer', 'X Scale').set(
+                renderer.getXScaleFlag())
+            self.getVariable('Sprite Renderer', 'Y Scale').set(
+                renderer.getYScaleFlag())
+            self.getVariable('Sprite Renderer', 'Anim Angle').set(
+                renderer.getAnimAngleFlag())
+            initialXScale = renderer.getInitialXScale()
+            self.getWidget('Sprite Renderer', 'Initial X Scale').set(
+                initialXScale)
+            initialYScale = renderer.getInitialYScale()
+            self.getWidget('Sprite Renderer', 'Initial Y Scale').set(
+                initialYScale)
+            finalXScale = renderer.getFinalXScale()
+            self.getWidget('Sprite Renderer', 'Final X Scale').set(
+                finalXScale)
+            finalYScale = renderer.getFinalYScale()
+            self.getWidget('Sprite Renderer', 'Final Y Scale').set(
+                finalYScale)
+            nonanimatedTheta = renderer.getNonanimatedTheta()
+            self.getWidget('Sprite Renderer', 'Non Animated Theta').set(
+                nonanimatedTheta)
+            blendMethod = renderer.getAlphaBlendMethod()
+            bMethod = "PP_NO_BLEND"
+            if (blendMethod == BaseParticleRenderer.PPNOBLEND):
+                bMethod = "PP_NO_BLEND"
+            elif (blendMethod == BaseParticleRenderer.PPBLENDLINEAR):
+                bMethod = "PP_BLEND_LINEAR"
+            elif (blendMethod == BaseParticleRenderer.PPBLENDCUBIC):
+                bMethod = "PP_BLEND_CUBIC"
+            self.getVariable('Sprite Renderer', 'Alpha Disable').set(
+                renderer.getAlphaDisable())
+
+    def selectRendererPage(self):
+        type = self.particles.renderer.__class__.__name__
+        self.rendererNotebook.selectpage(type)
+        self.getVariable('Renderer', 'Renderer Type').set(type)
+
+    # All #
+    def setRendererAlphaMode(self, alphaMode):
+        if alphaMode == 'NO_ALPHA':
+            aMode = BaseParticleRenderer.PRALPHANONE
+        elif alphaMode == 'ALPHA_OUT':
+            aMode = BaseParticleRenderer.PRALPHAOUT
+        elif alphaMode == 'ALPHA_IN':
+            aMode = BaseParticleRenderer.PRALPHAIN
+        elif alphaMode == 'ALPHA_USER':
+            aMode = BaseParticleRenderer.PRALPHAUSER
+        self.particles.renderer.setAlphaMode(aMode)
+
+    def setRendererUserAlpha(self, alpha):
+        self.particles.renderer.setUserAlpha(alpha)
+
+    # Line #
+    def setRendererLineHeadColor(self, color):
+        self.particles.renderer.setHeadColor(
+            Vec4(color[0]/255.0, color[1]/255.0,
+                 color[2]/255.0, color[3]/255.0))
+    def setRendererLineTailColor(self, color):
+        self.particles.renderer.setTailColor(
+            Vec4(color[0]/255.0, color[1]/255.0,
+                 color[2]/255.0, color[3]/255.0))
+    # Geom #
+    def setRendererGeomNode(self, event):
+        node = None
+        nodePath = loader.loadModel(self.rendererGeomNode.get())
+        if nodePath != None:
+            node = nodePath.node()
+        if (node != None):
+            self.particles.renderer.setGeomNode(node)
+    # Point #
+    def setRendererPointSize(self, size):
+        self.particles.renderer.setPointSize(size)
+    def setRendererPointStartColor(self, color):
+        self.particles.renderer.setStartColor(
+            Vec4(color[0]/255.0, color[1]/255.0,
+                 color[2]/255.0, color[3]/255.0))
+    def setRendererPointEndColor(self, color):
+        self.particles.renderer.setEndColor(
+            Vec4(color[0]/255.0, color[1]/255.0,
+                 color[2]/255.0, color[3]/255.0))
+    def rendererPointSelectBlendType(self, blendType):
+        if blendType == "PP_ONE_COLOR":
+            bType = PointParticleRenderer.PPONECOLOR
+        elif blendType == "PP_BLEND_LIFE":
+            bType = PointParticleRenderer.PPBLENDLIFE
+        elif blendType == "PP_BLEND_VEL":
+            bType = PointParticleRenderer.PPBLENDVEL
+        self.particles.renderer.setBlendType(bType)
+    def rendererPointSelectBlendMethod(self, blendMethod):
+        if blendMethod == "PP_NO_BLEND":
+            bMethod = BaseParticleRenderer.PPNOBLEND
+        elif blendMethod == "PP_BLEND_LINEAR":
+            bMethod = BaseParticleRenderer.PPBLENDLINEAR
+        elif blendMethod == "PP_BLEND_CUBIC":
+            bMethod = BaseParticleRenderer.PPBLENDCUBIC
+        self.particles.renderer.setBlendMethod(bMethod)
+    # Sparkle #
+    def setRendererSparkleCenterColor(self, color):
+        self.particles.renderer.setCenterColor(
+            Vec4(color[0]/255.0, color[1]/255.0,
+                 color[2]/255.0, color[3]/255.0))
+    def setRendererSparkleEdgeColor(self, color):
+        self.particles.renderer.setEdgeColor(
+            Vec4(color[0]/255.0, color[1]/255.0,
+                 color[2]/255.0, color[3]/255.0))
+    def setRendererSparkleBirthRadius(self, radius):
+        self.particles.renderer.setBirthRadius(radius)
+    def setRendererSparkleDeathRadius(self, radius):
+        self.particles.renderer.setDeathRadius(radius)
+    def setRendererSparkleLifeScale(self, lifeScaleMethod):
+        if lifeScaleMethod == 'SP_NO_SCALE':
+            lScale = SparkleParticleRenderer.SPNOSCALE
+        else:
+            lScale = SparkleParticleRenderer.SPSCALE
+        self.particles.renderer.setLifeScale(lScale)
+    # Sprite #
+    def setSpriteSourceType(self):
+        if self.rendererSpriteSourceType.get() == SpriteParticleRenderer.STTexture:
+            self.rendererSpriteTextureEntry['state'] = 'normal'
+            self.rendererSpriteFileEntry['state'] = 'disabled'
+            self.rendererSpriteNodeEntry['state'] = 'disabled'
+            self.rendererSpriteTextureEntry['background'] = '#FFFFFF'
+            self.rendererSpriteFileEntry['background'] = '#C0C0C0'
+            self.rendererSpriteNodeEntry['background'] = '#C0C0C0'
+        else:
+            self.rendererSpriteTextureEntry['state'] = 'disabled'
+            self.rendererSpriteFileEntry['state'] = 'normal'
+            self.rendererSpriteNodeEntry['state'] = 'normal'
+            self.rendererSpriteTextureEntry['background'] = '#C0C0C0'
+            self.rendererSpriteFileEntry['background'] = '#FFFFFF'
+            self.rendererSpriteNodeEntry['background'] = '#FFFFFF'
+    def setRendererSpriteTexture(self):
+        if self.rendererSpriteSourceType.get() == SpriteParticleRenderer.STTexture:
+            self.particles.renderer.setTextureFromFile(self.rendererSpriteTexture.get())
+        else:
+            self.particles.renderer.setTextureFromNode(
+                self.rendererSpriteFile.get(), self.rendererSpriteNode.get())
+    def toggleRendererSpriteXScale(self):
+        self.particles.renderer.setXScaleFlag(
+            self.getVariable('Sprite Renderer', 'X Scale').get())
+    def toggleRendererSpriteYScale(self):
+        self.particles.renderer.setYScaleFlag(
+            self.getVariable('Sprite Renderer', 'Y Scale').get())
+    def toggleRendererSpriteAnimAngle(self):
+        self.particles.renderer.setAnimAngleFlag(
+            self.getVariable('Sprite Renderer', 'Anim Angle').get())
+
+    def toggleAngularVelocity(self):
+        self.particles.factory.enableAngularVelocity(
+            self.getVariable('Z Spin Factory', 'Enable Angular Velocity').get())
+
+    def setRendererSpriteInitialXScale(self, xScale):
+        self.particles.renderer.setInitialXScale(xScale)
+    def setRendererSpriteFinalXScale(self, xScale):
+        self.particles.renderer.setFinalXScale(xScale)
+    def setRendererSpriteInitialYScale(self, yScale):
+        self.particles.renderer.setInitialYScale(yScale)
+    def setRendererSpriteFinalYScale(self, yScale):
+        self.particles.renderer.setFinalYScale(yScale)
+    def setRendererSpriteNonAnimatedTheta(self, theta):
+        self.particles.renderer.setNonanimatedTheta(theta)
+    def setRendererSpriteBlendMethod(self, blendMethod):
+        print blendMethod
+        if blendMethod == 'PP_NO_BLEND':
+            bMethod = BaseParticleRenderer.PPNOBLEND
+        elif blendMethod == 'PP_BLEND_LINEAR':
+            bMethod = BaseParticleRenderer.PPBLENDLINEAR
+        elif blendMethod == 'PP_BLEND_CUBIC':
+            bMethod = BaseParticleRenderer.PPBLENDCUBIC
+        else:
+            bMethod = BaseParticleRenderer.PPNOBLEND
+        self.particles.renderer.setAlphaBlendMethod(bMethod)
+    def toggleRendererSpriteAlphaDisable(self):
+        self.particles.renderer.setAlphaDisable(
+            self.getVariable('Sprite Renderer', 'Alpha Disable').get())
+
+    ## FORCEGROUP COMMANDS ##
+    def updateForceWidgets(self):
+        # Select appropriate notebook page
+        if self.forceGroup != None:
+            self.forceGroupNotebook.pack(fill = X)
+            self.forcePageName = (self.particleEffect.getName() + '-' +
+                                  self.forceGroup.getName())
+            self.forcePage = self.forcePagesDict.get(
+                self.forcePageName, None)
+            # Page doesn't exist, add it
+            if self.forcePage == None:
+                self.addForceGroupNotebookPage(
+                    self.particleEffect, self.forceGroup)
+            self.forceGroupNotebook.selectpage(self.forcePageName)
+        else:
+            self.forceGroupNotebook.pack_forget()
+
+    def addLinearVectorForce(self):
+        self.addForce(LinearVectorForce())
+    def addLinearFrictionForce(self):
+        self.addForce(LinearFrictionForce())
+    def addLinearJitterForce(self):
+        self.addForce(LinearJitterForce())
+    def addLinearNoiseForce(self):
+        self.addForce(LinearNoiseForce())
+    def addLinearSinkForce(self):
+        self.addForce(LinearSinkForce())
+    def addLinearSourceForce(self):
+        self.addForce(LinearSourceForce())
+    def addLinearCylinderVortexForce(self):
+        self.addForce(LinearCylinderVortexForce())
+    def addLinearUserDefinedForce(self):
+        self.addForce(LinearUserDefinedForce())
+
+    def addForce(self, f):
+        if self.forceGroup == None:
+            self.createNewForceGroup()
+        self.forceGroup.addForce(f)
+        self.addForceWidget(self.forceGroup,f)
+
+    ## SYSTEM COMMANDS ##
+    def createNewEffect(self):
+        name = askstring('Particle Panel', 'Effect Name:',
+                         parent = self.parent)
+        if name:
+            particles = seParticles.Particles()
+            particles.setBirthRate(0.02)
+            particles.setLitterSize(10)
+            particles.setLitterSpread(0)
+            particles.setFactory("PointParticleFactory")
+            particles.setRenderer("PointParticleRenderer")
+            particles.setEmitter("SphereVolumeEmitter")
+            particles.enable()
+            effect = seParticleEffect.ParticleEffect(name,particles)
+            self.effectsDict[name] = effect
+            self.updateMenusAndLabels()
+            self.selectEffectNamed(name)
+            self.emitter=loader.loadModel("sphere")
+            self.emitter.setName(name)
+            effect.reparentTo(self.emitter)
+            self.emitter.reparentTo(render)
+            effect.enable()
+            messenger.send('ParticlePanel_Added_Effect',[name,effect,self.emitter])
+            messenger.send('SGE_Update Explorer',[render])
+
+    def createNewParticles(self):
+        name = askstring('Particle Panel', 'Particles Name:',
+                         parent = self.parent)
+        if name:
+            p = seParticles.Particles(name)
+            p.setBirthRate(0.02)
+            p.setLitterSize(10)
+            p.setLitterSpread(0)
+            p.setFactory("PointParticleFactory")
+            p.setRenderer("PointParticleRenderer")
+            p.setEmitter("SphereVolumeEmitter")
+            self.particleEffect.addParticles(p)
+            self.updateParticlesMenus()
+            self.selectParticlesNamed(name)
+            p.enable()
+
+    def createNewForceGroup(self):
+        name = askstring('Particle Panel', 'ForceGroup Name:',
+                         parent = self.parent)
+        if name:
+            forceGroup = seForceGroup.ForceGroup(name)
+            self.particleEffect.addForceGroup(forceGroup)
+            self.updateForceGroupMenus()
+            self.addForceGroupNotebookPage(self.particleEffect, forceGroup)
+            self.selectForceGroupNamed(name)
+            forceGroup.enable()
+
+    def addForceGroupNotebookPage(self, particleEffect, forceGroup):
+        self.forcePageName = (particleEffect.getName() + '-' +
+                              forceGroup.getName())
+        self.forcePage = self.forceGroupNotebook.add(self.forcePageName)
+        self.forcePagesDict[self.forcePageName] = self.forcePage
+        for force in forceGroup:
+            self.addForceWidget(forceGroup, force)
+
+    def addForceWidget(self, forceGroup, force):
+        forcePage = self.forcePage
+        pageName = self.forcePageName
+        # How many forces of the same type in the force group object
+        count = 0
+        for f in forceGroup:
+            if f.getClassType().eq(force.getClassType()):
+                count += 1
+        if isinstance(force, LinearVectorForce):
+            self.createLinearVectorForceWidget(
+                forcePage, pageName, count, force)
+        elif isinstance(force, LinearNoiseForce):
+            self.createLinearRandomForceWidget(
+                forcePage, pageName, count, force, 'Noise')
+        elif isinstance(force, LinearJitterForce):
+            self.createLinearRandomForceWidget(
+                forcePage, pageName, count, force, 'Jitter')
+        elif isinstance(force, LinearFrictionForce):
+            self.createLinearFrictionForceWidget(
+                forcePage, pageName, count, force)
+        elif isinstance(force, LinearCylinderVortexForce):
+            self.createLinearCylinderVortexForceWidget(
+                forcePage, pageName, count, force)
+        elif isinstance(force, LinearSinkForce):
+            self.createLinearDistanceForceWidget(
+                forcePage, pageName, count, force, 'Sink')
+        elif isinstance(force, LinearSourceForce):
+            self.createLinearDistanceForceWidget(
+                forcePage, pageName, count, force, 'Source')
+        elif isinstance(force, LinearUserDefinedForce):
+            # Nothing
+            pass
+        self.forceGroupNotebook.setnaturalsize()
+
+    def createForceFrame(self, forcePage, forceName, force):
+        frame = Frame(forcePage, relief = RAISED, borderwidth = 2)
+        lFrame = Frame(frame, relief = FLAT)
+        def removeForce(s = self, f = force, fr = frame):
+            s.forceGroup.removeForce(f)
+            fr.pack_forget()
+        b = Button(lFrame, text = 'X',
+                   command = removeForce)
+        b.pack(side = 'right', expand = 0)
+        Label(lFrame, text = forceName,
+              foreground = 'Blue',
+              font=('MSSansSerif', 12, 'bold'),
+              ).pack(expand = 1, fill = 'x')
+        lFrame.pack(fill = 'x', expand =1)
+        frame.pack(pady = 3, fill = 'x', expand =0)
+        return frame
+
+    def createLinearForceWidgets(self, frame, pageName, forceName, force):
+        def setAmplitude(amp, f = force):
+            f.setAmplitude(amp)
+        def toggleMassDependent(s=self, f=force, p=pageName, n=forceName):
+            v = s.getVariable(p, n+' Mass Dependent')
+            f.setMassDependent(v.get())
+        def setVectorMasks(s=self, f=force, p=pageName, n=forceName):
+            xMask = s.getVariable(p, n+' Mask X').get()
+            yMask = s.getVariable(p, n+' Mask Y').get()
+            zMask = s.getVariable(p, n+' Mask Z').get()
+            f.setVectorMasks(xMask, yMask, zMask)
+        self.createFloater(frame, pageName, forceName + ' Amplitude',
+                           'Force amplitude multiplier',
+                           command = setAmplitude,
+                           value = force.getAmplitude())
+        cbf = Frame(frame, relief = FLAT)
+        self.createCheckbutton(cbf, pageName, forceName + ' Mass Dependent',
+                               ('On: force depends on mass; ' +
+                                'Off: force does not depend on mass'),
+                               toggleMassDependent,
+                               force.getMassDependent())
+        self.createCheckbutton(cbf, pageName, forceName + ' Mask X',
+                               'On: enable force along X axis',
+                               setVectorMasks, 1)
+        self.createCheckbutton(cbf, pageName, forceName + ' Mask Y',
+                               'On: enable force along X axis',
+                               setVectorMasks, 1)
+        self.createCheckbutton(cbf, pageName, forceName + ' Mask Z',
+                               'On: enable force along X axis',
+                               setVectorMasks, 1)
+        cbf.pack(fill = 'x', expand = 0)
+
+    def createForceActiveWidget(self, frame, pageName, forceName, force):
+        cbName = forceName + ' Active'
+        def toggle(s = self, f = force, p = pageName, n = cbName):
+            s.toggleForce(f, p, n)
+        self.createCheckbutton(frame, pageName, cbName,
+                               'On: force is enabled; Off: force is disabled',
+                               toggle, 1)
+
+    def createLinearVectorForceWidget(self, forcePage, pageName,
+                                      count, force):
+        def setVec(vec, f = force):
+            f.setVector(vec[0], vec[1], vec[2])
+        forceName = 'Vector Force-' + `count`
+        frame = self.createForceFrame(forcePage, forceName, force)
+        self.createLinearForceWidgets(frame, pageName, forceName, force)
+        vec = force.getLocalVector()
+        self.createVector3Entry(frame, pageName, forceName,
+                                'Set force direction and magnitude',
+                                command = setVec,
+                                value = [vec[0], vec[1], vec[2]])
+        self.createForceActiveWidget(frame, pageName, forceName, force)
+
+    def createLinearRandomForceWidget(self, forcePage, pageName, count,
+                                force, type):
+        forceName = type + ' Force-' + `count`
+        frame = self.createForceFrame(forcePage, forceName, force)
+        self.createLinearForceWidgets(frame, pageName, forceName, force)
+        self.createForceActiveWidget(frame, pageName, forceName, force)
+
+    def createLinearFrictionForceWidget(self, forcePage, pageName,
+                                        count, force):
+        def setCoef(coef, f = force):
+            f.setCoef(coef)
+        forceName = 'Friction Force-' + `count`
+        frame = self.createForceFrame(forcePage, forceName, force)
+        self.createLinearForceWidgets(frame, pageName, forceName, force)
+        self.createFloater(frame, pageName, forceName + ' Coef',
+                           'Set linear friction force',
+                           command = setCoef, min = None,
+                           value = force.getCoef())
+        self.createForceActiveWidget(frame, pageName, forceName, force)
+
+    def createLinearCylinderVortexForceWidget(self, forcePage, pageName,
+                                              count, force):
+        forceName = 'Vortex Force-' + `count`
+        def setCoef(coef, f = force):
+            f.setCoef(coef)
+        def setLength(length, f = force):
+            f.setLength(length)
+        def setRadius(radius, f = force):
+            f.setRadius(radius)
+        frame = self.createForceFrame(forcePage, forceName, force)
+        self.createLinearForceWidgets(frame, pageName, forceName, force)
+        self.createFloater(frame, pageName, forceName + ' Coef',
+                           'Set linear cylinder vortex coefficient',
+                           command = setCoef,
+                           value = force.getCoef())
+        self.createFloater(frame, pageName, forceName + ' Length',
+                           'Set linear cylinder vortex length',
+                           command = setLength,
+                           value = force.getLength())
+        self.createFloater(frame, pageName, forceName + ' Radius',
+                           'Set linear cylinder vortex radius',
+                           command = setRadius,
+                           value = force.getRadius())
+        self.createForceActiveWidget(frame, pageName, forceName, force)
+
+    def createLinearDistanceForceWidget(self, forcePage, pageName,
+                                        count, force, type):
+        def setFalloffType(type, f=force):
+            if type == 'FT_ONE_OVER_R':
+                #f.setFalloffType(LinearDistanceForce.FTONEOVERR)
+                f.setFalloffType(0)
+            if type == 'FT_ONE_OVER_R_SQUARED':
+                #f.setFalloffType(LinearDistanceForce.FTONEOVERRSQUARED)
+                f.setFalloffType(1)
+            if type == 'FT_ONE_OVER_R_CUBED':
+                #f.setFalloffType(LinearDistanceForce.FTONEOVERRCUBED)
+                f.setFalloffType(2)
+        def setForceCenter(vec, f = force):
+            f.setForceCenter(Point3(vec[0], vec[1], vec[2]))
+        def setRadius(radius, f = force):
+            f.setRadius(radius)
+        forceName = type + ' Force-' + `count`
+        frame = self.createForceFrame(forcePage, forceName, force)
+        self.createLinearForceWidgets(frame, pageName, forceName, force)
+        var = self.createOptionMenu(
+            frame, pageName, forceName + ' Falloff',
+            'Set force falloff type',
+            ('FT_ONE_OVER_R',
+             'FT_ONE_OVER_R_SQUARED',
+             'FT_ONE_OVER_R_CUBED'),
+            command = setFalloffType)
+        self.getWidget(pageName, forceName + ' Falloff').configure(
+            label_width = 16)
+        falloff = force.getFalloffType()
+        if falloff == LinearDistanceForce.FTONEOVERR:
+            var.set('FT_ONE_OVER_R')
+        elif falloff == LinearDistanceForce.FTONEOVERRSQUARED:
+            var.set('FT_ONE_OVER_R_SQUARED')
+        elif falloff == LinearDistanceForce.FTONEOVERRCUBED:
+            var.set('FT_ONE_OVER_R_CUBED')
+        vec = force.getForceCenter()
+        self.createVector3Entry(frame, pageName, forceName + ' Center',
+                                'Set center of force',
+                                command = setForceCenter,
+                                label_width = 16,
+                                value = [vec[0], vec[1], vec[2]])
+        self.createFloater(frame, pageName, forceName + ' Radius',
+                           'Set falloff radius',
+                           command = setRadius,
+                           min = 0.01,
+                           value = force.getRadius())
+        self.createForceActiveWidget(frame, pageName, forceName, force)
+
+######################################################################
+
+# Create demo in root window for testing.
+if __name__ == '__main__':
+    root = Pmw.initialise()
+    pp = ParticlePanel()
+    #ve = VectorEntry(Toplevel(), relief = GROOVE)
+    #ve.pack()

+ 449 - 0
contrib/src/sceneeditor/seParticles.py

@@ -0,0 +1,449 @@
+from pandac.PandaModules import *
+from direct.particles.ParticleManagerGlobal import *
+from direct.showbase.PhysicsManagerGlobal import *
+#Manakel 2/12/2005: replace from pandac import by from pandac.PandaModules import
+from pandac.PandaModules import ParticleSystem
+from pandac.PandaModules import BaseParticleFactory
+from pandac.PandaModules import PointParticleFactory
+from pandac.PandaModules import ZSpinParticleFactory
+#import OrientedParticleFactory
+from pandac.PandaModules import BaseParticleRenderer
+from pandac.PandaModules import PointParticleRenderer
+from pandac.PandaModules import LineParticleRenderer
+from pandac.PandaModules import GeomParticleRenderer
+from pandac.PandaModules import SparkleParticleRenderer
+from pandac.PandaModules import SpriteParticleRenderer
+from pandac.PandaModules import BaseParticleEmitter
+from pandac.PandaModules import BoxEmitter
+from pandac.PandaModules import DiscEmitter
+from pandac.PandaModules import LineEmitter
+from pandac.PandaModules import PointEmitter
+from pandac.PandaModules import RectangleEmitter
+from pandac.PandaModules import RingEmitter
+from pandac.PandaModules import SphereSurfaceEmitter
+from pandac.PandaModules import SphereVolumeEmitter
+from pandac.PandaModules import TangentRingEmitter
+import string
+import os
+from direct.directnotify import DirectNotifyGlobal
+import sys
+
+class Particles(ParticleSystem.ParticleSystem):
+
+    notify = DirectNotifyGlobal.directNotify.newCategory('Particles')
+    id = 1
+
+    def __init__(self, name=None, poolSize=1024):
+        """__init__(name, poolSize)"""
+
+        if (name == None):
+            self.name = 'particles-%d' % Particles.id
+            Particles.id += 1
+        else:
+            self.name = name
+        ParticleSystem.ParticleSystem.__init__(self, poolSize)
+        # self.setBirthRate(0.02)
+        # self.setLitterSize(10)
+        # self.setLitterSpread(0)
+
+        # Set up a physical node
+        self.node = PhysicalNode(self.name)
+        self.nodePath = NodePath(self.node)
+        self.setRenderParent(self.node)
+        self.node.addPhysical(self)
+
+        self.factory = None
+        self.factoryType = "undefined"
+        # self.setFactory("PointParticleFactory")
+        self.renderer = None
+        self.rendererType = "undefined"
+        # self.setRenderer("PointParticleRenderer")
+        self.emitter = None
+        self.emitterType = "undefined"
+        # self.setEmitter("SphereVolumeEmitter")
+
+        # Enable particles by default
+        self.fEnabled = 0
+        #self.enable()
+
+    def cleanup(self):
+        self.disable()
+        self.clearLinearForces()
+        self.clearAngularForces()
+        self.setRenderParent(self.node)
+        self.node.removePhysical(self)
+        self.nodePath.removeNode()
+        del self.node
+        del self.nodePath
+        del self.factory
+        del self.renderer
+        del self.emitter
+
+    def enable(self):
+        """enable()"""
+        if (self.fEnabled == 0):
+            physicsMgr.attachPhysical(self)
+            particleMgr.attachParticlesystem(self)
+            self.fEnabled = 1
+
+    def disable(self):
+        """disable()"""
+        if (self.fEnabled == 1):
+            physicsMgr.removePhysical(self)
+            particleMgr.removeParticlesystem(self)
+            self.fEnabled = 0
+
+    def isEnabled(self):
+        return self.fEnabled
+
+    def getNode(self):
+        return self.node
+
+    def setFactory(self, type):
+        """setFactory(type)"""
+        if (self.factoryType == type):
+            return None
+        if (self.factory):
+            self.factory = None
+        self.factoryType = type
+        if (type == "PointParticleFactory"):
+            self.factory = PointParticleFactory.PointParticleFactory()
+        elif (type == "ZSpinParticleFactory"):
+            self.factory = ZSpinParticleFactory.ZSpinParticleFactory()
+        elif (type == "OrientedParticleFactory"):
+            self.factory = OrientedParticleFactory.OrientedParticleFactory()
+        else:
+            print "unknown factory type: %s" % type
+            return None
+        self.factory.setLifespanBase(0.5)
+        ParticleSystem.ParticleSystem.setFactory(self, self.factory)
+
+    def setRenderer(self, type):
+        """setRenderer(type)"""
+        if (self.rendererType == type):
+            return None
+        if (self.renderer):
+            self.renderer = None
+        self.rendererType = type
+        if (type == "PointParticleRenderer"):
+            self.renderer = PointParticleRenderer.PointParticleRenderer()
+            self.renderer.setPointSize(1.0)
+        elif (type == "LineParticleRenderer"):
+            self.renderer = LineParticleRenderer.LineParticleRenderer()
+        elif (type == "GeomParticleRenderer"):
+            self.renderer = GeomParticleRenderer.GeomParticleRenderer()
+            npath = NodePath('default-geom')
+            # This was moved here because we do not want to download
+            # the direct tools with toontown.
+            from direct.directtools import DirectSelection
+            bbox = DirectSelection.DirectBoundingBox(npath)
+            self.renderer.setGeomNode(bbox.lines.node())
+        elif (type == "SparkleParticleRenderer"):
+            self.renderer = SparkleParticleRenderer.SparkleParticleRenderer()
+        elif (type == "SpriteParticleRenderer"):
+            self.renderer = SpriteParticleRenderer.SpriteParticleRenderer()
+            if (self.renderer.getSourceType() ==
+                SpriteParticleRenderer.SpriteParticleRenderer.STTexture):
+                # Use current default texture 
+                # See sourceTextureName SpriteParticleRenderer-extensions.py
+                self.renderer.setTextureFromFile()
+            else:
+                # Use current default model file and node
+                # See sourceFileName and sourceNodeName in SpriteParticleRenderer-extensions.py
+                self.renderer.setTextureFromNode()
+        else:
+            print "unknown renderer type: %s" % type
+            return None
+        ParticleSystem.ParticleSystem.setRenderer(self, self.renderer)
+
+    def setEmitter(self, type):
+        """setEmitter(type)"""
+        if (self.emitterType == type):
+            return None
+        if (self.emitter):
+            self.emitter = None
+        self.emitterType = type
+        if (type == "BoxEmitter"):
+            self.emitter = BoxEmitter.BoxEmitter()
+        elif (type == "DiscEmitter"):
+            self.emitter = DiscEmitter.DiscEmitter()
+        elif (type == "LineEmitter"):
+            self.emitter = LineEmitter.LineEmitter()
+        elif (type == "PointEmitter"):
+            self.emitter = PointEmitter.PointEmitter()
+        elif (type == "RectangleEmitter"):
+            self.emitter = RectangleEmitter.RectangleEmitter()
+        elif (type == "RingEmitter"):
+            self.emitter = RingEmitter.RingEmitter()
+        elif (type == "SphereSurfaceEmitter"):
+            self.emitter = SphereSurfaceEmitter.SphereSurfaceEmitter()
+        elif (type == "SphereVolumeEmitter"):
+            self.emitter = SphereVolumeEmitter.SphereVolumeEmitter()
+            self.emitter.setRadius(1.0)
+        elif (type == "TangentRingEmitter"):
+            self.emitter = TangentRingEmitter.TangentRingEmitter()
+        else:
+            print "unknown emitter type: %s" % type
+            return None
+        ParticleSystem.ParticleSystem.setEmitter(self, self.emitter)
+
+    def addForce(self, force):
+        """addForce(force)"""
+        if (force.isLinear()):
+            self.addLinearForce(force)
+        else:
+            self.addAngularForce(force)
+
+    def removeForce(self, force):
+        """removeForce(force)"""
+        if (force == None):
+            self.notify.warning('removeForce() - force == None!')
+            return
+        if (force.isLinear()):
+            self.removeLinearForce(force)
+        else:
+            self.removeAngularForce(force)
+
+    def setRenderNodePath(self, nodePath):
+        self.setRenderParent(nodePath.node())
+
+    ## Getters ##
+    def getName(self):
+        """getName()"""
+        return self.name
+    def getFactory(self):
+        """getFactory()"""
+        return self.factory
+    def getEmitter(self):
+        """getEmitter()"""
+        return self.emitter
+    def getRenderer(self):
+        """getRenderer()"""
+        return self.renderer
+
+    def printParams(self, file = sys.stdout, targ = 'self'):
+        """printParams(file, targ)"""
+        i1="    "
+        i2=i1+i1
+        file.write(i2+'# Particles parameters\n')
+        file.write(i2+targ + '.setFactory(\"' + self.factoryType + '\")\n')
+        file.write(i2+targ + '.setRenderer(\"' + self.rendererType + '\")\n')
+        file.write(i2+targ + '.setEmitter(\"' + self.emitterType + '\")\n')
+
+        # System parameters
+        file.write(i2+targ + ('.setPoolSize(%d)\n' %
+                           int(self.getPoolSize())))
+        file.write(i2+targ + ('.setBirthRate(%.4f)\n' %
+                           self.getBirthRate()))
+        file.write(i2+targ + ('.setLitterSize(%d)\n' %
+                           int(self.getLitterSize())))
+        file.write(i2+targ + ('.setLitterSpread(%d)\n' %
+                           self.getLitterSpread()))
+        file.write(i2+targ + ('.setSystemLifespan(%.4f)\n' %
+                           self.getSystemLifespan()))
+        file.write(i2+targ + ('.setLocalVelocityFlag(%d)\n' %
+                           self.getLocalVelocityFlag()))
+        file.write(i2+targ + ('.setSystemGrowsOlderFlag(%d)\n' %
+                           self.getSystemGrowsOlderFlag()))
+        file.write(i2+'# Factory parameters\n')
+        file.write(i2+targ + ('.factory.setLifespanBase(%.4f)\n' %
+                           self.factory.getLifespanBase()))
+        file.write(i2+targ + '.factory.setLifespanSpread(%.4f)\n' % \
+                                self.factory.getLifespanSpread())
+        file.write(i2+targ + '.factory.setMassBase(%.4f)\n' % \
+                                self.factory.getMassBase())
+        file.write(i2+targ + '.factory.setMassSpread(%.4f)\n' % \
+                                self.factory.getMassSpread())
+        file.write(i2+targ + '.factory.setTerminalVelocityBase(%.4f)\n' % \
+                                self.factory.getTerminalVelocityBase())
+        file.write(i2+targ + '.factory.setTerminalVelocitySpread(%.4f)\n' % \
+                                self.factory.getTerminalVelocitySpread())
+        if (self.factoryType == "PointParticleFactory"):
+            file.write(i2+'# Point factory parameters\n')
+        elif (self.factoryType == "ZSpinParticleFactory"):
+            file.write(i2+'# Z Spin factory parameters\n')
+            file.write(i2+targ + '.factory.setInitialAngle(%.4f)\n' % \
+                                        self.factory.getInitialAngle())
+            file.write(i2+targ + '.factory.setInitialAngleSpread(%.4f)\n' % \
+                                        self.factory.getInitialAngleSpread())
+            file.write(i2+targ + '.factory.enableAngularVelocity(%d)\n' % \
+                                        self.factory.getAngularVelocityEnabled())
+            if(self.factory.getAngularVelocityEnabled()):
+                file.write(i2+targ + '.factory.setAngularVelocity(%.4f)\n' % \
+                                            self.factory.getAngularVelocity())
+                file.write(i2+targ + '.factory.setAngularVelocitySpread(%.4f)\n' % \
+                                            self.factory.getAngularVelocitySpread())
+            else:
+                file.write(i2+targ + '.factory.setFinalAngle(%.4f)\n' % \
+                                            self.factory.getFinalAngle())
+                file.write(i2+targ + '.factory.setFinalAngleSpread(%.4f)\n' % \
+                                        self.factory.getFinalAngleSpread())
+
+        elif (self.factoryType == "OrientedParticleFactory"):
+            file.write(i2+'# Oriented factory parameters\n')
+            file.write(i2+targ + '.factory.setInitialOrientation(%.4f)\n' % \
+                                        self.factory.getInitialOrientation())
+            file.write(i2+targ + '.factory.setFinalOrientation(%.4f)\n' % \
+                                        self.factory.getFinalOrientation())
+
+        file.write(i2+'# Renderer parameters\n')
+        alphaMode = self.renderer.getAlphaMode()
+        aMode = "PRALPHANONE"
+        if (alphaMode == BaseParticleRenderer.BaseParticleRenderer.PRALPHANONE):
+            aMode = "PRALPHANONE"
+        elif (alphaMode ==
+                BaseParticleRenderer.BaseParticleRenderer.PRALPHAOUT):
+            aMode = "PRALPHAOUT"
+        elif (alphaMode ==
+                BaseParticleRenderer.BaseParticleRenderer.PRALPHAIN):
+            aMode = "PRALPHAIN"
+        elif (alphaMode ==
+                BaseParticleRenderer.BaseParticleRenderer.PRALPHAUSER):
+            aMode = "PRALPHAUSER"
+        file.write(i2+targ + '.renderer.setAlphaMode(BaseParticleRenderer.' + aMode + ')\n')
+        file.write(i2+targ + '.renderer.setUserAlpha(%.2f)\n' % \
+                                        self.renderer.getUserAlpha())
+        if (self.rendererType == "PointParticleRenderer"):
+            file.write(i2+'# Point parameters\n')
+            file.write(i2+targ + '.renderer.setPointSize(%.2f)\n' % \
+                                        self.renderer.getPointSize())
+            sColor = self.renderer.getStartColor()
+            file.write(i2+(targ + '.renderer.setStartColor(Vec4(%.2f, %.2f, %.2f, %.2f))\n' % (sColor[0], sColor[1], sColor[2], sColor[3])))
+            sColor = self.renderer.getEndColor()
+            file.write(i2+(targ + '.renderer.setEndColor(Vec4(%.2f, %.2f, %.2f, %.2f))\n' % (sColor[0], sColor[1], sColor[2], sColor[3])))
+            blendType = self.renderer.getBlendType()
+            bType = "PPONECOLOR"
+            if (blendType == PointParticleRenderer.PointParticleRenderer.PPONECOLOR):
+                bType = "PPONECOLOR"
+            elif (blendType == PointParticleRenderer.PointParticleRenderer.PPBLENDLIFE):
+                bType = "PPBLENDLIFE"
+            elif (blendType == PointParticleRenderer.PointParticleRenderer.PPBLENDVEL):
+                bType = "PPBLENDVEL"
+            file.write(i2+targ + '.renderer.setBlendType(PointParticleRenderer.' + bType + ')\n')
+            blendMethod = self.renderer.getBlendMethod()
+            bMethod = "PPNOBLEND"
+            if (blendMethod == BaseParticleRenderer.BaseParticleRenderer.PPNOBLEND):
+                bMethod = "PPNOBLEND"
+            elif (blendMethod == BaseParticleRenderer.BaseParticleRenderer.PPBLENDLINEAR):
+                bMethod = "PPBLENDLINEAR"
+            elif (blendMethod == BaseParticleRenderer.BaseParticleRenderer.PPBLENDCUBIC):
+                bMethod = "PPBLENDCUBIC"
+            file.write(i2+targ + '.renderer.setBlendMethod(BaseParticleRenderer.' + bMethod + ')\n')
+        elif (self.rendererType == "LineParticleRenderer"):
+            file.write(i2+'# Line parameters\n')
+            sColor = self.renderer.getHeadColor()
+            file.write(i2+(targ + '.renderer.setHeadColor(Vec4(%.2f, %.2f, %.2f, %.2f))\n' % (sColor[0], sColor[1], sColor[2], sColor[3])))
+            sColor = self.renderer.getTailColor()
+            file.write(i2+(targ + '.renderer.setTailColor(Vec4(%.2f, %.2f, %.2f, %.2f))\n' % (sColor[0], sColor[1], sColor[2], sColor[3])))
+        elif (self.rendererType == "GeomParticleRenderer"):
+            file.write(i2+'# Geom parameters\n')
+            node = self.renderer.getGeomNode()
+            file.write(i2+targ + '.renderer.setGeomNode(' + node.getName() + ')\n')
+        elif (self.rendererType == "SparkleParticleRenderer"):
+            file.write(i2+'# Sparkle parameters\n')
+            sColor = self.renderer.getCenterColor()
+            file.write(i2+(targ + '.renderer.setCenterColor(Vec4(%.2f, %.2f, %.2f, %.2f))\n' % (sColor[0], sColor[1], sColor[2], sColor[3])))
+            sColor = self.renderer.getEdgeColor()
+            file.write(i2+(targ + '.renderer.setEdgeColor(Vec4(%.2f, %.2f, %.2f, %.2f))\n' % (sColor[0], sColor[1], sColor[2], sColor[3])))
+            file.write(i2+targ + '.renderer.setBirthRadius(%.4f)\n' % self.renderer.getBirthRadius())
+            file.write(i2+targ + '.renderer.setDeathRadius(%.4f)\n' % self.renderer.getDeathRadius())
+            lifeScale = self.renderer.getLifeScale()
+            lScale = "SPNOSCALE"
+            if (lifeScale == SparkleParticleRenderer.SparkleParticleRenderer.SPSCALE):
+                lScale = "SPSCALE"
+            file.write(i2+targ + '.renderer.setLifeScale(SparkleParticleRenderer.' + lScale + ')\n')
+        elif (self.rendererType == "SpriteParticleRenderer"):
+            file.write(i2+'# Sprite parameters\n')
+            if (self.renderer.getSourceType() ==
+                SpriteParticleRenderer.SpriteParticleRenderer.STTexture):
+                tex = self.renderer.getTexture()
+                file.write(i2+targ + '.renderer.setTexture(loader.loadTexture(\'' + tex.getFilename().getFullpath() + '\'))\n')
+            else:
+                modelName = self.renderer.getSourceFileName()
+                nodeName = self.renderer.getSourceNodeName()
+                file.write(i2+targ + '.renderer.setTextureFromNode("%s", "%s")\n' % (modelName, nodeName))
+            sColor = self.renderer.getColor()
+            file.write(i2+(targ + '.renderer.setColor(Vec4(%.2f, %.2f, %.2f, %.2f))\n' % (sColor[0], sColor[1], sColor[2], sColor[3])))
+            file.write(i2+targ + '.renderer.setXScaleFlag(%d)\n' % self.renderer.getXScaleFlag())
+            file.write(i2+targ + '.renderer.setYScaleFlag(%d)\n' % self.renderer.getYScaleFlag())
+            file.write(i2+targ + '.renderer.setAnimAngleFlag(%d)\n' % self.renderer.getAnimAngleFlag())
+            file.write(i2+targ + '.renderer.setInitialXScale(%.4f)\n' % self.renderer.getInitialXScale())
+            file.write(i2+targ + '.renderer.setFinalXScale(%.4f)\n' % self.renderer.getFinalXScale())
+            file.write(i2+targ + '.renderer.setInitialYScale(%.4f)\n' % self.renderer.getInitialYScale())
+            file.write(i2+targ + '.renderer.setFinalYScale(%.4f)\n' % self.renderer.getFinalYScale())
+            file.write(i2+targ + '.renderer.setNonanimatedTheta(%.4f)\n' % self.renderer.getNonanimatedTheta())
+            blendMethod = self.renderer.getAlphaBlendMethod()
+            bMethod = "PPNOBLEND"
+            if (blendMethod == BaseParticleRenderer.BaseParticleRenderer.PPNOBLEND):
+                bMethod = "PPNOBLEND"
+            elif (blendMethod == BaseParticleRenderer.BaseParticleRenderer.PPBLENDLINEAR):
+                bMethod = "PPBLENDLINEAR"
+            elif (blendMethod == BaseParticleRenderer.BaseParticleRenderer.PPBLENDCUBIC):
+                bMethod = "PPBLENDCUBIC"
+            file.write(i2+targ + '.renderer.setAlphaBlendMethod(BaseParticleRenderer.' + bMethod + ')\n')
+            file.write(i2+targ + '.renderer.setAlphaDisable(%d)\n' % self.renderer.getAlphaDisable())
+
+        file.write(i2+'# Emitter parameters\n')
+        emissionType = self.emitter.getEmissionType()
+        eType = "ETEXPLICIT"
+        if (emissionType == BaseParticleEmitter.BaseParticleEmitter.ETEXPLICIT):
+            eType = "ETEXPLICIT"
+        elif (emissionType == BaseParticleEmitter.BaseParticleEmitter.ETRADIATE):
+            eType = "ETRADIATE"
+        elif (emissionType == BaseParticleEmitter.BaseParticleEmitter.ETCUSTOM):
+            eType = "ETCUSTOM"
+        file.write(i2+targ + '.emitter.setEmissionType(BaseParticleEmitter.' + eType + ')\n')
+        file.write(i2+targ + '.emitter.setAmplitude(%.4f)\n' % self.emitter.getAmplitude())
+        file.write(i2+targ + '.emitter.setAmplitudeSpread(%.4f)\n' % self.emitter.getAmplitudeSpread())
+        oForce = self.emitter.getOffsetForce()
+        file.write(i2+(targ + '.emitter.setOffsetForce(Vec3(%.4f, %.4f, %.4f))\n' % (oForce[0], oForce[1], oForce[2])))
+        oForce = self.emitter.getExplicitLaunchVector()
+        file.write(i2+(targ + '.emitter.setExplicitLaunchVector(Vec3(%.4f, %.4f, %.4f))\n' % (oForce[0], oForce[1], oForce[2])))
+        orig = self.emitter.getRadiateOrigin()
+        file.write(i2+(targ + '.emitter.setRadiateOrigin(Point3(%.4f, %.4f, %.4f))\n' % (orig[0], orig[1], orig[2])))
+        if (self.emitterType == "BoxEmitter"):
+            file.write(i2+'# Box parameters\n')
+            bound = self.emitter.getMinBound()
+            file.write(i2+(targ + '.emitter.setMinBound(Point3(%.4f, %.4f, %.4f))\n' % (bound[0], bound[1], bound[2])))
+            bound = self.emitter.getMaxBound()
+            file.write(i2+(targ + '.emitter.setMaxBound(Point3(%.4f, %.4f, %.4f))\n' % (bound[0], bound[1], bound[2])))
+        elif (self.emitterType == "DiscEmitter"):
+            file.write(i2+'# Disc parameters\n')
+            file.write(i2+targ + '.emitter.setRadius(%.4f)\n' % self.emitter.getRadius())
+            if (eType == "ETCUSTOM"):
+                file.write(i2+targ + '.emitter.setOuterAngle(%.4f)\n' % self.emitter.getOuterAngle())
+                file.write(i2+targ + '.emitter.setInnerAngle(%.4f)\n' % self.emitter.getInnerAngle())
+                file.write(i2+targ + '.emitter.setOuterMagnitude(%.4f)\n' % self.emitter.getOuterMagnitude())
+                file.write(i2+targ + '.emitter.setInnerMagnitude(%.4f)\n' % self.emitter.getInnerMagnitude())
+                file.write(i2+targ + '.emitter.setCubicLerping(%d)\n' % self.emitter.getCubicLerping())
+
+        elif (self.emitterType == "LineEmitter"):
+            file.write(i2+'# Line parameters\n')
+            point = self.emitter.getEndpoint1()
+            file.write(i2+(targ + '.emitter.setEndpoint1(Point3(%.4f, %.4f, %.4f))\n' % (point[0], point[1], point[2])))
+            point = self.emitter.getEndpoint2()
+            file.write(i2+(targ + '.emitter.setEndpoint2(Point3(%.4f, %.4f, %.4f))\n' % (point[0], point[1], point[2])))
+        elif (self.emitterType == "PointEmitter"):
+            file.write(i2+'# Point parameters\n')
+            point = self.emitter.getLocation()
+            file.write(i2+(targ + '.emitter.setLocation(Point3(%.4f, %.4f, %.4f))\n' % (point[0], point[1], point[2])))
+        elif (self.emitterType == "RectangleEmitter"):
+            file.write(i2+'# Rectangle parameters\n')
+            point = self.emitter.getMinBound()
+            file.write(i2+(targ + '.emitter.setMinBound(Point2(%.4f, %.4f))\n' % (point[0], point[1])))
+            point = self.emitter.getMaxBound()
+            file.write(i2+(targ + '.emitter.setMaxBound(Point2(%.4f, %.4f))\n' % (point[0], point[1])))
+        elif (self.emitterType == "RingEmitter"):
+            file.write(i2+'# Ring parameters\n')
+            file.write(i2+targ + '.emitter.setRadius(%.4f)\n' % self.emitter.getRadius())
+            if (eType == "ETCUSTOM"):
+                file.write(i2+targ + '.emitter.setAngle(%.4f)\n' % self.emitter.getAngle())
+        elif (self.emitterType == "SphereSurfaceEmitter"):
+            file.write(i2+'# Sphere Surface parameters\n')
+            file.write(i2+targ + '.emitter.setRadius(%.4f)\n' % self.emitter.getRadius())
+        elif (self.emitterType == "SphereVolumeEmitter"):
+            file.write(i2+'# Sphere Volume parameters\n')
+            file.write(i2+targ + '.emitter.setRadius(%.4f)\n' % self.emitter.getRadius())
+        elif (self.emitterType == "TangentRingEmitter"):
+            file.write(i2+'# Tangent Ring parameters\n')
+            file.write(i2+targ + '.emitter.setRadius(%.4f)\n' % self.emitter.getRadius())

+ 800 - 0
contrib/src/sceneeditor/sePlacer.py

@@ -0,0 +1,800 @@
+""" DIRECT Nine DoF Manipulation Panel """
+
+from direct.showbase.DirectObject import DirectObject
+from direct.directtools.DirectGlobals import *
+from direct.tkwidgets.AppShell import AppShell
+from direct.tkwidgets.Dial import AngleDial
+from direct.tkwidgets.Floater import Floater
+from Tkinter import Button, Menubutton, Menu, StringVar
+from pandac.PandaModules import *
+import Tkinter, Pmw
+"""
+TODO:
+Task to monitor pose
+"""
+
+class Placer(AppShell):
+    # Override class variables here
+    appname = 'Placer Panel'
+    frameWidth      = 625
+    frameHeight     = 215
+    usecommandarea = 0
+    usestatusarea  = 0
+
+    def __init__(self, parent = None, **kw):
+        INITOPT = Pmw.INITOPT
+        optiondefs = (
+            ('title',       self.appname,       None),
+            ('nodePath',    SEditor.camera,      None),
+            )
+        self.defineoptions(kw, optiondefs)
+
+        # Call superclass initialization function
+        AppShell.__init__(self)
+        
+        self.initialiseoptions(Placer)
+
+        # Accept the message from sceneEditor to update the information about the target nodePath
+        self.accept('placerUpdate', self.updatePlacer)
+
+    def appInit(self):
+        # Initialize state
+        self.tempCS = SEditor.group.attachNewNode('placerTempCS')
+        self.orbitFromCS = SEditor.group.attachNewNode(
+            'placerOrbitFromCS')
+        self.orbitToCS = SEditor.group.attachNewNode('placerOrbitToCS')
+        self.refCS = self.tempCS
+        
+        # Dictionary keeping track of all node paths manipulated so far
+        self.nodePathDict = {}
+        self.nodePathDict['camera'] = SEditor.camera
+        self.nodePathDict['widget'] = SEditor.widget
+        self.nodePathNames = ['camera', 'widget', 'selected']
+
+        self.refNodePathDict = {}
+        self.refNodePathDict['parent'] = self['nodePath'].getParent()
+        self.refNodePathDict['render'] = render
+        self.refNodePathDict['camera'] = SEditor.camera
+        self.refNodePathDict['widget'] = SEditor.widget
+        self.refNodePathNames = ['parent', 'self', 'render',
+                                 'camera', 'widget', 'selected']
+
+        # Initial state
+        self.initPos = Vec3(0)
+        self.initHpr = Vec3(0)
+        self.initScale = Vec3(1)
+        self.deltaHpr = Vec3(0)
+
+        # Offset for orbital mode
+        self.posOffset = Vec3(0)
+
+        # Set up event hooks
+        self.undoEvents = [('DIRECT_undo', self.undoHook),
+                           ('DIRECT_pushUndo', self.pushUndoHook),
+                           ('DIRECT_undoListEmpty', self.undoListEmptyHook),
+                           ('DIRECT_redo', self.redoHook),
+                           ('DIRECT_pushRedo', self.pushRedoHook),
+                           ('DIRECT_redoListEmpty', self.redoListEmptyHook)]
+        for event, method in self.undoEvents:
+            self.accept(event, method)
+
+        # Init movement mode
+        self.movementMode = 'Relative To:'
+
+    def createInterface(self):
+        # The interior of the toplevel panel
+        interior = self.interior()
+        interior['relief'] = Tkinter.FLAT
+        # Add placer commands to menubar
+        self.menuBar.addmenu('Placer', 'Placer Panel Operations')
+        self.menuBar.addmenuitem('Placer', 'command',
+                            'Zero Node Path',
+                            label = 'Zero All',
+                            command = self.zeroAll)
+        self.menuBar.addmenuitem('Placer', 'command',
+                            'Reset Node Path',
+                            label = 'Reset All',
+                            command = self.resetAll)
+        self.menuBar.addmenuitem('Placer', 'command',
+                            'Print Node Path Info',
+                            label = 'Print Info',
+                            command = self.printNodePathInfo)
+        self.menuBar.addmenuitem(
+            'Placer', 'command',
+            'Toggle widget visability',
+            label = 'Toggle Widget Vis',
+            command = SEditor.toggleWidgetVis)
+        self.menuBar.addmenuitem(
+            'Placer', 'command',
+            'Toggle widget manipulation mode',
+            label = 'Toggle Widget Mode',
+            command = SEditor.manipulationControl.toggleObjectHandlesMode)
+        
+        # Get a handle to the menu frame
+        menuFrame = self.menuFrame
+        self.nodePathMenu = Pmw.ComboBox(
+            menuFrame, labelpos = Tkinter.W, label_text = 'Node Path:',
+            entry_width = 20,
+            selectioncommand = self.selectNodePathNamed,
+            scrolledlist_items = self.nodePathNames)
+        self.nodePathMenu.selectitem('selected')
+        self.nodePathMenuEntry = (
+            self.nodePathMenu.component('entryfield_entry'))
+        self.nodePathMenuBG = (
+            self.nodePathMenuEntry.configure('background')[3])
+        self.nodePathMenu.pack(side = 'left', fill = 'x', expand = 1)
+        self.bind(self.nodePathMenu, 'Select node path to manipulate')
+
+        modeMenu = Pmw.OptionMenu(menuFrame,
+                                  items = ('Relative To:',
+                                           'Orbit:'),
+                                  initialitem = 'Relative To:',
+                                  command = self.setMovementMode,
+                                  menubutton_width = 8)
+        modeMenu.pack(side = 'left', expand = 0)
+        self.bind(modeMenu, 'Select manipulation mode')
+        
+        self.refNodePathMenu = Pmw.ComboBox(
+            menuFrame, entry_width = 16,
+            selectioncommand = self.selectRefNodePathNamed,
+            scrolledlist_items = self.refNodePathNames)
+        self.refNodePathMenu.selectitem('parent')
+        self.refNodePathMenuEntry = (
+            self.refNodePathMenu.component('entryfield_entry'))
+        self.refNodePathMenu.pack(side = 'left', fill = 'x', expand = 1)
+        self.bind(self.refNodePathMenu, 'Select relative node path')
+
+        self.undoButton = Button(menuFrame, text = 'Undo',
+                                 command = SEditor.undo)
+        if SEditor.undoList:
+            self.undoButton['state'] = 'normal'
+        else:
+            self.undoButton['state'] = 'disabled'
+        self.undoButton.pack(side = 'left', expand = 0)
+        self.bind(self.undoButton, 'Undo last operation')
+
+        self.redoButton = Button(menuFrame, text = 'Redo',
+                                 command = SEditor.redo)
+        if SEditor.redoList:
+            self.redoButton['state'] = 'normal'
+        else:
+            self.redoButton['state'] = 'disabled'
+        self.redoButton.pack(side = 'left', expand = 0)
+        self.bind(self.redoButton, 'Redo last operation')
+
+        # Create and pack the Pos Controls
+        posGroup = Pmw.Group(interior,
+                             tag_pyclass = Menubutton,
+                             tag_text = 'Position',
+                             tag_font=('MSSansSerif', 14),
+                             tag_activebackground = '#909090',
+                             ring_relief = Tkinter.RIDGE)
+        posMenubutton = posGroup.component('tag')
+        self.bind(posMenubutton, 'Position menu operations')
+        posMenu = Menu(posMenubutton, tearoff = 0)
+        posMenu.add_command(label = 'Set to zero', command = self.zeroPos)
+        posMenu.add_command(label = 'Reset initial',
+                            command = self.resetPos)
+        posMenubutton['menu'] = posMenu
+        posGroup.pack(side='left', fill = 'both', expand = 1)
+        posInterior = posGroup.interior()
+
+        # Create the dials
+        self.posX = self.createcomponent('posX', (), None,
+                                         Floater, (posInterior,),
+                                         text = 'X', relief = Tkinter.FLAT,
+                                         value = 0.0,
+                                         label_foreground = 'Red')
+        self.posX['commandData'] = ['x']
+        self.posX['preCallback'] = self.xformStart
+        self.posX['postCallback'] = self.xformStop
+        self.posX['callbackData'] = ['x']
+        self.posX.pack(expand=1,fill='both')
+        
+        self.posY = self.createcomponent('posY', (), None,
+                                         Floater, (posInterior,),
+                                         text = 'Y', relief = Tkinter.FLAT,
+                                         value = 0.0,
+                                         label_foreground = '#00A000')
+        self.posY['commandData'] = ['y']
+        self.posY['preCallback'] = self.xformStart
+        self.posY['postCallback'] = self.xformStop
+        self.posY['callbackData'] = ['y']
+        self.posY.pack(expand=1,fill='both')
+        
+        self.posZ = self.createcomponent('posZ', (), None,
+                                         Floater, (posInterior,),
+                                         text = 'Z', relief = Tkinter.FLAT,
+                                         value = 0.0,
+                                         label_foreground = 'Blue')
+        self.posZ['commandData'] = ['z']
+        self.posZ['preCallback'] = self.xformStart
+        self.posZ['postCallback'] = self.xformStop
+        self.posZ['callbackData'] = ['z']
+        self.posZ.pack(expand=1,fill='both')
+
+        # Create and pack the Hpr Controls
+        hprGroup = Pmw.Group(interior,
+                             tag_pyclass = Menubutton,
+                             tag_text = 'Orientation',
+                             tag_font=('MSSansSerif', 14),
+                             tag_activebackground = '#909090',
+                             ring_relief = Tkinter.RIDGE)
+        hprMenubutton = hprGroup.component('tag')
+        self.bind(hprMenubutton, 'Orientation menu operations')
+        hprMenu = Menu(hprMenubutton, tearoff = 0)
+        hprMenu.add_command(label = 'Set to zero', command = self.zeroHpr)
+        hprMenu.add_command(label = 'Reset initial', command = self.resetHpr)
+        hprMenubutton['menu'] = hprMenu
+        hprGroup.pack(side='left',fill = 'both', expand = 1)
+        hprInterior = hprGroup.interior()
+        
+        # Create the dials
+        self.hprH = self.createcomponent('hprH', (), None,
+                                         AngleDial, (hprInterior,),
+                                         style = 'mini',
+                                         text = 'H', value = 0.0,
+                                         relief = Tkinter.FLAT,
+                                         label_foreground = 'blue')
+        self.hprH['commandData'] = ['h']
+        self.hprH['preCallback'] = self.xformStart
+        self.hprH['postCallback'] = self.xformStop
+        self.hprH['callbackData'] = ['h']
+        self.hprH.pack(expand=1,fill='both')
+        
+        self.hprP = self.createcomponent('hprP', (), None,
+                                         AngleDial, (hprInterior,),
+                                         style = 'mini',
+                                         text = 'P', value = 0.0,
+                                         relief = Tkinter.FLAT,
+                                         label_foreground = 'red')
+        self.hprP['commandData'] = ['p']
+        self.hprP['preCallback'] = self.xformStart
+        self.hprP['postCallback'] = self.xformStop
+        self.hprP['callbackData'] = ['p']
+        self.hprP.pack(expand=1,fill='both')
+        
+        self.hprR = self.createcomponent('hprR', (), None,
+                                         AngleDial, (hprInterior,),
+                                         style = 'mini',
+                                         text = 'R', value = 0.0,
+                                         relief = Tkinter.FLAT,
+                                         label_foreground = '#00A000')
+        self.hprR['commandData'] = ['r']
+        self.hprR['preCallback'] = self.xformStart
+        self.hprR['postCallback'] = self.xformStop
+        self.hprR['callbackData'] = ['r']
+        self.hprR.pack(expand=1,fill='both')
+
+        # Create and pack the Scale Controls
+        # The available scaling modes
+        self.scalingMode = StringVar()
+        self.scalingMode.set('Scale Uniform')
+        # The scaling widgets
+        scaleGroup = Pmw.Group(interior,
+                               tag_text = 'Scale Uniform',
+                               tag_pyclass = Menubutton,
+                               tag_font=('MSSansSerif', 14),
+                               tag_activebackground = '#909090',
+                               ring_relief = Tkinter.RIDGE)
+        self.scaleMenubutton = scaleGroup.component('tag')
+        self.bind(self.scaleMenubutton, 'Scale menu operations')
+        self.scaleMenubutton['textvariable'] = self.scalingMode
+
+        # Scaling menu
+        scaleMenu = Menu(self.scaleMenubutton, tearoff = 0)
+        scaleMenu.add_command(label = 'Set to unity',
+                              command = self.unitScale)
+        scaleMenu.add_command(label = 'Reset initial',
+                              command = self.resetScale)
+        scaleMenu.add_radiobutton(label = 'Scale Free',
+                                      variable = self.scalingMode)
+        scaleMenu.add_radiobutton(label = 'Scale Uniform',
+                                      variable = self.scalingMode)
+        scaleMenu.add_radiobutton(label = 'Scale Proportional',
+                                      variable = self.scalingMode)
+        self.scaleMenubutton['menu'] = scaleMenu
+        # Pack group widgets
+        scaleGroup.pack(side='left',fill = 'both', expand = 1)
+        scaleInterior = scaleGroup.interior()
+        
+        # Create the dials
+        self.scaleX = self.createcomponent('scaleX', (), None,
+                                           Floater, (scaleInterior,),
+                                           text = 'X Scale',
+                                           relief = Tkinter.FLAT,
+                                           min = 0.0001, value = 1.0,
+                                           resetValue = 1.0,
+                                           label_foreground = 'Red')
+        self.scaleX['commandData'] = ['sx']
+        self.scaleX['callbackData'] = ['sx']
+        self.scaleX['preCallback'] = self.xformStart
+        self.scaleX['postCallback'] = self.xformStop
+        self.scaleX.pack(expand=1,fill='both')
+        
+        self.scaleY = self.createcomponent('scaleY', (), None,
+                                           Floater, (scaleInterior,),
+                                           text = 'Y Scale',
+                                           relief = Tkinter.FLAT,
+                                           min = 0.0001, value = 1.0,
+                                           resetValue = 1.0,
+                                           label_foreground = '#00A000')
+        self.scaleY['commandData'] = ['sy']
+        self.scaleY['callbackData'] = ['sy']
+        self.scaleY['preCallback'] = self.xformStart
+        self.scaleY['postCallback'] = self.xformStop
+        self.scaleY.pack(expand=1,fill='both')
+        
+        self.scaleZ = self.createcomponent('scaleZ', (), None,
+                                           Floater, (scaleInterior,),
+                                           text = 'Z Scale',
+                                           relief = Tkinter.FLAT,
+                                           min = 0.0001, value = 1.0,
+                                           resetValue = 1.0,
+                                           label_foreground = 'Blue')
+        self.scaleZ['commandData'] = ['sz']
+        self.scaleZ['callbackData'] = ['sz']
+        self.scaleZ['preCallback'] = self.xformStart
+        self.scaleZ['postCallback'] = self.xformStop
+        self.scaleZ.pack(expand=1,fill='both')
+
+        # Make sure appropriate labels are showing
+        self.setMovementMode('Relative To:')
+        # Set up placer for inital node path
+        self.selectNodePathNamed('init')
+        self.selectRefNodePathNamed('parent')
+        # Update place to reflect initial state
+        self.updatePlacer()
+        # Now that you're done setting up, attach commands
+        self.posX['command'] = self.xform
+        self.posY['command'] = self.xform
+        self.posZ['command'] = self.xform
+        self.hprH['command'] = self.xform
+        self.hprP['command'] = self.xform
+        self.hprR['command'] = self.xform
+        self.scaleX['command'] = self.xform
+        self.scaleY['command'] = self.xform
+        self.scaleZ['command'] = self.xform
+
+
+    ### WIDGET OPERATIONS ###
+    def setMovementMode(self, movementMode):
+        # Set prefix
+        namePrefix = ''
+        self.movementMode = movementMode
+        if (movementMode == 'Relative To:'):
+            namePrefix = 'Relative '
+        elif (movementMode == 'Orbit:'):
+            namePrefix = 'Orbit '
+        # Update pos widgets
+        self.posX['text'] = namePrefix + 'X'
+        self.posY['text'] = namePrefix + 'Y'
+        self.posZ['text'] = namePrefix + 'Z'
+        # Update hpr widgets
+        if (movementMode == 'Orbit:'):
+            namePrefix = 'Orbit delta '
+        self.hprH['text'] = namePrefix + 'H'
+        self.hprP['text'] = namePrefix + 'P'
+        self.hprR['text'] = namePrefix + 'R'
+        # Update temp cs and initialize widgets
+        self.updatePlacer()
+
+    def setScalingMode(self):
+        if self['nodePath']:
+            scale = self['nodePath'].getScale()
+            if ((scale[0] != scale[1]) or
+                (scale[0] != scale[2]) or
+                (scale[1] != scale[2])):
+                self.scalingMode.set('Scale Free')
+
+    def selectNodePathNamed(self, name):
+        nodePath = None
+        if name == 'init':
+            nodePath = self['nodePath']
+            # Add Combo box entry for the initial node path
+            self.addNodePath(nodePath)
+        elif name == 'selected':
+            nodePath = SEditor.selected.last
+            # Add Combo box entry for this selected object
+            self.addNodePath(nodePath)
+        else:
+            nodePath = self.nodePathDict.get(name, None)
+            if (nodePath == None):
+                # See if this evaluates into a node path
+                try:
+                    nodePath = eval(name)
+                    if isinstance(nodePath, NodePath):
+                        self.addNodePath(nodePath)
+                    else:
+                        # Good eval but not a node path, give up
+                        nodePath = None
+                except:
+                    # Bogus eval
+                    nodePath = None
+                    # Clear bogus entry from listbox
+                    listbox = self.nodePathMenu.component('scrolledlist')
+                    listbox.setlist(self.nodePathNames)
+            else:
+                if name == 'widget':
+                    # Record relationship between selected nodes and widget
+                    SEditor.selected.getWrtAll()                    
+        # Update active node path
+        self.setActiveNodePath(nodePath)
+
+    def setActiveNodePath(self, nodePath):
+        self['nodePath'] = nodePath
+        if self['nodePath']:
+            self.nodePathMenuEntry.configure(
+                background = self.nodePathMenuBG)
+            # Check to see if node path and ref node path are the same
+            if ((self.refCS != None) and
+                (self.refCS.id() == self['nodePath'].id())):
+                # Yes they are, use temp CS as ref
+                # This calls updatePlacer
+                self.setReferenceNodePath(self.tempCS)
+                # update listbox accordingly
+                self.refNodePathMenu.selectitem('parent')
+            else:
+                # Record initial value and initialize the widgets
+                self.updatePlacer()
+            # Record initial position
+            self.updateResetValues(self['nodePath'])
+            # Set scaling mode based on node path's current scale
+            self.setScalingMode()
+        else:
+            # Flash entry
+            self.nodePathMenuEntry.configure(background = 'Pink')
+
+    def selectRefNodePathNamed(self, name):
+        nodePath = None
+        if name == 'self':
+            nodePath = self.tempCS
+        elif name == 'selected':
+            nodePath = SEditor.selected.last
+            # Add Combo box entry for this selected object
+            self.addRefNodePath(nodePath)
+        elif name == 'parent':
+            nodePath = self['nodePath'].getParent()
+        else:
+            nodePath = self.refNodePathDict.get(name, None)
+            if (nodePath == None):
+                # See if this evaluates into a node path
+                try:
+                    nodePath = eval(name)
+                    if isinstance(nodePath, NodePath):
+                        self.addRefNodePath(nodePath)
+                    else:
+                        # Good eval but not a node path, give up
+                        nodePath = None
+                except:
+                    # Bogus eval
+                    nodePath = None
+                    # Clear bogus entry from listbox
+                    listbox = self.refNodePathMenu.component('scrolledlist')
+                    listbox.setlist(self.refNodePathNames)
+        # Check to see if node path and ref node path are the same
+        if (nodePath != None) and (nodePath.id() == self['nodePath'].id()):
+            # Yes they are, use temp CS and update listbox accordingly
+            nodePath = self.tempCS
+            self.refNodePathMenu.selectitem('parent')
+        # Update ref node path
+        self.setReferenceNodePath(nodePath)
+
+    def setReferenceNodePath(self, nodePath):
+        self.refCS = nodePath
+        if self.refCS:
+            self.refNodePathMenuEntry.configure(
+                background = self.nodePathMenuBG)
+            # Update placer to reflect new state
+            self.updatePlacer()
+        else:
+            # Flash entry
+            self.refNodePathMenuEntry.configure(background = 'Pink')
+        
+    def addNodePath(self, nodePath):
+        self.addNodePathToDict(nodePath, self.nodePathNames,
+                               self.nodePathMenu, self.nodePathDict)
+
+    def addRefNodePath(self, nodePath):
+        self.addNodePathToDict(nodePath, self.refNodePathNames,
+                               self.refNodePathMenu, self.refNodePathDict)
+
+    def addNodePathToDict(self, nodePath, names, menu, dict):
+        if not nodePath:
+            return
+        # Get node path's name
+        name = nodePath.getName()
+        if name in ['parent', 'render', 'camera']:
+            dictName = name
+        else:
+            # Generate a unique name for the dict
+            dictName = name + '-' + `nodePath.id()`
+        if not dict.has_key(dictName):
+            # Update combo box to include new item
+            names.append(dictName)
+            listbox = menu.component('scrolledlist')
+            listbox.setlist(names)
+            # Add new item to dictionary
+            dict[dictName] = nodePath
+        menu.selectitem(dictName)
+
+    def updatePlacer(self):
+        pos = Vec3(0)
+        hpr = Vec3(0)
+        scale = Vec3(1)
+        np = self['nodePath']
+        if (np != None) and isinstance(np, NodePath):
+            # Update temp CS
+            self.updateAuxiliaryCoordinateSystems()
+            # Update widgets
+            if self.movementMode == 'Orbit:':
+                pos.assign(self.posOffset)
+                hpr.assign(ZERO_VEC)
+                scale.assign(np.getScale())
+            elif self.refCS:
+                pos.assign(np.getPos(self.refCS))
+                hpr.assign(np.getHpr(self.refCS))
+                scale.assign(np.getScale())
+        self.updatePosWidgets(pos)
+        self.updateHprWidgets(hpr)
+        self.updateScaleWidgets(scale)
+
+    def updateAuxiliaryCoordinateSystems(self):
+        # Temp CS
+        self.tempCS.setPosHpr(self['nodePath'], 0,0,0,0,0,0)
+        # Orbit CS
+        # At reference
+        self.orbitFromCS.setPos(self.refCS, 0,0,0)
+        # But aligned with target
+        self.orbitFromCS.setHpr(self['nodePath'], 0,0,0)
+        # Also update to CS
+        self.orbitToCS.setPosHpr(self.orbitFromCS, 0,0,0,0,0,0)
+        # Get offset from origin
+        self.posOffset.assign(self['nodePath'].getPos(self.orbitFromCS))
+
+    ### NODE PATH TRANSFORMATION OPERATIONS ###
+    def xform(self, value, axis):
+        if axis in ['sx', 'sy', 'sz']:
+            self.xformScale(value,axis)
+        elif self.movementMode == 'Relative To:':
+            self.xformRelative(value, axis)
+        elif self.movementMode == 'Orbit:':
+            self.xformOrbit(value, axis)
+        if self.nodePathMenu.get() == 'widget':
+            if SEditor.manipulationControl.fSetCoa:
+                # Update coa based on current widget position
+                SEditor.selected.last.mCoa2Dnp.assign(
+                    SEditor.widget.getMat(SEditor.selected.last))
+            else:
+                # Move the objects with the widget
+                SEditor.selected.moveWrtWidgetAll()
+    
+    def xformStart(self, data):
+        # Record undo point
+        self.pushUndo()
+        # If moving widget kill follow task and update wrts
+        if self.nodePathMenu.get() == 'widget':
+            taskMgr.remove('followSelectedNodePath')
+            # Record relationship between selected nodes and widget
+            SEditor.selected.getWrtAll()
+        # Record initial state
+        self.deltaHpr = self['nodePath'].getHpr(self.refCS)
+        # Update placer to reflect new state
+        self.updatePlacer()
+        
+    def xformStop(self, data):
+        # Throw event to signal manipulation done
+        # Send nodepath as a list
+        messenger.send('DIRECT_manipulateObjectCleanup', [[self['nodePath']]])
+        # Update placer to reflect new state
+        self.updatePlacer()
+        # If moving widget restart follow task
+        if self.nodePathMenu.get() == 'widget':
+            # Restart followSelectedNodePath task
+            SEditor.manipulationControl.spawnFollowSelectedNodePathTask()
+
+    def xformRelative(self, value, axis):
+        nodePath = self['nodePath']
+        if (nodePath != None) and (self.refCS != None):
+            if axis == 'x':
+                nodePath.setX(self.refCS, value)
+            elif axis == 'y':
+                nodePath.setY(self.refCS, value)
+            elif axis == 'z':
+                nodePath.setZ(self.refCS, value)
+            else:
+                if axis == 'h':
+                    self.deltaHpr.setX(value)
+                elif axis == 'p':
+                    self.deltaHpr.setY(value)
+                elif axis == 'r':
+                    self.deltaHpr.setZ(value)
+                # Put node path at new hpr
+                nodePath.setHpr(self.refCS, self.deltaHpr)
+
+    def xformOrbit(self, value, axis):
+        nodePath = self['nodePath']
+        if ((nodePath != None) and (self.refCS != None) and
+            (self.orbitFromCS != None) and (self.orbitToCS != None)):
+            if axis == 'x':
+                self.posOffset.setX(value)
+            elif axis == 'y':
+                self.posOffset.setY(value)
+            elif axis == 'z':
+                self.posOffset.setZ(value)
+            elif axis == 'h':
+                self.orbitToCS.setH(self.orbitFromCS, value)
+            elif axis == 'p':
+                self.orbitToCS.setP(self.orbitFromCS, value)
+            elif axis == 'r':
+                self.orbitToCS.setR(self.orbitFromCS, value)
+            nodePath.setPosHpr(self.orbitToCS, self.posOffset, ZERO_VEC)
+
+    def xformScale(self, value, axis):
+        if self['nodePath']:
+            mode = self.scalingMode.get()
+            scale = self['nodePath'].getScale()
+            if mode == 'Scale Free':
+                if axis == 'sx':
+                    scale.setX(value)
+                elif axis == 'sy':
+                    scale.setY(value)
+                elif axis == 'sz':
+                    scale.setZ(value)
+            elif mode == 'Scale Uniform':
+                scale.set(value,value,value)
+            elif mode == 'Scale Proportional':
+                if axis == 'sx':
+                    sf = value/scale[0]
+                elif axis == 'sy':
+                    sf = value/scale[1]
+                elif axis == 'sz':
+                    sf = value/scale[2]
+                scale = scale * sf
+            self['nodePath'].setScale(scale)
+
+    def updatePosWidgets(self, pos):
+        self.posX.set(pos[0])
+        self.posY.set(pos[1])
+        self.posZ.set(pos[2])
+
+    def updateHprWidgets(self, hpr):
+        self.hprH.set(hpr[0])
+        self.hprP.set(hpr[1])
+        self.hprR.set(hpr[2])
+
+    def updateScaleWidgets(self, scale):
+        self.scaleX.set(scale[0])
+        self.scaleY.set(scale[1])
+        self.scaleZ.set(scale[2])
+
+    def zeroAll(self):
+        self.xformStart(None)
+        self.updatePosWidgets(ZERO_VEC)
+        self.updateHprWidgets(ZERO_VEC)
+        self.updateScaleWidgets(UNIT_VEC)
+        self.xformStop(None)
+
+    def zeroPos(self):
+        self.xformStart(None)
+        self.updatePosWidgets(ZERO_VEC)
+        self.xformStop(None)
+
+    def zeroHpr(self):
+        self.xformStart(None)
+        self.updateHprWidgets(ZERO_VEC)
+        self.xformStop(None)
+
+    def unitScale(self):
+        self.xformStart(None)
+        self.updateScaleWidgets(UNIT_VEC)
+        self.xformStop(None)
+
+    def updateResetValues(self, nodePath):
+        self.initPos.assign(nodePath.getPos())
+        self.posX['resetValue'] = self.initPos[0]
+        self.posY['resetValue'] = self.initPos[1]
+        self.posZ['resetValue'] = self.initPos[2]
+        self.initHpr.assign(nodePath.getHpr())
+        self.hprH['resetValue'] = self.initHpr[0]
+        self.hprP['resetValue'] = self.initHpr[1]
+        self.hprR['resetValue'] = self.initHpr[2]
+        self.initScale.assign(nodePath.getScale())
+        self.scaleX['resetValue'] = self.initScale[0]
+        self.scaleY['resetValue'] = self.initScale[1]
+        self.scaleZ['resetValue'] = self.initScale[2]
+
+    def resetAll(self):
+        if self['nodePath']:
+            self.xformStart(None)
+            self['nodePath'].setPosHprScale(
+                self.initPos, self.initHpr, self.initScale)
+            self.xformStop(None)
+
+    def resetPos(self):
+        if self['nodePath']:
+            self.xformStart(None)
+            self['nodePath'].setPos(self.initPos)
+            self.xformStop(None)
+
+    def resetHpr(self):
+        if self['nodePath']:
+            self.xformStart(None)
+            self['nodePath'].setHpr(self.initHpr)
+            self.xformStop(None)
+
+    def resetScale(self):
+        if self['nodePath']:
+            self.xformStart(None)
+            self['nodePath'].setScale(self.initScale)
+            self.xformStop(None)
+
+    def pushUndo(self, fResetRedo = 1):
+        SEditor.pushUndo([self['nodePath']])
+
+    def undoHook(self, nodePathList = []):
+        # Reflect new changes
+        self.updatePlacer()
+
+    def pushUndoHook(self):
+        # Make sure button is reactivated
+        self.undoButton.configure(state = 'normal')
+
+    def undoListEmptyHook(self):
+        # Make sure button is deactivated
+        self.undoButton.configure(state = 'disabled')
+
+    def pushRedo(self):
+        SEditor.pushRedo([self['nodePath']])
+        
+    def redoHook(self, nodePathList = []):
+        # Reflect new changes
+        self.updatePlacer()
+
+    def pushRedoHook(self):
+        # Make sure button is reactivated
+        self.redoButton.configure(state = 'normal')
+
+    def redoListEmptyHook(self):
+        # Make sure button is deactivated
+        self.redoButton.configure(state = 'disabled')
+        
+    def printNodePathInfo(self):
+        np = self['nodePath']
+        if np:
+            name = np.getName()
+            pos = np.getPos()
+            hpr = np.getHpr()
+            scale = np.getScale()
+            posString = '%.2f, %.2f, %.2f' % (pos[0], pos[1], pos[2])
+            hprString = '%.2f, %.2f, %.2f' % (hpr[0], hpr[1], hpr[2])
+            scaleString = '%.2f, %.2f, %.2f' % (scale[0], scale[1], scale[2])
+            print 'NodePath: %s' % name
+            print 'Pos: %s' % posString
+            print 'Hpr: %s' % hprString
+            print 'Scale: %s' % scaleString
+            print ('%s.setPosHprScale(%s, %s, %s)' %
+                   (name, posString, hprString, scaleString))
+
+    def onDestroy(self, event):
+        # Remove hooks
+        for event, method in self.undoEvents:
+            self.ignore(event)
+        self.tempCS.removeNode()
+        self.orbitFromCS.removeNode()
+        self.orbitToCS.removeNode()
+        # send out a message to let sceneEditor know that placer panel has been closed.
+        # Also, stop accepting the updata message from sceneEditor
+        messenger.send('Placer_close')
+        self.ignore('placerUpdate')
+        
+def place(nodePath):
+    return Placer(nodePath = nodePath)
+
+######################################################################
+
+# Create demo in root window for testing.
+if __name__ == '__main__':
+    root = Pmw.initialise()
+    widget = Placer()
+

+ 209 - 0
contrib/src/sceneeditor/seSceneGraphExplorer.py

@@ -0,0 +1,209 @@
+#################################################################
+# seSceneGraphExplorer.py
+# Originally from SceneGraphExplorer.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# we need a customized SceneGraphExplorer.
+#
+# Do forget to check the seTree. 
+#
+#################################################################
+from direct.showbase.DirectObject import DirectObject
+from Tkinter import IntVar, Frame, Label
+from seTree import TreeNode, TreeItem
+import Pmw, Tkinter
+
+# changing these strings requires changing sceneEditor.py SGE_ strs too!
+# This list of items will be showed on the pop out window when user right click on
+# any node on the graph. And, this is also the main reason we decide to copy from
+# the original one but not inherited from it.
+# Because except drawing part, we have changed a lot of things...
+DEFAULT_MENU_ITEMS = [
+    'Update Explorer',
+    'Separator',
+    'Properties',
+    'Separator',
+    'Duplicate',
+    'Remove',
+    'Add Dummy',
+    'Add Collision Object',
+    'Metadata',
+    'Separator',
+    'Set as Reparent Target',
+    'Reparent to Target',
+    'Separator',
+    'Animation Panel',
+    'Blend Animation Panel',
+    'MoPath Panel',
+    'Align Tool',
+    'Separator']
+
+class seSceneGraphExplorer(Pmw.MegaWidget, DirectObject):
+    "Graphical display of a scene graph"
+    def __init__(self, parent = None, nodePath = render, **kw): 
+        # Define the megawidget options.
+        optiondefs = (
+            ('menuItems',   [],   Pmw.INITOPT),
+            )
+        self.defineoptions(kw, optiondefs)
+ 
+        # Initialise superclass
+        Pmw.MegaWidget.__init__(self, parent)
+        
+        # Initialize some class variables
+        self.nodePath = nodePath
+
+        # Create the components.
+        
+        # Setup up container
+        interior = self.interior()
+        interior.configure(relief = Tkinter.GROOVE, borderwidth = 2)
+        
+        # Create a label and an entry
+        self._scrolledCanvas = self.createcomponent(
+            'scrolledCanvas',
+            (), None,
+            Pmw.ScrolledCanvas, (interior,),
+            hull_width = 200, hull_height = 300,
+            usehullsize = 1)
+        self._canvas = self._scrolledCanvas.component('canvas')
+        self._canvas['scrollregion'] = ('0i', '0i', '2i', '4i')
+        self._scrolledCanvas.resizescrollregion()
+        self._scrolledCanvas.pack(padx = 3, pady = 3, expand=1, fill = Tkinter.BOTH)
+        
+        self._canvas.bind('<ButtonPress-2>', self.mouse2Down)
+        self._canvas.bind('<B2-Motion>', self.mouse2Motion)
+        self._canvas.bind('<Configure>',
+                          lambda e, sc = self._scrolledCanvas:
+                          sc.resizescrollregion())
+        self.interior().bind('<Destroy>', self.onDestroy)
+        
+        # Create the contents
+        self._treeItem = SceneGraphExplorerItem(self.nodePath)
+
+        self._node = TreeNode(self._canvas, None, self._treeItem,
+                              DEFAULT_MENU_ITEMS + self['menuItems'])
+        self._node.expand()
+
+        self._parentFrame = Frame(interior)
+        self._label = self.createcomponent(
+            'parentLabel',
+            (), None,
+            Label, (interior,),
+            text = 'Active Reparent Target: ',
+            anchor = Tkinter.W, justify = Tkinter.LEFT)
+        self._label.pack(fill = Tkinter.X)
+
+        # Add update parent label
+        def updateLabel(nodePath = None, s = self):
+            s._label['text'] = 'Active Reparent Target: ' + nodePath.getName()
+        self.accept('DIRECT_activeParent', updateLabel)
+
+        # Add update hook
+        self.accept('SGE_Update Explorer',
+                    lambda np, s = self: s.update())
+
+        # Check keywords and initialise options based on input values.
+        self.initialiseoptions(seSceneGraphExplorer)
+
+    def update(self):
+        """ Refresh scene graph explorer """
+        self._node.update()
+
+    def mouse2Down(self, event):
+        self._width = 1.0 * self._canvas.winfo_width()
+        self._height = 1.0 * self._canvas.winfo_height()
+        xview = self._canvas.xview()
+        yview = self._canvas.yview()        
+        self._left = xview[0]
+        self._top = yview[0]
+        self._dxview = xview[1] - xview[0]
+        self._dyview = yview[1] - yview[0]
+        self._2lx = event.x
+        self._2ly = event.y
+
+    def mouse2Motion(self,event):
+        newx = self._left - ((event.x - self._2lx)/self._width) * self._dxview
+        self._canvas.xview_moveto(newx)
+        newy = self._top - ((event.y - self._2ly)/self._height) * self._dyview
+        self._canvas.yview_moveto(newy)
+        self._2lx = event.x
+        self._2ly = event.y
+        self._left = self._canvas.xview()[0]
+        self._top = self._canvas.yview()[0]
+
+    def onDestroy(self, event):
+        # Remove hooks
+        self.ignore('DIRECT_activeParent')
+        self.ignore('SGE_Update Explorer')
+
+    def deSelectTree(self):
+        self._node.deselecttree()
+
+    def selectNodePath(self,nodePath, callBack=True):
+        item = self._node.find(nodePath.id())
+        if item!= None:
+            item.select(callBack)
+        else:
+            print '----SGE: Error Selection'
+
+class SceneGraphExplorerItem(TreeItem):
+
+    """Example TreeItem subclass -- browse the file system."""
+
+    def __init__(self, nodePath):
+        self.nodePath = nodePath
+
+    def GetText(self):
+        type = self.nodePath.node().getType().getName()
+        name = self.nodePath.getName()
+        return type + "  " + name
+
+    def GetTextForEdit(self):
+        name = self.nodePath.getName()
+        return name
+
+    def GetKey(self):
+        return self.nodePath.id()
+
+    def IsEditable(self):
+        # All nodes' names can be edited nowadays.
+        return 1
+        #return issubclass(self.nodePath.node().__class__, NamedNode)
+
+    def SetText(self, text):
+        try:
+            messenger.send('SGE_changeName', [self.nodePath, text])
+        except AttributeError:
+            pass
+
+    def GetIconName(self):
+        return "sphere2" # XXX wish there was a "file" icon
+
+    def IsExpandable(self):
+        return self.nodePath.getNumChildren() != 0
+
+    def GetSubList(self):
+        sublist = []
+        for nodePath in self.nodePath.getChildren():
+            item = SceneGraphExplorerItem(nodePath)
+            sublist.append(item)
+        return sublist
+
+    def OnSelect(self, callback):
+        messenger.send('SGE_Flash', [self.nodePath])
+        if not callback:
+            messenger.send('SGE_madeSelection', [self.nodePath, callback])
+        else:
+            messenger.send('SGE_madeSelection', [self.nodePath])
+
+    def MenuCommand(self, command):
+        messenger.send('SGE_' + command, [self.nodePath])
+
+
+def explore(nodePath = render):
+    tl = Toplevel()
+    tl.title('Explore: ' + nodePath.getName())
+    sge = seSceneGraphExplorer(parent = tl, nodePath = nodePath)
+    sge.pack(expand = 1, fill = 'both')
+    return sge

+ 715 - 0
contrib/src/sceneeditor/seSelection.py

@@ -0,0 +1,715 @@
+#################################################################
+# seSelection.py
+# Originally from DirectSelection.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# We didn't change anything essential.
+# Just because we customized the seSession from DirectSession,
+# So we need related files can follow the change.
+# However, we don't want to change anything inside the original directool
+# to let them can work with our scene editor.
+# (If we do change original directools, it will force user has to install the latest version of OUR Panda)
+#
+#################################################################
+from pandac.PandaModules import GeomNode
+from direct.directtools.DirectGlobals import *
+from direct.directtools.DirectUtil import *
+from seGeometry import *
+from direct.showbase.DirectObject import *
+from quad import *
+COA_ORIGIN = 0
+COA_CENTER = 1
+
+# MRM: To do: handle broken node paths in selected and deselected dicts
+class DirectNodePath(NodePath):
+    # A node path augmented with info, bounding box, and utility methods
+    def __init__(self, nodePath):
+        # Initialize the superclass
+        NodePath.__init__(self)
+        self.assign(nodePath)
+        # Create a bounding box
+        self.bbox = DirectBoundingBox(self)
+        center = self.bbox.getCenter()
+        # Create matrix to hold the offset between the nodepath
+        # and its center of action (COA)
+        self.mCoa2Dnp = Mat4(Mat4.identMat())
+        if SEditor.coaMode == COA_CENTER:
+            self.mCoa2Dnp.setRow(3, Vec4(center[0], center[1], center[2], 1))
+            
+        # Transform from nodePath to widget
+        self.tDnp2Widget = TransformState.makeIdentity()
+
+    def highlight(self):
+        self.bbox.show()
+
+    def dehighlight(self):
+        self.bbox.hide()
+
+    def getCenter(self):
+        return self.bbox.getCenter()
+
+    def getRadius(self):
+        return self.bbox.getRadius()
+
+    def getMin(self):
+        return self.bbox.getMin()
+
+    def getMax(self):
+        return self.bbox.getMax()
+
+class SelectedNodePaths(DirectObject):
+    def __init__(self):
+        self.reset()
+
+    def reset(self):
+        self.selectedDict = {}
+        self.deselectedDict = {}
+        __builtins__["last"] = self.last = None
+
+    def select(self, nodePath, fMultiSelect = 0):
+        """ Select the specified node path.  Multiselect as required """
+        # Do nothing if nothing selected
+        if not nodePath:
+            print 'Nothing selected!!'
+            return None
+        
+        # Reset selected objects and highlight if multiSelect is false
+        if not fMultiSelect:
+            self.deselectAll()
+        
+        # Get this pointer
+        id = nodePath.id()
+        # First see if its already in the selected dictionary
+        dnp = self.getSelectedDict(id)
+        # If so, we're done
+        if not dnp:
+            # See if it is in the deselected dictionary
+            dnp = self.getDeselectedDict(id)
+            if dnp:
+                # Remove it from the deselected dictionary
+                del(self.deselectedDict[id])
+                # Show its bounding box
+                dnp.highlight()
+            else:
+                # Didn't find it, create a new selectedNodePath instance
+                dnp = DirectNodePath(nodePath)
+                # Show its bounding box
+                dnp.highlight()
+            # Add it to the selected dictionary
+            self.selectedDict[dnp.id()] = dnp
+        # And update last
+        __builtins__["last"] = self.last = dnp
+        return dnp
+
+    def deselect(self, nodePath):
+        """ Deselect the specified node path """
+        # Get this pointer
+        id = nodePath.id()
+        # See if it is in the selected dictionary
+        dnp = self.getSelectedDict(id)
+        if dnp:
+            # It was selected:
+            # Hide its bounding box
+            dnp.dehighlight()
+            # Remove it from the selected dictionary
+            del(self.selectedDict[id])
+            # And keep track of it in the deselected dictionary
+            self.deselectedDict[id] = dnp
+            # Send a message
+            messenger.send('DIRECT_deselectedNodePath', [dnp])
+        return dnp
+
+    def getSelectedAsList(self):
+        """
+        Return a list of all selected node paths.  No verification of
+        connectivity is performed on the members of the list
+        """
+        return self.selectedDict.values()[:]
+
+    def __getitem__(self,index):
+        return self.getSelectedAsList()[index]
+
+    def getSelectedDict(self, id):
+        """
+        Search selectedDict for node path, try to repair broken node paths.
+        """
+        dnp = self.selectedDict.get(id, None)
+        if dnp:
+            return dnp
+        else:
+            # Not in selected dictionary
+            return None
+
+    def getDeselectedAsList(self):
+        return self.deselectedDict.values()[:]
+
+    def getDeselectedDict(self, id):
+        """
+        Search deselectedDict for node path, try to repair broken node paths.
+        """
+        dnp = self.deselectedDict.get(id, None)
+        if dnp:
+            # Yes
+            return dnp
+        else:
+            # Not in deselected dictionary
+            return None
+
+    def forEachSelectedNodePathDo(self, func):
+        """
+        Perform given func on selected node paths.  No node path
+        connectivity verification performed
+        """
+        selectedNodePaths = self.getSelectedAsList()
+        for nodePath in selectedNodePaths:
+            func(nodePath)
+
+    def forEachDeselectedNodePathDo(self, func):
+        """
+        Perform given func on deselected node paths.  No node path
+        connectivity verification performed
+        """
+        deselectedNodePaths = self.getDeselectedAsList()
+        for nodePath in deselectedNodePaths:
+            func(nodePath)
+
+    def getWrtAll(self):
+        self.forEachSelectedNodePathDo(self.getWrt)
+
+    def getWrt(self, nodePath):
+        nodePath.tDnp2Widget = nodePath.getTransform(SEditor.widget)
+
+    def moveWrtWidgetAll(self):
+        self.forEachSelectedNodePathDo(self.moveWrtWidget)
+
+    def moveWrtWidget(self, nodePath):
+        nodePath.setTransform(SEditor.widget, nodePath.tDnp2Widget)
+
+    def deselectAll(self):
+        self.forEachSelectedNodePathDo(self.deselect)
+
+    def highlightAll(self):
+        self.forEachSelectedNodePathDo(DirectNodePath.highlight)
+
+    def dehighlightAll(self):
+        self.forEachSelectedNodePathDo(DirectNodePath.dehighlight)
+
+    def removeSelected(self):
+        selected = self.last
+        if selected:
+            selected.remove()
+        __builtins__["last"] = self.last = None
+        
+    def removeAll(self):
+        # Remove all selected nodePaths from the Scene Graph
+        self.forEachSelectedNodePathDo(NodePath.remove)
+
+    def toggleVisSelected(self):
+        selected = self.last
+        # Toggle visibility of selected node paths
+        if selected:
+            selected.toggleVis()
+
+    def toggleVisAll(self):
+        # Toggle viz for all selected node paths
+        self.forEachSelectedNodePathDo(NodePath.toggleVis)
+
+    def isolateSelected(self):
+        selected = self.last
+        if selected:
+            selected.isolate()
+
+    def getDirectNodePath(self, nodePath):
+        # Get this pointer
+        id = nodePath.id()
+        # First check selected dict
+        dnp = self.getSelectedDict(id)
+        if dnp:
+            return dnp
+        # Otherwise return result of deselected search
+        return self.getDeselectedDict(id)
+
+    def getNumSelected(self):
+        return len(self.selectedDict.keys())
+
+
+class DirectBoundingBox:
+    def __init__(self, nodePath):
+        # Record the node path
+        self.nodePath = nodePath
+        # Compute bounds, min, max, etc.
+        self.computeTightBounds()
+        # Generate the bounding box
+        self.lines = self.createBBoxLines()
+
+    def computeTightBounds(self):
+        # Compute bounding box using tighter calcTightBounds function
+        # Need to clear out existing transform on node path
+        tMat = Mat4()
+        tMat.assign(self.nodePath.getMat())
+        self.nodePath.clearMat()
+        # Get bounds
+        self.min = Point3(0)
+        self.max = Point3(0)
+        self.nodePath.calcTightBounds(self.min,self.max)
+        # Calc center and radius
+        self.center = Point3((self.min + self.max)/2.0)
+        self.radius = Vec3(self.max - self.min).length()
+        # Restore transform
+        self.nodePath.setMat(tMat)
+        del tMat
+        
+    def computeBounds(self):
+        self.bounds = self.getBounds()
+        if self.bounds.isEmpty() or self.bounds.isInfinite():
+            self.center = Point3(0)
+            self.radius = 1.0
+        else:
+            self.center = self.bounds.getCenter()
+            self.radius = self.bounds.getRadius()
+        self.min = Point3(self.center - Point3(self.radius))
+        self.max = Point3(self.center + Point3(self.radius))
+        
+    def createBBoxLines(self):
+        # Create a line segments object for the bbox
+        lines = LineNodePath(hidden)
+        lines.node().setName('bboxLines')
+        lines.setColor( VBase4( 1., 0., 0., 1. ) )
+        lines.setThickness( 0.5 )
+
+        minX = self.min[0]
+        minY = self.min[1]
+        minZ = self.min[2]
+        maxX = self.max[0]
+        maxY = self.max[1]
+        maxZ = self.max[2]
+        
+        # Bottom face
+        lines.moveTo( minX, minY, minZ )
+        lines.drawTo( maxX, minY, minZ )
+        lines.drawTo( maxX, maxY, minZ )
+        lines.drawTo( minX, maxY, minZ )
+        lines.drawTo( minX, minY, minZ )
+
+        # Front Edge/Top face
+        lines.drawTo( minX, minY, maxZ )
+        lines.drawTo( maxX, minY, maxZ )
+        lines.drawTo( maxX, maxY, maxZ )
+        lines.drawTo( minX, maxY, maxZ )
+        lines.drawTo( minX, minY, maxZ )
+
+        # Three remaining edges
+        lines.moveTo( maxX, minY, minZ )
+        lines.drawTo( maxX, minY, maxZ )
+        lines.moveTo( maxX, maxY, minZ )
+        lines.drawTo( maxX, maxY, maxZ )
+        lines.moveTo( minX, maxY, minZ )
+        lines.drawTo( minX, maxY, maxZ )
+
+        # Create and return bbox lines
+        lines.create()
+        
+        # Make sure bbox is never lit or drawn in wireframe
+        useDirectRenderStyle(lines)
+        
+        return lines
+
+    def updateBBoxLines(self):
+        ls = self.lines.lineSegs
+        
+        minX = self.min[0]
+        minY = self.min[1]
+        minZ = self.min[2]
+        maxX = self.max[0]
+        maxY = self.max[1]
+        maxZ = self.max[2]
+        
+        # Bottom face
+        ls.setVertex( 0, minX, minY, minZ )
+        ls.setVertex( 1, maxX, minY, minZ )
+        ls.setVertex( 2, maxX, maxY, minZ )
+        ls.setVertex( 3, minX, maxY, minZ )
+        ls.setVertex( 4, minX, minY, minZ )
+
+        # Front Edge/Top face
+        ls.setVertex( 5, minX, minY, maxZ )
+        ls.setVertex( 6, maxX, minY, maxZ )
+        ls.setVertex( 7, maxX, maxY, maxZ )
+        ls.setVertex( 8, minX, maxY, maxZ )
+        ls.setVertex( 9, minX, minY, maxZ )
+
+        # Three remaining edges
+        ls.setVertex( 10, maxX, minY, minZ )
+        ls.setVertex( 11, maxX, minY, maxZ )
+        ls.setVertex( 12, maxX, maxY, minZ )
+        ls.setVertex( 13, maxX, maxY, maxZ )
+        ls.setVertex( 14, minX, maxY, minZ )
+        ls.setVertex( 15, minX, maxY, maxZ )
+
+    def getBounds(self):
+        # Get a node path's bounds
+        nodeBounds = BoundingSphere()
+        nodeBounds.extendBy(self.nodePath.node().getInternalBound())
+        for child in self.nodePath.getChildren():
+            nodeBounds.extendBy(child.getBounds())
+        return nodeBounds.makeCopy()
+
+    def show(self):
+        self.lines.reparentTo(self.nodePath)
+
+    def hide(self):
+        self.lines.reparentTo(hidden)
+        
+    def getCenter(self):
+        return self.center
+
+    def getRadius(self):
+        return self.radius
+
+    def getMin(self):
+        return self.min
+
+    def getMax(self):
+        return self.max
+
+    def vecAsString(self, vec):
+        return '%.2f %.2f %.2f' % (vec[0], vec[1], vec[2])
+
+    def __repr__(self):
+        return (`self.__class__` + 
+                '\nNodePath:\t%s\n' % self.nodePath.getName() +
+                'Min:\t\t%s\n' % self.vecAsString(self.min) +
+                'Max:\t\t%s\n' % self.vecAsString(self.max) +
+                'Center:\t\t%s\n' % self.vecAsString(self.center) +
+                'Radius:\t\t%.2f' % self.radius
+                )
+
+
+class SelectionQueue(CollisionHandlerQueue):
+    def __init__(self, parentNP = render):
+        # Initialize the superclass
+        CollisionHandlerQueue.__init__(self)
+        # Current index and entry in collision queue
+        self.index = -1
+        self.entry = None
+        self.skipFlags = SKIP_NONE
+        # Create a collision node path attached to the given NP
+        self.collisionNodePath = NodePath(CollisionNode("collisionNP"))
+        self.setParentNP(parentNP)
+        # Don't pay the penalty of drawing this collision ray
+        self.collisionNodePath.hide()
+        self.collisionNode = self.collisionNodePath.node()
+        # Intersect with geometry to begin with
+        self.collideWithGeom()
+        # And a traverser to do the actual collision tests
+        self.ct = CollisionTraverser()
+        # Let the traverser know about the collision node and the queue
+        #Manakel 2/12/2005: replace CollisionNode by its nodepath
+        self.ct.addCollider(self.collisionNodePath, self)
+        # List of objects that can't be selected
+        self.unpickable = UNPICKABLE
+        # Derived class must add Collider to complete initialization
+
+    def setParentNP(self, parentNP):
+        # Update collisionNodePath's parent
+        self.collisionNodePath.reparentTo(parentNP)
+
+    def addCollider(self, collider):
+        # Inherited class must call this function to specify collider object
+        # Record collision object
+        self.collider = collider
+        # Add the collider to the collision Node
+        self.collisionNode.addSolid( self.collider )
+
+    def collideWithBitMask(self, bitMask):
+        # The into collide mask is the bit pattern colliders look at
+        # when deciding whether or not to test for a collision "into"
+        # this collision solid.  Set to all Off so this collision solid
+        # will not be considered in any collision tests
+        self.collisionNode.setIntoCollideMask(BitMask32().allOff())
+        # The from collide mask is the bit pattern *this* collision solid
+        # compares against the into collide mask of candidate collision solids
+        # Turn this mask all off since we're not testing for collisions against
+        # collision solids
+        self.collisionNode.setFromCollideMask(bitMask)
+
+    def collideWithGeom(self):
+        # The into collide mask is the bit pattern colliders look at
+        # when deciding whether or not to test for a collision "into"
+        # this collision solid.  Set to all Off so this collision solid
+        # will not be considered in any collision tests
+        self.collisionNode.setIntoCollideMask(BitMask32().allOff())
+        # The from collide mask is the bit pattern *this* collision solid
+        # compares against the into collide mask of candidate collision solids
+        # Turn this mask all off since we're not testing for collisions against
+        # collision solids, but we do want to test against geometry
+        self.collisionNode.setFromCollideMask(GeomNode.getDefaultCollideMask())
+
+    def collideWithWidget(self):
+        # This collision node should not be tested against by any other
+        # collision solids
+        self.collisionNode.setIntoCollideMask(BitMask32().allOff())
+        # This collision node will test for collisions with any collision
+        # solids with a bit mask set to 0x80000000
+        mask = BitMask32()
+        mask.setBit(31)
+        self.collisionNode.setFromCollideMask(mask)
+
+    def addUnpickable(self, item):
+        if item not in self.unpickable:
+            self.unpickable.append(item)
+
+    def removeUnpickable(self, item):
+        if item in self.unpickable:
+            self.unpickable.remove(item)
+
+    def setCurrentIndex(self, index):
+        if (index < 0) or (index >= self.getNumEntries()):
+            self.index = -1
+        else:
+            self.index = index
+
+    def setCurrentEntry(self, entry):
+        self.entry = entry
+
+    def getCurrentEntry(self):
+        return self.entry
+
+    def isEntryBackfacing(self, entry):
+        # If dot product of collision point surface normal and
+        # ray from camera to collision point is positive, we are
+        # looking at the backface of the polygon
+        if not entry.hasSurfaceNormal():
+            # Well, no way to tell.  Assume we're not backfacing.
+            return 0
+
+        fromNodePath = entry.getFromNodePath()
+        v = Vec3(entry.getSurfacePoint(fromNodePath))
+        n = entry.getSurfaceNormal(fromNodePath)
+        # Convert to camera space for backfacing test
+        if self.collisionNodePath.getParent() != base.cam:
+            # Problem: assumes base.cam is the camera in question
+            p2cam = self.collisionNodePath.getParent().getMat(base.cam)
+            v = Vec3(p2cam.xformPoint(v))
+            n = p2cam.xformVec(n)
+        # Normalize and check angle between to vectors
+        v.normalize()
+        return v.dot(n) >= 0
+
+    def findNextCollisionEntry(self, skipFlags = SKIP_NONE):
+        return self.findCollisionEntry(skipFlags, self.index + 1)
+
+    def findCollisionEntry(self, skipFlags = SKIP_NONE, startIndex = 0 ):
+        # Init self.index and self.entry
+        self.setCurrentIndex(-1)
+        self.setCurrentEntry(None)
+        # Pick out the closest object that isn't a widget
+        for i in range(startIndex,self.getNumEntries()):
+            entry = self.getEntry(i)
+            nodePath = entry.getIntoNodePath()
+            if (skipFlags & SKIP_HIDDEN) and nodePath.isHidden():
+                # Skip if hidden node
+                pass
+            elif (skipFlags & SKIP_BACKFACE) and self.isEntryBackfacing(entry):
+                # Skip, if backfacing poly
+                pass
+            elif ((skipFlags & SKIP_CAMERA) and
+                  (camera in nodePath.getAncestors())):
+                # Skip if parented to a camera.
+                pass
+            # Can pick unpickable, use the first visible node
+            elif ((skipFlags & SKIP_UNPICKABLE) and
+                  (nodePath.getName() in self.unpickable)):
+                # Skip if in unpickable list
+                pass
+            else:
+                self.setCurrentIndex(i)
+                self.setCurrentEntry(entry)
+                break
+        return self.getCurrentEntry()
+
+class SelectionRay(SelectionQueue):
+    def __init__(self, parentNP = render):
+        # Initialize the superclass
+        SelectionQueue.__init__(self, parentNP)
+        self.addCollider(CollisionRay())
+    
+    def pick(self, targetNodePath, xy = None):
+        # Determine ray direction based upon the mouse coordinates
+        if xy:
+            mx = xy[0]
+            my = xy[1]
+        elif direct:
+            mx = SEditor.dr.mouseX
+            my = SEditor.dr.mouseY
+        else:
+            if not base.mouseWatcherNode.hasMouse():
+                # No mouse in window.
+                self.clearEntries()
+                return
+            mx = base.mouseWatcherNode.getMouseX()
+            my = base.mouseWatcherNode.getMouseY()
+        #base.mouseWatcherNode.setDisplayRegion(base.win.getDisplayRegion(0))
+        #mx = base.mouseWatcherNode.getMouseX()+1
+        #my = base.mouseWatcherNode.getMouseY()+1
+        #print base.camNode.getName()
+        #print "Arrived X" + str(mx) + " Arrived Y " + str(my) 
+        self.collider.setFromLens( base.camNode, mx, my )
+
+        self.ct.traverse( targetNodePath )
+        self.sortEntries()
+
+
+    def pickBitMask(self, bitMask = BitMask32.allOff(),
+                    targetNodePath = render,
+                    skipFlags = SKIP_ALL ):
+        self.collideWithBitMask(bitMask)
+        self.pick(targetNodePath)
+        # Determine collision entry
+        return self.findCollisionEntry(skipFlags)
+
+    def pickGeom(self, targetNodePath = render, skipFlags = SKIP_ALL,
+                 xy = None):
+        self.collideWithGeom()
+        self.pick(targetNodePath, xy = xy)
+        # Determine collision entry
+        return self.findCollisionEntry(skipFlags)
+
+    def pickWidget(self, targetNodePath = render, skipFlags = SKIP_NONE ):
+        self.collideWithWidget()
+        self.pick(targetNodePath)
+        # Determine collision entry
+        return self.findCollisionEntry(skipFlags)
+
+    def pick3D(self, targetNodePath, origin, dir):
+        # Determine ray direction based upon the mouse coordinates
+        self.collider.setOrigin( origin )
+        self.collider.setDirection( dir )
+        self.ct.traverse( targetNodePath )
+        self.sortEntries()
+        
+    def pickGeom3D(self, targetNodePath = render,
+                   origin = Point3(0), dir = Vec3(0,0,-1),
+                   skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
+        self.collideWithGeom()
+        self.pick3D(targetNodePath, origin, dir)
+        # Determine collision entry
+        return self.findCollisionEntry(skipFlags)
+
+    def pickBitMask3D(self, bitMask = BitMask32.allOff(),
+                      targetNodePath = render,
+                      origin = Point3(0), dir = Vec3(0,0,-1),
+                      skipFlags = SKIP_ALL ):
+        self.collideWithBitMask(bitMask)
+        self.pick3D(targetNodePath, origin, dir)
+        # Determine collision entry
+        return self.findCollisionEntry(skipFlags)
+
+
+class SelectionSegment(SelectionQueue):
+    # Like a selection ray but with two endpoints instead of an endpoint
+    # and a direction
+    def __init__(self, parentNP = render, numSegments = 1):
+        # Initialize the superclass
+        SelectionQueue.__init__(self, parentNP)
+        self.colliders = []
+        self.numColliders = 0
+        for i in range(numSegments):
+            self.addCollider(CollisionSegment())
+    
+    def addCollider(self, collider):
+        # Record new collision object
+        self.colliders.append(collider)
+        # Add the collider to the collision Node
+        self.collisionNode.addSolid( collider )
+        self.numColliders += 1
+
+    def pickGeom(self, targetNodePath = render, endPointList = [],
+                 skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
+        self.collideWithGeom()
+        for i in range(min(len(endPointList), self.numColliders)):
+            pointA, pointB = endPointList[i]
+            collider = self.colliders[i]
+            collider.setPointA( pointA )
+            collider.setPointB( pointB )
+        self.ct.traverse( targetNodePath )
+        # Determine collision entry
+        return self.findCollisionEntry(skipFlags)
+
+    def pickBitMask(self, bitMask = BitMask32.allOff(),
+                    targetNodePath = render, endPointList = [],
+                 skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
+        self.collideWithBitMask(bitMask)
+        for i in range(min(len(endPointList), self.numColliders)):
+            pointA, pointB = endPointList[i]
+            collider = self.colliders[i]
+            collider.setPointA( pointA )
+            collider.setPointB( pointB )
+        self.ct.traverse( targetNodePath )
+        # Determine collision entry
+        return self.findCollisionEntry(skipFlags)
+
+
+class SelectionSphere(SelectionQueue):
+    # Wrapper around collision sphere
+    def __init__(self, parentNP = render, numSpheres = 1):
+        # Initialize the superclass
+        SelectionQueue.__init__(self, parentNP)
+        self.colliders = []
+        self.numColliders = 0
+        for i in range(numSpheres):
+            self.addCollider(CollisionSphere(Point3(0), 1))
+        
+    def addCollider(self, collider):
+        # Record new collision object
+        self.colliders.append(collider)
+        # Add the collider to the collision Node
+        self.collisionNode.addSolid( collider )
+        self.numColliders += 1
+    
+    def setCenter(self, i, center):
+        c = self.colliders[i]
+        c.setCenter(center)
+    
+    def setRadius(self, i, radius):
+        c = self.colliders[i]
+        c.setRadius(radius)
+    
+    def setCenterRadius(self, i, center, radius):
+        c = self.colliders[i]
+        c.setCenter(center)
+        c.setRadius(radius)
+    
+    def isEntryBackfacing(self, entry):
+        # If dot product of collision point surface normal and
+        # ray from sphere origin to collision point is positive, 
+        # center is on the backside of the polygon
+        fromNodePath = entry.getFromNodePath()
+        v = Vec3(entry.getSurfacePoint(fromNodePath) -
+                 entry.getFrom().getCenter())
+        n = entry.getSurfaceNormal(fromNodePath)
+        # If points almost on top of each other, reject face
+        # (treat as backfacing)
+        if v.length() < 0.05:
+            return 1
+        # Normalize and check angle between to vectors
+        v.normalize()
+        return v.dot(n) >= 0
+
+
+    def pick(self, targetNodePath, skipFlags):
+        self.ct.traverse( targetNodePath )
+        self.sortEntries()
+        return self.findCollisionEntry(skipFlags)
+
+    def pickGeom(self, targetNodePath = render,
+                 skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
+        self.collideWithGeom()
+        return self.pick(targetNodePath, skipFlags)
+    
+    def pickBitMask(self, bitMask = BitMask32.allOff(),
+                    targetNodePath = render,
+                    skipFlags = SKIP_HIDDEN | SKIP_CAMERA ):
+        self.collideWithBitMask(bitMask)
+        return self.pick(targetNodePath, skipFlags)
+

+ 991 - 0
contrib/src/sceneeditor/seSession.py

@@ -0,0 +1,991 @@
+#################################################################
+# seSession.py
+# Originally from DirectSession.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# We took out a lot of stuff we don't need in the sceneeditor.
+# This is also the main reason we didn't just inherite the original directSession.
+# Also, the way of selecting, renaming and some hot-key controls are changed.
+#
+#################################################################
+from direct.showbase.DirectObject import *
+from direct.directtools.DirectGlobals import *
+from direct.directtools.DirectUtil import*
+from direct.interval.IntervalGlobal import *
+from seCameraControl import *
+from seManipulation import *
+from seSelection import *
+from seGrid import *
+from seGeometry import *
+from direct.tkpanels import Placer
+from direct.tkwidgets import Slider
+from direct.gui import OnscreenText
+import types
+import string
+from direct.showbase import Loader
+
+class SeSession(DirectObject):  ### Customized DirectSession
+
+    def __init__(self):
+        # Establish a global pointer to the direct object early on
+        # so dependant classes can access it in their code
+        __builtins__["SEditor"] = self
+        # These come early since they are used later on
+        self.group = render.attachNewNode('SEditor')
+        self.font = TextNode.getDefaultFont()
+        self.fEnabled = 0
+        self.drList = DisplayRegionList()
+        self.iRayList = map(lambda x: x.iRay, self.drList)
+        self.dr = self.drList[0]
+        self.camera = base.camera
+        self.trueCamera = self.camera
+        self.iRay = self.dr.iRay
+        self.coaMode = COA_ORIGIN
+
+        self.enableAutoCamera = True
+
+        self.cameraControl = DirectCameraControl()
+        self.manipulationControl = DirectManipulationControl()
+        self.useObjectHandles()
+        self.grid = DirectGrid()
+        self.grid.disable()
+        
+        # Initialize the collection of selected nodePaths
+        self.selected = SelectedNodePaths()
+        # Ancestry of currently selected object
+        self.ancestry = []
+        self.ancestryIndex = 0
+        self.activeParent = None
+
+        self.selectedNPReadout = OnscreenText.OnscreenText(
+            pos = (-1.0, -0.9), bg=Vec4(1,1,1,1),
+            scale = 0.05, align = TextNode.ALeft,
+            mayChange = 1, font = self.font)
+        # Make sure readout is never lit or drawn in wireframe
+        useDirectRenderStyle(self.selectedNPReadout)
+        self.selectedNPReadout.reparentTo(hidden)
+
+        self.activeParentReadout = OnscreenText.OnscreenText(
+            pos = (-1.0, -0.975), bg=Vec4(1,1,1,1),
+            scale = 0.05, align = TextNode.ALeft,
+            mayChange = 1, font = self.font)
+        # Make sure readout is never lit or drawn in wireframe
+        useDirectRenderStyle(self.activeParentReadout)
+        self.activeParentReadout.reparentTo(hidden)
+
+        self.directMessageReadout = OnscreenText.OnscreenText(
+            pos = (-1.0, 0.9), bg=Vec4(1,1,1,1),
+            scale = 0.05, align = TextNode.ALeft,
+            mayChange = 1, font = self.font)
+        # Make sure readout is never lit or drawn in wireframe
+        useDirectRenderStyle(self.directMessageReadout)
+        self.directMessageReadout.reparentTo(hidden)
+
+        self.fControl = 0
+        self.fAlt = 0
+        self.fShift = 0
+
+        self.pos = VBase3()
+        self.hpr = VBase3()
+        self.scale = VBase3()
+
+        self.hitPt = Point3(0.0)
+
+        # Lists for managing undo/redo operations
+        self.undoList = []
+        self.redoList = []
+        
+        # One run through the context task to init everything
+        self.drList.updateContext()
+        for dr in self.drList:
+            dr.camUpdate()
+
+        
+
+        self.modifierEvents = ['control', 'control-up',
+                              'shift', 'shift-up',
+                              'alt', 'alt-up',
+                              ]
+        self.keyEvents = ['escape', 'delete', 'page_up', 'page_down', 
+                          '[', '{', ']', '}',
+                          'shift-a', 'b', 'control-f',
+                          'l', 'shift-l', 'o', 'p', 'r',
+                          'shift-r', 's', 't', 'v', 'w']
+        self.mouseEvents = ['mouse1', 'mouse1-up',
+                            'shift-mouse1', 'shift-mouse1-up',
+                            'control-mouse1', 'control-mouse1-up',
+                            'alt-mouse1', 'alt-mouse1-up',
+                            'mouse2', 'mouse2-up',
+                            'shift-mouse2', 'shift-mouse2-up',
+                            'control-mouse2', 'control-mouse2-up',
+                            'alt-mouse2', 'alt-mouse2-up',
+                            'mouse3', 'mouse3-up',
+                            'shift-mouse3', 'shift-mouse3-up',
+                            'control-mouse3', 'control-mouse3-up',
+                            'alt-mouse3', 'alt-mouse3-up',
+                            ]
+            
+    def enable(self):
+        if self.fEnabled:
+            return
+        # Make sure old tasks are shut down
+        self.disable()
+        # Start all display region context tasks
+        self.drList.spawnContextTask()
+        # Turn on mouse Flying
+        self.cameraControl.enableMouseFly()
+        # Turn on object manipulation
+        self.manipulationControl.enableManipulation()
+        # Make sure list of selected items is reset
+        self.selected.reset()
+        # Accept appropriate hooks
+        self.enableKeyEvents()
+        self.enableModifierEvents()
+        self.enableMouseEvents()
+        # Set flag
+        self.fEnabled = 1
+
+        if self.enableAutoCamera:
+            self.accept('DH_LoadingComplete', self.autoCameraMove)
+
+    def disable(self):
+        # Shut down all display region context tasks
+        self.drList.removeContextTask()
+        # Turn off camera fly
+        self.cameraControl.disableMouseFly()
+        # Turn off object manipulation
+        self.manipulationControl.disableManipulation()
+        self.disableKeyEvents()
+        self.disableModifierEvents()
+        self.disableMouseEvents()
+        self.ignore('DH_LoadingComplete')
+        # Kill tasks
+        taskMgr.remove('flashNodePath')
+        taskMgr.remove('hideDirectMessage')
+        taskMgr.remove('hideDirectMessageLater')
+        # Set flag
+        self.fEnabled = 0
+
+    def minimumConfiguration(self):
+        # Remove context task
+        self.drList.removeContextTask()
+        # Turn off camera fly
+        self.cameraControl.disableMouseFly()
+        # Ignore keyboard and action events
+        self.disableKeyEvents()
+        self.disableActionEvents()
+        # But let mouse events pass through
+        self.enableMouseEvents()
+        self.enableModifierEvents()
+
+    def oobe(self):
+        # If oobeMode was never set, set it to false and create the
+        # structures we need to implement OOBE.
+        try:
+            self.oobeMode
+        except:
+            self.oobeMode = 0
+
+            self.oobeCamera = hidden.attachNewNode('oobeCamera')
+
+            self.oobeVis = loader.loadModelOnce('models/misc/camera')
+            if self.oobeVis:
+                self.oobeVis.node().setFinal(1)
+
+        if self.oobeMode:
+            # Position a target point to lerp the oobe camera to
+            self.cameraControl.camManipRef.iPosHpr(self.trueCamera)
+            t = self.oobeCamera.lerpPosHpr(
+                Point3(0), Vec3(0), 2.0,
+                other = self.cameraControl.camManipRef,
+                task = 'manipulateCamera',
+                blendType = 'easeInOut')
+            # When move is done, switch to oobe mode
+            t.uponDeath = self.endOOBE
+        else:
+            # Place camera marker at true camera location
+            self.oobeVis.reparentTo(self.trueCamera)
+            # Remove any transformation on the models arc
+            self.oobeVis.clearMat()
+            # Make oobeCamera be a sibling of wherever camera is now.
+            cameraParent = self.camera.getParent()
+            # Prepare oobe camera
+            self.oobeCamera.reparentTo(cameraParent)
+            self.oobeCamera.iPosHpr(self.trueCamera)
+            # Put camera under new oobe camera
+            base.cam.reparentTo(self.oobeCamera)
+            # Position a target point to lerp the oobe camera to
+            self.cameraControl.camManipRef.setPos(
+                self.trueCamera, Vec3(-2,-20, 5))
+            self.cameraControl.camManipRef.lookAt(self.trueCamera)
+            t = self.oobeCamera.lerpPosHpr(
+                Point3(0), Vec3(0), 2.0,
+                other = self.cameraControl.camManipRef,
+                task = 'manipulateCamera',
+                blendType = 'easeInOut')
+            # When move is done, switch to oobe mode
+            t.uponDeath = self.beginOOBE
+
+    def beginOOBE(self, state):
+        # Make sure we've reached our final destination
+        self.oobeCamera.iPosHpr(self.cameraControl.camManipRef)
+        self.camera = self.oobeCamera
+        self.oobeMode = 1
+
+    def endOOBE(self, state):
+        # Make sure we've reached our final destination
+        self.oobeCamera.iPosHpr(self.trueCamera)
+        # Disable OOBE mode.
+        base.cam.reparentTo(self.trueCamera)
+        self.camera = self.trueCamera
+        # Get rid of ancillary node paths
+        self.oobeVis.reparentTo(hidden)
+        self.oobeCamera.reparentTo(hidden)
+        self.oobeMode = 0
+
+    def destroy(self):
+        self.disable()
+
+    def reset(self):
+        self.enable()
+
+    # EVENT FUNCTIONS
+    def enableModifierEvents(self):
+        for event in self.modifierEvents:
+            self.accept(event, self.inputHandler, [event])
+
+    def enableKeyEvents(self):
+        for event in self.keyEvents:
+            self.accept(event, self.inputHandler, [event])
+
+    def enableMouseEvents(self):
+        for event in self.mouseEvents:
+            self.accept(event, self.inputHandler, [event])
+
+    def disableModifierEvents(self):
+        for event in self.modifierEvents:
+            self.ignore(event)
+
+    def disableKeyEvents(self):
+        for event in self.keyEvents:
+            self.ignore(event)
+
+    def disableMouseEvents(self):
+        for event in self.mouseEvents:
+            self.ignore(event)
+
+    def inputHandler(self, input):
+        # Deal with keyboard and mouse input
+        if input == 'mouse1-up':
+            messenger.send('DIRECT-mouse1Up')
+            if SEditor.widget.fActive:
+                messenger.send('shift-f')
+        elif input.find('mouse1') != -1:
+            modifiers = self.getModifiers(input, 'mouse1')
+            messenger.send('DIRECT-mouse1', sentArgs = [modifiers])
+        elif input == 'mouse2-up':
+            messenger.send('DIRECT-mouse2Up')
+            if SEditor.widget.fActive:
+                messenger.send('shift-f')
+        elif input.find('mouse2') != -1:
+            modifiers = self.getModifiers(input, 'mouse2')
+            messenger.send('DIRECT-mouse2', sentArgs = [modifiers])
+        elif input == 'mouse3-up':
+            messenger.send('DIRECT-mouse3Up')
+            if SEditor.widget.fActive:
+                messenger.send('shift-f')
+        elif input.find('mouse3') != -1:
+            modifiers = self.getModifiers(input, 'mouse3')
+            messenger.send('DIRECT-mouse3', sentArgs = [modifiers])
+        elif input == 'shift':
+            self.fShift = 1
+        elif input == 'shift-up':
+            self.fShift = 0
+        elif input == 'control':
+            self.fControl = 1
+        elif input == 'control-up':
+            self.fControl = 0
+        elif input == 'alt':
+            self.fAlt = 1
+        elif input == 'alt-up':
+            self.fAlt = 0
+        elif input == 'page_up':
+            self.upAncestry()
+        elif input == 'page_down':
+            self.downAncestry()
+        elif input == 'escape':
+            self.deselectAll()
+        elif input == 'delete':
+            taskMgr.remove('followSelectedNodePath')
+            #self.removeAllSelected()
+            messenger.send('SGE_Remove',[None])
+            self.deselectAll()
+        elif input == 'v':
+            messenger.send('SEditor-ToggleWidgetVis')
+            self.toggleWidgetVis()
+            if SEditor.widget.fActive:
+                messenger.send('shift-f')
+        elif input == 'b':
+            messenger.send('SEditor-ToggleBackface')
+            base.toggleBackface()
+        #elif input == 'control-f':
+        #    self.flash(last)
+        elif input == 'shift-l':
+            self.cameraControl.toggleCOALock()
+        elif input == 'o':
+            self.oobe()
+        elif input == 'p':
+            if self.selected.last:
+                self.setActiveParent(self.selected.last)
+        elif input == 'r':
+            # Do wrt reparent
+            if self.selected.last:
+                self.reparent(self.selected.last, fWrt = 1)
+        elif input == 'shift-r':
+            # Do regular reparent
+            if self.selected.last:
+                self.reparent(self.selected.last)
+        elif input == 's':
+            if self.selected.last:
+                self.select(self.selected.last)
+        elif input == 't':
+            messenger.send('SEditor-ToggleTexture')
+            base.toggleTexture()
+        elif input == 'shift-a':
+            self.selected.toggleVisAll()
+        elif input == 'w':
+            messenger.send('SEditor-ToggleWireframe')
+            base.toggleWireframe()
+        elif (input == '[') or (input == '{'):
+            self.undo()
+        elif (input == ']') or (input == '}'):
+            self.redo()
+
+        
+    def getModifiers(self, input, base):
+        modifiers = DIRECT_NO_MOD
+        modifierString = input[: input.find(base)]
+        if modifierString.find('shift') != -1:
+            modifiers |= DIRECT_SHIFT_MOD
+        if modifierString.find('control') != -1:
+            modifiers |= DIRECT_CONTROL_MOD
+        if modifierString.find('alt') != -1:
+            modifiers |= DIRECT_ALT_MOD
+        return modifiers
+
+    def gotShift(self, modifiers):
+        return modifiers & DIRECT_SHIFT_MOD
+
+    def gotControl(self, modifiers):
+        return modifiers & DIRECT_CONTROL_MOD
+
+    def gotAlt(self, modifiers):
+        return modifiers & DIRECT_ALT_MOD
+
+    def select(self, nodePath, fMultiSelect = 0, fResetAncestry = 1, callback=False):
+        dnp = self.selected.select(nodePath, fMultiSelect)
+        if dnp:
+            messenger.send('DIRECT_preSelectNodePath', [dnp])
+            if fResetAncestry:
+                # Update ancestry
+                self.ancestry = dnp.getAncestors()
+                self.ancestry.reverse()
+                self.ancestryIndex = 0
+            # Update the selectedNPReadout
+            self.selectedNPReadout.reparentTo(aspect2d)
+            self.selectedNPReadout.setText(
+                'Selected:' + dnp.getName())
+            # Show the manipulation widget
+            self.widget.showWidget()
+            # Update camera controls coa to this point
+            # Coa2Camera = Coa2Dnp * Dnp2Camera
+            mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(self.camera)
+            row = mCoa2Camera.getRow(3)
+            coa = Vec3(row[0], row[1], row[2])
+            self.cameraControl.updateCoa(coa)
+            # Adjust widgets size
+            # This uses the additional scaling factor used to grow and
+            # shrink the widget            
+            self.widget.setScalingFactor(dnp.getRadius())
+            # Spawn task to have object handles follow the selected object
+            taskMgr.remove('followSelectedNodePath')
+            t = Task.Task(self.followSelectedNodePathTask)
+            t.dnp = dnp
+            taskMgr.add(t, 'followSelectedNodePath')
+            # Send an message marking the event
+            messenger.send('DIRECT_selectedNodePath', [dnp])
+            if callback:
+                messenger.send('se_selectedNodePath', [dnp, False])
+            else:
+                messenger.send('se_selectedNodePath', [dnp])
+
+            self.upAncestry()
+
+            if SEditor.widget.fActive:
+                messenger.send('shift-f')
+
+    def followSelectedNodePathTask(self, state):
+        mCoa2Render = state.dnp.mCoa2Dnp * state.dnp.getMat(render)
+        decomposeMatrix(mCoa2Render,
+                        self.scale,self.hpr,self.pos,
+                        CSDefault)
+        self.widget.setPosHpr(self.pos,self.hpr)
+        return Task.cont
+
+    def deselect(self, nodePath):
+        dnp = self.selected.deselect(nodePath)
+        if dnp:
+            # Hide the manipulation widget
+            self.widget.hideWidget()
+            self.selectedNPReadout.reparentTo(hidden)
+            self.selectedNPReadout.setText(' ')
+            taskMgr.remove('followSelectedNodePath')
+            self.ancestry = []
+            # Send an message marking the event
+            messenger.send('DIRECT_deselectedNodePath', [dnp])
+
+    def deselectAll(self):
+        self.selected.deselectAll()
+        # Hide the manipulation widget
+        self.widget.hideWidget()
+        self.selectedNPReadout.reparentTo(hidden)
+        self.selectedNPReadout.setText(' ')
+        taskMgr.remove('followSelectedNodePath')
+        messenger.send('se_deselectedAll')
+
+    def setActiveParent(self, nodePath = None):
+        # Record new parent
+        self.activeParent = nodePath
+        # Update the activeParentReadout
+        self.activeParentReadout.reparentTo(aspect2d)
+        self.activeParentReadout.setText(
+            'Active Reparent Target:' + nodePath.getName())
+        # Alert everyone else
+        self.activeParentReadout.show()
+        
+    def reparent(self, nodePath = None, fWrt = 0):
+        if (nodePath and self.activeParent and
+            self.isNotCycle(nodePath, self.activeParent)):
+            oldParent = nodePath.getParent()
+            if fWrt:
+                nodePath.wrtReparentTo(self.activeParent)
+            else:
+                nodePath.reparentTo(self.activeParent)
+            # Alert everyone else
+            messenger.send('DIRECT_reparent',
+                           [nodePath, oldParent, self.activeParent])
+            messenger.send('SGE_Update Explorer',[render])
+            self.activeParentReadout.hide()
+
+
+    def isNotCycle(self, nodePath, parent):
+        if nodePath.id() == parent.id():
+            print 'DIRECT.reparent: Invalid parent'
+            return 0
+        elif parent.hasParent():
+            return self.isNotCycle(nodePath, parent.getParent())
+        else:
+            return 1
+        
+    def fitOnNodePath(self, nodePath = 'None Given'):
+        if nodePath == 'None Given':
+            # If nothing specified, try selected node path
+            nodePath = self.selected.last
+        SEditor.select(nodePath)
+        def fitTask(state, self = self):
+            self.cameraControl.fitOnWidget()
+            return Task.done
+        taskMgr.doMethodLater(0.1, fitTask, 'manipulateCamera')
+
+    def isolate(self, nodePath = 'None Given'):
+        """ Show a node path and hide its siblings """
+        # First kill the flashing task to avoid complications
+        taskMgr.remove('flashNodePath')
+        # Use currently selected node path if node selected
+        if nodePath == 'None Given':
+            nodePath = self.selected.last
+        # Do we have a node path?
+        if nodePath:
+            # Yes, show everything in level
+            self.showAllDescendants(nodePath.getParent())
+            # Now hide all of this node path's siblings
+            nodePath.hideSiblings()
+
+    def toggleVis(self, nodePath = 'None Given'):
+        """ Toggle visibility of node path """
+        # First kill the flashing task to avoid complications
+        taskMgr.remove('flashNodePath')
+        if nodePath == 'None Given':
+            # If nothing specified, try selected node path
+            nodePath = self.selected.last
+        if nodePath:
+            # Now toggle node path's visibility state
+            nodePath.toggleVis()
+
+    def removeNodePath(self, nodePath = 'None Given'):
+        if nodePath == 'None Given':
+            # If nothing specified, try selected node path
+            nodePath = self.selected.last
+        if nodePath:
+            nodePath.remove()
+
+    def removeAllSelected(self):
+        self.selected.removeAll()
+
+    def showAllDescendants(self, nodePath = render):
+        """ Show the level and its descendants """
+        nodePath.showAllDescendants()
+        nodePath.hideCS()
+
+    def upAncestry(self):
+        if self.ancestry:
+            l = len(self.ancestry)
+            i = self.ancestryIndex + 1
+            if i < l:
+                np = self.ancestry[i]
+                name = np.getName()
+                if i>0:
+                    type = self.ancestry[i-1].node().getType().getName()
+                else:
+                    type = self.ancestry[0].node().getType().getName()
+
+                ntype = np.node().getType().getName()
+                if (name != 'render') and (name != 'renderTop')and(self.checkTypeNameForAncestry(type, ntype)):
+                    self.ancestryIndex = i
+                    self.select(np, 0, 0, True)
+            
+    def checkTypeNameForAncestry(self, type, nextType ):
+        if (type=='ModelRoot'):
+            if (nextType=='AmbientLight')or(nextType=='PointLight')or(nextType=='DirectionalLight')or(nextType=='Spotlight'):
+                return True
+            return False
+        elif (type=='ModelNode'):
+            if (nextType=='ModelNode'):
+                return True
+            return False
+        elif (type=='CollisionNode'):
+            return False
+        elif (type=='ActorNode'):
+            return False
+        elif (type=='AmbientLight')or(type=='PointLight')or(type=='DirectionalLight')or(type=='Spotlight'):
+            return False
+        else:
+            return True
+
+    def downAncestry(self):
+        if self.ancestry:
+            l = len(self.ancestry)
+            i = self.ancestryIndex - 1
+            if i >= 0:
+                np = self.ancestry[i]
+                name = np.getName()
+                if (name != 'render') and (name != 'renderTop'):
+                    self.ancestryIndex = i
+                    self.select(np, 0, 0, True)
+
+
+    def getAndSetName(self, nodePath):
+        """ Prompt user for new node path name """
+        from tkSimpleDialog import askstring
+        newName = askstring('Node Path: ' + nodePath.getName(),
+                            'Enter new name:')
+        if newName:
+            nodePath.setName(newName)
+            messenger.send('DIRECT_nodePathSetName', [nodePath, newName])
+
+    # UNDO REDO FUNCTIONS
+    def pushUndo(self, nodePathList, fResetRedo = 1):
+        # Assemble group of changes
+        undoGroup = []
+        for nodePath in nodePathList:
+            t = nodePath.getTransform()
+            undoGroup.append([nodePath, t])
+        # Now record group
+        self.undoList.append(undoGroup)
+        # Truncate list
+        self.undoList = self.undoList[-25:]
+        # Alert anyone who cares
+        messenger.send('DIRECT_pushUndo')
+        if fResetRedo and (nodePathList != []):
+            self.redoList = []
+            messenger.send('DIRECT_redoListEmpty')
+
+    def popUndoGroup(self):
+        # Get last item
+        undoGroup = self.undoList[-1]
+        # Strip last item off of undo list
+        self.undoList = self.undoList[:-1]
+        # Update state of undo button
+        if not self.undoList:
+            messenger.send('DIRECT_undoListEmpty')
+        # Return last item
+        return undoGroup
+        
+    def pushRedo(self, nodePathList):
+        # Assemble group of changes
+        redoGroup = []
+        for nodePath in nodePathList:
+            t = nodePath.getTransform()
+            redoGroup.append([nodePath, t])
+        # Now record redo group
+        self.redoList.append(redoGroup)
+        # Truncate list
+        self.redoList = self.redoList[-25:]
+        # Alert anyone who cares
+        messenger.send('DIRECT_pushRedo')
+
+    def popRedoGroup(self):
+        # Get last item
+        redoGroup = self.redoList[-1]
+        # Strip last item off of redo list
+        self.redoList = self.redoList[:-1]
+        # Update state of redo button
+        if not self.redoList:
+            messenger.send('DIRECT_redoListEmpty')
+        # Return last item
+        return redoGroup
+        
+    def undo(self):
+        if self.undoList:
+            # Get last item off of redo list
+            undoGroup = self.popUndoGroup()
+            # Record redo information
+            nodePathList = map(lambda x: x[0], undoGroup)
+            self.pushRedo(nodePathList)
+            # Now undo xform for group
+            for pose in undoGroup:
+                # Undo xform
+                pose[0].setTransform(pose[1])
+            # Alert anyone who cares
+            messenger.send('DIRECT_undo')
+
+    def redo(self):
+        if self.redoList:
+            # Get last item off of redo list
+            redoGroup = self.popRedoGroup()
+            # Record undo information
+            nodePathList = map(lambda x: x[0], redoGroup)
+            self.pushUndo(nodePathList, fResetRedo = 0)
+            # Redo xform
+            for pose in redoGroup:
+                pose[0].setTransform(pose[1])
+            # Alert anyone who cares
+            messenger.send('DIRECT_redo')
+
+    # UTILITY FUNCTIONS
+    def message(self, text):
+        taskMgr.remove('hideDirectMessage')
+        taskMgr.remove('hideDirectMessageLater')
+        self.directMessageReadout.reparentTo(aspect2d)
+        self.directMessageReadout.setText(text)
+        self.hideDirectMessageLater()
+
+    def hideDirectMessageLater(self):
+        taskMgr.doMethodLater(3.0, self.hideDirectMessage, 'hideDirectMessage')
+
+    def hideDirectMessage(self, state):
+        self.directMessageReadout.reparentTo(hidden)
+        return Task.done
+
+    def useObjectHandles(self):
+        self.widget = self.manipulationControl.objectHandles
+        self.widget.reparentTo(SEditor.group)
+
+    def hideSelectedNPReadout(self):
+        self.selectedNPReadout.reparentTo(hidden)
+
+    def hideActiveParentReadout(self):
+        self.activeParentReadout.reparentTo(hidden)
+
+    def toggleWidgetVis(self):
+        self.widget.toggleWidget()
+
+    def setCOAMode(self, mode):
+        self.coaMode = mode
+
+    def isEnabled(self):
+        return self.fEnabled
+
+    def addUnpickable(self, item):
+        for iRay in self.iRayList:
+            iRay.addUnpickable(item)
+
+    def removeUnpickable(self, item):
+        for iRay in self.iRayList:
+            iRay.removeUnpickable(item)
+
+    def toggleAutoCamera(self):
+        self.enableAutoCamera = (self.enableAutoCamera+1)%2
+        if self.enableAutoCamera==1:
+            self.accept('DH_LoadingComplete', self.autoCameraMove)
+        else:
+            self.ignore('DH_LoadingComplete')
+        return
+
+    def autoCameraMove(self, nodePath):
+        time = 1
+        node = DirectNodePath(nodePath)
+        radius = node.getRadius()
+        center = node.getCenter()
+        node.dehighlight()
+        posB = base.camera.getPos()
+        hprB = base.camera.getHpr()
+        posE = Point3((radius*-1.41)+center.getX(), (radius*-1.41)+center.getY(), (radius*1.41)+center.getZ())
+        hprE = Point3(-45, -38, 0)
+        print posB, hprB
+        print posE, hprE
+        posInterval1 = base.camera.posInterval(time, posE, bakeInStart = 1)
+        posInterval2 = base.camera.posInterval(time, posB, bakeInStart = 1)
+        
+        hprInterval1 = base.camera.hprInterval(time, hprE, bakeInStart = 1)
+        hprInterval2 = base.camera.hprInterval(time, hprB, bakeInStart = 1)
+
+        parallel1 = Parallel(posInterval1, hprInterval1)
+        parallel2 = Parallel(posInterval2, hprInterval2)
+
+        Sequence(Wait(7), parallel1, Wait(1), parallel2).start()
+        
+        return
+    
+
+class DisplayRegionContext(DirectObject):
+    regionCount = 0
+    def __init__(self, cam):
+        self.cam = cam
+        self.camNode = self.cam.node()
+        self.camLens = self.camNode.getLens()
+        # set lens change callback
+        changeEvent = 'dr%d-change-event' % DisplayRegionContext.regionCount
+        DisplayRegionContext.regionCount += 1
+        self.camLens.setChangeEvent(changeEvent)
+        self.accept(changeEvent, self.camUpdate)
+        self.iRay = SelectionRay(self.cam)
+        self.nearVec = Vec3(0)
+        self.mouseX = 0.0
+        self.mouseY = 0.0
+        # A Camera node can have more than one display region
+        # associated with it.  Here I assume that there is only
+        # one display region per camera, since we are defining a
+        # display region on a per-camera basis.  See note in
+        # DisplayRegionList.__init__()
+        try:
+            self.dr = self.camNode.getDr(0)
+        except:
+            self.dr = self.camNode.getDisplayRegion(0)
+        left = self.dr.getLeft()
+        right = self.dr.getRight()
+        bottom = self.dr.getBottom()
+        top = self.dr.getTop()
+        self.originX = left+right-1
+        self.originY = top+bottom-1
+        self.scaleX = 1.0/(right-left)
+        self.scaleY = 1.0/(top-bottom)
+        self.setOrientation()
+        self.camUpdate()
+
+    def __getitem__(self,key):
+        return self.__dict__[key]
+
+    def setOrientation(self):
+        # MRM This assumes orientation is set on transform above cam
+        hpr = self.cam.getHpr()
+        if hpr[2] < 135 and hpr[2]>45 or hpr[2]>225 and hpr[2]<315:
+            self.isSideways = 1
+        elif hpr[2] > -135 and hpr[2] < -45 or hpr[2] < -225 and hpr[2] > -315:
+            self.isSideways = 1
+        else:
+            self.isSideways = 0
+
+    # The following take into consideration sideways displays
+    def getHfov(self):
+        if self.isSideways:
+            return self.camLens.getVfov()
+        else:
+            return self.camLens.getHfov()
+
+    def getVfov(self):
+        if self.isSideways:
+            return self.camLens.getHfov()
+        else:
+            return self.camLens.getVfov()
+
+    def setHfov(self,hfov):
+        if self.isSideways:
+            self.camLens.setFov(self.camLens.getHfov(), hfov)
+        else:
+            self.camLens.setFov(hfov, self.camLens.getVfov())
+
+    def setVfov(self,vfov):
+        if self.isSideways:
+            self.camLens.setFov(vfov, self.camLens.getVfov())
+        else:
+            self.camLens.setFov(self.camLens.getHfov(), vfov)
+
+    def setFov(self,hfov,vfov):
+        if self.isSideways:
+            self.camLens.setFov(vfov, hfov)
+        else:
+            self.camLens.setFov(hfov, vfov)
+
+    def getWidth(self):
+        prop = base.win.getProperties()
+        if prop.hasSize():
+            return prop.getXSize()
+        else:
+            return 640
+            
+    def getHeight(self):
+        prop = base.win.getProperties()
+        if prop.hasSize():
+            return prop.getYSize()
+        else:
+            return 480
+            
+    def camUpdate(self, lens = None):
+        # Window Data
+        self.near = self.camLens.getNear()
+        self.far = self.camLens.getFar()
+        self.fovH = self.camLens.getHfov()
+        self.fovV = self.camLens.getVfov()
+        self.nearWidth = math.tan(deg2Rad(self.fovH * 0.5)) * self.near * 2.0
+        self.nearHeight = math.tan(deg2Rad(self.fovV * 0.5)) * self.near * 2.0
+        self.left = -self.nearWidth * 0.5
+        self.right = self.nearWidth * 0.5
+        self.top = self.nearHeight * 0.5
+        self.bottom = -self.nearHeight * 0.5
+
+    def mouseUpdate(self):
+        # Mouse Data
+        # Last frame
+        self.mouseLastX = self.mouseX
+        self.mouseLastY = self.mouseY
+        # Values for this frame
+        # This ranges from -1 to 1
+        if (base.mouseWatcherNode.hasMouse()):
+            self.mouseX = base.mouseWatcherNode.getMouseX()
+            self.mouseY = base.mouseWatcherNode.getMouseY()
+            self.mouseX = (self.mouseX-self.originX)*self.scaleX
+            self.mouseY = (self.mouseY-self.originY)*self.scaleY
+        # Delta percent of window the mouse moved
+        self.mouseDeltaX = self.mouseX - self.mouseLastX
+        self.mouseDeltaY = self.mouseY - self.mouseLastY
+        self.nearVec.set((self.nearWidth*0.5) * self.mouseX,
+                         self.near,
+                         (self.nearHeight*0.5) * self.mouseY)
+
+class DisplayRegionList(DirectObject):
+    def __init__(self):
+        self.displayRegionList = []
+        i = 0
+        # Things are funky if we are oobe
+        if (hasattr(base, 'oobeMode') and base.oobeMode):
+            # assume we only have one cam at this point
+            drc = DisplayRegionContext(base.cam)
+            self.displayRegionList.append(drc)
+        else:
+            # MRM: Doesn't properly handle multiple camera groups anymore
+            # Assumes everything is under main camera
+            
+                # This is following the old way of setting up
+                # display regions.  A display region is set up for
+                # each camera node in the scene graph.  This was done
+                # so that only display regions in the scene graph are
+                # considered.  The right way to do this is to set up
+                # a display region for each real display region, and then
+                # keep track of which are currently active (e.g. use a flag)
+                # processing only them.
+                for camIndex in range(len(base.camList)):
+                    cam = base.camList[camIndex]
+                    if cam.getName()=='<noname>':
+                        cam.setName('Camera%d' % camIndex)
+                    drc = DisplayRegionContext(cam)
+                    self.displayRegionList.append(drc)
+
+        self.accept("DIRECT-mouse1",self.mouseUpdate)
+        self.accept("DIRECT-mouse2",self.mouseUpdate)
+        self.accept("DIRECT-mouse3",self.mouseUpdate)
+        self.accept("DIRECT-mouse1Up",self.mouseUpdate)
+        self.accept("DIRECT-mouse2Up",self.mouseUpdate)
+        self.accept("DIRECT-mouse3Up",self.mouseUpdate)
+
+    def __getitem__(self, index):
+        return self.displayRegionList[index]
+
+    def __len__(self):
+        return len(self.displayRegionList)
+
+    def updateContext(self):
+        self.contextTask(None)
+
+    def setNearFar(self, near, far):
+        for dr in self.displayRegionList:
+            dr.camLens.setNearFar(near, far)
+    
+    def setNear(self, near):
+        for dr in self.displayRegionList:
+            dr.camLens.setNear(near)
+    
+    def setFar(self, far):
+        for dr in self.displayRegionList:
+            dr.camLens.setFar(far)
+
+    def setFov(self, hfov, vfov):
+        for dr in self.displayRegionList:
+            dr.setFov(hfov, vfov)
+
+    def setHfov(self, fov):
+        for dr in self.displayRegionList:
+            dr.setHfov(fov)
+
+    def setVfov(self, fov):
+        for dr in self.displayRegionList:
+            dr.setVfov(fov)
+
+    def mouseUpdate(self, modifiers = DIRECT_NO_MOD):
+        for dr in self.displayRegionList:
+            dr.mouseUpdate()
+        SEditor.dr = self.getCurrentDr()
+
+    def getCurrentDr(self):
+        for dr in self.displayRegionList:
+            if (dr.mouseX >= -1.0 and dr.mouseX <= 1.0 and
+                dr.mouseY >= -1.0 and dr.mouseY <= 1.0):
+                return dr
+        return self.displayRegionList[0]
+
+    def start(self):
+        # First shutdown any existing task
+        self.stop()
+        # Start a new context task
+        self.spawnContextTask()
+
+    def stop(self):
+        # Kill the existing context task
+        taskMgr.remove('DIRECTContextTask')
+
+    def spawnContextTask(self):
+        taskMgr.add(self.contextTask, 'DIRECTContextTask')
+
+    def removeContextTask(self):
+        taskMgr.remove('DIRECTContextTask')
+
+    def contextTask(self, state):
+        # Window Data
+        self.mouseUpdate()
+        # hack to test movement
+        return Task.cont
+
+# Create one
+#__builtins__['direct'] = base.direct = DirectSession()
+
+
+
+
+
+
+
+
+
+
+
+

+ 418 - 0
contrib/src/sceneeditor/seTree.py

@@ -0,0 +1,418 @@
+#################################################################
+# seTree.py
+# Originally from Tree.py
+# Altered by Yi-Hong Lin, [email protected], 2004
+#
+# This class actually decides the behavior of the sceneGraphExplorer
+# You might feel it realy looks like the original one, but we actually did a lots of change in it.
+# such as, when selection happend in other place, such as picking directly inside the scene. or,
+# when user removed something by hot key.
+# The rename process has also been changed. It won't be rename in here anymore.
+# Instead, here we will send out a message to sceneEditor to reaname the target.
+#
+#################################################################
+
+import os, sys, string, Pmw, Tkinter
+from direct.showbase.DirectObject import DirectObject
+from Tkinter import IntVar, Menu, PhotoImage, Label, Frame, Entry
+from pandac.PandaModules import *
+
+# Initialize icon directory
+ICONDIR = getModelPath().findFile(Filename('icons')).toOsSpecific()
+if not os.path.isdir(ICONDIR):
+    raise RuntimeError, "can't find DIRECT icon directory (%s)" % `ICONDIR`
+
+class TreeNode:
+
+    def __init__(self, canvas, parent, item, menuList = []):
+        self.canvas = canvas
+        self.parent = parent
+        self.item = item
+        self.state = 'collapsed'
+        self.selected = 0
+        self.children = {}
+        self.kidKeys = []
+        self.x = self.y = None
+        self.iconimages = {} # cache of PhotoImage instances for icons
+        self.menuList = menuList
+        self.menuVar = IntVar()
+        self.menuVar.set(0)
+        self._popupMenu = None
+        self.image_id = None
+        if self.menuList:
+            if self.menuList[-1] == 'Separator':
+                self.menuList = self.menuList[:-1]
+            self._popupMenu = Menu(self.canvas, tearoff = 0)
+            for i in range(len(self.menuList)):
+                item = self.menuList[i]
+                if item == 'Separator':
+                    self._popupMenu.add_separator()
+                else:
+                    self._popupMenu.add_radiobutton(
+                        label = item,
+                        variable = self.menuVar,
+                        value = i,
+                        indicatoron = 0,
+                        command = self.popupMenuCommand)
+                    
+    def destroy(self):
+        for key in self.kidKeys:
+            c = self.children[key]
+            del self.children[key]
+            c.destroy()
+        self.parent = None
+
+    def geticonimage(self, name):
+        try:
+            return self.iconimages[name]
+        except KeyError:
+            pass
+        file, ext = os.path.splitext(name)
+        ext = ext or ".gif"
+        fullname = os.path.join(ICONDIR, file + ext)
+        image = PhotoImage(master=self.canvas, file=fullname)
+        self.iconimages[name] = image
+        return image
+
+    def select(self, event=None):
+        if self.selected:
+            return
+        self.deselectall()
+        self.selected = 1
+        if self.parent != None:
+            if self.parent.state == 'expanded':
+                self.canvas.delete(self.image_id)
+                self.drawicon()
+                self.drawtext()
+        self.item.OnSelect(event)
+
+    def deselect(self, event=None):
+        if not self.selected:
+            return
+        self.selected = 0
+        if self.parent != None:
+            if self.parent.state == 'expanded':
+                self.canvas.delete(self.image_id)
+                self.drawicon()
+                self.drawtext()
+
+    def deselectall(self):
+        if self.parent:
+            self.parent.deselectall()
+        else:
+            self.deselecttree()
+
+    def deselecttree(self):
+        if self.selected:
+            self.deselect()
+        for key in self.kidKeys:
+            child = self.children[key]
+            child.deselecttree()
+
+    def flip(self, event=None):
+        if self.state == 'expanded':
+            self.collapse()
+        else:
+            self.expand()
+        self.item.OnDoubleClick()
+        return "break"
+
+    def popupMenu(self, event=None):
+        if self._popupMenu:
+            self._popupMenu.post(event.widget.winfo_pointerx(),
+                                 event.widget.winfo_pointery())
+            return "break"
+
+    def popupMenuCommand(self):
+        command = self.menuList[self.menuVar.get()]
+        self.item.MenuCommand(command)
+        if self.parent and (command != 'Update Explorer'):
+            # Update parent to try to keep explorer up to date
+            self.parent.update()
+
+    def expand(self, event=None):
+        if not self.item.IsExpandable():
+            return
+        if self.state != 'expanded':
+            self.state = 'expanded'
+            self.update()
+            self.view()
+
+    def collapse(self, event=None):
+        if self.state != 'collapsed':
+            self.state = 'collapsed'
+            self.update()
+
+    def view(self):
+        top = self.y - 2
+        bottom = self.lastvisiblechild().y + 17
+        height = bottom - top
+        visible_top = self.canvas.canvasy(0)
+        visible_height = self.canvas.winfo_height()
+        visible_bottom = self.canvas.canvasy(visible_height)
+        if visible_top <= top and bottom <= visible_bottom:
+            return
+        x0, y0, x1, y1 = self.canvas._getints(self.canvas['scrollregion'])
+        if top >= visible_top and height <= visible_height:
+            fraction = top + height - visible_height
+        else:
+            fraction = top
+        fraction = float(fraction) / y1
+        self.canvas.yview_moveto(fraction)
+
+    def reveal(self):
+        # Make sure all parent nodes are marked as expanded
+        parent = self.parent
+        while parent:
+            if parent.state == 'collapsed':
+                parent.state = 'expanded'
+                parent = parent.parent
+            else:
+                break
+        # Redraw tree accordingly
+        self.update()
+        # Bring this item into view
+        self.view()
+
+    def lastvisiblechild(self):
+        if self.kidKeys and self.state == 'expanded':
+            return self.children[self.kidKeys[-1]].lastvisiblechild()
+        else:
+            return self
+
+    def update(self):
+        if self.parent:
+            self.parent.update()
+        else:
+            oldcursor = self.canvas['cursor']
+            self.canvas['cursor'] = "watch"
+            self.canvas.update()
+            self.canvas.delete(Tkinter.ALL)     # XXX could be more subtle
+            self.draw(7, 2)
+            x0, y0, x1, y1 = self.canvas.bbox(Tkinter.ALL)
+            self.canvas.configure(scrollregion=(0, 0, x1, y1))
+            self.canvas['cursor'] = oldcursor
+
+    def draw(self, x, y):
+        # XXX This hard-codes too many geometry constants!
+        self.x, self.y = x, y
+        self.drawicon()
+        self.drawtext()
+        if self.state != 'expanded':
+            return y+17
+        # draw children
+        sublist = self.item._GetSubList()
+        if not sublist:
+            # IsExpandable() was mistaken; that's allowed
+            return y+17
+        self.kidKeys = []
+        for item in sublist:
+            key = item.GetKey()
+            if self.children.has_key(key):
+                child = self.children[key]
+            else:
+                child = TreeNode(self.canvas, self, item, self.menuList)
+            self.children[key] = child
+            self.kidKeys.append(key)
+        # Remove unused children
+        for key in self.children.keys():
+            if key not in self.kidKeys:
+                del(self.children[key])
+        cx = x+20
+        cy = y+17
+        cylast = 0
+        for key in self.kidKeys:
+            child = self.children[key]
+            cylast = cy
+            self.canvas.create_line(x+9, cy+7, cx, cy+7, fill="gray50")
+            cy = child.draw(cx, cy)
+            if child.item.IsExpandable():
+                if child.state == 'expanded':
+                    iconname = "minusnode"
+                    callback = child.collapse
+                else:
+                    iconname = "plusnode"
+                    callback = child.expand
+                image = self.geticonimage(iconname)
+                id = self.canvas.create_image(x+9, cylast+7, image=image)
+                # XXX This leaks bindings until canvas is deleted:
+                self.canvas.tag_bind(id, "<1>", callback)
+                self.canvas.tag_bind(id, "<Double-1>", lambda x: None)
+        id = self.canvas.create_line(x+9, y+10, x+9, cylast+7,
+            ##stipple="gray50",     # XXX Seems broken in Tk 8.0.x
+            fill="gray50")
+        self.canvas.tag_lower(id) # XXX .lower(id) before Python 1.5.2
+        return cy
+
+    def drawicon(self):
+        if self.selected:
+            imagename = (self.item.GetSelectedIconName() or
+                         self.item.GetIconName() or
+                         "openfolder")
+        else:
+            imagename = self.item.GetIconName() or "folder"
+        image = self.geticonimage(imagename)
+        id = self.canvas.create_image(self.x, self.y, anchor="nw", image=image)
+        self.image_id = id
+        self.canvas.tag_bind(id, "<1>", self.select)
+        self.canvas.tag_bind(id, "<Double-1>", self.flip)
+        self.canvas.tag_bind(id, "<3>", self.popupMenu)
+        
+    def drawtext(self, text=None):
+        textx = self.x+20-1
+        texty = self.y-1
+        labeltext = self.item.GetLabelText()
+        if labeltext:
+            id = self.canvas.create_text(textx, texty, anchor="nw",
+                                         text=labeltext)
+            self.canvas.tag_bind(id, "<1>", self.select)
+            self.canvas.tag_bind(id, "<Double-1>", self.flip)
+            x0, y0, x1, y1 = self.canvas.bbox(id)
+            textx = max(x1, 200) + 10
+        if text==None:
+            text = self.item.GetText() or "<no text>"
+        try:
+            self.entry
+        except AttributeError:
+            pass
+        else:
+            self.edit_finish()
+        try:
+            label = self.label
+        except AttributeError:
+            # padding carefully selected (on Windows) to match Entry widget:
+            self.label = Label(self.canvas, text=text, bd=0, padx=2, pady=2)
+        if self.selected:
+            self.label.configure(fg="white", bg="darkblue")
+        else:
+            fg = self.item.GetTextFg()
+            self.label.configure(fg=fg, bg="white")
+        id = self.canvas.create_window(textx, texty,
+                                       anchor="nw", window=self.label)
+        self.label.bind("<1>", self.select_or_edit)
+        self.label.bind("<Double-1>", self.flip)
+        self.label.bind("<3>", self.popupMenu)
+        # Update text if necessary
+        if text != self.label['text']:
+            self.label['text'] = text
+        self.text_id = id
+
+    def select_or_edit(self, event=None):
+        if self.selected and self.item.IsEditable():
+            text = self.item.GetTextForEdit()
+            self.label['text'] = text
+            self.drawtext(text)
+            self.edit(event)
+        else:
+            self.select(event)
+
+    def edit(self, event=None):
+        self.entry = Entry(self.label, bd=0, highlightthickness=1, width=0)
+        self.entry.insert(0, self.label['text'])
+        self.entry.selection_range(0, Tkinter.END)
+        self.entry.pack(ipadx=5)
+        self.entry.focus_set()
+        self.entry.bind("<Return>", self.edit_finish)
+        self.entry.bind("<Escape>", self.edit_cancel)
+
+    def edit_finish(self, event=None):
+        try:
+            entry = self.entry
+            del self.entry
+        except AttributeError:
+            return
+        text = entry.get()
+        entry.destroy()
+        if text and text != self.item.GetText():
+            self.item.SetText(text)
+        text = self.item.GetText()
+        self.label['text'] = text
+        self.drawtext()
+        self.canvas.focus_set()
+
+    def edit_cancel(self, event=None):
+        self.drawtext()
+        self.canvas.focus_set()
+
+    def find(self, searchKey):
+        # Search for a node who's key matches the given key
+        # Is it this node
+        if searchKey == self.item.GetKey():
+            return self
+        # Nope, check the children
+        sublist = self.item._GetSubList()
+        for item in sublist:
+            key = item.GetKey()
+            # Use existing child or create new TreeNode if none exists
+            if self.children.has_key(key):
+                child = self.children[key]
+            else:
+                child = TreeNode(self.canvas, self, item, self.menuList)
+                # Update local list of children and keys
+                self.children[key] = child
+                self.kidKeys.append(key)
+            # See if node is child (or one of child's descendants)
+            retVal = child.find(searchKey)
+            if retVal:
+                return retVal
+        # Not here
+        return None
+
+class TreeItem:
+
+    """Abstract class representing tree items.
+
+    Methods should typically be overridden, otherwise a default action
+    is used.
+
+    """
+
+    def __init__(self):
+        """Constructor.  Do whatever you need to do."""
+
+    def GetText(self):
+        """Return text string to display."""
+
+    def GetTextFg(self):
+        return "black"
+
+    def GetLabelText(self):
+        """Return label text string to display in front of text (if any)."""
+
+    def IsExpandable(self):
+        """Return whether there are subitems."""
+        return 1
+
+    def _GetSubList(self):
+        """Do not override!  Called by TreeNode."""
+        if not self.IsExpandable():
+            return []
+        sublist = self.GetSubList()
+        return sublist
+
+    def IsEditable(self):
+        """Return whether the item's text may be edited."""
+
+    def SetText(self, text):
+        """Change the item's text (if it is editable)."""
+
+    def GetIconName(self):
+        """Return name of icon to be displayed normally."""
+
+    def GetSelectedIconName(self):
+        """Return name of icon to be displayed when selected."""
+
+    def GetSubList(self):
+        """Return list of items forming sublist."""
+
+    def OnDoubleClick(self):
+        """Called on a double-click on the item."""
+
+    def OnSelect(self):
+        """Called when item selected."""
+        
+    def GetTextForEdit(self):
+        """Called before editting the item."""
+
+
+