convert_obj_three.py 47 KB

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