[Bf-extensions-cvs] [dddf346f] master: Rigify: port the ability to generate Action constraints from CloudRig.

Alexander Gavrilov noreply at git.blender.org
Mon Nov 7 19:18:02 CET 2022


Commit: dddf346f1adf49f51351ead084fbb7d5e1a8f9a0
Author: Alexander Gavrilov
Date:   Tue Oct 25 16:12:18 2022 +0300
Branches: master
https://developer.blender.org/rBAdddf346f1adf49f51351ead084fbb7d5e1a8f9a0

Rigify: port the ability to generate Action constraints from CloudRig.

CloudRig has a feature that allows the user to automatically generate
Action constraints that move bones of the rig based on the position
of other bones. This is done by adding and configuring the actions
in a UI panel of the metarig. The feature also supports corrective
actions that activate based on the state of two other actions.

This ports the feature to base Rigify with the necessary changes in
code organization and style, and replacing CloudRig-specific code.

There are also some functional changes:

- The order of action constraints is reversed.
- The way symmetry of LOCATION_X is handed is changed to
  match how Paste Pose Flipped works.
- The action slot UI is shown even without a generated rig.
- More alerts in the UI, e.g. for duplicate rows.

Differential Revision: https://developer.blender.org/D16336

===================================================================

M	rigify/__init__.py
M	rigify/generate.py
M	rigify/operators/__init__.py
A	rigify/operators/action_layers.py
A	rigify/operators/generic_ui_list.py
A	rigify/utils/action_layers.py

===================================================================

diff --git a/rigify/__init__.py b/rigify/__init__.py
index 1b5c2680..9bb2eab4 100644
--- a/rigify/__init__.py
+++ b/rigify/__init__.py
@@ -44,6 +44,7 @@ initial_load_order = [
     'rig_lists',
     'metarig_menu',
     'rig_ui_template',
+    'utils.action_layers',
     'generate',
     'rot_mode',
     'operators',
diff --git a/rigify/generate.py b/rigify/generate.py
index 4f30adff..7060c448 100644
--- a/rigify/generate.py
+++ b/rigify/generate.py
@@ -16,6 +16,7 @@ from .utils.misc import gamma_correct, select_object, ArmatureObject, verify_arm
 from .utils.collections import (ensure_collection, list_layer_collections,
                                 filter_layer_collections_by_object)
 from .utils.rig import get_rigify_type, get_rigify_layers
+from .utils.action_layers import ActionLayerBuilder
 
 from . import base_generate
 from . import rig_ui_template
@@ -37,6 +38,7 @@ class Timer:
 
 class Generator(base_generate.BaseGenerator):
     usable_collections: list[bpy.types.LayerCollection]
+    action_layers: ActionLayerBuilder
 
     def __init__(self, context, metarig):
         super().__init__(context, metarig)
@@ -456,6 +458,7 @@ class Generator(base_generate.BaseGenerator):
         obj.data["rig_id"] = self.rig_id
 
         self.script = rig_ui_template.ScriptGenerator(self)
+        self.action_layers = ActionLayerBuilder(self)
 
         ###########################################
         bpy.ops.object.mode_set(mode='OBJECT')
diff --git a/rigify/operators/__init__.py b/rigify/operators/__init__.py
index 4cedf4a0..cce9de4d 100644
--- a/rigify/operators/__init__.py
+++ b/rigify/operators/__init__.py
@@ -5,6 +5,8 @@ import importlib
 
 # Submodules to load during register
 submodules = (
+    'generic_ui_list',
+    'action_layers',
     'copy_mirror_parameters',
     'upgrade_face',
 )
diff --git a/rigify/operators/action_layers.py b/rigify/operators/action_layers.py
new file mode 100644
index 00000000..c089722a
--- /dev/null
+++ b/rigify/operators/action_layers.py
@@ -0,0 +1,525 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import bpy
+
+from typing import Tuple, Optional, Sequence
+
+from bpy.types import PropertyGroup, Action, UIList, UILayout, Context, Panel, Operator, Armature
+from bpy.props import (EnumProperty, IntProperty, BoolProperty, StringProperty, FloatProperty,
+                       PointerProperty, CollectionProperty)
+
+from .generic_ui_list import draw_ui_list
+
+from ..utils.naming import mirror_name
+from ..utils.action_layers import ActionSlotBase
+
+
+def get_action_slots(arm: Armature) -> Sequence['ActionSlot']:
+    # noinspection PyUnresolvedReferences
+    return arm.rigify_action_slots
+
+
+def get_action_slots_active(arm: Armature) -> tuple[Sequence['ActionSlot'], int]:
+    # noinspection PyUnresolvedReferences
+    return arm.rigify_action_slots, arm.rigify_active_action_slot
+
+
+def poll_trigger_action(_self, action):
+    """Whether an action can be used as a corrective action's trigger or not."""
+    armature_id_store = bpy.context.object.data
+    assert isinstance(armature_id_store, Armature)
+
+    slots, idx = get_action_slots_active(armature_id_store)
+
+    active_slot = slots[idx] if 0 <= idx < len(slots) else None
+
+    # If this action is the same as the active slot's action, don't show it.
+    if active_slot and action == active_slot.action:
+        return False
+
+    # If this action is used by any other action slot, show it.
+    for slot in slots:
+        if slot.action == action and not slot.is_corrective:
+            return True
+
+    return False
+
+
+class ActionSlot(PropertyGroup, ActionSlotBase):
+    action: PointerProperty(
+        name="Action",
+        type=Action,
+        description="Action to apply to the rig via constraints"
+    )
+
+    enabled: BoolProperty(
+        name="Enabled",
+        description="Create constraints for this action on the generated rig",
+        default=True
+    )
+
+    symmetrical: BoolProperty(
+        name="Symmetrical",
+        description="Apply the same setup but mirrored to the opposite side control, shown in "
+                    "parentheses. Bones will only be affected by the control with the same side "
+                    "(eg., .L bones will only be affected by the .L control). Bones without a "
+                    "side in their name (so no .L or .R) will be affected by both controls "
+                    "with 0.5 influence each",
+        default=True
+    )
+
+    subtarget: StringProperty(
+        name="Control Bone",
+        description="Select a bone on the generated rig which will drive this action"
+    )
+
+    transform_channel: EnumProperty(name="Transform Channel",
+                                    items=[("LOCATION_X", "X Location", "X Location"),
+                                           ("LOCATION_Y", "Y Location", "Y Location"),
+                                           ("LOCATION_Z", "Z Location", "Z Location"),
+                                           ("ROTATION_X", "X Rotation", "X Rotation"),
+                                           ("ROTATION_Y", "Y Rotation", "Y Rotation"),
+                                           ("ROTATION_Z", "Z Rotation", "Z Rotation"),
+                                           ("SCALE_X", "X Scale", "X Scale"),
+                                           ("SCALE_Y", "Y Scale", "Y Scale"),
+                                           ("SCALE_Z", "Z Scale", "Z Scale")],
+                                    description="Transform channel",
+                                    default="LOCATION_X")
+
+    target_space: EnumProperty(
+        name="Transform Space",
+        items=[("WORLD", "World Space", "World Space"),
+               ("POSE", "Pose Space", "Pose Space"),
+               ("LOCAL_WITH_PARENT", "Local With Parent", "Local With Parent"),
+               ("LOCAL", "Local Space", "Local Space")],
+        default="LOCAL"
+    )
+
+    def update_frame_start(self, _context):
+        if self.frame_start > self.frame_end:
+            self.frame_end = self.frame_start
+
+    frame_start: IntProperty(
+        name="Start Frame",
+        description="First frame of the action's timeline",
+        update=update_frame_start
+    )
+
+    def update_frame_end(self, _context):
+        if self.frame_end < self.frame_start:
+            self.frame_start = self.frame_end
+
+    frame_end: IntProperty(
+        name="End Frame",
+        default=2,
+        description="Last frame of the action's timeline",
+        update=update_frame_end
+    )
+
+    trans_min: FloatProperty(
+        name="Min",
+        default=-0.05,
+        description="Value that the transformation value must reach to put the action's timeline"
+                    "to the first frame. Rotations are in degrees"
+    )
+
+    trans_max: FloatProperty(
+        name="Max",
+        default=0.05,
+        description="Value that the transformation value must reach to put the action's timeline"
+                    "to the last frame. Rotations are in degrees"
+    )
+
+    is_corrective: BoolProperty(
+        name="Corrective",
+        description="Indicate that this is a corrective action. Corrective actions will activate"
+                    "based on the activation of two other actions (using End Frame if both inputs"
+                    "are at their End Frame, and Start Frame if either is at Start Frame)"
+    )
+
+    trigger_action_a: PointerProperty(
+        name="Trigger A",
+        type=Action,
+        description="Action whose activation will trigger the corrective action",
+        poll=poll_trigger_action
+    )
+
+    trigger_action_b: PointerProperty(
+        name="Trigger B",
+        description="Action whose activation will trigger the corrective action",
+        type=Action,
+        poll=poll_trigger_action
+    )
+
+    show_action_a: BoolProperty(name="Show Settings")
+    show_action_b: BoolProperty(name="Show Settings")
+
+
+def find_slot_by_action(metarig_data: Armature, action) -> Tuple[Optional[ActionSlot], int]:
+    """Find the ActionSlot in the rig which targets this action."""
+    if not action:
+        return None, -1
+
+    for i, slot in enumerate(get_action_slots(metarig_data)):
+        if slot.action == action:
+            return slot, i
+    else:
+        return None, -1
+
+
+def find_duplicate_slot(metarig_data: Armature, action_slot: ActionSlot) -> Optional[ActionSlot]:
+    """Find a different ActionSlot in the rig which has the same action."""
+
+    for slot in get_action_slots(metarig_data):
+        if slot.action == action_slot.action and slot != action_slot:
+            return slot
+
+    return None
+
+# =============================================
+# Operators
+
+
+class RIGIFY_OT_action_create(Operator):
+    """Create new Action"""
+    # This is needed because bpy.ops.action.new() has a poll function that blocks
+    # the operator unless it's drawn in an animation UI panel.
+
+    bl_idname = "object.rigify_action_create"
+    bl_label = "New"
+    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
+
+    def execute(self, context):
+        armature_id_store = context.object.data
+        assert isinstance(armature_id_store, Armature)
+        action_slots, action_slot_idx = get_action_slots_active(armature_id_store)
+        action_slot = action_slots[action_slot_idx]
+        action = bpy.data.actions.new(name="Action")
+        action_slot.action = action
+        return {'FINISHED'}
+
+
+class RIGIFY_OT_jump_to_action_slot(Operator):
+    """Set Active Action Slot Index"""
+
+    bl_idname = "object.rigify_jump_to_action_slot"
+    bl_label = "Jump to Action Slot"
+    bl_options = {'REGISTER', 'UNDO', 'INTERNAL'}
+
+    to_index: IntProperty()
+
+    def execute(self, context):
+        armature_id_store = context.object.data
+        armature_id_store.rigify_active_action_slot = self.to_index
+        return {'FINISHED'}
+
+
+# =============================================
+# UI Panel
+
+class RIGIFY_UL_action_slots(UIList):
+    def draw_item(self, context: Context, layout: UILayout, data: Armature,
+                  action_slot: ActionSlot, icon, active_data, active_propname: str,
+              

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list