Skip to content

Commit 6489112

Browse files
angavriloveigen-value
authored andcommitted
Make rig_ui_template modular: allow individual rigs to add code.
Allow rig classes to return new module imports, utility functions and operators, and classes to register. This is necessary to make script generation truly modular. In order to avoid code duplication, rigs return lists of strings, which are merged and filtered for duplicates before insertion into the script. In this way, multiple rigs can easily share utility code by simply ensuring that they include exactly the same string. As an example, existing super_limb rigs are changed to use this feature. For now arm and leg utilities are also included by default to provide backward compatibility to legacy rigs.
1 parent bd1fd4f commit 6489112

File tree

6 files changed

+170
-55
lines changed

6 files changed

+170
-55
lines changed

README

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -240,15 +240,39 @@ Implementation classes are shown in the metarig samples list and generate a samp
240240

241241
GENERATING A PYTHON UI
242242
----------------------
243-
The generate() method can also, optionally, return python code as a single
244-
string. This python code is added to the "rig properties" panel that gets
245-
auto-generated along with the rig. This is useful for exposing things like
246-
IK/FK switches in a nice way to the animator.
243+
The generate() method can also, optionally, return a dictionary with data
244+
used for generating the python UI. The dictionary may have the following
245+
keys (all optional):
247246

248-
The string must be returned in a list, e.g.:
247+
'script': python code as list of strings.
249248

250-
return ["my python code"]
249+
This python code is added to the "rig properties" panel that gets
250+
auto-generated along with the rig. This is useful for exposing things like
251+
IK/FK switches in a nice way to the animator.
251252

252-
The reason it needs to be put in a list is to leave room for expanding the API
253-
in the future, for returning additional information.
253+
'utilities': python code as list of strings.
254254

255+
This code is added to the utility function/class area of the auto-generated
256+
python script, and should contain functions, classes and operators for use
257+
in the 'script' code and other utilities.
258+
259+
'imports': python code as list of strings.
260+
261+
These lines are added to the import section of the python script.
262+
263+
'register': list of class names.
264+
265+
These are used in generating the register() and unregister() functions.
266+
267+
The 'utilities', 'imports' and 'register' lists returned by all rigs are concatenated
268+
and filtered for duplicates before use, in order to allow easy sharing of utility
269+
functions and classes: all that is needed is to ensure that shared code is included
270+
in the lists as exactly the same string by all users.
271+
272+
The python code strings from the lists are implicitly terminated with a newline.
273+
274+
Some rigs return a list with a single string, e.g.:
275+
276+
return ["my python code"]
277+
278+
This is a deprecated format equivalent to {'script': ["my python code"]}.

generate.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import traceback
2525
import sys
2626
from rna_prop_ui import rna_idprop_ui_prop_get
27+
from collections import OrderedDict
2728

2829
from .utils import MetarigError, new_bone, get_rig_type
2930
from .utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name
@@ -32,7 +33,7 @@
3233
from .utils import random_id
3334
from .utils import copy_attributes
3435
from .utils import gamma_correct
35-
from .rig_ui_template import UI_SLIDERS, layers_ui, UI_REGISTER
36+
from .rig_ui_template import UI_IMPORTS, UI_BASE_UTILITIES, UI_UTILITIES, UI_SLIDERS, layers_ui, UI_REGISTER
3637

3738

3839
RIG_MODULE = "rigs"
@@ -341,14 +342,26 @@ def generate_rig(context, metarig):
341342

342343
# Generate all the rigs.
343344
ui_scripts = []
345+
ui_imports = UI_IMPORTS.copy()
346+
ui_utilities = UI_UTILITIES.copy()
347+
ui_register = UI_REGISTER.copy()
344348
for rig in rigs:
345349
# Go into editmode in the rig armature
346350
bpy.ops.object.mode_set(mode='OBJECT')
347351
context.scene.objects.active = obj
348352
obj.select = True
349353
bpy.ops.object.mode_set(mode='EDIT')
350354
scripts = rig.generate()
351-
if scripts is not None:
355+
if isinstance(scripts, dict):
356+
if 'script' in scripts:
357+
ui_scripts += scripts['script']
358+
if 'imports' in scripts:
359+
ui_imports += scripts['imports']
360+
if 'utilities' in scripts:
361+
ui_utilities += scripts['utilities']
362+
if 'register' in scripts:
363+
ui_register += scripts['register']
364+
elif scripts is not None:
352365
ui_scripts += [scripts[0]]
353366
t.tick("Generate rigs: ")
354367

@@ -496,11 +509,23 @@ def generate_rig(context, metarig):
496509

497510
id_store.rigify_rig_ui = script.name
498511

499-
script.write(UI_SLIDERS % rig_id)
512+
for s in OrderedDict.fromkeys(ui_imports):
513+
script.write(s + "\n")
514+
script.write(UI_BASE_UTILITIES % rig_id)
515+
for s in OrderedDict.fromkeys(ui_utilities):
516+
script.write(s + "\n")
517+
script.write(UI_SLIDERS)
500518
for s in ui_scripts:
501519
script.write("\n " + s.replace("\n", "\n ") + "\n")
502520
script.write(layers_ui(vis_layers, layer_layout))
503-
script.write(UI_REGISTER)
521+
script.write("\ndef register():\n")
522+
ui_register = OrderedDict.fromkeys(ui_register)
523+
for s in ui_register:
524+
script.write(" bpy.utils.register_class("+s+");\n")
525+
script.write("\ndef unregister():\n")
526+
for s in ui_register:
527+
script.write(" bpy.utils.unregister_class("+s+");\n")
528+
script.write("\nregister()\n")
504529
script.use_module = True
505530

506531
# Run UI script

rig_ui_template.py

Lines changed: 91 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818

1919
# <pep8 compliant>
2020

21-
UI_SLIDERS = '''
22-
import bpy
23-
from mathutils import Matrix, Vector
24-
from math import acos, pi, radians
25-
21+
UI_IMPORTS = [
22+
'import bpy',
23+
'import math',
24+
'from math import pi',
25+
'from mathutils import Euler, Matrix, Quaternion, Vector',
26+
]
27+
28+
UI_BASE_UTILITIES = '''
2629
rig_id = "%s"
2730
2831
@@ -54,7 +57,7 @@ def rotation_difference(mat1, mat2):
5457
"""
5558
q1 = mat1.to_quaternion()
5659
q2 = mat2.to_quaternion()
57-
angle = acos(min(1,max(-1,q1.dot(q2)))) * 2
60+
angle = math.acos(min(1,max(-1,q1.dot(q2)))) * 2
5861
if angle > pi:
5962
angle = -angle + (2*pi)
6063
return angle
@@ -295,6 +298,22 @@ def set_pole(pvi):
295298
if ang1 < ang2:
296299
set_pole(pv1)
297300
301+
##########
302+
## Misc ##
303+
##########
304+
305+
def parse_bone_names(names_string):
306+
if names_string[0] == '[' and names_string[-1] == ']':
307+
return eval(names_string)
308+
else:
309+
return names_string
310+
311+
'''
312+
313+
UTILITIES_FUNC_ARM_FKIK = ['''
314+
######################
315+
## IK Arm functions ##
316+
######################
298317
299318
def fk2ik_arm(obj, fk, ik):
300319
""" Matches the fk bones in an arm rig to the ik bones.
@@ -390,6 +409,12 @@ def ik2fk_arm(obj, fk, ik):
390409
match_pose_scale(uarmi, uarm)
391410
# Rotation Correction
392411
correct_rotation(uarmi, uarm)
412+
''']
413+
414+
UTILITIES_FUNC_LEG_FKIK = ['''
415+
######################
416+
## IK Leg functions ##
417+
######################
393418
394419
def fk2ik_leg(obj, fk, ik):
395420
""" Matches the fk bones in a leg rig to the ik bones.
@@ -518,18 +543,13 @@ def ik2fk_leg(obj, fk, ik):
518543
519544
# Pole target position
520545
match_pole_target(thighi, shini, pole, thigh, (thighi.length + shini.length))
546+
''']
521547

522-
548+
UTILITIES_FUNC_POLE = ['''
523549
################################
524550
## IK Rotation-Pole functions ##
525551
################################
526552
527-
def parse_bone_names(names_string):
528-
if names_string[0] == '[' and names_string[-1] == ']':
529-
return eval(names_string)
530-
else:
531-
return names_string
532-
533553
def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
534554
535555
rig_id = rig.data['rig_id']
@@ -588,10 +608,14 @@ def rotPoleToggle(rig, limb_type, controls, ik_ctrl, fk_ctrl, parent, pole):
588608
func2(**kwargs2)
589609
590610
bpy.ops.pose.select_all(action='DESELECT')
611+
''']
591612

592-
##############################
593-
## IK/FK snapping operators ##
594-
##############################
613+
REGISTER_OP_ARM_FKIK = ['Rigify_Arm_FK2IK', 'Rigify_Arm_IK2FK']
614+
615+
UTILITIES_OP_ARM_FKIK = ['''
616+
##################################
617+
## IK/FK Arm snapping operators ##
618+
##################################
595619
596620
class Rigify_Arm_FK2IK(bpy.types.Operator):
597621
""" Snaps an FK arm to an IK arm.
@@ -652,7 +676,14 @@ def execute(self, context):
652676
finally:
653677
context.user_preferences.edit.use_global_undo = use_global_undo
654678
return {'FINISHED'}
679+
''']
680+
681+
REGISTER_OP_LEG_FKIK = ['Rigify_Leg_FK2IK', 'Rigify_Leg_IK2FK']
655682

683+
UTILITIES_OP_LEG_FKIK = ['''
684+
##################################
685+
## IK/FK Leg snapping operators ##
686+
##################################
656687
657688
class Rigify_Leg_FK2IK(bpy.types.Operator):
658689
""" Snaps an FK leg to an IK leg.
@@ -717,7 +748,11 @@ def execute(self, context):
717748
finally:
718749
context.user_preferences.edit.use_global_undo = use_global_undo
719750
return {'FINISHED'}
751+
''']
720752

753+
REGISTER_OP_POLE = ['Rigify_Rot2PoleSwitch']
754+
755+
UTILITIES_OP_POLE = ['''
721756
###########################
722757
## IK Rotation Pole Snap ##
723758
###########################
@@ -743,7 +778,47 @@ def execute(self, context):
743778
744779
rotPoleToggle(rig, self.limb_type, self.controls, self.ik_ctrl, self.fk_ctrl, self.parent, self.pole)
745780
return {'FINISHED'}
781+
''']
782+
783+
REGISTER_RIG_ARM = REGISTER_OP_ARM_FKIK + REGISTER_OP_POLE
746784

785+
UTILITIES_RIG_ARM = [
786+
*UTILITIES_FUNC_ARM_FKIK,
787+
*UTILITIES_FUNC_POLE,
788+
*UTILITIES_OP_ARM_FKIK,
789+
*UTILITIES_OP_POLE,
790+
]
791+
792+
REGISTER_RIG_LEG = REGISTER_OP_LEG_FKIK + REGISTER_OP_POLE
793+
794+
UTILITIES_RIG_LEG = [
795+
*UTILITIES_FUNC_LEG_FKIK,
796+
*UTILITIES_FUNC_POLE,
797+
*UTILITIES_OP_LEG_FKIK,
798+
*UTILITIES_OP_POLE,
799+
]
800+
801+
##############################
802+
## Default set of utilities ##
803+
##############################
804+
805+
UI_REGISTER = [
806+
'RigUI',
807+
'RigLayers',
808+
*REGISTER_OP_ARM_FKIK,
809+
*REGISTER_OP_LEG_FKIK,
810+
]
811+
812+
# Include arm and leg utilities for now in case somebody wants to use
813+
# legacy limb rigs, which expect these to be available by default.
814+
UI_UTILITIES = [
815+
*UTILITIES_FUNC_ARM_FKIK,
816+
*UTILITIES_FUNC_LEG_FKIK,
817+
*UTILITIES_OP_ARM_FKIK,
818+
*UTILITIES_OP_LEG_FKIK,
819+
]
820+
821+
UI_SLIDERS = '''
747822
###################
748823
## Rig UI Panels ##
749824
###################
@@ -837,27 +912,3 @@ def draw(self, context):
837912
code += " row.prop(context.active_object.data, 'layers', index=28, toggle=True, text='Root')\n"
838913

839914
return code
840-
841-
842-
UI_REGISTER = '''
843-
844-
def register():
845-
bpy.utils.register_class(Rigify_Arm_FK2IK)
846-
bpy.utils.register_class(Rigify_Arm_IK2FK)
847-
bpy.utils.register_class(Rigify_Leg_FK2IK)
848-
bpy.utils.register_class(Rigify_Leg_IK2FK)
849-
bpy.utils.register_class(Rigify_Rot2PoleSwitch)
850-
bpy.utils.register_class(RigUI)
851-
bpy.utils.register_class(RigLayers)
852-
853-
def unregister():
854-
bpy.utils.unregister_class(Rigify_Arm_FK2IK)
855-
bpy.utils.unregister_class(Rigify_Arm_IK2FK)
856-
bpy.utils.unregister_class(Rigify_Leg_FK2IK)
857-
bpy.utils.unregister_class(Rigify_Leg_IK2FK)
858-
bpy.utils.register_class(Rigify_Rot2PoleSwitch)
859-
bpy.utils.unregister_class(RigUI)
860-
bpy.utils.unregister_class(RigLayers)
861-
862-
register()
863-
'''

rigs/limbs/arm.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from ...utils import MetarigError, make_mechanism_name, org
1010
from ...utils import create_limb_widget, connected_children_names
1111
from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis
12+
from ...rig_ui_template import UTILITIES_RIG_ARM, REGISTER_RIG_ARM
1213
from rna_prop_ui import rna_idprop_ui_prop_get
1314
from ..widgets import create_ikarrow_widget
1415
from math import trunc, pi
@@ -1067,7 +1068,11 @@ def generate(self):
10671068
script += extra_script % (controls_string, bones['main_parent'], 'IK_follow',
10681069
'pole_follow', 'pole_follow', 'root/parent', 'root/parent')
10691070

1070-
return [script]
1071+
return {
1072+
'script': [script],
1073+
'utilities': UTILITIES_RIG_ARM,
1074+
'register': REGISTER_RIG_ARM,
1075+
}
10711076

10721077

10731078
def add_parameters(params):

rigs/limbs/leg.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from ...utils import MetarigError, make_mechanism_name, org
1111
from ...utils import create_limb_widget, connected_children_names
1212
from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis
13+
from ...rig_ui_template import UTILITIES_RIG_LEG, REGISTER_RIG_LEG
1314
from rna_prop_ui import rna_idprop_ui_prop_get
1415
from ..widgets import create_ikarrow_widget
1516
from math import trunc, pi
@@ -1390,7 +1391,11 @@ def generate(self):
13901391
script += extra_script % (controls_string, bones['main_parent'], 'IK_follow',
13911392
'pole_follow', 'pole_follow', 'root/parent', 'root/parent')
13921393

1393-
return [script]
1394+
return {
1395+
'script': [script],
1396+
'utilities': UTILITIES_RIG_LEG,
1397+
'register': REGISTER_RIG_LEG,
1398+
}
13941399

13951400

13961401
def add_parameters(params):

rigs/limbs/paw.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from ...utils import MetarigError, make_mechanism_name, org
99
from ...utils import create_limb_widget, connected_children_names
1010
from ...utils import align_bone_y_axis, align_bone_x_axis, align_bone_z_axis
11+
from ...rig_ui_template import UTILITIES_RIG_LEG, REGISTER_RIG_LEG
1112
from rna_prop_ui import rna_idprop_ui_prop_get
1213
from ..widgets import create_ikarrow_widget, create_gear_widget
1314
from ..widgets import create_foot_widget, create_ballsocket_widget
@@ -1218,7 +1219,11 @@ def generate(self):
12181219
script += extra_script % (controls_string, bones['main_parent'], 'IK_follow',
12191220
'pole_follow', 'pole_follow', 'root/parent', 'root/parent')
12201221

1221-
return [script]
1222+
return {
1223+
'script': [script],
1224+
'utilities': UTILITIES_RIG_LEG,
1225+
'register': REGISTER_RIG_LEG,
1226+
}
12221227

12231228

12241229
def add_parameters(params):

0 commit comments

Comments
 (0)