convert_obj_three.py 44 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468
  1. """Convert Wavefront OBJ / MTL files into Three.js (JSON model version, to be used with web worker based ascii / binary loader)
  2. -------------------------
  3. How to use this converter
  4. -------------------------
  5. 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]
  6. Notes:
  7. - by default:
  8. converted model will be centered (middle of bounding box goes to 0,0,0)
  9. use smooth shading (if there were vertex normals in the original model)
  10. will be in ASCII format
  11. original model is assumed to use non-inverted transparency / dissolve (0.0 fully transparent, 1.0 fully opaque)
  12. - binary conversion will create two files:
  13. outfile.js (materials)
  14. outfile.bin (binary buffers)
  15. --------------------------------------------------
  16. How to use generated JS file in your HTML document
  17. --------------------------------------------------
  18. <script type="text/javascript" src="Three.js"></script>
  19. ...
  20. <script type="text/javascript">
  21. ...
  22. // load ascii model
  23. var jsonLoader = new THREE.JSONLoader();
  24. jsonLoader.load( { model: "Model_ascii.js", callback: function( geometry ) { createScene( geometry) } } );
  25. // load binary model
  26. var binLoader = new THREE.BinaryLoader();
  27. binLoader.load( { model: "Model_bin.js", callback: function( geometry ) { createScene( geometry) } } );
  28. function createScene( geometry ) {
  29. var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial() );
  30. }
  31. ...
  32. </script>
  33. -------------------------------------
  34. Parsers based on formats descriptions
  35. -------------------------------------
  36. http://en.wikipedia.org/wiki/Obj
  37. http://en.wikipedia.org/wiki/Material_Template_Library
  38. -------------------
  39. Current limitations
  40. -------------------
  41. - for the moment, only diffuse color and texture are used
  42. (will need to extend shaders / renderers / materials in Three)
  43. - models can have more than 65,536 vertices,
  44. but in most cases it will not work well with browsers,
  45. which currently seem to have troubles with handling
  46. large JS files
  47. - texture coordinates can be wrong in canvas renderer
  48. (there is crude normalization, but it doesn't
  49. work for all cases)
  50. - smoothing can be turned on/off only for the whole mesh
  51. ----------------------------------------------
  52. How to get proper OBJ + MTL files with Blender
  53. ----------------------------------------------
  54. 0. Remove default cube (press DEL and ENTER)
  55. 1. Import / create model
  56. 2. Select all meshes (Select -> Select All by Type -> Mesh)
  57. 3. Export to OBJ (File -> Export -> Wavefront .obj) [*]
  58. - enable following options in exporter
  59. Material Groups
  60. Rotate X90
  61. Apply Modifiers
  62. High Quality Normals
  63. Copy Images
  64. Selection Only
  65. Objects as OBJ Objects
  66. UVs
  67. Normals
  68. Materials
  69. Edges
  70. - select empty folder
  71. - give your exported file name with "obj" extension
  72. - click on "Export OBJ" button
  73. 4. Your model is now all files in this folder (OBJ, MTL, number of images)
  74. - this converter assumes all files staying in the same folder,
  75. (OBJ / MTL files use relative paths)
  76. - for WebGL, textures must be power of 2 sized
  77. [*] If OBJ export fails (Blender 2.54 beta), patch your Blender installation
  78. following instructions here:
  79. http://www.blendernation.com/2010/09/12/blender-2-54-beta-released/
  80. ------
  81. Author
  82. ------
  83. AlteredQualia http://alteredqualia.com
  84. """
  85. import fileinput
  86. import operator
  87. import random
  88. import os.path
  89. import getopt
  90. import sys
  91. import struct
  92. import math
  93. import glob
  94. # #####################################################
  95. # Configuration
  96. # #####################################################
  97. ALIGN = "center" # center centerxz bottom top none
  98. SHADING = "smooth" # smooth flat
  99. TYPE = "ascii" # ascii binary
  100. TRANSPARENCY = "normal" # normal invert
  101. BAKE_COLORS = False
  102. # default colors for debugging (each material gets one distinct color):
  103. # white, red, green, blue, yellow, cyan, magenta
  104. COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
  105. # #####################################################
  106. # Templates
  107. # #####################################################
  108. TEMPLATE_FILE_ASCII = u"""\
  109. // Converted from: %(fname)s
  110. // vertices: %(nvertex)d
  111. // faces: %(nface)d
  112. // normals: %(nnormal)d
  113. // colors: %(ncolor)d
  114. // uvs: %(nuv)d
  115. // materials: %(nmaterial)d
  116. //
  117. // Generated with OBJ -> Three.js converter
  118. // http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_three.py
  119. var model = {
  120. "version" : 2,
  121. "materials": [%(materials)s],
  122. "vertices": [%(vertices)s],
  123. "morphTargets": [%(morphTargets)s],
  124. "morphColors": [%(morphColors)s],
  125. "normals": [%(normals)s],
  126. "colors": [%(colors)s],
  127. "uvs": [[%(uvs)s]],
  128. "faces": [%(faces)s]
  129. };
  130. postMessage( model );
  131. """
  132. TEMPLATE_FILE_BIN = u"""\
  133. // Converted from: %(fname)s
  134. // vertices: %(nvertex)d
  135. // faces: %(nface)d
  136. // materials: %(nmaterial)d
  137. //
  138. // Generated with OBJ -> Three.js converter
  139. // http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_three.py
  140. var model = {
  141. "version" : 1,
  142. "materials": [%(materials)s],
  143. "buffers": "%(buffers)s"
  144. };
  145. postMessage( model );
  146. """
  147. TEMPLATE_VERTEX = "%f,%f,%f"
  148. TEMPLATE_N = "%f,%f,%f"
  149. TEMPLATE_UV = "%f,%f"
  150. TEMPLATE_COLOR = "%.3f,%.3f,%.3f"
  151. TEMPLATE_COLOR_DEC = "%d"
  152. TEMPLATE_MORPH_VERTICES = '\t{ "name": "%s", "vertices": [%s] }'
  153. TEMPLATE_MORPH_COLORS = '\t{ "name": "%s", "colors": [%s] }'
  154. # #####################################################
  155. # Utils
  156. # #####################################################
  157. def file_exists(filename):
  158. """Return true if file exists and is accessible for reading.
  159. Should be safer than just testing for existence due to links and
  160. permissions magic on Unix filesystems.
  161. @rtype: boolean
  162. """
  163. try:
  164. f = open(filename, 'r')
  165. f.close()
  166. return True
  167. except IOError:
  168. return False
  169. def get_name(fname):
  170. """Create model name based of filename ("path/fname.js" -> "fname").
  171. """
  172. return os.path.splitext(os.path.basename(fname))[0]
  173. def bbox(vertices):
  174. """Compute bounding box of vertex array.
  175. """
  176. if len(vertices)>0:
  177. minx = maxx = vertices[0][0]
  178. miny = maxy = vertices[0][1]
  179. minz = maxz = vertices[0][2]
  180. for v in vertices[1:]:
  181. if v[0]<minx:
  182. minx = v[0]
  183. elif v[0]>maxx:
  184. maxx = v[0]
  185. if v[1]<miny:
  186. miny = v[1]
  187. elif v[1]>maxy:
  188. maxy = v[1]
  189. if v[2]<minz:
  190. minz = v[2]
  191. elif v[2]>maxz:
  192. maxz = v[2]
  193. return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
  194. else:
  195. return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
  196. def translate(vertices, t):
  197. """Translate array of vertices by vector t.
  198. """
  199. for i in xrange(len(vertices)):
  200. vertices[i][0] += t[0]
  201. vertices[i][1] += t[1]
  202. vertices[i][2] += t[2]
  203. def center(vertices):
  204. """Center model (middle of bounding box).
  205. """
  206. bb = bbox(vertices)
  207. cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
  208. cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
  209. cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
  210. translate(vertices, [-cx,-cy,-cz])
  211. def top(vertices):
  212. """Align top of the model with the floor (Y-axis) and center it around X and Z.
  213. """
  214. bb = bbox(vertices)
  215. cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
  216. cy = bb['y'][1]
  217. cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
  218. translate(vertices, [-cx,-cy,-cz])
  219. def bottom(vertices):
  220. """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
  221. """
  222. bb = bbox(vertices)
  223. cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
  224. cy = bb['y'][0]
  225. cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
  226. translate(vertices, [-cx,-cy,-cz])
  227. def centerxz(vertices):
  228. """Center model around X and Z.
  229. """
  230. bb = bbox(vertices)
  231. cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
  232. cy = 0
  233. cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
  234. translate(vertices, [-cx,-cy,-cz])
  235. def normalize(v):
  236. """Normalize 3d vector"""
  237. l = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
  238. if l:
  239. v[0] /= l
  240. v[1] /= l
  241. v[2] /= l
  242. # #####################################################
  243. # MTL parser
  244. # #####################################################
  245. def texture_relative_path(fullpath):
  246. texture_file = os.path.basename(fullpath)
  247. return texture_file
  248. def parse_mtl(fname):
  249. """Parse MTL file.
  250. """
  251. materials = {}
  252. for line in fileinput.input(fname):
  253. chunks = line.split()
  254. if len(chunks) > 0:
  255. # Material start
  256. # newmtl identifier
  257. if chunks[0] == "newmtl" and len(chunks) == 2:
  258. identifier = chunks[1]
  259. if not identifier in materials:
  260. materials[identifier] = {}
  261. # Diffuse color
  262. # Kd 1.000 1.000 1.000
  263. if chunks[0] == "Kd" and len(chunks) == 4:
  264. materials[identifier]["colorDiffuse"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
  265. # Ambient color
  266. # Ka 1.000 1.000 1.000
  267. if chunks[0] == "Ka" and len(chunks) == 4:
  268. materials[identifier]["colorAmbient"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
  269. # Specular color
  270. # Ks 1.000 1.000 1.000
  271. if chunks[0] == "Ks" and len(chunks) == 4:
  272. materials[identifier]["colorSpecular"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
  273. # Specular coefficient
  274. # Ns 154.000
  275. if chunks[0] == "Ns" and len(chunks) == 2:
  276. materials[identifier]["specularCoef"] = float(chunks[1])
  277. # Transparency
  278. # Tr 0.9 or d 0.9
  279. if (chunks[0] == "Tr" or chunks[0] == "d") and len(chunks) == 2:
  280. if TRANSPARENCY == "invert":
  281. materials[identifier]["transparency"] = 1.0 - float(chunks[1])
  282. else:
  283. materials[identifier]["transparency"] = float(chunks[1])
  284. # Optical density
  285. # Ni 1.0
  286. if chunks[0] == "Ni" and len(chunks) == 2:
  287. materials[identifier]["opticalDensity"] = float(chunks[1])
  288. # Diffuse texture
  289. # map_Kd texture_diffuse.jpg
  290. if chunks[0] == "map_Kd" and len(chunks) == 2:
  291. materials[identifier]["mapDiffuse"] = texture_relative_path(chunks[1])
  292. # Ambient texture
  293. # map_Ka texture_ambient.jpg
  294. if chunks[0] == "map_Ka" and len(chunks) == 2:
  295. materials[identifier]["mapAmbient"] = texture_relative_path(chunks[1])
  296. # Specular texture
  297. # map_Ks texture_specular.jpg
  298. if chunks[0] == "map_Ks" and len(chunks) == 2:
  299. materials[identifier]["mapSpecular"] = texture_relative_path(chunks[1])
  300. # Alpha texture
  301. # map_d texture_alpha.png
  302. if chunks[0] == "map_d" and len(chunks) == 2:
  303. materials[identifier]["mapAlpha"] = texture_relative_path(chunks[1])
  304. # Bump texture
  305. # map_bump texture_bump.jpg or bump texture_bump.jpg
  306. if (chunks[0] == "map_bump" or chunks[0] == "bump") and len(chunks) == 2:
  307. materials[identifier]["mapBump"] = texture_relative_path(chunks[1])
  308. # Illumination
  309. # illum 2
  310. #
  311. # 0. Color on and Ambient off
  312. # 1. Color on and Ambient on
  313. # 2. Highlight on
  314. # 3. Reflection on and Ray trace on
  315. # 4. Transparency: Glass on, Reflection: Ray trace on
  316. # 5. Reflection: Fresnel on and Ray trace on
  317. # 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
  318. # 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
  319. # 8. Reflection on and Ray trace off
  320. # 9. Transparency: Glass on, Reflection: Ray trace off
  321. # 10. Casts shadows onto invisible surfaces
  322. if chunks[0] == "illum" and len(chunks) == 2:
  323. materials[identifier]["illumination"] = int(chunks[1])
  324. return materials
  325. # #####################################################
  326. # OBJ parser
  327. # #####################################################
  328. def parse_vertex(text):
  329. """Parse text chunk specifying single vertex.
  330. Possible formats:
  331. vertex index
  332. vertex index / texture index
  333. vertex index / texture index / normal index
  334. vertex index / / normal index
  335. """
  336. v = 0
  337. t = 0
  338. n = 0
  339. chunks = text.split("/")
  340. v = int(chunks[0])
  341. if len(chunks) > 1:
  342. if chunks[1]:
  343. t = int(chunks[1])
  344. if len(chunks) > 2:
  345. if chunks[2]:
  346. n = int(chunks[2])
  347. return { 'v':v, 't':t, 'n':n }
  348. def parse_obj(fname):
  349. """Parse OBJ file.
  350. """
  351. vertices = []
  352. normals = []
  353. uvs = []
  354. faces = []
  355. materials = {}
  356. mcounter = 0
  357. mcurrent = 0
  358. mtllib = ""
  359. # current face state
  360. group = 0
  361. object = 0
  362. smooth = 0
  363. for line in fileinput.input(fname):
  364. chunks = line.split()
  365. if len(chunks) > 0:
  366. # Vertices as (x,y,z) coordinates
  367. # v 0.123 0.234 0.345
  368. if chunks[0] == "v" and len(chunks) == 4:
  369. x = float(chunks[1])
  370. y = float(chunks[2])
  371. z = float(chunks[3])
  372. vertices.append([x,y,z])
  373. # Normals in (x,y,z) form; normals might not be unit
  374. # vn 0.707 0.000 0.707
  375. if chunks[0] == "vn" and len(chunks) == 4:
  376. x = float(chunks[1])
  377. y = float(chunks[2])
  378. z = float(chunks[3])
  379. normals.append([x,y,z])
  380. # Texture coordinates in (u,v[,w]) coordinates, w is optional
  381. # vt 0.500 -1.352 [0.234]
  382. if chunks[0] == "vt" and len(chunks) >= 3:
  383. u = float(chunks[1])
  384. v = float(chunks[2])
  385. w = 0
  386. if len(chunks)>3:
  387. w = float(chunks[3])
  388. uvs.append([u,v,w])
  389. # Face
  390. if chunks[0] == "f" and len(chunks) >= 4:
  391. vertex_index = []
  392. uv_index = []
  393. normal_index = []
  394. for v in chunks[1:]:
  395. vertex = parse_vertex(v)
  396. if vertex['v']:
  397. vertex_index.append(vertex['v'])
  398. if vertex['t']:
  399. uv_index.append(vertex['t'])
  400. if vertex['n']:
  401. normal_index.append(vertex['n'])
  402. faces.append({
  403. 'vertex':vertex_index,
  404. 'uv':uv_index,
  405. 'normal':normal_index,
  406. 'material':mcurrent,
  407. 'group':group,
  408. 'object':object,
  409. 'smooth':smooth,
  410. })
  411. # Group
  412. if chunks[0] == "g" and len(chunks) == 2:
  413. group = chunks[1]
  414. # Object
  415. if chunks[0] == "o" and len(chunks) == 2:
  416. object = chunks[1]
  417. # Materials definition
  418. if chunks[0] == "mtllib" and len(chunks) == 2:
  419. mtllib = chunks[1]
  420. # Material
  421. if chunks[0] == "usemtl" and len(chunks) == 2:
  422. material = chunks[1]
  423. if not material in materials:
  424. mcurrent = mcounter
  425. materials[material] = mcounter
  426. mcounter += 1
  427. else:
  428. mcurrent = materials[material]
  429. # Smooth shading
  430. if chunks[0] == "s" and len(chunks) == 2:
  431. smooth = chunks[1]
  432. return faces, vertices, uvs, normals, materials, mtllib
  433. # #####################################################
  434. # Generator - faces
  435. # #####################################################
  436. def setBit(value, position, on):
  437. if on:
  438. mask = 1 << position
  439. return (value | mask)
  440. else:
  441. mask = ~(1 << position)
  442. return (value & mask)
  443. def generate_face(f, fc):
  444. isTriangle = ( len(f['vertex']) == 3 )
  445. if isTriangle:
  446. nVertices = 3
  447. else:
  448. nVertices = 4
  449. hasMaterial = True # for the moment OBJs without materials get default material
  450. hasFaceUvs = False # not supported in OBJ
  451. hasFaceVertexUvs = ( len(f['uv']) >= nVertices )
  452. hasFaceNormals = False # don't export any face normals (as they are computed in engine)
  453. hasFaceVertexNormals = ( len(f["normal"]) >= nVertices and SHADING == "smooth" )
  454. hasFaceColors = BAKE_COLORS
  455. hasFaceVertexColors = False # not supported in OBJ
  456. faceType = 0
  457. faceType = setBit(faceType, 0, not isTriangle)
  458. faceType = setBit(faceType, 1, hasMaterial)
  459. faceType = setBit(faceType, 2, hasFaceUvs)
  460. faceType = setBit(faceType, 3, hasFaceVertexUvs)
  461. faceType = setBit(faceType, 4, hasFaceNormals)
  462. faceType = setBit(faceType, 5, hasFaceVertexNormals)
  463. faceType = setBit(faceType, 6, hasFaceColors)
  464. faceType = setBit(faceType, 7, hasFaceVertexColors)
  465. faceData = []
  466. # order is important, must match order in JSONLoader
  467. # face type
  468. # vertex indices
  469. # material index
  470. # face uvs index
  471. # face vertex uvs indices
  472. # face normal index
  473. # face vertex normals indices
  474. # face color index
  475. # face vertex colors indices
  476. faceData.append(faceType)
  477. # must clamp in case on polygons bigger than quads
  478. for i in xrange(nVertices):
  479. index = f['vertex'][i] - 1
  480. faceData.append(index)
  481. faceData.append( f['material'] )
  482. if hasFaceVertexUvs:
  483. for i in xrange(nVertices):
  484. index = f['uv'][i] - 1
  485. faceData.append(index)
  486. if hasFaceVertexNormals:
  487. for i in xrange(nVertices):
  488. index = f['normal'][i] - 1
  489. faceData.append(index)
  490. if hasFaceColors:
  491. index = fc['material']
  492. faceData.append(index)
  493. return ",".join( map(str, faceData) )
  494. # #####################################################
  495. # Generator - chunks
  496. # #####################################################
  497. def hexcolor(c):
  498. return ( int(c[0] * 255) << 16 ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
  499. def generate_vertex(v):
  500. return TEMPLATE_VERTEX % (v[0], v[1], v[2])
  501. def generate_normal(n):
  502. return TEMPLATE_N % (n[0], n[1], n[2])
  503. def generate_uv(uv):
  504. return TEMPLATE_UV % (uv[0], 1.0 - uv[1])
  505. def generate_color_rgb(c):
  506. return TEMPLATE_COLOR % (c[0], c[1], c[2])
  507. def generate_color_decimal(c):
  508. return TEMPLATE_COLOR_DEC % hexcolor(c)
  509. # #####################################################
  510. # Morphs
  511. # #####################################################
  512. def generate_morph_vertex(name, vertices):
  513. vertex_string = ",".join(generate_vertex(v) for v in vertices)
  514. return TEMPLATE_MORPH_VERTICES % (name, vertex_string)
  515. def generate_morph_color(name, colors):
  516. color_string = ",".join(generate_color_rgb(c) for c in colors)
  517. return TEMPLATE_MORPH_COLORS % (name, color_string)
  518. def extract_material_colors(materials, mtlfilename, basename):
  519. """Extract diffuse colors from MTL materials
  520. """
  521. if not materials:
  522. materials = { 'default': 0 }
  523. mtl = create_materials(materials, mtlfilename, basename)
  524. mtlColorArraySrt = []
  525. for m in mtl:
  526. if m in materials:
  527. index = materials[m]
  528. color = mtl[m].get("colorDiffuse", [1,0,0])
  529. mtlColorArraySrt.append([index, color])
  530. mtlColorArraySrt.sort()
  531. mtlColorArray = [x[1] for x in mtlColorArraySrt]
  532. return mtlColorArray
  533. def extract_face_colors(faces, material_colors):
  534. """Extract colors from materials and assign them to faces
  535. """
  536. faceColors = []
  537. for face in faces:
  538. material_index = face['material']
  539. faceColors.append(material_colors[material_index])
  540. return faceColors
  541. def generate_morph_targets(morphfiles, n_vertices, infile):
  542. skipOriginalMorph = False
  543. norminfile = os.path.normpath(infile)
  544. morphVertexData = []
  545. for mfilepattern in morphfiles.split():
  546. matches = glob.glob(mfilepattern)
  547. matches.sort()
  548. for path in matches:
  549. normpath = os.path.normpath(path)
  550. if normpath != norminfile or not skipOriginalMorph:
  551. name = os.path.basename(normpath)
  552. morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(normpath)
  553. n_morph_vertices = len(morphVertices)
  554. if n_vertices != n_morph_vertices:
  555. print "WARNING: skipping morph [%s] with different number of vertices [%d] than the original model [%d]" % (name, n_morph_vertices, n_vertices)
  556. else:
  557. if ALIGN == "center":
  558. center(morphVertices)
  559. elif ALIGN == "centerxz":
  560. centerxz(morphVertices)
  561. elif ALIGN == "bottom":
  562. bottom(morphVertices)
  563. elif ALIGN == "top":
  564. top(morphVertices)
  565. morphVertexData.append((get_name(name), morphVertices))
  566. print "adding [%s] with %d vertices" % (name, n_morph_vertices)
  567. morphTargets = ""
  568. if len(morphVertexData):
  569. morphTargets = "\n%s\n\t" % ",\n".join(generate_morph_vertex(name, vertices) for name, vertices in morphVertexData)
  570. return morphTargets
  571. def generate_morph_colors(colorfiles, n_vertices, n_faces):
  572. morphColorData = []
  573. colorFaces = []
  574. materialColors = []
  575. for mfilepattern in colorfiles.split():
  576. matches = glob.glob(mfilepattern)
  577. matches.sort()
  578. for path in matches:
  579. normpath = os.path.normpath(path)
  580. name = os.path.basename(normpath)
  581. morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(normpath)
  582. n_morph_vertices = len(morphVertices)
  583. n_morph_faces = len(morphFaces)
  584. if n_vertices != n_morph_vertices:
  585. print "WARNING: skipping morph color map [%s] with different number of vertices [%d] than the original model [%d]" % (name, n_morph_vertices, n_vertices)
  586. elif n_faces != n_morph_faces:
  587. print "WARNING: skipping morph color map [%s] with different number of faces [%d] than the original model [%d]" % (name, n_morph_faces, n_faces)
  588. else:
  589. morphMaterialColors = extract_material_colors(morphMaterials, morphMtllib, normpath)
  590. morphFaceColors = extract_face_colors(morphFaces, morphMaterialColors)
  591. morphColorData.append((get_name(name), morphFaceColors))
  592. # take first color map for baking into face colors
  593. if len(colorFaces) == 0:
  594. colorFaces = morphFaces
  595. materialColors = morphMaterialColors
  596. print "adding [%s] with %d face colors" % (name, len(morphFaceColors))
  597. morphColors = ""
  598. if len(morphColorData):
  599. morphColors = "\n%s\n\t" % ",\n".join(generate_morph_color(name, colors) for name, colors in morphColorData)
  600. return morphColors, colorFaces, materialColors
  601. # #####################################################
  602. # Materials
  603. # #####################################################
  604. def generate_color(i):
  605. """Generate hex color corresponding to integer.
  606. Colors should have well defined ordering.
  607. First N colors are hardcoded, then colors are random
  608. (must seed random number generator with deterministic value
  609. before getting colors).
  610. """
  611. if i < len(COLORS):
  612. #return "0x%06x" % COLORS[i]
  613. return COLORS[i]
  614. else:
  615. #return "0x%06x" % int(0xffffff * random.random())
  616. return int(0xffffff * random.random())
  617. def value2string(v):
  618. if type(v)==str and v[0:2] != "0x":
  619. return '"%s"' % v
  620. elif type(v) == bool:
  621. return str(v).lower()
  622. return str(v)
  623. def generate_materials(mtl, materials):
  624. """Generate JS array of materials objects
  625. JS material objects are basically prettified one-to-one
  626. mappings of MTL properties in JSON format.
  627. """
  628. mtl_array = []
  629. for m in mtl:
  630. if m in materials:
  631. index = materials[m]
  632. # add debug information
  633. # materials should be sorted according to how
  634. # they appeared in OBJ file (for the first time)
  635. # this index is identifier used in face definitions
  636. mtl[m]['DbgName'] = m
  637. mtl[m]['DbgIndex'] = index
  638. mtl[m]['DbgColor'] = generate_color(index)
  639. if BAKE_COLORS:
  640. mtl[m]['vertexColors'] = "face"
  641. mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
  642. mtl_string = "\t{\n%s\n\t}" % mtl_raw
  643. mtl_array.append([index, mtl_string])
  644. return ",\n\n".join([m for i,m in sorted(mtl_array)])
  645. def generate_mtl(materials):
  646. """Generate dummy materials (if there is no MTL file).
  647. """
  648. mtl = {}
  649. for m in materials:
  650. index = materials[m]
  651. mtl[m] = {
  652. 'DbgName': m,
  653. 'DbgIndex': index,
  654. 'DbgColor': generate_color(index)
  655. }
  656. return mtl
  657. def generate_materials_string(materials, mtlfilename, basename):
  658. """Generate final materials string.
  659. """
  660. if not materials:
  661. materials = { 'default': 0 }
  662. mtl = create_materials(materials, mtlfilename, basename)
  663. return generate_materials(mtl, materials)
  664. def create_materials(materials, mtlfilename, basename):
  665. """Parse MTL file and create mapping between its materials and OBJ materials.
  666. Eventual edge cases are handled here (missing materials, missing MTL file).
  667. """
  668. random.seed(42) # to get well defined color order for debug colors
  669. # default materials with debug colors for when
  670. # there is no specified MTL / MTL loading failed,
  671. # or if there were no materials / null materials
  672. mtl = generate_mtl(materials)
  673. if mtlfilename:
  674. # create full pathname for MTL (included from OBJ)
  675. path = os.path.dirname(basename)
  676. fname = os.path.join(path, mtlfilename)
  677. if file_exists(fname):
  678. # override default materials with real ones from MTL
  679. # (where they exist, otherwise keep defaults)
  680. mtl.update(parse_mtl(fname))
  681. else:
  682. print "Couldn't find [%s]" % fname
  683. return mtl
  684. # #####################################################
  685. # Faces
  686. # #####################################################
  687. def is_triangle_flat(f):
  688. return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and not f['uv']
  689. def is_triangle_flat_uv(f):
  690. return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==3
  691. def is_triangle_smooth(f):
  692. return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and not f['uv']
  693. def is_triangle_smooth_uv(f):
  694. return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and len(f['uv'])==3
  695. def is_quad_flat(f):
  696. return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and not f['uv']
  697. def is_quad_flat_uv(f):
  698. return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==4
  699. def is_quad_smooth(f):
  700. return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and not f['uv']
  701. def is_quad_smooth_uv(f):
  702. return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and len(f['uv'])==4
  703. def sort_faces(faces):
  704. data = {
  705. 'triangles_flat': [],
  706. 'triangles_flat_uv': [],
  707. 'triangles_smooth': [],
  708. 'triangles_smooth_uv': [],
  709. 'quads_flat': [],
  710. 'quads_flat_uv': [],
  711. 'quads_smooth': [],
  712. 'quads_smooth_uv': []
  713. }
  714. for f in faces:
  715. if is_triangle_flat(f):
  716. data['triangles_flat'].append(f)
  717. elif is_triangle_flat_uv(f):
  718. data['triangles_flat_uv'].append(f)
  719. elif is_triangle_smooth(f):
  720. data['triangles_smooth'].append(f)
  721. elif is_triangle_smooth_uv(f):
  722. data['triangles_smooth_uv'].append(f)
  723. elif is_quad_flat(f):
  724. data['quads_flat'].append(f)
  725. elif is_quad_flat_uv(f):
  726. data['quads_flat_uv'].append(f)
  727. elif is_quad_smooth(f):
  728. data['quads_smooth'].append(f)
  729. elif is_quad_smooth_uv(f):
  730. data['quads_smooth_uv'].append(f)
  731. return data
  732. # #####################################################
  733. # API - ASCII converter
  734. # #####################################################
  735. def convert_ascii(infile, morphfiles, colorfiles, outfile):
  736. """Convert infile.obj to outfile.js
  737. Here is where everything happens. If you need to automate conversions,
  738. just import this file as Python module and call this method.
  739. """
  740. if not file_exists(infile):
  741. print "Couldn't find [%s]" % infile
  742. return
  743. # parse OBJ / MTL files
  744. faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
  745. n_vertices = len(vertices)
  746. n_faces = len(faces)
  747. # align model
  748. if ALIGN == "center":
  749. center(vertices)
  750. elif ALIGN == "centerxz":
  751. centerxz(vertices)
  752. elif ALIGN == "bottom":
  753. bottom(vertices)
  754. elif ALIGN == "top":
  755. top(vertices)
  756. # generate normals string
  757. nnormal = 0
  758. normals_string = ""
  759. if SHADING == "smooth":
  760. normals_string = ",".join(generate_normal(n) for n in normals)
  761. nnormal = len(normals)
  762. # extract morph vertices
  763. morphTargets = generate_morph_targets(morphfiles, n_vertices, infile)
  764. # extract morph colors
  765. morphColors, colorFaces, materialColors = generate_morph_colors(colorfiles, n_vertices, n_faces)
  766. # generate colors string
  767. ncolor = 0
  768. colors_string = ""
  769. if len(colorFaces) < len(faces):
  770. colorFaces = faces
  771. materialColors = extract_material_colors(materials, mtllib, infile)
  772. if BAKE_COLORS:
  773. colors_string = ",".join(generate_color_decimal(c) for c in materialColors)
  774. ncolor = len(materialColors)
  775. # generate ascii model string
  776. text = TEMPLATE_FILE_ASCII % {
  777. "name" : get_name(outfile),
  778. "fname" : infile,
  779. "nvertex" : len(vertices),
  780. "nface" : len(faces),
  781. "nuv" : len(uvs),
  782. "nnormal" : nnormal,
  783. "ncolor" : ncolor,
  784. "nmaterial" : len(materials),
  785. "materials" : generate_materials_string(materials, mtllib, infile),
  786. "normals" : normals_string,
  787. "colors" : colors_string,
  788. "uvs" : ",".join(generate_uv(uv) for uv in uvs),
  789. "vertices" : ",".join(generate_vertex(v) for v in vertices),
  790. "morphTargets" : morphTargets,
  791. "morphColors" : morphColors,
  792. "faces" : ",".join(generate_face(f, fc) for f, fc in zip(faces, colorFaces))
  793. }
  794. out = open(outfile, "w")
  795. out.write(text)
  796. out.close()
  797. print "%d vertices, %d faces, %d materials" % (len(vertices), len(faces), len(materials))
  798. # #############################################################################
  799. # API - Binary converter
  800. # #############################################################################
  801. def convert_binary(infile, outfile):
  802. """Convert infile.obj to outfile.js + outfile.bin
  803. """
  804. if not file_exists(infile):
  805. print "Couldn't find [%s]" % infile
  806. return
  807. binfile = get_name(outfile) + ".bin"
  808. faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
  809. if ALIGN == "center":
  810. center(vertices)
  811. elif ALIGN == "centerxz":
  812. centerxz(vertices)
  813. elif ALIGN == "bottom":
  814. bottom(vertices)
  815. elif ALIGN == "top":
  816. top(vertices)
  817. sfaces = sort_faces(faces)
  818. # ###################
  819. # generate JS file
  820. # ###################
  821. text = TEMPLATE_FILE_BIN % {
  822. "name" : get_name(outfile),
  823. "materials" : generate_materials_string(materials, mtllib, infile),
  824. "buffers" : binfile,
  825. "fname" : infile,
  826. "nvertex" : len(vertices),
  827. "nface" : len(faces),
  828. "nmaterial" : len(materials)
  829. }
  830. out = open(outfile, "w")
  831. out.write(text)
  832. out.close()
  833. # ###################
  834. # generate BIN file
  835. # ###################
  836. if SHADING == "smooth":
  837. nnormals = len(normals)
  838. else:
  839. nnormals = 0
  840. buffer = []
  841. # header
  842. # ------
  843. header_bytes = struct.calcsize('<8s')
  844. header_bytes += struct.calcsize('<BBBBBBBB')
  845. header_bytes += struct.calcsize('<IIIIIIIIIII')
  846. # signature
  847. signature = struct.pack('<8s', 'Three.js')
  848. # metadata (all data is little-endian)
  849. vertex_coordinate_bytes = 4
  850. normal_coordinate_bytes = 1
  851. uv_coordinate_bytes = 4
  852. vertex_index_bytes = 4
  853. normal_index_bytes = 4
  854. uv_index_bytes = 4
  855. material_index_bytes = 2
  856. # header_bytes unsigned char 1
  857. # vertex_coordinate_bytes unsigned char 1
  858. # normal_coordinate_bytes unsigned char 1
  859. # uv_coordinate_bytes unsigned char 1
  860. # vertex_index_bytes unsigned char 1
  861. # normal_index_bytes unsigned char 1
  862. # uv_index_bytes unsigned char 1
  863. # material_index_bytes unsigned char 1
  864. bdata = struct.pack('<BBBBBBBB', header_bytes,
  865. vertex_coordinate_bytes,
  866. normal_coordinate_bytes,
  867. uv_coordinate_bytes,
  868. vertex_index_bytes,
  869. normal_index_bytes,
  870. uv_index_bytes,
  871. material_index_bytes)
  872. # nvertices unsigned int 4
  873. # nnormals unsigned int 4
  874. # nuvs unsigned int 4
  875. # ntri_flat unsigned int 4
  876. # ntri_smooth unsigned int 4
  877. # ntri_flat_uv unsigned int 4
  878. # ntri_smooth_uv unsigned int 4
  879. # nquad_flat unsigned int 4
  880. # nquad_smooth unsigned int 4
  881. # nquad_flat_uv unsigned int 4
  882. # nquad_smooth_uv unsigned int 4
  883. ndata = struct.pack('<IIIIIIIIIII', len(vertices),
  884. nnormals,
  885. len(uvs),
  886. len(sfaces['triangles_flat']),
  887. len(sfaces['triangles_smooth']),
  888. len(sfaces['triangles_flat_uv']),
  889. len(sfaces['triangles_smooth_uv']),
  890. len(sfaces['quads_flat']),
  891. len(sfaces['quads_smooth']),
  892. len(sfaces['quads_flat_uv']),
  893. len(sfaces['quads_smooth_uv']))
  894. buffer.append(signature)
  895. buffer.append(bdata)
  896. buffer.append(ndata)
  897. # 1. vertices
  898. # ------------
  899. # x float 4
  900. # y float 4
  901. # z float 4
  902. for v in vertices:
  903. data = struct.pack('<fff', v[0], v[1], v[2])
  904. buffer.append(data)
  905. # 2. normals
  906. # ---------------
  907. # x signed char 1
  908. # y signed char 1
  909. # z signed char 1
  910. if SHADING == "smooth":
  911. for n in normals:
  912. normalize(n)
  913. data = struct.pack('<bbb', math.floor(n[0]*127+0.5),
  914. math.floor(n[1]*127+0.5),
  915. math.floor(n[2]*127+0.5))
  916. buffer.append(data)
  917. # 3. uvs
  918. # -----------
  919. # u float 4
  920. # v float 4
  921. for uv in uvs:
  922. data = struct.pack('<ff', uv[0], 1.0-uv[1])
  923. buffer.append(data)
  924. # 4. flat triangles
  925. # ------------------
  926. # a unsigned int 4
  927. # b unsigned int 4
  928. # c unsigned int 4
  929. # m unsigned short 2
  930. for f in sfaces['triangles_flat']:
  931. vi = f['vertex']
  932. data = struct.pack('<IIIH',
  933. vi[0]-1, vi[1]-1, vi[2]-1,
  934. f['material'])
  935. buffer.append(data)
  936. # 5. smooth triangles
  937. # -------------------
  938. # a unsigned int 4
  939. # b unsigned int 4
  940. # c unsigned int 4
  941. # m unsigned short 2
  942. # na unsigned int 4
  943. # nb unsigned int 4
  944. # nc unsigned int 4
  945. for f in sfaces['triangles_smooth']:
  946. vi = f['vertex']
  947. ni = f['normal']
  948. data = struct.pack('<IIIHIII',
  949. vi[0]-1, vi[1]-1, vi[2]-1,
  950. f['material'],
  951. ni[0]-1, ni[1]-1, ni[2]-1)
  952. buffer.append(data)
  953. # 6. flat triangles uv
  954. # --------------------
  955. # a unsigned int 4
  956. # b unsigned int 4
  957. # c unsigned int 4
  958. # m unsigned short 2
  959. # ua unsigned int 4
  960. # ub unsigned int 4
  961. # uc unsigned int 4
  962. for f in sfaces['triangles_flat_uv']:
  963. vi = f['vertex']
  964. ui = f['uv']
  965. data = struct.pack('<IIIHIII',
  966. vi[0]-1, vi[1]-1, vi[2]-1,
  967. f['material'],
  968. ui[0]-1, ui[1]-1, ui[2]-1)
  969. buffer.append(data)
  970. # 7. smooth triangles uv
  971. # ----------------------
  972. # a unsigned int 4
  973. # b unsigned int 4
  974. # c unsigned int 4
  975. # m unsigned short 2
  976. # na unsigned int 4
  977. # nb unsigned int 4
  978. # nc unsigned int 4
  979. # ua unsigned int 4
  980. # ub unsigned int 4
  981. # uc unsigned int 4
  982. for f in sfaces['triangles_smooth_uv']:
  983. vi = f['vertex']
  984. ni = f['normal']
  985. ui = f['uv']
  986. data = struct.pack('<IIIHIIIIII',
  987. vi[0]-1, vi[1]-1, vi[2]-1,
  988. f['material'],
  989. ni[0]-1, ni[1]-1, ni[2]-1,
  990. ui[0]-1, ui[1]-1, ui[2]-1)
  991. buffer.append(data)
  992. # 8. flat quads
  993. # ------------------
  994. # a unsigned int 4
  995. # b unsigned int 4
  996. # c unsigned int 4
  997. # d unsigned int 4
  998. # m unsigned short 2
  999. for f in sfaces['quads_flat']:
  1000. vi = f['vertex']
  1001. data = struct.pack('<IIIIH',
  1002. vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
  1003. f['material'])
  1004. buffer.append(data)
  1005. # 9. smooth quads
  1006. # -------------------
  1007. # a unsigned int 4
  1008. # b unsigned int 4
  1009. # c unsigned int 4
  1010. # d unsigned int 4
  1011. # m unsigned short 2
  1012. # na unsigned int 4
  1013. # nb unsigned int 4
  1014. # nc unsigned int 4
  1015. # nd unsigned int 4
  1016. for f in sfaces['quads_smooth']:
  1017. vi = f['vertex']
  1018. ni = f['normal']
  1019. data = struct.pack('<IIIIHIIII',
  1020. vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
  1021. f['material'],
  1022. ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1)
  1023. buffer.append(data)
  1024. # 10. flat quads uv
  1025. # ------------------
  1026. # a unsigned int 4
  1027. # b unsigned int 4
  1028. # c unsigned int 4
  1029. # d unsigned int 4
  1030. # m unsigned short 2
  1031. # ua unsigned int 4
  1032. # ub unsigned int 4
  1033. # uc unsigned int 4
  1034. # ud unsigned int 4
  1035. for f in sfaces['quads_flat_uv']:
  1036. vi = f['vertex']
  1037. ui = f['uv']
  1038. data = struct.pack('<IIIIHIIII',
  1039. vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
  1040. f['material'],
  1041. ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
  1042. buffer.append(data)
  1043. # 11. smooth quads uv
  1044. # -------------------
  1045. # a unsigned int 4
  1046. # b unsigned int 4
  1047. # c unsigned int 4
  1048. # d unsigned int 4
  1049. # m unsigned short 2
  1050. # na unsigned int 4
  1051. # nb unsigned int 4
  1052. # nc unsigned int 4
  1053. # nd unsigned int 4
  1054. # ua unsigned int 4
  1055. # ub unsigned int 4
  1056. # uc unsigned int 4
  1057. # ud unsigned int 4
  1058. for f in sfaces['quads_smooth_uv']:
  1059. vi = f['vertex']
  1060. ni = f['normal']
  1061. ui = f['uv']
  1062. data = struct.pack('<IIIIHIIIIIIII',
  1063. vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1,
  1064. f['material'],
  1065. ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1,
  1066. ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
  1067. buffer.append(data)
  1068. path = os.path.dirname(outfile)
  1069. fname = os.path.join(path, binfile)
  1070. out = open(fname, "wb")
  1071. out.write("".join(buffer))
  1072. out.close()
  1073. # #############################################################################
  1074. # Helpers
  1075. # #############################################################################
  1076. def usage():
  1077. 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])
  1078. # #####################################################
  1079. # Main
  1080. # #####################################################
  1081. if __name__ == "__main__":
  1082. # get parameters from the command line
  1083. try:
  1084. opts, args = getopt.getopt(sys.argv[1:], "hbi:m:c:b:o:a:s:t:d:", ["help", "bakecolors", "input=", "morphs=", "colors=", "output=", "align=", "shading=", "type=", "dissolve="])
  1085. except getopt.GetoptError:
  1086. usage()
  1087. sys.exit(2)
  1088. infile = outfile = ""
  1089. morphfiles = ""
  1090. colorfiles = ""
  1091. for o, a in opts:
  1092. if o in ("-h", "--help"):
  1093. usage()
  1094. sys.exit()
  1095. elif o in ("-i", "--input"):
  1096. infile = a
  1097. elif o in ("-m", "--morphs"):
  1098. morphfiles = a
  1099. elif o in ("-c", "--colors"):
  1100. colorfiles = a
  1101. elif o in ("-o", "--output"):
  1102. outfile = a
  1103. elif o in ("-a", "--align"):
  1104. if a in ("top", "bottom", "center", "centerxz", "none"):
  1105. ALIGN = a
  1106. elif o in ("-s", "--shading"):
  1107. if a in ("flat", "smooth"):
  1108. SHADING = a
  1109. elif o in ("-t", "--type"):
  1110. if a in ("binary", "ascii"):
  1111. TYPE = a
  1112. elif o in ("-d", "--dissolve"):
  1113. if a in ("normal", "invert"):
  1114. TRANSPARENCY = a
  1115. elif o in ("-b", "--bakecolors"):
  1116. BAKE_COLORS = True
  1117. if infile == "" or outfile == "":
  1118. usage()
  1119. sys.exit(2)
  1120. print "Converting [%s] into [%s] ..." % (infile, outfile)
  1121. if morphfiles:
  1122. print "Morphs [%s]" % morphfiles
  1123. if colorfiles:
  1124. print "Colors [%s]" % colorfiles
  1125. if TYPE == "ascii":
  1126. convert_ascii(infile, morphfiles, colorfiles, outfile)
  1127. elif TYPE == "binary":
  1128. convert_binary(infile, outfile)