Rigify: include widgets in generated metarig code.

Since rigs like super_copy already support using widgets assigned
directly to metarig bones, implement adding them with the metarig.
This commit is contained in:
Alexander Gavrilov 2020-12-07 22:56:50 +03:00
parent f8d6489fb6
commit 9bc98387d9
4 changed files with 118 additions and 32 deletions

View File

@ -270,18 +270,25 @@ class Generator(base_generate.BaseGenerator):
# others make non-deforming.
for bone in bones:
name = bone.name
layers = None
bone.use_deform = name.startswith(DEF_PREFIX)
# Move all the original bones to their layer.
if name.startswith(ORG_PREFIX):
bone.layers = ORG_LAYER
layers = ORG_LAYER
# Move all the bones with names starting with "MCH-" to their layer.
elif name.startswith(MCH_PREFIX):
bone.layers = MCH_LAYER
layers = MCH_LAYER
# Move all the bones with names starting with "DEF-" to their layer.
elif name.startswith(DEF_PREFIX):
bone.layers = DEF_LAYER
layers = DEF_LAYER
if layers is not None:
bone.layers = layers
# Remove custom shapes from non-control bones
bone.custom_shape = None
bone.bbone_x = bone.bbone_z = bone.length * 0.05

View File

@ -858,7 +858,7 @@ class EncodeMetarig(bpy.types.Operator):
else:
text_block = bpy.data.texts.new(name)
text = write_metarig(context.active_object, layers=True, func_name="create", groups=True)
text = write_metarig(context.active_object, layers=True, func_name="create", groups=True, widgets=True)
text_block.write(text)
bpy.ops.object.mode_set(mode='EDIT')

View File

@ -22,6 +22,9 @@ import bpy
import importlib
import importlib.util
import os
import re
from itertools import count
from bpy.types import bpy_struct, bpy_prop_array, Constraint
@ -173,16 +176,54 @@ def _generate_properties(lines, prefix, obj, base_class, *, defaults={}, objects
lines.append('%s.%s = %r' % (prefix, prop.identifier, cur_value))
def write_metarig(obj, layers=False, func_name="create", groups=False):
def write_metarig_widgets(obj):
from .widgets import write_widget
widget_set = set()
for pbone in obj.pose.bones:
if pbone.custom_shape:
widget_set.add(pbone.custom_shape)
id_set = set()
widget_map = {}
code = []
for widget_obj in widget_set:
ident = re.sub("[^0-9a-zA-Z_]+", "_", widget_obj.name)
if ident in id_set:
for i in count(1):
if ident+'_'+str(i) not in id_set:
break
id_set.add(ident)
widget_map[widget_obj] = ident
code.append(write_widget(widget_obj, name=ident, use_size=False))
return widget_map, code
def write_metarig(obj, layers=False, func_name="create", groups=False, widgets=False):
"""
Write a metarig as a python script, this rig is to have all info needed for
generating the real rig with rigify.
"""
code = []
code.append("import bpy\n\n")
code.append("from mathutils import Color\n\n")
code.append("import bpy\n")
code.append("from mathutils import Color\n")
# Widget object creation functions if requested
if widgets:
widget_map, widget_code = write_metarig_widgets(obj)
if widget_map:
code.append("from rigify.utils.widgets import widget_generator\n\n")
code += widget_code
# Start of the metarig function
code.append("def %s(obj):" % func_name)
code.append(" # generated by rigify.utils.write_metarig")
bpy.ops.object.mode_set(mode='EDIT')
@ -247,6 +288,9 @@ def write_metarig(obj, layers=False, func_name="create", groups=False):
code.append("")
code.append(" bpy.ops.object.mode_set(mode='OBJECT')")
if widgets and widget_map:
code.append(" widget_map = {}")
# Rig type and other pose properties
for bone_name in bones:
pbone = obj.pose.bones[bone_name]
@ -294,6 +338,12 @@ def write_metarig(obj, layers=False, func_name="create", groups=False):
},
objects={obj: 'obj'},
)
# Custom widgets
if widgets and pbone.custom_shape:
widget_id = widget_map[pbone.custom_shape]
code.append(" if %r not in widget_map:" % (widget_id))
code.append(" widget_map[%r] = create_%s_widget(obj, pbone.name, widget_name=%r, widget_force_new=True)" % (widget_id, widget_id, pbone.custom_shape.name))
code.append(" pbone.custom_shape = widget_map[%r]" % (widget_id))
code.append("\n bpy.ops.object.mode_set(mode='EDIT')")
code.append(" for bone in arm.edit_bones:")

