diff --git a/dependencies/OBJExporter.js b/dependencies/OBJExporter.js new file mode 100644 index 0000000000000000000000000000000000000000..27c251f1e58fb0e37a1b5231baf084bfe2ea32af --- /dev/null +++ b/dependencies/OBJExporter.js @@ -0,0 +1,261 @@ +/** + * @author mrdoob / http://mrdoob.com/ + */ + +THREE.OBJExporter = function () {}; + +THREE.OBJExporter.prototype = { + + constructor: THREE.OBJExporter, + + parse: function ( object ) { + + var output = ''; + + var indexVertex = 0; + var indexVertexUvs = 0; + var indexNormals = 0; + + var vertex = new THREE.Vector3(); + var normal = new THREE.Vector3(); + var uv = new THREE.Vector2(); + + var i, j, k, l, m, face = []; + + var parseMesh = function ( mesh ) { + + var nbVertex = 0; + var nbNormals = 0; + var nbVertexUvs = 0; + + var geometry = mesh.geometry; + + var normalMatrixWorld = new THREE.Matrix3(); + + if ( geometry instanceof THREE.Geometry ) { + + geometry = new THREE.BufferGeometry().setFromObject( mesh ); + + } + + if ( geometry instanceof THREE.BufferGeometry ) { + + // shortcuts + var vertices = geometry.getAttribute( 'position' ); + var normals = geometry.getAttribute( 'normal' ); + var uvs = geometry.getAttribute( 'uv' ); + var indices = geometry.getIndex(); + + // name of the mesh object + output += 'o ' + mesh.name + '\n'; + + // name of the mesh material + if ( mesh.material && mesh.material.name ) { + output += 'usemtl ' + mesh.material.name + '\n'; + } + + // vertices + + if( vertices !== undefined ) { + + for ( i = 0, l = vertices.count; i < l; i ++, nbVertex++ ) { + + vertex.x = vertices.getX( i ); + vertex.y = vertices.getY( i ); + vertex.z = vertices.getZ( i ); + + // transfrom the vertex to world space + vertex.applyMatrix4( mesh.matrixWorld ); + + // transform the vertex to export format + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + // uvs + + if( uvs !== undefined ) { + + for ( i = 0, l = uvs.count; i < l; i ++, nbVertexUvs++ ) { + + uv.x = uvs.getX( i ); + uv.y = uvs.getY( i ); + + // transform the uv to export format + output += 'vt ' + uv.x + ' ' + uv.y + '\n'; + + } + + } + + // normals + + if( normals !== undefined ) { + + normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); + + for ( i = 0, l = normals.count; i < l; i ++, nbNormals++ ) { + + normal.x = normals.getX( i ); + normal.y = normals.getY( i ); + normal.z = normals.getZ( i ); + + // transfrom the normal to world space + normal.applyMatrix3( normalMatrixWorld ); + + // transform the normal to export format + output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n'; + + } + + } + + // faces + + if( indices !== null ) { + + for ( i = 0, l = indices.count; i < l; i += 3 ) { + + for( m = 0; m < 3; m ++ ){ + + j = indices.getX( i + m ) + 1; + + face[ m ] = ( indexVertex + j ) + '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + '/' + ( indexNormals + j ); + + } + + // transform the face to export format + output += 'f ' + face.join( ' ' ) + "\n"; + + } + + } else { + + for ( i = 0, l = vertices.count; i < l; i += 3 ) { + + for( m = 0; m < 3; m ++ ){ + + j = i + m + 1; + + face[ m ] = ( indexVertex + j ) + '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + '/' + ( indexNormals + j ); + + } + + // transform the face to export format + output += 'f ' + face.join( ' ' ) + "\n"; + + } + + } + + } else { + + console.warn( 'THREE.OBJExporter.parseMesh(): geometry type unsupported', geometry ); + + } + + // update index + indexVertex += nbVertex; + indexVertexUvs += nbVertexUvs; + indexNormals += nbNormals; + + }; + + var parseLine = function( line ) { + + var nbVertex = 0; + + var geometry = line.geometry; + var type = line.type; + + if ( geometry instanceof THREE.Geometry ) { + + geometry = new THREE.BufferGeometry().setFromObject( line ); + + } + + if ( geometry instanceof THREE.BufferGeometry ) { + + // shortcuts + var vertices = geometry.getAttribute( 'position' ); + var indices = geometry.getIndex(); + + // name of the line object + output += 'o ' + line.name + '\n'; + + if( vertices !== undefined ) { + + for ( i = 0, l = vertices.count; i < l; i ++, nbVertex++ ) { + + vertex.x = vertices.getX( i ); + vertex.y = vertices.getY( i ); + vertex.z = vertices.getZ( i ); + + // transfrom the vertex to world space + vertex.applyMatrix4( line.matrixWorld ); + + // transform the vertex to export format + output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n'; + + } + + } + + if ( type === 'Line' ) { + + output += 'l '; + + for ( j = 1, l = vertices.count; j <= l; j++ ) { + + output += ( indexVertex + j ) + ' '; + + } + + output += '\n'; + + } + + if ( type === 'LineSegments' ) { + + for ( j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) { + + output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n'; + + } + + } + + } else { + + console.warn('THREE.OBJExporter.parseLine(): geometry type unsupported', geometry ); + + } + + // update index + indexVertex += nbVertex; + + }; + + object.traverse( function ( child ) { + + if ( child instanceof THREE.Mesh ) { + + parseMesh( child ); + + } + + if ( child instanceof THREE.Line ) { + + parseLine( child ); + + } + + } ); + + return output; + + } + +}; diff --git a/index.html b/index.html index 2ec3b0e681d6ab6582c1bb1a0029b92d4cab4aaa..1c328f8f6ede1ae9913943b54742bd4c2b2d57a4 100755 --- a/index.html +++ b/index.html @@ -359,6 +359,7 @@ <script type="text/javascript" src="dependencies/underscore-min.js"></script> <script type="text/javascript" src="dependencies/FileSaver.min.js"></script> <script type="text/javascript" src="dependencies/SVGLoader.js"></script> + <script type="text/javascript" src="dependencies/OBJExporter.js"></script> <script type="text/javascript" src="dependencies/path-data-polyfill.js"></script> <script type="text/javascript" src="dependencies/earcut.js"></script> <script type="text/javascript" src="dependencies/numeric-1.2.6.js"></script> @@ -415,6 +416,7 @@ <li class="divider"></li> <li><a id="exportFOLD" href="#">Save Simulation as FOLD...</a></li> <li><a id="exportSTL" href="#">Save Simulation as STL...</a></li> + <li><a id="exportOBJ" href="#">Save Simulation as OBJ...</a></li> <li><a id="saveSVG" href="#">Save Pattern as SVG...</a></li> <!--<li><a id="saveSVGScreenshot" href="#">Save SVG screenshot</a></li>--> </ul> @@ -764,6 +766,29 @@ </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> +<div class="modal fade" id="exportOBJModal" tabindex="-1" role="dialog"> + <div class="modal-dialog modal-med"> + <div class="modal-content"> + <div class="modal-body"> + <button type="button" class="close" data-dismiss="modal" aria-label="Close"> + <span aria-hidden="true">×</span> + </button> + <b>Export OBJ</b><br/><br/> + Filename: <input id="objFilename" value="" placeholder="" class="bigInput text form-control" type="text"> .stl<br/><br/> + Scale: <input value="" placeholder="" class="exportScale form-control" type="text"><br/><br/> + Dimensions: <b><span class="exportDimensions"></span></b><br/> + <span class="smallTxt">(the OBJ file format is unitless, but typically assumed to be either in inches or mm)</span><br/> + <label class="bigLabel checkbox" for="doublesidedOBJ"> + <input id="doublesidedOBJ" data-toggle="checkbox" class="custom-checkbox layersSelector" type="checkbox"><span class="icons"><span class="icon-unchecked"></span><span class="icon-checked"></span></span> + Double Sided + </label> + </div> + <div class="modal-footer"> + <button id="doOBJsave" type="button" class="actionButton btn btn-success" data-dismiss="modal">Save</button> + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> <div class="modal fade" id="exportFOLDModal" tabindex="-1" role="dialog"> <div class="modal-dialog modal-med"> <div class="modal-content"> diff --git a/js/controls.js b/js/controls.js index 5782f7a506d8d8cdec70e7ce5f1abc95deac5fde..4ef814fa730dcd1ff80ef314b2bf6ce2d1bde677 100755 --- a/js/controls.js +++ b/js/controls.js @@ -37,6 +37,11 @@ function initControls(globals){ $("#stlFilename").val(globals.filename + " : " + parseInt(globals.creasePercent*100) + "PercentFolded"); $('#exportSTLModal').modal('show'); }); + setLink("#exportOBJ", function(){ + updateDimensions(); + $("#objFilename").val(globals.filename + " : " + parseInt(globals.creasePercent*100) + "PercentFolded"); + $('#exportOBJModal').modal('show'); + }); setInput(".exportScale", globals.exportScale, function(val){ globals.exportScale = val; updateDimensions(); @@ -48,6 +53,10 @@ function initControls(globals){ } setCheckbox("#doublesidedSTL", globals.doublesidedSTL, function(val){ globals.doublesidedSTL = val; + //todo sync stuff + }); + setCheckbox("#doublesidedOBJ", globals.doublesidedSTL, function(val){ + globals.doublesidedSTL = val; }); setLink(".units", function(e){ var units = $(e.target).data("id"); @@ -62,6 +71,9 @@ function initControls(globals){ setLink("#doSTLsave", function(){ saveSTL(); }); + setLink("#doOBJsave", function(){ + saveOBJ(); + }); setLink("#doFOLDsave", function(){ saveFOLD(); }); diff --git a/js/saveSTL.js b/js/saveSTL.js index 7e114983fd47602adb2ca744054b2fddbe870ddd..8b23a887bdcac84f8f410e152f241a53052a5bbf 100755 --- a/js/saveSTL.js +++ b/js/saveSTL.js @@ -2,8 +2,7 @@ * Created by amandaghassaei on 5/2/17. */ -function saveSTL(){ - +function makeSaveGEO(){ var geo = new THREE.Geometry().fromBufferGeometry( globals.model.getGeometry() ); if (geo.vertices.length == 0 || geo.faces.length == 0) { @@ -24,13 +23,27 @@ function saveSTL(){ geo.faces.push(new THREE.Face3(face.a, face.c, face.b)); } } + return geo; +} + +function saveSTL(){ var data = []; - data.push({geo: geo, offset:new THREE.Vector3(0,0,0), orientation:new THREE.Quaternion(0,0,0,1)}); + data.push({geo: makeSaveGEO(), offset:new THREE.Vector3(0,0,0), orientation:new THREE.Quaternion(0,0,0,1)}); var stlBin = geometryToSTLBin(data); if (!stlBin) return; var blob = new Blob([stlBin], {type: 'application/octet-binary'}); var filename = $("#stlFilename").val(); if (filename == "") filename = globals.filename; saveAs(blob, filename + ".stl"); +} + +function saveOBJ(){ + var exporter = new THREE.OBJExporter(); + var result = exporter.parse (new THREE.Mesh(makeSaveGEO())); + if (!result) return; + var blob = new Blob([result], {type: 'application/octet-binary'}); + var filename = $("#objFilename").val(); + if (filename == "") filename = globals.filename; + saveAs(blob, filename + ".obj"); } \ No newline at end of file