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