Browse Source

*** empty log message ***

Mark Mine 25 years ago
parent
commit
f361e4d922

+ 12 - 6
direct/src/directdevices/DirectJoybox.py

@@ -175,6 +175,9 @@ class DirectJoybox(PandaObject):
         self.enable()
         self.enable()
         
         
     def joyboxFly(self):
     def joyboxFly(self):
+        # Do nothing if no nodePath selected
+        if self.nodePath == None:
+            return
         hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) *
         hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) *
                     DirectJoybox.hprMultiplier)
                     DirectJoybox.hprMultiplier)
         posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) *
         posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) *
@@ -215,6 +218,12 @@ class DirectJoybox(PandaObject):
         self.modifier = [1,1,1,-1,1,0]
         self.modifier = [1,1,1,-1,1,0]
         self.setMode(self.joyboxFly, 'Look At Mode')
         self.setMode(self.joyboxFly, 'Look At Mode')
 
 
+    def lookAroundMode(self):
+        self.mapping = [NULL_AXIS, NULL_AXIS, NULL_AXIS,
+                        R_LEFT_RIGHT, R_FWD_BACK, NULL_AXIS]
+        self.modifier = [0,0,0,-1,-1,0]
+        self.setMode(self.joyboxFly, 'Lookaround Mode')
+
     def demoMode(self):
     def demoMode(self):
         self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_FWD_BACK,
         self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_FWD_BACK,
                         R_TWIST, NULL_AXIS, NULL_AXIS]
                         R_TWIST, NULL_AXIS, NULL_AXIS]
@@ -227,12 +236,6 @@ class DirectJoybox(PandaObject):
         self.modifier = [1,1,-1,-1,-1,1]
         self.modifier = [1,1,-1,-1,-1,1]
         self.setMode(self.joyboxFly, 'HprXyz Mode')
         self.setMode(self.joyboxFly, 'HprXyz Mode')
 
 
-    def lookaroundMode(self):
-        self.mapping = [NULL_AXIS, NULL_AXIS, NULL_AXIS,
-                        R_LEFT_RIGHT, R_FWD_BACK, NULL_AXIS]
-        self.modifier = [0,0,0,-1,-1,0]
-        self.setMode(self.joyboxFly, 'Lookaround Mode')
-
     def walkthruMode(self):
     def walkthruMode(self):
         self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_TWIST,
         self.mapping = [R_LEFT_RIGHT, R_FWD_BACK, L_TWIST,
                         R_TWIST, L_FWD_BACK, L_LEFT_RIGHT]
                         R_TWIST, L_FWD_BACK, L_LEFT_RIGHT]
@@ -243,6 +246,9 @@ class DirectJoybox(PandaObject):
         self.setMode(self.orbitFly, 'Orbit Mode')
         self.setMode(self.orbitFly, 'Orbit Mode')
 
 
     def orbitFly(self):
     def orbitFly(self):
