1 | # @author zfedoran / http://github.com/zfedoran
|
---|
2 |
|
---|
3 | import os
|
---|
4 | import sys
|
---|
5 | import math
|
---|
6 | import operator
|
---|
7 |
|
---|
8 | # #####################################################
|
---|
9 | # Globals
|
---|
10 | # #####################################################
|
---|
11 | option_triangulate = True
|
---|
12 | option_textures = True
|
---|
13 | option_prefix = True
|
---|
14 | option_geometry = False
|
---|
15 | option_default_camera = False
|
---|
16 | option_default_light = False
|
---|
17 |
|
---|
18 | converter = None
|
---|
19 | global_up_vector = None
|
---|
20 |
|
---|
21 | # #####################################################
|
---|
22 | # Templates
|
---|
23 | # #####################################################
|
---|
24 | def Vector2String(v, no_brackets = False):
|
---|
25 | if no_brackets:
|
---|
26 | return '%g,%g' % (v[0], v[1])
|
---|
27 | else:
|
---|
28 | return '[ %g, %g ]' % (v[0], v[1])
|
---|
29 |
|
---|
30 | def Vector3String(v, no_brackets = False):
|
---|
31 | if no_brackets:
|
---|
32 | return '%g,%g,%g' % (v[0], v[1], v[2])
|
---|
33 | else:
|
---|
34 | return '[ %g, %g, %g ]' % (v[0], v[1], v[2])
|
---|
35 |
|
---|
36 | def ColorString(c, no_brackets = False):
|
---|
37 | if no_brackets:
|
---|
38 | return '%g, %g, %g' % (c[0], c[1], c[2])
|
---|
39 | else:
|
---|
40 | return '[ %g, %g, %g ]' % (c[0], c[1], c[2])
|
---|
41 |
|
---|
42 | def LabelString(s):
|
---|
43 | return '"%s"' % s
|
---|
44 |
|
---|
45 | def ArrayString(s):
|
---|
46 | return '[ %s ]' % s
|
---|
47 |
|
---|
48 | def PaddingString(n):
|
---|
49 | output = ""
|
---|
50 | for i in range(n):
|
---|
51 | output += "\t"
|
---|
52 | return output
|
---|
53 |
|
---|
54 | def BoolString(value):
|
---|
55 | if value:
|
---|
56 | return "true"
|
---|
57 | return "false"
|
---|
58 |
|
---|
59 | # #####################################################
|
---|
60 | # Helpers
|
---|
61 | # #####################################################
|
---|
62 | def getObjectName(o, force_prefix = False):
|
---|
63 | if not o:
|
---|
64 | return ""
|
---|
65 | prefix = ""
|
---|
66 | if option_prefix or force_prefix:
|
---|
67 | prefix = "Object_%s_" % o.GetUniqueID()
|
---|
68 | return prefix + o.GetName()
|
---|
69 |
|
---|
70 | def getGeometryName(g, force_prefix = False):
|
---|
71 | prefix = ""
|
---|
72 | if option_prefix or force_prefix:
|
---|
73 | prefix = "Geometry_%s_" % g.GetUniqueID()
|
---|
74 | return prefix + g.GetName()
|
---|
75 |
|
---|
76 | def getEmbedName(e, force_prefix = False):
|
---|
77 | prefix = ""
|
---|
78 | if option_prefix or force_prefix:
|
---|
79 | prefix = "Embed_%s_" % e.GetUniqueID()
|
---|
80 | return prefix + e.GetName()
|
---|
81 |
|
---|
82 | def getMaterialName(m, force_prefix = False):
|
---|
83 | prefix = ""
|
---|
84 | if option_prefix or force_prefix:
|
---|
85 | prefix = "Material_%s_" % m.GetUniqueID()
|
---|
86 | return prefix + m.GetName()
|
---|
87 |
|
---|
88 | def getTextureName(t, force_prefix = False):
|
---|
89 | texture_file = t.GetFileName()
|
---|
90 | texture_id = os.path.splitext(os.path.basename(texture_file))[0]
|
---|
91 | prefix = ""
|
---|
92 | if option_prefix or force_prefix:
|
---|
93 | prefix = "Texture_%s_" % t.GetUniqueID()
|
---|
94 | return prefix + texture_id
|
---|
95 |
|
---|
96 | def getFogName(f, force_prefix = False):
|
---|
97 | prefix = ""
|
---|
98 | if option_prefix or force_prefix:
|
---|
99 | prefix = "Fog_%s_" % f.GetUniqueID()
|
---|
100 | return prefix + f.GetName()
|
---|
101 |
|
---|
102 | def getObjectVisible(n):
|
---|
103 | return BoolString(True)
|
---|
104 |
|
---|
105 | def getRadians(v):
|
---|
106 | return ((v[0]*math.pi)/180, (v[1]*math.pi)/180, (v[2]*math.pi)/180)
|
---|
107 |
|
---|
108 | def getHex(c):
|
---|
109 | color = (int(c[0]*255) << 16) + (int(c[1]*255) << 8) + int(c[2]*255)
|
---|
110 | return color
|
---|
111 |
|
---|
112 | def setBit(value, position, on):
|
---|
113 | if on:
|
---|
114 | mask = 1 << position
|
---|
115 | return (value | mask)
|
---|
116 | else:
|
---|
117 | mask = ~(1 << position)
|
---|
118 | return (value & mask)
|
---|
119 |
|
---|
120 | def convert_fbx_color(color):
|
---|
121 | return [color.mRed, color.mGreen, color.mBlue, color.mAlpha]
|
---|
122 |
|
---|
123 | def convert_fbx_vec2(v):
|
---|
124 | return [v[0], v[1]]
|
---|
125 |
|
---|
126 | def convert_fbx_vec3(v):
|
---|
127 | return [v[0], v[1], v[2]]
|
---|
128 |
|
---|
129 | def generate_uvs(uv_layers):
|
---|
130 | layers = []
|
---|
131 | for uvs in uv_layers:
|
---|
132 | layer = ",".join(Vector2String(n, True) for n in uvs)
|
---|
133 | layers.append(layer)
|
---|
134 |
|
---|
135 | return ",".join("[%s]" % n for n in layers)
|
---|
136 |
|
---|
137 | def generateMultiLineString(lines, separator, padding):
|
---|
138 | cleanLines = []
|
---|
139 | for i in range(len(lines)):
|
---|
140 | line = lines[i]
|
---|
141 | line = PaddingString(padding) + line
|
---|
142 | cleanLines.append(line)
|
---|
143 | return separator.join(cleanLines)
|
---|
144 |
|
---|
145 | def get_up_vector(scene):
|
---|
146 | global_settings = scene.GetGlobalSettings()
|
---|
147 | axis_system = global_settings.GetAxisSystem()
|
---|
148 | up_vector = axis_system.GetUpVector()
|
---|
149 | tmp = [0,0,0]
|
---|
150 | tmp[up_vector[0] - 1] = up_vector[1] * 1
|
---|
151 | return FbxVector4(tmp[0], tmp[1], tmp[2], 1)
|
---|
152 |
|
---|
153 | def generate_bounding_box(vertices):
|
---|
154 | minx = 0
|
---|
155 | miny = 0
|
---|
156 | minz = 0
|
---|
157 | maxx = 0
|
---|
158 | maxy = 0
|
---|
159 | maxz = 0
|
---|
160 |
|
---|
161 | for vertex in vertices:
|
---|
162 | if vertex[0] < minx:
|
---|
163 | minx = vertex[0]
|
---|
164 | if vertex[1] < miny:
|
---|
165 | miny = vertex[1]
|
---|
166 | if vertex[2] < minz:
|
---|
167 | minz = vertex[2]
|
---|
168 |
|
---|
169 | if vertex[0] > maxx:
|
---|
170 | maxx = vertex[0]
|
---|
171 | if vertex[1] > maxy:
|
---|
172 | maxy = vertex[1]
|
---|
173 | if vertex[2] > maxz:
|
---|
174 | maxz = vertex[2]
|
---|
175 |
|
---|
176 | return [minx, miny, minz], [maxx, maxy, maxz]
|
---|
177 |
|
---|
178 |
|
---|
179 | # #####################################################
|
---|
180 | # Generate - Triangles
|
---|
181 | # #####################################################
|
---|
182 | def triangulate_node_hierarchy(node):
|
---|
183 | node_attribute = node.GetNodeAttribute();
|
---|
184 |
|
---|
185 | if node_attribute:
|
---|
186 | if node_attribute.GetAttributeType() == FbxNodeAttribute.eMesh or \
|
---|
187 | node_attribute.GetAttributeType() == FbxNodeAttribute.eNurbs or \
|
---|
188 | node_attribute.GetAttributeType() == FbxNodeAttribute.eNurbsSurface or \
|
---|
189 | node_attribute.GetAttributeType() == FbxNodeAttribute.ePatch:
|
---|
190 | converter.TriangulateInPlace(node);
|
---|
191 |
|
---|
192 | child_count = node.GetChildCount()
|
---|
193 | for i in range(child_count):
|
---|
194 | triangulate_node_hierarchy(node.GetChild(i))
|
---|
195 |
|
---|
196 | def triangulate_scene(scene):
|
---|
197 | node = scene.GetRootNode()
|
---|
198 | if node:
|
---|
199 | for i in range(node.GetChildCount()):
|
---|
200 | triangulate_node_hierarchy(node.GetChild(i))
|
---|
201 |
|
---|
202 | # #####################################################
|
---|
203 | # Generate - Material String
|
---|
204 | # #####################################################
|
---|
205 | def generate_texture_bindings(material_property, texture_list):
|
---|
206 | binding_types = {
|
---|
207 | "DiffuseColor": "map", "DiffuseFactor": "diffuseFactor", "EmissiveColor": "emissiveMap",
|
---|
208 | "EmissiveFactor": "emissiveFactor", "AmbientColor": "ambientMap", "AmbientFactor": "ambientFactor",
|
---|
209 | "SpecularColor": "specularMap", "SpecularFactor": "specularFactor", "ShininessExponent": "shininessExponent",
|
---|
210 | "NormalMap": "normalMap", "Bump": "bumpMap", "TransparentColor": "transparentMap",
|
---|
211 | "TransparencyFactor": "transparentFactor", "ReflectionColor": "reflectionMap",
|
---|
212 | "ReflectionFactor": "reflectionFactor", "DisplacementColor": "displacementMap",
|
---|
213 | "VectorDisplacementColor": "vectorDisplacementMap"
|
---|
214 | }
|
---|
215 |
|
---|
216 | if material_property.IsValid():
|
---|
217 | #Here we have to check if it's layeredtextures, or just textures:
|
---|
218 | layered_texture_count = material_property.GetSrcObjectCount(FbxLayeredTexture.ClassId)
|
---|
219 | if layered_texture_count > 0:
|
---|
220 | for j in range(layered_texture_count):
|
---|
221 | layered_texture = material_property.GetSrcObject(FbxLayeredTexture.ClassId, j)
|
---|
222 | texture_count = layered_texture.GetSrcObjectCount(FbxTexture.ClassId)
|
---|
223 | for k in range(texture_count):
|
---|
224 | texture = layered_texture.GetSrcObject(FbxTexture.ClassId,k)
|
---|
225 | if texture:
|
---|
226 | texture_id = getTextureName(texture, True)
|
---|
227 | texture_binding = ' "%s": "%s",' % (binding_types[str(material_property.GetName())], texture_id)
|
---|
228 | texture_list.append(texture_binding)
|
---|
229 | else:
|
---|
230 | # no layered texture simply get on the property
|
---|
231 | texture_count = material_property.GetSrcObjectCount(FbxTexture.ClassId)
|
---|
232 | for j in range(texture_count):
|
---|
233 | texture = material_property.GetSrcObject(FbxTexture.ClassId,j)
|
---|
234 | if texture:
|
---|
235 | texture_id = getTextureName(texture, True)
|
---|
236 | texture_binding = ' "%s": "%s",' % (binding_types[str(material_property.GetName())], texture_id)
|
---|
237 | texture_list.append(texture_binding)
|
---|
238 |
|
---|
239 | def generate_material_string(material):
|
---|
240 | #Get the implementation to see if it's a hardware shader.
|
---|
241 | implementation = GetImplementation(material, "ImplementationHLSL")
|
---|
242 | implementation_type = "HLSL"
|
---|
243 | if not implementation:
|
---|
244 | implementation = GetImplementation(material, "ImplementationCGFX")
|
---|
245 | implementation_type = "CGFX"
|
---|
246 |
|
---|
247 | output = []
|
---|
248 |
|
---|
249 | if implementation:
|
---|
250 | # This material is a hardware shader, skip it
|
---|
251 | print("Shader materials are not supported")
|
---|
252 | return ''
|
---|
253 |
|
---|
254 | elif material.GetClassId().Is(FbxSurfaceLambert.ClassId):
|
---|
255 |
|
---|
256 | ambient = str(getHex(material.Ambient.Get()))
|
---|
257 | diffuse = str(getHex(material.Diffuse.Get()))
|
---|
258 | emissive = str(getHex(material.Emissive.Get()))
|
---|
259 | opacity = 1.0 - material.TransparencyFactor.Get()
|
---|
260 | opacity = 1.0 if opacity == 0 else opacity
|
---|
261 | opacity = str(opacity)
|
---|
262 | transparent = BoolString(False)
|
---|
263 | reflectivity = "1"
|
---|
264 |
|
---|
265 | output = [
|
---|
266 |
|
---|
267 | '\t' + LabelString( getMaterialName( material ) ) + ': {',
|
---|
268 | ' "type" : "MeshLambertMaterial",',
|
---|
269 | ' "parameters" : {',
|
---|
270 | ' "color" : ' + diffuse + ',',
|
---|
271 | ' "ambient" : ' + ambient + ',',
|
---|
272 | ' "emissive" : ' + emissive + ',',
|
---|
273 | ' "reflectivity" : ' + reflectivity + ',',
|
---|
274 | ' "transparent" : ' + transparent + ',',
|
---|
275 | ' "opacity" : ' + opacity + ',',
|
---|
276 |
|
---|
277 | ]
|
---|
278 |
|
---|
279 | elif material.GetClassId().Is(FbxSurfacePhong.ClassId):
|
---|
280 |
|
---|
281 | ambient = str(getHex(material.Ambient.Get()))
|
---|
282 | diffuse = str(getHex(material.Diffuse.Get()))
|
---|
283 | emissive = str(getHex(material.Emissive.Get()))
|
---|
284 | specular = str(getHex(material.Specular.Get()))
|
---|
285 | opacity = 1.0 - material.TransparencyFactor.Get()
|
---|
286 | opacity = 1.0 if opacity == 0 else opacity
|
---|
287 | opacity = str(opacity)
|
---|
288 | shininess = str(material.Shininess.Get())
|
---|
289 | transparent = BoolString(False)
|
---|
290 | reflectivity = "1"
|
---|
291 | bumpScale = "1"
|
---|
292 |
|
---|
293 | output = [
|
---|
294 |
|
---|
295 | '\t' + LabelString( getMaterialName( material ) ) + ': {',
|
---|
296 | ' "type" : "MeshPhongMaterial",',
|
---|
297 | ' "parameters" : {',
|
---|
298 | ' "color" : ' + diffuse + ',',
|
---|
299 | ' "ambient" : ' + ambient + ',',
|
---|
300 | ' "emissive" : ' + emissive + ',',
|
---|
301 | ' "specular" : ' + specular + ',',
|
---|
302 | ' "shininess" : ' + shininess + ',',
|
---|
303 | ' "bumpScale" : ' + bumpScale + ',',
|
---|
304 | ' "reflectivity" : ' + reflectivity + ',',
|
---|
305 | ' "transparent" : ' + transparent + ',',
|
---|
306 | ' "opacity" : ' + opacity + ',',
|
---|
307 |
|
---|
308 | ]
|
---|
309 |
|
---|
310 | else:
|
---|
311 | print("Unknown type of Material")
|
---|
312 | return ''
|
---|
313 |
|
---|
314 | if option_textures:
|
---|
315 | texture_list = []
|
---|
316 | texture_count = FbxLayerElement.sTypeTextureCount()
|
---|
317 | for texture_index in range(texture_count):
|
---|
318 | material_property = material.FindProperty(FbxLayerElement.sTextureChannelNames(texture_index))
|
---|
319 | generate_texture_bindings(material_property, texture_list)
|
---|
320 |
|
---|
321 | output += texture_list
|
---|
322 |
|
---|
323 | wireframe = BoolString(False)
|
---|
324 | wireframeLinewidth = "1"
|
---|
325 |
|
---|
326 | output.append(' "wireframe" : ' + wireframe + ',')
|
---|
327 | output.append(' "wireframeLinewidth" : ' + wireframeLinewidth)
|
---|
328 | output.append(' }')
|
---|
329 | output.append('}')
|
---|
330 |
|
---|
331 | return generateMultiLineString( output, '\n\t\t', 0 )
|
---|
332 |
|
---|
333 | def generate_proxy_material_string(node, material_names):
|
---|
334 |
|
---|
335 | output = [
|
---|
336 |
|
---|
337 | '\t' + LabelString( getMaterialName( node, True ) ) + ': {',
|
---|
338 | ' "type" : "MeshFaceMaterial",',
|
---|
339 | ' "parameters" : {',
|
---|
340 | ' "materials" : ' + ArrayString( ",".join(LabelString(m) for m in material_names) ),
|
---|
341 | ' }',
|
---|
342 | '}'
|
---|
343 |
|
---|
344 | ]
|
---|
345 |
|
---|
346 | return generateMultiLineString( output, '\n\t\t', 0 )
|
---|
347 |
|
---|
348 | # #####################################################
|
---|
349 | # Parse - Materials
|
---|
350 | # #####################################################
|
---|
351 | def extract_materials_from_node(node, material_list):
|
---|
352 | name = node.GetName()
|
---|
353 | mesh = node.GetNodeAttribute()
|
---|
354 |
|
---|
355 | node = None
|
---|
356 | if mesh:
|
---|
357 | node = mesh.GetNode()
|
---|
358 | if node:
|
---|
359 | material_count = node.GetMaterialCount()
|
---|
360 |
|
---|
361 | material_names = []
|
---|
362 | for l in range(mesh.GetLayerCount()):
|
---|
363 | materials = mesh.GetLayer(l).GetMaterials()
|
---|
364 | if materials:
|
---|
365 | if materials.GetReferenceMode() == FbxLayerElement.eIndex:
|
---|
366 | #Materials are in an undefined external table
|
---|
367 | continue
|
---|
368 | for i in range(material_count):
|
---|
369 | material = node.GetMaterial(i)
|
---|
370 | material_names.append(getMaterialName(material))
|
---|
371 | material_string = generate_material_string(material)
|
---|
372 | material_list.append(material_string)
|
---|
373 |
|
---|
374 | if material_count > 1:
|
---|
375 | proxy_material = generate_proxy_material_string(node, material_names)
|
---|
376 | material_list.append(proxy_material)
|
---|
377 |
|
---|
378 |
|
---|
379 | def generate_materials_from_hierarchy(node, material_list):
|
---|
380 | if node.GetNodeAttribute() == None:
|
---|
381 | pass
|
---|
382 | else:
|
---|
383 | attribute_type = (node.GetNodeAttribute().GetAttributeType())
|
---|
384 | if attribute_type == FbxNodeAttribute.eMesh:
|
---|
385 | extract_materials_from_node(node, material_list)
|
---|
386 | for i in range(node.GetChildCount()):
|
---|
387 | generate_materials_from_hierarchy(node.GetChild(i), material_list)
|
---|
388 |
|
---|
389 | def generate_material_list(scene):
|
---|
390 | material_list = []
|
---|
391 | node = scene.GetRootNode()
|
---|
392 | if node:
|
---|
393 | for i in range(node.GetChildCount()):
|
---|
394 | generate_materials_from_hierarchy(node.GetChild(i), material_list)
|
---|
395 | return material_list
|
---|
396 |
|
---|
397 | # #####################################################
|
---|
398 | # Generate - Texture String
|
---|
399 | # #####################################################
|
---|
400 | def generate_texture_string(texture):
|
---|
401 |
|
---|
402 | #TODO: extract more texture properties
|
---|
403 | wrap_u = texture.GetWrapModeU()
|
---|
404 | wrap_v = texture.GetWrapModeV()
|
---|
405 | offset = texture.GetUVTranslation()
|
---|
406 |
|
---|
407 | output = [
|
---|
408 |
|
---|
409 | '\t' + LabelString( getTextureName( texture, True ) ) + ': {',
|
---|
410 | ' "url" : "' + texture.GetFileName() + '",',
|
---|
411 | ' "repeat" : ' + Vector2String( (1,1) ) + ',',
|
---|
412 | ' "offset" : ' + Vector2String( texture.GetUVTranslation() ) + ',',
|
---|
413 | ' "magFilter" : ' + LabelString( "LinearFilter" ) + ',',
|
---|
414 | ' "minFilter" : ' + LabelString( "LinearMipMapLinearFilter" ) + ',',
|
---|
415 | ' "anisotropy" : ' + BoolString( True ),
|
---|
416 | '}'
|
---|
417 |
|
---|
418 | ]
|
---|
419 |
|
---|
420 | return generateMultiLineString( output, '\n\t\t', 0 )
|
---|
421 |
|
---|
422 | # #####################################################
|
---|
423 | # Parse - Textures
|
---|
424 | # #####################################################
|
---|
425 | def extract_material_textures(material_property, texture_list):
|
---|
426 | if material_property.IsValid():
|
---|
427 | #Here we have to check if it's layeredtextures, or just textures:
|
---|
428 | layered_texture_count = material_property.GetSrcObjectCount(FbxLayeredTexture.ClassId)
|
---|
429 | if layered_texture_count > 0:
|
---|
430 | for j in range(layered_texture_count):
|
---|
431 | layered_texture = material_property.GetSrcObject(FbxLayeredTexture.ClassId, j)
|
---|
432 | texture_count = layered_texture.GetSrcObjectCount(FbxTexture.ClassId)
|
---|
433 | for k in range(texture_count):
|
---|
434 | texture = layered_texture.GetSrcObject(FbxTexture.ClassId,k)
|
---|
435 | if texture:
|
---|
436 | texture_string = generate_texture_string(texture)
|
---|
437 | texture_list.append(texture_string)
|
---|
438 | else:
|
---|
439 | # no layered texture simply get on the property
|
---|
440 | texture_count = material_property.GetSrcObjectCount(FbxTexture.ClassId)
|
---|
441 | for j in range(texture_count):
|
---|
442 | texture = material_property.GetSrcObject(FbxTexture.ClassId,j)
|
---|
443 | if texture:
|
---|
444 | texture_string = generate_texture_string(texture)
|
---|
445 | texture_list.append(texture_string)
|
---|
446 |
|
---|
447 | def extract_textures_from_node(node, texture_list):
|
---|
448 | name = node.GetName()
|
---|
449 | mesh = node.GetNodeAttribute()
|
---|
450 |
|
---|
451 | #for all materials attached to this mesh
|
---|
452 | material_count = mesh.GetNode().GetSrcObjectCount(FbxSurfaceMaterial.ClassId)
|
---|
453 | for material_index in range(material_count):
|
---|
454 | material = mesh.GetNode().GetSrcObject(FbxSurfaceMaterial.ClassId, material_index)
|
---|
455 |
|
---|
456 | #go through all the possible textures types
|
---|
457 | if material:
|
---|
458 | texture_count = FbxLayerElement.sTypeTextureCount()
|
---|
459 | for texture_index in range(texture_count):
|
---|
460 | material_property = material.FindProperty(FbxLayerElement.sTextureChannelNames(texture_index))
|
---|
461 | extract_material_textures(material_property, texture_list)
|
---|
462 |
|
---|
463 | def generate_textures_from_hierarchy(node, texture_list):
|
---|
464 | if node.GetNodeAttribute() == None:
|
---|
465 | pass
|
---|
466 | else:
|
---|
467 | attribute_type = (node.GetNodeAttribute().GetAttributeType())
|
---|
468 | if attribute_type == FbxNodeAttribute.eMesh:
|
---|
469 | extract_textures_from_node(node, texture_list)
|
---|
470 | for i in range(node.GetChildCount()):
|
---|
471 | generate_textures_from_hierarchy(node.GetChild(i), texture_list)
|
---|
472 |
|
---|
473 | def generate_texture_list(scene):
|
---|
474 | if not option_textures:
|
---|
475 | return []
|
---|
476 |
|
---|
477 | texture_list = []
|
---|
478 | node = scene.GetRootNode()
|
---|
479 | if node:
|
---|
480 | for i in range(node.GetChildCount()):
|
---|
481 | generate_textures_from_hierarchy(node.GetChild(i), texture_list)
|
---|
482 | return texture_list
|
---|
483 |
|
---|
484 | # #####################################################
|
---|
485 | # Extract - Fbx Mesh data
|
---|
486 | # #####################################################
|
---|
487 | def extract_fbx_vertex_positions(mesh):
|
---|
488 | control_points_count = mesh.GetControlPointsCount()
|
---|
489 | control_points = mesh.GetControlPoints()
|
---|
490 |
|
---|
491 | positions = []
|
---|
492 | for i in range(control_points_count):
|
---|
493 | positions.append(convert_fbx_vec3(control_points[i]))
|
---|
494 |
|
---|
495 | node = mesh.GetNode()
|
---|
496 | if node and option_geometry:
|
---|
497 | # FbxMeshes are local to their node, we need the vertices in global space
|
---|
498 | # when scene nodes are not exported
|
---|
499 | transform = node.EvaluateGlobalTransform()
|
---|
500 | transform = FbxMatrix(transform)
|
---|
501 |
|
---|
502 | for i in range(len(positions)):
|
---|
503 | v = positions[i]
|
---|
504 | position = FbxVector4(v[0], v[1], v[2])
|
---|
505 | position = transform.MultNormalize(position)
|
---|
506 | positions[i] = convert_fbx_vec3(position)
|
---|
507 |
|
---|
508 | return positions
|
---|
509 |
|
---|
510 | def extract_fbx_vertex_normals(mesh):
|
---|
511 | # eNone The mapping is undetermined.
|
---|
512 | # eByControlPoint There will be one mapping coordinate for each surface control point/vertex.
|
---|
513 | # 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.
|
---|
514 | # eByPolygon There can be only one mapping coordinate for the whole polygon.
|
---|
515 | # eByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
|
---|
516 | # eAllSame There can be only one mapping coordinate for the whole surface.
|
---|
517 |
|
---|
518 | layered_normal_indices = []
|
---|
519 | layered_normal_values = []
|
---|
520 |
|
---|
521 | poly_count = mesh.GetPolygonCount()
|
---|
522 | control_points = mesh.GetControlPoints()
|
---|
523 |
|
---|
524 | for l in range(mesh.GetLayerCount()):
|
---|
525 | mesh_normals = mesh.GetLayer(l).GetNormals()
|
---|
526 | if not mesh_normals:
|
---|
527 | continue
|
---|
528 |
|
---|
529 | normals_array = mesh_normals.GetDirectArray()
|
---|
530 | normals_count = normals_array.GetCount()
|
---|
531 |
|
---|
532 | if normals_count == 0:
|
---|
533 | continue
|
---|
534 |
|
---|
535 | normal_indices = []
|
---|
536 | normal_values = []
|
---|
537 |
|
---|
538 | # values
|
---|
539 | for i in range(normals_count):
|
---|
540 | normal = convert_fbx_vec3(normals_array.GetAt(i))
|
---|
541 | normal_values.append(normal)
|
---|
542 |
|
---|
543 | node = mesh.GetNode()
|
---|
544 | if node and option_geometry:
|
---|
545 | # FbxMeshes are local to their node, we need the normals in global space
|
---|
546 | # when scene nodes are not exported
|
---|
547 | transform = node.EvaluateGlobalTransform()
|
---|
548 | transform.SetT(FbxVector4(0,0,0,0))
|
---|
549 | transform = FbxMatrix(transform)
|
---|
550 |
|
---|
551 | for i in range(len(normal_values)):
|
---|
552 | n = normal_values[i]
|
---|
553 | normal = FbxVector4(n[0], n[1], n[2])
|
---|
554 | normal = transform.MultNormalize(normal)
|
---|
555 | normal_values[i] = convert_fbx_vec3(normal)
|
---|
556 |
|
---|
557 | # indices
|
---|
558 | vertexId = 0
|
---|
559 | for p in range(poly_count):
|
---|
560 | poly_size = mesh.GetPolygonSize(p)
|
---|
561 | poly_normals = []
|
---|
562 |
|
---|
563 | for v in range(poly_size):
|
---|
564 | control_point_index = mesh.GetPolygonVertex(p, v)
|
---|
565 |
|
---|
566 | if mesh_normals.GetMappingMode() == FbxLayerElement.eByControlPoint:
|
---|
567 | if mesh_normals.GetReferenceMode() == FbxLayerElement.eDirect:
|
---|
568 | poly_normals.append(control_point_index)
|
---|
569 | elif mesh_normals.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
|
---|
570 | index = mesh_normals.GetIndexArray().GetAt(control_point_index)
|
---|
571 | poly_normals.append(index)
|
---|
572 | elif mesh_normals.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
|
---|
573 | if mesh_normals.GetReferenceMode() == FbxLayerElement.eDirect:
|
---|
574 | poly_normals.append(vertexId)
|
---|
575 | elif mesh_normals.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
|
---|
576 | index = mesh_normals.GetIndexArray().GetAt(vertexId)
|
---|
577 | poly_normals.append(index)
|
---|
578 | elif mesh_normals.GetMappingMode() == FbxLayerElement.eByPolygon or \
|
---|
579 | mesh_normals.GetMappingMode() == FbxLayerElement.eAllSame or \
|
---|
580 | mesh_normals.GetMappingMode() == FbxLayerElement.eNone:
|
---|
581 | print("unsupported normal mapping mode for polygon vertex")
|
---|
582 |
|
---|
583 | vertexId += 1
|
---|
584 | normal_indices.append(poly_normals)
|
---|
585 |
|
---|
586 | layered_normal_values.append(normal_values)
|
---|
587 | layered_normal_indices.append(normal_indices)
|
---|
588 |
|
---|
589 | normal_values = []
|
---|
590 | normal_indices = []
|
---|
591 |
|
---|
592 | # Three.js only supports one layer of normals
|
---|
593 | if len(layered_normal_values) > 0:
|
---|
594 | normal_values = layered_normal_values[0]
|
---|
595 | normal_indices = layered_normal_indices[0]
|
---|
596 |
|
---|
597 | return normal_values, normal_indices
|
---|
598 |
|
---|
599 | def extract_fbx_vertex_colors(mesh):
|
---|
600 | # eNone The mapping is undetermined.
|
---|
601 | # eByControlPoint There will be one mapping coordinate for each surface control point/vertex.
|
---|
602 | # 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.
|
---|
603 | # eByPolygon There can be only one mapping coordinate for the whole polygon.
|
---|
604 | # eByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
|
---|
605 | # eAllSame There can be only one mapping coordinate for the whole surface.
|
---|
606 |
|
---|
607 | layered_color_indices = []
|
---|
608 | layered_color_values = []
|
---|
609 |
|
---|
610 | poly_count = mesh.GetPolygonCount()
|
---|
611 | control_points = mesh.GetControlPoints()
|
---|
612 |
|
---|
613 | for l in range(mesh.GetLayerCount()):
|
---|
614 | mesh_colors = mesh.GetLayer(l).GetVertexColors()
|
---|
615 | if not mesh_colors:
|
---|
616 | continue
|
---|
617 |
|
---|
618 | colors_array = mesh_colors.GetDirectArray()
|
---|
619 | colors_count = colors_array.GetCount()
|
---|
620 |
|
---|
621 | if colors_count == 0:
|
---|
622 | continue
|
---|
623 |
|
---|
624 | color_indices = []
|
---|
625 | color_values = []
|
---|
626 |
|
---|
627 | # values
|
---|
628 | for i in range(colors_count):
|
---|
629 | color = convert_fbx_color(colors_array.GetAt(i))
|
---|
630 | color_values.append(color)
|
---|
631 |
|
---|
632 | # indices
|
---|
633 | vertexId = 0
|
---|
634 | for p in range(poly_count):
|
---|
635 | poly_size = mesh.GetPolygonSize(p)
|
---|
636 | poly_colors = []
|
---|
637 |
|
---|
638 | for v in range(poly_size):
|
---|
639 | control_point_index = mesh.GetPolygonVertex(p, v)
|
---|
640 |
|
---|
641 | if mesh_colors.GetMappingMode() == FbxLayerElement.eByControlPoint:
|
---|
642 | if mesh_colors.GetReferenceMode() == FbxLayerElement.eDirect:
|
---|
643 | poly_colors.append(control_point_index)
|
---|
644 | elif mesh_colors.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
|
---|
645 | index = mesh_colors.GetIndexArray().GetAt(control_point_index)
|
---|
646 | poly_colors.append(index)
|
---|
647 | elif mesh_colors.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
|
---|
648 | if mesh_colors.GetReferenceMode() == FbxLayerElement.eDirect:
|
---|
649 | poly_colors.append(vertexId)
|
---|
650 | elif mesh_colors.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
|
---|
651 | index = mesh_colors.GetIndexArray().GetAt(vertexId)
|
---|
652 | poly_colors.append(index)
|
---|
653 | elif mesh_colors.GetMappingMode() == FbxLayerElement.eByPolygon or \
|
---|
654 | mesh_colors.GetMappingMode() == FbxLayerElement.eAllSame or \
|
---|
655 | mesh_colors.GetMappingMode() == FbxLayerElement.eNone:
|
---|
656 | print("unsupported color mapping mode for polygon vertex")
|
---|
657 |
|
---|
658 | vertexId += 1
|
---|
659 | color_indices.append(poly_colors)
|
---|
660 |
|
---|
661 | color_values = []
|
---|
662 | color_indices = []
|
---|
663 |
|
---|
664 | # Three.js only supports one layer of colors
|
---|
665 | if len(layered_color_values) > 0:
|
---|
666 | color_values = layered_color_values[0]
|
---|
667 | color_indices = layered_color_indices[0]
|
---|
668 |
|
---|
669 | return color_values, color_indices
|
---|
670 |
|
---|
671 | def extract_fbx_vertex_uvs(mesh):
|
---|
672 | # eNone The mapping is undetermined.
|
---|
673 | # eByControlPoint There will be one mapping coordinate for each surface control point/vertex.
|
---|
674 | # 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.
|
---|
675 | # eByPolygon There can be only one mapping coordinate for the whole polygon.
|
---|
676 | # eByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
|
---|
677 | # eAllSame There can be only one mapping coordinate for the whole surface.
|
---|
678 |
|
---|
679 | layered_uv_indices = []
|
---|
680 | layered_uv_values = []
|
---|
681 |
|
---|
682 | poly_count = mesh.GetPolygonCount()
|
---|
683 | control_points = mesh.GetControlPoints()
|
---|
684 |
|
---|
685 | for l in range(mesh.GetLayerCount()):
|
---|
686 | mesh_uvs = mesh.GetLayer(l).GetUVs()
|
---|
687 | if not mesh_uvs:
|
---|
688 | continue
|
---|
689 |
|
---|
690 | uvs_array = mesh_uvs.GetDirectArray()
|
---|
691 | uvs_count = uvs_array.GetCount()
|
---|
692 |
|
---|
693 | if uvs_count == 0:
|
---|
694 | continue
|
---|
695 |
|
---|
696 | uv_indices = []
|
---|
697 | uv_values = []
|
---|
698 |
|
---|
699 | # values
|
---|
700 | for i in range(uvs_count):
|
---|
701 | uv = convert_fbx_vec2(uvs_array.GetAt(i))
|
---|
702 | uv_values.append(uv)
|
---|
703 |
|
---|
704 | # indices
|
---|
705 | vertexId = 0
|
---|
706 | for p in range(poly_count):
|
---|
707 | poly_size = mesh.GetPolygonSize(p)
|
---|
708 | poly_uvs = []
|
---|
709 |
|
---|
710 | for v in range(poly_size):
|
---|
711 | control_point_index = mesh.GetPolygonVertex(p, v)
|
---|
712 |
|
---|
713 | if mesh_uvs.GetMappingMode() == FbxLayerElement.eByControlPoint:
|
---|
714 | if mesh_uvs.GetReferenceMode() == FbxLayerElement.eDirect:
|
---|
715 | poly_uvs.append(control_point_index)
|
---|
716 | elif mesh_uvs.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
|
---|
717 | index = mesh_uvs.GetIndexArray().GetAt(control_point_index)
|
---|
718 | poly_uvs.append(index)
|
---|
719 | elif mesh_uvs.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
|
---|
720 | uv_texture_index = mesh.GetTextureUVIndex(p, v)
|
---|
721 | if mesh_uvs.GetReferenceMode() == FbxLayerElement.eDirect or \
|
---|
722 | mesh_uvs.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
|
---|
723 | poly_uvs.append(uv_texture_index)
|
---|
724 | elif mesh_uvs.GetMappingMode() == FbxLayerElement.eByPolygon or \
|
---|
725 | mesh_uvs.GetMappingMode() == FbxLayerElement.eAllSame or \
|
---|
726 | mesh_uvs.GetMappingMode() == FbxLayerElement.eNone:
|
---|
727 | print("unsupported uv mapping mode for polygon vertex")
|
---|
728 |
|
---|
729 | vertexId += 1
|
---|
730 | uv_indices.append(poly_uvs)
|
---|
731 |
|
---|
732 | layered_uv_values.append(uv_values)
|
---|
733 | layered_uv_indices.append(uv_indices)
|
---|
734 |
|
---|
735 | return layered_uv_values, layered_uv_indices
|
---|
736 |
|
---|
737 | # #####################################################
|
---|
738 | # Generate - Mesh String (for scene output)
|
---|
739 | # #####################################################
|
---|
740 | def generate_mesh_string_for_scene_output(node):
|
---|
741 | mesh = node.GetNodeAttribute()
|
---|
742 | mesh_list = [ mesh ]
|
---|
743 |
|
---|
744 | vertices, vertex_offsets = process_mesh_vertices(mesh_list)
|
---|
745 | materials, material_offsets = process_mesh_materials(mesh_list)
|
---|
746 |
|
---|
747 | normals_to_indices = generate_unique_normals_dictionary(mesh_list)
|
---|
748 | colors_to_indices = generate_unique_colors_dictionary(mesh_list)
|
---|
749 | uvs_to_indices_list = generate_unique_uvs_dictionary_layers(mesh_list)
|
---|
750 |
|
---|
751 | normal_values = generate_normals_from_dictionary(normals_to_indices)
|
---|
752 | color_values = generate_colors_from_dictionary(colors_to_indices)
|
---|
753 | uv_values = generate_uvs_from_dictionary_layers(uvs_to_indices_list)
|
---|
754 |
|
---|
755 | faces = process_mesh_polygons(mesh_list,
|
---|
756 | normals_to_indices,
|
---|
757 | colors_to_indices,
|
---|
758 | uvs_to_indices_list,
|
---|
759 | vertex_offsets,
|
---|
760 | material_offsets)
|
---|
761 |
|
---|
762 | nuvs = []
|
---|
763 | for layer_index, uvs in enumerate(uv_values):
|
---|
764 | nuvs.append(str(len(uvs)))
|
---|
765 |
|
---|
766 | nvertices = len(vertices)
|
---|
767 | nnormals = len(normal_values)
|
---|
768 | ncolors = len(color_values)
|
---|
769 | nfaces = len(faces)
|
---|
770 | nuvs = ",".join(nuvs)
|
---|
771 |
|
---|
772 | aabb_min, aabb_max = generate_bounding_box(vertices)
|
---|
773 | aabb_min = ",".join(str(f) for f in aabb_min)
|
---|
774 | aabb_max = ",".join(str(f) for f in aabb_max)
|
---|
775 |
|
---|
776 | vertices = ",".join(Vector3String(v, True) for v in vertices)
|
---|
777 | normals = ",".join(Vector3String(v, True) for v in normal_values)
|
---|
778 | colors = ",".join(Vector3String(v, True) for v in color_values)
|
---|
779 | faces = ",".join(faces)
|
---|
780 | uvs = generate_uvs(uv_values)
|
---|
781 |
|
---|
782 | output = [
|
---|
783 |
|
---|
784 | '\t' + LabelString( getEmbedName( node, True ) ) + ' : {',
|
---|
785 | ' "metadata" : {',
|
---|
786 | ' "vertices" : ' + str(nvertices) + ',',
|
---|
787 | ' "normals" : ' + str(nnormals) + ',',
|
---|
788 | ' "colors" : ' + str(ncolors) + ',',
|
---|
789 | ' "faces" : ' + str(nfaces) + ',',
|
---|
790 | ' "uvs" : ' + ArrayString(nuvs),
|
---|
791 | ' },',
|
---|
792 | ' "boundingBox" : {',
|
---|
793 | ' "min" : ' + ArrayString(aabb_min) + ',',
|
---|
794 | ' "max" : ' + ArrayString(aabb_max),
|
---|
795 | ' },',
|
---|
796 | ' "scale" : ' + str( 1 ) + ',',
|
---|
797 | ' "materials" : ' + ArrayString("") + ',',
|
---|
798 | ' "vertices" : ' + ArrayString(vertices) + ',',
|
---|
799 | ' "normals" : ' + ArrayString(normals) + ',',
|
---|
800 | ' "colors" : ' + ArrayString(colors) + ',',
|
---|
801 | ' "uvs" : ' + ArrayString(uvs) + ',',
|
---|
802 | ' "faces" : ' + ArrayString(faces),
|
---|
803 | '}'
|
---|
804 |
|
---|
805 | ]
|
---|
806 |
|
---|
807 | return generateMultiLineString( output, '\n\t\t', 0 )
|
---|
808 |
|
---|
809 | # #####################################################
|
---|
810 | # Generate - Mesh String (for non-scene output)
|
---|
811 | # #####################################################
|
---|
812 | def generate_mesh_string_for_non_scene_output(scene):
|
---|
813 | mesh_list = generate_mesh_list(scene)
|
---|
814 |
|
---|
815 | vertices, vertex_offsets = process_mesh_vertices(mesh_list)
|
---|
816 | materials, material_offsets = process_mesh_materials(mesh_list)
|
---|
817 |
|
---|
818 | normals_to_indices = generate_unique_normals_dictionary(mesh_list)
|
---|
819 | colors_to_indices = generate_unique_colors_dictionary(mesh_list)
|
---|
820 | uvs_to_indices_list = generate_unique_uvs_dictionary_layers(mesh_list)
|
---|
821 |
|
---|
822 | normal_values = generate_normals_from_dictionary(normals_to_indices)
|
---|
823 | color_values = generate_colors_from_dictionary(colors_to_indices)
|
---|
824 | uv_values = generate_uvs_from_dictionary_layers(uvs_to_indices_list)
|
---|
825 |
|
---|
826 | faces = process_mesh_polygons(mesh_list,
|
---|
827 | normals_to_indices,
|
---|
828 | colors_to_indices,
|
---|
829 | uvs_to_indices_list,
|
---|
830 | vertex_offsets,
|
---|
831 | material_offsets)
|
---|
832 |
|
---|
833 | nuvs = []
|
---|
834 | for layer_index, uvs in enumerate(uv_values):
|
---|
835 | nuvs.append(str(len(uvs)))
|
---|
836 |
|
---|
837 | nvertices = len(vertices)
|
---|
838 | nnormals = len(normal_values)
|
---|
839 | ncolors = len(color_values)
|
---|
840 | nfaces = len(faces)
|
---|
841 | nuvs = ",".join(nuvs)
|
---|
842 |
|
---|
843 | aabb_min, aabb_max = generate_bounding_box(vertices)
|
---|
844 | aabb_min = ",".join(str(f) for f in aabb_min)
|
---|
845 | aabb_max = ",".join(str(f) for f in aabb_max)
|
---|
846 |
|
---|
847 | vertices = ",".join(Vector3String(v, True) for v in vertices)
|
---|
848 | normals = ",".join(Vector3String(v, True) for v in normal_values)
|
---|
849 | colors = ",".join(Vector3String(v, True) for v in color_values)
|
---|
850 | faces = ",".join(faces)
|
---|
851 | uvs = generate_uvs(uv_values)
|
---|
852 |
|
---|
853 | output = [
|
---|
854 |
|
---|
855 | '{',
|
---|
856 | ' "metadata" : {',
|
---|
857 | ' "formatVersion" : 3.2,',
|
---|
858 | ' "type" : "geometry",',
|
---|
859 | ' "generatedBy" : "convert-to-threejs.py"' + ',',
|
---|
860 | ' "vertices" : ' + str(nvertices) + ',',
|
---|
861 | ' "normals" : ' + str(nnormals) + ',',
|
---|
862 | ' "colors" : ' + str(ncolors) + ',',
|
---|
863 | ' "faces" : ' + str(nfaces) + ',',
|
---|
864 | ' "uvs" : ' + ArrayString(nuvs),
|
---|
865 | ' },',
|
---|
866 | ' "boundingBox" : {',
|
---|
867 | ' "min" : ' + ArrayString(aabb_min) + ',',
|
---|
868 | ' "max" : ' + ArrayString(aabb_max),
|
---|
869 | ' },',
|
---|
870 | ' "scale" : ' + str( 1 ) + ',',
|
---|
871 | ' "materials" : ' + ArrayString("") + ',',
|
---|
872 | ' "vertices" : ' + ArrayString(vertices) + ',',
|
---|
873 | ' "normals" : ' + ArrayString(normals) + ',',
|
---|
874 | ' "colors" : ' + ArrayString(colors) + ',',
|
---|
875 | ' "uvs" : ' + ArrayString(uvs) + ',',
|
---|
876 | ' "faces" : ' + ArrayString(faces),
|
---|
877 | '}'
|
---|
878 |
|
---|
879 | ]
|
---|
880 |
|
---|
881 | return generateMultiLineString( output, '\n', 0 )
|
---|
882 |
|
---|
883 | # #####################################################
|
---|
884 | # Process - Mesh Geometry
|
---|
885 | # #####################################################
|
---|
886 | def generate_normal_key(normal):
|
---|
887 | return (round(normal[0], 6), round(normal[1], 6), round(normal[2], 6))
|
---|
888 |
|
---|
889 | def generate_color_key(color):
|
---|
890 | return getHex(color)
|
---|
891 |
|
---|
892 | def generate_uv_key(uv):
|
---|
893 | return (round(uv[0], 6), round(uv[1], 6))
|
---|
894 |
|
---|
895 | def append_non_duplicate_uvs(source_uvs, dest_uvs, counts):
|
---|
896 | source_layer_count = len(source_uvs)
|
---|
897 | for layer_index in range(source_layer_count):
|
---|
898 |
|
---|
899 | dest_layer_count = len(dest_uvs)
|
---|
900 |
|
---|
901 | if dest_layer_count <= layer_index:
|
---|
902 | dest_uv_layer = {}
|
---|
903 | count = 0
|
---|
904 | dest_uvs.append(dest_uv_layer)
|
---|
905 | counts.append(count)
|
---|
906 | else:
|
---|
907 | dest_uv_layer = dest_uvs[layer_index]
|
---|
908 | count = counts[layer_index]
|
---|
909 |
|
---|
910 | source_uv_layer = source_uvs[layer_index]
|
---|
911 |
|
---|
912 | for uv in source_uv_layer:
|
---|
913 | key = generate_uv_key(uv)
|
---|
914 | if key not in dest_uv_layer:
|
---|
915 | dest_uv_layer[key] = count
|
---|
916 | count += 1
|
---|
917 |
|
---|
918 | counts[layer_index] = count
|
---|
919 |
|
---|
920 | return counts
|
---|
921 |
|
---|
922 | def generate_unique_normals_dictionary(mesh_list):
|
---|
923 | normals_dictionary = {}
|
---|
924 | nnormals = 0
|
---|
925 |
|
---|
926 | # Merge meshes, remove duplicate data
|
---|
927 | for mesh in mesh_list:
|
---|
928 | node = mesh.GetNode()
|
---|
929 | normal_values, normal_indices = extract_fbx_vertex_normals(mesh)
|
---|
930 |
|
---|
931 | if len(normal_values) > 0:
|
---|
932 | for normal in normal_values:
|
---|
933 | key = generate_normal_key(normal)
|
---|
934 | if key not in normals_dictionary:
|
---|
935 | normals_dictionary[key] = nnormals
|
---|
936 | nnormals += 1
|
---|
937 |
|
---|
938 | return normals_dictionary
|
---|
939 |
|
---|
940 | def generate_unique_colors_dictionary(mesh_list):
|
---|
941 | colors_dictionary = {}
|
---|
942 | ncolors = 0
|
---|
943 |
|
---|
944 | # Merge meshes, remove duplicate data
|
---|
945 | for mesh in mesh_list:
|
---|
946 | color_values, color_indices = extract_fbx_vertex_colors(mesh)
|
---|
947 |
|
---|
948 | if len(color_values) > 0:
|
---|
949 | for color in color_values:
|
---|
950 | key = generate_color_key(color)
|
---|
951 | if key not in colors_dictionary:
|
---|
952 | colors_dictionary[key] = count
|
---|
953 | count += 1
|
---|
954 |
|
---|
955 | return colors_dictionary
|
---|
956 |
|
---|
957 | def generate_unique_uvs_dictionary_layers(mesh_list):
|
---|
958 | uvs_dictionary_layers = []
|
---|
959 | nuvs_list = []
|
---|
960 |
|
---|
961 | # Merge meshes, remove duplicate data
|
---|
962 | for mesh in mesh_list:
|
---|
963 | uv_values, uv_indices = extract_fbx_vertex_uvs(mesh)
|
---|
964 |
|
---|
965 | if len(uv_values) > 0:
|
---|
966 | nuvs_list = append_non_duplicate_uvs(uv_values, uvs_dictionary_layers, nuvs_list)
|
---|
967 |
|
---|
968 | return uvs_dictionary_layers
|
---|
969 |
|
---|
970 | def generate_normals_from_dictionary(normals_dictionary):
|
---|
971 | normal_values = []
|
---|
972 | for key, index in sorted(normals_dictionary.items(), key = operator.itemgetter(1)):
|
---|
973 | normal_values.append(key)
|
---|
974 |
|
---|
975 | return normal_values
|
---|
976 |
|
---|
977 | def generate_colors_from_dictionary(colors_dictionary):
|
---|
978 | color_values = []
|
---|
979 | for key, index in sorted(colors_dictionary.items(), key = operator.itemgetter(1)):
|
---|
980 | color_values.append(key)
|
---|
981 |
|
---|
982 | return color_values
|
---|
983 |
|
---|
984 | def generate_uvs_from_dictionary_layers(uvs_dictionary_layers):
|
---|
985 | uv_values = []
|
---|
986 | for uvs_dictionary in uvs_dictionary_layers:
|
---|
987 | uv_values_layer = []
|
---|
988 | for key, index in sorted(uvs_dictionary.items(), key = operator.itemgetter(1)):
|
---|
989 | uv_values_layer.append(key)
|
---|
990 | uv_values.append(uv_values_layer)
|
---|
991 |
|
---|
992 | return uv_values
|
---|
993 |
|
---|
994 | def generate_normal_indices_for_poly(poly_index, mesh_normal_values, mesh_normal_indices, normals_to_indices):
|
---|
995 | if len(mesh_normal_indices) <= 0:
|
---|
996 | return []
|
---|
997 |
|
---|
998 | poly_normal_indices = mesh_normal_indices[poly_index]
|
---|
999 | poly_size = len(poly_normal_indices)
|
---|
1000 |
|
---|
1001 | output_poly_normal_indices = []
|
---|
1002 | for v in range(poly_size):
|
---|
1003 | normal_index = poly_normal_indices[v]
|
---|
1004 | normal_value = mesh_normal_values[normal_index]
|
---|
1005 |
|
---|
1006 | key = generate_normal_key(normal_value)
|
---|
1007 |
|
---|
1008 | output_index = normals_to_indices[key]
|
---|
1009 | output_poly_normal_indices.append(output_index)
|
---|
1010 |
|
---|
1011 | return output_poly_normal_indices
|
---|
1012 |
|
---|
1013 | def generate_color_indices_for_poly(poly_index, mesh_color_values, mesh_color_indices, colors_to_indices):
|
---|
1014 | if len(mesh_color_indices) <= 0:
|
---|
1015 | return []
|
---|
1016 |
|
---|
1017 | poly_color_indices = mesh_color_indices[poly_index]
|
---|
1018 | poly_size = len(poly_color_indices)
|
---|
1019 |
|
---|
1020 | output_poly_color_indices = []
|
---|
1021 | for v in range(poly_size):
|
---|
1022 | color_index = poly_color_indices[v]
|
---|
1023 | color_value = mesh_color_values[color_index]
|
---|
1024 |
|
---|
1025 | key = generate_color_key(color_value)
|
---|
1026 |
|
---|
1027 | output_index = colors_to_indices[key]
|
---|
1028 | output_poly_color_indices.append(output_index)
|
---|
1029 |
|
---|
1030 | return output_poly_color_indices
|
---|
1031 |
|
---|
1032 | def generate_uv_indices_for_poly(poly_index, mesh_uv_values, mesh_uv_indices, uvs_to_indices):
|
---|
1033 | if len(mesh_uv_indices) <= 0:
|
---|
1034 | return []
|
---|
1035 |
|
---|
1036 | poly_uv_indices = mesh_uv_indices[poly_index]
|
---|
1037 | poly_size = len(poly_uv_indices)
|
---|
1038 |
|
---|
1039 | output_poly_uv_indices = []
|
---|
1040 | for v in range(poly_size):
|
---|
1041 | uv_index = poly_uv_indices[v]
|
---|
1042 | uv_value = mesh_uv_values[uv_index]
|
---|
1043 |
|
---|
1044 | key = generate_uv_key(uv_value)
|
---|
1045 |
|
---|
1046 | output_index = uvs_to_indices[key]
|
---|
1047 | output_poly_uv_indices.append(output_index)
|
---|
1048 |
|
---|
1049 | return output_poly_uv_indices
|
---|
1050 |
|
---|
1051 | def process_mesh_vertices(mesh_list):
|
---|
1052 | vertex_offset = 0
|
---|
1053 | vertex_offset_list = [0]
|
---|
1054 | vertices = []
|
---|
1055 | for mesh in mesh_list:
|
---|
1056 | node = mesh.GetNode()
|
---|
1057 | mesh_vertices = extract_fbx_vertex_positions(mesh)
|
---|
1058 |
|
---|
1059 | vertices.extend(mesh_vertices[:])
|
---|
1060 | vertex_offset += len(mesh_vertices)
|
---|
1061 | vertex_offset_list.append(vertex_offset)
|
---|
1062 |
|
---|
1063 | return vertices, vertex_offset_list
|
---|
1064 |
|
---|
1065 | def process_mesh_materials(mesh_list):
|
---|
1066 | material_offset = 0
|
---|
1067 | material_offset_list = [0]
|
---|
1068 | materials_list = []
|
---|
1069 |
|
---|
1070 | #TODO: remove duplicate mesh references
|
---|
1071 | for mesh in mesh_list:
|
---|
1072 | node = mesh.GetNode()
|
---|
1073 |
|
---|
1074 | material_count = node.GetMaterialCount()
|
---|
1075 | if material_count > 0:
|
---|
1076 | for l in range(mesh.GetLayerCount()):
|
---|
1077 | materials = mesh.GetLayer(l).GetMaterials()
|
---|
1078 | if materials:
|
---|
1079 | if materials.GetReferenceMode() == FbxLayerElement.eIndex:
|
---|
1080 | #Materials are in an undefined external table
|
---|
1081 | continue
|
---|
1082 |
|
---|
1083 | for i in range(material_count):
|
---|
1084 | material = node.GetMaterial(i)
|
---|
1085 | materials_list.append( material )
|
---|
1086 |
|
---|
1087 | material_offset += material_count
|
---|
1088 | material_offset_list.append(material_offset)
|
---|
1089 |
|
---|
1090 | return materials_list, material_offset_list
|
---|
1091 |
|
---|
1092 | def process_mesh_polygons(mesh_list, normals_to_indices, colors_to_indices, uvs_to_indices_list, vertex_offset_list, material_offset_list):
|
---|
1093 | faces = []
|
---|
1094 | for mesh_index in range(len(mesh_list)):
|
---|
1095 | mesh = mesh_list[mesh_index]
|
---|
1096 | poly_count = mesh.GetPolygonCount()
|
---|
1097 | control_points = mesh.GetControlPoints()
|
---|
1098 |
|
---|
1099 | normal_values, normal_indices = extract_fbx_vertex_normals(mesh)
|
---|
1100 | color_values, color_indices = extract_fbx_vertex_colors(mesh)
|
---|
1101 | uv_values_layers, uv_indices_layers = extract_fbx_vertex_uvs(mesh)
|
---|
1102 |
|
---|
1103 | for poly_index in range(poly_count):
|
---|
1104 | poly_size = mesh.GetPolygonSize(poly_index)
|
---|
1105 |
|
---|
1106 | face_normals = generate_normal_indices_for_poly(poly_index, normal_values, normal_indices, normals_to_indices)
|
---|
1107 | face_colors = generate_color_indices_for_poly(poly_index, color_values, color_indices, colors_to_indices)
|
---|
1108 |
|
---|
1109 | face_uv_layers = []
|
---|
1110 | for l in range(len(uv_indices_layers)):
|
---|
1111 | uv_values = uv_values_layers[l]
|
---|
1112 | uv_indices = uv_indices_layers[l]
|
---|
1113 | face_uv_indices = generate_uv_indices_for_poly(poly_index, uv_values, uv_indices, uvs_to_indices_list[l])
|
---|
1114 | face_uv_layers.append(face_uv_indices)
|
---|
1115 |
|
---|
1116 | face_vertices = []
|
---|
1117 | for vertex_index in range(poly_size):
|
---|
1118 | control_point_index = mesh.GetPolygonVertex(poly_index, vertex_index)
|
---|
1119 | face_vertices.append(control_point_index)
|
---|
1120 |
|
---|
1121 | #TODO: assign a default material to any mesh without one
|
---|
1122 | if len(material_offset_list) <= mesh_index:
|
---|
1123 | material_offset = 0
|
---|
1124 | else:
|
---|
1125 | material_offset = material_offset_list[mesh_index]
|
---|
1126 |
|
---|
1127 | vertex_offset = vertex_offset_list[mesh_index]
|
---|
1128 |
|
---|
1129 | face = generate_mesh_face(mesh,
|
---|
1130 | poly_index,
|
---|
1131 | face_vertices,
|
---|
1132 | face_normals,
|
---|
1133 | face_colors,
|
---|
1134 | face_uv_layers,
|
---|
1135 | vertex_offset,
|
---|
1136 | material_offset)
|
---|
1137 |
|
---|
1138 | faces.append(face)
|
---|
1139 |
|
---|
1140 |
|
---|
1141 | return faces
|
---|
1142 |
|
---|
1143 | def generate_mesh_face(mesh, polygon_index, vertex_indices, normals, colors, uv_layers, vertex_offset, material_offset):
|
---|
1144 | isTriangle = ( len(vertex_indices) == 3 )
|
---|
1145 | nVertices = 3 if isTriangle else 4
|
---|
1146 |
|
---|
1147 | hasMaterial = False
|
---|
1148 | for l in range(mesh.GetLayerCount()):
|
---|
1149 | materials = mesh.GetLayer(l).GetMaterials()
|
---|
1150 | if materials:
|
---|
1151 | hasMaterial = True
|
---|
1152 | break
|
---|
1153 |
|
---|
1154 | hasFaceUvs = False
|
---|
1155 | hasFaceVertexUvs = len(uv_layers) > 0
|
---|
1156 | hasFaceNormals = False
|
---|
1157 | hasFaceVertexNormals = len(normals) > 0
|
---|
1158 | hasFaceColors = False
|
---|
1159 | hasFaceVertexColors = len(colors) > 0
|
---|
1160 |
|
---|
1161 | faceType = 0
|
---|
1162 | faceType = setBit(faceType, 0, not isTriangle)
|
---|
1163 | faceType = setBit(faceType, 1, hasMaterial)
|
---|
1164 | faceType = setBit(faceType, 2, hasFaceUvs)
|
---|
1165 | faceType = setBit(faceType, 3, hasFaceVertexUvs)
|
---|
1166 | faceType = setBit(faceType, 4, hasFaceNormals)
|
---|
1167 | faceType = setBit(faceType, 5, hasFaceVertexNormals)
|
---|
1168 | faceType = setBit(faceType, 6, hasFaceColors)
|
---|
1169 | faceType = setBit(faceType, 7, hasFaceVertexColors)
|
---|
1170 |
|
---|
1171 | faceData = []
|
---|
1172 |
|
---|
1173 | # order is important, must match order in JSONLoader
|
---|
1174 |
|
---|
1175 | # face type
|
---|
1176 | # vertex indices
|
---|
1177 | # material index
|
---|
1178 | # face uvs index
|
---|
1179 | # face vertex uvs indices
|
---|
1180 | # face color index
|
---|
1181 | # face vertex colors indices
|
---|
1182 |
|
---|
1183 | faceData.append(faceType)
|
---|
1184 |
|
---|
1185 | tmp = []
|
---|
1186 | for i in range(nVertices):
|
---|
1187 | tmp.append(vertex_indices[i])
|
---|
1188 | index = vertex_indices[i] + vertex_offset
|
---|
1189 | faceData.append(index)
|
---|
1190 |
|
---|
1191 | if hasMaterial:
|
---|
1192 | material_id = 0
|
---|
1193 | for l in range(mesh.GetLayerCount()):
|
---|
1194 | materials = mesh.GetLayer(l).GetMaterials()
|
---|
1195 | if materials:
|
---|
1196 | material_id = materials.GetIndexArray().GetAt(polygon_index)
|
---|
1197 | break
|
---|
1198 | material_id += material_offset
|
---|
1199 | faceData.append( material_id )
|
---|
1200 |
|
---|
1201 | if hasFaceVertexUvs:
|
---|
1202 | for polygon_uvs in uv_layers:
|
---|
1203 | for i in range(nVertices):
|
---|
1204 | index = polygon_uvs[i]
|
---|
1205 | faceData.append(index)
|
---|
1206 |
|
---|
1207 | if hasFaceVertexNormals:
|
---|
1208 | for i in range(nVertices):
|
---|
1209 | index = normals[i]
|
---|
1210 | faceData.append(index)
|
---|
1211 |
|
---|
1212 | if hasFaceVertexColors:
|
---|
1213 | for i in range(nVertices):
|
---|
1214 | index = colors[i]
|
---|
1215 | faceData.append(index)
|
---|
1216 |
|
---|
1217 | return ",".join( map(str, faceData) )
|
---|
1218 |
|
---|
1219 |
|
---|
1220 | # #####################################################
|
---|
1221 | # Generate - Mesh List
|
---|
1222 | # #####################################################
|
---|
1223 | def generate_mesh_list_from_hierarchy(node, mesh_list):
|
---|
1224 | if node.GetNodeAttribute() == None:
|
---|
1225 | pass
|
---|
1226 | else:
|
---|
1227 | attribute_type = (node.GetNodeAttribute().GetAttributeType())
|
---|
1228 | if attribute_type == FbxNodeAttribute.eMesh or \
|
---|
1229 | attribute_type == FbxNodeAttribute.eNurbs or \
|
---|
1230 | attribute_type == FbxNodeAttribute.eNurbsSurface or \
|
---|
1231 | attribute_type == FbxNodeAttribute.ePatch:
|
---|
1232 |
|
---|
1233 | if attribute_type != FbxNodeAttribute.eMesh:
|
---|
1234 | converter.TriangulateInPlace(node);
|
---|
1235 |
|
---|
1236 | mesh_list.append(node.GetNodeAttribute())
|
---|
1237 |
|
---|
1238 | for i in range(node.GetChildCount()):
|
---|
1239 | generate_mesh_list_from_hierarchy(node.GetChild(i), mesh_list)
|
---|
1240 |
|
---|
1241 | def generate_mesh_list(scene):
|
---|
1242 | mesh_list = []
|
---|
1243 | node = scene.GetRootNode()
|
---|
1244 | if node:
|
---|
1245 | for i in range(node.GetChildCount()):
|
---|
1246 | generate_mesh_list_from_hierarchy(node.GetChild(i), mesh_list)
|
---|
1247 | return mesh_list
|
---|
1248 |
|
---|
1249 | # #####################################################
|
---|
1250 | # Generate - Embeds
|
---|
1251 | # #####################################################
|
---|
1252 | def generate_embed_list_from_hierarchy(node, embed_list):
|
---|
1253 | if node.GetNodeAttribute() == None:
|
---|
1254 | pass
|
---|
1255 | else:
|
---|
1256 | attribute_type = (node.GetNodeAttribute().GetAttributeType())
|
---|
1257 | if attribute_type == FbxNodeAttribute.eMesh or \
|
---|
1258 | attribute_type == FbxNodeAttribute.eNurbs or \
|
---|
1259 | attribute_type == FbxNodeAttribute.eNurbsSurface or \
|
---|
1260 | attribute_type == FbxNodeAttribute.ePatch:
|
---|
1261 |
|
---|
1262 | if attribute_type != FbxNodeAttribute.eMesh:
|
---|
1263 | converter.TriangulateInPlace(node);
|
---|
1264 |
|
---|
1265 | embed_string = generate_mesh_string_for_scene_output(node)
|
---|
1266 | embed_list.append(embed_string)
|
---|
1267 |
|
---|
1268 | for i in range(node.GetChildCount()):
|
---|
1269 | generate_embed_list_from_hierarchy(node.GetChild(i), embed_list)
|
---|
1270 |
|
---|
1271 | def generate_embed_list(scene):
|
---|
1272 | embed_list = []
|
---|
1273 | node = scene.GetRootNode()
|
---|
1274 | if node:
|
---|
1275 | for i in range(node.GetChildCount()):
|
---|
1276 | generate_embed_list_from_hierarchy(node.GetChild(i), embed_list)
|
---|
1277 | return embed_list
|
---|
1278 |
|
---|
1279 | # #####################################################
|
---|
1280 | # Generate - Geometries
|
---|
1281 | # #####################################################
|
---|
1282 | def generate_geometry_string(node):
|
---|
1283 |
|
---|
1284 | output = [
|
---|
1285 | '\t' + LabelString( getGeometryName( node, True ) ) + ' : {',
|
---|
1286 | ' "type" : "embedded",',
|
---|
1287 | ' "id" : ' + LabelString( getEmbedName( node, True ) ),
|
---|
1288 | '}'
|
---|
1289 | ]
|
---|
1290 |
|
---|
1291 | return generateMultiLineString( output, '\n\t\t', 0 )
|
---|
1292 |
|
---|
1293 | def generate_geometry_list_from_hierarchy(node, geometry_list):
|
---|
1294 | if node.GetNodeAttribute() == None:
|
---|
1295 | pass
|
---|
1296 | else:
|
---|
1297 | attribute_type = (node.GetNodeAttribute().GetAttributeType())
|
---|
1298 | if attribute_type == FbxNodeAttribute.eMesh:
|
---|
1299 | geometry_string = generate_geometry_string(node)
|
---|
1300 | geometry_list.append(geometry_string)
|
---|
1301 | for i in range(node.GetChildCount()):
|
---|
1302 | generate_geometry_list_from_hierarchy(node.GetChild(i), geometry_list)
|
---|
1303 |
|
---|
1304 | def generate_geometry_list(scene):
|
---|
1305 | geometry_list = []
|
---|
1306 | node = scene.GetRootNode()
|
---|
1307 | if node:
|
---|
1308 | for i in range(node.GetChildCount()):
|
---|
1309 | generate_geometry_list_from_hierarchy(node.GetChild(i), geometry_list)
|
---|
1310 | return geometry_list
|
---|
1311 |
|
---|
1312 | # #####################################################
|
---|
1313 | # Generate - Camera Names
|
---|
1314 | # #####################################################
|
---|
1315 | def generate_camera_name_list_from_hierarchy(node, camera_list):
|
---|
1316 | if node.GetNodeAttribute() == None:
|
---|
1317 | pass
|
---|
1318 | else:
|
---|
1319 | attribute_type = (node.GetNodeAttribute().GetAttributeType())
|
---|
1320 | if attribute_type == FbxNodeAttribute.eCamera:
|
---|
1321 | camera_string = getObjectName(node)
|
---|
1322 | camera_list.append(camera_string)
|
---|
1323 | for i in range(node.GetChildCount()):
|
---|
1324 | generate_camera_name_list_from_hierarchy(node.GetChild(i), camera_list)
|
---|
1325 |
|
---|
1326 | def generate_camera_name_list(scene):
|
---|
1327 | camera_list = []
|
---|
1328 | node = scene.GetRootNode()
|
---|
1329 | if node:
|
---|
1330 | for i in range(node.GetChildCount()):
|
---|
1331 | generate_camera_name_list_from_hierarchy(node.GetChild(i), camera_list)
|
---|
1332 | return camera_list
|
---|
1333 |
|
---|
1334 | # #####################################################
|
---|
1335 | # Generate - Light Object
|
---|
1336 | # #####################################################
|
---|
1337 | def generate_default_light_string(padding):
|
---|
1338 | direction = (1,1,1)
|
---|
1339 | color = (1,1,1)
|
---|
1340 | intensity = 80.0
|
---|
1341 |
|
---|
1342 | output = [
|
---|
1343 |
|
---|
1344 | '\t\t' + LabelString( 'default_light' ) + ' : {',
|
---|
1345 | ' "type" : "DirectionalLight",',
|
---|
1346 | ' "color" : ' + str(getHex(color)) + ',',
|
---|
1347 | ' "intensity" : ' + str(intensity/100.0) + ',',
|
---|
1348 | ' "direction" : ' + Vector3String( direction ) + ',',
|
---|
1349 | ' "target" : ' + LabelString( getObjectName( None ) ),
|
---|
1350 | ' }'
|
---|
1351 |
|
---|
1352 | ]
|
---|
1353 |
|
---|
1354 | return generateMultiLineString( output, '\n\t\t', padding )
|
---|
1355 |
|
---|
1356 | def generate_light_string(node, padding):
|
---|
1357 | light = node.GetNodeAttribute()
|
---|
1358 | light_types = ["point", "directional", "spot", "area", "volume"]
|
---|
1359 | light_type = light_types[light.LightType.Get()]
|
---|
1360 |
|
---|
1361 | transform = node.EvaluateLocalTransform()
|
---|
1362 | position = transform.GetT()
|
---|
1363 |
|
---|
1364 | output = []
|
---|
1365 |
|
---|
1366 | if light_type == "directional":
|
---|
1367 |
|
---|
1368 | # Three.js directional lights emit light from a point in 3d space to a target node or the origin.
|
---|
1369 | # When there is no target, we need to take a point, one unit away from the origin, and move it
|
---|
1370 | # into the right location so that the origin acts like the target
|
---|
1371 |
|
---|
1372 | if node.GetTarget():
|
---|
1373 | direction = position
|
---|
1374 | else:
|
---|
1375 | translation = FbxVector4(0,0,0,0)
|
---|
1376 | scale = FbxVector4(1,1,1,1)
|
---|
1377 | rotation = transform.GetR()
|
---|
1378 | matrix = FbxMatrix(translation, rotation, scale)
|
---|
1379 | direction = matrix.MultNormalize(global_up_vector)
|
---|
1380 |
|
---|
1381 | output = [
|
---|
1382 |
|
---|
1383 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1384 | ' "type" : "DirectionalLight",',
|
---|
1385 | ' "color" : ' + str(getHex(light.Color.Get())) + ',',
|
---|
1386 | ' "intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
|
---|
1387 | ' "direction" : ' + Vector3String( direction ) + ',',
|
---|
1388 | ' "target" : ' + LabelString( getObjectName( node.GetTarget() ) ) + ( ',' if node.GetChildCount() > 0 else '' )
|
---|
1389 | ]
|
---|
1390 |
|
---|
1391 | elif light_type == "point":
|
---|
1392 |
|
---|
1393 | output = [
|
---|
1394 |
|
---|
1395 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1396 | ' "type" : "PointLight",',
|
---|
1397 | ' "color" : ' + str(getHex(light.Color.Get())) + ',',
|
---|
1398 | ' "intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
|
---|
1399 | ' "position" : ' + Vector3String( position ) + ',',
|
---|
1400 | ' "distance" : ' + str(light.FarAttenuationEnd.Get()) + ( ',' if node.GetChildCount() > 0 else '' )
|
---|
1401 |
|
---|
1402 | ]
|
---|
1403 |
|
---|
1404 | elif light_type == "spot":
|
---|
1405 |
|
---|
1406 | output = [
|
---|
1407 |
|
---|
1408 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1409 | ' "type" : "SpotLight",',
|
---|
1410 | ' "color" : ' + str(getHex(light.Color.Get())) + ',',
|
---|
1411 | ' "intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
|
---|
1412 | ' "position" : ' + Vector3String( position ) + ',',
|
---|
1413 | ' "distance" : ' + str(light.FarAttenuationEnd.Get()) + ',',
|
---|
1414 | ' "angle" : ' + str((light.OuterAngle.Get()*math.pi)/180) + ',',
|
---|
1415 | ' "exponent" : ' + str(light.DecayType.Get()) + ',',
|
---|
1416 | ' "target" : ' + LabelString( getObjectName( node.GetTarget() ) ) + ( ',' if node.GetChildCount() > 0 else '' )
|
---|
1417 |
|
---|
1418 | ]
|
---|
1419 |
|
---|
1420 | return generateMultiLineString( output, '\n\t\t', padding )
|
---|
1421 |
|
---|
1422 | def generate_ambient_light_string(scene):
|
---|
1423 |
|
---|
1424 | scene_settings = scene.GetGlobalSettings()
|
---|
1425 | ambient_color = scene_settings.GetAmbientColor()
|
---|
1426 | ambient_color = (ambient_color.mRed, ambient_color.mGreen, ambient_color.mBlue)
|
---|
1427 |
|
---|
1428 | if ambient_color[0] == 0 and ambient_color[1] == 0 and ambient_color[2] == 0:
|
---|
1429 | return None
|
---|
1430 |
|
---|
1431 | class AmbientLight:
|
---|
1432 | def GetName(self):
|
---|
1433 | return "AmbientLight"
|
---|
1434 |
|
---|
1435 | node = AmbientLight()
|
---|
1436 |
|
---|
1437 | output = [
|
---|
1438 |
|
---|
1439 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1440 | ' "type" : "AmbientLight",',
|
---|
1441 | ' "color" : ' + str(getHex(ambient_color)),
|
---|
1442 | '}'
|
---|
1443 |
|
---|
1444 | ]
|
---|
1445 |
|
---|
1446 | return generateMultiLineString( output, '\n\t\t', 0 )
|
---|
1447 |
|
---|
1448 | # #####################################################
|
---|
1449 | # Generate - Camera Object
|
---|
1450 | # #####################################################
|
---|
1451 | def generate_default_camera_string(padding):
|
---|
1452 | position = (100, 100, 100)
|
---|
1453 | near = 0.1
|
---|
1454 | far = 1000
|
---|
1455 | fov = 75
|
---|
1456 |
|
---|
1457 | output = [
|
---|
1458 |
|
---|
1459 | '\t\t' + LabelString( 'default_camera' ) + ' : {',
|
---|
1460 | ' "type" : "PerspectiveCamera",',
|
---|
1461 | ' "fov" : ' + str(fov) + ',',
|
---|
1462 | ' "near" : ' + str(near) + ',',
|
---|
1463 | ' "far" : ' + str(far) + ',',
|
---|
1464 | ' "position" : ' + Vector3String( position ),
|
---|
1465 | ' }'
|
---|
1466 |
|
---|
1467 | ]
|
---|
1468 |
|
---|
1469 | return generateMultiLineString( output, '\n\t\t', padding )
|
---|
1470 |
|
---|
1471 | def generate_camera_string(node, padding):
|
---|
1472 | camera = node.GetNodeAttribute()
|
---|
1473 |
|
---|
1474 | target_node = node.GetTarget()
|
---|
1475 | target = ""
|
---|
1476 | if target_node:
|
---|
1477 | transform = target.EvaluateLocalTransform()
|
---|
1478 | target = transform.GetT()
|
---|
1479 | else:
|
---|
1480 | target = camera.InterestPosition.Get()
|
---|
1481 |
|
---|
1482 | position = camera.Position.Get()
|
---|
1483 |
|
---|
1484 | projection_types = [ "perspective", "orthogonal" ]
|
---|
1485 | projection = projection_types[camera.ProjectionType.Get()]
|
---|
1486 |
|
---|
1487 | near = camera.NearPlane.Get()
|
---|
1488 | far = camera.FarPlane.Get()
|
---|
1489 |
|
---|
1490 | output = []
|
---|
1491 |
|
---|
1492 | if projection == "perspective":
|
---|
1493 |
|
---|
1494 | aspect = camera.PixelAspectRatio.Get()
|
---|
1495 | fov = camera.FieldOfView.Get()
|
---|
1496 |
|
---|
1497 | output = [
|
---|
1498 |
|
---|
1499 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1500 | ' "type" : "PerspectiveCamera",',
|
---|
1501 | ' "fov" : ' + str(fov) + ',',
|
---|
1502 | ' "aspect" : ' + str(aspect) + ',',
|
---|
1503 | ' "near" : ' + str(near) + ',',
|
---|
1504 | ' "far" : ' + str(far) + ',',
|
---|
1505 | ' "position" : ' + Vector3String( position ) + ( ',' if node.GetChildCount() > 0 else '' )
|
---|
1506 |
|
---|
1507 | ]
|
---|
1508 |
|
---|
1509 | elif projection == "orthogonal":
|
---|
1510 |
|
---|
1511 | left = ""
|
---|
1512 | right = ""
|
---|
1513 | top = ""
|
---|
1514 | bottom = ""
|
---|
1515 |
|
---|
1516 | output = [
|
---|
1517 |
|
---|
1518 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1519 | ' "type" : "OrthographicCamera",',
|
---|
1520 | ' "left" : ' + left + ',',
|
---|
1521 | ' "right" : ' + right + ',',
|
---|
1522 | ' "top" : ' + top + ',',
|
---|
1523 | ' "bottom" : ' + bottom + ',',
|
---|
1524 | ' "near" : ' + str(near) + ',',
|
---|
1525 | ' "far" : ' + str(far) + ',',
|
---|
1526 | ' "position" : ' + Vector3String( position ) + ( ',' if node.GetChildCount() > 0 else '' )
|
---|
1527 |
|
---|
1528 | ]
|
---|
1529 |
|
---|
1530 | return generateMultiLineString( output, '\n\t\t', padding )
|
---|
1531 |
|
---|
1532 | # #####################################################
|
---|
1533 | # Generate - Mesh Object
|
---|
1534 | # #####################################################
|
---|
1535 | def generate_mesh_object_string(node, padding):
|
---|
1536 | mesh = node.GetNodeAttribute()
|
---|
1537 | transform = node.EvaluateLocalTransform()
|
---|
1538 | position = transform.GetT()
|
---|
1539 | scale = transform.GetS()
|
---|
1540 | rotation = getRadians(transform.GetR())
|
---|
1541 |
|
---|
1542 | material_count = node.GetMaterialCount()
|
---|
1543 | material_name = ""
|
---|
1544 |
|
---|
1545 | if material_count > 0:
|
---|
1546 | material_names = []
|
---|
1547 | for l in range(mesh.GetLayerCount()):
|
---|
1548 | materials = mesh.GetLayer(l).GetMaterials()
|
---|
1549 | if materials:
|
---|
1550 | if materials.GetReferenceMode() == FbxLayerElement.eIndex:
|
---|
1551 | #Materials are in an undefined external table
|
---|
1552 | continue
|
---|
1553 | for i in range(material_count):
|
---|
1554 | material = node.GetMaterial(i)
|
---|
1555 | material_names.append( getMaterialName(material) )
|
---|
1556 | #If this mesh has more than one material, use a proxy material
|
---|
1557 | material_name = getMaterialName( node, True) if material_count > 1 else material_names[0]
|
---|
1558 |
|
---|
1559 | output = [
|
---|
1560 |
|
---|
1561 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1562 | ' "geometry" : ' + LabelString( getGeometryName( node, True ) ) + ',',
|
---|
1563 | ' "material" : ' + LabelString( material_name ) + ',',
|
---|
1564 | ' "position" : ' + Vector3String( position ) + ',',
|
---|
1565 | ' "rotation" : ' + Vector3String( rotation ) + ',',
|
---|
1566 | ' "scale" : ' + Vector3String( scale ) + ',',
|
---|
1567 | ' "visible" : ' + getObjectVisible( node ) + ( ',' if node.GetChildCount() > 0 else '' )
|
---|
1568 |
|
---|
1569 | ]
|
---|
1570 |
|
---|
1571 | return generateMultiLineString( output, '\n\t\t', padding )
|
---|
1572 |
|
---|
1573 | # #####################################################
|
---|
1574 | # Generate - Object
|
---|
1575 | # #####################################################
|
---|
1576 | def generate_object_string(node, padding):
|
---|
1577 | node_types = ["Unknown", "Null", "Marker", "Skeleton", "Mesh", "Nurbs", "Patch", "Camera",
|
---|
1578 | "CameraStereo", "CameraSwitcher", "Light", "OpticalReference", "OpticalMarker", "NurbsCurve",
|
---|
1579 | "TrimNurbsSurface", "Boundary", "NurbsSurface", "Shape", "LODGroup", "SubDiv", "CachedEffect", "Line"]
|
---|
1580 |
|
---|
1581 | transform = node.EvaluateLocalTransform()
|
---|
1582 | position = transform.GetT()
|
---|
1583 | scale = transform.GetS()
|
---|
1584 | rotation = getRadians(transform.GetR())
|
---|
1585 |
|
---|
1586 | node_type = ""
|
---|
1587 | if node.GetNodeAttribute() == None:
|
---|
1588 | node_type = "Null"
|
---|
1589 | else:
|
---|
1590 | node_type = node_types[node.GetNodeAttribute().GetAttributeType()]
|
---|
1591 |
|
---|
1592 | output = [
|
---|
1593 |
|
---|
1594 | '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
|
---|
1595 | ' "fbx_type" : ' + LabelString( node_type ) + ',',
|
---|
1596 | ' "position" : ' + Vector3String( position ) + ',',
|
---|
1597 | ' "rotation" : ' + Vector3String( rotation ) + ',',
|
---|
1598 | ' "scale" : ' + Vector3String( scale ) + ',',
|
---|
1599 | ' "visible" : ' + getObjectVisible( node ) + ( ',' if node.GetChildCount() > 0 else '' )
|
---|
1600 |
|
---|
1601 | ]
|
---|
1602 |
|
---|
1603 | return generateMultiLineString( output, '\n\t\t', padding )
|
---|
1604 |
|
---|
1605 | # #####################################################
|
---|
1606 | # Parse - Objects
|
---|
1607 | # #####################################################
|
---|
1608 | def generate_object_hierarchy(node, object_list, pad, siblings_left):
|
---|
1609 | object_count = 0
|
---|
1610 | if node.GetNodeAttribute() == None:
|
---|
1611 | object_string = generate_object_string(node, pad)
|
---|
1612 | object_list.append(object_string)
|
---|
1613 | object_count += 1
|
---|
1614 | else:
|
---|
1615 | attribute_type = (node.GetNodeAttribute().GetAttributeType())
|
---|
1616 | if attribute_type == FbxNodeAttribute.eMesh:
|
---|
1617 | object_string = generate_mesh_object_string(node, pad)
|
---|
1618 | object_list.append(object_string)
|
---|
1619 | object_count += 1
|
---|
1620 | elif attribute_type == FbxNodeAttribute.eLight:
|
---|
1621 | object_string = generate_light_string(node, pad)
|
---|
1622 | object_list.append(object_string)
|
---|
1623 | object_count += 1
|
---|
1624 | elif attribute_type == FbxNodeAttribute.eCamera:
|
---|
1625 | object_string = generate_camera_string(node, pad)
|
---|
1626 | object_list.append(object_string)
|
---|
1627 | object_count += 1
|
---|
1628 | else:
|
---|
1629 | object_string = generate_object_string(node, pad)
|
---|
1630 | object_list.append(object_string)
|
---|
1631 | object_count += 1
|
---|
1632 |
|
---|
1633 | if node.GetChildCount() > 0:
|
---|
1634 | object_list.append( PaddingString( pad + 1 ) + '\t\t"children" : {\n' )
|
---|
1635 |
|
---|
1636 | for i in range(node.GetChildCount()):
|
---|
1637 | object_count += generate_object_hierarchy(node.GetChild(i), object_list, pad + 2, node.GetChildCount() - i - 1)
|
---|
1638 |
|
---|
1639 | object_list.append( PaddingString( pad + 1 ) + '\t\t}' )
|
---|
1640 | object_list.append( PaddingString( pad ) + '\t\t}' + (',\n' if siblings_left > 0 else ''))
|
---|
1641 |
|
---|
1642 | return object_count
|
---|
1643 |
|
---|
1644 | def generate_scene_objects_string(scene):
|
---|
1645 | object_count = 0
|
---|
1646 | object_list = []
|
---|
1647 |
|
---|
1648 | ambient_light = generate_ambient_light_string(scene)
|
---|
1649 | if ambient_light:
|
---|
1650 | if scene.GetNodeCount() > 0 or option_default_light or option_default_camera:
|
---|
1651 | ambient_light += (',\n')
|
---|
1652 | object_list.append(ambient_light)
|
---|
1653 | object_count += 1
|
---|
1654 |
|
---|
1655 | if option_default_light:
|
---|
1656 | default_light = generate_default_light_string(0)
|
---|
1657 | if scene.GetNodeCount() > 0 or option_default_camera:
|
---|
1658 | default_light += (',\n')
|
---|
1659 | object_list.append(default_light)
|
---|
1660 | object_count += 1
|
---|
1661 |
|
---|
1662 | if option_default_camera:
|
---|
1663 | default_camera = generate_default_camera_string(0)
|
---|
1664 | if scene.GetNodeCount() > 0:
|
---|
1665 | default_camera += (',\n')
|
---|
1666 | object_list.append(default_camera)
|
---|
1667 | object_count += 1
|
---|
1668 |
|
---|
1669 | node = scene.GetRootNode()
|
---|
1670 | if node:
|
---|
1671 | for i in range(node.GetChildCount()):
|
---|
1672 | object_count += generate_object_hierarchy(node.GetChild(i), object_list, 0, node.GetChildCount() - i - 1)
|
---|
1673 |
|
---|
1674 | return "\n".join(object_list), object_count
|
---|
1675 |
|
---|
1676 | # #####################################################
|
---|
1677 | # Parse - Geometry (non-scene output)
|
---|
1678 | # #####################################################
|
---|
1679 | def extract_geometry(scene, filename):
|
---|
1680 | mesh_string = generate_mesh_string_for_non_scene_output(scene)
|
---|
1681 | return mesh_string
|
---|
1682 |
|
---|
1683 | # #####################################################
|
---|
1684 | # Parse - Scene (scene output)
|
---|
1685 | # #####################################################
|
---|
1686 | def extract_scene(scene, filename):
|
---|
1687 | global_settings = scene.GetGlobalSettings()
|
---|
1688 | objects, nobjects = generate_scene_objects_string(scene)
|
---|
1689 |
|
---|
1690 | textures = generate_texture_list(scene)
|
---|
1691 | materials = generate_material_list(scene)
|
---|
1692 | geometries = generate_geometry_list(scene)
|
---|
1693 | embeds = generate_embed_list(scene)
|
---|
1694 | fogs = []
|
---|
1695 |
|
---|
1696 | ntextures = len(textures)
|
---|
1697 | nmaterials = len(materials)
|
---|
1698 | ngeometries = len(geometries)
|
---|
1699 |
|
---|
1700 | #TODO: extract actual root/scene data here
|
---|
1701 | position = Vector3String( (0,0,0) )
|
---|
1702 | rotation = Vector3String( (0,0,0) )
|
---|
1703 | scale = Vector3String( (1,1,1) )
|
---|
1704 |
|
---|
1705 | camera_names = generate_camera_name_list(scene)
|
---|
1706 | scene_settings = scene.GetGlobalSettings()
|
---|
1707 |
|
---|
1708 | #TODO: this might exist as part of the FBX spec
|
---|
1709 | bgcolor = Vector3String( (0.667,0.667,0.667) )
|
---|
1710 | bgalpha = 1
|
---|
1711 |
|
---|
1712 | # This does not seem to be any help here
|
---|
1713 | # global_settings.GetDefaultCamera()
|
---|
1714 |
|
---|
1715 | defcamera = LabelString(camera_names[0] if len(camera_names) > 0 else "")
|
---|
1716 | if option_default_camera:
|
---|
1717 | defcamera = LabelString('default_camera')
|
---|
1718 |
|
---|
1719 | #TODO: extract fog info from scene
|
---|
1720 | deffog = LabelString("")
|
---|
1721 |
|
---|
1722 | geometries = generateMultiLineString( geometries, ",\n\n\t", 0 )
|
---|
1723 | materials = generateMultiLineString( materials, ",\n\n\t", 0 )
|
---|
1724 | textures = generateMultiLineString( textures, ",\n\n\t", 0 )
|
---|
1725 | embeds = generateMultiLineString( embeds, ",\n\n\t", 0 )
|
---|
1726 | fogs = generateMultiLineString( fogs, ",\n\n\t", 0 )
|
---|
1727 |
|
---|
1728 | output = [
|
---|
1729 |
|
---|
1730 | '{',
|
---|
1731 | ' "metadata": {',
|
---|
1732 | ' "formatVersion" : 3.2,',
|
---|
1733 | ' "type" : "scene",',
|
---|
1734 | ' "generatedBy" : "convert-to-threejs.py",',
|
---|
1735 | ' "objects" : ' + str(nobjects) + ',',
|
---|
1736 | ' "geometries" : ' + str(ngeometries) + ',',
|
---|
1737 | ' "materials" : ' + str(nmaterials) + ',',
|
---|
1738 | ' "textures" : ' + str(ntextures),
|
---|
1739 | ' },',
|
---|
1740 |
|
---|
1741 | '',
|
---|
1742 | ' "urlBaseType": "relativeToScene",',
|
---|
1743 | '',
|
---|
1744 |
|
---|
1745 | ' "objects" :',
|
---|
1746 | ' {',
|
---|
1747 | objects,
|
---|
1748 | ' },',
|
---|
1749 | '',
|
---|
1750 |
|
---|
1751 | ' "geometries" :',
|
---|
1752 | ' {',
|
---|
1753 | '\t' + geometries,
|
---|
1754 | ' },',
|
---|
1755 | '',
|
---|
1756 |
|
---|
1757 | ' "materials" :',
|
---|
1758 | ' {',
|
---|
1759 | '\t' + materials,
|
---|
1760 | ' },',
|
---|
1761 | '',
|
---|
1762 |
|
---|
1763 | ' "textures" :',
|
---|
1764 | ' {',
|
---|
1765 | '\t' + textures,
|
---|
1766 | ' },',
|
---|
1767 | '',
|
---|
1768 |
|
---|
1769 | ' "embeds" :',
|
---|
1770 | ' {',
|
---|
1771 | '\t' + embeds,
|
---|
1772 | ' },',
|
---|
1773 | '',
|
---|
1774 |
|
---|
1775 | ' "fogs" :',
|
---|
1776 | ' {',
|
---|
1777 | '\t' + fogs,
|
---|
1778 | ' },',
|
---|
1779 | '',
|
---|
1780 |
|
---|
1781 | ' "transform" :',
|
---|
1782 | ' {',
|
---|
1783 | ' "position" : ' + position + ',',
|
---|
1784 | ' "rotation" : ' + rotation + ',',
|
---|
1785 | ' "scale" : ' + scale,
|
---|
1786 | ' },',
|
---|
1787 | '',
|
---|
1788 |
|
---|
1789 | ' "defaults" :',
|
---|
1790 | ' {',
|
---|
1791 | ' "bgcolor" : ' + str(bgcolor) + ',',
|
---|
1792 | ' "bgalpha" : ' + str(bgalpha) + ',',
|
---|
1793 | ' "camera" : ' + defcamera + ',',
|
---|
1794 | ' "fog" : ' + deffog,
|
---|
1795 | ' }',
|
---|
1796 | '}'
|
---|
1797 |
|
---|
1798 | ]
|
---|
1799 |
|
---|
1800 | return "\n".join(output)
|
---|
1801 |
|
---|
1802 | # #####################################################
|
---|
1803 | # file helpers
|
---|
1804 | # #####################################################
|
---|
1805 | def write_file(fname, content):
|
---|
1806 | out = open(fname, "w")
|
---|
1807 | out.write(content)
|
---|
1808 | out.close()
|
---|
1809 |
|
---|
1810 | # #####################################################
|
---|
1811 | # main
|
---|
1812 | # #####################################################
|
---|
1813 | if __name__ == "__main__":
|
---|
1814 | from optparse import OptionParser
|
---|
1815 |
|
---|
1816 | try:
|
---|
1817 | from FbxCommon import *
|
---|
1818 | except ImportError:
|
---|
1819 | import platform
|
---|
1820 | msg = 'Could not locate the python FBX SDK!\n'
|
---|
1821 | msg += 'You need to copy the FBX SDK into your python install folder such as '
|
---|
1822 | if platform.system() == 'Windows' or platform.system() == 'Microsoft':
|
---|
1823 | msg += '"Python26/Lib/site-packages"'
|
---|
1824 | elif platform.system() == 'Linux':
|
---|
1825 | msg += '"/usr/local/lib/python2.6/site-packages"'
|
---|
1826 | elif platform.system() == 'Darwin':
|
---|
1827 | msg += '"/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages"'
|
---|
1828 | msg += ' folder.'
|
---|
1829 | print(msg)
|
---|
1830 | sys.exit(1)
|
---|
1831 |
|
---|
1832 | usage = "Usage: %prog [source_file.fbx] [output_file.js] [options]"
|
---|
1833 | parser = OptionParser(usage=usage)
|
---|
1834 |
|
---|
1835 | parser.add_option('-t', '--triangulate', action='store_true', dest='triangulate', help="force quad geometry into triangles", default=False)
|
---|
1836 | parser.add_option('-x', '--no-textures', action='store_true', dest='notextures', help="don't include texture references in output file", default=False)
|
---|
1837 | parser.add_option('-p', '--prefix', action='store_true', dest='prefix', help="prefix object names in output file", default=False)
|
---|
1838 | parser.add_option('-g', '--geometry-only', action='store_true', dest='geometry', help="output geometry only", default=False)
|
---|
1839 | parser.add_option('-c', '--default-camera', action='store_true', dest='defcamera', help="include default camera in output scene", default=False)
|
---|
1840 | parser.add_option('-l', '--defualt-light', action='store_true', dest='deflight', help="include default light in output scene", default=False)
|
---|
1841 |
|
---|
1842 | (options, args) = parser.parse_args()
|
---|
1843 |
|
---|
1844 | option_triangulate = options.triangulate
|
---|
1845 | option_textures = True if not options.notextures else False
|
---|
1846 | option_prefix = options.prefix
|
---|
1847 | option_geometry = options.geometry
|
---|
1848 | option_default_camera = options.defcamera
|
---|
1849 | option_default_light = options.deflight
|
---|
1850 |
|
---|
1851 | # Prepare the FBX SDK.
|
---|
1852 | sdk_manager, scene = InitializeSdkObjects()
|
---|
1853 | converter = FbxGeometryConverter(sdk_manager)
|
---|
1854 | global_up_vector = get_up_vector(scene)
|
---|
1855 |
|
---|
1856 | # The converter takes an FBX file as an argument.
|
---|
1857 | if len(args) > 1:
|
---|
1858 | print("\nLoading file: %s" % args[0])
|
---|
1859 | result = LoadScene(sdk_manager, scene, args[0])
|
---|
1860 | else:
|
---|
1861 | result = False
|
---|
1862 | print("\nUsage: convert_fbx_to_threejs [source_file.fbx] [output_file.js]\n")
|
---|
1863 |
|
---|
1864 | if not result:
|
---|
1865 | print("\nAn error occurred while loading the file...")
|
---|
1866 | else:
|
---|
1867 | if option_triangulate:
|
---|
1868 | print("\nForcing geometry to triangles")
|
---|
1869 | triangulate_scene(scene)
|
---|
1870 |
|
---|
1871 | if option_geometry:
|
---|
1872 | output_content = extract_geometry(scene, os.path.basename(args[0]))
|
---|
1873 | else:
|
---|
1874 | output_content = extract_scene(scene, os.path.basename(args[0]))
|
---|
1875 |
|
---|
1876 | output_path = os.path.join(os.getcwd(), args[1])
|
---|
1877 | write_file(output_path, output_content)
|
---|
1878 |
|
---|
1879 | print("\nExported Three.js file to:\n%s\n" % output_path)
|
---|
1880 |
|
---|
1881 | # Destroy all objects created by the FBX SDK.
|
---|
1882 | sdk_manager.Destroy()
|
---|
1883 | sys.exit(0)
|
---|