Browse Source

Merge pull request #7928 from leroyron/dev

Dev: convert_obj_three_for_python3.py
Mr.doob 9 years ago
parent
commit
162d06cf4a
1 changed files with 1604 additions and 0 deletions
  1. 1604 0
      utils/converters/obj/convert_obj_three_for_python3.py

+ 1604 - 0
utils/converters/obj/convert_obj_three_for_python3.py

@@ -0,0 +1,1604 @@
+"""Convert Wavefront OBJ / MTL files into Three.js (JSON model version, to be used with ascii / binary loader)
+
+-------------------------
+How to use this converter
+-------------------------
+
+python convert_obj_three.py -i infile.obj -o outfile.js [-m "morphfiles*.obj"] [-c "morphcolors*.obj"] [-a center|centerxz|top|bottom|none] [-s smooth|flat] [-t ascii|binary] [-d invert|normal] [-b] [-e]
+
+Notes:
+    - flags
+        -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
+
+    - by default:
+        use smooth shading (if there were vertex normals in the original model)
+        will be in ASCII format
+        no face colors baking
+        no scale and truncate
+        morph frame step = 1 (all files will be processed)
+
+    - binary conversion will create two files:
+        outfile.js  (materials)
+        outfile.bin (binary buffers)
+
+--------------------------------------------------
+How to use generated JS file in your HTML document
+--------------------------------------------------
+
+    <script type="text/javascript" src="Three.js"></script>
+
+    ...
+
+    <script type="text/javascript">
+        ...
+
+        // load ascii model
+
+        var jsonLoader = new THREE.JSONLoader();
+        jsonLoader.load( "Model_ascii.js", createScene );
+
+        // load binary model
+
+        var binLoader = new THREE.BinaryLoader();
+        binLoader.load( "Model_bin.js", createScene );
+
+        function createScene( geometry, materials ) {
+
+            var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial( materials ) );
+
+        }
+
+        ...
+    </script>
+
+-------------------------------------
+Parsers based on formats descriptions
+-------------------------------------
+
+    http://en.wikipedia.org/wiki/Obj
+    http://en.wikipedia.org/wiki/Material_Template_Library
+
+-------------------
+Current limitations
+-------------------
+
+    - for the moment, only diffuse color and texture are used
+      (will need to extend shaders / renderers / materials in Three)
+
+    - texture coordinates can be wrong in canvas renderer
+      (there is crude normalization, but it doesn't
+       work for all cases)
+
+    - smoothing can be turned on/off only for the whole mesh
+
+----------------------------------------------
+How to get proper OBJ + MTL files with Blender
+----------------------------------------------
+
+    0. Remove default cube (press DEL and ENTER)
+
+    1. Import / create model
+
+    2. Select all meshes (Select -> Select All by Type -> Mesh)
+
+    3. Export to OBJ (File -> Export -> Wavefront .obj)
+        - enable following options in exporter
+            Material Groups
+            Rotate X90
+            Apply Modifiers
+            High Quality Normals
+            Copy Images
+            Selection Only
+            Objects as OBJ Objects
+            UVs
+            Normals
+            Materials
+
+        - select empty folder
+        - give your exported file name with "obj" extension
+        - click on "Export OBJ" button
+
+    4. Your model is now all files in this folder (OBJ, MTL, number of images)
+        - this converter assumes all files staying in the same folder,
+          (OBJ / MTL files use relative paths)
+
+        - for WebGL, textures must be power of 2 sized
+
+------
+Author
+------
+AlteredQualia http://alteredqualia.com
+
+"""
+
+import fileinput
+import operator
+import random
+import os.path
+import getopt
+import sys
+import struct
+import math
+import glob
+
+# #####################################################
+# Configuration
+# #####################################################
+ALIGN = "none"        	# center centerxz bottom top none
+SHADING = "smooth"      # smooth flat
+TYPE = "ascii"          # ascii binary
+
+TRUNCATE = False
+SCALE = 1.0
+
+FRAMESTEP = 1
+
+BAKE_COLORS = False
+
+# default colors for debugging (each material gets one distinct color):
+# white, red, green, blue, yellow, cyan, magenta
+COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
+
+# #####################################################
+# Templates
+# #####################################################
+TEMPLATE_FILE_ASCII = u"""\
+{
+
+    "metadata" :
+    {
+        "formatVersion" : 3.1,
+        "sourceFile"    : "%(fname)s",
+        "generatedBy"   : "OBJConverter",
+        "vertices"      : %(nvertex)d,
+        "faces"         : %(nface)d,
+        "normals"       : %(nnormal)d,
+        "colors"        : %(ncolor)d,
+        "uvs"           : %(nuv)d,
+        "materials"     : %(nmaterial)d
+    },
+
+    "scale" : %(scale)f,
+
+    "materials": [%(materials)s],
+
+    "vertices": [%(vertices)s],
+
+    "morphTargets": [%(morphTargets)s],
+
+    "morphColors": [%(morphColors)s],
+
+    "normals": [%(normals)s],
+
+    "colors": [%(colors)s],
+
+    "uvs": [[%(uvs)s]],
+
+    "faces": [%(faces)s]
+
+}
+"""
+
+TEMPLATE_FILE_BIN = u"""\
+{
+
+    "metadata" :
+    {
+        "formatVersion" : 3.1,
+        "sourceFile"    : "%(fname)s",
+        "generatedBy"   : "OBJConverter",
+        "vertices"      : %(nvertex)d,
+        "faces"         : %(nface)d,
+        "normals"       : %(nnormal)d,
+        "uvs"           : %(nuv)d,
+        "materials"     : %(nmaterial)d
+    },
+
+    "materials": [%(materials)s],
+
+    "buffers": "%(buffers)s"
+
+}
+"""
+
+TEMPLATE_VERTEX = "%f,%f,%f"
+TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
+
+TEMPLATE_N = "%.5g,%.5g,%.5g"
+TEMPLATE_UV = "%.5g,%.5g"
+TEMPLATE_COLOR = "%.3g,%.3g,%.3g"
+TEMPLATE_COLOR_DEC = "%d"
+
+TEMPLATE_MORPH_VERTICES = '\t{ "name": "%s", "vertices": [%s] }'
+TEMPLATE_MORPH_COLORS   = '\t{ "name": "%s", "colors": [%s] }'
+
+# #####################################################
+# Utils
+# #####################################################
+def file_exists(filename):
+    """Return true if file exists and is accessible for reading.
+
+    Should be safer than just testing for existence due to links and
+    permissions magic on Unix filesystems.
+
+    @rtype: boolean
+    """
+
+    try:
+        f = open(filename, 'r')
+        f.close()
+        return True
+    except IOError:
+        return False
+
+
+def get_name(fname):
+    """Create model name based of filename ("path/fname.js" -> "fname").
+    """
+
+    return os.path.splitext(os.path.basename(fname))[0]
+
+def bbox(vertices):
+    """Compute bounding box of vertex array.
+    """
+
+    if len(vertices)>0:
+        minx = maxx = vertices[0][0]
+        miny = maxy = vertices[0][1]
+        minz = maxz = vertices[0][2]
+
+        for v in vertices[1:]:
+            if v[0]<minx:
+                minx = v[0]
+            elif v[0]>maxx:
+                maxx = v[0]
+
+            if v[1]<miny:
+                miny = v[1]
+            elif v[1]>maxy:
+                maxy = v[1]
+
+            if v[2]<minz:
+                minz = v[2]
+            elif v[2]>maxz:
+                maxz = v[2]
+
+        return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
+
+    else:
+        return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
+
+def translate(vertices, t):
+    """Translate array of vertices by vector t.
+    """
+
+    for i in xrange(len(vertices)):
+        vertices[i][0] += t[0]
+        vertices[i][1] += t[1]
+        vertices[i][2] += t[2]
+
+def center(vertices):
+    """Center model (middle of bounding box).
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def top(vertices):
+    """Align top of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][1]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def bottom(vertices):
+    """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def centerxz(vertices):
+    """Center model around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = 0
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def normalize(v):
+    """Normalize 3d vector"""
+
+    l = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
+    if l:
+        v[0] /= l
+        v[1] /= l
+        v[2] /= l
+
+def veckey3(v):
+    return round(v[0], 6), round(v[1], 6), round(v[2], 6)
+
+# #####################################################
+# MTL parser
+# #####################################################
+def texture_relative_path(fullpath):
+    texture_file = os.path.basename(fullpath.replace("\\", "/"))
+    return texture_file
+
+def parse_mtl(fname):
+    """Parse MTL file.
+    """
+
+    materials = {}
+
+    previous_line = ""
+    for line in fileinput.input(fname):
+        line = previous_line + line
+        if line[-2:-1] == '\\':
+            previous_line = line[:-2]
+            continue
+        previous_line = ""
+
+        # Only split once initially for single-parameter tags that might have additional spaces in
+        # their values (i.e. "newmtl Material with spaces").
+        chunks = line.split(None, 1)
+        if len(chunks) > 0:
+
+            if len(chunks) > 1:
+                chunks[1] = chunks[1].strip()
+
+            # Material start
+            # newmtl identifier
+            if chunks[0] == "newmtl":
+                if len(chunks) > 1:
+                    identifier = chunks[1]
+                else:
+                    identifier = ""
+                if not identifier in materials:
+                    materials[identifier] = {}
+
+            # Diffuse texture
+            # map_Kd texture_diffuse.jpg
+            if chunks[0] == "map_Kd" and len(chunks) == 2:
+                materials[identifier]["mapDiffuse"] = texture_relative_path(chunks[1])
+
+            # Specular texture
+            # map_Ks texture_specular.jpg
+            if chunks[0] == "map_Ks" and len(chunks) == 2:
+                materials[identifier]["mapSpecular"] = texture_relative_path(chunks[1])
+
+            # Alpha texture
+            # map_d texture_alpha.png
+            if chunks[0] == "map_d" and len(chunks) == 2:
+                materials[identifier]["transparent"] = True
+                materials[identifier]["mapAlpha"] = texture_relative_path(chunks[1])
+
+            # Bump texture
+            # map_bump texture_bump.jpg or bump texture_bump.jpg
+            if (chunks[0] == "map_bump" or chunks[0] == "bump") and len(chunks) == 2:
+                materials[identifier]["mapBump"] = texture_relative_path(chunks[1])
+
+            # Split the remaining parameters.
+            if len(chunks) > 1:
+                chunks = [chunks[0]] + chunks[1].split()
+
+            # Diffuse color
+            # Kd 1.000 1.000 1.000
+            if chunks[0] == "Kd" and len(chunks) == 4:
+                materials[identifier]["colorDiffuse"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Specular color
+            # Ks 1.000 1.000 1.000
+            if chunks[0] == "Ks" and len(chunks) == 4:
+                materials[identifier]["colorSpecular"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Specular coefficient
+            # Ns 154.000
+            if chunks[0] == "Ns" and len(chunks) == 2:
+                materials[identifier]["specularCoef"] = float(chunks[1])
+
+            # Dissolves
+            # d 0.9
+            if chunks[0] == "d" and len(chunks) == 2:
+                materials[identifier]["opacity"] = float(chunks[1])
+                if materials[identifier]["opacity"] < 1.0:
+                    materials[identifier]["transparent"] = True
+
+            # Transparency
+            # Tr 0.1
+            if chunks[0] == "Tr" and len(chunks) == 2:
+                materials[identifier]["opacity"] = 1.0 - float(chunks[1])
+                if materials[identifier]["opacity"] < 1.0:
+                    materials[identifier]["transparent"] = True
+
+            # Optical density
+            # Ni 1.0
+            if chunks[0] == "Ni" and len(chunks) == 2:
+                materials[identifier]["opticalDensity"] = float(chunks[1])
+
+            # Illumination
+            # illum 2
+            #
+            # 0. Color on and Ambient off
+            # 1. Color on and Ambient on
+            # 2. Highlight on
+            # 3. Reflection on and Ray trace on
+            # 4. Transparency: Glass on, Reflection: Ray trace on
+            # 5. Reflection: Fresnel on and Ray trace on
+            # 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
+            # 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
+            # 8. Reflection on and Ray trace off
+            # 9. Transparency: Glass on, Reflection: Ray trace off
+            # 10. Casts shadows onto invisible surfaces
+            if chunks[0] == "illum" and len(chunks) == 2:
+                materials[identifier]["illumination"] = int(chunks[1])
+
+    return materials
+
+# #####################################################
+# OBJ parser
+# #####################################################
+def parse_vertex(text):
+    """Parse text chunk specifying single vertex.
+
+    Possible formats:
+        vertex index
+        vertex index / texture index
+        vertex index / texture index / normal index
+        vertex index / / normal index
+    """
+
+    v = 0
+    t = 0
+    n = 0
+
+    chunks = text.split("/")
+
+    v = int(chunks[0])
+    if len(chunks) > 1:
+        if chunks[1]:
+            t = int(chunks[1])
+    if len(chunks) > 2:
+        if chunks[2]:
+            n = int(chunks[2])
+
+    return { 'v':v, 't':t, 'n':n }
+
+def parse_obj(fname):
+    """Parse OBJ file.
+    """
+
+    vertices = []
+    normals = []
+    uvs = []
+
+    faces = []
+
+    materials = {}
+    material = ""
+    mcounter = 0
+    mcurrent = 0
+
+    mtllib = ""
+
+    # current face state
+    group = 0
+    object = 0
+    smooth = 0
+
+    previous_line = ""
+    for line in fileinput.input(fname):
+        line = previous_line + line
+        if line[-2:-1] == '\\':
+            previous_line = line[:-2]
+            continue
+        previous_line = ""
+
+        # Only split once initially for single-parameter tags that might have additional spaces in
+        # their values (i.e. "usemtl Material with spaces").
+        chunks = line.split(None, 1)
+        if len(chunks) > 0:
+
+            if len(chunks) > 1:
+                chunks[1] = chunks[1].strip()
+
+            # Group
+            if chunks[0] == "g" and len(chunks) == 2:
+                group = chunks[1]
+
+            # Object
+            if chunks[0] == "o" and len(chunks) == 2:
+                object = chunks[1]
+
+            # Materials definition
+            if chunks[0] == "mtllib" and len(chunks) == 2:
+                mtllib = chunks[1]
+
+            # Material
+            if chunks[0] == "usemtl":
+                if len(chunks) > 1:
+                    material = chunks[1]
+                else:
+                    material = ""
+                if not material in materials:
+                    mcurrent = mcounter
+                    materials[material] = mcounter
+                    mcounter += 1
+                else:
+                    mcurrent = materials[material]
+
+            # Split the remaining parameters.
+            if len(chunks) > 1:
+                chunks = [chunks[0]] + chunks[1].split()
+
+            # Vertices as (x,y,z) coordinates
+            # v 0.123 0.234 0.345
+            if chunks[0] == "v" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                vertices.append([x,y,z])
+
+            # Normals in (x,y,z) form; normals might not be unit
+            # vn 0.707 0.000 0.707
+            if chunks[0] == "vn" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                normals.append([x,y,z])
+
+            # Texture coordinates in (u,v[,w]) coordinates, w is optional
+            # vt 0.500 -1.352 [0.234]
+            if chunks[0] == "vt" and len(chunks) >= 3:
+                u = float(chunks[1])
+                v = float(chunks[2])
+                w = 0
+                if len(chunks)>3:
+                    w = float(chunks[3])
+                uvs.append([u,v,w])
+
+            # Face
+            if chunks[0] == "f" and len(chunks) >= 4:
+                vertex_index = []
+                uv_index = []
+                normal_index = []
+
+
+                # Precompute vert / normal / uv lists
+                # for negative index lookup
+                vertlen = len(vertices) + 1
+                normlen = len(normals) + 1
+                uvlen = len(uvs) + 1
+
+                for v in chunks[1:]:
+                    vertex = parse_vertex(v)
+                    if vertex['v']:
+                        if vertex['v'] < 0:
+                            vertex['v'] += vertlen
+                        vertex_index.append(vertex['v'])
+                    if vertex['t']:
+                        if vertex['t'] < 0:
+                            vertex['t'] += uvlen
+                        uv_index.append(vertex['t'])
+                    if vertex['n']:
+                        if vertex['n'] < 0:
+                            vertex['n'] += normlen
+                        normal_index.append(vertex['n'])
+                faces.append({
+                    'vertex':vertex_index,
+                    'uv':uv_index,
+                    'normal':normal_index,
+
+                    'material':mcurrent,
+                    'group':group,
+                    'object':object,
+                    'smooth':smooth,
+                    })
+
+            # Smooth shading
+            if chunks[0] == "s" and len(chunks) == 2:
+                smooth = chunks[1]
+
+    return faces, vertices, uvs, normals, materials, mtllib
+
+# #####################################################
+# Generator - faces
+# #####################################################
+def setBit(value, position, on):
+    if on:
+        mask = 1 << position
+        return (value | mask)
+    else:
+        mask = ~(1 << position)
+        return (value & mask)
+
+def generate_face(f, fc):
+    isTriangle = ( len(f['vertex']) == 3 )
+
+    if isTriangle:
+        nVertices = 3
+    else:
+        nVertices = 4
+
+    hasMaterial = True # for the moment OBJs without materials get default material
+
+    hasFaceUvs = False # not supported in OBJ
+    hasFaceVertexUvs = ( len(f['uv']) >= nVertices )
+
+    hasFaceNormals = False # don't export any face normals (as they are computed in engine)
+    hasFaceVertexNormals = ( len(f["normal"]) >= nVertices and SHADING == "smooth" )
+
+    hasFaceColors = BAKE_COLORS
+    hasFaceVertexColors = False # not supported in OBJ
+
+    faceType = 0
+    faceType = setBit(faceType, 0, not isTriangle)
+    faceType = setBit(faceType, 1, hasMaterial)
+    faceType = setBit(faceType, 2, hasFaceUvs)
+    faceType = setBit(faceType, 3, hasFaceVertexUvs)
+    faceType = setBit(faceType, 4, hasFaceNormals)
+    faceType = setBit(faceType, 5, hasFaceVertexNormals)
+    faceType = setBit(faceType, 6, hasFaceColors)
+    faceType = setBit(faceType, 7, hasFaceVertexColors)
+
+    faceData = []
+
+    # order is important, must match order in JSONLoader
+
+    # face type
+    # vertex indices
+    # material index
+    # face uvs index
+    # face vertex uvs indices
+    # face normal index
+    # face vertex normals indices
+    # face color index
+    # face vertex colors indices
+
+    faceData.append(faceType)
+
+    # must clamp in case on polygons bigger than quads
+
+    for i in xrange(nVertices):
+        index = f['vertex'][i] - 1
+        faceData.append(index)
+
+    faceData.append( f['material'] )
+
+    if hasFaceVertexUvs:
+        for i in xrange(nVertices):
+            index = f['uv'][i] - 1
+            faceData.append(index)
+
+    if hasFaceVertexNormals:
+        for i in xrange(nVertices):
+            index = f['normal'][i] - 1
+            faceData.append(index)
+
+    if hasFaceColors:
+        index = fc['material']
+        faceData.append(index)
+
+    return ",".join( map(str, faceData) )
+
+# #####################################################
+# Generator - chunks
+# #####################################################
+def hexcolor(c):
+    return ( int(c[0] * 255) << 16  ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
+
+def generate_vertex(v, option_vertices_truncate, scale):
+    if not option_vertices_truncate:
+        return TEMPLATE_VERTEX % (v[0], v[1], v[2])
+    else:
+        return TEMPLATE_VERTEX_TRUNCATE % (scale * v[0], scale * v[1], scale * v[2])
+
+def generate_normal(n):
+    return TEMPLATE_N % (n[0], n[1], n[2])
+
+def generate_uv(uv):
+    return TEMPLATE_UV % (uv[0], uv[1])
+
+def generate_color_rgb(c):
+    return TEMPLATE_COLOR % (c[0], c[1], c[2])
+
+def generate_color_decimal(c):
+    return TEMPLATE_COLOR_DEC % hexcolor(c)
+
+# #####################################################
+# Morphs
+# #####################################################
+def generate_morph_vertex(name, vertices):
+    vertex_string = ",".join(generate_vertex(v, TRUNCATE, SCALE) for v in vertices)
+    return TEMPLATE_MORPH_VERTICES % (name, vertex_string)
+
+def generate_morph_color(name, colors):
+    color_string = ",".join(generate_color_rgb(c) for c in colors)
+    return TEMPLATE_MORPH_COLORS % (name, color_string)
+
+def extract_material_colors(materials, mtlfilename, basename):
+    """Extract diffuse colors from MTL materials
+    """
+
+    if not materials:
+        materials = { 'default': 0 }
+
+    mtl = create_materials(materials, mtlfilename, basename)
+
+    mtlColorArraySrt = []
+    for m in mtl:
+        if m in materials:
+            index = materials[m]
+            color = mtl[m].get("colorDiffuse", [1,0,0])
+            mtlColorArraySrt.append([index, color])
+
+    mtlColorArraySrt.sort()
+    mtlColorArray = [x[1] for x in mtlColorArraySrt]
+
+    return mtlColorArray
+
+def extract_face_colors(faces, material_colors):
+    """Extract colors from materials and assign them to faces
+    """
+
+    faceColors = []
+
+    for face in faces:
+        material_index = face['material']
+        faceColors.append(material_colors[material_index])
+
+    return faceColors
+
+def generate_morph_targets(morphfiles, n_vertices, infile):
+    skipOriginalMorph = False
+    norminfile = os.path.normpath(infile)
+
+    morphVertexData = []
+
+    for mfilepattern in morphfiles.split():
+
+        matches = glob.glob(mfilepattern)
+        matches.sort()
+
+        indices = range(0, len(matches), FRAMESTEP)
+        for i in indices:
+            path = matches[i]
+
+            normpath = os.path.normpath(path)
+
+            if normpath != norminfile or not skipOriginalMorph:
+
+                name = os.path.basename(normpath)
+
+                morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(normpath)
+
+                n_morph_vertices = len(morphVertices)
+
+                if n_vertices != n_morph_vertices:
+
+                    print("WARNING: skipping morph [%s] with different number of vertices [%d] than the original model [%d]" % (name, n_morph_vertices, n_vertices))
+
+                else:
+
+                    if ALIGN == "center":
+                        center(morphVertices)
+                    elif ALIGN == "centerxz":
+                        centerxz(morphVertices)
+                    elif ALIGN == "bottom":
+                        bottom(morphVertices)
+                    elif ALIGN == "top":
+                        top(morphVertices)
+
+                    morphVertexData.append((get_name(name), morphVertices))
+                    print("adding [%s] with %d vertices" % (name, n_morph_vertices))
+
+    morphTargets = ""
+    if len(morphVertexData):
+        morphTargets = "\n%s\n\t" % ",\n".join(generate_morph_vertex(name, vertices) for name, vertices in morphVertexData)
+
+    return morphTargets
+
+def generate_morph_colors(colorfiles, n_vertices, n_faces):
+    morphColorData = []
+    colorFaces = []
+    materialColors = []
+
+    for mfilepattern in colorfiles.split():
+
+        matches = glob.glob(mfilepattern)
+        matches.sort()
+        for path in matches:
+            normpath = os.path.normpath(path)
+            name = os.path.basename(normpath)
+
+            morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(normpath)
+
+            n_morph_vertices = len(morphVertices)
+            n_morph_faces = len(morphFaces)
+
+            if n_vertices != n_morph_vertices:
+
+                print("WARNING: skipping morph color map [%s] with different number of vertices [%d] than the original model [%d]" % (name, n_morph_vertices, n_vertices))
+
+            elif n_faces != n_morph_faces:
+
+                print("WARNING: skipping morph color map [%s] with different number of faces [%d] than the original model [%d]" % (name, n_morph_faces, n_faces))
+
+            else:
+
+                morphMaterialColors = extract_material_colors(morphMaterials, morphMtllib, normpath)
+                morphFaceColors = extract_face_colors(morphFaces, morphMaterialColors)
+                morphColorData.append((get_name(name), morphFaceColors))
+
+                # take first color map for baking into face colors
+
+                if len(colorFaces) == 0:
+                    colorFaces = morphFaces
+                    materialColors = morphMaterialColors
+
+                print("adding [%s] with %d face colors" % (name, len(morphFaceColors)))
+
+    morphColors = ""
+    if len(morphColorData):
+        morphColors = "\n%s\n\t" % ",\n".join(generate_morph_color(name, colors) for name, colors in morphColorData)
+
+    return morphColors, colorFaces, materialColors
+
+# #####################################################
+# Materials
+# #####################################################
+def generate_color(i):
+    """Generate hex color corresponding to integer.
+
+    Colors should have well defined ordering.
+    First N colors are hardcoded, then colors are random
+    (must seed random number  generator with deterministic value
+    before getting colors).
+    """
+
+    if i < len(COLORS):
+        #return "0x%06x" % COLORS[i]
+        return COLORS[i]
+    else:
+        #return "0x%06x" % int(0xffffff * random.random())
+        return int(0xffffff * random.random())
+
+def value2string(v):
+    if type(v)==str and v[0:2] != "0x":
+        return '"%s"' % v
+    elif type(v) == bool:
+        return str(v).lower()
+    return str(v)
+
+def generate_materials(mtl, materials):
+    """Generate JS array of materials objects
+
+    JS material objects are basically prettified one-to-one
+    mappings of MTL properties in JSON format.
+    """
+
+    mtl_array = []
+    for m in mtl:
+        if m in materials:
+            index = materials[m]
+
+            # add debug information
+            #  materials should be sorted according to how
+            #  they appeared in OBJ file (for the first time)
+            #  this index is identifier used in face definitions
+            mtl[m]['DbgName'] = m
+            mtl[m]['DbgIndex'] = index
+            mtl[m]['DbgColor'] = generate_color(index)
+
+            if BAKE_COLORS:
+                mtl[m]['vertexColors'] = "face"
+
+            mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
+            mtl_string = "\t{\n%s\n\t}" % mtl_raw
+            mtl_array.append([index, mtl_string])
+
+    return ",\n\n".join([m for i,m in sorted(mtl_array)])
+
+def generate_mtl(materials):
+    """Generate dummy materials (if there is no MTL file).
+    """
+
+    mtl = {}
+    for m in materials:
+        index = materials[m]
+        mtl[m] = {
+            'DbgName': m,
+            'DbgIndex': index,
+            'DbgColor': generate_color(index)
+        }
+    return mtl
+
+def generate_materials_string(materials, mtlfilename, basename):
+    """Generate final materials string.
+    """
+
+    if not materials:
+        materials = { 'default': 0 }
+
+    mtl = create_materials(materials, mtlfilename, basename)
+    return generate_materials(mtl, materials)
+
+def create_materials(materials, mtlfilename, basename):
+    """Parse MTL file and create mapping between its materials and OBJ materials.
+       Eventual edge cases are handled here (missing materials, missing MTL file).
+    """
+
+    random.seed(42) # to get well defined color order for debug colors
+
+    # default materials with debug colors for when
+    # there is no specified MTL / MTL loading failed,
+    # or if there were no materials / null materials
+
+    mtl = generate_mtl(materials)
+
+    if mtlfilename:
+
+        # create full pathname for MTL (included from OBJ)
+
+        path = os.path.dirname(basename)
+        fname = os.path.join(path, mtlfilename)
+
+        if file_exists(fname):
+
+            # override default materials with real ones from MTL
+            # (where they exist, otherwise keep defaults)
+
+            mtl.update(parse_mtl(fname))
+
+        else:
+
+            print("Couldn't find [%s]" % fname)
+
+    return mtl
+
+# #####################################################
+# Faces
+# #####################################################
+def is_triangle_flat(f):
+    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and not f['uv']
+
+def is_triangle_flat_uv(f):
+    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==3
+
+def is_triangle_smooth(f):
+    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and not f['uv']
+
+def is_triangle_smooth_uv(f):
+    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and len(f['uv'])==3
+
+def is_quad_flat(f):
+    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and not f['uv']
+
+def is_quad_flat_uv(f):
+    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==4
+
+def is_quad_smooth(f):
+    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and not f['uv']
+
+def is_quad_smooth_uv(f):
+    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and len(f['uv'])==4
+
+def sort_faces(faces):
+    data = {
+    'triangles_flat': [],
+    'triangles_flat_uv': [],
+    'triangles_smooth': [],
+    'triangles_smooth_uv': [],
+
+    'quads_flat': [],
+    'quads_flat_uv': [],
+    'quads_smooth': [],
+    'quads_smooth_uv': []
+    }
+
+    for f in faces:
+        if is_triangle_flat(f):
+            data['triangles_flat'].append(f)
+        elif is_triangle_flat_uv(f):
+            data['triangles_flat_uv'].append(f)
+        elif is_triangle_smooth(f):
+            data['triangles_smooth'].append(f)
+        elif is_triangle_smooth_uv(f):
+            data['triangles_smooth_uv'].append(f)
+
+        elif is_quad_flat(f):
+            data['quads_flat'].append(f)
+        elif is_quad_flat_uv(f):
+            data['quads_flat_uv'].append(f)
+        elif is_quad_smooth(f):
+            data['quads_smooth'].append(f)
+        elif is_quad_smooth_uv(f):
+            data['quads_smooth_uv'].append(f)
+
+    return data
+
+# #####################################################
+# API - ASCII converter
+# #####################################################
+def convert_ascii(infile, morphfiles, colorfiles, outfile):
+    """Convert infile.obj to outfile.js
+
+    Here is where everything happens. If you need to automate conversions,
+    just import this file as Python module and call this method.
+    """
+
+    if not file_exists(infile):
+        print("Couldn't find [%s]" % infile)
+        return
+
+    # parse OBJ / MTL files
+
+    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
+
+    n_vertices = len(vertices)
+    n_faces = len(faces)
+
+    # align model
+
+    if ALIGN == "center":
+        center(vertices)
+    elif ALIGN == "centerxz":
+        centerxz(vertices)
+    elif ALIGN == "bottom":
+        bottom(vertices)
+    elif ALIGN == "top":
+        top(vertices)
+
+    # generate normals string
+
+    nnormal = 0
+    normals_string = ""
+    if SHADING == "smooth":
+        normals_string = ",".join(generate_normal(n) for n in normals)
+        nnormal = len(normals)
+
+    # extract morph vertices
+
+    morphTargets = generate_morph_targets(morphfiles, n_vertices, infile)
+
+    # extract morph colors
+
+    morphColors, colorFaces, materialColors = generate_morph_colors(colorfiles, n_vertices, n_faces)
+
+    # generate colors string
+
+    ncolor = 0
+    colors_string = ""
+
+    if len(colorFaces) < len(faces):
+        colorFaces = faces
+        materialColors = extract_material_colors(materials, mtllib, infile)
+
+    if BAKE_COLORS:
+        colors_string = ",".join(generate_color_decimal(c) for c in materialColors)
+        ncolor = len(materialColors)
+
+    # generate ascii model string
+
+    text = TEMPLATE_FILE_ASCII % {
+    "name"      : get_name(outfile),
+    "fname"     : os.path.basename(infile),
+    "nvertex"   : len(vertices),
+    "nface"     : len(faces),
+    "nuv"       : len(uvs),
+    "nnormal"   : nnormal,
+    "ncolor"    : ncolor,
+    "nmaterial" : len(materials),
+
+    "materials" : generate_materials_string(materials, mtllib, infile),
+
+    "normals"       : normals_string,
+    "colors"        : colors_string,
+    "uvs"           : ",".join(generate_uv(uv) for uv in uvs),
+    "vertices"      : ",".join(generate_vertex(v, TRUNCATE, SCALE) for v in vertices),
+
+    "morphTargets"  : morphTargets,
+    "morphColors"   : morphColors,
+
+    "faces"     : ",".join(generate_face(f, fc) for f, fc in zip(faces, colorFaces)),
+
+    "scale"    : SCALE
+    }
+
+    out = open(outfile, "w")
+    out.write(text)
+    out.close()
+
+    print("%d vertices, %d faces, %d materials" % (len(vertices), len(faces), len(materials)))
+
+
+# #############################################################################
+# API - Binary converter
+# #############################################################################
+def dump_materials_to_buffer(faces, buffer):
+    for f in faces:
+        data = struct.pack('<H',
+                            f['material'])
+        buffer.append(data)
+
+def dump_vertices3_to_buffer(faces, buffer):
+    for f in faces:
+        vi = f['vertex']
+        data = struct.pack('<III',
+                            vi[0]-1, vi[1]-1, vi[2]-1)
+        buffer.append(data)
+
+def dump_vertices4_to_buffer(faces, buffer):
+    for f in faces:
+        vi = f['vertex']
+        data = struct.pack('<IIII',
+                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1)
+        buffer.append(data)
+
+def dump_normals3_to_buffer(faces, buffer):
+    for f in faces:
+        ni = f['normal']
+        data = struct.pack('<III',
+                            ni[0]-1, ni[1]-1, ni[2]-1)
+        buffer.append(data)
+
+def dump_normals4_to_buffer(faces, buffer):
+    for f in faces:
+        ni = f['normal']
+        data = struct.pack('<IIII',
+                            ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1)
+        buffer.append(data)
+
+def dump_uvs3_to_buffer(faces, buffer):
+    for f in faces:
+        ui = f['uv']
+        data = struct.pack('<III',
+                            ui[0]-1, ui[1]-1, ui[2]-1)
+        buffer.append(data)
+
+def dump_uvs4_to_buffer(faces, buffer):
+    for f in faces:
+        ui = f['uv']
+        data = struct.pack('<IIII',
+                            ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
+        buffer.append(data)
+
+def add_padding(buffer, n):
+    if n % 4:
+        for i in range(4 - n % 4):
+            data = struct.pack('<B', 0)
+            buffer.append(data)
+
+def convert_binary(infile, outfile):
+    """Convert infile.obj to outfile.js + outfile.bin
+    """
+
+    if not file_exists(infile):
+        print("Couldn't find [%s]" % infile)
+        return
+
+    binfile = get_name(outfile) + ".bin"
+
+    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
+
+    if ALIGN == "center":
+        center(vertices)
+    elif ALIGN == "centerxz":
+        centerxz(vertices)
+    elif ALIGN == "bottom":
+        bottom(vertices)
+    elif ALIGN == "top":
+        top(vertices)
+
+    sfaces = sort_faces(faces)
+
+    if SHADING == "smooth":
+        nnormals = len(normals)
+    else:
+        nnormals = 0
+
+    # ###################
+    # generate JS file
+    # ###################
+
+    text = TEMPLATE_FILE_BIN % {
+    "name"       : get_name(outfile),
+
+    "materials" : generate_materials_string(materials, mtllib, infile),
+    "buffers"   : binfile,
+
+    "fname"     : os.path.basename(infile),
+    "nvertex"   : len(vertices),
+    "nface"     : len(faces),
+    "nmaterial" : len(materials),
+    "nnormal"   : nnormals,
+    "nuv"       : len(uvs)
+    }
+
+    out = open(outfile, "w")
+    out.write(text)
+    out.close()
+
+    # ###################
+    # generate BIN file
+    # ###################
+
+    buffer = []
+
+    # header
+    # ------
+    header_bytes  = struct.calcsize('<12s')
+    header_bytes += struct.calcsize('<BBBBBBBB')
+    header_bytes += struct.calcsize('<IIIIIIIIIII')
+
+    # signature
+    signature = struct.pack(b'<12s', b'Three.js 003')
+
+    # metadata (all data is little-endian)
+    vertex_coordinate_bytes = 4
+    normal_coordinate_bytes = 1
+    uv_coordinate_bytes = 4
+
+    vertex_index_bytes = 4
+    normal_index_bytes = 4
+    uv_index_bytes = 4
+    material_index_bytes = 2
+
+    # header_bytes            unsigned char   1
+
+    # vertex_coordinate_bytes unsigned char   1
+    # normal_coordinate_bytes unsigned char   1
+    # uv_coordinate_bytes     unsigned char   1
+
+    # vertex_index_bytes      unsigned char   1
+    # normal_index_bytes      unsigned char   1
+    # uv_index_bytes          unsigned char   1
+    # material_index_bytes    unsigned char   1
+    bdata = struct.pack('<BBBBBBBB', header_bytes,
+                               vertex_coordinate_bytes,
+                               normal_coordinate_bytes,
+                               uv_coordinate_bytes,
+                               vertex_index_bytes,
+                               normal_index_bytes,
+                               uv_index_bytes,
+                               material_index_bytes)
+
+    ntri_flat = len(sfaces['triangles_flat'])
+    ntri_smooth = len(sfaces['triangles_smooth'])
+    ntri_flat_uv = len(sfaces['triangles_flat_uv'])
+    ntri_smooth_uv = len(sfaces['triangles_smooth_uv'])
+
+    nquad_flat = len(sfaces['quads_flat'])
+    nquad_smooth = len(sfaces['quads_smooth'])
+    nquad_flat_uv = len(sfaces['quads_flat_uv'])
+    nquad_smooth_uv = len(sfaces['quads_smooth_uv'])
+
+    # nvertices       unsigned int    4
+    # nnormals        unsigned int    4
+    # nuvs            unsigned int    4
+
+    # ntri_flat       unsigned int    4
+    # ntri_smooth     unsigned int    4
+    # ntri_flat_uv    unsigned int    4
+    # ntri_smooth_uv  unsigned int    4
+
+    # nquad_flat      unsigned int    4
+    # nquad_smooth    unsigned int    4
+    # nquad_flat_uv   unsigned int    4
+    # nquad_smooth_uv unsigned int    4
+    ndata = struct.pack('<IIIIIIIIIII', len(vertices),
+                               nnormals,
+                               len(uvs),
+                               ntri_flat,
+                               ntri_smooth,
+                               ntri_flat_uv,
+                               ntri_smooth_uv,
+                               nquad_flat,
+                               nquad_smooth,
+                               nquad_flat_uv,
+                               nquad_smooth_uv)
+    buffer.append(signature)
+    buffer.append(bdata)
+    buffer.append(ndata)
+
+    # 1. vertices
+    # ------------
+    # x float   4
+    # y float   4
+    # z float   4
+    for v in vertices:
+        data = struct.pack('<fff', v[0], v[1], v[2])
+        buffer.append(data)
+
+    # 2. normals
+    # ---------------
+    # x signed char 1
+    # y signed char 1
+    # z signed char 1
+    if SHADING == "smooth":
+        for n in normals:
+            normalize(n)
+            data = struct.pack('<bbb', math.floor(n[0]*127+0.5),
+                                       math.floor(n[1]*127+0.5),
+                                       math.floor(n[2]*127+0.5))
+            buffer.append(data)
+
+        add_padding(buffer, nnormals * 3)
+
+    # 3. uvs
+    # -----------
+    # u float   4
+    # v float   4
+    for uv in uvs:
+        data = struct.pack('<ff', uv[0], uv[1])
+        buffer.append(data)
+
+    # padding
+    #data = struct.pack('<BB', 0, 0)
+    #buffer.append(data)
+
+    # 4. flat triangles (vertices + materials)
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # ------------------
+    # m unsigned short 2
+
+    dump_vertices3_to_buffer(sfaces['triangles_flat'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_flat'], buffer)
+    add_padding(buffer, ntri_flat * 2)
+
+    # 5. smooth triangles (vertices + materials + normals)
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # -------------------
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # -------------------
+    # m  unsigned short 2
+
+    dump_vertices3_to_buffer(sfaces['triangles_smooth'], buffer)
+    dump_normals3_to_buffer(sfaces['triangles_smooth'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_smooth'], buffer)
+    add_padding(buffer, ntri_smooth * 2)
+
+    # 6. flat triangles uv (vertices + materials + uvs)
+    # --------------------
+    # a  unsigned int    4
+    # b  unsigned int    4
+    # c  unsigned int    4
+    # --------------------
+    # ua unsigned int    4
+    # ub unsigned int    4
+    # uc unsigned int    4
+    # --------------------
+    # m  unsigned short  2
+
+    dump_vertices3_to_buffer(sfaces['triangles_flat_uv'], buffer)
+    dump_uvs3_to_buffer(sfaces['triangles_flat_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_flat_uv'], buffer)
+    add_padding(buffer, ntri_flat_uv * 2)
+
+    # 7. smooth triangles uv (vertices + materials + normals + uvs)
+    # ----------------------
+    # a  unsigned int    4
+    # b  unsigned int    4
+    # c  unsigned int    4
+    # --------------------
+    # na unsigned int    4
+    # nb unsigned int    4
+    # nc unsigned int    4
+    # --------------------
+    # ua unsigned int    4
+    # ub unsigned int    4
+    # uc unsigned int    4
+    # --------------------
+    # m  unsigned short  2
+
+    dump_vertices3_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+    dump_normals3_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+    dump_uvs3_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+    add_padding(buffer, ntri_smooth_uv * 2)
+
+    # 8. flat quads (vertices + materials)
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # d unsigned int   4
+    # --------------------
+    # m unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_flat'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_flat'], buffer)
+    add_padding(buffer, nquad_flat * 2)
+
+    # 9. smooth quads (vertices + materials + normals)
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # d  unsigned int   4
+    # --------------------
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # nd unsigned int   4
+    # --------------------
+    # m  unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_smooth'], buffer)
+    dump_normals4_to_buffer(sfaces['quads_smooth'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_smooth'], buffer)
+    add_padding(buffer, nquad_smooth * 2)
+
+    # 10. flat quads uv (vertices + materials + uvs)
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # d unsigned int   4
+    # --------------------
+    # ua unsigned int  4
+    # ub unsigned int  4
+    # uc unsigned int  4
+    # ud unsigned int  4
+    # --------------------
+    # m unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_flat_uv'], buffer)
+    dump_uvs4_to_buffer(sfaces['quads_flat_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_flat_uv'], buffer)
+    add_padding(buffer, nquad_flat_uv * 2)
+
+    # 11. smooth quads uv
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # d  unsigned int   4
+    # --------------------
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # nd unsigned int   4
+    # --------------------
+    # ua unsigned int   4
+    # ub unsigned int   4
+    # uc unsigned int   4
+    # ud unsigned int   4
+    # --------------------
+    # m  unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_smooth_uv'], buffer)
+    dump_normals4_to_buffer(sfaces['quads_smooth_uv'], buffer)
+    dump_uvs4_to_buffer(sfaces['quads_smooth_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_smooth_uv'], buffer)
+    add_padding(buffer, nquad_smooth_uv * 2)
+
+    path = os.path.dirname(outfile)
+    fname = os.path.join(path, binfile)
+
+    out = open(fname, "wb")
+    out.write(b"".join(buffer))
+    out.close()
+
+# #############################################################################
+# Helpers
+# #############################################################################
+def usage():
+    print("Usage: %s -i filename.obj -o filename.js [-m morphfiles*.obj] [-c morphcolors*.obj] [-a center|top|bottom] [-s flat|smooth] [-t binary|ascii] [-d invert|normal]" % os.path.basename(sys.argv[0]))
+
+# #####################################################
+# Main
+# #####################################################
+if __name__ == "__main__":
+
+    # get parameters from the command line
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hbi:m:c:b:o:a:s:t:d:x:f:", ["help", "bakecolors", "input=", "morphs=", "colors=", "output=", "align=", "shading=", "type=", "dissolve=", "truncatescale=", "framestep="])
+
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+
+    infile = outfile = ""
+    morphfiles = ""
+    colorfiles = ""
+
+    for o, a in opts:
+        if o in ("-h", "--help"):
+            usage()
+            sys.exit()
+
+        elif o in ("-i", "--input"):
+            infile = a
+
+        elif o in ("-m", "--morphs"):
+            morphfiles = a
+
+        elif o in ("-c", "--colors"):
+            colorfiles = a
+
+        elif o in ("-o", "--output"):
+            outfile = a
+
+        elif o in ("-a", "--align"):
+            if a in ("top", "bottom", "center", "centerxz", "none"):
+                ALIGN = a
+
+        elif o in ("-s", "--shading"):
+            if a in ("flat", "smooth"):
+                SHADING = a
+
+        elif o in ("-t", "--type"):
+            if a in ("binary", "ascii"):
+                TYPE = a
+
+        elif o in ("-b", "--bakecolors"):
+            BAKE_COLORS = True
+
+        elif o in ("-x", "--truncatescale"):
+            TRUNCATE = True
+            SCALE = float(a)
+
+        elif o in ("-f", "--framestep"):
+            FRAMESTEP = int(a)
+
+    if infile == "" or outfile == "":
+        usage()
+        sys.exit(2)
+
+    print("Converting [%s] into [%s] ..." % (infile, outfile))
+
+    if morphfiles:
+        print("Morphs [%s]" % morphfiles)
+
+    if colorfiles:
+        print("Colors [%s]" % colorfiles)
+
+    if TYPE == "ascii":
+        convert_ascii(infile, morphfiles, colorfiles, outfile)
+    elif TYPE == "binary":
+        convert_binary(infile, outfile)