Newer
Older
Campbell Barton
committed
material_decals = set()
else:
material_decals = None
scene = context.scene
#### Get some info from GlobalSettings.
fbx_settings = elem_find_first(elem_root, b'GlobalSettings')
fbx_settings_props = elem_find_first(fbx_settings, b'Properties70')
if fbx_settings is None or fbx_settings_props is None:
operator.report({'ERROR'}, "No 'GlobalSettings' found in file %r" % filepath)
return {'CANCELLED'}
# 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'))
# FBX base unit seems to be the centimeter, while raw Blender Unit is equivalent to the meter...
global_scale = elem_props_get_number(fbx_settings_props, b'UnitScaleFactor', 100.0) / 100.0
global_matrix = (Matrix.Scale(global_scale, 4) *
axis_conversion(from_forward=axis_forward, from_up=axis_up).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
#### And now, the "real" data.
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)
fbx_item[1] = blen_read_geom(fbx_tmpl, fbx_obj)
_(); 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)
fbx_item[1] = blen_read_material(fbx_tmpl, fbx_obj,
cycles_material_wrap_map, use_cycles)
_(); 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
fbx_item[1] = blen_read_texture(fbx_tmpl, fbx_obj, basedir, image_cache,
use_image_search)
_(); 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[c_uuid],))
if (fbx_id is None) or (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)
def _():
fbx_tmpl = fbx_template_get((b'Model', b'KFbxNode'))
# Link objects, keep first, this also creates objects
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Model':
continue
# Create empty object or search for object data
if fbx_obj.props[2] == b'Null':
fbx_lnk_item = None
ok = True
else:
ok = False
for (fbx_lnk,
fbx_lnk_item,
fbx_lnk_type) in connection_filter_reverse(fbx_uuid, None):
if fbx_lnk_type.props[0] != b'OO':
continue
if not isinstance(fbx_lnk_item, bpy.types.ID):
continue
if isinstance(fbx_lnk_item, (bpy.types.Material, bpy.types.Image)):
continue
# Need to check why this happens, Bird_Leg.fbx
Bastien Montagne
committed
# This is basic object parenting, also used by "bones".
if isinstance(fbx_lnk_item, (bpy.types.Object)):
continue
# create when linking since we need object data
obj = blen_read_object(fbx_tmpl, fbx_obj, fbx_lnk_item)
assert(fbx_item[1] is None)
fbx_item[1] = obj
# instance in scene
obj_base = scene.objects.link(obj)
obj_base.select = True
objects.append(obj)
# Parent objects, after we created them...
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Model':
continue
if fbx_item[1] is None:
continue # no object loaded.. ignore
for (fbx_lnk,
fbx_lnk_item,
fbx_lnk_type) in connection_filter_forward(fbx_uuid, b'Model'):
fbx_item[1].parent = fbx_lnk_item
_(); del _
def _():
if global_matrix is not None:
# Apply global matrix last (after parenting)
for fbx_uuid, fbx_item in fbx_table_nodes.items():
fbx_obj, blen_data = fbx_item
if fbx_obj.id != b'Model':
continue
if fbx_item[1] is None:
continue # no object loaded.. ignore
if fbx_item[1].parent is None:
fbx_item[1].matrix_basis = global_matrix * fbx_item[1].matrix_basis
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[fbx_uuid][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)
_(); 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
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[fbx_uuid][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
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
# 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[fbx_uuid][1]
image = material_images.get(material, {}).get(b'DiffuseColor')
# 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)
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
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
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'}