Voxels versus Triangles

We’ve been studying the size of voxel versus triangle models recently. For really complex models we knew that the file size for voxels would be smaller. As you approach one triangle per voxel you end up using significantly more space to express the details. The basic information for a triangle is at least 3 floats to describe its vertices. Compared to using a bit or byte per voxel that quickly adds up. At Shapeways we are starting to see a bunch more data exhibits this type of density. Scanned data, digital fabrics and fractal art all push the limits of what triangle formats can comfortably express. We’re seeing files that compress down to 66% of the original all the way to 4%. This spreadsheet shows a small trial we ran to get an idea of the data.

File Size Results

Now you might be thinking that binary STL files are not the best way to pack 3D data. It’s true that you can find much better formats to transmit triangle data. But what you won’t find is a format that is more widely adopted. About 70% of all uploads are STL. Import and Exporter writers have voted and STL is it. I believe this is related to how simple and stable the format is. It’s a few pages of wiki definition and it hasn’t changed in years. Any solution that is going to replace STL will need to best it in key features and remain as simple to implement. It’s possible to write an STL reader or writer in a night and lots of people have done it.

So how does SVX compare when handling normal files? I’d like to define 3 classes of data. Simple data are models with 25K and make up the bulk of today’s 3D printed models. The last class are Complex data. These models approach the complexity available from a 3D printer. Shapeways sits on an interesting pile of user data with over 3 million user models in our database. For this analysis we took a small sample of user uploads, removed any duplicates and then analyzed models over 25K triangles. What we found was that encoding these models in SVX had an average of a 48% size from binary STL. While we don’t consider the main advantage of using voxels as a space savings operation it is heartening to know it won’t mushroom our bandwidth and disk bills. We’ll want to perform a deeper analysis of many more models to really characterize this change but we feel this gives us a good place to start.

As the industry moves to denser models and multiple materials we think a native voxel format for uploads is necessary. Our users are already pushing the envelope for triangle uploads and by switching to voxels we can enable much higher fidelity designs that will have complete control of each voxel the printer prints.

Posted in Uncategorized | Leave a comment

Image Lathe – Variant

It’s been brought to my attention that our first Image Lathe example exhibited a great flaw. It seems some people think a Vase should be able to hold water! Now around the 3D printing world we talk about water-tight meshes but not usually this definition.

vase_diffuse

Here are the pieces:
react-diffuse2
profile

This code is fairly close to the original Image Lathe example. Instead of intersecting the pattern image with the profile we instead union the pattern with a slightly smaller profile shape. The innerShell is the profile shape that’s rotated to a cylinder that’s patternHeight smaller radius. If you wanted the pattern on the inside of something then you could make the innerShell larger instead.

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.25*MM;

	var vaseWidth = 80*MM;
	var vaseHeight = 100*MM;
	var patternHeight = 4*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 innerShell = makeShell(profilePath, vaseWidth-2*patternHeight, vaseHeight, voxelSize);

	var intersection = new Intersection(shell, image);
	var union = new Union(innerShell, intersection);	
	dest = createGrid(-gWidth/2,gWidth/2,-gHeight/2,gHeight/2,-gWidth/2,gWidth/2,voxelSize);	

        var maker = new GridMaker();
	maker.setSource(union);

	maker.makeGrid(dest);

	meshSmoothingWidth = 2;
	meshErrorFactor = 0.05;

	return dest;

}
Posted in Uncategorized | Leave a comment

Image Lathe – Aspect Ratio

One important issue when using images for making objects is getting the aspect ratio correct. You can somewhat ignore this at the beginning but eventually you’ll start asking why straight lines are off and how to get the exact wall thickness you want. For the Image Lathe this comes down to using the right aspect ratio for your profile and pattern image. If you use the correct values then your conversion from 2D to 3D will be much more enjoyable.

