Procházet zdrojové kódy

better support for multiple windows, multiple trackballs, one camera

David Rose před 14 roky
rodič
revize
50e53a4d4e

+ 1 - 1
direct/src/directtools/DirectSession.py

@@ -1184,7 +1184,7 @@ class DisplayRegionContext(DirectObject):
 
 
         # Values for this frame
         # Values for this frame
         # This ranges from -1 to 1
         # This ranges from -1 to 1
-        if (base.mouseWatcherNode.hasMouse()):
+        if base.mouseWatcherNode and base.mouseWatcherNode.hasMouse():
             self.mouseX = base.mouseWatcherNode.getMouseX()
             self.mouseX = base.mouseWatcherNode.getMouseX()
             self.mouseY = base.mouseWatcherNode.getMouseY()
             self.mouseY = base.mouseWatcherNode.getMouseY()
             self.mouseX = (self.mouseX-self.originX)*self.scaleX
             self.mouseX = (self.mouseX-self.originX)*self.scaleX

+ 1 - 1
direct/src/leveleditor/ObjectMgrBase.py

@@ -14,7 +14,7 @@ from ObjectPaletteBase import ObjectGen
 # python wrapper around a panda.NodePath object
 # python wrapper around a panda.NodePath object
 class PythonNodePath(NodePath):
 class PythonNodePath(NodePath):
     def __init__(self,node):
     def __init__(self,node):
-        NodePath.NodePath.__init__(self,node)
+        NodePath.__init__(self,node)
 
 
 class ObjectMgrBase:
 class ObjectMgrBase:
     """ ObjectMgr will create, manage, update objects in the scene """
     """ ObjectMgr will create, manage, update objects in the scene """

+ 123 - 79
direct/src/showbase/ShowBase.py

@@ -246,6 +246,13 @@ class ShowBase(DirectObject.DirectObject):
                 props.setRawMice(1)
                 props.setRawMice(1)
             self.openDefaultWindow(startDirect = False, props=props)
             self.openDefaultWindow(startDirect = False, props=props)
 
 
+        # The default is trackball mode, which is more convenient for
+        # ad-hoc development in Python using ShowBase.  Applications
+        # can explicitly call base.useDrive() if they prefer a drive
+        # interface.
+        self.mouseInterface = self.trackball
+        self.useTrackball()
+
         self.loader = Loader.Loader(self)
         self.loader = Loader.Loader(self)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
         self.graphicsEngine.setDefaultLoader(self.loader.loader)
             
             
@@ -566,19 +573,102 @@ class ShowBase(DirectObject.DirectObject):
 
 
     def openWindow(self, props = None, pipe = None, gsg = None,
     def openWindow(self, props = None, pipe = None, gsg = None,
                    type = None, name = None, size = None, aspectRatio = None,
                    type = None, name = None, size = None, aspectRatio = None,
-                   makeCamera = 1, keepCamera = 0,
-                   scene = None, stereo = None, rawmice = 0,
-                   callbackWindowDict = None):
+                   makeCamera = True, keepCamera = False,
+                   scene = None, stereo = None, 
+                   callbackWindowDict = None, requireWindow = None):
         """
         """
         Creates a window and adds it to the list of windows that are
         Creates a window and adds it to the list of windows that are
         to be updated every frame.
         to be updated every frame.
 
 
+        props is the WindowProperties that describes the window.
+
+        type is either 'onscreen', 'offscreen', or 'none'.
+
+        If keepCamera is true, the existing base.cam is set up to
+        render into the new window.
+
+        If keepCamera is false but makeCamera is true, a new camera is
+        set up to render into the new window.
+
         If callbackWindowDict is not None, a CallbackGraphicWindow is
         If callbackWindowDict is not None, a CallbackGraphicWindow is
         created instead, which allows the caller to create the actual
         created instead, which allows the caller to create the actual
         window with its own OpenGL context, and direct Panda's
         window with its own OpenGL context, and direct Panda's
         rendering into that window.
         rendering into that window.
+
+        If requireWindow is true, it means that the function should
+        raise an exception if the window fails to open correctly.
+        
         """
         """
 
 
