convert_obj_threejs.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839
  1. """Convert Wavefront OBJ / MTL files into Three.js
  2. -------------------------
  3. How to use this converter
  4. -------------------------
  5. python convert_obj_threejs.py -i filename.obj -o filename.js [-a center|top|bottom]
  6. Note: by default, model is centered (middle of bounding box goes to 0,0,0).
  7. --------------------------------------------------
  8. How to use generated JS file in your HTML document
  9. --------------------------------------------------
  10. <script type="text/javascript" src="Three.js"></script>
  11. <script type="text/javascript" src="ModelName.js"></script>
  12. ...
  13. <script type="text/javascript">
  14. ...
  15. var normalizeUVsFlag = 1; // set to 1 if canvas render has missing materials
  16. var geometry = new ModelName( path_to_textures );
  17. var mesh = new THREE.Mesh( geometry, geometry.materials, normalizeUVsFlag );
  18. ...
  19. </script>
  20. -------------------------------------
  21. Parsers based on formats descriptions
  22. -------------------------------------
  23. http://en.wikipedia.org/wiki/Obj
  24. http://en.wikipedia.org/wiki/Material_Template_Library
  25. -------------------
  26. Current limitations
  27. -------------------
  28. - for the moment, only diffuse color and texture are used
  29. (will need to extend shaders / renderers / materials in Three)
  30. - models cannot have more than 65,536 vertices
  31. (this comes from WebGL using just 16-bit indices,
  32. could be worked around by expanding indexed
  33. faces into full vertex definitions)
  34. - texture coordinates can be wrong in canvas renderer
  35. (there is crude normalization, but it doesn't
  36. work for all cases)
  37. - everything is using smoothing
  38. (if you want flat shading for whole mesh,
  39. don't export normals, then Three will
  40. compute own normals)
  41. ----------------------------------------------
  42. How to get proper OBJ + MTL files with Blender
  43. ----------------------------------------------
  44. 0. Remove default cube (press DEL and ENTER)
  45. 1. Import / create model
  46. 2. Select all meshes (Select -> Select All by Type -> Mesh)
  47. 3. Export to OBJ (File -> Export -> Wavefront .obj) [*]
  48. - enable following options in exporter
  49. Material Groups
  50. Rotate X90
  51. Apply Modifiers
  52. High Quality Normals
  53. Copy Images
  54. Selection Only
  55. Objects as OBJ Objects
  56. UVs
  57. Normals
  58. Materials
  59. Edges
  60. - select empty folder
  61. - give your exported file name with "obj" extension
  62. - click on "Export OBJ" button
  63. 4. Your model is now all files in this folder (OBJ, MTL, number of images)
  64. - this converter assumes all files staying in the same folder,
  65. (OBJ / MTL files use relative paths)
  66. - for WebGL, textures must be power of 2 sized
  67. [*] If OBJ export fails (Blender 2.54 beta), patch your Blender installation
  68. following instructions here:
  69. http://www.blendernation.com/2010/09/12/blender-2-54-beta-released/
  70. ------
  71. Author
  72. ------
  73. AlteredQualia http://alteredqualia.com
  74. """
  75. import fileinput
  76. import operator
  77. import random
  78. import os.path
  79. import getopt
  80. import sys
  81. # #####################################################
  82. # Configuration
  83. # #####################################################
  84. ALIGN = "center" # center bottom top none
  85. # default colors for debugging (each material gets one distinct color):
  86. # white, red, green, blue, yellow, cyan, magenta
  87. COLORS = [0xffeeeeee, 0xffee0000, 0xff00ee00, 0xff0000ee, 0xffeeee00, 0xff00eeee, 0xffee00ee]
  88. # #####################################################
  89. # Templates
  90. # #####################################################
  91. TEMPLATE_FILE = u"""\
  92. // Converted from: %(fname)s
  93. // vertices: %(nvertex)d
  94. // faces: %(nface)d
  95. // materials: %(nmaterial)d
  96. //
  97. // This file was generated by "convert_obj_treejs.py"
  98. var %(name)s = function ( urlbase ) {
  99. var scope = this;
  100. THREE.Geometry.call(this);
  101. var materials = [%(materials)s];
  102. init_materials();
  103. var normals = [%(normals)s];
  104. %(vertices)s
  105. %(uvs)s
  106. %(faces)s
  107. this.computeCentroids();
  108. this.computeNormals();
  109. function material_color( mi ) {
  110. var m = materials[mi];
  111. if( m.col_diffuse )
  112. return (m.col_diffuse[0]*255 << 16) + (m.col_diffuse[1]*255 << 8) + m.col_diffuse[2]*255;
  113. else if ( m.a_dbg_color )
  114. return m.a_dbg_color;
  115. else
  116. return 0xffeeeeee;
  117. }
  118. function v( x, y, z ) {
  119. scope.vertices.push( new THREE.Vertex( new THREE.Vector3( x, y, z ) ) );
  120. }
  121. function f3( a, b, c, material ) {
  122. var color = material_color(material);
  123. scope.faces.push( new THREE.Face3( a, b, c, null, new THREE.Color(color), material ) );
  124. }
  125. function f4( a, b, c, d, material ) {
  126. var color = material_color(material);
  127. scope.faces.push( new THREE.Face4( a, b, c, d, null, new THREE.Color(color), material ) );
  128. }
  129. function f3n( a, b, c, material, n1, n2, n3 ) {
  130. var color = material_color(material);
  131. var n1x = normals[n1][0];
  132. var n1y = normals[n1][1];
  133. var n1z = normals[n1][2];
  134. var n2x = normals[n2][0];
  135. var n2y = normals[n2][1];
  136. var n2z = normals[n2][2];
  137. var n3x = normals[n3][0];
  138. var n3y = normals[n3][1];
  139. var n3z = normals[n3][2];
  140. scope.faces.push( new THREE.Face3( a, b, c,
  141. [new THREE.Vector3( n1x, n1y, n1z ), new THREE.Vector3( n2x, n2y, n2z ), new THREE.Vector3( n3x, n3y, n3z )],
  142. new THREE.Color(color), material ) );
  143. }
  144. function f4n( a, b, c, d, material, n1, n2, n3, n4 ) {
  145. var color = material_color(material);
  146. var n1x = normals[n1][0];
  147. var n1y = normals[n1][1];
  148. var n1z = normals[n1][2];
  149. var n2x = normals[n2][0];
  150. var n2y = normals[n2][1];
  151. var n2z = normals[n2][2];
  152. var n3x = normals[n3][0];
  153. var n3y = normals[n3][1];
  154. var n3z = normals[n3][2];
  155. var n4x = normals[n4][0];
  156. var n4y = normals[n4][1];
  157. var n4z = normals[n4][2];
  158. scope.faces.push( new THREE.Face4( a, b, c, d,
  159. [new THREE.Vector3( n1x, n1y, n1z ), new THREE.Vector3( n2x, n2y, n2z ), new THREE.Vector3( n3x, n3y, n3z ), new THREE.Vector3( n4x, n4y, n4z )],
  160. new THREE.Color(color), material ) );
  161. }
  162. function uv( u1, v1, u2, v2, u3, v3, u4, v4 ) {
  163. var uv = [];
  164. uv.push( new THREE.UV( u1, v1 ) );
  165. uv.push( new THREE.UV( u2, v2 ) );
  166. uv.push( new THREE.UV( u3, v3 ) );
  167. if ( u4 && v4 ) uv.push( new THREE.UV( u4, v4 ) );
  168. scope.uvs.push( uv );
  169. }
  170. function init_materials() {
  171. scope.materials = [];
  172. for(var i=0; i<materials.length; ++i) {
  173. scope.materials[i] = create_material( materials[i], urlbase );
  174. }
  175. }
  176. function is_pow2( n ) {
  177. var l = Math.log(n) / Math.LN2;
  178. return Math.floor(l) == l;
  179. }
  180. function nearest_pow2(n) {
  181. var l = Math.log(n) / Math.LN2;
  182. return Math.pow( 2, Math.round(l) );
  183. }
  184. function create_material( m ) {
  185. var material;
  186. if( m.map_diffuse && urlbase ) {
  187. var texture = document.createElement( 'canvas' );
  188. material = new THREE.MeshBitmapUVMappingMaterial( texture );
  189. var image = new Image();
  190. image.onload = function () {
  191. if ( !is_pow2(this.width) || !is_pow2(this.height) ) {
  192. var w = nearest_pow2( this.width );
  193. var h = nearest_pow2( this.height );
  194. material.bitmap.width = w;
  195. material.bitmap.height = h;
  196. material.bitmap.getContext("2d").drawImage( this, 0, 0, w, h );
  197. }
  198. else {
  199. material.bitmap = this;
  200. }
  201. material.loaded = 1;
  202. };
  203. image.src = urlbase + "/" + m.map_diffuse;
  204. }
  205. else if( m.col_diffuse ) {
  206. var color = (m.col_diffuse[0]*255 << 16) + (m.col_diffuse[1]*255 << 8) + m.col_diffuse[2]*255;
  207. material = new THREE.MeshColorFillMaterial( color, m.transparency );
  208. }
  209. else if( m.a_dbg_color ) {
  210. material = new THREE.MeshColorFillMaterial( m.a_dbg_color );
  211. }
  212. else {
  213. material = new THREE.MeshFaceColorFillMaterial( );
  214. }
  215. return material;
  216. }
  217. }
  218. %(name)s.prototype = new THREE.Geometry();
  219. %(name)s.prototype.constructor = %(name)s;
  220. """
  221. TEMPLATE_VERTEX = "\tv(%f,%f,%f);"
  222. TEMPLATE_UV3 = "\tuv(%f,%f,%f,%f,%f,%f);"
  223. TEMPLATE_UV4 = "\tuv(%f,%f,%f,%f,%f,%f,%f,%f);"
  224. TEMPLATE_FACE3 = "\tf3(%d,%d,%d,%d);"
  225. TEMPLATE_FACE4 = "\tf4(%d,%d,%d,%d,%d);"
  226. TEMPLATE_FACE3N = "\tf3n(%d,%d,%d, %d, %d,%d,%d);"
  227. TEMPLATE_FACE4N = "\tf4n(%d,%d,%d,%d, %d, %d,%d,%d,%d);"
  228. TEMPLATE_N = "[%f,%f,%f]"
  229. # #####################################################
  230. # Utils
  231. # #####################################################
  232. def file_exists(filename):
  233. """Return true if file exists and is accessible for reading.
  234. Should be safer than just testing for existence due to links and
  235. permissions magic on Unix filesystems.
  236. @rtype: boolean
  237. """
  238. try:
  239. f = open(filename, 'r')
  240. f.close()
  241. return True
  242. except IOError:
  243. return False
  244. def get_name(fname):
  245. """Create model name based of filename ("path/fname.js" -> "Fname").
  246. """
  247. return os.path.basename(fname).split(".")[0].capitalize()
  248. def bbox(vertices):
  249. """Compute bounding box of vertex array.
  250. """
  251. if len(vertices)>0:
  252. minx = maxx = vertices[0][0]
  253. miny = maxy = vertices[0][1]
  254. minz = maxz = vertices[0][2]
  255. for v in vertices[1:]:
  256. if v[0]<minx:
  257. minx = v[0]
  258. elif v[0]>maxx:
  259. maxx = v[0]
  260. if v[1]<miny:
  261. miny = v[1]
  262. elif v[1]>maxy:
  263. maxy = v[1]
  264. if v[2]<minz:
  265. minz = v[2]
  266. elif v[2]>maxz:
  267. maxz = v[2]
  268. return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
  269. else:
  270. return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
  271. def translate(vertices, t):
  272. """Translate array of vertices by vector t.
  273. """
  274. for i in xrange(len(vertices)):
  275. vertices[i][0] += t[0]
  276. vertices[i][1] += t[1]
  277. vertices[i][2] += t[2]
  278. def center(vertices):
  279. """Center model (middle of bounding box).
  280. """
  281. bb = bbox(vertices)
  282. cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
  283. cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
  284. cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
  285. translate(vertices, [-cx,-cy,-cz])
  286. def top(vertices):
  287. """Align top of the model with the floor (Y-axis) and center it around X and Z.
  288. """
  289. bb = bbox(vertices)
  290. cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
  291. cy = bb['y'][1]
  292. cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
  293. translate(vertices, [-cx,-cy,-cz])
  294. def bottom(vertices):
  295. """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
  296. """
  297. bb = bbox(vertices)
  298. cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
  299. cy = bb['y'][0]
  300. cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
  301. translate(vertices, [-cx,cy,-cz])
  302. def normalize(v):
  303. """Normalize 3d vector"""
  304. l = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
  305. v[0] /= l
  306. v[1] /= l
  307. v[2] /= l
  308. # #####################################################
  309. # MTL parser
  310. # #####################################################
  311. def parse_mtl(fname):
  312. """Parse MTL file.
  313. """
  314. materials = {}
  315. for line in fileinput.input(fname):
  316. chunks = line.split()
  317. if len(chunks) > 0:
  318. # Material start
  319. # newmtl identifier
  320. if chunks[0] == "newmtl" and len(chunks) == 2:
  321. identifier = chunks[1]
  322. if not identifier in materials:
  323. materials[identifier] = {}
  324. # Diffuse color
  325. # Kd 1.000 1.000 1.000
  326. if chunks[0] == "Kd" and len(chunks) == 4:
  327. materials[identifier]["col_diffuse"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
  328. # Ambient color
  329. # Ka 1.000 1.000 1.000
  330. if chunks[0] == "Ka" and len(chunks) == 4:
  331. materials[identifier]["col_ambient"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
  332. # Specular color
  333. # Ks 1.000 1.000 1.000
  334. if chunks[0] == "Ks" and len(chunks) == 4:
  335. materials[identifier]["col_specular"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
  336. # Specular coefficient
  337. # Ns 154.000
  338. if chunks[0] == "Ns" and len(chunks) == 2:
  339. materials[identifier]["specular_coef"] = float(chunks[1])
  340. # Transparency
  341. # Tr 0.9 or d 0.9
  342. if (chunks[0] == "Tr" or chunks[0] == "d") and len(chunks) == 2:
  343. materials[identifier]["transparency"] = float(chunks[1])
  344. # Optical density
  345. # Ni 1.0
  346. if chunks[0] == "Ni" and len(chunks) == 2:
  347. materials[identifier]["optical_density"] = float(chunks[1])
  348. # Diffuse texture
  349. # map_Kd texture_diffuse.jpg
  350. if chunks[0] == "map_Kd" and len(chunks) == 2:
  351. materials[identifier]["map_diffuse"] = chunks[1]
  352. # Ambient texture
  353. # map_Ka texture_ambient.jpg
  354. if chunks[0] == "map_Ka" and len(chunks) == 2:
  355. materials[identifier]["map_ambient"] = chunks[1]
  356. # Specular texture
  357. # map_Ks texture_specular.jpg
  358. if chunks[0] == "map_Ks" and len(chunks) == 2:
  359. materials[identifier]["map_specular"] = chunks[1]
  360. # Alpha texture
  361. # map_d texture_alpha.png
  362. if chunks[0] == "map_d" and len(chunks) == 2:
  363. materials[identifier]["map_alpha"] = chunks[1]
  364. # Bump texture
  365. # map_bump texture_bump.jpg or bump texture_bump.jpg
  366. if (chunks[0] == "map_bump" or chunks[0] == "bump") and len(chunks) == 2:
  367. materials[identifier]["map_bump"] = chunks[1]
  368. # Illumination
  369. # illum 2
  370. #
  371. # 0. Color on and Ambient off
  372. # 1. Color on and Ambient on
  373. # 2. Highlight on
  374. # 3. Reflection on and Ray trace on
  375. # 4. Transparency: Glass on, Reflection: Ray trace on
  376. # 5. Reflection: Fresnel on and Ray trace on
  377. # 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
  378. # 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
  379. # 8. Reflection on and Ray trace off
  380. # 9. Transparency: Glass on, Reflection: Ray trace off
  381. # 10. Casts shadows onto invisible surfaces
  382. if chunks[0] == "illum" and len(chunks) == 2:
  383. materials[identifier]["illumination"] = int(chunks[1])
  384. return materials
  385. # #####################################################
  386. # OBJ parser
  387. # #####################################################
  388. def parse_vertex(text):
  389. """Parse text chunk specifying single vertex.
  390. Possible formats:
  391. vertex index
  392. vertex index / texture index
  393. vertex index / texture index / normal index
  394. vertex index / / normal index
  395. """
  396. v = 0
  397. t = 0
  398. n = 0
  399. chunks = text.split("/")
  400. v = int(chunks[0])
  401. if len(chunks) > 1:
  402. if chunks[1]:
  403. t = int(chunks[1])
  404. if len(chunks) > 2:
  405. if chunks[2]:
  406. n = int(chunks[2])
  407. return { 'v':v, 't':t, 'n':n }
  408. def parse_obj(fname):
  409. """Parse OBJ file.
  410. """
  411. vertices = []
  412. normals = []
  413. uvs = []
  414. faces = []
  415. materials = {}
  416. mcounter = 0
  417. mcurrent = 0
  418. mtllib = ""
  419. # current face state
  420. group = 0
  421. object = 0
  422. smooth = 0
  423. for line in fileinput.input(fname):
  424. chunks = line.split()
  425. if len(chunks) > 0:
  426. # Vertices as (x,y,z) coordinates
  427. # v 0.123 0.234 0.345
  428. if chunks[0] == "v" and len(chunks) == 4:
  429. x = float(chunks[1])
  430. y = float(chunks[2])
  431. z = float(chunks[3])
  432. vertices.append([x,y,z])
  433. # Normals in (x,y,z) form; normals might not be unit
  434. # vn 0.707 0.000 0.707
  435. if chunks[0] == "vn" and len(chunks) == 4:
  436. x = float(chunks[1])
  437. y = float(chunks[2])
  438. z = float(chunks[3])
  439. normals.append([x,y,z])
  440. # Texture coordinates in (u,v[,w]) coordinates, w is optional
  441. # vt 0.500 -1.352 [0.234]
  442. if chunks[0] == "vt" and len(chunks) >= 3:
  443. u = float(chunks[1])
  444. v = float(chunks[2])
  445. w = 0
  446. if len(chunks)>3:
  447. w = float(chunks[3])
  448. uvs.append([u,v,w])
  449. # Face
  450. if chunks[0] == "f" and len(chunks) >= 4:
  451. vertex_index = []
  452. uv_index = []
  453. normal_index = []
  454. for v in chunks[1:]:
  455. vertex = parse_vertex(v)
  456. if vertex['v']:
  457. vertex_index.append(vertex['v'])
  458. if vertex['t']:
  459. uv_index.append(vertex['t'])
  460. if vertex['n']:
  461. normal_index.append(vertex['n'])
  462. faces.append({
  463. 'vertex':vertex_index,
  464. 'uv':uv_index,
  465. 'normal':normal_index,
  466. 'material':mcurrent,
  467. 'group':group,
  468. 'object':object,
  469. 'smooth':smooth,
  470. })
  471. # Group
  472. if chunks[0] == "g" and len(chunks) == 2:
  473. group = chunks[1]
  474. # Object
  475. if chunks[0] == "o" and len(chunks) == 2:
  476. object = chunks[1]
  477. # Materials definition
  478. if chunks[0] == "mtllib" and len(chunks) == 2:
  479. mtllib = chunks[1]
  480. # Material
  481. if chunks[0] == "usemtl" and len(chunks) == 2:
  482. material = chunks[1]
  483. if not material in materials:
  484. mcurrent = mcounter
  485. materials[material] = mcounter
  486. mcounter += 1
  487. else:
  488. mcurrent = materials[material]
  489. # Smooth shading
  490. if chunks[0] == "s" and len(chunks) == 2:
  491. smooth = chunks[1]
  492. return faces, vertices, uvs, normals, materials, mtllib
  493. # #####################################################
  494. # Generator
  495. # #####################################################
  496. def generate_vertex(v):
  497. return TEMPLATE_VERTEX % (v[0], v[1], v[2])
  498. def generate_uv(f, uvs):
  499. ui = f['uv']
  500. if len(ui) == 3:
  501. return TEMPLATE_UV3 % (uvs[ui[0]-1][0], 1.0 - uvs[ui[0]-1][1],
  502. uvs[ui[1]-1][0], 1.0 - uvs[ui[1]-1][1],
  503. uvs[ui[2]-1][0], 1.0 - uvs[ui[2]-1][1])
  504. elif len(ui) == 4:
  505. return TEMPLATE_UV4 % (uvs[ui[0]-1][0], 1.0 - uvs[ui[0]-1][1],
  506. uvs[ui[1]-1][0], 1.0 - uvs[ui[1]-1][1],
  507. uvs[ui[2]-1][0], 1.0 - uvs[ui[2]-1][1],
  508. uvs[ui[3]-1][0], 1.0 - uvs[ui[3]-1][1])
  509. return ""
  510. def generate_face(f):
  511. vi = f['vertex']
  512. if f["normal"]:
  513. ni = f['normal']
  514. if len(vi) == 3:
  515. return TEMPLATE_FACE3N % (vi[0]-1, vi[1]-1, vi[2]-1, f['material'], ni[0]-1, ni[1]-1, ni[2]-1)
  516. elif len(vi) == 4:
  517. return TEMPLATE_FACE4N % (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)
  518. else:
  519. if len(vi) == 3:
  520. return TEMPLATE_FACE3 % (vi[0]-1, vi[1]-1, vi[2]-1, f['material'])
  521. elif len(vi) == 4:
  522. return TEMPLATE_FACE4 % (vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1, f['material'])
  523. return ""
  524. def generate_normal(n):
  525. return TEMPLATE_N % (n[0], n[1], n[2])
  526. def generate_color(i):
  527. """Generate hex color corresponding to integer.
  528. Colors should have well defined ordering.
  529. First N colors are hardcoded, then colors are random
  530. (must seed random number generator with deterministic value
  531. before getting colors).
  532. """
  533. if i < len(COLORS):
  534. return "0x%x" % COLORS[i]
  535. else:
  536. return "0x%x" % (int(0xffffff * random.random()) + 0xff000000)
  537. def value2string(v):
  538. if type(v)==str and v[0] != "0":
  539. return '"%s"' % v
  540. return str(v)
  541. def generate_materials(mtl, materials):
  542. """Generate JS array of materials objects
  543. JS material objects are basically prettified one-to-one
  544. mappings of MTL properties in JSON format.
  545. """
  546. mtl_array = []
  547. for m in mtl:
  548. index = materials[m]
  549. # add debug information
  550. # materials should be sorted according to how
  551. # they appeared in OBJ file (for the first time)
  552. # this index is identifier used in face definitions
  553. mtl[m]['a_dbg_name'] = m
  554. mtl[m]['a_dbg_index'] = index
  555. mtl[m]['a_dbg_color'] = generate_color(index)
  556. mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
  557. mtl_string = "\t{\n%s\n\t}" % mtl_raw
  558. mtl_array.append([index, mtl_string])
  559. return ",\n\n".join([m for i,m in sorted(mtl_array)])
  560. def generate_mtl(materials):
  561. """Generate dummy materials (if there is no MTL file).
  562. """
  563. mtl = {}
  564. for m in materials:
  565. index = materials[m]
  566. mtl[m] = {
  567. 'a_dbg_name': m,
  568. 'a_dbg_index': index,
  569. 'a_dbg_color': generate_color(index)
  570. }
  571. return mtl
  572. # #####################################################
  573. # API
  574. # #####################################################
  575. def convert(infile, outfile):
  576. """Convert infile.obj to outfile.js
  577. Here is where everything happens. If you need to automate conversions,
  578. just import this file as Python module and call this method.
  579. """
  580. if not file_exists(infile):
  581. print "Couldn't find [%s]" % infile
  582. return
  583. faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
  584. if ALIGN == "center":
  585. center(vertices)
  586. elif ALIGN == "bottom":
  587. bottom(vertices)
  588. elif ALIGN == "top":
  589. top(vertices)
  590. random.seed(42) # to get well defined color order for materials
  591. uv_string = ""
  592. if len(uvs)>0:
  593. uv_string = "\n".join([generate_uv(f, uvs) for f in faces])
  594. # default materials with debug colors for when
  595. # there is no specified MTL, if loading failed
  596. # or there were null materials
  597. mtl = generate_mtl(materials)
  598. if mtllib:
  599. # create full pathname for MTL (included from OBJ)
  600. path = os.path.dirname(infile)
  601. fname = os.path.join(path, mtllib)
  602. if file_exists(fname):
  603. # override default materials with real ones from MTL
  604. # (where they exist, otherwise keep defaults)
  605. mtl.update(parse_mtl(fname))
  606. else:
  607. print "Couldn't find [%s]" % fname
  608. text = TEMPLATE_FILE % {
  609. "name" : get_name(outfile),
  610. "vertices" : "\n".join([generate_vertex(v) for v in vertices]),
  611. "faces" : "\n".join([generate_face(f) for f in faces]),
  612. "uvs" : uv_string,
  613. "normals" : ",".join(generate_normal(n) for n in normals),
  614. "materials" : generate_materials(mtl, materials),
  615. "fname" : infile,
  616. "nvertex" : len(vertices),
  617. "nface" : len(faces),
  618. "nmaterial" : len(materials)
  619. }
  620. out = open(outfile, "w")
  621. out.write(text)
  622. out.close()
  623. print "%d vertices, %d faces, %d materials" % (len(vertices), len(faces), len(materials))
  624. # #############################################################################
  625. # Helpers
  626. # #############################################################################
  627. def usage():
  628. print "Usage: %s -i filename.obj -o filename.js [-a center|top|bottom]" % os.path.basename(sys.argv[0])
  629. # #####################################################
  630. # Main
  631. # #####################################################
  632. if __name__ == "__main__":
  633. # get parameters from the command line
  634. try:
  635. opts, args = getopt.getopt(sys.argv[1:], "hi:o:a:", ["help", "input=", "output=", "align="])
  636. except getopt.GetoptError:
  637. usage()
  638. sys.exit(2)
  639. infile = outfile = ""
  640. for o, a in opts:
  641. if o in ("-h", "--help"):
  642. usage()
  643. sys.exit()
  644. elif o in ("-i", "--input"):
  645. infile = a
  646. elif o in ("-o", "--output"):
  647. outfile = a
  648. elif o in ("-a", "--align"):
  649. if a in ("top", "bottom", "center"):
  650. ALIGN = a
  651. if infile == "" or outfile == "":
  652. usage()
  653. sys.exit(2)
  654. print "Converting [%s] into [%s] ..." % (infile, outfile)
  655. convert(infile, outfile)