Computational Making with Twoville

Following are the notes from a talk I gave at CCSC-NE 2023 in Ithaca, New York.

For the next few minutes, I will tell some stories about a piece of software that I've been writing for making two-dimensional shapes using mathematics and computer science. There are a few things you should know before we begin:

Twoville

The software is called Twoville. It is a programming environment for generating scalable vector graphics files (SVGs). Its name was inspired by Papert's notion of Mathland. Just as one moves to France to become fluent in French, one moves to Mathland to become fluent in mathematics. In Twoville, you live in two dimensions and become fluent in shape.

There are many 2D drawing languages and libraries. Twoville is different from most of them in that the shapes you define can be modified using either the mouse or code. This feature is best understood in a live demonstration. I usually have students start with a circle, like this:

This feels a lot more like "Hello, World" than printing a string to a terminal. But it looks like there might not be anybody left to say hello back.

Many graphics programs would draw a circle with code like this:

circle([50, 50], 45, :cornflower)
circle([50, 50], 45, :cornflower)

In Twoville, the circle command is also a function call. But the call itself has no parameters. Positional parameters are like unlabeled buttons on an elevator. You have little choice but to press things; thinking isn't welcomed. An alternative is keyword parameters:

circle(center = [50, 50], radius = 45, color = :cornflower)
circle(center = [50, 50], radius = 45, color = :cornflower)

But this makes for wide code. It doesn't fit on tablets and budget laptops issued by schools, leading to horizontal overflow or awkward wrapping. Instead, the parameters are set on the circle after it is made.

Bidirectional Editing

Another very important feature of Twoville is bidirectional editing. When the user places the cursor in a shape's code or hovers the mouse over its visual representation, handles appear. Each handle corresponds to one of the properties that was set in code. If I drag on a handle, it changes both the picture and the code.

When I first implemented bidirectional editing, I replaced the existing property \(p\) with a value \(p'\) determined by the mouse position. But then I ran into occasions where the property was built out of a complex expression. It felt wrong to simply overwrite what the programmer had in place. So, I tried to figure out ways to preserve the structure of the original expression. I'll show what I have currently on this circle:

The expression preservation algorithm walks this gauntlet to decide how to update the source code:

If the expression is an invertible binary operator, I favor updating its rightmost operand. If this operand is precious, the programmer may lock it with parentheses.

To implement this selective updating, I must maintain a representation of the expression structure and the values of its subexpressions to help figure out what the new value should be. A lot of work needs to be done to make this system consistent and stable. It doesn't handle properties generated by loops very well.

Polygons and Ungons

Besides rectangles and circles, you can make several other kinds of shapes. We can for instance make a mountain range using the polygon command:

Each of the vertices can be moved around via its handle.

Sharp points don't hold up very long in the physical world. On mountains, the sharp points erode. On vinyl stickers, the sharp points catch and start to peel. We can preemptively round our polygon by switching to an ungon:

That's a little too rounded, however. What happens behind the scenes with an ungon is Chaikin's algorithm. The vertices are replaced by their midpoints. The straight lines connecting these midpoints are pulled into curves using control points at the old vertices.

We can adjust how these control points are placed to alter the rounding:

Bézier Curves

The most versatile command for drawing shapes is path. It allows for a combination of straight lines, circular arcs, and Bézier curves. Most students don't know what Bézier curves are. I introduce by showing first a straight line between two points:

And then putting a black hole into the scene that sucks the line toward it. This black hole is officially called the control point:

Two quadratics form a nice simple, elliptic leaf:

Loops and Functions

Not every outreach event I put on with Twoville gets students exploring iteration and abstraction. I'm okay with them just having a positive experience with numbers. Nevertheless, Twoville does support standard computational devices like loops, functions, and recursion.

When you motorize turtle drawing with loops, you can trace out interesting algorithmic shapes. Here's the standard regular polygon:

Those sharp angles aren't aesthetically pleasing. Twoville's turtle supports a gradual curl that makes the turtle bend around a circular arc:

In this program, I define a helper function that moves forward and curls 90 degrees:

With repeated calls, I can trace out a finger and then loop around the calls to make an asterisk:

Fabrication

The output of a Twoville program is a scalable vector graphics (SVG) file. We feed the SVG file into a fabrication tool like a vinyl cutter, laser cutter, pen plotter, or embroidery machine to turn our design into a physical object that has a life independent of the computer used to make it.

Conclusion

In closing, I'd like to anticipate a few questions that you might have.

What ages of students have been using Twoville? We've done many hour-long workshops with middle schoolers. An hour isn't enough time to go much farther than frankenshapes. We've also done a couple of week-long summer camps for high schoolers. And my own elementary and middle school children have had a number of opportunities to use it, but they get a level of support that others don't.

Why invent a new language? To support bidirectional editing, I need to have intimate knowledge of the program. In particular, I need to know which expressions produce the spatial properties so that when the mouse changes those properties, I can update the expression. I need to write my own interpreter to achieve this intimacy. Supporting all of Python sounded like too much work. Smaller, domain-specific languages have fewer commands and fewer alternatives, which makes them easier to learn and easier to implement.

Why not use blocks? Someday I might support both modes, allowing one form to be translated into the other. I have written blocks languages in the past, and I recognize that they fix several problems. They make the commands of the language discoverable, and they reduce the typing burden that many young people are not ready to take on. But blocks also have ergonomic issues. They consume a lot of space, they are hard to share outside the IDE, and they are slower to assemble for experienced users. Having a small language makes a blocks interface feel less important, so it's a low priority right now.

Is there a 3D version? I started with a 3D language named Madeup and spent a year taking it to an elementary school classroom. But 3D printing is far too slow. Students fought over the scraps of plastic and the misprints. The vinyl cutter finishes most of the jobs I send it in under a minute. Everyone can go home with something, even in the hour-long workshops.

Thus we have Twoville, a language for making two-dimensional designs using code and direct manipulation via the mouse.