html/convert.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Convert STL to OpenSCAD</title>
<style>
body {
font-family: Helvetica, Verdana;
font-size: 16px;
padding: 1em 2em;
}
p {
padding: 7px 10px;
}
.controls {
background: aliceblue;
padding: 1em 2em;
}
.controls div {
margin: 0.5em;
}
input, button {
font-size: 16px;
}
#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;
}
</style>
</head>
<body>
<h1>
STL to OpenSCAD Convertor
</h1>
<div class="controls">
<div id="error"></div>
<div><input type="file" id="files" name="file"/></div>
<div><button id="abort">Cancel</button></div>
<div id="progress_bar">
<div class="percent">0%</div>
</div>
<div><span id="conversion"></span>
</div>
<div id="result"></div>
<a href="#" id="download">Download!</a>
</div>
<p>
Based on the <a href="https://jsfiddle.net/Riham/yzvGD/" target="_new">original work</a> by Riham:
<a href="http://www.thingiverse.com/thing:62666" target="_new">Thingiverse: STL to OpenSCAD converter</a>.
</p>
<p>
<a href="http://jsfiddle.net/_sir/yzvGD/595/" target="_new">This update can also be found on JSFiddle.</a>
</p>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
<script>
// 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.
// Elements
var progress_bar = document.getElementById('progress_bar');
var progress = document.querySelector('.percent');
var conversion = document.getElementById('conversion');
var error = document.getElementById('error');
var reader;
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;
error.innerText = '';
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);
}
}
/**
* @param {string} vertex
* @returns {number}
*/
function getIndex(vertex) {
var index = vertices.indexOf(vertex);
if (index === -1) {
index = vertices.length;
vertices.push(vertex);
}
return index;
}
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 {
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*/
// TODO: Use Normal Vector to match faces
// 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() + ']';
var triangle = '[' + getIndex(v1) + ',' + getIndex(v2) + ',' + getIndex(v3) + ']';
br.readUInt16();
triangles.push(triangle);
} catch (err) {
showError(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 {
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) {
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 = '[' + getIndex(v1) + ',' + getIndex(v2) + ',' + getIndex(v3) + ']';
// 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) {
showError(err);
return;
}
}
saveResult(vertices, triangles);
}
}
function showError(err) {
error.innerText = "An Error has occured while trying to convert your file!\r\nPlease make sure this is a valid STL file";
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;
var blob = new Blob([result], {
type: 'text/plain'
});
$('#download')
.attr("download", "FromSTL.SCAD")
.attr("href", window.URL.createObjectURL(blob));
conversion.innerText = 'Conversion complete - Click the download link to download you OpenSCAD file! Total Triangels: ' + 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%';
$()
reader = new FileReader();
reader.onerror = errorHandler;
reader.onprogress = updateProgress;
reader.onabort = function (e) {
alert('File read cancelled');
};
reader.onloadstart = function (e) {
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(function() {
progress_bar.className = '';
}, 2000);
parseResult(reader.result);
};
// Read in the STL file as a binary string.
reader.readAsBinaryString(evt.target.files[0]);
}
function abortRead() {
reader.abort();
}
document.getElementById('files').addEventListener('change', handleFileSelect, false);
document.getElementById('abort').addEventListener('click', abortRead);
</script>
</body>
</html>