title: Load STL from buffer (not path) in Three.js
body: <p>I want to display a user-uploaded STL file using Three.js. </p>
  
  <p>My file is sent to the front-end via a GET request: <code>res.sendFile(path)</code>. The problem is, I can't use that raw data to load a file without editing Three.js's loader! </p>
  
  <p>Could someone help me edit the <a href="https://github.com/mrdoob/three.js/blob/dev/examples/jsm/loaders/STLLoader.js" rel="nofollow noreferrer">STLLoader</a> to accept the result of <code>res.sendFile(path)</code> instead of a path (URL)?</p>
  
  <p>After a week of head-scratching, I've had no luck! </p>
  
  <p>Here's the code of STLLoader.js:</p>
  
  <pre><code>import {
    BufferAttribute,
    BufferGeometry,
    DefaultLoadingManager,
    FileLoader,
    Float32BufferAttribute,
    LoaderUtils,
    Vector3
  } from "three/build/three.module.js";
  
  
  var STLLoader = function ( manager ) {
  
      this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
  
  };
  
  STLLoader.prototype = {
  
      constructor: STLLoader,
  
      load: function ( url, onLoad, onProgress, onError ) {
  
          var scope = this;
  
          var loader = new FileLoader( scope.manager );
          loader.setPath( scope.path );
          loader.setResponseType( 'arraybuffer' );
          loader.load( url, function ( text ) {
  
              try {
  
                  onLoad( scope.parse( text ) );
  
              } catch ( exception ) {
  
                  if ( onError ) {
  
                      onError( exception );
  
                  }
  
              }
  
          }, onProgress, onError );
  
      },
  
      setPath: function ( value ) {
  
          this.path = value;
          return this;
  
      },
  
      parse: function ( data ) {
  
          function isBinary( data ) {
  
              var expect, face_size, n_faces, reader;
              reader = new DataView( data );
              face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 );
              n_faces = reader.getUint32( 80, true );
              expect = 80 + ( 32 / 8 ) + ( n_faces * face_size );
  
              if ( expect === reader.byteLength ) {
  
                  return true;
  
              }
  
              // An ASCII STL data must begin with 'solid ' as the first six bytes.
              // However, ASCII STLs lacking the SPACE after the 'd' are known to be
              // plentiful.  So, check the first 5 bytes for 'solid'.
  
              // Several encodings, such as UTF-8, precede the text with up to 5 bytes:
              // https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
              // Search for "solid" to start anywhere after those prefixes.
  
              // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd'
  
              var solid = [ 115, 111, 108, 105, 100 ];
  
              for ( var off = 0; off &lt; 5; off ++ ) {
  
                  // If "solid" text is matched to the current offset, declare it to be an ASCII STL.
  
                  if ( matchDataViewAt( solid, reader, off ) ) return false;
  
              }
  
              // Couldn't find "solid" text at the beginning; it is binary STL.
  
              return true;
  
          }
  
          function matchDataViewAt( query, reader, offset ) {
  
              // Check if each byte in query matches the corresponding byte from the current offset
  
              for ( var i = 0, il = query.length; i &lt; il; i ++ ) {
  
                  if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false;
  
              }
  
              return true;
  
          }
  
          function parseBinary( data ) {
  
              var reader = new DataView( data );
              var faces = reader.getUint32( 80, true );
  
              var r, g, b, hasColors = false, colors;
              var defaultR, defaultG, defaultB, alpha;
  
              // process STL header
              // check for default color in header ("COLOR=rgba" sequence).
  
              for ( var index = 0; index &lt; 80 - 10; index ++ ) {
  
                  if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) &amp;&amp;
                      ( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) &amp;&amp;
                      ( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) {
  
                      hasColors = true;
                      colors = new Float32Array( faces * 3 * 3 );
  
                      defaultR = reader.getUint8( index + 6 ) / 255;
                      defaultG = reader.getUint8( index + 7 ) / 255;
                      defaultB = reader.getUint8( index + 8 ) / 255;
                      alpha = reader.getUint8( index + 9 ) / 255;
  
                  }
  
              }
  
              var dataOffset = 84;
              var faceLength = 12 * 4 + 2;
  
              var geometry = new BufferGeometry();
  
              var vertices = new Float32Array( faces * 3 * 3 );
              var normals = new Float32Array( faces * 3 * 3 );
  
              for ( var face = 0; face &lt; faces; face ++ ) {
  
                  var start = dataOffset + face * faceLength;
                  var normalX = reader.getFloat32( start, true );
                  var normalY = reader.getFloat32( start + 4, true );
                  var normalZ = reader.getFloat32( start + 8, true );
  
                  if ( hasColors ) {
  
                      var packedColor = reader.getUint16( start + 48, true );
  
                      if ( ( packedColor &amp; 0x8000 ) === 0 ) {
  
                          // facet has its own unique color
  
                          r = ( packedColor &amp; 0x1F ) / 31;
                          g = ( ( packedColor &gt;&gt; 5 ) &amp; 0x1F ) / 31;
                          b = ( ( packedColor &gt;&gt; 10 ) &amp; 0x1F ) / 31;
  
                      } else {
  
                          r = defaultR;
                          g = defaultG;
                          b = defaultB;
  
                      }
  
                  }
  
                  for ( var i = 1; i &lt;= 3; i ++ ) {
  
                      var vertexstart = start + i * 12;
                      var componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 );
  
                      vertices[ componentIdx ] = reader.getFloat32( vertexstart, true );
                      vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true );
                      vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true );
  
                      normals[ componentIdx ] = normalX;
                      normals[ componentIdx + 1 ] = normalY;
                      normals[ componentIdx + 2 ] = normalZ;
  
                      if ( hasColors ) {
  
                          colors[ componentIdx ] = r;
                          colors[ componentIdx + 1 ] = g;
                          colors[ componentIdx + 2 ] = b;
  
                      }
  
                  }
  
              }
  
              geometry.addAttribute( 'position', new BufferAttribute( vertices, 3 ) );
              geometry.addAttribute( 'normal', new BufferAttribute( normals, 3 ) );
  
              if ( hasColors ) {
  
                  geometry.addAttribute( 'color', new BufferAttribute( colors, 3 ) );
                  geometry.hasColors = true;
                  geometry.alpha = alpha;
  
              }
  
              return geometry;
  
          }
  
          function parseASCII( data ) {
  
              var geometry = new BufferGeometry();
              var patternSolid = /solid([\s\S]*?)endsolid/g;
              var patternFace = /facet([\s\S]*?)endfacet/g;
              var faceCounter = 0;
  
              var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
              var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
              var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
  
              var vertices = [];
              var normals = [];
  
              var normal = new Vector3();
  
              var result;
  
              var groupVertexes = [];
              var groupCount = 0;
              var startVertex = 0;
              var endVertex = 0;
  
              while ( ( result = patternSolid.exec( data ) ) !== null ) {
  
                  startVertex = endVertex;
  
                  var solid = result[ 0 ];
  
                  while ( ( result = patternFace.exec( solid ) ) !== null ) {
  
                      var vertexCountPerFace = 0;
                      var normalCountPerFace = 0;
  
                      var text = result[ 0 ];
  
                      while ( ( result = patternNormal.exec( text ) ) !== null ) {
  
                          normal.x = parseFloat( result[ 1 ] );
                          normal.y = parseFloat( result[ 2 ] );
                          normal.z = parseFloat( result[ 3 ] );
                          normalCountPerFace ++;
  
                      }
  
                      while ( ( result = patternVertex.exec( text ) ) !== null ) {
  
                          vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
                          normals.push( normal.x, normal.y, normal.z );
                          vertexCountPerFace ++;
                          endVertex ++;
  
                      }
  
                      // every face have to own ONE valid normal
  
                      if ( normalCountPerFace !== 1 ) {
  
                          console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter );
  
                      }
  
                      // each face have to own THREE valid vertices
  
                      if ( vertexCountPerFace !== 3 ) {
  
                          console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter );
  
                      }
  
                      faceCounter ++;
  
                  }
  
                  groupVertexes.push( { startVertex: startVertex, endVertex: endVertex } );
                  groupCount ++;
  
              }
  
              geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
              geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
  
              if ( groupCount &gt; 0 ) {
  
                  for ( var i = 0; i &lt; groupVertexes.length; i ++ ) {
  
                      geometry.addGroup( groupVertexes[ i ].startVertex, groupVertexes[ i ].endVertex, i );
  
                  }
  
              }
  
              return geometry;
  
          }
  
          function ensureString( buffer ) {
  
              if ( typeof buffer !== 'string' ) {
  
                  return LoaderUtils.decodeText( new Uint8Array( buffer ) );
  
              }
  
              return buffer;
  
          }
  
          function ensureBinary( buffer ) {
  
              if ( typeof buffer === 'string' ) {
  
                  var array_buffer = new Uint8Array( buffer.length );
                  for ( var i = 0; i &lt; buffer.length; i ++ ) {
  
                      array_buffer[ i ] = buffer.charCodeAt( i ) &amp; 0xff; // implicitly assumes little-endian
  
                  }
  
                  return array_buffer.buffer || array_buffer;
  
              } else {
  
                  return buffer;
  
              }
  
          }
  
          // start
  
          var binData = ensureBinary( data );
  
          return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
  
      }
  
  };
  
  export { STLLoader };
  </code></pre>
  
  <p><strong>Some extra info:</strong> </p>
  
  <p>The file data, when received by my frontend, looks like this: <code>�u#;�O_?����W�Bt��B���A��B凪B�F�A���BJJ�BȧA�ۢ&gt;#(q&gt;�k?��B</code></p>
  
  <p>I've been attempting to encode it as an <code>ArrayBuffer</code> using:</p>
  
  <pre><code>  var enc = new TextEncoder();
    var arrayBuffer = enc.encode(fileString).buffer;
  </code></pre>