From 149353ee5577fcda820584973ce46d65d1db74aa Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Sat, 28 Oct 2023 15:10:43 +0100 Subject: [PATCH 1/2] FBX IO: Export normals with IndexToDirect reference mode The FBX SDK documentation says that normals should be IndexToDirect mapped. 9 years ago, we didn't export with IndexToDirect mode because apps at the time didn't support it well. 9 years is a long time and the apps I've tried all work with IndexToDirect, so the export of normals has been updated to use the IndexToDirect mode, using the already existing, but disabled code. The FBX importer was not previously set up to handle the import of ByVertice-IndexToDirect normals, so this has been added to ensure that FBX IO exported .fbx files can also be imported. --- io_scene_fbx/export_fbx_bin.py | 54 +++++++++++++--------------------- io_scene_fbx/import_fbx.py | 15 ++++++++-- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/io_scene_fbx/export_fbx_bin.py b/io_scene_fbx/export_fbx_bin.py index 2b384cae8..d5a9a1b48 100644 --- a/io_scene_fbx/export_fbx_bin.py +++ b/io_scene_fbx/export_fbx_bin.py @@ -1155,10 +1155,6 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): # Loop normals. tspacenumber = 0 if write_normals: - # NOTE: ByVertice-IndexToDirect is not supported by the importer currently. - # XXX Official docs says normals should use IndexToDirect, - # but this does not seem well supported by apps currently... - normal_bl_dtype = np.single normal_fbx_dtype = np.float64 match me.normals_domain: @@ -1182,41 +1178,31 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes): t_normal = np.empty(len(normal_source) * 3, dtype=normal_bl_dtype) normal_source.foreach_get("vector", t_normal) t_normal = nors_transformed(t_normal, geom_mat_no, normal_fbx_dtype) - if 0: - normal_idx_fbx_dtype = np.int32 - lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0) - elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION) - elem_data_single_string(lay_nor, b"Name", b"") - elem_data_single_string(lay_nor, b"MappingInformationType", normal_mapping) - elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect") + normal_idx_fbx_dtype = np.int32 + lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0) + elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION) + elem_data_single_string(lay_nor, b"Name", b"") + elem_data_single_string(lay_nor, b"MappingInformationType", normal_mapping) + # FBX SDK documentation says that normals should use IndexToDirect. + elem_data_single_string(lay_nor, b"ReferenceInformationType", b"IndexToDirect") - # Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_normal. - # Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique - # helper function. - t_normal, t_normal_idx = fast_first_axis_unique(t_normal.reshape(-1, 3), return_inverse=True) + # Tuple of unique sorted normals and then the index in the unique sorted normals of each normal in t_normal. + # Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique + # helper function. + t_normal, t_normal_idx = fast_first_axis_unique(t_normal.reshape(-1, 3), return_inverse=True) - # Convert to the type for fbx - t_normal_idx = astype_view_signedness(t_normal_idx, normal_idx_fbx_dtype) + # Convert to the type for fbx + t_normal_idx = astype_view_signedness(t_normal_idx, normal_idx_fbx_dtype) - elem_data_single_float64_array(lay_nor, b"Normals", t_normal) - # Normal weights, no idea what it is. - # t_normal_w = np.zeros(len(t_normal), dtype=np.float64) - # elem_data_single_float64_array(lay_nor, b"NormalsW", t_normal_w) + elem_data_single_float64_array(lay_nor, b"Normals", t_normal) + # Normal weights, no idea what it is. + # t_normal_w = np.zeros(len(t_normal), dtype=np.float64) + # elem_data_single_float64_array(lay_nor, b"NormalsW", t_normal_w) - elem_data_single_int32_array(lay_nor, b"NormalsIndex", t_normal_idx) + elem_data_single_int32_array(lay_nor, b"NormalsIndex", t_normal_idx) - del t_normal_idx - # del t_normal_w - else: - lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0) - elem_data_single_int32(lay_nor, b"Version", FBX_GEOMETRY_NORMAL_VERSION) - elem_data_single_string(lay_nor, b"Name", b"") - elem_data_single_string(lay_nor, b"MappingInformationType", normal_mapping) - elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct") - elem_data_single_float64_array(lay_nor, b"Normals", t_normal) - # Normal weights, no idea what it is. - # t_normal = np.zeros(len(me.loops), dtype=np.float64) - # elem_data_single_float64_array(lay_nor, b"NormalsW", t_normal) + del t_normal_idx + # del t_normal_w del t_normal # tspace diff --git a/io_scene_fbx/import_fbx.py b/io_scene_fbx/import_fbx.py index 044f95d35..cc8bcd941 100644 --- a/io_scene_fbx/import_fbx.py +++ b/io_scene_fbx/import_fbx.py @@ -1392,7 +1392,18 @@ def blen_read_geom_array_mapped_vert( xform=None, quiet=False, ): if fbx_layer_mapping == b'ByVertice': - if fbx_layer_ref == b'Direct': + if fbx_layer_ref == b'IndexToDirect': + # XXX Looks like we often get no fbx_layer_index in this case, shall not happen but happens... + # We fallback to 'Direct' mapping in this case. + #~ assert(fbx_layer_index is not None) + if fbx_layer_index is None: + blen_read_geom_array_foreach_set_direct(blen_data, blen_attr, blen_dtype, fbx_layer_data, stride, + item_size, descr, xform) + else: + blen_read_geom_array_foreach_set_indexed(blen_data, blen_attr, blen_dtype, fbx_layer_data, + fbx_layer_index, stride, item_size, descr, xform) + return True + elif fbx_layer_ref == b'Direct': assert(fbx_layer_index is None) blen_read_geom_array_foreach_set_direct(blen_data, blen_attr, blen_dtype, fbx_layer_data, stride, item_size, descr, xform) @@ -1748,8 +1759,6 @@ def blen_read_geom_layer_normal(fbx_obj, mesh, xform=None): loop_normals = np.repeat(bdata, poly_loop_totals, axis=0) mesh.attributes["temp_custom_normals"].data.foreach_set("vector", loop_normals.ravel()) elif blen_data_type == "Vertices": - # Note: Currently unreachable because `blen_read_geom_array_mapped_polyloop` covers all the supported - # import cases covered by `blen_read_geom_array_mapped_vert`. # We have to copy vnors to lnors! Far from elegant, but simple. loop_vertex_indices = MESH_ATTRIBUTE_CORNER_VERT.to_ndarray(mesh.attributes) mesh.attributes["temp_custom_normals"].data.foreach_set("vector", bdata[loop_vertex_indices].ravel()) -- 2.30.2 From c332758bf688266d8f3dc22e2f7065aa010468ca Mon Sep 17 00:00:00 2001 From: Thomas Barlow Date: Sat, 2 Dec 2023 01:03:13 +0000 Subject: [PATCH 2/2] Increase FBX minor version --- io_scene_fbx/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io_scene_fbx/__init__.py b/io_scene_fbx/__init__.py index a3850aed8..784cfcc74 100644 --- a/io_scene_fbx/__init__.py +++ b/io_scene_fbx/__init__.py @@ -5,7 +5,7 @@ bl_info = { "name": "FBX format", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem", - "version": (5, 10, 3), + "version": (5, 11, 0), "blender": (4, 1, 0), "location": "File > Import-Export", "description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions", -- 2.30.2