Lab Exercise 11: 3D Turtle
The purpose of this lab is to give you one more chance to extend your drawing system. By now you should have a reasonable understanding of how all the pieces fit together, so we will swap out the standard 2D turtle and put in a 3D turtle. All of your 2D turtle programs should continue to work just fine, but now you can make 3D shapes as well. In addition, you will be able to rotate your completed drawings with the mouse.
Tasks
This lab consists of three major components:
- Edit the Interpreter class to make use of the 3D turtle. Try drawing some simple shapes with the modified interpreter.
- Interpret the 3D symbols for L-systems to draw 3D trees.
- Update the Shape class and possibly some subclasses to make use of the 3rd dimension
The goal is to be able to make 3D shapes and scenes as easily as 2D. You should be able to create a Cube class, for example, as well as 3D trees and other L-system shapes that work exactly the same way as their 2D counterparts.
- Create a working folder for project 11. Copy your lsystem.py, interpreter.py, shape.py, and tree.py files from last week. Label them as version 5 in the comments at the top of the files. Download this file: turtleTk3D.py.
- One difference between the standard turtle and the 3D turtle is that the
latter is implemented as a class. Therefore, you need to create a single
instance of a 3D turtle object that will be used by all Interpreter
objects.
At the top of interpreter.py, import turtleTk3D instead of turtle. Just below the import, create a global variable named turtle and initialize it to None.
Modify the Interpreter.__init__() to look like the following. You may have other style fields that you want to keep in your code.
def __init__(self, width=800, height=800): ''' Initializes an Interpreter object and sets up a turtle graphics window and initial turtle parameters. This method is set up so there is only one turtle window created for all Interpreter instances. Parameters: width, height - the width and height of the graphics window to create ''' # drawing style information self.lineStyle = "normal" self.jitterSigma = 2 # ensures the code following is only executed once if Interpreter.initialized: return Interpreter.initialized = True # initialize the turtle graphics window global turtle turtle = turtleTk3D.Turtle3D(width, height) turtle.tracer(False)Note how the code for initializing the turtle window works. The first line tells the function to use the turtle variable in the global symbol table, and the second line creates a new Turtle3D object. By putting the 3D turtle object in a variable named turtle, expressions like turtle.left(angle) or turtle.forward(distance) still work as expected.
- Make the following additional changes to interpreter.py.
- Edit your hold method so that it calls only turtle.mainloop().
- If you have not already done so, make your '[' and ']' cases in drawString store and restore the turtle width in addition to the turtle position and heading.
- Modify the goto method by adding an optional parameter
zpos with a default value of 0. Change the turtle.goto() call
to include zpos as the third argument. The function declaration should
look like:
def goto( self, xpos, ypos, zpos=0 ):
- Modify the orient method so it accepts two additional optional
parameters named roll and pitch. The declaration should look
like:
def orient(self, angle, roll=0, pitch=0):
This function should call turtle.setheading(0), then call the roll, pitch, and yaw functions with the appropriate arguments.
- Modify the place method so its declaration looks like:
def place( self, xpos, ypos, angle=None, roll=0, pitch=0, zpos=0 ):
Note that this parameter order is chosen to not break existing code that assumes angle is the third parameter. Modify the body of the function appropriately, but keep the test on the angle parameter (in other words, to specify a roll or pitch you must also specify the yaw).
- Create additional methods named roll, pitch, and yaw, that call the corresponding 3D turtle functions. These functions will look like the existing width or color functions.
- The turtle.position() method now returns the tuple (x, y, z) instead of just (x, y). There are likely some places in the Interpreter.forward method that need to be updated to handle the z coordinate. In the jitter case, for example, add a random offset in z, in addition to x and y.
- Add cases in the drawString method for pitch and roll. The
& symbol means to execute the pitch method with a
positive angle, and ^ should execute the pitch method with a
negative angle. The backslash \ should execute the roll
method with a positive angle, and the forward slash / should execute
the roll with a negative angle.
Yaw is still + and -. (Either turtle.yaw() or turtle.left() and turtle.right() work.)
Note that you will have to use the string "\\" to represent the backward slash character because it is a special character (e.g., '\n' is a newline and '\t' is a tab).
- The 3D turtle color function works slightly differently from the original turtle. The primary difference is that it takes as input r, g, b values, a color string, or a single tuple (r, g, b) and it returns only a single color value. You will need to edit your angle brack cases '<' and '>' to take into account that there is no pencolor/fillcolor distinction.
- The 3D turtle class does not have as many duplicate functions with different names, such as the standard turtle functions goto(), setposition() and setpos(). You may therefore need to modify a few cases in the drawString method to get around this. You will find the documentation for Turtle 3D at the bottom of this page.
When you are done with these changes, try the first test program: test11-1.py.
- Make the following changes to shape.py.
- Add three optional parameters to the Shape.draw() method for roll, pitch, and zpos. Give them all default values of 0. The orientation parameter already holds the yaw information.
- Add roll, pitch, and zpos to the call to the Interpreter's place() method before the call to drawString.
- Run the test file test11-2.py. It is a simple example of how to begin building a 3D scene.
- Update your tree.py file. Add roll, pitch, and zpos to the parameter list of the draw method, giving them all default values of 0. Then pass the three parameters on to the parent draw function.
- Run the test file test11-3.py using one of the the L-systems below.
When you are done with the lab exercises, get started on the project.
Appendix: Turtle3D Documentation
The Turtle3D class implements a 3D turtle abstraction using the Tkinter package.
The Turtle3D class includes the following methods for public use.
-
__init__: the constructor function has six optional arguments.
- winx (default 800) is the horizontal window size
- winy (default 800) is the vertical window size
- title (default 'Turtle 3D')
- position (default (0, 0, 0) ) is the initial 3D position of the turtle
- heading (default (1.0, 0.0, 0.0)) is the initial forward direction of the turtle
- up (default (0.0, 0.0, 1.0)) is the initial up direction, which defines left and right (yaw) relative to the forward direction. The default values mean that left and right work identically to the standard 2D turtle as long as the turtle does not pitch or roll.
- reset(): deletes all drawing and resets the turtle to its initial position.
- forward(distance): goes forward in the current turtle direction. If the pen is down, it creates a line.
-
goto(xnew, ynew, znew): The function can take one, two or
three parameters. If the pen is down, it creates a line.
- One parameter: xnew is treated as a two-element tuple (x, y) and the turtle is placed at (x, y, 0).
- Two parameters: xnew and ynew are used and znew is set to 0.
- Three parameters: the turtle is placed at (xnew, ynew, znew).
- left(angle): turn left (yaw) as defined by the current heading and up direction.
- right(angle): turn right (yaw) as defined by the current heading and up direction.
- yaw(angle): go left (positive) or right (negative) relative to the current turtle orientation.
- pitch(angle): go up (positive) or down (negative) relative to the current turtle orientation.
- roll(angle): rotate right (positive) or left (negative) about the current turtle heading.
- width(w): set the width of the pen to w
- color(r, g, b): sets the pen color. Arguments can be the standard X11 color strings defined in rgb.txt or r, g, b values. The first argument can be a tuple (r, g, b) or the color values can be given individually. Color values should be in [0, 1].
- up(): pick up the pen
- down(): put down the pen
- tracer(blah): does nothing (no visible turtle)
- circle(radius, theta): draws a circle of the given radius. The theta argument is optional and permits drawing only the angular fraction of the circle specified.
- position(): returns a tuple with (x, y, z)
- heading(): returns a tuple of two tuples with the current heading and up vectors. ( (hx, hy, hz), (ux, uy, uz) )
- setheading(heading): ideally heading should be two vectors representing the turtle's forward and up directions. If a single scalar is provided, the turtle is set so the turtle is in the drawing plane (up of (0, 0, 1)) and is rotated according to the argument. This gives it the same functionality as the 2D turtle.
- fill(q): if q is True, the turtle will begin to store points until the fill function is called with False as the argument. Then it fills in the area defined by the points. The turtle must visit at least three points after fill(True) is called and before fill(False) is called in order to generate a polygon.
- nudge(n): allows the user to adjust the turtle's coordinate system by nudging the forward vector in the specified direction. The argument n should be a 3-element sequence (list or tuple). A value like (0.0, -0.1, 0.0) nudges the turtle's orientation down (i.e. like gravity).
- cube(distance): takes one optional parameter (size) and draws a cube.
- setRightMouseCallback(func): takes one argument, which should be a function with an argument (other than self for a class method) called event. The mouse click location will be in event.x and event.y. Note that the click location will be in window coordinates, not turtle coordinates.
- window2turtle(x, y): takes in window coordinates (like those in event.x and event.y above) and returns a tuple (x', y', 0) with the corresponding turtle coordinates in the default view.
- hold(): goes into a main loop waiting for user input.
- wait(): goes into a main loop waiting for user input.
- updateCanvas(): updates the Canvas to draw any new shapes.

