#objwire.py version 0.0d - Tkinter 3D OBJ Wireframe - 3/14/2002 #an original script written by Bill Allen for Python 2+ #this script has no price, limitations, or warranty - use at your own risk """ This script shows how an OBJ file wireframe can be displayed orthogonally using Python 2+ with Tkinter. Only XY vertex and 3- or 4-sided face data is read and used. Since there is no perspective projection, Z data can be ignored. As a demonstration of speed and how a minimum of coding (107 lines) can produce a 3D display with Python/Tkinter, only simple OBJ face statements are read (Poser OBJs won't work), and no hidden line removal is performed. This script starts up with a file-open dialog, displays the selected file if it can, gives a brief report, and waits for the window close icon to be clicked. """ import os.path, sys import Tkinter as tk from tkFileDialog import askopenfilename as tkFileGet from string import rstrip, split def drawWireframe(cv,cvSize,fn): try: fin = open(fn,'r') except IOError,(eNum,eStr): return fn+'\n I/O Error ('+str(eNum)+'): '+eStr txList = map(rstrip,fin.readlines()) #strip trailing white space fin.close() vDat = [] #vertex (point) list fDat = [] #face (polygon) list ex = infoClass() #extents info collector ex.minX = None for i in range(len(txList)): #initialize extents if txList[i]: #not an empty line tx = split(txList[i]) if ex.minX == None and tx[0] == 'v': #find first vertex x,y = float(tx[1]),float(tx[2]) ex.minX = x ex.maxX = x ex.minY = y ex.maxY = y if tx[0] == 'f': #find first polygon if tx[1].find('/') > -1: #faces specified in v/vt/vn form return 'Cannot handle complex OBJ face statements' break #finished with inits if ex.minX == None: #no vertex data was found return fn+' is not a valid OBJ-format file' for i in range(len(txList)): if txList[i]: #not an empty line tx = split(txList[i]) if tx[0] == 'v': #read vertices & find extents x,y = float(tx[1]),float(tx[2]) if x < ex.minX: ex.minX = x elif x > ex.maxX: ex.maxX = x if y < ex.minY: ex.minY = y elif y > ex.maxY: ex.maxY = y vDat.append([x,y]) elif tx[0] == 'f': #read faces p1,p2,p3 = int(tx[1])-1, int(tx[2])-1, int(tx[3])-1 if len(tx) == 5: #quad fDat.append([p1,p2,p3,int(tx[4])-1]) else: #triangle fDat.append([p1,p2,p3]) ex.xOff = -(((ex.maxX-ex.minX)/2.0)+ex.minX) #determine offset if any ex.yOff = -(((ex.maxY-ex.minY)/2.0)+ex.minY) # needed to center object ex.xScale = float(cvSize[0])/(ex.maxX-ex.minX) #find scale to X pixels ex.yScale = float(cvSize[1])/(ex.maxY-ex.minY) # find scale to Y pixels if ex.yScale < ex.xScale: scale = ex.yScale # & use most critical of 2 else: scale = ex.xScale cvOffX = (cvSize[0]/2) + 10 #offset to center within display window cvOffY = (cvSize[1]/2) + 5 for i in range(len(vDat)): #center & scale XY coords to raster integers vDat[i][0] = cvOffX + int((vDat[i][0]+ex.xOff)*scale) vDat[i][1] = cvOffY - int((vDat[i][1]+ex.yOff)*scale) for i in range(len(fDat)): if len(fDat) == 4: f = cv.create_polygon(vDat[fDat[i][0]][0],vDat[fDat[i][0]][1], vDat[fDat[i][1]][0],vDat[fDat[i][1]][1], vDat[fDat[i][2]][0],vDat[fDat[i][2]][1], vDat[fDat[i][3]][0],vDat[fDat[i][3]][1], fill='',outline='black') else: f = cv.create_polygon(vDat[fDat[i][0]][0],vDat[fDat[i][0]][1], vDat[fDat[i][1]][0],vDat[fDat[i][1]][1], vDat[fDat[i][2]][0],vDat[fDat[i][2]][1], fill='',outline='black') return fn+' - '+str(len(vDat))+' points & '+str(len(fDat))+' faces' #end def drawWireframe class infoClass: #empty class to hold temporary info def __init__(self): pass #end class infoClass class mainWindowClass: def __init__(self,master): self.frame = tk.Toplevel(master) self.frame.title('Tkinter 3D OBJ Wireframe 0.0d') self.frame.geometry('660x490+10+10') self.frame.resizable(0,0) self.canvas = tk.Canvas(self.frame,bg='white',bd=3,relief='sunken') self.canvas.pack(side='top',anchor='nw',fill='both',expand=1) self.frame.protocol('WM_DELETE_WINDOW',self.quit) #close button def getFile(self): fn = tkFileGet(title='Select a file',parent=self.frame, initialdir=os.path.split(sys.argv[0])[0], filetypes=[('OBJ','*.obj *.tab'),('All Files','*.*')]) self.frame.update_idletasks() if fn: if fn[-4:].lower() in ['.obj','.tab']: return drawWireframe(self.canvas,(640,480),fn) else: return fn+' selected\n\nNeed .obj or .tab file in OBJ format' else: return 'No file selected' def quit(self): self.frame.destroy() #end class mainWindowClass #--- go to work --- root = tk.Tk() root.withdraw() mainWin = mainWindowClass(root) mainWin.canvas.create_text(10,10,anchor='nw',font=('Helvetica',10), text=mainWin.getFile()) root.wait_window(mainWin.frame) ### end script ###