+        # Do nothing if no nodePath selected
+        if self.nodePath == None:
+            return
         hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) *
         hprScale = (self.analogs.normalizeChannel(L_SLIDE, 0.1, 100) *
                     DirectJoybox.hprMultiplier)
                     DirectJoybox.hprMultiplier)
         posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) *
         posScale = (self.analogs.normalizeChannel(R_SLIDE, 0.1, 100) *

+ 60 - 11
direct/src/directtools/DirectSelection.py

@@ -380,9 +380,9 @@ class DirectBoundingBox:
 
 
 
 
 class SelectionRay:
 class SelectionRay:
-    def __init__(self, camera):
-        # Create a collision node path attached to the given camera
-        self.rayCollisionNodePath = camera.attachNewNode( CollisionNode() )
+    def __init__(self, parent):
+        # Create a collision node path attached to the given parent
+        self.rayCollisionNodePath = parent.attachNewNode( CollisionNode() )
         # Don't pay the penalty of drawing this collision ray
         # Don't pay the penalty of drawing this collision ray
         self.rayCollisionNodePath.hide()
         self.rayCollisionNodePath.hide()
         self.rayCollisionNode = self.rayCollisionNodePath.node()
         self.rayCollisionNode = self.rayCollisionNodePath.node()
@@ -419,7 +419,7 @@ class SelectionRay:
         index = -1
         index = -1
         # Pick out the closest object that isn't a widget
         # Pick out the closest object that isn't a widget
         for i in range(0,numEntries):
         for i in range(0,numEntries):
-            entry = direct.iRay.cq.getEntry(i)
+            entry = self.cq.getEntry(i)
             node = entry.getIntoNode()
             node = entry.getIntoNode()
             # Don't pick hidden nodes
             # Don't pick hidden nodes
             if node.isHidden():
             if node.isHidden():
@@ -443,8 +443,8 @@ class SelectionRay:
         # Did we hit an object?
         # Did we hit an object?
         if(index >= 0):
         if(index >= 0):
             # Yes!
             # Yes!
-            # Find hit point in camera's space
-            hitPt = direct.iRay.camToHitPt(index)
+            # Find hit point in parent's space
+            hitPt = self.parentToHitPt(index)
             hitPtDist = Vec3(hitPt - ZERO_POINT).length()
             hitPtDist = Vec3(hitPt - ZERO_POINT).length()
             return (node, hitPt, hitPtDist)
             return (node, hitPt, hitPtDist)
         else:
         else:
@@ -460,11 +460,11 @@ class SelectionRay:
             # Yes!
             # Yes!
             # Entry 0 is the closest hit point if multiple hits
             # Entry 0 is the closest hit point if multiple hits
             minPt = 0
             minPt = 0
-            # Find hit point in camera's space
-            hitPt = direct.iRay.camToHitPt(minPt)
+            # Find hit point in parent's space
+            hitPt = self.parentToHitPt(minPt)
             hitPtDist = Vec3(hitPt).length()
             hitPtDist = Vec3(hitPt).length()
             # Get the associated collision queue object
             # Get the associated collision queue object
-            entry = direct.iRay.cq.getEntry(minPt)
+            entry = self.cq.getEntry(minPt)
             # Extract the node
             # Extract the node
             node = entry.getIntoNode()
             node = entry.getIntoNode()
             # Return info
             # Return info
@@ -481,6 +481,55 @@ class SelectionRay:
         self.cq.sortEntries()
         self.cq.sortEntries()
         return self.numEntries
         return self.numEntries
 
 
+    def pickGeom3D(self, targetNodePath = render, origin = Point3(0),
+                   dir = Vec3(0,0,-1), fIntersectUnpickable = 0):
+        self.collideWithGeom()
+        numEntries = self.pick3D(targetNodePath, origin, dir)
+        # Init index
+        index = -1
+        # Pick out the closest object that isn't a widget
+        for i in range(0,numEntries):
+            entry = self.cq.getEntry(i)
+            node = entry.getIntoNode()
+            # Don't pick hidden nodes
+            if node.isHidden():
+                pass
+            # Can pick unpickable, use the first visible node
+            elif fIntersectUnpickable:
+                index = i
+                break
+            # Is it a named node?, If so, see if it has a name
+            elif issubclass(node.__class__, NamedNode):
+                name = node.getName()
+                if name in self.unpickable:
+                    pass
+                else:
+                    index = i
+                    break
+            # Not hidden and not one of the widgets, use it
+            else:
+                index = i
+                break
+        # Did we hit an object?
+        if(index >= 0):
+            # Yes!
+            # Find hit point in parent's space
+            hitPt = self.parentToHitPt(index)
+            hitPtDist = Vec3(hitPt - ZERO_POINT).length()
+            return (node, hitPt, hitPtDist)
+        else:
+            return (None, ZERO_POINT, 0)
+
+    def pick3D(self, targetNodePath, origin, dir):
+        # Determine ray direction based upon the mouse coordinates
+        # Note! This has to be a cam object (of type ProjectionNode)
+        self.ray.setOrigin( origin )
+        self.ray.setDirection( dir )
+        self.ct.traverse( targetNodePath.node() )
+        self.numEntries = self.cq.getNumEntries()
+        self.cq.sortEntries()
+        return self.numEntries
+
     def collideWithGeom(self):
     def collideWithGeom(self):
         self.rayCollisionNode.setIntoCollideMask(BitMask32().allOff())
         self.rayCollisionNode.setIntoCollideMask(BitMask32().allOff())
         self.rayCollisionNode.setFromCollideMask(BitMask32().allOff())
         self.rayCollisionNode.setFromCollideMask(BitMask32().allOff())
@@ -496,10 +545,10 @@ class SelectionRay:
     def objectToHitPt(self, index):
     def objectToHitPt(self, index):
         return self.cq.getEntry(index).getIntoIntersectionPoint()
         return self.cq.getEntry(index).getIntoIntersectionPoint()
 
 
-    def camToHitPt(self, index):
+    def parentToHitPt(self, index):
         # Get the specified entry
         # Get the specified entry
         entry = self.cq.getEntry(index)
         entry = self.cq.getEntry(index)
         hitPt = entry.getIntoIntersectionPoint()
         hitPt = entry.getIntoIntersectionPoint()
-        # Convert point from object local space to camera space
+        # Convert point from object local space to parent's space
         return entry.getInvWrtSpace().xformPoint(hitPt)
         return entry.getInvWrtSpace().xformPoint(hitPt)
 
 

+ 2 - 0
direct/src/directtools/DirectSession.py

@@ -59,6 +59,8 @@ class DirectSession(PandaObject):
             if joybox:
             if joybox:
                 from DirectJoybox import *
                 from DirectJoybox import *
                 self.joybox = DirectJoybox(joybox)
                 self.joybox = DirectJoybox(joybox)
+            else:
+                self.joybox = None
         else:
         else:
             self.deviceManager = None
             self.deviceManager = None
 
 

+ 236 - 80
direct/src/tkpanels/DirectSessionPanel.py

@@ -59,6 +59,13 @@ class DirectSessionPanel(AppShell):
         self.nodePathDict['widget'] = direct.widget
         self.nodePathDict['widget'] = direct.widget
         self.nodePathNames = ['widget']
         self.nodePathNames = ['widget']
 
 
+        # Dictionary keeping track of all jb node paths selected so far
+        self.jbNodePathDict = {}
+        self.jbNodePathDict['none'] = 'No Node Path'
+        self.jbNodePathDict['widget'] = direct.widget
+        self.jbNodePathDict['camera'] = direct.camera
+        self.jbNodePathNames = ['camera', 'selected', 'none']
+
         # Set up event hooks
         # Set up event hooks
         self.actionEvents = [('undo', self.undoHook),
         self.actionEvents = [('undo', self.undoHook),
                              ('pushUndo', self.pushUndoHook),
                              ('pushUndo', self.pushUndoHook),
@@ -108,7 +115,7 @@ class DirectSessionPanel(AppShell):
             self.nodePathMenu.component('entryfield_entry'))
             self.nodePathMenu.component('entryfield_entry'))
         self.nodePathMenuBG = (
         self.nodePathMenuBG = (
             self.nodePathMenuEntry.configure('background')[3])
             self.nodePathMenuEntry.configure('background')[3])
-        self.nodePathMenu.pack(side = 'left', fill = 'x', expand = 1)
+        self.nodePathMenu.pack(side = LEFT, fill = X, expand = 1)
         self.bind(self.nodePathMenu, 'Select node path to manipulate')
         self.bind(self.nodePathMenu, 'Select node path to manipulate')
 
 
         self.undoButton = Button(menuFrame, text = 'Undo',
         self.undoButton = Button(menuFrame, text = 'Undo',
@@ -117,7 +124,7 @@ class DirectSessionPanel(AppShell):
             self.undoButton['state'] = 'normal'
             self.undoButton['state'] = 'normal'
         else:
         else:
             self.undoButton['state'] = 'disabled'
             self.undoButton['state'] = 'disabled'
-        self.undoButton.pack(side = 'left', expand = 0)
+        self.undoButton.pack(side = LEFT, expand = 0)
         self.bind(self.undoButton, 'Undo last operation')
         self.bind(self.undoButton, 'Undo last operation')
 
 
         self.redoButton = Button(menuFrame, text = 'Redo',
         self.redoButton = Button(menuFrame, text = 'Redo',
@@ -126,7 +133,7 @@ class DirectSessionPanel(AppShell):
             self.redoButton['state'] = 'normal'
             self.redoButton['state'] = 'normal'
         else:
         else:
             self.redoButton['state'] = 'disabled'
             self.redoButton['state'] = 'disabled'
-        self.redoButton.pack(side = 'left', expand = 0)
+        self.redoButton.pack(side = LEFT, expand = 0)
         self.bind(self.redoButton, 'Redo last operation')
         self.bind(self.redoButton, 'Redo last operation')
 
 
         # The master frame for the dials
         # The master frame for the dials
@@ -135,7 +142,7 @@ class DirectSessionPanel(AppShell):
         # Scene Graph Explorer
         # Scene Graph Explorer
         sgeFrame = Frame(mainFrame)
         sgeFrame = Frame(mainFrame)
         self.sgeUpdate = Button(sgeFrame, text = 'Update Explorer')
         self.sgeUpdate = Button(sgeFrame, text = 'Update Explorer')
-        self.sgeUpdate.pack(fill = 'x', expand = 0)
+        self.sgeUpdate.pack(fill = X, expand = 0)
         self.SGE = SceneGraphExplorer.SceneGraphExplorer(
         self.SGE = SceneGraphExplorer.SceneGraphExplorer(
             sgeFrame, nodePath = render,
             sgeFrame, nodePath = render,
             scrolledCanvas_hull_width = 200,
             scrolledCanvas_hull_width = 200,
@@ -149,8 +156,8 @@ class DirectSessionPanel(AppShell):
         notebook.pack(fill = BOTH, expand = 1)
         notebook.pack(fill = BOTH, expand = 1)
         envPage = notebook.add('Environment')
         envPage = notebook.add('Environment')
         lightsPage = notebook.add('Lights')
         lightsPage = notebook.add('Lights')
-        renderPage = notebook.add('Render Style')
         gridPage = notebook.add('Grid')
         gridPage = notebook.add('Grid')
+        devicePage = notebook.add('Devices')
         scenePage = notebook.add('Scene')
         scenePage = notebook.add('Scene')
         # Put this here so it isn't called right away
         # Put this here so it isn't called right away
         notebook['raisecommand'] = self.updateInfo
         notebook['raisecommand'] = self.updateInfo
@@ -165,7 +172,7 @@ class DirectSessionPanel(AppShell):
         self.backgroundColor = VectorWidgets.ColorEntry(
         self.backgroundColor = VectorWidgets.ColorEntry(
             bkgrdFrame, text = 'Background Color')
             bkgrdFrame, text = 'Background Color')
         self.backgroundColor['command'] = self.setBackgroundColor
         self.backgroundColor['command'] = self.setBackgroundColor
-        self.backgroundColor.pack(fill = 'x', expand = 0)
+        self.backgroundColor.pack(fill = X, expand = 0)
         self.bind(self.backgroundColor, 'Set background color')
         self.bind(self.backgroundColor, 'Set background color')
         bkgrdFrame.pack(fill = BOTH, expand = 0)
         bkgrdFrame.pack(fill = BOTH, expand = 0)
 
 
@@ -180,7 +187,7 @@ class DirectSessionPanel(AppShell):
             entry_width = 20,
             entry_width = 20,
             selectioncommand = self.selectDisplayRegionNamed,
             selectioncommand = self.selectDisplayRegionNamed,
             scrolledlist_items = nameList)
             scrolledlist_items = nameList)
-        self.drMenu.pack(fill = 'x', expand = 0)
+        self.drMenu.pack(fill = X, expand = 0)
         self.bind(self.drMenu, 'Select display region to configure')
         self.bind(self.drMenu, 'Select display region to configure')
         
         
         self.nearPlane = Floater.Floater(
         self.nearPlane = Floater.Floater(
@@ -188,7 +195,7 @@ class DirectSessionPanel(AppShell):
             text = 'Near Plane',
             text = 'Near Plane',
             min = 0.01)
             min = 0.01)
         self.nearPlane['command'] = self.setNear
         self.nearPlane['command'] = self.setNear
-        self.nearPlane.pack(fill = 'x', expand = 0)
+        self.nearPlane.pack(fill = X, expand = 0)
         self.bind(self.nearPlane, 'Set near plane distance')
         self.bind(self.nearPlane, 'Set near plane distance')
            
            
         self.farPlane = Floater.Floater(
         self.farPlane = Floater.Floater(
@@ -196,36 +203,78 @@ class DirectSessionPanel(AppShell):
             text = 'Far Plane',
             text = 'Far Plane',
             min = 0.01)
             min = 0.01)
         self.farPlane['command'] = self.setFar
         self.farPlane['command'] = self.setFar
-        self.farPlane.pack(fill = 'x', expand = 0)
+        self.farPlane.pack(fill = X, expand = 0)
         self.bind(self.farPlane, 'Set far plane distance')
         self.bind(self.farPlane, 'Set far plane distance')
-           
-        self.hFov = Floater.Floater(
-            drFrame,
+
+        fovFrame = Frame(drFrame)
+        fovFloaterFrame = Frame(fovFrame)
+        self.hFov = EntryScale.EntryScale(
+            fovFloaterFrame,
             text = 'Horizontal FOV',
             text = 'Horizontal FOV',
-            min = 0.01, max = 179.9)
+            min = 0.01, max = 170.0)
         self.hFov['command'] = self.setHFov
         self.hFov['command'] = self.setHFov
-        self.hFov.pack(fill = 'x', expand = 0)
+        self.hFov.pack(fill = X, expand = 0)
         self.bind(self.hFov, 'Set horizontal field of view')
         self.bind(self.hFov, 'Set horizontal field of view')
            
            
-        self.vFov = Floater.Floater(
-            drFrame,
+        self.vFov = EntryScale.EntryScale(
+            fovFloaterFrame,
             text = 'Vertical FOV',
             text = 'Vertical FOV',
-            min = 0.01, max = 179.9)
+            min = 0.01, max = 170.0)
         self.vFov['command'] = self.setVFov
         self.vFov['command'] = self.setVFov
-        self.vFov.pack(fill = 'x', expand = 0)
+        self.vFov.pack(fill = X, expand = 0)
         self.bind(self.vFov, 'Set vertical field of view')
         self.bind(self.vFov, 'Set vertical field of view')
-           
+        fovFloaterFrame.pack(side = LEFT, fill = X, expand = 1)
+
+        frame = Frame(fovFrame)
         self.lockedFov = BooleanVar()
         self.lockedFov = BooleanVar()
         self.lockedFov.set(1)
         self.lockedFov.set(1)
         self.lockedFovButton = Checkbutton(
         self.lockedFovButton = Checkbutton(
-            drFrame,
-            text = 'FOV Locked',
-            anchor = 'w', justify = 'left',
+            frame,
+            text = 'Locked',
+            anchor = 'w', justify = LEFT,
             variable = self.lockedFov)
             variable = self.lockedFov)
-        self.lockedFovButton.pack(fill = 'x', expand = 0)
-
+        self.lockedFovButton.pack(fill = X, expand = 0)
+
+        self.resetFovButton = Button(
+            frame,
+            text = 'Reset',
+            command = self.resetFov)
+        self.resetFovButton.pack(fill = X, expand = 0)
+        frame.pack(side = LEFT, fill = X, expand = 0)
+        fovFrame.pack(fill = X, expand = 1)
+        
+        
         drFrame.pack(fill = BOTH, expand = 0)
         drFrame.pack(fill = BOTH, expand = 0)
 
 
+        ## Render Style ##
+        toggleFrame = Frame(envPage, borderwidth = 2, relief = 'sunken')
+        Label(toggleFrame, text = 'Toggle Render Style',
+              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
+        self.toggleBackfaceButton = Button(
+            toggleFrame,
+            text = 'Backface',
+            command = base.toggleBackface)
+        self.toggleBackfaceButton.pack(side = LEFT, fill = X, expand = 1)
+        
+        self.toggleLightsButton = Button(
+            toggleFrame,
+            text = 'Lights',
+            command = direct.lights.toggle)
+        self.toggleLightsButton.pack(side = LEFT, fill = X, expand = 1)
+
+        self.toggleTextureButton = Button(
+            toggleFrame,
+            text = 'Texture',
+            command = base.toggleTexture)
+        self.toggleTextureButton.pack(side = LEFT, fill = X, expand = 1)
+        
+        self.toggleWireframeButton = Button(
+            toggleFrame,
+            text = 'Wireframe',
+            command = base.toggleWireframe)
+        self.toggleWireframeButton.pack(fill = X, expand = 1)
+        toggleFrame.pack(side = LEFT, fill = X, expand = 1)
+
         ## Lights page ##
         ## Lights page ##
         # Lights #
         # Lights #
         lightFrame = Frame(lightsPage, borderwidth = 2, relief = 'sunken')
         lightFrame = Frame(lightsPage, borderwidth = 2, relief = 'sunken')
@@ -258,15 +307,15 @@ class DirectSessionPanel(AppShell):
         # Main light switch
         # Main light switch
         mainSwitchFrame = Frame(lightFrame)
         mainSwitchFrame = Frame(lightFrame)
         Label(mainSwitchFrame,
         Label(mainSwitchFrame,
-              text = 'Lighting:').pack(side = 'left', expand = 0)
+              text = 'Lighting:').pack(side = LEFT, expand = 0)
         self.enableLights = BooleanVar()
         self.enableLights = BooleanVar()
         self.enableLightsButton = Checkbutton(
         self.enableLightsButton = Checkbutton(
             mainSwitchFrame,
             mainSwitchFrame,
             text = 'Enabled/Disabled',
             text = 'Enabled/Disabled',
             variable = self.enableLights,
             variable = self.enableLights,
             command = self.toggleLights)
             command = self.toggleLights)
-        self.enableLightsButton.pack(side = 'left', fill = 'x', expand = 0)
-        mainSwitchFrame.pack(fill = 'x', expand = 0)
+        self.enableLightsButton.pack(side = LEFT, fill = X, expand = 0)
+        mainSwitchFrame.pack(fill = X, expand = 0)
         
         
         # Widget to select a light to configure
         # Widget to select a light to configure
         nameList = direct.lights.nameList
         nameList = direct.lights.nameList
@@ -277,7 +326,7 @@ class DirectSessionPanel(AppShell):
             entry_width = 20,
             entry_width = 20,
             selectioncommand = self.selectLightNamed,
             selectioncommand = self.selectLightNamed,
             scrolledlist_items = nameList)
             scrolledlist_items = nameList)
-        self.lightMenu.pack(side = 'left', fill = 'x', expand = 0)
+        self.lightMenu.pack(side = LEFT, fill = X, expand = 0)
         self.bind(self.lightMenu, 'Select light to configure')
         self.bind(self.lightMenu, 'Select light to configure')
         
         
         self.lightActive = BooleanVar()
         self.lightActive = BooleanVar()
@@ -286,22 +335,22 @@ class DirectSessionPanel(AppShell):
             text = 'On/Off',
             text = 'On/Off',
             variable = self.lightActive,
             variable = self.lightActive,
             command = self.toggleActiveLight)
             command = self.toggleActiveLight)
-        self.lightActiveButton.pack(side = 'left', fill = 'x', expand = 0)
+        self.lightActiveButton.pack(side = LEFT, fill = X, expand = 0)
 
 
         # Pack light menu
         # Pack light menu
-        lightMenuFrame.pack(fill = 'x', expand = 0, padx = 2)
+        lightMenuFrame.pack(fill = X, expand = 0, padx = 2)
 
 
         self.lightColor = VectorWidgets.ColorEntry(
         self.lightColor = VectorWidgets.ColorEntry(
             lightFrame, text = 'Light Color')
             lightFrame, text = 'Light Color')
         self.lightColor['command'] = self.setLightColor
         self.lightColor['command'] = self.setLightColor
-        self.lightColor.pack(fill = 'x', expand = 0, padx = 4)
+        self.lightColor.pack(fill = X, expand = 0, padx = 4)
         self.bind(self.lightColor, 'Set active light color')
         self.bind(self.lightColor, 'Set active light color')
 
 
         # Directional light controls
         # Directional light controls
         self.dSpecularColor = VectorWidgets.ColorEntry(
         self.dSpecularColor = VectorWidgets.ColorEntry(
             directionalPage, text = 'Specular Color')
             directionalPage, text = 'Specular Color')
         self.dSpecularColor['command'] = self.setSpecularColor
         self.dSpecularColor['command'] = self.setSpecularColor
-        self.dSpecularColor.pack(fill = 'x', expand = 0)
+        self.dSpecularColor.pack(fill = X, expand = 0)
         self.bind(self.dSpecularColor,
         self.bind(self.dSpecularColor,
                   'Set directional light specular color')
                   'Set directional light specular color')
 
 
@@ -309,7 +358,7 @@ class DirectSessionPanel(AppShell):
         self.pSpecularColor = VectorWidgets.ColorEntry(
         self.pSpecularColor = VectorWidgets.ColorEntry(
             pointPage, text = 'Specular Color')
             pointPage, text = 'Specular Color')
         self.pSpecularColor['command'] = self.setSpecularColor
         self.pSpecularColor['command'] = self.setSpecularColor
-        self.pSpecularColor.pack(fill = 'x', expand = 0)
+        self.pSpecularColor.pack(fill = X, expand = 0)
         self.bind(self.pSpecularColor,
         self.bind(self.pSpecularColor,
                   'Set point light specular color')
                   'Set point light specular color')
 
 
@@ -318,7 +367,7 @@ class DirectSessionPanel(AppShell):
             text = 'Constant Attenuation',
             text = 'Constant Attenuation',
             min = 0.0, max = 1.0, initialValue = 1.0)
             min = 0.0, max = 1.0, initialValue = 1.0)
         self.pConstantAttenuation['command'] = self.setConstantAttenuation
         self.pConstantAttenuation['command'] = self.setConstantAttenuation
