Esempio n. 1
0
    def onUpdateThumbnail(self, evt, item):
        if self.treeLibrary.GetItemParent(item) == self.meshes:
            asset = self.lib.meshes[self.treeLibrary.GetItemText(item)]
        elif self.treeLibrary.GetItemParent(item) == self.actors:
            asset = self.lib.actors[self.treeLibrary.GetItemText(item)]

        if asset.thumbnail:
            f = asset.getThumbnail()
        else:
            f = Filename(asset.getFullFilename().getDirname() +
                         '/Thumbnails/' +
                         asset.filename.getBasenameWoExtension() + '.jpg')
        camnode = self.editor.ui.perspView.cam.node()
        dr = camnode.getDisplayRegion(0)
        try:
            dr.saveScreenshot(f)
        except Exception:
            pass
        else:
            cwd = os.getcwd()
            os.chdir(Filename(f.getDirname()).toOsSpecific())
            subprocess.call([
                "image-resize", "-x 60", "-y 60", "-g 1",
                "-o" + f.getBasename(),
                f.getBasename()
            ])
            os.chdir(cwd)
            if not asset.thumbnail:
                asset.thumbnail = f
                asset.thumbnail.makeRelativeTo(asset.projDir)
    def buildAPP(self, output, platform):

        output = Filename(output)
        if output.isDirectory() and output.getExtension() != 'app':
            output = Filename(output, "%s.app" % self.fullname)
        Installer.notify.info("Creating %s..." % output)

        # Create the executable for the application bundle
        exefile = Filename(output, "Contents/MacOS/" + self.shortname)
        exefile.makeDir()
        if self.includeRequires:
            extraTokens = {"host_dir": "../Resources"}
        else:
            extraTokens = {}
        self.standalone.build(exefile, platform, extraTokens)
        hostDir = Filename(output, "Contents/Resources/")
        hostDir.makeDir()
        self.installPackagesInto(hostDir, platform)

        # Create the application plist file.
        # Although it might make more sense to use Python's plistlib module here,
        # it is not available on non-OSX systems before Python 2.6.
        plist = open(
            Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
        print >> plist, '<?xml version="1.0" encoding="UTF-8"?>'
        print >> plist, '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
        print >> plist, '<plist version="1.0">'
        print >> plist, '<dict>'
        print >> plist, '\t<key>CFBundleDevelopmentRegion</key>'
        print >> plist, '\t<string>English</string>'
        print >> plist, '\t<key>CFBundleDisplayName</key>'
        print >> plist, '\t<string>%s</string>' % self.fullname
        print >> plist, '\t<key>CFBundleExecutable</key>'
        print >> plist, '\t<string>%s</string>' % exefile.getBasename()
        print >> plist, '\t<key>CFBundleIdentifier</key>'
        print >> plist, '\t<string>%s.%s</string>' % (self.authorid,
                                                      self.shortname)
        print >> plist, '\t<key>CFBundleInfoDictionaryVersion</key>'
        print >> plist, '\t<string>6.0</string>'
        print >> plist, '\t<key>CFBundleName</key>'
        print >> plist, '\t<string>%s</string>' % self.shortname
        print >> plist, '\t<key>CFBundlePackageType</key>'
        print >> plist, '\t<string>APPL</string>'
        print >> plist, '\t<key>CFBundleShortVersionString</key>'
        print >> plist, '\t<string>%s</string>' % self.version
        print >> plist, '\t<key>CFBundleVersion</key>'
        print >> plist, '\t<string>%s</string>' % self.version
        print >> plist, '\t<key>LSHasLocalizedDisplayName</key>'
        print >> plist, '\t<false/>'
        print >> plist, '\t<key>NSAppleScriptEnabled</key>'
        print >> plist, '\t<false/>'
        print >> plist, '\t<key>NSPrincipalClass</key>'
        print >> plist, '\t<string>NSApplication</string>'
        print >> plist, '</dict>'
        print >> plist, '</plist>'
        plist.close()

        return output
    def buildAPP(self, output, platform):

        output = Filename(output)
        if output.isDirectory() and output.getExtension() != "app":
            output = Filename(output, "%s.app" % self.fullname)
        Installer.notify.info("Creating %s..." % output)

        # Create the executable for the application bundle
        exefile = Filename(output, "Contents/MacOS/" + self.shortname)
        exefile.makeDir()
        if self.includeRequires:
            extraTokens = {"host_dir": "../Resources"}
        else:
            extraTokens = {}
        self.standalone.build(exefile, platform, extraTokens)
        hostDir = Filename(output, "Contents/Resources/")
        hostDir.makeDir()
        self.installPackagesInto(hostDir, platform)

        # Create the application plist file.
        # Although it might make more sense to use Python's plistlib module here,
        # it is not available on non-OSX systems before Python 2.6.
        plist = open(Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
        print >> plist, '<?xml version="1.0" encoding="UTF-8"?>'
        print >> plist, '<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">'
        print >> plist, '<plist version="1.0">'
        print >> plist, "<dict>"
        print >> plist, "\t<key>CFBundleDevelopmentRegion</key>"
        print >> plist, "\t<string>English</string>"
        print >> plist, "\t<key>CFBundleDisplayName</key>"
        print >> plist, "\t<string>%s</string>" % self.fullname
        print >> plist, "\t<key>CFBundleExecutable</key>"
        print >> plist, "\t<string>%s</string>" % exefile.getBasename()
        print >> plist, "\t<key>CFBundleIdentifier</key>"
        print >> plist, "\t<string>%s.%s</string>" % (self.authorid, self.shortname)
        print >> plist, "\t<key>CFBundleInfoDictionaryVersion</key>"
        print >> plist, "\t<string>6.0</string>"
        print >> plist, "\t<key>CFBundleName</key>"
        print >> plist, "\t<string>%s</string>" % self.shortname
        print >> plist, "\t<key>CFBundlePackageType</key>"
        print >> plist, "\t<string>APPL</string>"
        print >> plist, "\t<key>CFBundleShortVersionString</key>"
        print >> plist, "\t<string>%s</string>" % self.version
        print >> plist, "\t<key>CFBundleVersion</key>"
        print >> plist, "\t<string>%s</string>" % self.version
        print >> plist, "\t<key>LSHasLocalizedDisplayName</key>"
        print >> plist, "\t<false/>"
        print >> plist, "\t<key>NSAppleScriptEnabled</key>"
        print >> plist, "\t<false/>"
        print >> plist, "\t<key>NSPrincipalClass</key>"
        print >> plist, "\t<string>NSApplication</string>"
        print >> plist, "</dict>"
        print >> plist, "</plist>"
        plist.close()

        return output
Esempio n. 4
0
    def fromFile(self, packageDir, filename, pathname = None, st = None):
        """ Reads the file information from the indicated file.  If st
        is supplied, it is the result of os.stat on the filename. """
        
        vfs = VirtualFileSystem.getGlobalPtr()

        filename = Filename(filename)
        if pathname is None:
            pathname = Filename(packageDir, filename)
        
        self.filename = filename.cStr()
        self.basename = filename.getBasename()

        if st is None:
            st = os.stat(pathname.toOsSpecific())
        self.size = st.st_size
        self.timestamp = st.st_mtime

        self.readHash(pathname)
Esempio n. 5
0
    def fromFile(self, packageDir, filename, pathname=None, st=None):
        """ Reads the file information from the indicated file.  If st
        is supplied, it is the result of os.stat on the filename. """

        vfs = VirtualFileSystem.getGlobalPtr()

        filename = Filename(filename)
        if pathname is None:
            pathname = Filename(packageDir, filename)

        self.filename = filename.cStr()
        self.basename = filename.getBasename()

        if st is None:
            st = os.stat(pathname.toOsSpecific())
        self.size = st.st_size
        self.timestamp = st.st_mtime

        self.readHash(pathname)
Esempio n. 6
0
 def onUpdateThumbnail(self, evt, item):
     if self.treeLibrary.GetItemParent(item) == self.meshes:
         asset = self.lib.meshes[self.treeLibrary.GetItemText(item)]
     elif self.treeLibrary.GetItemParent(item) == self.actors:
         asset = self.lib.actors[self.treeLibrary.GetItemText(item)]
     
     if asset.thumbnail:
         f = asset.getThumbnail()
     else:
         f = Filename(asset.getFullFilename().getDirname() + '/Thumbnails/' + asset.filename.getBasenameWoExtension() + '.jpg')
     camnode = self.editor.ui.perspView.cam.node()
     dr = camnode.getDisplayRegion(0)
     try:
         dr.saveScreenshot(f)
     except Exception:
         pass
     else:
         cwd = os.getcwd()
         os.chdir(Filename(f.getDirname()).toOsSpecific())
         subprocess.call(["image-resize","-x 60", "-y 60", "-g 1", "-o" + f.getBasename(), f.getBasename()])
         os.chdir(cwd)
         if not asset.thumbnail:
             asset.thumbnail = f
             asset.thumbnail.makeRelativeTo(asset.projDir)
class EditorApp(AppShell):
  appversion    = "cvs"
  appname       = "Panda Editor"
  copyright     = "Copyright (c) Carnegie Mellon University.\nAll rights reserved."
  contactname   = "pro-rsoft"
  contactemail  = "*****@*****.**"
  frameWidth    = defWP.getXSize()
  frameHeight   = defWP.getYSize()
  
  def __init__(self, editorInstance):
    """EditorApp constructor."""
    # Instance of the editor
    self.editorInstance = editorInstance
    
    # Create the Wx app
    self.wxApp = wx.App(redirect = False)
    self.wxApp.SetAppName("Panda Editor")
    self.wxApp.SetClassName("PEditor")
    
    self.modified = True
    self.filename = Filename()
    
    # Initialize the app shell and add some controls
    AppShell.__init__(self, title = "Panda Editor", pos = origin)
    self.splitter1 = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_BORDER)
    self.splitter1.SetMinimumPaneSize(1)
    self.splitter2 = wx.SplitterWindow(self.splitter1, style = wx.SP_3D | wx.SP_BORDER)
    self.splitter2.SetMinimumPaneSize(1)
    self.leftBarSplitter = wx.SplitterWindow(self.splitter2, style = wx.SP_3D | wx.SP_BORDER)
    self.leftBarSplitter.SetMinimumPaneSize(1)
    #self.rightBarSplitter = wx.SplitterWindow(self.splitter1, style = wx.SP_3D | wx.SP_BORDER)
    #self.rightBarSplitter.SetMinimumPaneSize(1)
    self.sceneGraphTree = SceneGraphTree(self.leftBarSplitter)
    self.propertyGrid = PropertyGrid(self.leftBarSplitter)
    self.textureManager = TextureManager(self.splitter1, style = wx.SP_3D | wx.SUNKEN_BORDER)
    self.view = Viewport.makePerspective(self.splitter2)
    sizer = wx.BoxSizer(wx.VERTICAL)
    assert self.leftBarSplitter.SplitHorizontally(self.sceneGraphTree, self.propertyGrid)
    assert self.splitter2.SplitVertically(self.leftBarSplitter, self.view, 200)
    #assert self.rightBarSplitter.SplitHorizontally(self.textureManager, None)
    assert self.splitter1.SplitVertically(self.splitter2, self.textureManager, -200)
    sizer.Add(self.splitter1, 1, wx.EXPAND, 0)
    self.splitter1.Unsplit() # Yes, I know this looks odd.
    self.SetSizer(sizer); self.Layout()
    self.initialize()
    self.splitter2.SetSashPosition(200)
    
    # Setup some events
    base.accept("c", self.onCenterTrackball)
    
    base.accept(EVENT_MODELCONTROLLER_SELECTED_OBJECT_CHANGE, self.onModelSelect)
    # If a model-translate-rotate-scale tool is selected the automatic mouse
    # movement has to be disable to prevent camera & object movement.
    # Hmm doesnt really work as well... (camera is still moved)
    base.accept(EVENT_MODELCONTROLLER_EDITTOOL_SELECTED, lambda x=None:base.disableMouse())
    base.accept(EVENT_MODELCONTROLLER_EDITTOOL_DESELECTED, lambda x=None:base.enableMouse())
    base.accept(EVENT_MODELCONTROLLER_FULL_REFRESH, self.__setattr__, ["modified", True])
    # The object has been modified in the scene, this event happens every frame
    #base.accept(EVENT_MODELCONTROLLER_FAST_REFRESH, )
    # The editor has been disabled, collisions etc are deleted
    #base.accept(EDITOR_TOGGLE_OFF_EVENT, )
    # The editor has been enabled, collisions etc are created.
    # This event happens shortly after the wxgui has been created
    #base.accept(EDITOR_TOGGLE_ON_EVENT, )
  
  def appInit(self):
    print "I: EditorApp.appInit"
    """Overridden from WxAppShell.py."""
    # Create a new event loop (to overide default wxEventLoop)
    self.evtLoop = wx.EventLoop()
    self.oldLoop = wx.EventLoop.GetActive()
    wx.EventLoop.SetActive(self.evtLoop)
    taskMgr.add(self.wxStep, "evtLoopTask")
  
  def createMenuBar(self):
    """Overridden from WxAppShell.py."""
    # File menu
    self.menuFile = wx.Menu()
    self.menuBar.Append(self.menuFile, "&File")
    self.Bind(wx.EVT_MENU, self.onNew, self.menuFile.Append(wx.ID_NEW, "&New"))
    self.Bind(wx.EVT_MENU, self.onOpen, self.menuFile.Append(wx.ID_OPEN, "&Open"))
    self.Bind(wx.EVT_MENU, self.onSave, self.menuFile.Append(wx.ID_SAVE, "&Save"))
    self.Bind(wx.EVT_MENU, self.onSaveAs, self.menuFile.Append(wx.ID_SAVEAS, "Save &As..."))
    self.menuFile.AppendSeparator()
    self.Bind(wx.EVT_MENU, self.quit, self.menuFile.Append(wx.ID_EXIT, "&Quit"))
    
    # Edit menu
    #self.menuEdit = wx.Menu()
    #self.menuBar.Append(self.menuEdit, "&Edit")
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_UNDO, "&Undo"))
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_REDO, "&Redo"))
    #self.menuEdit.AppendSeparator()
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_CUT, "Cu&t"))
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_COPY, "&Copy"))
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_PASTE, "&Paste"))
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_DELETE, "&Delete"))
    #self.menuEdit.AppendSeparator()
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_SELECTALL, "Select &All"))
    #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_ANY, "Select &None"))
    
    # View menu
    self.menuView = wx.Menu()
    self.menuBar.Append(self.menuView, "&View")
    self.Bind(wx.EVT_MENU, self.onToggleGrid, self.menuView.AppendCheckItem(ID_ENABLE_GRID, "E&nable Grid"))
    self.Bind(wx.EVT_MENU, self.onCenterTrackball, self.menuView.Append(wx.ID_ANY, "&Center Model"))
    self.menuBar.Check(ID_ENABLE_GRID, True)
    
    # Create menu
    self.menuCreate = wx.Menu()
    self.menuBar.Append(self.menuCreate, "&Create")
    self.Bind(wx.EVT_MENU, self.onCreateObject, self.menuCreate.Append(ID_MODEL, "&Model..."))
    self.Bind(wx.EVT_MENU, self.onCreateObject, self.menuCreate.Append(ID_TERRAIN, "&Terrain..."))
    
    self.menuCreateLight = wx.Menu()
    self.Bind(wx.EVT_MENU, self.onCreateObject, self.menuCreateLight.Append(ID_AMBIENT, "&Ambient"))
    self.Bind(wx.EVT_MENU, self.onCreateObject, self.menuCreateLight.Append(ID_DIRECTIONAL, "&Directional"))
    self.Bind(wx.EVT_MENU, self.onCreateObject, self.menuCreateLight.Append(ID_POINT, "&Point"))
    self.Bind(wx.EVT_MENU, self.onCreateObject, self.menuCreateLight.Append(ID_SPOT, "&Spotlight"))
    self.menuCreate.AppendSubMenu(self.menuCreateLight, "&Light")
    
    # Viewports menu
    self.menuViewports = wx.Menu()
    self.menuBar.Append(self.menuViewports, "View&ports")
    self.Bind(wx.EVT_MENU, self.onChangeViewports, self.menuViewports.AppendRadioItem(ID_SINGLE_VIEWPORT, "&Single Viewport"))
    self.Bind(wx.EVT_MENU, self.onChangeViewports, self.menuViewports.AppendRadioItem(ID_4x4_GRID, "4x4 &Grid"))
    self.Bind(wx.EVT_MENU, self.onChangeViewports, self.menuViewports.AppendRadioItem(ID_2_HORIZONTAL, "2 &Horizontal"))
    self.Bind(wx.EVT_MENU, self.onChangeViewports, self.menuViewports.AppendRadioItem(ID_2_VERTICAL, "2 &Vertical"))
    self.menuBar.Check(ID_SINGLE_VIEWPORT, True)
    self.menuViewports.AppendSeparator()
    self.viewportMenus = []
  
  def createInterface(self):
    """Overridden from WxAppShell.py."""
    self.CreateStatusBar()
    self.SetStatusText("Welcome to the Panda3D Editor")
    self.Update()
  
  def initialize(self):
    """Initializes the viewports and editor."""
    print "I: EditorApp.initialize"
    self.Update()
    ViewportManager.updateAll()
    self.wxStep()
    ViewportManager.initializeAll()
    self.reloadViewportMenus()
    #self.editorInstance.toggle("WorldEditMode")
    # Position the camera
    if base.trackball != None:
      base.trackball.node().setPos(0, 30, 0)
      base.trackball.node().setHpr(0, 15, 0)
    
    # Load the direct things
    self.grid = DirectGrid(parent = render)
    self.sceneGraphTree.reload()
    if not isinstance(self.view, Viewport):
      self.view.center()
    
    # Initialize some stuff
    self.editorInstance.toggleEditmode(True)
  
  def reloadViewportMenus(self):
    """Reloads the viewport menus."""
    # Add the individual viewport menus
    for m in self.viewportMenus:
      m.Destroy()
    self.viewportMenus = []
    collect()
    #for v in range(len(ViewportManager.viewports)):
    #  self.viewportMenus.append(self.menuViewports.AppendSubMenu(ViewportMenu(ViewportManager.viewports[v]), "Viewport %d" % (v + 1)))
  
  def wxStep(self, task = None):
    """A step in the WX event loop. You can either call this yourself or use as task."""
    while self.evtLoop.Pending():
      self.evtLoop.Dispatch()
    self.wxApp.ProcessIdle()
    if task != None: return task.cont
  
  def onModelSelect(self, model):
    """Invoked when a model is selected. Shows/hides the texture panel."""
    if model == None and self.splitter1.IsSplit():
      self.splitter1.Unsplit()
    elif model != None and not self.splitter1.IsSplit():
      assert self.splitter1.SplitVertically(self.splitter2, self.textureManager, -200)
  
  def onDestroy(self, evt):
    """Invoked when the window is destroyed."""
    wx.EventLoop.SetActive(self.oldLoop)
  
  def onNew(self, evt = None):
    self.filename = Filename()
    self.modified = True
    self.SetTitle("Panda Editor")
    self.editorInstance.destroyAllModels()
  
  def onOpen(self, evt = None):
    filter = "Panda3D Egg Scene Format (*.egs)|*.[eE][gG][sS];*.egs"
    #filter += "|Panda3D Compressed Egg Format (*.egg.pz)|*.[eE][gG][gG].[pP][zZ];*.egg.pz"
    #filter += "|Panda3D Binary Format (*.bam)|*.[bB][aA][mM];*.bam"
    #filter += "|Panda3D Compressed Binary Format (*.bam)|*.[bB][aA][mM].[pP][zZ];*.bam.pz"
    ''' # disabled by hypnos, making the loading work
    filter += "|MultiGen (*.flt)|*.[fF][lL][tT]"
    filter += "|Lightwave (*.lwo)|*.[lL][wW][oO]"
    filter += "|AutoCAD (*.dxf)|*.[dD][xX][fF]"
    filter += "|VRML (*.wrl)|*.[wW][rR][lL]"
    filter += "|DirectX (*.x)|*.[xX]"
    filter += "|COLLADA (*.dae)|*.[dD][aA][eE]" '''
    dlg = wx.FileDialog(self, "Load file", "", "", filter, wx.OPEN)
    try:
      if dlg.ShowModal() == wx.ID_OK:
        #self.filename = Filename.fromOsSpecific(dlg.GetPath())
        p3dFilename = Filename.fromOsSpecific(dlg.GetPath())
        self.filename = str(dlg.GetPath())
        self.SetTitle(p3dFilename.getBasename() + " - Panda Editor")
        self.modified = False
        self.editorInstance.loadEggModelsFile( self.filename )
        # Reset the camera
        if base.trackball != None:
          base.trackball.node().setPos(0, 30, 0)
          base.trackball.node().setHpr(0, 15, 0)
        self.onCenterTrackball()
        if p3dFilename.getExtension().lower() != "bam":
          self.filename = Filename()
          self.modified = True
        self.sceneGraphTree.reload()
    finally:
      dlg.Destroy()
  
  def onSave(self, evt = None):
    if not self.modified: return
    if self.filename == None or self.filename.empty():
      self.onSaveAs(evt)
    else:
      dlg = wx.FileDialog(self, "Save file", "", "", "Panda3D Binary Format (*.bam)|.[bB][aA][mM];*.bam", wx.SAVE | wx.FD_OVERWRITE_PROMPT)
      try:
        if dlg.ShowModal() == wx.ID_OK:
          self.filename = Filename.fromOsSpecific(dlg.GetPath())
          self.SetTitle(self.filename.getBasename() + " - Panda Editor")
          self.modified = False
          self.editorInstance.saveEggModelsFile(self.filename.getFullpath())
      finally:
        dlg.Destroy()
  
  def onSaveAs(self, evt = None):
    dlg = wx.FileDialog(self, "Save file as", "", "", "Panda3D Binary Format (*.bam)|.[bB][aA][mM];*.bam", wx.SAVE | wx.FD_OVERWRITE_PROMPT)
    try:
      if dlg.ShowModal() == wx.ID_OK:
        self.onNew()
        self.filename = Filename.fromOsSpecific(dlg.GetPath())
        self.SetTitle(self.filename.getBasename() + " - Panda Editor")
        self.modified = False
        self.editorInstance.saveEggModelsFile(self.filename.getFullpath())
    finally:
      dlg.Destroy()
  
  def onCreateObject(self, e):
    """Invoked when the user hits one of the buttons in the "Create" menu."""
    
    modelParent = modelController.getSelectedObject() 
    if modelParent == None: modelParent = render
    objectInstance = None
    if e.Id == ID_NODEPATH:
      objectInstance = NodePathWrapper.onCreateInstance(modelParent)
    elif e.Id == ID_MODEL:
      filter = "Panda3D Egg Format (*.egg)|*.[eE][gG][gG];*.egg"
      filter += "|Panda3D Binary Format (*.bam)|*.[bB][aA][mM];*.bam"
      filter += "|MultiGen (*.flt)|*.[fF][lL][tT];*.flt"
      filter += "|Lightwave (*.lwo)|*.[lL][wW][oO];*.lwo"
      filter += "|AutoCAD (*.dxf)|*.[dD][xX][fF];*.dxf"
      filter += "|VRML (*.wrl)|*.[wW][rR][lL];*.wrl"
      filter += "|DirectX (*.x)|*.[xX];*.x"
      filter += "|COLLADA (*.dae)|*.[dD][aA][eE];*.dae"
      dlg = wx.FileDialog(self, "Select model", "", "", filter, wx.OPEN)
      try:
        if dlg.ShowModal() == wx.ID_OK:
          objectInstance = NodePathWrapper.onCreateInstance(modelParent, Filename.fromOsSpecific(dlg.GetPath()).getFullpath())
      finally:
        dlg.Destroy()
    elif e.Id == ID_TERRAIN:
      filter = "Portable Network Graphics (*.png)|*.[pP][nN][gG];*.png"
      dlg = wx.FileDialog(self, "Select heightfield", "", "", filter, wx.OPEN)
      try:
        if dlg.ShowModal() == wx.ID_OK:
          objectInstance = GeoMipTerrainNodeWrapper.onCreateInstance(modelParent, Filename.fromOsSpecific(dlg.GetPath()).getFullpath())
      finally:
        dlg.Destroy()
    elif e.Id == ID_AMBIENT:
      objectInstance = AmbientLightNodeWrapper.onCreateInstance(modelParent)
    elif e.Id == ID_DIRECTIONAL:
      objectInstance = DirectionalLightNodeWrapper.onCreateInstance(modelParent)
    elif e.Id == ID_POINT:
      objectInstance = PointLightNodeWrapper.onCreateInstance(modelParent)
    elif e.Id == ID_SPOT:
      objectInstance = SpotLightNodeWrapper.onCreateInstance(modelParent)
    
    if objectInstance != None:
      objectInstance.reparentTo(modelParent)
      objectInstance.enableEditmode() 
      modelController.selectObject(objectInstance)
      messenger.send(EVENT_SCENEGRAPH_REFRESH)
  
  def onChangeViewports(self, e):
    """Invoked when the user changes viewport layout."""
    self.Update()
    sashpos = self.splitter2.GetSashPosition()
    if e.Id == ID_SINGLE_VIEWPORT:
      if isinstance(self.view, Viewport): return
      self.view.close()
      self.view = Viewport.makePerspective(self.splitter2)
    elif e.Id == ID_4x4_GRID:
      if isinstance(self.view, ViewportGrid): return
      self.view.close()
      self.view = ViewportGrid(self.splitter2, [[Viewport.VPTOP,  Viewport.VPFRONT],
                                               [Viewport.VPLEFT, Viewport.VPPERSPECTIVE]])
      self.view.center()
    else:
      if e.Id == ID_2_HORIZONTAL: orientation = wx.SPLIT_HORIZONTAL
      elif e.Id == ID_2_VERTICAL: orientation = wx.SPLIT_VERTICAL
      else: return
      if isinstance(self.view, ViewportSplitter) and not isinstance(self.view, ViewportGrid):
        if self.view.GetSplitMode() == orientation: return
        self.view.close()
        self.view.split(Viewport.VPTOP, Viewport.VPPERSPECTIVE, orientation)
      else:
        self.view.close()
        self.view = ViewportSplitter(self.splitter2, Viewport.VPTOP, Viewport.VPPERSPECTIVE, orientation)
    self.splitter2.Unsplit()
    assert self.splitter2.SplitVertically(self.leftBarSplitter, self.view, sashpos)
    # Reload the menus
    collect()
    self.reloadViewportMenus()
    # Make sure the viewports are initialized correctly
    self.Update()
    ViewportManager.updateAll()
    self.wxStep()
    ViewportManager.initializeAll()
    messenger.send("window-event")
  
  def onToggleGrid(self, evt = None):
    """Toggles the grid on/off."""
    if evt.GetEventObject().IsChecked(ID_ENABLE_GRID):
      self.grid.enable()
    else:
      self.grid.disable()
  
  def onCenterTrackball(self, evt = None):
    """Center the trackball, like 'c' does in pview."""
    gbv = render.getBounds();
    # Determine the bounding sphere around the object.
    if gbv.isInfinite(): return
    if gbv.isEmpty(): return
    
    # The BoundingVolume might be a sphere (it's likely), but since it
    # might not, we'll take no chances and make our own sphere.
    sphere = BoundingSphere(gbv.getApproxCenter(), 0.0)
    if (not sphere.extendBy(gbv)): return
    
    radius = 50.0
    
    # Loop through the windows/viewports
    for w in WindowManager.windows:
      # Choose a suitable distance to view the whole volume in our frame.
      # This is based on the camera lens in use.
      fov = w.camLens.getFov();
      distance = radius / tan(deg2Rad(min(fov[0], fov[1]) / 2.0));
      
      # Ensure the far plane is far enough back to see the entire object.
      idealFarPlane = distance + radius * 1.5;
      w.camLens.setFar(max(w.camLens.getDefaultFar(), idealFarPlane));
      
      # And that the near plane is far enough forward.
      w.camLens.setNear(min(w.camLens.getDefaultNear(), radius - sphere.getRadius()))
      
      w.trackball.node().setOrigin(sphere.getCenter())
      w.trackball.node().setPos(Vec3.forward() * distance)
      
      # Also set the movement scale on the trackball to be consistent
      # with the size of the model and the lens field-of-view.
      w.trackball.node().setForwardScale(distance * 0.006)
