Преглед на файлове

*** empty log message ***

David Rose преди 17 години
родител
ревизия
0ce8a5861d
променени са 2 файла, в които са добавени 260 реда и са изтрити 9 реда
  1. 245 0
      direct/src/showbase/MakeAppMF.py
  2. 15 9
      direct/src/showbase/RunAppMF.py

+ 245 - 0
direct/src/showbase/MakeAppMF.py

@@ -0,0 +1,245 @@
+""" This module will pack a Panda application, consisting of a
+directory tree of .py files and models, into a multifile for
+distribution and running with RunAppMF.py.  To run it, use:
+
+python MakeAppMF.py app.mf [application_root]
+
+where application_root is the root directory of the application.  If
+it is omitted, the default is the current directory.  The root
+directory should contain at a file named main.py, which is imported to
+start the application.  If main.py contains a function called main(),
+this function is called after importing it; otherwise, taskMgr.run()
+is called.
+"""
+
+import sys
+import direct
+from pandac.PandaModules import *
+
+vfs = VirtualFileSystem.getGlobalPtr()
+
+class ArgumentError(AttributeError):
+    pass
+
+class AppPacker:
+
+    compression_level = 6
+    imported_maps = 'imported_maps'
+
+    # Text files that are copied (and compressed) to the multifile
+    # without processing.
+    text_extensions = [ 'prc' ]
+
+    # Binary files that are copied and compressed without processing.
+    binary_extensions = [ 'ttf', 'wav', 'mid' ]
+
+    # Binary files that are considered uncompressible, and are copied
+    # without compression.
+    uncompressible_extensions = [ 'mp3' ]
+
+    def __init__(self, multifile_name):
+        # Make sure any pre-existing file is removed.
+        Filename(multifile_name).unlink()
+            
+        self.multifile = Multifile()
+        if not self.multifile.openReadWrite(multifile_name):
+            raise ArgumentError, 'Could not open %s for writing' % (multifile_name)
+        self.imported_textures = {}
+
+        # Get the list of filename extensions that are recognized as
+        # image files.
+        self.image_extensions = []
+        for type in PNMFileTypeRegistry.getGlobalPtr().getTypes():
+            self.image_extensions += type.getExtensions()
+
+    def scan(self, root):
+        self.root = Filename(root)
+        self.root.makeAbsolute(vfs.getCwd())
+        self._recurse(self.root)
+
+        self.multifile.repack()
+
+    def _recurse(self, filename):
+        dirList = vfs.scanDirectory(filename)
+        if dirList:
+            # It's a directory name.  Recurse.
+            for subfile in dirList:
+                self._recurse(subfile.getFilename())
+            return
+
+        # It's a real file.  Is it something we care about?
+        ext = filename.getExtension().lower()
+        outFilename = filename
+        if ext == 'pz':
+            # Strip off an implicit .pz extension.
+            outFilename = Filename(filename)
+            outFilename.setExtension('')
+            outFilename = Filename(outFilename.cStr())
+            ext = outFilename.getExtension().lower()
+            
+        if ext == 'py':
+            self.addPyFile(filename)
+        elif ext == 'egg':
+            self.addEggFile(filename, outFilename)
+        elif ext == 'bam':
+            self.addBamFile(filename, outFilename)
+        elif ext in self.image_extensions:
+            self.addTexture(filename)
+        elif ext in self.text_extensions:
+            self.addTextFile(filename)
+        elif ext in self.binary_extensions:
+            self.addBinaryFile(filename)
+        elif ext in self.uncompressible_extensions:
+            self.addUncompressibleFile(filename)
+
+    def addPyFile(self, filename):
+        # For now, just add it as an ordinary file.  Later we'll
+        # precompile these to .pyo's.
+        self.addTextFile(filename)
+
+    def addEggFile(self, filename, outFilename):
+        # Precompile egg files to bam's.
+        node = loadEggFile(filename)
+        if not node:
+            raise StandardError, 'Could not read egg file %s' % (filename)
+
+        self.addNode(node, outFilename)
+
+    def addBamFile(self, filename, outFilename):
+        # Load the bam file so we can massage its textures.
+        bamFile = BamFile()
+        if not bamFile.openRead(filename):
+            raise StandardError, 'Could not read bam file %s' % (filename)
+
+        if not bamFile.resolve():
+            raise StandardError, 'Could not resolve bam file %s' % (filename)
+
+        node = bamFile.readNode()
+        if not node:
+            raise StandardError, 'Not a model file: %s' % (filename)
+            
+        self.addNode(node, outFilename)
+
+    def addNode(self, node, filename):
+        """ Converts the indicated node to a bam stream, and adds the
+        bam file to the multifile under the indicated filename. """
+        
+        # Be sure to import all of the referenced textures, and tell
+        # them their new location within the multifile.
+        
+        for tex in NodePath(node).findAllTextures():
+            tex.setFilename(self.addTexture(tex.getFullpath()))
+            if tex.hasAlphaFilename():
+                tex.setAlphaFilename(self.addTexture(tex.getAlphaFullpath()))
+                
+        # Now generate an in-memory bam file.  Tell the bam writer to
+        # keep the textures referenced by their in-multifile path.
+        bamFile = BamFile()
+        stream = StringStream()
+        bamFile.openWrite(stream)
+        bamFile.getWriter().setFileTextureMode(BTMUnchanged)
+        bamFile.writeObject(node)
+        bamFile.close()
+        node = None
+
+        # Now we have an in-memory bam file.
+        rel = self.makeRelFilename(filename)
+        rel.setExtension('bam')
+        stream.seekg(0)
+        self.multifile.addSubfile(rel.cStr(), stream, self.compression_level)
+
+        # Flush it so the data gets written to disk immediately, so we
+        # don't have to keep it around in ram.
+        self.multifile.flush()
+    
+    def addTexture(self, filename):
+        """ Adds the texture to the multifile, if it has not already
+        been added.  If it is not within the root directory, copies it
+        in (virtually) into a directory within the multifile named
+        imported_maps.  Returns the new filename within the
+        multifile. """
+
+        assert not filename.isLocal()
+
+        filename = Filename(filename)
+        filename.makeAbsolute(vfs.getCwd())
+        filename.makeTrueCase()
+
+        rel = self.imported_textures.get(filename.cStr())
+        if not rel:
+            rel = Filename(filename)
+            if not rel.makeRelativeTo(self.root, False):
+                # Not within the multifile.
+                rel = Filename(self.imported_maps, filename.getBasename())
+
+            # Need to add it now.
+            self.imported_textures[filename.cStr()] = rel
+            filename = Filename(filename)
+            filename.setBinary()
+            self.multifile.addSubfile(rel.cStr(), filename, 0)
+
+        return rel
+
+    def mapTextureFilename(self, filename):
+        """ Returns the filename within the multifile of the
+        already-added texture. """
+
+        filename = Filename(filename)
+        filename.makeAbsolute(vfs.getCwd())
+        filename.makeTrueCase()
+
+        return self.imported_textures[filename.cStr()]
+
+    def addTextFile(self, filename):
+        """ Adds a generic text file to the multifile. """
+
+        rel = self.makeRelFilename(filename)
+        filename = Filename(filename)
+        filename.setText()
+        self.multifile.addSubfile(rel.cStr(), filename, self.compression_level)
+
+    def addBinaryFile(self, filename):
+        """ Adds a generic binary file to the multifile. """
+
+        rel = self.makeRelFilename(filename)
+        filename = Filename(filename)
+        filename.setBinary()
+        self.multifile.addSubfile(rel.cStr(), filename, self.compression_level)
+
+    def addUncompressibleFile(self, filename):
+        """ Adds a generic binary file to the multifile, without compression. """
+
+        rel = self.makeRelFilename(filename)
+        filename = Filename(filename)
+        filename.setBinary()
+        self.multifile.addSubfile(rel.cStr(), filename, 0)
+
+    def makeRelFilename(self, filename):
+        """ Returns the same filename, relative to self.root """
+        rel = Filename(filename)
+        result = rel.makeRelativeTo(self.root, False)
+        assert result
+        return rel
+
+
+def makePackedApp(args):
+    if not args:
+        raise ArgumentError, "No destination app specified.  Use:\npython MakeAppMF.py app.mf"
+
+    multifile_name = args[0]
+    root = '.'
+    if len(args) > 1:
+        root = args[1]
+
+    if len(args) > 2:
+        raise ArgumentError, "Too many arguments."
+
+    p = AppPacker(multifile_name)
+    p.scan(root)
+
+if __name__ == '__main__':
+    try:
+        makePackedApp(sys.argv[1:])
+    except ArgumentError, e:
+        print e.args[0]
+        sys.exit(1)

