Generating STL Models

I mentioned a few posts back that the STL file format for 3D models is very simple, and easy to write code to generate.

A Few Posts Back

In fact, I would say it’s astonishingly simple; here is the Dart code.

First, you need vertices and triangles:

class Vertex {
  final num x;
  final num y;
  final num z;

  Vertex(this.x, this.y, this.z);

  @override
  String toString() => '$x.toStringAsFixed(8)} '
      '${y.toStringAsFixed(8)} '
      '${z.toStringAsFixed(8)}';
}

class Triangle {
  final Vertex v1;
  final Vertex v2;
  final Vertex v3;

  Triangle(this.v1, this.v2, this.v3);
}

Notice that Vertex#toString() outputs the format needed for STL files.

Then, all you need to produce an STL file is:

String toStl(Iterable<Triange> triangles) {
  final buffer = StringBuffer();
  buffer.write('solid model\n');
  for (final triangle in triangles) {
    buffer.write('''
facet normal 0 0 0
  outer loop
    vertex ${triangle.v1}
    vertex ${triangle.v2}
    vertex ${triangle.v3}
  endloop
endfacet
''');
  }
  buffer.write('endsolid model\n');
  return buffer.toString();
}

It’s true that there is also a binary format for STL files, but the programs I care about—PrusaSlicer and Blender—will happily consume the ASCII format.

The one complication I noticed was with the normals. PrusaSlicer and Blender seem to be content to recalculate the normals, so `0 0 0` works; but this doesn’t say which direction the normal should point.

Flipped normals causes all kinds of badness because it confuses tools about what is the inside and what is the outside of the solid.

Apparently PrusaSlicer guesses a normal direction based on the order of the vertices; so to make PrusaSlicer happy I had to be careful to use the right vertex order when constructing triangles.

Blender can recalculate normals to fix them if they’re flipped, but that would have added an extra step between generating models and printing them, so I thought it was worth getting right in the first place.

So from triangles, to cubes. This is quite lengthy, but still simple enough:

void cube(Vertex v1, Vertex v2) {
  final x1 = v1.x;
  final y1 = v1.y;
  final z1 = v1.z;
  final x2 = v2.x;
  final y2 = v2.y;
  final z2 = v2.z;

  triangle(Vertex(x1, y1, z1), Vertex(x1, y2, z1), Vertex(x2, y1, z1));
  triangle(Vertex(x2, y1, z1), Vertex(x1, y2, z1), Vertex(x2, y2, z1));
  triangle(Vertex(x1, y1, z2), Vertex(x1, y2, z2), Vertex(x2, y1, z2), flip: true);
  triangle(Vertex(x2, y2, z2), Vertex(x1, y2, z2), Vertex(x2, y1, z2));
  triangle(Vertex(x1, y1, z1), Vertex(x1, y2, z1), Vertex(x1, y1, z2), flip: true);
  triangle(Vertex(x1, y2, z2), Vertex(x1, y2, z1), Vertex(x1, y1, z2));
  triangle(Vertex(x2, y1, z1), Vertex(x2, y2, z1), Vertex(x2, y1, z2));
  triangle(Vertex(x2, y2, z2), Vertex(x2, y2, z1), Vertex(x2, y1, z2), flip: true);
  triangle(Vertex(x1, y1, z1), Vertex(x2, y1, z1), Vertex(x1, y1, z2));
  triangle(Vertex(x2, y1, z2), Vertex(x2, y1, z1), Vertex(x1, y1, z2), flip: true);
  triangle(Vertex(x1, y2, z1), Vertex(x2, y2, z1), Vertex(x1, y2, z2), flip: true);
  triangle(Vertex(x2, y2, z2), Vertex(x2, y2, z1), Vertex(x1, y2, z2));
}

void triangle(Vertex v1, Vertex v2, Vertex v3, {bool flip = false}) {
  if (flip) {
    triangles.add(Triangle(v3, v2, v1));
  } else {
    triangles.add(Triangle(v1, v2, v3));
  }
}

Notice the `flip` boolean; this was me getting the vertex order right, by trial and error, so that the normals are in the right direction. I did that by loading the STL file in Blender, asking it to show the normals, then tweaking until they were all as they should be—facing outwards.

With this first primitive, the cube, in place, I figured I’d spend some time exploring the possibilities of using tiny 3D cubes like pixels—or rather, voxels. Which programmer hasn’t wanted to fill a `List<List<List<bool>>>` and press `print`? Or maybe it’s just me.

But this post gets long, so I will resume another day. Here’s a picture to keep things interesting:

An Early Generated STL Print

That’s really not all that interesting, is it? Give me time, I’m new at this.

To tide you over here is a much more interesting print that I did not design but merely downloaded, and its source:

Working Cube Gear

To Download on Thingiverse

Feedback 📮

👍 Thanks!

👎 Not for me.

🤷 No opinion.

Comments.

So far today, 2023-07-22, feedback has been received 12 times. Of these, 9 were likely from bots, and 3 might have been from real people. Thank you, maybe-real people!

   ———
 /     \   i a
| C   a \ D   n |
   irc   \     /
           ———