FBX IO: Export normals matching the mesh's normals_domain #104976

Merged
Thomas Barlow merged 4 commits from Mysteryem/blender-addons:fbx_update_for_auto_smooth into main 2023-11-03 16:42:29 +01:00
2 changed files with 44 additions and 26 deletions

View File

@ -5,7 +5,7 @@
bl_info = { bl_info = {
"name": "FBX format", "name": "FBX format",
"author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem", "author": "Campbell Barton, Bastien Montagne, Jens Restemeier, @Mysteryem",
"version": (5, 9, 1), "version": (5, 10, 0),
"blender": (4, 1, 0), "blender": (4, 1, 0),
"location": "File > Import-Export", "location": "File > Import-Export",
"description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions", "description": "FBX IO meshes, UVs, vertex colors, materials, textures, cameras, lamps and actions",

View File

@ -1155,51 +1155,69 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
# Loop normals. # Loop normals.
tspacenumber = 0 tspacenumber = 0
if write_normals: if write_normals:
# NOTE: this is not supported by importer currently. # NOTE: ByVertice-IndexToDirect is not supported by the importer currently.
# XXX Official docs says normals should use IndexToDirect, # XXX Official docs says normals should use IndexToDirect,
# but this does not seem well supported by apps currently... # but this does not seem well supported by apps currently...
ln_bl_dtype = np.single normal_bl_dtype = np.single
ln_fbx_dtype = np.float64 normal_fbx_dtype = np.float64
t_ln = np.empty(len(me.loops) * 3, dtype=ln_bl_dtype) match me.normals_domain:
me.loops.foreach_get("normal", t_ln) case 'POINT':
t_ln = nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype) # All faces are smooth shaded, so we can get normals from the vertices.
normal_source = me.vertex_normals
normal_mapping = b"ByVertice"
case 'FACE':
# Either all faces or all edges are sharp, so we can get normals from the faces.
normal_source = me.polygon_normals
normal_mapping = b"ByPolygon"
case 'CORNER':
# We have a mix of sharp/smooth edges/faces or custom split normals, so need to get normals from
# corners.
normal_source = me.corner_normals
normal_mapping = b"ByPolygonVertex"
case _:
# Unreachable
raise AssertionError("Unexpected normals domain '%s'" % me.normals_domain)
# Each normal has 3 components, so the length is multiplied by 3.
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: if 0:
lnidx_fbx_dtype = np.int32 normal_idx_fbx_dtype = np.int32
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0) 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_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"Name", b"")
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex") elem_data_single_string(lay_nor, b"MappingInformationType", normal_mapping)
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"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_ln. # 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 # Since we don't care about how the normals are sorted, only that they're unique, we can use the fast unique
# helper function. # helper function.
t_ln, t_lnidx = fast_first_axis_unique(t_ln.reshape(-1, 3), return_inverse=True) t_normal, t_normal_idx = fast_first_axis_unique(t_normal.reshape(-1, 3), return_inverse=True)
# Convert to the type for fbx # Convert to the type for fbx
t_lnidx = astype_view_signedness(t_lnidx, lnidx_fbx_dtype) t_normal_idx = astype_view_signedness(t_normal_idx, normal_idx_fbx_dtype)
elem_data_single_float64_array(lay_nor, b"Normals", t_ln) elem_data_single_float64_array(lay_nor, b"Normals", t_normal)
# Normal weights, no idea what it is. # Normal weights, no idea what it is.
# t_lnw = np.zeros(len(t_ln), dtype=np.float64) # t_normal_w = np.zeros(len(t_normal), dtype=np.float64)
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_lnw) # elem_data_single_float64_array(lay_nor, b"NormalsW", t_normal_w)
elem_data_single_int32_array(lay_nor, b"NormalsIndex", t_lnidx) elem_data_single_int32_array(lay_nor, b"NormalsIndex", t_normal_idx)
del t_lnidx del t_normal_idx
# del t_lnw # del t_normal_w
else: else:
lay_nor = elem_data_single_int32(geom, b"LayerElementNormal", 0) 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_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"Name", b"")
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex") elem_data_single_string(lay_nor, b"MappingInformationType", normal_mapping)
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct") elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
elem_data_single_float64_array(lay_nor, b"Normals", t_ln) elem_data_single_float64_array(lay_nor, b"Normals", t_normal)
# Normal weights, no idea what it is. # Normal weights, no idea what it is.
# t_ln = np.zeros(len(me.loops), dtype=np.float64) # t_normal = np.zeros(len(me.loops), dtype=np.float64)
# elem_data_single_float64_array(lay_nor, b"NormalsW", t_ln) # elem_data_single_float64_array(lay_nor, b"NormalsW", t_normal)
del t_ln del t_normal
# tspace # tspace
if scene_data.settings.use_tspace: if scene_data.settings.use_tspace:
@ -1218,7 +1236,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
else: else:
del t_lt del t_lt
num_loops = len(me.loops) num_loops = len(me.loops)
t_ln = np.empty(num_loops * 3, dtype=ln_bl_dtype) t_ln = np.empty(num_loops * 3, dtype=normal_bl_dtype)
# t_lnw = np.zeros(len(me.loops), dtype=np.float64) # t_lnw = np.zeros(len(me.loops), dtype=np.float64)
uv_names = [uvlayer.name for uvlayer in me.uv_layers] uv_names = [uvlayer.name for uvlayer in me.uv_layers]
# Annoying, `me.calc_tangent` errors in case there is no geometry... # Annoying, `me.calc_tangent` errors in case there is no geometry...
@ -1236,7 +1254,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex") elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct") elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
elem_data_single_float64_array(lay_nor, b"Binormals", elem_data_single_float64_array(lay_nor, b"Binormals",
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype)) nors_transformed(t_ln, geom_mat_no, normal_fbx_dtype))
# Binormal weights, no idea what it is. # Binormal weights, no idea what it is.
# elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw) # elem_data_single_float64_array(lay_nor, b"BinormalsW", t_lnw)
@ -1249,7 +1267,7 @@ def fbx_data_mesh_elements(root, me_obj, scene_data, done_meshes):
elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex") elem_data_single_string(lay_nor, b"MappingInformationType", b"ByPolygonVertex")
elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct") elem_data_single_string(lay_nor, b"ReferenceInformationType", b"Direct")
elem_data_single_float64_array(lay_nor, b"Tangents", elem_data_single_float64_array(lay_nor, b"Tangents",
nors_transformed(t_ln, geom_mat_no, ln_fbx_dtype)) nors_transformed(t_ln, geom_mat_no, normal_fbx_dtype))
# Tangent weights, no idea what it is. # Tangent weights, no idea what it is.
# elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw) # elem_data_single_float64_array(lay_nor, b"TangentsW", t_lnw)