Refactor: Anim, keyframe paste: add unittest for bone name flipping func

Add a unit test for the bone name flipping function of the keyframe
paste functionality.

The function itself has been changed a little bit, so that it returns a
`std::string` instead of using a `char **` return parameter that needs
explicit freeing at the call site. This made the tests considerably
simpler to write. This change does cause an extra copy of the RNA path,
but that should be refactored away soon.

No functional changes.

Pull Request: https://projects.blender.org/blender/blender/pulls/133005
This commit is contained in:
Sybren A. Stüvel 2025-01-13 15:34:48 +01:00
parent bdc420037d
commit ae84e23110
3 changed files with 39 additions and 13 deletions

View file

@ -1364,24 +1364,25 @@ short copy_animedit_keys(bAnimContext *ac, ListBase *anim_data)
return 0;
}
static void flip_names(tAnimCopybufItem *aci, char **r_name)
std::optional<std::string> flip_names(const tAnimCopybufItem *aci)
{
if (!aci->is_bone) {
return;
return {};
}
int ofs_start, ofs_end;
if (!BLI_str_quoted_substr_range(aci->rna_path, "pose.bones[", &ofs_start, &ofs_end)) {
return;
return {};
}
char *str_start = aci->rna_path + ofs_start;
/* Cast away the constness; we're going to modify data, but it'll be put back
* before the function ends. */
char *str_start = const_cast<tAnimCopybufItem *>(aci)->rna_path + ofs_start;
const char *str_end = aci->rna_path + ofs_end;
/* Swap out the name.
* NOTE: there is no need to un-escape the string to flip it.
* However the buffer does need to be twice the size. */
char bname_new[MAX_VGROUP_NAME * 2];
char *str_iter;
int len_old, prefix_l, postfix_l;
prefix_l = str_start - aci->rna_path;
@ -1395,8 +1396,9 @@ static void flip_names(tAnimCopybufItem *aci, char **r_name)
const int len_new = BLI_string_flip_side_name(bname_new, str_start, false, sizeof(bname_new));
str_start[len_old] = '\"';
str_iter = *r_name = static_cast<char *>(
char *rna_path_flipped_charptr = static_cast<char *>(
MEM_mallocN(sizeof(char) * (prefix_l + postfix_l + len_new + 1), "flipped_path"));
char *str_iter = rna_path_flipped_charptr;
memcpy(str_iter, aci->rna_path, prefix_l);
str_iter += prefix_l;
@ -1404,6 +1406,12 @@ static void flip_names(tAnimCopybufItem *aci, char **r_name)
str_iter += len_new;
memcpy(str_iter, str_end, postfix_l);
str_iter[postfix_l] = '\0';
const std::string rna_path_flipped(rna_path_flipped_charptr);
MEM_freeN(rna_path_flipped_charptr);
return rna_path_flipped;
}
/* ------------------- */
@ -1443,13 +1451,8 @@ bool pastebuf_match_path_full(Main * /*bmain*/,
if (to_simple || (aci->rna_path && fcu->rna_path)) {
if (!to_simple && flip && aci->is_bone && fcu->rna_path) {
if ((from_single) || (aci->array_index == fcu->array_index)) {
char *name = nullptr;
flip_names(const_cast<tAnimCopybufItem *>(aci), &name);
if (STREQ(name, fcu->rna_path)) {
MEM_freeN(name);
return true;
}
MEM_freeN(name);
const std::optional<std::string> with_flipped_name = flip_names(aci);
return with_flipped_name && with_flipped_name == fcu->rna_path;
}
}
else if (to_simple || STREQ(aci->rna_path, fcu->rna_path)) {

View file

@ -34,6 +34,14 @@ struct tAnimCopybufItem {
void tAnimCopybufItem_free(tAnimCopybufItem *aci);
/**
* Flip bone names in the RNA path, returning the flipped path.
*
* Returns empty optional if the aci is not a bone (is_bone=false or RNA path
* has an unexpected prefix).
*/
std::optional<std::string> flip_names(const tAnimCopybufItem *aci);
/**
* Most strict paste buffer matching method: exact matches only.
*/

View file

@ -82,6 +82,21 @@ CopyBufItemPtr fake_aci_owned(const char *rna_path,
} // namespace
TEST(keyframes_paste, flip_names)
{
EXPECT_EQ(std::nullopt, flip_names(fake_aci("whatever", 1, false).get())) << "is_bone is false";
EXPECT_EQ(std::nullopt, flip_names(fake_aci("whatever", 1, true).get())) << "not a bone prefix";
EXPECT_EQ("pose.bones[\"head\"]", flip_names(fake_aci("pose.bones[\"head\"]", 1, true).get()))
<< "unflippable name should remain unchanged";
EXPECT_EQ("pose.bones[\"Arm_L\"]", flip_names(fake_aci("pose.bones[\"Arm_R\"]", 1, true).get()))
<< "flippable name should be flipped";
EXPECT_EQ("pose.bones[\"Arm_L\"].rotation_euler",
flip_names(fake_aci("pose.bones[\"Arm_R\"].rotation_euler", 1, true).get()))
<< "flippable name should be flipped";
}
TEST(keyframes_paste, pastebuf_match_path_full)
{
{ /* NULL RNA paths. */