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.
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.
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.
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
.
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:
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:
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.
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 formatTransform
and
parseTransform
. 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)
, parseTransform
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.
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.