浏览代码

*** empty log message ***

Mark Mine 25 年之前
父节点
当前提交
74ce0bff3a

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

@@ -8,27 +8,53 @@ Y_AXIS = Vec3(0,1,0)
 class DirectCameraControl(PandaObject):
     def __init__(self):
         # Create the grid
-        self.chan = direct.chan
-        self.camera = self.chan.camera
 	self.orthoViewRoll = 0.0
 	self.lastView = 0
         self.coa = Point3(0,100,0)
         self.coaDist = 100
         self.coaMarker = loader.loadModel('models/misc/sphere')
+        self.coaMarker.setName('DirectCameraCOAMarker')
         self.coaMarker.setColor(1,0,0)
+        self.coaMarker.setPos(0,0,0)
         useDirectRenderStyle(self.coaMarker)
         self.coaMarkerPos = Point3(0)
-	self.relNodePath = render.attachNewNode(NamedNode('targetNode'))
+	self.camManipRef = direct.group.attachNewNode('camManipRef')
         self.zeroBaseVec = VBase3(0)
         self.zeroVector = Vec3(0)
         self.centerVec = Vec3(0, 1, 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
-	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)):
             # MOUSE IS IN CENTRAL REGION
             # Hide the marker for this kind of motion
@@ -42,7 +68,7 @@ class DirectCameraControl(PandaObject):
                 # current mouse position
                 # And then spawn task to determine mouse mode
                 numEntries = direct.iRay.pickGeom(
-                    render,chan.mouseX,chan.mouseY)
+                    render,direct.dr.mouseX,direct.dr.mouseY)
                 # Filter out hidden nodes from entry list
                 indexList = []
                 for i in range(0,numEntries):
@@ -73,16 +99,22 @@ class DirectCameraControl(PandaObject):
                                 coa.set(hitPt[0],hitPt[1],hitPt[2])
                                 minPt = i
                     # 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
-                        coa.assign(self.coaMarker.getPos(camera))
+                        coa.assign(self.coaMarker.getPos(direct.camera))
                         coaDist = Vec3(coa - self.zeroPoint).length()
+                        if coaDist < (1.1 * direct.dr.near):
+                            coa.set(0,100,0)
+                            coaDist = 100
                 else:
                     # If no intersection point:
                     # Use existing point
-                    coa.assign(self.coaMarker.getPos(camera))
+                    coa.assign(self.coaMarker.getPos(direct.camera))
                     coaDist = Vec3(coa - self.zeroPoint).length()
+                    if coaDist < (1.1 * direct.dr.near):
+                        coa.set(0,100,0)
+                        coaDist = 100
                 # Update coa and marker
                 self.updateCoa(coa, coaDist)
                 # Now spawn task to determine mouse fly mode
@@ -106,8 +138,8 @@ class DirectCameraControl(PandaObject):
         taskMgr.spawnTaskNamed(t, 'determineMouseFlyMode')
 
     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)):
             return Task.cont
         else:
@@ -124,7 +156,7 @@ class DirectCameraControl(PandaObject):
         else:
             self.coaDist = Vec3(self.coa - self.zeroPoint).length()
         # Place the marker in render space
-        self.coaMarker.setPos(self.camera,self.coa)
+        self.coaMarker.setPos(direct.camera,self.coa)
         # Resize it
         self.updateCoaMarkerSize(coaDist)
         # Record marker pos in render space
@@ -132,60 +164,69 @@ class DirectCameraControl(PandaObject):
 
     def updateCoaMarkerSize(self, coaDist = None):
         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 *
-                                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')
-        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')
-        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()
 	scaledCenterVec = self.centerVec * dist
 	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,
-                            other = self.relNodePath,
+                            other = self.camManipRef,
                             blendType = 'easeInOut',
                             task = 'manipulateCamera')
 
-    def zoomCam(self, chan, zoomFactor, t):
+    def zoomCam(self, zoomFactor, t):
 	taskMgr.removeTasksNamed('manipulateCamera')
+        # Record undo point
+        direct.pushUndo([direct.camera])
 	# Find a point zoom factor times the current separation
         # of the widget and cam
-        zoomPtToCam = self.coaMarker.getPos(chan.camera) * zoomFactor
+        zoomPtToCam = self.coaMarker.getPos(direct.camera) * zoomFactor
 	# Put a target nodePath there
