Skip to content
Snippets Groups Projects
import_fbx.py 134 KiB
Newer Older
# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

# Script copyright (C) Blender Foundation

# FBX 7.1.0 -> 7.4.0 loader for Blender
# Not totally pep8 compliant.
#   pep8 import_fbx.py --ignore=E501,E123,E702,E125

if "bpy" in locals():
    import importlib
    if "parse_fbx" in locals():
        importlib.reload(parse_fbx)
    if "fbx_utils" in locals():
        importlib.reload(fbx_utils)
Bastien Montagne's avatar
Bastien Montagne committed
from mathutils import Matrix, Euler, Vector
Bastien Montagne's avatar
Bastien Montagne committed
from . import parse_fbx, fbx_utils
Bastien Montagne's avatar
Bastien Montagne committed
from .fbx_utils import (
    units_blender_to_fbx_factor,
Bastien Montagne's avatar
Bastien Montagne committed
    units_convertor_iter,
    array_to_matrix4,
    similar_values,
    similar_values_iter,
# global singleton, assign on execution
fbx_elem_nil = None

Bastien Montagne's avatar
Bastien Montagne committed
# Units convertors...
convert_deg_to_rad_iter = units_convertor_iter("degree", "radian")
MAT_CONVERT_BONE = fbx_utils.MAT_CONVERT_BONE.inverted()
MAT_CONVERT_LIGHT = fbx_utils.MAT_CONVERT_LIGHT.inverted()
MAT_CONVERT_CAMERA = fbx_utils.MAT_CONVERT_CAMERA.inverted()

def validate_blend_names(name):
    assert(type(name) == bytes)
    # Blender typically does not accept names over 63 bytes...
    if len(name) > 63:
        import hashlib
        h = hashlib.sha1(name).hexdigest()
        n = 55
        name_utf8 = name[:n].decode('utf-8', 'replace') + "_" + h[:7]
        while len(name_utf8.encode()) > 63:
            n -= 1
            name_utf8 = name[:n].decode('utf-8', 'replace') + "_" + h[:7]
        return name_utf8
    else:
        # We use 'replace' even though FBX 'specs' say it should always be utf8, see T53841.
        return name.decode('utf-8', 'replace')


def elem_find_first(elem, id_search, default=None):
    for fbx_item in elem.elems:
        if fbx_item.id == id_search:
            return fbx_item
def elem_find_iter(elem, id_search):
    for fbx_item in elem.elems:
        if fbx_item.id == id_search:
            yield fbx_item


def elem_find_first_string(elem, id_search):
    fbx_item = elem_find_first(elem, id_search)
    if fbx_item is not None and fbx_item.props:  # Do not error on complete empty properties (see T45291).
        assert(len(fbx_item.props) == 1)
        assert(fbx_item.props_type[0] == data_types.STRING)
        return fbx_item.props[0].decode('utf-8', 'replace')
def elem_find_first_string_as_bytes(elem, id_search):
    fbx_item = elem_find_first(elem, id_search)
    if fbx_item is not None and fbx_item.props:  # Do not error on complete empty properties (see T45291).
        assert(len(fbx_item.props) == 1)
        assert(fbx_item.props_type[0] == data_types.STRING)
        return fbx_item.props[0]  # Keep it as bytes as requested...
    return None


def elem_find_first_bytes(elem, id_search, decode=True):
    fbx_item = elem_find_first(elem, id_search)
    if fbx_item is not None and fbx_item.props:  # Do not error on complete empty properties (see T45291).
        assert(len(fbx_item.props) == 1)
        assert(fbx_item.props_type[0] == data_types.BYTES)
        return fbx_item.props[0]
    return None


def elem_repr(elem):
    return "%s: props[%d=%r], elems=(%r)" % (
        elem.id,
        len(elem.props),
        ", ".join([repr(p) for p in elem.props]),
        # elem.props_type,
        b", ".join([e.id for e in elem.elems]),
        )


def elem_split_name_class(elem):
    assert(elem.props_type[-2] == data_types.STRING)
    elem_name, elem_class = elem.props[-2].split(b'\x00\x01')
    return elem_name, elem_class


Bastien Montagne's avatar
Bastien Montagne committed
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 validate_blend_names(elem_name)
def elem_name_ensure_classes(elem, clss=...):
    elem_name, elem_class = elem_split_name_class(elem)
    if clss is not ...:
        assert(elem_class in clss)
    return validate_blend_names(elem_name)
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')
    assert(elem_class == b'NodeAttribute')
    assert(elem.props_type[-1] == data_types.STRING)
    elem_class = elem.props[-1]
    return elem_name, elem_class


def elem_uuid(elem):
    assert(elem.props_type[0] == data_types.INT64)
    return elem.props[0]


Bastien Montagne's avatar
Bastien Montagne committed
def elem_prop_first(elem, default=None):
    return elem.props[0] if (elem is not None) and elem.props else default


# ----
# Support for
# Properties70: { ... P:
def elem_props_find_first(elem, elem_prop_id):
    if elem is None:
        # When properties are not found... Should never happen, but happens - as usual.
        return None
    # support for templates (tuple of elems)
    if type(elem) is not FBXElem:
        assert(type(elem) is tuple)
        for e in elem:
            result = elem_props_find_first(e, elem_prop_id)
            if result is not None:
                return result
        assert(len(elem) > 0)
        return None

    for subelem in elem.elems:
        assert(subelem.id == b'P')
        if subelem.props[0] == elem_prop_id:
            return subelem
    return None


def elem_props_get_color_rgb(elem, elem_prop_id, default=None):
    elem_prop = elem_props_find_first(elem, elem_prop_id)
    if elem_prop is not None:
        assert(elem_prop.props[0] == elem_prop_id)
        if elem_prop.props[1] == b'Color':
Loading
Loading full blame...