Converting multiple models from 3DS to THREE.JS

Nicolas Barradeau
Jul 21, 2017 · 6 min read

I worked with WebGL for the past four years and I always try to get the 3D assets’ batch conversion sorted from the very begining of a project. The main reason for starting with it is that the models will always change and it will change until the very last minute. When you’re busy fixing and debugging stuff, it is soothing not to care about your export pipeline.

I first experienced this while working on my Deejo in 2014.

my Deejo, online knife configurator.

It’s an online configurator where the user can choose between 3 different models of knives each of which can be made of up to 29 unique pieces.

an exploded view of the knife

The goal of this article is to explain my workflow with multiple 3D objects. I use 3DS MAX and the THREE.js Library. I’ve used 3DS before it was called MAX and THREE.js is almost the defacto library when it comes to WebGL.

Batch conversion of 3DS objects

Along with other excellent scripts, Jos Balcaen wrote a batch exporter from 3DS MAX this is a direct link to the file (and an alternate link just in case ).

You can paste the .MZP file in the 3DS MAX folder, usually: C:\Program Files\Autodesk\3ds Max 2017\scripts\ then run 3DS, go to scripting -> run script , select and run josbalcaen_batch_exporter_importer.mzp then restart 3DS.

Go to Customize > customize User Interface, select the Toolbars tab, choose the “Batch Exporter Importer” category and you should see a “Open Batch Exporter Importer” item. by default it has no icon so you can right-click it > edit macro script:

and replace icon:( … ) with icon:#(“Standard_Modifiers”, 8) which willl assign an icon to the button. Finally, drag the “Open Batch Exporter Importer” item to the main toolbar.

Now we’re all set, you can click the main toolbar’s button. it should look something like this:

Some options are specific to 3DS Max (deleting the turbosmooth) and others can be quite helpful (moving objects to 0,0,0), the nice thing here is that, after selecting a destination folder, you can export either the whole scene or a selection of objects (that’s very handy).

as far as exports settings are concerned, here’s how I configured mine:

3DSmax to THREE.js export settings

Flipping the YZ axis is important to match THREE’s coordintae stystem, as is the ‘faces: triangles’. I usually do not export materials as .mtl files, instead, I rebuild my materials in THREE directly. Also, I round up the vertices values to 3 digits, same for the normals but not the texture coordinates’ values (somehow out of superstition I must admit).

Note that it is possible to export 3D polylines too (‘2d’ shapes and 3D paths) by checking the ‘shapes/lines’ box. All the meshes will be ignored and only the 3D polylines will be exported. This is very helpful if you need to follow a path of some sort.

Once you have exported a series of OBJ files, you can use them directly in THREE by using the THREE.OBJLoader or you can convert them to a THREE.js friendly format.

Batch conversion of OBJ files

for this step, we’ll need to use Python. AlteredQualia wrote a Python Utility that converts OBJ files to a THREE.js frienfly format. The utility offers some handy settings:

python convert_obj_three.py 

Notes:
-i infile.obj input OBJ file
-o outfile.js output JS file
-m "morphfiles*.obj" morph OBJ files (can use wildcards, enclosed in quotes multiple patterns separate by space)
-c "morphcolors*.obj" morph colors OBJ files (can use wildcards, enclosed in quotes multiple patterns separate by space)
-a center|centerxz|top|bottom|none model alignment
-s smooth|flat smooth = export vertex normals, flat = no normals (face normals computed in loader)
-t ascii|binary export ascii or binary format (ascii has more features, binary just supports vertices, faces, normals, uvs and materials)
-b bake material colors into face colors
-x 10.0 scale and truncate
-f 2 morph frame sampling step

I usually prefer not to rely on ‘post-processing’ or ‘transforms’. I tend to leave the edition part to the 3D software and streamline the conversion ; if something must change, it has to change in the 3D software, not in a post process task.

There is a feature I massively use though, the binary format. This will export 2 files ; a .js and its .bin counterpart. the .bin contains a binary version of the model, it’s much lighter than the JSON file and usually covers my needs (vertices, normals, uvs). You’ll need to use the THREE.BinaryLoader instead of the TREE.OBJLoader. So for me a regular call would be :

python convert.py   -i src/model.obj -o dst/model.js -t binary

with src and dst being the source and destination folders respectively. Note that I renamed convert_obj_three.py to convert.py.

I mentioned above that it is possible to export 3D paths. Unfortunately, converting the OBJ files will fail as there are no faces to the object. What I do instead is simply copy the OBJ file with this glorious Python script :

import os.path
import getopt
import sys
import shutil

if __name__ == "__main__":

# get parameters from the command line
try:
opts, args = getopt.getopt(sys.argv[1:], "i:o:h:", ["input=", "output="])

except getopt.GetoptError:
sys.exit(2)

infile = outfile = ""

for o, a in opts:

if o in ("-i", "--input"):
infile = a

elif o in ("-o", "--output"):
outfile = a

print("copying [%s] into [%s] ..." % (infile, outfile))
shutil.copy( infile, outfile )

Then I load the OBJ as a regular TXT file (using a XMLHttpRequest) and parse the responseText separately using this script:

function parseObj( objString ) {
var anchors = [];
var vertices = objString.match( /v\s.*/g );
vertices.map( function( vertex ){
var st = vertex.replace( /v\s+/, '' ).split( /\s/ );
return anchors.push( parseFloat( st[0] ), parseFloat( st[1] ), parseFloat( st[2] ) );
} );
return anchors;
}

this will retrieve the coordinates of the vertices as a series of float triplets. Converting this buffer to an actual spline is quite straight forward:

function getVerticesList( floatArray ) {
var tmp = [];
var i = 0;
while( i < floatArray.length ) {
tmp.push( new THREE.Vector3( floatArray[i++], floatArray[i++], floatArray[i++] ) )
}
return tmp
}
var anchors = parseObj( xhrResponseText );
var spline = new THREE.CatmullRomCurve3(getVerticesList( anchors ));

So, depending on the type of 3D object (Path or Mesh), I will either convert or copy the OBJ file. To do so in one call, I create a .bat file that will perform all the operations in a row. A .bat file is a plain text file which contains the following:

REM this converts a mesh 
python convert.py -i src/model.obj -o dst/model.js -t binary
REM this copies a path
python copy.py -i src/path.obj -o dst/path.obj
REM the 'pause' instruction leaves the console opened to check if everything went well, replace with 'exit' to close when finished
pause

you can add as many conversions as you want and when an asset changes, you simply re-export it from 3DS max and run the .bat file :)

And that was it,
cheers :)

)

Nicolas Barradeau

Written by

graphic coder

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade