import { Vector3 } from 'three'; /** * Usage: * const exporter = new STLExporter(); * * // second argument is a list of options * const data = exporter.parse( mesh, { binary: true } ); * */ class STLExporter { parse( scene, options = {} ) { options = Object.assign( { binary: false }, options ); const binary = options.binary; // const objects = []; let triangles = 0; scene.traverse( function ( object ) { if ( object.isMesh ) { const geometry = object.geometry; const index = geometry.index; const positionAttribute = geometry.getAttribute( 'position' ); triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 ); objects.push( { object3d: object, geometry: geometry } ); } } ); let output; let offset = 80; // skip header if ( binary === true ) { const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; const arrayBuffer = new ArrayBuffer( bufferLength ); output = new DataView( arrayBuffer ); output.setUint32( offset, triangles, true ); offset += 4; } else { output = ''; output += 'solid exported\n'; } const vA = new Vector3(); const vB = new Vector3(); const vC = new Vector3(); const cb = new Vector3(); const ab = new Vector3(); const normal = new Vector3(); for ( let i = 0, il = objects.length; i < il; i ++ ) { const object = objects[ i ].object3d; const geometry = objects[ i ].geometry; const index = geometry.index; const positionAttribute = geometry.getAttribute( 'position' ); if ( index !== null ) { // indexed geometry for ( let j = 0; j < index.count; j += 3 ) { const a = index.getX( j + 0 ); const b = index.getX( j + 1 ); const c = index.getX( j + 2 ); writeFace( a, b, c, positionAttribute, object ); } } else { // non-indexed geometry for ( let j = 0; j < positionAttribute.count; j += 3 ) { const a = j + 0; const b = j + 1; const c = j + 2; writeFace( a, b, c, positionAttribute, object ); } } } if ( binary === false ) { output += 'endsolid exported\n'; } return output; function writeFace( a, b, c, positionAttribute, object ) { vA.fromBufferAttribute( positionAttribute, a ); vB.fromBufferAttribute( positionAttribute, b ); vC.fromBufferAttribute( positionAttribute, c ); if ( object.isSkinnedMesh === true ) { object.applyBoneTransform( a, vA ); object.applyBoneTransform( b, vB ); object.applyBoneTransform( c, vC ); } vA.applyMatrix4( object.matrixWorld ); vB.applyMatrix4( object.matrixWorld ); vC.applyMatrix4( object.matrixWorld ); writeNormal( vA, vB, vC ); writeVertex( vA ); writeVertex( vB ); writeVertex( vC ); if ( binary === true ) { output.setUint16( offset, 0, true ); offset += 2; } else { output += '\t\tendloop\n'; output += '\tendfacet\n'; } } function writeNormal( vA, vB, vC ) { cb.subVectors( vC, vB ); ab.subVectors( vA, vB ); cb.cross( ab ).normalize(); normal.copy( cb ).normalize(); if ( binary === true ) { output.setFloat32( offset, normal.x, true ); offset += 4; output.setFloat32( offset, normal.y, true ); offset += 4; output.setFloat32( offset, normal.z, true ); offset += 4; } else { output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n'; output += '\t\touter loop\n'; } } function writeVertex( vertex ) { if ( binary === true ) { output.setFloat32( offset, vertex.x, true ); offset += 4; output.setFloat32( offset, vertex.y, true ); offset += 4; output.setFloat32( offset, vertex.z, true ); offset += 4; } else { output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; } } } } export { STLExporter };