├── omtk
├── ui
│ ├── __init__.py
│ ├── widget_list_meshes.ui
│ ├── widget_list_modules.ui
│ ├── widget_list_influences.ui
│ ├── widget_component_wizard_parts.ui
│ ├── widget_list_modules.py
│ ├── preferences_window.py
│ ├── widget_list_meshes.py
│ ├── widget_list_influences.py
│ ├── preferences_window.ui
│ ├── widget_component_wizard_parts.py
│ ├── widget_logger.ui
│ ├── pluginmanager_window.ui
│ ├── pluginmanager_window.py
│ ├── widget_create_component.ui
│ ├── widget_logger.py
│ └── widget_create_component.py
├── vendor
│ ├── __init__.py
│ └── libSerialization
│ │ ├── .gitignore
│ │ ├── __init__.py
│ │ ├── plugin_yaml.py
│ │ ├── plugin_json.py
│ │ ├── decorators.py
│ │ ├── cache.py
│ │ └── plugin_maya_json.py
├── patches
│ ├── __init__.py
│ ├── patch_0.2.0_missing_rig_attr.py
│ ├── patch_0.3.0_convert_facelids_to_faceeyelids.py
│ ├── patch_0_4_7_add_leg_auto_roll_threshold.py
│ ├── patch_0_4_32_support_multiple_root_ctrls.py
│ └── patch_0.2.0_missing_spaceswitch_targets.py
├── modules
│ ├── __init__.py
│ ├── rigHead.py
│ ├── template
│ ├── rigFaceSquint.py
│ ├── rigFaceBrow.py
│ ├── rigFaceNose.py
│ ├── rigNeck.py
│ ├── rigArm.py
│ ├── rigSplineIK.py
│ ├── rigFaceEyeLids.py
│ ├── rigFaceJaw.py
│ ├── rigFK.py
│ └── rigFKAdditive.py
├── rigs
│ └── __init__.py
├── animation
│ ├── __init__.py
│ └── ikfkTools.py
├── core
│ ├── macros.py
│ ├── __init__.py
│ ├── utils.py
│ ├── preferences.py
│ ├── classModuleMap.py
│ └── classModuleCompound.py
├── macros
│ ├── __init__.py
│ ├── modules_build_selected.py
│ ├── modules_unbuild_selected.py
│ ├── ctrls_calibrate_selected.py
│ ├── launch_create_component_wizard.py
│ ├── ctrls_mirror_selected.py
│ ├── ctrls_match_selected.py
│ ├── ctrls_interactive_swap_mesh.py
│ ├── ctrls_bake_selected.py
│ └── avar_selection_toggle.py
├── libs
│ ├── __init__.py
│ ├── libStringMap.py
│ ├── libQt.py
│ ├── libDagTraversal.py
│ ├── libUtils.py
│ ├── libHistory.py
│ ├── libSkeleton.py
│ └── libCtrlMatch.py
├── models
│ ├── __init__.py
│ ├── model_avar_linear.py
│ └── model_avar_base.py
├── __init__.py
├── constants.py
├── preferences_window.py
├── ui_shared.py
├── widget_create_component.py
├── widget_list_meshes.py
├── pluginmanager_window.py
├── widget_list_influences.py
└── widget_create_component_wizard_parts.py
├── .gitattributes
├── images
├── omtk_rig_show.png
├── omtk_rig_show.xcf
├── reload_scene.png
├── reload_scene.xcf
├── omtk_anm_switch_fk.png
├── omtk_anm_switch_fk.xcf
├── omtk_anm_switch_ik.png
├── omtk_anm_switch_ik.xcf
├── omtk_rig_build_all.png
├── omtk_rig_build_all.xcf
├── omtk_rig_ctrl_bake.png
├── omtk_rig_ctrl_bake.xcf
├── omtk_rig_ctrl_swap.png
├── omtk_rig_ctrl_swap.xcf
├── omtk_anm_ctrl_mirror.png
├── omtk_anm_ctrl_mirror.xcf
├── omtk_rig_ctrl_mirror.png
├── omtk_rig_ctrl_mirror.xcf
├── omtk_rig_rebuild_all.png
├── omtk_rig_rebuild_all.xcf
├── omtk_rig_select_avar.png
├── omtk_rig_select_avar.xcf
├── omtk_rig_unbuild_all.png
├── omtk_rig_unbuild_all.xcf
├── omtk_rig_ctrl_calibrate.png
├── omtk_rig_ctrl_calibrate.xcf
├── omtk_rig_build_selection.png
├── omtk_rig_build_selection.xcf
├── omtk_rig_ictrl_swap_mesh.png
├── omtk_rig_ictrl_swap_mesh.xcf
├── omtk_rig_rebuild_selection.png
├── omtk_rig_rebuild_selection.xcf
├── omtk_rig_unbuild_selection.png
└── omtk_rig_unbuild_selection.xcf
├── tests
├── run.sh
├── test_simple.py
├── run.py
├── doctest_rigSqueeze.txt
├── test_nomenclature.py
├── test_nomenclature_squeeze.py
├── test_libFormula.py
└── test_animtools.py
├── package.py
├── .gitignore
├── test.sh
├── LICENSE
├── README.md
└── shelf_omtk_anim.mel
/omtk/ui/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/omtk/vendor/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/omtk/patches/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | *.ma -diff
2 |
--------------------------------------------------------------------------------
/omtk/vendor/libSerialization/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/*
2 | *.pyc
3 |
--------------------------------------------------------------------------------
/images/omtk_rig_show.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_show.png
--------------------------------------------------------------------------------
/images/omtk_rig_show.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_show.xcf
--------------------------------------------------------------------------------
/images/reload_scene.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/reload_scene.png
--------------------------------------------------------------------------------
/images/reload_scene.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/reload_scene.xcf
--------------------------------------------------------------------------------
/omtk/modules/__init__.py:
--------------------------------------------------------------------------------
1 | import pkgutil
2 | __path__ = pkgutil.extend_path(__path__, __name__)
3 |
--------------------------------------------------------------------------------
/omtk/rigs/__init__.py:
--------------------------------------------------------------------------------
1 | import pkgutil
2 | __path__ = pkgutil.extend_path(__path__, __name__)
3 |
--------------------------------------------------------------------------------
/images/omtk_anm_switch_fk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_anm_switch_fk.png
--------------------------------------------------------------------------------
/images/omtk_anm_switch_fk.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_anm_switch_fk.xcf
--------------------------------------------------------------------------------
/images/omtk_anm_switch_ik.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_anm_switch_ik.png
--------------------------------------------------------------------------------
/images/omtk_anm_switch_ik.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_anm_switch_ik.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_build_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_build_all.png
--------------------------------------------------------------------------------
/images/omtk_rig_build_all.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_build_all.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_bake.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_bake.png
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_bake.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_bake.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_swap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_swap.png
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_swap.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_swap.xcf
--------------------------------------------------------------------------------
/images/omtk_anm_ctrl_mirror.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_anm_ctrl_mirror.png
--------------------------------------------------------------------------------
/images/omtk_anm_ctrl_mirror.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_anm_ctrl_mirror.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_mirror.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_mirror.png
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_mirror.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_mirror.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_rebuild_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_rebuild_all.png
--------------------------------------------------------------------------------
/images/omtk_rig_rebuild_all.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_rebuild_all.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_select_avar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_select_avar.png
--------------------------------------------------------------------------------
/images/omtk_rig_select_avar.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_select_avar.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_unbuild_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_unbuild_all.png
--------------------------------------------------------------------------------
/images/omtk_rig_unbuild_all.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_unbuild_all.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_calibrate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_calibrate.png
--------------------------------------------------------------------------------
/images/omtk_rig_ctrl_calibrate.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ctrl_calibrate.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_build_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_build_selection.png
--------------------------------------------------------------------------------
/images/omtk_rig_build_selection.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_build_selection.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_ictrl_swap_mesh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ictrl_swap_mesh.png
--------------------------------------------------------------------------------
/images/omtk_rig_ictrl_swap_mesh.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_ictrl_swap_mesh.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_rebuild_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_rebuild_selection.png
--------------------------------------------------------------------------------
/images/omtk_rig_rebuild_selection.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_rebuild_selection.xcf
--------------------------------------------------------------------------------
/images/omtk_rig_unbuild_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_unbuild_selection.png
--------------------------------------------------------------------------------
/images/omtk_rig_unbuild_selection.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renaudll/omtk/HEAD/images/omtk_rig_unbuild_selection.xcf
--------------------------------------------------------------------------------
/omtk/animation/__init__.py:
--------------------------------------------------------------------------------
1 | import re
2 | import sys
3 |
4 | if re.match('maya.*', sys.executable, re.IGNORECASE):
5 | import ikfkTools
6 |
--------------------------------------------------------------------------------
/tests/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | find . -name "*.pyc" -delete
3 | cd `dirname $0` # ensure 'tests' is the current directory
4 | /usr/autodesk/maya2016/bin/mayapy run.py $*
--------------------------------------------------------------------------------
/tests/test_simple.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import pymel.core as pymel
3 |
4 | class TestCase(unittest.TestCase):
5 | def test_simple(self):
6 | pymel.createNode('transform')
7 |
--------------------------------------------------------------------------------
/omtk/core/macros.py:
--------------------------------------------------------------------------------
1 | """
2 | A macro is a simple action that can be in a shelf or in a keyboard shortcut. Nothing fancy.
3 | """
4 |
5 |
6 | class BaseMacro(object):
7 | def run(self):
8 | raise NotImplementedError
9 |
--------------------------------------------------------------------------------
/omtk/macros/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | This module contain various tools that can be used to facilitate the rigging workflow with omtk.
3 | Previously they were in omtk.core.api, however the increasing number of macros necessitate a dedicated directory.
4 | """
5 |
6 |
--------------------------------------------------------------------------------
/package.py:
--------------------------------------------------------------------------------
1 | name = 'omtk'
2 |
3 | version = '0.6.0'
4 |
5 |
6 | def commands():
7 | env.PYTHONPATH.append('{root}/python')
8 |
9 | if system.platform == 'windows':
10 | env.XBMLANGPATH.append("{root}/images/")
11 | else:
12 | env.XBMLANGPATH.append("{root}/images/%B")
13 |
--------------------------------------------------------------------------------
/omtk/macros/modules_build_selected.py:
--------------------------------------------------------------------------------
1 | from omtk.core import api
2 | from omtk.core.macros import BaseMacro
3 |
4 |
5 | class BuildSelectedModulesMacro(BaseMacro):
6 | def run(self):
7 | api.build_selected()
8 |
9 |
10 | def register_plugin():
11 | return BuildSelectedModulesMacro
12 |
--------------------------------------------------------------------------------
/omtk/macros/modules_unbuild_selected.py:
--------------------------------------------------------------------------------
1 | from omtk.core import api
2 | from omtk.core.macros import BaseMacro
3 |
4 |
5 | class UnbuildSelectedModulesMacro(BaseMacro):
6 | def run(self):
7 | api.unbuild_selected()
8 |
9 |
10 | def register_plugin():
11 | return UnbuildSelectedModulesMacro
12 |
--------------------------------------------------------------------------------
/omtk/macros/ctrls_calibrate_selected.py:
--------------------------------------------------------------------------------
1 | from omtk.core import api
2 | from omtk.core.macros import BaseMacro
3 |
4 |
5 | class CalibrateSelectedModulesMacro(BaseMacro):
6 | def run(self):
7 | api.calibrate_selected()
8 |
9 |
10 | def register_plugin():
11 | return CalibrateSelectedModulesMacro
12 |
--------------------------------------------------------------------------------
/omtk/macros/launch_create_component_wizard.py:
--------------------------------------------------------------------------------
1 | from omtk.core.macros import BaseMacro
2 |
3 |
4 | class LaunchCreateComponentWizard(BaseMacro):
5 | def run(self):
6 | from omtk import widget_create_component
7 | widget_create_component.show()
8 |
9 |
10 | def register_plugin():
11 | return LaunchCreateComponentWizard
12 |
--------------------------------------------------------------------------------
/tests/run.py:
--------------------------------------------------------------------------------
1 | """
2 | Usage:
3 | /usr/autodesk/maya2016/bin/mayapy ~/packages/omtk/9.9.9/tests/run.py
4 | """
5 | import os
6 | import sys
7 | import mayaunittest
8 |
9 | path_module_omtk = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts'))
10 | sys.path.append(path_module_omtk)
11 |
12 | mayaunittest.run_tests_from_commandline(directories=[os.path.dirname(__file__)])
13 |
--------------------------------------------------------------------------------
/omtk/macros/ctrls_mirror_selected.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 | from omtk.core import macros
3 | from omtk.libs import libCtrlMatch
4 |
5 |
6 | class MacroMirrorSelectedCtrls(macros.BaseMacro):
7 | def run(self):
8 | libCtrlMatch.controller_matcher(selection=pymel.selected(), mirror_prefix=["L_", "R_"], flip=True)
9 |
10 |
11 | def register_plugin():
12 | return MacroMirrorSelectedCtrls
13 |
--------------------------------------------------------------------------------
/omtk/libs/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import sys
4 |
5 | if re.match('maya.*', os.path.basename(sys.executable), re.IGNORECASE):
6 | pass
7 |
8 | # Reload libs
9 | import libAttr
10 | import libCtrlShapes
11 | import libFormula
12 | import libPython
13 | import libQt
14 | import libPymel
15 | import libSkeleton
16 | import libRigging
17 | import libSkinning
18 | import libStringMap
19 | import libUtils
20 | import libHistory
21 | import libComponent
22 |
--------------------------------------------------------------------------------
/omtk/models/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Models provides the link between a controller and an influence in OMTK.
3 |
4 | By changing models, we can change how a certain entity controller something.
5 | For example, the facial modules exposes 'avars' that mights be controlled by multiple type of setup depending on the flavor.
6 | - LinearCtrl
7 | - InteractiveCtrl (ctrls directly on the face)
8 | - FaceBoard
9 |
10 | Currently this is only used to link avars to ctrl/faceboard/sliders/etc.
11 | """
12 |
--------------------------------------------------------------------------------
/omtk/macros/ctrls_match_selected.py:
--------------------------------------------------------------------------------
1 | """
2 | Original code by Jimmy Goulet (https://github.com/goujin), thanks for the contribution!
3 | """
4 | import pymel.core as pymel
5 | from omtk.core import macros
6 | from omtk.libs import libCtrlMatch
7 |
8 |
9 | class CtrlMatchSelected(macros.BaseMacro):
10 | def run(self):
11 | libCtrlMatch.controller_matcher(selection=pymel.selected(), mirror_prefix=None, flip=False)
12 |
13 |
14 | def register_plugin():
15 | return CtrlMatchSelected
16 |
--------------------------------------------------------------------------------
/omtk/vendor/libSerialization/__init__.py:
--------------------------------------------------------------------------------
1 | from core import *
2 |
3 | try:
4 | from plugin_maya import *
5 | except ImportError, e: # will raise when executed outside maya
6 | pass
7 |
8 | try:
9 | from plugin_maya_json import *
10 | except ImportError, e: # will raise when executed outside maya
11 | pass
12 |
13 | try:
14 | from plugin_json import *
15 | except ImportError, e:
16 | pass
17 |
18 | try:
19 | from plugin_yaml import *
20 | except ImportError, e:
21 | pass
22 |
--------------------------------------------------------------------------------
/omtk/modules/rigHead.py:
--------------------------------------------------------------------------------
1 | from omtk.modules import rigFK
2 |
3 |
4 | class CtrlHead(rigFK.CtrlFk):
5 | pass
6 |
7 |
8 | class Head(rigFK.FK):
9 | """
10 | Simple FK setup customized for head rigging. Mandatory when using facial modules.
11 | """
12 | _CLS_CTRL = CtrlHead
13 | _NAME_CTRL_MERGE = True # By default we only expect one controller for the head. (Head_Ctrl > than Head_Head_Ctrl)
14 | _NAME_CTRL_ENUMERATE = True # If we find additional influences, we'll use enumeration.
15 |
16 |
17 | def register_plugin():
18 | return Head
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Pycharm files
2 | .idea/*
3 |
4 | *.egg-info/*
5 | cpp/includes/
6 |
7 | /*cpp-related exclude*/
8 | cpp/build/*
9 | Mac OS X
10 | *.DS_Store
11 |
12 | # Xcode
13 | *.pbxuser
14 | *.mode1v3
15 | *.mode2v3
16 | *.perspectivev3
17 | *.xcuserstate
18 | project.xcworkspace/
19 | xcuserdata/
20 |
21 | # Generated files
22 | *.o
23 | *.pyc
24 |
25 | #Python modules
26 | MANIFEST
27 | dist/
28 | build/
29 |
30 | # Backup files
31 | *~.nib
32 |
33 | # Configuration file
34 | config.json
35 |
36 | # NFS junk
37 | .nfs*
38 |
39 | # py.test files
40 | .cache
41 | .coverage
42 |
--------------------------------------------------------------------------------
/omtk/libs/libStringMap.py:
--------------------------------------------------------------------------------
1 | class StringMap(object):
2 | def __init__(self, content, **kwargs):
3 | self.content = content
4 | self.set_fields(**kwargs)
5 |
6 | def get_fields(self):
7 | return self.fields
8 |
9 | def set_fields(self, **kwargs):
10 | new_fields = {}
11 | for key, val in kwargs.iteritems():
12 | new_fields['{0}'.format(key)] = val
13 | self.fields = kwargs
14 |
15 | def __str__(self):
16 | return self.content.format(**self.fields)
17 |
18 | def __repr__(self):
19 | return repr(self.fields)
20 |
--------------------------------------------------------------------------------
/omtk/__init__.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | from .core import *
4 | import constants
5 | import pymel.core as pymel
6 |
7 | # HACK: Load matrixNodes.dll
8 | pymel.loadPlugin('matrixNodes', quiet=True)
9 |
10 |
11 | def _reload():
12 | """
13 | Reload all module in their respective order.
14 | """
15 | # Hack: prevent a crash related to loosing our OpenMaya.MSceneMessage events.
16 | try:
17 | pymel.deleteUI('OpenRiggingToolkit')
18 | except:
19 | pass
20 |
21 | import omtk
22 | libPython.rreload(omtk)
23 |
24 |
25 | def show():
26 | import main_window
27 | main_window.show()
28 |
--------------------------------------------------------------------------------
/omtk/libs/libQt.py:
--------------------------------------------------------------------------------
1 | def get_all_QTreeWidgetItem(widget, qt_item=None):
2 | """
3 | Iterate through all items of a provided QTreeWidgetItem.
4 | :param widget: The QTreeWidgetItem to iterate through.
5 | :param qt_item: The starting point of the iteration. If nothing is provided the invisibleRootItem will be used.
6 | :return:
7 | """
8 | if qt_item is None:
9 | qt_item = widget.invisibleRootItem()
10 |
11 | num_child = qt_item.childCount()
12 | for i in reversed(range(num_child)):
13 | qt_sub_item = qt_item.child(i)
14 | yield qt_sub_item
15 | for x in get_all_QTreeWidgetItem(widget, qt_sub_item):
16 | yield x
17 |
--------------------------------------------------------------------------------
/tests/doctest_rigSqueeze.txt:
--------------------------------------------------------------------------------
1 | >>> from omtk.rigs.rigSqueeze import SqueezeNomenclature
2 |
3 | # Construct a naming from scratch
4 | >>> n = SqueezeNomenclature(tokens=['Eye', 'Jnt'], side=SqueezeNomenclature.SIDE_L)
5 | >>> n.resolve()
6 | 'L_Eye_Jnt'
7 |
8 | # Construct a naming from another existing naming
9 | >>> n = SqueezeNomenclature('L_Eye_Jnt')
10 | >>> n.prefix is None
11 | True
12 | >>> n.suffix is None
13 | True
14 | >>> n.side is None
15 | False
16 |
17 | # Adding of tokens using suffix
18 | >>> n = SqueezeNomenclature(tokens=['Eye'], side=SqueezeNomenclature.SIDE_L, suffix='Jnt')
19 | >>> n.resolve()
20 | 'L_Eye_Jnt'
21 | >>> n.tokens.append('Micro')
22 | >>> n.resolve()
23 | 'L_Eye_Micro_Jnt'
--------------------------------------------------------------------------------
/omtk/libs/libDagTraversal.py:
--------------------------------------------------------------------------------
1 | """
2 | Functions that traverse the DAG tree in search of specific networks/serialized objects.
3 | """
4 | from omtk.vendor import libSerialization
5 |
6 |
7 | def get_avar_network_by_name(avar_name):
8 | """Find a serialized avar with a provided name in the scene."""
9 | for network in libSerialization.iter_networks_from_class('AbstractAvar'):
10 | if network.hasAttr('name') and network.attr('name').get() == avar_name:
11 | return network
12 |
13 |
14 | def get_avar_by_name(avar_name):
15 | """Find an avar with a provided name in the scene."""
16 | network = get_avar_network_by_name(avar_name)
17 | if network:
18 | avar = libSerialization.import_network(network)
19 | if avar:
20 | return avar
21 |
--------------------------------------------------------------------------------
/omtk/patches/patch_0.2.0_missing_rig_attr.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk.vendor import libSerialization
4 |
5 | def run():
6 | nets = pymel.ls(type='network')
7 | net_rig = next(iter(net for net in nets if libSerialization.is_network_from_class(net, 'Rig')), None)
8 |
9 | for net in nets:
10 | if not libSerialization.is_network_from_class(net, 'Module'):
11 | continue
12 | if not net.hasAttr('rig'):
13 | print("Adding attribute 'rig' on {0}".format(net))
14 | pymel.addAttr(net, longName='rig', niceName='rig', attributeType='message')
15 | if not net.rig.isDestination():
16 | print("Connecting attribute 'rig' on {0}".format(net))
17 | pymel.connectAttr(net_rig.message, net.rig)
18 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | # Linux bash script to simplify testing omtk with pytest
3 |
4 | # Expose omtk
5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
6 | echo "[setup] Adding to PYTHONPATH: ${DIR}"
7 | PYTHONPATH="${DIR}:{$PYTHONPATH}"
8 |
9 | # Expose mayapy
10 | _MAYA_BIN=$(readlink -f `command -v maya`)
11 | _MAYA_DIR=$(dirname "${_MAYA_BIN}")
12 |
13 | echo "[setup] Adding to PATH: ${_MAYA_DIR}"
14 | PATH="${PATH}:${_MAYA_DIR}"
15 |
16 | # Export pytest
17 | _PYTEST_BIN=$(python -c "import pytest; print pytest.__file__")
18 | _PYTEST_DIR=$(dirname "${_PYTEST_BIN}")
19 |
20 | echo "[setup] Adding to PYTHONPATH: ${_PYTEST_DIR}"
21 | PYTHONPATH="${PYTHONPATH}:${_PYTEST_DIR}"
22 |
23 | # Run unit tests
24 | mayapy -m "py.test" $@ --cov=omtk--cov-branch --cov-report term-missing
25 |
--------------------------------------------------------------------------------
/tests/test_nomenclature.py:
--------------------------------------------------------------------------------
1 | import os
2 | import mayaunittest
3 | from maya import cmds
4 | import omtk
5 |
6 | class NomenclatureTestCase(mayaunittest.TestCase):
7 |
8 | def test(self):
9 | from omtk.core.className import BaseName
10 |
11 | # Construct a naming from scratch
12 | n = BaseName(tokens=['eye', 'jnt'], side=BaseName.SIDE_L)
13 | self.assertEqual(n.resolve(), 'l_eye_jnt')
14 |
15 | # Construct a naming from another existing naming
16 | n = BaseName('l_eye_jnt')
17 | self.assertEqual(n.prefix, None)
18 | self.assertEqual(n.suffix, None)
19 | self.assertEqual(n.side, n.SIDE_L)
20 |
21 | # Adding of tokens using suffix
22 | n = BaseName(tokens=['eye'], side=BaseName.SIDE_L, suffix='jnt')
23 | self.assertEqual(n.resolve(), 'l_eye_jnt')
24 | n.tokens.append('micro')
25 | self.assertEqual(n.resolve(), 'l_eye_micro_jnt')
26 |
--------------------------------------------------------------------------------
/tests/test_nomenclature_squeeze.py:
--------------------------------------------------------------------------------
1 | import mayaunittest
2 | from omtk.rigs.rigSqueeze import SqueezeNomenclature
3 |
4 | class NomenclatureTestCase(mayaunittest.TestCase):
5 |
6 | def test(self):
7 | # Construct a naming from scratch
8 | n = SqueezeNomenclature(tokens=['Eye', 'Jnt'], side=SqueezeNomenclature.SIDE_L)
9 | self.assertEqual(n.resolve(), 'L_Eye_Jnt')
10 |
11 | # Construct a naming from another existing naming
12 | n = SqueezeNomenclature('L_Eye_Jnt')
13 | self.assertEqual(n.prefix, None)
14 | self.assertEqual(n.suffix, 'Jnt')
15 | self.assertEqual(n.side, n.SIDE_L)
16 |
17 | # Adding of tokens using suffix
18 | n = SqueezeNomenclature(tokens=['Eye'], side=SqueezeNomenclature.SIDE_L, suffix='Jnt')
19 | self.assertEqual(n.resolve(), 'L_Eye_Jnt')
20 | n.tokens.append('Micro')
21 | self.assertEqual(n.resolve(), 'L_Eye_Micro_Jnt')
22 |
--------------------------------------------------------------------------------
/omtk/core/__init__.py:
--------------------------------------------------------------------------------
1 | import inspect
2 | import json
3 | import logging
4 | import os
5 |
6 | import utils
7 | import classCtrl
8 | import classCtrlModel
9 | import classModule
10 | import classModuleMap
11 | import classModuleCompound
12 | import className
13 | import classNode
14 | import classRig
15 | from api import *
16 | from . import plugin_manager
17 |
18 | log = logging.getLogger('omtk')
19 | log.setLevel(logging.DEBUG)
20 |
21 | # Load configuration file
22 | # Currently this only allow the default rig class from being used.
23 | config = {}
24 | config_dir = os.path.abspath(os.path.join(os.path.dirname(inspect.getfile(inspect.currentframe())), '..'))
25 | config_path = os.path.join(config_dir, 'config.json')
26 | if os.path.exists(config_path):
27 | with open(config_path) as fp:
28 | config = json.load(fp)
29 |
30 | # Load plugins
31 | plugin_manager.plugin_manager.get_plugins() # force evaluating lazy singleton (todo: remove it?)
--------------------------------------------------------------------------------
/omtk/core/utils.py:
--------------------------------------------------------------------------------
1 | import functools
2 |
3 |
4 | class decorator_uiexpose(object):
5 | """
6 | Use this decorator to expose instance method to the GUI.
7 | """
8 |
9 | def __init__(self, flags=None):
10 | if flags is None:
11 | flags = []
12 | self.flags = flags
13 |
14 | def __call__(self, fn, *args, **kwargs):
15 | def wrapped_f(*args, **kwargs):
16 | return fn(*args, **kwargs)
17 |
18 | wrapped_f.__can_show__ = self.__can_show__
19 | wrapped_f._flags = self.flags
20 | return wrapped_f
21 |
22 | def __get__(self, inst, owner):
23 | fn = functools.partial(self.__call__, inst)
24 | fn.__can_show__ = self.__can_show__ # todo: necessary?
25 | fn._flags = self.flags # todo: necessary?
26 | return fn
27 |
28 | def __can_show__(self):
29 | """
30 | This method is used for duck-typing by the interface.
31 | """
32 | return True
33 |
--------------------------------------------------------------------------------
/omtk/constants.py:
--------------------------------------------------------------------------------
1 | class Axis:
2 | """
3 | Fake enum as class with constant variable to represent the axis value that could change
4 | """
5 | x = 'X'
6 | y = 'Y'
7 | z = 'Z'
8 |
9 |
10 | class SpaceSwitchReservedIndex:
11 | """
12 | Fake enum as class with constant variable to represent the reserved index in controller space switch
13 | """
14 | world = -3
15 | local = -2
16 | root = -1
17 |
18 |
19 | class UIExposeFlags:
20 | """
21 | Flags used when exposing Rig or Module functionality in the ui.
22 | """
23 | trigger_network_export = 1
24 |
25 |
26 | class EnvironmentVariables:
27 | """
28 | Customize the behavior of OMTK by defining thoses environment variables.
29 | """
30 | # Force a specific rig type as default.
31 | # Usefull for project-specific configurations.
32 | OMTK_DEFAULT_RIG = 'OMTK_DEFAULT_RIG'
33 |
34 | # Define additional location on disk to search for plugins.
35 | OMTK_PLUGINS = 'OMTK_PLUGINS'
36 |
--------------------------------------------------------------------------------
/omtk/patches/patch_0.3.0_convert_facelids_to_faceeyelids.py:
--------------------------------------------------------------------------------
1 | """
2 | In omtk 0.3.0, the FaceLids module was renamed to FaceEyeLids to prevent
3 | confusion with the FaceLips module.
4 | Run this script to convert a pre-0.3.0 scene.
5 | """
6 |
7 | from omtk.vendor import libSerialization
8 |
9 | cls_rename_map = (
10 | ('FaceLids', 'FaceEyeLids'),
11 | ('CtrlLidUpp', 'CtrlEyeLidUpp'),
12 | ('CtrlLidLow', 'CtrlEyeLidLow'),
13 | ('AvarGrpAreaOnSurface', 'AvarGrpOnSurface')
14 | )
15 | affected_attr_names = ('_class', '_class_namespace')
16 |
17 | for old_name, new_name in cls_rename_map:
18 | for net in libSerialization.get_networks_from_class(old_name):
19 | for attr_name in affected_attr_names:
20 | if not net.hasAttr(attr_name):
21 | print("Missing attribute {} on {}".format(attr_name, net))
22 | continue
23 | print("Renaming {}".format(net))
24 | attr = net.attr(attr_name)
25 | attr.set('.'.join((token.replace(old_name, new_name) for token in attr.get().split('.'))))
--------------------------------------------------------------------------------
/omtk/vendor/libSerialization/plugin_yaml.py:
--------------------------------------------------------------------------------
1 | import yaml
2 | import os
3 | import core
4 |
5 | __all__ = (
6 | 'export_yaml',
7 | 'export_yaml_file',
8 | 'import_yaml',
9 | 'import_yaml_file'
10 | )
11 |
12 |
13 | def export_yaml(data, **kwargs):
14 | dicData = core.export_dict(data)
15 | return yaml.dump(dicData, **kwargs)
16 |
17 |
18 | def export_yaml_file(data, path, mkdir=True, **kwargs):
19 | if mkdir:
20 | dirname = os.path.dirname(path)
21 | if not os.path.exists(dirname):
22 | os.makedirs(path)
23 |
24 | dicData = core.export_dict(data)
25 |
26 | with open(path, 'w') as fp:
27 | yaml.dump(dicData, fp)
28 |
29 | return True
30 |
31 |
32 | def import_yaml(str_, **kwargs):
33 | data = yaml.load(str_)
34 | return core.import_dict(data)
35 |
36 |
37 | def import_yaml_file(path, **kwargs):
38 | if not os.path.exists(path):
39 | raise Exception("Can't importFromYamlFile, file does not exist! {0}".format(path))
40 |
41 | with open(path, 'r') as fp:
42 | data = yaml.load(fp)
43 | return core.import_dict(data)
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Renaud Lessard Larouche
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/omtk/vendor/libSerialization/plugin_json.py:
--------------------------------------------------------------------------------
1 | import os
2 | import json
3 | import core
4 |
5 | __all__ = (
6 | 'export_json',
7 | 'export_json_file',
8 | 'import_json',
9 | 'import_json_file'
10 | )
11 |
12 | #
13 | # Json Support
14 | #
15 |
16 |
17 | def _make_dir(path):
18 | path_dir = os.path.dirname(path)
19 |
20 | # Create destination folder if needed
21 | if not os.path.exists(path_dir):
22 | os.makedirs(path_dir)
23 |
24 |
25 | def export_json(data, indent=4, **kwargs):
26 | data = core.export_dict(data)
27 | return json.dumps(data, indent=indent, **kwargs)
28 |
29 |
30 | def export_json_file(data, path, mkdir=True, indent=4, **kwargs):
31 | if mkdir:
32 | _make_dir(path)
33 |
34 | data_dict = core.export_dict(data)
35 |
36 | with open(path, 'w') as fp:
37 | json.dump(data_dict, fp, indent=indent, **kwargs)
38 |
39 | return True
40 |
41 |
42 | def import_json(str_, **kwargs):
43 | data = json.loads(str_, **kwargs)
44 | return core.import_dict(data)
45 |
46 |
47 | def import_json_file(path, **kwargs):
48 | if not os.path.exists(path):
49 | raise Exception("Can't importFromJsonFile, file does not exist! {0}".format(path))
50 |
51 | with open(path, 'r') as fp:
52 | data = json.load(fp, **kwargs)
53 | return core.import_dict(data)
54 |
--------------------------------------------------------------------------------
/omtk/vendor/libSerialization/decorators.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import functools
3 |
4 | # src: https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
5 | # modified to support kwargs
6 | class memoized(object):
7 | '''Decorator. Caches a function's return value each time it is called.
8 | If called later with the same arguments, the cached value is returned
9 | (not reevaluated).
10 | '''
11 |
12 | def __init__(self, func):
13 | self.func = func
14 | self.cache = {}
15 |
16 | def __call__(self, *args, **kwargs):
17 | if not isinstance(args, collections.Hashable):
18 | # uncacheable. a list, for instance.
19 | # better to not cache than blow up.
20 | return self.func(*args)
21 |
22 | # Include kwargs
23 | # src: http://stackoverflow.com/questions/6407993/how-to-memoize-kwargs
24 | key = (args, frozenset(kwargs.items()))
25 | if key in self.cache:
26 | return self.cache[key]
27 | else:
28 | value = self.func(*args, **kwargs)
29 | self.cache[key] = value
30 | return value
31 |
32 | def __repr__(self):
33 | """Return the function's docstring."""
34 | return self.func.__doc__
35 |
36 | def __get__(self, obj, objtype):
37 | """Support instance methods."""
38 | return functools.partial(self.__call__, obj)
--------------------------------------------------------------------------------
/omtk/macros/ctrls_interactive_swap_mesh.py:
--------------------------------------------------------------------------------
1 | """
2 | Simple utility to swap the mesh used by an InteractiveCtrl in case the wrong mesh was used.
3 | """
4 | from omtk.core.macros import BaseMacro
5 |
6 |
7 | class CtrlInteractiveSwapMesh(BaseMacro):
8 | def _iter_ictrl(self):
9 | from omtk import api
10 | from omtk.modules import rigFaceAvarGrps
11 | from omtk.modules import rigFaceAvar
12 | from omtk.models import model_ctrl_linear
13 | rig, modules = api._get_modules_from_selection()
14 | # Build selected modules
15 | for module in modules:
16 | if isinstance(module, rigFaceAvarGrps.AvarGrp):
17 | for avar in module._iter_all_avars():
18 | if isinstance(avar, rigFaceAvar.AbstractAvar) and \
19 | avar.model_ctrl and \
20 | isinstance(avar.model_ctrl, model_ctrl_linear.ModelCtrlLinear):
21 | yield avar.model_ctrl
22 |
23 | def run(self):
24 | import pymel.core as pymel
25 | from omtk.libs import libPymel
26 |
27 | mesh = next((obj for obj in pymel.selected() if libPymel.isinstance_of_shape(obj, cls=pymel.nodetypes.Mesh)), None)
28 | if not mesh:
29 | pymel.warning("Please select a mesh to follow.")
30 | return
31 |
32 | for ctrl_model in self._iter_ictrl():
33 | ctrl_model.swap_mesh(mesh)
34 |
35 |
36 | def register_plugin():
37 | return CtrlInteractiveSwapMesh
38 |
--------------------------------------------------------------------------------
/omtk/ui/widget_list_meshes.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 316
10 | 295
11 |
12 |
13 |
14 | Form
15 |
16 |
17 |
18 | 0
19 |
20 | -
21 |
22 |
-
23 |
24 |
25 | -
26 |
27 |
28 | Update
29 |
30 |
31 |
32 |
33 |
34 | -
35 |
36 |
37 | false
38 |
39 |
40 |
41 | 1
42 |
43 |
44 |
45 |
46 | -
47 |
48 |
49 | Select Meshes Grp
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/omtk/preferences_window.py:
--------------------------------------------------------------------------------
1 | from ui import preferences_window
2 | from omtk.core import plugin_manager
3 | from omtk.core import preferences
4 |
5 | from omtk.vendor.Qt import QtCore, QtWidgets
6 |
7 |
8 | class PreferencesWindow(QtWidgets.QDialog):
9 | searchQueryChanged = QtCore.Signal(str)
10 |
11 | def __init__(self, parent=None):
12 | super(PreferencesWindow, self).__init__(parent=parent)
13 |
14 | # Initialize GUI
15 | self.ui = preferences_window.Ui_Dialog()
16 | self.ui.setupUi(self)
17 |
18 | # Fill the QComboBox
19 | self.rig_plugins = sorted(plugin_manager.plugin_manager.get_loaded_plugins_by_type('rigs'))
20 | rig_plugins_names = [plugin.cls.__name__ for plugin in self.rig_plugins if plugin]
21 | labels = ['Default'] + rig_plugins_names
22 |
23 | self.ui.comboBox.addItems(labels)
24 |
25 | default_rig_type_name = preferences.preferences.get_default_rig_class().__name__
26 | if default_rig_type_name in rig_plugins_names:
27 | self.ui.comboBox.setCurrentIndex(rig_plugins_names.index(default_rig_type_name) + 1)
28 |
29 | # Connect events
30 | self.ui.comboBox.currentIndexChanged.connect(self.on_default_rig_changed)
31 |
32 | def on_default_rig_changed(self, index):
33 | if index == 0:
34 | preferences.preferences.default_rig = None
35 | else:
36 | preferences.preferences.default_rig = self.rig_plugins[index - 1].cls.__name__
37 |
38 | preferences.preferences.save()
39 |
40 |
41 | gui = PreferencesWindow()
42 |
43 |
44 | def show():
45 | global gui
46 | gui.show()
47 |
--------------------------------------------------------------------------------
/omtk/modules/template:
--------------------------------------------------------------------------------
1 | import collections
2 |
3 | from omtk.core.classCtrl import BaseCtrl
4 | from omtk.core.classModule import Module
5 | from omtk.libs import libRigging, libCtrlShapes
6 |
7 |
8 | class BaseAttHolder(BaseCtrl):
9 | def __createNode__(self, size=None, refs=None, **kwargs):
10 | # Resolve size automatically if refs are provided.
11 | ref = next(iter(refs), None) if isinstance(refs, collections.Iterable) else refs
12 | if size is None and ref is not None:
13 | size = libRigging.get_recommended_ctrl_size(ref)
14 | else:
15 | size = 1.0
16 |
17 | node = libCtrlShapes.create_shape_attrholder(size=size, **kwargs)
18 |
19 | # Hide default keyable attributes
20 | node.t.set(channelBox=False)
21 | node.r.set(channelBox=False)
22 | node.s.set(channelBox=False)
23 |
24 | return node
25 |
26 |
27 | class MyCtrl(BaseCtrl):
28 | """
29 | If you need specific ctrls for you module, you can inherit from BaseCtrl directly.
30 | """
31 | pass
32 |
33 |
34 | class MyModule(Module):
35 | def __init__(self, *args, **kwargs):
36 | """
37 | Pre-declare here all the used members.
38 | """
39 | super(MyModule, self).__init__(*args, **kwargs)
40 |
41 | def build(self, *args, **kwargs):
42 | super(MyModule, self).build(*args, **kwargs)
43 |
44 | nomenclature_anm = self.get_nomenclature_anm()
45 | nomenclature_rig = self.get_nomenclature_rig()
46 |
47 | raise NotImplementedError
48 |
49 | def unbuild(self):
50 | """
51 | If you are using sub-modules, you might want to clean them here.
52 | :return:
53 | """
54 | super(MyModule, self).unbuild()
55 |
--------------------------------------------------------------------------------
/omtk/ui/widget_list_modules.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 316
10 | 295
11 |
12 |
13 |
14 | Form
15 |
16 |
17 |
18 | 0
19 |
20 | -
21 |
22 |
-
23 |
24 |
25 | -
26 |
27 |
28 | Update
29 |
30 |
31 |
32 |
33 |
34 | -
35 |
36 |
37 | Qt::StrongFocus
38 |
39 |
40 | Qt::CustomContextMenu
41 |
42 |
43 | true
44 |
45 |
46 | QAbstractItemView::ExtendedSelection
47 |
48 |
49 | false
50 |
51 |
52 |
53 | 1
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/omtk/modules/rigFaceSquint.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk.libs import libCtrlShapes
4 | from omtk.libs import libRigging
5 | from omtk.modules import rigFaceAvar
6 | from omtk.modules import rigFaceAvarGrps
7 |
8 |
9 | class CtrlSquint(rigFaceAvar.BaseCtrlFace):
10 | def __createNode__(self, **kwargs):
11 | return libCtrlShapes.create_triangle_upp()
12 |
13 |
14 | class FaceSquint(rigFaceAvarGrps.AvarGrpOnSurface):
15 | """
16 | AvarGrp setup customized for squint rigging.
17 | """
18 | _CLS_CTRL = CtrlSquint
19 | IS_SIDE_SPECIFIC = False
20 | SHOW_IN_UI = True
21 | CREATE_MACRO_AVAR_HORIZONTAL = True
22 | CREATE_MACRO_AVAR_VERTICAL = False
23 | CREATE_MACRO_AVAR_ALL = False
24 |
25 | def get_default_name(self):
26 | return 'squint'
27 |
28 | def _create_avar_macro_l_ctrls(self, ctrl_tm=None, **kwargs):
29 | # Find the middle of l eyebrow.
30 | pos = libRigging.get_average_pos_between_vectors(self.get_jnts_l())
31 | ctrl_tm = pymel.datatypes.Matrix(
32 | 1, 0, 0, 0,
33 | 0, 1, 0, 0,
34 | 0, 0, 1, 0,
35 | pos.x, pos.y, pos.z, 1
36 | )
37 |
38 | super(FaceSquint, self)._create_avar_macro_l_ctrls(ctrl_tm=ctrl_tm)
39 |
40 | def _create_avar_macro_r_ctrls(self, ctrl_tm=None, **kwargs):
41 | # Find the middle of l eyebrow.
42 | pos = libRigging.get_average_pos_between_vectors(self.get_jnts_r())
43 | ctrl_tm = pymel.datatypes.Matrix(
44 | 1, 0, 0, 0,
45 | 0, 1, 0, 0,
46 | 0, 0, 1, 0,
47 | pos.x, pos.y, pos.z, 1
48 | )
49 |
50 | super(FaceSquint, self)._create_avar_macro_r_ctrls(ctrl_tm=ctrl_tm)
51 |
52 |
53 | def register_plugin():
54 | return FaceSquint
55 |
--------------------------------------------------------------------------------
/omtk/patches/patch_0_4_7_add_leg_auto_roll_threshold.py:
--------------------------------------------------------------------------------
1 | """
2 | In omtk 0.4.7, the Leg module now preserve the auto-roll threshold value.
3 | Run this script to convert a pre-0.4.7 scene.
4 | """
5 |
6 | import omtk; reload(omtk); omtk._reload()
7 | from omtk.modules import rigLeg
8 | import pymel.core as pymel
9 |
10 | NET_ATTR_NAME = 'attrAutoRollThreshold'
11 |
12 |
13 | def _module_need_patch(module):
14 | # Verify module type
15 | if not isinstance(module, rigLeg.Leg):
16 | return False
17 |
18 | # Verify module state
19 | if not module.is_built():
20 | return False
21 | if not (module.sysIK and module.sysIK.is_built()):
22 | return False
23 |
24 | # Verify module version
25 | major, minor, patch = [int(val) for val in getattr(module, 'version', '0.0.0').split('.')]
26 | if major > 0 or minor > 4 or patch > 7:
27 | return False
28 |
29 | # Verify the module is not already patched.
30 | net = module.sysIK._network
31 | if net.hasAttr(NET_ATTR_NAME):
32 | return False
33 |
34 | return True
35 |
36 |
37 | def _patch_module(module):
38 | net = module.sysIK._network
39 | ctrl = module.sysIK.ctrl_ik
40 | attr_anm = ctrl.rollAutoThreshold
41 | if not net.hasAttr('attrAutoRollThreshold'):
42 | print("Fixing {}".format(net))
43 | pymel.addAttr(net, longName='attrAutoRollThreshold')
44 | pymel.connectAttr(attr_anm, net.attrAutoRollThreshold)
45 |
46 |
47 | def run():
48 | rig = omtk.find_one()
49 | modules = [module for module in rig.modules if _module_need_patch(module)]
50 |
51 | if not modules:
52 | print("Nothing to patch.")
53 | return
54 |
55 | for module in modules:
56 | print("Patching {}".format(module))
57 | _patch_module(module)
58 |
--------------------------------------------------------------------------------
/omtk/ui/widget_list_influences.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 316
10 | 295
11 |
12 |
13 |
14 | Form
15 |
16 |
17 |
18 | 0
19 |
20 | -
21 |
22 |
-
23 |
24 |
25 | -
26 |
27 |
28 | Update
29 |
30 |
31 |
32 |
33 |
34 | -
35 |
36 |
37 | Hide Assigned
38 |
39 |
40 | true
41 |
42 |
43 |
44 | -
45 |
46 |
47 | Qt::CustomContextMenu
48 |
49 |
50 | QAbstractItemView::ExtendedSelection
51 |
52 |
53 | false
54 |
55 |
56 |
57 | 1
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/omtk/models/model_avar_linear.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk.core import classNode
4 | from omtk.libs import libAttr
5 | from omtk.libs import libRigging
6 |
7 | from .model_avar_base import AvarInflBaseModel
8 |
9 |
10 | class AvarLinearModel(AvarInflBaseModel):
11 | """
12 | A deformation point on the face that move accordingly to nurbsSurface.
13 | """
14 | SHOW_IN_UI = False
15 |
16 | _ATTR_NAME_MULT_LR = 'multiplierLr'
17 | _ATTR_NAME_MULT_UD = 'multiplierUd'
18 | _ATTR_NAME_MULT_FB = 'multiplierFb'
19 |
20 | def __init__(self, *args, **kwargs):
21 | super(AvarLinearModel, self).__init__(*args, **kwargs)
22 |
23 | # Reference to the object containing the bind pose of the avar.
24 | self._obj_offset = None
25 |
26 | def _build(self):
27 | nomenclature_rig = self.get_nomenclature_rig()
28 |
29 | grp_output = pymel.createNode(
30 | 'transform',
31 | name=nomenclature_rig.resolve('output'),
32 | parent=self.grp_rig,
33 | )
34 |
35 | attr_get_t = libRigging.create_utility_node(
36 | 'multiplyDivide',
37 | input1X=self._attr_inn_lr,
38 | input1Y=self._attr_inn_ud,
39 | input1Z=self._attr_inn_fb,
40 | input2X=self.multiplier_lr,
41 | input2Y=self.multiplier_ud,
42 | input2Z=self.multiplier_fb,
43 | ).output
44 |
45 | pymel.connectAttr(attr_get_t, grp_output.translate)
46 | pymel.connectAttr(self._attr_inn_pt, grp_output.rotateX)
47 | pymel.connectAttr(self._attr_inn_yw, grp_output.rotateY)
48 | pymel.connectAttr(self._attr_inn_rl, grp_output.rotateZ)
49 | pymel.connectAttr(self._attr_inn_sx, grp_output.scaleX)
50 | pymel.connectAttr(self._attr_inn_sy, grp_output.scaleY)
51 | pymel.connectAttr(self._attr_inn_sz, grp_output.scaleZ)
52 |
53 | return grp_output.matrix
54 |
--------------------------------------------------------------------------------
/omtk/modules/rigFaceBrow.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 | from omtk.modules import rigFaceAvarGrps
3 | from omtk.libs import libCtrlShapes
4 | from omtk.libs import libRigging
5 | from omtk.modules import rigFaceAvar
6 |
7 |
8 | class CtrlBrow(rigFaceAvar.BaseCtrlFace):
9 | def __createNode__(self, **kwargs):
10 | return libCtrlShapes.create_triangle_low()
11 |
12 |
13 | class FaceBrow(rigFaceAvarGrps.AvarGrpOnSurface):
14 | """
15 | AvarGrp customized for Brow rigging.
16 | """
17 | _CLS_CTRL = CtrlBrow
18 |
19 | SHOW_IN_UI = True
20 | IS_SIDE_SPECIFIC = False
21 | CREATE_MACRO_AVAR_HORIZONTAL = True
22 | CREATE_MACRO_AVAR_VERTICAL = False
23 | CREATE_MACRO_AVAR_ALL = False
24 |
25 | def _create_avar_macro_l_ctrls(self, ctrl_tm=None, **kwargs):
26 | # Find the middle of l eyebrow.
27 | pos = libRigging.get_average_pos_between_vectors(self.get_jnts_l())
28 | ctrl_tm = pymel.datatypes.Matrix(
29 | 1, 0, 0, 0,
30 | 0, 1, 0, 0,
31 | 0, 0, 1, 0,
32 | pos.x, pos.y, pos.z, 1
33 | )
34 |
35 | super(FaceBrow, self)._create_avar_macro_l_ctrls(ctrl_tm=ctrl_tm)
36 |
37 | def _create_avar_macro_r_ctrls(self, ctrl_tm=None, **kwargs):
38 | # Find the middle of l eyebrow.
39 | # We expect the right-side influence to be mirrored in behavior.
40 | # However this should be applied to to influence tm, not the ctrl tm. Clean this please.
41 | pos = libRigging.get_average_pos_between_vectors(self.get_jnts_r())
42 | ctrl_tm = pymel.datatypes.Matrix(
43 | 1, 0, 0, 0,
44 | 0, -1.0, 0, 0,
45 | 0, 0, -1.0, 0,
46 | pos.x, pos.y, pos.z, 1
47 | )
48 |
49 | super(FaceBrow, self)._create_avar_macro_r_ctrls(ctrl_tm=ctrl_tm)
50 |
51 | def get_default_name(self):
52 | return 'brow'
53 |
54 |
55 | def register_plugin():
56 | return FaceBrow
57 |
--------------------------------------------------------------------------------
/omtk/ui/widget_component_wizard_parts.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 400
10 | 300
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | -
18 |
19 |
20 | QAbstractItemView::SelectRows
21 |
22 |
23 | true
24 |
25 |
26 |
27 | -
28 |
29 |
-
30 |
31 |
32 | Add
33 |
34 |
35 |
36 | -
37 |
38 |
39 | Remove
40 |
41 |
42 |
43 |
44 |
45 | -
46 |
47 |
-
48 |
49 |
50 | Connect
51 |
52 |
53 |
54 | -
55 |
56 |
57 | Disconnect
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/omtk/modules/rigFaceNose.py:
--------------------------------------------------------------------------------
1 | from omtk.modules import rigFaceAvarGrps
2 | from omtk.modules import rigFaceAvar
3 | from omtk.models import model_ctrl_linear
4 |
5 |
6 | class CtrlNose(rigFaceAvar.CtrlFaceMicro):
7 | pass
8 |
9 |
10 | class ModelMicroAvarNose(model_ctrl_linear.ModelCtrlLinear):
11 | def connect(self, avar, avar_grp, ud=True, fb=True, lr=True, yw=True, pt=True, rl=True, sx=True, sy=True, sz=True):
12 | avar_tweak = avar_grp._get_micro_tweak_avars_dict().get(avar, None)
13 | if avar_tweak:
14 | super(ModelMicroAvarNose, self).connect(avar, avar_grp, ud=ud, fb=fb, lr=False, yw=False, pt=False,
15 | rl=False, sx=False, sy=False, sz=False)
16 | super(ModelMicroAvarNose, self).connect(avar_tweak, avar_grp, ud=False, fb=False, lr=lr, yw=yw, pt=pt,
17 | rl=rl, sx=sx, sy=sy, sz=sz)
18 | else:
19 | super(ModelMicroAvarNose, self).connect(avar, avar_grp, ud=ud, fb=fb, lr=lr, yw=yw, pt=pt, rl=rl, sx=sx,
20 | sy=sy, sz=sz)
21 |
22 |
23 | class FaceNose(rigFaceAvarGrps.AvarGrpOnSurface):
24 | """
25 | The Nose is composed of two zones. The uppernose and the lower nose.
26 | The uppernose is user specifically for it's yaw and pitch rotation.
27 | Everything under is considered a nostril.
28 |
29 | Note that this was done reallllly quickly and cleanup may be needed in the future.
30 | """
31 | # _DEFORMATION_ORDER = 'post'
32 | # _CLS_AVAR = AvarJaw
33 | SHOW_IN_UI = True
34 | IS_SIDE_SPECIFIC = False
35 | _CLS_CTRL = CtrlNose
36 | _CLS_MODEL_CTRL_MICRO = ModelMicroAvarNose
37 | CREATE_MACRO_AVAR_ALL = True
38 | CREATE_MACRO_AVAR_HORIZONTAL = False
39 | CREATE_MACRO_AVAR_VERTICAL = False
40 |
41 | def get_default_name(self):
42 | return 'nose'
43 |
44 |
45 | def register_plugin():
46 | return FaceNose
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Open Maya Rigging Toolkit
2 |
3 | Omtk is a lightweight suite of production rigging tools for Maya.
4 | This include an object-oriented modular autorig solution.
5 | Using metadata, generated rigs can be unbuilt and rebuilt if modifications need to be made.
6 |
7 | ### Installation
8 |
9 | 1. Clone the repository or download a release
10 |
11 | 2. Add this directory to your `PYTHONPATH` environment variable
12 |
13 | 3. Open Maya
14 |
15 | Alternatively, if you are using [rez](https://github.com/nerdvegas/rez), a `package.py` file is included.
16 |
17 | ### Rules
18 |
19 | The following rules simplify the implementation of the system and must be respected for Omtk to work properly.
20 |
21 | - All influences are in a separated hierarchy.
22 | - All joints point toward the X axis, Z is their up axis. This mean the roll axis is always x and the primary axis is z.
23 | - The character should look toward the positive Z axis.
24 | - Feets orientation and straight, always.
25 | - All python code should respect the PEP8 standards.
26 | - All nodes and attributes created in a Maya scene are in camelCase for better integration in Maya.
27 |
28 | ### Third parties
29 |
30 | Omtk vendor the following third party libraries:
31 |
32 | ### libFormula
33 | A lightweight programming language that create maya utility nodes setup by parsing mathematical formulas.
34 |
35 | Read the [documentation](http://github.com/renaudll/omtk/wiki/omtk.libs.libFormula).
36 |
37 | ### libSerialization
38 | An IO module that allow serialization/deserialisation of Python objects to Maya networks.
39 |
40 | Read the [documentation](https://github.com/renaudll/libSerialization).
41 |
42 | ### Qt.py
43 | Minimal Python 2 & 3 shim around all Qt bindings - PySide, PySide2, PyQt4 and PyQt5.
44 |
45 | Read the [documentation](https://github.com/mottosso/Qt.py)
46 |
47 | ### Testing
48 |
49 | If you are on Linux and have `pytest` installed you can run the unittests with the `test.sh` script.
50 |
51 | ### Support
52 |
53 | If you are in need of support, please create a GitHub issue.
--------------------------------------------------------------------------------
/omtk/animation/ikfkTools.py:
--------------------------------------------------------------------------------
1 | import functools
2 | import logging
3 | import pymel.core as pymel
4 |
5 | from omtk.vendor import libSerialization
6 |
7 |
8 | def _get_module_networks_from_selection(module_name):
9 | """
10 | Search for specific module networks recursively, starting at selection.
11 | Note that to help with performances, we'll skip any Rig network we encounter.
12 | :param module_name: The name of the module to search for.
13 | :return: A list of pymel.nodetypes.Network.
14 | """
15 |
16 | def fn_skip(network):
17 | return libSerialization.is_network_from_class(network, 'Rig')
18 |
19 | def fn_key(network):
20 | return libSerialization.is_network_from_class(network, module_name)
21 |
22 | sel = pymel.selected()
23 | networks = libSerialization.get_connected_networks(sel, recursive=True, key=fn_key, key_skip=fn_skip)
24 |
25 | modules = [libSerialization.import_network(network, fn_skip=fn_skip) for network in networks]
26 | modules = filter(None, modules) # Filter any invalid networks that libSerialization doesn't protect us from.
27 | return modules
28 |
29 |
30 | def _call_on_networks_by_class(fn_name, module_name):
31 | modules = _get_module_networks_from_selection(module_name)
32 | for module in modules:
33 | # Resolve function
34 | if not hasattr(module, fn_name):
35 | logging.warning("Can't find attribute {0} in {1}".format(fn_name, module))
36 | continue
37 | fn = getattr(module, fn_name)
38 | if not hasattr(fn, '__call__'):
39 | logging.warning("Can't execute {0} in {1}, not callable!".format(fn_name, module))
40 | continue
41 |
42 | # Execute function
43 | try:
44 | fn()
45 | except Exception, e:
46 | logging.warning("Error excecuting {0} in {1}! {2}".format(fn_name, module, str(e)))
47 |
48 |
49 | switchToIk = functools.partial(_call_on_networks_by_class, 'switch_to_ik', 'Limb')
50 | switchToFk = functools.partial(_call_on_networks_by_class, 'switch_to_fk', 'Limb')
51 |
--------------------------------------------------------------------------------
/omtk/ui/widget_list_modules.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/widget_list_modules.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:54 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_Form(object):
13 | def setupUi(self, Form):
14 | Form.setObjectName("Form")
15 | Form.resize(316, 295)
16 | self.verticalLayout = QtWidgets.QVBoxLayout(Form)
17 | self.verticalLayout.setContentsMargins(0, 0, 0, 0)
18 | self.verticalLayout.setObjectName("verticalLayout")
19 | self.horizontalLayout = QtWidgets.QHBoxLayout()
20 | self.horizontalLayout.setObjectName("horizontalLayout")
21 | self.lineEdit_search = QtWidgets.QLineEdit(Form)
22 | self.lineEdit_search.setObjectName("lineEdit_search")
23 | self.horizontalLayout.addWidget(self.lineEdit_search)
24 | self.btn_update = QtWidgets.QPushButton(Form)
25 | self.btn_update.setObjectName("btn_update")
26 | self.horizontalLayout.addWidget(self.btn_update)
27 | self.verticalLayout.addLayout(self.horizontalLayout)
28 | self.treeWidget = QtWidgets.QTreeWidget(Form)
29 | self.treeWidget.setFocusPolicy(QtCore.Qt.StrongFocus)
30 | self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
31 | self.treeWidget.setAutoFillBackground(True)
32 | self.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
33 | self.treeWidget.setObjectName("treeWidget")
34 | self.treeWidget.headerItem().setText(0, "1")
35 | self.treeWidget.header().setVisible(False)
36 | self.verticalLayout.addWidget(self.treeWidget)
37 |
38 | self.retranslateUi(Form)
39 | QtCore.QMetaObject.connectSlotsByName(Form)
40 |
41 | def retranslateUi(self, Form):
42 | Form.setWindowTitle(QtCompat.translate("Form", "Form", None, -1))
43 | self.btn_update.setText(QtCompat.translate("Form", "Update", None, -1))
44 |
45 |
--------------------------------------------------------------------------------
/omtk/ui/preferences_window.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/preferences_window.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:53 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_Dialog(object):
13 | def setupUi(self, Dialog):
14 | Dialog.setObjectName("Dialog")
15 | Dialog.resize(383, 229)
16 | self.verticalLayout = QtWidgets.QVBoxLayout(Dialog)
17 | self.verticalLayout.setObjectName("verticalLayout")
18 | self.gridLayout = QtWidgets.QGridLayout()
19 | self.gridLayout.setObjectName("gridLayout")
20 | self.label = QtWidgets.QLabel(Dialog)
21 | self.label.setObjectName("label")
22 | self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
23 | self.comboBox = QtWidgets.QComboBox(Dialog)
24 | self.comboBox.setObjectName("comboBox")
25 | self.gridLayout.addWidget(self.comboBox, 0, 1, 1, 1)
26 | self.verticalLayout.addLayout(self.gridLayout)
27 | spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
28 | self.verticalLayout.addItem(spacerItem)
29 | self.buttonBox = QtWidgets.QDialogButtonBox(Dialog)
30 | self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
31 | self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
32 | self.buttonBox.setObjectName("buttonBox")
33 | self.verticalLayout.addWidget(self.buttonBox)
34 |
35 | self.retranslateUi(Dialog)
36 | QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("accepted()"), Dialog.accept)
37 | QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL("rejected()"), Dialog.reject)
38 | QtCore.QMetaObject.connectSlotsByName(Dialog)
39 |
40 | def retranslateUi(self, Dialog):
41 | Dialog.setWindowTitle(QtCompat.translate("Dialog", "OMTK - Preferences", None, -1))
42 | self.label.setText(QtCompat.translate("Dialog", "Default Rig Class", None, -1))
43 |
44 |
--------------------------------------------------------------------------------
/omtk/ui/widget_list_meshes.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/widget_list_meshes.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:53 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_Form(object):
13 | def setupUi(self, Form):
14 | Form.setObjectName("Form")
15 | Form.resize(316, 295)
16 | self.verticalLayout = QtWidgets.QVBoxLayout(Form)
17 | self.verticalLayout.setContentsMargins(0, 0, 0, 0)
18 | self.verticalLayout.setObjectName("verticalLayout")
19 | self.horizontalLayout = QtWidgets.QHBoxLayout()
20 | self.horizontalLayout.setObjectName("horizontalLayout")
21 | self.lineEdit_search = QtWidgets.QLineEdit(Form)
22 | self.lineEdit_search.setObjectName("lineEdit_search")
23 | self.horizontalLayout.addWidget(self.lineEdit_search)
24 | self.btn_update = QtWidgets.QPushButton(Form)
25 | self.btn_update.setObjectName("btn_update")
26 | self.horizontalLayout.addWidget(self.btn_update)
27 | self.verticalLayout.addLayout(self.horizontalLayout)
28 | self.treeWidget = QtWidgets.QTreeWidget(Form)
29 | self.treeWidget.setObjectName("treeWidget")
30 | self.treeWidget.headerItem().setText(0, "1")
31 | self.treeWidget.header().setVisible(False)
32 | self.verticalLayout.addWidget(self.treeWidget)
33 | self.pushButton_selectGrpMeshes = QtWidgets.QPushButton(Form)
34 | self.pushButton_selectGrpMeshes.setObjectName("pushButton_selectGrpMeshes")
35 | self.verticalLayout.addWidget(self.pushButton_selectGrpMeshes)
36 |
37 | self.retranslateUi(Form)
38 | QtCore.QMetaObject.connectSlotsByName(Form)
39 |
40 | def retranslateUi(self, Form):
41 | Form.setWindowTitle(QtCompat.translate("Form", "Form", None, -1))
42 | self.btn_update.setText(QtCompat.translate("Form", "Update", None, -1))
43 | self.pushButton_selectGrpMeshes.setText(QtCompat.translate("Form", "Select Meshes Grp", None, -1))
44 |
45 |
--------------------------------------------------------------------------------
/omtk/core/preferences.py:
--------------------------------------------------------------------------------
1 | """
2 | Provide a Preference class to store the user preferences of the local installation.
3 | """
4 | import os
5 | import inspect
6 | import json
7 | import logging
8 |
9 | log = logging.getLogger('omtk')
10 |
11 | from omtk import constants
12 |
13 | CONFIG_FILENAME = 'config.json'
14 |
15 |
16 | def get_path_preferences():
17 | """
18 | :return: The search path of the configuration file.
19 | """
20 | current_dir = os.path.dirname(inspect.getfile(inspect.currentframe()))
21 | config_dir = os.path.abspath(os.path.join(current_dir, '..', '..'))
22 | config_path = os.path.join(config_dir, CONFIG_FILENAME)
23 | return config_path
24 |
25 |
26 | class Preferences(object):
27 | def __init__(self):
28 | self.default_rig = None
29 |
30 | def save(self, path=None):
31 | if path is None:
32 | path = get_path_preferences()
33 |
34 | data = self.__dict__
35 | with open(path, 'w') as fp:
36 | json.dump(data, fp)
37 |
38 | def load(self, path=None):
39 | if path is None:
40 | path = get_path_preferences()
41 |
42 | if not path or not os.path.exists(path):
43 | log.warning("Can't find config file. Using default config.")
44 | return
45 |
46 | with open(path, 'r') as fp:
47 | data = json.load(fp)
48 | self.__dict__.update(data)
49 |
50 | def get_default_rig_class(self):
51 | from omtk.core import plugin_manager
52 |
53 | # Listen to an environment variable to drive the default rig for specific projects.
54 | default_rig = self.default_rig
55 |
56 | default_rig_override = os.environ.get(constants.EnvironmentVariables.OMTK_DEFAULT_RIG, None)
57 | if default_rig_override:
58 | default_rig = default_rig_override
59 |
60 | if default_rig:
61 | for plugin in plugin_manager.plugin_manager.iter_loaded_plugins_by_type('rigs'):
62 | if plugin.cls.__name__ == default_rig:
63 | return plugin.cls
64 | log.warning("Can't find default rig type {0}.".format(default_rig))
65 |
66 | # If no match is found, return the base implementation
67 | from omtk.core import classRig
68 | return classRig.Rig
69 |
70 |
71 | preferences = Preferences()
72 | preferences.load()
73 |
--------------------------------------------------------------------------------
/omtk/ui/widget_list_influences.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/widget_list_influences.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:53 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_Form(object):
13 | def setupUi(self, Form):
14 | Form.setObjectName("Form")
15 | Form.resize(316, 295)
16 | self.verticalLayout = QtWidgets.QVBoxLayout(Form)
17 | self.verticalLayout.setContentsMargins(0, 0, 0, 0)
18 | self.verticalLayout.setObjectName("verticalLayout")
19 | self.horizontalLayout = QtWidgets.QHBoxLayout()
20 | self.horizontalLayout.setObjectName("horizontalLayout")
21 | self.lineEdit_search = QtWidgets.QLineEdit(Form)
22 | self.lineEdit_search.setObjectName("lineEdit_search")
23 | self.horizontalLayout.addWidget(self.lineEdit_search)
24 | self.btn_update = QtWidgets.QPushButton(Form)
25 | self.btn_update.setObjectName("btn_update")
26 | self.horizontalLayout.addWidget(self.btn_update)
27 | self.verticalLayout.addLayout(self.horizontalLayout)
28 | self.checkBox_hideAssigned = QtWidgets.QCheckBox(Form)
29 | self.checkBox_hideAssigned.setChecked(True)
30 | self.checkBox_hideAssigned.setObjectName("checkBox_hideAssigned")
31 | self.verticalLayout.addWidget(self.checkBox_hideAssigned)
32 | self.treeWidget = QtWidgets.QTreeWidget(Form)
33 | self.treeWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
34 | self.treeWidget.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
35 | self.treeWidget.setObjectName("treeWidget")
36 | self.treeWidget.headerItem().setText(0, "1")
37 | self.treeWidget.header().setVisible(False)
38 | self.verticalLayout.addWidget(self.treeWidget)
39 |
40 | self.retranslateUi(Form)
41 | QtCore.QMetaObject.connectSlotsByName(Form)
42 |
43 | def retranslateUi(self, Form):
44 | Form.setWindowTitle(QtCompat.translate("Form", "Form", None, -1))
45 | self.btn_update.setText(QtCompat.translate("Form", "Update", None, -1))
46 | self.checkBox_hideAssigned.setText(QtCompat.translate("Form", "Hide Assigned", None, -1))
47 |
48 |
--------------------------------------------------------------------------------
/omtk/ui/preferences_window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Dialog
4 |
5 |
6 |
7 | 0
8 | 0
9 | 383
10 | 229
11 |
12 |
13 |
14 | OMTK - Preferences
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Default Rig Class
23 |
24 |
25 |
26 | -
27 |
28 |
29 |
30 |
31 | -
32 |
33 |
34 | Qt::Vertical
35 |
36 |
37 |
38 | 20
39 | 40
40 |
41 |
42 |
43 |
44 | -
45 |
46 |
47 | Qt::Horizontal
48 |
49 |
50 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | buttonBox
60 | accepted()
61 | Dialog
62 | accept()
63 |
64 |
65 | 248
66 | 254
67 |
68 |
69 | 157
70 | 274
71 |
72 |
73 |
74 |
75 | buttonBox
76 | rejected()
77 | Dialog
78 | reject()
79 |
80 |
81 | 316
82 | 260
83 |
84 |
85 | 286
86 | 274
87 |
88 |
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/omtk/patches/patch_0_4_32_support_multiple_root_ctrls.py:
--------------------------------------------------------------------------------
1 | """
2 | In omtk 0.4.32, we added support for multiple root ctrls.
3 | Some facial systems were using the root ctrl local transform which caused issues when having an hyerarchy of root ctrls.
4 | """
5 | import logging
6 | import pymel.core as pymel
7 | import omtk
8 | from omtk.modules import rigFaceAvar
9 | from omtk.modules import rigFaceAvarGrps
10 | from omtk.models import model_ctrl_linear
11 | from omtk.libs import libRigging
12 |
13 |
14 | def _get_ctrl_model_parent_grp(ctrl_model):
15 | if ctrl_model and ctrl_model.is_built():
16 | grp_rig = ctrl_model.grp_rig
17 | for child in grp_rig.getChildren():
18 | if '_parent' in child.nodeName().lower():
19 | return child
20 |
21 |
22 | def _grp_parent_need_fix(grp_parent):
23 | # We expect the grp_parent inputs to be a a decomposeMatrix node.
24 | obj_input = next(iter(grp_parent.translate.inputs()), None)
25 | return not isinstance(obj_input, pymel.nodetypes.DecomposeMatrix)
26 |
27 |
28 | def _patch_grp_parent(grp_parent, rig):
29 | u = libRigging.create_utility_node('decomposeMatrix', inputMatrix=rig.grp_anm.worldMatrix)
30 | pymel.connectAttr(u.outputTranslate, grp_parent.translate, force=True)
31 | pymel.connectAttr(u.outputRotate, grp_parent.rotate, force=True)
32 | pymel.connectAttr(u.outputScale, grp_parent.scale, force=True)
33 |
34 |
35 | def _iter_all_ctrl_models():
36 | rigs = omtk.find()
37 | for rig in rigs:
38 | for module in rig.modules:
39 | if isinstance(module, rigFaceAvarGrps.AvarGrp) and module.get_version() < (
40 | 0, 4, 32): # AvarGrp contain avars
41 | for avar in module._iter_all_avars():
42 | if isinstance(avar, rigFaceAvar.AvarSimple): # AvarSimple introduced the ctrl_model attribute
43 | ctrl_model = avar.model_ctrl
44 | if isinstance(ctrl_model, model_ctrl_linear.ModelCtrlLinear):
45 | yield rig, ctrl_model
46 |
47 |
48 | def run():
49 | for rig, ctrl_model in _iter_all_ctrl_models():
50 | grp_parent = _get_ctrl_model_parent_grp(ctrl_model)
51 | if not grp_parent:
52 | logging.warning("Could not find a grp_parent for {0}".format(ctrl_model))
53 | continue
54 | if _grp_parent_need_fix(grp_parent):
55 | logging.info("Patching {0}".format(grp_parent))
56 | _patch_grp_parent(grp_parent, rig)
57 |
58 |
--------------------------------------------------------------------------------
/tests/test_libFormula.py:
--------------------------------------------------------------------------------
1 | import mayaunittest
2 | from omtk.libs import libFormula
3 | import pymel.core as pymel
4 |
5 | class SampleTests(mayaunittest.TestCase):
6 |
7 | def _create_pymel_node(self, val_tx=1, val_ty=2, val_tz=3, val_rx=4, val_ry=5, val_rz=6, val_sx=7, val_sy=8, val_sz=9):
8 | t = pymel.createNode('transform')
9 | t.tx.set(val_tx)
10 | t.ty.set(val_ty)
11 | t.tz.set(val_tz)
12 | t.rx.set(val_rx)
13 | t.ry.set(val_ry)
14 | t.rz.set(val_rz)
15 | t.sx.set(val_sx)
16 | t.sy.set(val_sy)
17 | t.sz.set(val_sz)
18 | return t
19 |
20 | def _create_pymel_attrs(self, *args, **kwargs):
21 | t = self._create_pymel_node(*args, **kwargs)
22 | return t.tx, t.ty, t.tz, t.rx, t.ry, t.rz, t.sx, t.sy, t.sz
23 |
24 | def test_add(self):
25 | v = libFormula.parse('2+3')
26 | self.assertEqual(v, 5)
27 |
28 | def test_sub(self):
29 | v = libFormula.parse('5-3')
30 | self.assertEqual(v, 2)
31 |
32 | def test_mul(self):
33 | v = libFormula.parse('2*3')
34 | self.assertEqual(v, 6)
35 |
36 | def test_div(self):
37 | v = libFormula.parse('6/2')
38 | self.assertEqual(v, 3)
39 |
40 | def test_operation_priority(self):
41 | v = libFormula.parse("a+3*(6+(3*b))", a=4, b=7)
42 | self.assertEqual(v, 85)
43 |
44 | # usage of '-'
45 | v = libFormula.parse("-2^1.0*-1.0+3.3")
46 | self.assertEqual(v, 5.3)
47 |
48 | # usage of '-'
49 | v = libFormula.parse("-2*(1.0-(3^(3*-1.0)))")
50 | self.assertAlmostEqual(v, -1.925925925925926)
51 |
52 | def test_add_pymel(self):
53 | a, b, c, _, _, _, _, _, _ = self._create_pymel_attrs(1, 2, 3)
54 | result = libFormula.parse('a+b+c', a=a, b=b, c=c)
55 | self.assertEqual(result.get(), 6)
56 |
57 | def test_sub_pymel(self):
58 | a, b, c, _, _, _, _, _, _ = self._create_pymel_attrs(8, 2, 1)
59 | result = libFormula.parse('a-b-c', a=a, b=b, c=c)
60 | self.assertEqual(result.get(), 5)
61 |
62 | def test_mul_pymel(self):
63 | a, b, c, _, _, _, _, _, _ = self._create_pymel_attrs(2, 3, 4)
64 | result = libFormula.parse('a*b*c', a=a, b=b, c=c)
65 | self.assertEqual(result.get(), 24)
66 |
67 | # def test_add3D_pymel(self):
68 | # t = self._create_pymel_node(1, 2, 3, 4, 5, 6, 7, 8, 9)
69 | # a = t.t
70 | # b = t.r
71 | # c = t.s
72 | # result = libFormula.parse('a+b+c', a=a, b=b, c=c)
73 | # print(result.get())
74 |
75 |
--------------------------------------------------------------------------------
/omtk/ui_shared.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk.vendor import libSerialization
4 | from omtk.vendor.Qt import QtGui
5 |
6 |
7 | # todo: Move to a shared location
8 | class MetadataType:
9 | """
10 | Used to quickly determine what metadata have been monkey-patched to a QWidget.
11 | """
12 | Rig = 0
13 | Module = 1
14 | Influence = 2
15 | Mesh = 3
16 |
17 |
18 | # http://forums.cgsociety.org/archive/index.php?t-1096914.html
19 | # Use the intern maya ressources icon
20 | _STYLE_SHEET = \
21 | """
22 |
23 | QTreeView::item::selected
24 | {
25 | background-color: highlight;
26 | color: rgb(40,40,40);
27 | }
28 |
29 | QTreeView::branch
30 | {
31 | selection-background-color: highlight;
32 | background-color: rgb(45,45,45);
33 | }
34 |
35 | QTreeView::branch:has-children:!has-siblings:closed,
36 | QTreeView::branch:closed:has-children:has-siblings
37 | {
38 | border-image: none;
39 | image: url(:/openObject.png);
40 | }
41 |
42 | QTreeView::branch:open:has-children:!has-siblings,
43 | QTreeView::branch:open:has-children:has-siblings
44 | {
45 | border-image: none;
46 | image: url(:/closeObject.png);
47 | }
48 |
49 | QTreeView::indicator:checked
50 | {
51 | image: url(:/checkboxOn.png);
52 | }
53 |
54 | QTreeView::indicator:unchecked
55 | {
56 | image: url(:/checkboxOff.png);
57 | }
58 | """
59 |
60 |
61 | def set_icon_from_type(obj, qItem):
62 | if isinstance(obj, pymel.nodetypes.Joint):
63 | qItem.setIcon(0, QtGui.QIcon(":/pickJointObj.png"))
64 | elif isinstance(obj, pymel.nodetypes.Transform):
65 | set_icon_from_type(obj.getShape(), qItem)
66 | elif isinstance(obj, pymel.nodetypes.NurbsCurve):
67 | qItem.setIcon(0, QtGui.QIcon(":/nurbsCurve.svg"))
68 | elif isinstance(obj, pymel.nodetypes.NurbsSurface):
69 | qItem.setIcon(0, QtGui.QIcon(":/nurbsSurface.svg"))
70 | elif isinstance(obj, pymel.nodetypes.Mesh):
71 | qItem.setIcon(0, QtGui.QIcon(":/mesh.svg"))
72 | else:
73 | qItem.setIcon(0, QtGui.QIcon(":/question.png"))
74 |
75 |
76 | def _update_network(module, item=None):
77 | if hasattr(module, "_network"):
78 | pymel.delete(module._network)
79 | new_network = libSerialization.export_network(module) # TODO : Automatic update
80 | # If needed, update the network item net property to match the new exported network
81 | if item:
82 | item.net = new_network
83 | return new_network
84 |
--------------------------------------------------------------------------------
/omtk/vendor/libSerialization/cache.py:
--------------------------------------------------------------------------------
1 | from decorators import memoized
2 | from .core import get_class_namespace, get_class_module_root
3 |
4 | def iter_subclasses_recursive(cls):
5 | yield cls
6 |
7 | try:
8 | for sub_cls in cls.__subclasses__():
9 | for x in iter_subclasses_recursive(sub_cls):
10 | yield x
11 | except TypeError: # This will fail when encountering the 'type' datatype.
12 | pass
13 |
14 | def iter_module_subclasses_recursive(module_root, cls):
15 | for sub_cls in iter_subclasses_recursive(cls):
16 | cur_module_root = get_class_module_root(sub_cls)
17 | if module_root == cur_module_root:
18 | yield sub_cls
19 |
20 | class Cache(object):
21 | def __init__(self):
22 | self.classes = None
23 | self._cache_import_by_id = {}
24 | self._cache_networks_by_id = {} # todo: merge with _cache_import_by_id
25 |
26 | @memoized
27 | def _get_cls_cache_by_module(self, module_name, base_class=object):
28 | i = iter_module_subclasses_recursive(module_name, base_class)
29 | result = {}
30 | for cls in i:
31 | result[cls.__name__] = cls
32 | return result
33 |
34 | @memoized
35 | def _get_cls_cache(self, base_class=object):
36 | i = iter_subclasses_recursive(base_class)
37 | result = {}
38 | for cls in i:
39 | result[cls.__name__] = cls
40 | return result
41 |
42 | def get_class_by_name(self, cls_name, module_name=None, base_class=object):
43 | if module_name is None:
44 | cache = self._get_cls_cache(base_class=base_class)
45 | else:
46 | cache = self._get_cls_cache_by_module(module_name=module_name, base_class=base_class)
47 | return cache.get(cls_name, None)
48 |
49 | def get_class_by_namespace(self, cls_namespace, module_name=None, base_class=object):
50 | if module_name is None:
51 | cache = self._get_cls_cache(base_class=base_class)
52 | else:
53 | cache = self._get_cls_cache_by_module(base_class=base_class)
54 | for cls in cache.values():
55 | cur_namespace = get_class_namespace(cls)
56 | if cls_namespace == cur_namespace:
57 | return cls
58 |
59 | def get_import_value_by_id(self, id, default=None):
60 | return self._cache_import_by_id.get(id, default)
61 |
62 | def set_import_value_by_id(self, id, val):
63 | self._cache_import_by_id[id] = val
64 |
65 | def get_network_by_id(self, id, default=None):
66 | return self._cache_networks_by_id.get(id, default)
67 |
68 | def set_network_by_id(self, id, net):
69 | self._cache_networks_by_id[id] = net
--------------------------------------------------------------------------------
/omtk/ui/widget_component_wizard_parts.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/widget_component_wizard_parts.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:53 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_Form(object):
13 | def setupUi(self, Form):
14 | Form.setObjectName("Form")
15 | Form.resize(400, 300)
16 | self.verticalLayout = QtWidgets.QVBoxLayout(Form)
17 | self.verticalLayout.setObjectName("verticalLayout")
18 | self.tableView = QtWidgets.QTableView(Form)
19 | self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
20 | self.tableView.setObjectName("tableView")
21 | self.tableView.horizontalHeader().setStretchLastSection(True)
22 | self.verticalLayout.addWidget(self.tableView)
23 | self.horizontalLayout = QtWidgets.QHBoxLayout()
24 | self.horizontalLayout.setObjectName("horizontalLayout")
25 | self.pushButton_add = QtWidgets.QPushButton(Form)
26 | self.pushButton_add.setObjectName("pushButton_add")
27 | self.horizontalLayout.addWidget(self.pushButton_add)
28 | self.pushButton_remove = QtWidgets.QPushButton(Form)
29 | self.pushButton_remove.setObjectName("pushButton_remove")
30 | self.horizontalLayout.addWidget(self.pushButton_remove)
31 | self.verticalLayout.addLayout(self.horizontalLayout)
32 | self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
33 | self.horizontalLayout_2.setObjectName("horizontalLayout_2")
34 | self.pushButton_connect = QtWidgets.QPushButton(Form)
35 | self.pushButton_connect.setObjectName("pushButton_connect")
36 | self.horizontalLayout_2.addWidget(self.pushButton_connect)
37 | self.pushButton_disconnect = QtWidgets.QPushButton(Form)
38 | self.pushButton_disconnect.setObjectName("pushButton_disconnect")
39 | self.horizontalLayout_2.addWidget(self.pushButton_disconnect)
40 | self.verticalLayout.addLayout(self.horizontalLayout_2)
41 |
42 | self.retranslateUi(Form)
43 | QtCore.QMetaObject.connectSlotsByName(Form)
44 |
45 | def retranslateUi(self, Form):
46 | Form.setWindowTitle(QtCompat.translate("Form", "Form", None, -1))
47 | self.pushButton_add.setText(QtCompat.translate("Form", "Add", None, -1))
48 | self.pushButton_remove.setText(QtCompat.translate("Form", "Remove", None, -1))
49 | self.pushButton_connect.setText(QtCompat.translate("Form", "Connect", None, -1))
50 | self.pushButton_disconnect.setText(QtCompat.translate("Form", "Disconnect", None, -1))
51 |
52 |
--------------------------------------------------------------------------------
/omtk/libs/libUtils.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 | import logging
3 | from omtk.libs import libSkinning
4 |
5 | '''
6 | This method will create a locator at the center of the selection.
7 | Support : Transform objects, vertices, faces and edges selection
8 | '''
9 |
10 |
11 | def get_center(objs):
12 | pos = pymel.datatypes.Point()
13 | count = 0
14 | for obj in objs:
15 | if isinstance(obj, pymel.general.MeshVertex):
16 | for vert in obj:
17 | pos += vert.getPosition(space='world')
18 | count += 1
19 | elif isinstance(obj, pymel.nodetypes.Transform):
20 | pos += obj.getTranslation(space="world")
21 | count += 1
22 | elif isinstance(obj, pymel.general.MeshEdge):
23 | pos += obj.getPoint(0, space="world")
24 | pos += obj.getPoint(1, space="world")
25 | count += 2
26 | elif isinstance(obj, pymel.general.MeshFace):
27 | aPointVtx = obj.getPoints(space="world")
28 | for oPointVtx in aPointVtx:
29 | pos += oPointVtx
30 | count += 1
31 | elif isinstance(obj, pymel.general.NurbsCurveCV):
32 | pos += obj.getPosition(space="world")
33 | count += 1
34 | else:
35 | logging.warning("Unsupported data type ({0}), will be skipped".format(type(obj)))
36 | if count != 0:
37 | pos /= count
38 | return pos
39 |
40 |
41 | def createLocToCenter():
42 | old_selection = pymel.selected()
43 | p3Pos = get_center(pymel.selected(flatten=True))
44 | pPoint = pymel.general.spaceLocator()
45 | pPoint.setTranslation(p3Pos, space='world')
46 | if old_selection:
47 | pymel.select(old_selection)
48 |
49 |
50 | '''Snap two or more objects with the last selected using their world matrix'''
51 |
52 |
53 | def snapObj():
54 | aSelection = pymel.selected()
55 | if len(aSelection) < 2:
56 | pymel.error("Select at least two objects")
57 |
58 | aSources = aSelection[:-1]
59 | oTarget = aSelection[-1]
60 |
61 | for oCurSource in aSources:
62 | pMatrixToMatch = oTarget.getMatrix(worldSpace=True)
63 | oCurSource.setMatrix(pMatrixToMatch, worldSpace=True)
64 |
65 |
66 | '''Get skin cluster attach to an objects'''
67 |
68 |
69 | def getSkinCluster(_oObj):
70 | for oCurHistory in pymel.listHistory(_oObj):
71 | if isinstance(oCurHistory, pymel.nodetypes.SkinCluster):
72 | return oCurHistory
73 | return None
74 |
75 |
76 | '''Get bone included in a skin'''
77 |
78 |
79 | def getSkinBones():
80 | aInfluences = []
81 | for oCurObj in pymel.selected():
82 | oSkinCluster = getSkinCluster(oCurObj)
83 | if oSkinCluster is not None:
84 | aInfluences += libSkinning.get_skin_cluster_influence_objects(oSkinCluster)
85 | pymel.select(aInfluences)
86 |
--------------------------------------------------------------------------------
/omtk/modules/rigNeck.py:
--------------------------------------------------------------------------------
1 | from omtk.modules import rigFK
2 | from omtk.modules import rigTwistbone
3 | from omtk.core.utils import decorator_uiexpose
4 |
5 |
6 | class CtrlNeck(rigFK.CtrlFk):
7 | pass
8 |
9 |
10 | class Neck(rigFK.FK):
11 | """
12 | Simple FK setup with twistbone support.
13 | """
14 |
15 | def __init__(self, *args, **kwarg):
16 | super(Neck, self).__init__(*args, **kwarg)
17 | self.create_twist = True
18 | self.sys_twist = None
19 |
20 | _CLS_CTRL = CtrlNeck
21 | _CLASS_SYS_TWIST = rigTwistbone.Twistbone
22 | _NAME_CTRL_MERGE = True # By default we only expect one controller for the head. (Head_Ctrl > than Head_Head_Ctrl)
23 | _NAME_CTRL_ENUMERATE = True # If we find additional influences, we'll use enumeration.
24 |
25 | def build(self, *args, **kwargs):
26 | super(Neck, self).build(create_grp_rig=True, *args, **kwargs)
27 |
28 | # Create twistbone system if needed
29 | if self.create_twist:
30 | jnt_s = self.jnt
31 | jnt_e = self.get_head_jnt()
32 |
33 | twist_nomenclature = self.get_nomenclature().copy()
34 | twist_nomenclature.add_tokens('bend')
35 |
36 | self.sys_twist = self.init_module(self._CLASS_SYS_TWIST, self.sys_twist, inputs=[jnt_s, jnt_e])
37 | self.sys_twist.name = twist_nomenclature.resolve()
38 | self.sys_twist.build(num_twist=3, create_bend=True)
39 | if self.sys_twist.grp_anm:
40 | self.sys_twist.grp_anm.setParent(self.grp_anm)
41 | self.sys_twist.grp_rig.setParent(self.grp_rig)
42 |
43 | def unbuild(self):
44 | if self.sys_twist:
45 | self.sys_twist.unbuild()
46 |
47 | super(Neck, self).unbuild()
48 |
49 | def validate(self):
50 | """
51 | Allow the ui to know if the module is valid to be builded or not
52 | :return: True or False depending if it pass the building validation
53 | """
54 | super(Neck, self).validate()
55 | num_jnts = len(self.jnts)
56 |
57 | if num_jnts != 1:
58 | raise Exception("Expected only one influences, got {}".format(num_jnts))
59 |
60 | head_jnt = self.get_head_jnt()
61 | if not head_jnt:
62 | raise Exception("Cannot resolve Head influence from {}".format(self.jnt))
63 |
64 | return True
65 |
66 | @decorator_uiexpose()
67 | def assign_twist_weights(self):
68 | for module in self.sys_twist:
69 | if isinstance(module, rigTwistbone.Twistbone) and module.is_built():
70 | module.assign_twist_weights()
71 |
72 | @decorator_uiexpose()
73 | def unassign_twist_weights(self):
74 | for module in self.sys_twist:
75 | if isinstance(module, rigTwistbone.Twistbone) and module.is_built():
76 | module.unassign_twist_weights()
77 |
78 |
79 | def register_plugin():
80 | return Neck
81 |
--------------------------------------------------------------------------------
/omtk/widget_create_component.py:
--------------------------------------------------------------------------------
1 | """
2 | Helper UI to create "Component".
3 | Component are a concept of OMTK2 backported to OMTK1 for convinience.
4 | """
5 |
6 | from ui import widget_create_component
7 |
8 | reload(widget_create_component)
9 | from omtk.vendor import libSerialization
10 | from omtk.libs import libComponent
11 |
12 | import pymel.core as pymel
13 |
14 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets
15 |
16 |
17 | def create_offset_grp(obj, suffix='offset'):
18 | offset = pymel.createNode('transform', name=(obj.name() + '_' + suffix))
19 | offset.setMatrix(obj.getMatrix(worldSpace=True), worldSpace=True)
20 | offset.setParent(obj.getParent())
21 | obj.setParent(offset)
22 | return offset
23 |
24 |
25 | class WidgetCreateComponent(QtWidgets.QMainWindow):
26 | def __init__(self):
27 | super(WidgetCreateComponent, self).__init__()
28 |
29 | self.ui = widget_create_component.Ui_MainWindow()
30 | self.ui.setupUi(self)
31 |
32 | self._wizard_network = None
33 | self._wizard = None
34 | wizard = self.import_()
35 | if not wizard:
36 | wizard = libComponent.ComponentWizard()
37 | wizard.initialize()
38 |
39 | self.set_wizard(wizard)
40 |
41 | self.export()
42 |
43 | self.ui.widget_view_ctrl.onNetworkChanged.connect(self.on_network_changed)
44 | self.ui.widget_view_infl.onNetworkChanged.connect(self.on_network_changed)
45 | self.ui.widget_view_guid.onNetworkChanged.connect(self.on_network_changed)
46 |
47 | def set_wizard(self, wizard):
48 | # type: (libComponent.ComponentWizard) -> None
49 | assert (isinstance(wizard, libComponent.ComponentWizard))
50 | self._wizard = wizard
51 | self.ui.widget_view_ctrl.set_entries(wizard, libComponent.ComponentPartCtrl, wizard.parts_ctrl)
52 | self.ui.widget_view_infl.set_entries(wizard, libComponent.ComponentPartInfluence, wizard.parts_influences)
53 | self.ui.widget_view_guid.set_entries(wizard, libComponent.ComponentPartGuide, wizard.parts_guides)
54 |
55 | def reset(self):
56 | self.model_ctrl.reset()
57 | self.model_guid.reset()
58 | self.model_infl.reset()
59 |
60 | def on_network_changed(self):
61 | self.export()
62 |
63 | def import_(self):
64 | networks = libSerialization.get_networks_from_class(libComponent.ComponentWizard.__name__)
65 | if not networks:
66 | return
67 | network = networks[0]
68 | self._wizard_network = network
69 | wizard = libSerialization.import_network(network)
70 | return wizard
71 |
72 | def export(self):
73 | if self._wizard_network:
74 | pymel.delete(self._wizard_network)
75 | self._wizard_network = libSerialization.export_network(self._wizard)
76 |
77 |
78 | _gui = None
79 |
80 |
81 | def show():
82 | global _gui
83 | _gui = WidgetCreateComponent()
84 | _gui.show()
85 |
--------------------------------------------------------------------------------
/omtk/ui/widget_logger.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 435
10 | 300
11 |
12 |
13 |
14 | Form
15 |
16 |
17 |
18 | 0
19 |
20 | -
21 |
22 |
-
23 |
24 |
25 | Logs
26 |
27 |
28 |
29 | -
30 |
31 |
32 | Qt::Horizontal
33 |
34 |
35 |
36 | 40
37 | 20
38 |
39 |
40 |
41 |
42 | -
43 |
44 |
45 | Search
46 |
47 |
48 |
49 | -
50 |
51 |
52 | -
53 |
54 |
55 | Level:
56 |
57 |
58 |
59 | -
60 |
61 |
62 | 1
63 |
64 |
-
65 |
66 | Error
67 |
68 |
69 | -
70 |
71 | Warning
72 |
73 |
74 | -
75 |
76 | Info
77 |
78 |
79 | -
80 |
81 | Debug
82 |
83 |
84 |
85 |
86 | -
87 |
88 |
89 | Save
90 |
91 |
92 |
93 | -
94 |
95 |
96 | Clear
97 |
98 |
99 |
100 |
101 |
102 | -
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/tests/test_animtools.py:
--------------------------------------------------------------------------------
1 | import os
2 | import mayaunittest
3 | from maya import cmds
4 | import pymel.core as pymel
5 | import omtk
6 | import omtk
7 | from omtk.vendor import libSerialization
8 | from omtk.libs import libRigging
9 | from omtk.modules.rigArm import Arm
10 | from omtk.animation import ikfkTools
11 | from omtk.animation import ikfkTools
12 |
13 | class SampleTests(mayaunittest.TestCase):
14 |
15 | def _create_simple_arm_build_and_export(self):
16 | jnt_1 = pymel.joint(position=[0,0,0])
17 | jnt_2 = pymel.joint(position=[10,0,10])
18 | jnt_3 = pymel.joint(position=[20,0,0])
19 |
20 | rig = omtk.create()
21 | module = Arm([jnt_1, jnt_2, jnt_3])
22 | rig.add_module(module)
23 | rig.build()
24 | libSerialization.export_network(rig)
25 |
26 | return rig, module
27 |
28 | def _verify_ik_fk_match(self, module):
29 | ctrl_ik = module.sysIK.ctrl_ik.node
30 | ctrl_swivel = module.sysIK.ctrl_swivel.node
31 | ctrl_fk_01 = module.sysFK.ctrls[0].node
32 | ctrl_fk_02 = module.sysFK.ctrls[1].node
33 | ctrl_fk_03 = module.sysFK.ctrls[2].node
34 |
35 | ctrl_ik_pos = ctrl_ik.getTranslation(space='world')
36 | ctrl_swivel_pos = ctrl_swivel.getTranslation(space='world')
37 | ctrl_fk_01_pos = ctrl_fk_01.getTranslation(space='world')
38 | ctrl_fk_02_pos = ctrl_fk_02.getTranslation(space='world')
39 | ctrl_fk_03_pos = ctrl_fk_03.getTranslation(space='world')
40 |
41 | # Verify IK ctrl
42 | self.assertAlmostEqual((ctrl_ik_pos - ctrl_fk_03_pos).length(), 0, places=3)
43 |
44 | # Verify IK swivel
45 | look_vec = ctrl_fk_03_pos - ctrl_fk_01_pos
46 | upp_vec = ctrl_fk_02_pos - ctrl_fk_01_pos
47 | tm = libRigging.get_matrix_from_direction(look_vec, upp_vec,
48 | look_axis=pymel.datatypes.Vector.xAxis,
49 | upp_axis=pymel.datatypes.Vector.zAxis
50 | )
51 | distorsion = (ctrl_swivel_pos * tm.inverse()).y
52 | self.assertAlmostEqual(distorsion, 0)
53 |
54 | def test_ikfk_switch(self):
55 | rig, module = self._create_simple_arm_build_and_export()
56 |
57 | ctrl_ik = module.sysIK.ctrl_ik.node
58 |
59 | ctrl_fk_01 = module.sysFK.ctrls[0].node
60 | ctrl_fk_02 = module.sysFK.ctrls[1].node
61 | ctrl_fk_03 = module.sysFK.ctrls[2].node
62 |
63 | # Do a brainless IK->FK->IK switch to ensure nothing is moving.
64 | pymel.select(ctrl_ik)
65 | ikfkTools.switchToFk()
66 | self._verify_ik_fk_match(module)
67 | ikfkTools.switchToIk()
68 | self._verify_ik_fk_match(module)
69 |
70 | # Modify IK pose and perform an IK-FK switch
71 | ctrl_ik.setTranslation([10,0,0], space='world')
72 | pymel.select(ctrl_ik)
73 | ikfkTools.switchToFk()
74 | self._verify_ik_fk_match(module)
75 |
76 | # Modify FK pose and perform an FK->IK switch
77 | ctrl_fk_01.rotateX.set(45)
78 | ctrl_fk_02.rotateY.set(-45)
79 | pymel.select(ctrl_fk_02)
80 | ikfkTools.switchToIk()
81 | self._verify_ik_fk_match(module)
82 |
--------------------------------------------------------------------------------
/omtk/modules/rigArm.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk import constants
4 | from omtk.modules import rigIK
5 | from omtk.modules import rigLimb
6 | from omtk.libs import libCtrlShapes
7 | from omtk.libs import libRigging
8 |
9 |
10 | class CtrlIkArm(rigIK.CtrlIk):
11 | def __createNode__(self, *args, **kwargs):
12 | return libCtrlShapes.create_shape_box_arm(*args, **kwargs)
13 |
14 |
15 | def get_spaceswitch_targets(self, module, *args, **kwargs):
16 | targets, labels, indexes = super(CtrlIkArm, self).get_spaceswitch_targets(module, *args, **kwargs)
17 | jnt_head = module.get_head_jnt()
18 | if jnt_head:
19 | targets.append(jnt_head)
20 | labels.append(None)
21 | indexes.append(self.get_bestmatch_index(jnt_head))
22 | return targets, labels, indexes
23 |
24 |
25 | class ArmIk(rigIK.IK):
26 | _CLASS_CTRL_IK = CtrlIkArm
27 | SHOW_IN_UI = False
28 |
29 | def _get_ik_ctrl_bound_refs_raycast(self):
30 | """
31 | Resolve what objects to use for computing the bound of the ik ctrl using raycasts.
32 | This also use the first phalanges to have a more precise bound height.
33 | :return: An array of pymel.general.PyNode instances.
34 | """
35 | jnt_hand = self.input[self.iCtrlIndex]
36 | return [jnt_hand] + jnt_hand.getChildren()
37 |
38 | def _get_ik_ctrl_tms(self):
39 | """
40 | Compute the desired rotation for the ik ctrl.
41 | If the LEGACY_ARM_IK_CTRL_ORIENTATION is set, we'll simply align to the influence.
42 | :return: A two-size tuple containing the transformation matrix for the ctrl offset and the ctrl itself.
43 | """
44 | if self.rig.LEGACY_ARM_IK_CTRL_ORIENTATION:
45 | return super(ArmIk, self)._get_ik_ctrl_tms()
46 |
47 | inf_tm = self.input[self.iCtrlIndex].getMatrix(worldSpace=True)
48 | side = self.get_side()
49 |
50 | # Resolve offset_tm
51 | offset_tm = pymel.datatypes.Matrix()
52 |
53 | # Resolve ctrl_tm
54 | axis_dir = constants.Axis.x
55 | axis_upp = self.rig.DEFAULT_UPP_AXIS # normally the z axis
56 | inn_tm_dir = libRigging.get_matrix_axis(inf_tm, axis_dir)
57 | inn_tm_upp = libRigging.get_matrix_axis(inf_tm, axis_upp)
58 |
59 | ctrl_tm = libRigging.get_matrix_from_direction(
60 | inn_tm_dir,
61 | inn_tm_upp,
62 | look_axis=pymel.datatypes.Vector(1, 0, 0),
63 | upp_axis=pymel.datatypes.Vector(0, -1,
64 | 0) if side == self.rig.nomenclature.SIDE_R else pymel.datatypes.Vector(0, 1,
65 | 0)
66 | )
67 | ctrl_tm.translate = inf_tm.translate
68 |
69 | return offset_tm, ctrl_tm
70 |
71 |
72 | class Arm(rigLimb.Limb):
73 | """
74 | IK/FK Setup customized for Arm riging.
75 | """
76 | _CLASS_SYS_IK = ArmIk
77 |
78 | def __init__(self, *args, **kwargs):
79 | super(Arm, self).__init__(*args, **kwargs)
80 | self.sysFootRoll = None
81 |
82 |
83 | def register_plugin():
84 | return Arm
85 |
--------------------------------------------------------------------------------
/omtk/vendor/libSerialization/plugin_maya_json.py:
--------------------------------------------------------------------------------
1 | #
2 | # Define a custom JSON Encoder that can be used to export PyNode and Attribute.
3 | #
4 | from maya import cmds
5 | import pymel.core as pymel
6 | import json
7 | from plugin_json import export_json
8 | from plugin_json import export_json_file
9 | from plugin_json import import_json
10 | from plugin_json import import_json_file
11 | __all__ = (
12 | 'export_json_maya',
13 | 'export_json_file_maya',
14 | 'import_json_maya',
15 | 'import_json_file_maya'
16 | )
17 |
18 |
19 | # TODO: Add support for matrix and vector datatypes
20 | class PymelJSONEncoder(json.JSONEncoder):
21 | def default(self, o):
22 | if isinstance(o, pymel.PyNode):
23 | return {'_class_pymel':'pymel.PyNode', '__melobject__': o.__melobject__()}
24 | elif isinstance(o, pymel.Attribute):
25 | return {'_class_pymel':'pymel.Attribute', '__melobject__': o.__melobject__()}
26 | elif isinstance(o, pymel.datatypes.Matrix):
27 | return {'_class_pymel':'pymel.datatypes.Matrix', '__melobject__': o.__melobject__()}
28 | elif isinstance(o, pymel.datatypes.Vector):
29 | return {'_class_pymel':'pymel.datatypes.Vector', '__melobject__': [o.x, o.y, o.z]}
30 | elif isinstance(o, pymel.datatypes.Point):
31 | return {'_class_pymel':'pymel.datatypes.Point', '__melobject__': [o.w, o.x, o.y, o.z]}
32 | else:
33 | return super(PymelJSONEncoder, self).default(o)
34 |
35 |
36 | # TODO: Add support for matrix and vector datatypes
37 | class PymelJSONDecoder(json.JSONDecoder):
38 | def __init__(self, *args, **kwargs):
39 | super(PymelJSONDecoder, self).__init__(object_hook=self.object_hook, *args, **kwargs)
40 |
41 | def object_hook(self, val):
42 | if isinstance(val, dict):
43 | cls = val.get('_class_pymel', None)
44 | if cls == 'pymel.PyNode':
45 | dagpath = val.get('__melobject__')
46 | val = pymel.PyNode(dagpath) if cmds.objExists(dagpath) else None # TODO: add warning?
47 | elif cls == 'pymel.Attribute':
48 | dagpath = val.get('__melobject__')
49 | val = pymel.Attribute(dagpath) if cmds.objExists(dagpath) else None # TODO: add warning?
50 | elif cls == 'pymel.datatypes.Matrix':
51 | melval = val.get('__melobject__')
52 | val = pymel.datatypes.Matrix(melval)
53 | elif cls == 'pymel.datatypes.Vector':
54 | coords = val.get('__melobject__')
55 | val = pymel.datatypes.Vector(coords)
56 | elif cls == 'pymel.datatypes.Point':
57 | coords = val.get('__melobject__')
58 | val = pymel.datatypes.Point(coords)
59 | return val
60 |
61 | def export_json_maya(*args, **kwargs):
62 | return export_json(cls=PymelJSONEncoder, *args, **kwargs)
63 |
64 | def export_json_file_maya(*args, **kwargs):
65 | return export_json_file(cls=PymelJSONEncoder, *args, **kwargs)
66 |
67 | def import_json_maya(*args, **kwargs):
68 | return import_json(cls=PymelJSONDecoder, *args, **kwargs)
69 |
70 | def import_json_file_maya(*args, **kwargs):
71 | return import_json_file(cls=PymelJSONDecoder, *args, **kwargs)
--------------------------------------------------------------------------------
/omtk/ui/pluginmanager_window.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | mainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 485
10 | 391
11 |
12 |
13 |
14 | OMTK - Plugin Manager
15 |
16 |
17 |
18 | -
19 |
20 |
21 | -
22 |
23 |
24 | QAbstractItemView::SelectRows
25 |
26 |
27 | true
28 |
29 |
30 |
31 | -
32 |
33 |
-
34 |
35 |
36 | Reload
37 |
38 |
39 |
40 | -
41 |
42 |
43 | Qt::Horizontal
44 |
45 |
46 |
47 | 40
48 | 20
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
67 |
68 |
69 |
70 | Reload
71 |
72 |
73 |
74 |
75 | SearchQueryChanged
76 |
77 |
78 |
79 |
80 |
81 |
82 | pushButton_reload
83 | released()
84 | actionReload
85 | trigger()
86 |
87 |
88 | 52
89 | 347
90 |
91 |
92 | -1
93 | -1
94 |
95 |
96 |
97 |
98 | lineEdit_search
99 | textChanged(QString)
100 | actionSearchQueryChanged
101 | trigger()
102 |
103 |
104 | 242
105 | 52
106 |
107 |
108 | -1
109 | -1
110 |
111 |
112 |
113 |
114 |
115 |
--------------------------------------------------------------------------------
/omtk/modules/rigSplineIK.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk.core.classModule import Module
4 | from omtk.libs import libRigging, libPymel
5 |
6 |
7 | # Todo: Support more complex IK limbs (ex: 2 knees)
8 | class SplineIK(Module):
9 | """
10 | Generic ik setup on a spline.
11 | """
12 |
13 | def __init__(self, *args, **kwargs):
14 | super(SplineIK, self).__init__(*args, **kwargs)
15 | self.bStretch = True
16 | self.iCtrlIndex = 2
17 | self.ikEffector = None
18 | self.ikHandle = None
19 |
20 | def build(self, stretch=True, squash=False, *args, **kwargs):
21 | # TODO: Use self.chain_jnt
22 | self._joints = [input for input in self.input if libPymel.isinstance_of_transform(input, pymel.nodetypes.Joint)]
23 | self._curves = [input for input in self.input if
24 | libPymel.isinstance_of_shape(input, pymel.nodetypes.CurveShape)]
25 |
26 | if len(self._joints) < 2:
27 | raise Exception("Can't build SplineIK. Expected at least two joints, got {0}".format(self._joints))
28 | if len(self._curves) < 1:
29 | raise Exception("Can't build SplineIK. Expected at least one nurbsCurve, got {0}".format(self._curves))
30 |
31 | super(SplineIK, self).build(*args, **kwargs)
32 |
33 | nomenclature_rig = self.get_nomenclature_rig()
34 |
35 | # todo: handle multiple curves?
36 | curve = next(iter(self._curves), None)
37 | curve_shape = next((shape for shape in curve.getShapes() if isinstance(shape, pymel.nodetypes.NurbsCurve)),
38 | None)
39 |
40 | # Create ik solver
41 | handle_name = nomenclature_rig.resolve('ikHandle')
42 | eff_name = nomenclature_rig.resolve('ikEffector')
43 | self.ikHandle, self.ikEffector = pymel.ikHandle(
44 | solver="ikSplineSolver",
45 | curve=curve,
46 | startJoint=self._joints[0],
47 | endEffector=self._joints[-1],
48 | createCurve=False,
49 | name=handle_name,
50 | parentCurve=False,
51 | snapCurve=False)
52 | self.ikHandle.setParent(self.grp_rig)
53 | self.ikEffector.rename(eff_name)
54 |
55 | # Create stretch
56 | # Todo: use shape instead of transform as curve input?
57 | if stretch:
58 | stretch_attr = libRigging.create_strech_attr_from_curve(curve_shape)
59 | for jnt in self._joints:
60 | pymel.connectAttr(stretch_attr, jnt.sx, force=True)
61 |
62 | # Create squash
63 | if squash:
64 | num_joints = len(self._joints)
65 | squash_attrs = libRigging.create_squash_atts(stretch_attr, num_joints)
66 | # Todo: Find correct axis orient
67 | for jnt, squash in zip(self._joints, squash_attrs):
68 | pymel.connectAttr(squash, jnt.sy, force=True)
69 | pymel.connectAttr(squash, jnt.sz, force=True)
70 |
71 | def unbuild(self):
72 | # hack: the ikEffector is parented to the bone chain and need to be deleted manually
73 | if libPymel.is_valid_PyNode(self.ikEffector):
74 | pymel.delete(self.ikEffector)
75 |
76 | super(SplineIK, self).unbuild()
77 |
78 |
79 | def register_plugin():
80 | return SplineIK
81 |
--------------------------------------------------------------------------------
/shelf_omtk_anim.mel:
--------------------------------------------------------------------------------
1 | global proc shelf_omtk_anim () {
2 | global string $gBuffStr;
3 | global string $gBuffStr0;
4 | global string $gBuffStr1;
5 |
6 | shelfButton
7 | -enableCommandRepeat 1
8 | -enable 1
9 | -width 35
10 | -height 35
11 | -manage 1
12 | -visible 1
13 | -preventOverride 0
14 | -annotation "from omtk.animation import ikfkTools\nikfkTools.switchToIk()"
15 | -enableBackground 0
16 | -align "center"
17 | -label "from omtk.animation import ikfkTools\nikfkTools.switchToIk()"
18 | -labelOffset 0
19 | -rotation 0
20 | -flipX 0
21 | -flipY 0
22 | -useAlpha 1
23 | -font "plainLabelFont"
24 | -overlayLabelColor 0.8 0.8 0.8
25 | -overlayLabelBackColor 0 0 0 0.2
26 | -image "omtk_anm_switch_ik.png"
27 | -image1 "omtk_anm_switch_ik.png"
28 | -style "iconOnly"
29 | -marginWidth 1
30 | -marginHeight 1
31 | -command "from omtk.animation import ikfkTools\nikfkTools.switchToIk()"
32 | -sourceType "python"
33 | -commandRepeatable 1
34 | -flat 1
35 | ;
36 | shelfButton
37 | -enableCommandRepeat 1
38 | -enable 1
39 | -width 35
40 | -height 35
41 | -manage 1
42 | -visible 1
43 | -preventOverride 0
44 | -annotation "from omtk.animation import ikfkTools\nikfkTools.switchToFk()"
45 | -enableBackground 0
46 | -align "center"
47 | -label "from omtk.animation import ikfkTools\nikfkTools.switchToFk()"
48 | -labelOffset 0
49 | -rotation 0
50 | -flipX 0
51 | -flipY 0
52 | -useAlpha 1
53 | -font "plainLabelFont"
54 | -overlayLabelColor 0.8 0.8 0.8
55 | -overlayLabelBackColor 0 0 0 0.2
56 | -image "omtk_anm_switch_fk.png"
57 | -image1 "omtk_anm_switch_fk.png"
58 | -style "iconOnly"
59 | -marginWidth 1
60 | -marginHeight 1
61 | -command "from omtk.animation import ikfkTools\nikfkTools.switchToFk()"
62 | -sourceType "python"
63 | -commandRepeatable 1
64 | -flat 1
65 | ;
66 | shelfButton
67 | -enableCommandRepeat 1
68 | -enable 1
69 | -width 35
70 | -height 35
71 | -manage 1
72 | -visible 1
73 | -preventOverride 0
74 | -annotation "from omtk.animation import mirrorPose; reload(mirrorPose)\nimport pymel.core as pymel\nmirrorPose.mirror_objs(pymel.selected())"
75 | -enableBackground 0
76 | -align "center"
77 | -label "from omtk.animation import mirrorPose; reload(mirrorPose)\nimport..."
78 | -labelOffset 0
79 | -rotation 0
80 | -flipX 0
81 | -flipY 0
82 | -useAlpha 1
83 | -font "plainLabelFont"
84 | -overlayLabelColor 0.8 0.8 0.8
85 | -overlayLabelBackColor 0 0 0 0.2
86 | -image "omtk_anm_ctrl_mirror.png"
87 | -image1 "omtk_anm_ctrl_mirror.png"
88 | -style "iconOnly"
89 | -marginWidth 1
90 | -marginHeight 1
91 | -command "from omtk.animation import mirrorPose; reload(mirrorPose)\nimport pymel.core as pymel\nmirrorPose.mirror_objs(pymel.selected())"
92 | -sourceType "python"
93 | -commandRepeatable 1
94 | -flat 1
95 | ;
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/omtk/ui/pluginmanager_window.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/pluginmanager_window.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:53 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_mainWindow(object):
13 | def setupUi(self, mainWindow):
14 | mainWindow.setObjectName("mainWindow")
15 | mainWindow.resize(485, 391)
16 | self.centralwidget = QtWidgets.QWidget(mainWindow)
17 | self.centralwidget.setObjectName("centralwidget")
18 | self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
19 | self.verticalLayout.setObjectName("verticalLayout")
20 | self.lineEdit_search = QtWidgets.QLineEdit(self.centralwidget)
21 | self.lineEdit_search.setObjectName("lineEdit_search")
22 | self.verticalLayout.addWidget(self.lineEdit_search)
23 | self.tableView = QtWidgets.QTableView(self.centralwidget)
24 | self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
25 | self.tableView.setObjectName("tableView")
26 | self.tableView.horizontalHeader().setStretchLastSection(True)
27 | self.verticalLayout.addWidget(self.tableView)
28 | self.horizontalLayout = QtWidgets.QHBoxLayout()
29 | self.horizontalLayout.setObjectName("horizontalLayout")
30 | self.pushButton_reload = QtWidgets.QPushButton(self.centralwidget)
31 | self.pushButton_reload.setObjectName("pushButton_reload")
32 | self.horizontalLayout.addWidget(self.pushButton_reload)
33 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
34 | self.horizontalLayout.addItem(spacerItem)
35 | self.verticalLayout.addLayout(self.horizontalLayout)
36 | mainWindow.setCentralWidget(self.centralwidget)
37 | self.menubar = QtWidgets.QMenuBar(mainWindow)
38 | self.menubar.setGeometry(QtCore.QRect(0, 0, 485, 28))
39 | self.menubar.setObjectName("menubar")
40 | mainWindow.setMenuBar(self.menubar)
41 | self.statusbar = QtWidgets.QStatusBar(mainWindow)
42 | self.statusbar.setObjectName("statusbar")
43 | mainWindow.setStatusBar(self.statusbar)
44 | self.actionReload = QtWidgets.QAction(mainWindow)
45 | self.actionReload.setObjectName("actionReload")
46 | self.actionSearchQueryChanged = QtWidgets.QAction(mainWindow)
47 | self.actionSearchQueryChanged.setObjectName("actionSearchQueryChanged")
48 |
49 | self.retranslateUi(mainWindow)
50 | QtCore.QObject.connect(self.pushButton_reload, QtCore.SIGNAL("released()"), self.actionReload.trigger)
51 | QtCore.QObject.connect(self.lineEdit_search, QtCore.SIGNAL("textChanged(QString)"), self.actionSearchQueryChanged.trigger)
52 | QtCore.QMetaObject.connectSlotsByName(mainWindow)
53 |
54 | def retranslateUi(self, mainWindow):
55 | mainWindow.setWindowTitle(QtCompat.translate("mainWindow", "OMTK - Plugin Manager", None, -1))
56 | self.pushButton_reload.setText(QtCompat.translate("mainWindow", "Reload", None, -1))
57 | self.actionReload.setText(QtCompat.translate("mainWindow", "Reload", None, -1))
58 | self.actionSearchQueryChanged.setText(QtCompat.translate("mainWindow", "SearchQueryChanged", None, -1))
59 |
60 |
--------------------------------------------------------------------------------
/omtk/ui/widget_create_component.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | MainWindow
4 |
5 |
6 |
7 | 0
8 | 0
9 | 819
10 | 416
11 |
12 |
13 |
14 | Component Creation Wizard
15 |
16 |
17 |
18 | -
19 |
20 |
-
21 |
22 |
23 | Ctrls:
24 |
25 |
26 |
27 | -
28 |
29 |
30 |
31 | 0
32 | 0
33 |
34 |
35 |
36 |
37 |
38 |
39 | -
40 |
41 |
-
42 |
43 |
44 | Influences:
45 |
46 |
47 |
48 | -
49 |
50 |
51 |
52 | 0
53 | 0
54 |
55 |
56 |
57 |
58 |
59 |
60 | -
61 |
62 |
-
63 |
64 |
65 | Guides:
66 |
67 |
68 |
69 | -
70 |
71 |
72 |
73 | 0
74 | 0
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
93 |
94 |
95 |
96 |
97 | WidgetCreateComponentWizardParts
98 | QWidget
99 | ..widget_create_component_wizard_parts.h
100 | 1
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/omtk/modules/rigFaceEyeLids.py:
--------------------------------------------------------------------------------
1 | from omtk.modules import rigFaceAvarGrps
2 | from omtk.modules import rigFaceAvar
3 | from omtk.libs import libCtrlShapes
4 | from omtk.libs import libPymel
5 | import pymel.core as pymel
6 |
7 |
8 | class CtrlEyeLidUpp(rigFaceAvar.BaseCtrlFace):
9 | def __createNode__(self, size=1.0, **kwargs):
10 | return libCtrlShapes.create_triangle_upp(size=size)
11 |
12 |
13 | class CtrlEyeLidLow(rigFaceAvar.BaseCtrlFace):
14 | def __createNode__(self, size=1.0, **kwargs):
15 | return libCtrlShapes.create_triangle_low(size=size)
16 |
17 |
18 | class FaceEyeLids(rigFaceAvarGrps.AvarGrpOnSurface):
19 | """
20 | AvarGrp setup customized for Eyelids rigging.
21 | FaceLids behave the same a a standard AvarGrp with an Upp and Low section.
22 | However to adapt ourself to non-symetrical eyes, we use two surface to slide on.
23 | """
24 | _CLS_CTRL_UPP = CtrlEyeLidUpp
25 | _CLS_CTRL_LOW = CtrlEyeLidLow
26 | IS_SIDE_SPECIFIC = True
27 | CREATE_MACRO_AVAR_HORIZONTAL = True
28 | CREATE_MACRO_AVAR_VERTICAL = True
29 | CREATE_MACRO_AVAR_ALL = True
30 |
31 | SHOW_IN_UI = True
32 |
33 | def __init__(self, *args, **kwargs):
34 | super(FaceEyeLids, self).__init__(*args, **kwargs)
35 | self.surface_upp = None
36 | self.surface_low = None
37 |
38 | def handle_surface(self):
39 | """
40 | Create a separated surface for the upper and lower lids.
41 | This allow the rigger to easily tweak how each lids react which may be necessary with hyper-realistic characters.
42 |
43 | If the user provided it's own surface (via the input property), we'll use it for the upper AND lower lids.
44 | This is mainly to support custom surface like spheres for very cartoony characters.
45 | """
46 |
47 | # Get all surfaces provided by the input property.
48 | def get_surface(obj):
49 | if libPymel.isinstance_of_shape(obj, pymel.nodetypes.NurbsSurface):
50 | return obj
51 |
52 | surfaces = filter(None, map(get_surface, self.input))
53 |
54 | # If the user provided surface, pop them out of the input property and store them in the surface_[upp/low] property.
55 | if surfaces:
56 | surface = next(iter(surfaces), None)
57 | for surface in surfaces:
58 | self.input.remove(surface)
59 | self.surface_upp = surface
60 | self.surface_low = surface
61 |
62 | # Create surfaces if they were not provided
63 | if self.surface_upp is None:
64 | pymel.warning("Can't find surface for {0}, creating one.".format(self))
65 | self.surface_upp = self.create_surface(name='SurfaceUpp')
66 |
67 | if self.surface_low is None:
68 | pymel.warning("Can't find surface for {0}, creating one.".format(self))
69 | self.surface_low = self.create_surface(name='SurfaceLow')
70 |
71 | # We still provide the surface property in case we need a controller to be connected directly to it.
72 | self.surface = self.surface_upp
73 |
74 | def configure_avar(self, avar):
75 | inf = avar.jnt
76 | if inf in self.get_jnts_upp():
77 | avar.surface = self.surface_upp
78 | else:
79 | avar.surface = self.surface_low
80 |
81 | def get_default_name(self):
82 | return 'eyelid'
83 |
84 | def get_multiplier_u(self):
85 | # Since the V go all around the sphere, it is too much range.
86 | # We'll restrict ourself only to a single quadrant.
87 | return 0.25
88 |
89 |
90 | def register_plugin():
91 | return FaceEyeLids
92 |
--------------------------------------------------------------------------------
/omtk/macros/ctrls_bake_selected.py:
--------------------------------------------------------------------------------
1 | """
2 | Use this macro to 'fix' any calibrated facial shape that have been manually adjusted.
3 | """
4 | import pymel.core as pymel
5 | from omtk.libs import libRigging
6 | from omtk.core import api
7 | from omtk.core.macros import BaseMacro
8 |
9 |
10 | def _fix_ctrl_shape(ctrl):
11 | """
12 | When the rigger want to resize an InteractiveCtrl, he will modify the ctrl shape 'controlPoints' attributes.
13 | This can be problematic since the shape 'create' attribute is feed from a transformGeometry node
14 | to compensate the non-uniform scaling caused by the calibration. This will 'skew' the shape which we don't want.
15 | We always want to make sure that there's only data in the orig shape 'controlPoints' attributes.
16 | This method will create a temporary shape that will receive the 'local' attribute from the ctrl shape (which
17 | contain the deformation from the 'controlPoints' attribute). The 'local' attribute of that shape will then be
18 | fed back to the orig shape. Finally, all the original 'controlPoints' will be set to zero.
19 | """
20 | grp_offset = ctrl.getParent()
21 |
22 | def get_orig_shape(shape):
23 | return next((hist for hist in shape.listHistory()
24 | if isinstance(hist, pymel.nodetypes.NurbsCurve)
25 | and hist != shape
26 | and hist.intermediateObject.get()), None)
27 |
28 | def get_transformGeometry(shape):
29 | return next((hist for hist in shape.listHistory()
30 | if isinstance(hist, pymel.nodetypes.TransformGeometry)), None)
31 |
32 | for shape in ctrl.getShapes(noIntermediate=True):
33 | # Resolve orig shape
34 | shape_orig = get_orig_shape(shape)
35 | if not shape_orig:
36 | pymel.warning("Skipping {}. Cannot find orig shape.".format(shape))
37 | continue
38 |
39 | # Resolve compensation matrix
40 | util_transform_geometry = get_transformGeometry(shape)
41 | if not util_transform_geometry:
42 | pymel.warning("Skipping {}. Cannot find transformGeometry.".format(shape))
43 | continue
44 | attr_compensation_tm = next(iter(util_transform_geometry.transform.inputs(plugs=True)), None)
45 | if not attr_compensation_tm:
46 | pymel.warning("Skipping {}. Cannot find compensation matrix.".format(shape))
47 | continue
48 |
49 | tmp_shape = pymel.createNode('nurbsCurve')
50 | tmp_shape.getParent().setParent(grp_offset)
51 |
52 | # Apply the inverted compensation matrix to access the desired orig_shape 'create' attr.
53 | tmp_transform_geometry = libRigging.create_utility_node(
54 | 'transformGeometry',
55 | inputGeometry=shape.local,
56 | transform=attr_compensation_tm,
57 | invertTransform=True
58 | )
59 | attr_output_geometry = tmp_transform_geometry.outputGeometry
60 | pymel.connectAttr(attr_output_geometry, tmp_shape.create)
61 | pymel.disconnectAttr(tmp_shape.create)
62 |
63 | pymel.connectAttr(tmp_shape.local, shape_orig.create)
64 |
65 | # Remove any extraneous controlPoints coordinates.
66 | for attr_cp in shape.cp:
67 | attr_cp.set(0, 0, 0)
68 | for attr_cp in shape_orig.cp:
69 | attr_cp.set(0, 0, 0)
70 |
71 | # Cleanup
72 | pymel.disconnectAttr(shape_orig.create)
73 | pymel.delete(tmp_shape.getParent())
74 | pymel.delete(tmp_transform_geometry)
75 |
76 |
77 | class CtrlBakeSelectedMacro(BaseMacro):
78 | def run(self):
79 | for obj in pymel.selected():
80 | _fix_ctrl_shape(obj)
81 |
82 |
83 | def register_plugin():
84 | return CtrlBakeSelectedMacro
85 |
--------------------------------------------------------------------------------
/omtk/libs/libHistory.py:
--------------------------------------------------------------------------------
1 | """
2 | Utility functions to deal with construction history.
3 | Please learn the following used nomenclature:
4 | - mesh: A polygonal geometry shape
5 | - surface: A nurbs geometry shape
6 | - shape: Any geometry shape, including mesh and surface.
7 | """
8 | import pymel.core as pymel
9 |
10 | from omtk.libs.libRigging import _filter_shape
11 |
12 |
13 | def _is_mesh(shape):
14 | return isinstance(shape, pymel.nodetypes.Mesh)
15 |
16 |
17 | def _is_surface(shape):
18 | return isinstance(shape, pymel.nodetypes.NurbsSurface)
19 |
20 |
21 | #
22 | # Utility functions that compliment pymel.listHistory.
23 | #
24 |
25 | def _iter_history(shape, key=None, fn_stop=None, stop_at_shape=False, **kwargs):
26 | """
27 | Go through the history of the provided shapes and yield interesting elements.
28 | :param shape: The starting po
29 | :param key:
30 | :param stop_at_shape: If True, we will stop iterating as soon as we encounter a shape. Note that if you provided a
31 | transform, it will stop at the first encounter of the main shape.
32 | :param kwargs:
33 | :return:
34 | """
35 | # Determine what condition make us stop iterating though history.
36 | if stop_at_shape:
37 | shape_start = shape.getShape() if isinstance(shape, pymel.nodetypes.Transform) else shape
38 | if fn_stop:
39 | fn_stop = lambda shape: isinstance(shape, pymel.nodetypes.Shape) and shape != shape_start or fn_stop(shape)
40 | else:
41 | fn_stop = lambda shape: isinstance(shape, pymel.nodetypes.Shape) and shape != shape_start
42 |
43 | for hist in shape.listHistory(**kwargs):
44 | if hist == shape:
45 | continue
46 | if fn_stop and fn_stop(hist):
47 | return
48 | if key and key(hist):
49 | yield hist
50 |
51 |
52 | def iter_history_foward(shape, **kwargs):
53 | for hist in _iter_history(shape, future=True, **kwargs):
54 | yield hist
55 |
56 |
57 | def iter_history_backward(shape, **kwargs):
58 | for hist in _iter_history(shape, **kwargs):
59 | yield hist
60 |
61 |
62 | def get_history_farthest_sibling(shape, **kwargs):
63 | i = iter_history_foward(shape, **kwargs)
64 | return next(reversed(list(i)), None)
65 |
66 |
67 | def get_history_previous_sibling(shape, **kwargs):
68 | i = iter_history_backward(shape, **kwargs)
69 | return next(i, None)
70 |
71 |
72 | #
73 | # Utility functions to resolve skinning shapes from influences.
74 | #
75 |
76 | def iter_affected_shapes(objs, key=None):
77 | """
78 | :param obj: A reference object, generally a pymel.nodetypes.Joint.
79 | :return: The geometries affected by the object.
80 | """
81 | known_skinClusters = []
82 |
83 | # Ensure objs is iterable
84 | if not isinstance(objs, (tuple, list)):
85 | objs = [objs]
86 |
87 | for obj in objs:
88 | if isinstance(obj, pymel.nodetypes.Joint):
89 | # Collect all geometries affected by the joint.
90 | for hist in obj.worldMatrix.outputs():
91 | if isinstance(hist, pymel.nodetypes.SkinCluster) and not hist in known_skinClusters:
92 | known_skinClusters.append(hist)
93 | for geometry in hist.getOutputGeometry():
94 | if key is None or key(geometry):
95 | yield geometry
96 |
97 |
98 | def get_affected_shapes(objs, key=None):
99 | return list(iter_affected_shapes(objs, key=key))
100 |
101 |
102 | def get_affected_meshes(objs):
103 | return list(iter_affected_shapes(objs, key=_is_mesh))
104 |
105 |
106 | def get_affected_surface(objs):
107 | return list(iter_affected_shapes(objs, key=_is_surface))
108 |
--------------------------------------------------------------------------------
/omtk/ui/widget_logger.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/widget_logger.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:53 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_Form(object):
13 | def setupUi(self, Form):
14 | Form.setObjectName("Form")
15 | Form.resize(435, 300)
16 | self.verticalLayout = QtWidgets.QVBoxLayout(Form)
17 | self.verticalLayout.setContentsMargins(0, 0, 0, 0)
18 | self.verticalLayout.setObjectName("verticalLayout")
19 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
20 | self.horizontalLayout_4.setObjectName("horizontalLayout_4")
21 | self.label_2 = QtWidgets.QLabel(Form)
22 | self.label_2.setObjectName("label_2")
23 | self.horizontalLayout_4.addWidget(self.label_2)
24 | spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
25 | self.horizontalLayout_4.addItem(spacerItem)
26 | self.label_4 = QtWidgets.QLabel(Form)
27 | self.label_4.setObjectName("label_4")
28 | self.horizontalLayout_4.addWidget(self.label_4)
29 | self.lineEdit_log_search = QtWidgets.QLineEdit(Form)
30 | self.lineEdit_log_search.setObjectName("lineEdit_log_search")
31 | self.horizontalLayout_4.addWidget(self.lineEdit_log_search)
32 | self.label_3 = QtWidgets.QLabel(Form)
33 | self.label_3.setObjectName("label_3")
34 | self.horizontalLayout_4.addWidget(self.label_3)
35 | self.comboBox_log_level = QtWidgets.QComboBox(Form)
36 | self.comboBox_log_level.setObjectName("comboBox_log_level")
37 | self.comboBox_log_level.addItem("")
38 | self.comboBox_log_level.addItem("")
39 | self.comboBox_log_level.addItem("")
40 | self.comboBox_log_level.addItem("")
41 | self.horizontalLayout_4.addWidget(self.comboBox_log_level)
42 | self.pushButton_logs_save = QtWidgets.QPushButton(Form)
43 | self.pushButton_logs_save.setObjectName("pushButton_logs_save")
44 | self.horizontalLayout_4.addWidget(self.pushButton_logs_save)
45 | self.pushButton_logs_clear = QtWidgets.QPushButton(Form)
46 | self.pushButton_logs_clear.setObjectName("pushButton_logs_clear")
47 | self.horizontalLayout_4.addWidget(self.pushButton_logs_clear)
48 | self.verticalLayout.addLayout(self.horizontalLayout_4)
49 | self.tableView_logs = QtWidgets.QTableView(Form)
50 | self.tableView_logs.setObjectName("tableView_logs")
51 | self.verticalLayout.addWidget(self.tableView_logs)
52 |
53 | self.retranslateUi(Form)
54 | self.comboBox_log_level.setCurrentIndex(1)
55 | QtCore.QMetaObject.connectSlotsByName(Form)
56 |
57 | def retranslateUi(self, Form):
58 | Form.setWindowTitle(QtCompat.translate("Form", "Form", None, -1))
59 | self.label_2.setText(QtCompat.translate("Form", "Logs", None, -1))
60 | self.label_4.setText(QtCompat.translate("Form", "Search", None, -1))
61 | self.label_3.setText(QtCompat.translate("Form", "Level:", None, -1))
62 | self.comboBox_log_level.setItemText(0, QtCompat.translate("Form", "Error", None, -1))
63 | self.comboBox_log_level.setItemText(1, QtCompat.translate("Form", "Warning", None, -1))
64 | self.comboBox_log_level.setItemText(2, QtCompat.translate("Form", "Info", None, -1))
65 | self.comboBox_log_level.setItemText(3, QtCompat.translate("Form", "Debug", None, -1))
66 | self.pushButton_logs_save.setText(QtCompat.translate("Form", "Save", None, -1))
67 | self.pushButton_logs_clear.setText(QtCompat.translate("Form", "Clear", None, -1))
68 |
69 |
--------------------------------------------------------------------------------
/omtk/macros/avar_selection_toggle.py:
--------------------------------------------------------------------------------
1 | """
2 | Toogle selection between the facial animation layer and the rigging layer.
3 | """
4 | import pymel.core as pymel
5 | from omtk.vendor import libSerialization
6 | from omtk.core import macros
7 |
8 |
9 | def get_avars_networks_from_selection(objs):
10 | """
11 | Return all avars associated with the current selection.
12 | :param objs: A list of pymel.PyNode. If nothing is provided the selection is used.
13 | :return: A list of Avar instances.
14 | """
15 |
16 | def fn_skip(network):
17 | return libSerialization.is_network_from_class(network, 'Rig')
18 |
19 | def fn_filter(network):
20 | return libSerialization.is_network_from_class(network, 'AbstractAvar')
21 |
22 | if objs is None:
23 | objs = pymel.selected()
24 |
25 | networks = libSerialization.get_connected_networks(objs, key=fn_filter, key_skip=fn_skip)
26 |
27 | # avars = filter(None, [libSerialization.import_network(network) for network in networks])
28 |
29 | return networks
30 |
31 |
32 | def iter_connected_ctrl_models_networks(objs):
33 | def fn_skip(network):
34 | return libSerialization.is_network_from_class(network, 'Module') and not libSerialization.is_network_from_class(
35 | network, 'BaseCtrlModel')
36 |
37 | def fn_filter(network):
38 | return libSerialization.is_network_from_class(network, 'BaseCtrlModel')
39 |
40 | if objs is None:
41 | objs = pymel.selected()
42 |
43 | for network in libSerialization.iter_connected_networks(objs, key=fn_filter, key_skip=fn_skip):
44 | yield network
45 |
46 |
47 | def get_connected_ctrl_models_networks(objs):
48 | return list(iter_connected_ctrl_models_networks(objs))
49 |
50 |
51 | def iter_connected_ctrl_models(objs):
52 | for network in iter_connected_ctrl_models_networks(objs):
53 | ctrl_model = libSerialization.import_network(network)
54 | if ctrl_model:
55 | yield ctrl_model
56 |
57 |
58 | def get_connected_ctrl_models(objs):
59 | return list(iter_connected_ctrl_models(objs))
60 |
61 |
62 | class ToogleAvarSelection(macros.BaseMacro):
63 | def run(self):
64 | sel = pymel.selected()
65 |
66 | avar_networks = get_avars_networks_from_selection(sel)
67 | if not avar_networks:
68 | pymel.warning("Please select an avar controller or grp.")
69 | return
70 |
71 | # Since this is a toogle, we'll want to know if we are currently in 'animation' mode or 'rigging' mode.
72 |
73 | # If there's any controllers selected, we are in animation mode and want to select avar_grp.
74 | is_animation_mode = bool(get_connected_ctrl_models_networks(sel))
75 | if is_animation_mode:
76 | result = set()
77 | for network in avar_networks:
78 | if network.hasAttr('grp_rig'):
79 | grp_rig = next(iter(network.attr('grp_rig').inputs()), None)
80 | if grp_rig and grp_rig.hasAttr('avar_ud'): # we duck type avars by checking the UD avar
81 | result.add(grp_rig)
82 | pymel.select(result)
83 |
84 | # If there aren't any controllers selected, we are in rigging mode and want to select avar ctrls.
85 | else:
86 | result = set()
87 |
88 | def fn_filter(network):
89 | return isinstance(network, pymel.nodetypes.Network) and \
90 | libSerialization.is_network_from_class(network, 'BaseCtrl')
91 |
92 | for avar_network in avar_networks:
93 | if avar_network.hasAttr('ctrl'):
94 | for hist in avar_network.attr('ctrl').listHistory():
95 | if fn_filter(hist):
96 | ctrl = next(iter(hist.attr('node').inputs()), None) if hist.hasAttr('node') else None
97 | if ctrl:
98 | result.add(ctrl)
99 | pymel.select(result)
100 |
101 |
102 | def register_plugin():
103 | return ToogleAvarSelection
104 |
--------------------------------------------------------------------------------
/omtk/libs/libSkeleton.py:
--------------------------------------------------------------------------------
1 | """
2 | Various utility methods that help the job of laying out the skeletton for a Rig.
3 | """
4 | import pymel.core as pymel
5 | from omtk.libs import libPymel
6 | from maya import OpenMaya
7 | import math
8 |
9 |
10 | def mirror_obj(obj_src, obj_dst=None):
11 | """
12 | Method to mirror joints in behavior.
13 | This use existing joint and doesn't break the skin or the network associated with the joints.
14 | """
15 | from omtk.animation import mirrorPose
16 | if obj_dst is None:
17 | obj_dst = mirrorPose.get_ctrl_friend(obj_src)
18 | if obj_src is obj_dst:
19 | return False
20 | tm = obj_src.getMatrix(worldSpace=True)
21 | new_tm = mirrorPose.mirror_matrix(tm, mirror_x=True, flip_rot_x=True, flip_rot_y=True, flip_rot_z=True)
22 | obj_dst.setMatrix(new_tm, worldSpace=True)
23 | return obj_dst
24 |
25 |
26 | def transfer_rotation_to_joint_orient(obj):
27 | """
28 | In Maya it is not possible to do a "makeIdentity" command on a joint that is bound to a skinCluster.
29 | This method bypass this limitation.
30 | """
31 | mfn = obj.__apimfn__()
32 |
33 | rotation_orig = OpenMaya.MEulerRotation()
34 | mfn.getRotation(rotation_orig)
35 | rotation_xyz = rotation_orig.reorder(OpenMaya.MEulerRotation.kXYZ)
36 |
37 | # Apply existing jointOrient values
38 | orientation_orig = OpenMaya.MEulerRotation()
39 | mfn.getOrientation(orientation_orig)
40 | rotation_xyz *= orientation_orig
41 |
42 | def is_attr_accessible(attr):
43 | return not attr.isFreeToChange() == OpenMaya.MPlug.kFreeToChange
44 |
45 | if is_attr_accessible(obj.rotateX) or is_attr_accessible(obj.rotateY) or is_attr_accessible(obj.rotateZ):
46 | pymel.warning("Can't transfer rotation to joint orient. {0} rotation is locked.".format(obj.name()))
47 | return
48 |
49 | if is_attr_accessible(obj.jointOrientX) or is_attr_accessible(obj.jointOrientY) or is_attr_accessible(
50 | obj.jointOrientZ):
51 | pymel.warning("Can't transfer rotation to joint orient. {0} jointOrient is locked.".format(obj.name()))
52 | return
53 |
54 | obj.jointOrientX.set(math.degrees(rotation_xyz.x))
55 | obj.jointOrientY.set(math.degrees(rotation_xyz.y))
56 | obj.jointOrientZ.set(math.degrees(rotation_xyz.z))
57 | obj.rotateX.set(0)
58 | obj.rotateY.set(0)
59 | obj.rotateZ.set(0)
60 |
61 |
62 | def mirror_jnt(obj_src, handle_joint_orient=True, create_missing=True):
63 | from omtk.animation import mirrorPose
64 | obj_dst = mirrorPose.get_ctrl_friend(obj_src)
65 | if obj_dst is None:
66 | src_name = obj_src.name()
67 | dst_name = mirrorPose.get_name_friend(src_name)
68 | if src_name == dst_name:
69 | return False
70 |
71 | obj_dst = pymel.createNode('joint')
72 | obj_dst.rename(dst_name)
73 |
74 | obj_src_parent = obj_src.getParent()
75 | if obj_src_parent:
76 | obj_dst_parent = mirrorPose.get_ctrl_friend(obj_src_parent)
77 | if obj_dst_parent:
78 | obj_dst.setParent(obj_dst_parent)
79 |
80 | mirror_obj(obj_src, obj_dst)
81 | if handle_joint_orient and isinstance(obj_src, pymel.nodetypes.Joint) and isinstance(obj_dst,
82 | pymel.nodetypes.Joint):
83 | transfer_rotation_to_joint_orient(obj_dst)
84 | obj_dst.radius.set(obj_src.radius.get())
85 | return obj_dst
86 |
87 |
88 | def mirror_jnts(objs, **kwargs):
89 | # Sort objects by hyerarchy so we mirror parents before their children.
90 | objs = sorted(objs, key=libPymel.get_num_parents)
91 | with pymel.UndoChunk():
92 | for obj in objs:
93 | mirror_jnt(obj, **kwargs)
94 |
95 |
96 | def freeze_selected_joints_rotation():
97 | jnts = [obj for obj in pymel.selected() if isinstance(obj, pymel.nodetypes.Joint)]
98 | for jnt in jnts:
99 | if not isinstance(jnt, pymel.nodetypes.Joint):
100 | pymel.warning("Skipping non-joint {0}".format(jnt))
101 | continue
102 |
103 | transfer_rotation_to_joint_orient(jnt)
104 |
--------------------------------------------------------------------------------
/omtk/core/classModuleMap.py:
--------------------------------------------------------------------------------
1 | from .classModule import Module
2 |
3 |
4 | class ModuleMap(Module):
5 | """
6 | Define a Module that use a CtrlModel to control each inputs.
7 | # todo: Use this new class in the AvarGrp class!!!!!!
8 | """
9 | _CLS_CTRL_MODEL = None # please redefine!
10 | _CLS_CTRL = None # please redefine!
11 | DEFAULT_NAME_USE_FIRST_INPUT = True
12 |
13 | def __init__(self, *args, **kwargs):
14 | super(ModuleMap, self).__init__(*args, **kwargs)
15 | self.models = []
16 |
17 | def get_influences(self):
18 | return self.jnts
19 |
20 | def init_model(self, model, inputs, cls_model=None, cls_ctrl=None):
21 | """
22 | Initialize a new CtrlModel instance, reuse existing data as much as necessary.
23 | :param model: The current definition. If never defined, the value is None.
24 | :param inputs: The inputs to use.
25 | :param cls: The desired CtrlModel datatype.
26 | :param cls_ctrl: The desired Ctrl datatype.
27 | :return: A CtrlModel instance.
28 | """
29 | if cls_model is None:
30 | cls_model = self._CLS_CTRL_MODEL
31 | if cls_ctrl is None:
32 | cls_ctrl = self._CLS_CTRL
33 | # todo: validate inputs from existing model?
34 |
35 | # Use existing model if possible.
36 | if not isinstance(model, cls_model):
37 | if model:
38 | self.warning("Unexpected Model type for {0}. Expected {1}, got {2}.".format(
39 | model, cls_model.__name__, type(model).__name__
40 | ))
41 | model = cls_model(
42 | inputs,
43 | rig=self.rig
44 | )
45 |
46 | # Hack: Ensure a model has a name.
47 | if not model.name:
48 | model.name = model.get_default_name()
49 |
50 | # Hack: Force a certain ctrl type to the model.
51 | model._CLS_CTRL = cls_ctrl
52 |
53 | return model
54 |
55 | def init_models(self):
56 | new_models = []
57 | influences = self.get_influences()
58 | known_influences = set()
59 |
60 | # Check existing models
61 | for model in self.models:
62 | # Remove any unrecognized model
63 | if model.jnt not in influences:
64 | self.warning("Unexpected Model {0} will be deleted.".format(model.name))
65 | continue
66 |
67 | model_inputs = [model.jnt]
68 | model = self.init_model(model, model_inputs)
69 | new_models.append(model)
70 | known_influences.update(model_inputs)
71 |
72 | for influence in influences:
73 | if influence not in known_influences:
74 | model = self.init_model(None, [influence])
75 | new_models.append(model)
76 |
77 | return new_models
78 |
79 | def build_model(self, model, parent_grp_anm=True, parent_grp_rig=True, **kwargs):
80 | model.build(self, **kwargs)
81 | # todo: reduce cluttering by using direct connection and reducing grp_anm count
82 |
83 | if parent_grp_anm and model.grp_anm and self.grp_anm:
84 | model.grp_anm.setParent(self.grp_anm)
85 |
86 | if parent_grp_rig and model.grp_rig and self.grp_rig:
87 | model.grp_rig.setParent(self.grp_rig)
88 |
89 | def build_models(self, **kwargs):
90 | for model in self.models:
91 | self.build_model(model, **kwargs)
92 |
93 | def build(self, create_grp_anm=True, create_grp_rig=True, connect_global_scale=True,
94 | parent=True, **model_kwargs):
95 | super(ModuleMap, self).build(
96 | create_grp_anm=create_grp_anm,
97 | create_grp_rig=create_grp_rig,
98 | connect_global_scale=connect_global_scale,
99 | parent=parent
100 | )
101 |
102 | self.models = self.init_models()
103 |
104 | self.build_models(
105 | **model_kwargs
106 | )
107 |
108 | def unbuild(self, **kwargs):
109 | for model in self.models:
110 | model.unbuild()
111 |
112 | super(ModuleMap, self).unbuild(**kwargs)
113 |
--------------------------------------------------------------------------------
/omtk/widget_list_meshes.py:
--------------------------------------------------------------------------------
1 | import re
2 | import pymel.core as pymel
3 | from ui import widget_list_meshes
4 |
5 | from omtk.libs import libSkinning
6 | from omtk.libs import libQt
7 |
8 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets
9 |
10 | import ui_shared
11 |
12 |
13 | class WidgetListMeshes(QtWidgets.QWidget):
14 | def __init__(self, parent=None):
15 | super(WidgetListMeshes, self).__init__(parent=parent)
16 |
17 | self._rig = None
18 |
19 | self.ui = widget_list_meshes.Ui_Form()
20 | self.ui.setupUi(self)
21 |
22 | # Connect events
23 | self.ui.treeWidget.itemSelectionChanged.connect(self.on_mesh_selection_changed)
24 | self.ui.lineEdit_search.textChanged.connect(self.on_meshes_query_changed)
25 | self.ui.pushButton_selectGrpMeshes.pressed.connect(self.on_SelectGrpMeshes)
26 | self.ui.btn_update.pressed.connect(self.update)
27 |
28 | def set_rig(self, rig, update=True):
29 | self._rig = rig
30 | if update:
31 | self.update_list()
32 |
33 | def update_list(self):
34 | self.ui.treeWidget.clear()
35 |
36 | if self._rig is None:
37 | return
38 |
39 | # Hack: force cache to invalidate
40 | # try:
41 | # self._rig.get_meshes.func.im_self.cache.clear()
42 | # except Exception, e:
43 | # pass
44 | all_meshes = self._rig.get_shapes()
45 |
46 | if all_meshes:
47 | widget_root = self.ui.treeWidget.invisibleRootItem()
48 |
49 | for mesh in all_meshes:
50 | influences = None
51 |
52 | skincluster = libSkinning.get_skin_cluster(mesh)
53 | if skincluster:
54 | influences = sorted(libSkinning.get_skin_cluster_influence_objects(skincluster))
55 |
56 | self._fill_widget_meshes(widget_root, mesh, influences)
57 |
58 | self.update_list_visibility()
59 |
60 | def _fill_widget_meshes(self, qt_parent, mesh, influences):
61 | textBrush = QtGui.QBrush(QtCore.Qt.white)
62 |
63 | # Add mesh
64 | item_mesh = QtWidgets.QTreeWidgetItem(0)
65 | item_mesh.setText(0, str(mesh))
66 | item_mesh.setForeground(0, textBrush)
67 | ui_shared.set_icon_from_type(mesh.getParent(), item_mesh)
68 | qt_parent.addChild(item_mesh)
69 |
70 | # Monkey-patch mesh QWidget
71 | item_mesh.metadata_type = ui_shared.MetadataType.Mesh
72 | item_mesh.metadata_data = mesh
73 |
74 | # Add influences
75 | if influences:
76 | for influence in influences:
77 | item = QtWidgets.QTreeWidgetItem(0)
78 | item.setText(0, str(influence))
79 | item.setForeground(0, textBrush)
80 | ui_shared.set_icon_from_type(influence, item)
81 | item_mesh.addChild(item)
82 |
83 | # Monkey-patch influence QWidget
84 | item.metadata_type = ui_shared.MetadataType.Influence
85 | item.metadata_data = influence
86 |
87 | def update_list_visibility(self, query_regex=None):
88 | if query_regex is None:
89 | query_raw = self.ui.lineEdit_search.text()
90 | query_regex = ".*{0}.*".format(query_raw) if query_raw else ".*"
91 |
92 | def fn_can_show(qItem, query_regex):
93 | if qItem.metadata_type == ui_shared.MetadataType.Influence: # Always show influences
94 | return True
95 |
96 | return not query_regex or re.match(query_regex, qItem.text(0), re.IGNORECASE)
97 |
98 | for qt_item in libQt.get_all_QTreeWidgetItem(self.ui.treeWidget):
99 | can_show = fn_can_show(qt_item, query_regex)
100 | qt_item.setHidden(not can_show)
101 |
102 | def get_selection(self):
103 | result = []
104 | for item in self.ui.treeWidget.selectedItems():
105 | if item.metadata_data.exists():
106 | result.append(item.metadata_data.getParent())
107 | return result
108 |
109 | #
110 | # Events
111 | #
112 |
113 | def on_mesh_selection_changed(self):
114 | pymel.select(self.get_selection())
115 |
116 | def on_meshes_query_changed(self, *args, **kwargs):
117 | self.update_list_visibility()
118 |
119 | def on_SelectGrpMeshes(self):
120 | grp = self._rig.grp_geo
121 | if not grp or not grp.exists():
122 | pymel.warning("Can't find influence grp!")
123 | else:
124 | pymel.select(grp)
125 |
--------------------------------------------------------------------------------
/omtk/modules/rigFaceJaw.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk.libs import libCtrlShapes
4 | from omtk.libs import libRigging
5 | from omtk.libs import libPython
6 | from omtk.modules import rigFaceAvar
7 | from omtk.modules import rigFaceAvarGrps
8 | from omtk.models import model_ctrl_linear
9 |
10 |
11 | class CtrlJaw(rigFaceAvar.BaseCtrlFace):
12 | def __createNode__(self, **kwargs):
13 | node = libCtrlShapes.create_triangle_low()
14 | # node.r.lock()
15 | node.s.lock()
16 | return node
17 |
18 |
19 | class ModelCtrlJaw(model_ctrl_linear.ModelCtrlLinear):
20 | def connect(self, avar, avar_grp, ud=True, fb=True, lr=True, yw=True, pt=True, rl=True, sx=True, sy=True, sz=True):
21 | libRigging.connectAttr_withLinearDrivenKeys(
22 | self.ctrl.translateX, avar.attr_lr
23 | )
24 | libRigging.connectAttr_withLinearDrivenKeys(
25 | self.ctrl.translateY, avar.attr_ud,
26 | )
27 | libRigging.connectAttr_withLinearDrivenKeys(
28 | self.ctrl.translateZ, avar.attr_fb
29 | )
30 | libRigging.connectAttr_withLinearDrivenKeys(
31 | self.ctrl.rotateX, avar.attr_pt
32 | )
33 | libRigging.connectAttr_withLinearDrivenKeys(
34 | self.ctrl.rotateY, avar.attr_yw
35 | )
36 | libRigging.connectAttr_withLinearDrivenKeys(
37 | self.ctrl.rotateZ, avar.attr_rl
38 | )
39 | # todo: connect jaw ratio
40 |
41 | def get_default_tm_ctrl(self):
42 | """
43 | Find the chin location using raycast. This is the prefered location for the jaw doritos.
44 | If raycast don't return any information, use the default behavior.
45 | """
46 | ref = self.jnt.getMatrix(worldSpace=True)
47 | pos_s = pymel.datatypes.Point(self.jnt.getTranslation(space='world'))
48 | pos_e = pymel.datatypes.Point(1, 0, 0) * ref
49 | dir = pos_e - pos_s
50 | result = self.rig.raycast_farthest(pos_s, dir)
51 | if not result:
52 | return super(ModelCtrlJaw, self).get_default_tm_ctrl()
53 |
54 | tm = pymel.datatypes.Matrix([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, result.x, result.y, result.z, 1])
55 | return tm
56 |
57 |
58 | class AvarJaw(rigFaceAvar.AvarSimple):
59 | """
60 | This avar is not designed to use any surface.
61 | """
62 | SHOW_IN_UI = False
63 | IS_SIDE_SPECIFIC = False
64 |
65 | def get_default_name(self):
66 | return 'Jaw'
67 |
68 |
69 | class FaceJaw(rigFaceAvarGrps.AvarGrpOnSurface):
70 | """
71 | AvarGrp customized for jaw rigging. Necessary for some facial modules.
72 | The Jaw is a special zone since it doesn't happen in pre-deform, it happen in the main skinCluster.
73 | The Jaw global avars are made
74 | """
75 | _CLS_AVAR = AvarJaw
76 | _CLS_AVAR_MACRO = rigFaceAvar.AvarSimple # todo: use AbstractAvar???
77 | CREATE_MACRO_AVAR_ALL = True
78 | CREATE_MACRO_AVAR_HORIZONTAL = False
79 | CREATE_MACRO_AVAR_VERTICAL = False
80 | _CLS_CTRL_ALL = CtrlJaw
81 | _CLS_CTRL_MICRO = None
82 | _CLS_MODEL_CTRL_ALL = ModelCtrlJaw
83 | _CLS_MODEL_CTRL_MICRO = None
84 | SHOW_IN_UI = True
85 | SINGLE_INFLUENCE = True
86 |
87 | def handle_surface(self):
88 | pass # todo: better class schema!
89 |
90 | @libPython.memoized_instancemethod
91 | def _get_avar_macro_all_ctrl_tm(self):
92 | """
93 | If the rigger provided an extra influence (jaw_end), we'll use it to define the ctrl and influence position.
94 | """
95 | jnt_jaw = self.jnt
96 | if len(self.jnts) > 1:
97 | self.info('Using {} as the ctrl position reference.'.format(jnt_jaw.name()))
98 | jnt_ref = self.jnts[-1]
99 | p = jnt_ref.getTranslation(space='world')
100 | else:
101 | ref = jnt_jaw.getMatrix(worldSpace=True)
102 | pos_s = pymel.datatypes.Point(self.jnt.getTranslation(space='world'))
103 | pos_e = pymel.datatypes.Point(1, 0, 0) * ref
104 | dir = pos_e - pos_s
105 | p = self.rig.raycast_farthest(pos_s, dir)
106 | if p is None:
107 | self.warning("Raycast failed. Using {} as the ctrl position reference.".format(jnt_jaw))
108 | p = pos_s
109 |
110 | result = pymel.datatypes.Matrix(
111 | 1, 0, 0, 0,
112 | 0, 1, 0, 0,
113 | 0, 0, 1, 0,
114 | p.x, p.y, p.z, 1
115 | )
116 | return result
117 |
118 |
119 | def register_plugin():
120 | return FaceJaw
121 |
--------------------------------------------------------------------------------
/omtk/ui/widget_create_component.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Form implementation generated from reading ui file '/home/rlessard/packages/omtk/0.4.999/python/omtk/ui/widget_create_component.ui'
4 | #
5 | # Created: Tue Feb 20 10:34:52 2018
6 | # by: pyside2-uic running on Qt 2.0.0~alpha0
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets, QtCompat
11 |
12 | class Ui_MainWindow(object):
13 | def setupUi(self, MainWindow):
14 | MainWindow.setObjectName("MainWindow")
15 | MainWindow.resize(819, 416)
16 | self.centralwidget = QtWidgets.QWidget(MainWindow)
17 | self.centralwidget.setObjectName("centralwidget")
18 | self.horizontalLayout_4 = QtWidgets.QHBoxLayout(self.centralwidget)
19 | self.horizontalLayout_4.setObjectName("horizontalLayout_4")
20 | self.verticalLayout = QtWidgets.QVBoxLayout()
21 | self.verticalLayout.setObjectName("verticalLayout")
22 | self.label_ctrl = QtWidgets.QLabel(self.centralwidget)
23 | self.label_ctrl.setObjectName("label_ctrl")
24 | self.verticalLayout.addWidget(self.label_ctrl)
25 | self.widget_view_ctrl = WidgetCreateComponentWizardParts(self.centralwidget)
26 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
27 | sizePolicy.setHorizontalStretch(0)
28 | sizePolicy.setVerticalStretch(0)
29 | sizePolicy.setHeightForWidth(self.widget_view_ctrl.sizePolicy().hasHeightForWidth())
30 | self.widget_view_ctrl.setSizePolicy(sizePolicy)
31 | self.widget_view_ctrl.setObjectName("widget_view_ctrl")
32 | self.verticalLayout.addWidget(self.widget_view_ctrl)
33 | self.horizontalLayout_4.addLayout(self.verticalLayout)
34 | self.verticalLayout_2 = QtWidgets.QVBoxLayout()
35 | self.verticalLayout_2.setObjectName("verticalLayout_2")
36 | self.label_infl = QtWidgets.QLabel(self.centralwidget)
37 | self.label_infl.setObjectName("label_infl")
38 | self.verticalLayout_2.addWidget(self.label_infl)
39 | self.widget_view_infl = WidgetCreateComponentWizardParts(self.centralwidget)
40 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
41 | sizePolicy.setHorizontalStretch(0)
42 | sizePolicy.setVerticalStretch(0)
43 | sizePolicy.setHeightForWidth(self.widget_view_infl.sizePolicy().hasHeightForWidth())
44 | self.widget_view_infl.setSizePolicy(sizePolicy)
45 | self.widget_view_infl.setObjectName("widget_view_infl")
46 | self.verticalLayout_2.addWidget(self.widget_view_infl)
47 | self.horizontalLayout_4.addLayout(self.verticalLayout_2)
48 | self.verticalLayout_3 = QtWidgets.QVBoxLayout()
49 | self.verticalLayout_3.setObjectName("verticalLayout_3")
50 | self.label_guid = QtWidgets.QLabel(self.centralwidget)
51 | self.label_guid.setObjectName("label_guid")
52 | self.verticalLayout_3.addWidget(self.label_guid)
53 | self.widget_view_guid = WidgetCreateComponentWizardParts(self.centralwidget)
54 | sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.MinimumExpanding)
55 | sizePolicy.setHorizontalStretch(0)
56 | sizePolicy.setVerticalStretch(0)
57 | sizePolicy.setHeightForWidth(self.widget_view_guid.sizePolicy().hasHeightForWidth())
58 | self.widget_view_guid.setSizePolicy(sizePolicy)
59 | self.widget_view_guid.setObjectName("widget_view_guid")
60 | self.verticalLayout_3.addWidget(self.widget_view_guid)
61 | self.horizontalLayout_4.addLayout(self.verticalLayout_3)
62 | MainWindow.setCentralWidget(self.centralwidget)
63 | self.menubar = QtWidgets.QMenuBar(MainWindow)
64 | self.menubar.setGeometry(QtCore.QRect(0, 0, 819, 19))
65 | self.menubar.setObjectName("menubar")
66 | MainWindow.setMenuBar(self.menubar)
67 | self.statusbar = QtWidgets.QStatusBar(MainWindow)
68 | self.statusbar.setObjectName("statusbar")
69 | MainWindow.setStatusBar(self.statusbar)
70 |
71 | self.retranslateUi(MainWindow)
72 | QtCore.QMetaObject.connectSlotsByName(MainWindow)
73 |
74 | def retranslateUi(self, MainWindow):
75 | MainWindow.setWindowTitle(QtCompat.translate("MainWindow", "Component Creation Wizard", None, -1))
76 | self.label_ctrl.setText(QtCompat.translate("MainWindow", "Ctrls:", None, -1))
77 | self.label_infl.setText(QtCompat.translate("MainWindow", "Influences:", None, -1))
78 | self.label_guid.setText(QtCompat.translate("MainWindow", "Guides:", None, -1))
79 |
80 | from ..widget_create_component_wizard_parts import WidgetCreateComponentWizardParts
81 |
--------------------------------------------------------------------------------
/omtk/pluginmanager_window.py:
--------------------------------------------------------------------------------
1 | from omtk.core import plugin_manager
2 |
3 | from omtk.vendor.Qt import QtCore, QtWidgets
4 |
5 | from ui import pluginmanager_window
6 |
7 |
8 | class PluginListModel(QtCore.QAbstractTableModel):
9 | """
10 | QTableModel that list attributes.
11 | # TODO: Benchmark pymel.Attribute vs lazy proxy class.
12 | """
13 | HEADER = ('Name', 'Type', 'Status', 'Location', 'Description')
14 |
15 | ROW_NAME = 0
16 | ROW_TYPE = 1
17 | ROW_STAT = 2
18 | ROW_LOCA = 3
19 | ROW_DESC = 4
20 |
21 | def __init__(self, parent, data, *args):
22 | super(PluginListModel, self).__init__(parent, *args)
23 | self.items = data
24 | self.header = self.HEADER
25 |
26 | def rowCount(self, parent):
27 | return len(self.items)
28 |
29 | def columnCount(self, parent):
30 | return len(self.header)
31 |
32 | def data(self, index, role):
33 | if not index.isValid():
34 | return None
35 |
36 | if role == QtCore.Qt.DisplayRole:
37 | plugin = self.items[index.row()]
38 | col_index = index.column()
39 | if col_index == self.ROW_NAME:
40 | return plugin.name
41 | elif col_index == self.ROW_TYPE:
42 | return plugin.type_name.title()
43 | elif col_index == self.ROW_STAT:
44 | return plugin.status
45 | elif col_index == self.ROW_LOCA:
46 | return plugin.module.__file__ if plugin.module else 'n/a'
47 | elif col_index == self.ROW_DESC:
48 | return plugin.description
49 | return ''
50 | elif role == QtCore.Qt.BackgroundColorRole:
51 | plugin = self.items[index.row()]
52 | if plugin.status == plugin_manager.PluginStatus.Failed:
53 | return QtCore.Qt.red
54 | return None
55 |
56 | return None
57 |
58 | def headerData(self, col, orientation, role):
59 | if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
60 | return self.header[col]
61 | return None
62 |
63 | def reset(self):
64 | """Backport of Qt4 .reset method()"""
65 | self.beginResetModel()
66 | self.endResetModel()
67 |
68 |
69 | class PluginListFilterProxyModel(QtCore.QSortFilterProxyModel):
70 | def __init__(self, parent):
71 | super(PluginListFilterProxyModel, self).__init__(parent)
72 | self._search_query = None
73 |
74 | def set_search_query(self, search_query, update=True):
75 | self._search_query = search_query
76 | if update:
77 | self.reset()
78 |
79 | def filterAcceptsRow(self, row, index):
80 | if not self._search_query:
81 | return True
82 |
83 | model = self.sourceModel()
84 | item = model.items[row]
85 | return self._search_query in item.module_name
86 |
87 | def reset(self):
88 | """Backport of Qt4 .reset method()"""
89 | self.beginResetModel()
90 | self.endResetModel()
91 |
92 |
93 | class PluginManagerWindow(QtWidgets.QMainWindow):
94 | searchQueryChanged = QtCore.Signal(str)
95 |
96 | def __init__(self, parent=None):
97 | super(PluginManagerWindow, self).__init__(parent=parent)
98 |
99 | # Initialize GUI
100 | self.ui = pluginmanager_window.Ui_mainWindow()
101 | self.ui.setupUi(self)
102 |
103 | # Initialize MVC
104 | self._data = plugin_manager.plugin_manager.get_plugins()
105 | self._model = PluginListModel(self, self._data)
106 | self._proxy_model = PluginListFilterProxyModel(self)
107 | self._proxy_model.setSourceModel(self._model)
108 | self._proxy_model.setDynamicSortFilter(False)
109 | self.ui.tableView.setModel(self._proxy_model)
110 |
111 | # Connect actions
112 | self.ui.actionReload.triggered.connect(self.on_reload)
113 | self.ui.actionSearchQueryChanged.triggered.connect(self.on_searchquery_changed)
114 |
115 | def iter_selected_plugins(self):
116 | for row in self.ui.tableView.selectionModel().selectedRows():
117 | plugin = self._data[row.row()]
118 | yield plugin
119 |
120 | def get_selected_plugins(self):
121 | return list(self.iter_selected_plugins())
122 |
123 | def on_reload(self):
124 | for plugin in self.iter_selected_plugins():
125 | plugin.load(force=True)
126 | self._proxy_model.reset()
127 |
128 | def on_searchquery_changed(self, *args, **kwargs):
129 | query = self.ui.lineEdit_search.text()
130 | self._proxy_model.set_search_query(query)
131 |
132 |
133 | gui = PluginManagerWindow()
134 |
135 |
136 | def show():
137 | global gui
138 | gui.show()
139 |
--------------------------------------------------------------------------------
/omtk/patches/patch_0.2.0_missing_spaceswitch_targets.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | import pymel.core as pymel
4 |
5 | from omtk.modules import rigDpSpine
6 |
7 | from omtk.vendor import libSerialization
8 |
9 | log = logging.getLogger('omtk')
10 |
11 |
12 | def patch_spaceswitch_data_into_network(sel=None):
13 | if sel is None:
14 | sel = pymel.selected()
15 |
16 | networks = libSerialization.get_connected_networks(sel, key=lambda
17 | net: libSerialization.is_network_from_class(net, 'BaseCtrl'))
18 |
19 | for net in networks:
20 | ctrl_instance = libSerialization.import_network(net)
21 | data = ctrl_instance.get_spaceswitch_enum_targets()
22 |
23 | if data:
24 | # Create missing attributes if needed
25 | if not net.hasAttr('targets_indexes'):
26 | log.info('targets_indexes attribute is missing on network {0}. It will be created'.format(net))
27 | pymel.addAttr(net, longName='targets_indexes', at='long', multi=True)
28 | if not net.hasAttr('targets'):
29 | log.info('targets attribute is missing on network {0}. It will be created'.format(net))
30 | pymel.addAttr(net, longName='targets', at='message', multi=True)
31 | if not net.hasAttr('local_index'):
32 | log.info('targets_indexes attribute is missing on network {0}. It will be created'.format(net))
33 | pymel.addAttr(net, longName='local_index', at='long')
34 |
35 | # Add data if needed
36 | for index, (name, obj) in data.items():
37 | target_data = net.targets.get()
38 | indexes_data = net.targets_indexes.get()
39 | if obj not in target_data:
40 | if index not in indexes_data:
41 | if obj is not None:
42 | if isinstance(obj, pymel.nt.Joint):
43 | log.info('Patching network {0} space switch data named {1} - Index {2} on object {3}'
44 | .format(net.name(), name, index, obj))
45 | pymel.connectAttr(obj.message, net.targets[len(target_data)])
46 | elif isinstance(obj, pymel.nt.Transform):
47 | key = lambda network: libSerialization.is_network_from_class(network, 'Node')
48 | ctrl_net = libSerialization.get_connected_networks(obj, key)
49 | if ctrl_net > 0:
50 | log.info('Patching network {0} space switch data named {1} - '
51 | 'Index {2} on object {3}'.format(net.name(), name, index, ctrl_net[0]))
52 | pymel.connectAttr(ctrl_net[0].message, net.targets[len(target_data)])
53 | net.targets_indexes[len(target_data)].set(index)
54 | elif name == 'Local':
55 | log.info('Patching network {0} space switch data named {1} - Index {2} as local index'
56 | .format(net.name(), name, index))
57 | net.local_index.set(index)
58 | else:
59 | if obj is not None:
60 | log.warning('Object {0} have not been found in attribute targets but index {1} yes.'
61 | 'This object will not be patched to prevent any index conflict. '
62 | 'Look at your data to understand'.format(obj, index))
63 | else:
64 | log.info('Space Switch object {0} is already included in the targets attributes'.format(obj))
65 |
66 |
67 | def patch_spaceswitch_dpspine_objects():
68 | """
69 | This function is not clean, but it can be used to generate spaceswitch object for certain module and after look at
70 | all space switch target information to replace to space switch object target that could used a ctrl to the new space
71 | switch object that will not be removed after an unbuild
72 | """
73 |
74 | # Get the rig instance in the scene (Now support only the first one found
75 | rig_net = libSerialization.get_networks_from_class('Rig')[0]
76 | rig_instance = libSerialization.import_network(rig_net)
77 |
78 | # Get all the module that we could need to patch
79 | to_patch = [module for module in rig_instance.modules if isinstance(module, rigDpSpine.DpSpine)]
80 |
81 | for module in to_patch:
82 | module_to_patch = module
83 | if isinstance(module_to_patch, rigDpSpine.DpSpine):
84 | # Find all connection that have from the ctrl ik down (COG) and replace it
85 | connected_to_fk_dwn = module_to_patch.ctrl_fk_dwn._network.message.outputs(s=False, d=True, p=True)
86 |
87 | for connection in connected_to_fk_dwn:
88 | attr_name = connection.shortName()
89 | if attr_name.find('targets') >= 0:
90 | log.info('Reconnecting {0} from {1} to space switch node {2}'
91 | .format(connection, module_to_patch.ctrl_ik_dwn, module_to_patch.ctrl_ik_dwn._network))
92 | connection.disconnect()
93 | pymel.connectAttr(module_to_patch.ctrl_ik_dwn._network.message, connection, force=True)
--------------------------------------------------------------------------------
/omtk/modules/rigFK.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 | from omtk.core.classCtrl import BaseCtrl
3 | from omtk.core.classModule import Module
4 | from omtk.libs import libPython
5 | from omtk.libs import libRigging
6 |
7 |
8 | class CtrlFk(BaseCtrl):
9 | def __createNode__(self, *args, **kwargs):
10 | '''
11 | if 'shoulder' in name.lower():
12 | node = libCtrlShapes.create_shape_double_needle(size=size*0.04, normal=(0, 0, 1), *args, **kwargs)
13 | else:
14 | '''
15 | node = super(CtrlFk, self).__createNode__(multiplier=1.1, *args, **kwargs)
16 |
17 | make = next(iter(node.inputs()), None)
18 | if make:
19 | # TODO: Multiply radius???
20 | # make.radius.set(size)
21 | make.degree.set(1)
22 | make.sections.set(8)
23 |
24 | return node
25 |
26 |
27 | class FK(Module):
28 | """
29 | A Simple FK with support for multiple hyerarchy.
30 |
31 | Note that there's multiple way to name the ctrls.
32 | 1) Use the inputs as reference ( _NAME_CTRL_ENUMERATE = False )
33 | ex: (module name is arm_l)
34 | jnt_upperarm_l -> ctrl_arm_upperarm_l
35 | jnt_forearm_l -> ctrl_arm_forearm_l
36 | 2) Use enumeration ( _NAME_CTRL_ENUMERATE = True )
37 | ex: (module name is arm_l)
38 | jnt_upperarm_l -> ctrl_arm_01_l
39 | jnt_forearm_l -> ctrl_arm_02_l
40 | ex:
41 | """
42 | DEFAULT_NAME_USE_FIRST_INPUT = True
43 | _NAME_CTRL_ENUMERATE = False # If set to true, the ctrl will use the module name.
44 | _FORCE_INPUT_NAME = False # Force using the name of the input in the name of the ctrl
45 | # Otherwise they will use their associated input name.
46 | _CLS_CTRL = CtrlFk
47 |
48 | def __init__(self, *args, **kwargs):
49 | super(FK, self).__init__(*args, **kwargs)
50 | self.ctrls = []
51 | self.sw_translate = False
52 | self.create_spaceswitch = True
53 |
54 | #
55 | # libSerialization implementation
56 | #
57 | def __callbackNetworkPostBuild__(self):
58 | """
59 | Cleaning routine automatically called by libSerialization after a network import.
60 | """
61 | # Ensure there's no None value in the .ctrls array.
62 | # This can happen if the rigging delete the stored shape before rebuilding.
63 | try:
64 | self.ctrls = filter(None, self.ctrls)
65 | except (AttributeError, TypeError):
66 | pass
67 | super(FK, self).__callbackNetworkPostBuild__()
68 |
69 | def build(self, constraint=True, parent=True, create_grp_anm=True, create_grp_rig=False, *args, **kwargs):
70 | super(FK, self).build(create_grp_rig=create_grp_rig, *args, **kwargs)
71 | nomenclature_anm = self.get_nomenclature_anm()
72 | nomenclature_rig = self.get_nomenclature_rig()
73 |
74 | # Initialize ctrls
75 | libPython.resize_list(self.ctrls, len(self.jnts))
76 | for i, ctrl in enumerate(self.ctrls):
77 | self.ctrls[i] = self.init_ctrl(self._CLS_CTRL, ctrl)
78 |
79 | for i, chain in enumerate(self.chains):
80 | # Build chain ctrls
81 | chain_ctrls = []
82 | for j, jnt in enumerate(chain):
83 | jnt_index = self.jnts.index(jnt) # todo: optimize performance by created a map?
84 | ctrl = self.ctrls[jnt_index]
85 | chain_ctrls.append(ctrl)
86 |
87 | # Resolve ctrl name.
88 | # TODO: Validate with multiple chains
89 | nomenclature = nomenclature_anm + self.rig.nomenclature(jnt.stripNamespace().nodeName())
90 | if not self._FORCE_INPUT_NAME:
91 | if len(self.jnts) == 1 and len(self.chains) == 1:
92 | ctrl_name = nomenclature_anm.resolve()
93 | elif len(self.chains) == 1 or self._NAME_CTRL_ENUMERATE:
94 | ctrl_name = nomenclature_anm.resolve('{0:02d}'.format(j))
95 | else:
96 | ctrl_name = nomenclature.resolve()
97 | else:
98 | ctrl_name = nomenclature.resolve()
99 |
100 | ctrl.build(name=ctrl_name, refs=jnt, geometries=self.rig.get_meshes())
101 | ctrl.setMatrix(jnt.getMatrix(worldSpace=True))
102 |
103 | # Build space-switch for first chain ctrl
104 | if j == 0:
105 | if self.create_spaceswitch:
106 | if self.sw_translate:
107 | ctrl.create_spaceswitch(self, self.parent, add_world=True)
108 | else:
109 | ctrl.create_spaceswitch(self, self.parent, skipTranslate=['x', 'y', 'z'], add_world=True)
110 |
111 | if chain_ctrls:
112 | chain_ctrls[0].setParent(self.grp_anm)
113 | libRigging.create_hyerarchy(chain_ctrls)
114 |
115 | # Constraint jnts to ctrls if necessary
116 | if constraint is True:
117 | for jnt, ctrl in zip(self.jnts, self.ctrls):
118 | pymel.parentConstraint(ctrl, jnt, maintainOffset=True)
119 | pymel.connectAttr(ctrl.scaleX, jnt.scaleX)
120 | pymel.connectAttr(ctrl.scaleY, jnt.scaleY)
121 | pymel.connectAttr(ctrl.scaleZ, jnt.scaleZ)
122 |
123 |
124 | def register_plugin():
125 | return FK
126 |
--------------------------------------------------------------------------------
/omtk/modules/rigFKAdditive.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import pymel.core as pymel
3 | from omtk import constants
4 | from omtk.core.classCtrl import BaseCtrl
5 | from omtk.core.classModule import Module
6 | from omtk.libs import libRigging
7 | from omtk.libs import libCtrlShapes
8 | from omtk.libs import libPython
9 | from omtk.libs import libAttr
10 | from omtk.modules import rigFK
11 |
12 |
13 | class CtrlFkAdd(BaseCtrl):
14 | def __createNode__(self, size=None, refs=None, *args, **kwargs):
15 | # Resolve size automatically if refs are provided.
16 | ref = next(iter(refs), None) if isinstance(refs, collections.Iterable) else refs
17 | if size is None and ref is not None:
18 | size = libRigging.get_recommended_ctrl_size(ref)
19 | else:
20 | size = 1.0
21 |
22 | node = libCtrlShapes.create_shape_needle(size=size, *args, **kwargs)
23 |
24 | return node
25 |
26 |
27 | class AdditiveFK(rigFK.FK):
28 | """
29 | An AdditiveFK chain is a standard FK chain that have one or many additional controllers to rotate the entire chain.
30 | """
31 | _CLASS_CTRL_IK = CtrlFkAdd
32 |
33 | def __init__(self, *args, **kwargs):
34 | super(AdditiveFK, self).__init__(*args, **kwargs)
35 | self.num_ctrls = 1
36 | self.additive_ctrls = []
37 | # Deactivate additive fk ctrl to prevent anybody to use it
38 | self.enable_addfk_ctrl = True
39 |
40 | def build(self, *args, **kwargs):
41 | super(AdditiveFK, self).build(*args, **kwargs)
42 |
43 | nomenclature_anm = self.get_nomenclature_anm()
44 | nomenclature_rig = self.get_nomenclature_rig()
45 |
46 | # Ensure to create the finger ctrl in the good orientation
47 | if nomenclature_anm.side == self.rig.nomenclature.SIDE_L:
48 | normal_data = {constants.Axis.x: (1, 0, 0), constants.Axis.y: (0, 1, 0), constants.Axis.z: (0, 0, 1)}
49 | else:
50 | normal_data = {constants.Axis.x: (-1, 0, 0), constants.Axis.y: (0, -1, 0), constants.Axis.z: (0, 0, -1)}
51 |
52 | self.additive_ctrls = filter(None, self.additive_ctrls)
53 | if not self.additive_ctrls:
54 | ctrl_add = CtrlFkAdd()
55 | self.additive_ctrls.append(ctrl_add)
56 |
57 | # HACK - Temp since we don't support multiple ctrl for the moment
58 | ctrl_add = self.additive_ctrls[0]
59 | for i, ctrl in enumerate(self.additive_ctrls):
60 | # Resolve ctrl name
61 | nomenclature = nomenclature_anm + self.rig.nomenclature(self.jnt.stripNamespace().nodeName())
62 | if not self._FORCE_INPUT_NAME:
63 | if len(self.additive_ctrls) == 1 and len(self.chains) == 1:
64 | ctrl_name = nomenclature_anm.resolve("addFk")
65 | elif len(self.chains) == 1 or self._NAME_CTRL_ENUMERATE:
66 | ctrl_name = nomenclature_anm.resolve("addFk", "{0:02d}".format(i))
67 | else:
68 | ctrl_name = nomenclature.resolve("addFk")
69 | else:
70 | ctrl_name = nomenclature.resolve("addFk")
71 |
72 | ctrl.build(name=ctrl_name, refs=self.chain.start, normal=normal_data[self.rig.DEFAULT_UPP_AXIS])
73 | ctrl.offset.setMatrix(self.chain.start.getMatrix(worldSpace=True))
74 | ctrl.setParent(self.grp_anm)
75 | # In case we don't want to see addFk ctrl, like in a hand.
76 | # TODO - In this case, maybe the hand would be best to switch it's finger to fk
77 | if not self.enable_addfk_ctrl:
78 | ctrl.visibility.set(False)
79 | libAttr.lock_hide_trs(ctrl)
80 |
81 | for i, ctrl in enumerate(self.ctrls):
82 | # HACK Add a new layer if this is the first ctrl to prevent Gimbal lock problems
83 | if i == 0:
84 | ctrl.offset = ctrl.append_layer("gimbal")
85 | attr_rotate_x = libRigging.create_utility_node('addDoubleLinear',
86 | input1=ctrl.offset.rotateX.get(),
87 | input2=ctrl_add.rotateX
88 | ).output
89 | attr_rotate_y = libRigging.create_utility_node('addDoubleLinear',
90 | input1=ctrl.offset.rotateY.get(),
91 | input2=ctrl_add.rotateY
92 | ).output
93 | attr_rotate_z = libRigging.create_utility_node('addDoubleLinear',
94 | input1=ctrl.offset.rotateZ.get(),
95 | input2=ctrl_add.rotateZ
96 | ).output
97 | pymel.connectAttr(attr_rotate_x, ctrl.offset.rotateX)
98 | pymel.connectAttr(attr_rotate_y, ctrl.offset.rotateY)
99 | pymel.connectAttr(attr_rotate_z, ctrl.offset.rotateZ)
100 |
101 | # Constraint the fk ctrls in position to the additive fk ctrls
102 | pymel.pointConstraint(ctrl_add, self.ctrls[0].offset)
103 |
104 | def iter_ctrls(self):
105 | for ctrl in super(AdditiveFK, self).iter_ctrls():
106 | yield ctrl
107 | for ctrl in self.additive_ctrls:
108 | yield ctrl
109 |
110 |
111 | def register_plugin():
112 | return AdditiveFK
113 |
--------------------------------------------------------------------------------
/omtk/models/model_avar_base.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pymel
2 |
3 | from omtk.core import classModule
4 | from omtk.libs import libAttr
5 | from omtk.libs import libRigging
6 |
7 |
8 | def _connect_with_blend(attrs, attr_dst, attr_amount):
9 | """Quick function that create two attributes with a blend factor."""
10 | attr_blended = libRigging.create_utility_node(
11 | 'blendTwoAttr',
12 | attributesBlender=attr_amount,
13 | input=attrs,
14 | ).output
15 | pymel.connectAttr(attr_blended, attr_dst)
16 |
17 |
18 | class AvarInflBaseModel(classModule.Module):
19 | """
20 | A deformation point on the face that move accordingly to nurbsSurface.
21 | """
22 | SHOW_IN_UI = False
23 |
24 | _ATTR_NAME_MULT_LR = 'multiplierLr'
25 | _ATTR_NAME_MULT_UD = 'multiplierUd'
26 | _ATTR_NAME_MULT_FB = 'multiplierFb'
27 |
28 | default_multiplier_lr = 1.0
29 | default_multiplier_ud = 1.0
30 | default_multiplier_fb = 1.0
31 |
32 | support_no_inputs = True
33 |
34 | def __init__(self, *args, **kwargs):
35 | super(AvarInflBaseModel, self).__init__(*args, **kwargs)
36 |
37 | # An AvarModel will receive the avar values
38 | self._attr_inn_lr = None
39 | self._attr_inn_ud = None
40 | self._attr_inn_fb = None
41 | self._attr_inn_yw = None
42 | self._attr_inn_pt = None
43 | self._attr_inn_rl = None
44 | self._attr_inn_sx = None
45 | self._attr_inn_sy = None
46 | self._attr_inn_sz = None
47 |
48 | # The original transform of the influence
49 | self._attr_inn_offset_tm = None
50 |
51 | # The output transform of the system
52 | self._attr_out_tm = None
53 |
54 | # Reference to the object containing the bind pose of the avar.
55 | self._obj_offset = None
56 |
57 | # How much are we moving around the surface for a specific avar.
58 | self.multiplier_lr = self.default_multiplier_lr
59 | self.multiplier_ud = self.default_multiplier_ud
60 | self.multiplier_fb = self.default_multiplier_fb
61 |
62 | def _create_interface(self):
63 | # Create avar inputs
64 | self._attr_inn_lr = libAttr.addAttr(self.grp_rig, 'innAvarLr')
65 | self._attr_inn_ud = libAttr.addAttr(self.grp_rig, 'innAvarUd')
66 | self._attr_inn_fb = libAttr.addAttr(self.grp_rig, 'innAvarFb')
67 | self._attr_inn_yw = libAttr.addAttr(self.grp_rig, 'innAvarYw')
68 | self._attr_inn_pt = libAttr.addAttr(self.grp_rig, 'innAvarPt')
69 | self._attr_inn_rl = libAttr.addAttr(self.grp_rig, 'innAvarRl')
70 | self._attr_inn_sx = libAttr.addAttr(self.grp_rig, 'innAvarSx')
71 | self._attr_inn_sy = libAttr.addAttr(self.grp_rig, 'innAvarSy')
72 | self._attr_inn_sz = libAttr.addAttr(self.grp_rig, 'innAvarSz')
73 |
74 | self.multiplier_lr = libAttr.addAttr(self.grp_rig, 'innMultiplierLr', defaultValue=self.multiplier_lr)
75 | self.multiplier_ud = libAttr.addAttr(self.grp_rig, 'innMultiplierUd', defaultValue=self.multiplier_ud)
76 | self.multiplier_fb = libAttr.addAttr(self.grp_rig, 'innMultiplierFb', defaultValue=self.multiplier_fb)
77 |
78 | # Create influences
79 | _avar_filter_kwargs = {
80 | 'hasMinValue': True,
81 | 'hasMaxValue': True,
82 | 'minValue': 0.0,
83 | 'maxValue': 1.0,
84 | 'keyable': True
85 | }
86 |
87 | self._attr_inn_offset_tm = libAttr.addAttr(self.grp_rig, 'innOffset', dt='matrix')
88 | self._attr_out_tm = libAttr.addAttr(self.grp_rig, 'outTm', dataType='matrix')
89 |
90 | def build(self, **kwargs):
91 | """
92 | The dag stack is a chain of transform nodes daisy chained together that computer the final transformation of the influence.
93 | The decision of using transforms instead of multMatrix nodes is for clarity.
94 | Note also that because of it's parent (the offset node) the stack relative to the influence original translation.
95 | """
96 | super(AvarInflBaseModel, self).build(create_grp_anm=False, disconnect_inputs=False, **kwargs)
97 |
98 | self._create_interface()
99 | attr_tm = self._build()
100 | pymel.connectAttr(attr_tm, self._attr_out_tm)
101 |
102 | def unbuild(self):
103 | # Cleanup old deprecated properties to prevent invalid pynode warning.
104 | self.grp_offset = None
105 |
106 | # Save the current uv multipliers.
107 | # It is very rare that the rigger will tweak this advanced setting manually,
108 | # however for legacy reasons, it might be useful when upgrading an old rig.
109 | if isinstance(self.multiplier_lr, pymel.Attribute) and self.multiplier_lr.exists():
110 | self.multiplier_lr = self.multiplier_lr.get()
111 | if isinstance(self.multiplier_ud, pymel.Attribute) and self.multiplier_ud.exists():
112 | self.multiplier_ud = self.multiplier_ud.get()
113 | if isinstance(self.multiplier_fb, pymel.Attribute) and self.multiplier_fb.exists():
114 | self.multiplier_fb = self.multiplier_fb.get()
115 |
116 | super(AvarInflBaseModel, self).unbuild()
117 |
118 | def _build(self):
119 | """
120 | :return: A matrix pymel.Attribute containing the resulting transform.
121 | """
122 | raise NotImplementedError
123 |
124 | def connect_avar(self, avar):
125 | pymel.connectAttr(avar.attr_lr, self._attr_inn_lr)
126 | pymel.connectAttr(avar.attr_ud, self._attr_inn_ud)
127 | pymel.connectAttr(avar.attr_fb, self._attr_inn_fb)
128 | pymel.connectAttr(avar.attr_yw, self._attr_inn_yw)
129 | pymel.connectAttr(avar.attr_pt, self._attr_inn_pt)
130 | pymel.connectAttr(avar.attr_rl, self._attr_inn_rl)
131 | pymel.connectAttr(avar.attr_sx, self._attr_inn_sx)
132 | pymel.connectAttr(avar.attr_sy, self._attr_inn_sy)
133 | pymel.connectAttr(avar.attr_sz, self._attr_inn_sz)
134 | pymel.connectAttr(avar.grp_offset.matrix, self._attr_inn_offset_tm)
135 |
--------------------------------------------------------------------------------
/omtk/widget_list_influences.py:
--------------------------------------------------------------------------------
1 | import re
2 | import pymel.core as pymel
3 | from ui import widget_list_influences
4 |
5 | from omtk.libs import libQt
6 | from omtk.libs import libPython
7 | from omtk.libs import libPymel
8 |
9 | from omtk.vendor import libSerialization
10 | from omtk.vendor.Qt import QtCore, QtGui, QtWidgets
11 |
12 | import ui_shared
13 |
14 |
15 | class WidgetListInfluences(QtWidgets.QWidget):
16 | onRightClick = QtCore.Signal()
17 |
18 | def __init__(self, parent=None):
19 | super(WidgetListInfluences, self).__init__(parent=parent)
20 |
21 | self._rig = None
22 |
23 | self.ui = widget_list_influences.Ui_Form()
24 | self.ui.setupUi(self)
25 |
26 | # Tweak gui
27 | self.ui.treeWidget.setStyleSheet(ui_shared._STYLE_SHEET)
28 |
29 | # Connect signals
30 | self.ui.treeWidget.customContextMenuRequested.connect(self.onRightClick)
31 |
32 | # Connect events
33 | self.ui.treeWidget.itemSelectionChanged.connect(self.on_influence_selection_changed)
34 | self.ui.lineEdit_search.textChanged.connect(self.on_query_changed)
35 | self.ui.checkBox_hideAssigned.stateChanged.connect(self.on_query_changed)
36 | self.ui.btn_update.pressed.connect(self.update)
37 |
38 | def set_rig(self, rig, update=True):
39 | self._rig = rig
40 | if update:
41 | self.update()
42 |
43 | @libPython.log_execution_time('update_ui_jnts')
44 | def update(self, *args, **kwargs):
45 | self.ui.treeWidget.clear()
46 |
47 | if self._rig is None:
48 | return
49 |
50 | all_potential_influences = self._rig.get_potential_influences()
51 |
52 | if all_potential_influences:
53 | data = libPymel.get_tree_from_objs(all_potential_influences, sort=True)
54 |
55 | self._fill_widget_influences(self.ui.treeWidget.invisibleRootItem(), data)
56 | self.ui.treeWidget.sortItems(0, QtCore.Qt.AscendingOrder)
57 |
58 | self.update_list_visibility()
59 |
60 | def _fill_widget_influences(self, qt_parent, data):
61 | obj = pymel.PyNode(data.val) if data.val else None
62 | if obj:
63 | obj_name = obj.name()
64 |
65 | fnFilter = lambda x: libSerialization.is_network_from_class(x, 'Module')
66 | networks = libSerialization.get_connected_networks(obj, key=fnFilter, recursive=False)
67 |
68 | textBrush = QtGui.QBrush(QtCore.Qt.white)
69 |
70 | if self._is_influence(obj): # todo: listen to the Rig class
71 | qItem = QtWidgets.QTreeWidgetItem(0)
72 | qItem.obj = obj
73 |
74 | # Monkey-patch mesh QWidget
75 | qItem.metadata_type = ui_shared.MetadataType.Influence
76 | qItem.metadata_data = obj
77 |
78 | qItem.networks = networks
79 | qItem.setText(0, obj_name)
80 | qItem.setForeground(0, textBrush)
81 | ui_shared.set_icon_from_type(obj, qItem)
82 | qItem.setCheckState(0, QtCore.Qt.Checked if networks else QtCore.Qt.Unchecked)
83 | if qItem.flags() & QtCore.Qt.ItemIsUserCheckable:
84 | qItem.setFlags(qItem.flags() ^ QtCore.Qt.ItemIsUserCheckable)
85 | qt_parent.addChild(qItem)
86 | qt_parent = qItem
87 |
88 | for child_data in data.children:
89 | self._fill_widget_influences(qt_parent, child_data)
90 |
91 | def _is_influence(self, obj):
92 | """
93 | Supported influences are joints and nurbsSurface.
94 | :return:
95 | """
96 | return libPymel.isinstance_of_transform(obj, pymel.nodetypes.Joint) or \
97 | libPymel.isinstance_of_shape(obj, pymel.nodetypes.NurbsSurface)
98 |
99 | def update_list_visibility(self, query_regex=None):
100 | if query_regex is None:
101 | query_raw = self.ui.lineEdit_search.text()
102 | query_regex = ".*{0}.*".format(query_raw) if query_raw else ".*"
103 |
104 | unselectableBrush = QtGui.QBrush(QtCore.Qt.darkGray)
105 | selectableBrush = QtGui.QBrush(QtCore.Qt.white)
106 | for qt_item in libQt.get_all_QTreeWidgetItem(self.ui.treeWidget):
107 | can_show = self._can_show_QTreeWidgetItem(qt_item, query_regex)
108 | qt_item.setHidden(not can_show)
109 | if can_show:
110 | qt_item.setForeground(0, selectableBrush)
111 | flags = qt_item.flags()
112 | if not flags & QtCore.Qt.ItemIsSelectable: # Make selectable
113 | flags ^= QtCore.Qt.ItemIsSelectable
114 | qt_item.setFlags(flags)
115 | self._show_parent_recursive(qt_item.parent())
116 | else:
117 | qt_item.setForeground(0, unselectableBrush)
118 | flags = qt_item.flags()
119 | if flags & QtCore.Qt.ItemIsSelectable: # Make selectable
120 | flags ^= QtCore.Qt.ItemIsSelectable
121 | qt_item.setFlags(flags)
122 |
123 | self.ui.treeWidget.expandAll()
124 |
125 | def _can_show_QTreeWidgetItem(self, qItem, query_regex):
126 | obj = qItem.obj # Retrieve monkey-patched data
127 | obj_name = obj.name()
128 | # print obj_name
129 |
130 | if not re.match(query_regex, obj_name, re.IGNORECASE):
131 | return False
132 |
133 | if self.ui.checkBox_hideAssigned.isChecked():
134 | if qItem.networks:
135 | return False
136 |
137 | return True
138 |
139 | def _show_parent_recursive(self, qt_parent_item):
140 | if qt_parent_item is not None:
141 | if qt_parent_item.isHidden:
142 | qt_parent_item.setHidden(False)
143 | self._show_parent_recursive(qt_parent_item.parent())
144 |
145 | def get_selection(self):
146 | result = []
147 | for item in self.ui.treeWidget.selectedItems():
148 | if item.obj.exists():
149 | result.append(item.obj)
150 | return result
151 |
152 | #
153 | # Events
154 | #
155 |
156 | def on_influence_selection_changed(self):
157 | pymel.select(self.get_selection())
158 |
159 | def on_query_changed(self):
160 | self.update_list_visibility()
161 |
--------------------------------------------------------------------------------
/omtk/widget_create_component_wizard_parts.py:
--------------------------------------------------------------------------------
1 | from omtk.vendor.Qt import QtCore, QtWidgets
2 | from ui import widget_component_wizard_parts
3 |
4 | import pymel.core as pymel
5 |
6 |
7 | class ComponentPartModel(QtCore.QAbstractTableModel):
8 | ID_COLUMN_NAME = 0
9 | ID_COLUMN_ATTR_NAME = 1
10 |
11 | onNetworkChanged = QtCore.Signal()
12 |
13 | def __init__(self, entries):
14 | super(ComponentPartModel, self).__init__()
15 | self._entries = entries
16 |
17 | def rowCount(self, index):
18 | return len(self._entries)
19 |
20 | def columnCount(self, index):
21 | return 2
22 |
23 | def data(self, index, role):
24 | col = index.column()
25 | row = index.row()
26 | if col == self.ID_COLUMN_NAME:
27 | if role == QtCore.Qt.DisplayRole:
28 | return str(self._entries[row])
29 | if role == QtCore.Qt.CheckStateRole:
30 | entry = self._entries[row]
31 | return entry.is_connected()
32 | elif col == self.ID_COLUMN_ATTR_NAME:
33 | if role == QtCore.Qt.DisplayRole or role == QtCore.Qt.EditRole:
34 | entry = self._entries[row]
35 | return entry.attr_name
36 |
37 | def headerData(self, section, orientation, role):
38 | if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
39 | if section == self.ID_COLUMN_NAME:
40 | return 'Node'
41 | elif section == self.ID_COLUMN_ATTR_NAME:
42 | return 'Attr Name'
43 |
44 | def setData(self, index, value, role):
45 | if not value:
46 | return False
47 |
48 | if role == QtCore.Qt.CheckStateRole:
49 | index = index.row()
50 | is_checked = bool(value)
51 | entry = self._entries[index]
52 | if is_checked:
53 | entry.connect()
54 | else:
55 | entry.disconnect()
56 | return True
57 |
58 | if role == QtCore.Qt.EditRole:
59 | index = index.row()
60 | entry = self._entries[index]
61 | entry.rename_attr(value)
62 | self.onNetworkChanged.emit()
63 |
64 | return False
65 |
66 | def flags(self, index):
67 | flags = super(ComponentPartModel, self).flags(index)
68 | col = index.column()
69 | if col == self.ID_COLUMN_NAME:
70 | flags |= QtCore.Qt.ItemIsUserCheckable
71 | if col == self.ID_COLUMN_ATTR_NAME:
72 | flags |= QtCore.Qt.ItemIsEditable
73 | return flags
74 |
75 | def reset(self):
76 | self.beginResetModel()
77 | self.endResetModel()
78 |
79 | def add_entries(self, entries, update=False):
80 | need_update = False
81 | for entry in entries:
82 | # assert (isinstance(entry, ComponentPart))
83 | if not entry in self._entries:
84 | self._entries.append(entry)
85 | entry.initialize()
86 | entry.connect()
87 | need_update = True
88 |
89 | if need_update and update:
90 | self.reset()
91 |
92 | def have_entry_for_node(self, obj):
93 | for entry in self._entries:
94 | for entry_obj in entry.iter_nodes():
95 | if obj == entry_obj:
96 | return True
97 | return False
98 |
99 |
100 | class WidgetCreateComponentWizardParts(QtWidgets.QWidget):
101 | _cls = None
102 |
103 | # Triggered when the scene changed
104 | onNetworkChanged = QtCore.Signal()
105 |
106 | def __init__(self, parent):
107 | self._wizard = None
108 | self._cls = None
109 |
110 | super(WidgetCreateComponentWizardParts, self).__init__(parent)
111 |
112 | self.ui = widget_component_wizard_parts.Ui_Form()
113 | self.ui.setupUi(self)
114 |
115 | self.model = ComponentPartModel([])
116 | self.model.onNetworkChanged.connect(self.onNetworkChanged.emit)
117 |
118 | self.ui.tableView.setModel(self.model)
119 |
120 | self.ui.pushButton_add.pressed.connect(self.on_added)
121 | self.ui.pushButton_remove.pressed.connect(self.on_remove)
122 | self.ui.pushButton_connect.pressed.connect(self.on_connect)
123 | self.ui.pushButton_disconnect.pressed.connect(self.on_disconnect)
124 |
125 | def get_selected_entries(self):
126 | indexes = set(qindex.row() for qindex in self.ui.tableView.selectedIndexes())
127 | entries = [self.model._entries[index] for index in indexes]
128 | return entries
129 |
130 | def can_add(self, obj):
131 | # Prevent adding the same object twice.
132 | return not self.model.have_entry_for_node(obj)
133 |
134 | def on_added(self):
135 | assert (self._cls is not None) # ensure the class have been set
136 | new_entries = []
137 | for obj in pymel.selected():
138 | if self.can_add(obj):
139 | entry = self._cls(self._wizard, obj)
140 | new_entries.append(entry)
141 |
142 | if new_entries:
143 | self.model.add_entries(new_entries, update=True)
144 | self.onNetworkChanged.emit()
145 |
146 | def on_remove(self):
147 | need_update = False
148 | entries_to_remove = self.get_selected_entries()
149 | for entry in entries_to_remove:
150 | if entry.is_connected():
151 | entry.disconnect()
152 | entry.delete() # todo: implement
153 | self.model._entries.remove(entry)
154 | need_update = True
155 | if need_update:
156 | self.model.reset()
157 |
158 | def on_connect(self):
159 | need_update = False
160 | entries = self.get_selected_entries()
161 | for entry in entries:
162 | if not entry.is_connected():
163 | entry.connect()
164 | need_update = True
165 | if need_update:
166 | self.model.reset()
167 |
168 | def on_disconnect(self):
169 | need_update = False
170 | entries = self.get_selected_entries()
171 | for entry in entries:
172 | if entry.is_connected():
173 | entry.disconnect()
174 | need_update = True
175 | if need_update:
176 | self.model.reset()
177 |
178 | def set_entries(self, wizard, cls, entries):
179 | self._wizard = wizard
180 | self._cls = cls
181 | self.model._entries = entries
182 | self.model.reset()
183 |
--------------------------------------------------------------------------------
/omtk/core/classModuleCompound.py:
--------------------------------------------------------------------------------
1 | import os
2 | import inspect
3 |
4 | from omtk.core.classModule import Module
5 | from omtk.libs import libRigging
6 |
7 | import pymel.core as pymel
8 | from maya import cmds
9 |
10 |
11 | def _connect_matrix_attr_to_transform(attr_tm, node):
12 | util_decompose = libRigging.create_utility_node(
13 | 'decomposeMatrix',
14 | inputMatrix=attr_tm
15 | )
16 | pymel.connectAttr(util_decompose.outputTranslate, node.translate)
17 | pymel.connectAttr(util_decompose.outputRotate, node.rotate)
18 | pymel.connectAttr(util_decompose.outputScale, node.scale)
19 |
20 |
21 | class CompoundModule(Module):
22 | def __init__(self, *args, **kwargs):
23 | super(CompoundModule, self).__init__(*args, **kwargs)
24 | self.grp_inn = None
25 | self.grp_out = None
26 | self.grp_dag = None
27 | self.grp_guides = None
28 | self.grp_influences = None
29 |
30 | def __get_compounnd_path__(self):
31 | """Return the path to the .ma containing the compound."""
32 | cls_path = inspect.getfile(self.__class__)
33 | dirname = os.path.dirname(cls_path)
34 | filename = os.path.basename(cls_path)
35 | basename = os.path.splitext(filename)[0]
36 | path = os.path.join(dirname, basename + '.ma')
37 | if not os.path.exists(path):
38 | raise Exception("Cannot find {0}".format(path))
39 | return path
40 |
41 | def _import_component(self, path, namespace):
42 | """
43 | Simple implement of the component workflow in omtk.
44 | This will import a .ma file and resolve the public interface.
45 | This take a lot of things in consideration like how objects are named, and more.
46 | """
47 | cmds.file(path, i=True, namespace=namespace)
48 |
49 | def _get_compound_namespace(self):
50 | """
51 | Resolve the namespace associated with the Compound.
52 | Keeping the namespace is highly efficient in keep the scene clean.
53 | """
54 | return self.grp_inn.namespace().strip(':')
55 |
56 | def create_compound_ctrl(self, cls, inst, suffix, attr_inn_name, attr_out_name, **kwargs):
57 | """
58 | Initialize and build and connect a controller used in the Compound.
59 | :param cls: The desired class for the controller.
60 | :param inst: The current instance.
61 | :param suffix: A str that identify this controller.
62 | :param attr_inn_name: The name of the network attribute that receive the controller local matrix.
63 | :param attr_out_name: The name of the network attribute that will drive the controller offset world matrix.
64 | :param kwargs: Any keyword argument will be passed to the controller .build() method.
65 | :return: A controller instance of the desired type.
66 | """
67 | attr_inn = self.grp_inn.attr(attr_inn_name)
68 | attr_out = self.grp_out.attr(attr_out_name)
69 | nomenclature_anm = self.get_nomenclature_anm()
70 |
71 | inst = self.init_ctrl(cls, inst)
72 | ref_tm = attr_out.get()
73 |
74 | inst.build(
75 | name=nomenclature_anm.resolve(suffix),
76 | geometries=self.rig.get_meshes(),
77 | refs=[ref_tm],
78 | **kwargs
79 | )
80 | inst.setParent(self.grp_anm)
81 |
82 | pymel.connectAttr(inst.matrix, attr_inn)
83 | _connect_matrix_attr_to_transform(attr_out, inst.offset)
84 | return inst
85 |
86 | def create_compound_influence(self, suffix, attr_name, target=None, **kwargs):
87 | """
88 | Initialize and build and connect an influence used in the Compound.
89 | :param suffix: A str that identify this influence.
90 | :param attr_name: The name of the network attribute that will drive the influence world matrix.
91 | :param target: An optional pymel.nodetypes.Transform that will be constrained to the influence.
92 | :param kwargs: Any keyword argument will be passed to pymel.joint().
93 | :return: A pymel.nodetypes.Joint.
94 | """
95 | inst = pymel.createNode('joint', name=self.get_nomenclature_jnt().resolve(suffix))
96 | inst.setParent(self.grp_influences)
97 | attr = self.grp_out.attr(attr_name)
98 | _connect_matrix_attr_to_transform(attr, inst)
99 |
100 | if target:
101 | pymel.parentConstraint(inst, target, maintainOffset=True)
102 | pymel.scaleConstraint(inst, target, maintainOffset=True)
103 |
104 | return inst
105 |
106 | def build(self, create_grp_inf=True, **kwargs):
107 | nomenclature_rig = self.get_nomenclature_rig()
108 |
109 | # Hack: Need to create self.grp_influences first since parent_to is called by .build() ...
110 | if create_grp_inf:
111 | self.grp_influences = pymel.createNode('transform', name=nomenclature_rig.resolve('influences'))
112 |
113 | super(CompoundModule, self).build(**kwargs)
114 |
115 | path = self.__get_compounnd_path__()
116 | namespace = '_{0}'.format(self.name) # we prefix with an underscore to ensure that the namespace is recognized
117 | self._import_component(path, namespace)
118 |
119 | # Resolve grp_inn (mandatory)
120 | grp_inn_dagpath = '{0}:inn'.format(namespace)
121 | if not cmds.objExists(grp_inn_dagpath):
122 | raise Exception("Cannot find {0}".format(grp_inn_dagpath))
123 | self.grp_inn = pymel.PyNode(grp_inn_dagpath)
124 | self.grp_inn.setParent(self.grp_rig)
125 |
126 | # Resolve grp_out (mandatory)
127 | grp_out_dagpath = '{0}:out'.format(namespace)
128 | if not cmds.objExists(grp_out_dagpath):
129 | raise Exception("Cannot find {0}".format(grp_out_dagpath))
130 | self.grp_out = pymel.PyNode(grp_out_dagpath)
131 | self.grp_out.setParent(self.grp_rig)
132 |
133 | # Resolve grp_dag (optional)
134 | grp_dag_dagpath = '{0}:dag'.format(namespace)
135 | if cmds.objExists(grp_dag_dagpath):
136 | self.grp_dag = pymel.PyNode(grp_dag_dagpath)
137 | self.grp_dag.setParent(self.grp_rig)
138 | self.grp_dag.visibility.set(False)
139 | else:
140 | self.grp_dag = None
141 |
142 | # Resolve grp_guides (optional)
143 | grp_guides_dagpath = '{0}:guides'.format(namespace)
144 | if cmds.objExists(grp_guides_dagpath):
145 | self.grp_guides = pymel.PyNode(grp_guides_dagpath)
146 | self.grp_guides.setParent(self.grp_rig)
147 | self.grp_guides.visibility.set(False)
148 | else:
149 | self.grp_guides = None
150 |
151 | if self.grp_influences:
152 | self.grp_influences.setParent(self.grp_rig)
153 |
154 | def parent_to(self, parent):
155 | super(CompoundModule, self).parent_to(parent)
156 | if self.grp_influences:
157 | pymel.parentConstraint(parent, self.grp_influences)
158 | pymel.scaleConstraint(parent, self.grp_influences)
159 |
--------------------------------------------------------------------------------
/omtk/libs/libCtrlMatch.py:
--------------------------------------------------------------------------------
1 | """
2 | Library that allow facial ctrls to be matched/mirrored/baked.
3 | Original code by Jimmy Goulet (https://github.com/goujin), thanks for the contribution!
4 | todo: Use className to find sibling.
5 | """
6 | import pymel.core as pymel
7 | from omtk.libs import libRigging
8 |
9 |
10 | def get_orig_shape(shape):
11 | return next((hist for hist in shape.listHistory()
12 | if isinstance(hist, pymel.nodetypes.NurbsCurve)
13 | and hist != shape
14 | and hist.intermediateObject.get()), None)
15 |
16 |
17 | def safety_check(check_object=None, check_curve_list=None):
18 | if check_object:
19 | if not isinstance(check_object, pymel.nodetypes.Transform):
20 | return False
21 | elif check_curve_list:
22 | for each in check_curve_list:
23 | if not isinstance(each, pymel.nodetypes.NurbsCurve):
24 | return False
25 |
26 |
27 | def get_previous_controller_info(previous_controler):
28 | # this implementation assumes your only using one shape or that the the first shape of children shapes is representative of the lot
29 | assumed_only_shape = previous_controler.getShape()
30 |
31 | if assumed_only_shape.overrideEnabled.get(): # will return False if it isn't activated
32 | if assumed_only_shape.overrideRGBColors.get():
33 | rgb_color = assumed_only_shape.overrideColorRGB.get()
34 | color_info = [True, True, rgb_color]
35 | else:
36 | index_color = assumed_only_shape.overrideColor.get()
37 | color_info = [True, False, index_color]
38 | else:
39 | color_info = [False, False, []]
40 |
41 | if assumed_only_shape.visibility.isConnected():
42 | visibility_connection_info = assumed_only_shape.visibility.connections(plugs=True)[0]
43 |
44 | else:
45 | visibility_connection_info = False
46 |
47 | return (color_info, visibility_connection_info)
48 |
49 |
50 | def adapt_to_orig_shape(source, target):
51 | """
52 | :param source: source shape to transfer
53 | :param target: target to transfer to
54 | This is based out of Renaud's code on shape to orig when building and unbuilding with omtk to preserve shape info.
55 | """
56 |
57 | def get_transformGeometry(shape):
58 | return next((hist for hist in shape.listHistory()
59 | if isinstance(hist, pymel.nodetypes.TransformGeometry)), None)
60 |
61 | # Resolve orig shape
62 | shape_orig = get_orig_shape(target)
63 |
64 | # Resolve compensation matrix
65 | util_transform_geometry = get_transformGeometry(target)
66 | if not util_transform_geometry:
67 | target.warning("Skipping {}. Cannot find transformGeometry.".format(target))
68 | return
69 | attr_compensation_tm = next(iter(util_transform_geometry.transform.inputs(plugs=True)), None)
70 |
71 | if not attr_compensation_tm:
72 | target.warning("Skipping {}. Cannot find compensation matrix.".format(target))
73 | return
74 |
75 | tmp_transform_geometry = libRigging.create_utility_node(
76 | 'transformGeometry',
77 | inputGeometry=source.local,
78 | transform=attr_compensation_tm,
79 | invertTransform=True
80 | )
81 |
82 | # source.getParent().setParent(grp_offset) JG modification source should already be in place
83 | pymel.connectAttr(tmp_transform_geometry.outputGeometry, shape_orig.create)
84 |
85 | # Cleanup
86 | pymel.refresh(force=True) # but why do I have to refresh^!
87 | pymel.disconnectAttr(shape_orig.create)
88 | pymel.delete(tmp_transform_geometry)
89 |
90 |
91 | def controller_matcher(selection=None, mirror_prefix=None, flip=True):
92 | """it will try to find it's match on the other side of the rig
93 | Select controls curves (ex. 'leg_front_l_ik_ctrl'), and set the mirror prefix ('_l_', '_r_')
94 | flip is for flipping on the X axis the shapes of the ctrl"""
95 | if selection is None:
96 | selection = pymel.selected()
97 |
98 | if not mirror_prefix:
99 | if len(selection) != 2:
100 | msg = """The only supported behavior when no mirror_prefix is given, is to have only two controlers selected.
101 | It will match the first controller to the second one."""
102 | pymel.warning(msg)
103 | return "Error"
104 | transfer_shape(*selection, flip=flip)
105 |
106 | else:
107 |
108 | for selected_object in selection:
109 | _possible_sides = list(mirror_prefix)
110 | skip_mechanism = False # This is in place to protect from possible controller having no mirror prefix at all
111 |
112 | if mirror_prefix[0] in selected_object.name():
113 | current_side = _possible_sides.pop(0)
114 |
115 | elif mirror_prefix[1] in selected_object.name():
116 | current_side = _possible_sides.pop(1)
117 |
118 | else:
119 | skip_mechanism = True
120 |
121 | if skip_mechanism:
122 | pass
123 | else:
124 | target_name = selected_object.name().replace(current_side, _possible_sides[0])
125 | if pymel.objExists(target_name):
126 | target = pymel.PyNode(selected_object.name().replace(current_side, _possible_sides[0]))
127 | transfer_shape(selected_object, target, flip=True)
128 |
129 |
130 | def transfer_shape(source, target, flip=True):
131 | """it will replace the shape of selected number 2 with the shapes of selected number 1"""
132 |
133 | target_shape = target.getShape(noIntermediate=True)
134 | target_shape_orig = get_orig_shape(target_shape)
135 |
136 | dup = pymel.duplicate(source, rc=1)[0]
137 | tmp = pymel.createNode('transform')
138 | pymel.parent(tmp, dup)
139 | pymel.xform(tmp, t=(0, 0, 0), ro=(0, 0, 0), scale=(1, 1, 1))
140 | pymel.parent(tmp, w=1)
141 | for sh in dup.getShapes(noIntermediate=True):
142 | pymel.parent(sh, tmp, r=1, s=1)
143 |
144 | pymel.delete(dup)
145 | temp_grp_negScale = pymel.createNode('transform')
146 | pymel.parent(tmp, temp_grp_negScale)
147 | if flip:
148 | temp_grp_negScale.scaleX.set(-1)
149 |
150 | pymel.parent(tmp, target)
151 | pymel.delete(temp_grp_negScale)
152 |
153 | pymel.makeIdentity(tmp, t=True) # this brings translate values at 0 before scale freezing
154 | pymel.makeIdentity(tmp, apply=True, t=True, r=True, s=True)
155 | pymel.parent(tmp, w=1)
156 |
157 | color_info, vis_master = get_previous_controller_info(target)
158 |
159 | shapes_has_been_deleted = False
160 | for sh in tmp.getShapes():
161 | if target_shape_orig:
162 | adapt_to_orig_shape(sh, target.getShape())
163 | else:
164 | if not shapes_has_been_deleted:
165 | shapesDel = target.getShapes()
166 | if shapesDel: pymel.delete(shapesDel)
167 | shapes_has_been_deleted = True
168 |
169 | pymel.parent(sh, target, r=1, s=1)
170 | pymel.rename(sh.name(), target.name() + "Shape")
171 |
172 | if color_info[0]:
173 | if color_info[1]:
174 | sh.overrideEnabled.set(True)
175 | sh.overrideRGBColors.set(1)
176 | sh.overrideColorRGB.set(color_info[2])
177 |
178 | else:
179 | sh.overrideEnabled.set(True)
180 | sh.overrideRGBColors.set(0)
181 | sh.overrideColor.set(color_info[2])
182 |
183 | else:
184 | sh.overrideEnabled.set(False)
185 |
186 | if vis_master:
187 | vis_master.connect(sh.visibility)
188 |
189 | pymel.delete(tmp)
190 |
--------------------------------------------------------------------------------