Let’s start with a candle profile. I want to make the candle be 2.75″ wide and 5.25″ tall. Since we are rotating the profile image around to make the object we want to use 1/2 of the total width which is 1.375″. So we want an image 1.375″ / 5.25″ in aspect ratio(0.262). I used 198 pixels for the width so the height would be 756 pixels.

candle_profile

For the pattern image we need this relationship: Pwidth/Pheight = (vaseWidth/vaseHeight)*(PI/symmetryOrder).

The pattern image aspect ratio is related to the physical dimensions of the object and the symmetry order we are using. For this example I’m making the object be 2.75″ x 5.25″ and using a symmetry order of 12. If it set the width to 396 pixels then the height will need to be 2888. In reality this was way more pixels then I needed for this design so feel free to lower that.

candle_circles

Here is the final object created from this:

candle1

Posted in Uncategorized | Leave a comment

Image Lathe

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.

IMG_0631

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.
profile
vase_profile

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.

image_lathe_symmetry

We take this image from the user:
side_image2

And it generates this geometry using the symmetry engine:

vase_image_symmetry

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.

vase

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;
}
Posted in Uncategorized | 1 Comment

Journey from Twist to Spring to a new Creator

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.

twisttwist_orig

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.

spring3

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:

image_twist1

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.

Posted in Uncategorized | 8 Comments

Hollow Out Example

We have a new version of ShapeJS deployed that makes it easy to write a hollow out routine. This allows you to take a solid object and turn it into a shell of specified thickness. Pull up the attached script in ShapeJS, add two params. One text param for the desired thickness(0.001 is 1mm), and a file param for the STL file you want to hollow out.

This script uses two new classes. DistanceTransformMultiStep which calculates the distance from the surface to a voxel. The other class is DensityGridExtractor which makes a density grid from the distance transform, ie it turns those distances into geometry. In combination you can use these two operations to hollow out a model. We calculate the distance of each voxel, then keep all the voxels which are further inside then the thickness we want. We then subtract that geometry from the original model to hollow it out.

I’ve set the default to .2mm for the voxelSize. If your using small objects you may want to set this to .1mm or smaller.

Load the following script into shapejs.shapeways.com, add the params to the interface:

function main(args){
  var thickness = parseFloat(args[0]);
  var baseFile = args[1];
  var voxelSize = 0.2 * MM;
  var maxAttribute = 255;

  var grid = load(baseFile,voxelSize);

  var bounds = java.lang.reflect.Array.newInstance(java.lang.Double.TYPE, 6);
  grid.getGridBounds(bounds);
  var xDist = Math.abs(bounds[1] - bounds[0]);
  var yDist = Math.abs(bounds[3] - bounds[2]);
  var zDist = Math.abs(bounds[5] - bounds[4]);

  var maxDepth = Math.min(Math.min(xDist,yDist),zDist) / 2;
  var maxInDistance = maxDepth + voxelSize;
  var maxOutDistance = voxelSize;

  var distanceGrid = new DistanceTransformMultiStep(maxAttribute, maxInDistance, maxOutDistance);
  var dg = distanceGrid.execute(grid);

  var dge = new DensityGridExtractor(maxDepth, -thickness,dg,maxInDistance,maxOutDistance, maxAttribute);
  var subsurface = createGrid(grid);

  subsurface = dge.execute(subsurface);

  dest = createGrid(grid);

  var maker = new GridMaker();
  maker.setMaxAttributeValue(maxAttribute);
  var dsg1 = new DataSourceGrid(grid,maxAttribute);
  var dsg2 = new DataSourceGrid(subsurface,maxAttribute);

  var result = new Subtraction(dsg1,dsg2);

  maker.setSource(result);
  maker.makeGrid(dest);
  return dest;
}
Posted in Uncategorized | 1 Comment

DensityGridExtraction

