Andrew Koenig
Gillette, New Jersey
My wife and I recently remodeled our house extensively, including the kitchen. Among the kitchen-remodeling decisions was the shape of part of the counter and the design of the tile wall behind the stove. I wrote small Python programs to help with these two parts of the design.
This note recounts some of my thoughts in writing these programs, and discusses some of the attributes of Python that led me to choose it for this purpose.
When my wife and I remodeled our kitchen, one of our goals was to change the shape of our kitchen counter to provide better seating. The old counter was made of plastic laminate over plywood, and we had specified its shape so as to make it easy to fabricate: as a sequence of straight segments and circular arcs. Part of the counter extended past the end of the kitchen proper to form an eating area that went part way into the living room. The size and location of that extension worked well to entertain guests, but the somewhat abrupt corners and limited overhang made it less comfortable than it might be for eating.
Accordingly, when we started planning to remodel the kitchen, we thought about a more appropriate shape for that part of the counter. This counter was to be made of synthetic stone, and we believed that the company that was to fabricate the counter could make it in any shape we wanted--provided that we could specify it accurately enough.
I remembered reading, a number of years ago (Martin Gardner: The "Superellipse," a curve that lies between the ellipse and the rectangle, Scientific American, September 1965), that the Danish architect Piet Hein had come up with a family of shapes that combined some nice properties of circles and rectangles. I felt that one of these shapes might be just what we needed for the counter: corners that were gently rounded enough that people could sit at them, but more space than a circular extension would provide.
I remembered the fundamental equation for what I thought would be the right curve: x2.5+y2.5=r2.5, where r is the (minimum) radius of the desired shape. However, that memory left me with three problems:
Another uncertainty in the kitchen design was the tile wall behind the stove. We knew the shape of that area, and we had picked out a collection of tile colors that we liked. However, the tile store had boxes of tiles in some of those colors that had already been opened. If we needed any more tile than was in each of those boxes, we would have had to buy an additional full box, which was much more tile than we needed for this small area. Accordingly, we had to come up with a tile design that we liked, and that also did not exhaust the available tile.
The remainder of this paper will discuss the Python programs that I wrote to help solve these problems.
It is, of course, easy to write a program in any sensible programming language that will compute the coordinates of a series of points according to a given formula. The difficult part is finding the right way to deal with those coordinates. I wanted
In other words, I expected the hard part of the problem to be producing first a small-scale drawing, and then clipping appropriate sections out of a full-scale version of the same drawing so that I could print them and tape the sheets together. To simplify the problem, I started by looking around for software that I already knew how to use, and that might help with that part of the problem.
After a few false starts, I realized that I had actually been using
such a program for many years:
The
dpost
program that translates troff
output into PostScript
form has options to specify magnification, along with the x
and y coordinates of the area to be magnified.
So what I needed was a way to translate the coordinates that I would
generate into troff
form.
Fortunately, troff
has a companion program named
pic
that makes it easy to deal with coordinates.
I could therefore reduce the problem to one of transforming the
coordinates--once I had computed them--into appropriate
pic
incantations.
That realization made it clear that the programming I had to do was
fairly straightforward numerical computation and text
manipulation--a nearly ideal match for Python.
I know C++ much better than I know Python, and have been using it for much longer. Moreover, there is nothing in this program that makes it particularly ill-suited for C++. Why, then, did I do it in Python? The main reason was faster turnaround: Because of Python's interpretive implementation, I could see the effect of a change within a second or two, compared with 10-20 seconds in C++. Moreover, I found it hard to imagine that the final program would take long to execute, so run-time performance wasn't a factor.
The program itself is fairly straightforward, with no particular efforts toward elegance or long-term maintainability. Nevertheless, it has a few points of interest:
points = map(lambda(x, y): (-x, y), points[:1:-1]) + pointsThis code assumes that the two symmetric halves of the counter will meet in a single point, which should not be replicated. It avoids replication by ending the extended slice
points[:1:-1]
at 1
instead of at
0
.
The -1
causes the points to appear in reverse order after
the join, maintaining the symmetry.
pic
command:
def absline((x1, y1), (x2, y2)): print "line from %f, %f to %f, %f" % (x1, y1, x2, y2)
This program made it easy to experiment with shapes and sizes, such as
a circular counter, which has an exponent of 2,
and a counter with an exponent of 3.
It would be uncomfortable to sit at
one of those corners.
The foregoing images should make it clear that 2½ is a better
choice of exponent than 2 or 3:
Once I had the counter in its final form,
I could change the grid lines, previously
one scaled foot apart, to be one scaled inch apart:
When I enlarged this image to an appropriate size, I could print
segments of it onto individual sheets of paper, tape them together,
and give them to the fabrication company.
Finally, let's look at a photo of the completed counter, along with
the program's output in the corresponding orientation:
These images show how useful the program was in arriving at the final design.
Having figured out the shape for the counter, I turned my attention to the tile wall behind the stove. Our original design approach involved colored markers and graph paper, but we soon realized how slow that approach was. To streamline the process, I decided to try to write an interactive Python program that we could use to visualize prospective tile designs.
I know a smattering of Tcl and Tk--enough to realize that it is straightforward for a program to generate a large number of rectangular buttons, each of which has an associated action. Accordingly, I decided to adopt a brute-force strategy of assigning a separate button to each tile. The program would have a notion of a ``current color,'' along with a button that would change that color. Clicking a tile would change that tile to the current color.
This strategy did not address the question of output. The reason was that I had available a program named VuePrint, which, among other abilities, can capture the bitmap that a window contains. Such captured bitmaps would be more than adequate as a guide to laying out tile.
The hardest part of the tile-design program turned out to be making each button change its color when clicked. It is easy to associate a function with a button, so that clicking the button calls the function, but that function does not appear to have any easy way of determining the identity of the button that triggered it. For that reason, I could not figure out how to write a single function to implement the notion of ``change my button's color to the current color.''
To solve this problem, I used a bit of subterfuge: I defined a higher-order function that, given a button, would create a function to set that button's color:
def setcolor(obj): def f(): obj["bg"] = color return fThis seemingly trivial function probably took me more time to figure out than the rest of the program put together. Once I had it, however, it was easy to use it to create an appropriate number of buttons, each with its own dynamically created color-setting function:
def createWidgets(self): for i in range(nwidth): for j in range(nheight): b = Button(self, bg = "black") b.config(command = setcolor(b)) b.place(height = height, width = width, x = i * height, y = j * width) # and so on...Here, the magic is in the call to
b.config
, which
associates with the newly created Button
named
b
a function that, when called, will set b
's
color to the current color.
Aside from this magic, the program was straightforward. As with the other program, I did not try to make it elegant or easily maintainable. Nevertheless, it served its purpose in making it possible for us to try quite a number of layouts.
Our first try looked like this:
The large beige rectangles in the upper corners represent maple
kitchen cabinets; the inverted T is the range hood, and the gray
rectangle at the bottom is the stove's stainless-steel backsplash.
Our final design was yet another small variation on the same theme:
We felt that the blue background and yellow corners suggested sky and
sunlight, and the red and yellow tiles near the stove itself suggested
fire.
The details are less interesting than the finished kitchen:
in which the similarity to the screen shot is obvious.
Indeed, the first difference that I notice is the two outlet covers,
the locations of which I did not know at the time I wrote the program.
Because our kitchen project was unique, and because we were our own customers, the resulting software was of a rather different nature than a commercial project would have been:
pic
, troff
, dpost
, and
VuePrint
.
Tkinter
library.
It was easy to learn enough about that library to do useful things
with it.
What made Python particularly well suited for this context was the rapid turnaround that comes from an interpretive implementation, and the libraries that have grown up around it.