View File

@ -20,6 +20,7 @@
import bpy
import math
import functools
from mathutils import Matrix
@ -94,6 +95,39 @@ def create_widget(rig, bone_name, bone_transform_name=None, *, widget_name=None,
return obj
class GeometryData:
def __init__(self):
self.verts = []
self.edges = []
self.faces = []
def widget_generator(generate_func):
"""
Decorator that encapsulates a call to create_widget, and only requires
the actual function to fill the provided vertex and edge lists.
Accepts parameters of create_widget, plus any keyword arguments the
wrapped function has.
"""
@functools.wraps(generate_func)
def wrapper(rig, bone_name, bone_transform_name=None, widget_name=None, widget_force_new=False, **kwargs):
obj = create_widget(rig, bone_name, bone_transform_name, widget_name=widget_name, widget_force_new=widget_force_new)
if obj is not None:
geom = GeometryData()
generate_func(geom, **kwargs)
mesh = obj.data
mesh.from_pydata(geom.verts, geom.edges, geom.faces)
mesh.update()
return obj
else:
return None
return wrapper
def create_circle_polygon(number_verts, axis, radius=1.0, head_tail=0.0):
""" Creates a basic circle around of an axis selected.
number_verts: number of vertices of the polygon
@ -174,44 +208,39 @@ def adjust_widget_transform_mesh(obj, matrix, local=None):
obj.data.transform(matrix)
def write_widget(obj):
def write_widget(obj, name='thing', use_size=True):
""" Write a mesh object as a python script for widget use.
"""
script = ""
script += "def create_thing_widget(rig, bone_name, size=1.0, bone_transform_name=None):\n"
script += " obj = create_widget(rig, bone_name, bone_transform_name)\n"
script += " if obj is not None:\n"
script += "@widget_generator\n"
script += "def create_"+name+"_widget(geom";
if use_size:
script += ", *, size=1.0"
script += "):\n"
# Vertices
script += " verts = ["
szs = "*size" if use_size else ""
width = 2 if use_size else 3
script += " geom.verts = ["
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 += "({:g}{}, {:g}{}, {:g}{}),".format(v.co[0], szs, v.co[1], szs, v.co[2], szs)
script += "\n " if i % width == (width - 1) else " "
script += "]\n"
# Edges
script += " edges = ["
script += " geom.edges = ["
for i, e in enumerate(obj.data.edges):
script += "(" + str(e.vertices[0]) + ", " + str(e.vertices[1]) + "),"
script += "\n " if i % 10 == 9 else " "
script += "\n " if i % 10 == 9 else " "
script += "]\n"
# Faces
script += " faces = ["
for i, f in enumerate(obj.data.polygons):
script += "("
for v in f.vertices:
script += str(v) + ", "
script += "),"
script += "\n " if i % 10 == 9 else " "
script += "]\n"
# Build mesh
script += "\n mesh = obj.data\n"
script += " mesh.from_pydata(verts, edges, faces)\n"
script += " mesh.update()\n"
script += " return obj\n"
script += " else:\n"
script += " return None\n"
if obj.data.polygons:
script += " geom.faces = ["
for i, f in enumerate(obj.data.polygons):
script += "(" + ", ".join(str(v) for v in f.vertices) + "),"
script += "\n " if i % 10 == 9 else " "
script += "]\n"
return script