Skip to content

Instantly share code, notes, and snippets.

@jcarletto27
Last active September 23, 2020 13:09
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jcarletto27/e271bbb7639c4bed2427 to your computer and use it in GitHub Desktop.
Save jcarletto27/e271bbb7639c4bed2427 to your computer and use it in GitHub Desktop.
updated Three.js STLExporter to export model including morphTargets
/**
* Based on https://github.com/mrdoob/three.js/blob/a72347515fa34e892f7a9bfa66a34fdc0df55954/examples/js/exporters/STLExporter.js
* Tested on r68 and r70
* @author jcarletto / https://github.com/jcarletto27
* @author kjlubick / https://github.com/kjlubick
* @author kovacsv / http://kovacsv.hu/
* @author mrdoob / http://mrdoob.com/
*/
THREE.STLExporter = function () {};
THREE.STLExporter.prototype = {
constructor : THREE.STLExporter,
parse : (function () {
var vector = new THREE.Vector3();
var normalMatrixWorld = new THREE.Matrix3();
return function (scene) {
var output = '';
output += 'solid exported\n';
scene.traverse(function (object) {
if (object instanceof THREE.Mesh) {
var geometry = object.geometry;
var matrixWorld = object.matrixWorld;
var mesh = object;
if (geometry instanceof THREE.Geometry) {
var vertices = geometry.vertices;
var faces = geometry.faces;
normalMatrixWorld.getNormalMatrix(matrixWorld);
for (var i = 0, l = faces.length; i < l; i++) {
var face = faces[i];
vector.copy(face.normal).applyMatrix3(normalMatrixWorld).normalize();
output += '\tfacet normal ' + vector.x + ' ' + vector.y + ' ' + vector.z + '\n';
output += '\t\touter loop\n';
var indices = [face.a, face.b, face.c];
for (var j = 0; j < 3; j++) {
var vertexIndex = indices[j];
if (mesh.geometry.skinIndices.length == 0) {
vector.copy(vertices[vertexIndex]).applyMatrix4(matrixWorld);
output += '\t\t\tvertex ' + vector.x + ' ' + vector.y + ' ' + vector.z + '\n';
} else {
vector.copy(vertices[vertexIndex]); //.applyMatrix4( matrixWorld );
// see https://github.com/mrdoob/three.js/issues/3187
boneIndices = [];
boneIndices[0] = mesh.geometry.skinIndices[vertexIndex].x;
boneIndices[1] = mesh.geometry.skinIndices[vertexIndex].y;
boneIndices[2] = mesh.geometry.skinIndices[vertexIndex].z;
boneIndices[3] = mesh.geometry.skinIndices[vertexIndex].w;
weights = [];
weights[0] = mesh.geometry.skinWeights[vertexIndex].x;
weights[1] = mesh.geometry.skinWeights[vertexIndex].y;
weights[2] = mesh.geometry.skinWeights[vertexIndex].z;
weights[3] = mesh.geometry.skinWeights[vertexIndex].w;
inverses = [];
inverses[0] = mesh.skeleton.boneInverses[boneIndices[0]];
inverses[1] = mesh.skeleton.boneInverses[boneIndices[1]];
inverses[2] = mesh.skeleton.boneInverses[boneIndices[2]];
inverses[3] = mesh.skeleton.boneInverses[boneIndices[3]];
skinMatrices = [];
skinMatrices[0] = mesh.skeleton.bones[boneIndices[0]].matrixWorld;
skinMatrices[1] = mesh.skeleton.bones[boneIndices[1]].matrixWorld;
skinMatrices[2] = mesh.skeleton.bones[boneIndices[2]].matrixWorld;
skinMatrices[3] = mesh.skeleton.bones[boneIndices[3]].matrixWorld;
//this checks to see if the mesh has any morphTargets - jc
if (mesh.geometry.morphTargets !== 'undefined') {
morphMatricesX = [];
morphMatricesY = [];
morphMatricesZ = [];
morphMatricesInfluence = [];
for (var mt = 0; mt < mesh.geometry.morphTargets.length; mt++) {
//collect the needed vertex info - jc
morphMatricesX[mt] = mesh.geometry.morphTargets[mt].vertices[vertexIndex].x;
morphMatricesY[mt] = mesh.geometry.morphTargets[mt].vertices[vertexIndex].y;
morphMatricesZ[mt] = mesh.geometry.morphTargets[mt].vertices[vertexIndex].z;
morphMatricesInfluence[mt] = mesh.morphTargetInfluences[mt];
}
}
var finalVector = new THREE.Vector4();
if (mesh.geometry.morphTargets !== 'undefined') {
var morphVector = new THREE.Vector4(vector.x, vector.y, vector.z);
for (var mt = 0; mt < mesh.geometry.morphTargets.length; mt++) {
//not pretty, but it gets the job done - jc
morphVector.lerp(new THREE.Vector4(morphMatricesX[mt], morphMatricesY[mt], morphMatricesZ[mt], 1), morphMatricesInfluence[mt]);
}
}
for (var k = 0; k < 4; k++) {
if (mesh.geometry.morphTargets !== 'undefined') {
var tempVector = new THREE.Vector4(morphVector.x, morphVector.y, morphVector.z);
} else {
var tempVector = new THREE.Vector4(vector.x, vector.y, vector.z);
}
tempVector.multiplyScalar(weights[k]);
//the inverse takes the vector into local bone space
//which is then transformed to the appropriate world space
tempVector.applyMatrix4(inverses[k])
.applyMatrix4(skinMatrices[k]);
finalVector.add(tempVector);
}
output += '\t\t\tvertex ' + finalVector.x + ' ' + finalVector.y + ' ' + finalVector.z + '\n';
}
}
output += '\t\tendloop\n';
output += '\tendfacet\n';
}
}
}
});
output += 'endsolid exported\n';
return output;
};
}
())
};
function saveSTL(scene, name) {
var exporter = new THREE.STLExporter();
var stlString = exporter.parse(scene);
var blob = new Blob([stlString], {
type : 'text/plain'
});
saveAs(blob, name + '.stl');
}
var exporter = new THREE.STLExporter();
var exportString = function (output, filename) {
var blob = new Blob([output], {
type : 'text/plain'
});
var objectURL = URL.createObjectURL(blob);
var link = document.createElement('a');
link.href = objectURL;
link.download = filename || 'data.json';
link.target = '_blank';
link.click();
};
@verybigelephants
Copy link

