SVG, Inkscape, and the Isometric Projection

How to use affine transformation matrices to transform flat two-dimensional drawings in SVG to sides of an isometric projection with an Inkscape extension.

For simple woodworking projects I like to use free software drawing tool Inkscape to sketch up a plan for what I intend to build. Depending on the type of project, these drawings range from rough sketches, to fully detailed top-down, side, and frontal views. This is great for finding out how much timber is needed and if I can reuse anything from my stock. It also allows me to easily tweak parts of the design.

For a recent project I experimented with the isometric projection of the object I was designing. This type of drawing can help to get a better feel for the spatial layout of the design — it also makes your drawings look like they belong on the instruction sheets of a well-known Swedish manufacturer of flat-pack furniture. Inkscape offers an axonometric grid for this purpose in addition to its default rectangular grid. With both grid angles set to 30°, this grid is suitable for drawing in an isometric projection.

Simple example of isometric projection
An underwhelming example of an object in an isometric projection with grid.

For simple shapes consisting of straight lines, drawing the lines directly on this grid works quite well, but as soon as the drawing gets more complicated (e.g., round shapes indicating drill holes) this becomes tricky to get right, and inaccurate to boot. So I started wondering; is there a way to convert a flat two-dimensional representation a plane to each of the three visible sides in an isometric projection?

A bit of experimenting with the transformation tools in Inkscape shows that moving from a flat projection to isometric appears to be a matter of applying a sequence of transformations to the object.

Transformations in SVG
Transforming two-dimensional objects to an isometric projection by applying a sequence of transformations, step-by-step.

All of these transformations use 30° angles, or its derivates; the scaling value of ≈0.866 is the cosine of 30°. This makes sense considering the angles of the isometric grid — although I will freely admit that I arrived at these values by experimenting in Inkscape rather than sound mathematical reasoning.

A cardboard box
The result of these transformations; a fancy cardboard box.

So now I know the exact steps needed to go from one projection to another, but executing a bunch of transformations each time soon gets tedious, so can this be automated?

Enter linear algebra

Transforming vectors sounds suspiciously like maths, so time to brush up on some theory. As it turns out, all four basic transformations — i.e., scaling, shearing (or skewing), rotating, and translating (which is simply moving) — can be expressed in the form of a matrix. Multiplying a vector coordinate by this matrix results in a new transformed coordinate, and if you apply the same matrix to each vector coordinate in an object, the object is transformed. These matrices are called affine transformation matrices.

Now even though I am transforming a flat projection of an object to its isometric projection, the actual vector drawing remains rooted in the good old two dimensions of x and y. So in theory, these matrices ought to be 2×2 matrices right?

Not quite. It turns out that this holds true for the three transformations I am using here (scaling, shearing, and rotating), but for translation, a third dimension is needed. To solve this, a virtual third dimension is added to the two-dimensional (x, y) vectors, with a fixed coordinate of 1. To distinguish this fake dimension from a normal three-dimensional one, it is called w instead of z. So for purposes of transformation, all vectors are extended to (x, y, w) coordinates, where w = 1.

1 0 100 0 1 -30 0 0 1 M 100x 20y 1w Vin 200x -10y 1w Vout
Multiplying affine transformation matrix M with vector coordinate Vin results in a new coordinate Vout. Example matrix M performs a translation.

For each of the four basic transformations there exists a 3×3 matrix that can be applied to each vector coordinate of an object, resulting in a transformed object. These matrices look like this:

X 0 0 0 Y 0 0 0 1 Mscale 1 tan ∠x 0 tan ∠y 1 0 0 0 1 Mshear cos ∠ -sin ∠ 0 sin ∠ cos ∠ 0 0 0 1 Mrotate 1 0 X 0 1 Y 0 0 1 Mtranslate
The affine transformation matrices for the four basic operations. For Mscale the variables X and Y represent the horizontal and vertical scaling factors, with 1 causing no change. In Mshear the tangent of the desired slant angle is used, where 0 causes no change, and 1 effects a 45° degree slant. Mrotate requires the sine and cosine values of the desired angle (for a counter-clockwise rotation, swap the negative and positive sine value). Finally, Mtranslate moves an object by X and Y.

Conveniently, the Scalable Vector Graphics standard (SVG) supports these matrix transformations, as does Inkscape which uses this standard as its file format. In SVG, any object can have a transform attribute, where an affine transformation matrix may be specified — either directly through the matrix notation, or by using the scale, skewX, skewY, rotate, and translate short-cuts. The matrix notation used in SVG is a bit confusing at first, because it looks like matrix(A,B,C,D,E,F). It turns out the bottom row of the matrix is omitted, because it is always [0, 0, 1], and the rest of the values map to our 3×3 matrices like this:

A C E B D F 0 0 1
Mapping the positions in a 3×3 transformation matrix to the corresponding transform statement.

And now for something completely different

Knowing how to define a matrix that rotates, shears, or scales, the next step is to combine matrices into combined transformation matrices that transform objects from a flat projection to each of the three isometric sides — and back again. To combine multiple transformation matrices, they need to be multiplied in the correct order, which is the reverse of the sequence of transformations you want to apply. So for the conversion from flat projection to isometric top-down view described earlier, where an object is first scaled, then sheared, and finally rotated, the multiplication goes like this:

Mto-iso-topdown = Mrotate × (Mshear × Mscale)

Performing matrix multiplication on paper certainly proved to be nostalgic for a couple of tests, but performing maths is why we invented computers in the first place. Time to let them do some work for a change.

My first instinct was to open a Python console and do the multiplication there, and unsurprisingly Python comes with everything I need in the form of the Numpy library. Taking the case of the transformation to the top-side view of the isometric projection again, I can define and multiply the matrices like this:

$ python3
>>> import math
>>> import numpy
>>> 
>>> cos30 = math.cos(math.radians(30))
>>> sin30 = math.sin(math.radians(30))
>>> tan30 = math.tan(math.radians(30))
>>> 
>>> m_scale_y = numpy.matrix([[1, 0, 0], 
...                           [0, cos30, 0], 
...                           [0, 0, 1]])
>>> m_shear_x = numpy.matrix([[1, tan30, 0],
...                           [0, 1, 0], 
...                           [0, 0, 1]])
>>> m_rotate = numpy.matrix([[cos30, -sin30, 0], 
...                          [sin30, cos30, 0],
...                          [0, 0, 1]])
>>> m_rotate * (m_shear_x * m_scale_y)

matrix([[ 0.8660254, -0.8660254,  0.       ],
        [ 0.5      ,  0.5      ,  0.       ],
        [ 0.       ,  0.       ,  1.       ]])

When this matrix is converted to the SVG transformation notation it looks like this (rounded down to three decimal places for clarity):

matrix(0.866,0.5,-0.866,0.5,0,0)

I can repeat this process for all six transformations — i.e., three for converting from a flat projection to each of the three sides in an isometric projection, and another three to reverse those transformations — to arrive at a set of matrices I can apply.

Extending Inkscape

Now that I have my six combined matrices, I can manually set the transform attribute on an object in SVG (in Inkscape you can use the XML editor to do this), or I can enter the matrix in Inkscape's transformation tool. This is fine for one or two objects, but it gets cumbersome after a while. Luckily, Inkscape can be extended with a trivial amount of Python code, so I wrote an extension to do the grunt work for me.

Isometric projection Inkscape extension settings
The Isometric Projection Inkscape extension's settings.

To help authors of extensions, Inkscape developers have thoughtfully included a number of helpful Python classes and methods for extensions to depend on. For matrix operations simpletransform.py provides us with format­Transform and parse­Transform. The former converts a 2×3 matrix to its corresponding SVG notation; the latter greatly simplifies my extension by solving the problem of transforming objects that already have their transform attribute set with a transformation. So if an object has its transformation set to rotate(45), parse­Transform will convert that rotate short-cut to its corresponding matrix, and multiply the matrix you want to apply to it.

The amount of code needed for this extension turned out to be trivial, with most of the work being done by the six transformation matrices, and the code provided by Inkscape.

This works in the browser too

SVG is of course not limited to Inkscape; it works right here in today's web browsers, and integrates surprisingly well with HTML5, and its friends EcmaScript and CSS. Have a look at the source of this web page if you are curious, or just experiment with this live demonstration of an affine transformation matrix. The list of transformations can be reordered by dragging and dropping them.

1 0 0 0 1 0 0 0 1
Live demonstration of an applied affine transformation matrix.

Conclusion

With this Inkscape extension, drawing objects in the isometric projection became a lot easier for me. I like how I can draft parts of my design in a flat projection with the rectangular grid, and have the benefit using of Inkscape's useful Align and Distribute tool, before I transform the object to one of the isometric projection's sides.

I was pleasantly surprised at how powerful SVG can be once you get a better grasp of some of the mathematical underpinnings. I also took the opportunity to explore the current state of SVG and HTML integration targeting modern evergreen web browsers for this write-up, and it is encouraging to see how much can now be achieved even without any third-party EcmaScript libraries.

A patio table in isometric projection
The patio table I was working on, in glorious faux-3D.