200 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			200 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | 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 }; |