├── .gitignore ├── CHANGELOG.jp_JP.md ├── CHANGELOG.md ├── CHANGELOG.zh_CN.md ├── Contents └── scripts │ ├── humtools │ ├── __init__.py │ ├── blendshapedmesheditor │ │ ├── __init__.py │ │ ├── const.py │ │ ├── cv_freezer.py │ │ ├── non_deformer_history_deleter.py │ │ ├── rebuilt_target_mesh_deleter.py │ │ ├── selection.py │ │ ├── setup.py │ │ ├── startup_command.py │ │ ├── tweak_helper.py │ │ ├── uv_shifter.py │ │ └── window.py │ ├── blendshapedvertexmerger │ │ ├── __init__.py │ │ ├── blendshape_reconfigurator.py │ │ ├── blendshape_target_rebuilder.py │ │ ├── const.py │ │ ├── main.py │ │ ├── non_deformer_history_deleter.py │ │ ├── selected_getter.py │ │ ├── setup.py │ │ ├── startup_command.py │ │ ├── vtx_merger.py │ │ └── window.py │ ├── hum_tools_const.py │ ├── lib │ │ ├── __init__.py │ │ ├── blendshape.py │ │ └── tool_mode.py │ ├── menu_adder.py │ ├── module_reloader.py │ ├── skinweightsbugsearcher │ │ ├── __init__.py │ │ ├── bug_searcher.py │ │ ├── const.py │ │ ├── lang_op_var.py │ │ ├── om2_util.py │ │ ├── selection.py │ │ ├── setup.py │ │ ├── startup_command.py │ │ ├── weights_bug_text_scroll_list.py │ │ ├── window.py │ │ └── working_mesh.py │ ├── skinweightsio │ │ ├── __init__.py │ │ ├── auto_skin_binder.py │ │ ├── const.py │ │ ├── deformer_weights_exporter.py │ │ ├── deformer_weights_importer.py │ │ ├── lang_op_var.py │ │ ├── main.py │ │ ├── mesh_parent_transform_and_skincluster_dict_getter.py │ │ ├── option_settings.py │ │ ├── selection.py │ │ ├── setup.py │ │ ├── startup_command.py │ │ ├── window.py │ │ └── xml_text_scroll_list.py │ ├── startup.py │ └── util │ │ ├── __init__.py │ │ ├── dict_util.py │ │ ├── extensions.py │ │ ├── hum_window_base.py │ │ ├── in_view_message.py │ │ ├── lang.py │ │ ├── list_util.py │ │ ├── log.py │ │ ├── node_type.py │ │ ├── path.py │ │ ├── progress_window.py │ │ ├── readonly.py │ │ ├── selection_recorder.py │ │ ├── str_util.py │ │ ├── time_recorder.py │ │ └── unexpected_error.py │ └── userSetup.py ├── LICENSE ├── PackageContents.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | .idea/ 161 | -------------------------------------------------------------------------------- /CHANGELOG.jp_JP.md: -------------------------------------------------------------------------------- 1 | # 変更ログ 2 | 3 | このプロジェクトの目立った変更はすべてこのファイルに記録されます。 4 | 5 | フォーマットは[Keep a Changelog](http://keepachangelog.com/)に準拠しており、 6 | このプロジェクトは[Semantic Versioning](http://semver.org/)に基づいています。 7 | 8 | ## [1.1.0] - 2025-02-06 9 | ### SkinWeightsIO 10 | #### 追加 11 | - [#5](https://github.com/Hum9183/MayaHumTools/pull/5) 処理中にprogressWindow(進捗バー)を出すようにしました。 12 | 13 | #### 変更 14 | - [#4](https://github.com/Hum9183/MayaHumTools/pull/4) 言語のラベルをそれぞれの言語で表示するように変更しました。 15 | - [#11](https://github.com/Hum9183/MayaHumTools/pull/11) 処理を中断した際のinViewMessageの文言を変更しました。 16 | 17 | ### 全体 18 | #### 追加 19 | - CHANGELOGを追加しました。 20 | 21 | ## [1.0.0] - 2024-12-02 22 | - ツールをリリース 23 | 24 | [未リリース]: https://github.com/Hum9183/MayaHumTools/compare/1.0.0...master 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.1.0] - 2025-02-06 9 | ### SkinWeightsIO 10 | #### Added 11 | - [#5](https://github.com/Hum9183/MayaHumTools/pull/5) A progressWindow (progress bar) is now displayed during processing. 12 | 13 | #### Changed 14 | - [#4](https://github.com/Hum9183/MayaHumTools/pull/4) Change language labels to show each language in its own language. 15 | - [#11](https://github.com/Hum9183/MayaHumTools/pull/11) Change wording of inViewMessage when processing is interrupted. 16 | 17 | ### Whole 18 | #### Added 19 | - Add CHANGELOGs. 20 | 21 | ## [1.0.0] - 2024-12-02 22 | - release the tools. 23 | 24 | [Unreleased]: https://github.com/Hum9183/MayaHumTools/compare/1.0.0...master 25 | -------------------------------------------------------------------------------- /CHANGELOG.zh_CN.md: -------------------------------------------------------------------------------- 1 | # 更改日志 2 | 3 | 此包的所有重要更改都将记录在这个文件中。 4 | 5 | 格式基于[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) 并且此项目遵循[Semantic Versioning](https://semver.org/spec/v2.0.0.html)。 6 | 7 | ## [1.1.0] - 2025-02-06 8 | ### SkinWeightsIO 9 | #### 已添加 10 | - [#5](https://github.com/Hum9183/MayaHumTools/pull/5) 现在处理过程中会显示进度窗口(进度条)。 11 | 12 | #### 已更改 13 | - [#4](https://github.com/Hum9183/MayaHumTools/pull/4) 语言标签现在以相应的语言显示。 14 | - [#11](https://github.com/Hum9183/MayaHumTools/pull/11) 更改了进程中断时的 inViewMessage 措辞。 15 | 16 | ### 全部 17 | #### 已添加 18 | - 已添加更改日志。 19 | 20 | ## [1.0.0] - 2024-12-02 21 | - 发布工具 22 | 23 | [未发布]: https://github.com/Hum9183/MayaHumTools/compare/1.0.0...master 24 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hum9183/MayaHumTools/0bca53971a389aa8b5cefb34931736aaf4fc462f/Contents/scripts/humtools/__init__.py -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hum9183/MayaHumTools/0bca53971a389aa8b5cefb34931736aaf4fc462f/Contents/scripts/humtools/blendshapedmesheditor/__init__.py -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from ..util.readonly import metaclass 3 | 4 | 5 | class Const(metaclass('Const')): 6 | TOOL_NAME = 'HumBlendShapedMeshEditor' 7 | RUN_BUTTON = 'RunButton' 8 | 9 | START_TEXT = 'Start editing' 10 | START_ANNOTATION = u'ベースメッシュを選択し、ボタンを押します。\nターゲットメッシュが削除され、メッシュ編集を開始します。' 11 | START_COLOR = (0.4, 0.4, 0.4) 12 | 13 | FINISH_TEXT = 'Finish editing' 14 | FINISH_ANNOTATION = u'ベースメッシュを選択し、ボタンを押します。\nCVがフリーズされ、メッシュ編集を終了します。' 15 | FINISH_COLOR = (0.95, 0.95, 0.0) 16 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/cv_freezer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import non_deformer_history_deleter 4 | from . import selection 5 | from . import tweak_helper 6 | from . import uv_shifter 7 | from ..util.log import Log 8 | from ..util.selection_recorder import SelectionRecorder 9 | 10 | 11 | @SelectionRecorder.record 12 | def freeze(): 13 | mesh = selection.get_mesh() 14 | uv_shifter.move_and_restore(mesh) 15 | non_deformer_history_deleter.delete(mesh) 16 | tweak_helper.delete(mesh) 17 | Log.log(u'CVをフリーズしました。') 18 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/non_deformer_history_deleter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | 4 | 5 | def delete(meshes): 6 | if type(meshes) is list: 7 | [__delete(m) for m in meshes] 8 | else: 9 | __delete(meshes) 10 | 11 | 12 | def __delete(mesh): 13 | cmds.select(mesh) 14 | mel.eval('BakeNonDefHistory') 15 | cmds.select(cl=True) 16 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/rebuilt_target_mesh_deleter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import selection 4 | from . import tweak_helper 5 | from ..lib import blendshape 6 | from ..util.log import Log 7 | from ..util.selection_recorder import SelectionRecorder 8 | 9 | 10 | @SelectionRecorder.record 11 | def delete(): 12 | mesh = selection.get_mesh() 13 | bs_nodes = blendshape.get_nodes(mesh, raise_ex_if_is_none=True) 14 | blendshape.delete_rebuilt_target_meshes(bs_nodes) 15 | tweak_helper.add(mesh) 16 | Log.log(u'ターゲットメッシュを削除しました') 17 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/selection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from ..util.node_type import NodeType 5 | from ..util.unexpected_error import UnexpectedError 6 | 7 | 8 | def get_mesh(): 9 | sels = cmds.ls(sl=True) 10 | __exists_validate(sels) 11 | __onlyone_validate(sels) 12 | shape = __to_shape_validate(sels[0]) 13 | __is_mesh_validate(shape) 14 | return shape 15 | 16 | 17 | def __onlyone_validate(sels): 18 | if len(sels) >= 2: 19 | raise UnexpectedError(u'メッシュを1つのみ選択してください。') 20 | 21 | 22 | def __to_shape_validate(sel): 23 | shape = cmds.listRelatives(sel, s=True)[0] 24 | if shape is None: 25 | raise UnexpectedError(u'メッシュを選択してください。') 26 | return shape 27 | 28 | 29 | def __exists_validate(sels): 30 | if sels == []: 31 | raise UnexpectedError(u'メッシュを選択してください。') 32 | 33 | 34 | def __is_mesh_validate(shape): 35 | # NOTE: カメラ等ではないかの確認 36 | if cmds.nodeType(shape) != NodeType.MESH: 37 | raise UnexpectedError(u'メッシュを選択してください。') 38 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import inspect 3 | 4 | from . import startup_command 5 | from .const import Const 6 | from .. import menu_adder 7 | 8 | 9 | def add_menu(): 10 | # NOTE: シェルフに登録できるようにするために、起動コマンドはstringで渡す 11 | startup_command_str = inspect.getsource(startup_command) 12 | menu_adder.add(Const.TOOL_NAME, startup_command_str) 13 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/startup_command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def bsme_startup_command(): 4 | bsme_reload_modules() 5 | bsme_show_window() 6 | 7 | 8 | def bsme_reload_modules(): 9 | from humtools.blendshapedmesheditor import const, window, rebuilt_target_mesh_deleter, cv_freezer, uv_shifter, \ 10 | selection, \ 11 | non_deformer_history_deleter, tweak_helper 12 | 13 | from humtools import module_reloader 14 | modules = [const, window, rebuilt_target_mesh_deleter, cv_freezer, uv_shifter, selection, \ 15 | non_deformer_history_deleter, tweak_helper] 16 | module_reloader.reload_a_few_times(modules) 17 | 18 | 19 | def bsme_show_window(): 20 | from humtools.blendshapedmesheditor.window import Window 21 | from humtools.blendshapedmesheditor.const import Const 22 | wnd = Window(Const.TOOL_NAME) 23 | wnd.show() 24 | 25 | 26 | if __name__ == '__main__': 27 | bsme_startup_command() 28 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/tweak_helper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | 4 | from ..hum_tools_const import HumToolsConst 5 | from ..util.node_type import NodeType 6 | 7 | 8 | def add(mesh): 9 | if HumToolsConst.is_the_maya_version_before_2020(): 10 | return 11 | cmds.select(mesh, r=True) 12 | mel.eval('AddTweak') 13 | cmds.select(cl=True) 14 | 15 | 16 | def delete(mesh): 17 | if HumToolsConst.is_the_maya_version_before_2020(): 18 | return 19 | tweak = NodeType.get_histories(mesh, NodeType.TWEAK, raise_ex_if_is_none=True) 20 | cmds.delete(tweak) 21 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/uv_shifter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | 5 | def move_and_restore(mesh): 6 | cmds.select('{}.map[0]'.format(mesh), r=True) 7 | cmds.polyEditUV(u=1) 8 | cmds.polyEditUV(u=-1) 9 | cmds.select(cl=True) 10 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedmesheditor/window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | from maya.common.ui import LayoutManager 4 | 5 | from . import cv_freezer 6 | from . import rebuilt_target_mesh_deleter 7 | from .const import Const 8 | from ..util import in_view_message 9 | from ..util.hum_window_base import HumWindowBase 10 | from ..util.unexpected_error import UnexpectedError 11 | 12 | 13 | class Window(HumWindowBase): 14 | def __init__(self, tool_name): 15 | super(Window, self).__init__(tool_name) 16 | self.__editable = False 17 | 18 | def _create_window(self): 19 | cmds.window(self.tool_name, title=self.tool_name, mxb=False, mnb=False) 20 | with LayoutManager(cmds.columnLayout(adj=True)): 21 | cmds.text(l='') 22 | cmds.button(Const.RUN_BUTTON, 23 | l=Const.START_TEXT, 24 | ann=Const.START_TEXT, 25 | bgc=Const.START_COLOR, 26 | c=lambda arg: self.__main()) 27 | cmds.text(l='') 28 | 29 | def __toggle_editable(self, editable, label, annotation, color): 30 | self.__editable = editable 31 | cmds.button(Const.RUN_BUTTON, e=True, l=label, ann=annotation, bgc=color) 32 | 33 | @UnexpectedError.catch 34 | def __start(self): 35 | rebuilt_target_mesh_deleter.delete() 36 | self.__toggle_editable(True, Const.FINISH_TEXT, Const.FINISH_ANNOTATION, Const.FINISH_COLOR) 37 | msg = u'コンポーネント移動・マルチカット編集が可能です' 38 | in_view_message.show(msg, fadeStayTime=5000) 39 | 40 | @UnexpectedError.catch 41 | def __finish(self): 42 | cv_freezer.freeze() 43 | self.__toggle_editable(False, Const.START_TEXT, Const.START_ANNOTATION, Const.START_COLOR) 44 | msg = u'コンポーネント移動・マルチカットの編集が不可になりました。' 45 | in_view_message.show(msg, fadeStayTime=5000) 46 | 47 | def __main(self): 48 | self.__finish() if self.__editable else self.__start() 49 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hum9183/MayaHumTools/0bca53971a389aa8b5cefb34931736aaf4fc462f/Contents/scripts/humtools/blendshapedvertexmerger/__init__.py -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/blendshape_reconfigurator.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from ..lib import blendshape 5 | 6 | 7 | class BlendShapeReconfigurator: 8 | def __init__(self, reconfigure_bs_setting, base_mesh, bs_nodes): 9 | self.__reconfigure_bs_setting = reconfigure_bs_setting 10 | self.__base_mesh = base_mesh 11 | self.__bs_nodes = bs_nodes 12 | self.__preserved_bs_dict = dict() # dict{str, list[(long, str)]} 13 | 14 | def preserve(self): 15 | if not self.__reconfigure_bs_setting: 16 | return 17 | blendshape.exists_validate(self.__bs_nodes) 18 | self.__preserved_bs_dict = {bs: blendshape.get_targets_tuples(bs) for bs in self.__bs_nodes} 19 | 20 | def delete_nodes(self): 21 | if not self.__reconfigure_bs_setting: 22 | return 23 | blendshape.exists_validate(self.__bs_nodes) 24 | cmds.delete(self.__bs_nodes) 25 | 26 | def configure(self): 27 | if not self.__reconfigure_bs_setting: 28 | return 29 | blendshape.exists_validate(self.__bs_nodes) 30 | self.__create_blendshapes() 31 | self.__create_blendshape_targets() 32 | 33 | def __create_blendshapes(self): 34 | base_mesh = self.__base_mesh 35 | for bs_node, _ in self.__preserved_bs_dict.items(): 36 | cmds.blendShape(base_mesh, n=bs_node) 37 | 38 | def __create_blendshape_targets(self): 39 | base_mesh = self.__base_mesh 40 | for bs_node, bs_tuple in self.__preserved_bs_dict.items(): 41 | for idx, target in bs_tuple: 42 | cmds.blendShape(bs_node, e=True, t=(base_mesh, idx, target, 1)) -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/blendshape_target_rebuilder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from itertools import chain 3 | 4 | from maya import cmds 5 | 6 | from ..util.unexpected_error import UnexpectedError 7 | from .const import Const 8 | 9 | 10 | def rebuild(base_mesh): 11 | bs_nodes = __get_bs_nodes(base_mesh) 12 | 13 | __exists_validate(bs_nodes) 14 | 15 | rebuilt_targets = [__rebuild(bs) for bs in bs_nodes] 16 | return list(chain.from_iterable(rebuilt_targets)) 17 | 18 | 19 | def __get_bs_nodes(base_mesh): 20 | histories = cmds.listHistory(base_mesh) 21 | BLEND_SHAPE = 'blendShape' 22 | return cmds.ls(histories, type=BLEND_SHAPE) 23 | 24 | 25 | def __exists_validate(bs_nodes): 26 | if bs_nodes == []: 27 | raise UnexpectedError(u"ブレンドシェイプが存在しません。") 28 | 29 | 30 | def __rebuild(bs_node): 31 | target_indices = cmds.getAttr( 32 | '{}.targetDirectory[0].childIndices'.format(bs_node)) 33 | __set_bs_weight_to_zero(bs_node, target_indices) 34 | __delete_already_rebuilt_targets(bs_node) 35 | rebuilt_targets = __create_mesh(bs_node, target_indices) 36 | __hide(rebuilt_targets) 37 | return rebuilt_targets 38 | 39 | 40 | def __set_bs_weight_to_zero(bs_node, bs_targets_indices): 41 | # NOTE: ブレンドシェイプのウェイト値が入っていると、メッシュが壊れるため、セーフティとして行う 42 | WEIGHT_VALUE = 0 43 | for idx in bs_targets_indices: 44 | cmds.blendShape(bs_node, e=True, weight=(idx, WEIGHT_VALUE)) 45 | 46 | 47 | def __delete_already_rebuilt_targets(bs_node): 48 | # NOTE: すでにリビルド済みのメッシュはBSノードにコネクトしていてうまくマージできない可能性があるため、一旦すべて削除する 49 | # NOTE: ターゲット名とリビルド済のターゲットメッシュ名が異なる可能性もあるため、そういう場合のためにも一旦すべて削除する 50 | rebuilt_target_meshes = cmds.blendShape(bs_node, q=True, t=True) # WARNING: BS名ではない 51 | if (rebuilt_target_meshes != []): 52 | cmds.delete(rebuilt_target_meshes) 53 | 54 | 55 | def __create_mesh(bs_node, target_indices): 56 | return [cmds.sculptTarget(bs_node, e=True, regenerate=True, t=idx)[0] 57 | for idx in target_indices] 58 | 59 | 60 | def __hide(rebuilt_targets): 61 | [cmds.hide(t) for t in rebuilt_targets] 62 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from ..util.readonly import metaclass 3 | 4 | 5 | class Const(metaclass('Const')): 6 | TOOL_NAME = 'HumBlendShapedVertexMerger' 7 | RECONFIGURE_BS_CHECK_BOX = 'ReconfigureBSCheckBox' -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from maya import cmds 4 | 5 | from ..util.unexpected_error import UnexpectedError 6 | from ..lib import blendshape 7 | from .blendshape_reconfigurator import BlendShapeReconfigurator 8 | from .const import Const 9 | from . import blendshape_target_rebuilder 10 | from . import non_deformer_history_deleter 11 | from . import selected_getter 12 | from . import vtx_merger 13 | 14 | 15 | @UnexpectedError.catch 16 | def main(reconfigure_bs_setting): 17 | vtx_ids, base_mesh = selected_getter.get_vtx_ids_and_mesh() 18 | 19 | blendshape_targets = blendshape_target_rebuilder.rebuild(base_mesh) 20 | bs_reconfigurator = BlendShapeReconfigurator(reconfigure_bs_setting, base_mesh, blendshape.get_nodes(base_mesh)) 21 | bs_reconfigurator.preserve() 22 | bs_reconfigurator.delete_nodes() 23 | 24 | vtx_merger.merge(vtx_ids, base_mesh) 25 | non_deformer_history_deleter.delete(base_mesh) 26 | 27 | vtx_merger.merge(vtx_ids, blendshape_targets) 28 | non_deformer_history_deleter.delete(blendshape_targets) 29 | 30 | bs_reconfigurator.configure() -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/non_deformer_history_deleter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | from .const import Const 4 | 5 | def delete(meshes): 6 | if (type(meshes) is list): 7 | [__delete(m) for m in meshes] 8 | else: 9 | __delete(meshes) 10 | 11 | 12 | def __delete(mesh): 13 | cmds.select(mesh) 14 | mel.eval('BakeNonDefHistory') 15 | cmds.select(cl=True) -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/selected_getter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import re 3 | 4 | from maya import cmds 5 | 6 | from ..util.unexpected_error import UnexpectedError 7 | from .const import Const 8 | 9 | 10 | def get_vtx_ids_and_mesh(): 11 | sels = cmds.ls(sl=True, flatten=True) 12 | 13 | __exists_validate(sels) 14 | 15 | __only_vtx_validate(sels) 16 | 17 | mesh_name = __get_first_sel_mesh_name(sels) 18 | __only_one_mesh_validate(sels, mesh_name) 19 | 20 | __multiple_vtx_validate(sels) 21 | 22 | vtx_ids = __get_vtx_ids(sels) 23 | 24 | return vtx_ids, mesh_name 25 | 26 | 27 | def __exists_validate(sels): 28 | if (sels == []): 29 | raise UnexpectedError(u"頂点を選択してください。") 30 | 31 | 32 | def __only_vtx_validate(sels): 33 | vtx_comp_str = '.vtx[' 34 | for sel in sels: 35 | if not vtx_comp_str in sel: 36 | raise UnexpectedError(u"頂点のみを選択してください。") 37 | 38 | 39 | def __get_first_sel_mesh_name(sels): 40 | FIRST_IDX = 0 41 | return __get_split_by_a_dot(sels[0], FIRST_IDX) 42 | 43 | 44 | def __only_one_mesh_validate(sels, mesh_name): 45 | FIRST_IDX = 0 46 | for sel in sels: 47 | first_word = __get_split_by_a_dot(sel, FIRST_IDX) 48 | if first_word != mesh_name: 49 | raise UnexpectedError(u"一つのメッシュの頂点を選択してください。") 50 | 51 | 52 | def __get_split_by_a_dot(string, index): 53 | return string.split('.')[index] 54 | 55 | 56 | def __multiple_vtx_validate(sels): 57 | if len(sels) == 1: 58 | raise UnexpectedError(u"複数の頂点を選択してください") 59 | 60 | 61 | def __get_vtx_ids(sels): 62 | END_IDX = 1 63 | vtx_ids = [] 64 | for sel in sels: 65 | end_word = __get_split_by_a_dot(sel, END_IDX) 66 | vtx_ids.append(re.sub(r'\D', '', end_word)) 67 | return vtx_ids 68 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import inspect 3 | 4 | from maya import cmds 5 | 6 | from .. import menu_adder 7 | from .const import Const 8 | from . import startup_command 9 | 10 | 11 | def add_menu(): 12 | # NOTE: シェルフに登録できるようにするために、起動コマンドはstringで渡す 13 | startup_command_str = inspect.getsource(startup_command) 14 | menu_adder.add(Const.TOOL_NAME, startup_command_str) -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/startup_command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def bsvm_startup_command(): 4 | bsvm_reload_modules() 5 | bsvm_show_window() 6 | 7 | 8 | def bsvm_reload_modules(): 9 | from humtools.blendshapedvertexmerger import const, window, main, selected_getter, \ 10 | blendshape_target_rebuilder, vtx_merger, non_deformer_history_deleter, blendshape_reconfigurator 11 | 12 | from humtools import module_reloader 13 | modules = [const, window, main, selected_getter, blendshape_target_rebuilder, \ 14 | vtx_merger, non_deformer_history_deleter, blendshape_reconfigurator] 15 | module_reloader.reload_a_few_times(modules) 16 | 17 | 18 | def bsvm_show_window(): 19 | from humtools.blendshapedvertexmerger.window import Window 20 | from humtools.blendshapedvertexmerger.const import Const 21 | wnd = Window(Const.TOOL_NAME) 22 | wnd.show() 23 | 24 | 25 | if __name__ == '__main__': 26 | bsvm_startup_command() 27 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/vtx_merger.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | 4 | from .const import Const 5 | 6 | 7 | def merge(vtx_ids, meshes): 8 | if (type(meshes) is list): 9 | [__merge(vtx_ids, m) for m in meshes] 10 | else: 11 | __merge(vtx_ids, meshes) 12 | 13 | 14 | def __merge(vtx_ids, mesh): 15 | attr_strs = ['{}.vtx[{}]'.format(mesh, vtx_id) for vtx_id in vtx_ids] 16 | cmds.select(attr_strs) 17 | mel.eval('polyMergeToCenter') 18 | cmds.select(cl=True) -------------------------------------------------------------------------------- /Contents/scripts/humtools/blendshapedvertexmerger/window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | from maya.common.ui import LayoutManager 4 | 5 | from ..hum_tools_const import HumToolsConst 6 | from .const import Const 7 | from . import main 8 | 9 | 10 | class Window: 11 | def __init__(self, tool_name): 12 | self.tool_name = tool_name 13 | 14 | 15 | def show(self): 16 | self.__delete_window() 17 | self.__create_window() 18 | cmds.showWindow() 19 | 20 | 21 | def __delete_window(self): 22 | if cmds.window(self.tool_name, ex=True): 23 | cmds.deleteUI(self.tool_name) 24 | 25 | 26 | def __create_window(self): 27 | cmds.window(self.tool_name, title=self.tool_name, mxb=False, mnb=False) 28 | with LayoutManager(cmds.columnLayout(adj=True)): 29 | cmds.text(l='') 30 | cmds.button(l='Merge vertex', h=50, 31 | c=lambda args: self.__merge_vertex()) 32 | cmds.text(l='') 33 | self.__create_check_box() 34 | 35 | 36 | def __create_check_box(self): 37 | # NOTE: Maya2022以降はブレンドシェイプを再構築しないとメッシュが壊れる可能性が高い 38 | recommended_version = HumToolsConst.is_the_maya_version_after_2022() 39 | cmds.checkBox(Const.RECONFIGURE_BS_CHECK_BOX, l=u'ブレンドシェイプを再構築する', v=recommended_version) 40 | 41 | 42 | def __merge_vertex(self): 43 | reconfigure_bs_setting = cmds.checkBox(Const.RECONFIGURE_BS_CHECK_BOX, q=True, v=True) 44 | main.main(reconfigure_bs_setting) -------------------------------------------------------------------------------- /Contents/scripts/humtools/hum_tools_const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | 4 | from maya import cmds 5 | 6 | from .util.readonly import metaclass 7 | 8 | 9 | class HumToolsConst(metaclass('HumToolsConst')): 10 | HUM_TOOLS_FOLDER = 'HumToolsFolder' 11 | HUM_TOOLS = 'HumTools' 12 | MAYA_VERSION = int(cmds.about(v=True)) 13 | LANGUAGE = cmds.about(uiLanguage=True) 14 | LANG = 'Lang' 15 | 16 | @staticmethod 17 | def is_the_maya_version_after_2022(): 18 | return HumToolsConst.MAYA_VERSION >= 2022 19 | 20 | @staticmethod 21 | def is_the_maya_version_before_2020(): 22 | return HumToolsConst.MAYA_VERSION <= 2020 23 | 24 | @staticmethod 25 | def is_python_major_version_2(): 26 | ver = sys.version 27 | major_ver = ver.split('.')[0] 28 | return major_ver == '2' 29 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/lib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hum9183/MayaHumTools/0bca53971a389aa8b5cefb34931736aaf4fc462f/Contents/scripts/humtools/lib/__init__.py -------------------------------------------------------------------------------- /Contents/scripts/humtools/lib/blendshape.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from itertools import chain 3 | 4 | from maya import cmds 5 | 6 | from ..util.node_type import NodeType 7 | from ..util.unexpected_error import UnexpectedError 8 | 9 | 10 | def get_nodes(mesh, raise_ex_if_is_none=False): 11 | """BSノードを取得する 12 | 13 | Args: 14 | mesh (str): メッシュ 15 | 16 | Returns: 17 | list[str] or None: BSノードのリスト 18 | """ 19 | return NodeType.get_histories(mesh, NodeType.BLEND_SHAPE, raise_ex_if_is_none) 20 | 21 | 22 | def exists_validate(bs_nodes): 23 | """ブレンドシェイプが存在するかを確認する 24 | 25 | Args: 26 | bs_nodes (list[str]): BSノードのリスト 27 | 28 | Raises: 29 | UnexpectedError: listが空の場合 30 | """ 31 | if bs_nodes == []: 32 | raise UnexpectedError(u'ブレンドシェイプが存在しません。') 33 | 34 | 35 | def get_target_indices(bs_node): 36 | """BSターゲットのインデックスを取得する。 37 | ShapeEditorの並び順と同一。 38 | 39 | Args: 40 | bs_node (str): BSノード 41 | 42 | Returns: 43 | list[long]: BSターゲットのインデックスのリスト 44 | """ 45 | return cmds.getAttr('{}.targetDirectory[0].childIndices'.format(bs_node)) 46 | 47 | 48 | def get_rebuilt_target_meshes(bs_node): 49 | """リビルド済のターゲットメッシュのリストを取得する。 50 | ShapeEditorの並び順ではなく、index順。 51 | 52 | Args: 53 | bs_node (str): BSノード 54 | 55 | Returns: 56 | list[str]: リビルド済のターゲットメッシュのリスト 57 | """ 58 | return cmds.blendShape(bs_node, q=True, t=True) 59 | 60 | 61 | def delete_rebuilt_target_meshes(bs_nodes): 62 | """リビルド済のターゲットメッシュを削除する。 63 | 64 | Args: 65 | bs_node (list[str] or str): BSノード 66 | """ 67 | def delete(bs_node): 68 | rebuilt_target_meshes = get_rebuilt_target_meshes(bs_node) 69 | if (rebuilt_target_meshes != []): 70 | cmds.delete(rebuilt_target_meshes) 71 | 72 | if type(bs_nodes) is list: 73 | [delete(bs) for bs in bs_nodes] 74 | else: 75 | delete(bs_nodes) 76 | 77 | 78 | def sort_targets_by_shape_editor_display_order(target_indices, target_meshes): 79 | """リビルド済のメッシュListをShapeEditorの並び順にソートする""" 80 | return [m for _, m in sorted(zip(target_indices, target_meshes))] 81 | 82 | 83 | def get_targets_tuples(bs_node): 84 | """ターゲットのindexとリビルド済メッシュ名がペアになったタプルのリストを取得する。 85 | ターゲットがすべてリビルド済であることを想定。 86 | ShapeEditorの並び順にソートする。 87 | 88 | Args: 89 | bs_node (str): BSノード 90 | 91 | Returns: 92 | list[(long, string)]: (ターゲットのindex, リビルド済メッシュ名)のリスト 93 | """ 94 | target_indices = get_target_indices(bs_node) 95 | target_meshes = get_rebuilt_target_meshes(bs_node) 96 | sorted_target_meshes = sort_targets_by_shape_editor_display_order( 97 | target_indices, target_meshes) 98 | return zip(target_indices, sorted_target_meshes) 99 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/lib/tool_mode.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | import maya.api.OpenMaya as om2 4 | 5 | 6 | # ツール 7 | def select_tool(): 8 | """選択ツール""" 9 | mel.eval('SelectToolOptionsMarkingMenu') 10 | mel.eval('SelectToolOptionsMarkingMenuPopDown') 11 | 12 | def art_paint_skin_weights_tool(): 13 | mel.eval('ArtPaintSkinWeightsToolOptions') 14 | 15 | 16 | # モード 17 | def object_mode(mesh): 18 | mel.eval('maintainActiveChangeSelectMode {} 1'.format(mesh)) 19 | 20 | def vert_mode(mesh): 21 | build_model_panel4_object_pop() 22 | mel.eval('doMenuComponentSelectionExt("{}", "vertex", 1)'.format(mesh)) 23 | 24 | def build_model_panel4_object_pop(): 25 | # mayaを起動して一度もビューポートでRMBをしてない場合は、ビルドする必要がある 26 | mel.eval('buildObjectMenuItemsNow "MainPane|viewPanes|modelPanel4|modelPanel4|modelPanel4|modelPanel4ObjectPop"') 27 | 28 | 29 | # NOTE: 処理の備忘録 30 | # def reset_selection_mode(): 31 | # """無選択状態にする""" 32 | # om2.MGlobal.setSelectionMode(om2.MGlobal.kSelectObjectMode) 33 | # om2.MGlobal.setActiveSelectionList(om2.MSelectionList()) 34 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/menu_adder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from textwrap import dedent 3 | 4 | from maya import cmds 5 | from .hum_tools_const import HumToolsConst 6 | 7 | 8 | def add(tool_name, startup_command_str): 9 | cmds.menuItem( 10 | tool_name, 11 | label=tool_name, 12 | annotation='Run {}'.format(tool_name), 13 | parent=HumToolsConst.HUM_TOOLS_FOLDER, 14 | echoCommand=True, 15 | command=dedent(startup_command_str) 16 | ) 17 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/module_reloader.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .hum_tools_const import HumToolsConst 3 | 4 | 5 | def reload_a_few_times(modules): 6 | [__reload(modules) for _ in range(2)] # NOTE: 一回のreloadではリロードしきれない? 7 | 8 | 9 | def __reload(modules): 10 | [__reload_py_ver(m) for m in modules] 11 | 12 | 13 | def __reload_py_ver(module): 14 | if HumToolsConst.is_python_major_version_2(): 15 | reload(module) # Python2 Global空間の組み込みreload 16 | else: 17 | import importlib # Python3 18 | importlib.reload(module) 19 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hum9183/MayaHumTools/0bca53971a389aa8b5cefb34931736aaf4fc462f/Contents/scripts/humtools/skinweightsbugsearcher/__init__.py -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/bug_searcher.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | 4 | from ..util.lang import Lang 5 | from ..util.progress_window import ProgressWindow 6 | from ..util.time_recorder import TimeRecorder 7 | from .lang_op_var import LangOpVar 8 | from .selection import Selection 9 | from .working_mesh import WorkingMesh 10 | 11 | 12 | @TimeRecorder.wraps 13 | def search(): 14 | selection = Selection() 15 | sel_list = selection.get_meshes_as_sel_list() 16 | 17 | working_meshes = WorkingMesh.create_from_sel_list(sel_list) 18 | 19 | all_meshes_vert_count = __get_all_meshes_vert_count(working_meshes) 20 | progress_window = ProgressWindow(all_meshes_vert_count, Lang.pack(u'頂点を走査中', 'Scanning vertex', LangOpVar.get()), LangOpVar.get()) 21 | progress_window.show() 22 | 23 | all_bugs = [] 24 | for working_mesh in working_meshes: 25 | bugs = __get_bugs(working_mesh, progress_window) 26 | all_bugs.extend(bugs) 27 | 28 | progress_window.close() 29 | cmds.select(cl=True) 30 | 31 | return all_bugs 32 | 33 | 34 | def __get_all_meshes_vert_count(working_meshes): 35 | vert_count_list = [wm.vert_count for wm in working_meshes] 36 | return sum(vert_count_list) 37 | 38 | 39 | def __get_bugs(working_mesh, progress_window): 40 | bugs = [] 41 | while not working_mesh.mesh_vert_it_main.isDone(): 42 | if progress_window.is_cancelled(): 43 | break 44 | progress_window.next() 45 | 46 | nearby_vert_ids = working_mesh.get_vert_ids_nearby_current_vert() 47 | nearby_vert_infl_valid = working_mesh.calc_infl_vaild_nearby_verts(nearby_vert_ids) 48 | 49 | skin_weights = working_mesh.get_current_vert_skinweights() 50 | infl_names = working_mesh.calc_unexpected_influeces(skin_weights, nearby_vert_infl_valid) 51 | 52 | if infl_names != []: 53 | vert_id = working_mesh.get_current_vert_id() 54 | joined_infl_name = '@'.join(infl_names) 55 | item_name = '{}.vtx[{}]@{}'.format(working_mesh.name, vert_id, joined_infl_name) 56 | bugs.append(item_name) 57 | 58 | working_mesh.mesh_vert_it_main.next() 59 | 60 | return bugs 61 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from ..util.readonly import metaclass 3 | 4 | 5 | class Const(metaclass('Const')): 6 | TOOL_NAME = 'HumSkinWeightsBugSearcher' 7 | WEIGHTS_BUG_TEXT_SCROLL_LIST_NAME = 'HumSkinWeightsBugSearcher_WeightsBugTextScrollList' 8 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/lang_op_var.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from ..hum_tools_const import HumToolsConst 5 | from .const import Const 6 | 7 | 8 | class LangOpVar: 9 | op_var_name = '{}{}'.format(Const.TOOL_NAME, HumToolsConst.LANG) 10 | 11 | @staticmethod 12 | def get(): 13 | if cmds.optionVar(exists=LangOpVar.op_var_name) is False: 14 | LangOpVar.set(HumToolsConst.LANGUAGE) 15 | return cmds.optionVar(q=LangOpVar.op_var_name) 16 | 17 | @staticmethod 18 | def set(lang): 19 | cmds.optionVar(stringValue=(LangOpVar.op_var_name, lang)) 20 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/om2_util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import maya.api.OpenMaya as om2 3 | 4 | 5 | # NOTE: 仮置き 6 | def get_skincluster_node(mesh_depend_node): 7 | dg_iterator = om2.MItDependencyGraph( 8 | mesh_depend_node, 9 | om2.MFn.kSkinClusterFilter, # NOTE: kInvalidを渡すと無指定になるので全て列挙することもできる 10 | om2.MItDependencyGraph.kUpstream) 11 | while not dg_iterator.isDone(): 12 | m_object = dg_iterator.currentNode() 13 | if m_object.hasFn(om2.MFn.kSkinClusterFilter): 14 | return m_object 15 | dg_iterator.next() 16 | return None 17 | 18 | 19 | def get_influence_names(skinclister_fn): 20 | infl_dag_paths = skinclister_fn.influenceObjects() 21 | infl_count = len(infl_dag_paths) 22 | 23 | infl_short_names = [] 24 | for i in range(infl_count): 25 | # ショートネームを取得する 26 | infl_depend_node = infl_dag_paths[i].node() 27 | dn_fn = om2.MFnDependencyNode(infl_depend_node) 28 | infl_short_names.append(dn_fn.name()) 29 | 30 | return infl_short_names 31 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/selection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | import maya.api.OpenMaya as om2 4 | 5 | from ..util.lang import Lang 6 | from ..util.unexpected_error import UnexpectedError 7 | from .lang_op_var import LangOpVar 8 | 9 | 10 | class Selection: 11 | def __init__(self): 12 | self.__sel_list = om2.MSelectionList() 13 | 14 | def get_meshes_as_sel_list(self): 15 | active_sel_list = om2.MGlobal.getActiveSelectionList() 16 | if active_sel_list.isEmpty(): 17 | return self.__sel_list 18 | 19 | for index in range(active_sel_list.length()): 20 | self.__search_mesh_recursive(active_sel_list.getDagPath(index)) 21 | 22 | if self.__sel_list.isEmpty(): 23 | raise UnexpectedError(Lang.pack(u'スキンバインドされたメッシュを選択してください', 'Please Select skin bound meshes', LangOpVar.get())) 24 | 25 | return self.__sel_list 26 | 27 | def __search_mesh_recursive(self, dag_path): 28 | """meshを再帰的に走査する。見つけた場合はメンバのMSelectionListに追加する""" 29 | if self.__is_mesh_parent_transform(dag_path): 30 | self.__sel_list.add(dag_path) 31 | 32 | children_dag_paths = self.__get_children_dag_paths(dag_path) 33 | if len(children_dag_paths) == 0: 34 | return 35 | 36 | for child_dag_path in children_dag_paths: 37 | # 子がTransformなら再帰的に走査する 38 | if child_dag_path.apiType() == om2.MFn.kTransform: 39 | self.__search_mesh_recursive(child_dag_path) 40 | 41 | def __is_mesh_parent_transform(self, dag_path): 42 | """meshの親transformかどうか""" 43 | if dag_path.apiType() != om2.MFn.kTransform: 44 | return False 45 | 46 | # バインド済メッシュの親Transformなら、shapeとorigShapeの2つを子に持つ 47 | child_count = dag_path.childCount() 48 | if child_count != 2: 49 | return False 50 | 51 | for index in range(child_count): 52 | child = dag_path.child(index) 53 | if child.apiType() != om2.MFn.kMesh: 54 | return False 55 | 56 | return True 57 | 58 | def __get_children_dag_paths(self, dag_path): 59 | """子をMDagPathArrayで返す""" 60 | child_count = dag_path.childCount() 61 | children_dag_paths = om2.MDagPathArray() 62 | for index in range(child_count): 63 | child_m_object = dag_path.child(index) 64 | children_dag_paths.append(om2.MDagPath.getAPathTo(child_m_object)) 65 | return children_dag_paths 66 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import inspect 3 | 4 | from .const import Const 5 | from .. import menu_adder 6 | from . import startup_command 7 | 8 | 9 | def add_menu(): 10 | # NOTE: シェルフに登録できるようにするために、起動コマンドはstringで渡す 11 | startup_command_str = inspect.getsource(startup_command) 12 | menu_adder.add(Const.TOOL_NAME, startup_command_str) 13 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/startup_command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | def swbs_startup_command(): 5 | swbs_reload_modules() 6 | swbs_show_window() 7 | 8 | 9 | def swbs_reload_modules(): 10 | from humtools.skinweightsbugsearcher import const, window, bug_searcher, working_mesh, weights_bug_text_scroll_list, om2_util, selection 11 | from humtools import module_reloader 12 | modules = [const, window, bug_searcher, working_mesh, weights_bug_text_scroll_list, om2_util, selection] 13 | module_reloader.reload_a_few_times(modules) 14 | 15 | 16 | def swbs_show_window(): 17 | from humtools.skinweightsbugsearcher.const import Const 18 | from humtools.skinweightsbugsearcher.window import Window 19 | wnd = Window(Const.TOOL_NAME) 20 | wnd.show() 21 | 22 | 23 | if __name__ == '__main__': 24 | swbs_startup_command() 25 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/weights_bug_text_scroll_list.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | 4 | from ..lib import tool_mode 5 | from ..util.lang import Lang 6 | from ..util.unexpected_error import UnexpectedError 7 | from .lang_op_var import LangOpVar 8 | 9 | 10 | class WeightsBugTextScrollList: 11 | def __init__(self, name): 12 | self.__name = name 13 | 14 | def build(self): 15 | cmds.paneLayout(configuration='horizontal2') # NOTE: singleだとうまくadjustしない 16 | ann_text_ja = u'クリックするとバグ頂点が選択されます。\nダブルクリックするとバグウェイトが振られているジョイントを選択します。' 17 | ann_text_en = 'Clicking selects the bug vertex. \nDouble-clicking selects the joint to which the bug weight is assigned.' 18 | cmds.textScrollList(self.__name, 19 | ann=Lang.pack(ann_text_ja, ann_text_en, LangOpVar.get()), 20 | allowMultiSelection=False, 21 | selectCommand =lambda *args: self.__select_vert(), 22 | doubleClickCommand=lambda *args: self.__select_joint()) 23 | 24 | def delete_sel_item(self): 25 | sel_item = self.__get_sel_item() 26 | cmds.textScrollList(self.__name, e=True, removeItem=sel_item) 27 | 28 | def set_new_items(self, items): 29 | self.delete_all_items() 30 | self.__add_items(items) 31 | 32 | def delete_all_items(self): 33 | cmds.textScrollList(self.__name, e=True, removeAll=True) 34 | 35 | def __add_items(self, items): 36 | cmds.textScrollList(self.__name, e=True, append=items) 37 | 38 | @UnexpectedError.catch 39 | def __select_vert(self): 40 | mesh, mesh_vert, joint = self.__get_bug_items(validate_exists=True) 41 | 42 | tool_mode.select_tool() # NOTE: ArtPaintSkinWeightsTool等になっている場合、選択ツールに戻すために呼ぶ 43 | cmds.select(cl=True) 44 | 45 | tool_mode.vert_mode(mesh) 46 | cmds.select(mesh_vert, r=True) 47 | 48 | tool_mode.art_paint_skin_weights_tool() 49 | mel.eval('setSmoothSkinInfluence {}'.format(joint)) # インフルエンスの選択 50 | 51 | @UnexpectedError.catch 52 | def __select_joint(self): 53 | _, _, joint = self.__get_bug_items(validate_exists=True) 54 | cmds.select(joint, r=True) 55 | 56 | @UnexpectedError.catch 57 | def __get_sel_item(self): 58 | sel_item = cmds.textScrollList(self.__name, q=True, selectItem=True) 59 | if sel_item: 60 | return sel_item[0] 61 | else: 62 | raise UnexpectedError(Lang.pack(u'バグ項目が存在しません。', 'Bug item does not exist.', LangOpVar.get())) 63 | 64 | def __get_bug_items(self, validate_exists=False): 65 | sel_item = self.__get_sel_item() 66 | at_split = sel_item.split('@') 67 | mesh_vert = at_split[0] 68 | first_joint = at_split[1] # NOTE: 2つ目以降のジョイントがある場合でも無視する 69 | mesh = mesh_vert.split('.')[0] 70 | 71 | if validate_exists: 72 | self.__exists_validation(mesh, first_joint) 73 | 74 | return mesh, mesh_vert, first_joint 75 | 76 | def __exists_validation(self, mesh, joint): 77 | # NOTE: 頂点番号はなかったとしても無視する(選択はできてしまう) 78 | if cmds.objExists(mesh) is False: 79 | non_existent = mesh 80 | elif cmds.objExists(joint) is False: 81 | non_existent = joint 82 | else: 83 | return 84 | 85 | ja = u'{}がシーン内に存在しません。'.format(non_existent) 86 | en = u'{} does not exist in the scene.'.format(non_existent) 87 | raise UnexpectedError(Lang.pack(ja, en, LangOpVar.get())) 88 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import webbrowser 3 | 4 | from maya import cmds, mel 5 | from maya.common.ui import LayoutManager 6 | 7 | from ..lib import tool_mode 8 | from ..util.hum_window_base import HumWindowBase 9 | from ..util.lang import Lang 10 | from ..util.unexpected_error import UnexpectedError 11 | from ..util import in_view_message 12 | from .const import Const 13 | from .lang_op_var import LangOpVar 14 | from .weights_bug_text_scroll_list import WeightsBugTextScrollList 15 | from . import bug_searcher 16 | 17 | 18 | class Window(HumWindowBase): 19 | def __init__(self, tool_name): 20 | super(Window, self).__init__(tool_name) 21 | self.__weights_bug_text_scroll_list = None 22 | 23 | def _create_window(self): 24 | cmds.window(self.tool_name, title=self.tool_name, menuBar=True, mxb=False, mnb=False) 25 | self.__set_text_scroll_list() 26 | self.__build_guis() 27 | 28 | def __set_text_scroll_list(self): 29 | self.__weights_bug_text_scroll_list = WeightsBugTextScrollList(Const.WEIGHTS_BUG_TEXT_SCROLL_LIST_NAME) 30 | 31 | def __build_guis(self): 32 | lang = LangOpVar.get() 33 | self.__build_edit_menus(lang) 34 | self.__build_help_menus(lang) 35 | self.__build_button(lang) 36 | self.__weights_bug_text_scroll_list.build() 37 | 38 | def __build_edit_menus(self, lang): 39 | cmds.menu(l=Lang.pack(u'編集', 'Edit', lang), tearOff=True) 40 | cmds.menuItem(l=Lang.pack(u'選択しているバグ項目を削除', 'Delete selected bug item', lang), c=lambda *args: self.__weights_bug_text_scroll_list.delete_sel_item()) 41 | 42 | def __build_help_menus(self, lang): 43 | cmds.menu(l=Lang.pack(u'ヘルプ', 'Help', lang), tearOff=True, helpMenu=True) 44 | cmds.menuItem(l=Lang.pack(u'ドキュメントを開く', 'Open document', lang), c=lambda *args: self.__open_document_in_webbrowser()) 45 | self.__menu_divider() 46 | self.__build_language_radio_menu(lang) 47 | 48 | def __build_language_radio_menu(self, lang): 49 | cmds.menuItem(l=Lang.pack(u'言語', 'Language', lang), subMenu=True, tearOff=True) 50 | cmds.radioMenuItemCollection() 51 | labels = [Lang.pack(u'日本語', 'Japanese', lang), Lang.pack(u'英語', 'English', lang)] 52 | cmds.menuItem(l=labels[0], radioButton=(lang == Lang.ja_JP), c=lambda *args: self.__set_lang_option_var(Lang.ja_JP)) 53 | cmds.menuItem(l=labels[1], radioButton=(lang == Lang.en_US), c=lambda *args: self.__set_lang_option_var(Lang.en_US)) 54 | cmds.setParent('..', menu=True) 55 | 56 | def __set_lang_option_var(self, lang): 57 | LangOpVar.set(lang) 58 | super(Window, self).reload_window() 59 | 60 | def __open_document_in_webbrowser(self): 61 | url = 'https://github.com/Hum9183/MayaHumTools#skinweightsbugsearcher' 62 | webbrowser.open(url) 63 | 64 | def __build_button(self, lang): 65 | with LayoutManager(cmds.columnLayout(adj=True)): 66 | ann_text_ja = u'メッシュを選択してボタンを押すとウェイトバグのスキャンをスタートします。\nスキャンが終了すると、テキストリストにバグがリストアップされます。' 67 | ann_text_en = 'Select meshes and press this button to start scanning for weight bugs.\nWhen the scan is complete, the bug will be listed in the text list.' 68 | cmds.button(l=Lang.pack(u'ウェイトバグを探す', 'Search weights bug', lang), 69 | ann=Lang.pack(ann_text_ja, ann_text_en, lang), 70 | c=lambda *args: self.__main()) 71 | 72 | @UnexpectedError.catch 73 | def __main(self): 74 | # NOTE: skinweightペイントモードだった場合処理が重くなるため、あらかじめ選択ツールにする 75 | tool_mode.select_tool() 76 | 77 | bugs = bug_searcher.search() 78 | bug_count = len(bugs) 79 | 80 | if bug_count == 0: 81 | self.__weights_bug_text_scroll_list.delete_all_items() 82 | completion_text = Lang.pack(u"バグはありません。", 'No bugs.', LangOpVar.get()) 83 | else: 84 | self.__weights_bug_text_scroll_list.set_new_items(bugs) 85 | completion_text = Lang.pack(u"{}個のバグが見つかりました。".format(bug_count), '{} bugs found.'.format(bug_count), LangOpVar.get()) 86 | 87 | in_view_message.show(completion_text) 88 | 89 | def __menu_divider(self): 90 | cmds.menuItem(divider=True) 91 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsbugsearcher/working_mesh.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | import maya.api.OpenMaya as om2 4 | import maya.api.OpenMayaAnim as oma2 5 | 6 | from ..util.lang import Lang 7 | from ..util.unexpected_error import UnexpectedError 8 | from .lang_op_var import LangOpVar 9 | from . import om2_util 10 | 11 | 12 | class WorkingMesh: 13 | @staticmethod 14 | def create_from_sel_list(sel_list): 15 | working_meshes = [] 16 | 17 | for index in range(sel_list.length()): 18 | m_object_dn = sel_list.getDependNode(index) 19 | name = om2.MFnDependencyNode(m_object_dn).name() 20 | dag_path, m_object_component = sel_list.getComponent(index) 21 | 22 | working_mesh = WorkingMesh(name, dag_path, m_object_component) 23 | if working_mesh.initialize(): 24 | working_meshes.append(working_mesh) 25 | 26 | if working_meshes == []: 27 | raise UnexpectedError(Lang.pack(u'スキンバインドされたメッシュを選択してください', 'Please Select skin bound meshes', LangOpVar.get())) 28 | 29 | return working_meshes 30 | 31 | def __init__(self, name, dag_path, m_object_component): 32 | self.name = name # type: str 33 | self.vert_count = None # type: int 34 | self.mesh_vert_it_main = None # type: om2.MItMeshVertex 35 | self.__mesh_vert_it_sub = None # type: om2.MItMeshVertex 36 | self.__mesh_poly_it = None # type: om2.MItMeshPolygon 37 | self.__dag_path = dag_path # type: om2.MDagPath 38 | self.__mesh_dag_path = None # type: om2.MDagPath 39 | self.__m_object_component = m_object_component # type: om2.MDagPath # (component) 40 | self.__skincluster_fn = None # type: oma2.MFnSkinCluster 41 | self.__influences_names = None # type: list[str] 42 | self.__influences_count = None # type: int 43 | 44 | def initialize(self): 45 | # mesh dag path 46 | self.__mesh_dag_path = self.__dag_path.extendToShape() 47 | 48 | # skincluster 49 | skincluster_node = om2_util.get_skincluster_node(self.__mesh_dag_path.node()) 50 | if skincluster_node is None: 51 | return False 52 | self.__skincluster_fn = oma2.MFnSkinCluster(skincluster_node) 53 | 54 | # influences 55 | self.__influences_names = om2_util.get_influence_names(self.__skincluster_fn) 56 | self.__influences_count = len(self.__influences_names) 57 | 58 | # iterator mesh 59 | temp_dag_path = self.__dag_path 60 | temp_m_object_component = self.__m_object_component 61 | self.mesh_vert_it_main = om2.MItMeshVertex(temp_dag_path, temp_m_object_component) 62 | self.__mesh_vert_it_sub = om2.MItMeshVertex(temp_dag_path, temp_m_object_component) 63 | self.__mesh_poly_it = om2.MItMeshPolygon(temp_dag_path, temp_m_object_component) 64 | 65 | # vertex count 66 | self.vert_count = self.mesh_vert_it_main.count() 67 | 68 | return True 69 | 70 | def get_current_vert_id(self): 71 | return int(self.mesh_vert_it_main.index()) 72 | 73 | def get_current_vert_skinweights(self): 74 | MD_ARRAY_INDEX = 0 75 | return self.__skincluster_fn.getWeights(self.__mesh_dag_path, self.__get_current_vert_component())[MD_ARRAY_INDEX] 76 | 77 | def get_vert_ids_nearby_current_vert(self): 78 | """current頂点の「近くの頂点のID」を取得する""" 79 | nearby_vert_ids = [] 80 | face_ids = self.mesh_vert_it_main.getConnectedFaces() # NOTE: Faceで繋がっているものを取得する 81 | 82 | for face_id in face_ids: 83 | self.__mesh_poly_it.setIndex(face_id) 84 | vert_ids = self.__mesh_poly_it.getVertices() 85 | nearby_vert_ids.extend(vert_ids) 86 | 87 | nearby_vert_ids = list(set(nearby_vert_ids)) 88 | nearby_vert_ids.remove(self.get_current_vert_id()) # NOTE: 自身は消す 89 | 90 | return nearby_vert_ids 91 | 92 | def calc_infl_vaild_nearby_verts(self, nearby_vert_ids): 93 | """周囲の頂点の「ウェイトが振られているインフルエンスリスト」を作成する""" 94 | MD_ARRAY_INDEX = 0 95 | all_infl_valid = [False] * self.__influences_count 96 | 97 | for nearby_vert_id in nearby_vert_ids: 98 | # NOTE: mesh_vert_it_mainは頂点走査のメインループで使用しているため、subのイテレータを使用する 99 | self.__mesh_vert_it_sub.setIndex(nearby_vert_id) 100 | component = self.__mesh_vert_it_sub.currentItem() 101 | nearby_vert_weights = self.__skincluster_fn.getWeights(self.__mesh_dag_path, component)[MD_ARRAY_INDEX] 102 | nearby_vert_infl_valid = self.__calc_influences_valid(nearby_vert_weights) 103 | all_infl_valid = [all or nearby for all, nearby in zip(all_infl_valid, nearby_vert_infl_valid)] # WARNING: 再代入 104 | 105 | return all_infl_valid 106 | 107 | def calc_unexpected_influeces(self, skinweights, nearby_vert_infl_valid): 108 | """「周囲の頂点ではウェイトが振られていない骨」にウェイトが振られているかを計算する。 109 | 振られていた骨名のリストを返す。 110 | """ 111 | current_infl_valid = self.__calc_influences_valid(skinweights) 112 | unexpected_infl_valid = [(current is True) and (nearby is False) for current, nearby in zip(current_infl_valid, nearby_vert_infl_valid)] 113 | unexpected_infl_names = [name for vaild, name in zip(unexpected_infl_valid, self.__influences_names) if vaild] 114 | return unexpected_infl_names 115 | 116 | def __get_current_vert_component(self): 117 | return self.mesh_vert_it_main.currentItem() 118 | 119 | def __calc_influences_valid(self, skin_weights): 120 | """ウェイトが振られているかどうかのリストを返す""" 121 | return [skin_weight > 0 for skin_weight in skin_weights] 122 | 123 | # NOTE: 処理の備忘録 124 | # def select_current_vert(self): 125 | # sel_list = om2.MSelectionList() 126 | # vert_tuple = (self.__dag_path, self.get_current_vert_component()) 127 | # sel_list.add(vert_tuple) 128 | # om2.MGlobal.setSelectionMode(om2.MGlobal.kSelectComponentMode) 129 | # om2.MGlobal.setActiveSelectionList(sel_list) 130 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hum9183/MayaHumTools/0bca53971a389aa8b5cefb34931736aaf4fc462f/Contents/scripts/humtools/skinweightsio/__init__.py -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/auto_skin_binder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import xml.etree.ElementTree as ET 4 | 5 | from maya import cmds, mel 6 | 7 | from ..util.extensions import Extensions 8 | from ..util.lang import Lang 9 | from ..util.log import Log 10 | from ..util.node_type import NodeType 11 | from ..util import path 12 | from .lang_op_var import LangOpVar 13 | 14 | 15 | class AutoSkinBinder: 16 | def __init__(self, xmls_folder_path, mesh_parent_transform_and_skincluster_dict): 17 | self.__xmls_folder_path = xmls_folder_path 18 | self.__mesh_parent_transform_and_skincluster_dict = mesh_parent_transform_and_skincluster_dict 19 | 20 | def bind(self): 21 | [self.__bind(mesh_parent_transform, skin_cluster) 22 | for mesh_parent_transform, skin_cluster in self.__mesh_parent_transform_and_skincluster_dict.items()] 23 | 24 | def get_dict_skin_bound(self): 25 | return {mesh_parent_transform: NodeType.get_history(mesh_parent_transform, NodeType.SKIN_CLUSTER) 26 | for mesh_parent_transform in self.__mesh_parent_transform_and_skincluster_dict.keys()} 27 | 28 | def __bind(self, mesh_parent_transform, skin_cluster): 29 | if skin_cluster is not None: 30 | return 31 | joints = self.__get_joints_from_xml(mesh_parent_transform) 32 | if joints is None: 33 | return 34 | self.__smooth_bind_skin(mesh_parent_transform, joints) 35 | 36 | def __get_joints_from_xml(self, mesh_parent_transform): 37 | xml_file_name = '{}{}'.format(mesh_parent_transform, Extensions.XML) 38 | xml_path = path.combine([self.__xmls_folder_path, xml_file_name]) 39 | try: 40 | element_tree = ET.parse(xml_path) 41 | except IOError: 42 | warning_msg = Lang.pack(u'{}と同名のXMLが存在しないため、自動バインドができませんでした。'.format(mesh_parent_transform), 43 | 'Auto binding could not be performed because XML with the same name as {} does not exist.'.format(mesh_parent_transform), 44 | LangOpVar.get()) 45 | Log.warning(warning_msg) 46 | return None 47 | root = element_tree.getroot() 48 | joints = [weights.attrib['source'] for weights in root.iter('weights')] 49 | return joints 50 | 51 | def __smooth_bind_skin(self, mesh_parent_transform, joints): 52 | cmds.select(mesh_parent_transform, replace=True) 53 | cmds.select(joints, add=True) 54 | mel.eval('SmoothBindSkin') # NOTE: userの既存オプション設定準拠でバインドする 55 | cmds.select(cl=True) 56 | self.__remove_unnecessary_influences(mesh_parent_transform, joints) 57 | log_msg = Lang.pack(u'{}にジョイントをバインドしました。'.format(mesh_parent_transform), 58 | 'Joints bound to {}.'.format(mesh_parent_transform), 59 | LangOpVar.get()) 60 | Log.log(log_msg) 61 | 62 | def __remove_unnecessary_influences(self, mesh_parent_transform, joints): 63 | # NOTE: SmoothBindSkinによるバインドだと骨が全てインフルエンスに入ってしまうため、余分なインフルエンスを削除する 64 | skin_cluster = NodeType.get_history(mesh_parent_transform, NodeType.SKIN_CLUSTER) 65 | influences = cmds.skinCluster(skin_cluster, q=True, influence=True) 66 | unnecessary_influences = list(set(influences) - set(joints)) 67 | for infl in unnecessary_influences: 68 | cmds.skinCluster(skin_cluster, e=True, removeInfluence=infl) 69 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from ..util.readonly import metaclass 3 | 4 | 5 | class Const(metaclass('Const')): 6 | TOOL_NAME = 'HumSkinWeightsIO' 7 | XMLS_FOLDER_NAME = 'XMLs' 8 | OPTION_SETTINGS_FOLDER_NAME = 'OptionSettings' 9 | OPTION_SETTINGS_JSON_NAME = 'option_settings' 10 | 11 | XML_TEXT_SCROLL_LIST_NAME = 'HumSWIO_XMLTextScrollList' 12 | 13 | # Option settings json keys 14 | KEY_IMPORT_METHOD = 'importMethod' 15 | KEY_IGNORE_NAME = 'ignoreName' 16 | KEY_NORMALIZE_WEIGHTS = 'normalizeWeights' 17 | KEY_AUTO_BINDING = 'autoBinding' 18 | 19 | # Import methods 20 | METHOD_INDEX = 'index' 21 | METHOD_OVER = 'over' 22 | METHOD_NEAREST = 'nearest' 23 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/deformer_weights_exporter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from maya import cmds 4 | 5 | from .lang_op_var import LangOpVar 6 | from ..util.extensions import Extensions 7 | from ..util.lang import Lang 8 | from ..util.log import Log 9 | from ..util.progress_window import ProgressWindow 10 | 11 | 12 | class DeformerWeightsExporter: 13 | def __init__(self, xmls_folder_path, xml_text_scroll_list): 14 | self.__xmls_folder_path = xmls_folder_path 15 | self.__xml_text_scroll_list = xml_text_scroll_list 16 | 17 | def export_xmls(self, mesh_parent_transform_and_skincluster_dict): 18 | progress_window = ProgressWindow(len(mesh_parent_transform_and_skincluster_dict), 19 | Lang.pack(u'XMLを生成中...', 'Creating XMLs...', LangOpVar.get()), 20 | LangOpVar.get()) 21 | progress_window.show() 22 | 23 | success = False 24 | for mesh_parent_transform, skin_cluster in mesh_parent_transform_and_skincluster_dict.items(): 25 | # ProgressWindowの更新処理 26 | if progress_window.is_cancelled(): 27 | break 28 | progress_window.next() 29 | 30 | xml_file_name = self.__get_xml_file_name(mesh_parent_transform) 31 | self.__export_xmls(xml_file_name, skin_cluster) 32 | self.__log(mesh_parent_transform) 33 | else: 34 | success = True 35 | 36 | progress_window.close() 37 | self.__load_text_scroll_list() 38 | 39 | return success 40 | 41 | def __get_xml_file_name(self, mesh_parent_transform): 42 | return '{}{}'.format(mesh_parent_transform, Extensions.XML) 43 | 44 | def __export_xmls(self, xml_file_name, skin_cluster): 45 | # NOTE: 同名ファイルがすでにある場合は上書きする 46 | cmds.deformerWeights( 47 | xml_file_name, 48 | export=True, 49 | deformer=skin_cluster, 50 | path=self.__xmls_folder_path) 51 | 52 | def __log(self, mesh_parent_transform): 53 | log_msg = Lang.pack(u'{}のXMLを作成しました。'.format(mesh_parent_transform), 54 | 'XML of {} was created.'.format(mesh_parent_transform), 55 | LangOpVar.get()) 56 | Log.log(log_msg) 57 | 58 | def __load_text_scroll_list(self): 59 | self.__xml_text_scroll_list.load() 60 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/deformer_weights_importer.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | from maya import cmds 5 | 6 | from .lang_op_var import LangOpVar 7 | from ..util import path 8 | from ..util.extensions import Extensions 9 | from ..util.lang import Lang 10 | from ..util.log import Log 11 | from ..util.progress_window import ProgressWindow 12 | 13 | 14 | class DeformerWeightsImporter: 15 | def __init__(self, xmls_folder_path, option_settings): 16 | self.__xmls_folder_path = xmls_folder_path 17 | self.__option_settings = option_settings 18 | 19 | def import_xmls(self, transform_and_skincluster_dict): 20 | xmls_folder_path = self.__xmls_folder_path 21 | import_method = self.__option_settings.import_method 22 | ignore_name = self.__option_settings.ignore_name 23 | normalize_weights = self.__option_settings.normalize_weights 24 | 25 | progress_window = ProgressWindow(len(transform_and_skincluster_dict), 26 | Lang.pack(u'ウェイトをコピー中...', 'Copying weights...', LangOpVar.get()), 27 | LangOpVar.get()) 28 | progress_window.show() 29 | 30 | success = False 31 | # NOTE: xmlと同名のメッシュ(メッシュの親トランスフォーム)に自動でウェイトをコピーする 32 | for mesh_parent_transform, skin_cluster in transform_and_skincluster_dict.items(): 33 | # ProgressWindowの更新処理 34 | if progress_window.is_cancelled(): 35 | break 36 | progress_window.next() 37 | 38 | # XMLがあるか確認 39 | xml_file_name = self.__get_xml_file_name(mesh_parent_transform) 40 | if self.__exists_xml_file(xmls_folder_path, xml_file_name, mesh_parent_transform) is False: 41 | continue 42 | 43 | # ウェイトコピー処理 44 | cmds.deformerWeights( 45 | xml_file_name, 46 | im=True, 47 | method=import_method, 48 | ignoreName=ignore_name, 49 | deformer=skin_cluster, 50 | path=xmls_folder_path) 51 | self.__normalize_weights(skin_cluster, normalize_weights) 52 | log_msg = Lang.pack(u'{}にウェイトをコピーしました。'.format(mesh_parent_transform), 53 | 'Copied weights to {}.'.format(mesh_parent_transform), 54 | LangOpVar.get()) 55 | Log.log(log_msg) 56 | else: 57 | success = True 58 | 59 | progress_window.close() 60 | 61 | return success 62 | 63 | def __get_xml_file_name(self, mesh_parent_transform): 64 | return '{}{}'.format(mesh_parent_transform, Extensions.XML) 65 | 66 | def __exists_xml_file(self, xmls_folder_path, xml_file_name, mesh_parent_transform): 67 | xml_file_path = path.combine([xmls_folder_path, xml_file_name]) 68 | if os.path.isfile(xml_file_path): 69 | return True 70 | else: 71 | ja_JP = u'{}と同名のXMLが存在しないため、ウェイトコピーができませんでした。'.format(mesh_parent_transform) 72 | en_US = 'Weight copy could not be performed because XML with the same name as {} does not exist.'.format( 73 | mesh_parent_transform) 74 | warning_msg = Lang.pack(ja_JP, en_US, LangOpVar.get()) 75 | Log.warning(warning_msg) 76 | return False 77 | 78 | def __normalize_weights(self, skin_cluster, normalize_weights): 79 | if normalize_weights: 80 | cmds.skinCluster(skin_cluster, e=True, forceNormalizeWeights=True) 81 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/lang_op_var.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from ..hum_tools_const import HumToolsConst 5 | from .const import Const 6 | 7 | 8 | class LangOpVar: 9 | op_var_name = '{}{}'.format(Const.TOOL_NAME, HumToolsConst.LANG) 10 | 11 | @staticmethod 12 | def get(): 13 | if cmds.optionVar(exists=LangOpVar.op_var_name) is False: 14 | LangOpVar.set(HumToolsConst.LANGUAGE) 15 | return cmds.optionVar(q=LangOpVar.op_var_name) 16 | 17 | @staticmethod 18 | def set(lang): 19 | cmds.optionVar(stringValue=(LangOpVar.op_var_name, lang)) -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from ..util.lang import Lang 3 | from ..util.unexpected_error import UnexpectedError 4 | from ..util import dict_util 5 | from ..util import in_view_message 6 | from .auto_skin_binder import AutoSkinBinder 7 | from .deformer_weights_exporter import DeformerWeightsExporter 8 | from .deformer_weights_importer import DeformerWeightsImporter 9 | from .lang_op_var import LangOpVar 10 | from .selection import Selection 11 | from . import mesh_parent_transform_and_skincluster_dict_getter 12 | 13 | 14 | @UnexpectedError.catch 15 | def create_xml(xmls_folder_path, xml_text_scroll_list): 16 | # NOTE: 名前被り対策はしていない 17 | selection = Selection() 18 | mesh_parent_transforms = selection.get_mesh_parent_transforms() 19 | 20 | mesh_parent_transform_and_skincluster_dict = mesh_parent_transform_and_skincluster_dict_getter.get_for_export(mesh_parent_transforms) 21 | 22 | deformer_weights_exporter = DeformerWeightsExporter(xmls_folder_path, xml_text_scroll_list) 23 | success = deformer_weights_exporter.export_xmls(mesh_parent_transform_and_skincluster_dict) 24 | 25 | __show_completion_message(success) 26 | 27 | 28 | @UnexpectedError.catch 29 | def copy_weights(xmls_folder_path, option_settings): 30 | selection = Selection() 31 | mesh_parent_transforms = selection.get_mesh_parent_transforms() 32 | mesh_parent_transform_and_skincluster_dict = mesh_parent_transform_and_skincluster_dict_getter.get_for_import(mesh_parent_transforms) 33 | 34 | if option_settings.auto_binding: 35 | auto_skin_binder = AutoSkinBinder(xmls_folder_path, mesh_parent_transform_and_skincluster_dict) 36 | auto_skin_binder.bind() 37 | mesh_parent_transform_and_skincluster_dict = auto_skin_binder.get_dict_skin_bound() # WARNING: 再代入 38 | 39 | transform_and_skincluster_dict_normalized = dict_util.delete_elements_with_the_value_none(mesh_parent_transform_and_skincluster_dict) 40 | if len(transform_and_skincluster_dict_normalized) == 0: 41 | __raise_copy_weights_err() 42 | 43 | deformer_weights_importer = DeformerWeightsImporter(xmls_folder_path, option_settings) 44 | success = deformer_weights_importer.import_xmls(transform_and_skincluster_dict_normalized) 45 | 46 | __show_completion_message(success) 47 | 48 | 49 | def __raise_copy_weights_err(): 50 | ja_JP = (u"""ウェイトのコピーができませんでした。以下を試してください。 51 | ・skinClusterを持つメッシュを選択する。 52 | ・自動バインドオプションを有効にする。 53 | ・同名のXMLファイルが作成する。""") 54 | 55 | en_US = ("""Could not copy weights. Try the following: 56 | -Select the meshes that has skinCluster. 57 | -Enable the Auto binding option. 58 | -Create XML file with the same name.""") 59 | 60 | err_msg = Lang.pack(ja_JP, en_US, LangOpVar.get()) 61 | raise UnexpectedError(err_msg) 62 | 63 | 64 | def __show_completion_message(success): 65 | if success: 66 | in_view_message.show(Lang.pack(u"処理が完了しました。", 'The process has been completed.', LangOpVar.get())) 67 | else: 68 | in_view_message.show(Lang.pack(u"処理を中断しました。", 'Processing has been interrupted.', LangOpVar.get())) 69 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/mesh_parent_transform_and_skincluster_dict_getter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from maya import cmds 4 | 5 | from ..util.lang import Lang 6 | from ..util.log import Log 7 | from ..util.node_type import NodeType 8 | from ..util.unexpected_error import UnexpectedError 9 | from ..util import dict_util 10 | from .lang_op_var import LangOpVar 11 | 12 | 13 | 14 | 15 | def get_for_export(mesh_parent_transforms): 16 | transform_and_skincluster_dict = __get(mesh_parent_transforms) 17 | __exists_skincluster_log(transform_and_skincluster_dict) 18 | normalized_dict = dict_util.delete_elements_with_the_value_none(transform_and_skincluster_dict) 19 | err_msg = Lang.pack(u'skinClusterを持つメッシュを選択してください。', 'Select the meshes that has skinCluster.', LangOpVar.get()) 20 | __exist_validation(normalized_dict, err_msg) 21 | return normalized_dict 22 | 23 | 24 | def get_for_import(mesh_parent_transforms): 25 | transform_and_skincluster_dict = __get(mesh_parent_transforms) 26 | err_msg = Lang.pack(u'メッシュを選択してください。', 'Please select the meshes.', LangOpVar.get()) 27 | __exist_validation(transform_and_skincluster_dict, err_msg) 28 | return transform_and_skincluster_dict 29 | 30 | 31 | def __exists_skincluster_log(transform_and_skincluster_dict): 32 | for transform, skin_cluster in transform_and_skincluster_dict.items(): 33 | if skin_cluster is None: 34 | err_msg = Lang.pack(u'{}にskinClusterが存在しないため、XMLを作成できませんでした。'.format(transform), 35 | 'XML could not be created because skinCluster does not exist in {}'.format(transform), 36 | LangOpVar.get()) 37 | Log.warning(err_msg) 38 | 39 | 40 | def __exist_validation(_dict, err_text): 41 | if _dict == {}: 42 | raise UnexpectedError(err_text) 43 | 44 | 45 | def __get(mesh_parent_transforms): 46 | """メッシュの親トランスフォームとSkinClusterの辞書を取得する""" 47 | return {t: NodeType.get_history(t, NodeType.SKIN_CLUSTER) 48 | for t in mesh_parent_transforms} 49 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/option_settings.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import os 4 | from textwrap import dedent 5 | 6 | from maya import cmds 7 | 8 | from ..util.log import Log 9 | from .const import Const 10 | 11 | class OptionSettings: 12 | def __init__(self, json_path): 13 | self.__json_path = json_path 14 | 15 | self.import_method = '' 16 | self.ignore_name = None 17 | self.normalize_weights = None 18 | self.auto_binding = None 19 | 20 | def create(self): 21 | if os.path.isfile(self.__json_path): 22 | return 23 | else: 24 | self.__create_init() 25 | 26 | def overwrite(self): 27 | new_option_settings_dict = { 28 | Const.KEY_IMPORT_METHOD : self.import_method, 29 | Const.KEY_IGNORE_NAME : self.ignore_name, 30 | Const.KEY_NORMALIZE_WEIGHTS : self.normalize_weights, 31 | Const.KEY_AUTO_BINDING : self.auto_binding, 32 | } 33 | self.__save_json(new_option_settings_dict) 34 | 35 | def load(self): 36 | with open(self.__json_path) as text_io_wrapper: 37 | option_settings_json = json.load(text_io_wrapper) 38 | 39 | self.import_method = option_settings_json[Const.KEY_IMPORT_METHOD] 40 | self.ignore_name = option_settings_json[Const.KEY_IGNORE_NAME] 41 | self.normalize_weights = option_settings_json[Const.KEY_NORMALIZE_WEIGHTS] 42 | self.auto_binding = option_settings_json[Const.KEY_AUTO_BINDING] 43 | 44 | def reset(self): 45 | self.__create_init() 46 | self.load() 47 | 48 | def __save_json(self, dict): 49 | text_io_wrapper = open(self.__json_path , 'w') 50 | json.dump(dict, text_io_wrapper, indent=4) 51 | 52 | def __create_init(self): 53 | init_option_settings_dict = { 54 | Const.KEY_IMPORT_METHOD : Const.METHOD_INDEX, 55 | Const.KEY_IGNORE_NAME : False, 56 | Const.KEY_NORMALIZE_WEIGHTS : True, 57 | Const.KEY_AUTO_BINDING : True, 58 | } 59 | self.__save_json(init_option_settings_dict) 60 | 61 | def set_import_method(self, import_method): 62 | self.import_method = import_method 63 | self.overwrite() 64 | 65 | def toggle_ignore_name(self): 66 | self.ignore_name = not self.ignore_name 67 | self.overwrite() 68 | 69 | def toggle_normalize_weights(self): 70 | self.normalize_weights = not self.normalize_weights 71 | self.overwrite() 72 | 73 | def toggle_auto_binding(self): 74 | self.auto_binding = not self.auto_binding 75 | self.overwrite() 76 | 77 | def log(self): 78 | text = dedent( 79 | u""" 80 | # OptionSettings 81 | # import_method ...{} 82 | # ignore_name ...{} 83 | # normalize_weights ...{} 84 | # auto_binding ...{} 85 | """ 86 | .format( 87 | self.import_method, 88 | self.ignore_name, 89 | self.normalize_weights, 90 | self.auto_binding) 91 | ) 92 | Log.log(text) 93 | 94 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/selection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from ..util.lang import Lang 5 | from ..util.node_type import NodeType 6 | from ..util.unexpected_error import UnexpectedError 7 | from .lang_op_var import LangOpVar 8 | 9 | class Selection: 10 | def __init__(self): 11 | self.__mesh_parent_transforms = [] 12 | 13 | def get_mesh_parent_transforms(self): 14 | sels = cmds.ls(sl=True) 15 | self.__exists_validation(sels) 16 | [self.__get_mesh_parent_transform_recursive(s) for s in sels] 17 | return self.__mesh_parent_transforms 18 | 19 | def __exists_validation(self, sels): 20 | if sels == []: 21 | raise UnexpectedError(Lang.pack(u'メッシュを選択してください。', 'Please select the meshes.', LangOpVar.get())) 22 | 23 | def __get_mesh_parent_transform_recursive(self, node): 24 | """meshの親transformを再帰的に走査する。 25 | 見つけた場合、メンバにappendする。 26 | """ 27 | if self.__is_mesh_parent_transform(node): 28 | self.__mesh_parent_transforms.append(node) 29 | 30 | children = self.__get_children(node) 31 | if children is None: 32 | return 33 | 34 | for child in children: 35 | if self.__is_mesh(child): # nodeの子供のmeshは走査する必要がないためcontinueする。 36 | continue 37 | self.__get_mesh_parent_transform_recursive(child) 38 | 39 | def __is_mesh_parent_transform(self, node): 40 | shape = self.__get_child_shape(node) 41 | if shape is None: 42 | return False 43 | if self.__is_mesh(shape) is False: # NOTE: カメラやロケータではないかの確認 44 | return False 45 | return True 46 | 47 | def __get_children(self, node): 48 | return cmds.listRelatives(node, children=True, noIntermediate=True) 49 | 50 | def __get_child_shape(self, node): 51 | try: 52 | shapes = cmds.listRelatives(node, shapes=True, noIntermediate=True) 53 | except TypeError: # NOTE: NoneではなくTypeErrorが返ってくることがある 54 | return None 55 | if shapes is None: 56 | return None 57 | if len(shapes) >= 2: 58 | err_msg = Lang.pack(u'{}がシェイプである子供を2つ以上持っています。'.format(node), 59 | '{} has two or more children that are mesh.'.format(node), 60 | LangOpVar.get()) 61 | raise UnexpectedError(err_msg) 62 | return shapes[0] 63 | 64 | def __is_mesh(self, node): 65 | return cmds.nodeType(node) == NodeType.MESH 66 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import inspect 3 | 4 | from .const import Const 5 | from .. import menu_adder 6 | from . import startup_command 7 | 8 | 9 | def add_menu(): 10 | # NOTE: シェルフに登録できるようにするために、起動コマンドはstringで渡す 11 | startup_command_str = inspect.getsource(startup_command) 12 | menu_adder.add(Const.TOOL_NAME, startup_command_str) -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/startup_command.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | def swio_startup_command(): 4 | swio_reload_modules() 5 | swio_show_window() 6 | 7 | 8 | def swio_reload_modules(): 9 | from humtools.skinweightsio import const, window, selection, auto_skin_binder, mesh_parent_transform_and_skincluster_dict_getter, \ 10 | deformer_weights_exporter, deformer_weights_importer, main, xml_text_scroll_list, option_settings, lang_op_var 11 | 12 | from humtools import module_reloader 13 | modules = [const, window, selection, auto_skin_binder, mesh_parent_transform_and_skincluster_dict_getter, \ 14 | deformer_weights_exporter, deformer_weights_importer, main, xml_text_scroll_list, option_settings, lang_op_var] 15 | module_reloader.reload_a_few_times(modules) 16 | 17 | 18 | def swio_show_window(): 19 | from humtools.skinweightsio.window import Window 20 | from humtools.skinweightsio.const import Const 21 | wnd = Window(Const.TOOL_NAME) 22 | wnd.show() 23 | 24 | 25 | if __name__ == '__main__': 26 | swio_startup_command() 27 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import subprocess 3 | import webbrowser 4 | 5 | from maya import cmds 6 | from maya.common.ui import LayoutManager 7 | 8 | from ..util.extensions import Extensions 9 | from ..util.hum_window_base import HumWindowBase 10 | from ..util.lang import Lang 11 | from ..util import path 12 | from .const import Const 13 | from .lang_op_var import LangOpVar 14 | from .option_settings import OptionSettings 15 | from .xml_text_scroll_list import XMLTextScrollList 16 | from . import main 17 | 18 | 19 | class Window(HumWindowBase): 20 | def __init__(self, tool_name): 21 | super(Window, self).__init__(tool_name) 22 | 23 | self.__xmls_folder_path = '' 24 | self.__option_settings_folder_path = '' 25 | 26 | self.__option_settings = None 27 | self.__xml_text_scroll_list = None 28 | 29 | def _create_window(self): 30 | cmds.window(self.tool_name, title=self.tool_name, menuBar=True, mxb=False, mnb=False) 31 | self.__set_paths() 32 | self.__create_this_app_data_folders() 33 | self.__setup_option_settings() 34 | self.__set_xml_text_scroll_list() 35 | self.__build_guis() 36 | 37 | def __set_paths(self): 38 | humtools_app_data_folder = path.get_humtools_app_data_folder_path() 39 | this_app_data_folder_path = path.combine([humtools_app_data_folder, self.tool_name]) 40 | self.__xmls_folder_path = path.combine([this_app_data_folder_path, Const.XMLS_FOLDER_NAME]) 41 | self.__option_settings_folder_path = path.combine([this_app_data_folder_path, Const.OPTION_SETTINGS_FOLDER_NAME]) 42 | 43 | def __create_this_app_data_folders(self): 44 | path.create_folder_recursive(self.__xmls_folder_path) 45 | path.create_folder_recursive(self.__option_settings_folder_path) 46 | 47 | def __setup_option_settings(self): 48 | json_name = '{}{}'.format(Const.OPTION_SETTINGS_JSON_NAME, Extensions.JSON) 49 | json_path = path.combine([self.__option_settings_folder_path, json_name]) 50 | self.__option_settings = OptionSettings(json_path) 51 | self.__option_settings.create() 52 | self.__option_settings.load() 53 | 54 | def __set_xml_text_scroll_list(self): 55 | self.__xml_text_scroll_list = XMLTextScrollList(Const.XML_TEXT_SCROLL_LIST_NAME, self.__xmls_folder_path) 56 | 57 | def __build_guis(self): 58 | lang = LangOpVar.get() 59 | self.__build_edit_menus(lang) 60 | self.__build_option_menus(lang) 61 | self.__build_help_menus(lang) 62 | self.__build_run_buttons(lang) 63 | self.__xml_text_scroll_list.build() 64 | self.__xml_text_scroll_list.load() 65 | 66 | def __build_edit_menus(self, lang): 67 | cmds.menu(l=Lang.pack(u'編集', 'Edit', lang), tearOff=True) 68 | cmds.menuItem(l=Lang.pack(u'XMLを削除', 'Delete XML', lang), c=lambda *args: self.__xml_text_scroll_list.delete_xml_files_and_items()) 69 | cmds.menuItem(l=Lang.pack(u'XMLをすべて削除', 'Delete all XML', lang), c=lambda *args: self.__xml_text_scroll_list.delete_xml_files_and_items(all=True)) 70 | 71 | def __build_option_menus(self, lang): 72 | cmds.menu(l=Lang.pack(u'オプション', 'Option', lang), tearOff=True) 73 | self.__build_import_mothod_radio_menu(lang) 74 | self.__menu_divider() 75 | labels = [Lang.pack(u'名前を無視', 'Ignore name', lang), 76 | Lang.pack(u'ウェイトを正規化', 'Normalize weights', lang), 77 | Lang.pack(u'自動バインド', 'Auto binding', lang)] 78 | cmds.menuItem(l=labels[0], checkBox=self.__option_settings.ignore_name, c=lambda *args: self.__option_settings.toggle_ignore_name()) 79 | cmds.menuItem(l=labels[1], checkBox=self.__option_settings.normalize_weights, c=lambda *args: self.__option_settings.toggle_normalize_weights()) 80 | self.__menu_divider() 81 | cmds.menuItem(l=labels[2], checkBox=self.__option_settings.auto_binding, c=lambda *args: self.__option_settings.toggle_auto_binding()) 82 | 83 | def __build_import_mothod_radio_menu(self, lang): 84 | cmds.menuItem(l=Lang.pack(u'マッピング方法', 'Import method', lang), subMenu=True, tearOff=True) 85 | cmds.radioMenuItemCollection() 86 | labels = [Lang.pack(u'インデックス', 'Index', lang), Lang.pack(u'オーバー', 'Over', lang), Lang.pack(u'二アレスト', 'Nearest', lang)] 87 | current_import_method = self.__option_settings.import_method 88 | cmds.menuItem(l=labels[0], radioButton=(current_import_method == Const.METHOD_INDEX), c=lambda *args: self.__option_settings.set_import_method(Const.METHOD_INDEX)) 89 | cmds.menuItem(l=labels[1], radioButton=(current_import_method == Const.METHOD_OVER), c=lambda *args: self.__option_settings.set_import_method(Const.METHOD_OVER)) 90 | cmds.menuItem(l=labels[2], radioButton=(current_import_method == Const.METHOD_NEAREST), c=lambda *args: self.__option_settings.set_import_method(Const.METHOD_NEAREST)) 91 | cmds.setParent('..', menu=True) 92 | 93 | def __build_help_menus(self, lang): 94 | cmds.menu(l=Lang.pack(u'ヘルプ', 'Help', lang), tearOff=True, helpMenu=True) 95 | cmds.menuItem(l=Lang.pack(u'ドキュメントを開く', 'Open document', lang), c=lambda *args: self.__open_document_in_webbrowser()) 96 | self.__menu_divider() 97 | cmds.menuItem(l=Lang.pack(u'オプション設定のリセット', 'Reset option settings', lang), c=lambda *args: self.__reset_option_settings()) 98 | self.__menu_divider() 99 | self.__build_language_radio_menu(lang) 100 | self.__menu_divider() 101 | cmds.menuItem(l=Lang.pack(u'XMLフォルダを開く', 'Open XML Folder', lang), c=lambda *args: self.__open_xmls_folder_in_explorer()) 102 | 103 | def __build_language_radio_menu(self, lang): 104 | cmds.menuItem(l=Lang.select(['Language', '言語', '语言'], lang), subMenu=True, tearOff=True) 105 | cmds.radioMenuItemCollection() 106 | labels = ['English', '日本語', '简体中文'] 107 | cmds.menuItem(l=labels[0], radioButton=(lang == Lang.en_US), c=lambda *args: self.__set_lang_option_var(Lang.en_US)) 108 | cmds.menuItem(l=labels[1], radioButton=(lang == Lang.ja_JP), c=lambda *args: self.__set_lang_option_var(Lang.ja_JP)) 109 | cmds.menuItem(l=labels[2], radioButton=(lang == Lang.zh_CN), c=lambda *args: self.__set_lang_option_var(Lang.zh_CN)) 110 | cmds.setParent('..', menu=True) 111 | 112 | def __set_lang_option_var(self, lang): 113 | LangOpVar.set(lang) 114 | super(Window, self).reload_window() 115 | 116 | def __open_document_in_webbrowser(self): 117 | url = 'https://github.com/Hum9183/MayaHumTools#skinweightsio' 118 | webbrowser.open(url) 119 | 120 | def __open_xmls_folder_in_explorer(self): 121 | subprocess.Popen(['explorer', self.__xmls_folder_path], shell=True) 122 | 123 | def __reset_option_settings(self): 124 | self.__option_settings.reset() 125 | super(Window, self).reload_window() # NOTE: GUIを更新するためにウィンドウをリロードする 126 | 127 | def __build_run_buttons(self, lang): 128 | with LayoutManager(cmds.rowLayout(nc=3)): 129 | cmds.button(l=Lang.pack(u'XMLの作成', 'Create XMLs', lang), 130 | ann=Lang.pack(u'メッシュを選択してボタンを押すとXMLファイルが生成されます。', 131 | 'Select the meshes and press this button to create XML files.', 132 | lang), 133 | w=128, 134 | c=lambda *args: self.__create_xml_wrapper()) 135 | cmds.text(l='', w=1) 136 | cmds.button(l=Lang.pack(u'ウェイトのコピー', 'Copy weights', lang), 137 | ann=Lang.pack(u'メッシュを選択してボタンを押すと、メッシュと同名のXMLを自動で探し出し、スキンウェイトをコピーします。', 138 | 'Select the meshes and press this button to automatically find the XML with the same name as the meshes and copy the skin weights.', 139 | lang), 140 | w=128, 141 | c=lambda *args: self.__copy_weights_wrapper()) 142 | 143 | def __create_xml_wrapper(self): 144 | main.create_xml(self.__xmls_folder_path, self.__xml_text_scroll_list) 145 | 146 | def __copy_weights_wrapper(self): 147 | main.copy_weights(self.__xmls_folder_path, self.__option_settings) 148 | 149 | def __menu_divider(self): 150 | cmds.menuItem(divider=True) 151 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/skinweightsio/xml_text_scroll_list.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | from maya import cmds 5 | 6 | from ..util.extensions import Extensions 7 | from ..util.lang import Lang 8 | from ..util.unexpected_error import UnexpectedError 9 | from ..util import path 10 | from .lang_op_var import LangOpVar 11 | 12 | 13 | class XMLTextScrollList: 14 | def __init__(self, name, xmls_folder_path): 15 | self.__name = name 16 | self.__xmls_folder_path = xmls_folder_path 17 | 18 | def build(self): 19 | cmds.paneLayout(configuration='horizontal2') # NOTE: singleだとうまくadjustしない 20 | cmds.textScrollList(self.__name, 21 | allowMultiSelection=True, 22 | deleteKeyCommand=lambda *args: self.delete_xml_files_and_items()) 23 | 24 | def load(self): 25 | files = os.listdir(self.__xmls_folder_path) 26 | xmls = [f for f in files if f.endswith(Extensions.XML)] 27 | cmds.textScrollList(self.__name, e=True, removeAll=True) 28 | cmds.textScrollList(self.__name, e=True, append=xmls) 29 | 30 | @UnexpectedError.catch 31 | def delete_xml_files_and_items(self, all=False): 32 | selItems = self.__get_selected_items(all) 33 | self.__delete_xml_files(selItems) 34 | self.load() 35 | 36 | def __get_selected_items(self, all=False): 37 | if all: 38 | selItems = cmds.textScrollList(self.__name, q=True, allItems=True) 39 | if selItems is None: 40 | raise UnexpectedError(Lang.pack(u'XMLが存在しません。', 'XML does not exist.', LangOpVar.get())) 41 | else: 42 | selItems = cmds.textScrollList(self.__name, q=True, selectItem=True) 43 | if selItems is None: 44 | raise UnexpectedError(Lang.pack(u'削除したいXMLを選択してください。', 'Select the XML you wish to delete.', LangOpVar.get())) 45 | return selItems 46 | 47 | def __delete_xml_files(self, selItems): 48 | xml_file_paths = [path.combine([self.__xmls_folder_path, item]) for item in selItems] 49 | [os.remove(f) for f in xml_file_paths] 50 | 51 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/startup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds, mel 3 | 4 | from .hum_tools_const import HumToolsConst 5 | from .blendshapedvertexmerger import setup as bsvm_setup 6 | from .blendshapedmesheditor import setup as bsme_setup 7 | from .skinweightsbugsearcher import setup as swbs_setup 8 | from .skinweightsio import setup as swio_setup 9 | 10 | 11 | def execute(): 12 | mel.eval(''' 13 | buildViewMenu MayaWindow|mainWindowMenu; 14 | setParent -menu "MayaWindow|mainWindowMenu"; 15 | ''') 16 | __add_menu() 17 | 18 | 19 | def __add_folder(): 20 | cmds.menuItem(divider=True) 21 | cmds.menuItem( 22 | HumToolsConst.HUM_TOOLS_FOLDER, 23 | label=HumToolsConst.HUM_TOOLS, 24 | subMenu=True, 25 | tearOff=True) 26 | 27 | 28 | def __add_menu(): 29 | __add_folder() 30 | bsvm_setup.add_menu() 31 | bsme_setup.add_menu() 32 | swio_setup.add_menu() 33 | swbs_setup.add_menu() 34 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Hum9183/MayaHumTools/0bca53971a389aa8b5cefb34931736aaf4fc462f/Contents/scripts/humtools/util/__init__.py -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/dict_util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | def delete_elements_with_the_value_none(_dict): 5 | """辞書の要素のうち、valueがNoneのものを削除する。 6 | 7 | Args: 8 | _dict (dict): 辞書 9 | 10 | Returns: 11 | dict: valueがNoneの要素を削除した辞書。 12 | """ 13 | is_dict = type(_dict) is dict 14 | if is_dict: 15 | return {k: v for k, v in _dict.items() if v is not None} 16 | else: 17 | return None -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/extensions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class Extensions: 5 | """拡張子の文字列""" 6 | JSON = '.json' 7 | XML = '.xml' -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/hum_window_base.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from abc import ABCMeta, abstractmethod 3 | 4 | from maya import cmds 5 | 6 | 7 | class HumWindowBase: 8 | """cmdsを用いたGUIウィンドウを使用する場合に継承する基底クラス。""" 9 | __metaclass__ = ABCMeta 10 | 11 | def __init__(self, tool_name): 12 | """ 13 | Args: 14 | tool_name (str): ツール名 15 | """ 16 | self.tool_name = tool_name 17 | 18 | def show(self): 19 | """ウィンドウを生成し、表示する""" 20 | self.__delete_window() 21 | self._create_window() 22 | cmds.showWindow() 23 | 24 | def reload_window(self): 25 | """ウィンドウをリロードする。""" 26 | cmds.evalDeferred(lambda *args: self.show()) 27 | 28 | @abstractmethod 29 | def _create_window(self): 30 | """ウィンドウを生成する。 31 | 抽象メソッドであるため、実装はすべて派生クラスに記述する。 32 | """ 33 | pass 34 | 35 | def __delete_window(self): 36 | """ウィンドウを削除する。""" 37 | if cmds.window(self.tool_name, ex=True): 38 | cmds.deleteUI(self.tool_name) -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/in_view_message.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from .log import Log 5 | 6 | 7 | def show(message, fadeStayTime=3000): 8 | highlighted_msg = u'{}'.format(message) 9 | cmds.inViewMessage(amg=highlighted_msg, pos='botCenter', fade=True, fst=fadeStayTime) 10 | Log.log(message) -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/lang.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from . import str_util 3 | from .list_util import try_get_item 4 | from ..hum_tools_const import HumToolsConst 5 | 6 | 7 | class Lang: 8 | en_US = 'en_US' 9 | en_US_index = 0 10 | ja_JP = 'ja_JP' 11 | ja_JP_index = 1 12 | zh_CN = 'zh_CN' 13 | zh_CN_index = 2 14 | 15 | @staticmethod 16 | def select(texts, select=None): 17 | """日本語の文字列と英語の文字列をパックし、適切な言語の文字列を返す。 18 | 19 | Args: 20 | texts (list[str]): テキストたちのリスト 21 | select (str, optional): 返す言語を指定する。デフォルトはNone。 22 | 23 | Returns: 24 | str: Mayaを立ち上げた言語の文字列を返す。select引数で指定した場合は、指定した言語の文字列を返す。 25 | 26 | Note: 27 | Mayaを立ち上げた言語やselectで指定した言語が日本語や英語、中国語以外の場合は、英語になる。 28 | """ 29 | if select is None: 30 | lang = HumToolsConst.LANGUAGE 31 | else: 32 | lang = select 33 | 34 | text = str_util.EMPTY 35 | success = False 36 | if lang == Lang.en_US: 37 | text, success = try_get_item(texts, Lang.en_US_index) 38 | elif lang == Lang.ja_JP: 39 | text, success = try_get_item(texts, Lang.ja_JP_index) 40 | elif lang == Lang.zh_CN: 41 | text, success = try_get_item(texts, Lang.zh_CN_index) 42 | else: 43 | text, success = try_get_item(texts, Lang.en_US_index) 44 | 45 | if success: 46 | return str_util.decode(text) 47 | else: 48 | return str_util.EMPTY 49 | 50 | # noinspection PyPep8Naming 51 | @staticmethod 52 | def pack(_ja_JP, _en_US, select=None): 53 | """日本語の文字列と英語の文字列をパックし、適切な言語の文字列を返す。 54 | 55 | Args: 56 | _ja_JP (str): 日本語の文字列。 57 | _en_US (str): 英語の文字列。 58 | select (str, optional): 返す言語を指定する。デフォルトはNone。 59 | 60 | Returns: 61 | str: Mayaを立ち上げた言語の文字列を返す。select引数で指定した場合は、指定した言語の文字列を返す。 62 | 63 | Note: 64 | Mayaを立ち上げた言語やselectで指定した言語が日本語や英語以外の場合は、英語になる。 65 | """ 66 | if select is None: 67 | lang = HumToolsConst.LANGUAGE 68 | else: 69 | lang = select 70 | 71 | if lang == Lang.ja_JP: 72 | return _ja_JP 73 | elif lang == Lang.en_US: 74 | return _en_US 75 | else: 76 | return _en_US 77 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/list_util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | def try_get_item(_list, index): 5 | """listへのgetitemを試みる。IndexErrorがraiseされた場合はFalseが返る 6 | 7 | Args: 8 | _list (list[any]): リスト 9 | index (int): getitemしたいインデックス 10 | 11 | Returns: 12 | element, success (any, bool): 要素、getitemが成功したかどうか 13 | """ 14 | if _list is False: 15 | return None, False 16 | 17 | try: 18 | return _list[index], True 19 | except IndexError: 20 | return None, False 21 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/log.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from ..hum_tools_const import HumToolsConst 5 | 6 | 7 | class Log: 8 | @staticmethod 9 | def log(message): 10 | print(u'{} : {}'.format(HumToolsConst.HUM_TOOLS, message)) 11 | 12 | @staticmethod 13 | def warning(message): 14 | cmds.warning(u'{} : {}'.format(HumToolsConst.HUM_TOOLS, message)) 15 | 16 | @staticmethod 17 | def error(message): 18 | cmds.error(u'{} : {}'.format(HumToolsConst.HUM_TOOLS, message)) -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/node_type.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from ..util.lang import Lang 5 | from ..util.readonly import metaclass 6 | from ..util.unexpected_error import UnexpectedError 7 | from ..hum_tools_const import HumToolsConst 8 | 9 | 10 | class NodeType(metaclass('NodeType')): 11 | BLEND_SHAPE = 'blendShape' 12 | MESH = 'mesh' 13 | SKIN_CLUSTER = 'skinCluster' 14 | TWEAK = 'tweak' 15 | 16 | @staticmethod 17 | def get_history(mesh, sort_type, raise_ex_if_is_none=False, lang=HumToolsConst.LANGUAGE): 18 | """メッシュから指定のヒストリを取得する。単体版。 19 | 20 | Args: 21 | mesh (str): メッシュ 22 | sort_type (str): 取得したいヒストリのタイプ。 23 | raise_ex_if_is_none (bool, optional): ヒストリがなかった場合に例外を出すかどうか。デフォルトはFalse。 24 | lang (str, optional): 例外に使用する文字列の言語。デフォルトはHumToolsConst.LANGUAGE。 25 | 26 | Returns: 27 | str: ヒストリ。ない場合はNoneを返す。 28 | """ 29 | histories = NodeType.__get_histories(mesh, sort_type, raise_ex_if_is_none, lang) 30 | if histories == []: 31 | return None 32 | return histories[0] 33 | 34 | @staticmethod 35 | def get_histories(mesh, sort_type, raise_ex_if_is_none=False, lang=HumToolsConst.LANGUAGE): 36 | """メッシュから指定のヒストリを取得する。複数版。 37 | 38 | Args: 39 | mesh (str): メッシュ 40 | sort_type (str): 取得したいヒストリのタイプ。 41 | raise_ex_if_is_none (bool, optional): ヒストリがなかった場合に例外を出すかどうか。デフォルトはFalse。 42 | lang (str, optional): 例外に使用する文字列の言語。デフォルトはHumToolsConst.LANGUAGE。 43 | 44 | Returns: 45 | list[str]: ヒストリ 46 | """ 47 | return NodeType.__get_histories(mesh, sort_type, raise_ex_if_is_none, lang) 48 | 49 | @staticmethod 50 | def __get_histories(mesh, sort_type, raise_ex_if_is_none, lang): 51 | """メッシュから指定のヒストリを取得する。本処理。 52 | 53 | Args: 54 | mesh (str): メッシュ 55 | sort_type (str): 取得したいヒストリのタイプ。 56 | raise_ex_if_is_none (bool): ヒストリがなかった場合に例外を出すかどうか。 57 | lang (str): 例外に使用する文字列の言語。 58 | 59 | Raises: 60 | UnexpectedError: ヒストリが無く、かつraise_ex_if_is_noneがTrueの場合に投げる。 61 | 62 | Returns: 63 | list[str]: ヒストリ 64 | """ 65 | histories = cmds.listHistory(mesh) 66 | sorted_histories = cmds.ls(histories, type=sort_type) 67 | if raise_ex_if_is_none: 68 | if sorted_histories == []: 69 | err_msg = Lang.pack(u'{}が存在しません。'.format(sort_type), 70 | '{} does not exist.'.format(sort_type), 71 | lang) 72 | raise UnexpectedError(err_msg) 73 | return sorted_histories -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/path.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import os 3 | 4 | from ..hum_tools_const import HumToolsConst 5 | 6 | 7 | def combine(paths): 8 | """パスのlistを結合し、パスを返す。 9 | 10 | Args: 11 | paths (list[str]): パスのlist 12 | 13 | Returns: 14 | str: \で結合したパス。 15 | """ 16 | return '\\'.join(paths) 17 | 18 | 19 | def get_humtools_app_data_folder_path(): 20 | """HumToolsのAppDataのフォルダパスを取得する。 21 | 22 | Returns: 23 | str: HumToolsのAppDataのフォルダパス 24 | """ 25 | home_path = '{}{}'.format(os.getenv('HOMEDRIVE'), os.getenv('HOMEPATH')) 26 | maya_hum_tools = 'Maya{}'.format(HumToolsConst.HUM_TOOLS) 27 | folder_names = [home_path, 'AppData', 'Local', maya_hum_tools] 28 | return combine(folder_names) 29 | 30 | 31 | def create_folder_recursive(path): 32 | """フォルダを作成する。中間ディレクトリもすべて作成する。 33 | 34 | Args: 35 | path (str): 作成するフォルダのパス 36 | """ 37 | if os.path.isdir(path): 38 | return 39 | 40 | folder_names = path.split('\\') 41 | 42 | # すべてのフォルダパスのリストを作成 43 | folder_paths = [] 44 | for _ in range(len(folder_names)): 45 | folder_path = combine(folder_names) 46 | folder_paths.append(folder_path) 47 | del folder_names[-1] 48 | 49 | # フォルダ作成 50 | for _folder_path in reversed(folder_paths): 51 | if os.path.isdir(_folder_path) is False: 52 | os.mkdir(_folder_path) -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/progress_window.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from maya import cmds 3 | 4 | from .lang import Lang 5 | 6 | class ProgressWindow: 7 | def __init__(self, maxLoopCount, description, lang): 8 | if maxLoopCount < 1: 9 | raise ProgressWindowException(Lang.pack(u'maxLoopCountは1以上を指定してください。', 'maxLoopCount must be at least 1.', lang)) 10 | self.__maxLoopCount = maxLoopCount 11 | self.__description = description 12 | 13 | def show(self): 14 | cmds.progressWindow(title="Progress", 15 | maxValue=self.__maxLoopCount, 16 | status=self.__description, 17 | isInterruptable=True) 18 | 19 | def is_cancelled(self): 20 | isCancelled = cmds.progressWindow(q=True, isCancelled=True) 21 | return True if isCancelled else False 22 | 23 | def is_greater_than_max(self): 24 | progressCount = cmds.progressWindow(q=True, progress=True) 25 | isGreaterThanMax = progressCount >= self.__maxLoopCount 26 | return True if isGreaterThanMax else False 27 | 28 | def next(self): 29 | cmds.progressWindow(e=True, step=1) 30 | 31 | def close(self): 32 | cmds.progressWindow(endProgress=True) 33 | 34 | 35 | class ProgressWindowException(Exception): 36 | pass 37 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/readonly.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import inspect 3 | from textwrap import dedent 4 | 5 | from maya import cmds 6 | 7 | 8 | def metaclass(class_name): 9 | """Typeのインスタンスを返す。 10 | metaclassをpy2でもpy3でも使うためにこのかたちになっている 11 | https://astropengu.in/posts/32/ 12 | """ 13 | return ReadonlyMeta( 14 | class_name, 15 | (object, ), 16 | {'__doc__': ReadonlyMeta.__doc__}) 17 | 18 | 19 | class RebindError(Exception): 20 | pass 21 | 22 | 23 | class ReadonlyMeta(type): 24 | def __setattr__(self, name, value): 25 | if name in self.__dict__.keys(): 26 | file_name = get_file_name(inspect.stack()) 27 | func_name = get_func_name(inspect.stack()) 28 | raise RebindError(get_waring_str( 29 | file_name, func_name, name, value)) 30 | else: 31 | self.__dict__[name] = value 32 | 33 | 34 | def get_file_name(stack_records): 35 | if int(cmds.about(v=True)) >= 2022: # NOTE: 循環参照回避のためConstのメソッドは使わない 36 | return stack_records[1].filename 37 | else: 38 | return stack_records[1][1] 39 | 40 | 41 | def get_func_name(stack_records): 42 | if int(cmds.about(v=True)) >= 2022: 43 | return stack_records[1].function 44 | else: 45 | return stack_records[1][3] 46 | 47 | 48 | def get_waring_str(file_name, func_name, attr_name, attr_value): 49 | return u'再代入例外' + dedent( 50 | u""" 51 | # 再代入例外: ReadonlyMeta使用しているクラスはアトリビュートに再代入できません。 52 | # Info: 53 | # ファイル...'{}' 54 | # 関数...'{}' 55 | # アトリビュート...'{}' 56 | # 再代入しようとした値...'{}' 57 | //////////////////// Rebind Error /////////////////////// 58 | // Classes using ReadonlyMeta cannot rebind attribute. // 59 | ///////////////////////////////////////////////////////// 60 | """.format(file_name, func_name, attr_name, attr_value)) 61 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/selection_recorder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import functools 3 | 4 | from maya import cmds 5 | 6 | from ..util.readonly import metaclass 7 | 8 | 9 | class SelectionRecorder(metaclass('SelectionRecorder')): 10 | def __init__(self, selections): 11 | self.__selections = selections 12 | 13 | @staticmethod 14 | def record(function): 15 | """選択状態を記憶し元に戻すデコレータ""" 16 | @functools.wraps(function) 17 | def wrapper(*args, **keywords): 18 | sel_rec = SelectionRecorder.__record() 19 | returnValue = function(*args, **keywords) 20 | sel_rec.restore() 21 | return returnValue 22 | return wrapper 23 | 24 | @staticmethod 25 | def __record(): 26 | sels = cmds.ls(sl=True) 27 | return SelectionRecorder(sels) 28 | 29 | def restore(self): 30 | print(self.__selections) 31 | cmds.select(self.__selections, r=True) 32 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/str_util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | EMPTY = '' 3 | 4 | 5 | def decode(string): 6 | try: 7 | return string.decode('utf-8') 8 | except UnicodeEncodeError: 9 | return string 10 | except AttributeError: 11 | return string 12 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/time_recorder.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import functools 3 | import time 4 | 5 | from .log import Log 6 | 7 | 8 | class TimeRecorder: 9 | @staticmethod 10 | def wraps(function): 11 | """処理時間を記録するデコレータ""" 12 | @functools.wraps(function) 13 | def wrapper(*args, **keywords): 14 | timeRecorder = TimeRecorder() 15 | timeRecorder.start() 16 | returnValue = function(*args, **keywords) 17 | timeRecorder.end() 18 | Log.log(u"実行時間: {}秒".format(timeRecorder.getDeltaTime())) 19 | return returnValue 20 | return wrapper 21 | 22 | def __init__(self): 23 | self.__startTime = 0 24 | self.__endTime = 0 25 | 26 | def start(self): 27 | self.__startTime = time.time() 28 | 29 | def end(self): 30 | self.__endTime = time.time() 31 | 32 | def getDeltaTime(self): 33 | return self.__endTime - self.__startTime 34 | -------------------------------------------------------------------------------- /Contents/scripts/humtools/util/unexpected_error.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import functools 3 | 4 | from maya import cmds 5 | 6 | from .log import Log 7 | 8 | 9 | class UnexpectedError(Exception): 10 | @staticmethod 11 | def catch(function): 12 | '''UnexpectedErrorを検知するデコレータ''' 13 | @functools.wraps(function) 14 | def wrapper(*args, **keywords): 15 | try: 16 | returnValue = function(*args, **keywords) 17 | except UnexpectedError as e: 18 | e.log() 19 | return 20 | return returnValue 21 | return wrapper 22 | 23 | def log(self): 24 | self.__output_scripteditor() 25 | self.__show_inviewmessage() 26 | 27 | def __output_scripteditor(self): 28 | msg = u'{}'.format(self) 29 | Log.log(msg) 30 | 31 | def __show_inviewmessage(self): 32 | highlighted_msg = u'{}'.format(self) 33 | cmds.inViewMessage(amg=highlighted_msg, pos='botCenter', fade=True) 34 | -------------------------------------------------------------------------------- /Contents/scripts/userSetup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from textwrap import dedent 3 | 4 | import maya.cmds as cmds 5 | 6 | 7 | def __register_humtools_startup(): 8 | cmd = dedent( 9 | """ 10 | import humtools.startup 11 | humtools.startup.execute() 12 | """) 13 | cmds.evalDeferred(cmd) 14 | 15 | 16 | if __name__ == '__main__': 17 | try: 18 | __register_humtools_startup() 19 | 20 | except Exception as e: 21 | import traceback 22 | traceback.print_exc() 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Hum9183 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 | -------------------------------------------------------------------------------- /PackageContents.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MayaHumTools 2 | 3 | Mayaの便利なツール群 4 | 5 | ## インストール方法 6 | C:\Program Files\Autodesk\ApplicationPluginsにクローンするか、ZIPファイルを解凍して配置してください。 7 | 8 | ![image](https://user-images.githubusercontent.com/117564304/218305703-95018c61-2cd5-41b5-97c7-e79e37ad53ae.png) 9 | 10 | ウィンドウタブ内にHumToolsという項目が追加されたらOKです。 11 | 12 |
13 | 14 | ## BlendShapedVertexMerger 15 | 16 | ### バージョン 17 | Maya2018, 2022で正常な動作を確認しています。
18 | 19 | ### 概要 20 | ブレンドシェイプが設定してあるメッシュの頂点をマージするツールです。
21 | 普通の編集なら壊れてしまうところを、壊さずにマージができます。 22 | 23 | ### 使い方 24 | マージしたい頂点を選択した状態で「Merge vertex」を押すとマージが実行されます。
25 | 26 | ![BlendShapedVertexMerger_Demo](https://user-images.githubusercontent.com/117564304/219054782-81f45306-b419-4002-bc55-533d91a785a1.gif) 27 | 28 | ### ツール詳細 29 | - マージの種類は「頂点をセンターにマージ」を採用しています。
30 | - 「ブレンドシェイプを再構築する」オプションをオンにすると、ブレンドシェイプノードを一度削除し再構築します(Maya2022以降ではオン推奨)
31 | 32 |
33 | 34 | ## BlendShapedMeshEditor 35 | 36 | ### バージョン 37 | Maya2018, 2022で正常な動作を確認しています。
38 | 39 | ### 概要 40 | ブレンドシェイプが設定してあるメッシュにおいて、
41 | コンポーネントの移動やマルチカットを使った頂点の追加ができるツールです。
42 | 普通の編集なら壊れてしまうところを、壊さずに編集ができます。 43 | 44 | ### 使い方 45 | 1. 編集したいメッシュを選択した状態で「Start editing」を押します。
46 | 2. ボタンが黄色くなったら編集が可能になります。各々行いたい編集を行います。
47 | 3. 編集が終わったら、編集したメッシュを選択した状態で「Finish editing」を押します。
48 | 49 | ![BlendShapedMeshEditor_Demo](https://user-images.githubusercontent.com/117564304/219922084-c3bb9509-51e0-4688-b48a-026078c24f20.gif) 50 | 51 | ### 注意点 52 | マルチカットの編集には種類がありますが、**polySplit**にのみ対応しています。
53 | **polyCut**には非対応です(ビューポート上に線を引くように切る方法)
54 | 編集の種類はヒストリを見てご確認ください。 55 | 56 | ![bsme_1](https://user-images.githubusercontent.com/117564304/219922179-d44bbf42-ae6b-4c61-8e14-dc7f29fe3ffd.png) 57 | 58 | ### ツール詳細 59 | - 「Start editing」を押すと、リビルド済のターゲットメッシュが削除されます。
60 | これは、ターゲットメッシュが存在していると、コンポーネントの移動情報をターゲットに反映することができないためです。
61 | また、Maya2022以降ではtweakノードを追加しています(「Finish editing」を押したときに削除されます)
62 | 詳しくは 63 | [Autodesk公式ドキュメント](https://knowledge.autodesk.com/ja/support/maya/learn-explore/caas/CloudHelp/cloudhelp/2022/JPN/Maya-WhatsNewPR/files/GUID-C6BC495C-F1B6-4370-AC2D-24CA4B4AAF9B-htm.html) 64 | をご参照ください。 65 | 66 | - 「Finish editing」を押すと、あるNonDeformerHistory(当ツールではUVの移動)を生成し、NonDeformerHistoryを削除します。
67 | これを行うことにより、ベースメッシュでのコンポーネントの移動情報がターゲットたちにも反映されます。
68 | この処理はMayaが自動で行う処理であり、メッシュの頂点数やブレンドシェイプの数に合わせて処理時間が長くなる可能性があります。 69 | 70 |
71 | 72 | ## SkinWeightsIO 73 | 74 | ### バージョン 75 | Maya2018, 2022で正常な動作を確認しています。
76 | 77 | ### 概要 78 | スキンウェイト情報をXMLファイルで書き出したり読み込んだりできるツールです。
79 | 機能としてはMaya標準の「デフォーム」→「ウェイトを書き出し/読み込み」の機能と同等です。
80 | 81 | ![deformer_weights](https://github.com/Hum9183/MayaHumTools/assets/117564304/33685a42-bfc0-4422-88e0-cfa0f0e854cc) 82 | 83 | ### 使い方 84 | 1. ウェイト情報を保存したいメッシュを選択した状態で「XMLの作成」を押します。
85 | 2. ウェイトをコピーしたいメッシュを選択した状態で「ウェイトのコピー」を押します。
86 | 87 | ![SkinWeightsIO_Demo](https://github.com/Hum9183/MayaHumTools/assets/117564304/f61e87c5-7333-468f-a107-c508a64dc841) 88 | 89 | ### ツール詳細 90 | - 「ウェイトのコピー」に使用するXMLファイルは、選択しているメッシュ名から自動で選ばれます。XMLファイルを指定することはできません。 91 | - メッシュは複数選択可能です。親となるtransform(グループ)ノードを選択して実行することも可能です。 92 | - XMLファイルの生成時、同名のXMLファイルが存在する場合、上書きします。 93 | - 「オプション」メニューからウェイトコピーのオプションを設定できます。 94 | - 「ヘルプ」メニューから言語の設定が可能です。 95 | 96 |
97 | 98 | ## SkinWeightsBugSearcher 99 | 100 | ### バージョン 101 | Maya2018, 2022で正常な動作を確認しています。
102 | 103 | ### 概要 104 | スキンウェイトのバグを探し出すツールです。
105 | 106 | ### 使い方 107 | 1. ウェイトのバグを探したいメッシュを選択した状態で「ウェイトバグを探す」を押します。
108 | 2. 処理が完了したら、ツール下部のテキストリストに不具合がリストアップされます。
109 | 3. テキストリストをクリックして、バグがある頂点を選択します。
110 | 4. お好みの方法でウェイトを修正します。
111 | 112 | ![skin_weights_bug_searcher_demo](https://github.com/Hum9183/MayaHumTools/assets/117564304/b6d88345-cc2f-4265-81a8-c6b6686ebae8) 113 | 114 | ### ウェイトバグ判定のロジック 115 | 判定は一つ一つの頂点に対して行われます。
116 | 処理が始まると「対象の頂点」とその「周囲の頂点」のウェイト情報を取得します。
117 | 対象の頂点のウェイトのうち「周囲の頂点には振られていないインフルエンスのウェイト」が検出された場合、バグ判定となります。
118 | 119 | ### ツール詳細 120 | - メッシュは複数選択可能です。親となるtransform(グループ)ノードを選択して実行することも可能です。 121 | - 処理時間はホリゴン数によって決まります。ポリゴン数が多けれ多いほど時間がかかります。 122 | - ウェイトのバグを100%検出するものではありません。また、振り方的に正しくてもツール上、バグ判定になってしまうことがあります。 123 | - 「編集」メニューからテキストリストのバグ項目を削除できます。修正し終えたものや、バグではなかったものを削除するときに使用します。 124 | - テキストリストのバグ項目をダブルクリックすると、バグウェイトが振られているジョイントを選択します。 125 | - 「ヘルプ」メニューから言語の設定が可能です。 126 | --------------------------------------------------------------------------------