Esempio n. 8
0
class EditorApp(AppShell):
    appversion = "cvs"
    appname = "Panda Editor"
    copyright = "Copyright (c) Carnegie Mellon University.\nAll rights reserved."
    contactname = "pro-rsoft"
    contactemail = "*****@*****.**"
    frameWidth = defWP.getXSize()
    frameHeight = defWP.getYSize()

    def __init__(self, editorInstance):
        """EditorApp constructor."""
        # Instance of the editor
        self.editorInstance = editorInstance

        # Create the Wx app
        self.wxApp = wx.App(redirect=False)
        self.wxApp.SetAppName("Panda Editor")
        self.wxApp.SetClassName("PEditor")

        self.modified = True
        self.filename = Filename()

        # Initialize the app shell and add some controls
        AppShell.__init__(self, title="Panda Editor", pos=origin)
        self.splitter1 = wx.SplitterWindow(self, style=wx.SP_3D | wx.SP_BORDER)
        self.splitter1.SetMinimumPaneSize(1)
        self.splitter2 = wx.SplitterWindow(self.splitter1,
                                           style=wx.SP_3D | wx.SP_BORDER)
        self.splitter2.SetMinimumPaneSize(1)
        self.leftBarSplitter = wx.SplitterWindow(self.splitter2,
                                                 style=wx.SP_3D | wx.SP_BORDER)
        self.leftBarSplitter.SetMinimumPaneSize(1)
        #self.rightBarSplitter = wx.SplitterWindow(self.splitter1, style = wx.SP_3D | wx.SP_BORDER)
        #self.rightBarSplitter.SetMinimumPaneSize(1)
        self.sceneGraphTree = SceneGraphTree(self.leftBarSplitter)
        self.propertyGrid = PropertyGrid(self.leftBarSplitter)
        self.textureManager = TextureManager(self.splitter1,
                                             style=wx.SP_3D | wx.SUNKEN_BORDER)
        self.view = Viewport.makePerspective(self.splitter2)
        sizer = wx.BoxSizer(wx.VERTICAL)
        assert self.leftBarSplitter.SplitHorizontally(self.sceneGraphTree,
                                                      self.propertyGrid)
        assert self.splitter2.SplitVertically(self.leftBarSplitter, self.view,
                                              200)
        #assert self.rightBarSplitter.SplitHorizontally(self.textureManager, None)
        assert self.splitter1.SplitVertically(self.splitter2,
                                              self.textureManager, -200)
        sizer.Add(self.splitter1, 1, wx.EXPAND, 0)
        self.splitter1.Unsplit()  # Yes, I know this looks odd.
        self.SetSizer(sizer)
        self.Layout()
        self.initialize()
        self.splitter2.SetSashPosition(200)

        # Setup some events
        base.accept("c", self.onCenterTrackball)

        base.accept(EVENT_MODELCONTROLLER_SELECTED_OBJECT_CHANGE,
                    self.onModelSelect)
        # If a model-translate-rotate-scale tool is selected the automatic mouse
        # movement has to be disable to prevent camera & object movement.
        # Hmm doesnt really work as well... (camera is still moved)
        base.accept(EVENT_MODELCONTROLLER_EDITTOOL_SELECTED,
                    lambda x=None: base.disableMouse())
        base.accept(EVENT_MODELCONTROLLER_EDITTOOL_DESELECTED,
                    lambda x=None: base.enableMouse())
        base.accept(EVENT_MODELCONTROLLER_FULL_REFRESH, self.__setattr__,
                    ["modified", True])
        # The object has been modified in the scene, this event happens every frame
        #base.accept(EVENT_MODELCONTROLLER_FAST_REFRESH, )
        # The editor has been disabled, collisions etc are deleted
        #base.accept(EDITOR_TOGGLE_OFF_EVENT, )
        # The editor has been enabled, collisions etc are created.
        # This event happens shortly after the wxgui has been created
        #base.accept(EDITOR_TOGGLE_ON_EVENT, )

    def appInit(self):
        print "I: EditorApp.appInit"
        """Overridden from WxAppShell.py."""
        # Create a new event loop (to overide default wxEventLoop)
        self.evtLoop = wx.EventLoop()
        self.oldLoop = wx.EventLoop.GetActive()
        wx.EventLoop.SetActive(self.evtLoop)
        taskMgr.add(self.wxStep, "evtLoopTask")

    def createMenuBar(self):
        """Overridden from WxAppShell.py."""
        # File menu
        self.menuFile = wx.Menu()
        self.menuBar.Append(self.menuFile, "&File")
        self.Bind(wx.EVT_MENU, self.onNew,
                  self.menuFile.Append(wx.ID_NEW, "&New"))
        self.Bind(wx.EVT_MENU, self.onOpen,
                  self.menuFile.Append(wx.ID_OPEN, "&Open"))
        self.Bind(wx.EVT_MENU, self.onSave,
                  self.menuFile.Append(wx.ID_SAVE, "&Save"))
        self.Bind(wx.EVT_MENU, self.onSaveAs,
                  self.menuFile.Append(wx.ID_SAVEAS, "Save &As..."))
        self.menuFile.AppendSeparator()
        self.Bind(wx.EVT_MENU, self.quit,
                  self.menuFile.Append(wx.ID_EXIT, "&Quit"))

        # Edit menu
        #self.menuEdit = wx.Menu()
        #self.menuBar.Append(self.menuEdit, "&Edit")
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_UNDO, "&Undo"))
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_REDO, "&Redo"))
        #self.menuEdit.AppendSeparator()
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_CUT, "Cu&t"))
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_COPY, "&Copy"))
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_PASTE, "&Paste"))
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_DELETE, "&Delete"))
        #self.menuEdit.AppendSeparator()
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_SELECTALL, "Select &All"))
        #self.Bind(wx.EVT_MENU, self.quit, self.menuEdit.Append(wx.ID_ANY, "Select &None"))

        # View menu
        self.menuView = wx.Menu()
        self.menuBar.Append(self.menuView, "&View")
        self.Bind(
            wx.EVT_MENU, self.onToggleGrid,
            self.menuView.AppendCheckItem(ID_ENABLE_GRID, "E&nable Grid"))
        self.Bind(wx.EVT_MENU, self.onCenterTrackball,
                  self.menuView.Append(wx.ID_ANY, "&Center Model"))
        self.menuBar.Check(ID_ENABLE_GRID, True)

        # Create menu
        self.menuCreate = wx.Menu()
        self.menuBar.Append(self.menuCreate, "&Create")
        self.Bind(wx.EVT_MENU, self.onCreateObject,
                  self.menuCreate.Append(ID_MODEL, "&Model..."))
        self.Bind(wx.EVT_MENU, self.onCreateObject,
                  self.menuCreate.Append(ID_TERRAIN, "&Terrain..."))

        self.menuCreateLight = wx.Menu()
        self.Bind(wx.EVT_MENU, self.onCreateObject,
                  self.menuCreateLight.Append(ID_AMBIENT, "&Ambient"))
        self.Bind(wx.EVT_MENU, self.onCreateObject,
                  self.menuCreateLight.Append(ID_DIRECTIONAL, "&Directional"))
        self.Bind(wx.EVT_MENU, self.onCreateObject,
                  self.menuCreateLight.Append(ID_POINT, "&Point"))
        self.Bind(wx.EVT_MENU, self.onCreateObject,
                  self.menuCreateLight.Append(ID_SPOT, "&Spotlight"))
        self.menuCreate.AppendSubMenu(self.menuCreateLight, "&Light")

        # Viewports menu
        self.menuViewports = wx.Menu()
        self.menuBar.Append(self.menuViewports, "View&ports")
        self.Bind(
            wx.EVT_MENU, self.onChangeViewports,
            self.menuViewports.AppendRadioItem(ID_SINGLE_VIEWPORT,
                                               "&Single Viewport"))
        self.Bind(wx.EVT_MENU, self.onChangeViewports,
                  self.menuViewports.AppendRadioItem(ID_4x4_GRID, "4x4 &Grid"))
        self.Bind(
            wx.EVT_MENU, self.onChangeViewports,
            self.menuViewports.AppendRadioItem(ID_2_HORIZONTAL,
                                               "2 &Horizontal"))
        self.Bind(
            wx.EVT_MENU, self.onChangeViewports,
            self.menuViewports.AppendRadioItem(ID_2_VERTICAL, "2 &Vertical"))
        self.menuBar.Check(ID_SINGLE_VIEWPORT, True)
        self.menuViewports.AppendSeparator()
        self.viewportMenus = []

    def createInterface(self):
        """Overridden from WxAppShell.py."""
        self.CreateStatusBar()
        self.SetStatusText("Welcome to the Panda3D Editor")
        self.Update()

    def initialize(self):
        """Initializes the viewports and editor."""
        print "I: EditorApp.initialize"
        self.Update()
        ViewportManager.updateAll()
        self.wxStep()
        ViewportManager.initializeAll()
        self.reloadViewportMenus()
        #self.editorInstance.toggle("WorldEditMode")
        # Position the camera
        if base.trackball != None:
            base.trackball.node().setPos(0, 30, 0)
            base.trackball.node().setHpr(0, 15, 0)

        # Load the direct things
        self.grid = DirectGrid(parent=render)
        self.sceneGraphTree.reload()
        if not isinstance(self.view, Viewport):
            self.view.center()

        # Initialize some stuff
        self.editorInstance.toggleEditmode(True)

    def reloadViewportMenus(self):
        """Reloads the viewport menus."""
        # Add the individual viewport menus
        for m in self.viewportMenus:
            m.Destroy()
        self.viewportMenus = []
        collect()
        #for v in range(len(ViewportManager.viewports)):
        #  self.viewportMenus.append(self.menuViewports.AppendSubMenu(ViewportMenu(ViewportManager.viewports[v]), "Viewport %d" % (v + 1)))

    def wxStep(self, task=None):
        """A step in the WX event loop. You can either call this yourself or use as task."""
        while self.evtLoop.Pending():
            self.evtLoop.Dispatch()
        self.wxApp.ProcessIdle()
        if task != None: return task.cont

    def onModelSelect(self, model):
        """Invoked when a model is selected. Shows/hides the texture panel."""
        if model == None and self.splitter1.IsSplit():
            self.splitter1.Unsplit()
        elif model != None and not self.splitter1.IsSplit():
            assert self.splitter1.SplitVertically(self.splitter2,
                                                  self.textureManager, -200)

    def onDestroy(self, evt):
        """Invoked when the window is destroyed."""
        wx.EventLoop.SetActive(self.oldLoop)

    def onNew(self, evt=None):
        self.filename = Filename()
        self.modified = True
        self.SetTitle("Panda Editor")
        self.editorInstance.destroyAllModels()

    def onOpen(self, evt=None):
        filter = "Panda3D Egg Scene Format (*.egs)|*.[eE][gG][sS];*.egs"
        #filter += "|Panda3D Compressed Egg Format (*.egg.pz)|*.[eE][gG][gG].[pP][zZ];*.egg.pz"
        #filter += "|Panda3D Binary Format (*.bam)|*.[bB][aA][mM];*.bam"
        #filter += "|Panda3D Compressed Binary Format (*.bam)|*.[bB][aA][mM].[pP][zZ];*.bam.pz"
        ''' # disabled by hypnos, making the loading work
    filter += "|MultiGen (*.flt)|*.[fF][lL][tT]"
    filter += "|Lightwave (*.lwo)|*.[lL][wW][oO]"
    filter += "|AutoCAD (*.dxf)|*.[dD][xX][fF]"
    filter += "|VRML (*.wrl)|*.[wW][rR][lL]"
    filter += "|DirectX (*.x)|*.[xX]"
    filter += "|COLLADA (*.dae)|*.[dD][aA][eE]" '''
        dlg = wx.FileDialog(self, "Load file", "", "", filter, wx.OPEN)
        try:
            if dlg.ShowModal() == wx.ID_OK:
                #self.filename = Filename.fromOsSpecific(dlg.GetPath())
                p3dFilename = Filename.fromOsSpecific(dlg.GetPath())
                self.filename = str(dlg.GetPath())
                self.SetTitle(p3dFilename.getBasename() + " - Panda Editor")
                self.modified = False
                self.editorInstance.loadEggModelsFile(self.filename)
                # Reset the camera
                if base.trackball != None:
                    base.trackball.node().setPos(0, 30, 0)
                    base.trackball.node().setHpr(0, 15, 0)
                self.onCenterTrackball()
                if p3dFilename.getExtension().lower() != "bam":
                    self.filename = Filename()
                    self.modified = True
                self.sceneGraphTree.reload()
        finally:
            dlg.Destroy()

    def onSave(self, evt=None):
        if not self.modified: return
        if self.filename == None or self.filename.empty():
            self.onSaveAs(evt)
        else:
            dlg = wx.FileDialog(
                self, "Save file", "", "",
                "Panda3D Binary Format (*.bam)|.[bB][aA][mM];*.bam",
                wx.SAVE | wx.FD_OVERWRITE_PROMPT)
            try:
                if dlg.ShowModal() == wx.ID_OK:
                    self.filename = Filename.fromOsSpecific(dlg.GetPath())
                    self.SetTitle(self.filename.getBasename() +
                                  " - Panda Editor")
                    self.modified = False
                    self.editorInstance.saveEggModelsFile(
                        self.filename.getFullpath())
            finally:
                dlg.Destroy()

    def onSaveAs(self, evt=None):
        dlg = wx.FileDialog(
            self, "Save file as", "", "",
            "Panda3D Binary Format (*.bam)|.[bB][aA][mM];*.bam",
            wx.SAVE | wx.FD_OVERWRITE_PROMPT)
        try:
            if dlg.ShowModal() == wx.ID_OK:
                self.onNew()
                self.filename = Filename.fromOsSpecific(dlg.GetPath())
                self.SetTitle(self.filename.getBasename() + " - Panda Editor")
                self.modified = False
                self.editorInstance.saveEggModelsFile(
                    self.filename.getFullpath())
        finally:
            dlg.Destroy()

    def onCreateObject(self, e):
        """Invoked when the user hits one of the buttons in the "Create" menu."""

        modelParent = modelController.getSelectedObject()
        if modelParent == None: modelParent = render
        objectInstance = None
        if e.Id == ID_NODEPATH:
            objectInstance = NodePathWrapper.onCreateInstance(modelParent)
        elif e.Id == ID_MODEL:
            filter = "Panda3D Egg Format (*.egg)|*.[eE][gG][gG];*.egg"
            filter += "|Panda3D Binary Format (*.bam)|*.[bB][aA][mM];*.bam"
            filter += "|MultiGen (*.flt)|*.[fF][lL][tT];*.flt"
            filter += "|Lightwave (*.lwo)|*.[lL][wW][oO];*.lwo"
            filter += "|AutoCAD (*.dxf)|*.[dD][xX][fF];*.dxf"
            filter += "|VRML (*.wrl)|*.[wW][rR][lL];*.wrl"
            filter += "|DirectX (*.x)|*.[xX];*.x"
            filter += "|COLLADA (*.dae)|*.[dD][aA][eE];*.dae"
            dlg = wx.FileDialog(self, "Select model", "", "", filter, wx.OPEN)
            try:
                if dlg.ShowModal() == wx.ID_OK:
                    objectInstance = NodePathWrapper.onCreateInstance(
                        modelParent,
                        Filename.fromOsSpecific(dlg.GetPath()).getFullpath())
            finally:
                dlg.Destroy()
        elif e.Id == ID_TERRAIN:
            filter = "Portable Network Graphics (*.png)|*.[pP][nN][gG];*.png"
            dlg = wx.FileDialog(self, "Select heightfield", "", "", filter,
                                wx.OPEN)
            try:
                if dlg.ShowModal() == wx.ID_OK:
                    objectInstance = GeoMipTerrainNodeWrapper.onCreateInstance(
                        modelParent,
                        Filename.fromOsSpecific(dlg.GetPath()).getFullpath())
            finally:
                dlg.Destroy()
        elif e.Id == ID_AMBIENT:
            objectInstance = AmbientLightNodeWrapper.onCreateInstance(
                modelParent)
        elif e.Id == ID_DIRECTIONAL:
            objectInstance = DirectionalLightNodeWrapper.onCreateInstance(
                modelParent)
        elif e.Id == ID_POINT:
            objectInstance = PointLightNodeWrapper.onCreateInstance(
                modelParent)
        elif e.Id == ID_SPOT:
            objectInstance = SpotLightNodeWrapper.onCreateInstance(modelParent)

        if objectInstance != None:
            objectInstance.reparentTo(modelParent)
            objectInstance.enableEditmode()
            modelController.selectObject(objectInstance)
            messenger.send(EVENT_SCENEGRAPH_REFRESH)

    def onChangeViewports(self, e):
        """Invoked when the user changes viewport layout."""
        self.Update()
        sashpos = self.splitter2.GetSashPosition()
        if e.Id == ID_SINGLE_VIEWPORT:
            if isinstance(self.view, Viewport): return
            self.view.close()
            self.view = Viewport.makePerspective(self.splitter2)
        elif e.Id == ID_4x4_GRID:
            if isinstance(self.view, ViewportGrid): return
            self.view.close()
            self.view = ViewportGrid(
                self.splitter2, [[Viewport.VPTOP, Viewport.VPFRONT],
                                 [Viewport.VPLEFT, Viewport.VPPERSPECTIVE]])
            self.view.center()
        else:
            if e.Id == ID_2_HORIZONTAL: orientation = wx.SPLIT_HORIZONTAL
            elif e.Id == ID_2_VERTICAL: orientation = wx.SPLIT_VERTICAL
            else: return
            if isinstance(self.view, ViewportSplitter) and not isinstance(
                    self.view, ViewportGrid):
                if self.view.GetSplitMode() == orientation: return
                self.view.close()
                self.view.split(Viewport.VPTOP, Viewport.VPPERSPECTIVE,
                                orientation)
            else:
                self.view.close()
                self.view = ViewportSplitter(self.splitter2, Viewport.VPTOP,
                                             Viewport.VPPERSPECTIVE,
                                             orientation)
        self.splitter2.Unsplit()
        assert self.splitter2.SplitVertically(self.leftBarSplitter, self.view,
                                              sashpos)
        # Reload the menus
        collect()
        self.reloadViewportMenus()
        # Make sure the viewports are initialized correctly
        self.Update()
        ViewportManager.updateAll()
        self.wxStep()
        ViewportManager.initializeAll()
        messenger.send("window-event")

    def onToggleGrid(self, evt=None):
        """Toggles the grid on/off."""
        if evt.GetEventObject().IsChecked(ID_ENABLE_GRID):
            self.grid.enable()
        else:
            self.grid.disable()

    def onCenterTrackball(self, evt=None):
        """Center the trackball, like 'c' does in pview."""
        gbv = render.getBounds()
        # Determine the bounding sphere around the object.
        if gbv.isInfinite(): return
        if gbv.isEmpty(): return

        # The BoundingVolume might be a sphere (it's likely), but since it
        # might not, we'll take no chances and make our own sphere.
        sphere = BoundingSphere(gbv.getApproxCenter(), 0.0)
        if (not sphere.extendBy(gbv)): return

        radius = 50.0

        # Loop through the windows/viewports
        for w in WindowManager.windows:
            # Choose a suitable distance to view the whole volume in our frame.
            # This is based on the camera lens in use.
            fov = w.camLens.getFov()
            distance = radius / tan(deg2Rad(min(fov[0], fov[1]) / 2.0))

            # Ensure the far plane is far enough back to see the entire object.
            idealFarPlane = distance + radius * 1.5
            w.camLens.setFar(max(w.camLens.getDefaultFar(), idealFarPlane))

            # And that the near plane is far enough forward.
            w.camLens.setNear(
                min(w.camLens.getDefaultNear(), radius - sphere.getRadius()))

            w.trackball.node().setOrigin(sphere.getCenter())
            w.trackball.node().setPos(Vec3.forward() * distance)

            # Also set the movement scale on the trackball to be consistent
            # with the size of the model and the lens field-of-view.
            w.trackball.node().setForwardScale(distance * 0.006)