-	self.relNodePath.setPos(chan.camera, zoomPtToCam)
+	self.camManipRef.setPos(direct.camera, zoomPtToCam)
 	# Move to that point
-	chan.camera.lerpPos(self.zeroPoint,
+	direct.camera.lerpPos(self.zeroPoint,
                             CAM_MOVE_DURATION,
-                            other = self.relNodePath,
+                            other = self.camManipRef,
                             blendType = 'easeInOut',
                             task = 'manipulateCamera')
         
-    def SpawnMoveToView(self, chan, view):
+    def SpawnMoveToView(self, view):
         # Kill any existing tasks
         taskMgr.removeTasksNamed('manipulateCamera')
+        # Record undo point
+        direct.pushUndo([direct.camera])
         # Calc hprOffset
         hprOffset = VBase3()
         if view == 8:
@@ -211,39 +252,42 @@ class DirectCameraControl(PandaObject):
         elif view == 7:
             hprOffset.set(135., -35.264, 0.)
         # Position target
-        self.relNodePath.setPosHpr(self.coaMarker, self.zeroBaseVec,
+        self.camManipRef.setPosHpr(self.coaMarker, self.zeroBaseVec,
                                    hprOffset)
         # 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()
         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,
                                    self.zeroBaseVec)
         # Record view for next time around
         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
 	taskMgr.removeTasksNamed('manipulateCamera')
 	
+        # Record undo point
+        direct.pushUndo([direct.camera])
+        
 	# Coincident with widget
-        self.relNodePath.setPos(self.coaMarker, self.zeroPoint)
+        self.camManipRef.setPos(self.coaMarker, self.zeroPoint)
 	# 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,
                                              blendType = 'easeInOut',
                                              task = 'manipulateCamera')
@@ -252,7 +296,7 @@ class DirectCameraControl(PandaObject):
         manipTask.uponDeath = self.reparentCam
 
     def reparentCam(self, state):
-        self.camera.wrtReparentTo(state.parent)
+        direct.camera.wrtReparentTo(state.parent)
 
     def spawnHPanYZoom(self):
         # Kill any existing tasks
@@ -268,14 +312,14 @@ class DirectCameraControl(PandaObject):
     def HPanYZoomTask(self,state):
         targetVector = state.targetVector
         # 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
 
 
@@ -285,25 +329,25 @@ class DirectCameraControl(PandaObject):
         # Hide the marker
         self.coaMarker.hide()
         t = Task.Task(self.XZTranslateOrHPPanTask)
-        t.scaleFactor = (self.coaDist / self.chan.near)
+        t.scaleFactor = (self.coaDist / direct.dr.near)
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
     def XZTranslateOrHPPanTask(self, state):
         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)
         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),
                                0.0,
-                               (-0.5 * self.chan.mouseDeltaY *
-                                self.chan.nearHeight *
+                               (-0.5 * direct.dr.mouseDeltaY *
+                                direct.dr.nearHeight *
                                 state.scaleFactor))
         return Task.cont
 
@@ -313,37 +357,37 @@ class DirectCameraControl(PandaObject):
         # Hide the marker
         self.coaMarker.hide()
         t = Task.Task(self.XZTranslateTask)
-        t.scaleFactor = (self.coaDist / self.chan.near)
+        t.scaleFactor = (self.coaDist / direct.dr.near)
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
     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
 
     def spawnMouseRotateTask(self):
         # Kill any existing tasks
 	taskMgr.removeTasksNamed('manipulateCamera')
         # 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.wrtMat = self.camera.getMat( self.relNodePath )
+	t.wrtMat = direct.camera.getMat( self.camManipRef )
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
     def mouseRotateTask(self, state):
         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)
-        self.camera.setMat(self.relNodePath, wrtMat)
+        direct.camera.setMat(self.camManipRef, wrtMat)
         return Task.cont
 
     def spawnHPPan(self):
@@ -355,12 +399,12 @@ class DirectCameraControl(PandaObject):
         taskMgr.spawnTaskNamed(t, 'manipulateCamera')
 
     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
 
     def fitOnWidget(self):
@@ -372,27 +416,27 @@ class DirectCameraControl(PandaObject):
         # How big is the node?
         nodeScale = direct.widget.scalingNode.getScale(render)
         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?
         # 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?
         centerVec = Y_AXIS * camY
     
         # 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?
         deltaMove = vWidget2Camera - centerVec
     
         # 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,
                                       blendType = 'easeInOut',
                                       task = 'manipulateCamera')
@@ -401,50 +445,20 @@ class DirectCameraControl(PandaObject):
         fitTask.uponDeath = self.reparentCam                                
 
     def enableMouseFly(self):
-	self.enableMouseInteraction()
-	self.enableHotKeys()
-        self.coaMarker.reparentTo(render)
-
-    def enableMouseInteraction(self):
 	# disable C++ fly interface
 	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):
         # Hide the marker
         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):
         taskMgr.removeTasksNamed('manipulateCamera')

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

@@ -5,7 +5,7 @@ class DirectGrid(NodePath,PandaObject):
     def __init__(self):
         # Initialize superclass
         NodePath.__init__(self)
-        self.assign(hidden.attachNewNode( NamedNode('DirectGrid')))
+        self.assign(hidden.attachNewNode('DirectGrid'))
         # Don't wireframe or light
         useDirectRenderStyle(self)
 
@@ -16,7 +16,7 @@ class DirectGrid(NodePath,PandaObject):
 	self.gridBack.setColor(0.5,0.5,0.5,0.5)
 
 	# Grid Lines
-        self.lines = self.attachNewNode(NamedNode('gridLines'))
+        self.lines = self.attachNewNode('gridLines')
 	self.minorLines = LineNodePath(self.lines)
         self.minorLines.lineNode.setName('minorLines')
 	self.minorLines.setColor(VBase4(0.3,0.55,1,1))
@@ -49,17 +49,11 @@ class DirectGrid(NodePath,PandaObject):
         self.enable()
 
     def enable(self):
-        self.reparentTo(render)
-        self.accept('selectedNodePath', self.selectGridBackParent)
+        self.reparentTo(direct.group)
         self.updateGrid()
 
     def disable(self):
         self.reparentTo(hidden)
-        self.ignore('selectedNodePath')
-
-    def selectGridBackParent(self, nodePath):
-        if nodePath.getName() == 'GridBack':
-            direct.select(self)
 
     def updateGrid(self):
 	# 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):
     def __init__(self):
         # Create the grid
-        self.chan = direct.chan
-        self.camera = self.chan.camera
         self.objectHandles = ObjectHandles()
         self.hitPt = Point3(0)
         self.prevHit = Vec3(0)
         self.rotationCenter = Point3(0)
         self.initScaleMag = 1
-        self.refNodePath = render.attachNewNode(NamedNode('refNodePath'))
+        self.manipRef = direct.group.attachNewNode('manipRef')
         self.hitPtDist = 0
         self.constraint = None
         self.rotateAxis = 'x'
@@ -27,13 +25,22 @@ class DirectManipulationControl(PandaObject):
         self.fScaling = 1
         self.unpickable = UNPICKABLE
         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
         self.mode = 'select'
         # Check for a widget hit point
         numEntries = direct.iRay.pickWidget(
-            render,chan.mouseX,chan.mouseY)
+            render,direct.dr.mouseX,direct.dr.mouseY)
         # Did we hit a widget?
         if(numEntries):
             # Yes!
@@ -63,8 +70,8 @@ class DirectManipulationControl(PandaObject):
         # Or if we move far enough
         self.moveDir = None
         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')
 
     def switchToMoveMode(self, state):
@@ -74,8 +81,8 @@ class DirectManipulationControl(PandaObject):
         return Task.done
 
     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-switch-to-move')
             self.mode = 'move'
@@ -93,7 +100,7 @@ class DirectManipulationControl(PandaObject):
         if self.mode == 'select':
             # Check for object under mouse
             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
             index = -1
             for i in range(0,numEntries):
@@ -166,28 +173,14 @@ class DirectManipulationControl(PandaObject):
         return Task.cont
 
     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):
-	# 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):
         if item not in self.unpickable:
@@ -209,6 +202,8 @@ class DirectManipulationControl(PandaObject):
             taskMgr.removeTasksNamed('highlightWidgetTask')
             # Set manipulation flag
             self.fManip = 1
+            # Record undo point
+            direct.pushUndo(direct.selected)
             # Update object handles visibility
             self.objectHandles.showGuides()
             self.objectHandles.hideAllHandles()
@@ -320,25 +315,25 @@ class DirectManipulationControl(PandaObject):
         # (in case we later switch to another manipulation mode)
         #self.fHitInit = 1
         # Where is the widget relative to current camera view
