Newer
Older
def write_light(my_light):
light = my_light.blenObject.data
fw('\n\tModel: "Model::%s", "Light" {' % my_light.fbxName)
fw('\n\t\tVersion: 232')
write_object_props(my_light.blenObject, None, my_light.parRelMatrix())
# Why are these values here twice?????? - oh well, follow the holy sdk's output
# Blender light types match FBX's, funny coincidence, we just need to
# be sure that all unsupported types are made into a point light
#ePOINT,
#eDIRECTIONAL
#eSPOT
light_type_items = {'POINT': 0, 'SUN': 1, 'SPOT': 2, 'HEMI': 3, 'AREA': 4}
light_type = light_type_items[light.type]
if light_type > 2:
light_type = 1 # hemi and area lights become directional
if light.type == 'HEMI':
Campbell Barton
committed
do_light = not (light.use_diffuse or light.use_specular)
do_shadow = False
else:
Campbell Barton
committed
do_light = not (light.use_only_shadow or (not light.use_diffuse and not light.use_specular))
do_shadow = (light.shadow_method in {'RAY_SHADOW', 'BUFFER_SHADOW'})
# scale = abs(global_matrix.to_scale()[0]) # scale is always uniform in this case # UNUSED
fw('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
fw('\n\t\t\tProperty: "CastLightOnObject", "bool", "",1')
fw('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
fw('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
fw('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
fw('\n\t\t\tProperty: "GoboProperty", "object", ""')
fw('\n\t\t\tProperty: "Color", "Color", "A+",1,1,1')
if light.type == 'SPOT':
fw('\n\t\t\tProperty: "OuterAngle", "Number", "A+",%.2f' %
math.degrees(light.spot_size))
fw('\n\t\t\tProperty: "InnerAngle", "Number", "A+",%.2f' %
(math.degrees(light.spot_size) - math.degrees(light.spot_size) * light.spot_blend))
fw('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
fw('\n\t\t\tProperty: "Color", "Color", "A",%.2f,%.2f,%.2f' % tuple(light.color))
fw('\n\t\t\tProperty: "Intensity", "Intensity", "A+",%.2f' % (light.energy * 100.0))
fw('\n\t\t\tProperty: "Fog", "Fog", "A+",50')
fw('\n\t\t\tProperty: "LightType", "enum", "",%i' % light_type)
fw('\n\t\t\tProperty: "CastLightOnObject", "bool", "",%i' % do_light)
fw('\n\t\t\tProperty: "DrawGroundProjection", "bool", "",1')
fw('\n\t\t\tProperty: "DrawFrontFacingVolumetricLight", "bool", "",0')
fw('\n\t\t\tProperty: "DrawVolumetricLight", "bool", "",1')
fw('\n\t\t\tProperty: "GoboProperty", "object", ""')
if light.type in {'SPOT', 'POINT'}:
if light.falloff_type == 'CONSTANT':
fw('\n\t\t\tProperty: "DecayType", "enum", "",0')
if light.falloff_type == 'INVERSE_LINEAR':
fw('\n\t\t\tProperty: "DecayType", "enum", "",1')
fw('\n\t\t\tProperty: "EnableFarAttenuation", "bool", "",1')
fw('\n\t\t\tProperty: "FarAttenuationEnd", "double", "",%.2f' % (light.distance * 2.0))
if light.falloff_type == 'INVERSE_SQUARE':
fw('\n\t\t\tProperty: "DecayType", "enum", "",2')
fw('\n\t\t\tProperty: "EnableFarAttenuation", "bool", "",1')
fw('\n\t\t\tProperty: "FarAttenuationEnd", "double", "",%.2f' % (light.distance * 2.0))
fw('\n\t\t\tProperty: "CastShadows", "bool", "",%i' % do_shadow)
fw('\n\t\t\tProperty: "ShadowColor", "ColorRGBA", "",0,0,0,1')
fw('\n\t\t}')
fw('\n\t\tMultiLayer: 0'
'\n\t\tMultiTake: 0'
'\n\t\tShading: Y'
'\n\t\tCulling: "CullingOff"'
'\n\t\tTypeFlags: "Light"'
'\n\t\tGeometryVersion: 124'
'\n\t}'
)
# matrixOnly is not used at the moment
def write_null(my_null=None, fbxName=None, fbxType="Null", fbxTypeFlags="Null"):
# ob can be null
if not fbxName:
fbxName = my_null.fbxName
fw('\n\tModel: "Model::%s", "%s" {' % (fbxName, fbxType))
fw('\n\t\tVersion: 232')
if my_null:
poseMatrix = write_object_props(my_null.blenObject, None, my_null.parRelMatrix())[3]
else:
poseMatrix = write_object_props()[3]
pose_items.append((fbxName, poseMatrix))
fw('\n\t\t}'
'\n\t\tMultiLayer: 0'
'\n\t\tMultiTake: 1'
'\n\t\tShading: Y'
'\n\t\tCulling: "CullingOff"'
)
fw('\n\t\tTypeFlags: "%s"' % fbxTypeFlags)
fw('\n\t}')
# Material Settings
if world:
world_amb = world.ambient_color[:]
else:
world_amb = 0.0, 0.0, 0.0 # default value
def write_material(matname, mat):
# Todo, add more material Properties.
if mat:
mat_cold = tuple(mat.diffuse_color)
mat_cols = tuple(mat.specular_color)
#mat_colm = tuple(mat.mirCol) # we wont use the mirror color
mat_dif = mat.diffuse_intensity
mat_amb = mat.ambient
mat_hard = ((float(mat.specular_hardness) - 1.0) / 510.0) * 128.0
mat_spec = mat.specular_intensity
mat_alpha = mat.alpha
mat_emit = mat.emit
mat_shadeless = mat.use_shadeless
if mat_shadeless:
mat_shader = 'Lambert'
else:
if mat.diffuse_shader == 'LAMBERT':
mat_shader = 'Lambert'
else:
mat_shader = 'Phong'
else:
mat_cold = 0.8, 0.8, 0.8
mat_cols = 1.0, 1.0, 1.0
mat_colamb = 1.0, 1.0, 1.0
# mat_colm
mat_dif = 0.8
mat_amb = 1.0
mat_hard = 12.3
mat_spec = 0.5
mat_alpha = 1.0
mat_emit = 0.0
mat_shadeless = False
mat_shader = 'Phong'
fw('\n\t\tVersion: 102')
fw('\n\t\tShadingModel: "%s"' % mat_shader.lower())
fw('\n\t\tMultiLayer: 0')
fw('\n\t\tProperties60: {')
fw('\n\t\t\tProperty: "ShadingModel", "KString", "", "%s"' % mat_shader)
fw('\n\t\t\tProperty: "MultiLayer", "bool", "",0')
fw('\n\t\t\tProperty: "EmissiveColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold) # emit and diffuse color are he same in blender
fw('\n\t\t\tProperty: "EmissiveFactor", "double", "",%.4f' % mat_emit)
fw('\n\t\t\tProperty: "AmbientColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_colamb)
fw('\n\t\t\tProperty: "AmbientFactor", "double", "",%.4f' % mat_amb)
fw('\n\t\t\tProperty: "DiffuseColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cold)
fw('\n\t\t\tProperty: "DiffuseFactor", "double", "",%.4f' % mat_dif)
fw('\n\t\t\tProperty: "Bump", "Vector3D", "",0,0,0')
fw('\n\t\t\tProperty: "TransparentColor", "ColorRGB", "",1,1,1')
fw('\n\t\t\tProperty: "TransparencyFactor", "double", "",%.4f' % (1.0 - mat_alpha))
if not mat_shadeless:
fw('\n\t\t\tProperty: "SpecularColor", "ColorRGB", "",%.4f,%.4f,%.4f' % mat_cols)
fw('\n\t\t\tProperty: "SpecularFactor", "double", "",%.4f' % mat_spec)
fw('\n\t\t\tProperty: "ShininessExponent", "double", "",%.1f' % mat_hard)
fw('\n\t\t\tProperty: "ReflectionColor", "ColorRGB", "",0,0,0')
fw('\n\t\t\tProperty: "ReflectionFactor", "double", "",1')
fw('\n\t\t\tProperty: "Emissive", "ColorRGB", "",0,0,0')
fw('\n\t\t\tProperty: "Ambient", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_colamb)
fw('\n\t\t\tProperty: "Diffuse", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cold)
if not mat_shadeless:
fw('\n\t\t\tProperty: "Specular", "ColorRGB", "",%.1f,%.1f,%.1f' % mat_cols)
fw('\n\t\t\tProperty: "Shininess", "double", "",%.1f' % mat_hard)
fw('\n\t\t\tProperty: "Opacity", "double", "",%.1f' % mat_alpha)
if not mat_shadeless:
fw('\n\t\t\tProperty: "Reflectivity", "double", "",0')
# tex is an Image (Arystan)
def write_video(texname, tex):
# Same as texture really!
Campbell Barton
committed
Type: "Clip"
Properties60: {
Property: "FrameRate", "double", "",0
Property: "LastFrame", "int", "",0
Property: "Width", "int", "",0
Property: "Height", "int", "",0''')
if tex:
fname_rel = bpy_extras.io_utils.path_reference(tex.filepath, base_src, base_dst, path_mode, "", copy_set, tex.library)
fname_strip = bpy.path.basename(fname_rel)
else:
fname_strip = fname_rel = ""
fw('\n\t\t\tProperty: "Path", "charptr", "", "%s"' % fname_strip)
Campbell Barton
committed
Property: "StartFrame", "int", "",0
Property: "StopFrame", "int", "",0
Property: "PlaySpeed", "double", "",1
Property: "Offset", "KTime", "",0
Property: "InterlaceMode", "enum", "",0
Property: "FreeRunning", "bool", "",0
Property: "Loop", "bool", "",0
Property: "AccessMode", "enum", "",0
}
UseMipMap: 0''')
fw('\n\t\tFilename: "%s"' % fname_strip)
fw('\n\t\tRelativeFilename: "%s"' % fname_rel) # make relative
fw('\n\t}')
def write_texture(texname, tex, num):
# if tex is None then this is a dummy tex
fw('\n\tTexture: "Texture::%s", "TextureVideoClip" {' % texname)
fw('\n\t\tType: "TextureVideoClip"')
fw('\n\t\tVersion: 202')
# TODO, rare case _empty_ exists as a name.
Campbell Barton
committed
Properties60: {
Property: "Translation", "Vector", "A+",0,0,0
Property: "Rotation", "Vector", "A+",0,0,0
Property: "Scaling", "Vector", "A+",1,1,1''')
fw('\n\t\t\tProperty: "Texture alpha", "Number", "A+",%i' % num)
# WrapModeU/V 0==rep, 1==clamp, TODO add support
Campbell Barton
committed
Property: "TextureTypeUse", "enum", "",0
Property: "CurrentTextureBlendMode", "enum", "",1
Property: "UseMaterial", "bool", "",0
Property: "UseMipMap", "bool", "",0
Property: "CurrentMappingType", "enum", "",0
Property: "UVSwap", "bool", "",0''')
fw('\n\t\t\tProperty: "WrapModeU", "enum", "",%i' % tex.use_clamp_x)
fw('\n\t\t\tProperty: "WrapModeV", "enum", "",%i' % tex.use_clamp_y)
Campbell Barton
committed
Property: "TextureRotationPivot", "Vector3D", "",0,0,0
Property: "TextureScalingPivot", "Vector3D", "",0,0,0
Property: "VideoProperty", "object", ""
}''')
if tex:
fname_rel = bpy_extras.io_utils.path_reference(tex.filepath, base_src, base_dst, path_mode, "", copy_set, tex.library)
fname_strip = bpy.path.basename(fname_rel)
else:
fname_strip = fname_rel = ""
fw('\n\t\tFileName: "%s"' % fname_strip)
fw('\n\t\tRelativeFilename: "%s"' % fname_rel) # need some make relative command
Campbell Barton
committed
ModelUVTranslation: 0,0
ModelUVScaling: 1,1
Texture_Alpha_Source: "None"
Cropping: 0,0,0,0
}''')
def write_deformer_skin(obname):
Each mesh has its own deformer
fw('\n\tDeformer: "Deformer::Skin %s", "Skin" {' % obname)
fw('''
Campbell Barton
committed
Version: 100
MultiLayer: 0
Type: "Skin"
Properties60: {
}
Link_DeformAcuracy: 50
}''')
# in the example was 'Bip01 L Thigh_2'
def write_sub_deformer_skin(my_mesh, my_bone, weights):
Each subdeformer is specific to a mesh, but the bone it links to can be used by many sub-deformers
So the SubDeformer needs the mesh-object name as a prefix to make it unique
Its possible that there is no matching vgroup in this mesh, in that case no verts are in the subdeformer,
a but silly but dosnt really matter
fw('\n\tDeformer: "SubDeformer::Cluster %s %s", "Cluster" {' % (my_mesh.fbxName, my_bone.fbxName))
Campbell Barton
committed
Version: 100
MultiLayer: 0
Type: "Cluster"
Properties60: {
Property: "SrcModel", "object", ""
Property: "SrcModelReference", "object", ""
}
UserData: "", ""''')
# Support for bone parents
if my_mesh.fbxBoneParent:
if my_mesh.fbxBoneParent == my_bone:
# TODO - this is a bit lazy, we could have a simple write loop
# for this case because all weights are 1.0 but for now this is ok
# Parent Bones arent used all that much anyway.
vgroup_data = [(j, 1.0) for j in range(len(my_mesh.blenData.vertices))]
else:
# This bone is not a parent of this mesh object, no weights
vgroup_data = []
else:
# Normal weight painted mesh
if my_bone.blenName in weights[0]:
Campbell Barton
committed
# Before we used normalized weight list
group_index = weights[0].index(my_bone.blenName)
vgroup_data = [(j, weight[group_index]) for j, weight in enumerate(weights[1]) if weight[group_index]]
else:
vgroup_data = []
i = -1
for vg in vgroup_data:
if i == -1:
i = 0
else:
if i == 23:
i = 0
i += 1
i = -1
for vg in vgroup_data:
if i == -1:
i = 0
else:
if i == 38:
i = 0
i += 1
# Set TransformLink to the global transform of the bone and Transform
# equal to the mesh's transform in bone space.
# http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/why-the-values-return-by-fbxcluster-gettransformmatrix-x-not-same-with-the-value-in-ascii-fbx-file/
global_bone_matrix = (my_bone.fbxArm.matrixWorld * my_bone.restMatrix) * mtx4_z90
global_mesh_matrix = my_mesh.matrixWorld
transform_matrix = (global_bone_matrix.inverted() * global_mesh_matrix)
global_bone_matrix_string = mat4x4str(global_bone_matrix )
transform_matrix_string = mat4x4str(transform_matrix )
fw('\n\t\tTransform: %s' % transform_matrix_string)
fw('\n\t\tTransformLink: %s' % global_bone_matrix_string)
def write_mesh(my_mesh):
me = my_mesh.blenData
# if there are non None materials on this mesh
do_materials = bool([m for m in my_mesh.blenMaterials if m is not None])
do_textures = bool([t for t in my_mesh.blenTextures if t is not None])
do_shapekeys = (my_mesh.blenObject.type == 'MESH' and
my_mesh.blenObject.data.shape_keys and
len(my_mesh.blenObject.data.vertices) == len(me.vertices))
Bastien Montagne
committed
# print(len(my_mesh.blenObject.data.vertices), len(me.vertices)) # XXX does not work when org obj is no mesh!
fw('\n\tModel: "Model::%s", "Mesh" {' % my_mesh.fbxName)
fw('\n\t\tVersion: 232') # newline is added in write_object_props
# convert into lists once.
poseMatrix = write_object_props(my_mesh.blenObject, None, my_mesh.parRelMatrix())[3]
# Calculate the global transform for the mesh in the bind pose the same way we do
# in write_sub_deformer_skin
globalMeshBindPose = my_mesh.matrixWorld * mtx4_z90
pose_items.append((my_mesh.fbxName, globalMeshBindPose))
if do_shapekeys:
for kb in my_mesh.blenObject.data.shape_keys.key_blocks[1:]:
fw('\n\t\t\tProperty: "%s", "Number", "AN",0' % kb.name)
fw('\n\t\t}')
fw('\n\t\tMultiLayer: 0'
'\n\t\tMultiTake: 1'
'\n\t\tShading: Y'
'\n\t\tCulling: "CullingOff"'
)
# Write the Real Mesh data here
_nchunk = 12 # Number of coordinates per line.
t_co = [None] * len(me.vertices) * 3
me.vertices.foreach_get("co", t_co)
fw(',\n\t\t '.join(','.join('%.6f' % co for co in chunk) for chunk in grouper_exact(t_co, _nchunk)))
del t_co
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
_nchunk = 32 # Number of indices per line.
# A bit more complicated, as we have to ^-1 last index of each loop.
# NOTE: Here we assume that loops order matches polygons order!
t_vi = [None] * len(me.loops)
me.loops.foreach_get("vertex_index", t_vi)
t_ls = [None] * len(me.polygons)
me.polygons.foreach_get("loop_start", t_ls)
if t_ls != sorted(t_ls):
print("Error: polygons and loops orders do not match!")
for ls in t_ls:
t_vi[ls - 1] ^= -1
prep = ',\n\t\t '
fw(prep.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(t_vi, _nchunk)))
del t_vi
del t_ls
if use_mesh_edges:
t_vi = [None] * len(me.edges) * 2
me.edges.foreach_get("vertices", t_vi)
# write loose edges as faces.
t_el = [None] * len(me.edges)
me.edges.foreach_get("is_loose", t_el)
num_lose = sum(t_el)
if num_lose != 0:
it_el = ((vi ^ -1) if (idx % 2) else vi for idx, vi in enumerate(t_vi) if t_el[idx // 2])
if (len(me.loops)):
fw(prep)
fw(prep.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(it_el, _nchunk)))
fw('\n\t\tEdges: ')
fw(',\n\t\t '.join(','.join('%i' % vi for vi in chunk) for chunk in grouper_exact(t_vi, _nchunk)))
del t_vi
del t_el
_nchunk = 12 # Number of coordinates per line.
t_vn = [None] * len(me.loops) * 3
me.calc_normals_split()
# NOTE: Here we assume that loops order matches polygons order!
me.loops.foreach_get("normal", t_vn)
fw('\n\t\tLayerElementNormal: 0 {'
'\n\t\t\tVersion: 101'
'\n\t\t\tName: ""'
'\n\t\t\tMappingInformationType: "ByPolygonVertex"'
'\n\t\t\tReferenceInformationType: "Direct"' # We could save some space with IndexToDirect here too...
'\n\t\t\tNormals: ')
fw(',\n\t\t\t '.join(','.join('%.6f' % n for n in chunk) for chunk in grouper_exact(t_vn, _nchunk)))
# Write Face Smoothing
Campbell Barton
committed
if mesh_smooth_type == 'FACE':
t_ps = [None] * len(me.polygons)
me.polygons.foreach_get("use_smooth", t_ps)
fw('\n\t\tLayerElementSmoothing: 0 {'
'\n\t\t\tVersion: 102'
'\n\t\t\tName: ""'
'\n\t\t\tMappingInformationType: "ByPolygon"'
'\n\t\t\tReferenceInformationType: "Direct"'
'\n\t\t\tSmoothing: ')
fw(',\n\t\t\t '.join(','.join('%d' % b for b in chunk) for chunk in grouper_exact(t_ps, _nchunk)))
del t_ps
Campbell Barton
committed
elif mesh_smooth_type == 'EDGE':
# Write Edge Smoothing
t_es = [None] * len(me.edges)
me.edges.foreach_get("use_edge_sharp", t_es)
fw('\n\t\tLayerElementSmoothing: 0 {'
'\n\t\t\tVersion: 101'
'\n\t\t\tName: ""'
'\n\t\t\tMappingInformationType: "ByEdge"'
'\n\t\t\tReferenceInformationType: "Direct"'
'\n\t\t\tSmoothing: ')
fw(',\n\t\t\t '
''.join(','.join('%d' % (not b) for b in chunk) for chunk in grouper_exact(t_es, _nchunk)))
del t_es
Campbell Barton
committed
elif mesh_smooth_type == 'OFF':
pass
else:
raise Exception("invalid mesh_smooth_type: %r" % mesh_smooth_type)
# Write VertexColor Layers
# note, no programs seem to use this info :/
collayers = []
if len(me.vertex_colors):
collayers = me.vertex_colors
t_lc = [None] * len(me.loops) * 3
col2idx = None
_nchunk = 4 # Number of colors per line
_nchunk_idx = 64 # Number of color indices per line
for colindex, collayer in enumerate(collayers):
lc = tuple(zip(*[iter(t_lc)] * 3))
fw('\n\t\tLayerElementColor: %i {'
'\n\t\t\tVersion: 101'
'\n\t\t\tName: "%s"'
'\n\t\t\tMappingInformationType: "ByPolygonVertex"'
'\n\t\t\tReferenceInformationType: "IndexToDirect"'
'\n\t\t\tColors: ' % (colindex, collayer.name))
col2idx = tuple(set(lc))
fw(',\n\t\t\t '.join(','.join('%.6f,%.6f,%.6f,1' % c for c in chunk)
for chunk in grouper_exact(col2idx, _nchunk)))
col2idx = {col: idx for idx, col in enumerate(col2idx)}
fw(',\n\t\t\t '
''.join(','.join('%d' % col2idx[c] for c in chunk) for chunk in grouper_exact(lc, _nchunk_idx)))
# Write UV and texture layers.
uvlayers = []
if do_uvs:
uvtextures = me.uv_textures
t_uv = [None] * len(me.loops) * 2
t_pi = None
uv2idx = None
tex2idx = None
_nchunk = 6 # Number of UVs per line
_nchunk_idx = 64 # Number of UV indices per line
if do_textures:
is_tex_unique = len(my_mesh.blenTextures) == 1
tex2idx = {None: -1}
tex2idx.update({tex: i for i, tex in enumerate(my_mesh.blenTextures)})
for uvindex, (uvlayer, uvtexture) in enumerate(zip(uvlayers, uvtextures)):
uvlayer.data.foreach_get("uv", t_uv)
uvco = tuple(zip(*[iter(t_uv)] * 2))
fw('\n\t\tLayerElementUV: %d {'
'\n\t\t\tVersion: 101'
'\n\t\t\tName: "%s"'
'\n\t\t\tMappingInformationType: "ByPolygonVertex"'
'\n\t\t\tReferenceInformationType: "IndexToDirect"'
'\n\t\t\tUV: ' % (uvindex, uvlayer.name))
uv2idx = tuple(set(uvco))
fw(',\n\t\t\t '
''.join(','.join('%.6f,%.6f' % uv for uv in chunk) for chunk in grouper_exact(uv2idx, _nchunk)))
uv2idx = {uv: idx for idx, uv in enumerate(uv2idx)}
fw(',\n\t\t\t '
''.join(','.join('%d' % uv2idx[uv] for uv in chunk) for chunk in grouper_exact(uvco, _nchunk_idx)))
if do_textures:
fw('\n\t\tLayerElementTexture: %d {'
'\n\t\t\tVersion: 101'
'\n\t\t\tName: "%s"'
'\n\t\t\tMappingInformationType: "%s"'
'\n\t\t\tReferenceInformationType: "IndexToDirect"'
'\n\t\t\tBlendMode: "Translucent"'
'\n\t\t\tTextureAlpha: 1'
'\n\t\t\tTextureId: '
% (uvindex, uvlayer.name, ('AllSame' if is_tex_unique else 'ByPolygon')))
if is_tex_unique:
else:
t_pi = (d.image for d in uvtexture.data) # Can't use foreach_get here :(
fw(',\n\t\t\t '.join(','.join('%d' % tex2idx[i] for i in chunk)
for chunk in grouper_exact(t_pi, _nchunk_idx)))
Bastien Montagne
committed
fw('\n\t\t}')
if not do_textures:
fw('\n\t\tLayerElementTexture: 0 {'
'\n\t\t\tVersion: 101'
'\n\t\t\tName: ""'
'\n\t\t\tMappingInformationType: "NoMappingInformation"'
'\n\t\t\tReferenceInformationType: "IndexToDirect"'
'\n\t\t\tBlendMode: "Translucent"'
'\n\t\t\tTextureAlpha: 1'
'\n\t\t\tTextureId: '
'\n\t\t}')
# Done with UV/textures.
if do_materials:
is_mat_unique = len(my_mesh.blenMaterials) == 1
fw('\n\t\tLayerElementMaterial: 0 {'
'\n\t\t\tVersion: 101'
'\n\t\t\tName: ""'
'\n\t\t\tMappingInformationType: "%s"'
'\n\t\t\tReferenceInformationType: "IndexToDirect"'
'\n\t\t\tMaterials: ' % ('AllSame' if is_mat_unique else 'ByPolygon',))
if is_mat_unique:
else:
_nchunk = 64 # Number of material indices per line
# Build a material mapping for this
mat2idx = {mt: i for i, mt in enumerate(my_mesh.blenMaterials)} # (local-mat, tex) -> global index.
mats = my_mesh.blenMaterialList
if me.uv_textures.active and do_uvs:
poly_tex = me.uv_textures.active.data
else:
poly_tex = [None] * len(me.polygons)
_it_mat = (mats[p.material_index] for p in me.polygons)
_it_tex = (pt.image if pt else None for pt in poly_tex) # WARNING - MULTI UV LAYER IMAGES NOT SUPPORTED
t_mti = (mat2idx[m, t] for m, t in zip(_it_mat, _it_tex))
fw(',\n\t\t\t '
''.join(','.join('%d' % i for i in chunk) for chunk in grouper_exact(t_mti, _nchunk)))
fw('\n\t\tLayer: 0 {'
'\n\t\t\tVersion: 100'
'\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementNormal"'
'\n\t\t\t\tTypedIndex: 0'
'\n\t\t\t}')
Campbell Barton
committed
# Smoothing info
if mesh_smooth_type != 'OFF':
fw('\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementSmoothing"'
'\n\t\t\t\tTypedIndex: 0'
'\n\t\t\t}')
if me.vertex_colors:
fw('\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementColor"'
'\n\t\t\t\tTypedIndex: 0'
'\n\t\t\t}')
if do_uvs: # same as me.faceUV
fw('\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementUV"'
'\n\t\t\t\tTypedIndex: 0'
'\n\t\t\t}')
Bastien Montagne
committed
#if do_textures:
if True:
fw('\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementTexture"'
'\n\t\t\t\tTypedIndex: 0'
'\n\t\t\t}')
if do_materials:
fw('\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementMaterial"'
'\n\t\t\t\tTypedIndex: 0'
'\n\t\t\t}')
Bastien Montagne
committed
for i in range(1, len(uvlayers)):
fw('\n\t\tLayer: %d {'
'\n\t\t\tVersion: 100'
'\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementUV"'
'\n\t\t\t\tTypedIndex: %d'
'\n\t\t\t}' % (i, i))
if do_textures:
fw('\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementTexture"'
'\n\t\t\t\tTypedIndex: %d'
'\n\t\t\t}' % i)
Bastien Montagne
committed
else:
fw('\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementTexture"'
'\n\t\t\t\tTypedIndex: 0'
'\n\t\t\t}')
# XXX Col layers are written before UV ones above, why adding them after UV here???
# And why this offset based on len(UV layers) - 1???
# I have the feeling some indices are wrong here!
# --mont29
if len(collayers) > 1:
# Take into account any UV layers
layer_offset = len(uvlayers) - 1 if uvlayers else 0
for i in range(layer_offset, len(collayers) + layer_offset):
fw('\n\t\tLayer: %d {'
'\n\t\t\tVersion: 100'
'\n\t\t\tLayerElement: {'
'\n\t\t\t\tType: "LayerElementColor"'
'\n\t\t\t\tTypedIndex: %d'
'\n\t\t\t}'
'\n\t\t}' % (i, i))
# Not sure this works really good...
# Aren't key's co already relative if set as such?
# Also, does not handle custom relative option for each key...
# --mont29
import operator
key_blocks = my_mesh.blenObject.data.shape_keys.key_blocks[:]
t_sk_basis = [None] * len(me.vertices) * 3
t_sk = [None] * len(me.vertices) * 3
key_blocks[0].data.foreach_get("co", t_sk_basis)
_nchunk = 4 # Number of delta coordinates per line
_nchunk_idx = 32 # Number of vert indices per line
for kb in key_blocks[1:]:
kb.data.foreach_get("co", t_sk)
_dcos = tuple(zip(*[map(operator.sub, t_sk, t_sk_basis)] * 3))
verts = tuple(i for i, dco in enumerate(_dcos) if sum(map(operator.pow, dco, (2, 2, 2))) > 3e-12)
dcos = (_dcos[i] for i in verts)
fw('\n\t\tShape: "%s" {'
'\n\t\t\tIndexes: ' % kb.name)
fw(',\n\t\t\t '
''.join(','.join('%d' % i for i in chunk) for chunk in grouper_exact(verts, _nchunk_idx)))
fw(',\n\t\t\t '
''.join(','.join('%.6f,%.6f,%.6f' % c for c in chunk) for chunk in grouper_exact(dcos, _nchunk)))
# Would need to recompute them I guess... and I assume those are supposed to be delta as well?
fw(',\n\t\t\t '
''.join(','.join('0,0,0' for c in chunk) for chunk in grouper_exact(range(len(verts)), _nchunk)))
del t_sk_basis
del t_sk
def write_group(name):
fw('\n\tGroupSelection: "GroupSelection::%s", "Default" {' % name)
Campbell Barton
committed
Properties60: {
Property: "MultiLayer", "bool", "",0
Property: "Pickable", "bool", "",1
Property: "Transformable", "bool", "",1
Property: "Show", "bool", "",1
}
MultiLayer: 0
}''')
# add meshes here to clear because they are not used anywhere.
meshes_to_clear = []
ob_meshes = []
ob_lights = []
ob_cameras = []
# in fbx we export bones as children of the mesh
# armatures not a part of a mesh, will be added to ob_arms
ob_bones = []
ob_arms = []
ob_null = [] # emptys
# List of types that have blender objects (not bones)
ob_all_typegroups = [ob_meshes, ob_lights, ob_cameras, ob_arms, ob_null]
groups = [] # blender groups, only add ones that have objects in the selections
materials = set() # (mat, image) items
textures = set()
tmp_ob_type = None # in case no objects are exported, so as not to raise an error
Campbell Barton
committed
if 'ARMATURE' in object_types:
# This is needed so applying modifiers dosnt apply the armature deformation, its also needed
# ...so mesh objects return their rest worldspace matrix when bone-parents are exported as weighted meshes.
# set every armature to its rest, backup the original values so we done mess up the scene
ob_arms_orig_rest = [arm.pose_position for arm in bpy.data.armatures]
for arm in bpy.data.armatures:
arm.pose_position = 'REST'
if ob_arms_orig_rest:
for ob_base in bpy.data.objects:
if ob_base.type == 'ARMATURE':
# This causes the makeDisplayList command to effect the mesh
scene.frame_set(scene.frame_current)
# ignore dupli children
if ob_base.parent and ob_base.parent.dupli_type in {'VERTS', 'FACES'}:
continue
obs = [(ob_base, ob_base.matrix_world.copy())]
if ob_base.dupli_type != 'NONE':
obs = [(dob.object, dob.matrix.copy()) for dob in ob_base.dupli_list]
for ob, mtx in obs:
tmp_ob_type = ob.type
if tmp_ob_type == 'CAMERA':
Campbell Barton
committed
if 'CAMERA' in object_types:
ob_cameras.append(my_object_generic(ob, mtx))
elif tmp_ob_type == 'LAMP':
Campbell Barton
committed
if 'LAMP' in object_types:
ob_lights.append(my_object_generic(ob, mtx))
elif tmp_ob_type == 'ARMATURE':
Campbell Barton
committed
if 'ARMATURE' in object_types:
# TODO - armatures dont work in dupligroups!
if ob not in ob_arms:
ob_arms.append(ob)
# ob_arms.append(ob) # replace later. was "ob_arms.append(sane_obname(ob), ob)"
elif tmp_ob_type == 'EMPTY':
Campbell Barton
committed
if 'EMPTY' in object_types:
ob_null.append(my_object_generic(ob, mtx))
Campbell Barton
committed
elif 'MESH' in object_types:
origData = True
if tmp_ob_type != 'MESH':
try:
me = ob.to_mesh(scene, True, 'PREVIEW')
except:
me = None
if me:
meshes_to_clear.append(me)
mats = me.materials
origData = False
else:
# Mesh Type!
if use_mesh_modifiers:
me = ob.to_mesh(scene, True, 'PREVIEW')
# print ob, me, me.getVertGroupNames()
meshes_to_clear.append(me)
origData = False
mats = me.materials
else:
me = ob.data
me.update()
mats = me.materials
# # Support object colors
# tmp_colbits = ob.colbits
# if tmp_colbits:
# tmp_ob_mats = ob.getMaterials(1) # 1 so we get None's too.
# for i in xrange(16):
# if tmp_colbits & (1<<i):
# mats[i] = tmp_ob_mats[i]
# del tmp_ob_mats
# del tmp_colbits
if me:
# # This WILL modify meshes in blender if use_mesh_modifiers is disabled.
# # so strictly this is bad. but only in rare cases would it have negative results
# # say with dupliverts the objects would rotate a bit differently
# if EXP_MESH_HQ_NORMALS:
# BPyMesh.meshCalcNormals(me) # high quality normals nice for realtime engines.
if not mats:
mats = [None]
texture_set_local = set()
material_set_local = set()
if me.uv_textures:
for uvlayer in me.uv_textures:
for p, p_uv in zip(me.polygons, uvlayer.data):
tex = p_uv.image
texture_set_local.add(tex)
mat = mats[p.material_index]
# Should not be needed anymore.
#try:
#mat = mats[p.material_index]
#except:
#mat = None
material_set_local.add((mat, tex))
else:
for mat in mats:
# 2.44 use mat.lib too for uniqueness
material_set_local.add((mat, None))
textures |= texture_set_local
materials |= material_set_local
Campbell Barton
committed
if 'ARMATURE' in object_types:
armob = ob.find_armature()
blenParentBoneName = None
# parent bone - special case
if (not armob) and ob.parent and ob.parent.type == 'ARMATURE' and \
ob.parent_type == 'BONE':
armob = ob.parent
blenParentBoneName = ob.parent_bone
if armob and armob not in ob_arms:
ob_arms.append(armob)
# Warning for scaled, mesh objects with armatures
if abs(ob.scale[0] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05 or abs(ob.scale[1] - 1.0) > 0.05:
operator.report({'WARNING'}, "Object '%s' has a scale of (%.3f, %.3f, %.3f), " \
"Armature deformation will not work as expected " \
"(apply Scale to fix)" % ((ob.name,) + tuple(ob.scale)))
else:
blenParentBoneName = armob = None
my_mesh = my_object_generic(ob, mtx)
my_mesh.blenData = me
my_mesh.origData = origData
my_mesh.blenMaterials = list(material_set_local)
my_mesh.blenMaterialList = mats
my_mesh.blenTextures = list(texture_set_local)
Campbell Barton
committed
# sort the name so we get predictable output, some items may be NULL
Campbell Barton
committed
my_mesh.blenMaterials.sort(key=lambda m: (getattr(m[0], "name", ""), getattr(m[1], "name", "")))
my_mesh.blenTextures.sort(key=lambda m: getattr(m, "name", ""))
# if only 1 null texture then empty the list
if len(my_mesh.blenTextures) == 1 and my_mesh.blenTextures[0] is None:
my_mesh.blenTextures = []
my_mesh.fbxArm = armob # replace with my_object_generic armature instance later
my_mesh.fbxBoneParent = blenParentBoneName # replace with my_bone instance later
ob_meshes.append(my_mesh)
# not forgetting to free dupli_list
if ob_base.dupli_list:
Campbell Barton
committed
if 'ARMATURE' in object_types:
# now we have the meshes, restore the rest arm position
for i, arm in enumerate(bpy.data.armatures):
arm.pose_position = ob_arms_orig_rest[i]
if ob_arms_orig_rest:
for ob_base in bpy.data.objects:
if ob_base.type == 'ARMATURE':
# This causes the makeDisplayList command to effect the mesh
scene.frame_set(scene.frame_current)
del tmp_ob_type, context_objects
# now we have collected all armatures, add bones
for i, ob in enumerate(ob_arms):
ob_arms[i] = my_arm = my_object_generic(ob)
my_arm.fbxBones = []
my_arm.blenData = ob.data
if ob.animation_data:
my_arm.blenAction = ob.animation_data.action
else:
my_arm.blenAction = None
my_arm.blenActionList = []
# fbxName, blenderObject, my_bones, blenderActions
#ob_arms[i] = fbxArmObName, ob, arm_my_bones, (ob.action, [])
if use_armature_deform_only:
# tag non deforming bones that have no deforming children
deform_map = dict.fromkeys(my_arm.blenData.bones, False)
for bone in my_arm.blenData.bones:
if bone.use_deform:
deform_map[bone] = True
# tag all parents, even ones that are not deform since their child _is_
for parent in bone.parent_recursive:
deform_map[parent] = True
for bone in my_arm.blenData.bones:
if use_armature_deform_only:
# if this bone doesnt deform, and none of its children deform, skip it!
if not deform_map[bone]:
continue
my_bone = my_bone_class(bone, my_arm)
my_arm.fbxBones.append(my_bone)
ob_bones.append(my_bone)
if use_armature_deform_only:
del deform_map
# add the meshes to the bones and replace the meshes armature with own armature class
#for obname, ob, mtx, me, mats, arm, armname in ob_meshes:
for my_mesh in ob_meshes:
# Replace
# ...this could be sped up with dictionary mapping but its unlikely for
# it ever to be a bottleneck - (would need 100+ meshes using armatures)