mirror of
https://github.com/raviriley/STL-to-OpenSCAD-Converter.git
synced 2024-11-23 07:03:10 +01:00
Initial commit
This commit is contained in:
commit
a5cb6f33e0
2
.gitattributes
vendored
Normal file
2
.gitattributes
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Ravi Riley
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
30
STL-to-OpenSCAD converter.html
Normal file
30
STL-to-OpenSCAD converter.html
Normal file
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- based on http://jsfiddle.net/roha/353r2k8w/ -->
|
||||
<head>
|
||||
<title>STL to OpenSCAD Converter</title>
|
||||
<link rel="stylesheet" type="text/css" href="style.css">
|
||||
<script src="jquery-1.12.4.js"></script>
|
||||
<script src="binaryReader.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
Convert STL files to open SCAD, read more about it here:
|
||||
<a href='https://www.thingiverse.com/thing:1383325' target=_new>Thingiverse: STL to OpenSCAD converter</a>
|
||||
</p>
|
||||
<div><span id="error"></span></div>
|
||||
<div><input type="file" id="files" name="file"/></div>
|
||||
<button onclick="abortRead();">Cancel read</button>
|
||||
<div id="progress_bar">
|
||||
<div class="percent">0%</div>
|
||||
</div>
|
||||
<span id="conversion"></span>
|
||||
<span id="result"></span>
|
||||
<div>
|
||||
<button>
|
||||
<a href="#">Download</a>
|
||||
</button>
|
||||
</div>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
</html>
|
127
binaryReader.js
Normal file
127
binaryReader.js
Normal file
@ -0,0 +1,127 @@
|
||||
//obtained from:
|
||||
// http://blog.vjeux.com/wp-content/uploads/2010/01/binaryReader.js
|
||||
// BinaryReader
|
||||
// Refactored by Vjeux <vjeuxx@gmail.com>
|
||||
// http://blog.vjeux.com/2010/javascript/javascript-binary-reader.html
|
||||
|
||||
// Original
|
||||
//+ Jonas Raoni Soares Silva
|
||||
//@ http://jsfromhell.com/classes/binary-parser [rev. #1]
|
||||
|
||||
BinaryReader = function (data) {
|
||||
this._buffer = data;
|
||||
this._pos = 0;
|
||||
};
|
||||
|
||||
BinaryReader.prototype = {
|
||||
|
||||
/* Public */
|
||||
|
||||
readInt8: function (){ return this._decodeInt(8, true); },
|
||||
readUInt8: function (){ return this._decodeInt(8, false); },
|
||||
readInt16: function (){ return this._decodeInt(16, true); },
|
||||
readUInt16: function (){ return this._decodeInt(16, false); },
|
||||
readInt32: function (){ return this._decodeInt(32, true); },
|
||||
readUInt32: function (){ return this._decodeInt(32, false); },
|
||||
|
||||
readFloat: function (){ return this._decodeFloat(23, 8); },
|
||||
readDouble: function (){ return this._decodeFloat(52, 11); },
|
||||
|
||||
readChar: function () { return this.readString(1); },
|
||||
readString: function (length) {
|
||||
this._checkSize(length * 8);
|
||||
var result = this._buffer.substr(this._pos, length);
|
||||
this._pos += length;
|
||||
return result;
|
||||
},
|
||||
|
||||
seek: function (pos) {
|
||||
this._pos = pos;
|
||||
this._checkSize(0);
|
||||
},
|
||||
|
||||
getPosition: function () {
|
||||
return this._pos;
|
||||
},
|
||||
|
||||
getSize: function () {
|
||||
return this._buffer.length;
|
||||
},
|
||||
|
||||
|
||||
/* Private */
|
||||
|
||||
_decodeFloat: function(precisionBits, exponentBits){
|
||||
var length = precisionBits + exponentBits + 1;
|
||||
var size = length >> 3;
|
||||
this._checkSize(length);
|
||||
|
||||
var bias = Math.pow(2, exponentBits - 1) - 1;
|
||||
var signal = this._readBits(precisionBits + exponentBits, 1, size);
|
||||
var exponent = this._readBits(precisionBits, exponentBits, size);
|
||||
var significand = 0;
|
||||
var divisor = 2;
|
||||
var curByte = 0; //length + (-precisionBits >> 3) - 1;
|
||||
do {
|
||||
var byteValue = this._readByte(++curByte, size);
|
||||
var startBit = precisionBits % 8 || 8;
|
||||
var mask = 1 << startBit;
|
||||
while (mask >>= 1) {
|
||||
if (byteValue & mask) {
|
||||
significand += 1 / divisor;
|
||||
}
|
||||
divisor *= 2;
|
||||
}
|
||||
} while (precisionBits -= startBit);
|
||||
|
||||
this._pos += size;
|
||||
|
||||
return exponent == (bias << 1) + 1 ? significand ? NaN : signal ? -Infinity : +Infinity
|
||||
: (1 + signal * -2) * (exponent || significand ? !exponent ? Math.pow(2, -bias + 1) * significand
|
||||
: Math.pow(2, exponent - bias) * (1 + significand) : 0);
|
||||
},
|
||||
|
||||
_decodeInt: function(bits, signed){
|
||||
var x = this._readBits(0, bits, bits / 8), max = Math.pow(2, bits);
|
||||
var result = signed && x >= max / 2 ? x - max : x;
|
||||
|
||||
this._pos += bits / 8;
|
||||
return result;
|
||||
},
|
||||
|
||||
//shl fix: Henri Torgemane ~1996 (compressed by Jonas Raoni)
|
||||
_shl: function (a, b){
|
||||
for (++b; --b; a = ((a %= 0x7fffffff + 1) & 0x40000000) == 0x40000000 ? a * 2 : (a - 0x40000000) * 2 + 0x7fffffff + 1);
|
||||
return a;
|
||||
},
|
||||
|
||||
_readByte: function (i, size) {
|
||||
return this._buffer.charCodeAt(this._pos + size - i - 1) & 0xff;
|
||||
},
|
||||
|
||||
_readBits: function (start, length, size) {
|
||||
var offsetLeft = (start + length) % 8;
|
||||
var offsetRight = start % 8;
|
||||
var curByte = size - (start >> 3) - 1;
|
||||
var lastByte = size + (-(start + length) >> 3);
|
||||
var diff = curByte - lastByte;
|
||||
|
||||
var sum = (this._readByte(curByte, size) >> offsetRight) & ((1 << (diff ? 8 - offsetRight : length)) - 1);
|
||||
|
||||
if (diff && offsetLeft) {
|
||||
sum += (this._readByte(lastByte++, size) & ((1 << offsetLeft) - 1)) << (diff-- << 3) - offsetRight;
|
||||
}
|
||||
|
||||
while (diff) {
|
||||
sum += this._shl(this._readByte(lastByte++, size), (diff-- << 3) - offsetRight);
|
||||
}
|
||||
|
||||
return sum;
|
||||
},
|
||||
|
||||
_checkSize: function (neededBits) {
|
||||
if (!(this._pos + Math.ceil(neededBits / 8) < this._buffer.length)) {
|
||||
throw new Error("Index out of bound");
|
||||
}
|
||||
}
|
||||
};
|
11008
jquery-1.12.4.js
vendored
Normal file
11008
jquery-1.12.4.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
238
main.js
Normal file
238
main.js
Normal file
@ -0,0 +1,238 @@
|
||||
//STL to OpenSCAD converter
|
||||
//This code will read an STL file and Generate an OpenSCAD file based on the content
|
||||
//it supports both ASCII and Binary STL files.
|
||||
|
||||
var reader;
|
||||
var progress = document.querySelector('.percent');
|
||||
var vertices = [];
|
||||
var triangles = [];
|
||||
var modules = '';
|
||||
var calls = '';
|
||||
var vertexIndex = 0;
|
||||
var converted = 0;
|
||||
var totalObjects = 0;
|
||||
var convertedObjects = 0;
|
||||
|
||||
function _reset() {
|
||||
vertices = [];
|
||||
triangles = [];
|
||||
modules = '';
|
||||
calls = '';
|
||||
vertexIndex = 0;
|
||||
converted = 0;
|
||||
totalObjects = 0;
|
||||
document.getElementById('error').innerText = '';
|
||||
document.getElementById('conversion').innerText = '';
|
||||
}
|
||||
|
||||
//stl: the stl file context as a string
|
||||
//parseResult: This function checks if the file is ASCII or Binary, and parses the file accordingly
|
||||
function parseResult(stl) {
|
||||
_reset();
|
||||
var isAscii = true;
|
||||
|
||||
for (var i = 0; i < stl.length; i++) {
|
||||
|
||||
if (stl[i].charCodeAt(0) == 0) {
|
||||
isAscii = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isAscii) {
|
||||
parseBinaryResult(stl);
|
||||
} else {
|
||||
|
||||
parseAsciiResult(stl);
|
||||
}
|
||||
}
|
||||
|
||||
function parseBinaryResult(stl) {
|
||||
//This makes more sense if you read http://en.wikipedia.org/wiki/STL_(file_format)#Binary_STL
|
||||
var br = new BinaryReader(stl);
|
||||
br.seek(80); //Skip header
|
||||
var totalTriangles = br.readUInt32(); //Read # triangles
|
||||
|
||||
for (var tr = 0; tr < totalTriangles; tr++) {
|
||||
try {
|
||||
document.getElementById('conversion').innerText = 'In Progress - Converted ' + (++converted) + ' out of ' + totalTriangles + ' triangles!';
|
||||
/*
|
||||
REAL32[3] – Normal vector
|
||||
REAL32[3] – Vertex 1
|
||||
REAL32[3] – Vertex 2
|
||||
REAL32[3] – Vertex 3
|
||||
UINT16 – Attribute byte count*/
|
||||
//Skip Normal Vector;
|
||||
br.readFloat();
|
||||
br.readFloat();
|
||||
br.readFloat(); //SKIP NORMAL
|
||||
//Parse every 3 subsequent floats as a vertex
|
||||
var v1 = '[' + br.readFloat() + ',' + br.readFloat() + ',' + br.readFloat() + ']';
|
||||
var v2 = '[' + br.readFloat() + ',' + br.readFloat() + ',' + br.readFloat() + ']';
|
||||
var v3 = '[' + br.readFloat() + ',' + br.readFloat() + ',' + br.readFloat() + ']';
|
||||
//every 3 vertices create a triangle.
|
||||
var triangle = '[' + (vertexIndex++) + ',' + (vertexIndex++) + ',' + (vertexIndex++) + ']';
|
||||
|
||||
br.readUInt16();
|
||||
//Add 3 vertices for every triangle
|
||||
|
||||
//TODO: OPTIMIZE: Check if the vertex is already in the array, if it is just reuse the index
|
||||
vertices.push(v1);
|
||||
vertices.push(v2);
|
||||
vertices.push(v3);
|
||||
triangles.push(triangle);
|
||||
} catch (err) {
|
||||
error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
saveResult(vertices, triangles);
|
||||
}
|
||||
|
||||
function parseAsciiResult(stl) {
|
||||
|
||||
//Find all models
|
||||
var objects = stl.split('endsolid');
|
||||
|
||||
for (var o = 0; o < objects.length; o++) {
|
||||
|
||||
//Translation: a non-greedy regex for loop {...} endloop pattern
|
||||
var patt = /\bloop[\s\S]*?\endloop/mgi;
|
||||
var result = 'matches are: ';
|
||||
converted = 0;
|
||||
match = objects[o].match(patt);
|
||||
if (match == null) continue;
|
||||
|
||||
for (var i = 0; i < match.length; i++) {
|
||||
try {
|
||||
document.getElementById('conversion').innerText = 'In Progress - Object ' + (o + 1) + ' out of ' + objects.length + ' Converted ' + (++converted) + ' out of ' + match.length + ' facets!';
|
||||
|
||||
//3 different vertex objects each with 3 numbers.
|
||||
var vpatt = /\bvertex\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s*vertex\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s*vertex\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s+(-?\d+\.?\d*\E?\e?\-?\+?\d*\.?\d*)\s*/mgi;
|
||||
|
||||
var v = vpatt.exec(match[i]);
|
||||
if (v == null) continue;
|
||||
|
||||
if (v.length != 10) {
|
||||
document.getElementById('error').innerText = '\r\nFailed to parse ' + match[i];
|
||||
break;
|
||||
}
|
||||
|
||||
var v1 = '[' + v[1] + ',' + v[2] + ',' + v[3] + ']';
|
||||
var v2 = '[' + v[4] + ',' + v[5] + ',' + v[6] + ']';
|
||||
var v3 = '[' + v[7] + ',' + v[8] + ',' + v[9] + ']';
|
||||
var triangle = '[' + (vertexIndex++) + ',' + (vertexIndex++) + ',' + (vertexIndex++) + ']';
|
||||
//Add 3 vertices for every triangle
|
||||
|
||||
//TODO: OPTIMIZE: Check if the vertex is already in the array, if it is just reuse the index
|
||||
vertices.push(v1);
|
||||
vertices.push(v2);
|
||||
vertices.push(v3);
|
||||
triangles.push(triangle);
|
||||
} catch (err) {
|
||||
error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
saveResult(vertices, triangles);
|
||||
}
|
||||
}
|
||||
|
||||
function error(err) {
|
||||
document.getElementById('error').innerText = "An Error has occured while trying to convert your file!\r\nPlease make sure this is a valid STL file.";
|
||||
document.getElementById('conversion').innerText = '';
|
||||
}
|
||||
//Input: Set of vertices and triangles, both are strings
|
||||
//Makes the Download link create an OpenScad file with a polyhedron object that represents the parsed stl file
|
||||
function saveResult(vertices, triangles) {
|
||||
|
||||
var poly = 'polyhedron(\r\n points=[' + vertices + ' ],\r\nfaces=[' + triangles + ']);';
|
||||
|
||||
calls = calls + 'object' + (++totalObjects) + '(1);\r\n\r\n';
|
||||
|
||||
modules = modules + 'module object' + totalObjects + '(scale) {';
|
||||
modules = modules + poly + '}\r\n\r\n';
|
||||
|
||||
result = modules + calls;
|
||||
|
||||
|
||||
window.URL = window.URL || window.webkitURL;
|
||||
prompt("scad", result);
|
||||
var blob = new Blob([result], {
|
||||
type: 'text/plain'
|
||||
});
|
||||
|
||||
$('a').attr("href", window.URL.createObjectURL(blob));
|
||||
$('a').attr("download", "FromSTL.SCAD");
|
||||
|
||||
document.getElementById('conversion').innerText = 'Conversion complete - Click the download link to download your OpenSCAD file! Total Triangles: ' + triangles.length;
|
||||
}
|
||||
|
||||
function errorHandler(evt) {
|
||||
switch (evt.target.error.code) {
|
||||
case evt.target.error.NOT_FOUND_ERR:
|
||||
alert('File Not Found!');
|
||||
break;
|
||||
case evt.target.error.NOT_READABLE_ERR:
|
||||
alert('File is not readable');
|
||||
break;
|
||||
case evt.target.error.ABORT_ERR:
|
||||
break; // noop
|
||||
default:
|
||||
alert('An error occurred reading this file.');
|
||||
};
|
||||
}
|
||||
|
||||
function updateProgress(evt) {
|
||||
// evt is an ProgressEvent.
|
||||
if (evt.lengthComputable) {
|
||||
var percentLoaded = Math.round((evt.loaded / evt.total) * 100);
|
||||
// Increase the progress bar length.
|
||||
if (percentLoaded < 100) {
|
||||
progress.style.width = percentLoaded + '%';
|
||||
progress.textContent = percentLoaded + '%';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleFileSelect(evt) {
|
||||
// Reset progress indicator on new file selection.
|
||||
progress.style.width = '0%';
|
||||
progress.textContent = '0%';
|
||||
|
||||
var extension = String(evt).match(/\.[0-9a-z]+$/i);
|
||||
console.log(extension);
|
||||
console.log(evt);
|
||||
console.log(evt.target.baseURI);
|
||||
//if
|
||||
|
||||
reader = new FileReader();
|
||||
reader.onerror = errorHandler;
|
||||
reader.onprogress = updateProgress;
|
||||
reader.onabort = function(e) {
|
||||
alert('File read cancelled');
|
||||
};
|
||||
reader.onloadstart = function(e) {
|
||||
document.getElementById('progress_bar').className = 'loading';
|
||||
};
|
||||
|
||||
reader.onload = function(e) {
|
||||
// Ensure that the progress bar displays 100% at the end.
|
||||
progress.style.width = '100%';
|
||||
progress.textContent = '100%';
|
||||
setTimeout("document.getElementById('progress_bar').className='';", 2000);
|
||||
parseResult(reader.result);
|
||||
}
|
||||
|
||||
// Read in the stl file as a binary string.
|
||||
reader.readAsBinaryString(evt.target.files[0]);
|
||||
//}endif?
|
||||
}
|
||||
|
||||
function abortRead() {
|
||||
reader.abort();
|
||||
}
|
||||
|
||||
document.getElementById('files').addEventListener('change', handleFileSelect, false);
|
28
style.css
Normal file
28
style.css
Normal file
@ -0,0 +1,28 @@
|
||||
body {
|
||||
font-family: Helvetica, Verdana
|
||||
}
|
||||
p {
|
||||
padding: 7px 10px;
|
||||
}
|
||||
#error {
|
||||
color: red;
|
||||
}
|
||||
#progress_bar {
|
||||
margin: 10px 0;
|
||||
padding: 3px;
|
||||
border: 1px solid #000;
|
||||
font-size: 14px;
|
||||
clear: both;
|
||||
opacity: 0;
|
||||
-moz-transition: opacity 1s linear;
|
||||
-o-transition: opacity 1s linear;
|
||||
-webkit-transition: opacity 1s linear;
|
||||
}
|
||||
#progress_bar.loading {
|
||||
opacity: 1.0;
|
||||
}
|
||||
#progress_bar .percent {
|
||||
background-color: #99ccff;
|
||||
height: auto;
|
||||
width: 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user