very cool, would it be also possible to export colored stl?

@jcarletto27
Copy link
Author

@verybigelephants
I wasn't aware stl could store color, but three.js offers obj exporting with materials, might be your best bet. I tried modifying their stock obj exporter to include morphs and animations, but obj with material is a bit more complex than a stl export.

@markskelton
Copy link

Hey great work with this! When I open the exported STL file in Maya there seems to be double the number of vertices. Would you know the reason for this?

@sashidarsync
Copy link

when we try to export the 3D Scene loaded via threejs json loader to STL The scale/size is not outputting (we are applying blendshapes/morph targets)

@sashidarsync
Copy link

STL Export is working but only the base size is getting exported.

@atnartur
Copy link

atnartur commented Aug 3, 2016

Hi! I updated your code. I include support of BufferGeometry and support of AMD, common.js and ES6 modules. Also I published npm package and added you to contributors in package.json. https://github.com/atnartur/three-STLexporter

@tart2000
Copy link

Hi! All I get is scene.traverse is not a function...
Do you have any idea why?
(using revision 81)

@jcarletto27
Copy link
Author

I've modified the OBJ Exporter to incorporate bones, and morph data like this one, only it uses bufferGeometry instead of the standard Geometry type
https://gist.github.com/jcarletto27/35b19f811c657ec73b0238d78413f88d

atnartur's version converts the BufferGeometry to geometry which loses all the bone and morph data, this version works with them directly. Either version should work depending on your needs.

@alejandroLoz
Copy link

I manipulate the positions of my meshes before exporting, but they all appear centered in the output STL.
Any way to get the proper positions in the STL?
Thanks

@HelliceSaouli
Copy link

could some one help, i'm trying to make poser, where i have a fbx file send to three js and then via transform controller i i create poses, and i want to export the result . i tried this code and still did not work

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment