Rigify: extract skin parent mechanism mixing into a generator class.

This allows cleanly avoiding reparent propagation between mirror
siblings, which causes weird deformation in chains.
This commit is contained in:
Alexander Gavrilov 2021-08-18 13:46:45 +03:00
parent 8aa317ee13
commit 9ffc56b3b7
3 changed files with 91 additions and 53 deletions

View File

@ -32,7 +32,7 @@ from ...utils.rig import get_parent_rigs
from ...utils.node_merger import MainMergeNode, QueryMergeNode
from .skin_parents import ControlBoneParentLayer, ControlBoneWeakParentLayer
from .skin_parents import ControlBoneParentLayer, ControlBoneWeakParentLayer, ControlBoneParentMix
from .skin_rigs import BaseSkinRig, BaseSkinChainRig
@ -253,6 +253,8 @@ class ControlBoneNode(MainMergeNode, BaseSkinNode):
if isinstance(parent, ControlBoneParentLayer):
parent.parent = self.intern_parent(node, parent.parent)
elif isinstance(parent, ControlBoneParentMix):
parent.parents = [self.intern_parent(node, p) for p in parent.parents]
return parent
@ -267,11 +269,9 @@ class ControlBoneNode(MainMergeNode, BaseSkinNode):
if parent not in requests:
# If the actual reparent would be generated, weak parent will be needed.
if self.has_weak_parent and not self.use_weak_parent:
if self.use_mix_parent or parent != self.node_parent:
if parent is not self.node_parent:
self.use_weak_parent = True
for weak_parent in self.node_parent_list_weak:
self.register_use_parent(weak_parent)
self.register_use_parent(self.node_parent_weak)
self.register_use_parent(parent)
requests.append(parent)
@ -308,24 +308,34 @@ class ControlBoneNode(MainMergeNode, BaseSkinNode):
self.matrix.translation = self.point
# Create parents and decide if mix would be needed
parent_list = [node.build_parent(use=False) for node in mirror_sibling_list]
weak_parent_list = [node.build_parent(use=False) for node in mirror_sibling_list]
if all(parent == self.node_parent for parent in parent_list):
self.use_mix_parent = False
parent_list = [self.node_parent]
if all(parent == self.node_parent for parent in weak_parent_list):
weak_parent_list = [self.node_parent]
self.node_parent_weak = self.node_parent
else:
self.use_mix_parent = True
self.node_parent_weak = ControlBoneParentMix(self.rig, self, weak_parent_list)
# Prepare parenting without weak layers
parent_list = [ControlBoneWeakParentLayer.strip(p) for p in weak_parent_list]
self.use_weak_parent = False
self.node_parent_list_weak = parent_list
self.node_parent_list = [ControlBoneWeakParentLayer.strip(p) for p in parent_list]
self.has_weak_parent = any((p is not pw)
for p, pw in zip(self.node_parent_list, parent_list))
for p, pw in zip(weak_parent_list, parent_list))
for parent in self.node_parent_list:
self.register_use_parent(parent)
if not self.has_weak_parent:
self.node_parent = self.node_parent_weak
elif len(parent_list) > 1:
self.node_parent = ControlBoneParentMix(
self.rig, self, parent_list, suffix='_mix_base')
else:
self.node_parent = parent_list[0]
# Mirror siblings share the mixed parent for reparent
self.register_use_parent(self.node_parent)
for node in mirror_sibling_list:
node.node_parent = self.node_parent
# All nodes
if self.node_needs_parent or self.node_needs_reparent:
@ -392,16 +402,8 @@ class ControlBoneNode(MainMergeNode, BaseSkinNode):
self.weak_parent_bone = self.make_bone(
make_derived_name(self._control_bone, 'mch', '_weak_parent'), 1/2)
# Make mix parent if needed
self.reparent_bones = {}
if self.use_mix_parent:
self.mix_parent_bone = self.make_bone(
make_derived_name(self._control_bone, 'mch', '_mix_parent'), 1/2)
else:
self.reparent_bones[id(self.node_parent)] = self._control_bone
# Make requested reparents
self.reparent_bones = {id(self.node_parent): self._control_bone}
self.reparent_bones_fake = set(self.reparent_bones.values())
for parent in self.reparent_requests:
@ -421,21 +423,12 @@ class ControlBoneNode(MainMergeNode, BaseSkinNode):
def parent_bones(self):
if self.is_master_node:
if self.use_mix_parent:
self.set_bone_parent(self._control_bone, self.mix_parent_bone,
inherit_scale='AVERAGE')
self.rig.generator.disable_auto_parent(self.mix_parent_bone)
else:
self.set_bone_parent(self._control_bone, self.node_parent_list[0].output_bone,
inherit_scale='AVERAGE')
self.set_bone_parent(
self._control_bone, self.node_parent.output_bone, inherit_scale='AVERAGE')
if self.use_weak_parent:
if self.use_mix_parent:
self.rig.generator.disable_auto_parent(self.weak_parent_bone)
else:
parent = self.node_parent_list_weak[0]
self.set_bone_parent(self.weak_parent_bone, parent.output_bone,
inherit_scale=parent.inherit_scale_mode)
self.set_bone_parent(
self.weak_parent_bone, self.node_parent_weak.output_bone, inherit_scale='FULL')
for parent in self.reparent_requests:
bone = self.reparent_bones[id(parent)]
@ -454,12 +447,6 @@ class ControlBoneNode(MainMergeNode, BaseSkinNode):
def rig_bones(self):
if self.is_master_node:
# Rig the mixed parent
if self.use_mix_parent:
targets = [parent.output_bone for parent in self.node_parent_list]
self.make_constraint(self.mix_parent_bone, 'ARMATURE',
targets=targets, use_deform_preserve_volume=True)
# Invoke parent rig callbacks
for rig in reversed(self.rig.get_all_parent_skin_rigs()):
rig.extend_control_node_rig(self)
@ -473,11 +460,6 @@ class ControlBoneNode(MainMergeNode, BaseSkinNode):
self.make_constraint(reparent_source, 'COPY_TRANSFORMS',
self.control_bone, space='LOCAL')
if self.use_mix_parent:
targets = [parent.output_bone for parent in self.node_parent_list_weak]
self.make_constraint(self.weak_parent_bone, 'ARMATURE',
targets=targets, use_deform_preserve_volume=True)
set_bone_widget_transform(self.obj, self.control_bone, reparent_source)
for parent in self.reparent_requests:

View File

@ -145,6 +145,66 @@ class ControlBoneParentArmature(ControlBoneParentBase):
self.make_constraint(self.output_bone, 'COPY_SCALE', self.copy_scale)
class ControlBoneParentMix(ControlBoneParentBase):
"""Combine multiple parent mechanisms using the Armature constraint."""
def __init__(self, rig, node, parents, *, suffix=None):
super().__init__(rig, node)
self.parents = []
self.parent_weights = []
self.suffix = suffix
self.add_parents(parents)
def add_parents(self, parents):
for item in parents:
if isinstance(item, tuple):
parent, weight = item
else:
parent, weight = item, 1
for i, cur in enumerate(self.parents):
if parent == cur:
self.parent_weights[i] += weight
break
else:
self.parents.append(parent)
self.parent_weights.append(weight)
def enable_component(self):
for parent in self.parents:
parent.enable_component()
super().enable_component()
def __eq__(self, other):
return (
isinstance(other, ControlBoneParentMix) and
self.parents == other.parents and
self.parent_weights == other.parent_weights
)
def generate_bones(self):
self.output_bone = self.node.make_bone(
make_derived_name(self.node.name, 'mch', self.suffix or '_mix'), 1/2, rig=self.rig)
self.rig.generator.disable_auto_parent(self.output_bone)
def parent_bones(self):
if len(self.parents) == 1:
self.set_bone_parent(self.output_bone, target)
def rig_bones(self):
if len(self.parents) > 1:
targets = [(p.output_bone, w) for p, w in zip(self.parents, self.parent_weights)]
self.make_constraint(
self.output_bone, 'ARMATURE', targets=targets,
use_deform_preserve_volume=True
)
class ControlBoneParentLayer(ControlBoneParentBase):
"""Base class for parent generators that build on top of another mechanism."""
@ -164,9 +224,6 @@ class ControlBoneWeakParentLayer(ControlBoneParentLayer):
that have controls merged into this one.
"""
# Inherit mode used to parent the pseudo-control to the output of this generator.
inherit_scale_mode = 'AVERAGE'
@staticmethod
def strip(parent):
while isinstance(parent, ControlBoneWeakParentLayer):

View File

@ -394,7 +394,6 @@ class ControlBoneChainPropagate(ControlBoneWeakParentLayer):
Parent mechanism generator that propagates chain twist/scale
to the reparent system, if Propagate To Controls is used.
"""
inherit_scale_mode = 'FULL'
def __eq__(self, other):
return (