|
@@ -4,7 +4,7 @@
|
|
|
How to use this converter
|
|
|
-------------------------
|
|
|
|
|
|
-python convert_obj_three.py -i infile.obj -o outfile.js [-m morphfiles*.obj] [-a center|centerxz|top|bottom|none] [-s smooth|flat] [-t ascii|binary] [-d invert|normal]
|
|
|
+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]
|
|
|
|
|
|
Notes:
|
|
|
|
|
@@ -148,7 +148,7 @@ TEMPLATE_FILE_ASCII = u"""\
|
|
|
// Converted from: %(fname)s
|
|
|
// vertices: %(nvertex)d
|
|
|
// faces: %(nface)d
|
|
|
-// normals: %(nnormal)d
|
|
|
+// normals: %(nnormal)d
|
|
|
// uvs: %(nuv)d
|
|
|
// materials: %(nmaterial)d
|
|
|
//
|
|
@@ -163,9 +163,11 @@ var model = {
|
|
|
"materials": [%(materials)s],
|
|
|
|
|
|
"vertices": [%(vertices)s],
|
|
|
-
|
|
|
+
|
|
|
"morphTargets": [%(morphTargets)s],
|
|
|
|
|
|
+ "morphColors": [%(morphColors)s],
|
|
|
+
|
|
|
"normals": [%(normals)s],
|
|
|
|
|
|
"uvs": [[%(uvs)s]],
|
|
@@ -204,8 +206,10 @@ TEMPLATE_VERTEX = "%f,%f,%f"
|
|
|
|
|
|
TEMPLATE_N = "%f,%f,%f"
|
|
|
TEMPLATE_UV = "%f,%f"
|
|
|
+TEMPLATE_COLOR = "%.3f,%.3f,%.3f"
|
|
|
|
|
|
-TEMPLATE_MORPH = '\t{ "name": "%s", "vertices": [%s] }'
|
|
|
+TEMPLATE_MORPH_VERTICES = '\t{ "name": "%s", "vertices": [%s] }'
|
|
|
+TEMPLATE_MORPH_COLORS = '\t{ "name": "%s", "colors": [%s] }'
|
|
|
|
|
|
# #####################################################
|
|
|
# Utils
|
|
@@ -565,7 +569,7 @@ def parse_obj(fname):
|
|
|
return faces, vertices, uvs, normals, materials, mtllib
|
|
|
|
|
|
# #####################################################
|
|
|
-# Generator
|
|
|
+# Generator - faces
|
|
|
# #####################################################
|
|
|
def setBit(value, position, on):
|
|
|
if on:
|
|
@@ -573,10 +577,7 @@ def setBit(value, position, on):
|
|
|
return (value | mask)
|
|
|
else:
|
|
|
mask = ~(1 << position)
|
|
|
- return (value & mask)
|
|
|
-
|
|
|
-def generate_vertex(v):
|
|
|
- return TEMPLATE_VERTEX % (v[0], v[1], v[2])
|
|
|
+ return (value & mask)
|
|
|
|
|
|
def generate_face(f):
|
|
|
isTriangle = ( len(f['vertex']) == 3 )
|
|
@@ -640,19 +641,143 @@ def generate_face(f):
|
|
|
|
|
|
return ",".join( map(str, faceData) )
|
|
|
|
|
|
+# #####################################################
|
|
|
+# Generator - chunks
|
|
|
+# #####################################################
|
|
|
+def generate_vertex(v):
|
|
|
+ return TEMPLATE_VERTEX % (v[0], v[1], v[2])
|
|
|
+
|
|
|
def generate_normal(n):
|
|
|
return TEMPLATE_N % (n[0], n[1], n[2])
|
|
|
|
|
|
def generate_uv(uv):
|
|
|
return TEMPLATE_UV % (uv[0], 1.0 - uv[1])
|
|
|
|
|
|
+def generate_color_rgb(c):
|
|
|
+ return TEMPLATE_COLOR % (c[0], c[1], c[2])
|
|
|
+
|
|
|
# #####################################################
|
|
|
# Morphs
|
|
|
# #####################################################
|
|
|
-def generate_morph(name, vertices):
|
|
|
+def generate_morph_vertex(name, vertices):
|
|
|
vertex_string = ",".join(generate_vertex(v) for v in vertices)
|
|
|
- return TEMPLATE_MORPH % (name, vertex_string)
|
|
|
+ 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_face_colors(faces, materials, mtlfilename, basename):
|
|
|
+
|
|
|
+ # extract diffuse colors from MTL materials
|
|
|
+
|
|
|
+ if not materials:
|
|
|
+ materials = { 'default': 0 }
|
|
|
+
|
|
|
+ mtl = create_materials(materials, mtlfilename, basename)
|
|
|
+
|
|
|
+ mtl_array = []
|
|
|
+ for m in mtl:
|
|
|
+ if m in materials:
|
|
|
+ index = materials[m]
|
|
|
+ color = mtl[m].get("colorDiffuse", [1,0,0])
|
|
|
+ mtl_array.append([index, color])
|
|
|
+
|
|
|
+ mtl_array.sort()
|
|
|
+
|
|
|
+ # assign colors to faces
|
|
|
+
|
|
|
+ faceColors = []
|
|
|
+
|
|
|
+ for face in faces:
|
|
|
+ material_index = face['material']
|
|
|
+ faceColors.append(mtl_array[material_index][1])
|
|
|
+
|
|
|
+ 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()
|
|
|
+
|
|
|
+ for path in matches:
|
|
|
+
|
|
|
+ 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 = []
|
|
|
+
|
|
|
+ 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:
|
|
|
+
|
|
|
+ morphFaceColors = extract_face_colors(morphFaces, morphMaterials, morphMtllib, normpath)
|
|
|
+ morphColorData.append((get_name(name), morphFaceColors))
|
|
|
+ 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
|
|
|
+
|
|
|
# #####################################################
|
|
|
# Materials
|
|
|
# #####################################################
|
|
@@ -717,34 +842,49 @@ def generate_mtl(materials):
|
|
|
}
|
|
|
return mtl
|
|
|
|
|
|
-def generate_materials_string(materials, mtllib):
|
|
|
+def generate_materials_string(materials, mtlfilename, basename):
|
|
|
"""Generate final materials string.
|
|
|
"""
|
|
|
|
|
|
- random.seed(42) # to get well defined color order for materials
|
|
|
+ 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
|
|
|
- if not materials:
|
|
|
- materials = { 'default':0 }
|
|
|
+
|
|
|
mtl = generate_mtl(materials)
|
|
|
|
|
|
- if mtllib:
|
|
|
+ if mtlfilename:
|
|
|
+
|
|
|
# create full pathname for MTL (included from OBJ)
|
|
|
- path = os.path.dirname(infile)
|
|
|
- fname = os.path.join(path, mtllib)
|
|
|
+
|
|
|
+ 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 generate_materials(mtl, materials)
|
|
|
-
|
|
|
+
|
|
|
+ return mtl
|
|
|
+
|
|
|
# #####################################################
|
|
|
# Faces
|
|
|
# #####################################################
|
|
@@ -809,7 +949,7 @@ def sort_faces(faces):
|
|
|
# #####################################################
|
|
|
# API - ASCII converter
|
|
|
# #####################################################
|
|
|
-def convert_ascii(infile, morphfiles, outfile):
|
|
|
+def convert_ascii(infile, morphfiles, colorfiles, outfile):
|
|
|
"""Convert infile.obj to outfile.js
|
|
|
|
|
|
Here is where everything happens. If you need to automate conversions,
|
|
@@ -819,9 +959,16 @@ def convert_ascii(infile, morphfiles, outfile):
|
|
|
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":
|
|
@@ -831,48 +978,24 @@ def convert_ascii(infile, morphfiles, outfile):
|
|
|
elif ALIGN == "top":
|
|
|
top(vertices)
|
|
|
|
|
|
- n_vertices = len(vertices)
|
|
|
-
|
|
|
+ # generate normals string
|
|
|
+
|
|
|
nnormal = 0
|
|
|
normals_string = ""
|
|
|
if SHADING == "smooth":
|
|
|
normals_string = ",".join(generate_normal(n) for n in normals)
|
|
|
nnormal = len(normals)
|
|
|
|
|
|
- skipOriginalMorph = False
|
|
|
- norminfile = os.path.normpath(infile)
|
|
|
+ # extract morph vertices
|
|
|
|
|
|
- morphData = []
|
|
|
- for mfilepattern in morphfiles.split():
|
|
|
- matches = glob.glob(mfilepattern)
|
|
|
- matches.sort()
|
|
|
- for path in matches:
|
|
|
- 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)
|
|
|
-
|
|
|
- morphData.append((get_name(name), morphVertices ))
|
|
|
- print "adding [%s] with %d vertices" % (name, len(morphVertices))
|
|
|
+ morphTargets = generate_morph_targets(morphfiles, n_vertices, infile)
|
|
|
|
|
|
- morphTargets = ""
|
|
|
- if len(morphData):
|
|
|
- morphTargets = "\n%s\n\t" % ",\n".join(generate_morph(name, vertices) for name, vertices in morphData)
|
|
|
+ # extract morph colors
|
|
|
+
|
|
|
+ morphColors = generate_morph_colors(colorfiles, n_vertices, n_faces)
|
|
|
|
|
|
+ # generate ascii model string
|
|
|
+
|
|
|
text = TEMPLATE_FILE_ASCII % {
|
|
|
"name" : get_name(outfile),
|
|
|
"fname" : infile,
|
|
@@ -882,13 +1005,14 @@ def convert_ascii(infile, morphfiles, outfile):
|
|
|
"nnormal" : nnormal,
|
|
|
"nmaterial" : len(materials),
|
|
|
|
|
|
- "materials" : generate_materials_string(materials, mtllib),
|
|
|
+ "materials" : generate_materials_string(materials, mtllib, infile),
|
|
|
|
|
|
"normals" : normals_string,
|
|
|
"uvs" : ",".join(generate_uv(uv) for uv in uvs),
|
|
|
"vertices" : ",".join(generate_vertex(v) for v in vertices),
|
|
|
|
|
|
"morphTargets" : morphTargets,
|
|
|
+ "morphColors" : morphColors,
|
|
|
|
|
|
"faces" : ",".join(generate_face(f) for f in faces)
|
|
|
}
|
|
@@ -898,7 +1022,7 @@ def convert_ascii(infile, morphfiles, outfile):
|
|
|
out.close()
|
|
|
|
|
|
print "%d vertices, %d faces, %d materials" % (len(vertices), len(faces), len(materials))
|
|
|
-
|
|
|
+
|
|
|
|
|
|
# #############################################################################
|
|
|
# API - Binary converter
|
|
@@ -933,7 +1057,7 @@ def convert_binary(infile, outfile):
|
|
|
text = TEMPLATE_FILE_BIN % {
|
|
|
"name" : get_name(outfile),
|
|
|
|
|
|
- "materials" : generate_materials_string(materials, mtllib),
|
|
|
+ "materials" : generate_materials_string(materials, mtllib, infile),
|
|
|
"buffers" : binfile,
|
|
|
|
|
|
"fname" : infile,
|
|
@@ -1216,7 +1340,7 @@ def convert_binary(infile, outfile):
|
|
|
# Helpers
|
|
|
# #############################################################################
|
|
|
def usage():
|
|
|
- print "Usage: %s -i filename.obj -o filename.js [-m morphfiles*.obj] [-a center|top|bottom] [-s flat|smooth] [-t binary|ascii] [-d invert|normal]" % os.path.basename(sys.argv[0])
|
|
|
+ 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
|
|
@@ -1225,7 +1349,7 @@ if __name__ == "__main__":
|
|
|
|
|
|
# get parameters from the command line
|
|
|
try:
|
|
|
- opts, args = getopt.getopt(sys.argv[1:], "hi:m:o:a:s:t:d:", ["help", "input=", "morphs=", "output=", "align=", "shading=", "type=", "dissolve="])
|
|
|
+ opts, args = getopt.getopt(sys.argv[1:], "hi:m:c:o:a:s:t:d:", ["help", "input=", "morphs=", "colors=", "output=", "align=", "shading=", "type=", "dissolve="])
|
|
|
|
|
|
except getopt.GetoptError:
|
|
|
usage()
|
|
@@ -1233,6 +1357,7 @@ if __name__ == "__main__":
|
|
|
|
|
|
infile = outfile = ""
|
|
|
morphfiles = ""
|
|
|
+ colorfiles = ""
|
|
|
|
|
|
for o, a in opts:
|
|
|
if o in ("-h", "--help"):
|
|
@@ -1245,6 +1370,9 @@ if __name__ == "__main__":
|
|
|
elif o in ("-m", "--morphs"):
|
|
|
morphfiles = a
|
|
|
|
|
|
+ elif o in ("-c", "--colors"):
|
|
|
+ colorfiles = a
|
|
|
+
|
|
|
elif o in ("-o", "--output"):
|
|
|
outfile = a
|
|
|
|
|
@@ -1269,11 +1397,15 @@ if __name__ == "__main__":
|
|
|
sys.exit(2)
|
|
|
|
|
|
print "Converting [%s] into [%s] ..." % (infile, outfile)
|
|
|
+
|
|
|
if morphfiles:
|
|
|
- print "Morphs [%s]" % morphfiles
|
|
|
-
|
|
|
+ print "Morphs [%s]" % morphfiles
|
|
|
+
|
|
|
+ if colorfiles:
|
|
|
+ print "Colors [%s]" % colorfiles
|
|
|
+
|
|
|
if TYPE == "ascii":
|
|
|
- convert_ascii(infile, morphfiles, outfile)
|
|
|
+ convert_ascii(infile, morphfiles, colorfiles, outfile)
|
|
|
elif TYPE == "binary":
|
|
|
convert_binary(infile, outfile)
|
|
|
|