Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'rigify/rigs/skin/skin_parents.py')
-rw-r--r--rigify/rigs/skin/skin_parents.py395
1 files changed, 395 insertions, 0 deletions
diff --git a/rigify/rigs/skin/skin_parents.py b/rigify/rigs/skin/skin_parents.py
new file mode 100644
index 00000000..0cfaec36
--- /dev/null
+++ b/rigify/rigs/skin/skin_parents.py
@@ -0,0 +1,395 @@
+# ====================== BEGIN GPL LICENSE BLOCK ======================
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ======================= END GPL LICENSE BLOCK ========================
+
+# <pep8 compliant>
+
+import bpy
+
+from itertools import count
+from string import Template
+
+from ...utils.naming import make_derived_name
+from ...utils.misc import force_lazy, LazyRef
+
+from ...base_rig import LazyRigComponent, stage
+
+
+class ControlBoneParentBase(LazyRigComponent):
+ """
+ Base class for components that generate parent mechanisms for skin controls.
+ The generated parent bone is accessible through the output_bone field or property.
+ """
+
+ # Run this component after the @stage methods of the owner node and its slave nodes
+ rigify_sub_object_run_late = True
+
+ # This generator's output bone cannot be modified by generators layered on top.
+ # Otherwise they may optimize bone count by adding more constraints in place.
+ # (This generally signals the bone is shared between multiple users.)
+ is_parent_frozen = False
+
+ def __init__(self, rig, node):
+ super().__init__(node)
+
+ # Rig that provides this parent mechanism.
+ self.rig = rig
+ # Control node that the mechanism is provided for
+ self.node = node
+
+ def __eq__(self, other):
+ raise NotImplementedError()
+
+
+class ControlBoneParentOrg:
+ """Control node parent generator wrapping a single ORG bone."""
+
+ is_parent_frozen = True
+
+ def __init__(self, org):
+ self._output_bone = org
+
+ @property
+ def output_bone(self):
+ return force_lazy(self._output_bone)
+
+ def enable_component(self):
+ pass
+
+ def __eq__(self, other):
+ return isinstance(other, ControlBoneParentOrg) and self._output_bone == other._output_bone
+
+
+class ControlBoneParentArmature(ControlBoneParentBase):
+ """Control node parent generator using the Armature constraint to parent the bone."""
+
+ def __init__(self, rig, node, *, bones, orientation=None, copy_scale=None, copy_rotation=None):
+ super().__init__(rig, node)
+
+ # List of Armature constraint target specs for make_constraint (lazy).
+ self.bones = bones
+ # Orientation quaternion for the bone (lazy)
+ self.orientation = orientation
+ # Bone to copy scale from (lazy)
+ self.copy_scale = copy_scale
+ # Bone to copy rotation from (lazy)
+ self.copy_rotation = copy_rotation
+
+ if copy_scale or copy_rotation:
+ self.is_parent_frozen = True
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, ControlBoneParentArmature) and
+ self.node.point == other.node.point and
+ self.orientation == other.orientation and
+ self.bones == other.bones and
+ self.copy_scale == other.copy_scale and
+ self.copy_rotation == other.copy_rotation
+ )
+
+ def generate_bones(self):
+ self.output_bone = self.node.make_bone(
+ make_derived_name(self.node.name, 'mch', '_arm'), 1/4, rig=self.rig)
+
+ self.rig.generator.disable_auto_parent(self.output_bone)
+
+ if self.orientation:
+ matrix = force_lazy(self.orientation).to_matrix().to_4x4()
+ matrix.translation = self.node.point
+ self.get_bone(self.output_bone).matrix = matrix
+
+ def parent_bones(self):
+ self.targets = force_lazy(self.bones)
+
+ assert len(self.targets) > 0
+
+ # Single target can be simplified to parenting
+ if len(self.targets) == 1:
+ target = force_lazy(self.targets[0])
+ if isinstance(target, tuple):
+ target = target[0]
+
+ self.set_bone_parent(
+ self.output_bone, target,
+ inherit_scale='NONE' if self.copy_scale else 'FIX_SHEAR'
+ )
+
+ def rig_bones(self):
+ # Multiple targets use the Armature constraint
+ if len(self.targets) > 1:
+ self.make_constraint(
+ self.output_bone, 'ARMATURE', targets=self.targets,
+ use_deform_preserve_volume=True
+ )
+
+ self.make_constraint(self.output_bone, 'LIMIT_ROTATION')
+
+ if self.copy_rotation:
+ self.make_constraint(self.output_bone, 'COPY_ROTATION', self.copy_rotation)
+ if self.copy_scale:
+ self.make_constraint(self.output_bone, 'COPY_SCALE', self.copy_scale)
+
+
+class ControlBoneParentLayer(ControlBoneParentBase):
+ """Base class for parent generators that build on top of another mechanism."""
+
+ def __init__(self, rig, node, parent):
+ super().__init__(rig, node)
+ self.parent = parent
+
+ def enable_component(self):
+ self.parent.enable_component()
+ super().enable_component()
+
+
+class ControlBoneWeakParentLayer(ControlBoneParentLayer):
+ """
+ Base class for layered parent generator that is only used for the reparent source.
+ I.e. it doesn't affect the control for its owner rig, but only for other rigs
+ 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):
+ parent = parent.parent
+
+ return parent
+
+
+class ControlBoneParentOffset(ControlBoneParentLayer):
+ """
+ Parent mechanism generator that offsets the control's location.
+
+ Supports Copy Transforms (Local) constraints and location drivers.
+ Multiple offsets can be accumulated in the same generator, which
+ will automatically create as many bones as needed.
+ """
+
+ @classmethod
+ def wrap(cls, owner, parent, node, *constructor_args):
+ return cls(owner, node, parent, *constructor_args)
+
+ def __init__(self, rig, node, parent):
+ super().__init__(rig, node, parent)
+ self.copy_local = {}
+ self.add_local = {}
+ self.add_orientations = {}
+ self.limit_distance = []
+
+ def enable_component(self):
+ # Automatically merge an unfrozen sequence of this generator instances
+ while isinstance(self.parent, ControlBoneParentOffset) and not self.parent.is_parent_frozen:
+ self.prepend_contents(self.parent)
+ self.parent = self.parent.parent
+
+ super().enable_component()
+
+ def prepend_contents(self, other):
+ """Merge all offsets stored in the other generator into the current one."""
+ for key, val in other.copy_local.items():
+ if key not in self.copy_local:
+ self.copy_local[key] = val
+ else:
+ inf, expr, cbs = val
+ inf0, expr0, cbs0 = self.copy_local[key]
+ self.copy_local[key] = [inf+inf0, expr+expr0, cbs+cbs0]
+
+ for key, val in other.add_orientations.items():
+ if key not in self.add_orientations:
+ self.add_orientations[key] = val
+
+ for key, val in other.add_local.items():
+ if key not in self.add_local:
+ self.add_local[key] = val
+ else:
+ ot0, ot1, ot2 = val
+ my0, my1, my2 = self.add_local[key]
+ self.add_local[key] = (ot0+my0, ot1+my1, ot2+my2)
+
+ self.limit_distance = other.limit_distance + self.limit_distance
+
+ def add_copy_local_location(self, target, *, influence=1, influence_expr=None, influence_vars={}):
+ """
+ Add a Copy Location (Local, Owner Orientation) offset.
+ The influence may be specified as a (lazy) constant, or a driver expression
+ with variables (using the same $var syntax as add_location_driver).
+ """
+ if target not in self.copy_local:
+ self.copy_local[target] = [0, [], []]
+
+ if influence_expr:
+ self.copy_local[target][1].append((influence_expr, influence_vars))
+ elif callable(influence):
+ self.copy_local[target][2].append(influence)
+ else:
+ self.copy_local[target][0] += influence
+
+ def add_location_driver(self, orientation, index, expression, variables):
+ """
+ Add a driver offsetting along the specified axis in the given Quaternion orientation.
+ The variables may have to be renamed due to conflicts between multiple add requests,
+ so the expression should use the $var syntax of Template to reference them.
+ """
+ assert isinstance(variables, dict)
+
+ key = tuple(round(x*10000) for x in orientation)
+
+ if key not in self.add_local:
+ self.add_orientations[key] = orientation
+ self.add_local[key] = ([], [], [])
+
+ self.add_local[key][index].append((expression, variables))
+
+ def add_limit_distance(self, target, *, ensure_order=False, **kwargs):
+ """Add a limit distance constraint with the given make_constraint arguments."""
+ self.limit_distance.append((target, kwargs))
+
+ # Prevent merging from reordering this limit
+ if ensure_order:
+ self.is_parent_frozen = True
+
+ def __eq__(self, other):
+ return (
+ isinstance(other, ControlBoneParentOffset) and
+ self.parent == other.parent and
+ self.copy_local == other.copy_local and
+ self.add_local == other.add_local and
+ self.limit_distance == other.limit_distance
+ )
+
+ @property
+ def output_bone(self):
+ return self.mch_bones[-1] if self.mch_bones else self.parent.output_bone
+
+ def generate_bones(self):
+ self.mch_bones = []
+ self.reuse_mch = False
+
+ if self.copy_local or self.add_local or self.limit_distance:
+ mch_name = make_derived_name(self.node.name, 'mch', '_poffset')
+
+ if self.add_local:
+ # Generate a bone for every distinct orientation used for the drivers
+ for key in self.add_local:
+ self.mch_bones.append(self.node.make_bone(
+ mch_name, 1/4, rig=self.rig, orientation=self.add_orientations[key]))
+ else:
+ # Try piggybacking on the parent bone if allowed
+ if not self.parent.is_parent_frozen:
+ bone = self.get_bone(self.parent.output_bone)
+ if (bone.head - self.node.point).length < 1e-5:
+ self.reuse_mch = True
+ self.mch_bones = [bone.name]
+ return
+
+ self.mch_bones.append(self.node.make_bone(mch_name, 1/4, rig=self.rig))
+
+ def parent_bones(self):
+ if self.mch_bones:
+ if not self.reuse_mch:
+ self.rig.set_bone_parent(self.mch_bones[0], self.parent.output_bone)
+
+ self.rig.parent_bone_chain(self.mch_bones, use_connect=False)
+
+ def compile_driver(self, items):
+ variables = {}
+ expressions = []
+
+ # Loop through all expressions and combine the variable maps.
+ for expr, varset in items:
+ template = Template(expr)
+ varmap = {}
+
+ # Check that all variables are present
+ try:
+ template.substitute({k: '' for k in varset})
+ except Exception as e:
+ self.rig.raise_error('Invalid driver expression: {}\nError: {}', expr, e)
+
+ # Merge variables
+ for name, desc in varset.items():
+ # Check if the variable is used.
+ try:
+ template.substitute({k: '' for k in varset if k != name})
+ continue
+ except KeyError:
+ pass
+
+ # Descriptors may not be hashable, so linear search
+ for vn, vdesc in variables.items():
+ if vdesc == desc:
+ varmap[name] = vn
+ break
+ else:
+ # Find an unique name for the new variable and add to map
+ new_name = name
+ if new_name in variables:
+ for i in count(1):
+ new_name = '%s_%d' % (name, i)
+ if new_name not in variables:
+ break
+
+ variables[new_name] = desc
+ varmap[name] = new_name
+
+ # Substitute the new names into the expression
+ expressions.append(template.substitute(varmap))
+
+ # Add all expressions together
+ if len(expressions) > 1:
+ final_expr = '+'.join('('+expr+')' for expr in expressions)
+ else:
+ final_expr = expressions[0]
+
+ return final_expr, variables
+
+ def rig_bones(self):
+ # Emit the Copy Location constraints
+ if self.copy_local:
+ mch = self.mch_bones[0]
+ for target, (influence, drivers, lazyinf) in self.copy_local.items():
+ influence += sum(map(force_lazy, lazyinf))
+
+ con = self.make_constraint(
+ mch, 'COPY_LOCATION', target, use_offset=True,
+ target_space='LOCAL_OWNER_ORIENT', owner_space='LOCAL', influence=influence,
+ )
+
+ if drivers:
+ if influence > 0:
+ drivers.append((str(influence), {}))
+
+ expr, variables = self.compile_driver(drivers)
+ self.make_driver(con, 'influence', expression=expr, variables=variables)
+
+ # Add the direct offset drivers
+ if self.add_local:
+ for mch, (key, specs) in zip(self.mch_bones, self.add_local.items()):
+ for index, vals in enumerate(specs):
+ if vals:
+ expr, variables = self.compile_driver(vals)
+ self.make_driver(mch, 'location', index=index,
+ expression=expr, variables=variables)
+
+ # Add the limit distance constraints
+ for target, kwargs in self.limit_distance:
+ self.make_constraint(self.mch_bones[-1], 'LIMIT_DISTANCE', target, **kwargs)