Fix T78193 (Rigify): use bone history tracking to find derived DEF bones.

It is not really safe to assume that by swapping ORG to DEF you will
get a deform bone derived from the given ORG bone. The new base rig
API already tracks copying of bones, so polish it up and use here.

Note however that this tracking doesn't work with bones created
without self.copy_bone, e.g. by legacy rigs.
This commit is contained in:
Alexander Gavrilov 2020-06-29 20:19:52 +03:00
parent db23ff3d34
commit da8877fa8b
Notes: blender-bot 2023-02-14 18:53:22 +01:00
Referenced by issue #78193, Rigify: Strange Palm behavior when parent's Rig Type is set manually
5 changed files with 67 additions and 13 deletions

View File

@ -62,6 +62,8 @@ class GeneratorPlugin(base_rig.GenerateCallbackHost, metaclass=SingletonPluginMe
def register_new_bone(self, new_name, old_name=None):
self.generator.bone_owners[new_name] = None
if old_name:
self.generator.derived_bones[old_name].add(new_name)
#=============================================
@ -201,6 +203,7 @@ class BaseGenerator:
self.root_rigs = []
# Map from bone names to their rigs
self.bone_owners = {}
self.derived_bones = collections.defaultdict(set)
# Set of plugins
self.plugin_list = []
@ -228,6 +231,32 @@ class BaseGenerator:
self.noparent_bones.add(bone_name)
def find_derived_bones(self, bone_name, *, by_owner=False, recursive=True):
"""Find which bones were copied from the specified one."""
if by_owner:
owner = self.bone_owners.get(bone_name, None)
if not owner:
return {}
table = owner.rigify_derived_bones
else:
table = self.derived_bones
if recursive:
result = set()
def rec(name):
for child in table.get(name, {}):
result.add(child)
rec(child)
rec(bone_name)
return result
else:
return set(table.get(bone_name, {}))
def set_layer_group_priority(self, bone_name, layers, priority):
for i, val in enumerate(layers):
if val:

View File

@ -18,6 +18,8 @@
# <pep8 compliant>
import collections
from .utils.errors import RaiseErrorMixin
from .utils.bones import BoneDict, BoneUtilityMixin
from .utils.mechanism import MechanismUtilityMixin
@ -190,11 +192,15 @@ class BaseRig(GenerateCallbackHost, RaiseErrorMixin, BoneUtilityMixin, Mechanism
self.rigify_child_bones = set()
# Bones created by the rig (mapped to original names)
self.rigify_new_bones = dict()
self.rigify_derived_bones = collections.defaultdict(set)
def register_new_bone(self, new_name, old_name=None):
"""Registers this rig as the owner of this new bone."""
self.rigify_new_bones[new_name] = old_name
self.generator.bone_owners[new_name] = self
if old_name:
self.rigify_derived_bones[old_name].add(new_name)
self.generator.derived_bones[old_name].add(new_name)
###########################################################
# Bone ownership

View File

@ -20,7 +20,7 @@
import bpy
from ...utils.naming import strip_org, strip_prefix
from ...utils.naming import strip_org, strip_prefix, choose_derived_bone
from ...base_rig import BaseRig
from ...base_generate import SubstitutionRig
@ -83,15 +83,17 @@ class RelinkConstraintsMixin:
def find_relink_target(self, spec, old_target):
if spec == '':
return old_target
elif spec == 'CTRL':
spec = strip_prefix(old_target)
elif spec in {'DEF', 'MCH'}:
spec = spec + '-' + strip_prefix(old_target)
if spec not in self.obj.pose.bones:
self.report_error("Cannot find bone '{}' for relinking", spec)
return spec
elif spec in {'CTRL', 'DEF', 'MCH'}:
result = choose_derived_bone(self.generator, old_target, spec.lower())
if not result:
result = choose_derived_bone(self.generator, old_target, spec.lower(), by_owner=False)
if not result:
self.report_error("Cannot find derived {} bone of bone '{}' for relinking", spec, old_target)
return result
else:
if spec not in self.obj.pose.bones:
self.report_error("Cannot find bone '{}' for relinking", spec)
return spec
@classmethod

View File

@ -25,7 +25,7 @@ from math import cos, pi
from itertools import count, repeat
from rigify.utils.rig import is_rig_base_bone
from rigify.utils.naming import strip_org, make_derived_name
from rigify.utils.naming import strip_org, make_derived_name, choose_derived_bone
from rigify.utils.widgets import create_widget
from rigify.utils.misc import map_list
@ -81,9 +81,9 @@ class Rig(BaseRig):
self.rig_parent_bone = self.get_bone_parent(self.bones.org[0])
# Parent to the deform bone of the parent if exists
def_bone = make_derived_name(self.rig_parent_bone, 'def')
def_bone = choose_derived_bone(self.generator, self.rig_parent_bone, 'def')
if def_bone in self.obj.data.bones:
if def_bone:
self.rig_parent_bone = def_bone
####################################################

View File

@ -307,3 +307,20 @@ def random_id(length=8):
text += random.choice(chars)
text += str(hex(int(time.time())))[2:][-tlength:].rjust(tlength, '0')[::-1]
return text
def choose_derived_bone(generator, original, subtype, *, by_owner=True, recursive=True):
bones = generator.obj.pose.bones
names = generator.find_derived_bones(original, by_owner=by_owner, recursive=recursive)
direct = make_derived_name(original, subtype)
if direct in names and direct in bones:
return direct
prefix = _PREFIX_TABLE[subtype] + '-'
matching = [ name for name in names if name.startswith(prefix) and name in bones ]
if len(matching) > 0:
return matching[0]
return None