From 9ffc56b3b7fbc1b76ff50d32ab791d27f28b2d2b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 18 Aug 2021 13:46:45 +0300 Subject: 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. --- rigify/rigs/skin/skin_nodes.py | 80 +++++++++++++++----------------------- rigify/rigs/skin/skin_parents.py | 63 ++++++++++++++++++++++++++++-- rigify/rigs/skin/stretchy_chain.py | 1 - 3 files changed, 91 insertions(+), 53 deletions(-) diff --git a/rigify/rigs/skin/skin_nodes.py b/rigify/rigs/skin/skin_nodes.py index 9fddb168..7d3f65eb 100644 --- a/rigify/rigs/skin/skin_nodes.py +++ b/rigify/rigs/skin/skin_nodes.py @@ -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 - self.use_weak_parent = False - self.node_parent_list_weak = parent_list + parent_list = [ControlBoneWeakParentLayer.strip(p) for p in weak_parent_list] - self.node_parent_list = [ControlBoneWeakParentLayer.strip(p) for p in parent_list] + self.use_weak_parent = False 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)) + + 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 parent in self.node_parent_list: - self.register_use_parent(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: diff --git a/rigify/rigs/skin/skin_parents.py b/rigify/rigs/skin/skin_parents.py index 0cfaec36..956c5ca3 100644 --- a/rigify/rigs/skin/skin_parents.py +++ b/rigify/rigs/skin/skin_parents.py @@ -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): diff --git a/rigify/rigs/skin/stretchy_chain.py b/rigify/rigs/skin/stretchy_chain.py index ac3d7784..c6fae592 100644 --- a/rigify/rigs/skin/stretchy_chain.py +++ b/rigify/rigs/skin/stretchy_chain.py @@ -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 ( -- cgit v1.2.3