Tkinter 3D [page banner]

By Bill Allen

Getting Started with Tkinter for 3D Display

This page will be used to survey some underlying basics about 3D graphics programming, and in the process will show that, for some limited tasks, Tkinter may be all you need for 3D display without having to resort to OpenGL and all that OpenGL entails (dependence on third party support, a steep learning curve, and all the setup and effort).

My own breakthrough on this came when I wanted to display some fairly small meshes in a simple orthogonal view. I suddenly realized that "orthogonal" just means "no perspective," and thus the 3D data could be presented as a straight projection to 2D. That is, only the XY vertex coordinates were needed, and the Z data could be ignored. Well, hey! The Tkinter Canvas widget is a champ at drawing polygons with XY data.


  What's New


  PullSDTS

  Python Notes
  Tkinter Notes
  Tkinter 3D
  Code Samples

  File Formats
  SDTS Notes

  Glossary
  Index

Simple Wireframe Display

The first script here, objwire.py, was written to show how an absolute minimum of Python code--107 lines--can put up a wireframe view. When you run this script, it asks you to choose an .obj (or NASA .tab file in OBJ format). If the file has OBJ data in a non-complex form, the wireframe will soon appear along with a short report. The complete wireframe is shown, front and back. Click the window frame's close icon to finish.

If you don't have an OBJ file handy, grab the public-domain test cow. For details about the simple ASCII-based OBJ format, see the "File Formats" page.

Next up is objwire2.py. It can work with complex OBJ face statements, such as Poser exports, and it comes set to hide the lines of back-facing polygons (as in the image above). At the top of the script is a hideLines = 1 variable that can be changed to 0 (no line hiding) or -1 (lines show as light gray).

Inside objwire2.py you will find this section of code to handle the front/back-face decision:

        flag = 0
        if hideLines:           #do vector check with 2nd vertex vs. 1st & 3rd
            if ((vDat[fDat[i][0]][0]-vDat[fDat[i][1]][0])       \
               * (vDat[fDat[i][2]][1]-vDat[fDat[i][1]][1]))     \
               - ((vDat[fDat[i][0]][1]-vDat[fDat[i][1]][1])     \
               * (vDat[fDat[i][2]][0]-vDat[fDat[i][1]][0])) < 0:
                if hideLines == -1: flag = 1  #draw gray outline
                else: continue                #don't draw, loop back for another
While the direct referencing makes the script run faster, it sure doesn't do much to help comprehension. The easier-to-read but slower original code was this (notice that we're still not using Z values):
        flag = 0
        if hideLines:           #do vector check with 2nd vertex vs. 1st & 3rd
            p1x,p1y = vDat[fDat[i][0]][0],vDat[fDat[i][0]][1]
            p2x,p2y = vDat[fDat[i][1]][0],vDat[fDat[i][1]][1]
            p3x,p3y = vDat[fDat[i][2]][0],vDat[fDat[i][2]][1]
            v1 = p1x - p2x
            w1 = p3x - p2x
            v2 = p1y - p2y
            w2 = p3y - p2y
            normal = (v1*w2)-(v2*w1)
            if normal < 0:
                if hideLines == -1: flag = 1  #do gray outline
                else: continue                #don't draw, loop back for another
We don't care here about the value of the polygon normal, only whether it is negative. Negative means that the polygon is facing away from the orthogonal viewpoint, and thus either should not be drawn, or, optionally, should be drawn in a different color or line weight.

This shortcut using two sides of a polygon as XY vectors is a great trick from Chapter 51 of Michael Abrash's Graphics Programming Black Book (see references). He uses the first and last sides of a polygon for the calculation, but, in these scripts, the first and second sides are used, so we can handle any polygon without first having to determine which side is the last. Any two adjoining sides will do, so long as the entire polygon is, or can safely be assumed to be, lying in one plane. The mathematical explanation is that the "cross product" of these two vectors is perpendicular to the plane in which they lie.

In putting 3D elements onto a screen, the normal techniques are to either 1) draw the rearmost elements first and then progressively draw the more forward elements until all are drawn ("painter's algorithm"), or 2) do a bundle of calculating to figure out what is actually visible and then only draw that. With Tkinter we can draw the wireframe polygons in whatever order they arrive from the data flow. And, if we are drawing the back lines in gray, we can simply tell Tkinter to outline that polygon in gray and drop it to the bottom of "the pile" (polygons remain as distinct, addressable objects on the canvas):

        f = cv.create_polygon(x1,y1,x2,y2,x3,y3,fill='',outline='black')
        if flag:                       #back-facing
            cv.itemconfig(f,outline='#AAAAAA')
            cv.lower(f)                #cv is a Canvas widget
Try that in OpenGL!

There is one last point to note here, which is that these two scripts are safe to run by themselves or from Tkinter-based IDEs such as IDLE. Rather than dealing at all with a possible major conflict with a root.mainloop() command, the script instead goes into a root.wait_frame() loop that is exited when the Toplevel frame is destroyed. These are the few key lines:


import Tkinter as tk

class mainWindowClass:
    def __init__(self,master):
        self.frame = tk.Toplevel(master)
        self.frame.protocol('WM_DELETE_WINDOW',self.quit)   #close button
    def quit(self):
        self.frame.destroy()

root = tk.Tk()
root.withdraw()
mainWin = mainWindowClass(root)
root.wait_window(mainWin.frame)

Of course, trying to display interactive 3D wireframe rotation, let alone shaded or rendered views, is probably pretty much beyond Tkinter. But, if all you need to do is to inspect a 3D file to see what it contains, or to perform a task such as UV unwrapping, then check out Tkinter as a good bet.

Thanks again to Cecilia Ziemer for testing to make sure that this code runs OK under Mac OS9.

/// come back - there will be more ///

All of this comes out of recent new project to display 2D images and 3D shape models from NASA asteroid data archives. So there are examples of progressively more complex Tkinter 2D and 3D uses in preparation, up to and including a complete utility now in early alpha.


Tkinter 3D Page News

14 March 2002: This page first posted.

See also "What's New on the Project Site."


Revised: 14 Mar 02 rev 1
http://www.3dartist.com/WP/python/tkinter3d.htm
Page & presentation © Copyright 2002 Columbine, Inc. - All Rights Reserved
The author's Python code posted to this page is free software placed into the public domain.
Any mentioned trademarks are the property of their respective owners.