1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195 |
- """Convert Wavefront OBJ / MTL files into Three.js (slim models version, to be used with web worker based ascii / binary loader)
- -------------------------
- How to use this converter
- -------------------------
- python convert_obj_threejs_slim.py -i infile.obj -o outfile.js [-a center|top|bottom] [-s smooth|flat] [-t ascii|binary]
- Notes:
- - by default, model is centered (middle of bounding box goes to 0,0,0),
- uses smooth shading (if there were vertex normals in the original
- model) and is in ASCII format.
-
- - 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">
- ...
-
- var loader = new THREE.Loader();
-
- // load ascii model
- loader.loadAscii( "Model_slim.js", function( geometry ) { createScene( geometry) }, path_to_textures );
- // load binary model
- loader.loadBinary( "Model_bin.js", function( geometry ) { createScene( geometry) }, path_to_textures );
- function createScene( geometry ) {
-
- var normalizeUVsFlag = 1; // set to 1 if canvas render has missing materials
- var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial(), normalizeUVsFlag );
-
- }
-
- ...
- </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)
-
- - models can have more than 65,536 vertices,
- but in most cases it will not work well with browsers,
- which currently seem to have troubles with handling
- large JS files
-
- - 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
- Edges
-
- - 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
- [*] If OBJ export fails (Blender 2.54 beta), patch your Blender installation
- following instructions here:
-
- http://www.blendernation.com/2010/09/12/blender-2-54-beta-released/
-
- ------
- Author
- ------
- AlteredQualia http://alteredqualia.com
- """
- import fileinput
- import operator
- import random
- import os.path
- import getopt
- import sys
- import struct
- import math
- # #####################################################
- # Configuration
- # #####################################################
- ALIGN = "center" # center bottom top none
- SHADING = "smooth" # smooth flat
- TYPE = "ascii" # ascii binary
- # 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"""\
- // Converted from: %(fname)s
- // vertices: %(nvertex)d
- // faces: %(nface)d
- // materials: %(nmaterial)d
- //
- // Generated with OBJ -> Three.js converter
- // http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_threejs_slim.py
- var model = {
- 'materials': [%(materials)s],
- 'normals': [%(normals)s],
- 'vertices': [%(vertices)s],
- 'uvs': [%(uvs)s],
- 'triangles': [%(triangles)s],
- 'triangles_n': [%(triangles_n)s],
- 'triangles_uv': [%(triangles_uv)s],
- 'triangles_n_uv': [%(triangles_n_uv)s],
- 'quads': [%(quads)s],
- 'quads_n': [%(quads_n)s],
- 'quads_uv': [%(quads_uv)s],
- 'quads_n_uv': [%(quads_n_uv)s],
- 'end': (new Date).getTime()
- }
-
- postMessage( model );
- """
- TEMPLATE_FILE_BIN = u"""\
- // Converted from: %(fname)s
- // vertices: %(nvertex)d
- // faces: %(nface)d
- // materials: %(nmaterial)d
- //
- // Generated with OBJ -> Three.js converter
- // http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_threejs_slim.py
- var model = {
- 'materials': [%(materials)s],
- 'buffers': '%(buffers)s',
- 'end': (new Date).getTime()
- }
-
- postMessage( model );
- """
- TEMPLATE_VERTEX = "%f,%f,%f"
- TEMPLATE_UV_TRI = "%f,%f,%f,%f,%f,%f"
- TEMPLATE_UV_QUAD = "%f,%f,%f,%f,%f,%f,%f,%f"
- TEMPLATE_TRI = "%d,%d,%d,%d"
- TEMPLATE_QUAD = "%d,%d,%d,%d,%d"
- TEMPLATE_TRI_UV = "%d,%d,%d,%d,%d,%d,%d"
- TEMPLATE_QUAD_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d"
- TEMPLATE_TRI_N = "%d,%d,%d,%d,%d,%d,%d"
- TEMPLATE_QUAD_N = "%d,%d,%d,%d,%d,%d,%d,%d,%d"
- TEMPLATE_TRI_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"
- TEMPLATE_QUAD_N_UV = "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d"
- TEMPLATE_N = "%f,%f,%f"
- TEMPLATE_UV = "%f,%f"
- # #####################################################
- # 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.basename(fname).split(".")[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 normalize(v):
- """Normalize 3d vector"""
-
- l = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
- v[0] /= l
- v[1] /= l
- v[2] /= l
- # #####################################################
- # MTL parser
- # #####################################################
- def parse_mtl(fname):
- """Parse MTL file.
- """
-
- materials = {}
-
- for line in fileinput.input(fname):
- chunks = line.split()
- if len(chunks) > 0:
-
- # Material start
- # newmtl identifier
- if chunks[0] == "newmtl" and len(chunks) == 2:
- identifier = chunks[1]
- if not identifier in materials:
- materials[identifier] = {}
- # Diffuse color
- # Kd 1.000 1.000 1.000
- if chunks[0] == "Kd" and len(chunks) == 4:
- materials[identifier]["col_diffuse"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
- # Ambient color
- # Ka 1.000 1.000 1.000
- if chunks[0] == "Ka" and len(chunks) == 4:
- materials[identifier]["col_ambient"] = [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]["col_specular"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
- # Specular coefficient
- # Ns 154.000
- if chunks[0] == "Ns" and len(chunks) == 2:
- materials[identifier]["specular_coef"] = float(chunks[1])
- # Transparency
- # Tr 0.9 or d 0.9
- if (chunks[0] == "Tr" or chunks[0] == "d") and len(chunks) == 2:
- materials[identifier]["transparency"] = float(chunks[1])
- # Optical density
- # Ni 1.0
- if chunks[0] == "Ni" and len(chunks) == 2:
- materials[identifier]["optical_density"] = float(chunks[1])
- # Diffuse texture
- # map_Kd texture_diffuse.jpg
- if chunks[0] == "map_Kd" and len(chunks) == 2:
- materials[identifier]["map_diffuse"] = chunks[1]
- # Ambient texture
- # map_Ka texture_ambient.jpg
- if chunks[0] == "map_Ka" and len(chunks) == 2:
- materials[identifier]["map_ambient"] = chunks[1]
- # Specular texture
- # map_Ks texture_specular.jpg
- if chunks[0] == "map_Ks" and len(chunks) == 2:
- materials[identifier]["map_specular"] = chunks[1]
- # Alpha texture
- # map_d texture_alpha.png
- if chunks[0] == "map_d" and len(chunks) == 2:
- materials[identifier]["map_alpha"] = 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]["map_bump"] = 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 = {}
- mcounter = 0
- mcurrent = 0
-
- mtllib = ""
-
- # current face state
- group = 0
- object = 0
- smooth = 0
-
- for line in fileinput.input(fname):
- chunks = line.split()
- if len(chunks) > 0:
-
- # 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 = []
-
- for v in chunks[1:]:
- vertex = parse_vertex(v)
- if vertex['v']:
- vertex_index.append(vertex['v'])
- if vertex['t']:
- uv_index.append(vertex['t'])
- if vertex['n']:
- normal_index.append(vertex['n'])
-
- faces.append({
- 'vertex':vertex_index,
- 'uv':uv_index,
- 'normal':normal_index,
-
- 'material':mcurrent,
- 'group':group,
- 'object':object,
- 'smooth':smooth,
- })
-
- # 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" and len(chunks) == 2:
- material = chunks[1]
- if not material in materials:
- mcurrent = mcounter
- materials[material] = mcounter
- mcounter += 1
- else:
- mcurrent = materials[material]
- # Smooth shading
- if chunks[0] == "s" and len(chunks) == 2:
- smooth = chunks[1]
- return faces, vertices, uvs, normals, materials, mtllib
-
- # #####################################################
- # Generator
- # #####################################################
- def generate_vertex(v):
- return TEMPLATE_VERTEX % (v[0], v[1], v[2])
-
- def generate_triangle(f):
- v = f['vertex']
- return TEMPLATE_TRI % (v[0]-1, v[1]-1, v[2]-1,
- f['material'])
- def generate_triangle_uv(f):
- v = f['vertex']
- uv = f['uv']
- return TEMPLATE_TRI_UV % (v[0]-1, v[1]-1, v[2]-1,
- f['material'],
- uv[0]-1, uv[1]-1, uv[2]-1)
- def generate_triangle_n(f):
- v = f['vertex']
- n = f['normal']
- return TEMPLATE_TRI_N % (v[0]-1, v[1]-1, v[2]-1,
- f['material'],
- n[0]-1, n[1]-1, n[2]-1)
- def generate_triangle_n_uv(f):
- v = f['vertex']
- n = f['normal']
- uv = f['uv']
- return TEMPLATE_TRI_N_UV % (v[0]-1, v[1]-1, v[2]-1,
- f['material'],
- n[0]-1, n[1]-1, n[2]-1,
- uv[0]-1, uv[1]-1, uv[2]-1)
- def generate_quad(f):
- vi = f['vertex']
- return TEMPLATE_QUAD % (vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
- f['material'])
- def generate_quad_uv(f):
- v = f['vertex']
- uv = f['uv']
- return TEMPLATE_QUAD_UV % (v[0]-1, v[1]-1, v[2]-1, v[3]-1,
- f['material'],
- uv[0]-1, uv[1]-1, uv[2]-1, uv[3]-1)
- def generate_quad_n(f):
- v = f['vertex']
- n = f['normal']
- return TEMPLATE_QUAD_N % (v[0]-1, v[1]-1, v[2]-1, v[3]-1,
- f['material'],
- n[0]-1, n[1]-1, n[2]-1, n[3]-1)
- def generate_quad_n_uv(f):
- v = f['vertex']
- n = f['normal']
- uv = f['uv']
- return TEMPLATE_QUAD_N_UV % (v[0]-1, v[1]-1, v[2]-1, v[3]-1,
- f['material'],
- n[0]-1, n[1]-1, n[2]-1, n[3]-1,
- uv[0]-1, uv[1]-1, uv[2]-1, uv[3]-1)
- 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])
- # #####################################################
- # 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]
- else:
- return "0x%06x" % int(0xffffff * random.random())
-
- def value2string(v):
- if type(v)==str and v[0] != "0":
- return '"%s"' % v
- 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:
- 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]['a_dbg_name'] = m
- mtl[m]['a_dbg_index'] = index
- mtl[m]['a_dbg_color'] = generate_color(index)
-
- 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] = {
- 'a_dbg_name': m,
- 'a_dbg_index': index,
- 'a_dbg_color': generate_color(index)
- }
- return mtl
-
- def generate_materials_string(materials, mtllib):
- """Generate final materials string.
- """
- random.seed(42) # to get well defined color order for materials
-
- # 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:
- # create full pathname for MTL (included from OBJ)
- path = os.path.dirname(infile)
- fname = os.path.join(path, mtllib)
-
- 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)
-
- # #####################################################
- # 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, 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
-
- faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
-
- if ALIGN == "center":
- center(vertices)
- elif ALIGN == "bottom":
- bottom(vertices)
- elif ALIGN == "top":
- top(vertices)
-
- normals_string = ""
- if SHADING == "smooth":
- normals_string = ",".join(generate_normal(n) for n in normals)
-
- sfaces = sort_faces(faces)
-
- text = TEMPLATE_FILE_ASCII % {
- "name" : get_name(outfile),
- "vertices" : ",".join(generate_vertex(v) for v in vertices),
- "triangles" : ",".join(generate_triangle(f) for f in sfaces['triangles_flat']),
- "triangles_n" : ",".join(generate_triangle_n(f) for f in sfaces['triangles_smooth']),
- "triangles_uv" : ",".join(generate_triangle_uv(f) for f in sfaces['triangles_flat_uv']),
- "triangles_n_uv": ",".join(generate_triangle_n_uv(f) for f in sfaces['triangles_smooth_uv']),
- "quads" : ",".join(generate_quad(f) for f in sfaces['quads_flat']),
- "quads_n" : ",".join(generate_quad_n(f) for f in sfaces['quads_smooth']),
- "quads_uv" : ",".join(generate_quad_uv(f) for f in sfaces['quads_flat_uv']),
- "quads_n_uv" : ",".join(generate_quad_n_uv(f) for f in sfaces['quads_smooth_uv']),
- "uvs" : ",".join(generate_uv(uv) for uv in uvs),
- "normals" : normals_string,
-
- "materials" : generate_materials_string(materials, mtllib),
-
- "fname" : infile,
- "nvertex" : len(vertices),
- "nface" : len(faces),
- "nmaterial" : len(materials)
- }
-
- 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 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 == "bottom":
- bottom(vertices)
- elif ALIGN == "top":
- top(vertices)
-
- sfaces = sort_faces(faces)
-
- # ###################
- # generate JS file
- # ###################
-
- text = TEMPLATE_FILE_BIN % {
- "name" : get_name(outfile),
-
- "materials" : generate_materials_string(materials, mtllib),
- "buffers" : binfile,
-
- "fname" : infile,
- "nvertex" : len(vertices),
- "nface" : len(faces),
- "nmaterial" : len(materials)
- }
-
- out = open(outfile, "w")
- out.write(text)
- out.close()
-
- # ###################
- # generate BIN file
- # ###################
-
- if SHADING == "smooth":
- nnormals = len(normals)
- else:
- nnormals = 0
-
- buffer = []
- # header
- # ------
- header_bytes = struct.calcsize('<8s')
- header_bytes += struct.calcsize('<BBBBBBBB')
- header_bytes += struct.calcsize('<IIIIIIIIIII')
-
- # signature
- signature = struct.pack('<8s', 'Three.js')
-
- # 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)
- # 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),
- len(sfaces['triangles_flat']),
- len(sfaces['triangles_smooth']),
- len(sfaces['triangles_flat_uv']),
- len(sfaces['triangles_smooth_uv']),
- len(sfaces['quads_flat']),
- len(sfaces['quads_smooth']),
- len(sfaces['quads_flat_uv']),
- len(sfaces['quads_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)
-
- # 3. uvs
- # -----------
- # u float 4
- # v float 4
- for uv in uvs:
- data = struct.pack('<ff', uv[0], 1.0-uv[1])
- buffer.append(data)
-
- # 4. flat triangles
- # ------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # m unsigned short 2
- for f in sfaces['triangles_flat']:
- vi = f['vertex']
- data = struct.pack('<IIIH',
- vi[0]-1, vi[1]-1, vi[2]-1,
- f['material'])
- buffer.append(data)
- # 5. smooth triangles
- # -------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # m unsigned short 2
- # na unsigned int 4
- # nb unsigned int 4
- # nc unsigned int 4
- for f in sfaces['triangles_smooth']:
- vi = f['vertex']
- ni = f['normal']
- data = struct.pack('<IIIHIII',
- vi[0]-1, vi[1]-1, vi[2]-1,
- f['material'],
- ni[0]-1, ni[1]-1, ni[2]-1)
- buffer.append(data)
- # 6. flat triangles uv
- # --------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # m unsigned short 2
- # ua unsigned int 4
- # ub unsigned int 4
- # uc unsigned int 4
- for f in sfaces['triangles_flat_uv']:
- vi = f['vertex']
- ui = f['uv']
- data = struct.pack('<IIIHIII',
- vi[0]-1, vi[1]-1, vi[2]-1,
- f['material'],
- ui[0]-1, ui[1]-1, ui[2]-1)
- buffer.append(data)
- # 7. smooth triangles uv
- # ----------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # m unsigned short 2
- # na unsigned int 4
- # nb unsigned int 4
- # nc unsigned int 4
- # ua unsigned int 4
- # ub unsigned int 4
- # uc unsigned int 4
- for f in sfaces['triangles_smooth_uv']:
- vi = f['vertex']
- ni = f['normal']
- ui = f['uv']
- data = struct.pack('<IIIHIIIIII',
- vi[0]-1, vi[1]-1, vi[2]-1,
- f['material'],
- ni[0]-1, ni[1]-1, ni[2]-1,
- ui[0]-1, ui[1]-1, ui[2]-1)
- buffer.append(data)
- # 8. flat quads
- # ------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # d unsigned int 4
- # m unsigned short 2
- for f in sfaces['quads_flat']:
- vi = f['vertex']
- data = struct.pack('<IIIIH',
- vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
- f['material'])
- buffer.append(data)
-
- # 9. smooth quads
- # -------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # d unsigned int 4
- # m unsigned short 2
- # na unsigned int 4
- # nb unsigned int 4
- # nc unsigned int 4
- # nd unsigned int 4
- for f in sfaces['quads_smooth']:
- vi = f['vertex']
- ni = f['normal']
- data = struct.pack('<IIIIHIIII',
- vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
- f['material'],
- ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1)
- buffer.append(data)
-
- # 10. flat quads uv
- # ------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # d unsigned int 4
- # m unsigned short 2
- # ua unsigned int 4
- # ub unsigned int 4
- # uc unsigned int 4
- # ud unsigned int 4
- for f in sfaces['quads_flat_uv']:
- vi = f['vertex']
- ui = f['uv']
- data = struct.pack('<IIIIHIIII',
- vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
- f['material'],
- ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
- buffer.append(data)
- # 11. smooth quads uv
- # -------------------
- # a unsigned int 4
- # b unsigned int 4
- # c unsigned int 4
- # d unsigned int 4
- # m unsigned short 2
- # 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
- for f in sfaces['quads_smooth_uv']:
- vi = f['vertex']
- ni = f['normal']
- ui = f['uv']
- data = struct.pack('<IIIIHIIIIIIII',
- vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
- f['material'],
- ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1,
- ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
- buffer.append(data)
- path = os.path.dirname(outfile)
- fname = os.path.join(path, binfile)
- out = open(fname, "wb")
- out.write("".join(buffer))
- out.close()
- # #############################################################################
- # Helpers
- # #############################################################################
- def usage():
- print "Usage: %s -i filename.obj -o filename.js [-a center|top|bottom] [-s flat|smooth] [-t binary|ascii]" % os.path.basename(sys.argv[0])
-
- # #####################################################
- # Main
- # #####################################################
- if __name__ == "__main__":
-
- # get parameters from the command line
- try:
- opts, args = getopt.getopt(sys.argv[1:], "hi:o:a:s:t:", ["help", "input=", "output=", "align=", "shading=", "type="])
-
- except getopt.GetoptError:
- usage()
- sys.exit(2)
-
- infile = outfile = ""
-
- for o, a in opts:
- if o in ("-h", "--help"):
- usage()
- sys.exit()
-
- elif o in ("-i", "--input"):
- infile = a
- elif o in ("-o", "--output"):
- outfile = a
- elif o in ("-a", "--align"):
- if a in ("top", "bottom", "center"):
- 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
- if infile == "" or outfile == "":
- usage()
- sys.exit(2)
-
- print "Converting [%s] into [%s] ..." % (infile, outfile)
-
- if TYPE == "ascii":
- convert_ascii(infile, outfile)
- elif TYPE == "binary":
- convert_binary(infile, outfile)
-
|