STL export for 3D printing

Happy Holidays, Processing Community!

I have been making bump maps from images with processing.js for the past year and I was wondering if there is a way to translate that code so that it can export to an .STL file to 3d print?

Hello @asymmetric,

I had success with this at the time:

There are OBJ to STL convertors out there. A Google search will find these.

Processing.js - Happy Coding states:

:)

How would I edit the following code in order to OBJ Export?

I tried the following but it is not working…

import peasy.PeasyCam; // Import the PeasyCam library for easy camera navigation
import toxi.geom.Vec3D; // Import the Vec3D class from toxiclibs for vector operations
import wblut.hemesh.HEC_FromImage;
import wblut.hemesh.HE_Mesh;
import wblut.processing.WB_Render;

boolean record;
boolean mode = false;
float halfWidth, halfHeight;
int rows, cols;
GridPoint[][] grid;
PImage img;
HE_Mesh terrainMesh;
WB_Render render;
PeasyCam cam; // PeasyCam instance for easy camera navigation

void setup() {
  size(980, 980, P3D);
  noLoop();
  frameRate(1);
  smooth(8);
  halfWidth = width / 2.0;
  halfHeight = height / 2.0;
  initGrid(7.0);
  textureMode(IMAGE);

  cam = new PeasyCam(this, width / 2, height / 2, 0, 500);
  cam.setMinimumDistance(50);
  cam.setMaximumDistance(1000);

  // Create a terrain mesh using the HEC_FromImage class from toxiclibs
  HEC_FromImage hemeshBuilder = new HEC_FromImage(this);
  hemeshBuilder.setWidth(cols)
    .setDepth(rows)
    .setMeshResolutionX(1)
    .setMeshResolutionY(1)
    .setHeightMap(img)
    .setHeightScale(150.0);
  terrainMesh = new HE_Mesh(hemeshBuilder);
  render = new WB_Render(this);
}

void initGrid(float scl) {
  img = loadImage("gradient888_2.jpg");
  img.loadPixels();
  img.resize(980, 980);
  rows = floor(height / scl);
  cols = floor(width / scl);
  grid = new GridPoint[rows][cols];
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      int sx = floor(x * scl);
      int sy = floor(y * scl);
      grid[y][x] = new GridPoint(-halfWidth + sx + scl / 2, -halfHeight + sy + scl / 2, 150.0 * (brightness(img.get(sx, sy)) / 255 - 0.6));
    }
  }
}

void draw() {
  background(0);
  if (record) {
    beginRecord(OBJExport.class, "output_December19_2023.obj");
  }

  translate(halfWidth, halfHeight, 0.05);

  if (mode) {
    noStroke();
    directionalLight(255, 255, 255, 0, 0, -1);
  } else {
    noFill();
    stroke(128);
    strokeWeight(1.5);
    smooth(8);
  }

  cam.beginHUD(); // Switch to HUD (Heads-Up Display) mode for text
  fill(255);
  text("Press 'p' to record a single frame", 10, 20);
  cam.endHUD(); // Switch back to 3D mode

  render.drawFaces(terrainMesh); // Draw the terrain mesh

  if (record) {
    endRecord();
    record = false;
  }
}

void keyPressed() {
  if (key == 'p') {
    record = true;
  }
  redraw();
}

class GridPoint {
  float x, y, v;

  public GridPoint(float px, float py, float pv) {
    x = px;
    y = py;
    v = pv;
  }
}

I would start by installing the library and exploring the examples.

Using the code provided (with some modifications) I was able to export this OBJ file and view directly (no conversion) with 3D builder in Windows:

You need to understand the code that you are using.
I suggest removing everything related to texturing and only retain the mesh.

You may not be able to 3D print a thin film like this and may have to add some depth to it. I had this issue with Möbius strip I printed and had to modify.

Consider making columns with some depth (from the base plane is one option) to the height of the image (brightness) to provide a solid model to print.

As a courtesy and out of respect to the author please link to the original source code you are using. Add a few lines to the top of the code with a link to the source of the code.

Image used:
http://learningprocessing.com/code/assets/sunflower.jpg

:)

Hi @glv! Thank you for your help, but I am still struggling to export an OBJ or STL file for 3D printing. I downloaded Processing 3 but I am still not having any luck. When I remove the “texturing” I am left with a flat plain. I would like to 3d print the following code with a fill so it is easier to print.

Here is the following code that I have tried in Processing 3

// Add the necessary import for Toxiclibs OBJ writer
import toxi.geom.mesh.WETriangleMesh;
import toxi.processing.ToxiclibsSupport;
import toxi.obj.ToxiclibsOBJWriter;

// a class to represent a point on our grid.
class GridPoint {
  float x, y, v;
  public GridPoint(float px, float py, float pv) {
    x = px;  
    y = py;  
    v = pv;  
  }
}

