Procházet zdrojové kódy

*** empty log message ***

Mark Mine před 25 roky
rodič
revize
74ce0bff3a

+ 162 - 148
direct/src/directutil/DirectCameraControl.py

@@ -8,27 +8,53 @@ Y_AXIS = Vec3(0,1,0)
 class DirectCameraControl(PandaObject):
 class DirectCameraControl(PandaObject):
     def __init__(self):
     def __init__(self):
         # Create the grid
         # Create the grid
-        self.chan = direct.chan
-        self.camera = self.chan.camera
 	self.orthoViewRoll = 0.0
 	self.orthoViewRoll = 0.0
 	self.lastView = 0
 	self.lastView = 0
         self.coa = Point3(0,100,0)
         self.coa = Point3(0,100,0)
         self.coaDist = 100
         self.coaDist = 100
         self.coaMarker = loader.loadModel('models/misc/sphere')
         self.coaMarker = loader.loadModel('models/misc/sphere')
+        self.coaMarker.setName('DirectCameraCOAMarker')
         self.coaMarker.setColor(1,0,0)
         self.coaMarker.setColor(1,0,0)
+        self.coaMarker.setPos(0,0,0)
         useDirectRenderStyle(self.coaMarker)
         useDirectRenderStyle(self.coaMarker)
         self.coaMarkerPos = Point3(0)
         self.coaMarkerPos = Point3(0)
-	self.relNodePath = render.attachNewNode(NamedNode('targetNode'))
+	self.camManipRef = direct.group.attachNewNode('camManipRef')
         self.zeroBaseVec = VBase3(0)
         self.zeroBaseVec = VBase3(0)
         self.zeroVector = Vec3(0)
         self.zeroVector = Vec3(0)
         self.centerVec = Vec3(0, 1, 0)
         self.centerVec = Vec3(0, 1, 0)
         self.zeroPoint = Point3(0)
         self.zeroPoint = Point3(0)