We’ve been working with Distance Grids which calculate how far a point is away from the closest surface point. They return a gradient that can be used in different calculations. A common use case is turning that gradient into some geometry. Let’s say you have a 3D model and you want to add some surface detail to it. You could calculate all the voxels within say 2mm of the surface. If you turn this into geometry then you can Intersect that with some volumetric function to create an interesting surface. We’ve created a new class called DistanceGridExtractor for that purpose. It will create a smooth grid that for the inside and outside distances you specify.

Below is a surface created by taking a torus as the main object. I calculated all voxels outside the torus for 2mm. I then turned that into geometry and intersected it with a SchwarzPrimitive volume. It’s cut in half to show the inside.

DensityGridExtractor2DensityGridExtractor

    public void testTorusBumpy(){

        int max_attribute = 127;
        int nx = 400;
        double sphereRadius = 16.0 * MM;
        AttributeGrid grid = makeTorus(nx, sphereRadius, 2 * MM, 
           voxelSize, max_attribute, surfaceThickness);
        double[] bounds = new double[6];
        grid.getGridBounds(bounds);

        double maxInDistance = 0*MM;
        double maxOutDistance = 2.1*MM + voxelSize;

        DistanceTransformExact dt_exact = 
           new DistanceTransformExact(max_attribute, maxInDistance, 
               maxOutDistance);
        AttributeGrid dg_exact = dt_exact.execute(grid);

        DensityGridExtractor dge = new DensityGridExtractor(0, 
           maxOutDistance - voxelSize,
           dg_exact,maxInDistance,maxOutDistance, max_attribute);
        AttributeGrid supersurface = (AttributeGrid) 
           grid.createEmpty(grid.getWidth(), grid.getHeight(), 
              grid.getDepth(), grid.getSliceHeight(), 
              grid.getVoxelSize());
        supersurface.setGridBounds(bounds);
        supersurface = dge.execute(supersurface);

        AttributeGrid dest = (AttributeGrid) 
           grid.createEmpty(grid.getWidth(), grid.getHeight(), 
              grid.getDepth(), grid.getSliceHeight(), 
              grid.getVoxelSize());
        dest.setGridBounds(bounds);

        GridMaker gm = new GridMaker();
        gm.setMaxAttributeValue(max_attribute);

        DataSourceGrid dsg1 = new DataSourceGrid(grid,max_attribute);

        DataSourceGrid dsg2 = new DataSourceGrid(supersurface,
           max_attribute);
        DataSource schwarz = new 
           VolumePatterns.SchwarzPrimitive(2*MM,0.5*MM);
        Intersection pattern = new Intersection(dsg2, schwarz);

        Union result = new Union();
        result.add(pattern);
        Subtraction subtract = new Subtraction(result, 
           new Plane(new Vector3d(0,1,0), new Vector3d(0,0,0)));

        gm.setSource(subtract);
        gm.makeGrid(dest);

        try {
            writeGrid(dest, "bumpy_torus.stl", max_attribute);
        } catch(IOException ioe) {
            ioe.printStackTrace();
        }
    }

Aside | Posted on by | Leave a comment

Distance Transform

Distance function visualized through a gyroid volume. Solid red and blue are uncalculated inside/outside areas. Gradients are distance from the surface. Otherwise I thought it just made a cool animation.

This forms the core of applying surface effects at a specific distance. You could hollow a model > some distance from the surface or you could apply a pattern up to some distance on the surface to make it more interesting. Check out DistanceTransformExact.java for the code. We’re working on more optimized versions as well but this class forms the basis for unit testing other approximations. When we get the library level done then we’ll expose this at ShapeJS.

gyroid_distance

Posted in Uncategorized | Leave a comment

ShapeJS

One of the main goals of the AbFab3D toolkit is to make it easier to create 3D printable objects. As a Java toolkit it’s only going to reach a limited audience(cue Venn diagram of Java programmers with graphics programmers). The web has gone crazy over Javascript as its scripting language. Say what you will about its shortcomings as a language but you can’t fault its uptake numbers. There are many more people out there interested in making 3D printed objects that know Javascript then Java. This brings us to today’s post topic, ShapeJS. We’ve written an application that executes Javascript code using AbFab3D as the backing library. This type of language binding is very common in the game world. You provide an easy to program language that level editors can script game behaviors. This makes creating a game much more accessible and allows many more creative minds to help with the project.

The ShapeJS language provides a powerful system for generating 3D printable objects via a simple Javascript program. Shapeways is hosting a development environment where you can edit ShapeJS scripts, execute them on a server and then view the results in your browser using WebGL. When your happy with your model you can print it Shapeways or download the model for yourself. Here’s hoping this deployment environment is easier then writing a bunch of installer code for Java applications. Been down that road several times and don’t want to repeat it.

We’ve made a bunch of examples to show how to create some cool 3D printed items.
ShapeJS Examples

I have a feeling that someone soon is going to make a creator that’s a break out hit. Let’s define a hit as a million in revenue. So far no creator I’ve seen has made it near that mark. But the time is ripe and the market is expanding fast. There are a few gems in the examples which I think might qualify. I can tell you that dice and jewelry have been extremely popular at Shapeways.

The final piece you need to make these into your own creators is a way to execute the ShapeJS scripts in response to your own user interface. We’ll be putting out examples soon which will show you an easy and inexpensive way to deploy these to cloud providers such as Amazon. Stay tuned!

I’d also like to mention an early user, Leon Nicholls who’s made a cool riff on the project. Currently it doesn’t support color information. He’s made a colorizer that can add colors after the fact. This is a nice way to add color to your models. We’ll get around to adding color support but for now I thought I’d pimp he’s system: Colorized ShapeJS.

Posted in Uncategorized | Leave a comment

Voxel Sculpting

We’ve continued to hone the volume sculpting code. We recently added the ability to have GyroidGradients. These gradients allow you to modify the gyroid properties along the object space. A real world of example of this would be in reducing the cost of a 3D printed item while maintaining its strength. In this first picture we have a structural member that’s completely solid. It’s volume is 2.63 cm^3 and it’s area is 14.16 cm^2.

bar1

In this second version we’ve intersected a Gyroid with the Box. This lowers the total volume while increasing the area. This version using an uniform gyroid has a volume of 0.32 cm^3 and an area of 20.90 cm^2. This is a great savings in costs and it looks cool. In fact with the greater surface area you’d find this object cools much more rapidly.

bar2

In the third version we’ve started to use the GyroidGradient. The gradient is set to double the frequency of the gyroid by the time it reaches the end of the object. Notice how both of the ends are more dense. You’d use this if you needed to strengthen the ends of the object while keeping the main member light. This object has a volume of 0.36 and an area of 25.95.

bar3

The last object has the frequency doubling at 1/4 the object size. In this case we’ve significantly strengthened the ends. The volume is 0.67 and
the area is a whooping 59.73. In terms of cooling capacity this object would shed heat considerably faster then the original object and it
weighs and costs about 1/4 of the original.

As we move into multiple material 3D printers you’ll be able to change individual voxel material properties as well. AbFab3D will give you
individual control of each voxel you print with.

bar4

All of these sculpting patterns can be found in the abfab3d.grid.op.VolumePatterns class. They also include new implementations of Lidinoid, Schwarz, Enneper, Scherk patterns.

In addition to new Volumetric space filling patterns we’ve been extending the symmetry engines. We’ve added a Plane and Sphere symmetry group that can be added to a GridMaker pipeline. See DevTestSliceWriter for some examples such as hyperBall. These two pictures show you the fun you can have with symmetry and voxel spaces. You can make some really cool objects with this type of system.

reflection
reflection2

We’ve been getting some requests for more end-user level usage of AbFab3D. We’re considering adding a scripting language that could setup GridMaker pipelines. This would allow you to rapidly create 20-30 line programs that make really cool objects. Definitely pulling a card from OpenSCAD if we go down that front. If that interests you drop a comment so we know it’s desired.

Posted in Uncategorized | Leave a comment