-        self.pConstantAttenuation.pack(fill = 'x', expand = 0)
+        self.pConstantAttenuation.pack(fill = X, expand = 0)
         self.bind(self.pConstantAttenuation,
         self.bind(self.pConstantAttenuation,
                   'Set point light constant attenuation')
                   'Set point light constant attenuation')
            
            
@@ -327,7 +376,7 @@ class DirectSessionPanel(AppShell):
             text = 'Linear Attenuation',
             text = 'Linear Attenuation',
             min = 0.0, max = 1.0, initialValue = 0.0)
             min = 0.0, max = 1.0, initialValue = 0.0)
         self.pLinearAttenuation['command'] = self.setLinearAttenuation
         self.pLinearAttenuation['command'] = self.setLinearAttenuation
-        self.pLinearAttenuation.pack(fill = 'x', expand = 0)
+        self.pLinearAttenuation.pack(fill = X, expand = 0)
         self.bind(self.pLinearAttenuation,
         self.bind(self.pLinearAttenuation,
                   'Set point light linear attenuation')
                   'Set point light linear attenuation')
            
            
@@ -336,7 +385,7 @@ class DirectSessionPanel(AppShell):
             text = 'Quadratic Attenuation',
             text = 'Quadratic Attenuation',
             min = 0.0, max = 1.0, initialValue = 0.0)
             min = 0.0, max = 1.0, initialValue = 0.0)
         self.pQuadraticAttenuation['command'] = self.setQuadraticAttenuation
         self.pQuadraticAttenuation['command'] = self.setQuadraticAttenuation
-        self.pQuadraticAttenuation.pack(fill = 'x', expand = 0)
+        self.pQuadraticAttenuation.pack(fill = X, expand = 0)
         self.bind(self.pQuadraticAttenuation,
         self.bind(self.pQuadraticAttenuation,
                   'Set point light quadratic attenuation')
                   'Set point light quadratic attenuation')
            
            
@@ -344,7 +393,7 @@ class DirectSessionPanel(AppShell):
         self.sSpecularColor = VectorWidgets.ColorEntry(
         self.sSpecularColor = VectorWidgets.ColorEntry(
             spotPage, text = 'Specular Color')
             spotPage, text = 'Specular Color')
         self.sSpecularColor['command'] = self.setSpecularColor
         self.sSpecularColor['command'] = self.setSpecularColor
-        self.sSpecularColor.pack(fill = 'x', expand = 0)
+        self.sSpecularColor.pack(fill = X, expand = 0)
         self.bind(self.sSpecularColor,
         self.bind(self.sSpecularColor,
                   'Set spot light specular color')
                   'Set spot light specular color')
 
 
@@ -353,7 +402,7 @@ class DirectSessionPanel(AppShell):
             text = 'Constant Attenuation',
             text = 'Constant Attenuation',
             min = 0.0, max = 1.0, initialValue = 1.0)
             min = 0.0, max = 1.0, initialValue = 1.0)
         self.sConstantAttenuation['command'] = self.setConstantAttenuation
         self.sConstantAttenuation['command'] = self.setConstantAttenuation
-        self.sConstantAttenuation.pack(fill = 'x', expand = 0)
+        self.sConstantAttenuation.pack(fill = X, expand = 0)
         self.bind(self.sConstantAttenuation,
         self.bind(self.sConstantAttenuation,
                   'Set spot light constant attenuation')
                   'Set spot light constant attenuation')
            
            
