Newer
Older
# Compute global matrix and scale.
if not use_manual_orientation:
axis_forward = (elem_props_get_integer(fbx_settings_props, b'FrontAxis', 1),
elem_props_get_integer(fbx_settings_props, b'FrontAxisSign', 1))
axis_up = (elem_props_get_integer(fbx_settings_props, b'UpAxis', 2),
elem_props_get_integer(fbx_settings_props, b'UpAxisSign', 1))
axis_coord = (elem_props_get_integer(fbx_settings_props, b'CoordAxis', 0),
elem_props_get_integer(fbx_settings_props, b'CoordAxisSign', 1))
axis_key = (axis_up, axis_forward, axis_coord)
axis_up, axis_forward = {v: k for k, v in RIGHT_HAND_AXES.items()}.get(axis_key, ('Z', 'Y'))
global_matrix = (Matrix.Scale(global_scale, 4) *
axis_conversion(from_forward=axis_forward, from_up=axis_up).to_4x4())
Bastien Montagne
committed
# To cancel out unwanted rotation/scale on nodes.
global_matrix_inv = global_matrix.inverted()
# For transforming mesh normals.
global_matrix_inv_transposed = global_matrix_inv.transposed()
# Compute bone correction matrix
bone_correction_matrix = None # None means no correction/identity
if not automatic_bone_orientation:
if (primary_bone_axis, secondary_bone_axis) != ('Y', 'X'):
bone_correction_matrix = axis_conversion(from_forward='X',
from_up='Y',
to_forward=secondary_bone_axis,
to_up=primary_bone_axis,
).to_4x4()
# Compute framerate settings.
custom_fps = elem_props_get_number(fbx_settings_props, b'CustomFrameRate', 25.0)
time_mode = elem_props_get_enum(fbx_settings_props, b'TimeMode')
real_fps = {eid: val for val, eid in FBX_FRAMERATES[1:]}.get(time_mode, custom_fps)
if real_fps < 0.0:
real_fps = 25.0
scene.render.fps = round(real_fps)
scene.render.fps_base = scene.render.fps / real_fps
Jens Ch. Restemeier
committed
# store global settings that need to be accessed during conversion
settings = FBXImportSettings(
operator.report, (axis_up, axis_forward), global_matrix, global_scale,
Bastien Montagne
committed
bake_space_transform, global_matrix_inv, global_matrix_inv_transposed,
Jens Ch. Restemeier
committed
use_cycles, use_image_search,
use_alpha_decals, decal_offset,
use_custom_props, use_custom_props_enum_as_string,
Bastien Montagne
committed
cycles_material_wrap_map, image_cache,
ignore_leaf_bones, automatic_bone_orientation, bone_correction_matrix,
Bastien Montagne
committed
use_prepost_rot,
Jens Ch. Restemeier
committed
)
fbx_defs = elem_find_first(elem_root, b'Definitions') # can be None
fbx_nodes = elem_find_first(elem_root, b'Objects')
fbx_connections = elem_find_first(elem_root, b'Connections')
if fbx_nodes is None:
operator.report({'ERROR'}, "No 'Objects' found in file %r" % filepath)
return {'CANCELLED'}
if fbx_connections is None:
operator.report({'ERROR'}, "No 'Connections' found in file %r" % filepath)
return {'CANCELLED'}
# ----
# First load property templates
# Load 'PropertyTemplate' values.
# Key is a tuple, (ObjectType, FBXNodeType)
# eg, (b'Texture', b'KFbxFileTexture')
# (b'Geometry', b'KFbxMesh')
fbx_templates = {}
def _():
if fbx_defs is not None:
for fbx_def in fbx_defs.elems:
if fbx_def.id == b'ObjectType':
for fbx_subdef in fbx_def.elems:
if fbx_subdef.id == b'PropertyTemplate':
assert(fbx_def.props_type == b'S')
assert(fbx_subdef.props_type == b'S')
# (b'Texture', b'KFbxFileTexture') - eg.
key = fbx_def.props[0], fbx_subdef.props[0]
fbx_templates[key] = fbx_subdef
_(); del _
def fbx_template_get(key):
Bastien Montagne
committed
ret = fbx_templates.get(key, fbx_elem_nil)
if ret is None:
# Newest FBX (7.4 and above) use no more 'K' in their type names...
key = (key[0], key[1][1:])
return fbx_templates.get(key, fbx_elem_nil)
return ret
# ----
# Build FBX node-table
def _():
for fbx_obj in fbx_nodes.elems:
# TODO, investigate what other items after first 3 may be
assert(fbx_obj.props_type[:3] == b'LSS')
fbx_uuid = elem_uuid(fbx_obj)
fbx_table_nodes[fbx_uuid] = [fbx_obj, None]
_(); del _
# ----
# Load in the data
# http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html?url=
# WS73099cc142f487551fea285e1221e4f9ff8-7fda.htm,topicNumber=d0e6388
fbx_connection_map = {}
fbx_connection_map_reverse = {}
def _():
for fbx_link in fbx_connections.elems:
c_type = fbx_link.props[0]
if fbx_link.props_type[1:3] == b'LL':
c_src, c_dst = fbx_link.props[1:3]
fbx_connection_map.setdefault(c_src, []).append((c_dst, fbx_link))
fbx_connection_map_reverse.setdefault(c_dst, []).append((c_src, fbx_link))
_(); del _
# ----
# Load mesh data
def _():
fbx_tmpl = fbx_template_get((b'Geometry', b'KFbxMesh'))
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Geometry':
continue
if fbx_obj.props[-1] == b'Mesh':
assert(blen_data is None)
Jens Ch. Restemeier
committed
fbx_item[1] = blen_read_geom(fbx_tmpl, fbx_obj, settings)
_(); del _
# ----
# Load material data
def _():
fbx_tmpl = fbx_template_get((b'Material', b'KFbxSurfacePhong'))
# b'KFbxSurfaceLambert'
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Material':
continue
assert(blen_data is None)
Jens Ch. Restemeier
committed
fbx_item[1] = blen_read_material(fbx_tmpl, fbx_obj, settings)
_(); del _
# ----
# Load image data
def _():
fbx_tmpl = fbx_template_get((b'Texture', b'KFbxFileTexture'))
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Texture':
continue
Jens Ch. Restemeier
committed
fbx_item[1] = blen_read_texture(fbx_tmpl, fbx_obj, basedir, settings)
_(); del _
# ----
# Load camera data
def _():
fbx_tmpl = fbx_template_get((b'NodeAttribute', b'KFbxCamera'))
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'NodeAttribute':
continue
if fbx_obj.props[-1] == b'Camera':
assert(blen_data is None)
fbx_item[1] = blen_read_camera(fbx_tmpl, fbx_obj, global_scale)
_(); del _
# ----
# Load lamp data
def _():
fbx_tmpl = fbx_template_get((b'NodeAttribute', b'KFbxLight'))
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'NodeAttribute':
continue
if fbx_obj.props[-1] == b'Light':
assert(blen_data is None)
fbx_item[1] = blen_read_light(fbx_tmpl, fbx_obj, global_scale)
# ----
# Connections
def connection_filter_ex(fbx_uuid, fbx_id, dct):
return [(c_found[0], c_found[1], c_type)
for (c_uuid, c_type) in dct.get(fbx_uuid, ())
# 0 is used for the root node, which isnt in fbx_table_nodes
for c_found in (() if c_uuid is 0 else (fbx_table_nodes.get(c_uuid, (None, None)),))
if (fbx_id is None) or (c_found[0] and c_found[0].id == fbx_id)]
def connection_filter_forward(fbx_uuid, fbx_id):
return connection_filter_ex(fbx_uuid, fbx_id, fbx_connection_map)
def connection_filter_reverse(fbx_uuid, fbx_id):
return connection_filter_ex(fbx_uuid, fbx_id, fbx_connection_map_reverse)
Bastien Montagne
committed
# -- temporary helper hierarchy to build armatures and objects from
# lookup from uuid to helper node. Used to build parent-child relations and later to look up animated nodes.
fbx_helper_nodes = {}
def _():
# We build an intermediate hierarchy used to:
# - Calculate and store bone orientation correction matrices. The same matrices will be reused for animation.
# - Find/insert armature nodes.
# - Filter leaf bones.
Bastien Montagne
committed
# create scene root
fbx_helper_nodes[0] = root_helper = FbxImportHelperNode(None, None, None, False)
root_helper.is_root = True
# add fbx nodes
fbx_tmpl = fbx_template_get((b'Model', b'KFbxNode'))
for a_uuid, a_item in fbx_table_nodes.items():
Bastien Montagne
committed
if fbx_obj is None or fbx_obj.id != b'Model':
continue
Bastien Montagne
committed
fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
assert(fbx_props[0] is not None)
Bastien Montagne
committed
transform_data = blen_read_object_transform_preprocess(fbx_props, fbx_obj, Matrix(), use_prepost_rot)
is_bone = fbx_obj.props[2] in {b'LimbNode'} # Note: 'Root' "bones" are handled as (armature) objects.
Bastien Montagne
committed
fbx_helper_nodes[a_uuid] = FbxImportHelperNode(fbx_obj, bl_data, transform_data, is_bone)
# add parent-child relations and add blender data to the node
for fbx_link in fbx_connections.elems:
if fbx_link.props[0] != b'OO':
continue
if fbx_link.props_type[1:3] == b'LL':
c_src, c_dst = fbx_link.props[1:3]
parent = fbx_helper_nodes.get(c_dst)
if parent is None:
continue
Bastien Montagne
committed
child = fbx_helper_nodes.get(c_src)
if child is None:
# add blender data (meshes, lights, cameras, etc.) to a helper node
fbx_sdata, bl_data = p_item = fbx_table_nodes.get(c_src, (None, None))
if fbx_sdata is None:
continue
Bastien Montagne
committed
if fbx_sdata.id not in {b'Geometry', b'NodeAttribute'}:
continue
Bastien Montagne
committed
parent.bl_data = bl_data
else:
# set parent
child.parent = parent
Bastien Montagne
committed
# find armatures (either an empty below a bone or a new node inserted at the bone
root_helper.find_armatures()
Bastien Montagne
committed
# mark nodes that have bone children
root_helper.find_bone_children()
Bastien Montagne
committed
# mark nodes that need a bone to attach child-bones to
root_helper.find_fake_bones()
# mark leaf nodes that are only required to mark the end of their parent bone
if settings.ignore_leaf_bones:
root_helper.mark_leaf_bones()
# What a mess! Some bones have several BindPoses, some have none, clusters contain a bind pose as well,
# and you can have several clusters per bone!
Bastien Montagne
committed
# Maybe some conversion can be applied to put them all into the same frame of reference?
# get the bind pose from pose elements
for a_uuid, a_item in fbx_table_nodes.items():
Bastien Montagne
committed
if fbx_obj is None:
continue
Bastien Montagne
committed
if fbx_obj.id != b'Pose':
continue
if fbx_obj.props[2] != b'BindPose':
continue
for fbx_pose_node in fbx_obj.elems:
if fbx_pose_node.id != b'PoseNode':
continue
node_elem = elem_find_first(fbx_pose_node, b'Node')
node = elem_uuid(node_elem)
matrix_elem = elem_find_first(fbx_pose_node, b'Matrix')
matrix = array_to_matrix4(matrix_elem.props[0]) if matrix_elem else None
bone = fbx_helper_nodes.get(node)
if bone and matrix:
# Store the matrix in the helper node.
# There may be several bind pose matrices for the same node, but in tests they seem to be identical.
Bastien Montagne
committed
bone.bind_matrix = matrix # global space
# get clusters and bind pose
for helper_uuid, helper_node in fbx_helper_nodes.items():
if not helper_node.is_bone:
Bastien Montagne
committed
for cluster_uuid, cluster_link in fbx_connection_map.get(helper_uuid, ()):
if cluster_link.props[0] != b'OO':
continue
fbx_cluster, _ = fbx_table_nodes.get(cluster_uuid, (None, None))
if fbx_cluster is None or fbx_cluster.id != b'Deformer' or fbx_cluster.props[2] != b'Cluster':
continue
Bastien Montagne
committed
# Get the bind pose from the cluster:
tx_mesh_elem = elem_find_first(fbx_cluster, b'Transform', default=None)
tx_mesh = array_to_matrix4(tx_mesh_elem.props[0]) if tx_mesh_elem else Matrix()
tx_bone_elem = elem_find_first(fbx_cluster, b'TransformLink', default=None)
tx_bone = array_to_matrix4(tx_bone_elem.props[0]) if tx_bone_elem else None
Bastien Montagne
committed
tx_arm_elem = elem_find_first(fbx_cluster, b'TransformAssociateModel', default=None)
tx_arm = array_to_matrix4(tx_arm_elem.props[0]) if tx_arm_elem else None
Bastien Montagne
committed
mesh_matrix = tx_mesh
armature_matrix = tx_arm
Bastien Montagne
committed
if tx_bone:
mesh_matrix = tx_bone * mesh_matrix
helper_node.bind_matrix = tx_bone # overwrite the bind matrix
Bastien Montagne
committed
# Get the meshes driven by this cluster: (Shouldn't that be only one?)
meshes = set()
for skin_uuid, skin_link in fbx_connection_map.get(cluster_uuid):
if skin_link.props[0] != b'OO':
Bastien Montagne
committed
fbx_skin, _ = fbx_table_nodes.get(skin_uuid, (None, None))
if fbx_skin is None or fbx_skin.id != b'Deformer' or fbx_skin.props[2] != b'Skin':
continue
Bastien Montagne
committed
for mesh_uuid, mesh_link in fbx_connection_map.get(skin_uuid):
if mesh_link.props[0] != b'OO':
continue
fbx_mesh, _ = fbx_table_nodes.get(mesh_uuid, (None, None))
if fbx_mesh is None or fbx_mesh.id != b'Geometry' or fbx_mesh.props[2] != b'Mesh':
continue
for object_uuid, object_link in fbx_connection_map.get(mesh_uuid):
if object_link.props[0] != b'OO':
continue
mesh_node = fbx_helper_nodes[object_uuid]
if mesh_node:
# ----
# If we get a valid mesh matrix (in bone space), store armature and
# mesh global matrices, we need them to compute mesh's matrix_parent_inverse
# when actually binding them via the modifier.
# Note we assume all bones were bound with the same mesh/armature (global) matrix,
# we do not support otherwise in Blender anyway!
Bastien Montagne
committed
mesh_node.armature_setup = (mesh_matrix, armature_matrix)
meshes.add(mesh_node)
helper_node.clusters.append((fbx_cluster, meshes))
# convert bind poses from global space into local space
root_helper.make_bind_pose_local()
# collect armature meshes
root_helper.collect_armature_meshes()
Bastien Montagne
committed
# find the correction matrices to align FBX objects with their Blender equivalent
root_helper.find_correction_matrix(settings)
Bastien Montagne
committed
# build the Object/Armature/Bone hierarchy
root_helper.build_hierarchy(fbx_tmpl, settings, scene)
# root_helper.print_info(0)
_(); del _
# We can handle shapes.
blend_shape_channels = {} # We do not need Shapes themselves, but keyblocks, for anim.
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
def _():
fbx_tmpl = fbx_template_get((b'Geometry', b'KFbxShape'))
for s_uuid, s_item in fbx_table_nodes.items():
fbx_sdata, bl_sdata = s_item = fbx_table_nodes.get(s_uuid, (None, None))
if fbx_sdata is None or fbx_sdata.id != b'Geometry' or fbx_sdata.props[2] != b'Shape':
continue
# shape -> blendshapechannel -> blendshape -> mesh.
for bc_uuid, bc_ctype in fbx_connection_map.get(s_uuid, ()):
if bc_ctype.props[0] != b'OO':
continue
fbx_bcdata, _bl_bcdata = fbx_table_nodes.get(bc_uuid, (None, None))
if fbx_bcdata is None or fbx_bcdata.id != b'Deformer' or fbx_bcdata.props[2] != b'BlendShapeChannel':
continue
meshes = []
objects = []
for bs_uuid, bs_ctype in fbx_connection_map.get(bc_uuid, ()):
if bs_ctype.props[0] != b'OO':
continue
fbx_bsdata, _bl_bsdata = fbx_table_nodes.get(bs_uuid, (None, None))
if fbx_bsdata is None or fbx_bsdata.id != b'Deformer' or fbx_bsdata.props[2] != b'BlendShape':
continue
for m_uuid, m_ctype in fbx_connection_map.get(bs_uuid, ()):
if m_ctype.props[0] != b'OO':
continue
fbx_mdata, bl_mdata = fbx_table_nodes.get(m_uuid, (None, None))
if fbx_mdata is None or fbx_mdata.id != b'Geometry' or fbx_mdata.props[2] != b'Mesh':
continue
# Blenmeshes are assumed already created at that time!
assert(isinstance(bl_mdata, bpy.types.Mesh))
# And we have to find all objects using this mesh!
objects = []
for o_uuid, o_ctype in fbx_connection_map.get(m_uuid, ()):
if o_ctype.props[0] != b'OO':
continue
Bastien Montagne
committed
node = fbx_helper_nodes[o_uuid]
if node:
objects.append(node)
meshes.append((bl_mdata, objects))
# BlendShape deformers are only here to connect BlendShapeChannels to meshes, nothing else to do.
# keyblocks is a list of tuples (mesh, keyblock) matching that shape/blendshapechannel, for animation.
Bastien Montagne
committed
keyblocks = blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene)
blend_shape_channels[bc_uuid] = keyblocks
_(); del _
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
# Animation!
def _():
fbx_tmpl_astack = fbx_template_get((b'AnimationStack', b'FbxAnimStack'))
fbx_tmpl_alayer = fbx_template_get((b'AnimationLayer', b'FbxAnimLayer'))
stacks = {}
# AnimationStacks.
for as_uuid, fbx_asitem in fbx_table_nodes.items():
fbx_asdata, _blen_data = fbx_asitem
if fbx_asdata.id != b'AnimationStack' or fbx_asdata.props[2] != b'':
continue
stacks[as_uuid] = (fbx_asitem, {})
# AnimationLayers (mixing is completely ignored for now, each layer results in an independent set of actions).
def get_astacks_from_alayer(al_uuid):
for as_uuid, as_ctype in fbx_connection_map.get(al_uuid, ()):
if as_ctype.props[0] != b'OO':
continue
fbx_asdata, _bl_asdata = fbx_table_nodes.get(as_uuid, (None, None))
if (fbx_asdata is None or fbx_asdata.id != b'AnimationStack' or
fbx_asdata.props[2] != b'' or as_uuid not in stacks):
continue
yield as_uuid
for al_uuid, fbx_alitem in fbx_table_nodes.items():
fbx_aldata, _blen_data = fbx_alitem
if fbx_aldata.id != b'AnimationLayer' or fbx_aldata.props[2] != b'':
continue
for as_uuid in get_astacks_from_alayer(al_uuid):
_fbx_asitem, alayers = stacks[as_uuid]
alayers[al_uuid] = (fbx_alitem, {})
# AnimationCurveNodes (also the ones linked to actual animated data!).
curvenodes = {}
for acn_uuid, fbx_acnitem in fbx_table_nodes.items():
fbx_acndata, _blen_data = fbx_acnitem
if fbx_acndata.id != b'AnimationCurveNode' or fbx_acndata.props[2] != b'':
continue
cnode = curvenodes[acn_uuid] = {}
items = []
for n_uuid, n_ctype in fbx_connection_map.get(acn_uuid, ()):
if n_ctype.props[0] != b'OP':
continue
lnk_prop = n_ctype.props[3]
if lnk_prop in {b'Lcl Translation', b'Lcl Rotation', b'Lcl Scaling'}:
# n_uuid can (????) be linked to root '0' node, instead of a mere object node... See T41712.
Bastien Montagne
committed
ob = fbx_helper_nodes.get(n_uuid, None)
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
if ob is None:
continue
items.append((ob, lnk_prop))
elif lnk_prop == b'DeformPercent': # Shape keys.
keyblocks = blend_shape_channels.get(n_uuid)
if keyblocks is None:
continue
items += [(kb, lnk_prop) for kb in keyblocks]
for al_uuid, al_ctype in fbx_connection_map.get(acn_uuid, ()):
if al_ctype.props[0] != b'OO':
continue
fbx_aldata, _blen_aldata = fbx_alitem = fbx_table_nodes.get(al_uuid, (None, None))
if fbx_aldata is None or fbx_aldata.id != b'AnimationLayer' or fbx_aldata.props[2] != b'':
continue
for as_uuid in get_astacks_from_alayer(al_uuid):
_fbx_alitem, anim_items = stacks[as_uuid][1][al_uuid]
assert(_fbx_alitem == fbx_alitem)
for item, item_prop in items:
# No need to keep curvenode FBX data here, contains nothing useful for us.
anim_items.setdefault(item, {})[acn_uuid] = (cnode, item_prop)
# AnimationCurves (real animation data).
for ac_uuid, fbx_acitem in fbx_table_nodes.items():
fbx_acdata, _blen_data = fbx_acitem
if fbx_acdata.id != b'AnimationCurve' or fbx_acdata.props[2] != b'':
continue
for acn_uuid, acn_ctype in fbx_connection_map.get(ac_uuid, ()):
if acn_ctype.props[0] != b'OP':
continue
fbx_acndata, _bl_acndata = fbx_table_nodes.get(acn_uuid, (None, None))
if (fbx_acndata is None or fbx_acndata.id != b'AnimationCurveNode' or
fbx_acndata.props[2] != b'' or acn_uuid not in curvenodes):
continue
# Note this is an infamous simplification of the compound props stuff,
# seems to be standard naming but we'll probably have to be smarter to handle more exotic files?
channel = {b'd|X': 0, b'd|Y': 1, b'd|Z': 2, b'd|DeformPercent': 0}.get(acn_ctype.props[3], None)
if channel is None:
continue
curvenodes[acn_uuid][ac_uuid] = (fbx_acitem, channel)
# And now that we have sorted all this, apply animations!
Bastien Montagne
committed
blen_read_animations(fbx_tmpl_astack, fbx_tmpl_alayer, stacks, scene)
def _():
# link Material's to Geometry (via Model's)
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Geometry':
continue
mesh = fbx_table_nodes.get(fbx_uuid, (None, None))[1]
# can happen in rare cases
if mesh is None:
continue
for (fbx_lnk,
fbx_lnk_item,
fbx_lnk_type) in connection_filter_forward(fbx_uuid, b'Model'):
# link materials
fbx_lnk_uuid = elem_uuid(fbx_lnk)
for (fbx_lnk_material,
material,
fbx_lnk_material_type) in connection_filter_reverse(fbx_lnk_uuid, b'Material'):
mesh.materials.append(material)
Bastien Montagne
committed
# We have to validate mesh polygons' mat_idx, see T41015!
# Some FBX seem to have an extra 'default' material which is not defined in FBX file.
if mesh.validate_material_indices():
print("WARNING: mesh '%s' had invalid material indices, those were reset to first material" % mesh.name)
_(); del _
def _():
Campbell Barton
committed
material_images = {}
fbx_tmpl = fbx_template_get((b'Material', b'KFbxSurfacePhong'))
# b'KFbxSurfaceLambert'
# textures that use this material
def texture_bumpfac_get(fbx_obj):
assert(fbx_obj.id == b'Material')
fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
assert(fbx_props[0] is not None)
# (x / 7.142) is only a guess, cycles usable range is (0.0 -> 0.5)
return elem_props_get_number(fbx_props, b'BumpFactor', 2.5) / 7.142
def texture_mapping_get(fbx_obj):
assert(fbx_obj.id == b'Texture')
fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
assert(fbx_props[0] is not None)
return (elem_props_get_vector_3d(fbx_props, b'Translation', (0.0, 0.0, 0.0)),
elem_props_get_vector_3d(fbx_props, b'Rotation', (0.0, 0.0, 0.0)),
elem_props_get_vector_3d(fbx_props, b'Scaling', (1.0, 1.0, 1.0)),
(bool(elem_props_get_enum(fbx_props, b'WrapModeU', 0)),
bool(elem_props_get_enum(fbx_props, b'WrapModeV', 0))))
Campbell Barton
committed
if not use_cycles:
# Simple function to make a new mtex and set defaults
def material_mtex_new(material, image, tex_map):
Campbell Barton
committed
tex = texture_cache.get(image)
if tex is None:
tex = bpy.data.textures.new(name=image.name, type='IMAGE')
tex.image = image
texture_cache[image] = tex
# copy custom properties from image object to texture
for key, value in image.items():
tex[key] = value
# delete custom properties on the image object
for key in image.keys():
del image[key]
Campbell Barton
committed
mtex = material.texture_slots.add()
mtex.texture = tex
mtex.texture_coords = 'UV'
mtex.use_map_color_diffuse = False
# No rotation here...
mtex.offset[:] = tex_map[0]
mtex.scale[:] = tex_map[2]
Campbell Barton
committed
return mtex
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Material':
continue
material = fbx_table_nodes.get(fbx_uuid, (None, None))[1]
for (fbx_lnk,
image,
fbx_lnk_type) in connection_filter_reverse(fbx_uuid, b'Texture'):
if use_cycles:
if fbx_lnk_type.props[0] == b'OP':
lnk_type = fbx_lnk_type.props[3]
ma_wrap = cycles_material_wrap_map[material]
# tx/rot/scale
tex_map = texture_mapping_get(fbx_lnk)
if (tex_map[0] == (0.0, 0.0, 0.0) and
tex_map[1] == (0.0, 0.0, 0.0) and
tex_map[2] == (1.0, 1.0, 1.0) and
tex_map[3] == (False, False)):
use_mapping = False
else:
use_mapping = True
tex_map_kw = {
"translation": tex_map[0],
"rotation": [-i for i in tex_map[1]],
"scale": [((1.0 / i) if i != 0.0 else 1.0) for i in tex_map[2]],
"clamp": tex_map[3],
if lnk_type == b'DiffuseColor':
ma_wrap.diffuse_image_set(image)
if use_mapping:
ma_wrap.diffuse_mapping_set(**tex_map_kw)
elif lnk_type == b'SpecularColor':
ma_wrap.specular_image_set(image)
if use_mapping:
ma_wrap.specular_mapping_set(**tex_map_kw)
elif lnk_type == b'ReflectionColor':
ma_wrap.reflect_image_set(image)
if use_mapping:
ma_wrap.reflect_mapping_set(**tex_map_kw)
Campbell Barton
committed
elif lnk_type == b'TransparentColor': # alpha
ma_wrap.alpha_image_set(image)
if use_mapping:
ma_wrap.alpha_mapping_set(**tex_map_kw)
Campbell Barton
committed
if use_alpha_decals:
material_decals.add(material)
elif lnk_type == b'DiffuseFactor':
pass # TODO
elif lnk_type == b'ShininessExponent':
ma_wrap.hardness_image_set(image)
if use_mapping:
ma_wrap.hardness_mapping_set(**tex_map_kw)
elif lnk_type == b'NormalMap' or lnk_type == b'Bump': # XXX, applications abuse bump!
ma_wrap.normal_image_set(image)
ma_wrap.normal_factor_set(texture_bumpfac_get(fbx_obj))
if use_mapping:
ma_wrap.normal_mapping_set(**tex_map_kw)
"""
elif lnk_type == b'Bump':
ma_wrap.bump_image_set(image)
ma_wrap.bump_factor_set(texture_bumpfac_get(fbx_obj))
if use_mapping:
ma_wrap.bump_mapping_set(**tex_map_kw)
"""
Campbell Barton
committed
else:
print("WARNING: material link %r ignored" % lnk_type)
material_images.setdefault(material, {})[lnk_type] = (image, tex_map)
else:
if fbx_lnk_type.props[0] == b'OP':
lnk_type = fbx_lnk_type.props[3]
# tx/rot/scale (rot is ignored here!).
tex_map = texture_mapping_get(fbx_lnk)
mtex = material_mtex_new(material, image, tex_map)
if lnk_type == b'DiffuseColor':
mtex.use_map_color_diffuse = True
mtex.blend_type = 'MULTIPLY'
elif lnk_type == b'SpecularColor':
mtex.use_map_color_spec = True
mtex.blend_type = 'MULTIPLY'
elif lnk_type == b'ReflectionColor':
mtex.use_map_raymir = True
Campbell Barton
committed
elif lnk_type == b'TransparentColor': # alpha
material.use_transparency = True
material.transparency_method = 'RAYTRACE'
material.alpha = 0.0
mtex.use_map_alpha = True
mtex.alpha_factor = 1.0
if use_alpha_decals:
material_decals.add(material)
elif lnk_type == b'DiffuseFactor':
mtex.use_map_diffuse = True
elif lnk_type == b'ShininessExponent':
mtex.use_map_hardness = True
elif lnk_type == b'NormalMap' or lnk_type == b'Bump': # XXX, applications abuse bump!
mtex.texture.use_normal_map = True # not ideal!
mtex.use_map_normal = True
mtex.normal_factor = texture_bumpfac_get(fbx_obj)
elif lnk_type == b'Bump':
mtex.use_map_normal = True
mtex.normal_factor = texture_bumpfac_get(fbx_obj)
else:
print("WARNING: material link %r ignored" % lnk_type)
Campbell Barton
committed
material_images.setdefault(material, {})[lnk_type] = (image, tex_map)
Campbell Barton
committed
# Check if the diffuse image has an alpha channel,
# if so, use the alpha channel.
# Note: this could be made optional since images may have alpha but be entirely opaque
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Material':
continue
material = fbx_table_nodes.get(fbx_uuid, (None, None))[1]
image, tex_map = material_images.get(material, {}).get(b'DiffuseColor', (None, None))
Campbell Barton
committed
# do we have alpha?
if image and image.depth == 32:
if use_alpha_decals:
material_decals.add(material)
Campbell Barton
committed
if use_cycles:
ma_wrap = cycles_material_wrap_map[material]
if ma_wrap.node_bsdf_alpha.mute:
ma_wrap.alpha_image_set_from_diffuse()
else:
if not any((True for mtex in material.texture_slots if mtex and mtex.use_map_alpha)):
mtex = material_mtex_new(material, image, tex_map)
Campbell Barton
committed
material.use_transparency = True
material.transparency_method = 'RAYTRACE'
material.alpha = 0.0
mtex.use_map_alpha = True
mtex.alpha_factor = 1.0
# propagate mapping from diffuse to all other channels which have none defined.
if use_cycles:
ma_wrap = cycles_material_wrap_map[material]
ma_wrap.mapping_set_from_diffuse()
_(); del _
Campbell Barton
committed
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
def _():
# Annoying workaround for cycles having no z-offset
if material_decals and use_alpha_decals:
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Geometry':
continue
if fbx_obj.props[-1] == b'Mesh':
mesh = fbx_item[1]
if decal_offset != 0.0:
for material in mesh.materials:
if material in material_decals:
for v in mesh.vertices:
v.co += v.normal * decal_offset
break
if use_cycles:
for obj in (obj for obj in bpy.data.objects if obj.data == mesh):
obj.cycles_visibility.shadow = False
else:
for material in mesh.materials:
if material in material_decals:
# recieve but dont cast shadows
material.use_raytrace = False
_(); del _
print('Import finished in %.4f sec.' % (time.process_time() - start_time))
return {'FINISHED'}