convert_to_threejs.py 62 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782
  1. # @author zfedoran / http://github.com/zfedoran
  2. import os
  3. import sys
  4. import math
  5. # #####################################################
  6. # Globals
  7. # #####################################################
  8. option_triangulate = True
  9. option_textures = True
  10. option_prefix = True
  11. option_geometry = False
  12. option_default_camera = False
  13. option_default_light = False
  14. converter = None
  15. global_up_vector = None
  16. # #####################################################
  17. # Templates
  18. # #####################################################
  19. def Vector2String(v, no_brackets = False):
  20. if no_brackets:
  21. return '%g, %g' % (v[0], v[1])
  22. else:
  23. return '[ %g, %g ]' % (v[0], v[1])
  24. def Vector3String(v, no_brackets = False):
  25. if no_brackets:
  26. return '%g, %g, %g' % (v[0], v[1], v[2])
  27. else:
  28. return '[ %g, %g, %g ]' % (v[0], v[1], v[2])
  29. def ColorString(c, no_brackets = False):
  30. if no_brackets:
  31. return '%g, %g, %g' % (c[0], c[1], c[2])
  32. else:
  33. return '[ %g, %g, %g ]' % (c[0], c[1], c[2])
  34. def LabelString(s):
  35. return '"%s"' % s
  36. def ArrayString(s):
  37. return '[ %s ]' % s
  38. def PaddingString(n):
  39. output = ""
  40. for i in range(n):
  41. output += "\t"
  42. return output
  43. def BoolString(value):
  44. if value:
  45. return "true"
  46. return "false"
  47. # #####################################################
  48. # Helpers
  49. # #####################################################
  50. def getObjectName(o, force_prefix = False):
  51. if not o:
  52. return ""
  53. prefix = ""
  54. if option_prefix or force_prefix:
  55. prefix = "Object_%s_" % o.GetUniqueID()
  56. return prefix + o.GetName()
  57. def getGeometryName(g, force_prefix = False):
  58. prefix = ""
  59. if option_prefix or force_prefix:
  60. prefix = "Geometry_%s_" % g.GetUniqueID()
  61. return prefix + g.GetName()
  62. def getEmbedName(e, force_prefix = False):
  63. prefix = ""
  64. if option_prefix or force_prefix:
  65. prefix = "Embed_%s_" % e.GetUniqueID()
  66. return prefix + e.GetName()
  67. def getMaterialName(m, force_prefix = False):
  68. prefix = ""
  69. if option_prefix or force_prefix:
  70. prefix = "Material_%s_" % m.GetUniqueID()
  71. return prefix + m.GetName()
  72. def getTextureName(t, force_prefix = False):
  73. texture_file = t.GetFileName()
  74. texture_id = os.path.splitext(os.path.basename(texture_file))[0]
  75. prefix = ""
  76. if option_prefix or force_prefix:
  77. prefix = "Texture_%s_" % t.GetUniqueID()
  78. return prefix + texture_id
  79. def getFogName(f, force_prefix = False):
  80. prefix = ""
  81. if option_prefix or force_prefix:
  82. prefix = "Fog_%s_" % f.GetUniqueID()
  83. return prefix + f.GetName()
  84. def getObjectVisible(n):
  85. return BoolString(True)
  86. def getRadians(v):
  87. return ((v[0]*math.pi)/180, (v[1]*math.pi)/180, (v[2]*math.pi)/180)
  88. def getHex(c):
  89. color = (int(c[0]*255) << 16) + (int(c[1]*255) << 8) + int(c[2]*255)
  90. return color
  91. def setBit(value, position, on):
  92. if on:
  93. mask = 1 << position
  94. return (value | mask)
  95. else:
  96. mask = ~(1 << position)
  97. return (value & mask)
  98. def convert_fbx_color(color):
  99. return [color.mRed, color.mGreen, color.mBlue, color.mAlpha]
  100. def convert_fbx_vec2(v):
  101. return [v[0], v[1]]
  102. def convert_fbx_vec3(v):
  103. return [v[0], v[1], v[2]]
  104. def round_vec2(v):
  105. return [round(v[0], 6), round(v[1], 6)]
  106. def round_vec3(v):
  107. return [round(v[0], 6), round(v[1], 6), round(v[2], 6)]
  108. def generate_uvs(uv_layers):
  109. layers = []
  110. for uvs in uv_layers:
  111. layer = ",".join(Vector2String(n, True) for n in uvs)
  112. layers.append(layer)
  113. return ",".join("[%s]" % n for n in layers)
  114. def generateMultiLineString(lines, separator, padding):
  115. cleanLines = []
  116. for i in range(len(lines)):
  117. line = lines[i]
  118. line = PaddingString(padding) + line
  119. cleanLines.append(line)
  120. return separator.join(cleanLines)
  121. def get_up_vector(scene):
  122. global_settings = scene.GetGlobalSettings()
  123. axis_system = global_settings.GetAxisSystem()
  124. up_vector = axis_system.GetUpVector()
  125. tmp = [0,0,0]
  126. tmp[up_vector[0] - 1] = up_vector[1] * 1
  127. return FbxVector4(tmp[0], tmp[1], tmp[2], 1)
  128. # #####################################################
  129. # Generate - Triangles
  130. # #####################################################
  131. def triangulate_node_hierarchy(node):
  132. node_attribute = node.GetNodeAttribute();
  133. if node_attribute:
  134. if node_attribute.GetAttributeType() == FbxNodeAttribute.eMesh or \
  135. node_attribute.GetAttributeType() == FbxNodeAttribute.eNurbs or \
  136. node_attribute.GetAttributeType() == FbxNodeAttribute.eNurbsSurface or \
  137. node_attribute.GetAttributeType() == FbxNodeAttribute.ePatch:
  138. converter.TriangulateInPlace(node);
  139. child_count = node.GetChildCount()
  140. for i in range(child_count):
  141. triangulate_node_hierarchy(node.GetChild(i))
  142. def triangulate_scene(scene):
  143. node = scene.GetRootNode()
  144. if node:
  145. for i in range(node.GetChildCount()):
  146. triangulate_node_hierarchy(node.GetChild(i))
  147. # #####################################################
  148. # Generate - Material String
  149. # #####################################################
  150. def generate_texture_bindings(material_property, texture_list):
  151. binding_types = {
  152. "DiffuseColor": "map", "DiffuseFactor": "diffuseFactor", "EmissiveColor": "emissiveMap",
  153. "EmissiveFactor": "emissiveFactor", "AmbientColor": "ambientMap", "AmbientFactor": "ambientFactor",
  154. "SpecularColor": "specularMap", "SpecularFactor": "specularFactor", "ShininessExponent": "shininessExponent",
  155. "NormalMap": "normalMap", "Bump": "bumpMap", "TransparentColor": "transparentMap",
  156. "TransparencyFactor": "transparentFactor", "ReflectionColor": "reflectionMap",
  157. "ReflectionFactor": "reflectionFactor", "DisplacementColor": "displacementMap",
  158. "VectorDisplacementColor": "vectorDisplacementMap"
  159. }
  160. if material_property.IsValid():
  161. #Here we have to check if it's layeredtextures, or just textures:
  162. layered_texture_count = material_property.GetSrcObjectCount(FbxLayeredTexture.ClassId)
  163. if layered_texture_count > 0:
  164. for j in range(layered_texture_count):
  165. layered_texture = material_property.GetSrcObject(FbxLayeredTexture.ClassId, j)
  166. texture_count = layered_texture.GetSrcObjectCount(FbxTexture.ClassId)
  167. for k in range(texture_count):
  168. texture = layered_texture.GetSrcObject(FbxTexture.ClassId,k)
  169. if texture:
  170. texture_id = getTextureName(texture, True)
  171. texture_binding = ' "%s": "%s",' % (binding_types[str(material_property.GetName())], texture_id)
  172. texture_list.append(texture_binding)
  173. else:
  174. # no layered texture simply get on the property
  175. texture_count = material_property.GetSrcObjectCount(FbxTexture.ClassId)
  176. for j in range(texture_count):
  177. texture = material_property.GetSrcObject(FbxTexture.ClassId,j)
  178. if texture:
  179. texture_id = getTextureName(texture, True)
  180. texture_binding = ' "%s": "%s",' % (binding_types[str(material_property.GetName())], texture_id)
  181. texture_list.append(texture_binding)
  182. def generate_material_string(material):
  183. #Get the implementation to see if it's a hardware shader.
  184. implementation = GetImplementation(material, "ImplementationHLSL")
  185. implementation_type = "HLSL"
  186. if not implementation:
  187. implementation = GetImplementation(material, "ImplementationCGFX")
  188. implementation_type = "CGFX"
  189. output = []
  190. if implementation:
  191. # This material is a hardware shader, skip it
  192. print("Shader materials are not supported")
  193. return ''
  194. elif material.GetClassId().Is(FbxSurfaceLambert.ClassId):
  195. ambient = str(getHex(material.Ambient.Get()))
  196. diffuse = str(getHex(material.Diffuse.Get()))
  197. emissive = str(getHex(material.Emissive.Get()))
  198. opacity = 1.0 - material.TransparencyFactor.Get()
  199. opacity = 1.0 if opacity == 0 else opacity
  200. opacity = str(opacity)
  201. transparent = BoolString(False)
  202. reflectivity = "1"
  203. output = [
  204. '\t' + LabelString( getMaterialName( material ) ) + ': {',
  205. ' "type" : "MeshLambertMaterial",',
  206. ' "parameters" : {',
  207. ' "color" : ' + diffuse + ',',
  208. ' "ambient" : ' + ambient + ',',
  209. ' "emissive" : ' + emissive + ',',
  210. ' "reflectivity" : ' + reflectivity + ',',
  211. ' "transparent" : ' + transparent + ',',
  212. ' "opacity" : ' + opacity + ',',
  213. ]
  214. elif material.GetClassId().Is(FbxSurfacePhong.ClassId):
  215. ambient = str(getHex(material.Ambient.Get()))
  216. diffuse = str(getHex(material.Diffuse.Get()))
  217. emissive = str(getHex(material.Emissive.Get()))
  218. specular = str(getHex(material.Specular.Get()))
  219. opacity = 1.0 - material.TransparencyFactor.Get()
  220. opacity = 1.0 if opacity == 0 else opacity
  221. opacity = str(opacity)
  222. shininess = str(material.Shininess.Get())
  223. transparent = BoolString(False)
  224. reflectivity = "1"
  225. bumpScale = "1"
  226. output = [
  227. '\t' + LabelString( getMaterialName( material ) ) + ': {',
  228. ' "type" : "MeshPhongMaterial",',
  229. ' "parameters" : {',
  230. ' "color" : ' + diffuse + ',',
  231. ' "ambient" : ' + ambient + ',',
  232. ' "emissive" : ' + emissive + ',',
  233. ' "specular" : ' + specular + ',',
  234. ' "shininess" : ' + shininess + ',',
  235. ' "bumpScale" : ' + bumpScale + ',',
  236. ' "reflectivity" : ' + reflectivity + ',',
  237. ' "transparent" : ' + transparent + ',',
  238. ' "opacity" : ' + opacity + ',',
  239. ]
  240. else:
  241. print("Unknown type of Material")
  242. return ''
  243. if option_textures:
  244. texture_list = []
  245. texture_count = FbxLayerElement.sTypeTextureCount()
  246. for texture_index in range(texture_count):
  247. material_property = material.FindProperty(FbxLayerElement.sTextureChannelNames(texture_index))
  248. generate_texture_bindings(material_property, texture_list)
  249. output += texture_list
  250. wireframe = BoolString(False)
  251. wireframeLinewidth = "1"
  252. output.append(' "wireframe" : ' + wireframe + ',')
  253. output.append(' "wireframeLinewidth" : ' + wireframeLinewidth)
  254. output.append(' }')
  255. output.append('}')
  256. return generateMultiLineString( output, '\n\t\t', 0 )
  257. def generate_proxy_material_string(node, material_names):
  258. output = [
  259. '\t' + LabelString( getMaterialName( node, True ) ) + ': {',
  260. ' "type" : "MeshFaceMaterial",',
  261. ' "parameters" : {',
  262. ' "materials" : ' + ArrayString( ",".join(LabelString(m) for m in material_names) ),
  263. ' }',
  264. '}'
  265. ]
  266. return generateMultiLineString( output, '\n\t\t', 0 )
  267. # #####################################################
  268. # Parse - Materials
  269. # #####################################################
  270. def extract_materials_from_node(node, material_list):
  271. name = node.GetName()
  272. mesh = node.GetNodeAttribute()
  273. node = None
  274. if mesh:
  275. node = mesh.GetNode()
  276. if node:
  277. material_count = node.GetMaterialCount()
  278. material_names = []
  279. for l in range(mesh.GetLayerCount()):
  280. materials = mesh.GetLayer(l).GetMaterials()
  281. if materials:
  282. if materials.GetReferenceMode() == FbxLayerElement.eIndex:
  283. #Materials are in an undefined external table
  284. continue
  285. for i in range(material_count):
  286. material = node.GetMaterial(i)
  287. material_names.append(getMaterialName(material))
  288. material_string = generate_material_string(material)
  289. material_list.append(material_string)
  290. if material_count > 1:
  291. proxy_material = generate_proxy_material_string(node, material_names)
  292. material_list.append(proxy_material)
  293. def generate_materials_from_hierarchy(node, material_list):
  294. if node.GetNodeAttribute() == None:
  295. pass
  296. else:
  297. attribute_type = (node.GetNodeAttribute().GetAttributeType())
  298. if attribute_type == FbxNodeAttribute.eMesh:
  299. extract_materials_from_node(node, material_list)
  300. for i in range(node.GetChildCount()):
  301. generate_materials_from_hierarchy(node.GetChild(i), material_list)
  302. def generate_material_list(scene):
  303. material_list = []
  304. node = scene.GetRootNode()
  305. if node:
  306. for i in range(node.GetChildCount()):
  307. generate_materials_from_hierarchy(node.GetChild(i), material_list)
  308. return material_list
  309. # #####################################################
  310. # Generate - Texture String
  311. # #####################################################
  312. def generate_texture_string(texture):
  313. #TODO: extract more texture properties
  314. wrap_u = texture.GetWrapModeU()
  315. wrap_v = texture.GetWrapModeV()
  316. offset = texture.GetUVTranslation()
  317. output = [
  318. '\t' + LabelString( getTextureName( texture, True ) ) + ': {',
  319. ' "url" : "' + texture.GetFileName() + '",',
  320. ' "repeat" : ' + Vector2String( (1,1) ) + ',',
  321. ' "offset" : ' + Vector2String( texture.GetUVTranslation() ) + ',',
  322. ' "magFilter" : ' + LabelString( "LinearFilter" ) + ',',
  323. ' "minFilter" : ' + LabelString( "LinearMipMapLinearFilter" ) + ',',
  324. ' "anisotropy" : ' + BoolString( True ),
  325. '}'
  326. ]
  327. return generateMultiLineString( output, '\n\t\t', 0 )
  328. # #####################################################
  329. # Parse - Textures
  330. # #####################################################
  331. def extract_material_textures(material_property, texture_list):
  332. if material_property.IsValid():
  333. #Here we have to check if it's layeredtextures, or just textures:
  334. layered_texture_count = material_property.GetSrcObjectCount(FbxLayeredTexture.ClassId)
  335. if layered_texture_count > 0:
  336. for j in range(layered_texture_count):
  337. layered_texture = material_property.GetSrcObject(FbxLayeredTexture.ClassId, j)
  338. texture_count = layered_texture.GetSrcObjectCount(FbxTexture.ClassId)
  339. for k in range(texture_count):
  340. texture = layered_texture.GetSrcObject(FbxTexture.ClassId,k)
  341. if texture:
  342. texture_string = generate_texture_string(texture)
  343. texture_list.append(texture_string)
  344. else:
  345. # no layered texture simply get on the property
  346. texture_count = material_property.GetSrcObjectCount(FbxTexture.ClassId)
  347. for j in range(texture_count):
  348. texture = material_property.GetSrcObject(FbxTexture.ClassId,j)
  349. if texture:
  350. texture_string = generate_texture_string(texture)
  351. texture_list.append(texture_string)
  352. def extract_textures_from_node(node, texture_list):
  353. name = node.GetName()
  354. mesh = node.GetNodeAttribute()
  355. #for all materials attached to this mesh
  356. material_count = mesh.GetNode().GetSrcObjectCount(FbxSurfaceMaterial.ClassId)
  357. for material_index in range(material_count):
  358. material = mesh.GetNode().GetSrcObject(FbxSurfaceMaterial.ClassId, material_index)
  359. #go through all the possible textures types
  360. if material:
  361. texture_count = FbxLayerElement.sTypeTextureCount()
  362. for texture_index in range(texture_count):
  363. material_property = material.FindProperty(FbxLayerElement.sTextureChannelNames(texture_index))
  364. extract_material_textures(material_property, texture_list)
  365. def generate_textures_from_hierarchy(node, texture_list):
  366. if node.GetNodeAttribute() == None:
  367. pass
  368. else:
  369. attribute_type = (node.GetNodeAttribute().GetAttributeType())
  370. if attribute_type == FbxNodeAttribute.eMesh:
  371. extract_textures_from_node(node, texture_list)
  372. for i in range(node.GetChildCount()):
  373. generate_textures_from_hierarchy(node.GetChild(i), texture_list)
  374. def generate_texture_list(scene):
  375. if not option_textures:
  376. return []
  377. texture_list = []
  378. node = scene.GetRootNode()
  379. if node:
  380. for i in range(node.GetChildCount()):
  381. generate_textures_from_hierarchy(node.GetChild(i), texture_list)
  382. return texture_list
  383. # #####################################################
  384. # Extract - Fbx Mesh data
  385. # #####################################################
  386. def extract_fbx_vertex_positions(mesh):
  387. control_points_count = mesh.GetControlPointsCount()
  388. control_points = mesh.GetControlPoints()
  389. positions = []
  390. for i in range(control_points_count):
  391. positions.append(convert_fbx_vec3(control_points[i]))
  392. return positions
  393. def extract_fbx_vertex_normals(mesh):
  394. # eNone The mapping is undetermined.
  395. # eByControlPoint There will be one mapping coordinate for each surface control point/vertex.
  396. # eByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part.
  397. # eByPolygon There can be only one mapping coordinate for the whole polygon.
  398. # eByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
  399. # eAllSame There can be only one mapping coordinate for the whole surface.
  400. layered_normal_indices = []
  401. layered_normal_values = []
  402. poly_count = mesh.GetPolygonCount()
  403. control_points = mesh.GetControlPoints()
  404. for l in range(mesh.GetLayerCount()):
  405. mesh_normals = mesh.GetLayer(l).GetNormals()
  406. if not mesh_normals:
  407. continue
  408. normals_array = mesh_normals.GetDirectArray()
  409. normals_count = normals_array.GetCount()
  410. if normals_count == 0:
  411. continue
  412. normal_indices = []
  413. normal_values = []
  414. # values
  415. for i in range(normals_count):
  416. normal = convert_fbx_vec3(normals_array.GetAt(i))
  417. normal_values.append(normal)
  418. # indices
  419. vertexId = 0
  420. for p in range(poly_count):
  421. poly_size = mesh.GetPolygonSize(p)
  422. poly_normals = []
  423. for v in range(poly_size):
  424. control_point_index = mesh.GetPolygonVertex(p, v)
  425. if mesh_normals.GetMappingMode() == FbxLayerElement.eByControlPoint:
  426. if mesh_normals.GetReferenceMode() == FbxLayerElement.eDirect:
  427. poly_normals.append(control_point_index)
  428. elif mesh_normals.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
  429. index = mesh_normals.GetIndexArray().GetAt(control_point_index)
  430. poly_normals.append(index)
  431. elif mesh_normals.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
  432. if mesh_normals.GetReferenceMode() == FbxLayerElement.eDirect:
  433. poly_normals.append(vertexId)
  434. elif mesh_normals.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
  435. index = mesh_normals.GetIndexArray().GetAt(vertexId)
  436. poly_normals.append(index)
  437. elif mesh_normals.GetMappingMode() == FbxLayerElement.eByPolygon or \
  438. mesh_normals.GetMappingMode() == FbxLayerElement.eAllSame or \
  439. mesh_normals.GetMappingMode() == FbxLayerElement.eNone:
  440. print("unsupported normal mapping mode for polygon vertex")
  441. vertexId += 1
  442. normal_indices.append(poly_normals)
  443. layered_normal_values.append(normal_values)
  444. layered_normal_indices.append(normal_indices)
  445. return layered_normal_values, layered_normal_indices
  446. def extract_fbx_vertex_colors(mesh):
  447. # eNone The mapping is undetermined.
  448. # eByControlPoint There will be one mapping coordinate for each surface control point/vertex.
  449. # eByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part.
  450. # eByPolygon There can be only one mapping coordinate for the whole polygon.
  451. # eByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
  452. # eAllSame There can be only one mapping coordinate for the whole surface.
  453. layered_color_indices = []
  454. layered_color_values = []
  455. poly_count = mesh.GetPolygonCount()
  456. control_points = mesh.GetControlPoints()
  457. for l in range(mesh.GetLayerCount()):
  458. mesh_colors = mesh.GetLayer(l).GetVertexColors()
  459. if not mesh_colors:
  460. continue
  461. colors_array = mesh_colors.GetDirectArray()
  462. colors_count = colors_array.GetCount()
  463. if colors_count == 0:
  464. continue
  465. color_indices = []
  466. color_values = []
  467. # values
  468. for i in range(colors_count):
  469. color = convert_fbx_color(colors_array.GetAt(i))
  470. color_values.append(color)
  471. # indices
  472. vertexId = 0
  473. for p in range(poly_count):
  474. poly_size = mesh.GetPolygonSize(p)
  475. poly_colors = []
  476. for v in range(poly_size):
  477. control_point_index = mesh.GetPolygonVertex(p, v)
  478. if mesh_colors.GetMappingMode() == FbxLayerElement.eByControlPoint:
  479. if mesh_colors.GetReferenceMode() == FbxLayerElement.eDirect:
  480. poly_colors.append(control_point_index)
  481. elif mesh_colors.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
  482. index = mesh_colors.GetIndexArray().GetAt(control_point_index)
  483. poly_colors.append(index)
  484. elif mesh_colors.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
  485. if mesh_colors.GetReferenceMode() == FbxLayerElement.eDirect:
  486. poly_colors.append(vertexId)
  487. elif mesh_colors.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
  488. index = mesh_colors.GetIndexArray().GetAt(vertexId)
  489. poly_colors.append(index)
  490. elif mesh_colors.GetMappingMode() == FbxLayerElement.eByPolygon or \
  491. mesh_colors.GetMappingMode() == FbxLayerElement.eAllSame or \
  492. mesh_colors.GetMappingMode() == FbxLayerElement.eNone:
  493. print("unsupported color mapping mode for polygon vertex")
  494. vertexId += 1
  495. color_indices.append(poly_colors)
  496. layered_color_values.append(color_values)
  497. layered_color_indices.append(color_indices)
  498. return layered_color_values, layered_color_indices
  499. def extract_fbx_vertex_uvs(mesh):
  500. # eNone The mapping is undetermined.
  501. # eByControlPoint There will be one mapping coordinate for each surface control point/vertex.
  502. # eByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part.
  503. # eByPolygon There can be only one mapping coordinate for the whole polygon.
  504. # eByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
  505. # eAllSame There can be only one mapping coordinate for the whole surface.
  506. layered_uv_indices = []
  507. layered_uv_values = []
  508. poly_count = mesh.GetPolygonCount()
  509. control_points = mesh.GetControlPoints()
  510. for l in range(mesh.GetLayerCount()):
  511. mesh_uvs = mesh.GetLayer(l).GetUVs()
  512. if not mesh_uvs:
  513. continue
  514. uvs_array = mesh_uvs.GetDirectArray()
  515. uvs_count = uvs_array.GetCount()
  516. if uvs_count == 0:
  517. continue
  518. uv_indices = []
  519. uv_values = []
  520. # values
  521. for i in range(uvs_count):
  522. uv = convert_fbx_vec2(uvs_array.GetAt(i))
  523. uv_values.append(uv)
  524. # indices
  525. vertexId = 0
  526. for p in range(poly_count):
  527. poly_size = mesh.GetPolygonSize(p)
  528. poly_uvs = []
  529. for v in range(poly_size):
  530. control_point_index = mesh.GetPolygonVertex(p, v)
  531. if mesh_uvs.GetMappingMode() == FbxLayerElement.eByControlPoint:
  532. if mesh_uvs.GetReferenceMode() == FbxLayerElement.eDirect:
  533. poly_uvs.append(control_point_index)
  534. elif mesh_uvs.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
  535. index = mesh_uvs.GetIndexArray().GetAt(control_point_index)
  536. poly_uvs.append(index)
  537. elif mesh_uvs.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
  538. uv_texture_index = mesh.GetTextureUVIndex(p, v)
  539. if mesh_uvs.GetReferenceMode() == FbxLayerElement.eDirect or \
  540. mesh_uvs.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
  541. poly_uvs.append(uv_texture_index)
  542. elif mesh_uvs.GetMappingMode() == FbxLayerElement.eByPolygon or \
  543. mesh_uvs.GetMappingMode() == FbxLayerElement.eAllSame or \
  544. mesh_uvs.GetMappingMode() == FbxLayerElement.eNone:
  545. print("unsupported uv mapping mode for polygon vertex")
  546. vertexId += 1
  547. uv_indices.append(poly_uvs)
  548. layered_uv_values.append(uv_values)
  549. layered_uv_indices.append(uv_indices)
  550. return layered_uv_values, layered_uv_indices
  551. # #####################################################
  552. # Generate - Mesh String
  553. # #####################################################
  554. def generate_mesh_bounding_box(mesh):
  555. control_points_count = mesh.GetControlPointsCount()
  556. control_points = mesh.GetControlPoints()
  557. minx = 0
  558. miny = 0
  559. minz = 0
  560. maxx = 0
  561. maxy = 0
  562. maxz = 0
  563. for i in range(control_points_count):
  564. vertex = control_points[i]
  565. if vertex[0] < minx:
  566. minx = vertex[0]
  567. if vertex[1] < miny:
  568. miny = vertex[1]
  569. if vertex[2] < minz:
  570. minz = vertex[2]
  571. if vertex[0] > maxx:
  572. maxx = vertex[0]
  573. if vertex[1] > maxy:
  574. maxy = vertex[1]
  575. if vertex[2] > maxz:
  576. maxz = vertex[2]
  577. return [minx, miny, minz], [maxx, maxy, maxz]
  578. def generate_mesh_face(mesh, vertex_indices, polygon_index, normals, colors, uv_layers, material_count, material_is_same):
  579. isTriangle = ( len(vertex_indices) == 3 )
  580. nVertices = 3 if isTriangle else 4
  581. hasMaterial = material_count > 0
  582. hasFaceUvs = False
  583. hasFaceVertexUvs = len(uv_layers) > 0
  584. hasFaceNormals = False # don't export any face normals (as they are computed in engine)
  585. hasFaceVertexNormals = len(normals) > 0
  586. hasFaceColors = False
  587. hasFaceVertexColors = len(colors) > 0
  588. faceType = 0
  589. faceType = setBit(faceType, 0, not isTriangle)
  590. faceType = setBit(faceType, 1, hasMaterial)
  591. faceType = setBit(faceType, 2, hasFaceUvs)
  592. faceType = setBit(faceType, 3, hasFaceVertexUvs)
  593. faceType = setBit(faceType, 4, hasFaceNormals)
  594. faceType = setBit(faceType, 5, hasFaceVertexNormals)
  595. faceType = setBit(faceType, 6, hasFaceColors)
  596. faceType = setBit(faceType, 7, hasFaceVertexColors)
  597. faceData = []
  598. # order is important, must match order in JSONLoader
  599. # face type
  600. # vertex indices
  601. # material index
  602. # face uvs index
  603. # face vertex uvs indices
  604. # face color index
  605. # face vertex colors indices
  606. faceData.append(faceType)
  607. # must clamp in case on polygons bigger than quads
  608. for i in range(nVertices):
  609. index = vertex_indices[i]
  610. faceData.append(index)
  611. if hasMaterial:
  612. material_id = 0
  613. if not material_is_same:
  614. for l in range(mesh.GetLayerCount()):
  615. materials = mesh.GetLayer(l).GetMaterials()
  616. if materials:
  617. material_id = materials.GetIndexArray().GetAt(polygon_index)
  618. break
  619. faceData.append( material_id )
  620. if hasFaceVertexUvs:
  621. for layer_index, uvs in enumerate(uv_layers):
  622. polygon_uvs = uvs[polygon_index]
  623. for i in range(nVertices):
  624. index = polygon_uvs[i]
  625. faceData.append(index)
  626. if hasFaceVertexNormals:
  627. polygon_normals = normals[polygon_index]
  628. for i in range(nVertices):
  629. index = polygon_normals[i]
  630. faceData.append(index)
  631. if hasFaceVertexColors:
  632. polygon_colors = colors[polygon_index]
  633. for i in range(nVertices):
  634. index = polygon_colors[i]
  635. faceData.append(index)
  636. return ",".join( map(str, faceData) )
  637. def generate_mesh_faces(mesh, normals, colors, uv_layers):
  638. has_same_material_for_all_polygons = True
  639. for l in range(mesh.GetLayerCount()):
  640. materials = mesh.GetLayer(l).GetMaterials()
  641. if materials:
  642. if materials.GetMappingMode() == FbxLayerElement.eByPolygon:
  643. has_same_material_for_all_polygons = False
  644. break
  645. node = mesh.GetNode()
  646. if node:
  647. material_count = node.GetMaterialCount()
  648. poly_count = mesh.GetPolygonCount()
  649. control_points = mesh.GetControlPoints()
  650. faces = []
  651. for p in range(poly_count):
  652. poly_size = mesh.GetPolygonSize(p)
  653. vertex_indices = []
  654. for v in range(poly_size):
  655. control_point_index = mesh.GetPolygonVertex(p, v)
  656. vertex_indices.append(control_point_index)
  657. face = generate_mesh_face(mesh, vertex_indices, p, normals, colors, uv_layers, material_count, has_same_material_for_all_polygons)
  658. faces.append(face)
  659. return faces
  660. def generate_mesh_string(node):
  661. mesh = node.GetNodeAttribute()
  662. vertices = extract_fbx_vertex_positions(mesh)
  663. aabb_min, aabb_max = generate_mesh_bounding_box(mesh)
  664. normal_values, normal_indices = extract_fbx_vertex_normals(mesh)
  665. color_values, color_indices = extract_fbx_vertex_colors(mesh)
  666. uv_values, uv_indices = extract_fbx_vertex_uvs(mesh)
  667. # Three.js only supports one layer of normals
  668. if len(normal_values) > 0:
  669. normal_values = normal_values[0]
  670. normal_indices = normal_indices[0]
  671. # Three.js only supports one layer of colors
  672. if len(color_values) > 0:
  673. color_values = color_values[0]
  674. color_indices = color_indices[0]
  675. faces = generate_mesh_faces(mesh, normal_indices, color_indices, uv_indices)
  676. nuvs = []
  677. for layer_index, uvs in enumerate(uv_values):
  678. nuvs.append(str(len(uvs)))
  679. nvertices = len(vertices)
  680. nnormals = len(normal_values)
  681. ncolors = len(color_values)
  682. nfaces = len(faces)
  683. nuvs = ",".join(nuvs)
  684. vertices = ",".join(Vector3String(v, True) for v in vertices)
  685. normals = ",".join(Vector3String(v, True) for v in normal_values)
  686. colors = ",".join(Vector3String(v, True) for v in color_values)
  687. faces = ",".join(faces)
  688. uvs = generate_uvs(uv_values)
  689. aabb_min = ",".join(str(f) for f in aabb_min)
  690. aabb_max = ",".join(str(f) for f in aabb_max)
  691. if option_geometry:
  692. output = [
  693. '"boundingBox" : {',
  694. ' "min" : ' + ArrayString(aabb_min) + ',',
  695. ' "max" : ' + ArrayString(aabb_max),
  696. '},',
  697. '"scale" : ' + str( 1 ) + ',',
  698. '"materials" : ' + ArrayString("") + ',',
  699. '"vertices" : ' + ArrayString(vertices) + ',',
  700. '"normals" : ' + ArrayString(normals) + ',',
  701. '"colors" : ' + ArrayString(colors) + ',',
  702. '"uvs" : ' + ArrayString(uvs) + ',',
  703. '"faces" : ' + ArrayString(faces),
  704. ]
  705. return generateMultiLineString( output, '\n\t', 0 )
  706. else:
  707. output = [
  708. '\t' + LabelString( getEmbedName( node, True ) ) + ' : {',
  709. ' "metadata" : {',
  710. ' "vertices" : ' + str(nvertices) + ',',
  711. ' "normals" : ' + str(nnormals) + ',',
  712. ' "colors" : ' + str(ncolors) + ',',
  713. ' "faces" : ' + str(nfaces) + ',',
  714. ' "uvs" : ' + ArrayString(nuvs),
  715. ' },',
  716. ' "boundingBox" : {',
  717. ' "min" : ' + ArrayString(aabb_min) + ',',
  718. ' "max" : ' + ArrayString(aabb_max),
  719. ' },',
  720. ' "scale" : ' + str( 1 ) + ',',
  721. ' "materials" : ' + ArrayString("") + ',',
  722. ' "vertices" : ' + ArrayString(vertices) + ',',
  723. ' "normals" : ' + ArrayString(normals) + ',',
  724. ' "colors" : ' + ArrayString(colors) + ',',
  725. ' "uvs" : ' + ArrayString(uvs) + ',',
  726. ' "faces" : ' + ArrayString(faces),
  727. '}'
  728. ]
  729. return generateMultiLineString( output, '\n\t\t', 0 )
  730. def generate_mesh_string2(node):
  731. mesh = node.GetNodeAttribute()
  732. aabb_min, aabb_max = generate_mesh_bounding_box(mesh)
  733. mesh_list = [mesh]
  734. vertices, normals, colors, uvs, faces = process_mesh_geometry(mesh_list)
  735. nuvs = []
  736. for uv_layer in uvs:
  737. nuvs.append(str(len(uv_layer)))
  738. nvertices = len(vertices)
  739. nnormals = len(normal_values)
  740. ncolors = len(color_values)
  741. nfaces = len(faces)
  742. nuvs = ",".join(nuvs)
  743. output = [
  744. '\t' + LabelString( getEmbedName( node, True ) ) + ' : {',
  745. ' "metadata" : {',
  746. ' "vertices" : ' + str(nvertices) + ',',
  747. ' "normals" : ' + str(nnormals) + ',',
  748. ' "colors" : ' + str(ncolors) + ',',
  749. ' "faces" : ' + str(nfaces) + ',',
  750. ' "uvs" : ' + ArrayString(nuvs),
  751. ' },',
  752. ' "boundingBox" : {',
  753. ' "min" : ' + ArrayString(aabb_min) + ',',
  754. ' "max" : ' + ArrayString(aabb_max),
  755. ' },',
  756. ' "scale" : ' + str( 1 ) + ',',
  757. ' "materials" : ' + ArrayString("") + ',',
  758. ' "vertices" : ' + ArrayString(vertices) + ',',
  759. ' "normals" : ' + ArrayString(normals) + ',',
  760. ' "colors" : ' + ArrayString(colors) + ',',
  761. ' "uvs" : ' + ArrayString(uvs) + ',',
  762. ' "faces" : ' + ArrayString(faces),
  763. '}'
  764. ]
  765. return generateMultiLineString( output, '\n\t\t', 0 )
  766. # #####################################################
  767. # Process - Mesh Geometry
  768. # #####################################################
  769. def append_non_duplicate_normals(source_normals, dest_normals, count):
  770. for normal in source_normals:
  771. key = round_vec3(normal)
  772. if key not in dest_normals:
  773. dest_normals[key] = count
  774. count += 1
  775. return count
  776. def append_non_duplicate_colors(source_colors, dest_colors, count):
  777. for color in source_colors:
  778. key = getHex(color)
  779. if key not in dest_colors:
  780. dest_colors[key] = count
  781. count += 1
  782. return count
  783. def append_non_duplicate_uvs(source_uvs, dest_uvs, counts):
  784. source_layer_count = len(source_uvs)
  785. for layer_index in range(source_layer_count):
  786. dest_layer_count = len(dest_uvs)
  787. if dest_layer_count <= layer_index:
  788. dest_uv_layer = {}
  789. count = 0
  790. dest_uvs.append(uvs)
  791. counts.append(count)
  792. else:
  793. dest_uv_layer = dest_uvs[layer_index]
  794. count = nuvs[layer_index]
  795. source_uv_layer = source_uvs[layer_index]
  796. for uv in source_uv_layer:
  797. key = round_vec2(uv)
  798. if key not in dest_uv_layer:
  799. dest_uv_layer[key] = count
  800. count += 1
  801. counts[layer_index] = count
  802. return counts
  803. def process_mesh_normals(mesh_list):
  804. normals_dictionary = {}
  805. nnormals = 0
  806. for mesh in mesh_list:
  807. normal_values, normal_indices = extract_fbx_vertex_normals(mesh)
  808. # Three.js only supports one layer of normals
  809. if len(normal_values) > 0:
  810. normal_values = normal_values[0]
  811. normal_indices = normal_indices[0]
  812. # Remove the Fbx indices, we will make our own
  813. mesh_normals = []
  814. for i in range(len(normal_indices)):
  815. mesh_normals.append(normal_values[normal_indices[i])
  816. if len(mesh_normals) > 0
  817. nnormals = append_non_duplicate_normals(mesh_normals, normals_dictionary, nnormals)
  818. return normals_dictionary
  819. def process_mesh_colors(mesh_list):
  820. colors_dictionary = {}
  821. ncolors = 0
  822. for mesh in mesh_list:
  823. color_values, color_indices = extract_fbx_vertex_colors(mesh)
  824. # Three.js only supports one layer of colors
  825. if len(color_values) > 0:
  826. color_values = color_values[0]
  827. color_indices = color_indices[0]
  828. # Remove the Fbx indices, we will make our own
  829. mesh_colors = []
  830. for i in range(len(color_indices)):
  831. mesh_colors.append(color_values[color_indices[i])
  832. if len(mesh_colors) > 0
  833. ncolors = append_non_duplicate_colors(mesh_colors, colors_dictionary, ncolors)
  834. return colors_dictionary
  835. def process_mesh_uv_layers(mesh_list):
  836. uvs_dictionary_layers = []
  837. nuvs_list = []
  838. for mesh in mesh_list:
  839. uv_values, uv_indices = extract_fbx_vertex_uvs(mesh)
  840. # Remove the Fbx indices, we will make our own
  841. mesh_uvs = []
  842. for l in range(len(uv_indices)):
  843. layer_uv_values = uv_values[l]
  844. layer_uv_indices = uv_indices[l]
  845. mesh_layer_uvs = []
  846. for i in range(len(layer_uv_indices)):
  847. mesh_layer_uvs.append(layer_uv_values[layer_uv_indices[i])
  848. mesh_uvs.append(mesh_layer_uvs)
  849. if len(mesh_uvs) > 0
  850. nuvs_list = append_non_duplicate_uvs(mesh_uvs, uvs_dictionary_layers, nuvs_list)
  851. return uvs_dictionary_layers
  852. def process_mesh_vertices(mesh_list):
  853. vertices = []
  854. for mesh in mesh_list:
  855. node = mesh.GetNode()
  856. mesh_vertices = extract_fbx_vertex_positions(mesh)
  857. if option_geometry:
  858. # FbxMeshes are local to their node, we need the vertices in global space
  859. # when scene nodes are not exported
  860. transform = node.EvaluateGlobalTransform()
  861. transform = FbxMatrix(transform)
  862. for i in range(len(mesh_vertices)):
  863. v = mesh_vertices[i]
  864. position = FbxVector4(v[0], v[1], v[2])
  865. position = transform.MultNormalize(position)
  866. mesh_vertices[i] = convert_fbx_vec3(position)
  867. vertices.extend(mesh_vertices[:])
  868. return vertices
  869. def process_mesh_polygons(mesh_list, normals_dictionary, colors_dictionary, uv_layers_dictionary):
  870. vertex_offset = 0
  871. for mesh in mesh_list:
  872. poly_count = mesh.GetPolygonCount()
  873. control_points = mesh.GetControlPoints()
  874. faces = []
  875. for p in range(poly_count):
  876. poly_size = mesh.GetPolygonSize(p)
  877. vertex_indices = []
  878. for v in range(poly_size):
  879. control_point_index = mesh.GetPolygonVertex(p, v)
  880. vertex_indices.append(control_point_index)
  881. face = generate_mesh_face(mesh, vertex_indices, p, normals, colors, uv_layers, material_count, has_same_material_for_all_polygons)
  882. faces.append(face)
  883. vertex_offset += len(mesh_vertices)
  884. return faces
  885. # #####################################################
  886. # Generate - Mesh List
  887. # #####################################################
  888. def generate_mesh_list_from_hierarchy(node, mesh_list):
  889. if node.GetNodeAttribute() == None:
  890. pass
  891. else:
  892. attribute_type = (node.GetNodeAttribute().GetAttributeType())
  893. if attribute_type == FbxNodeAttribute.eMesh or \
  894. attribute_type == FbxNodeAttribute.eNurbs or \
  895. attribute_type == FbxNodeAttribute.eNurbsSurface or \
  896. attribute_type == FbxNodeAttribute.ePatch:
  897. if attribute_type != FbxNodeAttribute.eMesh:
  898. converter.TriangulateInPlace(node);
  899. mesh_list.append(node)
  900. for i in range(node.GetChildCount()):
  901. generate_embed_list_from_hierarchy(node.GetChild(i), mesh_list)
  902. def generate_mesh_list(scene):
  903. mesh_list = []
  904. node = scene.GetRootNode()
  905. if node:
  906. for i in range(node.GetChildCount()):
  907. generate_mesh_list_from_hierarchy(node.GetChild(i), mesh_list)
  908. return embed_list
  909. # #####################################################
  910. # Generate - Embeds
  911. # #####################################################
  912. def generate_embed_list_from_hierarchy(node, embed_list):
  913. if node.GetNodeAttribute() == None:
  914. pass
  915. else:
  916. attribute_type = (node.GetNodeAttribute().GetAttributeType())
  917. if attribute_type == FbxNodeAttribute.eMesh or \
  918. attribute_type == FbxNodeAttribute.eNurbs or \
  919. attribute_type == FbxNodeAttribute.eNurbsSurface or \
  920. attribute_type == FbxNodeAttribute.ePatch:
  921. if attribute_type != FbxNodeAttribute.eMesh:
  922. converter.TriangulateInPlace(node);
  923. embed_string = generate_mesh_string(node)
  924. embed_list.append(embed_string)
  925. for i in range(node.GetChildCount()):
  926. generate_embed_list_from_hierarchy(node.GetChild(i), embed_list)
  927. def generate_embed_list(scene):
  928. embed_list = []
  929. node = scene.GetRootNode()
  930. if node:
  931. for i in range(node.GetChildCount()):
  932. generate_embed_list_from_hierarchy(node.GetChild(i), embed_list)
  933. return embed_list
  934. # #####################################################
  935. # Generate - Geometries
  936. # #####################################################
  937. def generate_geometry_string(node):
  938. output = [
  939. '\t' + LabelString( getGeometryName( node, True ) ) + ' : {',
  940. ' "type" : "embedded",',
  941. ' "id" : ' + LabelString( getEmbedName( node, True ) ),
  942. '}'
  943. ]
  944. return generateMultiLineString( output, '\n\t\t', 0 )
  945. def generate_geometry_list_from_hierarchy(node, geometry_list):
  946. if node.GetNodeAttribute() == None:
  947. pass
  948. else:
  949. attribute_type = (node.GetNodeAttribute().GetAttributeType())
  950. if attribute_type == FbxNodeAttribute.eMesh:
  951. geometry_string = generate_geometry_string(node)
  952. geometry_list.append(geometry_string)
  953. for i in range(node.GetChildCount()):
  954. generate_geometry_list_from_hierarchy(node.GetChild(i), geometry_list)
  955. def generate_geometry_list(scene):
  956. geometry_list = []
  957. node = scene.GetRootNode()
  958. if node:
  959. for i in range(node.GetChildCount()):
  960. generate_geometry_list_from_hierarchy(node.GetChild(i), geometry_list)
  961. return geometry_list
  962. # #####################################################
  963. # Generate - Camera Names
  964. # #####################################################
  965. def generate_camera_name_list_from_hierarchy(node, camera_list):
  966. if node.GetNodeAttribute() == None:
  967. pass
  968. else:
  969. attribute_type = (node.GetNodeAttribute().GetAttributeType())
  970. if attribute_type == FbxNodeAttribute.eCamera:
  971. camera_string = getObjectName(node)
  972. camera_list.append(camera_string)
  973. for i in range(node.GetChildCount()):
  974. generate_camera_name_list_from_hierarchy(node.GetChild(i), camera_list)
  975. def generate_camera_name_list(scene):
  976. camera_list = []
  977. node = scene.GetRootNode()
  978. if node:
  979. for i in range(node.GetChildCount()):
  980. generate_camera_name_list_from_hierarchy(node.GetChild(i), camera_list)
  981. return camera_list
  982. # #####################################################
  983. # Generate - Light Object
  984. # #####################################################
  985. def generate_default_light_string(padding):
  986. direction = (1,1,1)
  987. color = (1,1,1)
  988. intensity = 80.0
  989. output = [
  990. '\t\t' + LabelString( 'default_light' ) + ' : {',
  991. ' "type" : "DirectionalLight",',
  992. ' "color" : ' + str(getHex(color)) + ',',
  993. ' "intensity" : ' + str(intensity/100.0) + ',',
  994. ' "direction" : ' + Vector3String( direction ) + ',',
  995. ' "target" : ' + LabelString( getObjectName( None ) ),
  996. ' }'
  997. ]
  998. return generateMultiLineString( output, '\n\t\t', padding )
  999. def generate_light_string(node, padding):
  1000. light = node.GetNodeAttribute()
  1001. light_types = ["point", "directional", "spot", "area", "volume"]
  1002. light_type = light_types[light.LightType.Get()]
  1003. transform = node.EvaluateLocalTransform()
  1004. position = transform.GetT()
  1005. output = []
  1006. if light_type == "directional":
  1007. # Three.js directional lights emit light from a point in 3d space to a target node or the origin.
  1008. # When there is no target, we need to take a point, one unit away from the origin, and move it
  1009. # into the right location so that the origin acts like the target
  1010. if node.GetTarget():
  1011. direction = position
  1012. else:
  1013. translation = FbxVector4(0,0,0,0)
  1014. scale = FbxVector4(1,1,1,1)
  1015. rotation = transform.GetR()
  1016. matrix = FbxMatrix(translation, rotation, scale)
  1017. direction = matrix.MultNormalize(global_up_vector)
  1018. output = [
  1019. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1020. ' "type" : "DirectionalLight",',
  1021. ' "color" : ' + str(getHex(light.Color.Get())) + ',',
  1022. ' "intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
  1023. ' "direction" : ' + Vector3String( direction ) + ',',
  1024. ' "target" : ' + LabelString( getObjectName( node.GetTarget() ) ) + ( ',' if node.GetChildCount() > 0 else '' )
  1025. ]
  1026. elif light_type == "point":
  1027. output = [
  1028. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1029. ' "type" : "PointLight",',
  1030. ' "color" : ' + str(getHex(light.Color.Get())) + ',',
  1031. ' "intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
  1032. ' "position" : ' + Vector3String( position ) + ',',
  1033. ' "distance" : ' + str(light.FarAttenuationEnd.Get()) + ( ',' if node.GetChildCount() > 0 else '' )
  1034. ]
  1035. elif light_type == "spot":
  1036. output = [
  1037. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1038. ' "type" : "SpotLight",',
  1039. ' "color" : ' + str(getHex(light.Color.Get())) + ',',
  1040. ' "intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
  1041. ' "position" : ' + Vector3String( position ) + ',',
  1042. ' "distance" : ' + str(light.FarAttenuationEnd.Get()) + ',',
  1043. ' "angle" : ' + str((light.OuterAngle.Get()*math.pi)/180) + ',',
  1044. ' "exponent" : ' + str(light.DecayType.Get()) + ',',
  1045. ' "target" : ' + LabelString( getObjectName( node.GetTarget() ) ) + ( ',' if node.GetChildCount() > 0 else '' )
  1046. ]
  1047. return generateMultiLineString( output, '\n\t\t', padding )
  1048. def generate_ambient_light_string(scene):
  1049. scene_settings = scene.GetGlobalSettings()
  1050. ambient_color = scene_settings.GetAmbientColor()
  1051. ambient_color = (ambient_color.mRed, ambient_color.mGreen, ambient_color.mBlue)
  1052. if ambient_color[0] == 0 and ambient_color[1] == 0 and ambient_color[2] == 0:
  1053. return None
  1054. class AmbientLight:
  1055. def GetName(self):
  1056. return "AmbientLight"
  1057. node = AmbientLight()
  1058. output = [
  1059. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1060. ' "type" : "AmbientLight",',
  1061. ' "color" : ' + str(getHex(ambient_color)),
  1062. '}'
  1063. ]
  1064. return generateMultiLineString( output, '\n\t\t', 0 )
  1065. # #####################################################
  1066. # Generate - Camera Object
  1067. # #####################################################
  1068. def generate_default_camera_string(padding):
  1069. position = (100, 100, 100)
  1070. near = 0.1
  1071. far = 1000
  1072. fov = 75
  1073. output = [
  1074. '\t\t' + LabelString( 'default_camera' ) + ' : {',
  1075. ' "type" : "PerspectiveCamera",',
  1076. ' "fov" : ' + str(fov) + ',',
  1077. ' "near" : ' + str(near) + ',',
  1078. ' "far" : ' + str(far) + ',',
  1079. ' "position" : ' + Vector3String( position ),
  1080. ' }'
  1081. ]
  1082. return generateMultiLineString( output, '\n\t\t', padding )
  1083. def generate_camera_string(node, padding):
  1084. camera = node.GetNodeAttribute()
  1085. target_node = node.GetTarget()
  1086. target = ""
  1087. if target_node:
  1088. transform = target.EvaluateLocalTransform()
  1089. target = transform.GetT()
  1090. else:
  1091. target = camera.InterestPosition.Get()
  1092. position = camera.Position.Get()
  1093. projection_types = [ "perspective", "orthogonal" ]
  1094. projection = projection_types[camera.ProjectionType.Get()]
  1095. near = camera.NearPlane.Get()
  1096. far = camera.FarPlane.Get()
  1097. output = []
  1098. if projection == "perspective":
  1099. aspect = camera.PixelAspectRatio.Get()
  1100. fov = camera.FieldOfView.Get()
  1101. output = [
  1102. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1103. ' "type" : "PerspectiveCamera",',
  1104. ' "fov" : ' + str(fov) + ',',
  1105. ' "aspect" : ' + str(aspect) + ',',
  1106. ' "near" : ' + str(near) + ',',
  1107. ' "far" : ' + str(far) + ',',
  1108. ' "position" : ' + Vector3String( position ) + ( ',' if node.GetChildCount() > 0 else '' )
  1109. ]
  1110. elif projection == "orthogonal":
  1111. left = ""
  1112. right = ""
  1113. top = ""
  1114. bottom = ""
  1115. output = [
  1116. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1117. ' "type" : "OrthographicCamera",',
  1118. ' "left" : ' + left + ',',
  1119. ' "right" : ' + right + ',',
  1120. ' "top" : ' + top + ',',
  1121. ' "bottom" : ' + bottom + ',',
  1122. ' "near" : ' + str(near) + ',',
  1123. ' "far" : ' + str(far) + ',',
  1124. ' "position" : ' + Vector3String( position ) + ( ',' if node.GetChildCount() > 0 else '' )
  1125. ]
  1126. return generateMultiLineString( output, '\n\t\t', padding )
  1127. # #####################################################
  1128. # Generate - Mesh Object
  1129. # #####################################################
  1130. def generate_mesh_object_string(node, padding):
  1131. mesh = node.GetNodeAttribute()
  1132. transform = node.EvaluateLocalTransform()
  1133. position = transform.GetT()
  1134. scale = transform.GetS()
  1135. rotation = getRadians(transform.GetR())
  1136. material_count = node.GetMaterialCount()
  1137. material_name = ""
  1138. if material_count > 0:
  1139. material_names = []
  1140. for l in range(mesh.GetLayerCount()):
  1141. materials = mesh.GetLayer(l).GetMaterials()
  1142. if materials:
  1143. if materials.GetReferenceMode() == FbxLayerElement.eIndex:
  1144. #Materials are in an undefined external table
  1145. continue
  1146. for i in range(material_count):
  1147. material = node.GetMaterial(i)
  1148. material_names.append( getMaterialName(material) )
  1149. #If this mesh has more than one material, use a proxy material
  1150. material_name = getMaterialName( node, True) if material_count > 1 else material_names[0]
  1151. output = [
  1152. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1153. ' "geometry" : ' + LabelString( getGeometryName( node, True ) ) + ',',
  1154. ' "material" : ' + LabelString( material_name ) + ',',
  1155. ' "position" : ' + Vector3String( position ) + ',',
  1156. ' "rotation" : ' + Vector3String( rotation ) + ',',
  1157. ' "scale" : ' + Vector3String( scale ) + ',',
  1158. ' "visible" : ' + getObjectVisible( node ) + ( ',' if node.GetChildCount() > 0 else '' )
  1159. ]
  1160. return generateMultiLineString( output, '\n\t\t', padding )
  1161. # #####################################################
  1162. # Generate - Object
  1163. # #####################################################
  1164. def generate_object_string(node, padding):
  1165. node_types = ["Unknown", "Null", "Marker", "Skeleton", "Mesh", "Nurbs", "Patch", "Camera",
  1166. "CameraStereo", "CameraSwitcher", "Light", "OpticalReference", "OpticalMarker", "NurbsCurve",
  1167. "TrimNurbsSurface", "Boundary", "NurbsSurface", "Shape", "LODGroup", "SubDiv", "CachedEffect", "Line"]
  1168. transform = node.EvaluateLocalTransform()
  1169. position = transform.GetT()
  1170. scale = transform.GetS()
  1171. rotation = getRadians(transform.GetR())
  1172. node_type = ""
  1173. if node.GetNodeAttribute() == None:
  1174. node_type = "Null"
  1175. else:
  1176. node_type = node_types[node.GetNodeAttribute().GetAttributeType()]
  1177. output = [
  1178. '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
  1179. ' "fbx_type" : ' + LabelString( node_type ) + ',',
  1180. ' "position" : ' + Vector3String( position ) + ',',
  1181. ' "rotation" : ' + Vector3String( rotation ) + ',',
  1182. ' "scale" : ' + Vector3String( scale ) + ',',
  1183. ' "visible" : ' + getObjectVisible( node ) + ( ',' if node.GetChildCount() > 0 else '' )
  1184. ]
  1185. return generateMultiLineString( output, '\n\t\t', padding )
  1186. # #####################################################
  1187. # Parse - Objects
  1188. # #####################################################
  1189. def generate_object_hierarchy(node, object_list, pad, siblings_left):
  1190. object_count = 0
  1191. if node.GetNodeAttribute() == None:
  1192. object_string = generate_object_string(node, pad)
  1193. object_list.append(object_string)
  1194. object_count += 1
  1195. else:
  1196. attribute_type = (node.GetNodeAttribute().GetAttributeType())
  1197. if attribute_type == FbxNodeAttribute.eMesh:
  1198. object_string = generate_mesh_object_string(node, pad)
  1199. object_list.append(object_string)
  1200. object_count += 1
  1201. elif attribute_type == FbxNodeAttribute.eLight:
  1202. object_string = generate_light_string(node, pad)
  1203. object_list.append(object_string)
  1204. object_count += 1
  1205. elif attribute_type == FbxNodeAttribute.eCamera:
  1206. object_string = generate_camera_string(node, pad)
  1207. object_list.append(object_string)
  1208. object_count += 1
  1209. else:
  1210. object_string = generate_object_string(node, pad)
  1211. object_list.append(object_string)
  1212. object_count += 1
  1213. if node.GetChildCount() > 0:
  1214. object_list.append( PaddingString( pad + 1 ) + '\t\t"children" : {\n' )
  1215. for i in range(node.GetChildCount()):
  1216. object_count += generate_object_hierarchy(node.GetChild(i), object_list, pad + 2, node.GetChildCount() - i - 1)
  1217. object_list.append( PaddingString( pad + 1 ) + '\t\t}' )
  1218. object_list.append( PaddingString( pad ) + '\t\t}' + (',\n' if siblings_left > 0 else ''))
  1219. return object_count
  1220. def generate_scene_objects_string(scene):
  1221. object_count = 0
  1222. object_list = []
  1223. ambient_light = generate_ambient_light_string(scene)
  1224. if ambient_light:
  1225. if scene.GetNodeCount() > 0 or option_default_light or option_default_camera:
  1226. ambient_light += (',\n')
  1227. object_list.append(ambient_light)
  1228. object_count += 1
  1229. if option_default_light:
  1230. default_light = generate_default_light_string(0)
  1231. if scene.GetNodeCount() > 0 or option_default_camera:
  1232. default_light += (',\n')
  1233. object_list.append(default_light)
  1234. object_count += 1
  1235. if option_default_camera:
  1236. default_camera = generate_default_camera_string(0)
  1237. if scene.GetNodeCount() > 0:
  1238. default_camera += (',\n')
  1239. object_list.append(default_camera)
  1240. object_count += 1
  1241. node = scene.GetRootNode()
  1242. if node:
  1243. for i in range(node.GetChildCount()):
  1244. object_count += generate_object_hierarchy(node.GetChild(i), object_list, 0, node.GetChildCount() - i - 1)
  1245. return "\n".join(object_list), object_count
  1246. # #####################################################
  1247. # Parse - Geometry
  1248. # #####################################################
  1249. def extract_geometry(scene, filename):
  1250. embeds_list = generate_embed_list(scene)
  1251. #TODO: update the Three.js Geometry Format to support multiple geometries?
  1252. embeds = [ embeds_list[0] ] if len(embeds_list) > 0 else []
  1253. embeds = generateMultiLineString( embeds_list, ",\n\n\t", 0 )
  1254. output = [
  1255. '{',
  1256. ' "metadata": {',
  1257. ' "formatVersion" : 3.2,',
  1258. ' "type" : "geometry",',
  1259. ' "generatedBy" : "convert-to-threejs.py"',
  1260. ' },',
  1261. '',
  1262. '\t' + embeds,
  1263. '}'
  1264. ]
  1265. return "\n".join(output)
  1266. # #####################################################
  1267. # Parse - Scene
  1268. # #####################################################
  1269. def extract_scene(scene, filename):
  1270. global_settings = scene.GetGlobalSettings()
  1271. objects, nobjects = generate_scene_objects_string(scene)
  1272. textures = generate_texture_list(scene)
  1273. materials = generate_material_list(scene)
  1274. geometries = generate_geometry_list(scene)
  1275. embeds = generate_embed_list(scene)
  1276. fogs = []
  1277. ntextures = len(textures)
  1278. nmaterials = len(materials)
  1279. ngeometries = len(geometries)
  1280. #TODO: extract actual root/scene data here
  1281. position = Vector3String( (0,0,0) )
  1282. rotation = Vector3String( (0,0,0) )
  1283. scale = Vector3String( (1,1,1) )
  1284. camera_names = generate_camera_name_list(scene)
  1285. scene_settings = scene.GetGlobalSettings()
  1286. #TODO: this might exist as part of the FBX spec
  1287. bgcolor = Vector3String( (0.667,0.667,0.667) )
  1288. bgalpha = 1
  1289. # This does not seem to be any help here
  1290. # global_settings.GetDefaultCamera()
  1291. defcamera = LabelString(camera_names[0] if len(camera_names) > 0 else "")
  1292. if option_default_camera:
  1293. defcamera = LabelString('default_camera')
  1294. #TODO: extract fog info from scene
  1295. deffog = LabelString("")
  1296. geometries = generateMultiLineString( geometries, ",\n\n\t", 0 )
  1297. materials = generateMultiLineString( materials, ",\n\n\t", 0 )
  1298. textures = generateMultiLineString( textures, ",\n\n\t", 0 )
  1299. embeds = generateMultiLineString( embeds, ",\n\n\t", 0 )
  1300. fogs = generateMultiLineString( fogs, ",\n\n\t", 0 )
  1301. output = [
  1302. '{',
  1303. ' "metadata": {',
  1304. ' "formatVersion" : 3.2,',
  1305. ' "type" : "scene",',
  1306. ' "generatedBy" : "convert-to-threejs.py",',
  1307. ' "objects" : ' + str(nobjects) + ',',
  1308. ' "geometries" : ' + str(ngeometries) + ',',
  1309. ' "materials" : ' + str(nmaterials) + ',',
  1310. ' "textures" : ' + str(ntextures),
  1311. ' },',
  1312. '',
  1313. ' "urlBaseType": "relativeToScene",',
  1314. '',
  1315. ' "objects" :',
  1316. ' {',
  1317. objects,
  1318. ' },',
  1319. '',
  1320. ' "geometries" :',
  1321. ' {',
  1322. '\t' + geometries,
  1323. ' },',
  1324. '',
  1325. ' "materials" :',
  1326. ' {',
  1327. '\t' + materials,
  1328. ' },',
  1329. '',
  1330. ' "textures" :',
  1331. ' {',
  1332. '\t' + textures,
  1333. ' },',
  1334. '',
  1335. ' "embeds" :',
  1336. ' {',
  1337. '\t' + embeds,
  1338. ' },',
  1339. '',
  1340. ' "fogs" :',
  1341. ' {',
  1342. '\t' + fogs,
  1343. ' },',
  1344. '',
  1345. ' "transform" :',
  1346. ' {',
  1347. ' "position" : ' + position + ',',
  1348. ' "rotation" : ' + rotation + ',',
  1349. ' "scale" : ' + scale,
  1350. ' },',
  1351. '',
  1352. ' "defaults" :',
  1353. ' {',
  1354. ' "bgcolor" : ' + str(bgcolor) + ',',
  1355. ' "bgalpha" : ' + str(bgalpha) + ',',
  1356. ' "camera" : ' + defcamera + ',',
  1357. ' "fog" : ' + deffog,
  1358. ' }',
  1359. '}'
  1360. ]
  1361. return "\n".join(output)
  1362. # #####################################################
  1363. # file helpers
  1364. # #####################################################
  1365. def write_file(fname, content):
  1366. out = open(fname, "w")
  1367. out.write(content)
  1368. out.close()
  1369. # #####################################################
  1370. # main
  1371. # #####################################################
  1372. if __name__ == "__main__":
  1373. from optparse import OptionParser
  1374. try:
  1375. from FbxCommon import *
  1376. except ImportError:
  1377. import platform
  1378. msg = 'Could not locate the python FBX SDK!\n'
  1379. msg += 'You need to copy the FBX SDK into your python install folder such as '
  1380. if platform.system() == 'Windows' or platform.system() == 'Microsoft':
  1381. msg += '"Python26/Lib/site-packages"'
  1382. elif platform.system() == 'Linux':
  1383. msg += '"/usr/local/lib/python2.6/site-packages"'
  1384. elif platform.system() == 'Darwin':
  1385. msg += '"/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages"'
  1386. msg += ' folder.'
  1387. print(msg)
  1388. sys.exit(1)
  1389. usage = "Usage: %prog [source_file.fbx] [output_file.js] [options]"
  1390. parser = OptionParser(usage=usage)
  1391. parser.add_option('-t', '--triangulate', action='store_true', dest='triangulate', help="force quad geometry into triangles", default=False)
  1392. parser.add_option('-x', '--no-textures', action='store_true', dest='notextures', help="don't include texture references in output file", default=False)
  1393. parser.add_option('-p', '--prefix', action='store_true', dest='prefix', help="prefix object names in output file", default=False)
  1394. parser.add_option('-g', '--geometry-only', action='store_true', dest='geometry', help="output geometry only", default=False)
  1395. parser.add_option('-c', '--default-camera', action='store_true', dest='defcamera', help="include default camera in output scene", default=False)
  1396. parser.add_option('-l', '--defualt-light', action='store_true', dest='deflight', help="include default light in output scene", default=False)
  1397. (options, args) = parser.parse_args()
  1398. option_triangulate = options.triangulate
  1399. option_textures = True if not options.notextures else False
  1400. option_prefix = options.prefix
  1401. option_geometry = options.geometry
  1402. option_default_camera = options.defcamera
  1403. option_default_light = options.deflight
  1404. # Prepare the FBX SDK.
  1405. sdk_manager, scene = InitializeSdkObjects()
  1406. converter = FbxGeometryConverter(sdk_manager)
  1407. global_up_vector = get_up_vector(scene)
  1408. # The converter takes an FBX file as an argument.
  1409. if len(args) > 1:
  1410. print("\nLoading file: %s" % args[0])
  1411. result = LoadScene(sdk_manager, scene, args[0])
  1412. else:
  1413. result = False
  1414. print("\nUsage: convert_fbx_to_threejs [source_file.fbx] [output_file.js]\n")
  1415. if not result:
  1416. print("\nAn error occurred while loading the file...")
  1417. else:
  1418. if option_triangulate:
  1419. print("\nForcing geometry to triangles")
  1420. triangulate_scene(scene)
  1421. if option_geometry:
  1422. output_content = extract_geometry(scene, os.path.basename(args[0]))
  1423. else:
  1424. output_content = extract_scene(scene, os.path.basename(args[0]))
  1425. output_path = os.path.join(os.getcwd(), args[1])
  1426. write_file(output_path, output_content)
  1427. print("\nExported Three.js file to:\n%s\n" % output_path)
  1428. # Destroy all objects created by the FBX SDK.
  1429. sdk_manager.Destroy()
  1430. sys.exit(0)