diff options
-rw-r--r-- | io_scene_fbx/__init__.py | 2 | ||||
-rw-r--r-- | io_scene_fbx/export_fbx_bin.py | 47 | ||||
-rw-r--r-- | io_scene_fbx/fbx_utils.py | 51 | ||||
-rw-r--r-- | io_scene_fbx/import_fbx.py | 55 |
4 files changed, 143 insertions, 12 deletions
diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index 96c5adf2..461189c1 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -21,7 +21,7 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier", - "version": (3, 2, 2), + "version": (3, 2, 3), "blender": (2, 74, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UV's, vertex colors, materials, " diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index 00d1f3c3..95aebbd8 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -61,7 +61,7 @@ from .fbx_utils import ( FBX_LIGHT_TYPES, FBX_LIGHT_DECAY_TYPES, RIGHT_HAND_AXES, FBX_FRAMERATES, # Miscellaneous utils. - units_convertor, units_convertor_iter, matrix4_to_array, similar_values, similar_values_iter, + PerfMon, units_convertor, units_convertor_iter, matrix4_to_array, similar_values, similar_values_iter, # Mesh transform helpers. vcos_transformed_gen, nors_transformed_gen, # UUID from key. @@ -2064,9 +2064,13 @@ def fbx_data_from_scene(scene, settings): Do some pre-processing over scene's data... """ objtypes = settings.object_types + perfmon = PerfMon() + perfmon.level_up() # ##### Gathering data... + perfmon.step("FBX export prepare: Wrapping Objects...") + # This is rather simple for now, maybe we could end generating templates with most-used values # instead of default ones? objects = OrderedDict() # Because we do not have any ordered set... @@ -2081,6 +2085,8 @@ def fbx_data_from_scene(scene, settings): objects[dp_obj] = None ob_obj.dupli_list_clear() + perfmon.step("FBX export prepare: Wrapping Data (lamps, cameras, empties)...") + data_lamps = OrderedDict((ob_obj.bdata.data, get_blenderID_key(ob_obj.bdata.data)) for ob_obj in objects if ob_obj.type == 'LAMP') # Unfortunately, FBX camera data contains object-level data (like position, orientation, etc.)... @@ -2090,6 +2096,8 @@ def fbx_data_from_scene(scene, settings): data_empties = OrderedDict((ob_obj, get_blender_empty_key(ob_obj.bdata)) for ob_obj in objects if ob_obj.type == 'EMPTY') + perfmon.step("FBX export prepare: Wrapping Meshes...") + data_meshes = OrderedDict() for ob_obj in objects: if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE: @@ -2120,6 +2128,8 @@ def fbx_data_from_scene(scene, settings): if use_org_data: data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False) + perfmon.step("FBX export prepare: Wrapping ShapeKeys...") + # ShapeKeys. data_deformers_shape = OrderedDict() geom_mat_co = settings.global_matrix if settings.bake_space_transform else None @@ -2152,6 +2162,8 @@ def fbx_data_from_scene(scene, settings): data = (channel_key, geom_key, shape_verts_co, shape_verts_idx) data_deformers_shape.setdefault(me, (me_key, shapes_key, OrderedDict()))[2][shape] = data + perfmon.step("FBX export prepare: Wrapping Armatures...") + # Armatures! data_deformers_skin = OrderedDict() data_bones = OrderedDict() @@ -2167,12 +2179,16 @@ def fbx_data_from_scene(scene, settings): if settings.add_leaf_bones: data_leaf_bones = fbx_generate_leaf_bones(settings, data_bones) + perfmon.step("FBX export prepare: Wrapping World...") + # Some world settings are embedded in FBX materials... if scene.world: data_world = OrderedDict(((scene.world, get_blenderID_key(scene.world)),)) else: data_world = OrderedDict() + perfmon.step("FBX export prepare: Wrapping Materials...") + # TODO: Check all the mat stuff works even when mats are linked to Objects # (we can then have the same mesh used with different materials...). # *Should* work, as FBX always links its materials to Models (i.e. objects). @@ -2194,6 +2210,8 @@ def fbx_data_from_scene(scene, settings): else: data_materials[mat] = (get_blenderID_key(mat), [ob_obj]) + perfmon.step("FBX export prepare: Wrapping Textures...") + # Note FBX textures also hold their mapping info. # TODO: Support layers? data_textures = OrderedDict() @@ -2231,6 +2249,8 @@ def fbx_data_from_scene(scene, settings): else: data_videos[img] = (get_blenderID_key(img), [tex]) + perfmon.step("FBX export prepare: Wrapping Animations...") + # Animation... animations = () frame_start = scene.frame_start @@ -2249,6 +2269,8 @@ def fbx_data_from_scene(scene, settings): # ##### Creation of templates... + perfmon.step("FBX export prepare: Generating templates...") + templates = OrderedDict() templates[b"GlobalSettings"] = fbx_template_def_globalsettings(scene, settings, nbr_users=1) @@ -2326,6 +2348,8 @@ def fbx_data_from_scene(scene, settings): # ##### Creation of connections... + perfmon.step("FBX export prepare: Generating Connections...") + connections = [] # Objects (with classical parenting). @@ -2448,6 +2472,8 @@ def fbx_data_from_scene(scene, settings): # Animcurve -> Animcurvenode. connections.append((b"OP", get_fbx_uuid_from_key(acurve_key), acurvenode_id, fbx_item.encode())) + perfmon.level_down() + # ##### And pack all this! return FBXExportData( @@ -2636,22 +2662,34 @@ def fbx_objects_elements(root, scene_data): """ Data (objects, geometry, material, textures, armatures, etc.). """ + perfmon = PerfMon() + perfmon.level_up() objects = elem_empty(root, b"Objects") + perfmon.step("FBX export fetch empties (%d)..." % len(scene_data.data_empties)) + for empty in scene_data.data_empties: fbx_data_empty_elements(objects, empty, scene_data) + perfmon.step("FBX export fetch lamps (%d)..." % len(scene_data.data_lamps)) + for lamp in scene_data.data_lamps: fbx_data_lamp_elements(objects, lamp, scene_data) + perfmon.step("FBX export fetch cameras (%d)..." % len(scene_data.data_cameras)) + for cam in scene_data.data_cameras: fbx_data_camera_elements(objects, cam, scene_data) + perfmon.step("FBX export fetch meshes (%d)..." % len(scene_data.data_meshes)) + done_meshes = set() for me_obj in scene_data.data_meshes: fbx_data_mesh_elements(objects, me_obj, scene_data, done_meshes) del done_meshes + perfmon.step("FBX export fetch objects (%d)..." % len(scene_data.objects)) + for ob_obj in scene_data.objects: if ob_obj.is_dupli: continue @@ -2663,6 +2701,8 @@ def fbx_objects_elements(root, scene_data): fbx_data_object_elements(objects, dp_obj, scene_data) ob_obj.dupli_list_clear() + perfmon.step("FBX export fetch remaining...") + for ob_obj in scene_data.objects: if not (ob_obj.is_object and ob_obj.type == 'ARMATURE'): continue @@ -2680,8 +2720,13 @@ def fbx_objects_elements(root, scene_data): for vid in scene_data.data_videos: fbx_data_video_elements(objects, vid, scene_data) + perfmon.step("FBX export fetch animations...") + start_time = time.process_time() + fbx_data_animation_elements(objects, scene_data) + perfmon.level_down() + def fbx_connections_elements(root, scene_data): """ diff --git a/io_scene_fbx/fbx_utils.py b/io_scene_fbx/fbx_utils.py index 07e4d824..65ab2470 100644 --- a/io_scene_fbx/fbx_utils.py +++ b/io_scene_fbx/fbx_utils.py @@ -22,6 +22,7 @@ import math +import time from collections import namedtuple, OrderedDict from collections.abc import Iterable @@ -147,6 +148,56 @@ FBX_FRAMERATES = ( # ##### Misc utilities ##### +DO_PERFMON = True + +if DO_PERFMON: + class PerfMon(): + def __init__(self): + self.level = -1 + self.ref_time = [] + + def level_up(self, message=""): + self.level += 1 + self.ref_time.append(None) + if message: + print("\t" * self.level, message, sep="") + + def level_down(self, message=""): + if not self.ref_time: + if message: + print(message) + return + ref_time = self.ref_time[self.level] + print("\t" * self.level, + "\tDone (%f sec)\n" % ((time.process_time() - ref_time) if ref_time is not None else 0.0), + sep="") + if message: + print("\t" * self.level, message, sep="") + del self.ref_time[self.level] + self.level -= 1 + + def step(self, message=""): + ref_time = self.ref_time[self.level] + curr_time = time.process_time() + if ref_time is not None: + print("\t" * self.level, "\tDone (%f sec)\n" % (curr_time - ref_time), sep="") + self.ref_time[self.level] = curr_time + print("\t" * self.level, message, sep="") +else: + class PerfMon(): + def __init__(self): + pass + + def level_up(self, message=""): + pass + + def level_down(self, message=""): + pass + + def step(self, message=""): + pass + + # Note: this could be in a utility (math.units e.g.)... UNITS = { diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 10a17dd3..33950f41 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -41,6 +41,7 @@ from . import parse_fbx, fbx_utils from .parse_fbx import data_types, FBXElem from .fbx_utils import ( + PerfMon, units_convertor_iter, array_to_matrix4, similar_values, @@ -2091,6 +2092,11 @@ def load(operator, context, filepath="", start_time_proc = time.process_time() start_time_sys = time.time() + perfmon = PerfMon() + perfmon.level_up() + perfmon.step("FBX Import: start importing %s" % filepath) + perfmon.level_up() + # detect ascii files if is_ascii(filepath, 24): operator.report({'ERROR'}, "ASCII FBX files are not supported %r" % filepath) @@ -2135,6 +2141,8 @@ def load(operator, context, filepath="", # #### Get some info from GlobalSettings. + perfmon.step("FBX import: Prepare...") + fbx_settings = elem_find_first(elem_root, b'GlobalSettings') fbx_settings_props = elem_find_first(fbx_settings, b'Properties70') if fbx_settings is None or fbx_settings_props is None: @@ -2194,6 +2202,8 @@ def load(operator, context, filepath="", # #### And now, the "real" data. + perfmon.step("FBX import: Templates...") + fbx_defs = elem_find_first(elem_root, b'Definitions') # can be None fbx_nodes = elem_find_first(elem_root, b'Objects') fbx_connections = elem_find_first(elem_root, b'Connections') @@ -2234,6 +2244,8 @@ def load(operator, context, filepath="", return fbx_templates.get(key, fbx_elem_nil) return ret + perfmon.step("FBX import: Nodes...") + # ---- # Build FBX node-table def _(): @@ -2249,6 +2261,8 @@ def load(operator, context, filepath="", # http://download.autodesk.com/us/fbx/20112/FBX_SDK_HELP/index.html?url= # WS73099cc142f487551fea285e1221e4f9ff8-7fda.htm,topicNumber=d0e6388 + perfmon.step("FBX import: Connections...") + fbx_connection_map = {} fbx_connection_map_reverse = {} @@ -2261,6 +2275,8 @@ def load(operator, context, filepath="", fbx_connection_map_reverse.setdefault(c_dst, []).append((c_src, fbx_link)) _(); del _ + perfmon.step("FBX import: Meshes...") + # ---- # Load mesh data def _(): @@ -2275,6 +2291,8 @@ def load(operator, context, filepath="", fbx_item[1] = blen_read_geom(fbx_tmpl, fbx_obj, settings) _(); del _ + perfmon.step("FBX import: Materials & Textures...") + # ---- # Load material data def _(): @@ -2310,6 +2328,8 @@ def load(operator, context, filepath="", fbx_item[1] = blen_read_texture_image(fbx_tmpl_tex, fbx_obj, basedir, settings) _(); del _ + perfmon.step("FBX import: Cameras & Lamps...") + # ---- # Load camera data def _(): @@ -2353,6 +2373,8 @@ def load(operator, context, filepath="", def connection_filter_reverse(fbx_uuid, fbx_id): return connection_filter_ex(fbx_uuid, fbx_id, fbx_connection_map_reverse) + perfmon.step("FBX import: Objects & Armatures...") + # -- temporary helper hierarchy to build armatures and objects from # lookup from uuid to helper node. Used to build parent-child relations and later to look up animated nodes. fbx_helper_nodes = {} @@ -2517,6 +2539,8 @@ def load(operator, context, filepath="", # root_helper.print_info(0) _(); del _ + perfmon.step("FBX import: ShapeKeys...") + # We can handle shapes. blend_shape_channels = {} # We do not need Shapes themselves, but keyblocks, for anim. @@ -2567,6 +2591,8 @@ def load(operator, context, filepath="", blend_shape_channels[bc_uuid] = keyblocks _(); del _ + perfmon.step("FBX import: Animations...") + # Animation! def _(): fbx_tmpl_astack = fbx_template_get((b'AnimationStack', b'FbxAnimStack')) @@ -2658,6 +2684,8 @@ def load(operator, context, filepath="", _(); del _ + perfmon.step("FBX import: Assign materials...") + def _(): # link Material's to Geometry (via Model's) for fbx_uuid, fbx_item in fbx_table_nodes.items(): @@ -2671,17 +2699,19 @@ def load(operator, context, filepath="", if mesh is None: continue - for (fbx_lnk, - fbx_lnk_item, - fbx_lnk_type) in connection_filter_forward(fbx_uuid, b'Model'): + # In Blender, we link materials to data, typically (meshes), while in FBX they are linked to objects... + # So we have to be careful not to re-add endlessly the same material to a mesh! + # This can easily happen with 'baked' dupliobjects, see T44386. + # TODO: add an option to link materials to objects in Blender instead? + done_mats = set() + 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) + for (fbx_lnk_material, material, fbx_lnk_material_type) in connection_filter_reverse(fbx_lnk_uuid, b'Material'): + if material not in done_mats: + mesh.materials.append(material) + done_mats.add(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. @@ -2689,6 +2719,8 @@ def load(operator, context, filepath="", print("WARNING: mesh '%s' had invalid material indices, those were reset to first material" % mesh.name) _(); del _ + perfmon.step("FBX import: Assign textures...") + def _(): material_images = {} @@ -2898,6 +2930,8 @@ def load(operator, context, filepath="", _(); del _ + perfmon.step("FBX import: Cycles z-offset workaround...") + def _(): # Annoying workaround for cycles having no z-offset if material_decals and use_alpha_decals: @@ -2925,6 +2959,7 @@ def load(operator, context, filepath="", material.use_raytrace = False _(); del _ - print('Import finished in %.4f sec (process time: %.4f sec).' % - (time.time() - start_time_sys, time.process_time() - start_time_proc)) + perfmon.level_down() + + perfmon.level_down("Import finished.") return {'FINISHED'} |