+ 15 - 9
direct/src/showbase/StartMF.py → direct/src/showbase/RunAppMF.py

@@ -1,12 +1,14 @@
 """ This module can serve as a startup script to play a Panda
 """ This module can serve as a startup script to play a Panda
 application packed within a .mf file.  Run via:
 application packed within a .mf file.  Run via:
 
 
-python StartMF.py app.mf
+python RunAppMF.py app.mf
 
 
 This is currently experimental, but the intent is to work towards a
 This is currently experimental, but the intent is to work towards a
 prepackaged Panda distributable, designed to run applications exactly
 prepackaged Panda distributable, designed to run applications exactly
 like this.
 like this.
 
 
+Also see MakeAppMF.py.
+
 """
 """
 
 
 import sys
 import sys
@@ -17,23 +19,22 @@ import __builtin__
 
 
 MultifileRoot = '/mf'
 MultifileRoot = '/mf'
 
 
+class ArgumentError(AttributeError):
+    pass
+
 def runPackedApp(args):
 def runPackedApp(args):
     if not args:
     if not args:
-        print "No Panda app specified.  Use:"
-        print "python StartMF.py app.mf"
-        sys.exit(1)
+        raise ArgumentError, "No Panda app specified.  Use:\npython RunAppMF.py app.mf"
 
 
     vfs = VirtualFileSystem.getGlobalPtr()
     vfs = VirtualFileSystem.getGlobalPtr()
 
 
     fname = Filename.fromOsSpecific(args[0])
     fname = Filename.fromOsSpecific(args[0])
     if not vfs.exists(fname):
     if not vfs.exists(fname):
-        print "No such file: %s" % (args[0])
-        sys.exit(1)
+        raise ArgumentError, "No such file: %s" % (args[0])
 
 
     mf = Multifile()
     mf = Multifile()
     if not mf.openRead(fname):
     if not mf.openRead(fname):
-        print "Not a Panda Multifile: %s" % (args[0])
-        sys.exit(1)
+        raise ArgumentError, "Not a Panda Multifile: %s" % (args[0])
 
 
     # Clear *all* the mount points, including "/", so that we no
     # Clear *all* the mount points, including "/", so that we no
     # longer access the disk directly.
     # longer access the disk directly.
@@ -78,5 +79,10 @@ def runPackedApp(args):
     import main
     import main
 
 
 if __name__ == '__main__':
 if __name__ == '__main__':
-    runPackedApp(sys.argv[1:])
+    try:
+        runPackedApp(sys.argv[1:])
+    except ArgumentError, e:
+        print e.args[0]
+        sys.exit(1)
+