Python for Designers

by Roberto Arista

Fork me on GitHub

Coordinates and Primitives

A DrawBot program can control the drawing of multiple PDF canvases. Think of the canvas as a sheet of graph paper. DrawBot is a friend who is willing to draw on the graph paper according to your instructions. The most unambiguous way to communicate what you need is to provide numerical data to describe shapes: these are coordinates.

As you launch DrawBot, it shows you an empty gray canvas (left), a text box where you can type code (top right) and a console (bottom right). DrawBot has a default canvas size of 1000 by 1000 points. We can define a different canvas size using the newPage() function. If you want to work with a specific canvas size, you should define it before invoking any other drawing command; otherwise, DrawBot will provide a standard one. Just like in the physical world, where we pick up a piece of paper and then we start to draw.

Differently, from an interactive interpreter, DrawBot is script based. It means that you can store your programs as text files with a .py extension. If you want to execute your program, you have to invoke the interpreter to “run” using cmd+R explicitly.

Given the two-dimensional nature of the PDF canvas you have to provide a pair of coordinates: x and y. The origin of the coordinate system is located in the lower left corner of the canvas.

The order of the coordinates is conventionally x followed by y. In other words, width (x) comes first and then height (y).

Take into account that unlike a pixel grid, a vector canvas is continuous. This means that it can accept floating points coordinates without roundings such as 1.3.

The newPage() function accepts two kind of parameters:

  • a pair of coordinates
  • a string with a page format standard from this list.

Since we will dive into strings in the next chapters, for now let’s stick to numerical values.

newPage(100, 100)
newPage(100, 200)
newPage(300, 20)

I assume width and height are straightforward notions, but, what do these numbers mean? Apples, meters, yards? Well, each dimension provided to any drawing function in DrawBot is expressed in typographical PostScript points. As you maybe know from your graphic design history classes, typographical points have a quite troublesome relation with other unit systems. Since the desktop publishing revolution, the typographic points have been univocally made proportional to another unit measure, the inch. One typographical point is equivalent to the 72nd part of an inch. Assume we are in the realm of pixel images and the density of the image is 72 dpi; then one typographic point is also equivalent to one pixel. We will talk more about image resolutions in the next chapters.

You should see DrawBot as a skilled and quick draughtsman executing the instructions you gave in the code editor. As any drawing activity, you should think of colors and tools before actually using them. You first decide which color, then you start to spread ink on paper, right?

DrawBot sets some standard values as soon as you start the application: white background for the canvas, RGB black for the fill color, transparent stroke of width 1pt. We will dive into colors more in detail in the upcoming chapters. For the following demos we will use shades of gray, which are expressed in values between 0 and 1:

  • 0 means black
  • 1 means white
  • 0.5 half gray

and so on. Think of these figures as percentages of light presence (0 as 0%, .5 as 50%, 1 as 100%)

The features of our drawing tool are defined by 3 functions:

  • fill(aColor)
  • stroke(aColor)
  • strokeWidth(thickness)

They need to be called before the drawing functions: first we choose the tool, then we draw.

DrawBot provides four functions for drawing primitive shapes.

rect(x, y, width, height)

This function draws a rectangle on the canvas using x and y as lower left corner.

newPage(100, 100)

rect(10, 10, 20, 20)
rect(120, 10, 20, 20)  # outside the canvas
rect(40, 10, 10, 30)

exercise 5.1

Create a 100x100pt canvas. Draw four squares, side 20 units, each one facing a corner of the canvas

>>> Solution (.py)

oval(x, y, width, height)

This function draws an oval on the canvas using x and y as the lower left point of the rectangle where you could inscribe the oval. The width argument and the height argument correspond to the horizontal and vertical diameter.

newPage(100, 100)
oval(10, 10, 25, 18)
oval(22, 50, 35, 35) # a circle!
oval(68, 18, 22, 55)

exercise 5.2

Create a 100x100pt canvas. Draw four ovals, diameter 20 units, each one having its center point to a canvas corner

>>> Solution (.py)

line((x1, y1), (x2, y2))

This function draws a line between two points. Remember to enclose each pair of coordinates between parenthesis.

newPage(100, 100)
stroke(0)
strokeWidth(2)
# three parallel horizontal lines
# their points share the x values
line((10, 20), (90, 20))
line((10, 50), (90, 50))
line((10, 80), (90, 80))

exercise 5.3

How could you improve the quality of the code adding two identifiers?

>>> Solution (.py)

newPage(100, 100)
stroke(0)
strokeWidth(2)
# three parallel vertical lines
# their points share the y values
line((20, 10), (20, 90))
line((50, 10), (50, 90))
line((80, 10), (80, 90))
newPage(100, 100)
stroke(0)
strokeWidth(2)
# three diagonal lines
# they do not share any value
line((74, 50), (15, 30))
line((42, 63), ( 9, 89))
line((47, 29), (97,  6))

exercise 5.4

Create a 100x100pt canvas. Draw two lines connecting two non-contiguous canvas corners

>>> Solution (.py)

newPage(100, 100)
stroke(0)
strokeWidth(2)
# three diagonal lines
# they share some points
# they are connected
line((74, 20), (15, 30))
line((15, 30), ( 9, 89))
line(( 9, 89), (97, 46))

exercise 5.5

Create a 100x100pt canvas. Draw a zig zag connected polyline starting from top left corner and ending in bottom right corner of the canvas

>>> Solution (.py)

polygon((x1, y1), (x2, y2), (x3, y3),... close=True)
newPage(100, 100)
polygon((10, 18), (25, 90), (84, 34))

exercise 5.6

Draw two polygons of three sides each one, each polygon should have two vertices matching two contiguous canvas corners and one vertex matching the canvas centre

>>> Solution (.py)

The order of execution of the code, i.e. the arrangement of your statements, reflects the order in which elements will be drawn onto the canvas. For example:

newPage(100, 100)
fill(1)
stroke(0)
strokeWidth(2)
rect(14, 40, 50, 50)
oval(40, 20, 50, 50)

gives a different result from:

newPage(100, 100)
fill(1)
stroke(0)
strokeWidth(2)
oval(40, 20, 50, 50)
rect(14, 40, 50, 50)

exercise 5.7

Create a 100x100pt canvas. Combine the circles and the squares exercises, but draw the four squares below the four ovals using different shades of gray

>>> Solution (.py)

Take also into account that once a fill() or stroke() is set, it is used until further change. This script:

newPage(100, 100)
fill(1)
stroke(0)
strokeWidth(2)
oval(10, 40, 50, 50)
oval(20, 30, 50, 50)
fill(.5)
oval(30, 20, 50, 50)
oval(40, 10, 50, 50)

is different than this one:

newPage(100, 100)
stroke(0)
strokeWidth(2)
fill(1)
oval(10, 40, 50, 50)
fill(.75)
oval(20, 30, 50, 50)
fill(.5)
oval(30, 20, 50, 50)
fill(.25)
oval(40, 10, 50, 50)

Workbook

exercise 5.8

Your goal is to draw a black rectangle positioned in the middle of the canvas. Its height is equal to the height of the canvas. The rectangle's width instead changes according to a variable "factor" between 0 and 1:

  • if 0, the rectangle's width is equal to 0
  • if 1, the rectangle's width is equal to the canvas width

>>> Solution (.py)

exercise 5.9

Draw two rectangles, one leaning on the left side of the canvas, the other leaning on the right side. Their heights are equal to the canvas. Instead, each rectangle width is controlled by a variable "factor" between 0 and 1. When "factor":

  • is 0, you see no rectangle
  • is 1, you cannot see the white canvas background

>>> Solution (.py)

exercise 5.10

Draw four triangles. Fill each triangle with a different shade of gray. Also, each triangle should have two vertices matching to contiguous canvas corners and the last vertex positioned in the middle of the canvas.

>>> Solution (.py)

exercise 5.11

Extend the previous exercise: how could you make the height of each triangle react to a variable called "factor"?

  • when "factor" is 0, you see no triangle (they have no height!)
  • when "factor" is 1, the vertices touch in the middle of the canvas

>>> Solution (.py)

exercise 5.12

Draw four circles vertically aligned in the middle of a canvas. The shapes should be equally spread out. A variable "radius" controls the size of each circle. The color of each shape should be calculated according to its horizontal position in the canvas: left darker, right lighter.

>>> Solution (.py)

exercise 5.13

Draw two crosses, a dark gray "plus" shape, and a light gray "multiply" shape. The variable "factor" controls the thickness of the crosses:

  • if 0, the thickness is 2pt
  • if 1, the thickness is 30pt

>>> Solution (.py)

exercise 5.14

Draw a grid with three columns and three rows. The program should allow the presence of a safe space between the elements (often referred to as "gutter"). The "gutter" value should be expressed in typographical points.

>>> Solution (.py)