*** empty log message ***

This commit is contained in:
David Rose 2008-11-11 23:27:19 +00:00
parent 992d7a7a58
commit 0ce8a5861d
2 changed files with 260 additions and 9 deletions

View File

@ -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)

View File

@ -1,12 +1,14 @@
""" This module can serve as a startup script to play a Panda
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
prepackaged Panda distributable, designed to run applications exactly
like this.
Also see MakeAppMF.py.
"""
import sys
@ -17,23 +19,22 @@ import __builtin__
MultifileRoot = '/mf'
class ArgumentError(AttributeError):
pass
def runPackedApp(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()
fname = Filename.fromOsSpecific(args[0])
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()
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
# longer access the disk directly.
@ -78,5 +79,10 @@ def runPackedApp(args):
import main
if __name__ == '__main__':
runPackedApp(sys.argv[1:])
try:
runPackedApp(sys.argv[1:])
except ArgumentError, e:
print e.args[0]
sys.exit(1)