Rigify rig API redesign #63138
Labels
No Label
Interest
Animation & Rigging
Interest
Blender Cloud
Interest
Collada
Interest
Core
Interest
Documentation
Interest
Eevee & Viewport
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
Import and Export
Interest
Modeling
Interest
Modifiers
Interest
Nodes & Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds, Tests & Devices
Interest
Python API
Interest
Rendering & Cycles
Interest
Sculpt, Paint & Texture
Interest
Translations
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Meta
Good First Issue
Meta
Papercut
Module
Add-ons (BF-Blender)
Module
Add-ons (Community)
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
No Assignees
6 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender-addons#63138
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Current Limitations
After adding custom rig (feature set) support, Rigify has effectively become a framework for rig generation rather than just a simple add-on. However the current API that rigs have to implement basically consists of a single generate() method and is thus very limited.
No support for rig interaction
The only information rigs receive about the environment is a reference to the base bone of the rig within the armature. The only way they can affect generation apart from changing the armature itself is returning certain values from generate().
Due to these limitations, implementing a set of semi-independent rigs that are intended to cooperate in certain aspects is impossible. This may for example be a valid approach in implementing a flexible face rig: instead of making one monolithic rig, or trying to separate it into completely independent units, it may be best to make a set of rigs that manage their own areas, but cooperate to create some global controls like the eye target etc.
Inefficient mode switching
Switching modes is actually a very expensive operation, especially in 2.8. However the single callback design forces mode switching to be done by individual rigs, and this encourages inefficient mode switching - investigation showed that even utilities did it, e.g. copy_bone. The result of that is that according to profiling results, most of the time spent generating a rig is mode switching.
The most efficient approach to mode switching is obviously to split one generate() method into multiple callbacks and do switching between the calls in the main generation code.
Rig code style isn't designed for subclassing
User-defined rigs provide a potential way for users to slightly tweak a standard rig by subclassing it and overriding methods. However the way all currently existing rigs are coded makes that basically impossible.
In order to allow meaningful subclassing, it is necessary to split the rig code into many simple methods that each do one logical thing, instead of creating enormous chunks of code that handle everything. One generate() method in fact encourages the latter.
Badly organized generate.py code
Most of the core generation management code is one huge function that directly handles all kind of things, and keeps various references in many local variables.
New Design
This describes the new API design implemented by the accompanying patch.
Generator class
The main generation driver code in generate.py is converted into a class. The code is split into multiple methods as appropriate, and data is stored in fields of the class instance.
Rigs receive a reference to the instance of the class, and can thus access the data and call methods to affect the generation process, for example:
scene
,view_layer
,collection
,metarig
,rig_id
etc.rig_list
,root_rigs
,bone_owners
.disable_auto_parent(bone)
method to suppress auto-parenting to root bone for a specific bone (previously handled bygenerate()
return value).BaseRig class
In order to provide more structure to the rigs, the new API requires them to inherit from a new BaseRig class.
The base rig defines a number of standard fields, that contain e.g:
generator
instance, armatureobj
,script
generator instance,base_bone
name, rigparams
.BoneDict
bones
for use in the rig code to keep bone names between callbacks.rigify_parent
,rigify_children
.rigify_org_bones
,rigify_child_bones
,rigify_new_bones
.Unlike the legacy API, rigs normally shouldn
t override
init. Instead there is a
find_org_bonesmethod that should just return which bones are exclusively owned by the rig, and stage callbacks that replace
generate()`.New style rigs also should define
add_parameters
andparameters_ui
as@classmethod
instead of a separate function in the module.Rig classes that don't inherit from BaseRig are assumed to follow the legacy API and are automatically wrapped in a LegacyRig class that translates between the interfaces. The only existing rig that didn't work without fixing was the face rig, because it relied on its constructor being called in Edit mode for no good reason; the fix was trivial.
Generation stages
Instead of one generate callback, rigs define a whole sequence of callbacks. Each of the stages in the sequence is executed for all rigs before proceeding to the next, and mode switches are performed between each stage as appropriate. Note that for python code that directly works with bone data structures there is no need to use Pose mode over Object mode, so the latter is used.
initialize
: called in Object mode for collecting information about the initial state of the rig.prepare_bones
: called in Edit mode to prepare bones for generation.generate_bones
: called in Edit mode to actually generate all required bones.generate()
method of rigs using the old API is called here.parent_bones
: called in Edit mode to assign bone parents, B-Bone custom handles etc.configure_bones
: called in Object mode to configure pose mode properties.rig_bones
: called in Object mode to create constraints and drivers.generate_widgets
: called in Object mode to create widgets.finalize
: called in Object mode at the very end of the generation process.Some reasons why there are more stages than might seem necessary just because of mode switching:
Stage decorators
To further aid in promoting the small method approach, there are two ways to get a rig method called in a certain stage. The most obvious one is overriding a method with the desired stage name:
This is simple and easy to understand, and actually turns out to be the best method for the initialize stage specifically, but for other stages in a more complex rig it runs into a problem. Specifically, keeping to the idea of small methods, it is desirable to split code dealing with different sub-parts of the rig into their separate methods; but it results in the main callback just calling other methods:
Such redirect-only callbacks look bad and are error-prone, as it is easy to forget to add one of the methods - especially with inheritance in the picture. For this reason, it is possible to mark any method to be called during a stage:
These methods will then be called before the main
generate_bones()
method in the order they were first declared (and base class before subclass). However it is recommended not to rely on the order within the stage unless absolutely unavoidable.Utility methods
The base rig class also inherits from certain mixins to provide utility methods for use in generating the rig. Among other things, these methods have the benefit of allowing the use of bone names in arguments, instead of having to look up the actual edit or pose bones.
BoneUtilityMixin
This class provides methods for creating and modifying bones. The methods automatically take care of managing the above mentioned tables that map bones to their owning rigs.
self.new_bone(name)
: creates a new bone with the specified name, and returs the actual name of the new bone.self.copy_bone(old_name, new_name, parent=False, bbone=False)
: copies the specified bone (optionally including parent and b-bone attributes); returns new name.self.copy_bone_properties(source_name, target_name)
: copies rotation mode, locks and custom properties from source to target.self.rename_bone(old_name, new_name)
: renames the specified bone; returns the actual new name.self.get_bone(name)
: looks up the edit or pose bone reference based on current mode. If the name is None, returns None.self.get_bone_parent(name)
: retrieves the name of the parent bone, or None.self.set_bone_parent(name, parent_name, use_connect=False)
: sets the parent of the given bone and the connect flag.use_connect=None
preserves the current state of the connect flag.self.parent_bone_chain(bone_names, use_connect=None)
: connects the listed bones into a chain viaset_bone_parent
.For more information see utils/bones.py
MechanismUtilityMixin
This class provides methods for creating the mechanism of the rig.
self.make_property(bone_name, prop_name, default_value, ...)
: creates a custom property with limits etc.self.make_constraint(bone_name, type, subtarget=None ...)
: creates a constraint that connects two bones.self.make_driver(owner, property, ...)
: creates a driver; the armature object is supplied as default for variable lookup.For more information see utils/mechanism.py
Generator plugins
In order to allow hooking into the generation stage system by objects other than rigs, a GeneratorPlugin class is defined. Its subclasses are singletons keyed by their constructor arguments (which include the generator instance), and their stage callbacks are called after all the rigs.
Currently the only such plugin is the script generator.
ScriptGenerator class
Generating the python script is a specialized task, and shouldn't be mixed with the main generation driver code, so it is moved into a separate class. It interfaces with the generation process by being a generator plugin.
Instead of providing data for script generation by returning values from
generate()
, new rigs should directly call methods of the script generator. Some of them replicate the functions of the old values:script.add_panel_code(str_list)
: adds raw code lines to the panel.script.add_imports(str_list)
: adds lines to the import section of the script.script.add_utilities(str_list)
: adds code to the utility function section of the script.script.register_classes(str_list)
: adds class names to register.script.register_driver_functions(str_list)
: adds driver function names to register.script.register_property(name, definition)
: adds a custom RNA property definition.However, the new interface also allowed creating higher level functions:
script.add_panel_selected_check(control_names)
: generates panel code that checks if one of the listed controls is selected.add_panel_code
.script.add_panel_custom_prop(bone_name, prop_name, ...)
: generates alayout.prop
call for the given bone and custom property.add_panel_selected_check
.script.add_panel_operator(operator_name, properties={...}, ...)
: generates alayout.operator
call.layout.operator
, while theproperties
map is used to initialize properties of the operator itself. Must be preceeded by a call toadd_panel_selected_check
.Example:
Added subscribers: @angavrilov, @cessen, @icappiello, @LucioRossi, @pioverfour
Added subscriber: @JulienDuroure
Added subscriber: @TakeshiFunahashi
Added subscriber: @ChristopheSwolfs
Added subscriber: @Mets
Changed status from 'Open' to: 'Resolved'
Added subscriber: @T.R.O.Nunes