Programming:
Printing Photos in 3D 13th
March 2011
One of the 3D printing projects that I've had the
most satisfaction
from is a Processing sketch to convert 2D photos into a model that can
be 3D printed. This isn't a 3D model of what's in the photo,
rather
it's a way of representing the photo itself with a 3D printed model, I quite liked the idea of a 3D way to print a 2D photo...
I describe 2 different techniques in this post and
you'll find the source code for the sketches at the end.
Halftone
The first technique borrows from a standard
printing technique - halftone
printing. A
halftone image uses dots of varying size to create varying brightness
in an image. I wanted to make a way to print photos with a
halftone pattern made from a 3D printed model, this would allow me to
create a hafltone that existed on its own, without paper.
(if you can't see the images, try moving back from your monitor)
The
first step was to see how I might be able to get information from a
photo. This is a task that is really easy to do in
Processing.
If I create a 'PImage' to store the photo I can then
interrogate it to find the brightness in the image pixel by pixel.
Once I have the brightness of the pixel I can use
it to scale the diameter of a cylinder. Since I am
using a white material I can use brightness value directly; a brighter
pixel will produce a larger cylinder which in turn will look brighter
when the model is printed.
To make the cylinders I used a function that I'd
developed before to produce a 3D cylinder from point to point in 3D
space:
//move first point to origin, equivalent transform for second // tx1, ty1, ty2 = 0 float tx2 = x2-x1; float ty2 = y2-y1; float tz2 = z2-z1; PVector t = newPVector(tx2,ty2,tz2);
t.mult(magmult); //set up reference vectors & calculate angles PVector z = newPVector(0,0,1); float rotX = PVector.angleBetween(t,z); float rotZ = PI/2+atan2(ty2,tx2);
pushMatrix(); //translate to start point translate(x1,y1,z1); //rotationZ first, then rotation X rotateZ(rotZ); rotateX(rotX); //draw cylinder cyl(t.mag()/magmult,wide,sides); popMatrix(); }
This code sets up a transform that allows me to
shift a generic cylinder into the position that I want with its ends at
the two sets of x, y, z coordinates I supplied in the arguments.
An interesting quirk I've found is that this code
used to fail when I specified cylinder lengths of less than 1.
This was a problem since I wanted to be able to use a
cylinder of about 0.8-0.9mm depth. In the end I traced this
problem to the PVector "t"; when I tried to use "t.mag()" to get the
length of the cylinder it would fail for vectors of less than unit
length. I have no idea why and I haven't found any other case
where this was a problem, it seems to be specific to this code. Anyway,
the float "magmult" is a hack to fix this, all I do is multiply the
vector by this value, do what I need to do to get the directions and
then remember to divide by the value again later.
The cylinder is actually drawn, vertex by vertex,
by the function "cyl":
void cyl(float cylHeight, float cylRadius, int sides) { //draws a cylinder at (0,0,0), given height, radius and detail float[] circX = newfloat[sides]; float[] circY = newfloat[sides]; //fill circle array for draw later for (int i=0;icos((2*float(i)*PI)/sides); circY[i] = cylRadius*sin((2*float(i)*PI)/sides); } //draw the base circle beginShape(TRIANGLE_FAN); vertex(0,0,0); for (int i=0;ivertex(circX[i],circY[i],0); } vertex(circX[0],circY[0],0); endShape();
//draw the end circle beginShape(TRIANGLE_FAN); vertex(0,0,cylHeight); for (int i=0;ivertex(circX[i],circY[i],cylHeight); } vertex(circX[0],circY[0],cylHeight); endShape();
[The auto formatting hasn't worked so well for the
above, it's the
"<" signs in the for loops termination condition that causes the
problem]
There is another problem with the code above, the
order
I set up to draw the vertices produces very dodgy normals that have to
be fixed in Blender or Meshlab; I accepted this since the fix is a 30
second job and working out the way to fix the initial draw would
take longer with no guarantee of success anyway given my coding skills.
To write an STL file for 3D printing I've used the
excellent unlekker library, all I have to do is have a switch to start
the export on a draw() after I press a key ("e" in this case) and then
end the export when all the parts have been written.
In the end I found that I could only easily
get about 30x30 pixels into a model of reasonable price and
size, I also found some problems with Shapeways' triangle limit,
although I'm not sure that this error is correct based on the polygon
count in Blender.
The final models are quite effective, even with
the low resolution, and I've used quite a few as gifts.
This
model is available in my Shapeways shop
as a co-creator customisable model (I think the
guide price of just over €50 is based on the maximum possible volume of
the model found with a not-very-interesting all-white image) and I've
had some orders from
it but I've decided that I will release the code for a few reasons:
It
was fun to set up and the code might be interesting or useful to
someone - if you do something interesting with it please tell me!
To
keep the price down I get less than a $10 markup on the model anyway
(more wouldn't seem fair since setting up the model takes minutes
and most of the rest of the time taken is corresponding with the people
who are interested in ordering one and I quite enjoy that)
I
quite
like the idea of sharing knowledge - it sounds cheesy but for me the
really nice thing about sharing code like this is that you can
give things to someone (everyone?) else and you still
have it yourself
So, if you want to you can get the code
below, download a copy of Processing with the unlekker library, use
Gimp or something to scale down your photo to 30x30 pixels, create the
model, process the model in Blender to make it printable (or fix the
code so it doesn't need fixing in Blender) and then order it from
Shapeways or anywhere else... or if you want to you can use the
co-creator and I'll do it for you.
Aperture
Grille
The second model is based on an aperture grille
CRT monitor.
Colour CRT monitors use three electron guns to
create three separate beams for red, green and blue parts of the final
picture. The different
colours come from three different phosphors on the front of the picture
tube. To control the colours separately each gun must only be able to
see one type of phosphor so the other two must be hidden from it.
Different monitors use different techniques, shadow masks
(metal sheets with holes in them) were common but higher-end monitors
sometimes used aperture grilles which were grids of wires oriented
vertically,
parallel to stripes of phosphor (they also had two horizontal damping
wires to keep the grille in alignment: a rather sadistic
trick used to be pointing these out to people who hadn't noticed
them and watching a good 30% go quietly crazy when they couldn't stop
seeing them...)
This second model uses a similar principle to the aperture grill monitor to
allow me to put two photos in one model. In the same way as I
did in the first model I take a photo and interrogate it to get the
brightness of each pixel. I then use this to scale a part of
the model, in this case the length of a cylinder not the diameter as
before.
To allow the model to display two photos I interleave
two independent sets of 'phosphors', one for each photo. The
aperture grille in front then hides one of the sets of cylinders from
one angle and the other set when viewed from a different angle.
All of these models work best from one or two metres away and the
change from one photo to another is quite strange when you see it
because you don't see the individual elements of the model from that distance; I've a
YouTube video of this happening below:
This model doesn't produce images that are as clear as the first
one, mainly since the 'pixels' are much smaller and half of what you
can see is plain white aperture grille. The model is also much
more fragile due to the thin rods it's made of so for both of these
reasons I'm not planning to make a co-creator of this one (if enough
people mail me I might change my mind but, since you have the code
below you can always make your own...)
Resources and Code
The parts of the source code below that I've written are released under the GNU General Public License,
the link will provide details of what this allows you to do.
Please note that you don't get a warranty of any kind with
this code (see the license terms for more detail); I'm not a
professional programmer and so if it doesn't work for you the odds are
I won't be able to fix it even if I have time and inclination to try.
Processing and the libraries & tools I reference have their own
license
terms, see their web pages for details.
See the sidebar on the right for links to Processing, Shapeways, Blender, Gimp and other useful things.
fastness
- Iain Banks Graphics
All of the content from my Iain M Banks website, now
shifted to be a section in this one
fastness
- Links & Resources:
Processing:
An open source programming tool aimed at artists,
engineers and designers. Simple, light and Java-based with a
wealth of libraries and a strong user community
Shapeways:
3D
printing for the masses - plastics and metal to your design or team up
with a desigenr to personalise a design with a 'co-creator'.
Visit my Shapeways
shop for some things I've designed.
Meshlab:
MeshLab is an open source, portable, and extensible
system for the processing and editing of unstructured 3D triangular
meshes
Blender:
Blender
is the free open source 3D content creation suite, available for all
major operating systems under the GNU General Public License
Gimp:
GIMP is the GNU Image Manipulation Program. It is a
freely distributed piece of software for such tasks as photo
retouching, image composition and image authoring. It works on many
operating systems, in many languages
Inkscape:
An Open Source vector graphics editor, with
capabilities similar to Illustrator, CorelDraw, or Xara X, using the
W3C standard Scalable Vector Graphics (SVG) file format
Ponoko:
Retail laser cutting outlet with centres in New
Zealand, USA, Germany, Italy and the UK (if not more by now)