-        vWidget2Camera = direct.widget.getPos(self.camera)
+        vWidget2Camera = direct.widget.getPos(direct.camera)
         x = vWidget2Camera[0]
         y = vWidget2Camera[1]
         z = vWidget2Camera[2]
         # Move widget (and objects) based upon mouse motion
         # Scaled up accordingly based upon widget distance
-        chan = self.chan
+        dr = direct.dr
         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(
-            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):
         """Constrained 2D motion perpendicular to camera's image plane
         This moves the object in the camera's XY plane"""
         # 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 self.fHitInit:
             self.fHitInit = 0
@@ -350,18 +345,18 @@ class DirectManipulationControl(PandaObject):
         z = vWidget2Camera[2]
         # Move widget (and objects) based upon mouse motion
         # Scaled up accordingly based upon widget distance
-        chan = self.chan
+        dr = direct.dr
         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)
     
     def getCrankAngle(self):
         # Used to compute current angle of mouse (relative to the widget's
         # 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)))
 
     def widgetCheck(self,type):
@@ -372,7 +367,7 @@ class DirectManipulationControl(PandaObject):
         # widget's origin and one of the three principle axes
         axis = self.constraint[:1]
         # 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
         pos = VBase3(0)
         decomposeMatrix(mWidget2Cam, VBase3(0), VBase3(0), pos,
@@ -398,19 +393,19 @@ class DirectManipulationControl(PandaObject):
     def getWidgetsNearProjectionPoint(self):
         # Find the position of the projection of the specified node path
         # on the near plane
-        widgetOrigin = direct.widget.getPos(self.camera)
+        widgetOrigin = direct.widget.getPos(direct.camera)
         # project this onto near plane
-        return widgetOrigin * (self.chan.near / widgetOrigin[1])
+        return widgetOrigin * (direct.dr.near / widgetOrigin[1])
 
     def getScreenXY(self):
         # Where does the widget's projection fall on the near plane
         nearVec = self.getWidgetsNearProjectionPoint()
         # 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?
-        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
         screenXY = Vec3((2 * percentX) - 1.0,nearVec[1],(2 * percentY) - 1.0)
         # Return the resulting value
@@ -469,10 +464,10 @@ class DirectManipulationControl(PandaObject):
         # Default is virtual trackball (handles 1D rotations better)
         self.fHitInit = 1
         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)
 
     def scale3D(self):
@@ -481,11 +476,11 @@ class DirectManipulationControl(PandaObject):
         # From midpoint to edge doubles or halves objects scale
         if self.fHitInit:
             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.objectHandles.getWidgetIntersectPt(
-                self.refNodePath, 'y')).length()
+                self.manipRef, 'y')).length()
             # record initial scale
             self.initScale = direct.widget.getScale()
         # Begin
@@ -493,7 +488,7 @@ class DirectManipulationControl(PandaObject):
         currScale = (
             self.initScale *
             (self.objectHandles.getWidgetIntersectPt(
-            self.refNodePath, 'y').length() /
+            self.manipRef, 'y').length() /
              self.initScaleMag)
             )
         direct.widget.setScale(currScale)