@@ -362,7 +411,7 @@ class DirectSessionPanel(AppShell):
             text = 'Linear Attenuation',
             text = 'Linear Attenuation',
             min = 0.0, max = 1.0, initialValue = 0.0)
             min = 0.0, max = 1.0, initialValue = 0.0)
         self.sLinearAttenuation['command'] = self.setLinearAttenuation
         self.sLinearAttenuation['command'] = self.setLinearAttenuation
-        self.sLinearAttenuation.pack(fill = 'x', expand = 0)
+        self.sLinearAttenuation.pack(fill = X, expand = 0)
         self.bind(self.sLinearAttenuation,
         self.bind(self.sLinearAttenuation,
                   'Set spot light linear attenuation')
                   'Set spot light linear attenuation')
            
            
@@ -371,7 +420,7 @@ class DirectSessionPanel(AppShell):
             text = 'Quadratic Attenuation',
             text = 'Quadratic Attenuation',
             min = 0.0, max = 1.0, initialValue = 0.0)
             min = 0.0, max = 1.0, initialValue = 0.0)
         self.sQuadraticAttenuation['command'] = self.setQuadraticAttenuation
         self.sQuadraticAttenuation['command'] = self.setQuadraticAttenuation
-        self.sQuadraticAttenuation.pack(fill = 'x', expand = 0)
+        self.sQuadraticAttenuation.pack(fill = X, expand = 0)
         self.bind(self.sQuadraticAttenuation,
         self.bind(self.sQuadraticAttenuation,
                   'Set spot light quadratic attenuation')
                   'Set spot light quadratic attenuation')
            
            
@@ -380,7 +429,7 @@ class DirectSessionPanel(AppShell):
             text = 'Exponent',
             text = 'Exponent',
             min = 0.0, max = 1.0, initialValue = 0.0)
             min = 0.0, max = 1.0, initialValue = 0.0)
         self.sExponent['command'] = self.setExponent
         self.sExponent['command'] = self.setExponent
-        self.sExponent.pack(fill = 'x', expand = 0)
+        self.sExponent.pack(fill = X, expand = 0)
         self.bind(self.sExponent,
         self.bind(self.sExponent,
                   'Set spot light exponent')
                   'Set spot light exponent')
 
 
@@ -391,33 +440,6 @@ class DirectSessionPanel(AppShell):
         
         
         lightFrame.pack(expand = 1, fill = BOTH)
         lightFrame.pack(expand = 1, fill = BOTH)
 
 
-        ## Render Style ##
-        toggleFrame = Frame(renderPage)
-        self.toggleBackfaceButton = Button(
-            toggleFrame,
-            text = 'Toggle backface',
-            command = base.toggleBackface)
-        self.toggleBackfaceButton.pack(fill = 'x', expand = 0)
-        
-        self.toggleLightsButton = Button(
-            toggleFrame,
-            text = 'Toggle lights',
-            command = direct.lights.toggle)
-        self.toggleLightsButton.pack(fill = 'x', expand = 0)
-
-        self.toggleTextureButton = Button(
-            toggleFrame,
-            text = 'Toggle texture',
-            command = base.toggleTexture)
-        self.toggleTextureButton.pack(fill = 'x', expand = 0)
-        
-        self.toggleWireframeButton = Button(
-            toggleFrame,
-            text = 'Toggle wireframe',
-            command = base.toggleWireframe)
-        self.toggleWireframeButton.pack(fill = 'x', expand = 0)
-        toggleFrame.pack(fill = BOTH, expand = 0)
-
         ## GRID PAGE ##
         ## GRID PAGE ##
         Label(gridPage, text = 'Grid',
         Label(gridPage, text = 'Grid',
               font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
               font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
@@ -425,28 +447,28 @@ class DirectSessionPanel(AppShell):
         self.enableGridButton = Checkbutton(
         self.enableGridButton = Checkbutton(
             gridPage,
             gridPage,
             text = 'Enabled/Disabled',
             text = 'Enabled/Disabled',
-            anchor = 'w', justify = 'left',
+            anchor = 'w', justify = LEFT,
             variable = self.enableGrid,
             variable = self.enableGrid,
             command = self.toggleGrid)
             command = self.toggleGrid)
-        self.enableGridButton.pack(fill = 'x', expand = 0)
+        self.enableGridButton.pack(fill = X, expand = 0)
 
 
         self.xyzSnap = BooleanVar()
         self.xyzSnap = BooleanVar()
         self.xyzSnapButton = Checkbutton(
         self.xyzSnapButton = Checkbutton(
             gridPage,
             gridPage,
             text = 'XYZ Snap',
             text = 'XYZ Snap',
-            anchor = 'w', justify = 'left',
+            anchor = 'w', justify = LEFT,
             variable = self.xyzSnap,
             variable = self.xyzSnap,
             command = self.toggleXyzSnap)
             command = self.toggleXyzSnap)
-        self.xyzSnapButton.pack(fill = 'x', expand = 0)
+        self.xyzSnapButton.pack(fill = X, expand = 0)
 
 
         self.hprSnap = BooleanVar()
         self.hprSnap = BooleanVar()
         self.hprSnapButton = Checkbutton(
         self.hprSnapButton = Checkbutton(
             gridPage,
             gridPage,
             text = 'HPR Snap',
             text = 'HPR Snap',
-            anchor = 'w', justify = 'left',
+            anchor = 'w', justify = LEFT,
             variable = self.hprSnap,
             variable = self.hprSnap,
             command = self.toggleHprSnap)
             command = self.toggleHprSnap)
-        self.hprSnapButton.pack(fill = 'x', expand = 0)
+        self.hprSnapButton.pack(fill = X, expand = 0)
 
 
         self.gridSpacing = Floater.Floater(
         self.gridSpacing = Floater.Floater(
             gridPage,
             gridPage,
@@ -454,7 +476,7 @@ class DirectSessionPanel(AppShell):
             min = 0.1,
             min = 0.1,
             initialValue = direct.grid.getGridSpacing())
             initialValue = direct.grid.getGridSpacing())
         self.gridSpacing['command'] = direct.grid.setGridSpacing
         self.gridSpacing['command'] = direct.grid.setGridSpacing
-        self.gridSpacing.pack(fill = 'x', expand = 0)
+        self.gridSpacing.pack(fill = X, expand = 0)
         
         
         self.gridSize = Floater.Floater(
         self.gridSize = Floater.Floater(
             gridPage,
             gridPage,
@@ -462,7 +484,7 @@ class DirectSessionPanel(AppShell):
             min = 1.0,
             min = 1.0,
             initialValue = direct.grid.getGridSize())
             initialValue = direct.grid.getGridSize())
         self.gridSize['command'] = direct.grid.setGridSize
         self.gridSize['command'] = direct.grid.setGridSize
-        self.gridSize.pack(fill = 'x', expand = 0)
+        self.gridSize.pack(fill = X, expand = 0)
 
 
         self.gridSnapAngle = Dial.Dial(
         self.gridSnapAngle = Dial.Dial(
             gridPage,
             gridPage,
@@ -472,8 +494,75 @@ class DirectSessionPanel(AppShell):
             fRollover = 0,
             fRollover = 0,
             initialValue = direct.grid.getSnapAngle())
             initialValue = direct.grid.getSnapAngle())
         self.gridSnapAngle['command'] = direct.grid.setSnapAngle
         self.gridSnapAngle['command'] = direct.grid.setSnapAngle
-        self.gridSnapAngle.pack(fill = 'x', expand = 0)
-        
+        self.gridSnapAngle.pack(fill = X, expand = 0)
+
+        ## DEVICE PAGE ##
+        Label(devicePage, text = 'DEVICES',
+              font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
+
+        if direct.joybox != None:
+            joyboxFrame = Frame(devicePage, borderwidth = 2, relief = 'sunken')
+            Label(joyboxFrame, text = 'Joybox',
+                  font=('MSSansSerif', 14, 'bold')).pack(expand = 0)
+            self.enableJoybox = BooleanVar()
+            self.enableJoybox.set(1)
+            self.enableJoyboxButton = Checkbutton(
+                joyboxFrame,
+                text = 'Enabled/Disabled',
+                anchor = 'w', justify = LEFT,
+                variable = self.enableJoybox,
+                command = self.toggleJoybox)
+            self.enableJoyboxButton.pack(fill = X, expand = 0)
+            joyboxFrame.pack(fill = X, expand = 0)
+
+            self.jbModeMenu = Pmw.ComboBox(
+                joyboxFrame, labelpos = W, label_text = 'Joybox Mode:',
+                label_width = 16, entry_width = 20,
+                selectioncommand = self.selectJBModeNamed,
+                scrolledlist_items = ['Joe Mode', 'Drive Mode', 'Orbit Mode',
+                                      'Look At Mode', 'Look Around Mode',
+                                      'Walkthru Mode', 'Demo Mode',
+                                      'HPRXYZ Mode'])
+            self.jbModeMenu.selectitem('Joe Mode')
+            self.jbModeMenu.pack(fill = X, expand = 1)
+            
+            self.jbNodePathMenu = Pmw.ComboBox(
+                joyboxFrame, labelpos = W, label_text = 'Joybox Node Path:',
+                label_width = 16, entry_width = 20,
+                selectioncommand = self.selectJBNodePathNamed,
+                scrolledlist_items = self.jbNodePathNames)
+            self.jbNodePathMenu.selectitem('camera')
+            self.jbNodePathMenuEntry = (
+                self.jbNodePathMenu.component('entryfield_entry'))
+            self.jbNodePathMenuBG = (
+                self.jbNodePathMenuEntry.configure('background')[3])
+            self.jbNodePathMenu.pack(fill = X, expand = 1)
+            self.bind(self.jbNodePathMenu,
+                      'Select node path to manipulate using the joybox')
+
+            self.jbXyzSF = EntryScale.EntryScale(
+                joyboxFrame,
+                text = 'XYZ Scale Factor',
+                initialValue = 1.0,
+                hull_relief = RIDGE, hull_borderwidth = 2,
+                min = 1.0, max = 100.0)
+            self.jbXyzSF['command'] = (
+                lambda v: direct.joybox.setXyzMultiplier(v))
+            self.jbXyzSF.pack(fill = X, expand = 0)
+            self.bind(self.jbXyzSF, 'Set joybox XYZ speed multiplier')
+
+            self.jbHprSF = EntryScale.EntryScale(
+                joyboxFrame,
+                text = 'HPR Scale Factor',
+                initialValue = 1.0,
+                hull_relief = RIDGE, hull_borderwidth = 2,
+                min = 1.0, max = 100.0)
+            self.jbHprSF['command'] = (
+                lambda v: direct.joybox.setHprMultiplier(v))
+            self.jbHprSF.pack(fill = X, expand = 0)
+            self.bind(self.jbHprSF, 'Set joybox HPR speed multiplier')
+
+
         notebook.setnaturalsize()
         notebook.setnaturalsize()
         
         
         mainFrame.pack(fill = 'both', expand = 1)
         mainFrame.pack(fill = 'both', expand = 1)
@@ -546,6 +635,59 @@ class DirectSessionPanel(AppShell):
         self.addNodePathToDict(nodePath, self.nodePathNames,
         self.addNodePathToDict(nodePath, self.nodePathNames,
                                self.nodePathMenu, self.nodePathDict)
                                self.nodePathMenu, self.nodePathDict)
 
 
+    def selectJBModeNamed(self, name):
+        if name == 'Joe Mode':
+            direct.joybox.joeMode()
+        elif name == 'Drive Mode':
+            direct.joybox.driveMode()
+        elif name == 'Orbit Mode':
+            direct.joybox.orbitMode()
+        elif name == 'Look At Mode':
+            direct.joybox.lookAtMode()
+        elif name == 'Look Around Mode':
+            direct.joybox.lookAroundMode()
+        elif name == 'Walkthru Mode':
+            direct.joybox.walkthruMode()
+        elif name == 'Demo Mode':
+            direct.joybox.demoMode()
+        elif name == 'HPRXYZ Mode':
+            direct.joybox.hprXyzMode()
+        
+    def selectJBNodePathNamed(self, name):
+        if name == 'selected':
+            nodePath = direct.selected.last
+            # Add Combo box entry for this selected object
+            self.addJBNodePath(nodePath)
+        else:
+            # See if node path has already been selected
+            nodePath = self.jbNodePathDict.get(name, None)
+            if (nodePath == None):
+                # If not, see if listbox evals into a node path
+                try:
+                    nodePath = eval(name)
+                    if isinstance(nodePath, NodePath):
+                        self.addJBNodePath(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.jbNodePathMenu.component('scrolledlist')
+                    listbox.setlist(self.jbNodePathNames)
+        # Did we finally get something?
+        if (nodePath != None):
+            # Yes, select it!
+            if (nodePath == 'No Node Path'):
+                direct.joybox.setNodePath(None)
+            else:
+                direct.joybox.setNodePath(nodePath)
+        
+    def addJBNodePath(self, nodePath):
+        self.addNodePathToDict(nodePath, self.jbNodePathNames,
+                               self.jbNodePathMenu, self.jbNodePathDict)
+
     def addNodePathToDict(self, nodePath, names, menu, dict):
     def addNodePathToDict(self, nodePath, names, menu, dict):
         if not nodePath:
         if not nodePath:
             return
             return
@@ -598,7 +740,7 @@ class DirectSessionPanel(AppShell):
         if dr:
         if dr:
             if self.lockedFov.get():
             if self.lockedFov.get():
                 sf = hFov/dr.camNode.getHfov()
                 sf = hFov/dr.camNode.getHfov()
-                vFov = dr.camNode.getVfov() * sf
+                vFov = min(dr.camNode.getVfov() * sf, 170.0)
                 dr.camNode.setFov(hFov, vFov)
                 dr.camNode.setFov(hFov, vFov)
                 # Update scale
                 # Update scale
                 self.vFov.set(vFov, 0)
                 self.vFov.set(vFov, 0)
@@ -611,7 +753,7 @@ class DirectSessionPanel(AppShell):
         if dr:
         if dr:
             if self.lockedFov.get():
             if self.lockedFov.get():
                 sf = vFov/dr.camNode.getVfov()
                 sf = vFov/dr.camNode.getVfov()
-                hFov = dr.camNode.getHfov() * sf
+                hFov = min(dr.camNode.getHfov() * sf, 170.0)
                 dr.camNode.setFov(hFov, vFov)
                 dr.camNode.setFov(hFov, vFov)
                 # Update scale
                 # Update scale
                 self.hFov.set(hFov, 0)
                 self.hFov.set(hFov, 0)
@@ -619,6 +761,13 @@ class DirectSessionPanel(AppShell):
                 # Just set horizontal
                 # Just set horizontal
                 dr.camNode.setVfov(vFov)
                 dr.camNode.setVfov(vFov)
 
 
+    def resetFov(self):
+        dr = self.activeDisplayRegion
+        if dr:
+            dr.camNode.setFov(45.0, 33.75)
+            self.hFov.set(45.0, 0)
+            self.vFov.set(33.75, 0)
+            
     # Lights #
     # Lights #
     def selectLightNamed(self, name):
     def selectLightNamed(self, name):
         self.activeLight = None
         self.activeLight = None
@@ -723,6 +872,13 @@ class DirectSessionPanel(AppShell):
     def toggleHprSnap(self):
     def toggleHprSnap(self):
         direct.grid.setHprSnap(self.hprSnap.get())
         direct.grid.setHprSnap(self.hprSnap.get())
 
 
+    ## DEVICE CONTROLS
+    def toggleJoybox(self):
+        if self.enableJoybox.get():
+            direct.joybox.enable()
+        else:
+            direct.joybox.disable()
+
     ## UPDATE INFO ##
     ## UPDATE INFO ##
     def updateInfo(self, page = 'Environment'):
     def updateInfo(self, page = 'Environment'):
         if page == 'Environment':
         if page == 'Environment':

+ 206 - 143
direct/src/tkpanels/MopathRecorder.py

@@ -5,36 +5,28 @@ from PandaObject import *
 from Tkinter import *
 from Tkinter import *
 from AppShell import *
 from AppShell import *
 from DirectGeometry import *
 from DirectGeometry import *
+from DirectSelection import *
 from tkFileDialog import *
 from tkFileDialog import *
 import os
 import os
 import Pmw
 import Pmw
 import Dial
 import Dial
 import Floater
 import Floater
 import EntryScale
 import EntryScale
+import VectorWidgets
 import __builtin__
 import __builtin__
 
 
-"""
-To Add
-Num Segs lines between knots
-Num Ticks
-setCvColor          	<function setCvColor at 01BADDCC>
-setColor            	<function setColor at 01B7B09C>
-setHullColor        	<function setHullColor at 01BADA7C>
-setKnotColor        	<function setKnotColor at 01BADA1C>
-setShowCvs          	<function setShowCvs at 01BAD9BC>
-setShowHull         	<function setShowHull at 01BAD8FC>
-setShowKnots        	<function setShowKnots at 01BADB0C>
-setNumSegs          	<function setNumSegs at 01B7B42C>
-setNumTicks         	<function setNumTicks at 01B7B15C>
-setTickColor        	<function setTickColor at 01B7B03C>
-setTickScale        	<function setTickScale at 01B7CF6C>
-"""
+PRF_UTILITIES = [
+    'lambda: direct.camera.lookAt(render)',
+    'lambda: direct.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, PandaObject):
 class MopathRecorder(AppShell, PandaObject):
     # Override class variables here
     # Override class variables here
     appname = 'Mopath Recorder Panel'
     appname = 'Mopath Recorder Panel'
     frameWidth      = 450
     frameWidth      = 450
-    frameHeight     = 510
+    frameHeight     = 535
     usecommandarea = 0
     usecommandarea = 0
     usestatusarea  = 0
     usestatusarea  = 0
     count = 0
     count = 0
@@ -67,8 +59,6 @@ class MopathRecorder(AppShell, PandaObject):
         self.recorderNodePath = direct.group.attachNewNode(self.name)
         self.recorderNodePath = direct.group.attachNewNode(self.name)
         self.tempCS = self.recorderNodePath.attachNewNode(
         self.tempCS = self.recorderNodePath.attachNewNode(
             'mopathRecorderTempCS')
             'mopathRecorderTempCS')
-        self.editCS = self.recorderNodePath.attachNewNode(
-            'mopathRecorderEditCS')
         self.playbackMarker = loader.loadModel('models/directmodels/smiley')
         self.playbackMarker = loader.loadModel('models/directmodels/smiley')
         self.playbackMarker.reparentTo(self.recorderNodePath)
         self.playbackMarker.reparentTo(self.recorderNodePath)
         self.playbackMarker.hide()
         self.playbackMarker.hide()
@@ -95,14 +85,12 @@ class MopathRecorder(AppShell, PandaObject):
         self.recNodePathDict['camera'] = direct.camera
         self.recNodePathDict['camera'] = direct.camera
         self.recNodePathDict['widget'] = direct.widget
         self.recNodePathDict['widget'] = direct.widget
         self.recNodePathDict['mopathRecorderTempCS'] = self.tempCS
         self.recNodePathDict['mopathRecorderTempCS'] = self.tempCS
-        self.recNodePathDict['edit CS'] = self.editCS
         self.recNodePathNames = ['marker', 'camera', 'selected']
         self.recNodePathNames = ['marker', 'camera', 'selected']
         self.pbNodePathDict = {}
         self.pbNodePathDict = {}
         self.pbNodePathDict['marker'] = self.playbackMarker
         self.pbNodePathDict['marker'] = self.playbackMarker
         self.pbNodePathDict['camera'] = direct.camera
         self.pbNodePathDict['camera'] = direct.camera
         self.pbNodePathDict['widget'] = direct.widget
         self.pbNodePathDict['widget'] = direct.widget
         self.pbNodePathDict['mopathRecorderTempCS'] = self.tempCS
         self.pbNodePathDict['mopathRecorderTempCS'] = self.tempCS
-        self.pbNodePathDict['edit CS'] = self.editCS
         self.pbNodePathNames = ['marker', 'camera', 'selected']
         self.pbNodePathNames = ['marker', 'camera', 'selected']
         # Count of point sets recorded
         # Count of point sets recorded
         self.pointSet = []
         self.pointSet = []
@@ -112,6 +100,7 @@ class MopathRecorder(AppShell, PandaObject):
         self.pointSetCount = 0
         self.pointSetCount = 0
         self.pointSetName = self.name + '-ps-' + `self.pointSetCount`
         self.pointSetName = self.name + '-ps-' + `self.pointSetCount`
         # User callback to call before recording point
         # User callback to call before recording point
+        self.samplingMode = 'Continuous'
         self.preRecordFunc = None
         self.preRecordFunc = None
         # Hook to start/stop recording
         # Hook to start/stop recording
         self.startStopHook = 'f6'
         self.startStopHook = 'f6'
@@ -125,7 +114,7 @@ class MopathRecorder(AppShell, PandaObject):
         self.numTicks = 1
         self.numTicks = 1
         # Number of segments to represent each parametric unit
         # Number of segments to represent each parametric unit
         # This just affects the visual appearance of the curve
         # This just affects the visual appearance of the curve
-        self.numSegs = 20
+        self.numSegs = 40
         # The nurbs curves
         # The nurbs curves
         self.xyzNurbsCurve = None
         self.xyzNurbsCurve = None
         self.hprNurbsCurve = None
         self.hprNurbsCurve = None
@@ -152,6 +141,10 @@ class MopathRecorder(AppShell, PandaObject):
         self.cropFrom = 0.0
         self.cropFrom = 0.0
         self.cropTo = 0.0
         self.cropTo = 0.0
         self.fAdjustingValues = 0
         self.fAdjustingValues = 0
+        # For terrain following
+        self.iRayCS = self.recorderNodePath.attachNewNode(
+            'mopathRecorderIRayCS')
+        self.iRay = SelectionRay(self.iRayCS)
         # Set up event hooks
         # Set up event hooks
         self.actionEvents = [
         self.actionEvents = [
             ('undo', self.undoHook),
             ('undo', self.undoHook),
@@ -194,52 +187,6 @@ class MopathRecorder(AppShell, PandaObject):
             'Save current curve as a new point set',
             'Save current curve as a new point set',
             label = 'Save Point Set',
             label = 'Save Point Set',
             command = self.savePointSet)
             command = self.savePointSet)
-        self.menuBar.addcascademenu(
-            'Recorder', 'Show/Hide',
-            statusHelp = 'Show/Hide Mopath Recorder Objects',
-            tearoff = 1)
-        self.pathVis = BooleanVar()
-        self.pathVis.set(1)
-        self.menuBar.addmenuitem('Show/Hide', 'checkbutton',
-                                 'Toggle Path Visability',
-                                 label = 'Toggle Path Vis',
-                                 variable = self.pathVis,
-                                 command = self.setPathVis)
-        self.hullVis = BooleanVar()
-        self.hullVis.set(0)
-        self.menuBar.addmenuitem('Show/Hide', 'checkbutton',
-                                 'Toggle Hull Visability',
-                                 label = 'Toggle Hull Vis',
-                                 variable = self.hullVis,
-                                 command = self.setHullVis)
-        self.cvVis = BooleanVar()
-        self.cvVis.set(0)
-        self.menuBar.addmenuitem('Show/Hide', 'checkbutton',
-                                 'Toggle CV Visability',
-                                 label = 'Toggle CV Vis',
-                                 variable = self.cvVis,
-                                 command = self.setCvVis)
-        self.knotVis = BooleanVar()
-        self.knotVis.set(1)
-        self.menuBar.addmenuitem('Show/Hide', 'checkbutton',
-                                 'Toggle Knot Visability',
-                                 label = 'Toggle Knot Vis',
-                                 variable = self.knotVis,
-                                 command = self.setKnotVis)
-        self.tickVis = BooleanVar()
-        self.tickVis.set(0)
-        self.menuBar.addmenuitem('Show/Hide', 'checkbutton',
-                                 'Toggle Tick Visability',
-                                 label = 'Toggle Tick Vis',
-                                 variable = self.tickVis,
-                                 command = self.setTickVis)
-        self.markerVis = BooleanVar()
-        self.markerVis.set(1)
-        self.menuBar.addmenuitem('Show/Hide', 'checkbutton',
-                                 'Toggle Marker Visability',
-                                 label = 'Toggle Marker Vis',
-                                 variable = self.markerVis,
-                                 command = self.setMarkerVis)
         self.menuBar.addmenuitem(
         self.menuBar.addmenuitem(
             'Recorder', 'command',
             'Recorder', 'command',
             'Toggle widget visability',
             'Toggle widget visability',
@@ -281,6 +228,10 @@ class MopathRecorder(AppShell, PandaObject):
             side = LEFT, fill = BOTH, expand = 1)
             side = LEFT, fill = BOTH, expand = 1)
         widget.configure(foreground = 'Red', relief = RAISED, borderwidth = 2,
         widget.configure(foreground = 'Red', relief = RAISED, borderwidth = 2,
                          anchor = CENTER, width = 16)
                          anchor = CENTER, width = 16)
+        widget = self.createButton(frame, 'Recording', 'Add Keyframe',
+                                   'Add Keyframe To Current Path',
+                                   self.addKeyframe,
+                                   side = LEFT, expand = 1)
         self.recordingType = StringVar()
         self.recordingType = StringVar()
         self.recordingType.set('New Curve')
         self.recordingType.set('New Curve')
         widget = self.createRadiobutton(
         widget = self.createRadiobutton(
@@ -381,6 +332,7 @@ class MopathRecorder(AppShell, PandaObject):
         self.refinePage = self.mainNotebook.add('Refine')
         self.refinePage = self.mainNotebook.add('Refine')
         self.extendPage = self.mainNotebook.add('Extend')
         self.extendPage = self.mainNotebook.add('Extend')
         self.cropPage = self.mainNotebook.add('Crop')
         self.cropPage = self.mainNotebook.add('Crop')
+        self.stylePage = self.mainNotebook.add('Style')
 
 
         ## RECORD PAGE ##
         ## RECORD PAGE ##
         recordFrame = Frame(self.recordPage, relief = SUNKEN,
         recordFrame = Frame(self.recordPage, relief = SUNKEN,
@@ -427,44 +379,18 @@ class MopathRecorder(AppShell, PandaObject):
         frame.pack(expand = 1, fill = X)
         frame.pack(expand = 1, fill = X)
         # PreRecordFunc
         # PreRecordFunc
         frame = Frame(recordFrame)
         frame = Frame(recordFrame)
-        widget = self.createLabeledEntry(
+        widget = self.createComboBox(
             frame, 'Recording', 'Pre-Record Func',
             frame, 'Recording', 'Pre-Record Func',
-            'Function called before recording each point',
-            command = self.setPreRecordFunc)[0]
-        label = self.getWidget('Recording', 'Pre-Record Func-Label')
-        label.configure(width = 16, anchor = W)
+            'Function called before sampling each point',
+            PRF_UTILITIES, self.setPreRecordFunc,
+            history = 1, expand = 1)
+        widget.configure(label_width = 16, label_anchor = W)
+        widget.configure(entryfield_entry_state = 'normal')
         self.createCheckbutton(frame, 'Recording', 'PRF Active',
         self.createCheckbutton(frame, 'Recording', 'PRF Active',
                                'On: Pre Record Func enabled',
                                'On: Pre Record Func enabled',
                                None, 0,
                                None, 0,
                                side = LEFT, fill = BOTH, expand = 0)
                                side = LEFT, fill = BOTH, expand = 0)
         frame.pack(expand = 1, fill = X)
         frame.pack(expand = 1, fill = X)
-
-        # Record type
-        frame = Frame(recordFrame)
-        self.samplingMode = StringVar()
-        self.samplingMode.set('Continuous')
-        widget = self.createRadiobutton(
-            frame, 'left',
-            'Recording', 'Continuous Sampling',
-            ('New point added to curve fitter every frame'),
-            self.samplingMode, 'Continuous', self.setSamplingMode,
-            expand = 1)
-        widget['anchor'] = 'center'
-        widget = self.createRadiobutton(
-            frame, 'left',
-            'Recording', 'Keyframe Sampling',
-            ('Add new point to curve fitter by pressing keyframe button'),
-            self.samplingMode, 'Keyframe', self.setSamplingMode,
-            expand = 1)
-        widget['anchor'] = 'center'
-        frame.pack(expand = 1, fill = X)
-
-        widget = self.createButton(recordFrame, 'Recording', 'Add Key Frame',
-                                   'Add Keyframe To Current Path',
-                                   self.addKeyframe,
-                                   side = LEFT, expand = 1)
-        widget.configure(state = 'disabled')
-
         # Pack record frame
         # Pack record frame
         recordFrame.pack(fill = X, pady = 2)
         recordFrame.pack(fill = X, pady = 2)
 
 
@@ -497,7 +423,7 @@ class MopathRecorder(AppShell, PandaObject):
               font=('MSSansSerif', 12, 'bold')).pack()
               font=('MSSansSerif', 12, 'bold')).pack()
         widget = self.createEntryScale(
         widget = self.createEntryScale(
             desampleFrame, 'Resample', 'Points Between Samples',
             desampleFrame, 'Resample', 'Points Between Samples',
-            'Recompute curve using every nth point',
+            'Specify number of points to skip between samples',
             min = 1, max = 100, resolution = 1,
             min = 1, max = 100, resolution = 1,
             command = self.setDesampleFrequency)
             command = self.setDesampleFrequency)
         widget.component('hull')['relief'] = RIDGE
         widget.component('hull')['relief'] = RIDGE
@@ -516,6 +442,7 @@ class MopathRecorder(AppShell, PandaObject):
                                        'Begin time of refine pass',
                                        'Begin time of refine pass',
                                        resolution = 0.01,
                                        resolution = 0.01,
                                        command = self.setRecordStart)
                                        command = self.setRecordStart)
+        widget.onPress = self.setRefineMode
         widget.onRelease = widget.onReturnRelease = (
         widget.onRelease = widget.onReturnRelease = (
             lambda s = self: s.getPrePoints('Refine'))
             lambda s = self: s.getPrePoints('Refine'))
         widget = self.createEntryScale(
         widget = self.createEntryScale(
@@ -524,18 +451,19 @@ class MopathRecorder(AppShell, PandaObject):
             'Time when full control of node path is given during refine pass',
             'Time when full control of node path is given during refine pass',
             resolution = 0.01,
             resolution = 0.01,
             command = self.setControlStart)
             command = self.setControlStart)
-        widget.onRelease = widget.onReturnRelease = self.setRefineMode
+        widget.onPress = widget.onReturn = self.setRefineMode
         widget = self.createEntryScale(
         widget = self.createEntryScale(
             refineFrame, 'Refine Page',
             refineFrame, 'Refine Page',
             'Control Stop',
             'Control Stop',
             'Time when node path begins transition back to original curve',
             'Time when node path begins transition back to original curve',
             resolution = 0.01,
             resolution = 0.01,
             command = self.setControlStop)
             command = self.setControlStop)
-        widget.onRelease = widget.onReturnRelease = self.setRefineMode
+        widget.onPress = widget.onReturn = self.setRefineMode
         widget = self.createEntryScale(refineFrame, 'Refine Page', 'Refine To',
         widget = self.createEntryScale(refineFrame, 'Refine Page', 'Refine To',
                                        'Stop time of refine pass',
                                        'Stop time of refine pass',
                                        resolution = 0.01,
                                        resolution = 0.01,
                                        command = self.setRefineStop)
                                        command = self.setRefineStop)
+        widget.onPress = self.setRefineMode
         widget.onRelease = widget.onReturnRelease = self.getPostPoints
         widget.onRelease = widget.onReturnRelease = self.getPostPoints
         refineFrame.pack(fill = X)
         refineFrame.pack(fill = X)
 
 
@@ -551,6 +479,7 @@ class MopathRecorder(AppShell, PandaObject):
                                        'Begin time of extend pass',
                                        'Begin time of extend pass',
                                        resolution = 0.01,
                                        resolution = 0.01,
                                        command = self.setRecordStart)
                                        command = self.setRecordStart)
+        widget.onPress = self.setExtendMode
         widget.onRelease = widget.onReturnRelease = (
         widget.onRelease = widget.onReturnRelease = (
             lambda s = self: s.getPrePoints('Extend'))
             lambda s = self: s.getPrePoints('Extend'))
         widget = self.createEntryScale(
         widget = self.createEntryScale(
@@ -559,7 +488,7 @@ class MopathRecorder(AppShell, PandaObject):
             'Time when full control of node path is given during extend pass',
             'Time when full control of node path is given during extend pass',
             resolution = 0.01,
             resolution = 0.01,
             command = self.setControlStart)
             command = self.setControlStart)
-        widget.onRelease = widget.onReturnRelease = self.setExtendMode
+        widget.onPress = widget.onReturn = self.setExtendMode
         extendFrame.pack(fill = X)
         extendFrame.pack(fill = X)
 
 
         ## CROP PAGE ##
         ## CROP PAGE ##
@@ -588,6 +517,88 @@ class MopathRecorder(AppShell, PandaObject):
                           self.cropCurve, fill = NONE)
                           self.cropCurve, fill = NONE)
         cropFrame.pack(fill = X)
         cropFrame.pack(fill = X)
 
 
+        ## STYLE PAGE ##
+        styleFrame = Frame(self.stylePage, relief = SUNKEN,
+                           borderwidth = 2)
+        label = Label(styleFrame, text = 'CURVE RENDERINNG STYLE',
+                      font=('MSSansSerif', 12, 'bold'))
+        label.pack(fill = X)
+
+        self.sf = Pmw.ScrolledFrame(styleFrame, horizflex = 'elastic')
+        self.sf.pack(fill = 'both', expand = 1)
+        sfFrame = self.sf.interior()
+        frame = Frame(sfFrame)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Show Path',
+            'On: path is visible', self.setPathVis, 1,
+            side = LEFT, fill = X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Show Knots',
+            'On: path knots are visible', self.setKnotVis, 1,
+            side = LEFT, fill = X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Show CVs',
+            'On: path CVs are visible', self.setCvVis, 0,
+            side = LEFT, fill = X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Show Hull',
+            'On: path hull is visible', self.setHullVis, 0,
+            side = LEFT, fill = X, expand = 1)
+        widget = self.createCheckbutton(
+            frame, 'Style', 'Show Marker',
+            'On: playback marker is visible', self.setMarkerVis, 0,
+            side = LEFT, fill = X, expand = 1)
+        frame.pack(fill = X, expand = 1)
+        # Sliders
+        widget = self.createEntryScale(
+            sfFrame, 'Style', 'Num Segs',
+            'Set number of segments used to approximate each parametric unit',
+            min = 1.0, max = 400, resolution = 1.0,
+            initialValue = 40, 
+            command = self.setNumSegs, side = TOP)
+        widget.component('hull')['relief'] = RIDGE
+        widget = self.createEntryScale(
+            sfFrame, 'Style', 'Num Ticks',
+            'Set number of tick marks drawn for each unit of time',
+            min = 0.0, max = 10.0, resolution = 1.0,
+            initialValue = 0.0,
+            command = self.setNumTicks, side = TOP)
+        widget.component('hull')['relief'] = RIDGE
+        widget = self.createEntryScale(
+            sfFrame, 'Style', 'Tick Scale',
+            'Set visible size of time tick marks',
+            min = 0.01, max = 100.0, resolution = 0.01,
+            initialValue = 1.0,
+            command = self.setTickScale, side = TOP)
+        widget.component('hull')['relief'] = RIDGE
+	self.createColorEntry(
+            sfFrame, 'Style', 'Path Color',
+            'Color of curve',
+            command = self.setPathColor,
+            initialValue = [255.0,255.0,255.0,255.0])
+	self.createColorEntry(
+            sfFrame, 'Style', 'Knot Color',
+            'Color of knots',
+            command = self.setKnotColor,
+            initialValue = [0,0,255.0,255.0])
+	self.createColorEntry(
+            sfFrame, 'Style', 'CV Color',
+            'Color of CVs',
+            command = self.setCvColor,
+            initialValue = [255.0,0,0,255.0])
+	self.createColorEntry(
+            sfFrame, 'Style', 'Tick Color',
+            'Color of Ticks',
+            command = self.setTickColor,
+            initialValue = [255.0,0,0,255.0])
+	self.createColorEntry(
+            sfFrame, 'Style', 'Hull Color',
+            'Color of Hull',
+            command = self.setHullColor,
+            initialValue = [255.0,128.0,128.0,255.0])
+
+        styleFrame.pack(fill = X)
+
         self.mainNotebook.setnaturalsize()        
         self.mainNotebook.setnaturalsize()        
         
         
     def pushUndo(self, fResetRedo = 1):
     def pushUndo(self, fResetRedo = 1):
@@ -761,33 +772,61 @@ class MopathRecorder(AppShell, PandaObject):
         # Compute curve
         # Compute curve
         self.computeCurves()
         self.computeCurves()
 
 
-    def setHullVis(self):
-        self.xyzNurbsCurveDrawer.setShowHull(self.hullVis.get())
-        
     def setPathVis(self):
     def setPathVis(self):
-        if self.pathVis.get():
+        if self.getVariable('Style', 'Show Path').get():
             self.curveNodePath.show()
             self.curveNodePath.show()
         else:
         else:
             self.curveNodePath.hide()
             self.curveNodePath.hide()
         
         
-    def setCvVis(self):
-        self.xyzNurbsCurveDrawer.setShowCvs(self.cvVis.get())
-        
     def setKnotVis(self):
     def setKnotVis(self):
-        self.xyzNurbsCurveDrawer.setShowKnots(self.knotVis.get())
-
-    def setTickVis(self):
-        if self.tickVis.get():
-            self.xyzNurbsCurveDrawer.setNumTicks(self.numTicks)
-        else:
-            self.xyzNurbsCurveDrawer.setNumTicks(0)
+        self.xyzNurbsCurveDrawer.setShowKnots(
+            self.getVariable('Style', 'Show Knots').get())
 
 
+    def setCvVis(self):
+        self.xyzNurbsCurveDrawer.setShowCvs(
+            self.getVariable('Style', 'Show CVs').get())
+        
+    def setHullVis(self):
+        self.xyzNurbsCurveDrawer.setShowHull(
+            self.getVariable('Style', 'Show Hull').get())
+        
     def setMarkerVis(self):
     def setMarkerVis(self):
-        if self.markerVis.get():
+        if self.getVariable('Style', 'Show Marker').get():
             self.playbackMarker.reparentTo(self.recorderNodePath)
             self.playbackMarker.reparentTo(self.recorderNodePath)
         else:
         else:
             self.playbackMarker.reparentTo(hidden)
             self.playbackMarker.reparentTo(hidden)
+
+    def setNumSegs(self, value):
+        self.numSegs = int(value)
+        self.xyzNurbsCurveDrawer.setNumSegs(self.numSegs)
         
         
+    def setNumTicks(self, value):
+        self.xyzNurbsCurveDrawer.setNumTicks(float(value))
+        
+    def setTickScale(self, value):
+        self.xyzNurbsCurveDrawer.setTickScale(float(value))
+
+    def setPathColor(self, color):
+        self.xyzNurbsCurveDrawer.setColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+        self.xyzNurbsCurveDrawer.draw()
+
+    def setKnotColor(self, color):
+        self.xyzNurbsCurveDrawer.setKnotColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
+    def setCvColor(self, color):
+        self.xyzNurbsCurveDrawer.setCvColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
+    def setTickColor(self, color):
+        self.xyzNurbsCurveDrawer.setTickColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
+    def setHullColor(self, color):
+        self.xyzNurbsCurveDrawer.setHullColor(
+            color[0]/255.0,color[1]/255.0,color[2]/255.0)
+
     def setStartStopHook(self, event = None):
     def setStartStopHook(self, event = None):
         # Clear out old hook
         # Clear out old hook
         self.ignore(self.startStopHook)
         self.ignore(self.startStopHook)
