from direct.showbase.ShowBase import ShowBase from pathlib import Path from direct.actor.Actor import Actor from tkinter.filedialog import askopenfilename from panda3d.core import Filename, OrthographicLens, GraphicsOutput, WindowProperties, Texture, GraphicsPipe, \ FrameBufferProperties, AntialiasAttrib from direct.gui.DirectGui import * import sys, os # We need to import the tkinter library to # disable the tk window that pops up. # We use tk for the file path selector. import tkinter as tk root = tk.Tk() root.withdraw() # Force high quality for our render from panda3d.core import loadPrcFileData loadPrcFileData('', 'default-antialias-enable 1') loadPrcFileData('', 'framebuffer-multisample 1') loadPrcFileData('', 'win-size 1920 1080') loadPrcFileData('', 'model-path $RESOURCE_DIR') """ Controls: s = Take screenshot o = toggle oobe/free camera r = reload loaded textures e = reset rotation b = toggle backface culling k = move backpack up l (L) = move backpack down mouse wheel up = zoom in mouse wheel down = zoom out mouse3(middle mouse click) = reset zoom left arrow = rotate negative heading right arrow = rotate positive heading up arrow = rotate positive pitch down arrow = rotate negative pitch """ """ Todo: Add onscreen text that displays the offset (camers zoom, clothing rotation, etc.) <-- will be hidden in screenshots """ class previewBackpack(ShowBase): def __init__(self): ShowBase.__init__(self) self.fileName = "output" # Output file name self.fileFormat = ".png" self.midPointX = 0 self.backpackModel = None self.backpackTex = None self.loadedTextures = [None] self.backpack = None base.cam.setPos(0, -7, 0.25) self.defaultCamPos = base.cam.getPos() base.camera.hide() self.i = 1 self.defaultH = 180 # todo: hotkey to reset all transformations self.currentH = self.defaultH self.defaultP = 0 self.currentP = self.defaultP self.defaultY = -7 self.currentY = self.defaultY self.defaultZ = 0.25 # note: negative goes up, positive goes down self.currentZ = self.defaultZ self.enabledAA = True self.enabledBFC = False # Camera # 16 : 9 aspect ratio default scaleMultiplier = 0.25 self.filmSizeX_BASE = 16 * scaleMultiplier self.filmSizeY_BASE = 9 * scaleMultiplier self.filmSizeX = 16 self.filmSizeY = 9 self.orthoLens = OrthographicLens() self.orthoLens.setFilmSize(self.filmSizeX, self.filmSizeY) self.isOrthoView = False self.defaultLens = base.cam.node().getLens() # Just in case we have these enabled in the config... base.setFrameRateMeter(False) base.setSceneGraphAnalyzerMeter(False) base.disableMouse() self.loadBackpack() self.loadGUI() """ If you want to change the default outfit texture (not desat), you can either change the texture path of the egg model(s) itself, or, alternatively, you can directly call to load specific textures, e.g.: self.loadBackpackModel("path/to/texture.png") """ self.accept('s', self.aspect2d.hide) # Hacky b/c hiding and showing in same method no work self.accept('s-up', self.saveScreenshot) self.accept('o', base.oobe) self.accept('b', self.toggleBFC) self.accept('r', self.reloadTextures) self.accept('e', self.defaultRotation) self.accept('c', self.toggleOrthoView) self.accept('q', sys.exit) self.accept('wheel_up', self.zoomCamera, [0.1]) self.accept('wheel_down', self.zoomCamera, [-0.1]) self.accept('mouse2', self.defaultCam) self.accept('arrow_left', self.rotateBackpackH, [-5]) self.accept('arrow_left-repeat', self.rotateBackpackH, [-5]) self.accept('arrow_right', self.rotateBackpackH, [5]) self.accept('arrow_right-repeat', self.rotateBackpackH, [5]) self.accept('arrow_up', self.rotateBackpackP, [5]) self.accept('arrow_up-repeat', self.rotateBackpackP, [5]) self.accept('arrow_down', self.rotateBackpackP, [-5]) self.accept('arrow_down-repeat', self.rotateBackpackP, [-5]) self.accept('k', self.translateBackpackZ, [-0.1]) self.accept('k-repeat', self.translateBackpackZ, [-0.1]) self.accept('l', self.translateBackpackZ, [0.1]) self.accept('l-repeat', self.translateBackpackZ, [0.1]) self.accept('p', print, ["H = {}, P = {}".format(self.defaultH, self.defaultP)]) self.accept('a', self.toggleAA) # self.accept('b', self.leg.showTightBounds) # most efficient color to use due to antialiasing. base.setBackgroundColor(0, 0, 0, 0) def toggleAA(self): if self.enabledAA: render.setAntialias(AntialiasAttrib.MNone) else: render.setAntialias(AntialiasAttrib.MAuto) self.enabledAA = not self.enabledAA print(f"AA = {self.enabledAA}") def toggleBFC(self): self.enabledBFC = not self.enabledBFC render.setTwoSided(self.enabledBFC) def loadBackpack(self, path=None): self.clearBackpack() if path is None: self.backpack = loader.loadModel("assets/backpack.egg") else: self.backpack = loader.loadModel(path) self.backpack.reparentTo(render) def clearBackpack(self): if self.backpack is not None: self.backpack.removeNode() self.backpack = None def loadGUI(self): # Todo: figure out how to reposition buttons when window changes size # guiFrame = DirectFrame(frameColor=(0, 0, 0, 1), # frameSize=(-1, 1, -1, 1), # pos=(1, -1, -1)) self.modelButton = DirectButton(text = ("Change Backpack Model"), scale = 0.05, pos = (-1.6, 0, -0.4), command = self.openModel) self.texButton = DirectButton(text = ("Change Backpack Texture"), scale = 0.05, pos = (-1.6, 0, -0.5), command = self.openTexture) def saveScreenshot(self): # intent: Image number would increment if the file already exists just so it doesn't overwrite self.newfileName = self.fileName if not (os.path.isfile(self.newfileName)): # wip self.newfileName = self.fileName + str(self.i) self.i += 1 filename = self.newfileName + self.fileFormat base.win.saveScreenshot(Filename(filename)) self.aspect2d.show() print("Screenshot saved! {}".format(filename)) def reloadTextures(self): for tex in self.loadedTextures: if tex: tex.reload() # Rotate clothing def rotateBackpackH(self, value): self.currentH = self.backpack.getH() + value self.backpack.setH(self.currentH) def rotateBackpackP(self, value): self.currentP = self.backpack.getP() + value self.backpack.setP(self.currentP) def defaultRotation(self): self.currentH = self.defaultH self.backpack.setH(self.currentH) self.currentP = self.defaultP self.backpack.setP(self.currentP) def translateBackpackZ(self, value): self.currentZ = self.backpack.getZ() + value self.backpack.setZ(self.currentZ) # Camera Modifiers def defaultCam(self): base.cam.setPos(self.defaultCamPos) self.orthoLens.setFilmSize(self.filmSizeX_BASE, self.filmSizeY_BASE) def zoomCamera(self, value): self.filmSizeX, self.filmSizeY = self.orthoLens.getFilmSize() self.orthoLens.setFilmSize(self.filmSizeX + (value * self.filmSizeX_BASE / 2), self.filmSizeY + (value * self.filmSizeY_BASE / 2)) base.cam.setPos(base.cam.getX(), base.cam.getY() + value, base.cam.getZ()) ### def browseForImage(self): path = Path(askopenfilename(filetypes = ( ("Image Files", "*.jpg;*.jpeg;*.png;*.psd;*.tga"), ("JPEG", "*.jpg;*.jpeg"), ("PNG", "*.png"), ("Photoshop File", "*.psd"), ("Targa", "*.tga")))) return path def browseForModel(self): path = Path(askopenfilename(filetypes = ( ("Panda3D Model Files", "*.egg;*.bam"), ("EGG", "*.egg"), ("BAM", "*.bam")))) return path def loadBackpackModel(self, file: str): self.backpackModel = file self.loadBackpack(self.backpackModel) def loadBackpackTexture(self, file: str): # File is an absolute path # So let's be compatible with both JPG+RGB and PNG variants of textures. fileExt = os.path.splitext(file)[1] fileTex = os.path.splitext(file)[0] if fileExt == '.png': # No special calls needed tex = loader.loadTexture(file) else: # todo: panda doesn't like loading _a.rgb like seen below # For now let's just only check for an a_rgb if not PNG tex = loader.loadTexture(file) # if os.path.isfile(fileTex + '_a.rgb'): # tex = loader.loadTexture(file, fileTex + '_a.rgb') # else: # tex = loader.loadTexture(file) self.backpack.setTexture(tex, 1) self.backpackTex = file self.loadedTextures[0] = tex def openModel(self): filename = self.browseForModel() if str(filename) == ".": return try: self.loadBackpackModel(filename) except: print(str(filename) + " could not be loaded!") def openTexture(self): filename = self.browseForImage() if str(filename) == ".": return try: self.loadBackpackTexture(filename) except: print(str(filename) + " could not be loaded!") def toggleOrthoView(self): self.isOrthoView = not self.isOrthoView if self.isOrthoView: base.cam.node().setLens(self.orthoLens) else: base.cam.node().setLens(self.defaultLens) app = previewBackpack() app.run()