-
-    def mouseFlyStart(self, chan):
+        t = CAM_MOVE_DURATION
+        self.actionEvents = [
+            ['handleMouse2', self.mouseFlyStart],
+            ['handleMouse2Up', self.mouseFlyStop],
+            ['u', self.uprightCam],
+            ['c', self.centerCamIn, 0.5],
+            ['h', self.homeCam],
+            ['f', self.fitOnWidget],
+            [`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 mouseFlyStart(self):
 	# Record starting mouse positions
 	# Record starting mouse positions
-	self.initMouseX = chan.mouseX
-	self.initMouseY = chan.mouseY
-	# Where are we in the channel?
+	self.initMouseX = direct.dr.mouseX
+	self.initMouseY = direct.dr.mouseY
+        # Record undo point
+        direct.pushUndo([direct.camera])
+	# Where are we in the display region?
         if ((abs(self.initMouseX) < 0.9) & (abs(self.initMouseY) < 0.9)):
         if ((abs(self.initMouseX) < 0.9) & (abs(self.initMouseY) < 0.9)):
             # MOUSE IS IN CENTRAL REGION
             # MOUSE IS IN CENTRAL REGION
             # Hide the marker for this kind of motion
             # Hide the marker for this kind of motion
@@ -42,7 +68,7 @@ class DirectCameraControl(PandaObject):
                 # current mouse position
                 # current mouse position
                 # And then spawn task to determine mouse mode
                 # And then spawn task to determine mouse mode
                 numEntries = direct.iRay.pickGeom(
                 numEntries = direct.iRay.pickGeom(
-                    render,chan.mouseX,chan.mouseY)
+                    render,direct.dr.mouseX,direct.dr.mouseY)
                 # Filter out hidden nodes from entry list
                 # Filter out hidden nodes from entry list
                 indexList = []
                 indexList = []
                 for i in range(0,numEntries):
                 for i in range(0,numEntries):
@@ -73,16 +99,22 @@ class DirectCameraControl(PandaObject):
                                 coa.set(hitPt[0],hitPt[1],hitPt[2])
                                 coa.set(hitPt[0],hitPt[1],hitPt[2])
                                 minPt = i
                                 minPt = i
                     # Handle case of bad coa point (too close or too far)
                     # Handle case of bad coa point (too close or too far)
-                    if ((coaDist < (1.1 * self.chan.near)) |
-                        (coaDist > self.chan.far)):
+                    if ((coaDist < (1.1 * direct.dr.near)) |
+                        (coaDist > direct.dr.far)):
                         # Just use existing point
                         # Just use existing point
-                        coa.assign(self.coaMarker.getPos(camera))
+                        coa.assign(self.coaMarker.getPos(direct.camera))
                         coaDist = Vec3(coa - self.zeroPoint).length()
                         coaDist = Vec3(coa - self.zeroPoint).length()
+                        if coaDist < (1.1 * direct.dr.near):
+                            coa.set(0,100,0)
+                            coaDist = 100
                 else:
                 else:
                     # If no intersection point:
                     # If no intersection point:
                     # Use existing point
                     # Use existing point
-                    coa.assign(self.coaMarker.getPos(camera))
+                    coa.assign(self.coaMarker.getPos(direct.camera))
                     coaDist = Vec3(coa - self.zeroPoint).length()
                     coaDist = Vec3(coa - self.zeroPoint).length()
+                    if coaDist < (1.1 * direct.dr.near):
+                        coa.set(0,100,0)
+                        coaDist = 100
                 # Update coa and marker
                 # Update coa and marker
                 self.updateCoa(coa, coaDist)
                 self.updateCoa(coa, coaDist)
                 # Now spawn task to determine mouse fly mode
                 # Now spawn task to determine mouse fly mode
@@ -106,8 +138,8 @@ class DirectCameraControl(PandaObject):
         taskMgr.spawnTaskNamed(t, 'determineMouseFlyMode')
         taskMgr.spawnTaskNamed(t, 'determineMouseFlyMode')
 
 
     def determineMouseFlyModeTask(self, state):
     def determineMouseFlyModeTask(self, state):
-        deltaX = self.chan.mouseX - self.initMouseX
-        deltaY = self.chan.mouseY - self.initMouseY
+        deltaX = direct.dr.mouseX - self.initMouseX
+        deltaY = direct.dr.mouseY - self.initMouseY
         if ((abs(deltaX) < 0.1) & (abs(deltaY) < 0.1)):
         if ((abs(deltaX) < 0.1) & (abs(deltaY) < 0.1)):
             return Task.cont
             return Task.cont
         else:
         else:
@@ -124,7 +156,7 @@ class DirectCameraControl(PandaObject):
         else:
         else:
             self.coaDist = Vec3(self.coa - self.zeroPoint).length()
             self.coaDist = Vec3(self.coa - self.zeroPoint).length()
         # Place the marker in render space
         # Place the marker in render space
-        self.coaMarker.setPos(self.camera,self.coa)
+        self.coaMarker.setPos(direct.camera,self.coa)
         # Resize it
         # Resize it
         self.updateCoaMarkerSize(coaDist)
         self.updateCoaMarkerSize(coaDist)
         # Record marker pos in render space
         # Record marker pos in render space
@@ -132,60 +164,69 @@ class DirectCameraControl(PandaObject):
 
 
     def updateCoaMarkerSize(self, coaDist = None):
     def updateCoaMarkerSize(self, coaDist = None):
         if not coaDist:
         if not coaDist:
-            coaDist = Vec3(self.coaMarker.getPos( self.chan.camera )).length()
+            coaDist = Vec3(self.coaMarker.getPos( direct.camera )).length()
         self.coaMarker.setScale(COA_MARKER_SF * coaDist *
         self.coaMarker.setScale(COA_MARKER_SF * coaDist *
-                                math.tan(deg2Rad(self.chan.fovV)))
+                                math.tan(deg2Rad(direct.dr.fovV)))
 
 
-    def homeCam(self, chan):
-        chan.camera.setMat(Mat4.identMat())
+    def homeCam(self):
+        # Record undo point
+        direct.pushUndo([direct.camera])
+        direct.camera.setMat(Mat4.identMat())
 
 
-    def uprightCam(self, chan):
+    def uprightCam(self):
 	taskMgr.removeTasksNamed('manipulateCamera')
 	taskMgr.removeTasksNamed('manipulateCamera')
-        currH = chan.camera.getH()
-	chan.camera.lerpHpr(currH, 0, 0,
-                            CAM_MOVE_DURATION,
-                            other = render,
-                            blendType = 'easeInOut',
-                            task = 'manipulateCamera')
-
-    def centerCam(self, chan):
-        # Chan is a display region context
-	self.centerCamIn(chan, 1.0)
+        currH = direct.camera.getH()
+        # Record undo point
+        direct.pushUndo([direct.camera])
+	direct.camera.lerpHpr(currH, 0, 0,
+                              CAM_MOVE_DURATION,
+                              other = render,
+                              blendType = 'easeInOut',
+                              task = 'manipulateCamera')
+
+    def centerCam(self):
+	self.centerCamIn(1.0)
         
         
-    def centerCamNow(self, chan):
-        self.centerCamIn(chan, 0.)
+    def centerCamNow(self):
+        self.centerCamIn(0.)
 
 
-    def centerCamIn(self, chan, t):
-        # Chan is a display region context
+    def centerCamIn(self, t):
 	taskMgr.removeTasksNamed('manipulateCamera')
 	taskMgr.removeTasksNamed('manipulateCamera')
-        markerToCam = self.coaMarker.getPos( chan.camera )
+        # Record undo point
+        direct.pushUndo([direct.camera])
+        # Determine marker location
+        markerToCam = self.coaMarker.getPos( direct.camera )
 	dist = Vec3(markerToCam - self.zeroPoint).length()
 	dist = Vec3(markerToCam - self.zeroPoint).length()
 	scaledCenterVec = self.centerVec * dist
 	scaledCenterVec = self.centerVec * dist
 	delta = markerToCam - scaledCenterVec
 	delta = markerToCam - scaledCenterVec
-	self.relNodePath.setPosHpr(chan.camera, Point3(0), Point3(0))
-	chan.camera.lerpPos(Point3(delta),
+	self.camManipRef.setPosHpr(direct.camera, Point3(0), Point3(0))
+	direct.camera.lerpPos(Point3(delta),
                             CAM_MOVE_DURATION,
                             CAM_MOVE_DURATION,
-                            other = self.relNodePath,
+                            other = self.camManipRef,
                             blendType = 'easeInOut',
                             blendType = 'easeInOut',
                             task = 'manipulateCamera')
                             task = 'manipulateCamera')
 
 
-    def zoomCam(self, chan, zoomFactor, t):
+    def zoomCam(self, zoomFactor, t):
 	taskMgr.removeTasksNamed('manipulateCamera')
 	taskMgr.removeTasksNamed('manipulateCamera')
+        # Record undo point
+        direct.pushUndo([direct.camera])
 	# Find a point zoom factor times the current separation
 	# Find a point zoom factor times the current separation
         # of the widget and cam
         # of the widget and cam
-        zoomPtToCam = self.coaMarker.getPos(chan.camera) * zoomFactor
+        zoomPtToCam = self.coaMarker.getPos(direct.camera) * zoomFactor
 	# Put a target nodePath there
 	# Put a target nodePath there
-	self.relNodePath.setPos(chan.camera, zoomPtToCam)
+	self.camManipRef.setPos(direct.camera, zoomPtToCam)
 	# Move to that point
 	# Move to that point
-	chan.camera.lerpPos(self.zeroPoint,
+	direct.camera.lerpPos(self.zeroPoint,
                             CAM_MOVE_DURATION,
                             CAM_MOVE_DURATION,
-                            other = self.relNodePath,
+                            other = self.camManipRef,
                             blendType = 'easeInOut',
                             blendType = 'easeInOut',
                             task = 'manipulateCamera')
                             task = 'manipulateCamera')
         
         
-    def SpawnMoveToView(self, chan, view):
+    def SpawnMoveToView(self, view):
         # Kill any existing tasks
         # Kill any existing tasks
         taskMgr.removeTasksNamed('manipulateCamera')
         taskMgr.removeTasksNamed('manipulateCamera')
+        # Record undo point
+        direct.pushUndo([direct.camera])
         # Calc hprOffset
         # Calc hprOffset
         hprOffset = VBase3()
         hprOffset = VBase3()
         if view == 8:
         if view == 8:
@@ -211,39 +252,42 @@ class DirectCameraControl(PandaObject):
         elif view == 7:
         elif view == 7:
             hprOffset.set(135., -35.264, 0.)
             hprOffset.set(135., -35.264, 0.)
         # Position target
         # Position target
-        self.relNodePath.setPosHpr(self.coaMarker, self.zeroBaseVec,
+        self.camManipRef.setPosHpr(self.coaMarker, self.zeroBaseVec,
                                    hprOffset)
                                    hprOffset)
         # Scale center vec by current distance to target
         # Scale center vec by current distance to target
-        offsetDistance = Vec3(chan.camera.getPos(self.relNodePath) - 
+        offsetDistance = Vec3(direct.camera.getPos(self.camManipRef) - 
                               self.zeroPoint).length()
                               self.zeroPoint).length()
         scaledCenterVec = self.centerVec * (-1.0 * offsetDistance)
         scaledCenterVec = self.centerVec * (-1.0 * offsetDistance)
-        # Now put the relNodePath at that point
-        self.relNodePath.setPosHpr(self.relNodePath,
+        # Now put the camManipRef at that point
+        self.camManipRef.setPosHpr(self.camManipRef,
                                    scaledCenterVec,
                                    scaledCenterVec,
                                    self.zeroBaseVec)
                                    self.zeroBaseVec)
         # Record view for next time around
         # Record view for next time around
         self.lastView = view
         self.lastView = view
-        chan.camera.lerpPosHpr(self.zeroPoint,
-                               VBase3(0,0,self.orthoViewRoll),
-                               CAM_MOVE_DURATION,
-                               other = self.relNodePath,
-                               blendType = 'easeInOut',
-                               task = 'manipulateCamera')
+        direct.camera.lerpPosHpr(self.zeroPoint,
+                                 VBase3(0,0,self.orthoViewRoll),
+                                 CAM_MOVE_DURATION,
+                                 other = self.camManipRef,
+                                 blendType = 'easeInOut',
+                                 task = 'manipulateCamera')
 
 
         
         
-    def swingCamAboutWidget(self, chan, degrees, t):
+    def swingCamAboutWidget(self, degrees, t):
         # Remove existing camera manipulation task
         # Remove existing camera manipulation task
 	taskMgr.removeTasksNamed('manipulateCamera')
 	taskMgr.removeTasksNamed('manipulateCamera')
 	
 	
+        # Record undo point
+        direct.pushUndo([direct.camera])
+        
 	# Coincident with widget
 	# Coincident with widget
-        self.relNodePath.setPos(self.coaMarker, self.zeroPoint)
+        self.camManipRef.setPos(self.coaMarker, self.zeroPoint)
 	# But aligned with render space
 	# But aligned with render space
-	self.relNodePath.setHpr(self.zeroPoint)
+	self.camManipRef.setHpr(self.zeroPoint)
 
 
-	parent = self.camera.getParent()
-	self.camera.wrtReparentTo(self.relNodePath)
+	parent = direct.camera.getParent()
+	direct.camera.wrtReparentTo(self.camManipRef)
 
 
-	manipTask = self.relNodePath.lerpHpr(VBase3(degrees,0,0),
+	manipTask = self.camManipRef.lerpHpr(VBase3(degrees,0,0),
                                              CAM_MOVE_DURATION,
                                              CAM_MOVE_DURATION,
                                              blendType = 'easeInOut',
                                              blendType = 'easeInOut',
                                              task = 'manipulateCamera')
                                              task = 'manipulateCamera')
@@ -252,7 +296,7 @@ class DirectCameraControl(PandaObject):
         manipTask.uponDeath = self.reparentCam
         manipTask.uponDeath = self.reparentCam
 
 
     def reparentCam(self, state):
     def reparentCam(self, state):
-        self.camera.wrtReparentTo(state.parent)
+        direct.camera.wrtReparentTo(state.parent)
 
 
     def spawnHPanYZoom(self):
     def spawnHPanYZoom(self):
         # Kill any existing tasks
         # Kill any existing tasks
@@ -268,14 +312,14 @@ class DirectCameraControl(PandaObject):
     def HPanYZoomTask(self,state):
     def HPanYZoomTask(self,state):
         targetVector = state.targetVector
         targetVector = state.targetVector
         # Can bring object to you by dragging across half the screen
         # Can bring object to you by dragging across half the screen
-        distToMove = targetVector * (2.0 * self.chan.mouseDeltaY)
-        self.camera.setPosHpr(self.camera,
-                              distToMove[0],
-                              distToMove[1],
-                              distToMove[2],
-                              (0.5 * self.chan.mouseDeltaX *
-                               self.chan.fovH),
-                              0.0, 0.0)
+        distToMove = targetVector * (2.0 * direct.dr.mouseDeltaY)
+        direct.camera.setPosHpr(direct.camera,
+                                distToMove[0],
+                                distToMove[1],
+                                distToMove[2],
+                                (0.5 * direct.dr.mouseDeltaX *
+                                 direct.dr.fovH),
+                                0.0, 0.0)
         return Task.cont
         return Task.cont
 
 
 
 
@@ -285,25 +329,25 @@ class DirectCameraControl(PandaObject):
         # Hide the marker
         # Hide the marker
         self.coaMarker.hide()
         self.coaMarker.hide()
         t = Task.Task(self.XZTranslateOrHPPanTask)
         t = Task.Task(self.XZTranslateOrHPPanTask)
-        t.scaleFactor = (self.coaDist / self.chan.near)
+        t.scaleFactor = (self.coaDist / direct.dr.near)
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
 
     def XZTranslateOrHPPanTask(self, state):
     def XZTranslateOrHPPanTask(self, state):
         if direct.fShift:
         if direct.fShift:
-            self.camera.setHpr(self.camera,
-                               (0.5 * self.chan.mouseDeltaX *
-                                self.chan.fovH),
-                               (-0.5 * self.chan.mouseDeltaY *
-                                self.chan.fovV),
+            direct.camera.setHpr(direct.camera,
+                               (0.5 * direct.dr.mouseDeltaX *
+                                direct.dr.fovH),
+                               (-0.5 * direct.dr.mouseDeltaY *
+                                direct.dr.fovV),
                                0.0)
                                0.0)
         else:
         else:
-            self.camera.setPos(self.camera,
-                               (-0.5 * self.chan.mouseDeltaX *
-                                self.chan.nearWidth *
+            direct.camera.setPos(direct.camera,
+                               (-0.5 * direct.dr.mouseDeltaX *
+                                direct.dr.nearWidth *
                                 state.scaleFactor),
                                 state.scaleFactor),
                                0.0,
                                0.0,
-                               (-0.5 * self.chan.mouseDeltaY *
-                                self.chan.nearHeight *
+                               (-0.5 * direct.dr.mouseDeltaY *
+                                direct.dr.nearHeight *
                                 state.scaleFactor))
                                 state.scaleFactor))
         return Task.cont
         return Task.cont
 
 
@@ -313,37 +357,37 @@ class DirectCameraControl(PandaObject):
         # Hide the marker
         # Hide the marker
         self.coaMarker.hide()
         self.coaMarker.hide()
         t = Task.Task(self.XZTranslateTask)
         t = Task.Task(self.XZTranslateTask)
-        t.scaleFactor = (self.coaDist / self.chan.near)
+        t.scaleFactor = (self.coaDist / direct.dr.near)
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
 
     def XZTranslateTask(self,state):
     def XZTranslateTask(self,state):
-        self.camera.setPos(self.camera,
-                           (-0.5 * self.chan.mouseDeltaX *
-                            self.chan.nearWidth *
-                            state.scaleFactor),
-                           0.0,
-                           (-0.5 * self.chan.mouseDeltaY *
-                            self.chan.nearHeight *
-                            state.scaleFactor))
+        direct.camera.setPos(direct.camera,
+                             (-0.5 * direct.dr.mouseDeltaX *
+                              direct.dr.nearWidth *
+                              state.scaleFactor),
+                             0.0,
+                             (-0.5 * direct.dr.mouseDeltaY *
+                              direct.dr.nearHeight *
+                              state.scaleFactor))
         return Task.cont
         return Task.cont
 
 
     def spawnMouseRotateTask(self):
     def spawnMouseRotateTask(self):
         # Kill any existing tasks
         # Kill any existing tasks
 	taskMgr.removeTasksNamed('manipulateCamera')
 	taskMgr.removeTasksNamed('manipulateCamera')
         # Set at markers position in render coordinates
         # Set at markers position in render coordinates
-	self.relNodePath.setPos(self.coaMarkerPos)
-	self.relNodePath.setHpr(self.camera, self.zeroPoint)
+	self.camManipRef.setPos(self.coaMarkerPos)
+	self.camManipRef.setHpr(direct.camera, self.zeroPoint)
         t = Task.Task(self.mouseRotateTask)
         t = Task.Task(self.mouseRotateTask)
-	t.wrtMat = self.camera.getMat( self.relNodePath )
+	t.wrtMat = direct.camera.getMat( self.camManipRef )
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
 
     def mouseRotateTask(self, state):
     def mouseRotateTask(self, state):
         wrtMat = state.wrtMat
         wrtMat = state.wrtMat
-        self.relNodePath.setHpr(self.relNodePath,
-                                (-0.5 * self.chan.mouseDeltaX * 180.0),
-                                (0.5 * self.chan.mouseDeltaY * 180.0),
+        self.camManipRef.setHpr(self.camManipRef,
+                                (-0.5 * direct.dr.mouseDeltaX * 180.0),
+                                (0.5 * direct.dr.mouseDeltaY * 180.0),
                                 0.0)
                                 0.0)
-        self.camera.setMat(self.relNodePath, wrtMat)
+        direct.camera.setMat(self.camManipRef, wrtMat)
         return Task.cont
         return Task.cont
 
 
     def spawnHPPan(self):
     def spawnHPPan(self):
@@ -355,12 +399,12 @@ class DirectCameraControl(PandaObject):
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
 
     def HPPanTask(self, state):
     def HPPanTask(self, state):
-        self.camera.setHpr(self.camera,
-                           (0.5 * self.chan.mouseDeltaX *
-                            self.chan.fovH),
-                           (-0.5 * self.chan.mouseDeltaY *
-                            self.chan.fovV),
-                           0.0)
+        direct.camera.setHpr(direct.camera,
+                             (0.5 * direct.dr.mouseDeltaX *
+                              direct.dr.fovH),
+                             (-0.5 * direct.dr.mouseDeltaY *
+                              direct.dr.fovV),
+                             0.0)
         return Task.cont
         return Task.cont
 
 
     def fitOnWidget(self):
     def fitOnWidget(self):
@@ -372,27 +416,27 @@ class DirectCameraControl(PandaObject):
         # How big is the node?
         # How big is the node?
         nodeScale = direct.widget.scalingNode.getScale(render)
         nodeScale = direct.widget.scalingNode.getScale(render)
         maxScale = max(nodeScale[0],nodeScale[1],nodeScale[2])
         maxScale = max(nodeScale[0],nodeScale[1],nodeScale[2])
-        maxDim = min(self.chan.nearWidth, self.chan.nearHeight)
+        maxDim = min(direct.dr.nearWidth, direct.dr.nearHeight)
 
 
         # At what distance does the object fill 30% of the screen?
         # At what distance does the object fill 30% of the screen?
         # Assuming radius of 1 on widget
         # Assuming radius of 1 on widget
-        camY = self.chan.near * (2.0 * maxScale)/(0.3 * maxDim)
+        camY = direct.dr.near * (2.0 * maxScale)/(0.3 * maxDim)
     
     
         # What is the vector through the center of the screen?
         # What is the vector through the center of the screen?
         centerVec = Y_AXIS * camY
         centerVec = Y_AXIS * camY
     
     
         # Where is the node relative to the viewpoint
         # Where is the node relative to the viewpoint
-        vWidget2Camera = direct.widget.getPos(self.camera)
+        vWidget2Camera = direct.widget.getPos(direct.camera)
     
     
         # How far do you move the camera to be this distance from the node?
         # How far do you move the camera to be this distance from the node?
         deltaMove = vWidget2Camera - centerVec
         deltaMove = vWidget2Camera - centerVec
     
     
         # Move a target there
         # Move a target there
-        self.relNodePath.setPos(self.camera, deltaMove)
+        self.camManipRef.setPos(direct.camera, deltaMove)
 
 
-	parent = self.camera.getParent()
-	self.camera.wrtReparentTo(self.relNodePath)
-	fitTask = self.camera.lerpPos(Point3(0,0,0),
+	parent = direct.camera.getParent()
+	direct.camera.wrtReparentTo(self.camManipRef)
+	fitTask = direct.camera.lerpPos(Point3(0,0,0),
                                       CAM_MOVE_DURATION,
                                       CAM_MOVE_DURATION,
                                       blendType = 'easeInOut',
                                       blendType = 'easeInOut',
                                       task = 'manipulateCamera')
                                       task = 'manipulateCamera')
@@ -401,50 +445,20 @@ class DirectCameraControl(PandaObject):
         fitTask.uponDeath = self.reparentCam                                
         fitTask.uponDeath = self.reparentCam                                
 
 
     def enableMouseFly(self):
     def enableMouseFly(self):
-	self.enableMouseInteraction()
-	self.enableHotKeys()
-        self.coaMarker.reparentTo(render)
-
-    def enableMouseInteraction(self):
 	# disable C++ fly interface
 	# disable C++ fly interface
 	base.disableMouse()
 	base.disableMouse()
-	# Accept middle mouse events
-	self.accept('handleMouse2', self.mouseFlyStart, [self.chan])
-	self.accept('handleMouse2Up', self.mouseFlyStop)
-
-    def enableHotKeys(self):
-        t = CAM_MOVE_DURATION
-	self.accept('u', self.uprightCam, [self.chan])
-	self.accept('c', self.centerCamIn, [self.chan, 0.5])
-	self.accept('h', self.homeCam, [self.chan])
-        self.accept('f', self.fitOnWidget)
-        for i in range(1,9):
-            self.accept(`i`, self.SpawnMoveToView, [self.chan, i])
-	self.accept('9', self.swingCamAboutWidget, [self.chan, -90.0, t])
-	self.accept('0', self.swingCamAboutWidget, [self.chan,  90.0, t])
-	self.accept('`', self.removeManipulateCameraTask)
-	self.accept('=', self.zoomCam, [self.chan, 0.5, t])
-	self.accept('+', self.zoomCam, [self.chan, 0.5, t])
-	self.accept('-', self.zoomCam, [self.chan, -2.0, t])
-	self.accept('_', self.zoomCam, [self.chan, -2.0, t])
+        # Enable events
+        for event in self.actionEvents:
+            self.accept(event[0], event[1], extraArgs = event[2:])
+        # Show marker
+        self.coaMarker.reparentTo(direct.group)
 
 
     def disableMouseFly(self):
     def disableMouseFly(self):
         # Hide the marker
         # Hide the marker
         self.coaMarker.reparentTo(hidden)
         self.coaMarker.reparentTo(hidden)
-	# Ignore middle mouse events
-	self.ignore('handleMouse2')
-	self.ignore('handleMouse2Up')
-	self.ignore('u')
-	self.ignore('c')
-	self.ignore('h')
-        self.ignore('f')
-        for i in range(0,10):
-            self.ignore(`i`)
-	self.ignore('=')
-	self.ignore('+')
-	self.ignore('-')
-	self.ignore('_')
-        self.ignore('`')
+	# Ignore events
+        for event in self.actionEvents:
+            self.ignore(event[0])
 
 
     def removeManipulateCameraTask(self):
     def removeManipulateCameraTask(self):
         taskMgr.removeTasksNamed('manipulateCamera')
         taskMgr.removeTasksNamed('manipulateCamera')

+ 3 - 9
direct/src/directutil/DirectGrid.py

@@ -5,7 +5,7 @@ class DirectGrid(NodePath,PandaObject):
     def __init__(self):
     def __init__(self):
         # Initialize superclass
         # Initialize superclass
         NodePath.__init__(self)
         NodePath.__init__(self)
-        self.assign(hidden.attachNewNode( NamedNode('DirectGrid')))
+        self.assign(hidden.attachNewNode('DirectGrid'))
         # Don't wireframe or light
         # Don't wireframe or light
         useDirectRenderStyle(self)
         useDirectRenderStyle(self)
 
 
@@ -16,7 +16,7 @@ class DirectGrid(NodePath,PandaObject):
 	self.gridBack.setColor(0.5,0.5,0.5,0.5)
 	self.gridBack.setColor(0.5,0.5,0.5,0.5)
 
 
 	# Grid Lines
 	# Grid Lines
-        self.lines = self.attachNewNode(NamedNode('gridLines'))
+        self.lines = self.attachNewNode('gridLines')
 	self.minorLines = LineNodePath(self.lines)
 	self.minorLines = LineNodePath(self.lines)
         self.minorLines.lineNode.setName('minorLines')
         self.minorLines.lineNode.setName('minorLines')
 	self.minorLines.setColor(VBase4(0.3,0.55,1,1))
 	self.minorLines.setColor(VBase4(0.3,0.55,1,1))
@@ -49,17 +49,11 @@ class DirectGrid(NodePath,PandaObject):
         self.enable()
         self.enable()
 
 
     def enable(self):
     def enable(self):
-        self.reparentTo(render)
-        self.accept('selectedNodePath', self.selectGridBackParent)
+        self.reparentTo(direct.group)
         self.updateGrid()
         self.updateGrid()
 
 
     def disable(self):
     def disable(self):
         self.reparentTo(hidden)
         self.reparentTo(hidden)
-        self.ignore('selectedNodePath')
-
-    def selectGridBackParent(self, nodePath):
-        if nodePath.getName() == 'GridBack':
-            direct.select(self)
 
 
     def updateGrid(self):
     def updateGrid(self):
 	# Update grid lines based upon current grid spacing and grid size
 	# Update grid lines based upon current grid spacing and grid size

+ 86 - 0
direct/src/directutil/DirectLights.py

@@ -0,0 +1,86 @@
+from PandaObject import *
+from DirectGeometry import *
+
+class DirectLights(NodePath):
+    def __init__(self, parent = None):
+        # Initialize the superclass
+        NodePath.__init__(self)
+        # Use direct.group as default parent
+        if parent == None:
+            parent = direct.group
+        # Create a node for the lights
+        self.assign(parent.attachNewNode('DIRECT Lights'))
+        # Create a light attribute 
+        self.la = LightAttribute()
+        # Create a list of all active lights
+        self.lightList = []
+        self.nodePathList = []
+        # Counts of the various types of lights
+        self.ambientCount = 0
+        self.directionalCount = 0
+        self.pointCount = 0
+        self.spotCount = 0
+
+    def __getitem__(self, index):
+        return self.lightList[index]
+
+    def getLightNodePath(self, index):
+        return self.nodePathList[index]
+
+    def create(self, type):
+        if type == 'ambient':
+            self.ambientCount += 1
+            light = AmbientLight('ambient_' + `self.ambientCount`)
+            light.setColor(VBase4(.3,.3,.3,1))
+        elif type == 'directional':
+            self.directionalCount += 1
+            light = DirectionalLight('directional_' + `self.directionalCount`)
+            light.setColor(VBase4(1))
+        elif type == 'point':
+            self.pointCount += 1
+            light = PointLight('point_' + `self.pointCount`)
+            light.setColor(VBase4(1))
+        elif type == 'spot':
+            self.spotCount += 1
+            light = SpotLight('spot_' + `self.spotCount`)
+            light.setColor(VBase4(1))
+        # Add the new light
+        self.addLight(light)
+        # Turn it on as a default
+        self.setOn(light)
+        # Return the new light
+        return light
+
+    def createDefaultLights(self):
+        self.create('ambient')
+        self.create('directional')
+
+    def addLight(self, light):
+        # Attach node to self
+        nodePath = self.attachNewNode(light.upcastToNamedNode())
+        # Store it in the lists
+        self.lightList.append(light)
+        self.nodePathList.append(nodePath)
+
+    def allOn(self):
+        """ Turn on all DIRECT lights """
+        base.initialState.setAttribute(LightTransition.getClassType(),
+                                       self.la)
+
+    def allOff(self):
+        """ Turn off all DIRECT lights """
+        base.initialState.clearAttribute(LightTransition.getClassType())
+
+    def setOnNum(self, index):
+        self.setOn(self.lightList[index])
+
+    def setOffNum(self, index):
+        self.setOff(self.lightList[index])
+
+    def setOn(self, light):
+        self.la.setOn(light.upcastToLight())
+
+    def setOff(self, light):
+        self.la.setOff(light.upcastToLight())
+
+

+ 65 - 70
direct/src/directutil/DirectManipulation.py

@@ -8,14 +8,12 @@ UNPICKABLE = ['x-disc-visible', 'y-disc-visible', 'z-disc-visible',
 class DirectManipulationControl(PandaObject):
 class DirectManipulationControl(PandaObject):
     def __init__(self):
     def __init__(self):
         # Create the grid
         # Create the grid
-        self.chan = direct.chan
-        self.camera = self.chan.camera
         self.objectHandles = ObjectHandles()
         self.objectHandles = ObjectHandles()
         self.hitPt = Point3(0)
         self.hitPt = Point3(0)
         self.prevHit = Vec3(0)
         self.prevHit = Vec3(0)
         self.rotationCenter = Point3(0)
         self.rotationCenter = Point3(0)
         self.initScaleMag = 1
         self.initScaleMag = 1
-        self.refNodePath = render.attachNewNode(NamedNode('refNodePath'))
+        self.manipRef = direct.group.attachNewNode('manipRef')
         self.hitPtDist = 0
         self.hitPtDist = 0
         self.constraint = None
         self.constraint = None
         self.rotateAxis = 'x'
         self.rotateAxis = 'x'
@@ -27,13 +25,22 @@ class DirectManipulationControl(PandaObject):
         self.fScaling = 1
         self.fScaling = 1
         self.unpickable = UNPICKABLE
         self.unpickable = UNPICKABLE
         self.mode = None
         self.mode = None
-
-    def manipulationStart(self, chan):
+        self.actionEvents = [
+            ['handleMouse1', self.manipulationStart],
+            ['handleMouse1Up', self.manipulationStop],
+            ['.', self.objectHandles.multiplyScalingFactorBy, 2.0],
+            ['>', self.objectHandles.multiplyScalingFactorBy, 2.0],
+            [',', self.objectHandles.multiplyScalingFactorBy, 0.5],
+            ['<', self.objectHandles.multiplyScalingFactorBy, 0.5],
+            ['F', self.objectHandles.growToFit],
+            ]
+
+    def manipulationStart(self):
         # Start out in select mode
         # Start out in select mode
         self.mode = 'select'
         self.mode = 'select'
         # Check for a widget hit point
         # Check for a widget hit point
         numEntries = direct.iRay.pickWidget(
         numEntries = direct.iRay.pickWidget(
-            render,chan.mouseX,chan.mouseY)
+            render,direct.dr.mouseX,direct.dr.mouseY)
         # Did we hit a widget?
         # Did we hit a widget?
         if(numEntries):
         if(numEntries):
             # Yes!
             # Yes!
@@ -63,8 +70,8 @@ class DirectManipulationControl(PandaObject):
         # Or if we move far enough
         # Or if we move far enough
         self.moveDir = None
         self.moveDir = None
         watchMouseTask = Task.Task(self.watchMouseTask)
         watchMouseTask = Task.Task(self.watchMouseTask)
-        watchMouseTask.initX = self.chan.mouseX
-        watchMouseTask.initY = self.chan.mouseY
+        watchMouseTask.initX = direct.dr.mouseX
+        watchMouseTask.initY = direct.dr.mouseY
         taskMgr.spawnTaskNamed(watchMouseTask, 'manip-watch-mouse')
         taskMgr.spawnTaskNamed(watchMouseTask, 'manip-watch-mouse')
 
 
     def switchToMoveMode(self, state):
     def switchToMoveMode(self, state):
@@ -74,8 +81,8 @@ class DirectManipulationControl(PandaObject):
         return Task.done
         return Task.done
 
 
     def watchMouseTask(self, state):
     def watchMouseTask(self, state):
-        if (((abs (state.initX - self.chan.mouseX)) > 0.01) |
-            ((abs (state.initY - self.chan.mouseY)) > 0.01)):
+        if (((abs (state.initX - direct.dr.mouseX)) > 0.01) |
+            ((abs (state.initY - direct.dr.mouseY)) > 0.01)):
             taskMgr.removeTasksNamed('manip-move-wait')
             taskMgr.removeTasksNamed('manip-move-wait')
             taskMgr.removeTasksNamed('manip-switch-to-move')
             taskMgr.removeTasksNamed('manip-switch-to-move')
             self.mode = 'move'
             self.mode = 'move'
@@ -93,7 +100,7 @@ class DirectManipulationControl(PandaObject):
         if self.mode == 'select':
         if self.mode == 'select':
             # Check for object under mouse
             # Check for object under mouse
             numEntries = direct.iRay.pickGeom(
             numEntries = direct.iRay.pickGeom(
-                render,self.chan.mouseX,self.chan.mouseY)
+                render,direct.dr.mouseX,direct.dr.mouseY)
             # Pick out the closest object that isn't a widget
             # Pick out the closest object that isn't a widget
             index = -1
             index = -1
             for i in range(0,numEntries):
             for i in range(0,numEntries):
@@ -166,28 +173,14 @@ class DirectManipulationControl(PandaObject):
         return Task.cont
         return Task.cont
 
 
     def enableManipulation(self):
     def enableManipulation(self):
-	# Accept mouse events
-	self.accept('handleMouse1', self.manipulationStart, [self.chan])
-	self.accept('handleMouse1Up', self.manipulationStop)
-        self.enableHotKeys()
-
-    def enableHotKeys(self):
-        self.accept(
-            '.', self.objectHandles.multiplyScalingFactorBy, [2.0])
-        self.accept(
-            ',', self.objectHandles.multiplyScalingFactorBy, [0.5])
-        self.accept('F', self.objectHandles.growToFit)
+	# Accept events
+        for event in self.actionEvents:
+            self.accept(event[0], event[1], extraArgs = event[2:])
 
 
     def disableManipulation(self):
     def disableManipulation(self):
-	# Ignore middle mouse events
-	self.ignore('handleMouse1')
-	self.ignore('handleMouse1Up')
-        self.disableHotKeys()
-
-    def disableHotKeys(self):
-        self.ignore('.')
-        self.ignore(',')
-        self.ignore('F')
+	# Ignore events
+        for event in self.actionEvents:
+            self.ignore(event[0])
 
 
     def addUnpickable(self, item):
     def addUnpickable(self, item):
         if item not in self.unpickable:
         if item not in self.unpickable:
@@ -209,6 +202,8 @@ class DirectManipulationControl(PandaObject):
             taskMgr.removeTasksNamed('highlightWidgetTask')
             taskMgr.removeTasksNamed('highlightWidgetTask')
             # Set manipulation flag
             # Set manipulation flag
             self.fManip = 1
             self.fManip = 1
+            # Record undo point
+            direct.pushUndo(direct.selected)
             # Update object handles visibility
             # Update object handles visibility
             self.objectHandles.showGuides()
             self.objectHandles.showGuides()
             self.objectHandles.hideAllHandles()
             self.objectHandles.hideAllHandles()
@@ -320,25 +315,25 @@ class DirectManipulationControl(PandaObject):
         # (in case we later switch to another manipulation mode)
         # (in case we later switch to another manipulation mode)
         #self.fHitInit = 1
         #self.fHitInit = 1
         # Where is the widget relative to current camera view
         # Where is the widget relative to current camera view
-        vWidget2Camera = direct.widget.getPos(self.camera)
+        vWidget2Camera = direct.widget.getPos(direct.camera)
         x = vWidget2Camera[0]
         x = vWidget2Camera[0]
         y = vWidget2Camera[1]
         y = vWidget2Camera[1]
         z = vWidget2Camera[2]
         z = vWidget2Camera[2]
         # Move widget (and objects) based upon mouse motion
         # Move widget (and objects) based upon mouse motion
         # Scaled up accordingly based upon widget distance
         # Scaled up accordingly based upon widget distance
-        chan = self.chan
+        dr = direct.dr
         direct.widget.setX(
         direct.widget.setX(
-            self.camera,
-            x + 0.5 * chan.mouseDeltaX * chan.nearWidth * (y/chan.near))
+            direct.camera,
+            x + 0.5 * dr.mouseDeltaX * dr.nearWidth * (y/dr.near))
         direct.widget.setZ(
         direct.widget.setZ(
-            self.camera,
-            z + 0.5 * chan.mouseDeltaY * chan.nearHeight * (y/chan.near))
+            direct.camera,
+            z + 0.5 * dr.mouseDeltaY * dr.nearHeight * (y/dr.near))
 
 
     def xlateCamXY(self):
     def xlateCamXY(self):
         """Constrained 2D motion perpendicular to camera's image plane
         """Constrained 2D motion perpendicular to camera's image plane
         This moves the object in the camera's XY plane"""
         This moves the object in the camera's XY plane"""
         # Now, where is the widget relative to current camera view
         # Now, where is the widget relative to current camera view
-        vWidget2Camera = direct.widget.getPos(self.camera)
+        vWidget2Camera = direct.widget.getPos(direct.camera)
         # If this is first time around, record initial y distance
         # If this is first time around, record initial y distance
         if self.fHitInit:
         if self.fHitInit:
             self.fHitInit = 0
             self.fHitInit = 0
@@ -350,18 +345,18 @@ class DirectManipulationControl(PandaObject):
         z = vWidget2Camera[2]
         z = vWidget2Camera[2]
         # Move widget (and objects) based upon mouse motion
         # Move widget (and objects) based upon mouse motion
         # Scaled up accordingly based upon widget distance
         # Scaled up accordingly based upon widget distance
-        chan = self.chan
+        dr = direct.dr
         direct.widget.setPos(
         direct.widget.setPos(
-            self.camera,
-            x + 0.5 * chan.mouseDeltaX * chan.nearWidth * (y/chan.near),
-            y + self.initY * chan.mouseDeltaY,
+            direct.camera,
+            x + 0.5 * dr.mouseDeltaX * dr.nearWidth * (y/dr.near),
+            y + self.initY * dr.mouseDeltaY,
             z)
             z)
     
     
     def getCrankAngle(self):
     def getCrankAngle(self):
         # Used to compute current angle of mouse (relative to the widget's
         # Used to compute current angle of mouse (relative to the widget's
         # origin) in screen space
         # origin) in screen space
-        x = self.chan.mouseX - self.rotationCenter[0]
-        y = self.chan.mouseY - self.rotationCenter[2]
+        x = direct.dr.mouseX - self.rotationCenter[0]
+        y = direct.dr.mouseY - self.rotationCenter[2]
         return (180 + rad2Deg(math.atan2(y,x)))
         return (180 + rad2Deg(math.atan2(y,x)))
 
 
     def widgetCheck(self,type):
     def widgetCheck(self,type):
@@ -372,7 +367,7 @@ class DirectManipulationControl(PandaObject):
         # widget's origin and one of the three principle axes
         # widget's origin and one of the three principle axes
         axis = self.constraint[:1]
         axis = self.constraint[:1]
         # First compute vector from eye through widget origin
         # First compute vector from eye through widget origin
-        mWidget2Cam = direct.widget.getMat(self.camera)
+        mWidget2Cam = direct.widget.getMat(direct.camera)
         # And determine where the viewpoint is relative to widget
         # And determine where the viewpoint is relative to widget
         pos = VBase3(0)
         pos = VBase3(0)
         decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos,
         decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos,
@@ -398,19 +393,19 @@ class DirectManipulationControl(PandaObject):
     def getWidgetsNearProjectionPoint(self):
     def getWidgetsNearProjectionPoint(self):
         # Find the position of the projection of the specified node path
         # Find the position of the projection of the specified node path
         # on the near plane
         # on the near plane
-        widgetOrigin = direct.widget.getPos(self.camera)
+        widgetOrigin = direct.widget.getPos(direct.camera)
         # project this onto near plane
         # project this onto near plane
-        return widgetOrigin * (self.chan.near / widgetOrigin[1])
+        return widgetOrigin * (direct.dr.near / widgetOrigin[1])
 
 
     def getScreenXY(self):
     def getScreenXY(self):
         # Where does the widget's projection fall on the near plane
         # Where does the widget's projection fall on the near plane
         nearVec = self.getWidgetsNearProjectionPoint()
         nearVec = self.getWidgetsNearProjectionPoint()
         # Clamp these coordinates to visible screen
         # Clamp these coordinates to visible screen
-        nearX = self.clamp(nearVec[0], self.chan.left, self.chan.right)
-        nearY = self.clamp(nearVec[2], self.chan.bottom, self.chan.top)
+        nearX = self.clamp(nearVec[0], direct.dr.left, direct.dr.right)
+        nearY = self.clamp(nearVec[2], direct.dr.bottom, direct.dr.top)
         # What percentage of the distance across the screen is this?
         # What percentage of the distance across the screen is this?
-        percentX = (nearX - self.chan.left)/self.chan.nearWidth
-        percentY = (nearY - self.chan.bottom)/self.chan.nearHeight
+        percentX = (nearX - direct.dr.left)/direct.dr.nearWidth
+        percentY = (nearY - direct.dr.bottom)/direct.dr.nearHeight
         # Map this percentage to the same -1 to 1 space as the mouse
         # 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)
         screenXY = Vec3((2 * percentX) - 1.0,nearVec[1],(2 * percentY) - 1.0)
         # Return the resulting value
         # Return the resulting value
@@ -469,10 +464,10 @@ class DirectManipulationControl(PandaObject):
         # Default is virtual trackball (handles 1D rotations better)
         # Default is virtual trackball (handles 1D rotations better)
         self.fHitInit = 1
         self.fHitInit = 1
         tumbleRate = 360
         tumbleRate = 360
-        # Mouse motion edge to edge of channel results in one full turn
-        self.relHpr(self.camera,
-                    self.chan.mouseDeltaX * tumbleRate,
-                    -self.chan.mouseDeltaY * tumbleRate,
+        # Mouse motion edge to edge of display region results in one full turn
+        self.relHpr(direct.camera,
+                    direct.dr.mouseDeltaX * tumbleRate,
+                    -direct.dr.mouseDeltaY * tumbleRate,
                     0)
                     0)
 
 
     def scale3D(self):
     def scale3D(self):
@@ -481,11 +476,11 @@ class DirectManipulationControl(PandaObject):
         # From midpoint to edge doubles or halves objects scale
         # From midpoint to edge doubles or halves objects scale
         if self.fHitInit:
         if self.fHitInit:
             self.fHitInit = 0
             self.fHitInit = 0
-            self.refNodePath.setPos(direct.widget, 0, 0, 0)
-            self.refNodePath.setHpr(self.camera, 0, 0, 0)
+            self.manipRef.setPos(direct.widget, 0, 0, 0)
+            self.manipRef.setHpr(direct.camera, 0, 0, 0)
             self.initScaleMag = Vec3(
             self.initScaleMag = Vec3(
                 self.objectHandles.getWidgetIntersectPt(
                 self.objectHandles.getWidgetIntersectPt(
-                self.refNodePath, 'y')).length()
+                self.manipRef, 'y')).length()
             # record initial scale
             # record initial scale
             self.initScale = direct.widget.getScale()
             self.initScale = direct.widget.getScale()
         # Begin
         # Begin
@@ -493,7 +488,7 @@ class DirectManipulationControl(PandaObject):
         currScale = (
         currScale = (
             self.initScale *
             self.initScale *
             (self.objectHandles.getWidgetIntersectPt(
             (self.objectHandles.getWidgetIntersectPt(
-            self.refNodePath, 'y').length() /
+            self.manipRef, 'y').length() /
              self.initScaleMag)
              self.initScaleMag)
             )
             )
         direct.widget.setScale(currScale)
         direct.widget.setScale(currScale)
@@ -732,8 +727,8 @@ class ObjectHandles(NodePath,PandaObject):
         taskMgr.removeTasksNamed('resizeObjectHandles')
         taskMgr.removeTasksNamed('resizeObjectHandles')
         # Increase handles scale until they cover 30% of the min dimension
         # Increase handles scale until they cover 30% of the min dimension
         pos = direct.widget.getPos(direct.camera)
         pos = direct.widget.getPos(direct.camera)
-        minDim = min(direct.chan.nearWidth, direct.chan.nearHeight)
-        sf = 0.15 * minDim * (pos[1]/direct.chan.near)
+        minDim = min(direct.dr.nearWidth, direct.dr.nearHeight)
+        sf = 0.15 * minDim * (pos[1]/direct.dr.near)
         self.ohScalingFactor = sf
         self.ohScalingFactor = sf
         self.scalingNode.lerpScale(sf,sf,sf, 0.5,
         self.scalingNode.lerpScale(sf,sf,sf, 0.5,
                                    blendType = 'easeInOut',
                                    blendType = 'easeInOut',
@@ -741,7 +736,7 @@ class ObjectHandles(NodePath,PandaObject):
 
 
     def createObjectHandleLines(self):
     def createObjectHandleLines(self):
         # X post
         # X post
-        self.xPost = self.xPostGroup.attachNewNode(NamedNode('x-post-visible'))
+        self.xPost = self.xPostGroup.attachNewNode('x-post-visible')
 	lines = LineNodePath(self.xPost)
 	lines = LineNodePath(self.xPost)
 	lines.setColor(VBase4(1,0,0,1))
 	lines.setColor(VBase4(1,0,0,1))
 	lines.setThickness(5)
 	lines.setThickness(5)
@@ -756,7 +751,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         lines.create()
         
         
 	# X ring
 	# X ring
-        self.xRing = self.xRingGroup.attachNewNode(NamedNode('x-ring-visible'))
+        self.xRing = self.xRingGroup.attachNewNode('x-ring-visible')
 	lines = LineNodePath(self.xRing)
 	lines = LineNodePath(self.xRing)
 	lines.setColor(VBase4(1,0,0,1))
 	lines.setColor(VBase4(1,0,0,1))
 	lines.setThickness(3)
 	lines.setThickness(3)
@@ -768,7 +763,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         lines.create()
         
         
         # Y post
         # Y post
-        self.yPost = self.yPostGroup.attachNewNode(NamedNode('y-post-visible'))
+        self.yPost = self.yPostGroup.attachNewNode('y-post-visible')
 	lines = LineNodePath(self.yPost)
 	lines = LineNodePath(self.yPost)
 	lines.setColor(VBase4(0,1,0,1))
 	lines.setColor(VBase4(0,1,0,1))
 	lines.setThickness(5)
 	lines.setThickness(5)
@@ -783,7 +778,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         lines.create()
         
         
 	# Y ring
 	# Y ring
-        self.yRing = self.yRingGroup.attachNewNode(NamedNode('y-ring-visible'))
+        self.yRing = self.yRingGroup.attachNewNode('y-ring-visible')
 	lines = LineNodePath(self.yRing)
 	lines = LineNodePath(self.yRing)
 	lines.setColor(VBase4(0,1,0,1))
 	lines.setColor(VBase4(0,1,0,1))
 	lines.setThickness(3)
 	lines.setThickness(3)
@@ -795,7 +790,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         lines.create()
 
 
         # Z post
         # Z post
-        self.zPost = self.zPostGroup.attachNewNode(NamedNode('z-post-visible'))
+        self.zPost = self.zPostGroup.attachNewNode('z-post-visible')
 	lines = LineNodePath(self.zPost)
 	lines = LineNodePath(self.zPost)
 	lines.setColor(VBase4(0,0,1,1))
 	lines.setColor(VBase4(0,0,1,1))
 	lines.setThickness(5)
 	lines.setThickness(5)
@@ -810,7 +805,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         lines.create()
         
         
 	# Z ring
 	# Z ring
-        self.zRing = self.zRingGroup.attachNewNode(NamedNode('z-ring-visible'))
+        self.zRing = self.zRingGroup.attachNewNode('z-ring-visible')
 	lines = LineNodePath(self.zRing)
 	lines = LineNodePath(self.zRing)
 	lines.setColor(VBase4(0,0,1,1))
 	lines.setColor(VBase4(0,0,1,1))
 	lines.setThickness(3)
 	lines.setThickness(3)
@@ -822,7 +817,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         lines.create()
 
 
     def createGuideLines(self):
     def createGuideLines(self):
-        self.guideLines = self.attachNewNode(NamedNode('guideLines'))
+        self.guideLines = self.attachNewNode('guideLines')
         # X guide lines
         # X guide lines
 	lines = LineNodePath(self.guideLines)
 	lines = LineNodePath(self.guideLines)
 	lines.setColor(VBase4(1,0,0,1))
 	lines.setColor(VBase4(1,0,0,1))
@@ -853,7 +848,7 @@ class ObjectHandles(NodePath,PandaObject):
     def getAxisIntersectPt(self, axis):
     def getAxisIntersectPt(self, axis):
         # Calc the xfrom from camera to widget
         # Calc the xfrom from camera to widget
         mCam2Widget = direct.camera.getMat(direct.widget)
         mCam2Widget = direct.camera.getMat(direct.widget)
-        lineDir = Vec3(mCam2Widget.xformVec(direct.chan.nearVec))
+        lineDir = Vec3(mCam2Widget.xformVec(direct.dr.nearVec))
         lineDir.normalize()
         lineDir.normalize()
         # And determine where the viewpoint is relative to widget
         # And determine where the viewpoint is relative to widget
         lineOrigin = VBase3(0)
         lineOrigin = VBase3(0)
@@ -910,7 +905,7 @@ class ObjectHandles(NodePath,PandaObject):
         # Next we find the vector from viewpoint to the widget through
         # Next we find the vector from viewpoint to the widget through
         # the mouse's position on near plane.
         # the mouse's position on near plane.
         # This defines the intersection ray
         # This defines the intersection ray
-        lineDir = Vec3(mCam2NodePath.xformVec(direct.chan.nearVec))
+        lineDir = Vec3(mCam2NodePath.xformVec(direct.dr.nearVec))
         lineDir.normalize()
         lineDir.normalize()
         # Find the hit point
         # Find the hit point
         if plane == 'x':
         if plane == 'x':

+ 9 - 10
direct/src/directutil/DirectSelection.py

@@ -202,26 +202,27 @@ class SelectedNodePaths(PandaObject):
         self.forEachSelectedNodePathDo(DirectNodePath.dehighlight)
         self.forEachSelectedNodePathDo(DirectNodePath.dehighlight)
 
 
     def removeSelected(self):
     def removeSelected(self):
-	selected = self.dnp.last
+	selected = self.last
         if selected:
         if selected:
             selected.remove()
             selected.remove()
+        self.last = None
         
         
     def removeAll(self):
     def removeAll(self):
 	# Remove all selected nodePaths from the Scene Graph
 	# Remove all selected nodePaths from the Scene Graph
         self.forEachSelectedNodePathDo(NodePath.remove)
         self.forEachSelectedNodePathDo(NodePath.remove)
 
 
-    def toggleVizSelected(self):
-	selected = self.dnp.last
+    def toggleVisSelected(self):
+	selected = self.last
         # Toggle visibility of selected node paths
         # Toggle visibility of selected node paths
         if selected:
         if selected:
-            selected.toggleViz()
+            selected.toggleVis()
 
 
-    def toggleVizAll(self):
+    def toggleVisAll(self):
         # Toggle viz for all selected node paths
         # Toggle viz for all selected node paths
-        self.forEachSelectedNodePathDo(NodePath.toggleViz)
+        self.forEachSelectedNodePathDo(NodePath.toggleVis)
 
 
     def isolateSelected(self):
     def isolateSelected(self):
-	selected = self.dnp.last
+	selected = self.last
         if selected:
         if selected:
             selected.isolate()
             selected.isolate()
 
 
@@ -375,9 +376,7 @@ class DirectBoundingBox:
 
 
 class SelectionRay:
 class SelectionRay:
     def __init__(self, camera):
     def __init__(self, camera):
-        # Record the camera associated with this selection ray
-        self.camera = camera
-        # Create a collision node
+        # Create a collision node path attached to the given camera
         self.rayCollisionNodePath = camera.attachNewNode( CollisionNode() )
         self.rayCollisionNodePath = camera.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()

+ 357 - 91
direct/src/directutil/DirectSession.py

@@ -4,31 +4,41 @@ from DirectManipulation import *
 from DirectSelection import *
 from DirectSelection import *
 from DirectGrid import *
 from DirectGrid import *
 from DirectGeometry import *
 from DirectGeometry import *
+from DirectLights import *
 import OnscreenText
 import OnscreenText
+import types
 import __builtin__
 import __builtin__
 
 
+DIRECT_FLASH_DURATION = 1.5
+
 class DirectSession(PandaObject):
 class DirectSession(PandaObject):
 
 
     def __init__(self):
     def __init__(self):
         # Establish a global pointer to the direct object early on
         # Establish a global pointer to the direct object early on
         # so dependant classes can access it in their code
         # so dependant classes can access it in their code
         __builtin__.direct = self
         __builtin__.direct = self
-        self.contextList = []
-        self.iRayList = []
-        for camera in base.cameraList:
-            self.contextList.append(DisplayRegionContext(base.win, camera))
-            self.iRayList.append(SelectionRay(camera))
-        self.chan = self.getChanData(0)
-        self.camera = base.cameraList[0]
+        self.drList = DisplayRegionList()
+        self.dr = self.drList[0]
+        self.camera = self.dr.camera
+        self.iRay = self.dr.iRay
 
 
+        self.group = render.attachNewNode('DIRECT')
         self.cameraControl = DirectCameraControl()
         self.cameraControl = DirectCameraControl()
         self.manipulationControl = DirectManipulationControl()
         self.manipulationControl = DirectManipulationControl()
         self.useObjectHandles()
         self.useObjectHandles()
         self.grid = DirectGrid()
         self.grid = DirectGrid()
         self.grid.disable()
         self.grid.disable()
+        self.lights = DirectLights()
+        # Create some default lights
+        self.lights.createDefaultLights()
+        # But turn them off
+        self.lights.allOff()
 
 
         # Initialize the collection of selected nodePaths
         # Initialize the collection of selected nodePaths
         self.selected = SelectedNodePaths()
         self.selected = SelectedNodePaths()
+        # Ancestry of currently selected object
+        self.ancestry = []
+        self.ancestryIndex = 0
 
 
         self.readout = OnscreenText.OnscreenText( '', 0.1, -0.95 )
         self.readout = OnscreenText.OnscreenText( '', 0.1, -0.95 )
         # Make sure readout is never lit or drawn in wireframe
         # Make sure readout is never lit or drawn in wireframe
@@ -44,87 +54,45 @@ class DirectSession(PandaObject):
         self.hpr = VBase3()
         self.hpr = VBase3()
         self.scale = VBase3()
         self.scale = VBase3()
 
 
-        self.iRay = self.iRayList[0]
         self.hitPt = Point3(0.0)
         self.hitPt = Point3(0.0)
 
 
+        # Lists for managing undo/redo operations
+        self.undoList = []
+        self.redoList = []
+        
         # One run through the context task to init everything
         # One run through the context task to init everything
-        for context in self.contextList:
-            context.contextTask(None)
-
-        self.actionEvents = [('select', self.select),
-                             ('deselect', self.deselect),
-                             ('deselectAll', self.deselectAll),
-                             ('highlightAll', self.selected.highlightAll),
-                             ('preRemoveNodePath', self.deselect)]
+        self.drList.updateContext()
+
+        self.actionEvents = [
+            ['select', self.select],
+            ['deselect', self.deselect],
+            ['deselectAll', self.deselectAll],
+            ['highlightAll', self.selected.highlightAll],
+            ['preRemoveNodePath', self.deselect],
+            # Scene graph explorer functions
+            ['SGENodePath_Select', self.select],
+            ['SGENodePath_Deselect', self.deselect],
+            ['SGENodePath_Flash', self.flash],
+            ['SGENodePath_Isolate', self.isolate],
+            ['SGENodePath_Toggle Vis', self.toggleVis],
+            ['SGENodePath_Show All', self.showAllDescendants],
+            ['SGENodePath_Delete', self.removeNodePath],
+            ]
         self.keyEvents = ['left', 'right', 'up', 'down',
         self.keyEvents = ['left', 'right', 'up', 'down',
                           'escape', 'space', 'delete',
                           'escape', 'space', 'delete',
                           'shift', 'shift-up', 'alt', 'alt-up',
                           'shift', 'shift-up', 'alt', 'alt-up',
                           'control', 'control-up',
                           'control', 'control-up',
+                          'page_up', 'page_down',
                           'b', 'c', 'f', 'l', 't', 'v', 'w']
                           'b', 'c', 'f', 'l', 't', 'v', 'w']
         self.mouseEvents = ['mouse1', 'mouse1-up',
         self.mouseEvents = ['mouse1', 'mouse1-up',
                             'mouse2', 'mouse2-up',
                             'mouse2', 'mouse2-up',
                             'mouse3', 'mouse3-up']
                             'mouse3', 'mouse3-up']
 
 
-    def select(self, nodePath, fMultiselect = 0):
-        dnp = self.selected.select(nodePath, fMultiselect)
-        if dnp:
-            messenger.send('preSelectNodePath', [dnp])
-            # Update the readout
-            self.readout.reparentTo(render2d)
-            self.readout.setText(dnp.name)
-            # Show the manipulation widget
-            self.widget.reparentTo(render)
-            # Update camera controls coa to this point
-            # Coa2Camera = Coa2Dnp * Dnp2Camera
-            mCoa2Camera = dnp.mCoa2Dnp * dnp.getMat(base.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.removeTasksNamed('followSelectedNodePath')
-            t = Task.Task(self.followSelectedNodePathTask)
-            t.dnp = dnp
-            taskMgr.spawnTaskNamed(t, 'followSelectedNodePath')
-            # Send an message marking the event
-            messenger.send('selectedNodePath', [dnp])
-
-    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.reparentTo(hidden)
-            self.readout.reparentTo(hidden)
-            self.readout.setText(' ')
-            taskMgr.removeTasksNamed('followSelectedNodePath')
-            # Send an message marking the event
-            messenger.send('deselectedNodePath', [dnp])
-
-    def deselectAll(self):
-        self.selected.deselectAll()
-        # Hide the manipulation widget
-        self.widget.reparentTo(hidden)
-        self.readout.reparentTo(hidden)
-        self.readout.setText(' ')
-        taskMgr.removeTasksNamed('followSelectedNodePath')
-
     def enable(self):
     def enable(self):
         # Make sure old tasks are shut down
         # Make sure old tasks are shut down
         self.disable()
         self.disable()
 	# Start all display region context tasks
 	# Start all display region context tasks
-        for context in self.contextList:
-            context.spawnContextTask()
+        self.drList.spawnContextTask()
 	# Turn on mouse Flying
 	# Turn on mouse Flying
 	self.cameraControl.enableMouseFly()
 	self.cameraControl.enableMouseFly()
         # Turn on object manipulation
         # Turn on object manipulation
@@ -138,8 +106,7 @@ class DirectSession(PandaObject):
 
 
     def disable(self):
     def disable(self):
 	# Shut down all display region context tasks
 	# Shut down all display region context tasks
-        for context in self.contextList:
-            context.removeContextTask()
+        self.drList.removeContextTask()
 	# Turn off camera fly
 	# Turn off camera fly
 	self.cameraControl.disableMouseFly()
 	self.cameraControl.disableMouseFly()
         # Turn off object manipulation
         # Turn off object manipulation
@@ -150,8 +117,7 @@ class DirectSession(PandaObject):
 
 
     def minimumConfiguration(self):
     def minimumConfiguration(self):
 	# Remove context task
 	# Remove context task
-	for context in self.contextList:
-            context.removeContextTask()
+        self.drList.removeContextTask()
 	# Turn off camera fly
 	# Turn off camera fly
 	self.cameraControl.disableMouseFly()
 	self.cameraControl.disableMouseFly()
 	# Ignore keyboard and action events
 	# Ignore keyboard and action events
@@ -166,9 +132,11 @@ class DirectSession(PandaObject):
     def reset(self):
     def reset(self):
 	self.enable()
 	self.enable()
 
 
+    # EVENT FUNCTIONS
+
     def enableActionEvents(self):
     def enableActionEvents(self):
-        for event, method in self.actionEvents:
-            self.accept(event, method)
+        for event in self.actionEvents:
+            self.accept(event[0], event[1], extraArgs = event[2:])
 
 
     def enableKeyEvents(self):
     def enableKeyEvents(self):
         for event in self.keyEvents:
         for event in self.keyEvents:
@@ -190,15 +158,6 @@ class DirectSession(PandaObject):
         for event in self.mouseEvents:
         for event in self.mouseEvents:
             self.ignore(event)
             self.ignore(event)
 
 
-    def useObjectHandles(self):
-        self.widget = self.manipulationControl.objectHandles
-
-    def hideReadout(self):
-	self.readout.reparentTo(hidden)
-
-    def getChanData(self, index):
-        return self.contextList[index]
-
     def inputHandler(self, input):
     def inputHandler(self, input):
 	# Deal with keyboard and mouse input
 	# Deal with keyboard and mouse input
         if input == 'mouse1':
         if input == 'mouse1':
@@ -225,15 +184,19 @@ class DirectSession(PandaObject):
             self.fAlt = 1
             self.fAlt = 1
         elif input == 'alt-up':
         elif input == 'alt-up':
             self.fAlt = 0
             self.fAlt = 0
+        elif input == 'page_up':
+            self.upAncestry()
+        elif input == 'page_down':
+            self.downAncestry()
         elif input == 'escape':
         elif input == 'escape':
             self.deselectAll()
             self.deselectAll()
         elif input == 'l':
         elif input == 'l':
             if self.selected.last:
             if self.selected.last:
                 self.select(self.selected.last)
                 self.select(self.selected.last)
         elif input == 'delete':
         elif input == 'delete':
-            self.selected.removeAll()
+            self.removeAllSelected()
         elif input == 'v':
         elif input == 'v':
-            self.selected.toggleVizAll()
+            self.selected.toggleVisAll()
         elif input == 'b':
         elif input == 'b':
             base.toggleBackface()
             base.toggleBackface()
         elif input == 't':
         elif input == 't':
@@ -241,12 +204,315 @@ class DirectSession(PandaObject):
         elif input == 'w':
         elif input == 'w':
             base.toggleWireframe()
             base.toggleWireframe()
         
         
-class DisplayRegionContext(PandaObject):
+    def select(self, nodePath, fMultiselect = 0, fResetAncestry = 1):
+        dnp = self.selected.select(nodePath, fMultiselect)
+        if dnp:
+            messenger.send('preSelectNodePath', [dnp])
+            if fResetAncestry:
+                # Update ancestry
+                self.ancestry = dnp.getAncestry()
+                self.ancestry.reverse()
+                self.ancestryIndex = 0
+            # Update the readout
+            self.readout.reparentTo(render2d)
+            self.readout.setText(dnp.name)
+            # Show the manipulation widget
+            self.widget.reparentTo(direct.group)
+            # 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.removeTasksNamed('followSelectedNodePath')
+            t = Task.Task(self.followSelectedNodePathTask)
+            t.dnp = dnp
+            taskMgr.spawnTaskNamed(t, 'followSelectedNodePath')
+            # Send an message marking the event
+            messenger.send('selectedNodePath', [dnp])
+
+    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.reparentTo(hidden)
+            self.readout.reparentTo(hidden)
+            self.readout.setText(' ')
+            taskMgr.removeTasksNamed('followSelectedNodePath')
+            self.ancestry = []
+            # Send an message marking the event
+            messenger.send('deselectedNodePath', [dnp])
+
+    def deselectAll(self):
+        self.selected.deselectAll()
+        # Hide the manipulation widget
+        self.widget.reparentTo(hidden)
+        self.readout.reparentTo(hidden)
+        self.readout.setText(' ')
+        taskMgr.removeTasksNamed('followSelectedNodePath')
+
+    def flash(self, nodePath = 'None Given'):
+        """ Highlight an object by setting it red for a few seconds """
+        # Clean up any existing task
+        taskMgr.removeTasksNamed('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.spawnTaskNamed(
+                Task.doLater(DIRECT_FLASH_DURATION,
+                             # This is just a dummy task
+                             Task.Task(self.flashDummy),
+                             'flashNodePath'),
+                '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.doneColor:
+            state.nodePath.setColor(state.doneColor)
+        else:
+            state.nodePath.clearColor()
+
+    def isolate(self, nodePath = 'None Given'):
+        """ Show a node path and hide its siblings """
+        # First kill the flashing task to avoid complications
+        taskMgr.removeTasksNamed('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.removeTasksNamed('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.hideCollisionSolids()
+
+    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 (name != 'render') & (name != 'renderTop'):
+                    self.ancestryIndex = i
+                    self.select(np, 0, 0)
+                    self.flash(np)
+
+    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') & (name != 'renderTop'):
+                    self.ancestryIndex = i
+                    self.select(np, 0, 0)
+                    self.flash(np)
+
+    # UNDO REDO FUNCTIONS
+    
+    def pushUndo(self, nodePathList, fResetRedo = 1):
+        # Assemble group of changes
+        undoGroup = []
+        for nodePath in nodePathList:
+            pos = nodePath.getPos()
+            hpr = nodePath.getHpr()
+            scale = nodePath.getScale()
+            undoGroup.append([nodePath, pos,hpr,scale])
+        # Now record group
+        self.undoList.append(undoGroup)
+        # Truncate list
+        self.undoList = self.undoList[-5:]
+        # Alert anyone who cares
+        messenger.send('pushUndo')
+        if fResetRedo & (nodePathList != []):
+            self.redoList = []
+            messenger.send('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('undoListEmpty')
+        # Return last item
+        return undoGroup
+        
+    def pushRedo(self, nodePathList):
+        # Assemble group of changes
+        redoGroup = []
+        for nodePath in nodePathList:
+            pos = nodePath.getPos()
+            hpr = nodePath.getHpr()
+            scale = nodePath.getScale()
+            redoGroup.append([nodePath, pos,hpr,scale])
+        # Now record redo group
+        self.redoList.append(redoGroup)
+        # Truncate list
+        self.redoList = self.redoList[-5:]
+        # Alert anyone who cares
+        messenger.send('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('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].setPosHprScale(pose[1], pose[2], pose[3])
+            # Alert anyone who cares
+            messenger.send('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].setPosHprScale(pose[1], pose[2], pose[3])
+            # Alert anyone who cares
+            messenger.send('redo')
+
+    # UTILITY FUNCTIONS
+    def useObjectHandles(self):
+        self.widget = self.manipulationControl.objectHandles
+
+    def hideReadout(self):
+	self.readout.reparentTo(hidden)
+
+class DisplayRegionList:
+    def __init__(self):
+        self.displayRegionList = []
+        for camera in base.cameraList:
+            self.displayRegionList.append(
+                DisplayRegionContext(base.win, camera))
+
+    def __getitem__(self, index):
+        return self.displayRegionList[index]
+
+    def updateContext(self):
+        for dr in self.displayRegionList:
+            dr.contextTask(None)
+        
+    def spawnContextTask(self):
+        for dr in self.displayRegionList:
+            dr.start()
+
+    def removeContextTask(self):
+        for dr in self.displayRegionList:
+            dr.stop()
+
+    def setNearFar(self, near, far):
+        for dr in self.displayRegionList:
+            dr.camNode.setNearFar(near, far)
+    
+    def setNear(self, near):
+        for dr in self.displayRegionList:
+            dr.camNode.setNear(near)
+    
+    def setFar(self, far):
+        for dr in self.displayRegionList:
+            dr.camNode.setFar(far)
+
+    def setFov(self, hfov, vfov):
+        for dr in self.displayRegionList:
+            dr.camNode.setFov(hfov, vfov)
+
+    def setHfov(self, fov):
+        for dr in self.displayRegionList:
+            dr.camNode.setHfov(fov)
+
+    def setVfov(self, fov):
+        for dr in self.displayRegionList:
+            dr.camNode.setVfov(fov)
+
+class DisplayRegionContext:
     def __init__(self, win, camera):
     def __init__(self, win, camera):
         self.win = win
         self.win = win
         self.camera = camera
         self.camera = camera
-        self.cam = camera.getChild(0)
+        self.cam = self.camera.find('**/+Camera')
         self.camNode = self.cam.getNode(0)
         self.camNode = self.cam.getNode(0)
+        self.iRay = SelectionRay(self.camera)
         self.nearVec = Vec3(0)
         self.nearVec = Vec3(0)
         self.mouseX = 0.0
         self.mouseX = 0.0
         self.mouseY = 0.0
         self.mouseY = 0.0

+ 1 - 0
direct/src/directutil/DirectSessionGlobal.py

@@ -5,6 +5,7 @@ import __builtin__
 if base.wantDIRECT:
 if base.wantDIRECT:
     from DirectSession import *
     from DirectSession import *
     __builtin__.direct = base.direct = DirectSession()
     __builtin__.direct = base.direct = DirectSession()
+    direct.enable()
 else:
 else:
     # Otherwise set the values to None
     # Otherwise set the values to None
     __builtin__.direct = base.direct = None
     __builtin__.direct = base.direct = None

+ 3 - 3
direct/src/extensions/NodePath-extensions.py

@@ -45,7 +45,7 @@
         for child in self.getChildrenAsList():
         for child in self.getChildrenAsList():
             print child.getName()
             print child.getName()
 
 
-    def toggleViz(self):
+    def toggleVis(self):
         """Toggles visibility of a nodePath"""
         """Toggles visibility of a nodePath"""
         if self.isHidden():
         if self.isHidden():
             self.show()
             self.show()
@@ -66,7 +66,8 @@
 
 
     def showAllDescendants(self):
     def showAllDescendants(self):
         """Show the node path and all its children"""
         """Show the node path and all its children"""
-	self.show()
+        if self.hasArcs():
+            self.show()
         for child in self.getChildrenAsList():
         for child in self.getChildrenAsList():
             child.showAllDescendants()
             child.showAllDescendants()
 
 
@@ -82,7 +83,6 @@
         # before node is deleted
         # before node is deleted
         messenger.send('preRemoveNodePath', [self])
         messenger.send('preRemoveNodePath', [self])
         # Remove nodePath
         # Remove nodePath
-        self.reparentTo(hidden)
         self.removeNode()
         self.removeNode()
 
 
     def reversels(self):
     def reversels(self):

+ 51 - 128
direct/src/leveleditor/LevelEditor.py

@@ -227,7 +227,7 @@ class LevelEditor(NodePath, PandaObject):
         # Initialize superclass
         # Initialize superclass
         NodePath.__init__(self)
         NodePath.__init__(self)
         # Become the new node path
         # Become the new node path
-        self.assign(hidden.attachNewNode( NamedNode('LevelEditor')))
+        self.assign(hidden.attachNewNode('LevelEditor'))
         
         
         # Create ancillary objects
         # Create ancillary objects
         # Style manager for keeping track of styles/colors
         # Style manager for keeping track of styles/colors
@@ -253,13 +253,9 @@ class LevelEditor(NodePath, PandaObject):
             ('manipulateObjectCleanup', self.updateSelectedPose),
             ('manipulateObjectCleanup', self.updateSelectedPose),
             
             
             # Actions in response to Level Editor Panel operations
             # Actions in response to Level Editor Panel operations
-            ('SGENodePath_Select', self.select),
             ('SGENodePath_Set Name', self.getAndSetName),
             ('SGENodePath_Set Name', self.getAndSetName),
             ('SGENodePath_Set Parent', self.setActiveParent),
             ('SGENodePath_Set Parent', self.setActiveParent),
             ('SGENodePath_Reparent', self.reparent),
             ('SGENodePath_Reparent', self.reparent),
-            ('SGENodePath_Flash', self.flash),
-            ('SGENodePath_Isolate', self.isolate),
-            ('SGENodePath_Toggle Viz', self.toggleViz),
             ('SGENodePath_Add Group', self.addGroup),
             ('SGENodePath_Add Group', self.addGroup),
             ('SGENodePath_Add Vis Group', self.addVisGroup),
             ('SGENodePath_Add Vis Group', self.addVisGroup),
             ('SGENodePath_Set Color', self.setNPColor),
             ('SGENodePath_Set Color', self.setNPColor),
@@ -331,7 +327,7 @@ class LevelEditor(NodePath, PandaObject):
     def enable(self):
     def enable(self):
         """ Enable level editing and show level """
         """ Enable level editing and show level """
         # Make sure level is visible
         # Make sure level is visible
-        self.reparentTo(render)
+        self.reparentTo(direct.group)
 	self.show()
 	self.show()
         # Add all the action events
         # Add all the action events
         for event in self.actionEvents:
         for event in self.actionEvents:
@@ -732,7 +728,7 @@ class LevelEditor(NodePath, PandaObject):
         if DNAClassEqual(dnaNode, DNA_STREET):
         if DNAClassEqual(dnaNode, DNA_STREET):
             self.snapList = OBJECT_SNAP_POINTS[dnaNode.getCode()]
             self.snapList = OBJECT_SNAP_POINTS[dnaNode.getCode()]
 	# Select the instance
 	# Select the instance
-	self.select(newNodePath)
+	direct.select(newNodePath)
         # Update grid to get ready for the next object
         # Update grid to get ready for the next object
         self.autoPositionGrid()
         self.autoPositionGrid()
 
 
@@ -1118,11 +1114,6 @@ class LevelEditor(NodePath, PandaObject):
         esg = EntryScale.setColor(nodePath, updateDNANodeColor)
         esg = EntryScale.setColor(nodePath, updateDNANodeColor)
     
     
     # SELECTION FUNCTIONS
     # SELECTION FUNCTIONS
-    def select(self, nodePath):
-        """ Call direct function to select node path """
-        # Select new node path
-	direct.select(nodePath)
-
     def selectedNodePathHook(self, nodePath):
     def selectedNodePathHook(self, nodePath):
         """
         """
         Hook called upon selection of a node path used to restrict
         Hook called upon selection of a node path used to restrict
@@ -1281,76 +1272,6 @@ class LevelEditor(NodePath, PandaObject):
             childVisGroups = (childVisGroups + self.getDNAVisGroups(child))
             childVisGroups = (childVisGroups + self.getDNAVisGroups(child))
         return childVisGroups
         return childVisGroups
     
     
-    def flash(self, nodePath = None):
-        if not nodePath:
-            # If nothing specified, try selected node path
-            nodePath = direct.selected.last
-        if nodePath:
-            """ Spawn a task to flash a node several times """
-            taskMgr.removeTasksNamed('flashNodePath')
-            t = Task.Task(self.flashNodePathTask)
-            t.nodePath = nodePath
-            t.initState = t.hidden = nodePath.isHidden()
-            t.flashCount = 0
-            t.frameCount = 0
-            t.uponDeath = self.flashDone
-            taskMgr.spawnTaskNamed(t, 'flashNodePath')
-
-    def flashNodePathTask(self, state):
-        nodePath = state.nodePath
-	initState = state.initState
-        hidden = state.hidden
-        flashCount = state.flashCount
-        frameCount = state.frameCount
-        if (flashCount < 4):
-            if (frameCount % 3) == 0:
-                if hidden:
-                    nodePath.show()
-                else:
-                    nodePath.hide()
-                state.hidden = not state.hidden
-                state.flashCount = flashCount + 1
-            state.frameCount = frameCount + 1
-            return Task.cont
-        else:
-            return Task.done
-
-    def flashDone(self,state):
-        if state.initState:
-            state.nodePath.hide()
-        else:
-            state.nodePath.show()
-
-    def isolate(self, nodePath = None):
-        """ Show a node path and hide its siblings """
-        if not nodePath:
-            # If nothing specified, try selected node path
-            nodePath = direct.selected.last
-        if nodePath:
-            # First show everything in level
-            self.showAll()
-            # Now hide all of this node path's siblings
-            nodePath.hideSiblings()
-
-    def toggleViz(self, nodePath = None):
-        """ Toggle visibility of node path """
-        if not nodePath:
-            # If nothing specified, try selected node path
-            nodePath = direct.selected.last
-        if nodePath:
-            # First kill the flashing task to avoid complications
-            taskMgr.removeTasksNamed('flashNodePath')
-            # Now toggle node path's visibility state
-            if nodePath.isHidden():
-                nodePath.show()
-            else:
-                nodePath.hide()
-
-    def showAll(self):
-        """ Show the level and its descendants """
-	self.showAllDescendants()
-	render.hideCollisionSolids()
-            
     def showGrid(self,flag):
     def showGrid(self,flag):
         """ toggle direct grid """
         """ toggle direct grid """
         if flag:
         if flag:
@@ -1364,7 +1285,7 @@ class LevelEditor(NodePath, PandaObject):
         Load up the various neighborhood maps
         Load up the various neighborhood maps
         """
         """
         # For neighborhood maps
         # For neighborhood maps
-        self.levelMap = hidden.attachNewNode(NamedNode('level-map'))
+        self.levelMap = hidden.attachNewNode('level-map')
         self.activeMap = None
         self.activeMap = None
         self.mapDictionary = {}
         self.mapDictionary = {}
         for neighborhood in NEIGHBORHOODS:
         for neighborhood in NEIGHBORHOODS:
@@ -1385,9 +1306,9 @@ class LevelEditor(NodePath, PandaObject):
         self.activeMap = self.mapDictionary[neighborhood]
         self.activeMap = self.mapDictionary[neighborhood]
         self.activeMap.reparentTo(self.levelMap)
         self.activeMap.reparentTo(self.levelMap)
 
 
-    def toggleMapViz(self, flag):
+    def toggleMapVis(self, flag):
         if flag:
         if flag:
-            self.levelMap.reparentTo(render)
+            self.levelMap.reparentTo(direct.group)
         else:
         else:
             self.levelMap.reparentTo(hidden)
             self.levelMap.reparentTo(hidden)
 
 
@@ -1484,11 +1405,11 @@ class LevelEditor(NodePath, PandaObject):
 	# Also move the camera
 	# Also move the camera
 	taskMgr.removeTasksNamed('autoMoveDelay')
 	taskMgr.removeTasksNamed('autoMoveDelay')
 	handlesToCam = direct.widget.getPos(direct.camera)
 	handlesToCam = direct.widget.getPos(direct.camera)
-	handlesToCam = handlesToCam * ( direct.chan.near/handlesToCam[1])
-	if ((abs(handlesToCam[0]) > (direct.chan.nearWidth * 0.4)) |
-            (abs(handlesToCam[2]) > (direct.chan.nearHeight * 0.4))):
+	handlesToCam = handlesToCam * ( direct.dr.near/handlesToCam[1])
+	if ((abs(handlesToCam[0]) > (direct.dr.nearWidth * 0.4)) |
+            (abs(handlesToCam[2]) > (direct.dr.nearHeight * 0.4))):
             taskMgr.removeTasksNamed('manipulateCamera')
             taskMgr.removeTasksNamed('manipulateCamera')
-            direct.cameraControl.centerCamIn(direct.chan, 0.5)
+            direct.cameraControl.centerCamIn(direct.dr, 0.5)
 
 
     def autoPositionCleanup(self,state):
     def autoPositionCleanup(self,state):
         direct.grid.setPosHpr(state.selectedNode, state.deltaPos,
         direct.grid.setPosHpr(state.selectedNode, state.deltaPos,
@@ -1516,17 +1437,18 @@ class LevelEditor(NodePath, PandaObject):
         if not selectedNode:
         if not selectedNode:
             return 0
             return 0
         # Find mouse point on near plane
         # Find mouse point on near plane
-        chan = direct.chan
-    	mouseX = chan.mouseX
-  	mouseY = chan.mouseY
-   	nearX = math.tan(deg2Rad(chan.fovH)/2.0) * mouseX * chan.near
-   	nearZ = math.tan(deg2Rad(chan.fovV)/2.0) * mouseY * chan.near
+    	mouseX = direct.dr.mouseX
+  	mouseY = direct.dr.mouseY
+   	nearX = (math.tan(deg2Rad(direct.dr.fovH)/2.0) *
+                 mouseX * direct.dr.near)
+   	nearZ = (math.tan(deg2Rad(direct.dr.fovV)/2.0) *
+                 mouseY * direct.dr.near)
         # Initialize points
         # Initialize points
-   	mCam2Wall = chan.camera.getMat(selectedNode)
+   	mCam2Wall = direct.camera.getMat(selectedNode)
 	mouseOrigin = Point3(0)
 	mouseOrigin = Point3(0)
    	mouseOrigin.assign(mCam2Wall.getRow3(3))
    	mouseOrigin.assign(mCam2Wall.getRow3(3))
 	mouseDir = Vec3(0)
 	mouseDir = Vec3(0)
-   	mouseDir.set(nearX, chan.near, nearZ)
+   	mouseDir.set(nearX, direct.dr.near, nearZ)
    	mouseDir.assign(mCam2Wall.xformVec(mouseDir))
    	mouseDir.assign(mCam2Wall.xformVec(mouseDir))
         # Calc intersection point
         # Calc intersection point
         return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, NEG_Y_AXIS)
         return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, NEG_Y_AXIS)
@@ -1537,17 +1459,18 @@ class LevelEditor(NodePath, PandaObject):
         through mouse. Return false, if nothing selected
         through mouse. Return false, if nothing selected
         """
         """
         # Find mouse point on near plane
         # Find mouse point on near plane
-        chan = direct.chan
-    	mouseX = chan.mouseX
-  	mouseY = chan.mouseY
-   	nearX = math.tan(deg2Rad(chan.fovH)/2.0) * mouseX * chan.near
-   	nearZ = math.tan(deg2Rad(chan.fovV)/2.0) * mouseY * chan.near
+    	mouseX = direct.dr.mouseX
+  	mouseY = direct.dr.mouseY
+   	nearX = (math.tan(deg2Rad(direct.dr.fovH)/2.0) *
+                 mouseX * direct.dr.near)
+   	nearZ = (math.tan(deg2Rad(direct.dr.fovV)/2.0) *
+                 mouseY * direct.dr.near)
         # Initialize points
         # Initialize points
-   	mCam2Grid = chan.camera.getMat(direct.grid)
+   	mCam2Grid = direct.camera.getMat(direct.grid)
 	mouseOrigin = Point3(0)
 	mouseOrigin = Point3(0)
    	mouseOrigin.assign(mCam2Grid.getRow3(3))
    	mouseOrigin.assign(mCam2Grid.getRow3(3))
 	mouseDir = Vec3(0)
 	mouseDir = Vec3(0)
-   	mouseDir.set(nearX, chan.near, nearZ)
+   	mouseDir.set(nearX, direct.dr.near, nearZ)
    	mouseDir.assign(mCam2Grid.xformVec(mouseDir))
    	mouseDir.assign(mCam2Grid.xformVec(mouseDir))
         # Calc intersection point
         # Calc intersection point
         return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, Z_AXIS)
         return planeIntersect(mouseOrigin, mouseDir, ZERO_POINT, Z_AXIS)
@@ -1929,15 +1852,14 @@ class LevelStyleManager:
         Create a wall style pie menu
         Create a wall style pie menu
         """
         """
 	numItems = len(dictionary)
 	numItems = len(dictionary)
-	newStyleMenu = hidden.attachNewNode(
-            NamedNode(neighborhood + '_style_menu'))
+	newStyleMenu = hidden.attachNewNode(neighborhood + '_style_menu')
 	radius = 0.7
 	radius = 0.7
 	angle = deg2Rad(360.0/numItems)
 	angle = deg2Rad(360.0/numItems)
         keys = dictionary.keys()
         keys = dictionary.keys()
         keys.sort()
         keys.sort()
         styles = map(lambda x, d = dictionary: d[x], keys)
         styles = map(lambda x, d = dictionary: d[x], keys)
         sf = 0.03
         sf = 0.03
-        aspectRatio = (direct.chan.width/float(direct.chan.height))
+        aspectRatio = (direct.dr.width/float(direct.dr.height))
         for i in range(numItems):
         for i in range(numItems):
             # Get the node
             # Get the node
             node = self.createWallStyleSample(styles[i])
             node = self.createWallStyleSample(styles[i])
@@ -2078,15 +2000,14 @@ class LevelStyleManager:
         Create a wall style pie menu
         Create a wall style pie menu
         """
         """
 	numItems = len(dictionary)
 	numItems = len(dictionary)
-	newStyleMenu = hidden.attachNewNode(
-            NamedNode(neighborhood + '_style_menu'))
+	newStyleMenu = hidden.attachNewNode(neighborhood + '_style_menu')
 	radius = 0.7
 	radius = 0.7
 	angle = deg2Rad(360.0/numItems)
 	angle = deg2Rad(360.0/numItems)
         keys = dictionary.keys()
         keys = dictionary.keys()
         keys.sort()
         keys.sort()
         styles = map(lambda x, d = dictionary: d[x], keys)
         styles = map(lambda x, d = dictionary: d[x], keys)
         sf = 0.02
         sf = 0.02
-        aspectRatio = (direct.chan.width/float(direct.chan.height))
+        aspectRatio = (direct.dr.width/float(direct.dr.height))
         for i in range(numItems):
         for i in range(numItems):
             # Get the node
             # Get the node
             node = self.createBuildingStyleSample(styles[i])
             node = self.createBuildingStyleSample(styles[i])
@@ -2285,10 +2206,10 @@ class LevelStyleManager:
         # Create color chips for each color
         # Create color chips for each color
 	numItems = len(colorList)
 	numItems = len(colorList)
 	# Attach it to hidden for now
 	# Attach it to hidden for now
-	newColorMenu = hidden.attachNewNode(NamedNode(menuName + 'Menu'))
+	newColorMenu = hidden.attachNewNode(menuName + 'Menu')
         # Compute the angle per item
         # Compute the angle per item
 	angle = deg2Rad(360.0/float(numItems))
 	angle = deg2Rad(360.0/float(numItems))
-        aspectRatio = (direct.chan.width / float(direct.chan.height))
+        aspectRatio = (direct.dr.width / float(direct.dr.height))
 	# Attach the color chips to the new menu and adjust sizes
 	# Attach the color chips to the new menu and adjust sizes
         for i in range (numItems):
         for i in range (numItems):
             # Create the node and set its color
             # Create the node and set its color
@@ -2353,10 +2274,10 @@ class LevelStyleManager:
 	# Get the currently available window options	
 	# Get the currently available window options	
 	numItems = len(dnaList)
 	numItems = len(dnaList)
         # Create a top level node to hold the menu
         # Create a top level node to hold the menu
-	newMenu = hidden.attachNewNode(NamedNode(dnaType + 'Menu'))
+	newMenu = hidden.attachNewNode(dnaType + 'Menu')
         # Compute angle increment per item
         # Compute angle increment per item
 	angle = deg2Rad(360.0/numItems)
 	angle = deg2Rad(360.0/numItems)
-        aspectRatio = direct.chan.width /float(direct.chan.height)
+        aspectRatio = direct.dr.width /float(direct.dr.height)
         # Add items
         # Add items
         for i in range(0, numItems):
         for i in range(0, numItems):
             if dnaList[i]:
             if dnaList[i]:
@@ -2385,10 +2306,10 @@ class LevelStyleManager:
     def createTextPieMenu(self, dnaType, textList, radius = 0.7, sf = 1.0):
     def createTextPieMenu(self, dnaType, textList, radius = 0.7, sf = 1.0):
 	numItems = len(textList)
 	numItems = len(textList)
         # Create top level node for new menu
         # Create top level node for new menu
-	newMenu = hidden.attachNewNode(NamedNode(dnaType + 'Menu'))
+	newMenu = hidden.attachNewNode(dnaType + 'Menu')
         # Compute angle per item
         # Compute angle per item
 	angle = deg2Rad(360.0/numItems)
 	angle = deg2Rad(360.0/numItems)
-        aspectRatio = direct.chan.width/float(direct.chan.height)
+        aspectRatio = direct.dr.width/float(direct.dr.height)
         # Add items
         # Add items
         for i in range (numItems):
         for i in range (numItems):
             # Create onscreen text node for each item
             # Create onscreen text node for each item
@@ -2893,13 +2814,13 @@ class LevelEditorPanel(Pmw.MegaToplevel):
         self.colorEntry.pack(fill = 'x')
         self.colorEntry.pack(fill = 'x')
 
 
         buttonFrame = Frame(hull)
         buttonFrame = Frame(hull)
-        self.fMapViz = IntVar()
-        self.fMapViz.set(0)
+        self.fMapVis = IntVar()
+        self.fMapVis.set(0)
         self.mapSnapButton = Checkbutton(buttonFrame,
         self.mapSnapButton = Checkbutton(buttonFrame,
-                                      text = 'Map Viz',
+                                      text = 'Map Vis',
                                       width = 6,
                                       width = 6,
-                                      variable = self.fMapViz,
-                                      command = self.toggleMapViz)
+                                      variable = self.fMapVis,
+                                      command = self.toggleMapVis)
         self.mapSnapButton.pack(side = 'left', expand = 1, fill = 'x')
         self.mapSnapButton.pack(side = 'left', expand = 1, fill = 'x')
 
 
         self.fXyzSnap = IntVar()
         self.fXyzSnap = IntVar()
@@ -2970,13 +2891,13 @@ class LevelEditorPanel(Pmw.MegaToplevel):
         self.isolateButton = Button(
         self.isolateButton = Button(
             buttonFrame3,
             buttonFrame3,
             text = 'Isolate Selected',
             text = 'Isolate Selected',
-            command = self.levelEditor.isolate)
+            command = direct.isolate)
         self.isolateButton.pack(side = 'left', expand = 1, fill = 'x')
         self.isolateButton.pack(side = 'left', expand = 1, fill = 'x')
 
 
         self.showAllButton = Button(
         self.showAllButton = Button(
             buttonFrame3,
             buttonFrame3,
             text = 'Show All',
             text = 'Show All',
-            command = self.levelEditor.showAll)
+            command = self.showAll)
         self.showAllButton.pack(side = 'left', expand = 1, fill = 'x')
         self.showAllButton.pack(side = 'left', expand = 1, fill = 'x')
 
 
         buttonFrame3.pack(fill = 'x')
         buttonFrame3.pack(fill = 'x')
@@ -3003,8 +2924,7 @@ class LevelEditorPanel(Pmw.MegaToplevel):
         self.sceneGraphExplorer = SceneGraphExplorer(
         self.sceneGraphExplorer = SceneGraphExplorer(
             parent = sceneGraphPage,
             parent = sceneGraphPage,
             root = self.levelEditor,
             root = self.levelEditor,
-            menuItems = ['Select', 'Isolate', 'Flash', 'Toggle Viz',
-                         'Set Parent', 'Reparent', 'Add Group',
+            menuItems = ['Set Parent', 'Reparent', 'Add Group',
                          'Add Vis Group', 'Set Color',
                          'Add Vis Group', 'Set Color',
                          'Set Name'])
                          'Set Name'])
         self.sceneGraphExplorer.pack(expand = 1, fill = 'both')
         self.sceneGraphExplorer.pack(expand = 1, fill = 'both')
@@ -3024,8 +2944,11 @@ class LevelEditorPanel(Pmw.MegaToplevel):
     def toggleHprSnap(self):
     def toggleHprSnap(self):
         direct.grid.setHprSnap(self.fXyzSnap.get())
         direct.grid.setHprSnap(self.fXyzSnap.get())
         
         
-    def toggleMapViz(self):
-        self.levelEditor.toggleMapViz(self.fMapViz.get())
+    def toggleMapVis(self):
+        self.levelEditor.toggleMapVis(self.fMapVis.get())
+
+    def showAll(self):
+        direct.showAll(self.levelEditor.NPToplevel)
 
 
     def createNewVisGroup(self):
     def createNewVisGroup(self):
         self.levelEditor.createNewGroup(type = 'vis')
         self.levelEditor.createNewGroup(type = 'vis')
@@ -3247,7 +3170,7 @@ class VisGroupsEditor(Pmw.MegaToplevel):
                 if groupName not in visList:
                 if groupName not in visList:
                     visList.append(groupName)
                     visList.append(groupName)
                     target.addVisible(groupName)
                     target.addVisible(groupName)
-                    # Update viz and color
+                    # Update vis and color
                     groupNP.show()
                     groupNP.show()
                     groupNP.setColor(1,0,0,1)
                     groupNP.setColor(1,0,0,1)
             else:
             else:
@@ -3255,7 +3178,7 @@ class VisGroupsEditor(Pmw.MegaToplevel):
                 if groupName in visList:
                 if groupName in visList:
                     visList.remove(groupName)
                     visList.remove(groupName)
                     target.removeVisible(groupName)
                     target.removeVisible(groupName)
-                    # Update viz and color
+                    # Update vis and color
                     if self.showMode.get() == 1:
                     if self.showMode.get() == 1:
                         groupNP.hide()
                         groupNP.hide()
                     groupNP.clearColor()
                     groupNP.clearColor()

+ 6 - 6
direct/src/leveleditor/PieMenu.py

@@ -6,7 +6,7 @@ class PieMenu(NodePath, PandaObject):
                  action = None, fUpdateOnlyOnChange = 1):
                  action = None, fUpdateOnlyOnChange = 1):
         NodePath.__init__(self)
         NodePath.__init__(self)
         # Create a toplevel node for aspect ratio scaling
         # Create a toplevel node for aspect ratio scaling
-        self.assign(hidden.attachNewNode(NamedNode('PieMenu')))
+        self.assign(hidden.attachNewNode('PieMenu'))
         # Attach the menu
         # Attach the menu
         self.visibleMenu = visibleMenu
         self.visibleMenu = visibleMenu
         # Try to flatten the visibleMenu (note, flattenStrong is too strong
         # Try to flatten the visibleMenu (note, flattenStrong is too strong
@@ -44,15 +44,15 @@ class PieMenu(NodePath, PandaObject):
 	taskMgr.removeTasksNamed('pieMenuTask')
 	taskMgr.removeTasksNamed('pieMenuTask')
 
 
 	# Where did the user press the button?
 	# Where did the user press the button?
-	self.originX = direct.chan.mouseX
-	self.originY = direct.chan.mouseY
+	self.originX = direct.dr.mouseX
+	self.originY = direct.dr.mouseY
 
 
 	# Pop up menu
 	# Pop up menu
 	self.reparentTo(render2d)
 	self.reparentTo(render2d)
 	self.setPos(self.originX,0.0,self.originY)
 	self.setPos(self.originX,0.0,self.originY)
         # Compensate for window aspect ratio
         # Compensate for window aspect ratio
         self.setScale(1.0, 1.0,1.0)
         self.setScale(1.0, 1.0,1.0)
-        #direct.chan.width/float(direct.chan.height))
+        #direct.dr.width/float(direct.dr.height))
 	# Start drawing the selection line
 	# Start drawing the selection line
 	self.lines.reset()
 	self.lines.reset()
 	self.lines.moveTo(0,0,0)
 	self.lines.moveTo(0,0,0)
@@ -65,8 +65,8 @@ class PieMenu(NodePath, PandaObject):
         taskMgr.spawnTaskNamed(t, 'pieMenuTask')
         taskMgr.spawnTaskNamed(t, 'pieMenuTask')
 
 
     def pieMenuTask(self,state):
     def pieMenuTask(self,state):
-        mouseX = direct.chan.mouseX
-        mouseY = direct.chan.mouseY
+        mouseX = direct.dr.mouseX
+        mouseY = direct.dr.mouseY
         deltaX = mouseX - self.originX
         deltaX = mouseX - self.originX
         deltaY = mouseY - self.originY
         deltaY = mouseY - self.originY
 
 

+ 55 - 84
direct/src/tkpanels/Placer.py

@@ -16,13 +16,13 @@ Task to monitor pose
 ZERO_VEC = Vec3(0)
 ZERO_VEC = Vec3(0)
 UNIT_VEC = Vec3(1)
 UNIT_VEC = Vec3(1)
 
 
-class Placer(Pmw.MegaToplevel):
+class Placer(Pmw.MegaToplevel, PandaObject):
     def __init__(self, parent = None, **kw):
     def __init__(self, parent = None, **kw):
 
 
         INITOPT = Pmw.INITOPT
         INITOPT = Pmw.INITOPT
         optiondefs = (
         optiondefs = (
             ('title',       'Placer Panel',     None),
             ('title',       'Placer Panel',     None),
-            ('nodePath',    camera,               None),
+            ('nodePath',    direct.camera,      None),
             )
             )
         self.defineoptions(kw, optiondefs)
         self.defineoptions(kw, optiondefs)
 
 
@@ -30,22 +30,23 @@ class Placer(Pmw.MegaToplevel):
         Pmw.MegaToplevel.__init__(self, parent)
         Pmw.MegaToplevel.__init__(self, parent)
 
 
         # Initialize state
         # Initialize state
-        self.tempCS = render.attachNewNode(NamedNode('placerTempCS'))
-        self.orbitFromCS = render.attachNewNode(NamedNode('placerOrbitFromCS'))
-        self.orbitToCS = render.attachNewNode(NamedNode('placerOrbitToCS'))
+        self.tempCS = direct.group.attachNewNode('placerTempCS')
+        self.orbitFromCS = direct.group.attachNewNode(
+            'placerOrbitFromCS')
+        self.orbitToCS = direct.group.attachNewNode('placerOrbitToCS')
         self.refCS = self.tempCS
         self.refCS = self.tempCS
         self.undoList = []
         self.undoList = []
         self.redoList = []
         self.redoList = []
         
         
         # Dictionary keeping track of all node paths manipulated so far
         # Dictionary keeping track of all node paths manipulated so far
         self.nodePathDict = {}
         self.nodePathDict = {}
-        self.nodePathDict['camera'] = camera
+        self.nodePathDict['camera'] = direct.camera
         self.nodePathNames = ['selected', 'camera']
         self.nodePathNames = ['selected', 'camera']
 
 
         self.refNodePathDict = {}
         self.refNodePathDict = {}
         self.refNodePathDict['parent'] = self['nodePath'].getParent()
         self.refNodePathDict['parent'] = self['nodePath'].getParent()
         self.refNodePathDict['render'] = render
         self.refNodePathDict['render'] = render
-        self.refNodePathDict['camera'] = camera
+        self.refNodePathDict['camera'] = direct.camera
         self.refNodePathNames = ['self', 'parent', 'render',
         self.refNodePathNames = ['self', 'parent', 'render',
                                  'camera', 'selected', 'coa']
                                  'camera', 'selected', 'coa']
 
 
@@ -57,6 +58,16 @@ class Placer(Pmw.MegaToplevel):
         # Offset for orbital mode
         # Offset for orbital mode
         self.posOffset = Vec3(0)
         self.posOffset = Vec3(0)
 
 
+        # Set up event hooks
+        self.undoEvents = [('undo', self.undoHook),
+                           ('pushUndo', self.pushUndoHook),
+                           ('undoListEmpty', self.undoListEmptyHook),
+                           ('redo', self.redoHook),
+                           ('pushRedo', self.pushRedoHook),
+                           ('redoListEmpty', self.redoListEmptyHook)]
+        for event, method in self.undoEvents:
+            self.accept(event, method)
+
         # Init movement mode
         # Init movement mode
         self.movementMode = 'Relative To:'
         self.movementMode = 'Relative To:'
 
 
@@ -89,7 +100,7 @@ class Placer(Pmw.MegaToplevel):
         menuBar.addmenuitem('Placer', 'command',
         menuBar.addmenuitem('Placer', 'command',
                             'Exit Placer Panel',
                             'Exit Placer Panel',
                             label = 'Exit',
                             label = 'Exit',
-                            command = self.preDestroy)
+                            command = self.destroy)
 
 
         menuBar.addmenu('Help', 'Placer Panel Help Operations')
         menuBar.addmenu('Help', 'Placer Panel Help Operations')
         self.toggleBalloonVar = IntVar()
         self.toggleBalloonVar = IntVar()
@@ -131,11 +142,11 @@ class Placer(Pmw.MegaToplevel):
 
 
         self.undoButton = Button(menuFrame, text = 'Undo',
         self.undoButton = Button(menuFrame, text = 'Undo',
                                  state = 'disabled',
                                  state = 'disabled',
-                                 command = self.undo)
+                                 command = direct.undo)
         self.undoButton.pack(side = 'left', expand = 0)
         self.undoButton.pack(side = 'left', expand = 0)
         self.redoButton = Button(menuFrame, text = 'Redo',
         self.redoButton = Button(menuFrame, text = 'Redo',
                                  state = 'disabled',
                                  state = 'disabled',
-                                 command = self.redo)
+                                 command = direct.redo)
         self.redoButton.pack(side = 'left', expand = 0)
         self.redoButton.pack(side = 'left', expand = 0)
 
 
         # The master frame for the dials
         # The master frame for the dials
@@ -341,6 +352,9 @@ class Placer(Pmw.MegaToplevel):
         # Set up placer for inital node path
         # Set up placer for inital node path
         self.selectNodePathNamed('init')
         self.selectNodePathNamed('init')
 
 
+        # Clean up things when you destroy the panel
+        self.bind('<Destroy>', self.onDestroy)
+
         # Make sure input variables processed 
         # Make sure input variables processed 
         self.initialiseoptions(Placer)
         self.initialiseoptions(Placer)
 
 
@@ -654,82 +668,35 @@ class Placer(Pmw.MegaToplevel):
         self.updatePlacer()
         self.updatePlacer()
 
 
     def pushUndo(self, fResetRedo = 1):
     def pushUndo(self, fResetRedo = 1):
-        nodePath = self['nodePath']
-        if nodePath != None:
-            name = self.nodePathMenuEntry.get()
-            pos = nodePath.getPos()
-            hpr = nodePath.getHpr()
-            scale = nodePath.getScale()
-            self.undoList.append([name, pos,hpr,scale])
-            # Make sure button is reactivated
-            self.undoButton.configure(state = 'normal')
-            if fResetRedo:
-                self.redoList = []
-                self.redoButton.configure(state = 'disabled')
-
-    def popUndo(self):
-        # Get last item
-        pose = self.undoList[-1]
-        # Strip last item off of undo list
-        self.undoList = self.undoList[:-1]
-        # Update state of undo button
-        if not self.undoList:
-            self.undoButton.configure(state = 'disabled')
-        # Return last item
-        return pose
-        
+        direct.pushUndo([self['nodePath']])
+
+    def undoHook(self):
+        # 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):
     def pushRedo(self):
-        nodePath = self['nodePath']
-        if nodePath != None:
-            name = self.nodePathMenuEntry.get()
-            pos = nodePath.getPos()
-            hpr = nodePath.getHpr()
-            scale = nodePath.getScale()
-            self.redoList.append([name, pos,hpr,scale])
-            # Make sure button is reactivated
-            self.redoButton.configure(state = 'normal')
-
-    def popRedo(self):
-        # Get last item
-        pose = self.redoList[-1]
-        # Strip last item off of redo list
-        self.redoList = self.redoList[:-1]
-        # Update state of redo button
-        if not self.redoList:
-            self.redoButton.configure(state = 'disabled')
-        # Return last item
-        return pose
+        direct.pushRedo([self['nodePath']])
         
         
-    def undo(self):
-        if self.undoList:
-            # Get last item off of redo list
-            pose = self.popUndo()
-            # Make that node path active
-            self.selectNodePathNamed(pose[0])
-            # Make sure combo box is showing the right thing
-            self.nodePathMenu.selectitem(pose[0])
-            # Record active's current state on redo stack
-            self.pushRedo()
-            # Undo xform
-            self['nodePath'].setPosHprScale(pose[1], pose[2], pose[3])
-            # Reflect new changes
-            self.updatePlacer()
+    def redoHook(self):
+        # Reflect new changes
+        self.updatePlacer()
 
 
-    def redo(self):
-        if self.redoList:
-            # Get last item off of redo list
-            pose = self.popRedo()
-            # Make that node path active
-            self.selectNodePathNamed(pose[0])
-            # Make sure combo box is showing the right thing
-            self.nodePathMenu.selectitem(pose[0])
-            # Record active's current state on redo stack
-            self.pushUndo(fResetRedo = 0)
-            # Redo xform
-            self['nodePath'].setPosHprScale(pose[1], pose[2], pose[3])
-            # 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):
     def printNodePathInfo(self):
         np = self['nodePath']
         np = self['nodePath']
         if np:
         if np:
@@ -747,11 +714,15 @@ class Placer(Pmw.MegaToplevel):
             print ('%s.setPosHprScale(%s, %s, %s)' %
             print ('%s.setPosHprScale(%s, %s, %s)' %
                    (name, posString, hprString, scaleString))
                    (name, posString, hprString, scaleString))
 
 
-    def preDestroy(self):
+    def onDestroy(self, event):
+        # Remove hooks
+        for event, method in self.undoEvents:
+            self.ignore(event)
         self.tempCS.removeNode()
         self.tempCS.removeNode()
         self.orbitFromCS.removeNode()
         self.orbitFromCS.removeNode()
         self.orbitToCS.removeNode()
         self.orbitToCS.removeNode()
-        self.destroy()
+        # Only do this once
+        self.unbind('<Destroy>')
         
         
     def toggleBalloon(self):
     def toggleBalloon(self):
         if self.toggleBalloonVar.get():
         if self.toggleBalloonVar.get():

+ 6 - 3
direct/src/tkwidgets/SceneGraphExplorer.py

@@ -3,12 +3,15 @@ from Tkinter import *
 from Tree import *
 from Tree import *
 import Pmw
 import Pmw
 
 
+DEFAULT_MENU_ITEMS = ['Select', 'Deselect', 'Flash', 'Toggle Vis',
+                      'Isolate', 'Show All', 'Delete']
+
 class SceneGraphExplorer(Pmw.MegaWidget):
 class SceneGraphExplorer(Pmw.MegaWidget):
     "Graphical display of a scene graph"
     "Graphical display of a scene graph"
     def __init__(self, root = render, parent = None, **kw):
     def __init__(self, root = render, parent = None, **kw):
         # Define the megawidget options.
         # Define the megawidget options.
         optiondefs = (
         optiondefs = (
-            ('menuItems',   ['Select'],   None),
+            ('menuItems',   [],   Pmw.INITOPT),
             )
             )
         self.defineoptions(kw, optiondefs)
         self.defineoptions(kw, optiondefs)
  
  
@@ -46,7 +49,7 @@ class SceneGraphExplorer(Pmw.MegaWidget):
         self._treeItem = SceneGraphExplorerItem(self.root)
         self._treeItem = SceneGraphExplorerItem(self.root)
 
 
         self._node = TreeNode(self._canvas, None, self._treeItem,
         self._node = TreeNode(self._canvas, None, self._treeItem,
-                              self['menuItems'])
+                              DEFAULT_MENU_ITEMS + self['menuItems'])
         self._node.expand()
         self._node.expand()
 
 
         # Check keywords and initialise options based on input values.
         # Check keywords and initialise options based on input values.
@@ -121,7 +124,7 @@ class SceneGraphExplorerItem(TreeItem):
         messenger.send('SGENodePath_' + command, [self.nodePath])
         messenger.send('SGENodePath_' + command, [self.nodePath])
 
 
 
 
-def explore(nodePath):
+def explore(nodePath = render):
     tl = Toplevel()
     tl = Toplevel()
     tl.title('Explore: ' + nodePath.getName())
     tl.title('Explore: ' + nodePath.getName())
     sge = SceneGraphExplorer(parent = tl, root = nodePath)
     sge = SceneGraphExplorer(parent = tl, root = nodePath)