Skip to content

Commit

Permalink
UE2Rigify - Custom Rig Template Path (#49) (#51)
Browse files Browse the repository at this point in the history
* Feature: Adding an option for custom template path

* restructure and small changes

- Removed unnecessary requirements
- Removed some unused imports
- Removed CUSTOM_RIG_TEMPLATES_PATH constant
- Fixed create new template issue of not adding the default metarig right away if selected starter for some reason isn't a Rigify default
- Moved get_rig_template_path() to utilities (second guessing this change)

---------

Co-authored-by: Alexander Dmitriev <[email protected]>
Co-authored-by: JoshQuake <[email protected]>
  • Loading branch information
3 people authored Jul 30, 2024
1 parent 5654ebb commit 9a530cf
Show file tree
Hide file tree
Showing 11 changed files with 97 additions and 29 deletions.
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ numpy==1.26.4
websocket==0.2.1
python-dotenv==1.0.1

# documenation
# documenation
mkdocs<=1.5.3
mkdocs-material<=9.5.13
mkdocs-material-extensions<=1.3.1
mkdocs-mermaid2-plugin<=1.1.1
mkdocs-monorepo-plugin<=1.1.0
termynal<=0.12.1
mkdocstrings[python]<=1.9.0
mkdocstrings[python]<=1.9.0
6 changes: 3 additions & 3 deletions src/addons/ue2rigify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ def register():
Registers the addon classes when the addon is enabled.
"""

templates.copy_default_templates()

# reload the submodules
if os.environ.get('UE2RIGIFY_DEV'):
for module in modules:
Expand All @@ -58,6 +56,8 @@ def register():
operators.register()
nodes.register()

templates.copy_default_templates()


def unregister():
"""
Expand All @@ -73,4 +73,4 @@ def unregister():
properties.unregister()
except Exception as e:
print(f"Error un-registering UE2Rigify: \n{e}")

2 changes: 1 addition & 1 deletion src/addons/ue2rigify/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class Nodes:

class Template:
@staticmethod
def RIG_TEMPLATES_PATH():
def DEFAULT_RIG_TEMPLATES_PATH():
if bpy.app.version[0] < 4:
return os.path.join(tempfile.gettempdir(), ToolInfo.NAME.value, 'resources', 'rig_templates', 'b3_6')
else:
Expand Down
4 changes: 2 additions & 2 deletions src/addons/ue2rigify/core/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
from . import scene
from . import utilities
from ..ui import node_editor
from ..constants import ToolInfo, Nodes, Modes, Rigify
from ..constants import Nodes, Modes, Rigify
from bpy.types import NodeTree, NodeSocket
from nodeitems_utils import NodeCategory, NodeItem
from nodeitems_utils import NodeItem

addon_key_maps = []
pie_menu_classes = []
Expand Down
16 changes: 13 additions & 3 deletions src/addons/ue2rigify/core/scene.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def set_meta_rig(self=None, context=None):
else:
remove_metarig()
create_meta_rig(self, file_path=os.path.join(
Template.RIG_TEMPLATES_PATH(),
utilities.get_rig_template_path(),
self.selected_starter_metarig_template,
f'{Rigify.META_RIG_NAME}.py'
))
Expand Down Expand Up @@ -697,7 +697,17 @@ def create_starter_metarig_template(properties):

# create the selected metarig
operator = properties.selected_starter_metarig_template
exec(operator)
if operator.startswith('bpy.ops'):
exec(operator)
else:
# If selected starter isn't set nor a default Rigify template i.e. ue manny
if not operator:
operator = Template.DEFAULT_MALE_TEMPLATE
create_meta_rig(properties, file_path=os.path.join(
utilities.get_rig_template_path(),
operator,
f'{Rigify.META_RIG_NAME}.py'
))

# get the meta rig object
meta_rig_object = bpy.data.objects.get(Rigify.META_RIG_NAME)
Expand Down Expand Up @@ -1449,7 +1459,7 @@ def edit_meta_rig_template(properties):
:param object properties: The property group that contains variables that maintain the addon's correct state.
"""
# if not creating a new template, initiate the
# if not creating a new template, initiate the metarig
if properties.selected_rig_template != 'create_new':
meta_rig_object = create_meta_rig(properties)
else:
Expand Down
23 changes: 14 additions & 9 deletions src/addons/ue2rigify/core/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import bpy
import json
import shutil
import tempfile
from mathutils import Color, Euler, Matrix, Quaternion, Vector

from ..constants import Template, Modes, Rigify
Expand All @@ -26,7 +25,7 @@ def copy_default_templates():
sub_folder = 'b3_6' if bpy.app.version[0] < 4 else 'b4_0'
template_location = os.path.join(os.path.dirname(__file__), os.path.pardir, 'resources', 'rig_templates', sub_folder)

shutil.copytree(template_location, Template.RIG_TEMPLATES_PATH(), dirs_exist_ok=True)
shutil.copytree(template_location, utilities.get_rig_template_path(), dirs_exist_ok=True)


def get_saved_node_data(properties):
Expand Down Expand Up @@ -189,7 +188,7 @@ def get_rig_templates(self=None, context=None, index_offset=0):
:return list: A list of tuples that define the rig template enumeration.
"""
rig_templates = []
rig_template_directories = next(os.walk(Template.RIG_TEMPLATES_PATH()))[1]
rig_template_directories = next(os.walk(utilities.get_rig_template_path()))[1]

# ensure that the male and female template are first
rig_templates.append((
Expand Down Expand Up @@ -235,7 +234,7 @@ def get_template_file_path(template_file_name, properties):
template_name = re.sub(r'\W+', '_', properties.new_template_name).lower()

return os.path.join(
Template.RIG_TEMPLATES_PATH(),
utilities.get_rig_template_path(),
template_name,
template_file_name
)
Expand Down Expand Up @@ -286,7 +285,7 @@ def remove_template_folder(properties, template):
properties.selected_mode = Modes.SOURCE.name

# delete the selected rig template folder
selected_template_path = os.path.join(Template.RIG_TEMPLATES_PATH(), template)
selected_template_path = os.path.join(utilities.get_rig_template_path(), template)

import logging
logging.info(selected_template_path)
Expand All @@ -304,10 +303,10 @@ def create_template_folder(template_name, properties):
:param object properties: The property group that contains variables that maintain the addon's correct state.
"""
# remove non alpha numeric characters
template_name = re.sub(r'\W+', '_', template_name.strip()).lower()
template_name = parse_template_name(template_name)

# create the template folder
template_path = os.path.join(Template.RIG_TEMPLATES_PATH(), template_name)
template_path = os.path.join(utilities.get_rig_template_path(), template_name)
if not os.path.exists(template_path):
os.makedirs(template_path, exist_ok=True)

Expand Down Expand Up @@ -396,14 +395,16 @@ def import_zip(zip_file_path, properties):
"""
# get the template name and path from the zip file
template_name = os.path.basename(zip_file_path).replace('.zip', '').lower()
template_folder_path = os.path.join((Template.RIG_TEMPLATES_PATH()), template_name)
template_folder_path = os.path.join(utilities.get_rig_template_path(), template_name)

# create the template folder
create_template_folder(template_name, properties)

# unpack the zip file into the new template folder
shutil.unpack_archive(zip_file_path, template_folder_path, 'zip')

# select new template
properties.selected_rig_template = parse_template_name(template_name)

# TODO move to a shared location and define as a generic zip export
def export_zip(zip_file_path, properties):
Expand All @@ -417,7 +418,7 @@ def export_zip(zip_file_path, properties):
no_extension_file_path = zip_file_path.replace('.zip', '')

# zip up the folder and save it to the given path
template_folder_path = os.path.join(Template.RIG_TEMPLATES_PATH(), properties.selected_export_template)
template_folder_path = os.path.join(utilities.get_rig_template_path(), properties.selected_export_template)
shutil.make_archive(no_extension_file_path, 'zip', template_folder_path)

#
Expand Down Expand Up @@ -484,3 +485,7 @@ def safe_get_rig_templates(self, context):
global _result_reference_get_rig_templates
_result_reference_get_rig_templates = items
return items

def parse_template_name(template_name):
return re.sub(r'\W+', '_', template_name.strip()).lower()

15 changes: 14 additions & 1 deletion src/addons/ue2rigify/core/utilities.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Copyright Epic Games, Inc. All Rights Reserved.
import bpy
import re
import os
from . import scene, templates
from ..constants import Viewport, Modes, Rigify
from ..constants import Viewport, Modes, Rigify, Template, ToolInfo
from mathutils import Vector, Quaternion


Expand Down Expand Up @@ -1183,3 +1184,15 @@ def get_rigify_bone_operator(un_hashed_operator_name, bone_name, properties):

if output_bones and input_bones and ctrl_bones:
return f'bpy.ops.{operator}({output_bones}, {input_bones}, {ctrl_bones})'

def get_rig_template_path():
preferences = bpy.context.preferences.addons.get(ToolInfo.NAME.value).preferences

template_path = preferences.custom_rig_template_path
# If custom_rig_template_path is empty, returns Temp folder
if template_path:
subpath = 'b3_6' if bpy.app.version[0] < 4 else 'b4_0'
template_path = os.path.join(template_path, subpath)
else:
template_path = Template.DEFAULT_RIG_TEMPLATES_PATH()
return template_path
16 changes: 14 additions & 2 deletions src/addons/ue2rigify/operators.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
# Copyright Epic Games, Inc. All Rights Reserved.

import bpy
from .constants import Modes, Rigify
from .constants import Modes, Rigify, Template
from .ui import exporter
from .core import scene, nodes, templates, utilities
from bpy_extras.io_utils import ImportHelper

class SetDefault(bpy.types.Operator):
"""Set a default control rig mode"""
bl_idname = "ue2rigify.set_default"
bl_label = "Default"

def execute(self, context):
properties = bpy.context.scene.ue2rigify
properties.selected_mode = Modes.SOURCE.name
properties.selected_rig_template = Template.DEFAULT_MALE_TEMPLATE
return {'FINISHED'}


class ConvertToRigifyRig(bpy.types.Operator):
"""Convert the source rig to a control rig"""
Expand Down Expand Up @@ -294,7 +305,8 @@ def execute(self, context):
ConstrainSourceToDeform,
RemoveConstraints,
SwitchModes,
NullOperator
NullOperator,
SetDefault
]


Expand Down
6 changes: 4 additions & 2 deletions src/addons/ue2rigify/properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,17 @@ class UE2RigifyProperties(bpy.types.PropertyGroup):
name="Metarig",
description=tool_tips.starter_metarig_template_tool_tip,
items=templates.safe_get_starter_metarig_templates,
update=scene.set_meta_rig
update=scene.set_meta_rig,
default=None
)

selected_rig_template: bpy.props.EnumProperty(
name="Rig Template",
description=tool_tips.rig_template_tool_tip,
items=templates.safe_populate_templates_dropdown,
options={'ANIMATABLE'},
update=templates.set_template
update=templates.set_template,
default=None
)

selected_mode: bpy.props.EnumProperty(
Expand Down
31 changes: 27 additions & 4 deletions src/addons/ue2rigify/ui/addon_preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,36 @@

import bpy
from ..constants import ToolInfo
from ..core import templates


class Ue2RigifyAddonPreferences(bpy.types.AddonPreferences):
"""
This class subclasses the AddonPreferences class to create the addon preferences interface.
"""
bl_idname = ToolInfo.NAME.value

def get_custom_location(self):
# create key if doesn't exist then return
try:
self['custom_template_path']
except:
self['custom_template_path'] = ''
return self['custom_template_path']

def set_custom_location(self, value):
self['custom_template_path'] = value
# Create default templates at custom_rig_template_path
templates.copy_default_templates()

custom_rig_template_path: bpy.props.StringProperty(
name='Custom Templates folder',
description='The location where your rig templates will be stored, including default templates. Defaults to Temp folder if empty',
subtype='DIR_PATH',
default='',
get=get_custom_location,
set=set_custom_location
)

def draw(self, context):
"""
Expand All @@ -19,6 +42,7 @@ def draw(self, context):
"""
layout = self.layout

layout.prop(self, 'custom_rig_template_path')
row = layout.row()
row.operator('ue2rigify.import_rig_template', icon='IMPORT')
row.operator('ue2rigify.export_rig_template', icon='EXPORT')
Expand All @@ -28,13 +52,12 @@ def register():
"""
Registers the addon preferences when the addon is enabled.
"""
if not hasattr(bpy.types, Ue2RigifyAddonPreferences.bl_idname):
bpy.utils.register_class(Ue2RigifyAddonPreferences)
bpy.utils.register_class(Ue2RigifyAddonPreferences)


def unregister():
"""
Unregisters the addon preferences when the addon is disabled.
"""
if hasattr(bpy.types, Ue2RigifyAddonPreferences.bl_idname):
bpy.utils.unregister_class(Ue2RigifyAddonPreferences)

bpy.utils.unregister_class(Ue2RigifyAddonPreferences)
3 changes: 3 additions & 0 deletions src/addons/ue2rigify/ui/view_3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ def draw(self, context):
row = layout.row()
row.prop(properties, 'selected_mode', text='')

if properties.selected_rig_template == '':
row.operator('ue2rigify.set_default')

box = layout.box()
row = box.row()
row.label(text='Rig Template Editor', icon='TOOL_SETTINGS')
Expand Down

0 comments on commit 9a530cf

Please sign in to comment.