@@ -732,8 +727,8 @@ class ObjectHandles(NodePath,PandaObject):
         taskMgr.removeTasksNamed('resizeObjectHandles')
         # Increase handles scale until they cover 30% of the min dimension
         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.scalingNode.lerpScale(sf,sf,sf, 0.5,
                                    blendType = 'easeInOut',
@@ -741,7 +736,7 @@ class ObjectHandles(NodePath,PandaObject):
 
     def createObjectHandleLines(self):
         # X post
-        self.xPost = self.xPostGroup.attachNewNode(NamedNode('x-post-visible'))
+        self.xPost = self.xPostGroup.attachNewNode('x-post-visible')
 	lines = LineNodePath(self.xPost)
 	lines.setColor(VBase4(1,0,0,1))
 	lines.setThickness(5)
@@ -756,7 +751,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         
 	# X ring
-        self.xRing = self.xRingGroup.attachNewNode(NamedNode('x-ring-visible'))
+        self.xRing = self.xRingGroup.attachNewNode('x-ring-visible')
 	lines = LineNodePath(self.xRing)
 	lines.setColor(VBase4(1,0,0,1))
 	lines.setThickness(3)
@@ -768,7 +763,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         
         # Y post
-        self.yPost = self.yPostGroup.attachNewNode(NamedNode('y-post-visible'))
+        self.yPost = self.yPostGroup.attachNewNode('y-post-visible')
 	lines = LineNodePath(self.yPost)
 	lines.setColor(VBase4(0,1,0,1))
 	lines.setThickness(5)
@@ -783,7 +778,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         
 	# Y ring
-        self.yRing = self.yRingGroup.attachNewNode(NamedNode('y-ring-visible'))
+        self.yRing = self.yRingGroup.attachNewNode('y-ring-visible')
 	lines = LineNodePath(self.yRing)
 	lines.setColor(VBase4(0,1,0,1))
 	lines.setThickness(3)
@@ -795,7 +790,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
 
         # Z post
-        self.zPost = self.zPostGroup.attachNewNode(NamedNode('z-post-visible'))
+        self.zPost = self.zPostGroup.attachNewNode('z-post-visible')
 	lines = LineNodePath(self.zPost)
 	lines.setColor(VBase4(0,0,1,1))
 	lines.setThickness(5)
@@ -810,7 +805,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
         
 	# Z ring
-        self.zRing = self.zRingGroup.attachNewNode(NamedNode('z-ring-visible'))
+        self.zRing = self.zRingGroup.attachNewNode('z-ring-visible')
 	lines = LineNodePath(self.zRing)
 	lines.setColor(VBase4(0,0,1,1))
 	lines.setThickness(3)
@@ -822,7 +817,7 @@ class ObjectHandles(NodePath,PandaObject):
         lines.create()
 
     def createGuideLines(self):
-        self.guideLines = self.attachNewNode(NamedNode('guideLines'))
+        self.guideLines = self.attachNewNode('guideLines')
         # X guide lines
 	lines = LineNodePath(self.guideLines)
 	lines.setColor(VBase4(1,0,0,1))
@@ -853,7 +848,7 @@ class ObjectHandles(NodePath,PandaObject):
     def getAxisIntersectPt(self, axis):
         # Calc the xfrom from camera to widget
         mCam2Widget = direct.camera.getMat(direct.widget)
-        lineDir = Vec3(mCam2Widget.xformVec(direct.chan.nearVec))
+        lineDir = Vec3(mCam2Widget.xformVec(direct.dr.nearVec))
         lineDir.normalize()
         # And determine where the viewpoint is relative to widget
         lineOrigin = VBase3(0)
@@ -910,7 +905,7 @@ class ObjectHandles(NodePath,PandaObject):
         # Next we find the vector from viewpoint to the widget through
         # the mouse's position on near plane.
         # This defines the intersection ray
-        lineDir = Vec3(mCam2NodePath.xformVec(direct.chan.nearVec))
+        lineDir = Vec3(mCam2NodePath.xformVec(direct.dr.nearVec))
         lineDir.normalize()
         # Find the hit point
         if plane == 'x':

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

@@ -202,26 +202,27 @@ class SelectedNodePaths(PandaObject):
         self.forEachSelectedNodePathDo(DirectNodePath.dehighlight)
 
     def removeSelected(self):
-	selected = self.dnp.last
+	selected = self.last
         if selected:
             selected.remove()
+        self.last = None
         
     def removeAll(self):
 	# Remove all selected nodePaths from the Scene Graph
         self.forEachSelectedNodePathDo(NodePath.remove)
 
-    def toggleVizSelected(self):
-	selected = self.dnp.last
+    def toggleVisSelected(self):
+	selected = self.last
         # Toggle visibility of selected node paths
         if selected:
-            selected.toggleViz()
+            selected.toggleVis()
 
-    def toggleVizAll(self):
+    def toggleVisAll(self):
         # Toggle viz for all selected node paths
-        self.forEachSelectedNodePathDo(NodePath.toggleViz)
+        self.forEachSelectedNodePathDo(NodePath.toggleVis)
 
     def isolateSelected(self):
-	selected = self.dnp.last
+	selected = self.last
         if selected:
             selected.isolate()
 
@@ -375,9 +376,7 @@ class DirectBoundingBox:
 
 class SelectionRay:
     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() )
         # Don't pay the penalty of drawing this collision ray
         self.rayCollisionNodePath.hide()

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

@@ -4,31 +4,41 @@ from DirectManipulation import *
 from DirectSelection import *
 from DirectGrid import *
 from DirectGeometry import *
+from DirectLights import *
 import OnscreenText
+import types
 import __builtin__
 
+DIRECT_FLASH_DURATION = 1.5
+
 class DirectSession(PandaObject):
 
     def __init__(self):
         # Establish a global pointer to the direct object early on
         # so dependant classes can access it in their code
         __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.manipulationControl = DirectManipulationControl()
         self.useObjectHandles()
         self.grid = DirectGrid()
         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
         self.selected = SelectedNodePaths()
+        # Ancestry of currently selected object
+        self.ancestry = []
+        self.ancestryIndex = 0
 
         self.readout = OnscreenText.OnscreenText( '', 0.1, -0.95 )
         # Make sure readout is never lit or drawn in wireframe
@@ -44,87 +54,45 @@ class DirectSession(PandaObject):
         self.hpr = VBase3()
         self.scale = VBase3()
 
-        self.iRay = self.iRayList[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
-        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',
                           'escape', 'space', 'delete',
                           'shift', 'shift-up', 'alt', 'alt-up',
                           'control', 'control-up',
+                          'page_up', 'page_down',
                           'b', 'c', 'f', 'l', 't', 'v', 'w']
         self.mouseEvents = ['mouse1', 'mouse1-up',
                             'mouse2', 'mouse2-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):
         # Make sure old tasks are shut down
         self.disable()
 	# Start all display region context tasks
-        for context in self.contextList:
-            context.spawnContextTask()
+        self.drList.spawnContextTask()
 	# Turn on mouse Flying
 	self.cameraControl.enableMouseFly()
         # Turn on object manipulation
@@ -138,8 +106,7 @@ class DirectSession(PandaObject):
 
     def disable(self):
 	# Shut down all display region context tasks
-        for context in self.contextList:
-            context.removeContextTask()
+        self.drList.removeContextTask()
 	# Turn off camera fly
 	self.cameraControl.disableMouseFly()
         # Turn off object manipulation
@@ -150,8 +117,7 @@ class DirectSession(PandaObject):
 
     def minimumConfiguration(self):
 	# Remove context task
-	for context in self.contextList:
-            context.removeContextTask()
+        self.drList.removeContextTask()
 	# Turn off camera fly
 	self.cameraControl.disableMouseFly()
 	# Ignore keyboard and action events
@@ -166,9 +132,11 @@ class DirectSession(PandaObject):
     def reset(self):
 	self.enable()
 
+    # EVENT FUNCTIONS
+
     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):
         for event in self.keyEvents:
@@ -190,15 +158,6 @@ class DirectSession(PandaObject):
         for event in self.mouseEvents:
             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):
 	# Deal with keyboard and mouse input
         if input == 'mouse1':
@@ -225,15 +184,19 @@ class DirectSession(PandaObject):
             self.fAlt = 1
         elif input == 'alt-up':
             self.fAlt = 0
+        elif input == 'page_up':
+            self.upAncestry()
+        elif input == 'page_down':
+            self.downAncestry()
         elif input == 'escape':
             self.deselectAll()
         elif input == 'l':
             if self.selected.last:
                 self.select(self.selected.last)
         elif input == 'delete':
-            self.selected.removeAll()
+            self.removeAllSelected()
         elif input == 'v':
-            self.selected.toggleVizAll()
+            self.selected.toggleVisAll()
         elif input == 'b':
             base.toggleBackface()
         elif input == 't':
@@ -241,12 +204,315 @@ class DirectSession(PandaObject):
         elif input == 'w':
             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):
         self.win = win
         self.camera = camera
-        self.cam = camera.getChild(0)
+        self.cam = self.camera.find('**/+Camera')
         self.camNode = self.cam.getNode(0)
+        self.iRay = SelectionRay(self.camera)
         self.nearVec = Vec3(0)
         self.mouseX = 0.0
         self.mouseY = 0.0

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

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

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

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

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

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

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

@@ -6,7 +6,7 @@ class PieMenu(NodePath, PandaObject):
                  action = None, fUpdateOnlyOnChange = 1):
         NodePath.__init__(self)
         # Create a toplevel node for aspect ratio scaling
-        self.assign(hidden.attachNewNode(NamedNode('PieMenu')))
+        self.assign(hidden.attachNewNode('PieMenu'))
         # Attach the menu
         self.visibleMenu = visibleMenu
         # Try to flatten the visibleMenu (note, flattenStrong is too strong
@@ -44,15 +44,15 @@ class PieMenu(NodePath, PandaObject):
 	taskMgr.removeTasksNamed('pieMenuTask')
 
 	# 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
 	self.reparentTo(render2d)
 	self.setPos(self.originX,0.0,self.originY)
         # Compensate for window aspect ratio
         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
 	self.lines.reset()
 	self.lines.moveTo(0,0,0)
@@ -65,8 +65,8 @@ class PieMenu(NodePath, PandaObject):
         taskMgr.spawnTaskNamed(t, 'pieMenuTask')
 
     def pieMenuTask(self,state):
-        mouseX = direct.chan.mouseX
-        mouseY = direct.chan.mouseY
+        mouseX = direct.dr.mouseX
+        mouseY = direct.dr.mouseY
         deltaX = mouseX - self.originX
         deltaY = mouseY - self.originY
 

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

@@ -16,13 +16,13 @@ Task to monitor pose
 ZERO_VEC = Vec3(0)
 UNIT_VEC = Vec3(1)
 
-class Placer(Pmw.MegaToplevel):
+class Placer(Pmw.MegaToplevel, PandaObject):
     def __init__(self, parent = None, **kw):
 
         INITOPT = Pmw.INITOPT
         optiondefs = (
             ('title',       'Placer Panel',     None),
-            ('nodePath',    camera,               None),
+            ('nodePath',    direct.camera,      None),
             )
         self.defineoptions(kw, optiondefs)
 
@@ -30,22 +30,23 @@ class Placer(Pmw.MegaToplevel):
         Pmw.MegaToplevel.__init__(self, parent)
 
         # 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.undoList = []
         self.redoList = []
         
         # Dictionary keeping track of all node paths manipulated so far
         self.nodePathDict = {}
-        self.nodePathDict['camera'] = camera
+        self.nodePathDict['camera'] = direct.camera
         self.nodePathNames = ['selected', 'camera']
 
         self.refNodePathDict = {}
         self.refNodePathDict['parent'] = self['nodePath'].getParent()
         self.refNodePathDict['render'] = render
-        self.refNodePathDict['camera'] = camera
+        self.refNodePathDict['camera'] = direct.camera
         self.refNodePathNames = ['self', 'parent', 'render',
                                  'camera', 'selected', 'coa']
 
@@ -57,6 +58,16 @@ class Placer(Pmw.MegaToplevel):
         # Offset for orbital mode
         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
         self.movementMode = 'Relative To:'
 
@@ -89,7 +100,7 @@ class Placer(Pmw.MegaToplevel):
         menuBar.addmenuitem('Placer', 'command',
                             'Exit Placer Panel',
                             label = 'Exit',
-                            command = self.preDestroy)
+                            command = self.destroy)
 
         menuBar.addmenu('Help', 'Placer Panel Help Operations')
         self.toggleBalloonVar = IntVar()
@@ -131,11 +142,11 @@ class Placer(Pmw.MegaToplevel):
 
         self.undoButton = Button(menuFrame, text = 'Undo',
                                  state = 'disabled',
-                                 command = self.undo)
+                                 command = direct.undo)
         self.undoButton.pack(side = 'left', expand = 0)
         self.redoButton = Button(menuFrame, text = 'Redo',
                                  state = 'disabled',
-                                 command = self.redo)
+                                 command = direct.redo)
         self.redoButton.pack(side = 'left', expand = 0)
 
         # The master frame for the dials
@@ -341,6 +352,9 @@ class Placer(Pmw.MegaToplevel):
         # Set up placer for inital node path
         self.selectNodePathNamed('init')
 
+        # Clean up things when you destroy the panel
+        self.bind('<Destroy>', self.onDestroy)
+
         # Make sure input variables processed 
         self.initialiseoptions(Placer)
 
@@ -654,82 +668,35 @@ class Placer(Pmw.MegaToplevel):
         self.updatePlacer()
 
     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):
-        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):
         np = self['nodePath']
         if np:
@@ -747,11 +714,15 @@ class Placer(Pmw.MegaToplevel):
             print ('%s.setPosHprScale(%s, %s, %s)' %
                    (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.orbitFromCS.removeNode()
         self.orbitToCS.removeNode()
-        self.destroy()
+        # Only do this once
+        self.unbind('<Destroy>')
         
     def toggleBalloon(self):
         if self.toggleBalloonVar.get():

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

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