Skip to content
Snippets Groups Projects
import_fbx.py 93.6 KiB
Newer Older
  • Learn to ignore specific revisions
  •         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.
                        ob = fbx_table_nodes.get(n_uuid, (None, None))[1]
    
                        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, force_global_objects, settings)
    
        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)
    
    
                # 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[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
    
    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[fbx_uuid][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))