示例#1
0
class Sprite2d:
    class Cell:
        def __init__(self, col, row):
            self.col = col
            self.row = row

        def __str__(self):
            return "Cell - Col %d, Row %d" % (self.col, self.row)

    class Animation:
        def __init__(self, cells, fps):
            self.cells = cells
            self.fps = fps
            self.playhead = 0

    ALIGN_CENTER = "Center"
    ALIGN_LEFT = "Left"
    ALIGN_RIGHT = "Right"
    ALIGN_BOTTOM = "Bottom"
    ALIGN_TOP = "Top"

    TRANS_ALPHA = TransparencyAttrib.MAlpha
    TRANS_DUAL = TransparencyAttrib.MDual
    # One pixel is divided by this much. If you load a 100x50 image with PIXEL_SCALE of 10.0
    # you get a card that is 1 unit wide, 0.5 units high
    PIXEL_SCALE = 20.0

    def __init__(self, image_path, rowPerFace, name=None,\
         rows=1, cols=1, scale=1.0,\
         twoSided=False, alpha=TRANS_ALPHA,\
         repeatX=1, repeatY=1,\
         anchorX=ALIGN_CENTER, anchorY=ALIGN_BOTTOM):
        """
		Create a card textured with an image. The card is sized so that the ratio between the
		card and image is the same.
		"""

        global SpriteId
        self.spriteNum = str(SpriteId)
        SpriteId += 1

        scale *= self.PIXEL_SCALE

        self.animations = {}

        self.scale = scale
        self.repeatX = repeatX
        self.repeatY = repeatY
        self.flip = {'x': False, 'y': False}
        self.rows = rows
        self.cols = cols

        self.currentFrame = 0
        self.currentAnim = None
        self.loopAnim = False
        self.frameInterrupt = True

        # Create the NodePath
        if name:
            self.node = NodePath("Sprite2d:%s" % name)
        else:
            self.node = NodePath("Sprite2d:%s" % image_path)

        # Set the attribute for transparency/twosided
        self.node.node().setAttrib(TransparencyAttrib.make(alpha))
        if twoSided:
            self.node.setTwoSided(True)

        # Make a filepath
        self.imgFile = Filename(image_path)
        if self.imgFile.empty():
            raise IOError, "File not found"

        # Instead of loading it outright, check with the PNMImageHeader if we can open
        # the file.
        imgHead = PNMImageHeader()
        if not imgHead.readHeader(self.imgFile):
            raise IOError, "PNMImageHeader could not read file. Try using absolute filepaths"

        # Load the image with a PNMImage
        image = PNMImage()
        image.read(self.imgFile)

        self.sizeX = image.getXSize()
        self.sizeY = image.getYSize()

        # We need to find the power of two size for the another PNMImage
        # so that the texture thats loaded on the geometry won't have artifacts
        textureSizeX = self.nextsize(self.sizeX)
        textureSizeY = self.nextsize(self.sizeY)

        # The actual size of the texture in memory
        self.realSizeX = textureSizeX
        self.realSizeY = textureSizeY

        self.paddedImg = PNMImage(textureSizeX, textureSizeY)
        if image.hasAlpha():
            self.paddedImg.alphaFill(0)
        # Copy the source image to the image we're actually using
        self.paddedImg.blendSubImage(image, 0, 0)
        # We're done with source image, clear it
        image.clear()

        # The pixel sizes for each cell
        self.colSize = self.sizeX / self.cols
        self.rowSize = self.sizeY / self.rows

        # How much padding the texture has
        self.paddingX = textureSizeX - self.sizeX
        self.paddingY = textureSizeY - self.sizeY

        # Set UV padding
        self.uPad = float(self.paddingX) / textureSizeX
        self.vPad = float(self.paddingY) / textureSizeY

        # The UV dimensions for each cell
        self.uSize = (1.0 - self.uPad) / self.cols
        self.vSize = (1.0 - self.vPad) / self.rows

        self.cards = []
        self.rowPerFace = rowPerFace
        for i in range(len(rowPerFace)):
            card = CardMaker("Sprite2d-Geom")

            # The positions to create the card at
            if anchorX == self.ALIGN_LEFT:
                posLeft = 0
                posRight = (self.colSize / scale) * repeatX
            elif anchorX == self.ALIGN_CENTER:
                posLeft = -(self.colSize / 2.0 / scale) * repeatX
                posRight = (self.colSize / 2.0 / scale) * repeatX
            elif anchorX == self.ALIGN_RIGHT:
                posLeft = -(self.colSize / scale) * repeatX
                posRight = 0

            if anchorY == self.ALIGN_BOTTOM:
                posTop = 0
                posBottom = (self.rowSize / scale) * repeatY
            elif anchorY == self.ALIGN_CENTER:
                posTop = -(self.rowSize / 2.0 / scale) * repeatY
                posBottom = (self.rowSize / 2.0 / scale) * repeatY
            elif anchorY == self.ALIGN_TOP:
                posTop = -(self.rowSize / scale) * repeatY
                posBottom = 0

            card.setFrame(posLeft, posRight, posTop, posBottom)
            card.setHasUvs(True)
            self.cards.append(self.node.attachNewNode(card.generate()))
            self.cards[-1].setH(i * 360 / len(rowPerFace))

        # Since the texture is padded, we need to set up offsets and scales to make
        # the texture fit the whole card
        self.offsetX = (float(self.colSize) / textureSizeX)
        self.offsetY = (float(self.rowSize) / textureSizeY)

        # self.node.setTexScale(TextureStage.getDefault(), self.offsetX * repeatX, self.offsetY * repeatY)
        # self.node.setTexOffset(TextureStage.getDefault(), 0, 1-self.offsetY)

        self.texture = Texture()

        self.texture.setXSize(textureSizeX)
        self.texture.setYSize(textureSizeY)
        self.texture.setZSize(1)

        # Load the padded PNMImage to the texture
        self.texture.load(self.paddedImg)

        self.texture.setMagfilter(Texture.FTNearest)
        self.texture.setMinfilter(Texture.FTNearest)

        #Set up texture clamps according to repeats
        if repeatX > 1:
            self.texture.setWrapU(Texture.WMRepeat)
        else:
            self.texture.setWrapU(Texture.WMClamp)
        if repeatY > 1:
            self.texture.setWrapV(Texture.WMRepeat)
        else:
            self.texture.setWrapV(Texture.WMClamp)

        self.node.setTexture(self.texture)
        self.setFrame(0)

    def nextsize(self, num):
        """ Finds the next power of two size for the given integer. """
        p2x = max(1, log(num, 2))
        notP2X = modf(p2x)[0] > 0
        return 2**int(notP2X + p2x)

    def setFrame(self, frame=0):
        """ Sets the current sprite to the given frame """
        self.frameInterrupt = True  # A flag to tell the animation task to shut it up ur face
        self.currentFrame = frame
        self.flipTexture()

    def playAnim(self, animName, loop=False):
        """ Sets the sprite to animate the given named animation. Booleon to loop animation"""
        if not taskMgr.hasTaskNamed("Animate sprite" + self.spriteNum):
            if hasattr(self, "task"):
                taskMgr.remove("Animate sprite" + self.spriteNum)
                del self.task
            self.frameInterrupt = False  # Clear any previous interrupt flags
            self.loopAnim = loop
            self.currentAnim = self.animations[animName]
            self.currentAnim.playhead = 0
            self.task = taskMgr.doMethodLater(
                1.0 / self.currentAnim.fps, self.animPlayer,
                "Animate sprite" + self.spriteNum)

    def createAnim(self, animName, frameCols, fps=12):
        """ Create a named animation. Takes the animation name and a tuple of frame numbers """
        self.animations[animName] = Sprite2d.Animation(frameCols, fps)
        return self.animations[animName]

    def flipX(self, val=None):
        """ Flip the sprite on X. If no value given, it will invert the current flipping."""
        if val:
            self.flip['x'] = val
        else:
            if self.flip['x']:
                self.flip['x'] = False
            else:
                self.flip['x'] = True
        self.flipTexture()
        return self.flip['x']

    def flipY(self, val=None):
        """ See flipX """
        if val:
            self.flip['y'] = val
        else:
            if self.flip['y']:
                self.flip['y'] = False
            else:
                self.flip['y'] = True
        self.flipTexture()
        return self.flip['y']

    def updateCameraAngle(self, cameraNode):
        baseH = cameraNode.getH(render) - self.node.getH(render)
        degreesBetweenCards = 360 / len(self.cards)
        bestCard = int(
            ((baseH) + degreesBetweenCards / 2) % 360 / degreesBetweenCards)
        #print baseH, bestCard
        for i in range(len(self.cards)):
            if i == bestCard:
                self.cards[i].show()
            else:
                self.cards[i].hide()

    def flipTexture(self):
        """ Sets the texture coordinates of the texture to the current frame"""
        for i in range(len(self.cards)):
            currentRow = self.rowPerFace[i]

            sU = self.offsetX * self.repeatX
            sV = self.offsetY * self.repeatY
            oU = 0 + self.currentFrame * self.uSize
            #oU = 0 + self.frames[self.currentFrame].col * self.uSize
            #oV = 1 - self.frames[self.currentFrame].row * self.vSize - self.offsetY
            oV = 1 - currentRow * self.vSize - self.offsetY
            if self.flip['x'] ^ i == 1:  ##hack to fix side view
                #print "flipping, i = ",i
                sU *= -1
                #oU = self.uSize + self.frames[self.currentFrame].col * self.uSize
                oU = self.uSize + self.currentFrame * self.uSize
            if self.flip['y']:
                sV *= -1
                #oV = 1 - self.frames[self.currentFrame].row * self.vSize
                oV = 1 - currentRow * self.vSize
            self.cards[i].setTexScale(TextureStage.getDefault(), sU, sV)
            self.cards[i].setTexOffset(TextureStage.getDefault(), oU, oV)

    def clear(self):
        """ Free up the texture memory being used """
        self.texture.clear()
        self.paddedImg.clear()
        self.node.removeNode()

    def animPlayer(self, task):
        if self.frameInterrupt:
            return task.done
        #print "Playing",self.currentAnim.cells[self.currentAnim.playhead]
        self.currentFrame = self.currentAnim.cells[self.currentAnim.playhead]
        self.flipTexture()
        if self.currentAnim.playhead + 1 < len(self.currentAnim.cells):
            self.currentAnim.playhead += 1
            return task.again
        if self.loopAnim:
            self.currentAnim.playhead = 0
            return task.again
