Fix #104714: Missing shape keys in FBX export when original mesh data cannot be used #104890

Merged
Thomas Barlow merged 7 commits from Mysteryem/blender-addons:fbx_fix_triangulate_removing_shapes into main 2023-09-19 02:26:19 +02:00
Showing only changes of commit 8b02121ae9 - Show all commits

View File

@ -2536,7 +2536,6 @@ def fbx_data_from_scene(scene, depsgraph, settings):
if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE: if ob_obj.type not in BLENDER_OBJECT_TYPES_MESHLIKE:
continue continue
ob = ob_obj.bdata ob = ob_obj.bdata
use_org_data = True
org_ob_obj = None org_ob_obj = None
# Do not want to systematically recreate a new mesh for dupliobject instances, kind of break purpose of those. # Do not want to systematically recreate a new mesh for dupliobject instances, kind of break purpose of those.
@ -2546,101 +2545,112 @@ def fbx_data_from_scene(scene, depsgraph, settings):
data_meshes[ob_obj] = data_meshes[org_ob_obj] data_meshes[ob_obj] = data_meshes[org_ob_obj]
continue continue
is_ob_material = any(ms.link == 'OBJECT' for ms in ob.material_slots) # There are 4 different cases for what we need to do with the original data of each Object:
# 1) The original data can be used without changes.
# 2) A copy of the original data needs to be made.
# - If a mesh needs to be modified upon export, e.g. it needs triangulating.
# - If a mesh has Object-linked materials. This is to do with how materials are currently mapped to FBX.
# 3) A non-mesh needs to be converted to a mesh.
# 4) The Object needs to be evaluated and then converted to a mesh.
# - Whenever use_mesh_modifiers is enabled and either there are modifiers to apply or the Object is not a mesh.
# If multiple cases apply to an Object, then only the last applicable case is relevant.
do_copy = any(ms.link == 'OBJECT' for ms in ob.material_slots) or (ob.type == 'MESH' and settings.use_triangles)
do_convert = ob.type in BLENDER_OTHER_OBJECT_TYPES
do_evaluate = do_convert and settings.use_mesh_modifiers
if settings.use_mesh_modifiers or settings.use_triangles or ob.type in BLENDER_OTHER_OBJECT_TYPES or is_ob_material: # If the Object is a mesh, and we're applying modifiers, check if there are actually any modifiers to apply.
# We cannot use default mesh in that case, or material would not be the right ones... # If there are then the mesh will need to be evaluated, and we may need to make some temporary changes before
use_org_data = not (is_ob_material or ob.type in BLENDER_OTHER_OBJECT_TYPES) # evaluating the mesh.
apply_modifiers = False backup_pose_positions = []
backup_pose_positions = [] tmp_mods = []
tmp_mods = [] if ob.type == 'MESH' and settings.use_mesh_modifiers:
if use_org_data and ob.type == 'MESH': # No need to create a new mesh in this case, if no modifier is active!
if settings.use_triangles: last_subsurf = None
use_org_data = False for mod in ob.modifiers:
# No need to create a new mesh in this case, if no modifier is active! # For meshes, when armature export is enabled, disable Armature modifiers here!
last_subsurf = None # XXX Temp hacks here since currently we only have access to a viewport depsgraph...
for mod in ob.modifiers: #
# For meshes, when armature export is enabled, disable Armature modifiers here! # NOTE: We put armature to the rest pose instead of disabling it so we still
# XXX Temp hacks here since currently we only have access to a viewport depsgraph... # have vertex groups in the evaluated mesh.
# if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types:
# NOTE: We put armature to the rest pose instead of disabling it so we still object = mod.object
# have vertex groups in the evaluated mesh. if object and object.type == 'ARMATURE':
if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types: armature = object.data
object = mod.object # If armature is already in REST position, there's nothing to back-up
if object and object.type == 'ARMATURE': # This cuts down on export time dramatically, if all armatures are already in REST position
armature = object.data # by not triggering dependency graph update
# If armature is already in REST position, there's nothing to back-up if armature.pose_position != 'REST':
# This cuts down on export time dramatically, if all armatures are already in REST position backup_pose_positions.append((armature, armature.pose_position))
# by not triggering dependency graph update armature.pose_position = 'REST'
if armature.pose_position != 'REST': elif mod.show_render or mod.show_viewport:
backup_pose_positions.append((armature, armature.pose_position)) # If exporting with subsurf collect the last Catmull-Clark subsurf modifier
armature.pose_position = 'REST' # and disable it. We can use the original data as long as this is the first
elif mod.show_render or mod.show_viewport: # found applicable subsurf modifier.
# If exporting with subsurf collect the last Catmull-Clark subsurf modifier if settings.use_subsurf and mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
# and disable it. We can use the original data as long as this is the first if last_subsurf:
# found applicable subsurf modifier. do_evaluate = True
if settings.use_subsurf and mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK': last_subsurf = mod
if last_subsurf: else:
use_org_data = False do_evaluate = True
apply_modifiers = True if settings.use_subsurf and last_subsurf:
last_subsurf = mod # XXX: When exporting with subsurf information temporarily disable
else: # the last subsurf modifier.
use_org_data = False tmp_mods.append((last_subsurf, last_subsurf.show_render, last_subsurf.show_viewport))
apply_modifiers = True
if settings.use_subsurf and last_subsurf:
# XXX: When exporting with subsurf information temporarily disable
# the last subsurf modifier.
tmp_mods.append((last_subsurf, last_subsurf.show_render, last_subsurf.show_viewport))
last_subsurf.show_render = False
last_subsurf.show_viewport = False
if not use_org_data:
# If modifiers has been altered need to update dependency graph.
if backup_pose_positions or tmp_mods:
depsgraph.update()
if apply_modifiers or ob.type in BLENDER_OTHER_OBJECT_TYPES:
ob_to_convert = ob.evaluated_get(depsgraph)
# NOTE: The dependency graph might be re-evaluating multiple times, which could
# potentially free the mesh created early on. So we put those meshes to bmain and
# free them afterwards. Not ideal but ensures correct ownerwhip.
tmp_me = bpy.data.meshes.new_from_object(
ob_to_convert, preserve_all_data_layers=True, depsgraph=depsgraph)
# Usually the materials of the evaluated object will be the same, but modifiers, such as Geometry if do_evaluate:
# Nodes, can change the materials. # If modifiers has been altered need to update dependency graph.
orig_mats = tuple(slot.material for slot in ob.material_slots)
eval_mats = tuple(slot.material.original if slot.material else None
for slot in ob_to_convert.material_slots)
if orig_mats != eval_mats:
# Override the default behaviour of getting materials from ob_obj.bdata.material_slots.
ob_obj.override_materials = eval_mats
else:
# bpy.data.meshes.new_from_object always removes shape keys (see #104714), so create a copy of the
# mesh instead.
tmp_me = ob.data.copy()
# Triangulate the mesh if requested
if settings.use_triangles:
import bmesh
bm = bmesh.new()
bm.from_mesh(tmp_me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(tmp_me)
bm.free()
data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True)
# Change armatures back.
for armature, pose_position in backup_pose_positions:
print((armature, pose_position))
armature.pose_position = pose_position
# Update now, so we don't leave modified state after last object was exported.
# Re-enable temporary disabled modifiers.
for mod, show_render, show_viewport in tmp_mods:
mod.show_render = show_render
mod.show_viewport = show_viewport
if backup_pose_positions or tmp_mods: if backup_pose_positions or tmp_mods:
depsgraph.update() depsgraph.update()
if use_org_data: ob_to_convert = ob.evaluated_get(depsgraph)
# NOTE: The dependency graph might be re-evaluating multiple times, which could
# potentially free the mesh created early on. So we put those meshes to bmain and
# free them afterwards. Not ideal but ensures correct ownership.
tmp_me = bpy.data.meshes.new_from_object(
ob_to_convert, preserve_all_data_layers=True, depsgraph=depsgraph)
# Usually the materials of the evaluated object will be the same, but modifiers, such as Geometry
# Nodes, can change the materials.
orig_mats = tuple(slot.material for slot in ob.material_slots)
eval_mats = tuple(slot.material.original if slot.material else None
for slot in ob_to_convert.material_slots)
if orig_mats != eval_mats:
# Override the default behaviour of getting materials from ob_obj.bdata.material_slots.
ob_obj.override_materials = eval_mats
elif do_convert:
tmp_me = bpy.data.meshes.new_from_object(ob, preserve_all_data_layers=True, depsgraph=depsgraph)
elif do_copy:
# bpy.data.meshes.new_from_object always removes shape keys (see #104714), so create a direct copy of the
# mesh instead.
tmp_me = ob.data.copy()
else:
tmp_me = None
if tmp_me is None:
# Use the original data of this Object.
data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False) data_meshes[ob_obj] = (get_blenderID_key(ob.data), ob.data, False)
else:
# Triangulate the mesh if requested
if settings.use_triangles:
import bmesh
bm = bmesh.new()
bm.from_mesh(tmp_me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(tmp_me)
bm.free()
# A temporary mesh was created for this Object, which should be deleted once the export is complete.
data_meshes[ob_obj] = (get_blenderID_key(tmp_me), tmp_me, True)
# Change armatures back.
for armature, pose_position in backup_pose_positions:
print((armature, pose_position))
armature.pose_position = pose_position
# Update now, so we don't leave modified state after last object was exported.
# Re-enable temporary disabled modifiers.
for mod, show_render, show_viewport in tmp_mods:
mod.show_render = show_render
mod.show_viewport = show_viewport
if backup_pose_positions or tmp_mods:
depsgraph.update()
# In case "real" source object of that dupli did not yet still existed in data_meshes, create it now! # In case "real" source object of that dupli did not yet still existed in data_meshes, create it now!
if org_ob_obj is not None: if org_ob_obj is not None: