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">&times;</span>
+                </button>
+                <b>Export OBJ</b><br/><br/>
+                Filename: &nbsp;&nbsp;<input id="objFilename" value="" placeholder="" class="bigInput text form-control" type="text"> .stl<br/><br/>
+                Scale: &nbsp;&nbsp;<input value="" placeholder="" class="exportScale form-control" type="text"><br/><br/>
+                Dimensions: &nbsp;&nbsp;<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