Using Objects by Drawing

Python is an object-oriented programming language. It uses objects as an organizing principle for code. While code organization is a more advanced concept we won’t discuss much in this course, we will need to use objects (rather than author them) to take advantage of Pythons’ expansive library. As a vehicle for learning how to use an object, we’ll use the drawingpanel class which acts as a wrapper around Python’s standard library for graphical user interfaces (GUI)

Summary (Notes)

The Drawing Panel Module

Download the source file containing the drawingpanel.py class here:

To use the class, we must import its containing module first. The module here is simply the filename. We import a module by using the import statement:

from drawingpanel import *

This imports all of the classes, functions, etc., declared in drawingpanel.py and makes them available to us in our program. Make sure to include this line at the top of your file!

Using the DrawingPanel Object

Here is a skeleton of a main function creating a Drawing Panel and then drawing on it.

def main():
    panel  = DrawingPanel(100, 100)
    panel.set_background("purple")

    canvas = panel.canvas
    canvas.create_oval(0, 0, 100, 100, fill="green", outline="blue")

    panel.mainloop()

DrawingPanel is a class, a blueprint for creating an object and a specification of what object provides as far as state and behavior that we can use. To create a DrawingPanel, we invoke its constructor by calling the name of the class like a function. The DrawingPanel constructor expects two arguments, the width and height of the panel. The constructor returns a new instance of the DrawingPanel class which we can then use.

One way to use an object is to call a method on it. A method is a function that is part of an object. The syntax for a method call or invocation is similar to a function but requires an object:

<expression>.<method name>(<arguments>)

The “dot notation” used here can be thought of as accessing a particular method of a particular object (denoted by the expression to the left of the dot). For example, the method call panel.set_background("purple") calls the set_background method on the panel object.

In addition to methods, we can access fields of objects. Think of a field as a variable that is tied to a particular object. The syntax of a field access is:

<expression>.<field name>

Like a normal local variable, we can use the value stored in the field or modify the field’s contents using assignment. The line canvas = panel.canvas creates a new local variable called canvas, assigning it the value stored in the canvas field of the panel object. The panel.canvas field contains the panel’s Canvas object which we use to draw onto the screen.

Note that even though the local variable and the field share the same name, the choice of name of the local is still arbitrary, and there is no link between the local and the field other than the fact the after the assignment canvas refers to the same canvas stored in panel.canvas. We could have performed the local variable initialization c = panel.canvas and the effect would be equivalent (modulo the change of name from canvas to c).

DrawingPanel Reference

To set the background color, call the set_background(color) method of the DrawingPanel object:

The color is a string drawn from the following choices:

To draw shapes, use the following methods on the DrawingPanel’s Canvas object:

To specify the color of a shape, use the fill and outline named parameters of these methods:

A full reference for the Canvas class can be found here:

Warm-up: Boxes in Boxes

First, in a Python program in a file called drawings.py, write a function boxesinboxes() that draws a series of concentric boxes to the screen.

The DrawingPanel itself is 500x500. For the rest of the figures:

Ehrenstein Illusions

Many optical illusions rely on a series of patterns in order to generate their effect. A well known optical illusion is the Cafe Wall Illusion. In reality, the cafe wall illusion is simply alternating rows of black and white squares. However, they are offset and thus give the illusion of being sloped when really they are straight lines. Drawing such figures can be tedious because of their repetitive design which makes them an excellent candidate to be drawn by a computer program.

We’ll be drawing Ehrenstein illusions, another optical illusion that consists of concentric circles with diamonds inside. Since you wrote the computer program that rendered the drawing, you’ll know that the diamond structure is indeed made of straight lines. But if you just stare at the drawing instead, you’ll swear that some of them are curved!

In your ehrenstein.py file, write a function ehrenstein() that produces the drawing below:

Here are some of the basic properties of the above the drawing.

The interesting bits of the drawing are the five figures it contains. Here are their properties.

Location Position Size of subfigure Number of circles Size of grid Miscellaneous
Top-left (0, 0) 75x75 6 N/A N/A
Top (105, 15) 50x50 10 7x1 N/A
Left (10, 100) 70x70 3 2x5 N/A
Middle (175, 115) 100x100 8 3x3 N/A
Bottom (200, 430) 25x25 4 10x2 Only circles (no diamond/box)

Your goal is to reproduce this drawing almost exactly (See addendum paragraph below). Note that because of differences in how Python renders shapes on different operating systems, the panel may render slightly differently on your machine versus what is in the above picture, e.g., a circle may become more pixelated. As long as your output looks identical on visual inspection, you will receive full credit for the output.

Design

First of all, remember to download drawingpanel.py from the links mentioned in the introduction. drawingpanel.py needs to reside in the same directory as your own .py files.

Also, remember to include the following line at the top of your program

from drawingpanel import *

which will tell Python to import all the names found in the drawingpanel module including the DrawingPanel class.

Like the previous homeworks, you should be on the look out for places to decompose the problem into smaller pieces in order to get full credit for design. To get you started, here are two functions that you should include in your program:

A function that draws Ehrenstein circles. A single combination of a box, diamond, and set of concentric circles makes up a Ehrenstein circle. Note that this subfigure appears repeatedly throughout the diagram albeit with different positions, sizes, etc. Your function should be flexible nought to account for these possibilities.

The circles themselves are evenly spaced concentric circles. To get a sense of how to draw such figures, you should think of the differences in the radii of the circles as in the following diagram containing 7 concentric circles:

r is the radius of the outer-most circle and a is the distance between successive concentric circles. a is a very important value in computing the relationship between the location and size of each successive concentric circle.

A function that draws a grid of Ehrenstein circles. Given your function to draw a single Ehrenstein circle, you should then construct a function to draw a grid of Ehrenstein circles. You can then use this function to construct each of the grids in the above diagram. This function will need many parameters since in addition to controlling the size of the grid, you will also need to control the particulars of the Ehrenstein circles within the grid.

There is more decomposition that should occur in this homework. Your job is to identify it and write functions to deal with those pieces accordingly.

In general, decomposition is the right way to tackle a problem like this. Breaking a down a seemingly-impossible problem into smaller problems until they look doable is a cornerstone of algorithmic thinking, something you can accomplish here in a very visual way.

Incremental development

In addition to decomposition, you should also exercise your skills in incremental development. In this homework, you need to write functions that generalize to many scenarios. Starting with a function that has many parameters to begin with may be difficult because you won’t immediately see how they should be used. Instead, try writing functions that take no parameters, e.g., a function that draws a fixed number of concentric circles to some place on the screen. From there, generalize the function incrementally by adding a parameter, checking to see if everything works as expected, and then repeating the process until your function can handle all the cases it needs to.