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
Campbell Barton
committed
# 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)
Campbell Barton
committed
import bpy
# -----
# 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
# Units convertors...
convert_deg_to_rad_iter = units_convertor_iter("degree", "radian")
def elem_find_first(elem, id_search, default=None):
for fbx_item in elem.elems:
if fbx_item.id == id_search:
return fbx_item
return default
def elem_find_iter(elem, id_search):
for fbx_item in elem.elems:
if fbx_item.id == id_search:
yield fbx_item
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
def elem_find_first_string(elem, id_search):
fbx_item = elem_find_first(elem, id_search)
if fbx_item is not None:
assert(len(fbx_item.props) == 1)
assert(fbx_item.props_type[0] == data_types.STRING)
return fbx_item.props[0].decode('utf-8')
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:
assert(len(fbx_item.props) == 1)
assert(fbx_item.props_type[0] == data_types.STRING)
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
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')
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]
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):
# 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':
# FBX version 7300
assert(elem_prop.props[1] == b'Color')
assert(elem_prop.props[2] == b'')
assert(elem_prop.props[3] in {b'A', b'A+', b'AU'})
else:
assert(elem_prop.props[1] == b'ColorRGB')
assert(elem_prop.props[2] == b'Color')
assert(elem_prop.props_type[4:7] == bytes((data_types.FLOAT64,)) * 3)
return elem_prop.props[4:7]
return default
def elem_props_get_vector_3d(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_type[4:7] == bytes((data_types.FLOAT64,)) * 3)
return elem_prop.props[4:7]
return default
def elem_props_get_number(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'double':
assert(elem_prop.props[1] == b'double')
assert(elem_prop.props[2] == b'Number')
else:
assert(elem_prop.props[1] == b'Number')
assert(elem_prop.props[2] == b'')
assert(elem_prop.props[3] in {b'A', b'A+', b'AU'})
# we could allow other number types
assert(elem_prop.props_type[4] == data_types.FLOAT64)
return elem_prop.props[4]
return default
def elem_props_get_integer(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'int':
assert(elem_prop.props[1] == b'int')
assert(elem_prop.props[2] == b'Integer')
elif elem_prop.props[1] == b'ULongLong':
assert(elem_prop.props[1] == b'ULongLong')
assert(elem_prop.props[2] == b'')
# we could allow other number types
assert(elem_prop.props_type[4] in {data_types.INT32, data_types.INT64})
return elem_prop.props[4]
return default
Campbell Barton
committed
def elem_props_get_bool(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)
assert(elem_prop.props[1] == b'bool')
assert(elem_prop.props[2] == b'')
assert(elem_prop.props[3] == b'')
# we could allow other number types
assert(elem_prop.props_type[4] == data_types.INT32)
assert(elem_prop.props[4] in {0, 1})
Campbell Barton
committed
return bool(elem_prop.props[4])
return default
def elem_props_get_enum(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)
assert(elem_prop.props[1] == b'enum')
assert(elem_prop.props[2] == b'')
assert(elem_prop.props[3] == b'')
# we could allow other number types
assert(elem_prop.props_type[4] == data_types.INT32)
return elem_prop.props[4]
return default
Bastien Montagne
committed
def elem_props_get_visibility(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)
assert(elem_prop.props[1] == b'Visibility')
assert(elem_prop.props[2] == b'')
assert(elem_prop.props[3] in {b'A', b'A+', b'AU'})
# we could allow other number types
assert(elem_prop.props_type[4] == data_types.FLOAT64)
return elem_prop.props[4]
return default
# ----------------------------------------------------------------------------
# Blender
# ------
# Object
def blen_read_object(fbx_tmpl, fbx_obj, object_data):
elem_name, elem_class = elem_split_name_class(fbx_obj)
elem_name_utf8 = elem_name.decode('utf-8')
const_vector_zero_3d = 0.0, 0.0, 0.0
const_vector_one_3d = 1.0, 1.0, 1.0
# Object data must be created already
obj = bpy.data.objects.new(name=elem_name_utf8, object_data=object_data)
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)
obj.color[0:3] = elem_props_get_color_rgb(fbx_props, b'Color', (0.8, 0.8, 0.8))
Bastien Montagne
committed
obj.hide = not bool(elem_props_get_visibility(fbx_props, b'Visibility', 1.0))
# This is quite involved, 'fbxRNode.cpp' from openscenegraph used as a reference
loc = elem_props_get_vector_3d(fbx_props, b'Lcl Translation', const_vector_zero_3d)
rot = elem_props_get_vector_3d(fbx_props, b'Lcl Rotation', const_vector_zero_3d)
sca = elem_props_get_vector_3d(fbx_props, b'Lcl Scaling', const_vector_one_3d)
rot_ofs = elem_props_get_vector_3d(fbx_props, b'RotationOffset', const_vector_zero_3d)
rot_piv = elem_props_get_vector_3d(fbx_props, b'RotationPivot', const_vector_zero_3d)
sca_ofs = elem_props_get_vector_3d(fbx_props, b'ScalingOffset', const_vector_zero_3d)
sca_piv = elem_props_get_vector_3d(fbx_props, b'ScalingPivot', const_vector_zero_3d)
is_rot_act = elem_props_get_bool(fbx_props, b'RotationActive', False)
if is_rot_act:
pre_rot = elem_props_get_vector_3d(fbx_props, b'PreRotation', const_vector_zero_3d)
pst_rot = elem_props_get_vector_3d(fbx_props, b'PostRotation', const_vector_zero_3d)
rot_ord = {
0: 'XYZ',
1: 'XYZ',
2: 'XZY',
3: 'YZX',
4: 'YXZ',
5: 'ZXY',
6: 'ZYX',
}.get(elem_props_get_enum(fbx_props, b'RotationOrder', 0))
else:
pre_rot = const_vector_zero_3d
pst_rot = const_vector_zero_3d
rot_ord = 'XYZ'
from mathutils import Matrix, Euler
from math import pi
# translation
lcl_translation = Matrix.Translation(loc)
# rotation
rot_alt_mat = Matrix.Rotation(pi / -2.0, 4, 'Y')
rot_alt_mat = Matrix.Rotation(pi / -2.0, 4, 'X')
else:
rot_alt_mat = Matrix()
# rotation
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)
sca_ofs = Matrix.Translation(sca_ofs)
sca_piv = Matrix.Translation(sca_piv)
# scale
lcl_scale = Matrix()
lcl_scale[0][0], lcl_scale[1][1], lcl_scale[2][2] = sca
obj.matrix_basis = (
lcl_translation *
rot_ofs *
rot_piv *
pre_rot *
lcl_rot *
pst_rot *
rot_piv.inverted() *
sca_ofs *
sca_piv *
lcl_scale *
sca_piv.inverted()
)
return obj
# ----
# Mesh
def blen_read_geom_layerinfo(fbx_layer):
return (
elem_find_first_string(fbx_layer, b'Name'),
elem_find_first_bytes(fbx_layer, b'MappingInformationType'),
elem_find_first_bytes(fbx_layer, b'ReferenceInformationType'),
)
def blen_read_geom_array_mapped_vert(
mesh, blen_data, blend_attr,
fbx_layer_data, fbx_layer_index,
fbx_layer_mapping, fbx_layer_ref,
stride, item_size, descr,
):
# TODO, generic mapping apply function
if fbx_layer_mapping == b'ByVertice':
if fbx_layer_ref == b'Direct':
assert(fbx_layer_index is None)
# TODO, more generic support for mapping types
for i, blen_data_item in enumerate(blen_data):
setattr(blen_data_item, blend_attr,
fbx_layer_data[(i * stride): (i * stride) + item_size])
return True
else:
print("warning layer %r ref type unsupported: %r" % (descr, fbx_layer_ref))
else:
print("warning layer %r mapping type unsupported: %r" % (descr, fbx_layer_mapping))
return False
def blen_read_geom_array_mapped_edge(
mesh, blen_data, blend_attr,
fbx_layer_data, fbx_layer_index,
fbx_layer_mapping, fbx_layer_ref,
stride, item_size, descr,
xform=None,
):
if fbx_layer_mapping == b'ByEdge':
if fbx_layer_ref == b'Direct':
if stride == 1:
if xform is None:
for i, blen_data_item in enumerate(blen_data):
setattr(blen_data_item, blend_attr,
fbx_layer_data[i])
else:
for i, blen_data_item in enumerate(blen_data):
setattr(blen_data_item, blend_attr,
xform(fbx_layer_data[i]))
if xform is None:
for i, blen_data_item in enumerate(blen_data):
setattr(blen_data_item, blend_attr,
fbx_layer_data[(i * stride): (i * stride) + item_size])
else:
for i, blen_data_item in enumerate(blen_data):
setattr(blen_data_item, blend_attr,
xform(fbx_layer_data[(i * stride): (i * stride) + item_size]))
return True
else:
print("warning layer %r ref type unsupported: %r" % (descr, fbx_layer_ref))
else:
print("warning layer %r mapping type unsupported: %r" % (descr, fbx_layer_mapping))
return False
mesh, blen_data, blend_attr,
fbx_layer_data, fbx_layer_index,
fbx_layer_mapping, fbx_layer_ref,
stride, item_size, descr,
xform=None,
):
if fbx_layer_ref == b'IndexToDirect':
if stride == 1:
for i, blen_data_item in enumerate(blen_data):
setattr(blen_data_item, blend_attr,
fbx_layer_data[i])
else:
for i, blen_data_item in enumerate(blen_data):
setattr(blen_data_item, blend_attr,
fbx_layer_data[(i * stride): (i * stride) + item_size])
elif fbx_layer_ref == b'Direct':
# looks like direct may have different meanings!
assert(stride == 1)
if xform is None:
for i in range(len(fbx_layer_data)):
setattr(blen_data[i], blend_attr, fbx_layer_data[i])
else:
for i in range(len(fbx_layer_data)):
setattr(blen_data[i], blend_attr, xform(fbx_layer_data[i]))
print("warning layer %r ref type unsupported: %r" % (descr, fbx_layer_ref))
print("warning layer %r mapping type unsupported: %r" % (descr, fbx_layer_mapping))
mesh, blen_data, blend_attr,
fbx_layer_data, fbx_layer_index,
fbx_layer_mapping, fbx_layer_ref,
stride, item_size, descr,
):
if fbx_layer_mapping == b'ByPolygonVertex':
if fbx_layer_ref == b'IndexToDirect':
assert(fbx_layer_index is not None)
for i, j in enumerate(fbx_layer_index):
Campbell Barton
committed
if j != -1:
setattr(blen_data[i], blend_attr,
fbx_layer_data[(j * stride): (j * stride) + item_size])
print("warning layer %r ref type unsupported: %r" % (descr, fbx_layer_ref))
elif fbx_layer_mapping == b'ByVertice':
if fbx_layer_ref == b'Direct':
assert(fbx_layer_index is None)
loops = mesh.loops
polygons = mesh.polygons
for p in polygons:
for i in p.loop_indices:
j = loops[i].vertex_index
setattr(blen_data[i], blend_attr,
fbx_layer_data[(j * stride): (j * stride) + item_size])
else:
print("warning layer %r ref type unsupported: %r" % (descr, fbx_layer_ref))
print("warning layer %r mapping type unsupported: %r" % (descr, fbx_layer_mapping))
def blen_read_geom_layer_material(fbx_obj, mesh):
fbx_layer = elem_find_first(fbx_obj, b'LayerElementMaterial')
if fbx_layer is None:
return
(fbx_layer_name,
fbx_layer_mapping,
fbx_layer_ref,
) = blen_read_geom_layerinfo(fbx_layer)
if fbx_layer_mapping == b'AllSame':
# only to quiet warning
return
layer_id = b'Materials'
fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, layer_id))
blen_data = mesh.polygons
mesh, blen_data, "material_index",
fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref,
def blen_read_geom_layer_uv(fbx_obj, mesh):
for layer_id in (b'LayerElementUV',):
for fbx_layer in elem_find_iter(fbx_obj, layer_id):
# all should be valid
(fbx_layer_name,
fbx_layer_mapping,
fbx_layer_ref,
) = blen_read_geom_layerinfo(fbx_layer)
fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, b'UV'))
fbx_layer_index = elem_prop_first(elem_find_first(fbx_layer, b'UVIndex'))
uv_tex = mesh.uv_textures.new(name=fbx_layer_name)
uv_lay = mesh.uv_layers[-1]
# some valid files omit this data
if fbx_layer_data is None:
print("%r %r missing data" % (layer_id, fbx_layer_name))
continue
blen_read_geom_array_mapped_polyloop(
mesh, blen_data, "uv",
fbx_layer_data, fbx_layer_index,
fbx_layer_mapping, fbx_layer_ref,
def blen_read_geom_layer_color(fbx_obj, mesh):
# almost same as UV's
for layer_id in (b'LayerElementColor',):
for fbx_layer in elem_find_iter(fbx_obj, layer_id):
# all should be valid
(fbx_layer_name,
fbx_layer_mapping,
fbx_layer_ref,
) = blen_read_geom_layerinfo(fbx_layer)
fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, b'Colors'))
fbx_layer_index = elem_prop_first(elem_find_first(fbx_layer, b'ColorIndex'))
color_lay = mesh.vertex_colors.new(name=fbx_layer_name)
blen_data = color_lay.data[:]
# some valid files omit this data
if fbx_layer_data is None:
print("%r %r missing data" % (layer_id, fbx_layer_name))
continue
# ignore alpha layer (read 4 items into 3)
blen_read_geom_array_mapped_polyloop(
mesh, blen_data, "color",
fbx_layer_data, fbx_layer_index,
fbx_layer_mapping, fbx_layer_ref,
4, 3, layer_id,
)
def blen_read_geom_layer_smooth(fbx_obj, mesh):
fbx_layer = elem_find_first(fbx_obj, b'LayerElementSmoothing')
if fbx_layer is None:
return False
# all should be valid
(fbx_layer_name,
fbx_layer_mapping,
fbx_layer_ref,
) = blen_read_geom_layerinfo(fbx_layer)
layer_id = b'Smoothing'
fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, layer_id))
# udk has 'Direct' mapped, with no Smoothing, not sure why, but ignore these
if fbx_layer_data is None:
return False
if fbx_layer_mapping == b'ByEdge':
# some models have bad edge data, we cant use this info...
if not mesh.edges:
return False
blen_data = mesh.edges
ok_smooth = blen_read_geom_array_mapped_edge(
mesh, blen_data, "use_edge_sharp",
fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref,
xform=lambda s: not s,
)
return ok_smooth
elif fbx_layer_mapping == b'ByPolygon':
blen_data = mesh.polygons
return blen_read_geom_array_mapped_polygon(
mesh, blen_data, "use_smooth",
fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref,
xform=lambda s: (s != 0), # smoothgroup bitflags, treat as booleans for now
)
else:
print("warning layer %r mapping type unsupported: %r" % (fbx_layer.id, fbx_layer_mapping))
return False
def blen_read_geom_layer_normal(fbx_obj, mesh):
fbx_layer = elem_find_first(fbx_obj, b'LayerElementNormal')
if fbx_layer is None:
return False
(fbx_layer_name,
fbx_layer_mapping,
fbx_layer_ref,
) = blen_read_geom_layerinfo(fbx_layer)
layer_id = b'Normals'
fbx_layer_data = elem_prop_first(elem_find_first(fbx_layer, layer_id))
return blen_read_geom_array_mapped_vert(
fbx_layer_data, None,
fbx_layer_mapping, fbx_layer_ref,
def blen_read_geom(fbx_tmpl, fbx_obj):
# TODO, use 'fbx_tmpl'
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'))
fbx_edges = elem_prop_first(elem_find_first(fbx_obj, b'Edges'))
if fbx_verts is None:
fbx_verts = ()
if fbx_polys is None:
fbx_polys = ()
mesh = bpy.data.meshes.new(name=elem_name_utf8)
mesh.vertices.add(len(fbx_verts) // 3)
mesh.vertices.foreach_set("co", fbx_verts)
if fbx_polys:
mesh.loops.add(len(fbx_polys))
poly_loop_starts = []
poly_loop_totals = []
poly_loop_prev = 0
for i, l in enumerate(mesh.loops):
index = fbx_polys[i]
if index < 0:
poly_loop_starts.append(poly_loop_prev)
poly_loop_totals.append((i - poly_loop_prev) + 1)
poly_loop_prev = i + 1
l.vertex_index = index
mesh.polygons.add(len(poly_loop_starts))
mesh.polygons.foreach_set("loop_start", poly_loop_starts)
mesh.polygons.foreach_set("loop_total", poly_loop_totals)
blen_read_geom_layer_material(fbx_obj, mesh)
blen_read_geom_layer_uv(fbx_obj, mesh)
blen_read_geom_layer_color(fbx_obj, mesh)
# edges in fact index the polygons (NOT the vertices)
import array
tot_edges = len(fbx_edges)
edges_conv = array.array('i', [0]) * (tot_edges * 2)
edge_index = 0
for i in fbx_edges:
e_a = fbx_polys[i]
if e_a >= 0:
e_b = fbx_polys[i + 1]
else:
# Last index of polygon, wrap back to the start.
# ideally we wouldn't have to search back,
# but it should only be 2-3 iterations.
j = i - 1
while j >= 0 and fbx_polys[j] >= 0:
j -= 1
edges_conv[edge_index] = e_a
edges_conv[edge_index + 1] = e_b
edge_index += 2
mesh.edges.add(tot_edges)
mesh.edges.foreach_set("vertices", edges_conv)
# must be after edge, face loading.
ok_smooth = blen_read_geom_layer_smooth(fbx_obj, mesh)
ok_normals = blen_read_geom_layer_normal(fbx_obj, mesh)
mesh.validate()
if not ok_normals:
mesh.calc_normals()
if not ok_smooth:
for p in mesh.polygons:
p.use_smooth = True
return mesh
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
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_utf8 = elem_name_ensure_class(fbx_obj, b'Material')
ma = bpy.data.materials.new(name=elem_name_utf8)
const_color_white = 1.0, 1.0, 1.0
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)
ma_diff = elem_props_get_color_rgb(fbx_props, b'DiffuseColor', const_color_white)
ma_spec = elem_props_get_color_rgb(fbx_props, b'SpecularColor', const_color_white)
ma_alpha = elem_props_get_number(fbx_props, b'Opacity', 1.0)
ma_spec_intensity = ma.specular_intensity = elem_props_get_number(fbx_props, b'SpecularFactor', 0.25) * 2.0
ma_spec_hardness = elem_props_get_number(fbx_props, b'Shininess', 9.6)
ma_refl_factor = elem_props_get_number(fbx_props, b'ReflectionFactor', 0.0)
ma_refl_color = elem_props_get_color_rgb(fbx_props, b'ReflectionColor', const_color_white)
if use_cycles:
from . import cycles_shader_compat
# viewport color
ma.diffuse_color = ma_diff
ma_wrap = cycles_shader_compat.CyclesShaderWrapper(ma)
ma_wrap.diffuse_color_set(ma_diff)
ma_wrap.specular_color_set([c * ma_spec_intensity for c in ma_spec])
ma_wrap.hardness_value_set(((ma_spec_hardness + 3.0) / 5.0) - 0.65)
ma_wrap.alpha_value_set(ma_alpha)
ma_wrap.reflect_factor_set(ma_refl_factor)
ma_wrap.reflect_color_set(ma_refl_color)
cycles_material_wrap_map[ma] = ma_wrap
else:
# TODO, number BumpFactor isnt used yet
ma.diffuse_color = ma_diff
ma.specular_color = ma_spec
ma.alpha = ma_alpha
ma.specular_intensity = ma_spec_intensity
ma.specular_hardness = ma_spec_hardness * 5.10 + 1.0
if ma_refl_factor != 0.0:
ma.raytrace_mirror.use = True
ma.raytrace_mirror.reflect_factor = ma_refl_factor
ma.mirror_color = ma_refl_color
return ma
# -------
# Texture
def blen_read_texture(fbx_tmpl, fbx_obj, basedir, image_cache,
use_image_search):
import os
from bpy_extras import image_utils
elem_name_utf8 = elem_name_ensure_class(fbx_obj, b'Texture')
filepath = elem_find_first_string(fbx_obj, b'FileName')
if os.sep == '/':
filepath = filepath.replace('\\', '/')
else:
filepath = filepath.replace('/', '\\')
image = image_cache.get(filepath)
if image is not None:
return image
image = image_utils.load_image(
filepath,
dirname=basedir,
place_holder=True,
recursive=use_image_search,
)
image_cache[filepath] = image
# name can be ../a/b/c
image.name = os.path.basename(elem_name_utf8)
return image
def blen_read_camera(fbx_tmpl, fbx_obj, global_scale):
# meters to inches
M2I = 0.0393700787
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))
assert(fbx_props[0] is not None)
camera = bpy.data.cameras.new(name=elem_name_utf8)
camera.lens = elem_props_get_number(fbx_props, b'FocalLength', 35.0)
camera.sensor_width = elem_props_get_number(fbx_props, b'FilmWidth', 32.0 * M2I) / M2I
camera.sensor_height = elem_props_get_number(fbx_props, b'FilmHeight', 32.0 * M2I) / M2I
filmaspect = camera.sensor_width / camera.sensor_height
# film offset
camera.shift_x = elem_props_get_number(fbx_props, b'FilmOffsetX', 0.0) / (M2I * camera.sensor_width)
camera.shift_y = elem_props_get_number(fbx_props, b'FilmOffsetY', 0.0) / (M2I * camera.sensor_height * filmaspect)
Campbell Barton
committed
camera.clip_start = elem_props_get_number(fbx_props, b'NearPlane', 0.01) * global_scale
camera.clip_end = elem_props_get_number(fbx_props, b'FarPlane', 100.0) * global_scale
def blen_read_light(fbx_tmpl, fbx_obj, global_scale):
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))
# rare
if fbx_props[0] is None:
lamp = bpy.data.lamps.new(name=elem_name_utf8, type='POINT')
return lamp
light_type = {
0: 'POINT',
1: 'SUN',
2: 'SPOT'}.get(elem_props_get_enum(fbx_props, b'LightType', 0), 'POINT')
lamp = bpy.data.lamps.new(name=elem_name_utf8, type=light_type)
spot_size = elem_props_get_number(fbx_props, b'OuterAngle', None)
if spot_size is None:
# Deprecated.
spot_size = elem_props_get_number(fbx_props, b'Cone angle', 45.0)
lamp.spot_size = math.radians(spot_size)
spot_blend = elem_props_get_number(fbx_props, b'InnerAngle', None)
if spot_blend is None:
# Deprecated.
spot_blend = elem_props_get_number(fbx_props, b'HotSpot', 45.0)
lamp.spot_blend = 1.0 - (spot_blend / spot_size)
lamp.color = elem_props_get_color_rgb(fbx_props, b'Color', (1.0, 1.0, 1.0))
Campbell Barton
committed
lamp.energy = elem_props_get_number(fbx_props, b'Intensity', 100.0) / 100.0
lamp.distance = elem_props_get_number(fbx_props, b'DecayStart', 25.0) * global_scale
lamp.shadow_method = ('RAY_SHADOW' if elem_props_get_bool(fbx_props, b'CastShadow', True) else 'NOSHADOW')
lamp.shadow_color = elem_props_get_color_rgb(fbx_props, b'ShadowColor', (0.0, 0.0, 0.0))
Campbell Barton
committed
def is_ascii(filepath, size):
with open(filepath, 'r', encoding="utf-8") as f:
try:
f.read(size)
return True
except UnicodeDecodeError:
pass
return False
def load(operator, context, filepath="",
use_manual_orientation=False,
axis_forward='-Z',
axis_up='Y',
global_scale=1.0,
use_cycles=True,
Campbell Barton
committed
use_image_search=False,
use_alpha_decals=False,
decal_offset=0.0):
global fbx_elem_nil
fbx_elem_nil = FBXElem('', (), (), ())
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
Campbell Barton
committed
# detect ascii files
if is_ascii(filepath, 24):
operator.report({'ERROR'}, "ASCII FBX files are not supported %r" % filepath)
return {'CANCELLED'}
try:
elem_root, version = parse_fbx.parse(filepath)
except:
import traceback
traceback.print_exc()
operator.report({'ERROR'}, "Couldn't open file %r" % filepath)
return {'CANCELLED'}
if version < 7100:
operator.report({'ERROR'}, "Version %r unsupported, must be %r or later" % (version, 7100))
return {'CANCELLED'}
if bpy.ops.object.mode_set.poll():
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
# deselect all
if bpy.ops.object.select_all.poll():
bpy.ops.object.select_all(action='DESELECT')
basedir = os.path.dirname(filepath)
cycles_material_wrap_map = {}
image_cache = {}
if not use_cycles:
texture_cache = {}
# Tables: (FBX_byte_id -> [FBX_data, None or Blender_datablock])
fbx_table_nodes = {}
Campbell Barton
committed
if use_alpha_decals: