├── .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 | 
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 | 
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 | 
50 |
51 | ### 注意点
52 | マルチカットの編集には種類がありますが、**polySplit**にのみ対応しています。
53 | **polyCut**には非対応です(ビューポート上に線を引くように切る方法)
54 | 編集の種類はヒストリを見てご確認ください。
55 |
56 | 
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 | 
82 |
83 | ### 使い方
84 | 1. ウェイト情報を保存したいメッシュを選択した状態で「XMLの作成」を押します。
85 | 2. ウェイトをコピーしたいメッシュを選択した状態で「ウェイトのコピー」を押します。
86 |
87 | 
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 | 
113 |
114 | ### ウェイトバグ判定のロジック
115 | 判定は一つ一つの頂点に対して行われます。
116 | 処理が始まると「対象の頂点」とその「周囲の頂点」のウェイト情報を取得します。
117 | 対象の頂点のウェイトのうち「周囲の頂点には振られていないインフルエンスのウェイト」が検出された場合、バグ判定となります。
118 |
119 | ### ツール詳細
120 | - メッシュは複数選択可能です。親となるtransform(グループ)ノードを選択して実行することも可能です。
121 | - 処理時間はホリゴン数によって決まります。ポリゴン数が多けれ多いほど時間がかかります。
122 | - ウェイトのバグを100%検出するものではありません。また、振り方的に正しくてもツール上、バグ判定になってしまうことがあります。
123 | - 「編集」メニューからテキストリストのバグ項目を削除できます。修正し終えたものや、バグではなかったものを削除するときに使用します。
124 | - テキストリストのバグ項目をダブルクリックすると、バグウェイトが振られているジョイントを選択します。
125 | - 「ヘルプ」メニューから言語の設定が可能です。
126 |
--------------------------------------------------------------------------------