Skip to content
Snippets Groups Projects
import_fbx.py 110 KiB
Newer Older
  • Learn to ignore specific revisions
  •     #     (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):
    
            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
    
        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 _
    
        # ----
    
    Bastien Montagne's avatar
    Bastien Montagne committed
        # 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))
    
            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, 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)
    
                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
    
                fbx_item[1] = blen_read_texture(fbx_tmpl, fbx_obj, basedir, settings)
    
        # ----
        # 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)
    
    
        # -- temporary helper hierarchy to build armatures and objects from
    
    Bastien Montagne's avatar
    Bastien Montagne committed
        # lookup from uuid to helper node. Used to build parent-child relations and later to look up animated nodes.
        fbx_helper_nodes = {}
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            # 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.
    
    
            # 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's avatar
    Bastien Montagne committed
                fbx_obj, bl_data = a_item
    
                if fbx_obj is None or fbx_obj.id != b'Model':
    
    
                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)
    
    
                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.
    
                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:
    
    
                    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:
    
                        if fbx_sdata.id in {b'Material', b'Texture', b'Video'}:
    
                        parent.bl_data = bl_data
                    else:
                        # set parent
                        child.parent = parent
    
            # find armatures (either an empty below a bone or a new node inserted at the bone
            root_helper.find_armatures()
    
            # mark nodes that have bone children
            root_helper.find_bone_children()
    
            # 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()
    
    
    Bastien Montagne's avatar
    Bastien Montagne committed
            # 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!
    
            # 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's avatar
    Bastien Montagne committed
                fbx_obj, bl_data = a_item
    
                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.
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                        # There may be several bind pose matrices for the same node, but in tests they seem to be identical.
    
                        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:
    
                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's avatar
    Bastien Montagne committed
                    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()
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    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's avatar
    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 Matrix()
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    mesh_matrix = tx_mesh
                    armature_matrix = tx_arm
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                    if tx_bone:
                        mesh_matrix = tx_bone * mesh_matrix
                        helper_node.bind_matrix = tx_bone  # overwrite the bind matrix
    
    
                    # 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':
    
                            continue
    
                        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':
    
                        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 to set temporarily
                                    # both objects to those matrices 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!
                                    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()
    
            # find the correction matrices to align FBX objects with their Blender equivalent
            root_helper.find_correction_matrix(settings)
    
            # 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.
    
        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
    
                                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.
    
                    keyblocks = blen_read_shape(fbx_tmpl, fbx_sdata, fbx_bcdata, meshes, scene)
    
                    blend_shape_channels[bc_uuid] = keyblocks
        _(); del _
    
    
        # 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.
    
                        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!
    
            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)
    
    
                # 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)
    
            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))))
    
            if not use_cycles:
                # Simple function to make a new mtex and set defaults
    
                def material_mtex_new(material, image, tex_map):
    
                    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]
    
    
                    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]
    
            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
    
    Bastien Montagne's avatar
    Bastien Montagne committed
                                    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]],
    
                            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)
    
                            elif lnk_type == b'TransparentColor':  # alpha
    
                                ma_wrap.alpha_image_set(image)
    
                                if use_mapping:
                                    ma_wrap.alpha_mapping_set(**tex_map_kw)
    
                                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)
                                """
    
                            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
    
                            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)
    
                            material_images.setdefault(material, {})[lnk_type] = (image, tex_map)
    
    
            # 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))
    
                # do we have alpha?
                if image and image.depth == 32:
                    if use_alpha_decals:
                        material_decals.add(material)
    
                    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)
    
    
                            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()
    
    
        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 _
    
    
    Bastien Montagne's avatar
    Bastien Montagne committed
        print('Import finished in %.4f sec.' % (time.process_time() - start_time))