From 3fbb8dc96ce802751506f382961ced1274e42d36 Mon Sep 17 00:00:00 2001 From: Martin Woodward Date: Tue, 15 Dec 2020 14:54:37 +0000 Subject: [PATCH] Add STL Exporter --- .gitignore | 2 + index.html | 4 + js/STLExporter.js | 210 ++++++++++++++++++++++++++++++++++++++++++++ js/contributions.js | 53 ++++++++++- package-lock.json | 85 +++++++++++++----- package.json | 4 + 6 files changed, 333 insertions(+), 25 deletions(-) create mode 100644 js/STLExporter.js diff --git a/.gitignore b/.gitignore index dfa6950..b58e5ca 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,5 @@ dist # TernJS port file .tern-port + +.vercel diff --git a/index.html b/index.html index fce4009..d2877ba 100644 --- a/index.html +++ b/index.html @@ -9,6 +9,10 @@ +
+ +
+ diff --git a/js/STLExporter.js b/js/STLExporter.js new file mode 100644 index 0000000..273dec7 --- /dev/null +++ b/js/STLExporter.js @@ -0,0 +1,210 @@ +import { + BufferGeometry, + Vector3 +} from './three.module.js'; + +/** + * Usage: + * var exporter = new STLExporter(); + * + * // second argument is a list of options + * var data = exporter.parse( mesh, { binary: true } ); + * + */ + +var STLExporter = function () {}; + +STLExporter.prototype = { + + constructor: STLExporter, + + parse: function ( scene, options ) { + + if ( options === undefined ) options = {}; + + var binary = options.binary !== undefined ? options.binary : false; + + // + + var objects = []; + var triangles = 0; + + scene.traverse( function ( object ) { + + if ( object.isMesh ) { + + var geometry = object.geometry; + + if ( geometry.isGeometry ) { + + geometry = new BufferGeometry().fromGeometry( geometry ); + + } + + var index = geometry.index; + var positionAttribute = geometry.getAttribute( 'position' ); + + triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 ); + + objects.push( { + object3d: object, + geometry: geometry + } ); + + } + + } ); + + var output; + var offset = 80; // skip header + + if ( binary === true ) { + + var bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4; + var arrayBuffer = new ArrayBuffer( bufferLength ); + output = new DataView( arrayBuffer ); + output.setUint32( offset, triangles, true ); offset += 4; + + } else { + + output = ''; + output += 'solid exported\n'; + + } + + var vA = new Vector3(); + var vB = new Vector3(); + var vC = new Vector3(); + var cb = new Vector3(); + var ab = new Vector3(); + var normal = new Vector3(); + + for ( var i = 0, il = objects.length; i < il; i ++ ) { + + var object = objects[ i ].object3d; + var geometry = objects[ i ].geometry; + + var index = geometry.index; + var positionAttribute = geometry.getAttribute( 'position' ); + + if ( index !== null ) { + + // indexed geometry + + for ( var j = 0; j < index.count; j += 3 ) { + + var a = index.getX( j + 0 ); + var b = index.getX( j + 1 ); + var c = index.getX( j + 2 ); + + writeFace( a, b, c, positionAttribute, object ); + + } + + } else { + + // non-indexed geometry + + for ( var j = 0; j < positionAttribute.count; j += 3 ) { + + var a = j + 0; + var b = j + 1; + var 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.boneTransform( a, vA ); + object.boneTransform( b, vB ); + object.boneTransform( 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 }; \ No newline at end of file diff --git a/js/contributions.js b/js/contributions.js index 76e7255..52d9be2 100644 --- a/js/contributions.js +++ b/js/contributions.js @@ -2,6 +2,7 @@ import * as THREE from "./three.module.js"; import { GLTFLoader } from './GLTFLoader.js'; import { OrbitControls } from './OrbitControls.js'; import { GUI } from './dat.gui.module.js'; +import { STLExporter } from './STLExporter.js'; const BASE_LENGTH = 0.834 const BASE_WIDTH = 0.167 @@ -10,8 +11,8 @@ const CUBE_SIZE = 0.0143 const MAX_HEIGHT = 0.14 const FACE_ANGLE = 104.79 -let username = "jasonlong" -let year = "2018" +let username = "nat" +let year = "2020" let json = {} let font = undefined let fontSize = 0.025 @@ -20,6 +21,8 @@ let fontHeight = 0.00658 // Extrusion thickness let camera, scene, renderer let bronzeMaterial +var exporter = new STLExporter(); + var urlParams = new URLSearchParams(window.location.search); if (urlParams.has('username')) { @@ -224,12 +227,33 @@ const init = () => { controls.autoRotate = false controls.screenSpacePanning = true controls.addEventListener('change', render); + + var buttonExportASCII = document.getElementById( 'exportASCII' ); + buttonExportASCII.addEventListener( 'click', exportASCII ); + + var buttonExportBinary = document.getElementById( 'exportBinary' ); + buttonExportBinary.addEventListener( 'click', exportBinary ); + } const render = () => { renderer.render(scene, camera) } +function exportASCII() { + + var result = exporter.parse( bronzeMaterial ); + saveString( result, username + '-' + year + '.stl' ); + +} + +function exportBinary() { + + var result = exporter.parse( scene, { binary: true } ); + saveArrayBuffer( result, username + '-' + year + '.stl' ); + +} + // // Event listeners // @@ -243,3 +267,28 @@ const onWindowResize = () => { } window.addEventListener('resize', onWindowResize, false) + + +var link = document.createElement( 'a' ); +link.style.display = 'none'; +document.body.appendChild( link ); + +function save( blob, filename ) { + + link.href = URL.createObjectURL( blob ); + link.download = filename; + link.click(); + +} + +function saveString( text, filename ) { + + save( new Blob( [ text ], { type: 'text/plain' } ), filename ); + +} + +function saveArrayBuffer( buffer, filename ) { + + save( new Blob( [ buffer ], { type: 'application/octet-stream' } ), filename ); + +} diff --git a/package-lock.json b/package-lock.json index 7090fd0..cc6e136 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1355,6 +1355,14 @@ "graceful-fs": "^4.1.2", "jsonfile": "^3.0.0", "universalify": "^0.1.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "fsevents": { @@ -2131,13 +2139,20 @@ "graceful-fs": "~1.2.0", "inherits": "1", "minimatch": "~0.2.11" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" }, "inherits": { "version": "1.0.2", @@ -2172,12 +2187,6 @@ "sparkles": "^1.0.0" } }, - "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true - }, "gulp": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", @@ -2827,6 +2836,15 @@ "dev": true, "requires": { "graceful-fs": "^4.1.6" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true, + "optional": true + } } }, "kind-of": { @@ -2877,6 +2895,14 @@ "pify": "^2.0.0", "pinkie-promise": "^2.0.0", "strip-bom": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "localtunnel": { @@ -3223,12 +3249,6 @@ "to-regex": "^3.0.1" } }, - "natives": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", - "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", - "dev": true - }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -3576,6 +3596,14 @@ "graceful-fs": "^4.1.2", "pify": "^2.0.0", "pinkie-promise": "^2.0.0" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "pify": { @@ -3729,6 +3757,14 @@ "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" + }, + "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + } } }, "rechoir": { @@ -4841,6 +4877,12 @@ "vinyl": "^1.1.0" }, "dependencies": { + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, "vinyl": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", @@ -4877,13 +4919,10 @@ "dev": true }, "graceful-fs": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.12.tgz", - "integrity": "sha512-J55gaCS4iTTJfTXIxSVw3EMQckcqkpdRv3IR7gu6sq0+tbC363Zx6KH/SEwXASK9JRbhyZmVjJEVJIOxYsB3Qg==", - "dev": true, - "requires": { - "natives": "^1.1.3" - } + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true }, "isarray": { "version": "0.0.1", diff --git a/package.json b/package.json index 136de26..e4b5f54 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "3d-contributions", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", + "preinstall": "npx npm-force-resolutions", "start": "node node_modules/gulp/bin/gulp.js auto" }, "devDependencies": { @@ -11,5 +12,8 @@ }, "dependencies": { "three": "^0.118.1" + }, + "resolutions": { + "graceful-fs": "^4.2.4" } }