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?
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
:)
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;
}
}
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.
:)