Stamp Act

Published Sep 1, 2016 by Pillow Computing Consortium at /blog/2016-08-31-stamp-act/

Today, let’s talk about stamps. At least a little bit.

Having a custom stamp is useful if, for instance, you need to stamp stuff. You can get custom stamps off of the internet, but you have to pay money, and often there are various properties of the stamps which are limited (e.g. size, content, etc.).

One can buy little pads of foam in “make-your-own-stamp” kits, which you simply heat up using a hair dryer and then pressing in an impression that you want the foam to hold. This is fine, but making the impression then poses a problem. You can’t simply print it like you would with an inkjet printer, you have to carve it.

Unless you have a 3d printer, which I do. Now you can just print the impression.

That’s all I really have to say about stamps. I could talk on end about how I tried different types of impressions, but I eventually settled on beveling the text. Not many programs handle beveling text well, though. Blender’s builtin bevel function dislikes the many corners that appear in text. OpenSCAD (famously) doesn’t even have a bevel function, and doing a minkowski transform with a sphere serves only (predominantly) to make the text smaller.

So, I wrote a Python script that converts a black-and-white bitmap into a beveled STL file.

It’s not a complicated script, most of the heavy lifting is done using numpy. Essentially, the bitmap is loaded, and then eroded away one step at a time. Then the erosion steps are layered, and the layered volume is converted into a STL file using the marching cubes implementation.

Here’s the code, complete with hardcoded values:

import numpy as np 
from scipy import misc, ndimage 
from skimage import measure 
 
im = misc.imread("lflstamp.png", flatten=False, mode="L") 
im = misc.imresize(im, 20) 
print im.shape 
volume = []#np.empty((im.shape[0], im.shape[1], 0)) 
 
# Have a few all-one layers 
volume.append(np.pad(np.zeros(im.shape), 1, "constant", constant_values=0)) 
for i in xrange(5): 
    volume.append(np.pad(np.ones(im.shape), 1, "constant", constant_values=0)) 
 
# Duplicate the raw image for a few layers 
for i in xrange(3): 
    volume.append(np.pad(im, 1, "constant", constant_values=0)) 
 
# For each layer, dilate & add to the volume 
for i in xrange(20): 
    print im.shape, len(volume) 
    volume.append(np.pad(im, 1, "constant", constant_values=0)) 
    #volume = np.concatenate((volume, im), axis=2) 
    im = ndimage.binary_erosion(im) 
    #print im 
    pass 
 
volume = np.stack(volume) 
 
verts,faces = measure.marching_cubes(volume, 0.5) 
 
print verts 
print faces 
 
# Figure out a scale for the thingy 
# Also, transpose into the X-Y plane 
verts = map(lambda v: (-v[2], v[1], v[0]), verts) 
maxs = [0,0,0] 
for v in verts: 
    if v[0] > maxs[0]: 
        maxs[0] = v[0] 
    if v[1] > maxs[1]: 
        maxs[1] = v[1] 
    if v[2] > maxs[2]: 
        maxs[2] = v[2] 
 
print maxs 
limits = [90,100,50] 
scales = [maxs[0]/limits[0], maxs[1]/limits[1],maxs[2]/limits[2]] 
print scales 
scale = 1.0 / max(scales) 
 
# Write out the STL 
f = open("out.stl", "w") 
f.write("solid foo\n") 
for face in faces: 
    f.write("facet normal 0 0 1\n   outer loop\n") 
    for vn in face: 
        f.write("vertex %f %f %f\n"%tuple(map(lambda x: x*scale,verts[vn]))) 
    f.write("endloop\nendfacet\n") 
    pass 
f.close()

 

Lane Kolbly

Story logo

© 2022 Pillow Computing Consortium