Page MenuHome

Fix T50393: Flipping bone names error
AbandonedPublic

Authored by Dalai Felinto (dfelinto) on Jan 9 2017, 12:10 PM.

Details

Summary

What is happening is that each bone needs a unique name at all times.
The "Flip Names" operator works one bone at a time, which in your case
generates a conflict during the renaming.

This patch handles that by temporarily adding a suffix whenever a
conflict would happen, and then stripping back the suffices.

Diff Detail

Repository
rB Blender
Branch
fix-flip-names
Build Status
Buildable 388
Build 388: arc lint + arc unit

Event Timeline

Dalai Felinto (dfelinto) retitled this revision from to Fix T50393: Flipping bone names error.
Dalai Felinto (dfelinto) updated this object.
Bastien Montagne (mont29) requested changes to this revision.Jan 9 2017, 12:48 PM
Bastien Montagne (mont29) edited edge metadata.

Problem here is that your bone suffixing is totally unsecure. You may already have another bone named curr_bone_name_BR and, worse, that code will totally fail in case you have a bone name already filling the whole available length.

So would rather do it a bit differently, e.g. storing editbone pointer itself in the list you pass to ED_armature_bones_flip_names(), doing rename without any special action, but checking new bone's name, and if not same as expected one, storing both (editbone pointer and expected name) into your temp list to-be-processed-later.

source/blender/editors/armature/armature_naming.c
318

\<doxycommand>, not @<doxycommand> please ;)

325–326

same, no @ for doxygen commands

This revision now requires changes to proceed.Jan 9 2017, 12:48 PM

That won't work. The new bone's name always match, the bones that are affected are the "other" bones. And since the entire remapping is name based (e.g., BKE_pose_channel_find_name) I fail to see how we can prevent calling ED_armature_bone_rename twice for the problematic cases.

You do not avoid calling rename twice in case of collisions, rather, you accept as temp placeholder name the one that renames generates for you, which is guaranteed to be unique and safely generated. That what I meant in “storing both (editbone pointer and expected name) into your temp list to-be-processed-later.” (as second loop in ED_armature_bones_flip_names()).

Dalai Felinto (dfelinto) edited edge metadata.
  • From review: no doxygen
  • From review: different logic
Bastien Montagne (mont29) requested changes to this revision.Jan 9 2017, 2:49 PM
Bastien Montagne (mont29) edited edge metadata.
Bastien Montagne (mont29) added inline comments.
source/blender/editors/armature/armature_naming.c
302–310

sigh I did not say 'no doxygen', I said 'use the \<doxycommand> syntax, not the @<doxycommand> syntax'!

So \param, not @param, etc. (though the \brief one was indeed useless).

312–343

This can be done much more efficiently I think, no need for three loops, in first one you can:

  • generate flip side name,
  • rename,
  • check if rename produced non-expected result, and only in that case generate a BoneConflict struct (and copying into it the expected name).

Then second loop only runs over actual conflicts to perfom second rename.

This revision now requires changes to proceed.Jan 9 2017, 2:49 PM

Just so we don't loose what was talked on IRC:

The current logic of the code (in master) first makes run for the new name (renaming any existing bone that has the newname) and then assign this name to the bone. So we can't check if rename produced a non-expected result, since it will potentially rename another bone.

Perhaps I'm missing something, but since the original problem was that the user was trying to use Flip Names to swap the names of a pair of selected bones, wouldn't it make sense to just swap the names of a pair of matching bones (if both are selected)? All the other cases we could leave as-is for now.

IIRC, there's a function to find the "mirror" bone (which often just looks for a bone with the flipped name). The only complication is ensuring that pairs of bones don't get operated on twice.

wouldn't it make sense to just swap the names of a pair of matching bones

That would not assign the original pose chains, bone constraints, ... to the flipped bone.

Dalai Felinto (dfelinto) edited edge metadata.
  • From review: doxygen fix, and remove one of the loops

Ah yes, there's that problem.

Hmm... Here's an option for how we could still specially handle this 2-bone swapping case:

  • We still have a special case where we check if we have both sides selected (i.e. both the .L and the .R)
  • Instead of doing a straight string-swap (which would bypass the rename() func), we instead temporarily rename one of them to a string of characters which should be impossible for users to accidentally name their bones (crazy scripters out to break the software aside). So something containing heaps of special characters (like \n \r \x \b and friends). The important thing is that the swap happens on the L and R bones in one block (even it it is 3 calls to ED_armature_bone_rename()) within this special case.
  • Then the only thing we have to do is to prevent the whole process to get repeated again when the bone for the other side is encountered. (A simple GSet of pchan pointers, or a solution using a "BONE_TEMP_FLAG" + 2 loops -> 1 to clear flags, then the second loop does the stuff above)

code snippets from @Joshua Leung (aligorith)

/* GSet Option */
GSet<bPoseChannel *> gset = BLI_gset_new();

for bone in sel_bones:
    flipped_name = flip_name(bone);
    flipped_bone = pose->ghash.get(flipped_name);
    if flipped_bone:
        # don't do anything if we've seen either bone before
        if (bone in gset) || (flipped_bone in gset):
             pass
        else:
            # do name swapping
            bone_name1 = bone.name
            ED_armature_bone_rename(bone.name, "brnxTEMP");
            ED_armature_bone_rename(flipped_bone.name, bone_name1);
            ED_armature_bone_rename(bone.name, flipped_name);

            gset.insert(bone)
            gset.insert(flipped_bone)
     else:
         # do normal rename
         ED_armature_bone_rename(bone.name, flipped_name);



/*--------------------------------*/
/* Flag option */

for bone in sel_bones:
     bone->flag &= ~BONE_TEMP;

for bone in sel_bones:
    if bone->flag & BONE_TEMP:
        continue;
   
    flipped_name = flip_name(bone);
    flipped_bone = pose->ghash.get(flipped_name);
    if flipped_bone:
        # do name swapping
        bone_name1 = bone.name
        ED_armature_bone_rename(bone.name, "brnxTEMP");
        ED_armature_bone_rename(flipped_bone.name, bone_name1);
        ED_armature_bone_rename(bone.name, flipped_name);

        flipped_bone.flag |= BONE_TEMP;
     else:
         # do normal rename
         ED_armature_bone_rename(bone.name, flipped_name);

Afaics, committed code (in rB702bc5ba26d5) is giving expected results, while staying simple and quite efficient… So think we can close this now. ;)