convert_obj_three_for_python3.py 48 KB

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