Shape Key editing: propagate updates through basis chains #105422

Closed
Alexander Gavrilov wants to merge 1 commits from angavrilov:pr-shapekey-propagate into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
5 changed files with 72 additions and 19 deletions

View File

@ -182,6 +182,12 @@ bool BKE_keyblock_move(struct Object *ob, int org_index, int new_index);
*/
bool BKE_keyblock_is_basis(const struct Key *key, int index);
/**
angavrilov marked this conversation as resolved Outdated

an array -> an allocated array

Would just help to write down that it's supposed to be freed

`an array` -> `an allocated array` Would just help to write down that it's supposed to be freed
* Returns a newly allocated array containing true for every key that has this one as basis.
* If none are found, returns null.
*/
bool *BKE_keyblock_get_dependent_keys(const struct Key *key, int index);
/* -------------------------------------------------------------------- */
/** \name Key-Block Data Access
* \{ */

View File

@ -2598,3 +2598,46 @@ bool BKE_keyblock_is_basis(const Key *key, const int index)
return false;
angavrilov marked this conversation as resolved Outdated

I don't think this Simple checks. comment is adding anything here. Such early return condition checking is not exactly out of the ordinary, and the fact that they're "simple" is already obvious from looking at the code.

I don't think this `Simple checks.` comment is adding anything here. Such early return condition checking is not exactly out of the ordinary, and the fact that they're "simple" is already obvious from looking at the code.
}
bool *BKE_keyblock_get_dependent_keys(const Key *key, const int index)
{
if (key->type != KEY_RELATIVE) {
return nullptr;
}
const int count = BLI_listbase_count(&key->block);
if (index < 0 || index >= count) {
return nullptr;

This is a C++ file, might as well use Array or BitVector here

This is a C++ file, might as well use `Array` or `BitVector` here

It's returned to the function caller, which still includes C code.

It's returned to the function caller, which still includes C code.
}
/* Seed the table with the specified key. */
bool *marked = static_cast<bool *>(MEM_callocN(sizeof(bool) * count, __func__));
marked[index] = true;
/* Iterative breadth-first search through the key list. This method minimizes
* the number of scans through the list and is failsafe vs reference cycles. */
bool updated, found = false;
int i;
do {
angavrilov marked this conversation as resolved Outdated

Use the LISTBASE_FOREACH_INDEX macro

Use the `LISTBASE_FOREACH_INDEX` macro
updated = false;
LISTBASE_FOREACH_INDEX (const KeyBlock *, kb, &key->block, i) {
if (!marked[i] && kb->relative >= 0 && kb->relative < count && marked[kb->relative]) {
marked[i] = true;
updated = found = true;
}
}
} while (updated);
if (!found) {
MEM_freeN(marked);
return nullptr;
angavrilov marked this conversation as resolved Outdated

else after return

else after return
}
/* After the search is complete, exclude the original key. */
marked[index] = false;
return marked;
}

View File

@ -795,6 +795,7 @@ static void bm_to_mesh_shape(BMesh *bm,
BMIter iter;
BMVert *eve;
float(*ofs)[3] = nullptr;
bool *dependent = nullptr;
/* Editing the basis key updates others. */
if ((key->type == KEY_RELATIVE) &&
@ -803,7 +804,7 @@ static void bm_to_mesh_shape(BMesh *bm,
/* Original key-indices are only used to check the vertex existed when entering edit-mode. */
(cd_shape_keyindex_offset != -1) &&
/* Offsets are only needed if the current shape is a basis for others. */
BKE_keyblock_is_basis(key, bm->shapenr - 1))
(dependent = BKE_keyblock_get_dependent_keys(key, bm->shapenr - 1)) != nullptr)
{
BLI_assert(actkey != nullptr); /* Assured by `actkey_has_layer` check. */
@ -830,6 +831,8 @@ static void bm_to_mesh_shape(BMesh *bm,
* ones, creating a mess when doing e.g. subdivide + translate. */
MEM_freeN(ofs);
ofs = nullptr;
MEM_freeN(dependent);
dependent = nullptr;
break;
}
}
@ -856,7 +859,8 @@ static void bm_to_mesh_shape(BMesh *bm,
}
}
LISTBASE_FOREACH (KeyBlock *, currkey, &key->block) {
int currkey_i;
LISTBASE_FOREACH_INDEX (KeyBlock *, currkey, &key->block, currkey_i) {
int keyi;
float(*currkey_data)[3];
@ -867,8 +871,7 @@ static void bm_to_mesh_shape(BMesh *bm,
/* Common case, the layer data is available, use it where possible. */
if (cd_shape_offset != -1) {
const bool apply_offset = (ofs != nullptr) && (currkey != actkey) &&
(bm->shapenr - 1 == currkey->relative);
const bool apply_offset = (ofs != nullptr) && (currkey != actkey) && dependent[currkey_i];
if (currkey->data && (currkey->totelem == bm->totvert)) {
/* Use memory in-place. */
@ -956,9 +959,8 @@ static void bm_to_mesh_shape(BMesh *bm,
}
}
if (ofs) {
MEM_freeN(ofs);
}
MEM_SAFE_FREE(ofs);
MEM_SAFE_FREE(dependent);
}
/** \} */

View File

@ -631,7 +631,7 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
return;
}
int a, i;
int a, i, currkey_i;
EditNurb *editnurb = cu->editnurb;
KeyBlock *actkey = BLI_findlink(&cu->key->block, editnurb->shapenr - 1);
BezTriple *bezt, *oldbezt;
@ -640,11 +640,14 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
int totvert = BKE_keyblock_curve_element_count(&editnurb->nurbs);
float(*ofs)[3] = NULL;
bool *dependent = NULL;
float *oldkey, *newkey, *ofp;
/* editing the base key should update others */
if (cu->key->type == KEY_RELATIVE) {
if (BKE_keyblock_is_basis(cu->key, editnurb->shapenr - 1)) { /* active key is a base */
dependent = BKE_keyblock_get_dependent_keys(cu->key, editnurb->shapenr - 1);
if (dependent) { /* active key is a base */
int totvec = 0;
/* Calculate needed memory to store offset */
@ -702,9 +705,8 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
}
}
LISTBASE_FOREACH (KeyBlock *, currkey, &cu->key->block) {
const bool apply_offset = (ofs && (currkey != actkey) &&
(editnurb->shapenr - 1 == currkey->relative));
LISTBASE_FOREACH_INDEX (KeyBlock *, currkey, &cu->key->block, currkey_i) {
const bool apply_offset = (ofs && (currkey != actkey) && dependent[currkey_i]);
float *fp = newkey = MEM_callocN(cu->key->elemsize * totvert, "currkey->data");
ofp = oldkey = currkey->data;
@ -866,9 +868,8 @@ static void calc_shapeKeys(Object *obedit, ListBase *newnurbs)
currkey->data = newkey;
}
if (ofs) {
MEM_freeN(ofs);
}
MEM_SAFE_FREE(ofs);
MEM_SAFE_FREE(dependent);
}
/** \} */

View File

@ -3406,11 +3406,11 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3])
{
Mesh *me = (Mesh *)ob->data;
float(*ofs)[3] = nullptr;
int a;
int a, currkey_i;
const int kb_act_idx = ob->shapenr - 1;
/* For relative keys editing of base should update other keys. */
if (BKE_keyblock_is_basis(me->key, kb_act_idx)) {
if (bool *dependent = BKE_keyblock_get_dependent_keys(me->key, kb_act_idx)) {
ofs = BKE_keyblock_convert_to_vertcos(ob, kb);
/* Calculate key coord offsets (from previous location). */
@ -3419,13 +3419,14 @@ void SCULPT_vertcos_to_key(Object *ob, KeyBlock *kb, const float (*vertCos)[3])
}
/* Apply offsets on other keys. */
LISTBASE_FOREACH (KeyBlock *, currkey, &me->key->block) {
if ((currkey != kb) && (currkey->relative == kb_act_idx)) {
LISTBASE_FOREACH_INDEX (KeyBlock *, currkey, &me->key->block, currkey_i) {
if ((currkey != kb) && dependent[currkey_i]) {
BKE_keyblock_update_from_offset(ob, currkey, ofs);
}
}
MEM_freeN(ofs);
MEM_freeN(dependent);
}
/* Modifying of basis key should update mesh. */