convert_obj_three_for_python3.py 47 KB

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