From ecf30de46c368ffddad259c125402a38e6093382 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov <angavrilov@gmail.com>
Date: Sun, 19 Sep 2021 19:16:20 +0300
Subject: [PATCH] Rigify: support executing an arbitrary script after
 generation.

Just in case the user wants to apply some custom changes to the
generated rig, allow executing a text datablock as a python script
after generation completes.

The script is executed with the generated rig active and in object
mode. When executed by rigify, the generator instance is available
via `rigify.get_generator()`. Outside of Rigify generation the return
value is None.
---
 rigify/__init__.py      |  9 +++++++++
 rigify/base_generate.py |  2 ++
 rigify/generate.py      | 17 ++++++++++++++++-
 rigify/ui.py            |  2 ++
 4 files changed, 29 insertions(+), 1 deletion(-)

diff --git a/rigify/__init__.py b/rigify/__init__.py
index 1bb633f6c..dd55be045 100644
--- a/rigify/__init__.py
+++ b/rigify/__init__.py
@@ -140,6 +140,11 @@ from bpy.props import (
 )
 
 
+def get_generator():
+    """Returns the currently active generator instance."""
+    return base_generate.BaseGenerator.instance
+
+
 class RigifyFeatureSets(bpy.types.PropertyGroup):
     name: bpy.props.StringProperty()
     module_name: bpy.props.StringProperty()
@@ -534,6 +539,10 @@ def register():
         name="Rigify Target Rig UI",
         description="Defines the UI to overwrite. If unset, 'rig_ui.py' will be used")
 
+    bpy.types.Armature.rigify_finalize_script = PointerProperty(type=bpy.types.Text,
+        name="Finalize Script",
+        description="Run this script after generation to apply user-specific changes")
+
     bpy.types.Armature.rigify_rig_basename = StringProperty(name="Rigify Rig Name",
         description="Defines the name of the Rig. If unset, in 'new' mode 'rig' will be used, in 'overwrite' mode the target rig name will be used",
         default="")
diff --git a/rigify/base_generate.py b/rigify/base_generate.py
index 162422626..7bdb8b0e1 100644
--- a/rigify/base_generate.py
+++ b/rigify/base_generate.py
@@ -189,6 +189,8 @@ class LegacyRig(base_rig.BaseRig):
 class BaseGenerator:
     """Base class for the main generator object. Contains rig and plugin management code."""
 
+    instance = None
+
     def __init__(self, context, metarig):
         self.context = context
         self.scene = context.scene
diff --git a/rigify/generate.py b/rigify/generate.py
index 5e95bd99f..ad8f43b59 100644
--- a/rigify/generate.py
+++ b/rigify/generate.py
@@ -573,6 +573,14 @@ class Generator(base_generate.BaseGenerator):
         # Clear any transient errors in drivers
         refresh_all_drivers()
 
+        #----------------------------------
+        # Execute the finalize script
+
+        if metarig.data.rigify_finalize_script:
+            bpy.ops.object.mode_set(mode='OBJECT')
+            exec(metarig.data.rigify_finalize_script.as_string(), {})
+            bpy.ops.object.mode_set(mode='OBJECT')
+
         #----------------------------------
         # Restore active collection
         view_layer.active_layer_collection = self.layer_collection
@@ -587,7 +595,11 @@ def generate_rig(context, metarig):
     metarig.data.pose_position = 'REST'
 
     try:
-        Generator(context, metarig).generate()
+        generator = Generator(context, metarig)
+
+        base_generate.BaseGenerator.instance = generator
+
+        generator.generate()
 
         metarig.data.pose_position = rest_backup
 
@@ -601,6 +613,9 @@ def generate_rig(context, metarig):
         # Continue the exception
         raise e
 
+    finally:
+        base_generate.BaseGenerator.instance = None
+
 
 def create_selection_set_for_rig_layer(
         rig: bpy.types.Object,
diff --git a/rigify/ui.py b/rigify/ui.py
index 6ba455daf..c9592677f 100644
--- a/rigify/ui.py
+++ b/rigify/ui.py
@@ -177,6 +177,8 @@ class DATA_PT_rigify_buttons(bpy.types.Panel):
                 if armature_id_store.rigify_generate_mode == 'new':
                     row.enabled = False
 
+                col.prop(armature_id_store, "rigify_finalize_script", text="Run Script")
+
         elif obj.mode == 'EDIT':
             # Build types list
             build_type_list(context, id_store.rigify_types)
-- 
GitLab