float halfWidth, halfHeight;
boolean mode = false;
int rows, cols;
GridPoint[][] grid;
PImage img;
WETriangleMesh exportMesh;  // Toxiclibs mesh for exporting
ToxiclibsSupport gfx;      // Toxiclibs support for mesh conversion

void setup() {
  size(980, 980, P3D);
  noLoop();
  frameRate(1);
  smooth(8);
  halfWidth = width / 2.0;
  halfHeight = height / 2.0;
  initGrid(7.0);
  textureMode(IMAGE);

  // Initialize Toxiclibs mesh and support
  exportMesh = new WETriangleMesh();
  gfx = new ToxiclibsSupport(this);
}

void initGrid(float scl) {
  img = loadImage("gradient888_2.jpg");
  img.loadPixels();
  img.resize(980, 980);
  rows = floor(height / scl);
  cols = floor(width / scl);
  grid = new GridPoint[rows][cols];
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      int sx = floor(x * scl);
      int sy = floor(y * scl);
      grid[y][x] = new GridPoint(-halfWidth + sx + scl / 2, -halfHeight + sy + scl / 2, 150. * (brightness(img.get(sx, sy)) / 255 - 0.6));
    }
  }
}

void draw() {
  translate(halfWidth, halfHeight, 0.05);
  exportMesh.clear();  // Clear existing vertices in the export mesh

  for (int y = 0; y < rows - 1; y++) {
    beginShape(QUAD_STRIP);
    for (int x = 0; x < cols; x++) {
      GridPoint ca = grid[y][x];
      GridPoint cb = grid[y + 1][x];
      exportMesh.addFace(gfx.createMeshFace(ca.x, ca.y, ca.v, cb.x, cb.y, cb.v, ca.x + halfWidth, ca.y + halfHeight),
                         gfx.createMeshFace(cb.x, cb.y, cb.v, ca.x + halfWidth, ca.y + halfHeight, cb.x + halfWidth, cb.y + halfHeight));
    }
    endShape();
  }

  // Export the mesh to OBJ file
  ToxiclibsOBJWriter.save(exportMesh, sketchPath("output.obj"));

  // Continue with your existing drawing code (commented out for brevity)
  /*
  if (mode) {
    noStroke();
    directionalLight(255, 255, 255, 0, 0, -1);
  } else {
    fill(150);
    stroke(255);
    strokeWeight(1.5);
    smooth(8);
  }

  for (int y = 0; y < rows - 1; y++) {
    beginShape(QUAD_STRIP);
    if (mode) {
      texture(img);
    }
    for (int x = 0; x < cols; x++) {
      GridPoint ca = grid[y][x];
      GridPoint cb = grid[y + 1][x];
      vertex(ca.x, ca.y, ca.v, ca.x + halfWidth, ca.y + halfHeight);
      if (mode) {
        PVector na = new PVector(ca.x - cb.x, ca.y - cb.y, 0).sub(new PVector(ca.x - cb.x, ca.y - cb.y, 1)).normalize();
        normal(na.x, na.y, na.z);
      }
      vertex(cb.x, cb.y, cb.v, cb.x + halfWidth, cb.y + halfHeight);
      if (mode) {
        PVector nb = new PVector(cb.x - ca.x, cb.y - ca.y, 0).sub(new PVector(cb.x - ca.x, cb.y - ca.y, 1)).normalize();
        normal(nb.x, nb.y, nb.z);
      }
    }
    endShape();
  }
  */
}

Hello,

I can’t offer any assistance with the library you are using.

This is the library I am using that comes with Processing 4:

https://funprogramming.org/152-Exporting-3D-shapes-as-obj-files-in-Processing.html

:)

1 Like

Hi @glv! Thank you again for your help. I have gotten somewhere with the nervous system library in Processing 3, but the output is a TIFF image with .obj in the name… The following code outputs a file type .obj.tif? What am I doing wrong? When I try the same code in Processing 4.3 there is no output.

import nervoussystem.obj.*;


// a class to representing a point on our grid.
// This should/would/could be done better by defining it as faces (of a triangle mesh).
// but would it keep as simple as possible 
class GridPoint {
  float x, y, v;
  public GridPoint(float px, float py, float pv) {
    x = px;  // x-pos of the grid point
    y = py;  // y-pos of the grid point
    v = pv;  // height of the grid point
  }
}

//helper variables
float halfWidth, halfHeight;
boolean mode = false;
// storage of out grid
int rows, cols;
GridPoint[][] grid;
// image used for texture and heightmap
PImage img;

boolean record = false;


void setup() {
  size(980, 980, P3D); // OPENGL?
  noLoop();
  frameRate(1);
  smooth(8);
  halfWidth  = width/2.;
  halfHeight = height/2.;
  initGrid(7.);
  // use textureMode IMAGE as the grid is not normalized
  textureMode(IMAGE);
  
}