示例#2
0
class Sprite2d:

	class Cell:
		def __init__(self, col, row):
			self.col = col
			self.row = row

		def __str__(self):
			return "Cell - Col %d, Row %d" % (self.col, self.row)

	class Animation:
		def __init__(self, cells, fps):
			self.cells = cells
			self.fps = fps
			self.playhead = 0

	ALIGN_CENTER = "Center"
	ALIGN_LEFT = "Left"
	ALIGN_RIGHT = "Right"
	ALIGN_BOTTOM = "Bottom"
	ALIGN_TOP = "Top"

	TRANS_ALPHA = TransparencyAttrib.MAlpha
	TRANS_DUAL = TransparencyAttrib.MDual
	# One pixel is divided by this much. If you load a 100x50 image with PIXEL_SCALE of 10.0
	# you get a card that is 1 unit wide, 0.5 units high
	PIXEL_SCALE = 20.0

	def __init__(self, image_path, rowPerFace, name=None,\
				  rows=1, cols=1, scale=1.0,\
				  twoSided=False, alpha=TRANS_ALPHA,\
				  repeatX=1, repeatY=1,\
				  anchorX=ALIGN_CENTER, anchorY=ALIGN_BOTTOM):
		"""
		Create a card textured with an image. The card is sized so that the ratio between the
		card and image is the same.
		"""

		global SpriteId
		self.spriteNum = str(SpriteId)
		SpriteId += 1

		scale *= self.PIXEL_SCALE

		self.animations = {}

		self.scale = scale
		self.repeatX = repeatX
		self.repeatY = repeatY
		self.flip = {'x':False,'y':False}
		self.rows = rows
		self.cols = cols

		self.currentFrame = 0
		self.currentAnim = None
		self.loopAnim = False
		self.frameInterrupt = True

		# Create the NodePath
		if name:
			self.node = NodePath("Sprite2d:%s" % name)
		else:
			self.node = NodePath("Sprite2d:%s" % image_path)

		# Set the attribute for transparency/twosided
		self.node.node().setAttrib(TransparencyAttrib.make(alpha))
		if twoSided:
			self.node.setTwoSided(True)

		# Make a filepath
		self.imgFile = Filename(image_path)
		if self.imgFile.empty():
			raise IOError, "File not found"

		# Instead of loading it outright, check with the PNMImageHeader if we can open
		# the file.
		imgHead = PNMImageHeader()
		if not imgHead.readHeader(self.imgFile):
			raise IOError, "PNMImageHeader could not read file. Try using absolute filepaths"

		# Load the image with a PNMImage
		image = PNMImage()
		image.read(self.imgFile)

		self.sizeX = image.getXSize()
		self.sizeY = image.getYSize()

		# We need to find the power of two size for the another PNMImage
		# so that the texture thats loaded on the geometry won't have artifacts
		textureSizeX = self.nextsize(self.sizeX)
		textureSizeY = self.nextsize(self.sizeY)

		# The actual size of the texture in memory
		self.realSizeX = textureSizeX
		self.realSizeY = textureSizeY

		self.paddedImg = PNMImage(textureSizeX, textureSizeY)
		if image.hasAlpha():
			self.paddedImg.alphaFill(0)
		# Copy the source image to the image we're actually using
		self.paddedImg.blendSubImage(image, 0, 0)
		# We're done with source image, clear it
		image.clear()

		# The pixel sizes for each cell
		self.colSize = self.sizeX/self.cols
		self.rowSize = self.sizeY/self.rows

		# How much padding the texture has
		self.paddingX = textureSizeX - self.sizeX
		self.paddingY = textureSizeY - self.sizeY

		# Set UV padding
		self.uPad = float(self.paddingX)/textureSizeX
		self.vPad = float(self.paddingY)/textureSizeY

		# The UV dimensions for each cell
		self.uSize = (1.0 - self.uPad) / self.cols
		self.vSize = (1.0 - self.vPad) / self.rows
	
		self.cards = []
		self.rowPerFace = rowPerFace
		for i in range(len(rowPerFace)):
			card = CardMaker("Sprite2d-Geom")

			# The positions to create the card at
			if anchorX == self.ALIGN_LEFT:
				posLeft = 0
				posRight = (self.colSize/scale)*repeatX
			elif anchorX == self.ALIGN_CENTER:
				posLeft = -(self.colSize/2.0/scale)*repeatX
				posRight = (self.colSize/2.0/scale)*repeatX
			elif anchorX == self.ALIGN_RIGHT:
				posLeft = -(self.colSize/scale)*repeatX
				posRight = 0

			if anchorY == self.ALIGN_BOTTOM:
				posTop = 0
				posBottom = (self.rowSize/scale)*repeatY
			elif anchorY == self.ALIGN_CENTER:
				posTop = -(self.rowSize/2.0/scale)*repeatY
				posBottom = (self.rowSize/2.0/scale)*repeatY
			elif anchorY == self.ALIGN_TOP:
				posTop = -(self.rowSize/scale)*repeatY
				posBottom = 0

			card.setFrame(posLeft, posRight, posTop, posBottom)
			card.setHasUvs(True)
			self.cards.append(self.node.attachNewNode(card.generate()))
			self.cards[-1].setH(i * 360/len(rowPerFace))

		# Since the texture is padded, we need to set up offsets and scales to make
		# the texture fit the whole card
		self.offsetX = (float(self.colSize)/textureSizeX)
		self.offsetY = (float(self.rowSize)/textureSizeY)

		# self.node.setTexScale(TextureStage.getDefault(), self.offsetX * repeatX, self.offsetY * repeatY)
		# self.node.setTexOffset(TextureStage.getDefault(), 0, 1-self.offsetY)
		
		self.texture = Texture()

		self.texture.setXSize(textureSizeX)
		self.texture.setYSize(textureSizeY)
		self.texture.setZSize(1)

		# Load the padded PNMImage to the texture
		self.texture.load(self.paddedImg)

		self.texture.setMagfilter(Texture.FTNearest)
		self.texture.setMinfilter(Texture.FTNearest)

		#Set up texture clamps according to repeats
		if repeatX > 1:
			self.texture.setWrapU(Texture.WMRepeat)
		else:
			self.texture.setWrapU(Texture.WMClamp)
		if repeatY > 1:
			self.texture.setWrapV(Texture.WMRepeat)
		else:
			self.texture.setWrapV(Texture.WMClamp)

		self.node.setTexture(self.texture)
		self.setFrame(0)

	def nextsize(self, num):
		""" Finds the next power of two size for the given integer. """
		p2x=max(1,log(num,2))
		notP2X=modf(p2x)[0]>0
		return 2**int(notP2X+p2x)

	def setFrame(self, frame=0):
		""" Sets the current sprite to the given frame """
		self.frameInterrupt = True # A flag to tell the animation task to shut it up ur face
		self.currentFrame = frame
		self.flipTexture()

	def playAnim(self, animName, loop=False):
		""" Sets the sprite to animate the given named animation. Booleon to loop animation"""
		if not taskMgr.hasTaskNamed("Animate sprite" + self.spriteNum):
			if hasattr(self, "task"):
					taskMgr.remove("Animate sprite" + self.spriteNum)
					del self.task
			self.frameInterrupt = False # Clear any previous interrupt flags
			self.loopAnim = loop
			self.currentAnim = self.animations[animName]
			self.currentAnim.playhead = 0
			self.task = taskMgr.doMethodLater(1.0/self.currentAnim.fps,self.animPlayer, "Animate sprite" + self.spriteNum)

	def createAnim(self, animName, frameCols, fps=12):
		""" Create a named animation. Takes the animation name and a tuple of frame numbers """
		self.animations[animName] = Sprite2d.Animation(frameCols, fps)
		return self.animations[animName]

	def flipX(self, val=None):
		""" Flip the sprite on X. If no value given, it will invert the current flipping."""
		if val:
			self.flip['x'] = val
		else:
			if self.flip['x']:
				self.flip['x'] = False
			else:
				self.flip['x'] = True
		self.flipTexture()
		return self.flip['x']

	def flipY(self, val=None):
		""" See flipX """
		if val:
			self.flip['y'] = val
		else:
			if self.flip['y']:
				self.flip['y'] = False
			else:
				self.flip['y'] = True
		self.flipTexture()
		return self.flip['y']

	def updateCameraAngle(self, cameraNode):
		baseH =  cameraNode.getH(render) - self.node.getH(render)
		degreesBetweenCards = 360/len(self.cards)
		bestCard = int(((baseH)+degreesBetweenCards/2)%360 / degreesBetweenCards)
		#print baseH, bestCard
		for i in range(len(self.cards)):
			if i == bestCard:
				self.cards[i].show()
			else:
				self.cards[i].hide()

	def flipTexture(self):
		""" Sets the texture coordinates of the texture to the current frame"""
		for i in range(len(self.cards)):
			currentRow = self.rowPerFace[i]

			sU = self.offsetX * self.repeatX
			sV = self.offsetY * self.repeatY
			oU = 0 + self.currentFrame * self.uSize
			#oU = 0 + self.frames[self.currentFrame].col * self.uSize
			#oV = 1 - self.frames[self.currentFrame].row * self.vSize - self.offsetY
			oV = 1 - currentRow * self.vSize - self.offsetY
			if self.flip['x'] ^ i==1: ##hack to fix side view
				#print "flipping, i = ",i
				sU *= -1
				#oU = self.uSize + self.frames[self.currentFrame].col * self.uSize
				oU = self.uSize + self.currentFrame * self.uSize
			if self.flip['y']:
				sV *= -1
				#oV = 1 - self.frames[self.currentFrame].row * self.vSize
				oV = 1 - currentRow * self.vSize
			self.cards[i].setTexScale(TextureStage.getDefault(), sU, sV)
			self.cards[i].setTexOffset(TextureStage.getDefault(), oU, oV)

	def clear(self):
		""" Free up the texture memory being used """
		self.texture.clear()
		self.paddedImg.clear()
		self.node.removeNode()

	def animPlayer(self, task):
		if self.frameInterrupt:
			return task.done
		#print "Playing",self.currentAnim.cells[self.currentAnim.playhead]
		self.currentFrame = self.currentAnim.cells[self.currentAnim.playhead]
		self.flipTexture()
		if self.currentAnim.playhead+1 < len(self.currentAnim.cells):
			self.currentAnim.playhead += 1
			return task.again
		if self.loopAnim:
			self.currentAnim.playhead = 0
			return task.again
class Installer:
    """ This class creates a (graphical) installer from a given .p3d file. """

    notify = directNotify.newCategory("Installer")

    def __init__(self, p3dfile, shortname, fullname, version, tokens={}):
        if not shortname:
            shortname = p3dfile.getBasenameWoExtension()
        self.shortname = shortname
        self.fullname = fullname
        self.version = str(version)
        self.includeRequires = False
        self.licensename = ""
        self.licensefile = Filename()
        self.authorid = "org.panda3d"
        self.authorname = os.environ.get("DEBFULLNAME", "")
        self.authoremail = os.environ.get("DEBEMAIL", "")

        # Try to determine a default author name ourselves.
        uname = None
        if pwd is not None and hasattr(os, "getuid"):
            uinfo = pwd.getpwuid(os.getuid())
            if uinfo:
                uname = uinfo.pw_name
                if not self.authorname:
                    self.authorname = uinfo.pw_gecos.split(",", 1)[0]

        # Fallbacks in case that didn't work or wasn't supported.
        if not uname:
            uname = getpass.getuser()
        if not self.authorname:
            self.authorname = uname
        if not self.authoremail and " " not in uname:
            self.authoremail = "%s@%s" % (uname, socket.gethostname())

        self.standalone = Standalone(p3dfile, tokens)
        self.tempDir = Filename.temporary("", self.shortname, "") + "/"
        self.tempDir.makeDir()
        self.__linuxRoot = None

        # Load the p3d file to read out the required packages
        mf = Multifile()
        if not mf.openRead(p3dfile):
            Installer.notify.error("Not a Panda3D application: %s" % (p3dFilename))
            return

        # Now load the p3dInfo file.
        self.hostUrl = PandaSystem.getPackageHostUrl()
        if not self.hostUrl:
            self.hostUrl = self.standalone.host.hostUrl
        self.requires = []
        i = mf.findSubfile("p3d_info.xml")
        if i >= 0:
            stream = mf.openReadSubfile(i)
            p3dInfo = readXmlStream(stream)
            mf.closeReadSubfile(stream)
            if p3dInfo:
                p3dPackage = p3dInfo.FirstChildElement("package")
                p3dHost = p3dPackage.FirstChildElement("host")
                if p3dHost.Attribute("url"):
                    self.hostUrl = p3dHost.Attribute("url")
                p3dRequires = p3dPackage.FirstChildElement("requires")
                while p3dRequires:
                    self.requires.append(
                        (p3dRequires.Attribute("name"), p3dRequires.Attribute("version"), p3dRequires.Attribute("host"))
                    )
                    p3dRequires = p3dRequires.NextSiblingElement("requires")

                if not self.fullname:
                    p3dConfig = p3dPackage.FirstChildElement("config")
                    if p3dConfig:
                        self.fullname = p3dConfig.Attribute("display_name")

        if not self.fullname:
            self.fullname = self.shortname

    def __del__(self):
        try:
            appRunner.rmtree(self.tempDir)
        except:
            try:
                shutil.rmtree(self.tempDir.toOsSpecific())
            except:
                pass

    def installPackagesInto(self, hostDir, platform):
        """ Installs the packages required by the .p3d file into
        the specified directory, for the given platform. """

        if not self.includeRequires:
            return

        pkgTree = PackageTree(platform, hostDir, self.hostUrl)
        pkgTree.installPackage("images", None, self.standalone.host.hostUrl)

        for name, version, hostUrl in self.requires:
            pkgTree.installPackage(name, version, hostUrl)

        # Remove the extracted files from the compressed archive, to save space.
        vfs = VirtualFileSystem.getGlobalPtr()
        for package in pkgTree.packages.values():
            if package.uncompressedArchive:
                archive = Filename(package.getPackageDir(), package.uncompressedArchive.filename)
                if not archive.exists():
                    continue

                mf = Multifile()
                # Make sure that it isn't mounted before altering it, just to be safe
                vfs.unmount(archive)
                os.chmod(archive.toOsSpecific(), 0644)
                if not mf.openReadWrite(archive):
                    Installer.notify.warning("Failed to open archive %s" % (archive))
                    continue

                # We don't iterate over getNumSubfiles because we're
                # removing subfiles while we're iterating over them.
                subfiles = mf.getSubfileNames()
                for subfile in subfiles:
                    # We do *NOT* call vfs.exists here in case the package is mounted.
                    if Filename(package.getPackageDir(), subfile).exists():
                        Installer.notify.debug("Removing already-extracted %s from multifile" % (subfile))
                        mf.removeSubfile(subfile)

                # This seems essential for mf.close() not to crash later.
                mf.repack()

                # If we have no subfiles left, we can just remove the multifile.
                # if mf.getNumSubfiles() == 0:
                #    Installer.notify.info("Removing empty archive %s" % (package.uncompressedArchive.filename))
                #    mf.close()
                #    archive.unlink()
                # else:
                mf.close()
                try:
                    os.chmod(archive.toOsSpecific(), 0444)
                except:
                    pass

        # Write out our own contents.xml file.
        doc = TiXmlDocument()
        decl = TiXmlDeclaration("1.0", "utf-8", "")
        doc.InsertEndChild(decl)

        xcontents = TiXmlElement("contents")
        for package in pkgTree.packages.values():
            xpackage = TiXmlElement("package")
            xpackage.SetAttribute("name", package.packageName)
            if package.platform:
                xpackage.SetAttribute("platform", package.platform)
                assert package.platform == platform
            if package.packageVersion:
                xpackage.SetAttribute("version", version)
                xpackage.SetAttribute(
                    "filename", package.packageName + "/" + package.packageVersion + "/" + package.descFileBasename
                )
            else:
                xpackage.SetAttribute("filename", package.packageName + "/" + package.descFileBasename)
            xcontents.InsertEndChild(xpackage)

        doc.InsertEndChild(xcontents)
        doc.SaveFile(Filename(hostDir, "contents.xml").toOsSpecific())

    def buildAll(self, outputDir="."):
        """ Creates a (graphical) installer for every known platform.
        Call this after you have set the desired parameters. """

        platforms = set()
        for package in self.standalone.host.getPackages(name="p3dembed"):
            platforms.add(package.platform)
        if len(platforms) == 0:
            Installer.notify.warning("No platforms found to build for!")

        outputDir = Filename(outputDir + "/")
        outputDir.makeDir()
        for platform in platforms:
            output = Filename(outputDir, platform + "/")
            output.makeDir()
            self.build(output, platform)

    def build(self, output, platform=None):
        """ Builds (graphical) installers and stores it into the path
        indicated by the 'output' argument. You can specify to build for
        a different platform by altering the 'platform' argument.
        If 'output' is a directory, the installer will be stored in it. """

        if platform == None:
            platform = PandaSystem.getPlatform()

        if platform == "win32":
            self.buildNSIS(output, platform)
            return
        elif "_" in platform:
            osname, arch = platform.split("_", 1)
            if osname == "linux":
                self.buildDEB(output, platform)
                self.buildArch(output, platform)
                return
            elif osname == "osx":
                self.buildPKG(output, platform)
                return
        Installer.notify.info("Ignoring unknown platform " + platform)

    def __buildTempLinux(self, platform):
        """ Builds a filesystem for Linux.  Used so that buildDEB,
        buildRPM and buildArch can share the same temp directory. """

        if self.__linuxRoot is not None:
            return self.__linuxRoot

        tempdir = Filename(self.tempDir, platform)
        tempdir.makeDir()

        Filename(tempdir, "usr/bin/").makeDir()
        if self.includeRequires:
            extraTokens = {"host_dir": "/usr/lib/" + self.shortname.lower()}
        else:
            extraTokens = {}
        self.standalone.build(Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform, extraTokens)
        if not self.licensefile.empty():
            Filename(tempdir, "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
            shutil.copyfile(
                self.licensefile.toOsSpecific(),
                Filename(tempdir, "usr/share/doc/%s/copyright" % self.shortname.lower()).toOsSpecific(),
            )
            shutil.copyfile(
                self.licensefile.toOsSpecific(),
                Filename(tempdir, "usr/share/doc/%s/LICENSE" % self.shortname.lower()).toOsSpecific(),
            )

        if self.includeRequires:
            hostDir = Filename(tempdir, "usr/lib/" + self.shortname.lower())
            hostDir.makeDir()
            self.installPackagesInto(hostDir, platform)

        totsize = 0
        for root, dirs, files in self.os_walk(tempdir.toOsSpecific()):
            for name in files:
                totsize += os.path.getsize(os.path.join(root, name))

        self.__linuxRoot = (tempdir, totsize)
        return self.__linuxRoot

    def buildDEB(self, output, platform):
        """ Builds a .deb archive and stores it in the path indicated
        by the 'output' argument. It will be built for the architecture
        specified by the 'arch' argument.
        If 'output' is a directory, the deb file will be stored in it. """

        arch = platform.rsplit("_", 1)[-1]
        output = Filename(output)
        if output.isDirectory():
            output = Filename(output, "%s_%s_%s.deb" % (self.shortname.lower(), self.version, arch))
        Installer.notify.info("Creating %s..." % output)
        modtime = int(time.time())

        # Create a temporary directory and write the launcher and dependencies to it.
        tempdir, totsize = self.__buildTempLinux(platform)

        # Create a control file in memory.
        controlfile = StringIO()
        print >> controlfile, "Package: %s" % self.shortname.lower()
        print >> controlfile, "Version: %s" % self.version
        print >> controlfile, "Maintainer: %s <%s>" % (self.authorname, self.authoremail)
        print >> controlfile, "Section: games"
        print >> controlfile, "Priority: optional"
        print >> controlfile, "Architecture: %s" % arch
        print >> controlfile, "Installed-Size: %d" % -(-totsize / 1024)
        print >> controlfile, "Description: %s" % self.fullname
        print >> controlfile, "Depends: libc6, libgcc1, libstdc++6, libx11-6"
        controlinfo = TarInfoRoot("control")
        controlinfo.mtime = modtime
        controlinfo.size = controlfile.tell()
        controlfile.seek(0)

        # Open the deb file and write to it. It's actually
        # just an AR file, which is very easy to make.
        if output.exists():
            output.unlink()
        debfile = open(output.toOsSpecific(), "wb")
        debfile.write("!<arch>\x0A")
        debfile.write("debian-binary   %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, 4))
        debfile.write("2.0\x0A")

        # Write the control.tar.gz to the archive.
        debfile.write("control.tar.gz  %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, 0))
        ctaroffs = debfile.tell()
        ctarfile = tarfile.open("control.tar.gz", "w:gz", debfile, tarinfo=TarInfoRoot)
        ctarfile.addfile(controlinfo, controlfile)
        ctarfile.close()
        ctarsize = debfile.tell() - ctaroffs
        if ctarsize & 1:
            debfile.write("\x0A")

        # Write the data.tar.gz to the archive.
        debfile.write("data.tar.gz     %-12lu0     0     100644  %-10ld\x60\x0A" % (modtime, 0))
        dtaroffs = debfile.tell()
        dtarfile = tarfile.open("data.tar.gz", "w:gz", debfile, tarinfo=TarInfoRoot)
        dtarfile.add(Filename(tempdir, "usr").toOsSpecific(), "/usr")
        dtarfile.close()
        dtarsize = debfile.tell() - dtaroffs
        if dtarsize & 1:
            debfile.write("\x0A")

        # Write the correct sizes of the archives.
        debfile.seek(ctaroffs - 12)
        debfile.write("%-10ld" % ctarsize)
        debfile.seek(dtaroffs - 12)
        debfile.write("%-10ld" % dtarsize)

        debfile.close()

        return output

    def buildArch(self, output, platform):
        """ Builds an ArchLinux package and stores it in the path
        indicated by the 'output' argument. It will be built for the
        architecture specified by the 'arch' argument.
        If 'output' is a directory, the deb file will be stored in it. """

        arch = platform.rsplit("_", 1)[-1]
        assert arch in ("i386", "amd64")
        arch = {"i386": "i686", "amd64": "x86_64"}[arch]
        pkgver = self.version + "-1"

        output = Filename(output)
        if output.isDirectory():
            output = Filename(output, "%s-%s-%s.pkg.tar.gz" % (self.shortname.lower(), pkgver, arch))
        Installer.notify.info("Creating %s..." % output)
        modtime = int(time.time())

        # Create a temporary directory and write the launcher and dependencies to it.
        tempdir, totsize = self.__buildTempLinux(platform)

        # Create a pkginfo file in memory.
        pkginfo = StringIO()
        print >> pkginfo, "# Generated using pdeploy"
        print >> pkginfo, "# %s" % time.ctime(modtime)
        print >> pkginfo, "pkgname = %s" % self.shortname.lower()
        print >> pkginfo, "pkgver = %s" % pkgver
        print >> pkginfo, "pkgdesc = %s" % self.fullname
        print >> pkginfo, "builddate = %s" % modtime
        print >> pkginfo, "packager = %s <%s>" % (self.authorname, self.authoremail)
        print >> pkginfo, "size = %d" % totsize
        print >> pkginfo, "arch = %s" % arch
        if self.licensename != "":
            print >> pkginfo, "license = %s" % self.licensename
        pkginfoinfo = TarInfoRoot(".PKGINFO")
        pkginfoinfo.mtime = modtime
        pkginfoinfo.size = pkginfo.tell()
        pkginfo.seek(0)

        # Create the actual package now.
        pkgfile = tarfile.open(output.toOsSpecific(), "w:gz", tarinfo=TarInfoRoot)
        pkgfile.addfile(pkginfoinfo, pkginfo)
        pkgfile.add(tempdir.toOsSpecific(), "/")
        if not self.licensefile.empty():
            pkgfile.add(self.licensefile.toOsSpecific(), "/usr/share/licenses/%s/LICENSE" % self.shortname.lower())
        pkgfile.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

    def buildPKG(self, output, platform):
        appfn = self.buildAPP(output, platform)
        appname = "/Applications/" + appfn.getBasename()
        output = Filename(output)
        if output.isDirectory():
            output = Filename(output, "%s %s.pkg" % (self.fullname, self.version))
        Installer.notify.info("Creating %s..." % output)

        Filename(output, "Contents/Resources/en.lproj/").makeDir()
        if self.licensefile:
            shutil.copyfile(
                self.licensefile.toOsSpecific(), Filename(output, "Contents/Resources/License.txt").toOsSpecific()
            )
        pkginfo = open(Filename(output, "Contents/PkgInfo").toOsSpecific(), "w")
        pkginfo.write("pkmkrpkg1")
        pkginfo.close()
        pkginfo = open(Filename(output, "Contents/Resources/package_version").toOsSpecific(), "w")
        pkginfo.write("major: 1\nminor: 9")
        pkginfo.close()

        # Although it might make more sense to use Python's plistlib here,
        # it is not available on non-OSX systems before Python 2.6.
        plist = open(Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
        plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        plist.write(
            '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
        )
        plist.write('<plist version="1.0">\n')
        plist.write("<dict>\n")
        plist.write("\t<key>CFBundleIdentifier</key>\n")
        plist.write("\t<string>%s.pkg.%s</string>\n" % (self.authorid, self.shortname))
        plist.write("\t<key>CFBundleShortVersionString</key>\n")
        plist.write("\t<string>%s</string>\n" % self.version)
        plist.write("\t<key>IFMajorVersion</key>\n")
        plist.write("\t<integer>1</integer>\n")
        plist.write("\t<key>IFMinorVersion</key>\n")
        plist.write("\t<integer>9</integer>\n")
        plist.write("\t<key>IFPkgFlagAllowBackRev</key>\n")
        plist.write("\t<false/>\n")
        plist.write("\t<key>IFPkgFlagAuthorizationAction</key>\n")
        plist.write("\t<string>RootAuthorization</string>\n")
        plist.write("\t<key>IFPkgFlagDefaultLocation</key>\n")
        plist.write("\t<string>/</string>\n")
        plist.write("\t<key>IFPkgFlagFollowLinks</key>\n")
        plist.write("\t<true/>\n")
        plist.write("\t<key>IFPkgFlagIsRequired</key>\n")
        plist.write("\t<false/>\n")
        plist.write("\t<key>IFPkgFlagOverwritePermissions</key>\n")
        plist.write("\t<false/>\n")
        plist.write("\t<key>IFPkgFlagRelocatable</key>\n")
        plist.write("\t<false/>\n")
        plist.write("\t<key>IFPkgFlagRestartAction</key>\n")
        plist.write("\t<string>None</string>\n")
        plist.write("\t<key>IFPkgFlagRootVolumeOnly</key>\n")
        plist.write("\t<true/>\n")
        plist.write("\t<key>IFPkgFlagUpdateInstalledLanguages</key>\n")
        plist.write("\t<false/>\n")
        plist.write("\t<key>IFPkgFormatVersion</key>\n")
        plist.write("\t<real>0.10000000149011612</real>\n")
        plist.write("\t<key>IFPkgPathMappings</key>\n")
        plist.write("\t<dict>\n")
        plist.write("\t\t<key>%s</key>\n" % appname)
        plist.write("\t\t<string>{pkmk-token-2}</string>\n")
        plist.write("\t</dict>\n")
        plist.write("</dict>\n")
        plist.write("</plist>\n")
        plist.close()

        plist = open(Filename(output, "Contents/Resources/TokenDefinitions.plist").toOsSpecific(), "w")
        plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        plist.write(
            '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
        )
        plist.write('<plist version="1.0">\n')
        plist.write("<dict>\n")
        plist.write("\t<key>pkmk-token-2</key>\n")
        plist.write("\t<array>\n")
        plist.write("\t\t<dict>\n")
        plist.write("\t\t\t<key>identifier</key>\n")
        plist.write("\t\t\t<string>%s.%s</string>\n" % (self.authorid, self.shortname))
        plist.write("\t\t\t<key>path</key>\n")
        plist.write("\t\t\t<string>%s</string>\n" % appname)
        plist.write("\t\t\t<key>searchPlugin</key>\n")
        plist.write("\t\t\t<string>CommonAppSearch</string>\n")
        plist.write("\t\t</dict>\n")
        plist.write("\t</array>\n")
        plist.write("</dict>\n")
        plist.write("</plist>\n")
        plist.close()

        plist = open(Filename(output, "Contents/Resources/en.lproj/Description.plist").toOsSpecific(), "w")
        plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        plist.write(
            '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
        )
        plist.write('<plist version="1.0">\n')
        plist.write("<dict>\n")
        plist.write("\t<key>IFPkgDescriptionDescription</key>\n")
        plist.write("\t<string></string>\n")
        plist.write("\t<key>IFPkgDescriptionTitle</key>\n")
        plist.write("\t<string>%s</string>\n" % self.fullname)
        plist.write("</dict>\n")
        plist.write("</plist>\n")
        plist.close()

        if hasattr(tarfile, "PAX_FORMAT"):
            archive = tarfile.open(
                Filename(output, "Contents/Archive.pax.gz").toOsSpecific(),
                "w:gz",
                format=tarfile.PAX_FORMAT,
                tarinfo=TarInfoRootOSX,
            )
        else:
            archive = tarfile.open(
                Filename(output, "Contents/Archive.pax.gz").toOsSpecific(), "w:gz", tarinfo=TarInfoRootOSX
            )
        archive.add(appfn.toOsSpecific(), appname)
        archive.close()

        # Put the .pkg into a zipfile
        archive = Filename(output.getDirname(), "%s %s.zip" % (self.fullname, self.version))
        dir = Filename(output.getDirname())
        dir.makeAbsolute()
        zip = zipfile.ZipFile(archive.toOsSpecific(), "w")
        for root, dirs, files in self.os_walk(output.toOsSpecific()):
            for name in files:
                file = Filename.fromOsSpecific(os.path.join(root, name))
                file.makeAbsolute()
                file.makeRelativeTo(dir)
                zip.write(os.path.join(root, name), str(file))
        zip.close()

        return output

    def buildNSIS(self, output, platform):
        # Check if we have makensis first
        makensis = None
        if sys.platform.startswith("win"):
            syspath = os.defpath.split(";") + os.environ["PATH"].split(";")
            for p in set(syspath):
                p1 = os.path.join(p, "makensis.exe")
                p2 = os.path.join(os.path.dirname(p), "nsis", "makensis.exe")
                if os.path.isfile(p1):
                    makensis = p1
                    break
                elif os.path.isfile(p2):
                    makensis = p2
                    break
            if not makensis:
                import pandac

                makensis = os.path.dirname(os.path.dirname(pandac.__file__))
                makensis = os.path.join(makensis, "nsis", "makensis.exe")
                if not os.path.isfile(makensis):
                    makensis = None
        else:
            for p in os.defpath.split(":") + os.environ["PATH"].split(":"):
                if os.path.isfile(os.path.join(p, "makensis")):
                    makensis = os.path.join(p, "makensis")

        if makensis == None:
            Installer.notify.warning("Makensis utility not found, no Windows installer will be built!")
            return None

        output = Filename(output)
        if output.isDirectory():
            output = Filename(output, "%s %s.exe" % (self.fullname, self.version))
        Installer.notify.info("Creating %s..." % output)
        output.makeAbsolute()
        extrafiles = self.standalone.getExtraFiles(platform)

        exefile = Filename(Filename.getTempDirectory(), self.shortname + ".exe")
        exefile.unlink()
        if self.includeRequires:
            extraTokens = {"host_dir": "."}
        else:
            extraTokens = {}
        self.standalone.build(exefile, platform, extraTokens)

        # Temporary directory to store the hostdir in
        hostDir = Filename(self.tempDir, platform + "/")
        if not hostDir.exists():
            hostDir.makeDir()
            self.installPackagesInto(hostDir, platform)

        nsifile = Filename(Filename.getTempDirectory(), self.shortname + ".nsi")
        nsifile.unlink()
        nsi = open(nsifile.toOsSpecific(), "w")

        # Some global info
        nsi.write('Name "%s"\n' % self.fullname)
        nsi.write('OutFile "%s"\n' % output.toOsSpecific())
        nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % self.fullname)
        nsi.write("SetCompress auto\n")
        nsi.write("SetCompressor lzma\n")
        nsi.write("ShowInstDetails nevershow\n")
        nsi.write("ShowUninstDetails nevershow\n")
        nsi.write('InstType "Typical"\n')

        # Tell Vista that we require admin rights
        nsi.write("RequestExecutionLevel admin\n")
        nsi.write("\n")
        nsi.write("Function launch\n")
        nsi.write('  ExecShell "open" "$INSTDIR\\%s.exe"\n' % self.shortname)
        nsi.write("FunctionEnd\n")
        nsi.write("\n")
        nsi.write('!include "MUI2.nsh"\n')
        nsi.write("!define MUI_ABORTWARNING\n")
        nsi.write("!define MUI_FINISHPAGE_RUN\n")
        nsi.write("!define MUI_FINISHPAGE_RUN_FUNCTION launch\n")
        nsi.write('!define MUI_FINISHPAGE_RUN_TEXT "Run %s"\n' % self.fullname)
        nsi.write("\n")
        nsi.write("Var StartMenuFolder\n")
        nsi.write("!insertmacro MUI_PAGE_WELCOME\n")
        if not self.licensefile.empty():
            abs = Filename(self.licensefile)
            abs.makeAbsolute()
            nsi.write('!insertmacro MUI_PAGE_LICENSE "%s"\n' % abs.toOsSpecific())
        nsi.write("!insertmacro MUI_PAGE_DIRECTORY\n")
        nsi.write("!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n")
        nsi.write("!insertmacro MUI_PAGE_INSTFILES\n")
        nsi.write("!insertmacro MUI_PAGE_FINISH\n")
        nsi.write("!insertmacro MUI_UNPAGE_WELCOME\n")
        nsi.write("!insertmacro MUI_UNPAGE_CONFIRM\n")
        nsi.write("!insertmacro MUI_UNPAGE_INSTFILES\n")
        nsi.write("!insertmacro MUI_UNPAGE_FINISH\n")
        nsi.write('!insertmacro MUI_LANGUAGE "English"\n')

        # This section defines the installer.
        nsi.write('Section "" SecCore\n')
        nsi.write('  SetOutPath "$INSTDIR"\n')
        nsi.write('  File "%s"\n' % exefile.toOsSpecific())
        for f in extrafiles:
            nsi.write('  File "%s"\n' % f.toOsSpecific())
        curdir = ""
        for root, dirs, files in self.os_walk(hostDir.toOsSpecific()):
            for name in files:
                file = Filename.fromOsSpecific(os.path.join(root, name))
                file.makeAbsolute()
                file.makeRelativeTo(hostDir)
                outdir = file.getDirname().replace("/", "\\")
                if curdir != outdir:
                    nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
                    curdir = outdir
                nsi.write('  File "%s"\n' % os.path.join(root, name))
        nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
        nsi.write("  ; Start menu items\n")
        nsi.write("  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n")
        nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
        nsi.write(
            '    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s.exe"\n'
            % (self.fullname, self.shortname)
        )
        nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
        nsi.write("  !insertmacro MUI_STARTMENU_WRITE_END\n")
        nsi.write("SectionEnd\n")

        # This section defines the uninstaller.
        nsi.write("Section Uninstall\n")
        nsi.write('  Delete "$INSTDIR\\%s.exe"\n' % self.shortname)
        for f in extrafiles:
            nsi.write('  Delete "%s"\n' % f.getBasename())
        nsi.write('  Delete "$INSTDIR\\Uninstall.exe"\n')
        nsi.write('  RMDir /r "$INSTDIR"\n')
        nsi.write("  ; Start menu items\n")
        nsi.write("  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n")
        nsi.write('  Delete "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk"\n')
        nsi.write('  RMDir "$SMPROGRAMS\\$StartMenuFolder"\n')
        nsi.write("SectionEnd")
        nsi.close()

        cmd = [makensis]
        for o in ["V2"]:
            if sys.platform.startswith("win"):
                cmd.append("/" + o)
            else:
                cmd.append("-" + o)
        cmd.append(nsifile.toOsSpecific())
        print cmd
        try:
            retcode = subprocess.call(cmd, shell=False)
            if retcode != 0:
                self.notify.warning("Failure invoking NSIS command.")
        except OSError:
            self.notify.warning("Unable to invoke NSIS command.")

        nsifile.unlink()
        return output

    def os_walk(self, top):
        """ Re-implements os.walk().  For some reason the built-in
        definition is failing on Windows when this is run within a p3d
        environment!? """

        dirnames = []
        filenames = []

        dirlist = os.listdir(top)
        if dirlist:
            for file in dirlist:
                path = os.path.join(top, file)
                if os.path.isdir(path):
                    dirnames.append(file)
                else:
                    filenames.append(file)

        yield (top, dirnames, filenames)

        for dir in dirnames:
            next = os.path.join(top, dir)
            for tuple in self.os_walk(next):
                yield tuple
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)
示例#5
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)
class Installer:
    """ This class creates a (graphical) installer from a given .p3d file. """
    notify = directNotify.newCategory("Installer")

    def __init__(self, p3dfile, shortname, fullname, version, tokens={}):
        if not shortname:
            shortname = p3dfile.getBasenameWoExtension()
        self.shortname = shortname
        self.fullname = fullname
        self.version = str(version)
        self.includeRequires = False
        self.licensename = ""
        self.licensefile = Filename()
        self.authorid = "org.panda3d"
        self.authorname = os.environ.get("DEBFULLNAME", "")
        self.authoremail = os.environ.get("DEBEMAIL", "")

        # Try to determine a default author name ourselves.
        uname = None
        if pwd is not None and hasattr(os, 'getuid'):
            uinfo = pwd.getpwuid(os.getuid())
            if uinfo:
                uname = uinfo.pw_name
                if not self.authorname:
                    self.authorname = \
                        uinfo.pw_gecos.split(',', 1)[0]

        # Fallbacks in case that didn't work or wasn't supported.
        if not uname:
            uname = getpass.getuser()
        if not self.authorname:
            self.authorname = uname
        if not self.authoremail and ' ' not in uname:
            self.authoremail = "%s@%s" % (uname, socket.gethostname())

        self.standalone = Standalone(p3dfile, tokens)
        self.tempDir = Filename.temporary("", self.shortname, "") + "/"
        self.tempDir.makeDir()
        self.__linuxRoot = None

        # Load the p3d file to read out the required packages
        mf = Multifile()
        if not mf.openRead(p3dfile):
            Installer.notify.error("Not a Panda3D application: %s" %
                                   (p3dFilename))
            return

        # Now load the p3dInfo file.
        self.hostUrl = PandaSystem.getPackageHostUrl()
        if not self.hostUrl:
            self.hostUrl = self.standalone.host.hostUrl
        self.requires = []
        i = mf.findSubfile('p3d_info.xml')
        if i >= 0:
            stream = mf.openReadSubfile(i)
            p3dInfo = readXmlStream(stream)
            mf.closeReadSubfile(stream)
            if p3dInfo:
                p3dPackage = p3dInfo.FirstChildElement('package')
                p3dHost = p3dPackage.FirstChildElement('host')
                if p3dHost.Attribute('url'):
                    self.hostUrl = p3dHost.Attribute('url')
                p3dRequires = p3dPackage.FirstChildElement('requires')
                while p3dRequires:
                    self.requires.append((p3dRequires.Attribute('name'),
                                          p3dRequires.Attribute('version'),
                                          p3dRequires.Attribute('host')))
                    p3dRequires = p3dRequires.NextSiblingElement('requires')

                if not self.fullname:
                    p3dConfig = p3dPackage.FirstChildElement('config')
                    if p3dConfig:
                        self.fullname = p3dConfig.Attribute('display_name')

        if not self.fullname:
            self.fullname = self.shortname

    def __del__(self):
        try:
            appRunner.rmtree(self.tempDir)
        except:
            try:
                shutil.rmtree(self.tempDir.toOsSpecific())
            except:
                pass

    def installPackagesInto(self, hostDir, platform):
        """ Installs the packages required by the .p3d file into
        the specified directory, for the given platform. """

        if not self.includeRequires:
            return

        pkgTree = PackageTree(platform, hostDir, self.hostUrl)
        pkgTree.installPackage("images", None, self.standalone.host.hostUrl)

        for name, version, hostUrl in self.requires:
            pkgTree.installPackage(name, version, hostUrl)

        # Remove the extracted files from the compressed archive, to save space.
        vfs = VirtualFileSystem.getGlobalPtr()
        for package in pkgTree.packages.values():
            if package.uncompressedArchive:
                archive = Filename(package.getPackageDir(),
                                   package.uncompressedArchive.filename)
                if not archive.exists():
                    continue

                mf = Multifile()
                # Make sure that it isn't mounted before altering it, just to be safe
                vfs.unmount(archive)
                os.chmod(archive.toOsSpecific(), 0644)
                if not mf.openReadWrite(archive):
                    Installer.notify.warning("Failed to open archive %s" %
                                             (archive))
                    continue

                # We don't iterate over getNumSubfiles because we're
                # removing subfiles while we're iterating over them.
                subfiles = mf.getSubfileNames()
                for subfile in subfiles:
                    # We do *NOT* call vfs.exists here in case the package is mounted.
                    if Filename(package.getPackageDir(), subfile).exists():
                        Installer.notify.debug(
                            "Removing already-extracted %s from multifile" %
                            (subfile))
                        mf.removeSubfile(subfile)

                # This seems essential for mf.close() not to crash later.
                mf.repack()

                # If we have no subfiles left, we can just remove the multifile.
                #if mf.getNumSubfiles() == 0:
                #    Installer.notify.info("Removing empty archive %s" % (package.uncompressedArchive.filename))
                #    mf.close()
                #    archive.unlink()
                #else:
                mf.close()
                try:
                    os.chmod(archive.toOsSpecific(), 0444)
                except:
                    pass

        # Write out our own contents.xml file.
        doc = TiXmlDocument()
        decl = TiXmlDeclaration("1.0", "utf-8", "")
        doc.InsertEndChild(decl)

        xcontents = TiXmlElement("contents")
        for package in pkgTree.packages.values():
            xpackage = TiXmlElement('package')
            xpackage.SetAttribute('name', package.packageName)
            if package.platform:
                xpackage.SetAttribute('platform', package.platform)
                assert package.platform == platform
            if package.packageVersion:
                xpackage.SetAttribute('version', version)
                xpackage.SetAttribute(
                    'filename', package.packageName + "/" +
                    package.packageVersion + "/" + package.descFileBasename)
            else:
                xpackage.SetAttribute(
                    'filename',
                    package.packageName + "/" + package.descFileBasename)
            xcontents.InsertEndChild(xpackage)

        doc.InsertEndChild(xcontents)
        doc.SaveFile(Filename(hostDir, "contents.xml").toOsSpecific())

    def buildAll(self, outputDir="."):
        """ Creates a (graphical) installer for every known platform.
        Call this after you have set the desired parameters. """

        platforms = set()
        for package in self.standalone.host.getPackages(name="p3dembed"):
            platforms.add(package.platform)
        if len(platforms) == 0:
            Installer.notify.warning("No platforms found to build for!")

        outputDir = Filename(outputDir + "/")
        outputDir.makeDir()
        for platform in platforms:
            output = Filename(outputDir, platform + "/")
            output.makeDir()
            self.build(output, platform)

    def build(self, output, platform=None):
        """ Builds (graphical) installers and stores it into the path
        indicated by the 'output' argument. You can specify to build for
        a different platform by altering the 'platform' argument.
        If 'output' is a directory, the installer will be stored in it. """

        if platform == None:
            platform = PandaSystem.getPlatform()

        if platform == "win32":
            self.buildNSIS(output, platform)
            return
        elif "_" in platform:
            osname, arch = platform.split("_", 1)
            if osname == "linux":
                self.buildDEB(output, platform)
                self.buildArch(output, platform)
                return
            elif osname == "osx":
                self.buildPKG(output, platform)
                return
        Installer.notify.info("Ignoring unknown platform " + platform)

    def __buildTempLinux(self, platform):
        """ Builds a filesystem for Linux.  Used so that buildDEB,
        buildRPM and buildArch can share the same temp directory. """

        if self.__linuxRoot is not None:
            return self.__linuxRoot

        tempdir = Filename(self.tempDir, platform)
        tempdir.makeDir()

        Filename(tempdir, "usr/bin/").makeDir()
        if self.includeRequires:
            extraTokens = {"host_dir": "/usr/lib/" + self.shortname.lower()}
        else:
            extraTokens = {}
        self.standalone.build(
            Filename(tempdir, "usr/bin/" + self.shortname.lower()), platform,
            extraTokens)
        if not self.licensefile.empty():
            Filename(tempdir,
                     "usr/share/doc/%s/" % self.shortname.lower()).makeDir()
            shutil.copyfile(
                self.licensefile.toOsSpecific(),
                Filename(tempdir, "usr/share/doc/%s/copyright" %
                         self.shortname.lower()).toOsSpecific())
            shutil.copyfile(
                self.licensefile.toOsSpecific(),
                Filename(tempdir, "usr/share/doc/%s/LICENSE" %
                         self.shortname.lower()).toOsSpecific())

        if self.includeRequires:
            hostDir = Filename(tempdir, "usr/lib/" + self.shortname.lower())
            hostDir.makeDir()
            self.installPackagesInto(hostDir, platform)

        totsize = 0
        for root, dirs, files in self.os_walk(tempdir.toOsSpecific()):
            for name in files:
                totsize += os.path.getsize(os.path.join(root, name))

        self.__linuxRoot = (tempdir, totsize)
        return self.__linuxRoot

    def buildDEB(self, output, platform):
        """ Builds a .deb archive and stores it in the path indicated
        by the 'output' argument. It will be built for the architecture
        specified by the 'arch' argument.
        If 'output' is a directory, the deb file will be stored in it. """

        arch = platform.rsplit("_", 1)[-1]
        output = Filename(output)
        if output.isDirectory():
            output = Filename(
                output,
                "%s_%s_%s.deb" % (self.shortname.lower(), self.version, arch))
        Installer.notify.info("Creating %s..." % output)
        modtime = int(time.time())

        # Create a temporary directory and write the launcher and dependencies to it.
        tempdir, totsize = self.__buildTempLinux(platform)

        # Create a control file in memory.
        controlfile = StringIO()
        print >> controlfile, "Package: %s" % self.shortname.lower()
        print >> controlfile, "Version: %s" % self.version
        print >> controlfile, "Maintainer: %s <%s>" % (self.authorname,
                                                       self.authoremail)
        print >> controlfile, "Section: games"
        print >> controlfile, "Priority: optional"
        print >> controlfile, "Architecture: %s" % arch
        print >> controlfile, "Installed-Size: %d" % -(-totsize / 1024)
        print >> controlfile, "Description: %s" % self.fullname
        print >> controlfile, "Depends: libc6, libgcc1, libstdc++6, libx11-6"
        controlinfo = TarInfoRoot("control")
        controlinfo.mtime = modtime
        controlinfo.size = controlfile.tell()
        controlfile.seek(0)

        # Open the deb file and write to it. It's actually
        # just an AR file, which is very easy to make.
        if output.exists():
            output.unlink()
        debfile = open(output.toOsSpecific(), "wb")
        debfile.write("!<arch>\x0A")
        debfile.write(
            "debian-binary   %-12lu0     0     100644  %-10ld\x60\x0A" %
            (modtime, 4))
        debfile.write("2.0\x0A")

        # Write the control.tar.gz to the archive.
        debfile.write(
            "control.tar.gz  %-12lu0     0     100644  %-10ld\x60\x0A" %
            (modtime, 0))
        ctaroffs = debfile.tell()
        ctarfile = tarfile.open("control.tar.gz",
                                "w:gz",
                                debfile,
                                tarinfo=TarInfoRoot)
        ctarfile.addfile(controlinfo, controlfile)
        ctarfile.close()
        ctarsize = debfile.tell() - ctaroffs
        if (ctarsize & 1): debfile.write("\x0A")

        # Write the data.tar.gz to the archive.
        debfile.write(
            "data.tar.gz     %-12lu0     0     100644  %-10ld\x60\x0A" %
            (modtime, 0))
        dtaroffs = debfile.tell()
        dtarfile = tarfile.open("data.tar.gz",
                                "w:gz",
                                debfile,
                                tarinfo=TarInfoRoot)
        dtarfile.add(Filename(tempdir, "usr").toOsSpecific(), "/usr")
        dtarfile.close()
        dtarsize = debfile.tell() - dtaroffs
        if (dtarsize & 1): debfile.write("\x0A")

        # Write the correct sizes of the archives.
        debfile.seek(ctaroffs - 12)
        debfile.write("%-10ld" % ctarsize)
        debfile.seek(dtaroffs - 12)
        debfile.write("%-10ld" % dtarsize)

        debfile.close()

        return output

    def buildArch(self, output, platform):
        """ Builds an ArchLinux package and stores it in the path
        indicated by the 'output' argument. It will be built for the
        architecture specified by the 'arch' argument.
        If 'output' is a directory, the deb file will be stored in it. """

        arch = platform.rsplit("_", 1)[-1]
        assert arch in ("i386", "amd64")
        arch = {"i386": "i686", "amd64": "x86_64"}[arch]
        pkgver = self.version + "-1"

        output = Filename(output)
        if output.isDirectory():
            output = Filename(
                output,
                "%s-%s-%s.pkg.tar.gz" % (self.shortname.lower(), pkgver, arch))
        Installer.notify.info("Creating %s..." % output)
        modtime = int(time.time())

        # Create a temporary directory and write the launcher and dependencies to it.
        tempdir, totsize = self.__buildTempLinux(platform)

        # Create a pkginfo file in memory.
        pkginfo = StringIO()
        print >> pkginfo, "# Generated using pdeploy"
        print >> pkginfo, "# %s" % time.ctime(modtime)
        print >> pkginfo, "pkgname = %s" % self.shortname.lower()
        print >> pkginfo, "pkgver = %s" % pkgver
        print >> pkginfo, "pkgdesc = %s" % self.fullname
        print >> pkginfo, "builddate = %s" % modtime
        print >> pkginfo, "packager = %s <%s>" % (self.authorname,
                                                  self.authoremail)
        print >> pkginfo, "size = %d" % totsize
        print >> pkginfo, "arch = %s" % arch
        if self.licensename != "":
            print >> pkginfo, "license = %s" % self.licensename
        pkginfoinfo = TarInfoRoot(".PKGINFO")
        pkginfoinfo.mtime = modtime
        pkginfoinfo.size = pkginfo.tell()
        pkginfo.seek(0)

        # Create the actual package now.
        pkgfile = tarfile.open(output.toOsSpecific(),
                               "w:gz",
                               tarinfo=TarInfoRoot)
        pkgfile.addfile(pkginfoinfo, pkginfo)
        pkgfile.add(tempdir.toOsSpecific(), "/")
        if not self.licensefile.empty():
            pkgfile.add(
                self.licensefile.toOsSpecific(),
                "/usr/share/licenses/%s/LICENSE" % self.shortname.lower())
        pkgfile.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

    def buildPKG(self, output, platform):
        appfn = self.buildAPP(output, platform)
        appname = "/Applications/" + appfn.getBasename()
        output = Filename(output)
        if output.isDirectory():
            output = Filename(output,
                              "%s %s.pkg" % (self.fullname, self.version))
        Installer.notify.info("Creating %s..." % output)

        Filename(output, "Contents/Resources/en.lproj/").makeDir()
        if self.licensefile:
            shutil.copyfile(
                self.licensefile.toOsSpecific(),
                Filename(output,
                         "Contents/Resources/License.txt").toOsSpecific())
        pkginfo = open(
            Filename(output, "Contents/PkgInfo").toOsSpecific(), "w")
        pkginfo.write("pkmkrpkg1")
        pkginfo.close()
        pkginfo = open(
            Filename(output,
                     "Contents/Resources/package_version").toOsSpecific(), "w")
        pkginfo.write("major: 1\nminor: 9")
        pkginfo.close()

        # Although it might make more sense to use Python's plistlib here,
        # it is not available on non-OSX systems before Python 2.6.
        plist = open(
            Filename(output, "Contents/Info.plist").toOsSpecific(), "w")
        plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        plist.write(
            '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
        )
        plist.write('<plist version="1.0">\n')
        plist.write('<dict>\n')
        plist.write('\t<key>CFBundleIdentifier</key>\n')
        plist.write('\t<string>%s.pkg.%s</string>\n' %
                    (self.authorid, self.shortname))
        plist.write('\t<key>CFBundleShortVersionString</key>\n')
        plist.write('\t<string>%s</string>\n' % self.version)
        plist.write('\t<key>IFMajorVersion</key>\n')
        plist.write('\t<integer>1</integer>\n')
        plist.write('\t<key>IFMinorVersion</key>\n')
        plist.write('\t<integer>9</integer>\n')
        plist.write('\t<key>IFPkgFlagAllowBackRev</key>\n')
        plist.write('\t<false/>\n')
        plist.write('\t<key>IFPkgFlagAuthorizationAction</key>\n')
        plist.write('\t<string>RootAuthorization</string>\n')
        plist.write('\t<key>IFPkgFlagDefaultLocation</key>\n')
        plist.write('\t<string>/</string>\n')
        plist.write('\t<key>IFPkgFlagFollowLinks</key>\n')
        plist.write('\t<true/>\n')
        plist.write('\t<key>IFPkgFlagIsRequired</key>\n')
        plist.write('\t<false/>\n')
        plist.write('\t<key>IFPkgFlagOverwritePermissions</key>\n')
        plist.write('\t<false/>\n')
        plist.write('\t<key>IFPkgFlagRelocatable</key>\n')
        plist.write('\t<false/>\n')
        plist.write('\t<key>IFPkgFlagRestartAction</key>\n')
        plist.write('\t<string>None</string>\n')
        plist.write('\t<key>IFPkgFlagRootVolumeOnly</key>\n')
        plist.write('\t<true/>\n')
        plist.write('\t<key>IFPkgFlagUpdateInstalledLanguages</key>\n')
        plist.write('\t<false/>\n')
        plist.write('\t<key>IFPkgFormatVersion</key>\n')
        plist.write('\t<real>0.10000000149011612</real>\n')
        plist.write('\t<key>IFPkgPathMappings</key>\n')
        plist.write('\t<dict>\n')
        plist.write('\t\t<key>%s</key>\n' % appname)
        plist.write('\t\t<string>{pkmk-token-2}</string>\n')
        plist.write('\t</dict>\n')
        plist.write('</dict>\n')
        plist.write('</plist>\n')
        plist.close()

        plist = open(
            Filename(
                output,
                "Contents/Resources/TokenDefinitions.plist").toOsSpecific(),
            "w")
        plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        plist.write(
            '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
        )
        plist.write('<plist version="1.0">\n')
        plist.write('<dict>\n')
        plist.write('\t<key>pkmk-token-2</key>\n')
        plist.write('\t<array>\n')
        plist.write('\t\t<dict>\n')
        plist.write('\t\t\t<key>identifier</key>\n')
        plist.write('\t\t\t<string>%s.%s</string>\n' %
                    (self.authorid, self.shortname))
        plist.write('\t\t\t<key>path</key>\n')
        plist.write('\t\t\t<string>%s</string>\n' % appname)
        plist.write('\t\t\t<key>searchPlugin</key>\n')
        plist.write('\t\t\t<string>CommonAppSearch</string>\n')
        plist.write('\t\t</dict>\n')
        plist.write('\t</array>\n')
        plist.write('</dict>\n')
        plist.write('</plist>\n')
        plist.close()

        plist = open(
            Filename(output, "Contents/Resources/en.lproj/Description.plist").
            toOsSpecific(), "w")
        plist.write('<?xml version="1.0" encoding="UTF-8"?>\n')
        plist.write(
            '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n'
        )
        plist.write('<plist version="1.0">\n')
        plist.write('<dict>\n')
        plist.write('\t<key>IFPkgDescriptionDescription</key>\n')
        plist.write('\t<string></string>\n')
        plist.write('\t<key>IFPkgDescriptionTitle</key>\n')
        plist.write('\t<string>%s</string>\n' % self.fullname)
        plist.write('</dict>\n')
        plist.write('</plist>\n')
        plist.close()

        if hasattr(tarfile, "PAX_FORMAT"):
            archive = tarfile.open(Filename(
                output, "Contents/Archive.pax.gz").toOsSpecific(),
                                   "w:gz",
                                   format=tarfile.PAX_FORMAT,
                                   tarinfo=TarInfoRootOSX)
        else:
            archive = tarfile.open(Filename(
                output, "Contents/Archive.pax.gz").toOsSpecific(),
                                   "w:gz",
                                   tarinfo=TarInfoRootOSX)
        archive.add(appfn.toOsSpecific(), appname)
        archive.close()

        # Put the .pkg into a zipfile
        archive = Filename(output.getDirname(),
                           "%s %s.zip" % (self.fullname, self.version))
        dir = Filename(output.getDirname())
        dir.makeAbsolute()
        zip = zipfile.ZipFile(archive.toOsSpecific(), 'w')
        for root, dirs, files in self.os_walk(output.toOsSpecific()):
            for name in files:
                file = Filename.fromOsSpecific(os.path.join(root, name))
                file.makeAbsolute()
                file.makeRelativeTo(dir)
                zip.write(os.path.join(root, name), str(file))
        zip.close()

        return output

    def buildNSIS(self, output, platform):
        # Check if we have makensis first
        makensis = None
        if (sys.platform.startswith("win")):
            syspath = os.defpath.split(";") + os.environ["PATH"].split(";")
            for p in set(syspath):
                p1 = os.path.join(p, "makensis.exe")
                p2 = os.path.join(os.path.dirname(p), "nsis", "makensis.exe")
                if os.path.isfile(p1):
                    makensis = p1
                    break
                elif os.path.isfile(p2):
                    makensis = p2
                    break
            if not makensis:
                import pandac
                makensis = os.path.dirname(os.path.dirname(pandac.__file__))
                makensis = os.path.join(makensis, "nsis", "makensis.exe")
                if not os.path.isfile(makensis): makensis = None
        else:
            for p in os.defpath.split(":") + os.environ["PATH"].split(":"):
                if os.path.isfile(os.path.join(p, "makensis")):
                    makensis = os.path.join(p, "makensis")

        if makensis == None:
            Installer.notify.warning(
                "Makensis utility not found, no Windows installer will be built!"
            )
            return None

        output = Filename(output)
        if output.isDirectory():
            output = Filename(output,
                              "%s %s.exe" % (self.fullname, self.version))
        Installer.notify.info("Creating %s..." % output)
        output.makeAbsolute()
        extrafiles = self.standalone.getExtraFiles(platform)

        exefile = Filename(Filename.getTempDirectory(),
                           self.shortname + ".exe")
        exefile.unlink()
        if self.includeRequires:
            extraTokens = {"host_dir": "."}
        else:
            extraTokens = {}
        self.standalone.build(exefile, platform, extraTokens)

        # Temporary directory to store the hostdir in
        hostDir = Filename(self.tempDir, platform + "/")
        if not hostDir.exists():
            hostDir.makeDir()
            self.installPackagesInto(hostDir, platform)

        nsifile = Filename(Filename.getTempDirectory(),
                           self.shortname + ".nsi")
        nsifile.unlink()
        nsi = open(nsifile.toOsSpecific(), "w")

        # Some global info
        nsi.write('Name "%s"\n' % self.fullname)
        nsi.write('OutFile "%s"\n' % output.toOsSpecific())
        nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % self.fullname)
        nsi.write('SetCompress auto\n')
        nsi.write('SetCompressor lzma\n')
        nsi.write('ShowInstDetails nevershow\n')
        nsi.write('ShowUninstDetails nevershow\n')
        nsi.write('InstType "Typical"\n')

        # Tell Vista that we require admin rights
        nsi.write('RequestExecutionLevel admin\n')
        nsi.write('\n')
        nsi.write('Function launch\n')
        nsi.write('  ExecShell "open" "$INSTDIR\\%s.exe"\n' % self.shortname)
        nsi.write('FunctionEnd\n')
        nsi.write('\n')
        nsi.write('!include "MUI2.nsh"\n')
        nsi.write('!define MUI_ABORTWARNING\n')
        nsi.write('!define MUI_FINISHPAGE_RUN\n')
        nsi.write('!define MUI_FINISHPAGE_RUN_FUNCTION launch\n')
        nsi.write('!define MUI_FINISHPAGE_RUN_TEXT "Run %s"\n' % self.fullname)
        nsi.write('\n')
        nsi.write('Var StartMenuFolder\n')
        nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
        if not self.licensefile.empty():
            abs = Filename(self.licensefile)
            abs.makeAbsolute()
            nsi.write('!insertmacro MUI_PAGE_LICENSE "%s"\n' %
                      abs.toOsSpecific())
        nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
        nsi.write(
            '!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
        nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
        nsi.write('!insertmacro MUI_PAGE_FINISH\n')
        nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
        nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
        nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
        nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
        nsi.write('!insertmacro MUI_LANGUAGE "English"\n')

        # This section defines the installer.
        nsi.write('Section "" SecCore\n')
        nsi.write('  SetOutPath "$INSTDIR"\n')
        nsi.write('  File "%s"\n' % exefile.toOsSpecific())
        for f in extrafiles:
            nsi.write('  File "%s"\n' % f.toOsSpecific())
        curdir = ""
        for root, dirs, files in self.os_walk(hostDir.toOsSpecific()):
            for name in files:
                file = Filename.fromOsSpecific(os.path.join(root, name))
                file.makeAbsolute()
                file.makeRelativeTo(hostDir)
                outdir = file.getDirname().replace('/', '\\')
                if curdir != outdir:
                    nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
                    curdir = outdir
                nsi.write('  File "%s"\n' % os.path.join(root, name))
        nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
        nsi.write('  ; Start menu items\n')
        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
        nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
        nsi.write(
            '    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s.exe"\n'
            % (self.fullname, self.shortname))
        nsi.write(
            '    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n'
        )
        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_END\n')
        nsi.write('SectionEnd\n')

        # This section defines the uninstaller.
        nsi.write('Section Uninstall\n')
        nsi.write('  Delete "$INSTDIR\\%s.exe"\n' % self.shortname)
        for f in extrafiles:
            nsi.write('  Delete "%s"\n' % f.getBasename())
        nsi.write('  Delete "$INSTDIR\\Uninstall.exe"\n')
        nsi.write('  RMDir /r "$INSTDIR"\n')
        nsi.write('  ; Start menu items\n')
        nsi.write(
            '  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n'
        )
        nsi.write('  Delete "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk"\n')
        nsi.write('  RMDir "$SMPROGRAMS\\$StartMenuFolder"\n')
        nsi.write('SectionEnd')
        nsi.close()

        cmd = [makensis]
        for o in ["V2"]:
            if sys.platform.startswith("win"):
                cmd.append("/" + o)
            else:
                cmd.append("-" + o)
        cmd.append(nsifile.toOsSpecific())
        print cmd
        try:
            retcode = subprocess.call(cmd, shell=False)
            if retcode != 0:
                self.notify.warning("Failure invoking NSIS command.")
        except OSError:
            self.notify.warning("Unable to invoke NSIS command.")

        nsifile.unlink()
        return output

    def os_walk(self, top):
        """ Re-implements os.walk().  For some reason the built-in
        definition is failing on Windows when this is run within a p3d
        environment!? """

        dirnames = []
        filenames = []

        dirlist = os.listdir(top)
        if dirlist:
            for file in dirlist:
                path = os.path.join(top, file)
                if os.path.isdir(path):
                    dirnames.append(file)
                else:
                    filenames.append(file)

        yield (top, dirnames, filenames)

        for dir in dirnames:
            next = os.path.join(top, dir)
            for tuple in self.os_walk(next):
                yield tuple