@@ -821,11 +860,13 @@ class MopathRecorder(AppShell, PandaObject):
         self.hprCurveFitter.reset()
         self.hprCurveFitter.reset()
         self.xyzNurbsCurveDrawer.hide()
         self.xyzNurbsCurveDrawer.hide()
         
         
-    def setSamplingMode(self):
-        if self.samplingMode.get() == 'Keyframe':
-            self.getWidget('Recording', 'Add Key Frame')['state'] = 'normal'
-        else:
-            self.getWidget('Recording', 'Add Key Frame')['state'] = 'disabled'
+    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):
     def setRecordingType(self, type):
         self.recordingType.set(type)
         self.recordingType.set(type)
@@ -856,12 +897,15 @@ class MopathRecorder(AppShell, PandaObject):
             # Reset curve fitters
             # Reset curve fitters
             self.xyzCurveFitter.reset()
             self.xyzCurveFitter.reset()
             self.hprCurveFitter.reset()
             self.hprCurveFitter.reset()
+            # Update sampling mode button if necessary
+            if self.samplingMode == 'Continuous':
+                self.disableKeyframeButton()
             # Create a new point set to hold raw data
             # Create a new point set to hold raw data
             self.createNewPointSet()
             self.createNewPointSet()
             # Record nopath's parent
             # Record nopath's parent
             self.nodePathParent = self['nodePath'].getParent()
             self.nodePathParent = self['nodePath'].getParent()
             # Keyframe mode?
             # Keyframe mode?
-            if (self.samplingMode.get() == 'Keyframe'):
+            if (self.samplingMode == 'Keyframe'):
                 # Add hook
                 # Add hook
                 self.acceptKeyframeHook()
                 self.acceptKeyframeHook()
                 # Record first point
                 # Record first point
@@ -891,14 +935,8 @@ class MopathRecorder(AppShell, PandaObject):
                 t = taskMgr.spawnMethodNamed(
                 t = taskMgr.spawnMethodNamed(
                     self.recordTask, self.name + '-recordTask')
                     self.recordTask, self.name + '-recordTask')
                 t.startTime = globalClock.getTime()
                 t.startTime = globalClock.getTime()
-
-            # Don't want to change record modes
-            self.getWidget('Recording', 'Continuous Sampling')['state'] = (
-                'disabled')
-            self.getWidget('Recording', 'Keyframe Sampling')['state'] = (
-                'disabled')
         else:
         else:
-            if self.samplingMode.get() == 'Continuous':
+            if self.samplingMode == 'Continuous':
                 # Kill old task
                 # Kill old task
                 taskMgr.removeTasksNamed(self.name + '-recordTask')
                 taskMgr.removeTasksNamed(self.name + '-recordTask')
                 if ((self.recordingType.get() == 'Refine') |
                 if ((self.recordingType.get() == 'Refine') |
@@ -915,6 +953,9 @@ class MopathRecorder(AppShell, PandaObject):
                 self.addKeyframe(0)
                 self.addKeyframe(0)
                 # Ignore hook
                 # Ignore hook
                 self.ignoreKeyframeHook()
                 self.ignoreKeyframeHook()
+            # Reset sampling mode
+            self.setSamplingMode('Continuous')
+            self.enableKeyframeButton()
             # Clean up after refine or extend
             # Clean up after refine or extend
             if ((self.recordingType.get() == 'Refine') |
             if ((self.recordingType.get() == 'Refine') |
                 (self.recordingType.get() == 'Extend')):
                 (self.recordingType.get() == 'Extend')):
@@ -927,11 +968,6 @@ class MopathRecorder(AppShell, PandaObject):
                 self.setNewCurveMode()
                 self.setNewCurveMode()
             # Compute curve
             # Compute curve
             self.computeCurves()
             self.computeCurves()
-            # Now you can change record modes
-            self.getWidget('Recording', 'Continuous Sampling')['state'] = (
-                'normal')
-            self.getWidget('Recording', 'Keyframe Sampling')['state'] = (
-                'normal')
             
             
     def recordTask(self, state):
     def recordTask(self, state):
         # Record raw data point
         # Record raw data point
@@ -943,6 +979,8 @@ class MopathRecorder(AppShell, PandaObject):
         # Make sure we're in a recording mode!
         # Make sure we're in a recording mode!
         if (fToggleRecord &
         if (fToggleRecord &
             (not self.getVariable('Recording', 'Record').get())):
             (not self.getVariable('Recording', 'Record').get())):
+            # Set sampling mode
+            self.setSamplingMode('Keyframe')
             # This will automatically add the first point
             # This will automatically add the first point
             self.toggleRecordVar()
             self.toggleRecordVar()
         else:
         else:
@@ -955,11 +993,10 @@ class MopathRecorder(AppShell, PandaObject):
         x = t * t
         x = t * t
         return (3 * x) - (2 * t * x)
         return (3 * x) - (2 * t * x)
 
 
-    def setPreRecordFunc(self, event):
+    def setPreRecordFunc(self, func):
         # Note: If func is one defined at command prompt, need to set
         # Note: If func is one defined at command prompt, need to set
         # __builtins__.func = func at command line
         # __builtins__.func = func at command line
-        self.preRecordFunc = eval(
-            self.getVariable('Recording', 'Pre-Record Func').get())
+        self.preRecordFunc = eval(func)
         # Update widget to reflect new value
         # Update widget to reflect new value
         self.getVariable('Recording', 'PRF Active').set(1)
         self.getVariable('Recording', 'PRF Active').set(1)
 
 
@@ -1336,8 +1373,24 @@ class MopathRecorder(AppShell, PandaObject):
         for i in range(self.hprNurbsCurve.getNumKnots()):
         for i in range(self.hprNurbsCurve.getNumKnots()):
             self.hprNurbsCurve.setKnot(i, sf * self.hprNurbsCurve.getKnot(i))
             self.hprNurbsCurve.setKnot(i, sf * self.hprNurbsCurve.getKnot(i))
         self.hprNurbsCurve.recompute()
         self.hprNurbsCurve.recompute()
-        # Update info
-        self.updateWidgets()
+        # Scale point set
+        # Save handle to old point set
+        oldPointSet = self.pointSet
+        # Create new point set
+        self.createNewPointSet()
+        # Reset curve fitters
+        self.xyzCurveFitter.reset()
+        self.hprCurveFitter.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.xyzCurveFitter.addPoint(newTime, pos )
+            self.hprCurveFitter.addPoint(newTime, hpr)
+        # Compute curve
+        self.computeCurves()
 
 
     def setRecordStart(self,value):
     def setRecordStart(self,value):
         self.recordStart = value
         self.recordStart = value
@@ -1620,6 +1673,14 @@ class MopathRecorder(AppShell, PandaObject):
             self.xyzNurbsCurve.writeEgg(mopathFilename)
             self.xyzNurbsCurve.writeEgg(mopathFilename)
             self.hprNurbsCurve.writeEgg(mopathFilename)
             self.hprNurbsCurve.writeEgg(mopathFilename)
 
 
+    def followTerrain(self, height = 1.0):
+        self.iRay.rayCollisionNodePath.reparentTo(self['nodePath'])
+        node, hitPt, hitPtDist = self.iRay.pickGeom3D()
+        if node:
+            self['nodePath'].setZ(self['nodePath'], height - hitPtDist)
+        self.iRay.rayCollisionNodePath.reparentTo(self.recorderNodePath)
+
+
     ## WIDGET UTILITY FUNCTIONS ##
     ## WIDGET UTILITY FUNCTIONS ##
     def addWidget(self, widget, category, text):
     def addWidget(self, widget, category, text):
         self.widgetDict[category + '-' + text] = widget
         self.widgetDict[category + '-' + text] = widget
@@ -1639,10 +1700,12 @@ class MopathRecorder(AppShell, PandaObject):
         variable.set(initialValue)
         variable.set(initialValue)
         label = Label(frame, text = text)
         label = Label(frame, text = text)
         label.pack(side = LEFT, fill = X)
         label.pack(side = LEFT, fill = X)
+        self.bind(label, balloonHelp)
         self.widgetDict[category + '-' + text + '-Label'] = label
         self.widgetDict[category + '-' + text + '-Label'] = label
         entry = Entry(frame, width = width, relief = relief,
         entry = Entry(frame, width = width, relief = relief,
                       textvariable = variable)
                       textvariable = variable)
         entry.pack(side = LEFT, fill = X, expand = expand)
         entry.pack(side = LEFT, fill = X, expand = expand)
+        self.bind(entry, balloonHelp)
         self.widgetDict[category + '-' + text] = entry
         self.widgetDict[category + '-' + text] = entry
         self.variableDict[category + '-' + text] = variable
         self.variableDict[category + '-' + text] = variable
         if command:
         if command:
@@ -1800,7 +1863,7 @@ class MopathRecorder(AppShell, PandaObject):
             widget.selectitem(items[0])
             widget.selectitem(items[0])
         # Bind selection command
         # Bind selection command
         widget['selectioncommand'] = command
         widget['selectioncommand'] = command
-        widget.pack(side = side, expand = expand)
+        widget.pack(side = side, fill = fill, expand = expand)
         # Bind help
         # Bind help
         self.bind(widget, balloonHelp)
         self.bind(widget, balloonHelp)
         # Record widget
         # Record widget