+        # Save this lambda here for convenience; we'll use it to call
+        # down to the underlying _doOpenWindow() with all of the above
+        # parameters.
+        func = lambda : self._doOpenWindow(
+            props = props, pipe = pipe, gsg = gsg,
+            type = type, name = name, size = size, aspectRatio = aspectRatio,
+            makeCamera = makeCamera, keepCamera = keepCamera,
+            scene = scene, stereo = stereo, 
+            callbackWindowDict = callbackWindowDict)
+        
+        if self.win:
+            # If we've already opened a window before, this is just a
+            # pass-through to _doOpenWindow().
+            win = func()
+            self.graphicsEngine.openWindows()
+            return win
+        
+        if type is None:
+            type = self.windowType
+        if requireWindow is None:
+            requireWindow = self.requireWindow
+            
+        win = func()
+
+        # Give the window a chance to truly open.
+        self.graphicsEngine.openWindows()
+        if win != None and not win.isValid():
+            self.notify.info("Window did not open, removing.")
+            self.closeWindow(win)
+            win = None
+
+        if win == None and pipe == None:
+            # Try a little harder if the window wouldn't open.
+            self.makeAllPipes()
+            try:
+                self.pipeList.remove(self.pipe)
+            except ValueError:
+                pass
+            while self.win == None and self.pipeList:
+                self.pipe = self.pipeList[0]
+                self.notify.info("Trying pipe type %s (%s)" % (
+                    self.pipe.getType(), self.pipe.getInterfaceName()))
+                win = func()
+
+                self.graphicsEngine.openWindows()
+                if win != None and not win.isValid():
+                    self.notify.info("Window did not open, removing.")
+                    self.closeWindow(win)
+                    win = None
+                if win == None:
+                    self.pipeList.remove(self.pipe)
+
+        if win == None:
+            self.notify.warning("Unable to open '%s' window." % (type))
+            if requireWindow:
+                # Unless require-window is set to false, it is an
+                # error not to open a window.
+                raise StandardError, 'Could not open window.'
+        else:
+            self.notify.info("Successfully opened window of type %s (%s)" % (
+                win.getType(), win.getPipe().getInterfaceName()))
+
+        return win
+
+    def _doOpenWindow(self, props = None, pipe = None, gsg = None,
+                      type = None, name = None, size = None, aspectRatio = None,
+                      makeCamera = True, keepCamera = False,
+                      scene = None, stereo = None, 
+                      callbackWindowDict = None):
         if pipe == None:
         if pipe == None:
             pipe = self.pipe
             pipe = self.pipe
 
 
@@ -769,69 +859,13 @@ class ShowBase(DirectObject.DirectObject):
         if 'startDirect' in kw:
         if 'startDirect' in kw:
             del kw['startDirect']
             del kw['startDirect']
 
 
-        if self.win:
-            # If we've already opened a window before, this does
-            # little more work than openMainWindow() alone.
-            self.openMainWindow(*args, **kw)
-            self.graphicsEngine.openWindows()
-            return
-            
         self.openMainWindow(*args, **kw)
         self.openMainWindow(*args, **kw)
 
 
-        # Give the window a chance to truly open.
-        self.graphicsEngine.openWindows()
-        if self.win != None and not self.isMainWindowOpen():
-            self.notify.info("Window did not open, removing.")
-            self.closeWindow(self.win)
-
-        if self.win == None and not kw.get('pipe', None):
-            # Try a little harder if the window wouldn't open.
-            self.makeAllPipes()
-            try:
-                self.pipeList.remove(self.pipe)
-            except ValueError:
-                pass
-            while self.win == None and self.pipeList:
-                self.pipe = self.pipeList[0]
-                self.notify.info("Trying pipe type %s (%s)" % (
-                    self.pipe.getType(), self.pipe.getInterfaceName()))
-                self.openMainWindow(*args, **kw)
-
-                self.graphicsEngine.openWindows()
-                if self.win != None and not self.isMainWindowOpen():
-                    self.notify.info("Window did not open, removing.")
-                    self.closeWindow(self.win)
-                if self.win == None:
-                    self.pipeList.remove(self.pipe)
-
-        if self.win == None:
-            self.notify.warning("Unable to open '%s' window." % (
-                self.windowType))
-            if self.requireWindow:
-                # Unless require-window is set to false, it is an
-                # error not to open a window.
-                raise StandardError, 'Could not open window.'
-        else:
-            self.notify.info("Successfully opened window of type %s (%s)" % (
-                self.win.getType(), self.win.getPipe().getInterfaceName()))
-
-        # The default is trackball mode, which is more convenient for
-        # ad-hoc development in Python using ShowBase.  Applications
-        # can explicitly call base.useDrive() if they prefer a drive
-        # interface.
-        self.mouseInterface = self.trackball
-        self.useTrackball()
-
         if startDirect:
         if startDirect:
             self.__doStartDirect()
             self.__doStartDirect()
 
 
         return self.win != None
         return self.win != None
 
 
