Skip to content

Commit 3a476f7

Browse files
PiOverFoureigen-value
authored andcommitted
External Rigs, metarigs and UI templates
1 parent 0c91579 commit 3a476f7

File tree

13 files changed

+538
-323
lines changed

13 files changed

+538
-323
lines changed

__init__.py

Lines changed: 74 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,17 @@
3737
importlib.reload(utils)
3838
importlib.reload(metarig_menu)
3939
importlib.reload(rig_lists)
40+
importlib.reload(template_list)
41+
importlib.reload(feature_sets)
4042
else:
41-
from . import utils, rig_lists, generate, ui, metarig_menu
43+
from . import (utils, rig_lists, template_list,
44+
generate, ui, metarig_menu, feature_sets)
4245

4346
import bpy
4447
import sys
4548
import os
4649
from bpy.types import AddonPreferences
4750
from bpy.props import BoolProperty
48-
from bpy.props import StringProperty
4951

5052

5153
class RigifyPreferences(AddonPreferences):
@@ -119,29 +121,28 @@ def update_legacy(self, context):
119121

120122
register()
121123

122-
def update_external_rigs(self, context):
123-
124+
def update_external_rigs(self):
125+
"""Get external feature sets"""
124126
if self.legacy_mode:
125127
return
126128

127-
custom_rigs_folder = bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder
129+
feature_sets_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
128130

129-
if custom_rigs_folder == "" and 'external' in rig_lists.rigs_dict:
130-
rig_lists.rigs_dict.pop('external')
131-
return
131+
invalid_path = not os.path.exists(feature_sets_path)
132+
if not invalid_path:
133+
if feature_sets_path not in sys.path:
134+
sys.path.append(feature_sets_path)
135+
# Reload rigs
136+
print('Reloading external rigs...')
137+
rig_lists.get_external_rigs(feature_sets_path)
132138

133-
if custom_rigs_folder not in sys.path:
134-
sys.path.append(bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder)
139+
# Reload metarigs
140+
print('Reloading external metarigs...')
141+
metarig_menu.get_external_metarigs(feature_sets_path)
135142

136-
rig_lists.get_external_rigs()
137-
if 'external' in rig_lists.rigs_dict and rig_lists.rigs_dict['external']:
138-
# Add external rig parameters
139-
for rig in rig_lists.rigs_dict['external']['rig_list']:
140-
r = utils.get_rig_type(rig, custom_rigs_folder)
141-
try:
142-
r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE))
143-
except AttributeError:
144-
pass
143+
# Reload templates
144+
print('Reloading external templates...')
145+
template_list.get_external_templates(feature_sets_path)
145146

146147
legacy_mode = BoolProperty(
147148
name='Rigify Legacy Mode',
@@ -150,14 +151,6 @@ def update_external_rigs(self, context):
150151
update=update_legacy
151152
)
152153

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

163156
show_rigs_folder_expanded = BoolProperty()
@@ -186,7 +179,7 @@ def draw(self, context):
186179
if expand:
187180
split = col.row().split(percentage=0.15)
188181
split.label('Description:')
189-
split.label(text='This is the folder containing user defined Rig Types')
182+
split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.')
190183

191184
box = column.box()
192185
rigs_expand = getattr(self, 'show_rigs_folder_expanded')
@@ -199,12 +192,21 @@ def draw(self, context):
199192
op = sub.operator('wm.context_toggle', text='', icon=icon,
200193
emboss=False)
201194
op.data_path = 'addon_prefs.show_rigs_folder_expanded'
202-
row.prop(self, 'custom_rigs_folder')
203-
195+
sub.label('{}: {}'.format('Rigify', 'External feature sets'))
204196
if rigs_expand:
197+
if os.path.exists(os.path.join(bpy.utils.script_path_user(), 'rigify')):
198+
feature_sets_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
199+
for fs in os.listdir(feature_sets_path):
200+
row = col.row()
201+
row.label(fs)
202+
op = row.operator("wm.rigify_remove_feature_set", text="Remove", icon='CANCEL')
203+
op.featureset = fs
204+
row = col.row(align=True)
205+
row.operator("wm.rigify_add_feature_set", text="Install Feature Set from File...", icon='FILESEL')
206+
205207
split = col.row().split(percentage=0.15)
206208
split.label('Description:')
207-
split.label(text='When enabled the add-on will run in legacy mode using the old 2.76b feature set.')
209+
split.label(text='External feature sets (rigs, metarigs, ui layouts)')
208210

209211
row = layout.row()
210212
row.label("End of Rigify Preferences")
@@ -259,6 +261,10 @@ class RigifySelectionColors(bpy.types.PropertyGroup):
259261
)
260262

261263

264+
class RigifyTemplate(bpy.types.PropertyGroup):
265+
name = bpy.props.StringProperty()
266+
267+
262268
class RigifyParameters(bpy.types.PropertyGroup):
263269
name = bpy.props.StringProperty()
264270

@@ -340,9 +346,11 @@ def set_group(self, value):
340346

341347
def register():
342348
ui.register()
349+
feature_sets.register()
343350
metarig_menu.register()
344351

345352
bpy.utils.register_class(RigifyName)
353+
bpy.utils.register_class(RigifyTemplate)
346354
bpy.utils.register_class(RigifyParameters)
347355

348356
bpy.utils.register_class(RigifyColorSet)
@@ -351,6 +359,12 @@ def register():
351359
bpy.utils.register_class(RigifyPreferences)
352360
bpy.types.Armature.rigify_layers = bpy.props.CollectionProperty(type=RigifyArmatureLayer)
353361

362+
bpy.types.Armature.active_feature_set = bpy.props.EnumProperty(
363+
items=feature_sets.feature_set_items,
364+
name="Feature Set",
365+
description="Feature set to select from for this bone"
366+
)
367+
354368
bpy.types.PoseBone.rigify_type = bpy.props.StringProperty(name="Rigify Type", description="Rig type for this bone")
355369
bpy.types.PoseBone.rigify_parameters = bpy.props.PointerProperty(type=RigifyParameters)
356370

@@ -383,7 +397,8 @@ def register():
383397
), name='Theme')
384398

385399
IDStore = bpy.types.WindowManager
386-
IDStore.rigify_collection = bpy.props.EnumProperty(items=rig_lists.col_enum_list, default="All",
400+
IDStore.rigify_collection = bpy.props.EnumProperty(items=(("All", "All", "All"),),
401+
default="All",
387402
name="Rigify Active Collection",
388403
description="The selected rig collection")
389404

@@ -429,19 +444,27 @@ def update_mode(self, context):
429444
if (ui and 'legacy' in str(ui)) or bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode:
430445
# update legacy on restart or reload
431446
bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode = True
447+
IDStore = bpy.types.Armature
448+
IDStore.rigify_templates = bpy.props.CollectionProperty(type=RigifyTemplate)
449+
IDStore.rigify_active_template = bpy.props.IntProperty(name="Rigify Active Template", description="The selected ui template", default=0)
432450

433-
# Add rig parameters
434-
for rig in rig_lists.rig_list:
435-
r = utils.get_rig_type(rig)
436-
try:
437-
r.add_parameters(RigifyParameterValidator(RigifyParameters, rig, RIGIFY_PARAMETER_TABLE))
438-
except AttributeError:
439-
pass
451+
bpy.context.user_preferences.addons['rigify'].preferences.update_external_rigs()
440452

441-
external_rigs_folder = bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder
442-
if external_rigs_folder and not 'external' in rig_lists.rigs_dict:
443-
#force update on reload
444-
bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder = external_rigs_folder
453+
# Add rig parameters
454+
if bpy.context.user_preferences.addons['rigify'].preferences.legacy_mode:
455+
for rig in rig_lists.rig_list:
456+
r = utils.get_rig_type(rig)
457+
try:
458+
r.add_parameters(RigifyParameters)
459+
except AttributeError:
460+
pass
461+
else:
462+
for rig in rig_lists.rigs:
463+
r = rig_lists.rigs[rig]['module']
464+
try:
465+
r.add_parameters(RigifyParameters)
466+
except AttributeError:
467+
pass
445468

446469

447470
def unregister():
@@ -464,17 +487,21 @@ def unregister():
464487
del IDStore.rigify_transfer_start_frame
465488
del IDStore.rigify_transfer_end_frame
466489

490+
IDStore = bpy.types.Armature
491+
del IDStore.rigify_templates
492+
del IDStore.rigify_active_template
493+
467494
bpy.utils.unregister_class(RigifyName)
495+
bpy.utils.unregister_class(RigifyTemplate)
468496
bpy.utils.unregister_class(RigifyParameters)
469497

470498
bpy.utils.unregister_class(RigifyColorSet)
471499
bpy.utils.unregister_class(RigifySelectionColors)
472500

473501
bpy.utils.unregister_class(RigifyArmatureLayer)
474-
475-
if bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder in sys.path:
476-
sys.path.remove(bpy.context.user_preferences.addons['rigify'].preferences.custom_rigs_folder)
477-
bpy.utils.unregister_class(RigifyPreferences)
502+
if "bl_rna" in RigifyPreferences.__dict__:
503+
bpy.utils.unregister_class(RigifyPreferences)
478504

479505
metarig_menu.unregister()
480506
ui.unregister()
507+
feature_sets.unregister()

feature_sets.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#====================== BEGIN GPL LICENSE BLOCK ======================
2+
#
3+
# This program is free software; you can redistribute it and/or
4+
# modify it under the terms of the GNU General Public License
5+
# as published by the Free Software Foundation; either version 2
6+
# of the License, or (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program; if not, write to the Free Software Foundation,
15+
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16+
#
17+
#======================= END GPL LICENSE BLOCK ========================
18+
19+
import bpy
20+
import os
21+
from zipfile import ZipFile
22+
from shutil import rmtree
23+
24+
25+
def feature_set_items(scene, context):
26+
"""Get items for the Feature Set EnumProperty"""
27+
feature_sets_path = os.path.join(
28+
bpy.utils.script_path_user(), 'rigify')
29+
items = [('all',)*3, ('rigify',)*3, ]
30+
for fs in os.listdir(feature_sets_path):
31+
items.append((fs,)*3)
32+
33+
return items
34+
35+
class DATA_OT_rigify_add_feature_set(bpy.types.Operator):
36+
bl_idname = "wm.rigify_add_feature_set"
37+
bl_label = "Add External Feature Set"
38+
bl_description = "Add external feature set (rigs, metarigs, ui templates)"
39+
bl_options = {"REGISTER", "UNDO"}
40+
41+
filter_glob = bpy.props.StringProperty(default="*.zip", options={'HIDDEN'})
42+
filepath = bpy.props.StringProperty(maxlen=1024, subtype='FILE_PATH', options={'HIDDEN', 'SKIP_SAVE'})
43+
44+
@classmethod
45+
def poll(cls, context):
46+
return True
47+
48+
def invoke(self, context, event):
49+
context.window_manager.fileselect_add(self)
50+
return {'RUNNING_MODAL'}
51+
52+
def execute(self, context):
53+
addon_prefs = context.user_preferences.addons[__package__].preferences
54+
55+
rigify_config_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
56+
os.makedirs(rigify_config_path, exist_ok=True)
57+
with ZipFile(bpy.path.abspath(self.filepath), 'r') as zip_archive:
58+
zip_archive.extractall(rigify_config_path)
59+
60+
addon_prefs.machin = bpy.props.EnumProperty(items=(('a',)*3, ('b',)*3, ('c',)*3),)
61+
62+
addon_prefs.update_external_rigs()
63+
return {'FINISHED'}
64+
65+
66+
class DATA_OT_rigify_remove_feature_set(bpy.types.Operator):
67+
bl_idname = "wm.rigify_remove_feature_set"
68+
bl_label = "Remove External Feature Set"
69+
bl_description = "Remove external feature set (rigs, metarigs, ui templates)"
70+
bl_options = {"REGISTER", "UNDO"}
71+
72+
featureset = bpy.props.StringProperty(maxlen=1024, options={'HIDDEN', 'SKIP_SAVE'})
73+
74+
@classmethod
75+
def poll(cls, context):
76+
return True
77+
78+
def invoke(self, context, event):
79+
return context.window_manager.invoke_confirm(self, event)
80+
81+
def execute(self, context):
82+
addon_prefs = context.user_preferences.addons[__package__].preferences
83+
84+
rigify_config_path = os.path.join(bpy.utils.script_path_user(), 'rigify')
85+
rmtree(os.path.join(rigify_config_path, self.featureset))
86+
87+
addon_prefs.update_external_rigs()
88+
return {'FINISHED'}
89+
90+
def register():
91+
bpy.utils.register_class(DATA_OT_rigify_add_feature_set)
92+
bpy.utils.register_class(DATA_OT_rigify_remove_feature_set)
93+
94+
def unregister():
95+
bpy.utils.unregister_class(DATA_OT_rigify_add_feature_set)
96+
bpy.utils.unregister_class(DATA_OT_rigify_remove_feature_set)

generate.py

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@
2626
from rna_prop_ui import rna_idprop_ui_prop_get
2727
from collections import OrderedDict
2828

29-
from .utils import MetarigError, new_bone, get_rig_type
30-
from .utils import ORG_PREFIX, MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name
31-
from .utils import RIG_DIR
29+
from .utils import MetarigError, new_bone
30+
from .utils import MCH_PREFIX, DEF_PREFIX, WGT_PREFIX, ROOT_NAME, make_original_name
3231
from .utils import create_root_widget
3332
from .utils import random_id
3433
from .utils import copy_attributes
3534
from .utils import gamma_correct
36-
from .rig_ui_template import UI_IMPORTS, UI_BASE_UTILITIES, UI_UTILITIES, UI_SLIDERS, layers_ui, UI_REGISTER
37-
35+
from . import rig_lists
36+
from . import template_list
3837

3938
RIG_MODULE = "rigs"
4039
ORG_LAYER = [n == 31 for n in range(0, 32)] # Armature layer that original bones should be moved to.
@@ -341,10 +340,18 @@ def generate_rig(context, metarig):
341340
t.tick("Initialize rigs: ")
342341

343342
# Generate all the rigs.
343+
armature_store = context.armature
344+
if len(template_list.templates) > 1:
345+
template_name = armature_store.rigify_templates[armature_store.rigify_active_template].name
346+
else:
347+
template_name = 'rig_ui_template'
348+
349+
template = template_list.templates[template_name]
350+
344351
ui_scripts = []
345-
ui_imports = UI_IMPORTS.copy()
346-
ui_utilities = UI_UTILITIES.copy()
347-
ui_register = UI_REGISTER.copy()
352+
ui_imports = template.UI_IMPORTS.copy()
353+
ui_utilities = template.UI_UTILITIES.copy()
354+
ui_register = template.UI_REGISTER.copy()
348355
noparent_bones = []
349356
for rig in rigs:
350357
# Go into editmode in the rig armature
@@ -497,13 +504,13 @@ def generate_rig(context, metarig):
497504

498505
for s in OrderedDict.fromkeys(ui_imports):
499506
script.write(s + "\n")
500-
script.write(UI_BASE_UTILITIES % rig_id)
507+
script.write(template.UI_BASE_UTILITIES % rig_id)
501508
for s in OrderedDict.fromkeys(ui_utilities):
502509
script.write(s + "\n")
503-
script.write(UI_SLIDERS)
510+
script.write(template.UI_SLIDERS)
504511
for s in ui_scripts:
505512
script.write("\n " + s.replace("\n", "\n ") + "\n")
506-
script.write(layers_ui(vis_layers, layer_layout))
513+
script.write(template.layers_ui(vis_layers, layer_layout))
507514
script.write("\ndef register():\n")
508515
ui_register = OrderedDict.fromkeys(ui_register)
509516
for s in ui_register:
@@ -644,8 +651,9 @@ def get_bone_rigs(obj, bone_name, halt_on_missing=False):
644651

645652
# Get the rig
646653
try:
647-
rig = get_rig_type(rig_type).Rig(obj, bone_name, params)
648-
except ImportError:
654+
rig = rig_lists.rigs[rig_type]["module"]
655+
rig = rig.Rig(obj, bone_name, params)
656+
except (KeyError, ImportError):
649657
message = "Rig Type Missing: python module for type '%s' not found (bone: %s)" % (rig_type, bone_name)
650658
if halt_on_missing:
651659
raise MetarigError(message)

0 commit comments

Comments
 (0)