WIP: FBX IO: Speed up shape key access using pointers #105126

Closed
Thomas Barlow wants to merge 3 commits from Mysteryem:fbx_shape_key_pointer_access into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
Showing only changes of commit 18c32f693f - Show all commits

View File

@ -713,6 +713,8 @@ def _fast_mesh_shape_key_co_check():
# The check has already been run and the result has been stored in _USE_FAST_SHAPE_KEY_CO_FOREACH_GETSET. # The check has already been run and the result has been stored in _USE_FAST_SHAPE_KEY_CO_FOREACH_GETSET.
return _USE_FAST_SHAPE_KEY_CO_FOREACH_GETSET return _USE_FAST_SHAPE_KEY_CO_FOREACH_GETSET
# Check that accessing a shape key's data through its pointer works, by creating a temporary mesh and adding a shape
# key to it.
tmp_mesh = None tmp_mesh = None
tmp_object = None tmp_object = None
try: try:
@ -737,14 +739,15 @@ def _fast_mesh_shape_key_co_check():
# The check is this function, so explicitly don't do the check. # The check is this function, so explicitly don't do the check.
co_memory_as_array = _shape_key_co_memory_as_ndarray(shape_key, do_check=False) co_memory_as_array = _shape_key_co_memory_as_ndarray(shape_key, do_check=False)
if co_memory_as_array is not None: if co_memory_as_array is not None:
# Immediately make a copy in case the `foreach_get` afterwards can cause the memory to be reallocated. # Immediately make a copy in the unlikely case the `foreach_get` call afterward can cause the memory to be
# reallocated.
co_array_from_memory = co_memory_as_array.copy() co_array_from_memory = co_memory_as_array.copy()
del co_memory_as_array del co_memory_as_array
# Check that the array created from the pointer has the exact same contents # Check that the array created from the pointer has the exact same contents
# as using foreach_get. # as using foreach_get.
co_array_check = np.empty(num_co * 3, dtype=co_dtype) co_array_from_foreach_get = np.empty(num_co * 3, dtype=co_dtype)
shape_data.foreach_get("co", co_array_check) shape_data.foreach_get("co", co_array_from_foreach_get)
if np.array_equal(co_array_check, co_array_from_memory, equal_nan=True): if np.array_equal(co_array_from_foreach_get, co_array_from_memory, equal_nan=True):
_USE_FAST_SHAPE_KEY_CO_FOREACH_GETSET = True _USE_FAST_SHAPE_KEY_CO_FOREACH_GETSET = True
return True return True
@ -761,6 +764,9 @@ def _fast_mesh_shape_key_co_check():
def fast_mesh_shape_key_co_foreach_get(shape_key, seq): def fast_mesh_shape_key_co_foreach_get(shape_key, seq):
"""
Replacement for ShapeKey.data.foreach_get that accesses the shape key data's memory directly if possible.
"""
co_memory_as_array = _shape_key_co_memory_as_ndarray(shape_key) co_memory_as_array = _shape_key_co_memory_as_ndarray(shape_key)
if co_memory_as_array is not None: if co_memory_as_array is not None:
seq[:] = co_memory_as_array seq[:] = co_memory_as_array
@ -770,9 +776,10 @@ def fast_mesh_shape_key_co_foreach_get(shape_key, seq):
def fast_mesh_shape_key_dvcos_foreach_set(new_shape_key, dvcos, indices, mesh_positions_fallback_vector_view): def fast_mesh_shape_key_dvcos_foreach_set(new_shape_key, dvcos, indices, mesh_positions_fallback_vector_view):
""" """
FBX Shape key data are sparse vectors relative to the mesh, unlike Blender shape keys which are coordinates. Apply sparse FBX shape key vectors to a newly created shape key.
The newly created shape keys must have been created with `from_mix=False`, so that they match the mesh positions. The newly created shape key must have been created with `from_mix=False`, so that its coordinates match the mesh
positions.
""" """
co_memory_as_array = _shape_key_co_memory_as_ndarray(new_shape_key) co_memory_as_array = _shape_key_co_memory_as_ndarray(new_shape_key)
if co_memory_as_array is not None: if co_memory_as_array is not None:
@ -782,6 +789,8 @@ def fast_mesh_shape_key_dvcos_foreach_set(new_shape_key, dvcos, indices, mesh_po
# Memory has been set directly, so call .update(). # Memory has been set directly, so call .update().
new_shape_key.data.update() new_shape_key.data.update()
else: else:
# As a fallback, copy the mesh positions, apply the sparse vectors to that copy and then set all the shape key
# coordinates to the modified copy of the mesh positions.
shape_cos = mesh_positions_fallback_vector_view.copy() shape_cos = mesh_positions_fallback_vector_view.copy()
shape_cos[indices] += dvcos shape_cos[indices] += dvcos
new_shape_key.data.foreach_set("co", shape_cos.ravel()) new_shape_key.data.foreach_set("co", shape_cos.ravel())