-    def isMainWindowOpen(self):
-        if self.win != None:
-            return self.win.isValid()
-        return 0
-
     def openMainWindow(self, *args, **kw):
     def openMainWindow(self, *args, **kw):
         """
         """
         Creates the initial, main window for the application, and sets
         Creates the initial, main window for the application, and sets
@@ -1148,11 +1182,7 @@ class ShowBase(DirectObject.DirectObject):
             win = self.win
             win = self.win
 
 
         if win != None and win.hasSize():
         if win != None and win.hasSize():
-            # Temporary hasattr for old Pandas
-            if not hasattr(win, 'getSbsLeftXSize'):
-                aspectRatio = float(win.getXSize()) / float(win.getYSize())
-            else:
-                aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
+            aspectRatio = float(win.getSbsLeftXSize()) / float(win.getSbsLeftYSize())
 
 
         else:
         else:
             if win == None or not hasattr(win, "getRequestedProperties"):
             if win == None or not hasattr(win, "getRequestedProperties"):
@@ -1216,6 +1246,8 @@ class ShowBase(DirectObject.DirectObject):
             self.camera.node().setPreserveTransform(ModelNode.PTLocal)
             self.camera.node().setPreserveTransform(ModelNode.PTLocal)
             __builtin__.camera = self.camera
             __builtin__.camera = self.camera
 
 
+            self.mouse2cam.node().setNode(self.camera.node())
+
         if useCamera:
         if useCamera:
             # Use the existing camera node.
             # Use the existing camera node.
             cam = useCamera
             cam = useCamera
@@ -1381,7 +1413,13 @@ class ShowBase(DirectObject.DirectObject):
         self.dataRoot = NodePath('dataRoot')
         self.dataRoot = NodePath('dataRoot')
         # Cache the node so we do not ask for it every frame
         # Cache the node so we do not ask for it every frame
         self.dataRootNode = self.dataRoot.node()
         self.dataRootNode = self.dataRoot.node()
-        self.dataUnused = NodePath('dataUnused')
+
+        # Now we have the main trackball & drive interfaces.
+        # useTrackball() and useDrive() switch these in and out; only
+        # one is in use at a given time.
+        self.trackball = NodePath(Trackball('trackball'))
+        self.drive = NodePath(DriveInterface('drive'))
+        self.mouse2cam = NodePath(Transform2SG('mouse2cam'))
 
 
     # [gjeon] now you can create multiple mouse watchers to support multiple windows
     # [gjeon] now you can create multiple mouse watchers to support multiple windows
     def setupMouse(self, win, fMultiWin=False):
     def setupMouse(self, win, fMultiWin=False):
@@ -1389,6 +1427,15 @@ class ShowBase(DirectObject.DirectObject):
         Creates the structures necessary to monitor the mouse input,
         Creates the structures necessary to monitor the mouse input,
         using the indicated window.  If the mouse has already been set
         using the indicated window.  If the mouse has already been set
         up for a different window, those structures are deleted first.
         up for a different window, those structures are deleted first.
+
+        The return value is the ButtonThrower NodePath created for
+        this window.
+
+        If fMultiWin is true, then the previous mouse structures are
+        not deleted; instead, multiple windows are allowed to monitor
+        the mouse input.  However, in this case, the trackball
+        controls are not set up, and must be set up by hand if
+        desired.
         """
         """
         if not fMultiWin and self.buttonThrowers != None:
         if not fMultiWin and self.buttonThrowers != None:
             for bt in self.buttonThrowers:
             for bt in self.buttonThrowers:
@@ -1409,6 +1456,9 @@ class ShowBase(DirectObject.DirectObject):
         self.mouseWatcher = self.buttonThrowers[0].getParent()
         self.mouseWatcher = self.buttonThrowers[0].getParent()
         self.mouseWatcherNode = self.mouseWatcher.node()  
         self.mouseWatcherNode = self.mouseWatcher.node()  
 
 
+        if self.mouseInterface:
+            self.mouseInterface.reparentTo(self.mouseWatcher)
+
         if self.recorder:
         if self.recorder:
             # If we have a recorder, the mouseWatcher belongs under a
             # If we have a recorder, the mouseWatcher belongs under a
             # special MouseRecorder node, which may intercept the
             # special MouseRecorder node, which may intercept the
@@ -1420,14 +1470,6 @@ class ShowBase(DirectObject.DirectObject):
             np = mw.getParent().attachNewNode(mouseRecorder)
             np = mw.getParent().attachNewNode(mouseRecorder)
             mw.reparentTo(np)
             mw.reparentTo(np)
 
 
-        # Now we have the main trackball & drive interfaces.
-        # useTrackball() and useDrive() switch these in and out; only
-        # one is in use at a given time.
-        self.trackball = self.dataUnused.attachNewNode(Trackball('trackball'))
-        self.drive = self.dataUnused.attachNewNode(DriveInterface('drive'))
-        self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam'))
-        self.mouse2cam.node().setNode(self.camera.node())
-
         # A special ButtonThrower to generate keyboard events and
         # A special ButtonThrower to generate keyboard events and
         # include the time from the OS.  This is separate only to
         # include the time from the OS.  This is separate only to
         # support legacy code that did not expect a time parameter; it
         # support legacy code that did not expect a time parameter; it
@@ -1445,6 +1487,8 @@ class ShowBase(DirectObject.DirectObject):
         self.pixel2dp.node().setMouseWatcher(mw.node())
         self.pixel2dp.node().setMouseWatcher(mw.node())
         mw.node().addRegion(PGMouseWatcherBackground())
         mw.node().addRegion(PGMouseWatcherBackground())
 
 
+        return self.buttonThrowers[0]
+
     # [gjeon] this function is seperated from setupMouse to allow multiple mouse watchers
     # [gjeon] this function is seperated from setupMouse to allow multiple mouse watchers
     def setupMouseCB(self, win):
     def setupMouseCB(self, win):
         # For each mouse/keyboard device, we create
         # For each mouse/keyboard device, we create
@@ -1471,8 +1515,7 @@ class ShowBase(DirectObject.DirectObject):
             mk = self.dataRoot.attachNewNode(MouseAndKeyboard(win, i, name))
             mk = self.dataRoot.attachNewNode(MouseAndKeyboard(win, i, name))
             mw = mk.attachNewNode(MouseWatcher("watcher%s" % (i)))
             mw = mk.attachNewNode(MouseWatcher("watcher%s" % (i)))
 
 
-            # Temporary hasattr for old Pandas
-            if hasattr(win, 'getSideBySideStereo') and win.getSideBySideStereo():
+            if win.getSideBySideStereo():
                 # If the window has side-by-side stereo enabled, then
                 # If the window has side-by-side stereo enabled, then
                 # we should constrain the MouseWatcher to the window's
                 # we should constrain the MouseWatcher to the window's
                 # DisplayRegion.  This will enable the MouseWatcher to
                 # DisplayRegion.  This will enable the MouseWatcher to
@@ -1991,7 +2034,7 @@ class ShowBase(DirectObject.DirectObject):
         # object out of there, so we won't be updating the camera any
         # object out of there, so we won't be updating the camera any
         # more.
         # more.
         if self.mouse2cam:
         if self.mouse2cam:
-            self.mouse2cam.reparentTo(self.dataUnused)
+            self.mouse2cam.detachNode()
 
 
     def enableMouse(self):
     def enableMouse(self):
         """
         """
@@ -2029,12 +2072,13 @@ class ShowBase(DirectObject.DirectObject):
         Switch mouse action
         Switch mouse action
         """
         """
         # Get rid of the prior interface:
         # Get rid of the prior interface:
-        self.mouseInterface.reparentTo(self.dataUnused)
+        self.mouseInterface.detachNode()
         # Update the mouseInterface to point to the drive
         # Update the mouseInterface to point to the drive
         self.mouseInterface = changeTo
         self.mouseInterface = changeTo
         self.mouseInterfaceNode = self.mouseInterface.node()
         self.mouseInterfaceNode = self.mouseInterface.node()
         # Hookup the drive to the camera.
         # Hookup the drive to the camera.
-        self.mouseInterface.reparentTo(self.mouseWatcher)
+        if self.mouseWatcher:
+            self.mouseInterface.reparentTo(self.mouseWatcher)
         if self.mouse2cam:
         if self.mouse2cam:
             self.mouse2cam.reparentTo(self.mouseInterface)
             self.mouse2cam.reparentTo(self.mouseInterface)
 
 
@@ -2157,7 +2201,7 @@ class ShowBase(DirectObject.DirectObject):
             self.oobeLens.setNearFar(0.1, 10000.0)
             self.oobeLens.setNearFar(0.1, 10000.0)
             self.oobeLens.setMinFov(40)
             self.oobeLens.setMinFov(40)
 
 
-            self.oobeTrackball = self.dataUnused.attachNewNode(Trackball('oobeTrackball'), 1)
+            self.oobeTrackball = NodePath(Trackball('oobeTrackball'), 1)
             self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
             self.oobe2cam = self.oobeTrackball.attachNewNode(Transform2SG('oobe2cam'))
             self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
             self.oobe2cam.node().setNode(self.oobeCameraTrackball.node())
 
 

+ 1 - 6
direct/src/wxwidgets/WxPandaWindow.py

@@ -35,12 +35,7 @@ class EmbeddedPandaWindow(wx.Window):
         if platform.system() != 'Darwin':
         if platform.system() != 'Darwin':
             wp.setParentWindow(self.GetHandle())
             wp.setParentWindow(self.GetHandle())
 
 
-        if base.win:
-            self.win = base.openWindow(props = wp, gsg = gsg, type = 'onscreen')
-        else:
-            base.openDefaultWindow(props = wp, gsg = gsg, type = 'onscreen')
-            self.win = base.win
-
+        self.win = base.openWindow(props = wp, gsg = gsg, type = 'onscreen')
         self.Bind(wx.EVT_SIZE, self.onSize)
         self.Bind(wx.EVT_SIZE, self.onSize)
 
 
         # This doesn't actually do anything, since wx won't call
         # This doesn't actually do anything, since wx won't call

+ 9 - 5
panda/src/dgraph/dataNode.cxx

@@ -52,7 +52,9 @@ transmit_data(DataGraphTraverser *trav,
     const DataConnection &connect = (*ci);
     const DataConnection &connect = (*ci);
     const EventParameter &data = 
     const EventParameter &data = 
       inputs[connect._parent_index].get_data(connect._output_index);
       inputs[connect._parent_index].get_data(connect._output_index);
-    new_input.set_data(connect._input_index, data);
+    if (!data.is_empty()) {
+      new_input.set_data(connect._input_index, data);
+    }
   }
   }
 
 
   #ifndef NDEBUG
   #ifndef NDEBUG
@@ -308,7 +310,7 @@ reconnect() {
             dgraph_cat.warning()
             dgraph_cat.warning()
               << "Ignoring mismatched type for connection " << name 
               << "Ignoring mismatched type for connection " << name 
               << " between " << *data_node << " and " << *this << "\n";
               << " between " << *data_node << " and " << *this << "\n";
-          } else if (num_found == 1) {
+          } else {
             DataConnection dc;
             DataConnection dc;
             dc._parent_index = i;
             dc._parent_index = i;
             dc._output_index = output_def._index;
             dc._output_index = output_def._index;
@@ -320,9 +322,11 @@ reconnect() {
     }
     }
 
 
     if (num_found > 1) {
     if (num_found > 1) {
-      dgraph_cat.warning()
-        << "Multiple connections found for " << name << " into " << *this
-        << "\n";
+      if (dgraph_cat.is_debug()) {
+        dgraph_cat.debug()
+          << "Multiple connections found for " << name << " into " << *this
+          << "\n";
+      }
     }
     }
   }
   }