diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py
index 483f0dbe35120734324461536b2cd212ce598fc5..928c296a90f2ddcaaf0816b8ae4e07327574f3f3 100644
--- a/io_scene_fbx/export_fbx_bin.py
+++ b/io_scene_fbx/export_fbx_bin.py
@@ -59,7 +59,7 @@ from .fbx_utils import (
     FBX_LIGHT_TYPES, FBX_LIGHT_DECAY_TYPES,
     RIGHT_HAND_AXES, FBX_FRAMERATES,
     # Miscellaneous utils.
-    units_convert, units_convert_iter, matrix_to_array, similar_values,
+    units_convertor, units_convertor_iter, matrix4_to_array, similar_values,
     # UUID from key.
     get_fbx_uuid_from_key,
     # Key generators.
@@ -87,6 +87,16 @@ from .fbx_utils import (
     FBXSettingsMedia, FBXSettings, FBXData,
 )
 
+# Units convertors!
+convert_sec_to_ktime = units_convertor("second", "ktime")
+convert_sec_to_ktime_iter = units_convertor_iter("second", "ktime")
+
+convert_mm_to_inch = units_convertor("millimeter", "inch")
+
+convert_rad_to_deg = units_convertor("radian", "degree")
+convert_rad_to_deg_iter = units_convertor_iter("radian", "degree")
+
+
 ##### Templates #####
 # TODO: check all those "default" values, they should match Blender's default as much as possible, I guess?
 
@@ -609,8 +619,8 @@ def fbx_data_camera_elements(root, cam_obj, scene_data):
     height = render.resolution_y
     aspect = width / height
     # Film width & height from mm to inches
-    filmwidth = units_convert(cam_data.sensor_width, "millimeter", "inch")
-    filmheight = units_convert(cam_data.sensor_height, "millimeter", "inch")
+    filmwidth = convert_mm_to_inch(cam_data.sensor_width)
+    filmheight = convert_mm_to_inch(cam_data.sensor_height)
     filmaspect = filmwidth / filmheight
     # Film offset
     offsetx = filmwidth * cam_data.shift_x
@@ -967,9 +977,8 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
         del _uvtuples_gen
 
     # Face's materials.
-    me_fbxmats_idx = None
-    if me in scene_data.mesh_mat_indices:
-        me_fbxmats_idx = scene_data.mesh_mat_indices[me]
+    me_fbxmats_idx = scene_data.mesh_mat_indices.get(me)
+    if me_fbxmats_idx is not None:
         me_blmats = me.materials
         if me_fbxmats_idx and me_blmats:
             lay_mat = elem_data_single_int32(geom, b"LayerElementMaterial", 0)
@@ -1284,7 +1293,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
             mat_world_obj = ob_obj.fbx_object_matrix(scene_data, global_space=True)
             fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
             elem_data_single_int64(fbx_posenode, b"Node", ob_obj.fbx_uuid)
-            elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix_to_array(mat_world_obj))
+            elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(mat_world_obj))
             # And all bones of armature!
             mat_world_bones = {}
             for bo_obj in bones:
@@ -1292,7 +1301,7 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
                 mat_world_bones[bo_obj] = bomat
                 fbx_posenode = elem_empty(fbx_pose, b"PoseNode")
                 elem_data_single_int64(fbx_posenode, b"Node", bo_obj.fbx_uuid)
-                elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix_to_array(bomat))
+                elem_data_single_float64_array(fbx_posenode, b"Matrix", matrix4_to_array(bomat))
 
             # Deformer.
             fbx_skin = elem_data_single_int64(root, b"Deformer", get_fbx_uuid_from_key(skin_key))
@@ -1341,9 +1350,9 @@ def fbx_data_armature_elements(root, arm_obj, scene_data):
                 #          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/
                 elem_data_single_float64_array(fbx_clstr, b"Transform",
-                                               matrix_to_array(mat_world_bones[bo_obj].inverted() * mat_world_obj))
-                elem_data_single_float64_array(fbx_clstr, b"TransformLink", matrix_to_array(mat_world_bones[bo_obj]))
-                elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix_to_array(mat_world_arm))
+                                               matrix4_to_array(mat_world_bones[bo_obj].inverted() * mat_world_obj))
+                elem_data_single_float64_array(fbx_clstr, b"TransformLink", matrix4_to_array(mat_world_bones[bo_obj]))
+                elem_data_single_float64_array(fbx_clstr, b"TransformAssociateModel", matrix4_to_array(mat_world_arm))
 
 
 def fbx_data_object_elements(root, ob_obj, scene_data):
@@ -1368,7 +1377,7 @@ def fbx_data_object_elements(root, ob_obj, scene_data):
 
     # Object transform info.
     loc, rot, scale, matrix, matrix_rot = ob_obj.fbx_object_tx(scene_data)
-    rot = tuple(units_convert_iter(rot, "radian", "degree"))
+    rot = tuple(convert_rad_to_deg_iter(rot))
 
     tmpl = elem_props_template_init(scene_data.templates, b"Model")
     # For now add only loc/rot/scale...
@@ -1423,7 +1432,7 @@ def fbx_data_animation_elements(root, scene_data):
     fps = scene.render.fps / scene.render.fps_base
 
     def keys_to_ktimes(keys):
-        return (int(v) for v in units_convert_iter((f / fps for f, _v in keys), "second", "ktime"))
+        return (int(v) for v in convert_sec_to_ktime_iter((f / fps for f, _v in keys)))
 
     # Animation stacks.
     for astack_key, alayers, alayer_key, name, f_start, f_end in animations:
@@ -1435,8 +1444,8 @@ def fbx_data_animation_elements(root, scene_data):
         astack_props = elem_properties(astack)
         r = scene_data.scene.render
         fps = r.fps / r.fps_base
-        start = int(units_convert(f_start / fps, "second", "ktime"))
-        end = int(units_convert(f_end / fps, "second", "ktime"))
+        start = int(convert_sec_to_ktime(f_start / fps))
+        end = int(convert_sec_to_ktime(f_end / fps))
         elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStart", start)
         elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"LocalStop", end)
         elem_props_template_set(astack_tmpl, astack_props, "p_timestamp", b"ReferenceStart", start)
@@ -1699,7 +1708,7 @@ def fbx_animations_objects_do(scene_data, ref_id, f_start, f_end, start_zero, ob
             p_rot = p_rots.get(ob_obj, None)
             loc, rot, scale, _m, _mr = ob_obj.fbx_object_tx(scene_data, rot_euler_compat=p_rot)
             p_rots[ob_obj] = rot
-            tx = tuple(loc) + tuple(units_convert_iter(rot, "radian", "degree")) + tuple(scale)
+            tx = tuple(loc) + tuple(convert_rad_to_deg_iter(rot)) + tuple(scale)
             animdata[ob_obj].append((real_currframe, tx, [False] * len(tx)))
         for ob_obj in objects:
             ob_obj.dupli_list_clear()
@@ -1725,7 +1734,7 @@ def fbx_animations_objects_do(scene_data, ref_id, f_start, f_end, start_zero, ob
         # Get PoseBone from bone...
         #tobj = bone_map[obj] if isinstance(obj, Bone) else obj
         #loc, rot, scale, _m, _mr = fbx_object_tx(scene_data, tobj)
-        #tx = tuple(loc) + tuple(units_convert_iter(rot, "radian", "degree")) + tuple(scale)
+        #tx = tuple(loc) + tuple(convert_rad_to_deg_iter(rot)) + tuple(scale)
         dtx = (0.0, 0.0, 0.0) + (0.0, 0.0, 0.0) + (1.0, 1.0, 1.0)
         # If animation for a channel, (True, keyframes), else (False, current value).
         final_keys = OrderedDict()
@@ -1980,8 +1989,9 @@ def fbx_data_from_scene(scene, settings):
             # We support any kind of 'surface' shader though, better to have some kind of default Lambert than nothing.
             # TODO: Support nodes (*BIG* todo!).
             if mat.type in {'SURFACE'} and not mat.use_nodes:
-                if mat in data_materials:
-                    data_materials[mat][1].append(ob_obj)
+                mat_data = data_materials.get(mat)
+                if mat_data is not None:
+                    mat_data[1].append(ob_obj)
                 else:
                     data_materials[mat] = (get_blenderID_key(mat), [ob_obj])
 
@@ -2009,12 +2019,14 @@ def fbx_data_from_scene(scene, settings):
             tex_fbx_props = fbx_mat_properties_from_texture(tex)
             if not tex_fbx_props:
                 continue
-            if tex in data_textures:
-                data_textures[tex][1][mat] = tex_fbx_props
+            tex_data = data_textures.get(tex)
+            if tex_data is not None:
+                tex_data[1][mat] = tex_fbx_props
             else:
                 data_textures[tex] = (get_blenderID_key(tex), OrderedDict(((mat, tex_fbx_props),)))
-            if img in data_videos:
-                data_videos[img][1].append(tex)
+            vid_data = data_videos.get(img)
+            if vid_data is not None:
+                vid_data[1].append(tex)
             else:
                 data_videos[img] = (get_blenderID_key(img), [tex])
 
@@ -2461,8 +2473,8 @@ def fbx_takes_elements(root, scene_data):
     for astack_key, animations, alayer_key, name, f_start, f_end in animations:
         scene = scene_data.scene
         fps = scene.render.fps / scene.render.fps_base
-        start_ktime = int(units_convert(f_start / fps, "second", "ktime"))
-        end_ktime = int(units_convert(f_end / fps, "second", "ktime"))  # +1 is unity hack...
+        start_ktime = int(convert_sec_to_ktime(f_start / fps))
+        end_ktime = int(convert_sec_to_ktime(f_end / fps))
 
         take = elem_data_single_string(takes, b"Take", name)
         elem_data_single_string(take, b"FileName", name + b".tak")
diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py
index 465688ada7c1c497d3fd453afc479e39ab4218ae..d864229f0fc002674f0e9ea1a104670a996aa322 100644
--- a/io_scene_fbx/fbx_utils.py
+++ b/io_scene_fbx/fbx_utils.py
@@ -159,24 +159,33 @@ UNITS = {
 }
 
 
-def units_convert(val, u_from, u_to):
-    """Convert value."""
+def units_convertor(u_from, u_to):
+    """Return a convertor between specified units."""
     conv = UNITS[u_to] / UNITS[u_from]
-    return val * conv
+    return lambda v: v * conv
 
 
-def units_convert_iter(it, u_from, u_to):
-    """Convert value."""
-    conv = UNITS[u_to] / UNITS[u_from]
-    return (v * conv for v in it)
+def units_convertor_iter(u_from, u_to):
+    """Return an iterable convertor between specified units."""
+    conv = units_convertor(u_from, u_to)
+    def convertor(it):
+        for v in it:
+            yield(conv(v))
+    return convertor
 
 
-def matrix_to_array(mat):
+def matrix4_to_array(mat):
     """Concatenate matrix's columns into a single, flat tuple"""
     # blender matrix is row major, fbx is col major so transpose on write
     return tuple(f for v in mat.transposed() for f in v)
 
 
+def array_to_matrix4(arr):
+    """Convert a single 16-len tuple into a valid 4D Blender matrix"""
+    # Blender matrix is row major, fbx is col major so transpose on read
+    return Matrix(tuple(zip(*[iter(arr)]*4))).transposed()
+
+
 def similar_values(v1, v2, e=1e-6):
     """Return True if v1 and v2 are nearly the same."""
     if v1 == v2:
@@ -184,6 +193,16 @@ def similar_values(v1, v2, e=1e-6):
     return ((abs(v1 - v2) / max(abs(v1), abs(v2))) <= e)
 
 
+def similar_values_iter(v1, v2, e=1e-6):
+    """Return True if iterables v1 and v2 are nearly the same."""
+    if v1 == v2:
+        return True
+    for v1, v2 in zip(v1, v2):
+        if (abs(v1 - v2) / max(abs(v1), abs(v2))) > e:
+            return False
+    return True
+
+
 ##### UIDs code. #####
 
 # ID class (mere int).
@@ -489,13 +508,13 @@ def elem_props_template_init(templates, template_type):
     """
     Init a writing template of given type, for *one* element's properties.
     """
-    ret = None
-    if template_type in templates:
-        tmpl = templates[template_type]
+    ret = OrderedDict()
+    tmpl = templates.get(template_type)
+    if tmpl is not None:
         written = tmpl.written[0]
         props = tmpl.properties
         ret = OrderedDict((name, [val, ptype, anim, written]) for name, (val, ptype, anim) in props.items())
-    return ret or OrderedDict()
+    return ret
 
 
 def elem_props_template_set(template, elem, ptype_name, name, value, animatable=False):
@@ -547,11 +566,12 @@ def fbx_templates_generate(root, fbx_templates):
 
     templates = OrderedDict()
     for type_name, prop_type_name, properties, nbr_users, _written in fbx_templates.values():
-        if type_name not in templates:
+        tmpl = templates.get(type_name)
+        if tmpl is None:
             templates[type_name] = [OrderedDict(((prop_type_name, (properties, nbr_users)),)), nbr_users]
         else:
-            templates[type_name][0][prop_type_name] = (properties, nbr_users)
-            templates[type_name][1] += nbr_users
+            tmpl[0][prop_type_name] = (properties, nbr_users)
+            tmpl[1] += nbr_users
 
     for type_name, (subprops, nbr_users) in templates.items():
         template = elem_data_single_string(root, b"ObjectType", type_name)
@@ -612,8 +632,8 @@ class MetaObjectWrapper(type):
         cache = getattr(cls, "_cache", None)
         if cache is None:
             cache = cls._cache = {}
-        if key in cache:
-            instance = cache[key]
+        instance = cache.get(key)
+        if instance is not None:
             # Duplis hack: since duplis are not persistent in Blender (we have to re-create them to get updated
             # info like matrix...), we *always* need to reset that matrix when calling ObjectWrapper() (all
             # other data is supposed valid during whole cache live, so we can skip resetting it).
diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py
index 0ee770f7d00a5f20f879c90fc41a731fcc4aee05..e42cbae77c70dd12b611ddf055efb0d2beca1773 100644
--- a/io_scene_fbx/import_fbx.py
+++ b/io_scene_fbx/import_fbx.py
@@ -34,18 +34,21 @@ import bpy
 
 # -----
 # Utils
-from . import parse_fbx
+from . import parse_fbx, fbx_utils
 
 from .parse_fbx import data_types, FBXElem
+from .fbx_utils import (
+    units_convertor_iter,
+    array_to_matrix4,
+    similar_values,
+    similar_values_iter,
+)
 
 # global singleton, assign on execution
 fbx_elem_nil = None
 
-
-def tuple_deg_to_rad(eul):
-    return (eul[0] / 57.295779513,
-            eul[1] / 57.295779513,
-            eul[2] / 57.295779513)
+# Units convertors...
+convert_deg_to_rad_iter = units_convertor_iter("degree", "radian")
 
 
 def elem_find_first(elem, id_search, default=None):
@@ -95,6 +98,13 @@ def elem_split_name_class(elem):
     return elem_name, elem_class
 
 
+def elem_name_ensure_class(elem, clss=...):
+    elem_name, elem_class = elem_split_name_class(elem)
+    if clss is not ...:
+        assert(elem_class == clss)
+    return elem_name.decode('utf-8')
+
+
 def elem_split_name_class_nodeattr(elem):
     assert(elem.props_type[-2] == data_types.STRING)
     elem_name, elem_class = elem.props[-2].split(b'\x00\x01')
@@ -109,8 +119,8 @@ def elem_uuid(elem):
     return elem.props[0]
 
 
-def elem_prop_first(elem):
-    return elem.props[0] if (elem is not None) and elem.props else None
+def elem_prop_first(elem, default=None):
+    return elem.props[0] if (elem is not None) and elem.props else default
 
 
 # ----
@@ -317,9 +327,9 @@ def blen_read_object(fbx_tmpl, fbx_obj, object_data):
         rot_alt_mat = Matrix()
 
     # rotation
-    lcl_rot = Euler(tuple_deg_to_rad(rot), rot_ord).to_matrix().to_4x4() * rot_alt_mat
-    pre_rot = Euler(tuple_deg_to_rad(pre_rot), rot_ord).to_matrix().to_4x4()
-    pst_rot = Euler(tuple_deg_to_rad(pst_rot), rot_ord).to_matrix().to_4x4()
+    lcl_rot = Euler(convert_deg_to_rad_iter(rot), rot_ord).to_matrix().to_4x4() * rot_alt_mat
+    pre_rot = Euler(convert_deg_to_rad_iter(pre_rot), rot_ord).to_matrix().to_4x4()
+    pst_rot = Euler(convert_deg_to_rad_iter(pst_rot), rot_ord).to_matrix().to_4x4()
 
     rot_ofs = Matrix.Translation(rot_ofs)
     rot_piv = Matrix.Translation(rot_piv)
@@ -646,9 +656,7 @@ def blen_read_geom_layer_normal(fbx_obj, mesh):
 
 def blen_read_geom(fbx_tmpl, fbx_obj):
     # TODO, use 'fbx_tmpl'
-    elem_name, elem_class = elem_split_name_class(fbx_obj)
-    assert(elem_class == b'Geometry')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Geometry')
 
     fbx_verts = elem_prop_first(elem_find_first(fbx_obj, b'Vertices'))
     fbx_polys = elem_prop_first(elem_find_first(fbx_obj, b'PolygonVertexIndex'))
@@ -733,14 +741,45 @@ def blen_read_geom(fbx_tmpl, fbx_obj):
     return mesh
 
 
+def blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene, global_matrix):
+    from mathutils import Vector
+
+    elem_name_utf8 = elem_name_ensure_class(fbx_sdata, b'Geometry')
+    indices = elem_prop_first(elem_find_first(fbx_sdata, b'Indexes'), default=())
+    dvcos = tuple(co for co in zip(*[iter(elem_prop_first(elem_find_first(fbx_sdata, b'Vertices'), default=()))] * 3))
+    # We completely ignore normals here!
+    weight = elem_prop_first(elem_find_first(fbx_bcdata, b'DeformPercent'), default=100.0) / 100.0
+    vgweights = tuple(vgw / 100.0 for vgw in elem_prop_first(elem_find_first(fbx_bcdata, b'FullWeights'), default=()))
+
+    assert(len(vgweights) == len(indices) == len(dvcos))
+    create_vg = bool(set(vgweights) - {1.0})
+
+    for me, objects in meshes:
+        vcos = tuple((idx, me.vertices[idx].co + Vector(dvco)) for idx, dvco in zip(indices, dvcos))
+        objects = list({blen_o for fbx_o, blen_o in objects})
+        assert(objects)
+
+        if me.shape_keys is None:
+            objects[0].shape_key_add(name="Basis", from_mix=False)
+        objects[0].shape_key_add(name=elem_name_utf8, from_mix=False)
+        me.shape_keys.use_relative = True  # Should already be set as such.
+
+        kb = me.shape_keys.key_blocks[elem_name_utf8]
+        for idx, co in vcos:
+            kb.data[idx].co[:] = co
+        kb.value = weight
+
+        # Add vgroup if necessary.
+        if create_vg:
+            add_vgroup_to_objects(indices, vgweights, elem_name_utf8, objects)
+            kb.vertex_group = elem_name_utf8
+
+
 # --------
 # Material
 
-def blen_read_material(fbx_tmpl, fbx_obj,
-                       cycles_material_wrap_map, use_cycles):
-    elem_name, elem_class = elem_split_name_class(fbx_obj)
-    assert(elem_class == b'Material')
-    elem_name_utf8 = elem_name.decode('utf-8')
+def blen_read_material(fbx_tmpl, fbx_obj, cycles_material_wrap_map, use_cycles):
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Material')
 
     ma = bpy.data.materials.new(name=elem_name_utf8)
 
@@ -796,9 +835,7 @@ def blen_read_texture(fbx_tmpl, fbx_obj, basedir, image_cache,
     import os
     from bpy_extras import image_utils
 
-    elem_name, elem_class = elem_split_name_class(fbx_obj)
-    assert(elem_class == b'Texture')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Texture')
 
     filepath = elem_find_first_string(fbx_obj, b'FileName')
     if os.sep == '/':
@@ -828,9 +865,7 @@ def blen_read_camera(fbx_tmpl, fbx_obj, global_scale):
     # meters to inches
     M2I = 0.0393700787
 
-    elem_name, elem_class = elem_split_name_class_nodeattr(fbx_obj)
-    assert(elem_class == b'Camera')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute')
 
     fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
                  elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
@@ -855,9 +890,7 @@ def blen_read_camera(fbx_tmpl, fbx_obj, global_scale):
 
 def blen_read_light(fbx_tmpl, fbx_obj, global_scale):
     import math
-    elem_name, elem_class = elem_split_name_class_nodeattr(fbx_obj)
-    assert(elem_class == b'Light')
-    elem_name_utf8 = elem_name.decode('utf-8')
+    elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'NodeAttribute')
 
     fbx_props = (elem_find_first(fbx_obj, b'Properties70'),
                  elem_find_first(fbx_tmpl, b'Properties70', fbx_elem_nil))
@@ -920,13 +953,15 @@ def load(operator, context, filepath="",
     global fbx_elem_nil
     fbx_elem_nil = FBXElem('', (), (), ())
 
-    import os
+    import os, time
     from bpy_extras.io_utils import axis_conversion
     from mathutils import Matrix
 
     from . import parse_fbx
     from .fbx_utils import RIGHT_HAND_AXES, FBX_FRAMERATES
 
+    start_time = time.process_time()
+
     # detect ascii files
     if is_ascii(filepath, 24):
         operator.report({'ERROR'}, "ASCII FBX files are not supported %r" % filepath)
@@ -1483,4 +1518,5 @@ def load(operator, context, filepath="",
                                 material.use_raytrace = False
     _(); del _
 
+    print('Import finished in %.4f sec.' % (time.process_time() - start_time))
     return {'FINISHED'}