Rigify: various additions to bone, mechanism and widget utilities.

Support easier setting of bone orientation via matrix, inherit_scale,
invert_x/y/z constraint properties, computing a matrix from two axis
vectors, adjusting widget positions, and add a pivot widget.
This commit is contained in:
Alexander Gavrilov 2019-09-29 11:00:38 +03:00
parent 73784e78de
commit 6bb8ab3ad7
6 changed files with 98 additions and 12 deletions

View File

@ -261,7 +261,7 @@ def flip_bone_chain(obj, bone_names):
bone.use_connect = True
def put_bone(obj, bone_name, pos):
def put_bone(obj, bone_name, pos, *, matrix=None, length=None, scale=None):
""" Places a bone at the given position.
"""
if bone_name not in obj.data.edit_bones:
@ -270,8 +270,25 @@ def put_bone(obj, bone_name, pos):
if obj == bpy.context.active_object and bpy.context.mode == 'EDIT_ARMATURE':
bone = obj.data.edit_bones[bone_name]
delta = pos - bone.head
bone.translate(delta)
if matrix is not None:
old_len = len(matrix)
matrix = matrix.to_4x4()
if pos is not None:
matrix.translation = pos
elif old_len < 4:
matrix.translation = bone.head
bone.matrix = matrix
else:
delta = pos - bone.head
bone.translate(delta)
if length is not None:
bone.length = length
elif scale is not None:
bone.length *= scale
else:
raise MetarigError("Cannot 'put' bones outside of edit mode")
@ -394,18 +411,20 @@ class BoneUtilityMixin(object):
"""Get the name of the parent bone, or None."""
return get_name(self.get_bone(bone_name).parent)
def set_bone_parent(self, bone_name, parent_name, use_connect=False):
def set_bone_parent(self, bone_name, parent_name, use_connect=False, inherit_scale=None):
"""Set the parent of the bone."""
eb = self.obj.data.edit_bones
bone = eb[bone_name]
if use_connect is not None:
bone.use_connect = use_connect
if inherit_scale is not None:
bone.inherit_scale = inherit_scale
bone.parent = (eb[parent_name] if parent_name else None)
def parent_bone_chain(self, bone_names, use_connect=None):
def parent_bone_chain(self, bone_names, use_connect=None, inherit_scale=None):
"""Link bones into a chain with parenting. First bone may be None."""
for parent, child in pairwise(bone_names):
self.set_bone_parent(child, parent, use_connect=use_connect)
self.set_bone_parent(child, parent, use_connect=use_connect, inherit_scale=inherit_scale)
#=============================================
# B-Bones

View File

