Skip to content
Snippets Groups Projects
Commit 4bd0f693 authored by Campbell Barton's avatar Campbell Barton
Browse files

initial FBX importer, work in progress but can load...

- binary fbx files only
- version 7.1 or newer
- meshes, uvs, materials, textures
- support for blender-internal and cycles materials (depends on engine selected)

note - yes, this cant load fbx files exported by blender, for that to work we would need to update the exporter.
parent 9072ee32
No related branches found
No related tags found
No related merge requests found
......@@ -35,6 +35,8 @@ bl_info = {
if "bpy" in locals():
import imp
if "import_fbx" in locals():
imp.reload(import_fbx)
if "export_fbx" in locals():
imp.reload(export_fbx)
......@@ -46,11 +48,78 @@ from bpy.props import (StringProperty,
EnumProperty,
)
from bpy_extras.io_utils import (ExportHelper,
from bpy_extras.io_utils import (ImportHelper,
ExportHelper,
path_reference_mode,
axis_conversion,
)
class ImportFBX(bpy.types.Operator, ImportHelper):
"""Load a FBX geometry file"""
bl_idname = "import_scene.fbx"
bl_label = "Import FBX"
bl_options = {'UNDO'}
directory = StringProperty()
filename_ext = ".fbx"
filter_glob = StringProperty(default="*.fbx", options={'HIDDEN'})
use_image_search = BoolProperty(
name="Image Search",
description="Search subdirs for any associated images "
"(Warning, may be slow)",
default=True,
)
axis_forward = EnumProperty(
name="Forward",
items=(('X', "X Forward", ""),
('Y', "Y Forward", ""),
('Z', "Z Forward", ""),
('-X', "-X Forward", ""),
('-Y', "-Y Forward", ""),
('-Z', "-Z Forward", ""),
),
default='-Z',
)
axis_up = EnumProperty(
name="Up",
items=(('X', "X Up", ""),
('Y', "Y Up", ""),
('Z', "Z Up", ""),
('-X', "-X Up", ""),
('-Y', "-Y Up", ""),
('-Z', "-Z Up", ""),
),
default='Y',
)
global_scale = FloatProperty(
name="Scale",
min=0.001, max=1000.0,
default=1.0,
)
def execute(self, context):
from mathutils import Matrix
keywords = self.as_keywords(ignore=("axis_forward",
"axis_up",
"global_scale",
"filter_glob",
"directory",
))
global_matrix = (Matrix.Scale(self.global_scale, 4) *
axis_conversion(from_forward=self.axis_forward,
from_up=self.axis_up,
).to_4x4())
keywords["global_matrix"] = global_matrix
keywords["use_cycles"] = (context.scene.render.engine == 'CYCLES')
from . import import_fbx
return import_fbx.load(self, context, **keywords)
class ExportFBX(bpy.types.Operator, ExportHelper):
"""Selection to an ASCII Autodesk FBX"""
......@@ -74,7 +143,7 @@ class ExportFBX(bpy.types.Operator, ExportHelper):
description=("Scale all data "
"(Some importers do not support scaled armatures!)"),
min=0.01, max=1000.0,
soft_min=0.01, soft_max=1000.0,
soft_min=0.001, soft_max=1000.0,
default=1.0,
)
axis_forward = EnumProperty(
......@@ -174,11 +243,6 @@ class ExportFBX(bpy.types.Operator, ExportHelper):
description="Disable global rotation, for XNA compatibility",
default=False,
)
xna_validate = BoolProperty(
name="XNA Strict Options",
description="Make sure options are compatible with Microsoft XNA",
default=False,
)
batch_mode = EnumProperty(
name="Batch Mode",
items=(('OFF', "Off", "Active scene to file"),
......@@ -197,69 +261,26 @@ class ExportFBX(bpy.types.Operator, ExportHelper):
options={'HIDDEN'},
)
# Validate that the options are compatible with XNA (JCB)
def _validate_xna_options(self):
if not self.xna_validate:
return False
changed = False
if not self.use_rotate_workaround:
changed = True
self.use_rotate_workaround = True
if self.global_scale != 1.0:
changed = True
self.global_scale = 1.0
if self.mesh_smooth_type != 'OFF':
changed = True
self.mesh_smooth_type = 'OFF'
if self.use_anim_optimize:
changed = True
self.use_anim_optimize = False
if self.use_mesh_edges:
changed = True
self.use_mesh_edges = False
if self.use_default_take:
changed = True
self.use_default_take = False
if self.object_types & {'CAMERA', 'LAMP', 'EMPTY'}:
changed = True
self.object_types -= {'CAMERA', 'LAMP', 'EMPTY'}
if self.path_mode != 'STRIP':
changed = True
self.path_mode = 'STRIP'
return changed
@property
def check_extension(self):
return self.batch_mode == 'OFF'
def check(self, context):
is_def_change = super().check(context)
is_xna_change = self._validate_xna_options()
return (is_xna_change or is_def_change)
def execute(self, context):
from mathutils import Matrix
if not self.filepath:
raise Exception("filepath not set")
global_matrix = Matrix()
global_matrix[0][0] = \
global_matrix[1][1] = \
global_matrix[2][2] = self.global_scale
if not self.use_rotate_workaround:
global_matrix = (global_matrix *
axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4())
global_matrix = (Matrix.Scale(self.global_scale, 4) *
axis_conversion(to_forward=self.axis_forward,
to_up=self.axis_up,
).to_4x4())
keywords = self.as_keywords(ignore=("axis_forward",
"axis_up",
"global_scale",
"check_existing",
"filter_glob",
"xna_validate",
))
keywords["global_matrix"] = global_matrix
......@@ -268,20 +289,26 @@ class ExportFBX(bpy.types.Operator, ExportHelper):
return export_fbx.save(self, context, **keywords)
def menu_func(self, context):
def menu_func_import(self, context):
self.layout.operator(ImportFBX.bl_idname, text="Autodesk FBX (.fbx)")
def menu_func_export(self, context):
self.layout.operator(ExportFBX.bl_idname, text="Autodesk FBX (.fbx)")
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_export.append(menu_func)
bpy.types.INFO_MT_file_import.append(menu_func_import)
bpy.types.INFO_MT_file_export.append(menu_func_export)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_export.remove(menu_func)
bpy.types.INFO_MT_file_import.remove(menu_func_import)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
......@@ -3050,19 +3050,6 @@ def save(operator, context,
# Please update the lists for UDK, Unity, XNA etc. on the following web page:
# http://wiki.blender.org/index.php/Dev:2.5/Py/Scripts/Import-Export/UnifiedFBX
# XNA FBX Requirements (JCB 29 July 2011)
# - Armature must be parented to the scene
# - Armature must be a 'Limb' never a 'null'. This is in several places.
# - First bone must be parented to the armature.
# - Rotation must be completely disabled including
# always returning the original matrix in In object_tx().
# It is the animation that gets distorted during rotation!
# - Lone edges cause intermittent errors in the XNA content pipeline!
# I have added a warning message and excluded them.
# - Bind pose must be included with the 'MESH'
# Typical settings for XNA export
# No Cameras, No Lamps, No Edges, No face smoothing, No Default_Take, Armature as bone, Disable rotation
# NOTE TO Campbell -
# Can any or all of the following notes be removed because some have been here for a long time? (JCB 27 July 2011)
# NOTES (all line numbers correspond to original export_fbx.py (under release/scripts)
......
# ##### 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.3.0 loader for Blender
import bpy
# -----
# Utils
from .parse_fbx import data_types
def tuple_deg_to_rad(eul):
return eul[0] / 57.295779513, eul[1] / 57.295779513, eul[2] / 57.295779513
def elem_find_first(elem, id_search):
for fbx_item in elem.elems:
if fbx_item.id == id_search:
return fbx_item
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):
""" Return
"""
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_uuid(elem):
assert(elem.props_type[0] == data_types.INT64)
return elem.props[0]
def elem_prop_first(elem):
return elem.props[0] if (elem is not None) and elem.props else None
# ----
# Support for
# Properties70: { ... P:
def elem_props_find_first(elem, elem_prop_id):
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] == b'A')
else:
assert(elem_prop.props[1] == b'ColorRGB')
assert(elem_prop.props[2] == b'Color')
#print(elem_prop.props_type[4:7])
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] == b'A')
# 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_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')
assert(fbx_props is not None)
loc = elem_props_get_color_rgb(fbx_props, b'Lcl Translation', const_vector_zero_3d)
rot = elem_props_get_color_rgb(fbx_props, b'Lcl Rotation', const_vector_zero_3d)
sca = elem_props_get_color_rgb(fbx_props, b'Lcl Scaling', const_vector_one_3d)
obj.location = loc
obj.rotation_euler = tuple_deg_to_rad(rot)
obj.scale = sca
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_uv(fbx_obj, mesh):
for uvlayer_id in (b'LayerElementUV',):
fbx_uvlayer = elem_find_first(fbx_obj, uvlayer_id)
if fbx_uvlayer is None:
continue
# all should be valid
(fbx_uvlayer_name,
fbx_uvlayer_mapping,
fbx_uvlayer_ref,
) = blen_read_geom_layerinfo(fbx_uvlayer)
# print(fbx_uvlayer_name, fbx_uvlayer_mapping, fbx_uvlayer_ref)
fbx_layer_data = elem_prop_first(elem_find_first(fbx_uvlayer, b'UV'))
fbx_layer_index = elem_prop_first(elem_find_first(fbx_uvlayer, b'UVIndex'))
# TODO, generic mappuing apply function
if fbx_uvlayer_mapping == b'ByPolygonVertex':
if fbx_uvlayer_ref == b'IndexToDirect':
# TODO, more generic support for mapping types
uv_tex = mesh.uv_textures.new(name=fbx_uvlayer_name)
uv_lay = mesh.uv_layers[fbx_uvlayer_name]
uv_data = [luv.uv for luv in uv_lay.data]
for i, j in enumerate(fbx_layer_index):
uv_data[i][:] = fbx_layer_data[(j * 2): (j * 2) + 2]
else:
print("warning uv layer ref type unsupported:", fbx_uvlayer_ref)
else:
print("warning uv layer mapping type unsupported:", fbx_uvlayer_mapping)
def blen_read_geom(fbx_obj):
elem_name, elem_class = elem_split_name_class(fbx_obj)
assert(elem_class == b'Geometry')
elem_name_utf8 = elem_name.decode('utf-8')
fbx_verts = elem_prop_first(elem_find_first(fbx_obj, b'Vertices'))
fbx_polys = elem_prop_first(elem_find_first(fbx_obj, b'PolygonVertexIndex'))
# TODO
# fbx_edges = elem_prop_first(elem_find_first(fbx_obj, b'Edges'))
mesh = bpy.data.meshes.new(name=elem_name_utf8)
mesh.vertices.add(len(fbx_verts) // 3)
mesh.vertices.foreach_set("co", fbx_verts)
mesh.loops.add(len(fbx_polys))
#poly_loops = [] # pairs (loop_start, loop_total)
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
index = -(index + 1)
l.vertex_index = index
poly_loop_starts.append(poly_loop_prev)
poly_loop_totals.append((i - poly_loop_prev) + 1)
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_uv(fbx_obj, mesh)
mesh.validate()
mesh.calc_normals()
return mesh
# --------
# Material
def blen_read_material(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')
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')
assert(fbx_props 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.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
ma.use_fake_user = 1
return ma
# -------
# Texture
def blen_read_texture(fbx_obj, basedir, image_cache,
use_image_search):
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')
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.name = elem_name_utf8
return image
def load(operator, context, filepath="",
global_matrix=None,
use_cycles=True,
use_image_search=False):
import os
from . import parse_fbx
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'}
# 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 = {}
scene = context.scene
fbx_nodes = elem_find_first(elem_root, b'Objects')
fbx_connections = elem_find_first(elem_root, b'Connections')
if fbx_nodes is None:
return print("no 'Objects' found")
if fbx_connections is None:
return print("no 'Connections' found")
def _():
for fbx_obj in fbx_nodes.elems:
assert(fbx_obj.props_type == b'LSS')
fbx_uuid = elem_uuid(fbx_obj)
fbx_table_nodes[fbx_uuid] = [fbx_obj, None]
_(); del _
# ----
# First load in the data
# 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:
# print(fbx_link)
c_type = fbx_link.props[0]
c_src, c_dst = fbx_link.props[1:3]
# if c_type == b'OO':
fbx_connection_map.setdefault(c_src, []).append((c_dst, fbx_link))
fbx_connection_map_reverse.setdefault(c_dst, []).append((c_src, fbx_link))
_(); del _
# ----
# Load mesh data
def _():
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_obj)
_(); del _
# ----
# Load material data
def _():
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_obj,
cycles_material_wrap_map, use_cycles)
_(); del _
# ----
# Load image data
def _():
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_obj, basedir, image_cache,
use_image_search)
_(); del _
# ----
# 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, ())
for c_found in (fbx_table_nodes[c_uuid],)
if 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)
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]
for fbx_lnk, fbx_lnk_item, fbx_lnk_type in connection_filter_forward(fbx_uuid, b'Model'):
# create when linking since we need object data
obj = blen_read_object(fbx_lnk, mesh)
# fbx_lnk_item[1] = obj
# instance in scene
# obj.matrix_world = global_matrix * obj.matrix_world
obj_base = scene.objects.link(obj)
obj_base.select = True
# 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)
_(); del _
def _():
# textures that use this material
def texture_bumpfac_get(fbx_obj):
fbx_props = elem_find_first(fbx_obj, b'Properties70')
return elem_props_get_number(fbx_props, b'BumpFactor', 1.0)
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]
if lnk_type == b'DiffuseColor':
ma_wrap.diffuse_image_set(image)
elif lnk_type == b'SpecularColor':
ma_wrap.specular_image_set(image)
elif lnk_type == b'ReflectionColor':
ma_wrap.reflect_image_set(image)
elif lnk_type == b'TransparentColor':
ma_wrap.alpha_image_set(image)
elif lnk_type == b'DiffuseFactor':
pass # TODO
elif lnk_type == b'ShininessExponent':
ma_wrap.hardness_image_set(image)
elif lnk_type == b'NormalMap':
ma_wrap.normal_image_set(image)
ma_wrap.normal_factor_set(texture_bumpfac_get(fbx_obj))
elif lnk_type == b'Bump':
ma_wrap.bump_image_set(image)
ma_wrap.bump_factor_set(texture_bumpfac_get(fbx_obj))
else:
if fbx_lnk_type.props[0] == b'OP':
lnk_type = fbx_lnk_type.props[3]
# cache converted texture
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
mtex = material.texture_slots.add()
mtex.texture = tex
mtex.texture_coords = 'UV'
mtex.use_map_color_diffuse = False
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':
pass
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':
tex.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)
_(); del _
# print(list(sorted(locals().keys())))
return {'FINISHED'}
# ##### 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) 2006-2012, assimp team
# Script copyright (C) 2013 Blender Foundation
__all__ = (
"parse",
"data_types",
"FBXElem",
)
from struct import unpack
import array
import zlib
# at the end of each nested block, there is a NUL record to indicate
# that the sub-scope exists (i.e. to distinguish between P: and P : {})
# this NUL record is 13 bytes long.
_BLOCK_SENTINEL_LENGTH = 13
_BLOCK_SENTINEL_DATA = (b'\0' * _BLOCK_SENTINEL_LENGTH)
_IS_BIG_ENDIAN = (__import__("sys").byteorder != 'little')
from collections import namedtuple
FBXElem = namedtuple("FBXElem", ("id", "props", "props_type", "elems"))
del namedtuple
def read_uint(read):
return unpack(b'<I', read(4))[0]
def read_ubyte(read):
return unpack(b'B', read(1))[0]
def read_string_ubyte(read):
size = read_ubyte(read)
data = read(size)
return data
def unpack_array(read, array_type, array_stride, array_byteswap):
length = read_uint(read)
encoding = read_uint(read)
comp_len = read_uint(read)
data = read(comp_len)
if encoding == 0:
pass
elif encoding == 1:
data = zlib.decompress(data)
assert(length * array_stride == len(data))
data_array = array.array(array_type, data)
if array_byteswap and _IS_BIG_ENDIAN:
data_array.byteswap()
return data_array
read_data_dict = {
b'Y'[0]: lambda read, size: unpack(b'<h', read(2))[0], # 16 bit int
b'C'[0]: lambda read, size: unpack(b'?', read(1))[0], # 1 bit bool (yes/no)
b'I'[0]: lambda read, size: unpack(b'<i', read(4))[0], # 32 bit int
b'F'[0]: lambda read, size: unpack(b'<f', read(4))[0], # 32 bit float
b'D'[0]: lambda read, size: unpack(b'<d', read(8))[0], # 64 bit float
b'L'[0]: lambda read, size: unpack(b'<q', read(8))[0], # 64 bit int
b'R'[0]: lambda read, size: read(read_uint(read)), # binary data
b'S'[0]: lambda read, size: read(read_uint(read)), # string data
b'f'[0]: lambda read, size: unpack_array(read, 'f', 4, False), # array (float)
b'i'[0]: lambda read, size: unpack_array(read, 'i', 4, True), # array (int)
b'd'[0]: lambda read, size: unpack_array(read, 'd', 8, False), # array (double)
b'l'[0]: lambda read, size: unpack_array(read, 'q', 8, True), # array (long)
b'b'[0]: lambda read, size: read(size), # unknown
}
def read_elem(read, tell, use_namedtuple):
# [0] the offset at which this block ends
# [1] the number of properties in the scope
# [2] the length of the property list
end_offset = read_uint(read)
if end_offset == 0:
return None
prop_count = read_uint(read)
prop_length = read_uint(read)
elem_id = read_string_ubyte(read) # elem name of the scope/key
elem_props_type = bytearray(prop_count) # elem property types
elem_props_data = [None] * prop_count # elem properties (if any)
elem_subtree = [] # elem children (if any)
for i in range(prop_count):
data_type = read(1)[0]
elem_props_data[i] = read_data_dict[data_type](read, prop_length)
elem_props_type[i] = data_type
if tell() < end_offset:
while tell() < (end_offset - _BLOCK_SENTINEL_LENGTH):
elem_subtree.append(read_elem(read, tell, use_namedtuple))
if read(_BLOCK_SENTINEL_LENGTH) != _BLOCK_SENTINEL_DATA:
raise IOError("failed to read nested block sentinel, "
"expected all bytes to be 0")
if tell() != end_offset:
raise IOError("scope length not reached, something is wrong")
args = (elem_id, elem_props_data, elem_props_type, elem_subtree)
return FBXElem(*args) if use_namedtuple else args
def parse(fn, use_namedtuple=True):
# import time
# t = time.time()
root_elems = []
with open(fn, 'rb') as f:
read = f.read
tell = f.tell
HEAD_MAGIC = b'Kaydara FBX Binary\x20\x20\x00\x1a\x00'
if read(len(HEAD_MAGIC)) != HEAD_MAGIC:
raise IOError("Invalid header")
fbx_version = read_uint(read)
while True:
elem = read_elem(read, tell, use_namedtuple)
if elem is None:
break
root_elems.append(elem)
# print("done in %.4f sec" % (time.time() - t))
args = (b'', [], bytearray(0), root_elems)
return FBXElem(*args) if use_namedtuple else args, fbx_version
# Inline module, only for external use
# pyfbx.data_types
data_types = type(array)("data_types")
data_types.__dict__.update(
dict(
INT16 = b'Y'[0],
BOOL = b'C'[0],
INT32 = b'I'[0],
FLOAT32 = b'F'[0],
FLOAT64 = b'D'[0],
INT64 = b'L'[0],
BYTES = b'R'[0],
STRING = b'S'[0],
FLOAT32_ARRAY = b'f'[0],
INT32_ARRAY = b'i'[0],
FLOAT64_ARRAY = b'd'[0],
INT64_ARRAY = b'l'[0],
))
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment