Fix #104714: Missing shape keys in FBX export when original mesh data cannot be used #104890
@ -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, 8, 0),
|
"version": (5, 8, 1),
|
||||||
"blender": (3, 6, 0),
|
"blender": (3, 6, 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",
|
||||||
|
@ -2592,7 +2592,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.
|
||||||
@ -2602,90 +2601,114 @@ 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 an export option modifies the data, e.g. Triangulate Faces is enabled.
|
||||||
|
# - If the Object has Object-linked materials. This is because our current mapping of materials to FBX requires
|
||||||
|
# that multiple Objects sharing a single mesh must have the same materials.
|
||||||
|
# 3) The Object needs to be converted to a mesh.
|
||||||
|
# - All mesh-like Objects that are not meshes need to be converted to a mesh in order to be exported.
|
||||||
|
# 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 needs to be
|
||||||
|
# converted to 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 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 to the
|
||||||
use_org_data = not (is_ob_material or ob.type in BLENDER_OTHER_OBJECT_TYPES)
|
# modifiers or scene before the mesh is evaluated.
|
||||||
backup_pose_positions = []
|
backup_pose_positions = []
|
||||||
tmp_mods = []
|
tmp_mods = []
|
||||||
if use_org_data and ob.type == 'MESH':
|
if ob.type == 'MESH' and settings.use_mesh_modifiers:
|
||||||
if settings.use_triangles:
|
# No need to create a new mesh in this case, if no modifier is active!
|
||||||
use_org_data = False
|
last_subsurf = None
|
||||||
# No need to create a new mesh in this case, if no modifier is active!
|
for mod in ob.modifiers:
|
||||||
last_subsurf = None
|
# For meshes, when armature export is enabled, disable Armature modifiers here!
|
||||||
for mod in ob.modifiers:
|
# XXX Temp hacks here since currently we only have access to a viewport depsgraph...
|
||||||
# For meshes, when armature export is enabled, disable Armature modifiers here!
|
#
|
||||||
# XXX Temp hacks here since currently we only have access to a viewport depsgraph...
|
# NOTE: We put armature to the rest pose instead of disabling it so we still
|
||||||
#
|
# have vertex groups in the evaluated mesh.
|
||||||
# NOTE: We put armature to the rest pose instead of disabling it so we still
|
if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types:
|
||||||
# have vertex groups in the evaluated mesh.
|
object = mod.object
|
||||||
if mod.type == 'ARMATURE' and 'ARMATURE' in settings.object_types:
|
if object and object.type == 'ARMATURE':
|
||||||
object = mod.object
|
armature = object.data
|
||||||
if object and object.type == 'ARMATURE':
|
# If armature is already in REST position, there's nothing to back-up
|
||||||
armature = object.data
|
# This cuts down on export time dramatically, if all armatures are already in REST position
|
||||||
# If armature is already in REST position, there's nothing to back-up
|
# by not triggering dependency graph update
|
||||||
# This cuts down on export time dramatically, if all armatures are already in REST position
|
if armature.pose_position != 'REST':
|
||||||
# by not triggering dependency graph update
|
backup_pose_positions.append((armature, armature.pose_position))
|
||||||
if armature.pose_position != 'REST':
|
armature.pose_position = 'REST'
|
||||||
backup_pose_positions.append((armature, armature.pose_position))
|
elif mod.show_render or mod.show_viewport:
|
||||||
armature.pose_position = 'REST'
|
# If exporting with subsurf collect the last Catmull-Clark subsurf modifier
|
||||||
elif mod.show_render or mod.show_viewport:
|
# and disable it. We can use the original data as long as this is the first
|
||||||
# If exporting with subsurf collect the last Catmull-Clark subsurf modifier
|
# found applicable subsurf modifier.
|
||||||
# and disable it. We can use the original data as long as this is the first
|
if settings.use_subsurf and mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
|
||||||
# found applicable subsurf modifier.
|
if last_subsurf:
|
||||||
if settings.use_subsurf and mod.type == 'SUBSURF' and mod.subdivision_type == 'CATMULL_CLARK':
|
do_evaluate = True
|
||||||
if last_subsurf:
|
last_subsurf = mod
|
||||||
use_org_data = False
|
else:
|
||||||
last_subsurf = mod
|
do_evaluate = True
|
||||||
else:
|
if settings.use_subsurf and last_subsurf:
|
||||||
use_org_data = False
|
# XXX: When exporting with subsurf information temporarily disable
|
||||||
if settings.use_subsurf and last_subsurf:
|
# the last subsurf modifier.
|
||||||
# XXX: When exporting with subsurf information temporarily disable
|
tmp_mods.append((last_subsurf, last_subsurf.show_render, last_subsurf.show_viewport))
|
||||||
# the last subsurf modifier.
|
|
||||||
tmp_mods.append((last_subsurf, last_subsurf.show_render, last_subsurf.show_viewport))
|
if do_evaluate:
|
||||||
last_subsurf.show_render = False
|
# If modifiers has been altered need to update dependency graph.
|
||||||
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()
|
|
||||||
ob_to_convert = ob.evaluated_get(depsgraph) if settings.use_mesh_modifiers else ob
|
|
||||||
# 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)
|
|
||||||
# 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()
|
|
||||||
# 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
|
|
||||||
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 removes shape keys (see #104714), so create a 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:
|
||||||
|
Loading…
Reference in New Issue
Block a user