From 4e6a962b2b75ae963256eabca925dd05ef29fb5d Mon Sep 17 00:00:00 2001 From: Hans Goudey Date: Mon, 8 Jul 2024 17:09:22 +0200 Subject: [PATCH] Fix #120590: FBX import gives sharp edges for non-normalized normals A few files included in reports contained normals that were not normalized. In order to contain consistent normals around smooth corner fans, sharp edges are added when neighboring corner normals aren't close enough. Non-unit-vector normals also triggered this check. Blender used to do this implicitly when the derived `MeshLoop.normal` value was set. That API has been changed already, and it's clearer if the addon provides normalized normals, so this PR adds a normalization step to the FBX importer. Pull Request: https://projects.blender.org/blender/blender/pulls/124261 --- scripts/addons_core/io_scene_fbx/import_fbx.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/addons_core/io_scene_fbx/import_fbx.py b/scripts/addons_core/io_scene_fbx/import_fbx.py index 13daf585cba..0c95352605e 100644 --- a/scripts/addons_core/io_scene_fbx/import_fbx.py +++ b/scripts/addons_core/io_scene_fbx/import_fbx.py @@ -1784,6 +1784,11 @@ def blen_read_geom_layer_normal(fbx_obj, mesh, xform=None): return False +def normalize_vecs(vectors): + norms = np.linalg.norm(vectors, axis=1, keepdims=True) + np.divide(vectors, norms, out=vectors, where=norms != 0) + + def blen_read_geom(fbx_tmpl, fbx_obj, settings): # Vertices are in object space, but we are post-multiplying all transforms with the inverse of the # global matrix, so we need to apply the global matrix to the vertices to get the correct result. @@ -1914,6 +1919,10 @@ def blen_read_geom(fbx_tmpl, fbx_obj, settings): clnors = np.empty(len(mesh.loops) * 3, dtype=bl_nors_dtype) mesh.attributes["temp_custom_normals"].data.foreach_get("vector", clnors) + clnors = clnors.reshape(len(mesh.loops), 3) + normalize_vecs(clnors) + clnors = clnors.reshape(len(mesh.loops) * 3) + # Iterating clnors into a nested tuple first is faster than passing clnors.reshape(-1, 3) directly into # normals_split_custom_set. We use clnors.data since it is a memoryview, which is faster to iterate than clnors. mesh.normals_split_custom_set(tuple(zip(*(iter(clnors.data),) * 3)))