Skip to content

Commit 03be04d

Browse files
committed
feat: import custom rigs from external folder
1 parent 6ff5fde commit 03be04d

File tree

4 files changed

+152
-19
lines changed

4 files changed

+152
-19
lines changed

__init__.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import os
4646
from bpy.types import AddonPreferences
4747
from bpy.props import BoolProperty
48+
from bpy.props import StringProperty
4849

4950

5051
class RigifyPreferences(AddonPreferences):
@@ -118,15 +119,48 @@ def update_legacy(self, context):
118119

119120
register()
120121

122+
def update_external_rigs(self, context):
123+
124+
if self.legacy_mode:
125+
return
126+
127+
custom_rigs_folder = bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder
128+
129+
if custom_rigs_folder not in sys.path:
130+
sys.path.append(bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder)
131+
132+
external_rigs_dict = rig_lists.get_external_rigs()
133+
if external_rigs_dict:
134+
135+
rig_lists.rigs_dict['external'] = external_rigs_dict
136+
137+
# Add external rig parameters
138+
for rig in external_rigs_dict['rig_list']:
139+
r = utils.get_rig_type(rig, custom_rigs_folder)
140+
try:
141+
r.add_parameters(RigifyParameters)
142+
except AttributeError:
143+
pass
144+
121145
legacy_mode = BoolProperty(
122146
name='Rigify Legacy Mode',
123147
description='Select if you want to use Rigify in legacy mode',
124148
default=False,
125149
update=update_legacy
126150
)
127151

152+
custom_rigs_folder = StringProperty(
153+
name='Rigify Custom Rigs',
154+
description='Folder Containing User Defined Rig Types',
155+
default='',
156+
subtype='DIR_PATH',
157+
update=update_external_rigs
158+
)
159+
128160
show_expanded = BoolProperty()
129161

162+
show_rigs_folder_expanded = BoolProperty()
163+
130164
def draw(self, context):
131165
layout = self.layout
132166
column = layout.column()
@@ -149,6 +183,24 @@ def draw(self, context):
149183
sub.prop(self, 'legacy_mode')
150184

151185
if expand:
186+
split = col.row().split(percentage=0.15)
187+
split.label('Description:')
188+
split.label(text='This is the folder containing user defined Rig Types')
189+
190+
box = column.box()
191+
rigs_expand = getattr(self, 'show_rigs_folder_expanded')
192+
icon = 'TRIA_DOWN' if rigs_expand else 'TRIA_RIGHT'
193+
col = box.column()
194+
row = col.row()
195+
sub = row.row()
196+
sub.context_pointer_set('addon_prefs', self)
197+
sub.alignment = 'LEFT'
198+
op = sub.operator('wm.context_toggle', text='', icon=icon,
199+
emboss=False)
200+
op.data_path = 'addon_prefs.show_rigs_folder_expanded'
201+
row.prop(self, 'custom_rigs_folder')
202+
203+
if rigs_expand:
152204
split = col.row().split(percentage=0.15)
153205
split.label('Description:')
154206
split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.')
@@ -333,6 +385,10 @@ def update_mode(self, context):
333385
except AttributeError:
334386
pass
335387

388+
external_rigs_folder = bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder
389+
if external_rigs_folder and not 'external' in rig_lists.rigs_dict:
390+
bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder = external_rigs_folder
391+
336392

337393
def unregister():
338394
del bpy.types.PoseBone.rigify_type
@@ -361,6 +417,9 @@ def unregister():
361417
bpy.utils.unregister_class(RigifySelectionColors)
362418

363419
bpy.utils.unregister_class(RigifyArmatureLayer)
420+
421+
if bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder in sys.path:
422+
sys.path.remove(bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder)
364423
bpy.utils.unregister_class(RigifyPreferences)
365424

366425
metarig_menu.unregister()

rig_lists.py

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,35 @@
1717
#======================= END GPL LICENSE BLOCK ========================
1818

1919
import os
20+
import bpy
2021

2122
from . import utils
2223

2324

24-
def get_rig_list(path):
25+
def get_rig_list(path, mode='relative'):
2526
""" Recursively searches for rig types, and returns a list.
27+
28+
:param path
29+
:type path:str
30+
:param mode: path is absolute or relative?
31+
:type mode: str
2632
"""
33+
34+
if mode == 'relative':
35+
base_path = ''
36+
MODULE_DIR = os.path.dirname(__file__)
37+
RIG_DIR_ABS = os.path.join(MODULE_DIR, utils.RIG_DIR)
38+
SEARCH_DIR_ABS = os.path.join(RIG_DIR_ABS, path)
39+
elif mode == 'absolute':
40+
base_path = path
41+
SEARCH_DIR_ABS = path
42+
else:
43+
return
44+
2745
rigs_dict = dict()
2846
rigs = []
29-
implementation_rigs = []
30-
MODULE_DIR = os.path.dirname(__file__)
31-
RIG_DIR_ABS = os.path.join(MODULE_DIR, utils.RIG_DIR)
32-
SEARCH_DIR_ABS = os.path.join(RIG_DIR_ABS, path)
47+
impl_rigs = []
48+
3349
files = os.listdir(SEARCH_DIR_ABS)
3450
files.sort()
3551

@@ -45,29 +61,36 @@ def get_rig_list(path):
4561

4662
if is_dir:
4763
# Check directories
48-
module_name = os.path.join(path, f).replace(os.sep, ".")
49-
rig = utils.get_rig_type(module_name)
64+
if mode == 'relative':
65+
module_name = os.path.join(path, f).replace(os.sep, ".")
66+
rig = utils.get_rig_type(module_name, base_path=base_path)
67+
else:
68+
module_name = "__init__"
69+
rig = utils.get_rig_type(module_name, base_path=base_path + f + os.sep)
5070
# Check if it's a rig itself
5171
if hasattr(rig, "Rig"):
5272
rigs += [f]
5373
else:
5474
# Check for sub-rigs
55-
sub_dict = get_rig_list(os.path.join(path, f, "")) # "" adds a final slash
75+
sub_dict = get_rig_list(os.path.join(path, f, ""), mode=mode) # "" adds a final slash
5676
rigs.extend(["%s.%s" % (f, l) for l in sub_dict['rig_list']])
57-
implementation_rigs.extend(["%s.%s" % (f, l) for l in sub_dict['implementation_rigs']])
77+
impl_rigs.extend(["%s.%s" % (f, l) for l in sub_dict['implementation_rigs']])
5878
elif f.endswith(".py"):
5979
# Check straight-up python files
6080
t = f[:-3]
61-
module_name = os.path.join(path, t).replace(os.sep, ".")
62-
rig = utils.get_rig_type(module_name)
81+
if mode == 'relative':
82+
module_name = os.path.join(path, t).replace(os.sep, ".")
83+
else:
84+
module_name = t
85+
rig = utils.get_rig_type(module_name, base_path=base_path)
6386
if hasattr(rig, "Rig"):
6487
rigs += [t]
6588
if hasattr(rig, 'IMPLEMENTATION') and rig.IMPLEMENTATION:
66-
implementation_rigs += [t]
89+
impl_rigs += [t]
6790
rigs.sort()
6891

6992
rigs_dict['rig_list'] = rigs
70-
rigs_dict['implementation_rigs'] = implementation_rigs
93+
rigs_dict['implementation_rigs'] = impl_rigs
7194

7295
return rigs_dict
7396

@@ -87,3 +110,12 @@ def get_collection_list(rig_list):
87110
implementation_rigs = rigs_dict['implementation_rigs']
88111
collection_list = get_collection_list(rig_list)
89112
col_enum_list = [("All", "All", ""), ("None", "None", "")] + [(c, c, "") for c in collection_list]
113+
114+
115+
def get_external_rigs():
116+
external_folder = bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder
117+
if external_folder:
118+
external_rigs_dict = get_rig_list(external_folder, mode='absolute')
119+
external_rig_list = external_rigs_dict['rig_list']
120+
external_implementation_rigs = external_rigs_dict['implementation_rigs']
121+
return external_rigs_dict

ui.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,18 @@ def draw(self, context):
173173
a = id_store.rigify_types.add()
174174
a.name = r
175175

176+
if 'external' in rig_lists.rigs_dict:
177+
for r in rig_lists.rigs_dict['external']['rig_list']:
178+
if collection_name == "All":
179+
a = id_store.rigify_types.add()
180+
a.name = r
181+
elif r.startswith(collection_name + '.'):
182+
a = id_store.rigify_types.add()
183+
a.name = r
184+
elif (collection_name == "None") and ("." not in r):
185+
a = id_store.rigify_types.add()
186+
a.name = r
187+
176188
# Rig type list
177189
row = layout.row()
178190
row.template_list("UI_UL_list", "rigify_types", id_store, "rigify_types", id_store, 'rigify_active_type')
@@ -598,14 +610,32 @@ def draw(self, context):
598610
a = id_store.rigify_types.add()
599611
a.name = r
600612

613+
if 'external' in rig_lists.rigs_dict:
614+
for r in rig_lists.rigs_dict['external']['rig_list']:
615+
if r in rig_lists.rigs_dict['external']['implementation_rigs']:
616+
continue
617+
if collection_name == "All":
618+
a = id_store.rigify_types.add()
619+
a.name = r
620+
elif r.startswith(collection_name + '.'):
621+
a = id_store.rigify_types.add()
622+
a.name = r
623+
elif collection_name == "None" and len(r.split('.')) == 1:
624+
a = id_store.rigify_types.add()
625+
a.name = r
626+
601627
# Rig type field
602628
row = layout.row()
603629
row.prop_search(bone, "rigify_type", id_store, "rigify_types", text="Rig type:")
604630

605631
# Rig type parameters / Rig type non-exist alert
606632
if rig_name != "":
607633
try:
608-
rig = get_rig_type(rig_name)
634+
if rig_name in rig_lists.rigs_dict['external']['rig_list']:
635+
custom_rigs_folder = bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder
636+
rig = get_rig_type(rig_name, custom_rigs_folder)
637+
else:
638+
rig = get_rig_type(rig_name)
609639
rig.Rig
610640
except (ImportError, AttributeError):
611641
row = layout.row()
@@ -812,7 +842,11 @@ def execute(self, context):
812842
use_global_undo = context.user_preferences.edit.use_global_undo
813843
context.user_preferences.edit.use_global_undo = False
814844
try:
815-
rig = get_rig_type(self.metarig_type)
845+
if 'external' in rig_lists.rigs_dict and self.metarig_type in rig_lists.rigs_dict['external']['rig_list']:
846+
custom_rigs_folder = bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder
847+
rig = get_rig_type(self.metarig_type, custom_rigs_folder)
848+
else:
849+
rig = get_rig_type(self.metarig_type)
816850
create_sample = rig.create_sample
817851
except (ImportError, AttributeError):
818852
raise Exception("rig type '" + self.metarig_type + "' has no sample.")

utils.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import bpy
2222
import imp
2323
import importlib
24+
import importlib.util
2425
import math
2526
import random
2627
import time
@@ -904,12 +905,19 @@ def copy_attributes(a, b):
904905
pass
905906

906907

907-
def get_rig_type(rig_type):
908+
def get_rig_type(rig_type, base_path=''):
908909
""" Fetches a rig module by name, and returns it.
909910
"""
910-
name = ".%s.%s" % (RIG_DIR, rig_type)
911-
submod = importlib.import_module(name, package=MODULE_NAME)
912-
importlib.reload(submod)
911+
if not base_path:
912+
name = ".%s.%s" % (RIG_DIR, rig_type)
913+
submod = importlib.import_module(name, package=MODULE_NAME)
914+
importlib.reload(submod)
915+
else:
916+
if '.' in rig_type:
917+
rig_type = str.join(os.sep, rig_type.split('.'))
918+
spec = importlib.util.spec_from_file_location(rig_type, base_path + rig_type + '.py')
919+
submod = importlib.util.module_from_spec(spec)
920+
spec.loader.exec_module(submod)
913921
return submod
914922

915923

0 commit comments

Comments
 (0)