Let’s start with the good stuff first. Here is a 3D print of a new ShapeJS creator called the Image Lathe. This was printed at Shapeways in Black Glossy Ceramics for $25.
The basic premise of the creator is to give the user 2 images to control creation of the object. The first image is the profile image. This describes the basic shape of the object. If your familiar with using a pottery wheel or a lathe then you know how this works. You take an outline and sweep it through a 360 degree path. This is great for making a bunch of common shapes like vases, bowls, cups, chess pieces etc. After you have the basic shape then we’ll use another image to make the surface pattern. This image is reflected several times to make an interesting final pattern.
Profile
The profile is created from an image that is rotated in space. This ShapeJS script takes an image and makes a shell out of it. So the 2D images on the left is turned into the 3D vase on the right.
function makeShell(profilePath, width, height, voxelSize){ var radius = width/2; var boxDepth = 2*Math.PI*radius; var boxWidth = radius; var boxHeight = height; var image = new ImageBitmap(profilePath, boxWidth, boxHeight, boxDepth, voxelSize); image.setBaseThickness(0.0); image.setUseGrayscale(false); image.setBlurWidth(2*voxelSize); var ct = new CompositeTransform(); ct.add(new Rotation(0,1,0, -Math.PI/2)); // align side of the image box with xy lane ct.add(new Translation(0, 0, -radius/2)); ct.add(new RingWrap(radius)); image.setTransform(ct); return image; } function main(arg){ var voxelSize = 0.4*MM; var vaseWidth = 80*MM; var vaseHeight = 100*MM; var profilePath = arg[0]; var padding = 2*MM; var gWidth = vaseWidth + 2*padding; var gHeight = vaseHeight + 2*padding; var shell = makeShell(profilePath, vaseWidth, vaseHeight, voxelSize); dest = createGrid(-gWidth/2,gWidth/2,-gHeight/2,gHeight/2,-gWidth/2,gWidth/2,voxelSize); var maker = new GridMaker(); maker.setSource(shell); maker.makeGrid(dest); return dest; }
Image Symmetry
The next step is to take a second image and use that to modify the profile geometry. Here we are going to use the symmetry engine of ShapeJS. This picture shows the setup for a symmetryOrder of 12. Your image is placed in a hall of mirrors. Here 12 planes of reflection are used. The reflection causes the image to invert and then tile. So you get 6 copies of the original + inverted image across the object. The volumetric space looks like pie slices from your image projected from a center point.
We take this image from the user:
And it generates this geometry using the symmetry engine:
Final Object
To make the final object we intersect the profile form with the image geometry. Everyplace the image geometry intersects the profile we get material.
Here is the code for the final creator:
function getDihedralSymmetry( n){ var a = PI/(n); var cosa = Math.cos(a); var sina = Math.sin(a); var symm = new ReflectionSymmetry(); var splanes = new Array(); var count = 0; splanes[count++] = new ReflectionSymmetry.getPlane(new Vector3d(cosa,0,-sina),0); splanes[count++] = new ReflectionSymmetry.getPlane(new Vector3d(-cosa,0, -sina), 0); symm.setGroup(splanes); return symm; } function makeShell(profilePath, width, height, voxelSize){ var radius = width/2; var boxDepth = 2*Math.PI*radius; var boxWidth = radius; var boxHeight = height; var image = new ImageBitmap(profilePath, boxWidth, boxHeight, boxDepth, voxelSize); image.setBaseThickness(0.0); image.setUseGrayscale(false); image.setBlurWidth(2*voxelSize); var ct = new CompositeTransform(); ct.add(new Rotation(0,1,0, -Math.PI/2)); // align side of the image box with xy lane ct.add(new Translation(0, 0, -radius/2)); ct.add(new RingWrap(radius)); image.setTransform(ct); return image; } function main(arg){ var voxelSize = 0.4*MM; var vaseWidth = 80*MM; var vaseHeight = 100*MM; var symmetryOrder = 12; var imgPath = arg[0]; var profilePath = arg[1]; var img = loadImage(imgPath); var imgBoxHeight = vaseHeight; var imgBoxWidth = img.getWidth() * imgBoxHeight /img.getHeight(); var imgBoxThickness = vaseWidth/2; var image = new ImageBitmap(img, imgBoxWidth, imgBoxHeight, imgBoxThickness, voxelSize); image.setBaseThickness(0.0); image.setUseGrayscale(false); image.setBlurWidth(voxelSize); image.setImagePlace(ImageBitmap.IMAGE_PLACE_TOP); var padding = 2*MM; var gWidth = vaseWidth + 2*padding; var gHeight = vaseHeight + 2*padding; var ct = new CompositeTransform(); ct.add(new Translation(0,0,-imgBoxThickness/2)); ct.add(new RingWrap(vaseWidth/2)); ct.add(getDihedralSymmetry(symmetryOrder)); image.setTransform(ct); var shell = makeShell(profilePath, vaseWidth, vaseHeight, voxelSize); var intersection = new Intersection(image, shell); dest = createGrid(-gWidth/2,gWidth/2,-gHeight/2,gHeight/2,-gWidth/2,gWidth/2,voxelSize); var maker = new GridMaker(); maker.setSource(intersection); maker.makeGrid(dest); meshSmoothingWidth = 2; meshErrorFactor = 0.05; return dest; }
Thank you for posting this example!