void initGrid(float scl) {
  // load the image, used for texture and heightmap.
  // usually more than one picture will be used. One for heightmap, one for normalmap and one with the texture.
  // for simplicity it has the same size than our grid to not make the code more math intensive.
  // to get this work our image is somehow a bit blurred (like noise is), otherwise the mesh isn't smooth enough
  img = loadImage("gradient888_2.jpg");
  img.loadPixels();
  img.resize(980, 980);
  // initialize our gridpoints
  rows = floor(height/scl);
  cols = floor(width/scl);
  grid= new GridPoint[rows][cols];
  for (int y = 0; y < rows; y++) {
    for (int x = 0; x < cols; x++) {
      int sx = floor(x*scl);
      int sy = floor(y*scl);
      // set x and y coordinates (center origin) and the height of the current point.
      // height is the normalized brightness value [0..1] of the pixel color from the image.
      // Subtract 0.5 to align it to center [-0.5..0.5]
      // scaling it by 50, so the range is [50.0*(-0.5)..50.0*(0.5)]
      grid[y][x] = new GridPoint(-halfWidth+sx+scl/2, -halfHeight+sy+scl/2, 150.*(brightness(img.get(sx, sy))/255-0.6));
    }
  }
}

void draw() {
  if (record){
    beginRecord("nervoussystem.obj.OBJExport", "output.obj");
  }
  
  // set the origin to the center of the screen, and move it 400px far away on z-axis
  translate(halfWidth, halfHeight, 0.05);  // zoom in (lower numbers) zoom out (higher numbers around 200)
  
  // switch between mesh and textured mode
  if (frameCount % int(TAU*100.) == 0) {
    mode = !mode;
  }

  if (mode) {
    // if textured mode no strokes and a light from viewpoint to object
    noStroke();
    directionalLight(255, 255, 255, 0, 0, -1);
  } else {
    // if mesh mode only show strokes
    fill(150);
    stroke(255);
    strokeWeight(1.5);
    smooth(8);
  }

  // apply the rotation after lighting, otherwise, the light gets also rotated, which we do not want.
  // tipping 30 degrees back on X-axis
  //rotateX(radians(30));
  // tipping left/right a bit per frame from -PI/4 - +PI/4 on Y-axis
  //rotateY(sin(frameCount/100.)*QUARTER_PI);

  // build the surface/terrain by triangle strips to display it
  for (int y = 0; y < rows-1; y ++) {
    beginShape (QUAD_STRIP); // or (TRIANGLE_STRIP),(QUAD_STRIP), 
    if (mode) {
      // on textured mode set the texture
      texture(img);
    }
    // common triangle strips
    for (int x = 0; x < cols; x++) {
      GridPoint ca = grid[y][x];
      GridPoint cb = grid[y+1][x];
      vertex(ca.x, ca.y, ca.v, ca.x+halfWidth, ca.y+halfHeight);
      if (mode) {
        // on textured mode, we need to set the normal vector so the light knows how to behave on hitting the surface
        // usually, there are better calculations than this cheap hack
        // comment it out to see the difference if normals are messed up
        PVector na = new PVector(ca.x-cb.x, ca.y-cb.y, 0).sub(new PVector(ca.x-cb.x, ca.y-cb.y, 1)).normalize();
        normal(na.x, na.y, na.z);
      }
      vertex(cb.x, cb.y, cb.v, cb.x+halfWidth, cb.y+halfHeight);
      if (mode) {
        // on textured mode, we need to set the normal vector so the light knows how to behave on hitting the surface
        // usually, there are better calculations than this cheap hack
        // comment it out to see the difference if normals are messed up
        PVector nb = new PVector(cb.x-ca.x, cb.y-ca.y, 0).sub(new PVector(cb.x-ca.x, cb.y-ca.y, 1)).normalize();
        normal(nb.x, nb.y, nb.z);
      }
    }
    endShape();
  }

  if(record){
      endRecord();
      record = false;
    }
}
void keyPressed(){
  if(key == 'p') {
    record = true;
  }
}

Screen Shot 2024-01-01 at 4.54.10 PM

Comment this:

// noLoop();

Use this image:

img = loadImage("http://learningprocessing.com/code/assets/sunflower.jpg");

You will then get an error! But that is a step toward success.

Remove the u and v parameters in your vertices.
You will have to take a look at the reference for vertex for this.

:)

1 Like

It seems to have worked, @glv!! Do you have any recommendations for making the surface thicker within Processing?


Happy New Year @glv!!! :star: :star2: :star: :star2: :star: :star2: :eight_pointed_black_star: :stars: :dizzy: :sparkles: