[Bf-extensions-cvs] [011f7afd] master: Rigify: refactor feature sets to avoid modifying global path.

Alexander Gavrilov noreply at git.blender.org
Wed May 29 09:42:30 CEST 2019


Commit: 011f7afde41efc6fa16084e0f406f5287dc3c481
Author: Alexander Gavrilov
Date:   Tue May 28 22:57:36 2019 +0300
Branches: master
https://developer.blender.org/rBA011f7afde41efc6fa16084e0f406f5287dc3c481

Rigify: refactor feature sets to avoid modifying global path.

Instead of adding the feature set installation directory
to the global path, and thus inserting the modules into
the top level namespace, add an empty rigify.feature_sets
package and use __path__ to redirect the module loader
to read its sub-modules from the feature set directory.

Now feature set modules are effectively installed into
that package and loaded as 'rigify.feature_sets.foo'.

As an aside, clean up loading code to avoid weird path
manipulations, add more safety checks when installing sets,
and add a way for sets to expose a user-friendly name.

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

M	rigify/__init__.py
R058	rigify/feature_sets.py	rigify/feature_set_list.py
A	rigify/feature_sets/__init__.py
M	rigify/metarig_menu.py
M	rigify/rig_lists.py
M	rigify/ui.py
M	rigify/utils/__init__.py
M	rigify/utils/rig.py

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

diff --git a/rigify/__init__.py b/rigify/__init__.py
index 092b882b..8bb09357 100644
--- a/rigify/__init__.py
+++ b/rigify/__init__.py
@@ -35,11 +35,11 @@ if "bpy" in locals():
     importlib.reload(generate)
     importlib.reload(ui)
     importlib.reload(utils)
+    importlib.reload(feature_set_list)
     importlib.reload(metarig_menu)
     importlib.reload(rig_lists)
-    importlib.reload(feature_sets)
 else:
-    from . import (utils, rig_lists, generate, ui, metarig_menu, feature_sets)
+    from . import (utils, feature_set_list, rig_lists, generate, ui, metarig_menu)
 
 import bpy
 import sys
@@ -132,18 +132,16 @@ class RigifyPreferences(AddonPreferences):
         if self.legacy_mode:
             return
 
-        feature_sets_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
+        set_list = feature_set_list.get_installed_list()
 
-        if os.path.exists(feature_sets_path):
-            if feature_sets_path not in sys.path:
-                sys.path.append(feature_sets_path)
+        if len(set_list) > 0:
             # Reload rigs
             print('Reloading external rigs...')
-            rig_lists.get_external_rigs(feature_sets_path)
+            rig_lists.get_external_rigs(set_list)
 
             # Reload metarigs
             print('Reloading external metarigs...')
-            metarig_menu.get_external_metarigs(feature_sets_path)
+            metarig_menu.get_external_metarigs(set_list)
 
             # Re-register rig paramaters
             register_rig_parameters()
@@ -198,13 +196,11 @@ class RigifyPreferences(AddonPreferences):
         op.data_path = 'addon_prefs.show_rigs_folder_expanded'
         sub.label(text='{}: {}'.format('Rigify', 'External feature sets'))
         if rigs_expand:
-            if os.path.exists(os.path.join(bpy.utils.script_path_user(), 'rigify')):
-                feature_sets_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
-                for fs in os.listdir(feature_sets_path):
-                    row = col.row()
-                    row.label(text=fs)
-                    op = row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL')
-                    op.featureset = fs
+            for fs in feature_set_list.get_installed_list():
+                row = col.split(factor=0.8)
+                row.label(text=feature_set_list.get_ui_name(fs))
+                op = row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL')
+                op.featureset = fs
             row = col.row(align=True)
             row.operator("wm.rigify_add_feature_set", text="Install Feature Set from File...", icon='FILEBROWSER')
 
@@ -372,7 +368,7 @@ def register():
 
     # Sub-modules.
     ui.register()
-    feature_sets.register()
+    feature_set_list.register()
     metarig_menu.register()
 
     # Classes.
@@ -383,7 +379,7 @@ def register():
     bpy.types.Armature.rigify_layers = CollectionProperty(type=RigifyArmatureLayer)
 
     bpy.types.Armature.active_feature_set = EnumProperty(
-        items=feature_sets.feature_set_items,
+        items=feature_set_list.feature_set_items,
         name="Feature Set",
         description="Restrict the rig list to a specific custom feature set"
         )
@@ -538,4 +534,4 @@ def unregister():
     # Sub-modules.
     metarig_menu.unregister()
     ui.unregister()
-    feature_sets.unregister()
+    feature_set_list.unregister()
diff --git a/rigify/feature_sets.py b/rigify/feature_set_list.py
similarity index 58%
rename from rigify/feature_sets.py
rename to rigify/feature_set_list.py
index 3a7c2bec..fb7075eb 100644
--- a/rigify/feature_sets.py
+++ b/rigify/feature_set_list.py
@@ -20,21 +20,95 @@ import bpy
 from bpy.props import StringProperty
 import os
 import re
+import importlib
 from zipfile import ZipFile
 from shutil import rmtree
 
+from . import feature_sets
+
+
+DEFAULT_NAME = 'rigify'
+
+INSTALL_PATH = feature_sets._install_path()
+NAME_PREFIX = feature_sets.__name__.split('.')
+
+
+def get_install_path(*, create=False):
+    if not os.path.exists(INSTALL_PATH):
+        if create:
+            os.makedirs(INSTALL_PATH, exist_ok=True)
+        else:
+            return None
+
+    return INSTALL_PATH
+
+
+def get_installed_list():
+    features_path = get_install_path()
+    if not features_path:
+        return []
+
+    sets = []
+
+    for fs in os.listdir(features_path):
+        if fs and fs[0] != '.' and fs != DEFAULT_NAME:
+            fs_path = os.path.join(features_path, fs)
+            if os.path.isdir(fs_path):
+                sets.append(fs)
+
+    return sets
+
+
+def get_module(feature_set):
+    return importlib.import_module('.'.join([*NAME_PREFIX, feature_set]))
+
+
+def get_module_safe(feature_set):
+    try:
+        return get_module(feature_set)
+    except:
+        return None
+
+
+def get_dir_path(feature_set, *extra_items):
+    base_dir = os.path.join(INSTALL_PATH, feature_set, *extra_items)
+    base_path = [*NAME_PREFIX, feature_set, *extra_items]
+    return base_dir, base_path
+
+
+def get_info_dict(feature_set):
+    module = get_module_safe(feature_set)
+
+    if module and hasattr(module, 'rigify_info'):
+        data = module.rigify_info
+        if isinstance(data, dict):
+            return data
+
+    return {}
+
+
+def get_ui_name(feature_set):
+    # Try to get user-defined name
+    info = get_info_dict(feature_set)
+    if 'name' in info:
+        return info['name']
+
+    # Default name based on directory
+    name = re.sub(r'[_.-]', ' ', feature_set)
+    name = re.sub(r'(?<=\d) (?=\d)', '.', name)
+    return name.title()
+
 
 def feature_set_items(scene, context):
     """Get items for the Feature Set EnumProperty"""
-    feature_sets_path = os.path.join(
-        bpy.utils.script_path_user(), 'rigify')
     items = [('all',)*3, ('rigify',)*3, ]
-    if os.path.exists(feature_sets_path):
-        for fs in os.listdir(feature_sets_path):
-            items.append((fs,)*3)
+
+    for fs in get_installed_list():
+        items.append((fs,)*3)
 
     return items
 
+
 def verify_feature_set_archive(zipfile):
     """Verify that the zip file contains one root directory, and some required files."""
     dirname = None
@@ -58,6 +132,7 @@ def verify_feature_set_archive(zipfile):
 
     return dirname, init_found, data_found
 
+
 class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
     bl_idname = "wm.rigify_add_feature_set"
     bl_label = "Add External Feature Set"
@@ -78,8 +153,8 @@ class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
     def execute(self, context):
         addon_prefs = context.preferences.addons[__package__].preferences
 
-        rigify_config_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
-        os.makedirs(rigify_config_path, exist_ok=True)
+        rigify_config_path = get_install_path(create=True)
+
         with ZipFile(bpy.path.abspath(self.filepath), 'r') as zip_archive:
             base_dirname, init_found, data_found = verify_feature_set_archive(zip_archive)
 
@@ -87,16 +162,35 @@ class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
                 self.report({'ERROR'}, "The feature set archive must contain one base directory.")
                 return {'CANCELLED'}
 
-            if not re.fullmatch(r'[a-zA-Z_][a-zA-Z_0-9-]*', base_dirname):
-                self.report({'ERROR'}, "The feature set archive has invalid characters in the base directory name: '%s'." % (base_dirname))
+            # Patch up some invalid characters to allow using 'Download ZIP' on GitHub.
+            fixed_dirname = re.sub(r'[.-]', '_', base_dirname)
+
+            if not re.fullmatch(r'[a-zA-Z][a-zA-Z_0-9]*', fixed_dirname):
+                self.report({'ERROR'}, "The feature set archive base directory name is not a valid identifier: '%s'." % (base_dirname))
+                return {'CANCELLED'}
+
+            if fixed_dirname == DEFAULT_NAME:
+                self.report({'ERROR'}, "The '%s' name is not allowed for feature sets." % (DEFAULT_NAME))
                 return {'CANCELLED'}
 
             if not init_found or not data_found:
                 self.report({'ERROR'}, "The feature set archive has no rigs or metarigs, or is missing __init__.py.")
                 return {'CANCELLED'}
 
+            base_dir = os.path.join(rigify_config_path, base_dirname)
+            fixed_dir = os.path.join(rigify_config_path, fixed_dirname)
+
+            for path, name in [(base_dir, base_dirname), (fixed_dir, fixed_dirname)]:
+                if os.path.exists(path):
+                    self.report({'ERROR'}, "Feature set directory already exists: '%s'." % (name))
+                    return {'CANCELLED'}
+
+            # Unpack the validated archive and fix the directory name if necessary
             zip_archive.extractall(rigify_config_path)
 
+            if base_dir != fixed_dir:
+                os.rename(base_dir, fixed_dir)
+
         addon_prefs.machin = bpy.props.EnumProperty(items=(('a',)*3, ('b',)*3, ('c',)*3),)
 
         addon_prefs.update_external_rigs()
@@ -121,9 +215,11 @@ class DATA_OT_rigify_remove_feature_set(bpy.types.Operator):
     def execute(self, context):
         addon_prefs = context.preferences.addons[__package__].preferences
 
-        rigify_config_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
-        if os.path.exists(os.path.join(rigify_config_path, self.featureset)):
-            rmtree(os.path.join(rigify_config_path, self.featureset))
+        rigify_config_path = get_install_path()
+        if rigify_config_path:
+            set_path = os.path.join(rigify_config_path, self.featureset)
+            if os.path.exists(set_path):
+                rmtree(set_path)
 
         addon_prefs.update_external_rigs()
         return {'FINISHED'}
diff --git a/rigify/feature_sets/__init__.py b/rigify/feature_sets/__init__.py
new file mode 100644
index 00000000..e9981d52
--- /dev/null
+++ b/rigify/feature_sets/__init__.py
@@ -0,0 +1,29 @@
+#====================== BEGIN GPL LICENSE BLOCK ======================
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Soft

@@ Diff output truncated at 10240 characters. @@



More information about the Bf-extensions-cvs mailing list