STLExporter.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import { Vector3 } from 'three';
  2. /**
  3. * An exporter for STL.
  4. *
  5. * STL files describe only the surface geometry of a three-dimensional object without
  6. * any representation of color, texture or other common model attributes. The STL format
  7. * specifies both ASCII and binary representations, with binary being more compact.
  8. * STL files contain no scale information or indexes, and the units are arbitrary.
  9. *
  10. * ```js
  11. * const exporter = new STLExporter();
  12. * const data = exporter.parse( mesh, { binary: true } );
  13. * ```
  14. *
  15. * @three_import import { STLExporter } from 'three/addons/exporters/STLExporter.js';
  16. */
  17. class STLExporter {
  18. /**
  19. * Parses the given 3D object and generates the STL output.
  20. *
  21. * If the 3D object is composed of multiple children and geometry, they are merged into a single mesh in the file.
  22. *
  23. * @param {Object3D} scene - A scene, mesh or any other 3D object containing meshes to encode.
  24. * @param {STLExporter~Options} options - The export options.
  25. * @return {string|ArrayBuffer} The exported STL.
  26. */
  27. parse( scene, options = {} ) {
  28. options = Object.assign( {
  29. binary: false
  30. }, options );
  31. const binary = options.binary;
  32. //
  33. const objects = [];
  34. let triangles = 0;
  35. scene.traverse( function ( object ) {
  36. if ( object.isMesh ) {
  37. const geometry = object.geometry;
  38. const index = geometry.index;
  39. const positionAttribute = geometry.getAttribute( 'position' );
  40. triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 );
  41. objects.push( {
  42. object3d: object,
  43. geometry: geometry
  44. } );
  45. }
  46. } );
  47. let output;
  48. let offset = 80; // skip header
  49. if ( binary === true ) {
  50. const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4;
  51. const arrayBuffer = new ArrayBuffer( bufferLength );
  52. output = new DataView( arrayBuffer );
  53. output.setUint32( offset, triangles, true ); offset += 4;
  54. } else {
  55. output = '';
  56. output += 'solid exported\n';
  57. }
  58. const vA = new Vector3();
  59. const vB = new Vector3();
  60. const vC = new Vector3();
  61. const cb = new Vector3();
  62. const ab = new Vector3();
  63. const normal = new Vector3();
  64. for ( let i = 0, il = objects.length; i < il; i ++ ) {
  65. const object = objects[ i ].object3d;
  66. const geometry = objects[ i ].geometry;
  67. const index = geometry.index;
  68. const positionAttribute = geometry.getAttribute( 'position' );
  69. if ( index !== null ) {
  70. // indexed geometry
  71. for ( let j = 0; j < index.count; j += 3 ) {
  72. const a = index.getX( j + 0 );
  73. const b = index.getX( j + 1 );
  74. const c = index.getX( j + 2 );
  75. writeFace( a, b, c, positionAttribute, object );
  76. }
  77. } else {
  78. // non-indexed geometry
  79. for ( let j = 0; j < positionAttribute.count; j += 3 ) {
  80. const a = j + 0;
  81. const b = j + 1;
  82. const c = j + 2;
  83. writeFace( a, b, c, positionAttribute, object );
  84. }
  85. }
  86. }
  87. if ( binary === false ) {
  88. output += 'endsolid exported\n';
  89. }
  90. return output;
  91. function writeFace( a, b, c, positionAttribute, object ) {
  92. vA.fromBufferAttribute( positionAttribute, a );
  93. vB.fromBufferAttribute( positionAttribute, b );
  94. vC.fromBufferAttribute( positionAttribute, c );
  95. if ( object.isSkinnedMesh === true ) {
  96. object.applyBoneTransform( a, vA );
  97. object.applyBoneTransform( b, vB );
  98. object.applyBoneTransform( c, vC );
  99. }
  100. vA.applyMatrix4( object.matrixWorld );
  101. vB.applyMatrix4( object.matrixWorld );
  102. vC.applyMatrix4( object.matrixWorld );
  103. writeNormal( vA, vB, vC );
  104. writeVertex( vA );
  105. writeVertex( vB );
  106. writeVertex( vC );
  107. if ( binary === true ) {
  108. output.setUint16( offset, 0, true ); offset += 2;
  109. } else {
  110. output += '\t\tendloop\n';
  111. output += '\tendfacet\n';
  112. }
  113. }
  114. function writeNormal( vA, vB, vC ) {
  115. cb.subVectors( vC, vB );
  116. ab.subVectors( vA, vB );
  117. cb.cross( ab ).normalize();
  118. normal.copy( cb ).normalize();
  119. if ( binary === true ) {
  120. output.setFloat32( offset, normal.x, true ); offset += 4;
  121. output.setFloat32( offset, normal.y, true ); offset += 4;
  122. output.setFloat32( offset, normal.z, true ); offset += 4;
  123. } else {
  124. output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
  125. output += '\t\touter loop\n';
  126. }
  127. }
  128. function writeVertex( vertex ) {
  129. if ( binary === true ) {
  130. output.setFloat32( offset, vertex.x, true ); offset += 4;
  131. output.setFloat32( offset, vertex.y, true ); offset += 4;
  132. output.setFloat32( offset, vertex.z, true ); offset += 4;
  133. } else {
  134. output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
  135. }
  136. }
  137. }
  138. }
  139. /**
  140. * Export options of `STLExporter`.
  141. *
  142. * @typedef {Object} STLExporter~Options
  143. * @property {boolean} [binary=false] - Whether to export in binary format or ASCII.
  144. **/
  145. export { STLExporter };