123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- <html>
- <title>Split STL mesh</title>
- <body>
- <p>This is a simple piece of javascript (licensed under the MIT license) by
- <a href="http://www.thingiverse.com/arpruss/">Alexander Pruss</a> that splits an STL mesh file that contains multiple parts into
- those multiple parts.</p>
- <p>This may well fail if the mesh is defective and has points that are meant to be coincident
- but are merely close together.</p>
- <p>All the processing is done in your browser--your STL is not uploaded to any server.</p>
- <input type="file" id="file" name="file" />
- <ul id="console">
- </ul>
- <script>
- var TRIANGLE_SIZE = (3+3*3)*4+2;
- var ASCII_MODE = true;
- var allSplitMeshes = [];
- var baseFilaname = "";
- function getString(view, position, length) {
- var out = "";
-
- for (var i=0; i<length; i++) {
- var b = view.getUint8(position + i);
- out += String.fromCharCode(b);
- }
-
- return out;
- }
- // For hashing during splitting, ASCII mode is used, where vectors are stored
- // as strings. From a binary STL, the vectors are stored as hex strings, and
- // from an ASCII STL, they are stored as directly extracted ASCII.
- function getVector(view, position) {
- if (ASCII_MODE) {
- function fixZero(a) {
- return a == 0x80000000 ? 0 : a;
- }
- x = fixZero(view.getUint32(position, true));
- y = fixZero(view.getUint32(position+4, true));
- z = fixZero(view.getUint32(position+8, true));
- return x.toString(16)+":"+y.toString(16)+":"+z.toString(16);
- }
- x = view.getFloat32(position, true);
- y = view.getFloat32(position+4, true);
- z = view.getFloat32(position+8, true);
- return [x,y,z]; // x.toString()+","+y.toString()+","+z.toString(); //[x,y,z];
- }
- function parseVector(vector) {
- if (typeof vector === "string") {
- var data = vector.split(":");
- var buf = new ArrayBuffer(4);
- var view = new DataView(buf);
- function parse(s) {
- view.setUint32(0, parseInt(s,16));
- return view.getFloat32(0);
- }
- return [parse(data[0]),parse(data[1]),parse(data[2])];
- }
- else {
- return vector;
- }
- }
- function setVector(view, position, vector) {
- if (typeof vector === "string") {
- var data = vector.split(":");
- view.setUint32(position, parseInt(data[0],16), true);
- view.setUint32(position+4, parseInt(data[1],16), true);
- view.setUint32(position+8, parseInt(data[2],16), true);
- }
- else {
- view.setFloat32(position, vector[0], true);
- view.setFloat32(position+4, vector[1], true);
- view.setFloat32(position+8, vector[2], true);
- }
- }
- function getVectorFromText(line, position) {
- var data = line.substr(position).split(/[\s,]+/);
-
- if (data.length < 3)
- throw 'Invalid vector';
-
- if (ASCII_MODE) {
- var buf = new ArrayBuffer(4);
- var view = new DataView(buf);
- function toHex32(number) {
- view.setFloat32(0, parseFloat(number));
- return view.getUint32(0).toString(16);
- }
- return toHex32(data[0])+":"+toHex32(data[1])+":"+toHex32(data[2]);
- }
- return [parseFloat(data[0]), parseFloat(data[1]), parseFloat(data[2])];
- }
- function message(text) {
- document.getElementById('console').innerHTML += '<li>'+text+'</li>';
- }
- function getASCIISTL(text) {
- var triangles = [];
-
- var triangle = [];
- var normal = [0,0,0];
-
- var lines = text.split(/[\r\n]+/);
-
- message(lines.length+" lines of data");
-
- for (var i = 0 ; i < lines.length ; i++ ) {
- l = lines[i].trim().toLowerCase();
- if (l == 'endfacet') {
- if (triangle.length != 3)
- throw 'invalid triangle';
- triangles.push([triangle[0],triangle[1],triangle[2],normal]);
- triangle = [];
- normal = [0,0,0];
- }
- else if (l.startsWith('facet')) {
- if (l.length > 6) {
- if (l.substr(6).startsWith('normal'))
- normal = getVectorFromText(l, 13);
- }
- }
- else if (l.startsWith('vertex')) {
- if (l.length > 7)
- triangle.push(getVectorFromText(l, 7));
- }
- }
-
- message(String(triangles.length) + " triangles");
-
- return triangles;
- }
- function getBinarySTL(view) {
- var triangles = [];
- var numTriangles = view.getUint32(80, true);
-
- message(String(numTriangles) + " triangles");
-
- for (var i = 0 ; i < numTriangles ; i++) {
- position = 84 + TRIANGLE_SIZE*i;
-
- normal = getVector(view, position);
- v1 = getVector(view, position+12);
- v2 = getVector(view, position+12*2);
- v3 = getVector(view, position+12*3);
- triangles.push( [v1,v2,v3,normal] );
- }
-
- return triangles;
- }
- function splitMesh(triangles) {
- meshes = [];
-
- for (var i = 0; i<triangles.length; i++) {
- var t = triangles[i];
- var matches = [];
- for (var j = 0 ; j < meshes.length; j++) {
- for (var k = 0 ; k < 3 ; k++) {
- if (t[k] in meshes[j].points) {
- matches.push(j);
- break;
- }
- }
- }
- var m;
- if (matches.length == 0) {
- m = {points:{}, triangles:[]};
- meshes.push(m);
- }
- else {
- m = meshes[matches[0]];
- for (var j = matches.length - 1 ; j >= 1 ; j--) {
- mm = meshes[matches[j]];
- for (var key in mm.points) {
- if (mm.points.hasOwnProperty(key))
- m.points[key] = true;
- }
- for (var k = 0 ; k < mm.triangles.length; k++) {
- m.triangles.push(mm.triangles[k]);
- }
- meshes.splice(matches[j], 1);
- }
- }
- for (var k = 0 ; k < 3 ; k++) {
- m.points[t[k]] = true;
- }
- m.triangles.push(t);
- }
-
- for (var i = 0 ; i < meshes.length ; i++) {
- var bounds = [Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY,
- Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.NEGATIVE_INFINITY ];
- for (var key in meshes[i].points) {
- if (meshes[i].points.hasOwnProperty(key)) {
- var v = parseVector(key);
- for (var k=0; k<3; k++) {
- bounds[k] = Math.min(bounds[k], v[k]);
- bounds[3+k] = Math.max(bounds[3+k], v[k]);
- }
- }
- }
-
- meshes[i].bounds = bounds;
- delete meshes[i].points;
- }
-
- function compareByBounds(m,m) {
- for (var i=0; i<6; i++) {
- if (m.bounds[i] < m.bounds[i])
- return -1;
- else if (m.bounds[i] < m.bounds[i])
- return -1;
- }
- return 0;
- }
-
- meshes.sort(compareByBounds);
-
- return meshes;
- }
- function downloadBlob(name,blob) {
- var link = document.createElement('a');
- link.download = name;
- link.href = window.URL.createObjectURL(blob);
- link.onclick = function(e) {
- setTimeout(function() {
- window.URL.revokeObjectURL(link.href);
- }, 1600);
- };
- link.click();
- link.remove();
- }
- function makeMeshByteArray(triangles) {
- data = new ArrayBuffer(84 + triangles.length * TRIANGLE_SIZE);
- view = new DataView(data);
- view.setUint32(80, triangles.length, true);
- for (var i=0; i<triangles.length; i++) {
- var offset = 84 + i*TRIANGLE_SIZE;
- setVector(view, offset, triangles[i][3]); // normal
- setVector(view, offset+12, triangles[i][0]); // v1
- setVector(view, offset+12*2, triangles[i][1]); // v2
- setVector(view, offset+12*3, triangles[i][2]); // v3
- }
- return view.buffer;
- }
- function downloadMesh(i) {
- downloadBlob(baseFilename+i+".stl", new Blob([makeMeshByteArray(allSplitMeshes[i])], {type: "application/octet-stream"}));
- }
- function describeBounds(bounds) {
- return "("+bounds[0].toFixed(2)+", "+bounds[1].toFixed(2)+", "+bounds[2].toFixed(2)+") - ("+
- bounds[3].toFixed(2)+", "+bounds[4].toFixed(2)+", "+bounds[5].toFixed(2)+")";
- }
- function processSTL(data) {
- length = data.byteLength;
- view = new DataView(data);
-
- var header = getString(view, 0, 5);
-
- var binary = true;
- var text;
-
- if (header == "solid") {
- // probably ASCII
- text = getString(view, 0, length);
- if (text.includes("endfacet")) {
- binary = false;
- }
- }
-
- message(binary ? "binary STL" : "ASCII STL");
- triangles = binary ? getBinarySTL(view) : getASCIISTL(text);
- message("data successfully read");
- splitMeshes = splitMesh(triangles);
- if (splitMeshes.length == 1) {
- message("No splitting done: Only one mesh in file.");
- }
- else if (splitMeshes.length == 0) {
- message("No mesh found in file.");
- }
- else {
- message(String(splitMeshes.length)+" meshes extracted");
- allSplitMeshes = [];
-
- for (var i=0; i<splitMeshes.length; i++) {
- allSplitMeshes.push(splitMeshes[i].triangles);
- message("<a href='#' onclick='downloadMesh("+i+");'>Download mesh part "+i+"</a> "+describeBounds(splitMeshes[i].bounds));
- }
- }
- }
- function handleFileSelect(evt) {
- var e = document.getElementById('file');
- e.disabled = true;
- document.getElementById('console').innerHTML = '';
- var f = evt.target.files[0];
-
- message( String(f.size) + ' bytes');
-
- var n = f.name.split(/[/\\]+/);
- baseFilename = f.name.replace(/.*[/\\]/, "").replace(/\.[sS][tT][lL]$/, "");
- var reader = new FileReader();
- reader.onload = function(event) {
- try {
- processSTL(event.target.result);
- }
- catch(err) {
- message( "Error: "+err);
- }
- var e = document.getElementById('file');
- e.disabled = false;
- }
- reader.readAsArrayBuffer(f);
- }
- document.getElementById('file').addEventListener('change', handleFileSelect, false);
- </script>
- </body>
- </html>
|