Fix #104669: Incorrect material assignment of imported FBX with duplicate materials #104683

Merged
Showing only changes of commit c98f8cd1d8 - Show all commits

View File

@ -3310,33 +3310,64 @@ def load(operator, context, filepath="",
def _(): def _():
# link Material's to Geometry (via Model's) # link Material's to Geometry (via Model's)
for fbx_uuid, fbx_item in fbx_table_nodes.items(): processed_meshes = set()
fbx_obj, blen_data = fbx_item for helper_uuid, helper_node in fbx_helper_nodes.items():
if fbx_obj.id != b'Geometry': obj = helper_node.bl_obj
if not obj or obj.type != 'MESH':
continue continue
mesh = fbx_table_nodes.get(fbx_uuid, (None, None))[1] # Get the Mesh corresponding to the Geometry used by this Model.
mesh = obj.data
processed_meshes.add(mesh)
# can happen in rare cases # Get the Materials from the Model's connections.
if mesh is None: material_connections = connection_filter_reverse(helper_uuid, b'Material')
if not material_connections:
continue continue
# In Blender, we link materials to data, typically (meshes), while in FBX they are linked to objects... mesh_mats = mesh.materials
# So we have to be careful not to re-add endlessly the same material to a mesh! num_mesh_mats = len(mesh_mats)
# This can easily happen with 'baked' dupliobjects, see T44386.
# TODO: add an option to link materials to objects in Blender instead?
done_materials = set()
for (fbx_lnk, fbx_lnk_item, fbx_lnk_type) in connection_filter_forward(fbx_uuid, b'Model'): if num_mesh_mats == 0:
# link materials # This is the first (or only) model to use this Geometry. This is the most common case when importing.
fbx_lnk_uuid = elem_uuid(fbx_lnk) # All the Materials can trivially be appended to the Mesh's Materials.
for (fbx_lnk_material, material, fbx_lnk_material_type) in connection_filter_reverse(fbx_lnk_uuid, b'Material'): mats_to_append = material_connections
if material not in done_materials: mats_to_compare = ()
mesh.materials.append(material) elif num_mesh_mats == len(material_connections):
done_materials.add(material) # Another Model uses the same Geometry and has already appended its Materials to the Mesh. This is the
# second most common case when importing.
# It's also possible that a Model could share the same Geometry and have the same number of Materials,
# but have different Materials, though this is less common.
# The Model Materials will need to be compared with the Mesh Materials at the same indices to check if
# they are different.
mats_to_append = ()
mats_to_compare = material_connections
else:
Review

The last branch of the if statement technically would cover all cases, but keeping each branch separate I think helps visually explain what each of the expected cases are.

The last branch of the if statement technically would cover all cases, but keeping each branch separate I think helps visually explain what each of the expected cases are.
# Under the assumption that only used Materials are connected to the Model, the number of Materials of
# each Model using a specific Geometry should be the same, otherwise the Material Indices of the
# Geometry will be out-of-bounds of the Materials of at least one of the Models using that Geometry.
# We wouldn't expect this case to happen, but there's nothing to say it can't.
# We'll handle a differing number of Materials by appending any extra Materials and comparing the rest.
mats_to_append = material_connections[num_mesh_mats:]
mats_to_compare = material_connections[:num_mesh_mats]
# We have to validate mesh polygons' ma_idx, see T41015! for _fbx_lnk_material, material, _fbx_lnk_material_type in mats_to_append:
# Some FBX seem to have an extra 'default' material which is not defined in FBX file. mesh_mats.append(material)
mats_to_compare_and_slots = zip(mats_to_compare, obj.material_slots)
for (_fbx_lnk_material, material, _fbx_lnk_material_type), mat_slot in mats_to_compare_and_slots:
if material != mat_slot.material:
# Material Slots default to being linked to the Mesh, so a previously processed Object is also using
# this Mesh, but the Mesh uses a different Material for this Material Slot.
# To have a different Material for this Material Slot on this Object only, the Material Slot must be
# linked to the Object rather than the Mesh.
# TODO: add an option to link all materials to objects in Blender instead?
mat_slot.link = 'OBJECT'
mat_slot.material = material
# We have to validate mesh polygons' ma_idx, see #41015!
# Some FBX seem to have an extra 'default' material which is not defined in FBX file.
for mesh in processed_meshes:
if mesh.validate_material_indices(): if mesh.validate_material_indices():
print("WARNING: mesh '%s' had invalid material indices, those were reset to first material" % mesh.name) print("WARNING: mesh '%s' had invalid material indices, those were reset to first material" % mesh.name)
_(); del _ _(); del _