8

I would like to be able to export an obj file just like in this example http://threejs.org/examples/#webgl_exporter_obj

However, i would like to export the .obj file with corresponding textures in the .mtl texture file (i have seen obj files with "usemtl someTextureNameFromMTL")

I have seen this question but the exporter seems to be exporting only mesh. I have also found this question but that discusses importer only.

The thing i wanted to implement is to make a 3d printable editor that would also export textures/colors, as there already is a .stl mesh exporter for 3d printing. however, i have found no mesh+color/texture exporter for three js :(

Community
  • 1
  • 1
user151496
  • 1,849
  • 24
  • 38
  • Brakebein's solution is great. About **a plain color one of texture** issue, is because the image has been repeated across the surface of a polygon. I tried using `-clamp on` to turn off repetition mode but it didn't work. – Nhat Nguyen Jul 07 '17 at 01:24

2 Answers2

8

I have extended the OBJExporter a little bit. It will return an object containing the .obj part and the .mtl part. I just wrote it down without testing, so there are probably bugs, but I hope it is something to start with.

I haven't looked up all the mtl values, I just used some standards values apart from color and texture information. Maybe I will improve it later on. You also need to be aware of the mtl filename. Currently, I'm writing a static name to the obj part. When you save the files the mtl file needs to be the same name as in the obj file declared. Otherwise 3ds max etc. won't read it.

/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.OBJExporter = function () {};

THREE.OBJExporter.prototype = {

 constructor: THREE.OBJExporter,

 parse: function ( object ) {

 var output = '';
  var materials = {};

  var indexVertex = 0;
  var indexVertexUvs = 0;
  var indexNormals = 0;
       
 var mtlFileName = 'objmaterial'; // maybe this value can be passed as parameter
 output += 'mtllib ' + mtlFileName +  '.mtl\n';

  var parseMesh = function ( mesh ) {

   var nbVertex = 0;
   var nbVertexUvs = 0;
   var nbNormals = 0;

   var geometry = mesh.geometry;
   var material = mesh.material;

   if ( geometry instanceof THREE.Geometry ) {

    output += 'o ' + mesh.name + '\n';

    var vertices = geometry.vertices;

    for ( var i = 0, l = vertices.length; i < l; i ++ ) {

     var vertex = vertices[ i ].clone();
     vertex.applyMatrix4( mesh.matrixWorld );

     output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';

     nbVertex ++;

    }

    // uvs

    var faces = geometry.faces;
    var faceVertexUvs = geometry.faceVertexUvs[ 0 ];
    var hasVertexUvs = faces.length === faceVertexUvs.length;

    if ( hasVertexUvs ) {

     for ( var i = 0, l = faceVertexUvs.length; i < l; i ++ ) {

      var vertexUvs = faceVertexUvs[ i ];

      for ( var j = 0, jl = vertexUvs.length; j < jl; j ++ ) {

       var uv = vertexUvs[ j ];

       output += 'vt ' + uv.x + ' ' + uv.y + '\n';

       nbVertexUvs ++;

      }

     }

    }

    // normals

    var normalMatrixWorld = new THREE.Matrix3();
    normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );

    for ( var i = 0, l = faces.length; i < l; i ++ ) {

     var face = faces[ i ];
     var vertexNormals = face.vertexNormals;

     if ( vertexNormals.length === 3 ) {

      for ( var j = 0, jl = vertexNormals.length; j < jl; j ++ ) {

       var normal = vertexNormals[ j ].clone();
       normal.applyMatrix3( normalMatrixWorld );

       output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';

       nbNormals ++;

      }

     } else {

      var normal = face.normal.clone();
      normal.applyMatrix3( normalMatrixWorld );

      for ( var j = 0; j < 3; j ++ ) {

       output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';

       nbNormals ++;

      }

     }

    }
              
   // material
              
   if (material.name !== '')
    output += 'usemtl ' + material.name + '\n';
   else
    output += 'usemtl material' + material.id + '\n';
              
   materials[material.id] = material;

    // faces


    for ( var i = 0, j = 1, l = faces.length; i < l; i ++, j += 3 ) {

     var face = faces[ i ];

     output += 'f ';
     output += ( indexVertex + face.a + 1 ) + '/' + ( hasVertexUvs ? ( indexVertexUvs + j     ) : '' ) + '/' + ( indexNormals + j     ) + ' ';
     output += ( indexVertex + face.b + 1 ) + '/' + ( hasVertexUvs ? ( indexVertexUvs + j + 1 ) : '' ) + '/' + ( indexNormals + j + 1 ) + ' ';
     output += ( indexVertex + face.c + 1 ) + '/' + ( hasVertexUvs ? ( indexVertexUvs + j + 2 ) : '' ) + '/' + ( indexNormals + j + 2 ) + '\n';

    }

   } else {

    console.warn( 'THREE.OBJExporter.parseMesh(): geometry type unsupported', mesh );
    // TODO: Support only BufferGeometry and use use setFromObject()

   }

   // update index
   indexVertex += nbVertex;
   indexVertexUvs += nbVertexUvs;
   indexNormals += nbNormals;

  };

  object.traverse( function ( child ) {

   if ( child instanceof THREE.Mesh ) parseMesh( child );

  } );
        
 // mtl output
      
 var mtlOutput = '';
      
 for (var key in materials) {
        
  var mat = materials[key];
          
  if (mat.name !== '')
   mtlOutput += 'newmtl ' + mat.name + '\n';
  else
   mtlOutput += 'newmtl material' + mat.id + '\n';
          
  mtlOutput += 'Ns 10.0000\n';
  mtlOutput += 'Ni 1.5000\n';
  mtlOutput += 'd 1.0000\n';
  mtlOutput += 'Tr 0.0000\n';
  mtlOutput += 'Tf 1.0000 1.0000 1.0000\n';
  mtlOutput += 'illum 2\n';
  mtlOutput += 'Ka ' + mat.color.r + ' ' + mat.color.g + ' ' + mat.color.b + ' ' + '\n';
  mtlOutput += 'Kd ' + mat.color.r + ' ' + mat.color.g + ' ' + mat.color.b + ' ' + '\n';
  mtlOutput += 'Ks 0.0000 0.0000 0.0000\n';
  mtlOutput += 'Ke 0.0000 0.0000 0.0000\n';
          
  if (mat.map && mat.map instanceof THREE.Texture) {
          
   var file = mat.map.image.currentSrc.slice( mat.map.image.currentSrc.slice.lastIndexOf("/"), mat.map.image.currentSrc.length - 1 );
            
   mtlOutput += 'map_Ka ' + file + '\n';
   mtlOutput += 'map_Kd ' + file + '\n';
            
  }
          
 }

 return {
  obj: output,
  mtl: mtlOutput
 }

 }

};
Brakebein
  • 2,197
  • 1
  • 16
  • 21
  • wow! so very cool! i will let you know how it goes when i test this – user151496 Jan 29 '16 at 08:35
  • This code references a global id variable that doesn't exist on this line `materials[id] = material;` that crashes the program – Andy Ray Jul 07 '16 at 19:28
  • Thanks. The line should be `materials[material.id] = material;`. – Brakebein Jul 12 '16 at 22:12
  • 1
    just to let you know, the export went nice and there were only little minor bugs. i had to make some custom adjustments though, as i was exporting not a textured model, but a plain color one. neverthles, this was a *huge* help as a starting point – user151496 Oct 08 '16 at 15:39
  • This seems to be exactly what I'm looking for, but how does it handle textures? I can't get it working with that. – arpo Feb 13 '17 at 15:43
  • How do you save the output of this? – shinzou Apr 10 '17 at 06:42
  • If you want to store it on your local machine out of the browser, you could use [Filesaver.js](https://github.com/eligrey/FileSaver.js/). – Brakebein Apr 10 '17 at 08:00
0

here's how you can save using filesaver and JSZip

var oexporter = new THREE.OBJExporter();
var result = oexporter.parse(scene);
zip.file('mymodel.obj', result.obj);
zip.file('objmaterial.mtl', result.mtl);
var zz=zip.generate({ type: 'blob' });
saveAs(zz, 'mymodel.zip');

My issue is, however, that 3D models I loaded in the scene from files are not saved.Only stuff that I created with three.js functions appears when I load the model with 3DBuilder.