usagi-picker 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usagi_picker-0.1.0/LICENSE +21 -0
- usagi_picker-0.1.0/PKG-INFO +72 -0
- usagi_picker-0.1.0/README.md +49 -0
- usagi_picker-0.1.0/pyproject.toml +45 -0
- usagi_picker-0.1.0/setup.cfg +4 -0
- usagi_picker-0.1.0/src/usagi_picker/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/constants.py +119 -0
- usagi_picker-0.1.0/src/usagi_picker/core/__init__.py +19 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/__init__.py +73 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/abstract.py +68 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/context_menu.py +55 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/deselect.py +22 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/key_all.py +15 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/key_customs.py +20 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/key_rotate.py +15 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/key_scale.py +15 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/key_translate.py +15 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/match_ik_fk.py +16 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/mirror.py +19 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/reset_all.py +19 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/reset_customs.py +19 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/reset_rotate.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/reset_scale.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/reset_translate.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/select.py +22 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/select_add.py +22 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/select_opposite.py +16 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/toggle_asset.py +19 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/toggle_controllers.py +24 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/toggle_nodes_visibility.py +17 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/toggle_shapes_visibility.py +18 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/toggle_smooth.py +20 -0
- usagi_picker-0.1.0/src/usagi_picker/core/actions/toggle_textures.py +32 -0
- usagi_picker-0.1.0/src/usagi_picker/core/graph.py +118 -0
- usagi_picker-0.1.0/src/usagi_picker/core/picker_data_node.py +11 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/__init__.py +17 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/abstract.py +57 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/backdrop.py +11 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/button.py +16 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/folder.py +16 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/label.py +16 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/select_shape.py +16 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/settings.py +27 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/shape.py +31 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/tab.py +11 -0
- usagi_picker-0.1.0/src/usagi_picker/core/widgets/toggle.py +24 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/__init__.py +12 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/alignments.py +71 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/picker_scene_items.py +358 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/__init__.py +98 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/abstract.py +187 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/backdrop.py +17 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/button.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/folder.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/label.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/select_shape.py +17 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/settings.py +50 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/shape.py +106 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/tab.py +16 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/core/widgets/toggle.py +39 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/picker_editor.py +890 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/widgets/__init__.py +3 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/widgets/action_arg_widget.py +105 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/widgets/action_edit_widget.py +234 -0
- usagi_picker-0.1.0/src/usagi_picker/editor/widgets/edit_modifier_widget.py +53 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/axis.py +68 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/color.py +72 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/contexts.py +22 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/data/__init__.py +10 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/decorators.py +43 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/exceptions.py +9 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/ik_fk.py +237 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/maya_enum_values.py +72 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/mirror.py +87 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/naming.py +127 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/nodes.py +23 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/nurbs.py +170 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/omutils.py +24 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/orig.py +67 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/sets.py +13 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/textures.py +20 -0
- usagi_picker-0.1.0/src/usagi_picker/maya_utils/transforms.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/__init__.py +18 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/dialogs/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/dialogs/select_asset_dialog.py +119 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/graphics/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/graphics/items/__init__.py +15 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/graphics/items/abstract.py +89 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/graphics/items/backdrop.py +30 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/graphics/items/select_shape.py +59 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/graphics/items/shape.py +21 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/graphics/picker_graphic_view.py +119 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/usagi_picker.py +122 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/widgets/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/widgets/main_widget.py +230 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/widgets/picker_tab_widget.py +71 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/widgets/toolbar_button.py +89 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/widgets/toolbar_folder.py +20 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/widgets/toolbar_label.py +15 -0
- usagi_picker-0.1.0/src/usagi_picker/picker/widgets/toolbar_toggle_switch.py +27 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/dialogs/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/maya_utils.py +11 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/qt.py +106 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/utils.py +115 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/__init__.py +1 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/array_widget.py +168 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/blend_widget.py +166 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/flow_layout.py +88 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/folder.py +156 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/maya_node_selector.py +100 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/maya_viewport_widget.py +89 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/path_line_edit.py +86 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/rules.py +17 -0
- usagi_picker-0.1.0/src/usagi_picker/qsagi/widgets/toggle_switch.py +93 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/__init__.py +8 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/3d-cube.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/add-dark.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/add.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/align-center.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/checked.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/click.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/close-hover.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/close.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/collapse.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/copy.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/down_arrow.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/duplicate.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/expand.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/flip.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/folder-white.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/format.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/office-push-pin.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/paste.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/reload.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/run.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/trashbin.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/icons/up_arrow.png +0 -0
- usagi_picker-0.1.0/src/usagi_picker/resources/styles/style.qss +520 -0
- usagi_picker-0.1.0/src/usagi_picker/tools/__init__.py +0 -0
- usagi_picker-0.1.0/src/usagi_picker/tools/mirror_tool/__init__.py +10 -0
- usagi_picker-0.1.0/src/usagi_picker/tools/mirror_tool/ui.py +410 -0
- usagi_picker-0.1.0/src/usagi_picker/tools/nurbs_shape_tool/__init__.py +10 -0
- usagi_picker-0.1.0/src/usagi_picker/tools/nurbs_shape_tool/ui.py +550 -0
- usagi_picker-0.1.0/src/usagi_picker.egg-info/PKG-INFO +72 -0
- usagi_picker-0.1.0/src/usagi_picker.egg-info/SOURCES.txt +148 -0
- usagi_picker-0.1.0/src/usagi_picker.egg-info/dependency_links.txt +1 -0
- usagi_picker-0.1.0/src/usagi_picker.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 MF_ZOOL
|
|
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.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: usagi-picker
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Animation picker for Autodesk Maya
|
|
5
|
+
License: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/zool-rig/usagi-picker
|
|
7
|
+
Project-URL: Repository, https://github.com/zool-rig/usagi-picker
|
|
8
|
+
Project-URL: Documentation, https://github.com/zool-rig/usagi-picker/tree/main/python/bozon-ui
|
|
9
|
+
Project-URL: Issues, https://github.com/zool-rig/usagi-picker/issues
|
|
10
|
+
Keywords: animation,maya,picker,rig,usagi,anim
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: End Users/Desktop
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
17
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
18
|
+
Classifier: Topic :: Software Development :: User Interfaces
|
|
19
|
+
Requires-Python: >=3.7
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# Usagi Picker 🐇
|
|
25
|
+
|
|
26
|
+
[](LICENSE)
|
|
27
|
+
[](https://www.python.org)
|
|
28
|
+
[](https://www.autodesk.com/products/maya/overview)
|
|
29
|
+
[](https://badge.fury.io/py/usagi-picker)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Table of Contents
|
|
34
|
+
|
|
35
|
+
- [Installation](#installation)
|
|
36
|
+
- [Features](#features)
|
|
37
|
+
- [Usage](#usage)
|
|
38
|
+
- [Project Status](#project-status)
|
|
39
|
+
- [Contributing](#contributing)
|
|
40
|
+
- [License](#license)
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
Usagi is a modern open source animation picker for Autodesk Maya.
|
|
45
|
+
|
|
46
|
+

|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
Clone this repository and drop the `drag_n_drop_install.py` file in the maya's viewport.
|
|
51
|
+
|
|
52
|
+
Or install it with pip
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
mayapy -m pip install usagi-picker
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Features
|
|
59
|
+
|
|
60
|
+
* Create a picker for any rigs using maya objects as templates.
|
|
61
|
+
|
|
62
|
+

|
|
63
|
+
|
|
64
|
+
* Navigate in the picker like in a 2D scene.
|
|
65
|
+
|
|
66
|
+
## Usage
|
|
67
|
+
|
|
68
|
+
## Project Status
|
|
69
|
+
|
|
70
|
+
## Contributing
|
|
71
|
+
|
|
72
|
+
## License
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Usagi Picker 🐇
|
|
2
|
+
|
|
3
|
+
[](LICENSE)
|
|
4
|
+
[](https://www.python.org)
|
|
5
|
+
[](https://www.autodesk.com/products/maya/overview)
|
|
6
|
+
[](https://badge.fury.io/py/usagi-picker)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Table of Contents
|
|
11
|
+
|
|
12
|
+
- [Installation](#installation)
|
|
13
|
+
- [Features](#features)
|
|
14
|
+
- [Usage](#usage)
|
|
15
|
+
- [Project Status](#project-status)
|
|
16
|
+
- [Contributing](#contributing)
|
|
17
|
+
- [License](#license)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Usagi is a modern open source animation picker for Autodesk Maya.
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
Clone this repository and drop the `drag_n_drop_install.py` file in the maya's viewport.
|
|
28
|
+
|
|
29
|
+
Or install it with pip
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
mayapy -m pip install usagi-picker
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
* Create a picker for any rigs using maya objects as templates.
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
* Navigate in the picker like in a 2D scene.
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
## Project Status
|
|
46
|
+
|
|
47
|
+
## Contributing
|
|
48
|
+
|
|
49
|
+
## License
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "usagi-picker"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Animation picker for Autodesk Maya"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.7"
|
|
7
|
+
license = { text = "MIT" }
|
|
8
|
+
keywords = ["animation", "maya", "picker", "rig", "usagi", "anim"]
|
|
9
|
+
classifiers = [
|
|
10
|
+
"Development Status :: 3 - Alpha",
|
|
11
|
+
"Intended Audience :: End Users/Desktop",
|
|
12
|
+
"License :: OSI Approved :: MIT License",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
15
|
+
"Programming Language :: Python :: 3.7",
|
|
16
|
+
"Topic :: Multimedia :: Graphics",
|
|
17
|
+
"Topic :: Software Development :: User Interfaces",
|
|
18
|
+
]
|
|
19
|
+
dependencies = []
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Homepage = "https://github.com/zool-rig/usagi-picker"
|
|
23
|
+
Repository = "https://github.com/zool-rig/usagi-picker"
|
|
24
|
+
Documentation = "https://github.com/zool-rig/usagi-picker/tree/main/python/bozon-ui"
|
|
25
|
+
Issues = "https://github.com/zool-rig/usagi-picker/issues"
|
|
26
|
+
|
|
27
|
+
[build-system]
|
|
28
|
+
requires = ["setuptools>=61.0"]
|
|
29
|
+
build-backend = "setuptools.build_meta"
|
|
30
|
+
|
|
31
|
+
[tool.setuptools]
|
|
32
|
+
include-package-data = true
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.package-data]
|
|
38
|
+
usagi_picker = [
|
|
39
|
+
"resources/styles/*.qss",
|
|
40
|
+
"resources/icons/*",
|
|
41
|
+
"resources/icons/**/*",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[tool.ruff.lint.per-file-ignores]
|
|
45
|
+
"**/__init__.py" = ["F401"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from usagi_picker.maya_utils.color import Color
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Naming convention
|
|
9
|
+
VALID_NAME_CHARS: str = r"^[a-zA-Z0-9_:]+$"
|
|
10
|
+
SIDE_MIRROR_MAP: dict[str, str] = {
|
|
11
|
+
"L": "R",
|
|
12
|
+
"R": "L",
|
|
13
|
+
"C": "C"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# Suffixes
|
|
17
|
+
ORIG_SUFFIX: str = "orig"
|
|
18
|
+
GROUP_SUFFIX: str = "grp"
|
|
19
|
+
PICKER_SUFFIX: str = "pkr"
|
|
20
|
+
|
|
21
|
+
# Groups
|
|
22
|
+
ROOT_GROUP: str = f"root__{GROUP_SUFFIX}"
|
|
23
|
+
PICKER_GROUP: str = f"picker__{GROUP_SUFFIX}"
|
|
24
|
+
SETTINGS_NODE = f"settings__{GROUP_SUFFIX}"
|
|
25
|
+
|
|
26
|
+
# Attributes
|
|
27
|
+
PICKER_DATA_ATTRIBUTE: str = "usagi_picker_data"
|
|
28
|
+
IS_TAB_TAG: str = "usagi_picker_is_tab"
|
|
29
|
+
IS_BACKDROP_TAG: str = "usagi_picker_is_backdrop"
|
|
30
|
+
IS_SETTINGS_TAG: str = "usagi_picker_is_settings"
|
|
31
|
+
IS_SELECT_SHAPE_TAG: str = "usagi_picker_is_select_shape"
|
|
32
|
+
IS_BUTTON_TAG: str = "usagi_picker_is_button"
|
|
33
|
+
IS_LABEL_TAG: str = "usagi_picker_is_label"
|
|
34
|
+
IS_TOGGLE_TAG: str = "usagi_picker_is_toggle"
|
|
35
|
+
IS_FOLDER_TAG: str = "usagi_picker_is_folder"
|
|
36
|
+
ACTION_TAG: str = "usagi_picker_actions"
|
|
37
|
+
TEXT_TAG: str = "usagi_picker_text"
|
|
38
|
+
COLOR_TAG: str = "usagi_picker_color"
|
|
39
|
+
OUTLINE_COLOR_TAG: str = "usagi_picker_outline_color"
|
|
40
|
+
SELCTION_COLOR_TAG: str = "usagi_picker_selection_color"
|
|
41
|
+
MIRROR_ATTRIBUTE = "mirror_behavior"
|
|
42
|
+
|
|
43
|
+
# Misc
|
|
44
|
+
PICKER_EXTENSION: str = ".upkr"
|
|
45
|
+
IDENTITY_MATRIX: list[float] = [
|
|
46
|
+
1.0,
|
|
47
|
+
0.0,
|
|
48
|
+
0.0,
|
|
49
|
+
0.0,
|
|
50
|
+
0.0,
|
|
51
|
+
1.0,
|
|
52
|
+
0.0,
|
|
53
|
+
0.0,
|
|
54
|
+
0.0,
|
|
55
|
+
0.0,
|
|
56
|
+
1.0,
|
|
57
|
+
0.0,
|
|
58
|
+
0.0,
|
|
59
|
+
0.0,
|
|
60
|
+
0.0,
|
|
61
|
+
1.0,
|
|
62
|
+
]
|
|
63
|
+
SHAPE_DEFAULT_RADIUS: int = 30
|
|
64
|
+
SHAPE_DEFAULT_COLOR: Color = Color(0, 1, 0)
|
|
65
|
+
SELECT_SHAPE_DEFAULT_ACTIONS: dict[str, Any] = {
|
|
66
|
+
"1": {"name": "Select", "args": [[]], "mandatory": True},
|
|
67
|
+
"2": {
|
|
68
|
+
"name": "Context Menu",
|
|
69
|
+
"args": [
|
|
70
|
+
[],
|
|
71
|
+
[
|
|
72
|
+
"Toggle Nodes Visibility",
|
|
73
|
+
"Toggle Shapes Visibility",
|
|
74
|
+
"Separator",
|
|
75
|
+
"Reset All",
|
|
76
|
+
"Reset Translate",
|
|
77
|
+
"Reset Rotate",
|
|
78
|
+
"Reset Scale",
|
|
79
|
+
"Reset Customs",
|
|
80
|
+
"Separator",
|
|
81
|
+
"Mirror",
|
|
82
|
+
# "Select Opposite",
|
|
83
|
+
"Separator",
|
|
84
|
+
"Key All",
|
|
85
|
+
"Key Translate",
|
|
86
|
+
"Key Rotate",
|
|
87
|
+
"Key Scale",
|
|
88
|
+
"Key Customs",
|
|
89
|
+
],
|
|
90
|
+
],
|
|
91
|
+
"mandatory": False,
|
|
92
|
+
},
|
|
93
|
+
"67108866": {
|
|
94
|
+
"name": "Context Menu",
|
|
95
|
+
"args": [
|
|
96
|
+
[],
|
|
97
|
+
[
|
|
98
|
+
"Toggle Nodes Visibility",
|
|
99
|
+
"Toggle Shapes Visibility",
|
|
100
|
+
"Separator",
|
|
101
|
+
"Reset All",
|
|
102
|
+
"Reset Translate",
|
|
103
|
+
"Reset Rotate",
|
|
104
|
+
"Reset Scale",
|
|
105
|
+
"Reset Customs",
|
|
106
|
+
"Separator",
|
|
107
|
+
"Mirror",
|
|
108
|
+
# "Select Opposite",
|
|
109
|
+
"Separator",
|
|
110
|
+
"Key All",
|
|
111
|
+
"Key Translate",
|
|
112
|
+
"Key Rotate",
|
|
113
|
+
"Key Scale",
|
|
114
|
+
"Key Customs",
|
|
115
|
+
],
|
|
116
|
+
],
|
|
117
|
+
"mandatory": False,
|
|
118
|
+
},
|
|
119
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from usagi_picker.constants import PICKER_DATA_ATTRIBUTE
|
|
6
|
+
|
|
7
|
+
from usagi_picker.core.picker_data_node import PickerDataNode
|
|
8
|
+
from usagi_picker.core.graph import Graph
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def collect_pickers() -> list[PickerDataNode]:
|
|
12
|
+
picker_data_nodes = []
|
|
13
|
+
picker_data_plugs = cmds.ls(f"::*.{PICKER_DATA_ATTRIBUTE}")
|
|
14
|
+
for plug in sorted(picker_data_plugs):
|
|
15
|
+
namespace = plug.rpartition(":")[0]
|
|
16
|
+
data = cmds.getAttr(plug)
|
|
17
|
+
graph = Graph.loads(data)
|
|
18
|
+
picker_data_nodes.append(PickerDataNode(namespace, graph))
|
|
19
|
+
return picker_data_nodes
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
from json import JSONDecoder, JSONEncoder
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from usagi_picker.core.actions.abstract import AbstractAction
|
|
5
|
+
from usagi_picker.core.actions.select import SelectAction
|
|
6
|
+
from usagi_picker.core.actions.select_add import SelectAddAction
|
|
7
|
+
from usagi_picker.core.actions.deselect import DeselectAction
|
|
8
|
+
from usagi_picker.core.actions.context_menu import ContextMenuAction
|
|
9
|
+
from usagi_picker.core.actions.reset_all import ResetAll
|
|
10
|
+
from usagi_picker.core.actions.reset_translate import ResetTranslate
|
|
11
|
+
from usagi_picker.core.actions.reset_rotate import ResetRotate
|
|
12
|
+
from usagi_picker.core.actions.reset_scale import ResetScale
|
|
13
|
+
from usagi_picker.core.actions.reset_customs import ResetCustoms
|
|
14
|
+
from usagi_picker.core.actions.toggle_textures import ToggleTextures
|
|
15
|
+
from usagi_picker.core.actions.toggle_smooth import ToggleSmooth
|
|
16
|
+
from usagi_picker.core.actions.toggle_controllers import ToggleControllers
|
|
17
|
+
from usagi_picker.core.actions.mirror import Mirror
|
|
18
|
+
from usagi_picker.core.actions.select_opposite import SelectOpposite
|
|
19
|
+
from usagi_picker.core.actions.key_all import KeyAll
|
|
20
|
+
from usagi_picker.core.actions.key_translate import KeyTranslate
|
|
21
|
+
from usagi_picker.core.actions.key_rotate import KeyRotate
|
|
22
|
+
from usagi_picker.core.actions.key_scale import KeyScale
|
|
23
|
+
from usagi_picker.core.actions.key_customs import KeyCustoms
|
|
24
|
+
from usagi_picker.core.actions.toggle_asset import ToggleAsset
|
|
25
|
+
from usagi_picker.core.actions.toggle_nodes_visibility import ToogleNodesVisibility
|
|
26
|
+
from usagi_picker.core.actions.toggle_shapes_visibility import ToogleShapesVisibility
|
|
27
|
+
from usagi_picker.core.actions.match_ik_fk import MatchIKFKAction
|
|
28
|
+
|
|
29
|
+
ACTIONS = {
|
|
30
|
+
SelectAction.NAME: SelectAction,
|
|
31
|
+
SelectAddAction.NAME: SelectAddAction,
|
|
32
|
+
DeselectAction.NAME: DeselectAction,
|
|
33
|
+
ContextMenuAction.NAME: ContextMenuAction,
|
|
34
|
+
ResetAll.NAME: ResetAll,
|
|
35
|
+
ResetTranslate.NAME: ResetTranslate,
|
|
36
|
+
ResetRotate.NAME: ResetRotate,
|
|
37
|
+
ResetScale.NAME: ResetScale,
|
|
38
|
+
ResetCustoms.NAME: ResetCustoms,
|
|
39
|
+
ToggleTextures.NAME: ToggleTextures,
|
|
40
|
+
ToggleSmooth.NAME: ToggleSmooth,
|
|
41
|
+
ToggleControllers.NAME: ToggleControllers,
|
|
42
|
+
Mirror.NAME: Mirror,
|
|
43
|
+
SelectOpposite.NAME: SelectOpposite,
|
|
44
|
+
KeyAll.NAME: KeyAll,
|
|
45
|
+
KeyTranslate.NAME: KeyTranslate,
|
|
46
|
+
KeyRotate.NAME: KeyRotate,
|
|
47
|
+
KeyScale.NAME: KeyScale,
|
|
48
|
+
KeyCustoms.NAME: KeyCustoms,
|
|
49
|
+
ToggleAsset.NAME: ToggleAsset,
|
|
50
|
+
ToogleNodesVisibility.NAME: ToogleNodesVisibility,
|
|
51
|
+
ToogleShapesVisibility.NAME: ToogleShapesVisibility,
|
|
52
|
+
MatchIKFKAction.NAME: MatchIKFKAction,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class ActionEncoder(JSONEncoder):
|
|
57
|
+
def default(self, o: Any) -> Any:
|
|
58
|
+
if isinstance(o, AbstractAction):
|
|
59
|
+
return o.serialize()
|
|
60
|
+
return super().default(o)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class ActionDecoder(JSONDecoder):
|
|
64
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
65
|
+
super().__init__(object_hook=self.object_hook, *args, **kwargs)
|
|
66
|
+
|
|
67
|
+
def object_hook(self, data: dict[str, Any]) -> Any:
|
|
68
|
+
result = data
|
|
69
|
+
if "name" in data and data["name"] in ACTIONS:
|
|
70
|
+
result = ACTIONS[data["name"]].from_dict(data)
|
|
71
|
+
if isinstance(result, dict):
|
|
72
|
+
result = {int(k): v for k, v in result.items()}
|
|
73
|
+
return result
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from usagi_picker.maya_utils.sets import get_sets_members
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AbstractAction(object):
|
|
11
|
+
NAME = "Abstract"
|
|
12
|
+
ARGS_TYPES = ()
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def from_dict(cls, data: dict[str, Any]) -> AbstractAction:
|
|
16
|
+
return cls(args=data["args"], mandatory=data["mandatory"])
|
|
17
|
+
|
|
18
|
+
def __init__(self, args: tuple[Any, ...], mandatory: bool = False) -> None:
|
|
19
|
+
self.args = args
|
|
20
|
+
self.mandatory = mandatory
|
|
21
|
+
|
|
22
|
+
def __call__(self, namespace: str) -> None:
|
|
23
|
+
raise NotImplementedError
|
|
24
|
+
|
|
25
|
+
def serialize(self) -> dict[str, Any]:
|
|
26
|
+
return {
|
|
27
|
+
"name": self.NAME,
|
|
28
|
+
"args": self.args,
|
|
29
|
+
"mandatory": self.mandatory,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def deserialize(cls, data: dict[str, Any]) -> AbstractAction:
|
|
34
|
+
return cls.from_dict(data)
|
|
35
|
+
|
|
36
|
+
def validate(self, namespace: str) -> bool:
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class AbstractActionOnNodes(AbstractAction):
|
|
41
|
+
ARGS_TYPES = (("Nodes", "node_list"),)
|
|
42
|
+
|
|
43
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
44
|
+
super().__init__(*args, **kwargs)
|
|
45
|
+
self.nodes = self.args[0]
|
|
46
|
+
|
|
47
|
+
def get_nodes(self, namespace: str) -> list[str]:
|
|
48
|
+
nodes = list()
|
|
49
|
+
if self.args and self.nodes:
|
|
50
|
+
nodes = cmds.ls([f"{namespace}:{node}" for node in self.nodes])
|
|
51
|
+
if not nodes:
|
|
52
|
+
cmds.warning(
|
|
53
|
+
f"No objects found for {self.nodes} in namespace {namespace}"
|
|
54
|
+
)
|
|
55
|
+
return
|
|
56
|
+
else:
|
|
57
|
+
nodes = cmds.ls(selection=True, type="transform")
|
|
58
|
+
if not nodes:
|
|
59
|
+
cmds.warning("Nothing is selected, please select some controllers")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
result = list()
|
|
63
|
+
for node in nodes:
|
|
64
|
+
if cmds.nodeType(node) == "objectSet":
|
|
65
|
+
result.extend(get_sets_members(node))
|
|
66
|
+
else:
|
|
67
|
+
result.append(node)
|
|
68
|
+
return result
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from usagi_picker.qsagi.qt import QComboBox, QMenu
|
|
4
|
+
from usagi_picker.qsagi.qt import QCursor
|
|
5
|
+
from usagi_picker.qsagi.qt import qt_exec
|
|
6
|
+
|
|
7
|
+
from functools import partial
|
|
8
|
+
|
|
9
|
+
from usagi_picker.core.actions.abstract import AbstractAction
|
|
10
|
+
from usagi_picker.qsagi.maya_utils import maya_main_window
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _combobox_default_func(combo: QComboBox) -> None:
|
|
14
|
+
from usagi_picker.core.actions import ACTIONS
|
|
15
|
+
|
|
16
|
+
actions = ["Separator"]
|
|
17
|
+
for name, action_type in sorted(ACTIONS.items(), key=lambda x: x[0]):
|
|
18
|
+
if len(action_type.ARGS_TYPES) == 1 and action_type.ARGS_TYPES[0][0] == "Nodes":
|
|
19
|
+
actions.append(name)
|
|
20
|
+
combo.addItems(actions)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ContextMenuAction(AbstractAction):
|
|
24
|
+
NAME = "Context Menu"
|
|
25
|
+
ARGS_TYPES = (
|
|
26
|
+
("Nodes", "node_list"),
|
|
27
|
+
(
|
|
28
|
+
"Menu Actions",
|
|
29
|
+
"list",
|
|
30
|
+
QComboBox,
|
|
31
|
+
"currentText",
|
|
32
|
+
"setCurrentText",
|
|
33
|
+
"currentIndexChanged",
|
|
34
|
+
_combobox_default_func,
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def __init__(self, *args, **kwargs) -> None:
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
self.nodes, self.menu_actions = self.args
|
|
41
|
+
|
|
42
|
+
def __call__(self, namespace: str) -> None:
|
|
43
|
+
from usagi_picker.core.actions import ACTIONS
|
|
44
|
+
|
|
45
|
+
menu = QMenu(parent=maya_main_window())
|
|
46
|
+
for action_name in self.menu_actions:
|
|
47
|
+
if action_name == "Separator":
|
|
48
|
+
menu.addSeparator()
|
|
49
|
+
else:
|
|
50
|
+
action = ACTIONS[action_name]([self.nodes])
|
|
51
|
+
if action.validate(namespace):
|
|
52
|
+
menu.addAction(action_name).triggered.connect(
|
|
53
|
+
partial(action, namespace)
|
|
54
|
+
)
|
|
55
|
+
qt_exec(menu, QCursor.pos())
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from usagi_picker.core.actions.abstract import AbstractAction
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DeselectAction(AbstractAction):
|
|
9
|
+
NAME = "Deselect"
|
|
10
|
+
ARGS_TYPES = (("Nodes", "node_list"),)
|
|
11
|
+
|
|
12
|
+
def __call__(self, namespace: str) -> None:
|
|
13
|
+
if not self.args:
|
|
14
|
+
return
|
|
15
|
+
nodes = self.args[0]
|
|
16
|
+
if not nodes:
|
|
17
|
+
return
|
|
18
|
+
to_select = cmds.ls([f"{namespace}:{node}" for node in nodes])
|
|
19
|
+
if to_select:
|
|
20
|
+
cmds.select(to_select, deselect=True)
|
|
21
|
+
else:
|
|
22
|
+
cmds.warning(f"No objects found for {nodes} in namespace {namespace}")
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from usagi_picker.core.actions.abstract import AbstractActionOnNodes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeyAll(AbstractActionOnNodes):
|
|
9
|
+
NAME = "Key All"
|
|
10
|
+
|
|
11
|
+
def __call__(self, namespace: str) -> None:
|
|
12
|
+
nodes = self.get_nodes(namespace)
|
|
13
|
+
if not nodes:
|
|
14
|
+
return
|
|
15
|
+
cmds.setKeyframe(nodes)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from usagi_picker.core.actions.abstract import AbstractActionOnNodes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeyCustoms(AbstractActionOnNodes):
|
|
9
|
+
NAME = "Key Customs"
|
|
10
|
+
|
|
11
|
+
def __call__(self, namespace: str) -> None:
|
|
12
|
+
nodes = self.get_nodes(namespace)
|
|
13
|
+
if not nodes:
|
|
14
|
+
return
|
|
15
|
+
for node in nodes:
|
|
16
|
+
custom_attributes = (
|
|
17
|
+
cmds.listAttr(node, userDefined=True, keyable=True) or []
|
|
18
|
+
)
|
|
19
|
+
if custom_attributes:
|
|
20
|
+
cmds.setKeyframe(nodes, attribute=custom_attributes)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from usagi_picker.core.actions.abstract import AbstractActionOnNodes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeyRotate(AbstractActionOnNodes):
|
|
9
|
+
NAME = "Key Rotate"
|
|
10
|
+
|
|
11
|
+
def __call__(self, namespace: str) -> None:
|
|
12
|
+
nodes = self.get_nodes(namespace)
|
|
13
|
+
if not nodes:
|
|
14
|
+
return
|
|
15
|
+
cmds.setKeyframe(nodes, attribute=("rx", "ry", "rz"))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from usagi_picker.core.actions.abstract import AbstractActionOnNodes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeyScale(AbstractActionOnNodes):
|
|
9
|
+
NAME = "Key Scale"
|
|
10
|
+
|
|
11
|
+
def __call__(self, namespace: str) -> None:
|
|
12
|
+
nodes = self.get_nodes(namespace)
|
|
13
|
+
if not nodes:
|
|
14
|
+
return
|
|
15
|
+
cmds.setKeyframe(nodes, attribute=("sx", "sy", "sz"))
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from maya import cmds
|
|
4
|
+
|
|
5
|
+
from usagi_picker.core.actions.abstract import AbstractActionOnNodes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class KeyTranslate(AbstractActionOnNodes):
|
|
9
|
+
NAME = "Key Translate"
|
|
10
|
+
|
|
11
|
+
def __call__(self, namespace: str) -> None:
|
|
12
|
+
nodes = self.get_nodes(namespace)
|
|
13
|
+
if not nodes:
|
|
14
|
+
return
|
|
15
|
+
cmds.setKeyframe(nodes, attribute=("tx", "ty", "tz"))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from usagi_picker.maya_utils.ik_fk import toggle_from_nodes
|
|
5
|
+
from usagi_picker.core.actions.abstract import AbstractActionOnNodes
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MatchIKFKAction(AbstractActionOnNodes):
|
|
9
|
+
NAME = "Match IK/FK"
|
|
10
|
+
|
|
11
|
+
def __call__(self, namespace: str) -> None:
|
|
12
|
+
nodes = self.get_nodes(namespace)
|
|
13
|
+
if not nodes:
|
|
14
|
+
return
|
|
15
|
+
|
|
16
|
+
toggle_from_nodes(nodes)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from usagi_picker.core.actions.abstract import AbstractActionOnNodes
|
|
5
|
+
from usagi_picker.maya_utils.mirror import has_mirror_value, mirror_transforms
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Mirror(AbstractActionOnNodes):
|
|
9
|
+
NAME = "Mirror"
|
|
10
|
+
|
|
11
|
+
def validate(self, namespace: str) -> bool:
|
|
12
|
+
return all(has_mirror_value(f"{namespace}:{node}") for node in self.nodes)
|
|
13
|
+
|
|
14
|
+
def __call__(self, namespace: str) -> None:
|
|
15
|
+
nodes = self.get_nodes(namespace)
|
|
16
|
+
if not nodes:
|
|
17
|
+
return
|
|
18
|
+
for node in nodes:
|
|
19
|
+
mirror_transforms(node)
|