This post is filled with new ShapeJS features, twist and turns in development and the birth of a new creator. It all started with some prototyping work for this years art project. We’ve been playing with some triple helix designs. Early in the process having some 3D printed prototypes in hand can really help communicate your designs. One way to make a triple helix is using a Twist transform. A Twist rotates the object around the z-axis. In this first example we take the union of three boxes and twist them around into a triple helix structure.


function main(args) {
var vs = 0.1*MM;
var r = 1*MM;
var R = 5*MM;
var length = 30*MM;
var w = R + r + vs;
var sizeZ = length + 4*vs;
var grid = createGrid(-w,w,-w,w,-sizeZ/2,sizeZ/2,vs);
var maker = new GridMaker();
var union = new Union();
var part1 = new Box(R, 0, 0, r, 4*r, length);
union.add(part1);
var part2 = new Box(R, 0, 0, r, 4*r, length);
part2.setTransform(new Rotation(0,0,1,PI*2/3));
union.add(part2);
var part3 = new Box(R, 0, 0, r, 4*r, length);
part3.setTransform(new Rotation(0,0,1,-PI*2/3));
union.add(part3);
union.setTransform(new Twist(period));
maker.setSource(union);
maker.makeGrid(grid);
meshSmoothingWidth = 2;
return grid;
}
This technique worked well for a few turns. But a twist operation starts to look weird as you crank it. If you want to make a true Spring or Helix for many turns then you need something else. This time we created a new datasource called Spring. It’s a parametric implementation of an infinite spring.

function main(args) {
var vs = 0.1*MM;
var r = 1.47*MM;
var R = 14.7*MM;
var springPeriod = 50*MM;
var springLength = 120*MM;
var w = R + r + vs;
var sizeZ = springLength + 2*r + 4*vs;
var baseHeight = 2*MM;
var grid = createGrid(-w,w,-w,w,-sizeZ/2,sizeZ/2,vs);
var maker = new GridMaker();
var spring1 = new Spring(R,r,springPeriod, springLength);
var spring2 = new Spring(R,r,springPeriod, springLength);
var spring3 = new Spring(R,r,springPeriod, springLength);
spring2.setTransform(new Rotation(0,0,1,2*Math.PI/3));
spring3.setTransform(new Rotation(0,0,1,2*2*Math.PI/3));
var union = new Union();
union.add(spring1);
union.add(spring2);
union.add(spring3);
union.add(new Cylinder(new Vector3d(0,0,-springLength / 2), new Vector3d(0,0,-springLength / 2 - baseHeight), R+r));
maker.setSource(union);
maker.makeGrid(grid);
return grid;
}
This is where our story takes a twist. One of the original creators I made for Shapeways is called the Statement Vase. This creator took a sentence and wrapped it around a cylinder. As we played with these twists and springs I thought it would make a nice variant of that idea. This creator takes an image tile and then wraps it around a cylinder with a specified number of bands. Here is the item 3D printed:

var voxelSize = 0.15*MM;
function makeTile(path, width, height, thickness){
var img = new ImageBitmap(path, width, height, thickness);
img.setBaseThickness(0.0);
img.setVoxelSize(voxelSize);
img.setBlurWidth(2*voxelSize);
img.setImagePlace(ImageBitmap.IMAGE_PLACE_BOTH);
img.setUseGrayscale(false);
return img;
}
// makes rectangular tiling of a plane
function getSymmetry(width, height){
var splanes = new Array();
var count = 0;
splanes[count++] = new ReflectionSymmetry.getPlane(new Vector3d(-1,0,0),width/2);
splanes[count++] = new ReflectionSymmetry.getPlane(new Vector3d(0,-1,0),height/2);
splanes[count++] = new ReflectionSymmetry.getPlane(new Vector3d(1,0,0),width/2);
splanes[count++] = new ReflectionSymmetry.getPlane(new Vector3d(0,1,0),height/2);
return new ReflectionSymmetry(splanes);
}
function main(args) {
var image = args[0];
var thickness = 2.2*MM + voxelSize / 2.0;
var baseThickness = 2.3*MM + voxelSize;
var tileWidth = 32 *MM;
var tileHeight = 32 *MM;
var height = 101.6*MM;
var wraps = 4; // how many bands
var tilt = 6; // how much tilt the bands (should be integer)
var gapWidth = 0.5; // relative width of gap between bands
var aspect = 1+ gapWidth;
var dx = wraps*aspect*tileWidth;
var dy = tilt*tileHeight;
var alpha = Math.atan(dy/dx);
var R = Math.sqrt(dx*dx + dy*dy)/(2*PI); // radius of wrap
var w = R + thickness + voxelSize;
var grid = createGrid(-w,w, -height/2, height/2 + baseThickness, -w, w, voxelSize);
var tile = makeTile(image, tileWidth, tileHeight, thickness);
var imageTransform = new CompositeTransform();
imageTransform.add(getSymmetry(aspect*tileWidth, tileHeight));
imageTransform.add(new Rotation(new Vector3d(0,0,1), alpha));
imageTransform.add(RingWrap(R));
tile.setTransform(imageTransform);
var union = new Union();
union.add(tile);
// add base
union.add(new Cylinder(new Vector3d(0,-height/2,0), new Vector3d(0,-height/2 + baseThickness,0), R+thickness/2));
// add top
var rim = new Subtraction(new Cylinder(new Vector3d(0,height/2,0), new Vector3d(0,height/2 + baseThickness,0), R+thickness/2),
new Cylinder(new Vector3d(0,height/2,0), new Vector3d(0,height/2 + baseThickness,0), R-thickness/2));
union.add(rim);
var maker = new GridMaker();
maker.setSource(union);
maker.makeGrid(grid);
meshSmoothingWidth = 1;
return grid;
}
Now if your watching closely you’ll see that I didn’t actually use Twist or Spring in this new creator. Turns out our RingWarp and Symmetry engine was a better fit for this creator. This allowed us to layout the tiles without any distortion. So there’s the twisted journey, I hope you enjoyed it.