oculus42/openscad-poly-tools

View on GitHub
html/convert.html

Summary

Maintainability
Test Coverage
<!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>