1/** 2 * @author aleeper / http://adamleeper.com/ 3 * @author mrdoob / http://mrdoob.com/ 4 * @author gero3 / https://github.com/gero3 5 * @author Mugen87 / https://github.com/Mugen87 6 * 7 * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs. 8 * 9 * Supports both binary and ASCII encoded files, with automatic detection of type. 10 * 11 * The loader returns a non-indexed buffer geometry. 12 * 13 * Limitations: 14 * Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL). 15 * There is perhaps some question as to how valid it is to always assume little-endian-ness. 16 * ASCII decoding assumes file is UTF-8. 17 * 18 * Usage: 19 * var loader = new THREEP3DL.STLLoader(); 20 * loader.load( './models/stl/slotted_disk.stl', function ( geometry ) { 21 * scene.add( new THREEP3DL.Mesh( geometry ) ); 22 * }); 23 * 24 * For binary STLs geometry might contain colors for vertices. To use it: 25 * // use the same code to load STL as above 26 * if (geometry.hasColors) { 27 * material = new THREEP3DL.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREEP3DL.VertexColors }); 28 * } else { .... } 29 * var mesh = new THREEP3DL.Mesh( geometry, material ); 30 */ 31 32 33THREEP3DL.STLLoader = function ( manager ) { 34 35 this.manager = ( manager !== undefined ) ? manager : THREEP3DL.DefaultLoadingManager; 36 37}; 38 39THREEP3DL.STLLoader.prototype = { 40 41 constructor: THREEP3DL.STLLoader, 42 43 load: function ( url, onLoad, onProgress, onError ) { 44 45 var scope = this; 46 47 var loader = new THREEP3DL.FileLoader( scope.manager ); 48 loader.setPath( scope.path ); 49 loader.setResponseType( 'arraybuffer' ); 50 loader.load( url, function ( text ) { 51 52 try { 53 54 onLoad( scope.parse( text ) ); 55 56 } catch ( exception ) { 57 58 if ( onError ) { 59 60 onError( exception ); 61 62 } 63 64 } 65 66 }, onProgress, onError ); 67 68 }, 69 70 setPath: function ( value ) { 71 72 this.path = value; 73 return this; 74 75 }, 76 77 parse: function ( data ) { 78 79 function isBinary( data ) { 80 81 var expect, face_size, n_faces, reader; 82 reader = new DataView( data ); 83 face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 ); 84 n_faces = reader.getUint32( 80, true ); 85 expect = 80 + ( 32 / 8 ) + ( n_faces * face_size ); 86 87 if ( expect === reader.byteLength ) { 88 89 return true; 90 91 } 92 93 // An ASCII STL data must begin with 'solid ' as the first six bytes. 94 // However, ASCII STLs lacking the SPACE after the 'd' are known to be 95 // plentiful. So, check the first 5 bytes for 'solid'. 96 97 // Several encodings, such as UTF-8, precede the text with up to 5 bytes: 98 // https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding 99 // Search for "solid" to start anywhere after those prefixes. 100 101 // US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd' 102 103 var solid = [ 115, 111, 108, 105, 100 ]; 104 105 for ( var off = 0; off < 5; off ++ ) { 106 107 // If "solid" text is matched to the current offset, declare it to be an ASCII STL. 108 109 if ( matchDataViewAt ( solid, reader, off ) ) return false; 110 111 } 112 113 // Couldn't find "solid" text at the beginning; it is binary STL. 114 115 return true; 116 117 } 118 119 function matchDataViewAt( query, reader, offset ) { 120 121 // Check if each byte in query matches the corresponding byte from the current offset 122 123 for ( var i = 0, il = query.length; i < il; i ++ ) { 124 125 if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false; 126 127 } 128 129 return true; 130 131 } 132 133 function parseBinary( data ) { 134 135 var reader = new DataView( data ); 136 var faces = reader.getUint32( 80, true ); 137 138 var r, g, b, hasColors = false, colors; 139 var defaultR, defaultG, defaultB, alpha; 140 141 p3dlite.model_total_volume=0; 142 p3dlite.model_surface_area=0; 143 144 145 // process STL header 146 // check for default color in header ("COLOR=rgba" sequence). 147 148 for ( var index = 0; index < 80 - 10; index ++ ) { 149 150 if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) && 151 ( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) && 152 ( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) { 153 154 hasColors = true; 155 colors = []; 156 157 defaultR = reader.getUint8( index + 6 ) / 255; 158 defaultG = reader.getUint8( index + 7 ) / 255; 159 defaultB = reader.getUint8( index + 8 ) / 255;
160 alpha = reader.getUint8( index + 9 ) / 255; 161 162 } 163 164 } 165 166 var dataOffset = 84; 167 var faceLength = 12 * 4 + 2; 168 169 var geometry = new THREEP3DL.BufferGeometry(); 170 171 var vertices = []; 172 var normals = []; 173 174 for ( var face = 0; face < faces; face ++ ) { 175 176 var start = dataOffset + face * faceLength; 177 var normalX = reader.getFloat32( start, true ); 178 var normalY = reader.getFloat32( start + 4, true ); 179 var normalZ = reader.getFloat32( start + 8, true ); 180 181 if ( hasColors ) { 182 183 var packedColor = reader.getUint16( start + 48, true ); 184 185 if ( ( packedColor & 0x8000 ) === 0 ) { 186 187 // facet has its own unique color 188 189 r = ( packedColor & 0x1F ) / 31; 190 g = ( ( packedColor >> 5 ) & 0x1F ) / 31; 191 b = ( ( packedColor >> 10 ) & 0x1F ) / 31; 192 193 } else { 194 195 r = defaultR; 196 g = defaultG; 197 b = defaultB; 198 199 } 200 201 } 202 var tetrahedron = new Array(); 203 for ( var i = 1; i <= 3; i ++ ) { 204 205 var vertexstart = start + i * 12; 206 207 vertices.push( reader.getFloat32( vertexstart, true ) ); 208 vertices.push( reader.getFloat32( vertexstart + 4, true ) ); 209 vertices.push( reader.getFloat32( vertexstart + 8, true ) ); 210 211 tetrahedron[i] = new Array(); 212 tetrahedron[i].push(reader.getFloat32( vertexstart, true )); 213 tetrahedron[i].push(reader.getFloat32( vertexstart + 4, true )); 214 tetrahedron[i].push(reader.getFloat32( vertexstart + 8, true )); 215 216 217 normals.push( normalX, normalY, normalZ ); 218 219 if ( hasColors ) { 220 221 colors.push( r, g, b ); 222 223 } 224 225 } 226 p3dlite.model_total_volume+=p3dliteSignedVolume(tetrahedron[1], tetrahedron[2], tetrahedron[3]); 227 p3dlite.model_surface_area+=p3dliteSurfaceArea(tetrahedron[1], tetrahedron[2], tetrahedron[3]); 228 229 230 } 231 232 geometry.addAttribute( 'position', new THREEP3DL.BufferAttribute( new Float32Array( vertices ), 3 ) ); 233 geometry.addAttribute( 'normal', new THREEP3DL.BufferAttribute( new Float32Array( normals ), 3 ) ); 234 235 if ( hasColors ) { 236 237 geometry.addAttribute( 'color', new THREEP3DL.BufferAttribute( new Float32Array( colors ), 3 ) ); 238 geometry.hasColors = true; 239 geometry.alpha = alpha; 240 241 } 242 243 return geometry; 244 245 } 246 247 function parseASCII( data ) { 248 249 var geometry = new THREEP3DL.BufferGeometry(); 250 var patternFace = /facet([\s\S]*?)endfacet/g; 251 var faceCounter = 0; 252 253 var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source; 254 var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' ); 255 var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' ); 256 257 var vertices = []; 258 var normals = []; 259 260 var normal = new THREEP3DL.Vector3(); 261 262 var result; 263 264 p3dlite.model_total_volume=0; 265 p3dlite.model_surface_area=0; 266 267 268 while ( ( result = patternFace.exec( data ) ) !== null ) { 269 270 var vertexCountPerFace = 0; 271 var normalCountPerFace = 0; 272 273 var text = result[ 0 ]; 274 275 while ( ( result = patternNormal.exec( text ) ) !== null ) { 276 277 normal.x = parseFloat( result[ 1 ] ); 278 normal.y = parseFloat( result[ 2 ] ); 279 normal.z = parseFloat( result[ 3 ] ); 280 normalCountPerFace ++; 281 282 } 283 284 tetrahedron = new Array(); 285 var i = 1; 286 287 288 while ( ( result = patternVertex.exec( text ) ) !== null ) { 289 tetrahedron[i] = new Array(); 290 tetrahedron[i].push(parseFloat( result[ 1 ] )); 291 tetrahedron[i].push(parseFloat( result[ 2 ] )); 292 tetrahedron[i].push(parseFloat( result[ 3 ] )); 293 294 295 vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) ); 296 normals.push( normal.x, normal.y, normal.z ); 297 vertexCountPerFace ++; 298 299 i++; 300 301 } 302 p3dlite.model_total_volume+=p3dliteSignedVolume(tetrahedron[1], tetrahedron[2], tetrahedron[3]); 303 p3dlite.model_surface_area+=p3dliteSurfaceArea(tetrahedron[1], tetrahedron[2], tetrahedron[3]); 304 305 306 // every face have to own ONE valid normal 307 308 if ( normalCountPerFace !== 1 ) { 309 310 console.error( 'THREEP3DL.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter ); 311 312 } 313 314 // each face have to own THREE valid vertices 315 316 if ( vertexCountPerFace !== 3 ) { 317 318 console.error( 'THREEP3DL.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter ); 319 320 } 321 322 faceCounter ++; 323 324 } 325 326 geometry.addAttribute( 'position', new THREEP3DL.Float32BufferAttribute( vertices, 3 ) );
327 geometry.addAttribute( 'normal', new THREEP3DL.Float32BufferAttribute( normals, 3 ) ); 328 329 return geometry; 330 331 } 332 333 function ensureString( buffer ) { 334 335 if ( typeof buffer !== 'string' ) { 336 337 return THREEP3DL.LoaderUtils.decodeText( new Uint8Array( buffer ) ); 338 339 } 340 341 return buffer; 342 343 } 344 345 function ensureBinary( buffer ) { 346 347 if ( typeof buffer === 'string' ) { 348 349 var array_buffer = new Uint8Array( buffer.length ); 350 for ( var i = 0; i < buffer.length; i ++ ) { 351 352 array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian 353 354 } 355 return array_buffer.buffer || array_buffer; 356 357 } else { 358 359 return buffer; 360 361 } 362 363 } 364 365 // start 366 367 var binData = ensureBinary( data ); 368 369 return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) ); 370 371 } 372 373};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.