@ -38,7 +38,7 @@ def _set_default_attr(obj, options, attr, value):
def make_constraint(
owner, type, target=None, subtarget=None, *, insert_index=None,
space=None, track_axis=None, use_xyz=None, use_limit_xyz=None,
space=None, track_axis=None, use_xyz=None, use_limit_xyz=None, invert_xyz=None,
**options):
"""
Creates and initializes constraint of the specified type for the owner bone.
@ -51,6 +51,7 @@ def make_constraint(
track_axis : allows shorter X, Y, Z, -X, -Y, -Z notation
use_xyz : list of 3 items is assigned to use_x, use_y and use_z options
use_limit_xyz : list of 3 items is assigned to use_limit_x/y/z options
invert_xyz : list of 3 items is assigned to invert_x, invert_y and invert_z options
min/max_x/y/z : a corresponding use_(min/max/limit)_(x/y/z) option is set to True
Other keyword arguments are directly assigned to the constraint options.
@ -80,6 +81,9 @@ def make_constraint(
if use_limit_xyz is not None:
con.use_limit_x, con.use_limit_y, con.use_limit_z = use_limit_xyz[0:3]
if invert_xyz is not None:
con.invert_x, con.invert_y, con.invert_z = invert_xyz[0:3]
for key in ['min_x', 'max_x', 'min_y', 'max_y', 'min_z', 'max_z']:
if key in options:
_set_default_attr(con, options, 'use_'+key, True)

View File

@ -18,6 +18,7 @@
# <pep8 compliant>
import bpy
import math
import collections
@ -56,6 +57,28 @@ def angle_on_plane(plane, vec1, vec2):
return angle * sign
# Convert between a matrix and axis+roll representations.
# Re-export the C implementation internally used by bones.
matrix_from_axis_roll = bpy.types.Bone.MatrixFromAxisRoll
axis_roll_from_matrix = bpy.types.Bone.AxisRollFromMatrix
def matrix_from_axis_pair(y_axis, other_axis, axis_name):
assert axis_name in 'xz'
y_axis = Vector(y_axis).normalized()
if axis_name == 'x':
z_axis = Vector(other_axis).cross(y_axis).normalized()
x_axis = y_axis.cross(z_axis)
else:
x_axis = y_axis.cross(other_axis).normalized()
z_axis = x_axis.cross(y_axis)
return Matrix((x_axis, y_axis, z_axis)).transposed()
#=============================================
# Color correction functions
#=============================================

View File

@ -235,8 +235,8 @@ def write_metarig(obj, layers=False, func_name="create", groups=False):
for bone_name in bones:
bone = arm.edit_bones[bone_name]
code.append(" bone = arm.edit_bones.new(%r)" % bone.name)
code.append(" bone.head[:] = %.4f, %.4f, %.4f" % bone.head.to_tuple(4))
code.append(" bone.tail[:] = %.4f, %.4f, %.4f" % bone.tail.to_tuple(4))
code.append(" bone.head = %.4f, %.4f, %.4f" % bone.head.to_tuple(4))
code.append(" bone.tail = %.4f, %.4f, %.4f" % bone.tail.to_tuple(4))
code.append(" bone.roll = %.4f" % bone.roll)
code.append(" bone.use_connect = %s" % str(bone.use_connect))
if bone.parent:

View File

@ -160,6 +160,14 @@ def adjust_widget_axis(obj, axis='y', offset=0.0):
vert.co = matrix @ vert.co
def adjust_widget_transform(obj, matrix):
"""Adjust the generated widget by applying a world space correction matrix to the mesh."""
if obj:
obmat = obj.matrix_basis
matrix = obmat.inverted() @ matrix @ obmat
obj.data.transform(matrix)
def write_widget(obj):
""" Write a mesh object as a python script for widget use.
"""
@ -170,9 +178,9 @@ def write_widget(obj):
# Vertices
script += " verts = ["
for v in obj.data.vertices:
script += "(" + str(v.co[0]) + "*size, " + str(v.co[1]) + "*size, " + str(v.co[2]) + "*size),"
script += "\n "
for i, v in enumerate(obj.data.vertices):
script += "({:g}*size, {:g}*size, {:g}*size),".format(v.co[0], v.co[1], v.co[2])
script += "\n " if i % 2 == 1 else " "
script += "]\n"
# Edges

View File

@ -127,3 +127,35 @@ def create_bone_widget(rig, bone_name, r1=0.1, l1=0.0, r2=0.04, l2=1.0, bone_tra
mesh.update()
def create_pivot_widget(rig, bone_name, axis_size=1.0, cap_size=1.0, square=False, bone_transform_name=None):
"""Creates a widget similar to Plain Axes empty, but with a cross or
a square on the end of each axis line.
"""
obj = create_widget(rig, bone_name, bone_transform_name)
if obj is not None:
axis = 0.5 * axis_size
cap = 0.05 * cap_size
if square:
verts = [(0, 0, -axis), (-axis, 0, 0), (0, 0, axis), (axis, 0, 0), (axis, cap, -cap), (axis, cap, cap),
(0, -axis, 0), (0, axis, 0), (cap, axis, cap), (cap, axis, -cap), (axis, -cap, -cap), (axis, -cap, cap),
(-cap, axis, cap), (-cap, axis, -cap), (-axis, cap, cap), (-axis, cap, -cap), (-axis, -cap, cap), (-axis, -cap, -cap),
(-cap, -axis, cap), (-cap, -axis, -cap), (cap, -axis, cap), (cap, -axis, -cap), (-cap, -cap, -axis), (-cap, cap, -axis),
(cap, -cap, -axis), (cap, cap, -axis), (-cap, cap, axis), (-cap, -cap, axis), (cap, cap, axis), (cap, -cap, axis) ]
edges = [(10, 4), (4, 5), (8, 9), (0, 2), (12, 8), (6, 7), (11, 10), (13, 12), (5, 11), (9, 13),
(3, 1), (14, 15), (16, 14), (17, 16), (15, 17), (18, 19), (20, 18), (21, 20), (19, 21), (22, 23),
(24, 22), (25, 24), (23, 25), (26, 27), (28, 26), (29, 28), (27, 29) ]
else:
verts = [(0, 0, -axis), (-axis, 0, 0), (0, 0, axis), (axis, 0, 0), (-cap, 0, -axis), (-axis, 0, -cap),
(-axis, 0, cap), (-cap, 0, axis), (cap, 0, axis), (axis, 0, cap), (axis, 0, -cap), (cap, 0, -axis),
(0, -axis, 0), (0, axis, 0), (0, -cap, -axis), (0, -axis, -cap), (0, -axis, cap), (0, -cap, axis),
(0, cap, axis), (0, axis, cap), (0, axis, -cap), (0, cap, -axis), (-axis, -cap, 0), (-cap, -axis, 0),
(cap, -axis, 0), (axis, -cap, 0), (axis, cap, 0), (cap, axis, 0), (-cap, axis, 0), (-axis, cap, 0) ]
edges = [(4, 0), (6, 1), (8, 2), (10, 3), (1, 5), (2, 7), (3, 9), (0, 11), (16, 12), (0, 21),
(2, 17), (20, 13), (12, 15), (0, 2), (18, 2), (13, 19), (12, 13), (1, 29), (22, 1), (3, 25),
(13, 27), (14, 0), (26, 3), (28, 13), (24, 12), (12, 23), (3, 1) ]
mesh = obj.data
mesh.from_pydata(verts, edges, [])
mesh.update()
return obj
else:
return None