├── .gitignore
├── LICENSE
├── README.md
├── crab
├── README.md
├── __init__.py
├── apps
│ ├── __init__.py
│ ├── animator
│ │ ├── __init__.py
│ │ ├── core.py
│ │ ├── options.py
│ │ ├── resources
│ │ │ ├── __init__.py
│ │ │ ├── animator.css
│ │ │ ├── animator.ui
│ │ │ └── crab.png
│ │ └── utils.py
│ ├── creator
│ │ ├── __init__.py
│ │ ├── core.py
│ │ ├── lists.py
│ │ └── resources
│ │ │ ├── add.png
│ │ │ ├── blackstar.png
│ │ │ ├── build.png
│ │ │ ├── creator.css
│ │ │ ├── creator.ui
│ │ │ ├── down.png
│ │ │ ├── edit.png
│ │ │ ├── help
│ │ │ └── help_new_rig.gif
│ │ │ ├── new.png
│ │ │ ├── remove.png
│ │ │ ├── search.png
│ │ │ ├── star.png
│ │ │ ├── up.png
│ │ │ └── validate.png
│ ├── menu.py
│ └── tooltip
│ │ ├── __init__.py
│ │ ├── core.py
│ │ └── resources
│ │ ├── crab.gif
│ │ ├── test.gif
│ │ ├── tooltip.css
│ │ └── tooltip.ui
├── config.py
├── constants.py
├── core
│ ├── __init__.py
│ ├── _factories.py
│ ├── behaviour.py
│ ├── component.py
│ ├── process.py
│ ├── rig.py
│ └── tools.py
├── create
│ ├── __init__.py
│ ├── basics.py
│ ├── controls.py
│ ├── guides.py
│ ├── ik.py
│ └── joints.py
├── plugins
│ ├── behaviours
│ │ ├── __init__.py
│ │ ├── connect_attr.py
│ │ ├── dummy.py
│ │ ├── execution.py
│ │ ├── follicles.py
│ │ ├── insert_control.py
│ │ ├── lock_and_hide.py
│ │ ├── parent_constraint.py
│ │ ├── proxy_attr.py
│ │ ├── remove.py
│ │ ├── reparent.py
│ │ ├── set_driven_key.py
│ │ ├── spaceswitch.gif
│ │ └── spaceswitch.py
│ ├── components
│ │ ├── creature
│ │ │ ├── spline_spine.py
│ │ │ └── trileg.py
│ │ ├── standard
│ │ │ ├── location.py
│ │ │ ├── singular.py
│ │ │ └── spine.py
│ │ └── utilities
│ │ │ ├── __init__.py
│ │ │ ├── sticky_patch.py
│ │ │ └── twist.py
│ ├── processes
│ │ ├── bonefilter.py
│ │ ├── colour_controls.py
│ │ ├── layers.py
│ │ ├── poses.py
│ │ ├── shapes.py
│ │ └── validation.py
│ └── tools
│ │ ├── anim
│ │ ├── __init__.py
│ │ ├── keying.py
│ │ ├── posing.py
│ │ ├── select.py
│ │ └── snap.py
│ │ ├── icons
│ │ ├── key_all.png
│ │ ├── key_character.png
│ │ ├── key_selected.png
│ │ ├── pose_apply.png
│ │ ├── pose_store.png
│ │ ├── reset_character.png
│ │ ├── reset_selection.png
│ │ ├── select_all.png
│ │ ├── select_all_character.png
│ │ └── select_opposite.png
│ │ └── rigging
│ │ ├── joints
│ │ ├── joint_mirroring.py
│ │ ├── joint_orientation.py
│ │ ├── joint_styling.py
│ │ ├── joints.png
│ │ ├── singulization.py
│ │ └── upvectors.py
│ │ ├── meshes
│ │ ├── meshes.png
│ │ └── meshes.py
│ │ ├── naming
│ │ ├── naming.png
│ │ └── naming.py
│ │ ├── organisation
│ │ ├── clean.png
│ │ └── clean.py
│ │ ├── poses
│ │ ├── poses.png
│ │ └── poses.py
│ │ ├── shapes
│ │ ├── shape_manipulation.py
│ │ ├── shape_selection.py
│ │ ├── shapes.png
│ │ └── shapes.py
│ │ ├── skinning
│ │ ├── __init__.py
│ │ ├── skin.py
│ │ └── skinning.png
│ │ └── transform
│ │ ├── constrain.py
│ │ └── transforms.png
├── resources
│ ├── icons
│ │ ├── add_button.png
│ │ ├── behaviour.png
│ │ ├── breaker.png
│ │ ├── build_button.png
│ │ ├── component.png
│ │ ├── crab.gif
│ │ ├── crab_animator.png
│ │ ├── crab_overview.png
│ │ ├── crab_overview_tab.png
│ │ ├── crab_tools_tab.png
│ │ ├── edit_button.png
│ │ ├── help_spaceswitch.gif
│ │ ├── new_button.png
│ │ └── tool.png
│ └── shapes
│ │ ├── AxisTest.json
│ │ ├── EyeCircle.json
│ │ ├── EyeTarget.json
│ │ ├── FacialBorder.json
│ │ ├── FacialConfig.json
│ │ ├── FacialJaw.json
│ │ ├── FacialLowerLidMarker.json
│ │ ├── FacialMarker.json
│ │ ├── FacialUpperLidMarker.json
│ │ ├── FacialWidget.json
│ │ ├── arrow_x.json
│ │ ├── arrow_z.json
│ │ ├── attach.json
│ │ ├── builtin_hip.json
│ │ ├── builtin_hipswivel.json
│ │ ├── builtin_spine.json
│ │ ├── buitlin_spineswivel.json
│ │ ├── circle.json
│ │ ├── cog.json
│ │ ├── config.json
│ │ ├── cross.json
│ │ ├── cross_handle.json
│ │ ├── cross_pivot.json
│ │ ├── cube.json
│ │ ├── cup.json
│ │ ├── cylinder.json
│ │ ├── foot.json
│ │ ├── guide.json
│ │ ├── head.json
│ │ ├── heel.json
│ │ ├── lollipop.json
│ │ ├── neck.json
│ │ ├── paddle.json
│ │ ├── pin.json
│ │ ├── pivot.json
│ │ ├── platform_paddle.json
│ │ ├── ripsaw.json
│ │ ├── rocker.json
│ │ ├── rotator.json
│ │ ├── soft_square.json
│ │ ├── sphere.json
│ │ ├── sphere_small.json
│ │ ├── spiral.json
│ │ ├── square.json
│ │ ├── teardrop.json
│ │ └── toe.json
├── utils
│ ├── __init__.py
│ ├── access.py
│ ├── contexts.py
│ ├── hierarchy.py
│ ├── joints.py
│ ├── maths.py
│ ├── organise.py
│ ├── resources.py
│ ├── shapes.py
│ ├── skinning.py
│ ├── snap.py
│ ├── transform.py
│ └── types.py
└── vendor
│ ├── __init__.py
│ ├── blackout.py
│ ├── factories
│ ├── __init__.py
│ ├── constants.py
│ └── factory.py
│ ├── qute
│ ├── __init__.py
│ ├── _res
│ │ ├── list_collapsed.png
│ │ ├── list_open.png
│ │ ├── squares.png
│ │ └── squares_lite.png
│ ├── constants.py
│ ├── extensions
│ │ ├── __init__.py
│ │ ├── buttons.py
│ │ ├── dividers.py
│ │ ├── flow_layout.py
│ │ ├── tray.py
│ │ └── windows.py
│ ├── resources.py
│ ├── styles
│ │ └── space.css
│ ├── utilities
│ │ ├── __init__.py
│ │ ├── _core.py
│ │ ├── derive.py
│ │ ├── designer.py
│ │ ├── events.py
│ │ ├── launch.py
│ │ ├── layouts.py
│ │ ├── menus.py
│ │ ├── pixmaps.py
│ │ ├── request.py
│ │ ├── sizing.py
│ │ ├── styling.py
│ │ ├── widgets.py
│ │ └── windows.py
│ └── vendor
│ │ ├── Qt.py
│ │ ├── __init__.py
│ │ └── scribble
│ │ ├── __init__.py
│ │ └── core.py
│ └── scribble
│ ├── __init__.py
│ └── core.py
└── userSetup.py
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .idea/crab.iml
3 | .idea/inspectionProfiles/profiles_settings.xml
4 | .idea/misc.xml
5 | .idea/modules.xml
6 | .idea/vcs.xml
7 | .idea/workspace.xml
8 | .idea/workspace.xml
9 | *.pyc
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 The Python Packaging Authority
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/crab/apps/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The apps module contains the user facing tools within crab
3 | """
4 | from . import menu
5 | from . import animator
6 | from . import creator
7 |
8 |
--------------------------------------------------------------------------------
/crab/apps/animator/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The animator app is designed to display custom plugin tools to
3 | animators.
4 | """
5 | from .core import launch
6 | from .core import CrabAnimator
7 |
8 |
--------------------------------------------------------------------------------
/crab/apps/animator/resources/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/animator/resources/__init__.py
--------------------------------------------------------------------------------
/crab/apps/animator/resources/animator.css:
--------------------------------------------------------------------------------
1 |
2 | QListView::item::selected {
3 | background-color: rgba(_FOREGROUND_, 24);
4 | }
--------------------------------------------------------------------------------
/crab/apps/animator/resources/crab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/animator/resources/crab.png
--------------------------------------------------------------------------------
/crab/apps/animator/utils.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 |
4 | # --------------------------------------------------------------------------------------
5 | def get_resource(name):
6 | """
7 | This is a convenience function to get files from the resources directory
8 | and correct handle the slashing.
9 |
10 | :param name: Name of file to pull from the resource directory
11 |
12 | :return: Absolute path to the resource requested.
13 | """
14 | return os.path.join(
15 | os.path.dirname(__file__),
16 | "resources",
17 | name,
18 | ).replace("\\", "/")
19 |
--------------------------------------------------------------------------------
/crab/apps/creator/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | The creator app is designed for the building and editing of
3 | rigs
4 | """
5 | from .core import launch
6 | from .core import CrabCreator
7 |
8 |
--------------------------------------------------------------------------------
/crab/apps/creator/resources/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/add.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/blackstar.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/blackstar.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/build.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/build.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/creator.css:
--------------------------------------------------------------------------------
1 | #newRig,
2 | #editRig,
3 | #buildRig,
4 | #addComponent,
5 | #addBehaviour,
6 | #removeBehaviour,
7 | #moveBehaviourUp,
8 | #moveBehaviourDown,
9 | #removeComponent{
10 | border: 1px solid black;
11 | background-color: qlineargradient(spread:pad, x1:0, y1:0, x2:0, y2:1, stop:0 rgba(_FOREGROUND_, 30), stop:1 rgba(_FOREGROUND_, 10));
12 | padding: 0px 5px 0px 5px;
13 | min-height: 20px;
14 |
15 | background-image: url(_SQUARES_PNG_);
16 | background-position: top right;
17 | background-origin: content;
18 | background-repeat: repeat-xy;
19 | }
20 |
21 | #newRig:pressed,
22 | #editRig:pressed,
23 | #buildRig:pressed,
24 | #addComponent:pressed,
25 | #addBehaviour:pressed,
26 | #removeBehaviour:pressed,
27 | #moveBehaviourUp:pressed,
28 | #moveBehaviourDown:pressed,
29 | #removeComponent:pressed {
30 | background-color: rgb(_HIGHLIGHT_);
31 | }
32 |
33 | #newRig:hover,
34 | #editRig:hover,
35 | #buildRig:hover,
36 | #addComponent:hover,
37 | #addBehaviour:hover,
38 | #removeBehaviour:hover,
39 | #moveBehaviourUp:hover,
40 | #moveBehaviourDown:hover,
41 | #removeComponent:hover,
42 | #newRig:released,
43 | #editRig:released,
44 | #buildRig:released,
45 | #addComponent:released,
46 | #addBehaviour:released,
47 | #removeBehaviour:released,
48 | #moveBehaviourUp:released,
49 | #moveBehaviourDown:released,
50 | #removeComponent:released {
51 | background-color: rgba(_FOREGROUND_, 50);
52 | color: rgb(20, 20, 20);
53 | }
54 |
55 | #componentOptionsWidget,
56 | #behaviourOptionsWidget,
57 | #appliedComponentsOptionsWidget,
58 | #appliedBehaviourOptionsWidget,
59 | #toolOptionsWidget {
60 | background-color: rgba(0, 0, 0, 50);
61 | }
--------------------------------------------------------------------------------
/crab/apps/creator/resources/down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/down.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/edit.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/help/help_new_rig.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/help/help_new_rig.gif
--------------------------------------------------------------------------------
/crab/apps/creator/resources/new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/new.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/remove.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/remove.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/search.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/star.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/up.png
--------------------------------------------------------------------------------
/crab/apps/creator/resources/validate.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/creator/resources/validate.png
--------------------------------------------------------------------------------
/crab/apps/menu.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import webbrowser
3 | import pymel.core as pm
4 |
5 | from crab.vendor import blackout
6 |
7 | CRAB_MENU_NAME = "Crab"
8 | CRAB_MENU_OBJ = "CrabMenuRoot"
9 |
10 |
11 | # --------------------------------------------------------------------------------------
12 | def _menu_edit_rig(*args, **kwargs):
13 | for rig in crab.Rig.all():
14 | rig.edit()
15 |
16 |
17 | # --------------------------------------------------------------------------------------
18 | def _menu_build_rig(*args, **kwargs):
19 | for rig in crab.Rig.all():
20 | rig.build()
21 |
22 |
23 | # --------------------------------------------------------------------------------------
24 | def _menu_goto_website(*args, **kwargs):
25 | webbrowser.open("https://github.com/mikemalinowski/crab")
26 |
27 |
28 | # --------------------------------------------------------------------------------------
29 | def _menu_reload(*args, **kwargs):
30 | blackout.drop("crab")
31 | pm.evalDeferred("import crab;crab.menu.initialize()")
32 |
33 |
34 | # --------------------------------------------------------------------------------------
35 | def initialize():
36 | """
37 | This will setup the menu and interface mechanisms for the Crab Rigging
38 | Tool.
39 |
40 | :return:
41 | """
42 |
43 | # -- If the menu already exists, we will delete it to allow
44 | # -- us to rebuild it
45 | if pm.menu(CRAB_MENU_OBJ, exists=True):
46 | pm.deleteUI(CRAB_MENU_OBJ)
47 |
48 | # -- Create the new menu for Crab
49 | new_menu = pm.menu(
50 | CRAB_MENU_OBJ,
51 | label=CRAB_MENU_NAME,
52 | tearOff=True,
53 | parent=pm.language.melGlobals["gMainWindow"],
54 | )
55 |
56 | add_menu_item("Creator", crab.creator.launch)
57 | add_menu_item("Animator", crab.animator.launch)
58 |
59 | pm.menuItem(divider=True, parent=new_menu)
60 | add_menu_item("Edit", _menu_edit_rig)
61 | add_menu_item("Build", _menu_build_rig)
62 |
63 | pm.menuItem(divider=True, parent=new_menu)
64 | add_menu_item("Website", _menu_goto_website)
65 |
66 | pm.menuItem(divider=True, parent=new_menu)
67 | add_menu_item("Reload", _menu_reload)
68 |
69 | # -- We specifically only want this menu to be visibile
70 | # -- in the rigging menu
71 | cached_menu_set = pm.menuSet(query=True, currentMenuSet=True)
72 | rigging_menu_set = pm.mel.findMenuSetFromLabel("Rigging")
73 |
74 | # -- Set our menu to the rigging menu and add it to
75 | # -- the menu set
76 | pm.menuSet(currentMenuSet=rigging_menu_set)
77 | pm.menuSet(addMenu=new_menu)
78 |
79 | # -- Restore the users cached menu set
80 | pm.menuSet(currentMenuSet=cached_menu_set)
81 |
82 |
83 | # --------------------------------------------------------------------------------------
84 | def add_menu_item(label, callable_func, parent=CRAB_MENU_OBJ):
85 | """
86 |
87 | :param label:
88 | :param icon:
89 | :param command:
90 | :return:
91 | """
92 |
93 | return pm.menuItem(
94 | CRAB_MENU_OBJ + label.replace(" ", "_"),
95 | label=label,
96 | command=callable_func,
97 | parent=parent,
98 | )
99 |
--------------------------------------------------------------------------------
/crab/apps/tooltip/__init__.py:
--------------------------------------------------------------------------------
1 | from .core import show_tooltip
2 |
--------------------------------------------------------------------------------
/crab/apps/tooltip/resources/crab.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/tooltip/resources/crab.gif
--------------------------------------------------------------------------------
/crab/apps/tooltip/resources/test.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/apps/tooltip/resources/test.gif
--------------------------------------------------------------------------------
/crab/apps/tooltip/resources/tooltip.css:
--------------------------------------------------------------------------------
1 | #title {
2 | font-size: 25px;
3 | color: rgb(255, 255, 255);
4 | }
--------------------------------------------------------------------------------
/crab/apps/tooltip/resources/tooltip.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | Form
4 |
5 |
6 |
7 | 0
8 | 0
9 | 564
10 | 550
11 |
12 |
13 |
14 | Form
15 |
16 |
17 | -
18 |
19 |
-
20 |
21 |
22 | Header
23 |
24 |
25 | Qt::AlignCenter
26 |
27 |
28 |
29 | -
30 |
31 |
32 | true
33 |
34 |
35 |
36 |
37 |
38 | tooltip_default.png
39 |
40 |
41 | Qt::AlignCenter
42 |
43 |
44 |
45 | -
46 |
47 |
48 | TextLabel
49 |
50 |
51 | Qt::AlignCenter
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/crab/constants.py:
--------------------------------------------------------------------------------
1 | import os
2 | import logging
3 |
4 | # --------------------------------------------------------------------------------------
5 | log = logging.getLogger("crab")
6 |
7 | # --------------------------------------------------------------------------------------
8 | PLUGIN_LOCATIONS = [
9 | os.path.join(
10 | os.path.dirname(__file__),
11 | "plugins",
12 | ),
13 | ]
14 |
15 |
16 | # --------------------------------------------------------------------------------------
17 | PLUGIN_ENVIRONMENT_VARIABLE = "CRAB_PLUGIN_PATHS"
18 |
--------------------------------------------------------------------------------
/crab/core/__init__.py:
--------------------------------------------------------------------------------
1 | from .process import Process
2 | from .component import Component
3 | from .behaviour import Behaviour
4 | from .behaviour import BehaviourUI
5 | from .rig import Rig
6 | from ._factories import factory_manager
7 |
8 | from . import tools
9 | from .tools import AnimTool
10 | from .tools import RigTool
11 |
12 | from .rig import get
13 |
--------------------------------------------------------------------------------
/crab/core/process.py:
--------------------------------------------------------------------------------
1 | # --------------------------------------------------------------------------------------
2 | # noinspection PyMethodMayBeStatic
3 | class Process(object):
4 | """
5 | A process is a plugin which is executed during an edit call as well as
6 | a build call. This plugin type has three stages:
7 |
8 | * snapshot
9 | This is done before the control rig is destroyed and its your
10 | oppotunity to read any information from the rig.
11 | Note: You must store the data you have read, as the same process
12 | instance will not be used during the build.
13 |
14 | * pre
15 | This is called after the control is destroyed, leaving the skeleton
16 | bare. This is typically a good time to do any skeleton modifications
17 | as nothing will be locking or driving the joints.
18 |
19 | * post
20 | This is called after all the components and behaviours are built,
21 | allowing you to perform any actions against the rig as a whole.
22 | """
23 |
24 | identifier = "unknown"
25 | version = 1
26 | order = 0
27 |
28 | # ----------------------------------------------------------------------------------
29 | def __init__(self, rig):
30 | self.rig = rig
31 |
32 | # ----------------------------------------------------------------------------------
33 | def snapshot(self):
34 | """
35 | This is done before the control rig is destroyed and its your
36 | oppotunity to read any information from the rig.
37 |
38 | Note: You must store the data you have read, as the same process
39 | instance will not be used during the build.
40 |
41 | :return: None
42 | """
43 | pass
44 |
45 | # ----------------------------------------------------------------------------------
46 | def post_edit(self):
47 | """
48 | This is called after the control is destroyed, leaving the skeleton
49 | bare. This is typically a good time to do any skeleton modifications
50 | as nothing will be locking or driving the joints.
51 |
52 | :return:
53 | """
54 | pass
55 |
56 | # ----------------------------------------------------------------------------------
57 | def pre_build(self):
58 | """
59 | This is called after validation but before the components are started
60 | to be built.
61 |
62 | :return:
63 | """
64 | pass
65 |
66 | # ----------------------------------------------------------------------------------
67 | def post_build(self):
68 | """
69 | This is called after all the components and behaviours are built,
70 | allowing you to perform any actions against the rig as a whole.
71 |
72 | :return:
73 | """
74 | pass
75 |
76 | # ----------------------------------------------------------------------------------
77 | def validate(self):
78 | """
79 | This occurs before the pre_build and should NOT modify the rig in any form. It
80 | should only be used to validate that the build process can begin.
81 | """
82 | return True
83 |
--------------------------------------------------------------------------------
/crab/create/__init__.py:
--------------------------------------------------------------------------------
1 | # -- Import our most fundamental elements directly
2 | from .basics import org
3 | from .basics import generic
4 | from .controls import control
5 | from .guides import guide
6 | from .joints import joint
7 |
8 | # -- Import the more exotic elements as modules
9 | from . import ik
--------------------------------------------------------------------------------
/crab/create/basics.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pm
2 | import maya.cmds as cmds
3 |
4 | from ..utils import shapes
5 | from .. import config
6 |
7 |
8 | # --------------------------------------------------------------------------------------
9 | def generic(
10 | node_type,
11 | prefix,
12 | description,
13 | side,
14 | parent=None,
15 | xform=None,
16 | match_to=None,
17 | shape=None,
18 | find_transform=False,
19 | counter=1,
20 | ):
21 | """
22 | Convenience function for creating a node, generating the name using
23 | the unique name method and giving the ability to assign the parent and
24 | transform.
25 |
26 | :param node_type: Type of node to create, such as "transform"
27 | :type node_type: str
28 |
29 | :param prefix: Prefix to assign to the node name
30 | :type prefix: str
31 |
32 | :param description: Descriptive section of the name
33 | :type description: str
34 |
35 | :param side: Tag for the location to be used during the name generation
36 | :type side: str
37 |
38 | :param parent: Optional parent to assign to the node
39 | :type parent: pm.nt.DagNode
40 |
41 | :param xform: Optional worldSpace matrix to apply to the object
42 | :type xform: pm.dt.Matrix
43 |
44 | :param match_to: Optional node to match in worldspace
45 | :type match_to: pm.nt.DagNode
46 |
47 | :param shape: Optional shape to apply to the node
48 | :type shape: name of shape or path
49 |
50 | :param find_transform: If True, then the nodes transform will
51 | be found if the created node is not a transform
52 | :type find_transform: bool
53 |
54 | :return: pm.nt.DependNode
55 | """
56 | # -- Create the node
57 | node = pm.createNode(node_type)
58 |
59 | if not isinstance(node, pm.nt.Transform) and find_transform:
60 | node = node.getParent()
61 |
62 | # -- Name it based on our naming convention
63 | node.rename(
64 | config.name(
65 | prefix=prefix,
66 | description=description,
67 | side=side,
68 | counter=counter,
69 | ),
70 | )
71 |
72 | # -- If we"re given a matrix utilise that
73 | if xform:
74 | cmds.xform(
75 | node,
76 | matrix=xform,
77 | worldspace=True,
78 | )
79 |
80 | # -- Match the object to the target object if one
81 | # -- is given.
82 | if match_to:
83 | target_matrix = cmds.xform(
84 | pm.PyNode(match_to).name(),
85 | query=True,
86 | matrix=True,
87 | worldSpace=True,
88 | )
89 | cmds.xform(
90 | node.name(),
91 | matrix=target_matrix,
92 | worldSpace=True,
93 | )
94 |
95 | # -- Parent the node if we"re given a parent
96 | if parent:
97 | cmds.parent(node.name(), parent.name())
98 |
99 | if shape:
100 | shapes.apply(node, shape)
101 |
102 | return node
103 |
104 |
105 | # --------------------------------------------------------------------------------------
106 | def org(description, side, parent=None):
107 | """
108 | Creates a simple org node
109 |
110 | :param description: Descriptive section of the name
111 | :type description: str
112 |
113 | :param side: Tag for the location to be used during the name generation
114 | :type side: str
115 |
116 | :param parent: Optional parent to assign to the node
117 | :type parent: pm.nt.DagNode
118 |
119 | """
120 | return generic(
121 | prefix=config.ORG,
122 | node_type="transform",
123 | description=description,
124 | side=side,
125 | parent=parent,
126 | match_to=parent,
127 | )
128 |
--------------------------------------------------------------------------------
/crab/create/controls.py:
--------------------------------------------------------------------------------
1 | from .. import config
2 | from .basics import generic
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | def control(
7 | description,
8 | side,
9 | parent=None,
10 | xform=None,
11 | match_to=None,
12 | shape=None,
13 | lock_list=None,
14 | hide_list=None,
15 | rotation_order=None,
16 | counter=1,
17 | ):
18 | """
19 | Creates a control structure - which is a structure which conforms to the
20 | following hierarchy:
21 |
22 | ORG -> ZRO -> OFF -> CTL
23 |
24 | :param description: Descriptive section of the name
25 | :type description: str
26 |
27 | :param side: Tag for the location to be used during the name generation
28 | :type side: str
29 |
30 | :param parent: Optional parent to assign to the node
31 | :type parent: pm.nt.DagNode
32 |
33 | :param xform: Optional worldSpace matrix to apply to the object
34 | :type xform: pm.dt.Matrix
35 |
36 | :param match_to: Optional node to match in worldspace
37 | :type match_to: pm.nt.DagNode
38 |
39 | :param shape: Optional shape to apply to the node
40 | :type shape: name of shape or path
41 |
42 | :param lock_list: This is a list of attribute names you want to lock. This
43 | is only applied to the control.
44 | :type lock_list: A list of strings, or a string deliminated by ;
45 |
46 | :param hide_list: This is a list of attribute names you want to hide. This
47 | is only applied to the control.
48 | :type hide_list: A list of strings, or a string deliminated by ;
49 |
50 | :param rotation_order: The rotation order that should be assigned to this
51 | control by default
52 | :type rotation_order: int
53 |
54 | :param counter: [Optional] What counter should be tested for generating
55 | the name of the rig element
56 | :type counter: int
57 |
58 | :return: pm.nt.DependNode
59 | """
60 | prefixes = [
61 | config.ORG,
62 | config.ZERO,
63 | config.OFFSET,
64 | config.CONTROL,
65 | ]
66 |
67 | for prefix in prefixes:
68 | # -- Declare any specific options for this iteration
69 | options = dict()
70 |
71 | # -- Controls are the only items which have shapes
72 | if prefix == config.CONTROL:
73 | options["shape"] = shape
74 |
75 | parent = generic(
76 | "transform",
77 | prefix,
78 | description,
79 | side,
80 | parent=parent,
81 | xform=xform,
82 | match_to=match_to,
83 | counter=counter,
84 | **options
85 | )
86 |
87 | # -- Check if we need to convert lock or hide data
88 | if hide_list and not isinstance(hide_list, list):
89 | hide_list = hide_list.split(";")
90 |
91 | if lock_list and not isinstance(lock_list, list):
92 | lock_list = lock_list.split(";")
93 |
94 | if hide_list:
95 | for attr_to_hide in hide_list:
96 | if attr_to_hide:
97 | parent.attr(attr_to_hide).set(k=False)
98 |
99 | if lock_list:
100 | for attr_to_lock in lock_list:
101 | if attr_to_lock:
102 | parent.attr(attr_to_lock).lock()
103 |
104 | # -- Now expose the rotation order
105 | parent.rotateOrder.set(k=True)
106 |
107 | parent.rotateOrder.set(rotation_order or config.DEFAULT_CONTROL_ROTATION_ORDER)
108 |
109 | return parent
110 |
--------------------------------------------------------------------------------
/crab/create/guides.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pm
2 |
3 | from .basics import generic
4 | from .. import config
5 |
6 |
7 | # --------------------------------------------------------------------------------------
8 | def guide(
9 | description,
10 | side,
11 | parent=None,
12 | xform=None,
13 | translation_offset=None,
14 | rotation_offset=None,
15 | match_to=None,
16 | link_to=None,
17 | shape=None,
18 | ):
19 | """
20 | Creates a control structure - which is a structure which conforms to the
21 | following hierarchy:
22 |
23 | ORG -> ZRO -> OFF -> CTL
24 |
25 | :param description: Descriptive section of the name
26 | :type description: str
27 |
28 | :param side: Tag for the location to be used during the name generation
29 | :type side: str
30 |
31 | :param parent: Optional parent to assign to the node
32 | :type parent: pm.nt.DagNode
33 |
34 | :param xform: Optional worldSpace matrix to apply to the object
35 | :type xform: pm.dt.Matrix
36 |
37 | :param translation_offset: Optional local space translation offset to be
38 | applied to the guide
39 | :type translation_offset: pm.nt.Vector
40 |
41 | :param rotation_offset: Optional local space rotation offset to be
42 | applied to the guide
43 | :type rotation_offset: pm.nt.Vector
44 |
45 | :param match_to: Optional node to match in worldspace
46 | :type match_to: pm.nt.DagNode
47 |
48 | :param shape: Optional shape to apply to the node
49 | :type shape: name of shape or path
50 |
51 | :param link_to: If given, an unselectable line is drawn between this
52 | guide control and the given transform.
53 | :type link_to: pm.nt.DagNode
54 |
55 | :return: pm.nt.DependNode
56 | """
57 | guide_node = generic(
58 | "transform",
59 | config.GUIDE,
60 | description,
61 | side,
62 | shape=shape or "cube",
63 | parent=parent,
64 | xform=xform,
65 | match_to=match_to or parent,
66 | )
67 |
68 | if link_to:
69 | curve = pm.curve(
70 | d=1,
71 | p=[
72 | [0, 0, 0],
73 | [0, 0, 0],
74 | ],
75 | )
76 |
77 | # -- Make the curve unselectable
78 | curve.getShape().template.set(True)
79 |
80 | # -- Create the first cluster
81 | pm.select("%s.cv[0]" % curve.name())
82 | cls_root_handle, cls_root_xfo = pm.cluster()
83 |
84 | # -- Create the second cluster
85 | pm.select("%s.cv[1]" % curve.name())
86 | cls_target_handle, cls_target_xfo = pm.cluster()
87 |
88 | # -- Hide the clusters, as we do not want them
89 | # -- to be interactable
90 | cls_root_xfo.visibility.set(False)
91 | cls_target_xfo.visibility.set(False)
92 |
93 | # -- Ensure they"re both children of the guide
94 | cls_root_xfo.setParent(guide_node)
95 | cls_target_xfo.setParent(guide_node)
96 |
97 | # -- Ensure the target is zero"d
98 | cls_target_xfo.setMatrix(pm.dt.Matrix())
99 |
100 | # -- Constrain the root to the linked object
101 | pm.parentConstraint(
102 | link_to,
103 | cls_root_xfo,
104 | maintainOffset=False,
105 | )
106 |
107 | # -- Set the guide specific colouring
108 | guide_node.useOutlinerColor.set(True)
109 | guide_node.outlinerColorR.set(config.GUIDE_COLOR[0] * (1.0 / 255))
110 | guide_node.outlinerColorG.set(config.GUIDE_COLOR[1] * (1.0 / 255))
111 | guide_node.outlinerColorB.set(config.GUIDE_COLOR[2] * (1.0 / 255))
112 |
113 | for guide_shape in guide_node.getShapes():
114 | # -- Set the display colour
115 | guide_shape.overrideEnabled.set(True)
116 | guide_shape.overrideRGBColors.set(True)
117 |
118 | guide_shape.overrideColorR.set(config.GUIDE_COLOR[0] * (1.0 / 255))
119 | guide_shape.overrideColorG.set(config.GUIDE_COLOR[1] * (1.0 / 255))
120 | guide_shape.overrideColorB.set(config.GUIDE_COLOR[2] * (1.0 / 255))
121 |
122 | if translation_offset:
123 | guide_node.setTranslation(
124 | translation_offset,
125 | worldSpace=False,
126 | )
127 |
128 | if rotation_offset:
129 | guide_node.setRotation(
130 | rotation_offset,
131 | worldSpace=False,
132 | )
133 |
134 | return guide_node
135 |
--------------------------------------------------------------------------------
/crab/create/ik.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pm
2 |
3 | from .. import config
4 |
5 |
6 | # --------------------------------------------------------------------------------------
7 | def _generate_ik(
8 | start,
9 | end,
10 | description,
11 | side,
12 | parent=None,
13 | visible=False,
14 | solver="ikRPsolver",
15 | polevector=None,
16 | **kwargs
17 | ):
18 | """
19 | Private function to generate the ik, returning the result from the ik handle call
20 |
21 | :param start:
22 | :param end:
23 | :param description:
24 | :param side:
25 | :param parent:
26 | :param visible:
27 | :param solver:
28 | :param polevector:
29 | :param kwargs:
30 | :return:
31 | """
32 | # -- Hook up the Ik Handle
33 | result = pm.ikHandle(
34 | startJoint=start,
35 | endEffector=end,
36 | solver=solver,
37 | priority=1,
38 | autoPriority=False,
39 | enableHandles=True,
40 | snapHandleToEffector=True,
41 | sticky=False,
42 | weight=1,
43 | positionWeight=1,
44 | **kwargs
45 | )
46 |
47 | ikh = result[0]
48 |
49 | ikh.visibility.set(visible)
50 |
51 | ikh.rename(
52 | config.name(
53 | prefix="IKH",
54 | description=description,
55 | side=side,
56 | ),
57 | )
58 |
59 | if parent:
60 | ikh.setParent(parent)
61 |
62 | if polevector:
63 | pm.poleVectorConstraint(
64 | polevector,
65 | ikh,
66 | )
67 |
68 | return result
69 |
70 |
71 | # --------------------------------------------------------------------------------------
72 | def simple_ik(
73 | start,
74 | end,
75 | description,
76 | side,
77 | parent=None,
78 | visible=False,
79 | solver="ikRPsolver",
80 | polevector=None,
81 | **kwargs
82 | ):
83 | """
84 | This is a convenience function for generating an IK handle
85 | and having it named.
86 |
87 | :param start:
88 | :param end:
89 | :param description:
90 | :param side:
91 | :param parent:
92 | :param visible:
93 | :param solver:
94 | :param polevector:
95 | :param kwargs:
96 |
97 | :return: ikHandle
98 | """
99 | # -- Hook up the Ik Handle
100 | result = _generate_ik(
101 | start=start,
102 | end=end,
103 | description=description,
104 | side=side,
105 | parent=parent,
106 | visible=visible,
107 | solver=solver,
108 | polevector=polevector,
109 | **kwargs
110 | )
111 |
112 | # -- Return the IK Handle
113 | return result[0]
114 |
115 |
116 | # --------------------------------------------------------------------------------------
117 | def spline_ik(
118 | start, end, description, side, parent=None, visible=False, polevector=None, **kwargs
119 | ):
120 | """
121 | This is a convenience function for generating an IK handle
122 | and having it named.
123 |
124 | :param start:
125 | :param end:
126 | :param description:
127 | :param side:
128 | :param parent:
129 | :param visible:
130 | :param polevector:
131 | :param kwargs:
132 |
133 | :return: [ikHandle, curve]
134 | """
135 | # -- Hook up the Ik Handle
136 | result = _generate_ik(
137 | start=start,
138 | end=end,
139 | description=description,
140 | side=side,
141 | parent=parent,
142 | visible=visible,
143 | solver="ikSplineSolver",
144 | polevector=polevector,
145 | **kwargs
146 | )
147 |
148 | # -- Return the IK Handle
149 | return result[0], result[-1]
150 |
--------------------------------------------------------------------------------
/crab/create/joints.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pm
2 |
3 | from . import basics
4 | from .. import config
5 |
6 |
7 | # --------------------------------------------------------------------------------------
8 | def joint(
9 | description,
10 | side,
11 | parent=None,
12 | xform=None,
13 | match_to=None,
14 | radius=3,
15 | counter=1,
16 | is_deformer=True,
17 | ):
18 | """
19 | Creates a joint, ensuring the right parenting and radius
20 |
21 | :param description: Descriptive section of the name
22 | :type description: str
23 |
24 | :param side: Tag for the location to be used during the name generation
25 | :type side: str
26 |
27 | :param parent: Optional parent to assign to the node
28 | :type parent: pm.nt.DagNode or None
29 |
30 | :param xform: Optional worldSpace matrix to apply to the object
31 | :type xform: pm.dt.Matrix
32 |
33 | :param match_to: Optional node to match in worldspace
34 | :type match_to: pm.nt.DagNode
35 |
36 | :param radius: Radius to assign to the joint
37 | :type radius: int
38 |
39 | :param counter: The counter number to start at. By default this is 1 and will
40 | count up until a unique number is found.
41 | :type counter: int
42 |
43 | :param is_deformer: If true, this will be placed in a deformers set
44 | :type is_deformer: bool
45 |
46 | :return: pm.nt.DependNode
47 | """
48 | # -- Joints always parent under whatever is selected, so
49 | # -- clear the selection
50 | pm.select(clear=True)
51 |
52 | # -- Create the joint
53 | new_joint = basics.generic(
54 | "joint",
55 | config.SKELETON,
56 | description,
57 | side,
58 | parent=parent,
59 | xform=xform,
60 | counter=counter,
61 | match_to=match_to,
62 | )
63 |
64 | new_joint.jointOrientX.set(0)
65 | new_joint.jointOrientY.set(0)
66 | new_joint.jointOrientZ.set(0)
67 |
68 | if parent:
69 | new_joint.setMatrix(
70 | parent.getMatrix(worldSpace=True),
71 | worldSpace=True,
72 | )
73 |
74 | # -- If we"re given a matrix utilise that
75 | if xform:
76 | new_joint.setMatrix(
77 | xform,
78 | worldSpace=True,
79 | )
80 |
81 | # -- Match the object to the target object if one
82 | # -- is given.
83 | if match_to:
84 | new_joint.setMatrix(
85 | match_to.getMatrix(worldSpace=True),
86 | worldSpace=True,
87 | )
88 |
89 | # -- Set the joint radius
90 | new_joint.radius.set(radius)
91 |
92 | if is_deformer:
93 | if not pm.objExists("deformers"):
94 | pm.sets(n="deformers", empty=True)
95 |
96 | deformer_set = pm.PyNode("deformers")
97 |
98 | if isinstance(deformer_set, pm.nt.ObjectSet):
99 | deformer_set.addMembers([new_joint])
100 |
101 | # -- Clear the selection
102 | pm.select(clear=True)
103 |
104 | return new_joint
105 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/behaviours/__init__.py
--------------------------------------------------------------------------------
/crab/plugins/behaviours/connect_attr.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class InsertControlBehaviour(crab.Behaviour):
7 | identifier = "Connect Attribute"
8 | version = 1
9 |
10 | tooltips = dict(
11 | description="A descriptive to label the behaviour",
12 | side="Typically LF, MD or RT - denoting the side/location of the behaviour",
13 | source="The longName to the attribute you want to read the values from",
14 | destination="The longName to the attribute you want to drive",
15 | )
16 |
17 | # -- We expect these options to be fullfilled with objects
18 | REQUIRED_NODE_OPTIONS = ["source", "destination"]
19 |
20 | # --------------------------------------------------------------------------
21 | def __init__(self, *args, **kwargs):
22 | super(InsertControlBehaviour, self).__init__(*args, **kwargs)
23 |
24 | self.options.source = ""
25 | self.options.destination = ""
26 |
27 | # --------------------------------------------------------------------------
28 | # noinspection PyUnresolvedReferences
29 | def apply(self):
30 |
31 | source = pm.PyNode(self.options.source)
32 | destination = pm.PyNode(self.options.destination)
33 |
34 | source.connect(destination)
35 |
36 | return True
37 |
38 | # --------------------------------------------------------------------------
39 | def can_build(self, available_nodes):
40 | if self.options.source not in available_nodes:
41 | print("%s is cannot be found" % self.options.source)
42 | return False
43 |
44 | if self.options.destination not in available_nodes:
45 | print("%s cannot be found" % self.options.destination)
46 | return False
47 |
48 | return True
49 |
50 |
51 | # ------------------------------------------------------------------------------
52 | class SetAttributeBehaviour(crab.Behaviour):
53 |
54 | identifier = "Attributes : Set"
55 | version = 1
56 |
57 | tooltips = dict(
58 | description="A descriptive to label the behaviour",
59 | side="Typically LF, MD or RT - denoting the side/location of the behaviour",
60 | objects="List of objects to set the values on",
61 | attribute_name="The name of the attribute that should be set (excluding object name)",
62 | attribute_value="The value to set to the attribute",
63 | is_string="If the attribute is a string/text attribute, then tick this",
64 | is_number="If the attribute is a float or integer then please tick this",
65 | )
66 |
67 | REQUIRED_NODE_OPTIONS = ["objects"]
68 |
69 | # --------------------------------------------------------------------------
70 | def __init__(self, *args, **kwargs):
71 | super(SetAttributeBehaviour, self).__init__(*args, **kwargs)
72 |
73 | self.options.objects = ""
74 | self.options.attribute_name = ""
75 | self.options.attribute_value = ""
76 | self.options.is_string = False
77 | self.options.is_number = True
78 |
79 | # --------------------------------------------------------------------------
80 | # noinspection PyUnresolvedReferences
81 | def apply(self):
82 |
83 | value = self.options.attribute_value
84 |
85 | if self.options.is_number:
86 | value = float(self.options.attribute_value)
87 |
88 | if "*" in self.options.objects:
89 | node_list = pm.ls(self.options.objects)
90 |
91 | else:
92 | node_list = self.options.objects.split(";")
93 |
94 | for node in node_list:
95 | if node and pm.objExists(node):
96 | try:
97 | pm.PyNode(node).attr(self.options.attribute_name).set(value)
98 |
99 | except:
100 | pass
101 |
102 | return True
103 |
104 |
105 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/dummy.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class Duplicate(crab.Behaviour):
7 | """
8 | This is meant as an example only to show how a behaviour
9 | can operate
10 | """
11 | identifier = "Duplicate"
12 | version = 1
13 |
14 | tooltips = dict(
15 | parent="The node which the dupicated object should be parented under",
16 | target="The object to duplicate",
17 | )
18 |
19 | REQUIRED_NODE_OPTIONS = ["parent", "target"]
20 |
21 | # --------------------------------------------------------------------------
22 | def __init__(self, *args, **kwargs):
23 | super(Duplicate, self).__init__(*args, **kwargs)
24 |
25 | self.options.parent = ""
26 | self.options.target = ""
27 |
28 | # --------------------------------------------------------------------------
29 | # noinspection PyUnresolvedReferences
30 | def apply(self):
31 | result = pm.duplicate(self.options.target)[0]
32 | result.setParent(self.options.parent, a=True)
33 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/execution.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class CodeExecutionBehaviour(crab.Behaviour):
7 | identifier = "Execute Code"
8 | version = 1
9 |
10 | tooltip = dict(
11 | code="Python code to evaluate. This will evaluate using eval(your_code)"
12 | )
13 |
14 | # --------------------------------------------------------------------------
15 | def __init__(self, *args, **kwargs):
16 | super(CodeExecutionBehaviour, self).__init__(*args, **kwargs)
17 |
18 | self.options.code = ""
19 |
20 | # --------------------------------------------------------------------------
21 | # noinspection PyUnresolvedReferences
22 | def apply(self):
23 |
24 | eval(self.options.code)
25 |
26 | return True
27 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/follicles.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class AddFollicleBehaviour(crab.Behaviour):
7 | identifier = "Add Follicle"
8 | version = 1
9 |
10 | tooltips = dict(
11 | parent="The node to parent the follicle under",
12 | surface="The name of the mesh or surface to attach to",
13 | drive="Optionally a node to constrain to the follicle",
14 | )
15 |
16 | REQUIRED_NODE_OPTIONS = ["parent", "surface"]
17 | OPTIONAL_NODE_OPTIONS = ["drive"]
18 |
19 | # --------------------------------------------------------------------------
20 | def __init__(self, *args, **kwargs):
21 | super(AddFollicleBehaviour, self).__init__(*args, **kwargs)
22 |
23 | self.options.parent = ""
24 | self.options.surface = ""
25 | self.options.drive = ""
26 |
27 | # --------------------------------------------------------------------------
28 | # noinspection PyUnresolvedReferences
29 | def apply(self, parent=None, surface=None, drive=None):
30 |
31 | if not parent and pm.objExists(self.options.parent):
32 | parent = pm.PyNode(self.options.parent)
33 |
34 | if not surface and pm.objExists(self.options.surface):
35 | surface = pm.PyNode(self.options.surface)
36 |
37 | if not surface and pm.selected():
38 | surface = pm.selected()[0]
39 |
40 | if not drive and pm.objExists(self.options.drive):
41 | drive = pm.PyNode(self.options.drive)
42 |
43 | # -- Drive is optional
44 | if self.options.drive:
45 | drive = pm.PyNode(self.options.drive)
46 |
47 | # -- If we have the transform then we need to get the
48 | # -- shape
49 | if isinstance(surface, pm.nt.Transform):
50 | surface = surface.getShape()
51 |
52 | # -- We need to test whether the surface is a mesh or not
53 | is_mesh = False
54 |
55 | if isinstance(surface, pm.nt.Mesh):
56 | is_mesh = True
57 |
58 | follicle = crab.create.generic(
59 | node_type="follicle",
60 | prefix=crab.config.MECHANICAL,
61 | description=self.options.description,
62 | side=self.options.side,
63 | parent=parent,
64 | find_transform=True,
65 | )
66 | follicle = follicle.getShape()
67 |
68 | if is_mesh:
69 | surface.attr("outMesh").connect(follicle.inputMesh)
70 |
71 | else:
72 | surface.attr("local").connect(follicle.inputSurface)
73 |
74 | surface.attr("worldMatrix[0]").connect(follicle.inputWorldMatrix)
75 |
76 | follicle.outTranslate.connect(follicle.getParent().translate)
77 | follicle.outRotate.connect(follicle.getParent().rotate)
78 |
79 | if drive:
80 |
81 | # -- Set the UV positions
82 | if is_mesh:
83 | u, v = surface.getUVAtPoint(
84 | drive.getTranslation(space="world"),
85 | space="world",
86 | )
87 |
88 | else:
89 | _, u, v = surface.closestPoint(
90 | drive.getTranslation(space="world"),
91 | space="world",
92 | )
93 |
94 | follicle.attr("parameterU").set(u)
95 | follicle.attr("parameterV").set(v)
96 |
97 | pm.parentConstraint(
98 | follicle.getParent(),
99 | drive,
100 | maintainOffset=True,
101 | )
102 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/lock_and_hide.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class LockAndHideBehaviour(crab.Behaviour):
7 | identifier = "Lock And Hide Attributes"
8 | version = 1
9 |
10 | tooltip = dict(
11 | node="The name of the node to have attributes locked and hidden",
12 | attributes="List of attributes to lock (seperated by ;)",
13 | )
14 |
15 | REQUIRED_NODE_OPTIONS = ["node", "attributes"]
16 |
17 | # --------------------------------------------------------------------------
18 | def __init__(self, *args, **kwargs):
19 | super(LockAndHideBehaviour, self).__init__(*args, **kwargs)
20 |
21 | self.options.node = ""
22 | self.options.attributes = ""
23 |
24 | # --------------------------------------------------------------------------
25 | # noinspection PyUnresolvedReferences
26 | def apply(self):
27 |
28 | if not pm.objExists(self.options.node):
29 | pm.error("Could not find : " + self.options.node)
30 | return False
31 |
32 | node = pm.PyNode(self.options.node)
33 |
34 | for attr in self.options.attributes.split(";"):
35 | if attr:
36 | node.attr(attr).lock()
37 | node.attr(attr).set(k=False, cb=False)
38 |
39 | return True
40 |
41 |
42 | # ------------------------------------------------------------------------------
43 | class UnLockAndShowBehaviour(crab.Behaviour):
44 | identifier = "UnLock And Show Attributes"
45 | version = 1
46 |
47 | tooltip = dict(
48 | node="The name of the node to have attributes unlocked and shown",
49 | attributes="List of attributes to unlock (seperated by ;)",
50 | )
51 |
52 | REQUIRED_NODE_OPTIONS = ["node", "attributes"]
53 |
54 | # --------------------------------------------------------------------------
55 | def __init__(self, *args, **kwargs):
56 | super(UnLockAndShowBehaviour, self).__init__(*args, **kwargs)
57 |
58 | self.options.node = ""
59 | self.options.attributes = ""
60 |
61 | # --------------------------------------------------------------------------
62 | # noinspection PyUnresolvedReferences
63 | def apply(self):
64 |
65 | if not pm.objExists(self.options.node):
66 | pm.error("Could not find : " + self.options.node)
67 | return False
68 |
69 | node = pm.PyNode(self.options.node)
70 |
71 | for attr in self.options.attributes.split(";"):
72 | if attr:
73 | node.attr(attr).unlock()
74 | node.attr(attr).set(k=True, cb=True)
75 |
76 | return True
77 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/parent_constraint.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class ParentConstraintBehaviour(crab.Behaviour):
7 | identifier = "Parent Constraint"
8 | version = 1
9 |
10 | tooltips = dict(
11 | constrain_this="Full name of the node to constrain",
12 | to_this="The name of the node you want to constrain to",
13 | maintain_offset="If True, the constraint will be made retaining the current transform offset",
14 | )
15 |
16 | REQUIRED_NODE_OPTIONS = ["constrain_this", "to_this"]
17 |
18 | # --------------------------------------------------------------------------
19 | def __init__(self, *args, **kwargs):
20 | super(ParentConstraintBehaviour, self).__init__(*args, **kwargs)
21 |
22 | self.options.constrain_this = ""
23 | self.options.to_this = ""
24 | self.options.maintain_offset = False
25 |
26 | # --------------------------------------------------------------------------
27 | # noinspection PyUnresolvedReferences
28 | def apply(self):
29 |
30 | constrain_this = pm.PyNode(self.options.constrain_this)
31 | to_this = pm.PyNode(self.options.to_this)
32 |
33 | pm.parentConstraint(
34 | to_this,
35 | constrain_this,
36 | maintainOffset=self.options.maintain_offset
37 | )
38 |
39 | return True
40 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/proxy_attr.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ----------------------------------------------------------------------------------------------------------------------
6 | class AddProxyAttrBehaviour(crab.Behaviour):
7 |
8 | # -- Unique identifier for the behaviur
9 | identifier = "Add Proxy Attribute"
10 | version = 0
11 |
12 | # ------------------------------------------------------------------------------------------------------------------
13 | def __init__(self, *args, **kwargs):
14 | super(AddProxyAttrBehaviour, self).__init__(*args, **kwargs)
15 |
16 | self.options.target = ""
17 | self.options.source_attr = ""
18 | self.options.display_name = ""
19 |
20 | # ------------------------------------------------------------------------------------------------------------------
21 | def apply(self):
22 |
23 | if not pm.objExists(self.options.target):
24 | print("{name} does not exist".format(name=self.options.target))
25 | return False
26 |
27 | if not pm.objExists(self.options.source_attr):
28 | print("{name} attribute does not exist".format(name=self.options.source_attr))
29 | return False
30 |
31 | target = pm.PyNode(self.options.target)
32 |
33 | target.addAttr(
34 | self.options.display_name,
35 | proxy=self.options.source_attr,
36 | )
37 |
38 | return True
--------------------------------------------------------------------------------
/crab/plugins/behaviours/remove.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class ReparentBehaviour(crab.Behaviour):
7 | identifier = "Delete Nodes"
8 | version = 1
9 |
10 | tooltip = dict(
11 | node="The name of the node to be reparented to",
12 | new_parent="The name of the node which should be the targets new parent",
13 | )
14 |
15 | REQUIRED_NODE_OPTIONS = ["node", "new_parent"]
16 |
17 | # --------------------------------------------------------------------------
18 | def __init__(self, *args, **kwargs):
19 | super(ReparentBehaviour, self).__init__(*args, **kwargs)
20 |
21 | self.options.nodes = ""
22 |
23 | # --------------------------------------------------------------------------
24 | # noinspection PyUnresolvedReferences
25 | def apply(self):
26 |
27 | for node in self.options.nodes.split(";"):
28 |
29 | if node and pm.objExists(node):
30 | pm.delete(node)
31 |
32 | return True
33 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/reparent.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class ReparentBehaviour(crab.Behaviour):
7 | identifier = "Re-Parent"
8 | version = 1
9 |
10 | tooltip = dict(
11 | node="The name of the node to be reparented to",
12 | new_parent="The name of the node which should be the targets new parent",
13 | )
14 |
15 | REQUIRED_NODE_OPTIONS = ["node", "new_parent"]
16 |
17 | # --------------------------------------------------------------------------
18 | def __init__(self, *args, **kwargs):
19 | super(ReparentBehaviour, self).__init__(*args, **kwargs)
20 |
21 | self.options.node = ""
22 | self.options.new_parent = ""
23 |
24 | # --------------------------------------------------------------------------
25 | # noinspection PyUnresolvedReferences
26 | def apply(self):
27 |
28 | # -- Check that we can actually get the parent
29 | if not pm.objExists(self.options.new_parent):
30 | raise Exception("%s does not exist" % self.options.new_parent)
31 |
32 | # -- Get hte parent as a pymel node
33 | new_parent = pm.PyNode(self.options.new_parent)
34 |
35 | for node_name in self.options.node.split(";"):
36 |
37 | node_name = node_name.strip()
38 |
39 | if not pm.objExists(node_name):
40 | raise Exception("%s does not exist" % node_name)
41 |
42 | node = pm.PyNode(node_name)
43 | node.setParent(new_parent)
44 |
45 | return True
46 |
--------------------------------------------------------------------------------
/crab/plugins/behaviours/spaceswitch.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/behaviours/spaceswitch.gif
--------------------------------------------------------------------------------
/crab/plugins/components/standard/location.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | class LocationComponent(crab.Component):
7 | """
8 | A segment represents a single rig element capable of building
9 | a guide along with a rig over that guide.
10 | """
11 |
12 | identifier = "Core : Location"
13 | legacy_identifiers = ["Location"]
14 | version = 1
15 |
16 | tooltips = dict(
17 | description="A descriptive name to apply to all objects",
18 | lock="Any control attributes you want to have locked",
19 | hide="Any control attributes you want to have hidden from the channel box",
20 | side="Typically LF, MD or RT - denoting the side/location of the control"
21 | )
22 |
23 | # --------------------------------------------------------------------------
24 | def __init__(self, *args, **kwargs):
25 | super(LocationComponent, self).__init__(*args, **kwargs)
26 |
27 | self.options.description = "Location"
28 | self.options.lock = ""
29 | self.options.hide = "v;"
30 |
31 | # --------------------------------------------------------------------------
32 | def create_skeleton(self, parent):
33 | """
34 | This should create your guide representation for your segment.
35 | The parent will be a pre-constructed crabSegment transform node.
36 |
37 | :param parent:
38 | :return:
39 | """
40 | # -- Create the joint for this singular
41 | root_joint = crab.create.generic(
42 | node_type="joint",
43 | prefix=crab.config.SKELETON,
44 | description="%s" % self.options.description,
45 | side=self.options.side,
46 | parent=parent,
47 | match_to=parent,
48 | )
49 |
50 | # -- Define this joint as being the skeleton root for
51 | # -- this component
52 | self.mark_as_skeletal_root(root_joint)
53 |
54 | # -- We"ll tag this joint with a label so we can easily
55 | # -- find it from within the create_rig function.
56 | self.tag(
57 | target=root_joint,
58 | label="RootJoint"
59 | )
60 |
61 | # -- Select the tip joint
62 | pm.select(root_joint)
63 |
64 | return True
65 |
66 | # --------------------------------------------------------------------------
67 | def create_rig(self, parent):
68 |
69 | # -- We"re given the skeleton component instance, so we can
70 | # -- utilise the find method to find the joint we need to build
71 | # -- a control against
72 | root_joint = self.find_first("RootJoint")
73 |
74 | # -- Create a transform to use as a control
75 | root_control = crab.create.control(
76 | description="Rig%s" % self.options.description,
77 | side=self.options.side,
78 | parent=parent,
79 | match_to=root_joint,
80 | shape="cog",
81 | lock_list=self.options.lock,
82 | hide_list=self.options.hide,
83 | )
84 |
85 | location_control = crab.create.control(
86 | description="%s" % self.options.description,
87 | side=self.options.side,
88 | parent=root_control,
89 | match_to=root_control,
90 | shape="arrow_x",
91 | lock_list=self.options.lock,
92 | hide_list=self.options.hide,
93 | )
94 | print("---")
95 | if pm.upAxis(q=True, axis=True).upper() == "Y":
96 | crab.utils.shapes.spin(location_control, y=-90)
97 |
98 | # -- All joints should have a binding. The binding allows crab
99 | # -- to know what control parent to utilise when building skeletal
100 | # -- components.
101 | # -- The act of binding also constrains the skeleton joint to the
102 | # -- control
103 | self.bind(
104 | root_joint,
105 | root_control,
106 | constrain=False,
107 | )
108 |
109 | # -- Constrain the joint to the location
110 | pm.parentConstraint(
111 | location_control,
112 | root_joint,
113 | mo=False,
114 | )
115 |
116 | # -- Constrain the joint to the location
117 | pm.scaleConstraint(
118 | location_control,
119 | root_joint,
120 | mo=False,
121 | )
122 |
123 | # -- Select our tip joint
124 | pm.select(root_control)
125 |
126 | return True
127 |
--------------------------------------------------------------------------------
/crab/plugins/components/utilities/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/components/utilities/__init__.py
--------------------------------------------------------------------------------
/crab/plugins/processes/bonefilter.py:
--------------------------------------------------------------------------------
1 | import crab
2 |
3 |
4 | # --------------------------------------------------------------------------------------
5 | class BoneFilterProcess(crab.Process):
6 | """
7 | Makes any bones which are part of the control hierarchy invisible
8 | by setting their draw style to None.
9 | """
10 |
11 | # -- Define the identifier for the plugin
12 | identifier = "BoneFilter"
13 | version = 1
14 |
15 | # ----------------------------------------------------------------------------------
16 | # noinspection PyUnresolvedReferences
17 | def post_build(self):
18 | """
19 | This is called after the entire rig has been built, so we will attempt
20 | to re-apply the shape information.
21 |
22 | :return:
23 | """
24 | for joint in self.rig.control_org().getChildren(ad=True, type="joint"):
25 | joint.drawStyle.set(2) # -- Hide
26 |
--------------------------------------------------------------------------------
/crab/plugins/processes/colour_controls.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class ColorControlsProcess(crab.Process):
7 | """
8 | Makes any bones which are part of the control hierarchy invisible
9 | by setting their draw style to None.
10 | """
11 |
12 | # -- Define the identifier for the plugin
13 | identifier = "ColourControls"
14 | version = 1
15 | order = 100
16 |
17 | # ----------------------------------------------------------------------------------
18 | # noinspection PyUnresolvedReferences
19 | def post_build(self):
20 | """
21 | This is called after the entire rig has been built, so we will attempt
22 | to re-apply the shape information.
23 |
24 | :return:
25 | """
26 | ignore_colour = crab.config.NON_ANIMATABLE_COLOUR
27 |
28 | for control in self.rig.control_org().getChildren(ad=True, type="transform"):
29 |
30 | # -- Get the colour to assign to this shape
31 | colour = self.get_colour(control)
32 | control.useOutlinerColor.set(True)
33 |
34 | if control.getShapes():
35 | # -- Set the outliner colour
36 | control.outlinerColorR.set(colour[0] * (1.0 / 255))
37 | control.outlinerColorG.set(colour[1] * (1.0 / 255))
38 | control.outlinerColorB.set(colour[2] * (1.0 / 255))
39 |
40 | else:
41 | control.outlinerColorR.set(ignore_colour[0] * (1.0 / 255))
42 | control.outlinerColorG.set(ignore_colour[1] * (1.0 / 255))
43 | control.outlinerColorB.set(ignore_colour[2] * (1.0 / 255))
44 |
45 | for shape in control.getShapes():
46 |
47 | # -- Set the display colour
48 | shape.overrideEnabled.set(True)
49 | shape.overrideRGBColors.set(True)
50 |
51 | shape.overrideColorR.set(colour[0] * (1.0 / 255))
52 | shape.overrideColorG.set(colour[1] * (1.0 / 255))
53 | shape.overrideColorB.set(colour[2] * (1.0 / 255))
54 |
55 | # ----------------------------------------------------------------------------------
56 | @classmethod
57 | def get_colour(cls, node):
58 | if node.name().endswith(crab.config.LEFT):
59 | return crab.config.LEFT_COLOR
60 |
61 | if node.name().endswith(crab.config.RIGHT):
62 | return crab.config.RIGHT_COLOR
63 |
64 | return crab.config.MIDDLE_COLOR
65 |
--------------------------------------------------------------------------------
/crab/plugins/processes/layers.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class LayerProcess(crab.Process):
7 | """
8 | Makes any bones which are part of the control hierarchy invisible
9 | by setting their draw style to None.
10 | """
11 |
12 | # -- Define the identifier for the plugin
13 | identifier = "Layers"
14 | version = 1
15 |
16 | # ----------------------------------------------------------------------------------
17 | # noinspection PyUnresolvedReferences
18 | def post_build(self):
19 | """
20 | This is called after the entire rig has been built, so we will attempt
21 | to re-apply the shape information.
22 |
23 | :return:
24 | """
25 | # -- Ensure the layers exist
26 | crab.utils.organise.add_to_layer(None, crab.config.CONTROL_LAYER)
27 | crab.utils.organise.add_to_layer(None, crab.config.GEOMETRY_LAYER)
28 | crab.utils.organise.add_to_layer(None, crab.config.SKELETON_LAYER)
29 |
30 | # -- Add all the elements into the layers
31 | for skeletal_joint in self.rig.skeleton_org().getChildren(ad=True, type="joint"):
32 | crab.utils.organise.add_to_layer(
33 | skeletal_joint,
34 | crab.config.SKELETON_LAYER,
35 | )
36 |
37 | for geometry in self.rig.find_org("Geometry").getChildren(ad=True, type="mesh"):
38 | crab.utils.organise.add_to_layer(
39 | geometry.getParent(),
40 | crab.config.GEOMETRY_LAYER,
41 | )
42 |
43 | for control in self.rig.control_org().getChildren(ad=True, type="transform"):
44 | if crab.config.CONTROL not in control.name():
45 | crab.utils.organise.add_to_layer(
46 | control,
47 | crab.config.CONTROL_LAYER,
48 | )
49 |
50 | # -- Ensure the layers are setup correctly
51 | pm.PyNode(crab.config.SKELETON_LAYER).visibility.set(0)
52 | pm.PyNode(crab.config.GEOMETRY_LAYER).displayType.set(2)
53 |
54 | # ----------------------------------------------------------------------------------
55 | def post_edit(self):
56 | """
57 | This is called after the control is destroyed, leaving the skeleton
58 | bare. This is typically a good time to do any skeleton modifications
59 | as nothing will be locking or driving the joints.
60 |
61 | :return:
62 | """
63 | pm.PyNode(crab.config.SKELETON_LAYER).visibility.set(1)
64 |
--------------------------------------------------------------------------------
/crab/plugins/processes/poses.py:
--------------------------------------------------------------------------------
1 | import crab
2 |
3 |
4 | # ------------------------------------------------------------------------------
5 | class LayerProcess(crab.Process):
6 | """
7 | Makes any bones which are part of the control hierarchy invisible
8 | by setting their draw style to None.
9 | """
10 |
11 | # -- Define the identifier for the plugin
12 | identifier = "Layer"
13 | version = 1
14 |
15 | # --------------------------------------------------------------------------
16 | # noinspection PyUnresolvedReferences
17 | def post_edit(self):
18 | """
19 | This is called after the entire rig has been built, so we will attempt
20 | to re-apply the shape information.
21 |
22 | :return:
23 | """
24 | crab.tools.rigging().request("poses_apply_a_pose")().run()
25 |
26 | # --------------------------------------------------------------------------
27 | # noinspection PyUnresolvedReferences
28 | def pre_build(self):
29 | """
30 | This is called after the entire rig has been built, so we will attempt
31 | to re-apply the shape information.
32 |
33 | :return:
34 | """
35 | crab.tools.rigging().request("poses_apply_t_pose")().run()
36 |
--------------------------------------------------------------------------------
/crab/plugins/processes/validation.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 | from crab.vendor import qute
5 |
6 |
7 | # --------------------------------------------------------------------------------------
8 | class CheckPose(crab.Process):
9 |
10 | identifier = "APose Validation"
11 |
12 | def validate(self):
13 | """
14 | We want to check that the pose the rig is in is the same as the stored
15 | pose - this prevents the user losing A-Pose changes when hitting the rig build
16 | """
17 | print("running APose Validation")
18 |
19 | failures = list()
20 |
21 | for joint in pm.ls(crab.config.SKELETON + "*"):
22 | if joint.hasAttr("APose"):
23 | expected_matrix = joint.APose.get()
24 | current_matrix = joint.getMatrix()
25 |
26 | if not current_matrix.isEquivalent(expected_matrix):
27 | failures.append(joint)
28 |
29 | # -- If we have failures, lets call them out
30 | if failures:
31 |
32 | # -- Print each failure
33 | for failure in failures:
34 | print("%s is not in its APose, your current pose might be lost" % failure.name())
35 |
36 | # -- If we are in batch mode then we return, we ignore this issue
37 | if pm.about(batch=True):
38 | return True
39 |
40 | # -- Because we"re in UI mode, we should ask the user whether they want to
41 | # -- continue
42 | confirmation = qute.utilities.request.confirmation(
43 | title="Pose Difference",
44 | label=(
45 | "There are differences between your current pose and "
46 | "the A-Pose. Do you want to continue?"
47 | ),
48 | )
49 |
50 | if not confirmation:
51 | return False
52 |
53 | else:
54 | print("\tNo failures found.")
55 |
56 | return True
57 |
--------------------------------------------------------------------------------
/crab/plugins/tools/anim/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/anim/__init__.py
--------------------------------------------------------------------------------
/crab/plugins/tools/anim/keying.py:
--------------------------------------------------------------------------------
1 | import os
2 | import crab
3 | import pymel.core as pm
4 |
5 |
6 | # --------------------------------------------------------------------------------------
7 | def get_icon(name):
8 | return os.path.join(
9 | os.path.dirname(os.path.dirname(__file__)),
10 | "icons",
11 | "%s.png" % name,
12 | )
13 |
14 |
15 | # --------------------------------------------------------------------------------------
16 | def get_namespace(node):
17 | if ":" in node:
18 | return ":".join(pm.selected()[0].name().split(":")[:-1])
19 |
20 | return None
21 |
22 |
23 | # --------------------------------------------------------------------------------------
24 | class KeyAllTool(crab.tools.AnimTool):
25 | """
26 | This will place a keyframe on all controls within the entire scene at their
27 | current values and at the current time.
28 | """
29 |
30 | identifier = "keyframe_all"
31 | display_name = "Key : All"
32 | icon = get_icon("key_all")
33 |
34 | def run(self):
35 | pm.setKeyframe(crab.utils.access.get_controls())
36 |
37 |
38 | # --------------------------------------------------------------------------------------
39 | class KeyAllOnCharacterTool(crab.tools.AnimTool):
40 | """
41 | This will keyframe all the controls within the currently selected rig at their
42 | current values on the current frame.
43 | """
44 |
45 | identifier = "keyframe_all_character"
46 | display_name = "Key : All : Character"
47 | icon = get_icon("key_character")
48 |
49 | def run(self):
50 | pm.setKeyframe(crab.utils.access.get_controls(current_only=True))
51 |
--------------------------------------------------------------------------------
/crab/plugins/tools/anim/posing.py:
--------------------------------------------------------------------------------
1 | import os
2 | import crab
3 | import pymel.core as pm
4 |
5 |
6 | # --------------------------------------------------------------------------------------
7 | def get_icon(name):
8 | return os.path.join(
9 | os.path.dirname(os.path.dirname(__file__)),
10 | "icons",
11 | "%s.png" % name,
12 | )
13 |
14 |
15 | # --------------------------------------------------------------------------------------
16 | def get_namespace(node):
17 | if ":" in node:
18 | return ":".join(pm.selected()[0].name().split(":")[:-1])
19 |
20 | return None
21 |
22 |
23 | # --------------------------------------------------------------------------------------
24 | class CopyPoseTool(crab.tools.AnimTool):
25 | """
26 | This will store the current local space transform of the selected
27 | controls and store them.
28 | """
29 |
30 | identifier = "poses_copy"
31 | display_name = "Pose : Copy"
32 | icon = get_icon("pose_store")
33 |
34 | Pose = dict()
35 |
36 | def run(self, nodes=None):
37 | nodes = nodes or pm.selected()
38 |
39 | for ctl in nodes:
40 | CopyPoseTool.Pose[ctl.name().split(":")[-1]] = ctl.getMatrix()
41 |
42 |
43 | # --------------------------------------------------------------------------------------
44 | class PastePoseTool(crab.tools.AnimTool):
45 | """
46 | This will apply the previously stored local space transforms back to the objects.
47 |
48 | If "Selection Only" is turned on, then the pose will only be applied to matching
49 | objects which are also selected.
50 | """
51 |
52 | identifier = "poses_copy_paste"
53 | display_name = "Pose : Paste"
54 | icon = get_icon("pose_apply")
55 |
56 | tooltips = dict(
57 | selection_only=(
58 | "If True the pose will only be applied to matched "
59 | "controls in the current selection"
60 | ),
61 | )
62 |
63 | def __init__(self):
64 | super(PastePoseTool, self).__init__()
65 |
66 | self.options.selection_only = False
67 |
68 | def run(self, nodes=None):
69 | nodes = nodes or pm.selected()
70 |
71 | selected_names = [
72 | node.name()
73 | for node in nodes
74 | ]
75 |
76 | if not selected_names:
77 | return
78 |
79 | ns = get_namespace(selected_names[0])
80 |
81 | for ctl, pose in CopyPoseTool.Pose.items():
82 |
83 | # -- Add the namespace onto the ctl
84 | resolved_name = ctl
85 | if ns:
86 | resolved_name = ns + ":" + ctl
87 |
88 | if not pm.objExists(resolved_name):
89 | continue
90 |
91 | if self.options.selection_only and ctl not in selected_names:
92 | continue
93 |
94 | pm.PyNode(resolved_name).setMatrix(pose)
95 |
96 |
97 | # --------------------------------------------------------------------------------------
98 | class CopyWorldSpaceTool(crab.tools.AnimTool):
99 | """
100 | This will store the current world space transform of the selected
101 | controls and store them.
102 | """
103 |
104 | identifier = "poses_copyworldspace"
105 | display_name = "Pose : Copy : World Space"
106 | icon = get_icon("pose_store")
107 |
108 | TRANSFORM_DATA = dict()
109 |
110 | def run(self):
111 | # -- Clear out any dictionary data
112 | CopyWorldSpaceTool.TRANSFORM_DATA = dict()
113 |
114 | for node in pm.selected():
115 | CopyWorldSpaceTool.TRANSFORM_DATA[node.name()] = node.getMatrix(
116 | worldSpace=True)
117 |
118 |
119 | # --------------------------------------------------------------------------------------
120 | class PasteWorldSpaceTool(crab.tools.AnimTool):
121 | """
122 | This will apply the previously stored world space transforms back to the objects.
123 |
124 | If "Selection Only" is turned on, then the pose will only be applied to matching
125 | objects which are also selected.
126 | """
127 |
128 | identifier = "poses_copyworldspace_paste"
129 | display_name = "Pose : Paste : World Space"
130 | icon = get_icon("pose_apply")
131 |
132 | def run(self):
133 |
134 | for name, matrix in CopyWorldSpaceTool.TRANSFORM_DATA.items():
135 | if pm.objExists(name):
136 | pm.PyNode(name).setMatrix(
137 | CopyWorldSpaceTool.TRANSFORM_DATA[name],
138 | worldSpace=True,
139 | )
140 |
--------------------------------------------------------------------------------
/crab/plugins/tools/anim/snap.py:
--------------------------------------------------------------------------------
1 | import os
2 | import crab
3 | import pymel.core as pm
4 |
5 |
6 | # --------------------------------------------------------------------------------------
7 | def get_icon(name):
8 | return os.path.join(
9 | os.path.dirname(os.path.dirname(__file__)),
10 | "icons",
11 | "%s.png" % name,
12 | )
13 |
14 |
15 | # --------------------------------------------------------------------------------------
16 | class SnapTool(crab.tools.AnimTool):
17 | """
18 | Snaps whichever limb is currently selected
19 | """
20 |
21 | identifier = "snap_ikfk"
22 | display_name = "Snap : IKFK"
23 | icon = None
24 |
25 | tooltips = dict(
26 | KeyOnReset="If True, the controls will be keyframed once altered",
27 | SelectionOnly="If true only objects in the current selection will be affected",
28 | AcrossTimeSpan=(
29 | "If True, this will be performed per-frame for the "
30 | "whole visual timespan"
31 | ),
32 | )
33 |
34 | def __init__(self):
35 | super(SnapTool, self).__init__()
36 |
37 | self.options.KeyOnSnap = False
38 | self.options.SelectionOnly = False
39 | self.options.AcrossTimeSpan = False
40 |
41 | def run(self):
42 |
43 | # -- Get a list of all the results
44 | results = [
45 | n
46 | for n in crab.utils.access.component_nodes(pm.selected()[0], "transform")
47 | if crab.config.CONTROL in n.name()
48 | ]
49 |
50 | # -- Create a unique list of labels
51 | labels = list()
52 | for node in results:
53 | labels.extend(crab.utils.snap.labels(node))
54 | labels = list(set(labels))
55 |
56 | if not labels:
57 | return
58 |
59 | # -- Apply the snap.
60 | crab.utils.snap.snap_label(
61 | label=labels[0],
62 | restrict_to=pm.selected() if self.options.SelectionOnly else None,
63 | start_time=int(
64 | pm.playbackOptions(
65 | q=True,
66 | min=True,
67 | ),
68 | ) if self.options.AcrossTimeSpan else None,
69 | end_time=int(
70 | pm.playbackOptions(
71 | q=True,
72 | max=True,
73 | ),
74 | ) if self.options.AcrossTimeSpan else None,
75 | key=self.options.KeyOnSnap,
76 | )
77 |
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/key_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/key_all.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/key_character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/key_character.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/key_selected.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/key_selected.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/pose_apply.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/pose_apply.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/pose_store.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/pose_store.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/reset_character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/reset_character.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/reset_selection.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/reset_selection.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/select_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/select_all.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/select_all_character.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/select_all_character.png
--------------------------------------------------------------------------------
/crab/plugins/tools/icons/select_opposite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/icons/select_opposite.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/joints/joint_orientation.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class MoveRotationsToOrientsTool(crab.RigTool):
7 |
8 | identifier = "joints_oriswitch_rotations_to_orients"
9 | display_name = "Move Rotations to Orients"
10 | icon = "joints.png"
11 |
12 | tooltips = dict(
13 | mirror_plane=(
14 | "Moves any rotations on the normal X,Y,Z channels to the "
15 | "joint orient channels"
16 | ),
17 | )
18 |
19 | # ----------------------------------------------------------------------------------
20 | def run(self):
21 | for node in pm.selected():
22 | crab.utils.joints.move_rotations_to_joint_orients(node)
23 |
24 |
25 | # --------------------------------------------------------------------------------------
26 | class MoveOrientsToRotationsTool(crab.RigTool):
27 |
28 | identifier = "joints_oriswitch_orients_to_rotations"
29 | display_name = "Move Orients to Rotations"
30 | icon = "joints.png"
31 |
32 | tooltips = dict(
33 | mirror_plane=(
34 | "Moves any rotations on the joint orient channels to "
35 | "the normal X,Y,Z channels"
36 | ),
37 | )
38 |
39 | # ----------------------------------------------------------------------------------
40 | def run(self):
41 | for node in pm.selected():
42 | crab.utils.joints.move_joint_orients_to_rotations(node)
43 |
44 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/joints/joint_styling.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class ToggleJointStyleTool(crab.RigTool):
7 |
8 | identifier = "joints_toggle_draw"
9 | display_name = "Toggle Draw"
10 | icon = "joints.png"
11 |
12 | # ----------------------------------------------------------------------------------
13 | def run(self):
14 | selected = pm.selected()
15 |
16 | if not selected:
17 | return
18 |
19 | if not isinstance(selected[0], pm.nt.Joint):
20 | return
21 |
22 | current_value = selected[0].drawStyle.get()
23 |
24 | value = 2 if not current_value else 0
25 |
26 | for joint in pm.selected():
27 | if isinstance(joint, pm.nt.Joint):
28 | joint.drawStyle.set(value)
29 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/joints/joints.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/joints/joints.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/joints/singulization.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class SingulizeSelected(crab.RigTool):
7 |
8 | identifier = "joints_singulize_selected"
9 | display_name = "Singulize Selected"
10 | icon = "joints.png"
11 |
12 | # ----------------------------------------------------------------------------------
13 | def run(self):
14 | rig = crab.Rig(node=pm.selected()[0])
15 |
16 | for node in pm.selected():
17 | rig.add_component("Core : Singular", pre_existing_joint=node.name())
18 |
19 |
20 | # --------------------------------------------------------------------------------------
21 | class SingulizeAll(crab.RigTool):
22 |
23 | identifier = "joints_singulize_all"
24 | display_name = "Singulize All"
25 | icon = "joints.png"
26 |
27 | # ----------------------------------------------------------------------------------
28 | def run(self):
29 |
30 | # -- Get the currently active rig
31 | rig = crab.Rig.all()[0]
32 |
33 | # -- Find all teh joints in the rig so we can check which
34 | # -- require singulizing
35 | all_joints = rig.skeleton_org().getChildren(
36 | allDescendents=True,
37 | type="joint",
38 | )
39 |
40 | for joint in all_joints:
41 |
42 | # -- Assume the joint will need singulizing unless
43 | # -- we find otherwise.
44 | requires_singulizing = True
45 |
46 | # -- If the joint has crab meta attached, then we do not
47 | # -- want to singulize it
48 | for possible_meta in joint.outputs(type="network"):
49 | if possible_meta.hasAttr("crabComponent"):
50 | requires_singulizing = False
51 | break
52 |
53 | # -- Singulize if required
54 | if requires_singulizing:
55 | rig.add_component("Core : Singular", pre_existing_joint=joint.name())
56 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/joints/upvectors.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class GenerateUpvectorPoint(crab.RigTool):
7 |
8 | identifier = "generate_upvector_position"
9 | display_name = "Generate Upvector Position"
10 | icon = "joints.png"
11 |
12 | # ----------------------------------------------------------------------------------
13 | def __init__(self):
14 | super(GenerateUpvectorPoint, self).__init__()
15 |
16 | self.options.length_factor = 1.0
17 |
18 | # ----------------------------------------------------------------------------------
19 | def run(self):
20 |
21 | if len(pm.selected()) < 3:
22 | print("You must select at least three objects")
23 | return False
24 |
25 | # -- Cache the selection
26 | user_selection = pm.selected()[:]
27 |
28 | # -- Calculate the position
29 | position = crab.utils.maths.calculate_upvector_position(
30 | user_selection[0],
31 | user_selection[1],
32 | user_selection[2],
33 | length=self.options.length_factor,
34 | )
35 |
36 | # -- Create the locator
37 | locator = pm.spaceLocator()
38 | locator.rename(
39 | "{}_{}_{}_vector".format(
40 | user_selection[0].name(),
41 | user_selection[1].name(),
42 | user_selection[2].name(),
43 | ),
44 | )
45 |
46 | # -- Position the locator
47 | locator.setTranslation(
48 | position,
49 | space="world",
50 | )
51 |
52 | return True
53 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/meshes/meshes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/meshes/meshes.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/meshes/meshes.py:
--------------------------------------------------------------------------------
1 | import re
2 | import crab
3 | import pymel.core as pm
4 |
5 |
6 | # --------------------------------------------------------------------------------------
7 | class GenerateCubeMeshTool(crab.RigTool):
8 |
9 | identifier = "meshes_generate_cubes"
10 | display_name = "Generate Cubes"
11 | icon = "meshes.png"
12 |
13 | tooltips = dict(
14 | size="How large the mesh cubes should be",
15 | size_regexes=(
16 | "If given this can be a regex:size;regex:size pattern, any "
17 | "joints matching that regex will then use that size instead of the default"
18 | ),
19 | )
20 |
21 | # ----------------------------------------------------------------------------------
22 | def __init__(self):
23 | super(GenerateCubeMeshTool, self).__init__()
24 |
25 | self.options.size = 4
26 | self.options.size_regexes = ""
27 |
28 | # ----------------------------------------------------------------------------------
29 | def run(self):
30 | cubes = list()
31 |
32 | regexes = {}
33 | if self.options.size_regexes:
34 | for size_details in self.options.size_regexes.split(";"):
35 |
36 | regex = re.compile(size_details.split(":")[0])
37 | value = float(size_details.split(":")[1])
38 |
39 | regexes[regex] = value
40 |
41 | for joint in pm.PyNode("deformers").members():
42 |
43 | size = self.options.size
44 |
45 | for regex, size_variant in regexes.items():
46 | if regex.search(joint.name()):
47 | size = size_variant
48 | break
49 |
50 | cube, shape = pm.polyCube(
51 | width=size,
52 | height=size,
53 | depth=size,
54 | )
55 |
56 | pm.delete(cube, constructionHistory=True)
57 |
58 | cube.setMatrix(
59 | joint.getMatrix(worldSpace=True),
60 | worldSpace=True,
61 | )
62 |
63 | pm.skinCluster(
64 | joint,
65 | cube,
66 | toSelectedBones=True,
67 | )
68 |
69 | cubes.append(cube)
70 |
71 | mesh, skin = pm.polyUniteSkinned(
72 | cubes,
73 | ch=1,
74 | mergeUVSets=1,
75 | centerPivot=True,
76 | )
77 |
78 | pm.select(mesh)
79 | pm.mel.BakeNonDefHistory()
80 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/naming/naming.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/naming/naming.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/organisation/clean.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/organisation/clean.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/organisation/clean.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class CleanMeta(crab.RigTool):
7 | """
8 | This will clean/remove any redundant crab meta nodes along with any orphan
9 | guide roots
10 | """
11 |
12 | identifier = "clean_meta"
13 | display_name = "Clean Rig Meta"
14 | icon = "clean.png"
15 |
16 | # ----------------------------------------------------------------------------------
17 | def run(self):
18 |
19 | meta_nodes = [
20 | node for node in pm.ls("META_*")
21 | if node.hasAttr(crab.config.COMPONENT_MARKER)
22 | ]
23 |
24 | for meta_node in meta_nodes:
25 |
26 | # -- Check if it has a skeletal root connection, if it does, then its
27 | # -- perfectly valid
28 | if meta_node.attr(crab.config.SKELETON_ROOT_LINK_ATTR).inputs():
29 | continue
30 |
31 | # -- If this is an orphan, then check for a guide connection, if there
32 | # -- is one, then we remove the guide
33 | guide_connection = meta_node.attr(crab.config.GUIDE_ROOT_LINK_ATTR).inputs()
34 |
35 | if guide_connection:
36 | pm.delete(guide_connection)
37 |
38 | # -- Finally delete the meta node
39 | if pm.objExists(meta_node):
40 | pm.delete(meta_node)
41 |
42 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/poses/poses.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/poses/poses.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/shapes/shape_selection.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class ShapeSelectTool(crab.RigTool):
7 |
8 | identifier = "shape_select"
9 | display_name = "Select Shape"
10 | icon = "shapes.png"
11 |
12 | # ----------------------------------------------------------------------------------
13 | def run(self):
14 |
15 | shapes = list()
16 |
17 | for node in pm.selected():
18 | if isinstance(node, pm.nt.NurbsCurve):
19 | shapes.append(node)
20 | continue
21 |
22 | shapes.extend(node.getShapes())
23 |
24 | pm.select(shapes)
25 |
26 |
27 | # --------------------------------------------------------------------------------------
28 | class ShapeSelectTransformsTool(crab.RigTool):
29 |
30 | identifier = "shape_select_transforms"
31 | display_name = "Select Shape: Transforms"
32 | icon = "shapes.png"
33 |
34 | # ----------------------------------------------------------------------------------
35 | def run(self):
36 |
37 | transforms = list()
38 |
39 | for node in pm.selected():
40 | if isinstance(node, pm.nt.NurbsCurve):
41 | transforms.append(node.getParent())
42 | continue
43 |
44 | transforms.extend(node)
45 |
46 | pm.select(transforms)
47 |
48 |
49 | # --------------------------------------------------------------------------------------
50 | class ShapeSelectLeftTool(crab.RigTool):
51 |
52 | identifier = "shape_select_shapes_left"
53 | display_name = "Select Shape: All Left"
54 | icon = "shapes.png"
55 |
56 | # ----------------------------------------------------------------------------------
57 | def run(self):
58 |
59 | shapes = list()
60 |
61 | for node in pm.ls("%s_*_%s" % (crab.config.CONTROL, crab.config.LEFT)):
62 | if isinstance(node, pm.nt.NurbsCurve):
63 | shapes.append(node)
64 | continue
65 |
66 | shapes.extend(node.getShapes())
67 |
68 | pm.select(shapes)
69 |
70 |
71 | # --------------------------------------------------------------------------------------
72 | class ShapeSelectRightTool(crab.RigTool):
73 |
74 | identifier = "shape_select_shapes_right"
75 | display_name = "Select Shape: All Right"
76 | icon = "shapes.png"
77 |
78 | # ----------------------------------------------------------------------------------
79 | def run(self):
80 |
81 | shapes = list()
82 |
83 | for node in pm.ls(
84 | "%s_*_%s" % (crab.config.CONTROL, crab.config.RIGHT)):
85 | if isinstance(node, pm.nt.NurbsCurve):
86 | shapes.append(node)
87 | continue
88 |
89 | shapes.extend(node.getShapes())
90 |
91 | pm.select(shapes)
92 |
93 |
94 | # --------------------------------------------------------------------------------------
95 | class ScaleShapeTool(crab.RigTool):
96 |
97 | identifier = "shape_scale"
98 | display_name = "Scale Shapes"
99 | icon = "shapes.png"
100 |
101 | # ----------------------------------------------------------------------------------
102 | def __init__(self):
103 | super(ScaleShapeTool, self).__init__()
104 |
105 | self.options.wildcard = crab.config.CONTROL + "*"
106 | self.options.scale_by = 1.0
107 | self.options.selection_only = False
108 |
109 | # ----------------------------------------------------------------------------------
110 | def run(self):
111 |
112 | if self.options.selection_only:
113 | nodes = pm.selected()
114 |
115 | else:
116 | nodes = pm.ls(self.options.wildcard, type="transform")
117 |
118 | for node in nodes:
119 |
120 | # -- If there are no curve shapes on this node, skip it
121 | if not node.getShape():
122 | continue
123 |
124 | for shape in node.getShapes():
125 |
126 | # -- If it is not a nurbs curve, skip it
127 | if not isinstance(shape, pm.nt.NurbsCurve):
128 | continue
129 |
130 | for idx in range(shape.numCVs()):
131 | # -- Match the worldspace cv positions
132 | shape.setCV(
133 | idx,
134 | shape.getCV(idx) * self.options.scale_by,
135 | )
136 |
137 | shape.updateCurve()
138 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/shapes/shapes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/shapes/shapes.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/skinning/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/skinning/__init__.py
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/skinning/skin.py:
--------------------------------------------------------------------------------
1 | import crab
2 | import pymel.core as pm
3 |
4 |
5 | # --------------------------------------------------------------------------------------
6 | class SkinDisconnect(crab.RigTool):
7 |
8 | identifier = "skinning_disconnect"
9 | display_name = "Skinning : Disconnect All Skins"
10 | icon = "skinning.png"
11 |
12 | # ----------------------------------------------------------------------------------
13 | def run(self):
14 |
15 | for skin in pm.ls(type="skinCluster"):
16 | skin.moveJointsMode(True)
17 |
18 |
19 | # --------------------------------------------------------------------------------------
20 | class SkinReconnect(crab.RigTool):
21 |
22 | identifier = "skinning_connect"
23 | display_name = "Skinning : Reconnect All Skins"
24 | icon = "skinning.png"
25 |
26 | # ----------------------------------------------------------------------------------
27 | def run(self):
28 | for skin in pm.ls(type="skinCluster"):
29 | skin.moveJointsMode(False)
30 |
31 |
32 | # --------------------------------------------------------------------------------------
33 | class CopyToUnboundMesh(crab.RigTool):
34 |
35 | identifier = "skinning_copy_to_unbound_mesh"
36 | display_name = "Skinning : Copy Skin To Unbound Mesh"
37 | icon = "skinning.png"
38 |
39 | # ----------------------------------------------------------------------------------
40 | def run(self, current_skin_host=None, target=None):
41 |
42 | # -- Get our mesh candidates
43 | current_skin_host = current_skin_host or pm.selected()[0]
44 | targets = target or pm.selected()[1:]
45 |
46 | if not isinstance(targets, list):
47 | targets = [targets]
48 |
49 | # -- Look for the mesh
50 | skin = pm.PyNode(pm.mel.findRelatedSkinCluster(current_skin_host))
51 |
52 | # -- Get a list of the influence objects being used
53 | # -- by the skin currently
54 | influences = skin.influenceObjects()
55 |
56 | for target in targets:
57 | # -- Apply a new skin cluster
58 | new_skin = pm.skinCluster(
59 | influences,
60 | target,
61 | toSelectedBones=True,
62 | maximumInfluences=skin.getMaximumInfluences()
63 | )
64 |
65 | # -- Copy the skin weights between them
66 | pm.copySkinWeights(
67 | sourceSkin=skin,
68 | destinationSkin=new_skin,
69 | noMirror=True,
70 | surfaceAssociation="closestPoint",
71 | influenceAssociation=["name", "closestJoint", "label"],
72 | )
73 |
74 | # -- Keep the source mesh selected
75 | pm.select(current_skin_host)
76 |
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/skinning/skinning.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/skinning/skinning.png
--------------------------------------------------------------------------------
/crab/plugins/tools/rigging/transform/transforms.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/plugins/tools/rigging/transform/transforms.png
--------------------------------------------------------------------------------
/crab/resources/icons/add_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/add_button.png
--------------------------------------------------------------------------------
/crab/resources/icons/behaviour.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/behaviour.png
--------------------------------------------------------------------------------
/crab/resources/icons/breaker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/breaker.png
--------------------------------------------------------------------------------
/crab/resources/icons/build_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/build_button.png
--------------------------------------------------------------------------------
/crab/resources/icons/component.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/component.png
--------------------------------------------------------------------------------
/crab/resources/icons/crab.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/crab.gif
--------------------------------------------------------------------------------
/crab/resources/icons/crab_animator.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/crab_animator.png
--------------------------------------------------------------------------------
/crab/resources/icons/crab_overview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/crab_overview.png
--------------------------------------------------------------------------------
/crab/resources/icons/crab_overview_tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/crab_overview_tab.png
--------------------------------------------------------------------------------
/crab/resources/icons/crab_tools_tab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/crab_tools_tab.png
--------------------------------------------------------------------------------
/crab/resources/icons/edit_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/edit_button.png
--------------------------------------------------------------------------------
/crab/resources/icons/help_spaceswitch.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/help_spaceswitch.gif
--------------------------------------------------------------------------------
/crab/resources/icons/new_button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/new_button.png
--------------------------------------------------------------------------------
/crab/resources/icons/tool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/resources/icons/tool.png
--------------------------------------------------------------------------------
/crab/resources/shapes/AxisTest.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.0,
7 | 0.0,
8 | 0.0
9 | ],
10 | [
11 | 0.0,
12 | 0.0,
13 | 0.0
14 | ],
15 | [
16 | 0.0,
17 | 0.0,
18 | 0.0
19 | ],
20 | [
21 | 0.0,
22 | 0.00184,
23 | -70.0
24 | ],
25 | [
26 | 0.0,
27 | 0.00184,
28 | -70.0
29 | ],
30 | [
31 | 0.0,
32 | 0.00184,
33 | -70.0
34 | ],
35 | [
36 | 0.0,
37 | -19.99868,
38 | -50.00053
39 | ],
40 | [
41 | 0.0,
42 | -19.99868,
43 | -50.00053
44 | ],
45 | [
46 | 0.0,
47 | -19.99868,
48 | -50.00053
49 | ],
50 | [
51 | 0.0,
52 | 20.00132,
53 | -49.99947
54 | ],
55 | [
56 | 0.0,
57 | 20.00132,
58 | -49.99947
59 | ],
60 | [
61 | 0.0,
62 | 20.00132,
63 | -49.99947
64 | ],
65 | [
66 | 0.0,
67 | 0.00184,
68 | -70.0
69 | ],
70 | [
71 | 0.0,
72 | 0.00184,
73 | -70.0
74 | ],
75 | [
76 | 0.0,
77 | 0.00184,
78 | -70.0
79 | ],
80 | [
81 | 0.0,
82 | 0.00053,
83 | -20.0
84 | ],
85 | [
86 | 0.0,
87 | 0.00053,
88 | -20.0
89 | ],
90 | [
91 | 0.0,
92 | 0.00053,
93 | -20.0
94 | ],
95 | [
96 | 40.0,
97 | 0.0,
98 | 0.0
99 | ],
100 | [
101 | 40.0,
102 | 0.0,
103 | 0.0
104 | ],
105 | [
106 | 40.0,
107 | 0.0,
108 | 0.0
109 | ],
110 | [
111 | 0.0,
112 | 0.0,
113 | 0.0
114 | ],
115 | [
116 | 0.0,
117 | 0.0,
118 | 0.0
119 | ],
120 | [
121 | 0.0,
122 | 0.0,
123 | 0.0
124 | ]
125 | ],
126 | "degree": 3,
127 | "form": 0,
128 | "knots": [
129 | 0.0,
130 | 0.0,
131 | 0.0,
132 | 1.0,
133 | 2.0,
134 | 3.0,
135 | 4.0,
136 | 5.0,
137 | 6.0,
138 | 7.0,
139 | 8.0,
140 | 9.0,
141 | 10.0,
142 | 11.0,
143 | 12.0,
144 | 13.0,
145 | 14.0,
146 | 15.0,
147 | 16.0,
148 | 17.0,
149 | 18.0,
150 | 19.0,
151 | 20.0,
152 | 21.0,
153 | 21.0,
154 | 21.0
155 | ]
156 | }
157 | ],
158 | "node": "transform4",
159 | "up_axis": "y"
160 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/EyeCircle.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.8113,
7 | 2e-05,
8 | -0.0
9 | ],
10 | [
11 | 0.74954,
12 | 0.3105,
13 | 0.0
14 | ],
15 | [
16 | 0.57366,
17 | 0.5737,
18 | 0.0
19 | ],
20 | [
21 | 0.31046,
22 | 0.74956,
23 | 0.0
24 | ],
25 | [
26 | -2e-05,
27 | 0.8113,
28 | 0.0
29 | ],
30 | [
31 | -0.3105,
32 | 0.74954,
33 | -0.0
34 | ],
35 | [
36 | -0.5737,
37 | 0.57366,
38 | 0.0
39 | ],
40 | [
41 | -0.74956,
42 | 0.31046,
43 | 0.0
44 | ],
45 | [
46 | -0.8113,
47 | -2e-05,
48 | 0.0
49 | ],
50 | [
51 | -0.74954,
52 | -0.3105,
53 | -0.0
54 | ],
55 | [
56 | -0.57366,
57 | -0.5737,
58 | 0.0
59 | ],
60 | [
61 | -0.31046,
62 | -0.74956,
63 | -0.0
64 | ],
65 | [
66 | 2e-05,
67 | -0.8113,
68 | -0.0
69 | ],
70 | [
71 | 0.3105,
72 | -0.74954,
73 | 0.0
74 | ],
75 | [
76 | 0.5737,
77 | -0.57366,
78 | -0.0
79 | ],
80 | [
81 | 0.74956,
82 | -0.31046,
83 | -0.0
84 | ],
85 | [
86 | 0.8113,
87 | 2e-05,
88 | -0.0
89 | ]
90 | ],
91 | "degree": 1,
92 | "form": 0,
93 | "knots": [
94 | 0.0,
95 | 1.0,
96 | 2.0,
97 | 3.0,
98 | 4.0,
99 | 5.0,
100 | 6.0,
101 | 7.0,
102 | 8.0,
103 | 9.0,
104 | 10.0,
105 | 11.0,
106 | 12.0,
107 | 13.0,
108 | 14.0,
109 | 15.0,
110 | 16.0
111 | ]
112 | }
113 | ],
114 | "node": "transform2",
115 | "up_axis": "y"
116 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/EyeTarget.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 4.21583,
7 | 1.02614,
8 | 0.0
9 | ],
10 | [
11 | -4.17432,
12 | 1.02592,
13 | -0.0
14 | ],
15 | [
16 | -4.56696,
17 | 0.94781,
18 | 0.0
19 | ],
20 | [
21 | -4.89983,
22 | 0.72539,
23 | -0.0
24 | ],
25 | [
26 | -5.12223,
27 | 0.39252,
28 | -0.0
29 | ],
30 | [
31 | -5.20032,
32 | -0.00014,
33 | 0.0
34 | ],
35 | [
36 | -5.12221,
37 | -0.39278,
38 | -0.0
39 | ],
40 | [
41 | -4.89979,
42 | -0.72565,
43 | -0.0
44 | ],
45 | [
46 | -4.56692,
47 | -0.94805,
48 | -0.0
49 | ],
50 | [
51 | -4.17426,
52 | -1.02614,
53 | -0.0
54 | ],
55 | [
56 | 4.21589,
57 | -1.02592,
58 | 0.0
59 | ],
60 | [
61 | 4.60853,
62 | -0.94781,
63 | 0.0
64 | ],
65 | [
66 | 4.9414,
67 | -0.72539,
68 | 0.0
69 | ],
70 | [
71 | 5.1638,
72 | -0.39251,
73 | 0.0
74 | ],
75 | [
76 | 5.24189,
77 | 0.00014,
78 | 0.0
79 | ],
80 | [
81 | 5.16378,
82 | 0.39279,
83 | 0.0
84 | ],
85 | [
86 | 4.94136,
87 | 0.72565,
88 | 0.0
89 | ],
90 | [
91 | 4.60849,
92 | 0.94805,
93 | 0.0
94 | ],
95 | [
96 | 4.21583,
97 | 1.02614,
98 | 0.0
99 | ]
100 | ],
101 | "degree": 1,
102 | "form": 0,
103 | "knots": [
104 | 0.0,
105 | 1.0,
106 | 2.0,
107 | 3.0,
108 | 4.0,
109 | 5.0,
110 | 6.0,
111 | 7.0,
112 | 8.0,
113 | 9.0,
114 | 10.0,
115 | 11.0,
116 | 12.0,
117 | 13.0,
118 | 14.0,
119 | 15.0,
120 | 16.0,
121 | 17.0,
122 | 18.0
123 | ]
124 | }
125 | ],
126 | "node": "transform1",
127 | "up_axis": "y"
128 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/FacialMarker.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | -0.22914,
7 | 0.22915,
8 | -0.22913
9 | ],
10 | [
11 | -0.22914,
12 | -0.22913,
13 | -0.22915
14 | ],
15 | [
16 | 0.22914,
17 | -0.22913,
18 | -0.22915
19 | ],
20 | [
21 | 0.22914,
22 | 0.22915,
23 | -0.22913
24 | ],
25 | [
26 | -0.22914,
27 | 0.22915,
28 | -0.22913
29 | ],
30 | [
31 | -0.22914,
32 | 0.22913,
33 | 0.22915
34 | ],
35 | [
36 | 0.22914,
37 | 0.22913,
38 | 0.22915
39 | ],
40 | [
41 | 0.22914,
42 | -0.22915,
43 | 0.22913
44 | ],
45 | [
46 | -0.22914,
47 | -0.22915,
48 | 0.22913
49 | ],
50 | [
51 | -0.22914,
52 | 0.22913,
53 | 0.22915
54 | ],
55 | [
56 | 0.22914,
57 | 0.22913,
58 | 0.22915
59 | ],
60 | [
61 | 0.22914,
62 | 0.22915,
63 | -0.22913
64 | ],
65 | [
66 | 0.22914,
67 | -0.22913,
68 | -0.22915
69 | ],
70 | [
71 | 0.22914,
72 | -0.22915,
73 | 0.22913
74 | ],
75 | [
76 | -0.22914,
77 | -0.22915,
78 | 0.22913
79 | ],
80 | [
81 | -0.22914,
82 | -0.22913,
83 | -0.22915
84 | ]
85 | ],
86 | "degree": 1,
87 | "form": 0,
88 | "knots": [
89 | 0.0,
90 | 1.0,
91 | 2.0,
92 | 3.0,
93 | 4.0,
94 | 5.0,
95 | 6.0,
96 | 7.0,
97 | 8.0,
98 | 9.0,
99 | 10.0,
100 | 11.0,
101 | 12.0,
102 | 13.0,
103 | 14.0,
104 | 15.0
105 | ]
106 | }
107 | ],
108 | "node": "transform24",
109 | "up_axis": "y"
110 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/FacialWidget.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.45197,
7 | 1e-05,
8 | -0.0
9 | ],
10 | [
11 | 0.41757,
12 | 0.17297,
13 | 0.0
14 | ],
15 | [
16 | 0.31959,
17 | 0.31961,
18 | -0.0
19 | ],
20 | [
21 | 0.17295,
22 | 0.41757,
23 | -0.0
24 | ],
25 | [
26 | -1e-05,
27 | 0.45197,
28 | 0.0
29 | ],
30 | [
31 | -0.17297,
32 | 0.41757,
33 | 0.0
34 | ],
35 | [
36 | -0.31961,
37 | 0.31959,
38 | 0.0
39 | ],
40 | [
41 | -0.41757,
42 | 0.17295,
43 | -0.0
44 | ],
45 | [
46 | -0.45197,
47 | -1e-05,
48 | 0.0
49 | ],
50 | [
51 | -0.41757,
52 | -0.17297,
53 | -0.0
54 | ],
55 | [
56 | -0.31959,
57 | -0.31961,
58 | 0.0
59 | ],
60 | [
61 | -0.17295,
62 | -0.41757,
63 | 0.0
64 | ],
65 | [
66 | 1e-05,
67 | -0.45197,
68 | -0.0
69 | ],
70 | [
71 | 0.17297,
72 | -0.41757,
73 | -0.0
74 | ],
75 | [
76 | 0.31961,
77 | -0.31959,
78 | -0.0
79 | ],
80 | [
81 | 0.41757,
82 | -0.17295,
83 | 0.0
84 | ],
85 | [
86 | 0.45197,
87 | 1e-05,
88 | -0.0
89 | ]
90 | ],
91 | "degree": 1,
92 | "form": 0,
93 | "knots": [
94 | 0.0,
95 | 1.0,
96 | 2.0,
97 | 3.0,
98 | 4.0,
99 | 5.0,
100 | 6.0,
101 | 7.0,
102 | 8.0,
103 | 9.0,
104 | 10.0,
105 | 11.0,
106 | 12.0,
107 | 13.0,
108 | 14.0,
109 | 15.0,
110 | 16.0
111 | ]
112 | }
113 | ],
114 | "node": "transform1",
115 | "up_axis": "y"
116 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/arrow_x.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 10.0,
7 | 0.0,
8 | 0.0
9 | ],
10 | [
11 | 5.0,
12 | -0.00013,
13 | 5.0
14 | ],
15 | [
16 | 5.0,
17 | -8e-05,
18 | 3.0
19 | ],
20 | [
21 | -11.0,
22 | 0.0,
23 | 0.0
24 | ],
25 | [
26 | 5.0,
27 | 8e-05,
28 | -3.0
29 | ],
30 | [
31 | 5.0,
32 | 0.00013,
33 | -5.0
34 | ],
35 | [
36 | 10.0,
37 | 0.0,
38 | 0.0
39 | ]
40 | ],
41 | "degree": 1,
42 | "form": 0,
43 | "knots": [
44 | 0.0,
45 | 1.0,
46 | 2.0,
47 | 3.0,
48 | 4.0,
49 | 5.0,
50 | 6.0
51 | ]
52 | }
53 | ],
54 | "node": "transform1",
55 | "up_axis": "y"
56 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/arrow_z.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.0,
7 | -0.50027,
8 | 10.49999
9 | ],
10 | [
11 | 5.0,
12 | -0.50014,
13 | 5.49999
14 | ],
15 | [
16 | 3.0,
17 | -0.50014,
18 | 5.49999
19 | ],
20 | [
21 | -1.0,
22 | -0.49973,
23 | -10.50001
24 | ],
25 | [
26 | -3.0,
27 | -0.50014,
28 | 5.49999
29 | ],
30 | [
31 | -5.0,
32 | -0.50014,
33 | 5.49999
34 | ],
35 | [
36 | 0.0,
37 | -0.50027,
38 | 10.49999
39 | ]
40 | ],
41 | "degree": 1,
42 | "form": 0,
43 | "knots": [
44 | 0.0,
45 | 1.0,
46 | 2.0,
47 | 3.0,
48 | 4.0,
49 | 5.0,
50 | 6.0
51 | ]
52 | }
53 | ],
54 | "node": "transform1",
55 | "up_axis": "y"
56 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/circle.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.0,
7 | 3e-05,
8 | -1.02603
9 | ],
10 | [
11 | -0.39265,
12 | 2e-05,
13 | -0.94793
14 | ],
15 | [
16 | -0.72552,
17 | 2e-05,
18 | -0.72552
19 | ],
20 | [
21 | -0.94793,
22 | 1e-05,
23 | -0.39265
24 | ],
25 | [
26 | -1.02603,
27 | 0.0,
28 | 0.0
29 | ],
30 | [
31 | -0.94793,
32 | -1e-05,
33 | 0.39265
34 | ],
35 | [
36 | -0.72552,
37 | -2e-05,
38 | 0.72552
39 | ],
40 | [
41 | -0.39265,
42 | -2e-05,
43 | 0.94793
44 | ],
45 | [
46 | 0.0,
47 | -3e-05,
48 | 1.02603
49 | ],
50 | [
51 | 0.39265,
52 | -2e-05,
53 | 0.94793
54 | ],
55 | [
56 | 0.72552,
57 | -2e-05,
58 | 0.72552
59 | ],
60 | [
61 | 0.94793,
62 | -1e-05,
63 | 0.39265
64 | ],
65 | [
66 | 1.02603,
67 | 0.0,
68 | 0.0
69 | ],
70 | [
71 | 0.94793,
72 | 1e-05,
73 | -0.39265
74 | ],
75 | [
76 | 0.72552,
77 | 2e-05,
78 | -0.72552
79 | ],
80 | [
81 | 0.39265,
82 | 2e-05,
83 | -0.94793
84 | ],
85 | [
86 | 0.0,
87 | 3e-05,
88 | -1.02603
89 | ]
90 | ],
91 | "degree": 1,
92 | "form": 0,
93 | "knots": [
94 | 0.0,
95 | 1.0,
96 | 2.0,
97 | 3.0,
98 | 4.0,
99 | 5.0,
100 | 6.0,
101 | 7.0,
102 | 8.0,
103 | 9.0,
104 | 10.0,
105 | 11.0,
106 | 12.0,
107 | 13.0,
108 | 14.0,
109 | 15.0,
110 | 16.0
111 | ]
112 | }
113 | ],
114 | "node": "transform9",
115 | "up_axis": "y"
116 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/cube.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | -3.0,
7 | 3.00008,
8 | -2.99992
9 | ],
10 | [
11 | -3.0,
12 | -2.99992,
13 | -3.00008
14 | ],
15 | [
16 | 3.0,
17 | -2.99992,
18 | -3.00008
19 | ],
20 | [
21 | 3.0,
22 | 3.00008,
23 | -2.99992
24 | ],
25 | [
26 | -3.0,
27 | 3.00008,
28 | -2.99992
29 | ],
30 | [
31 | -3.0,
32 | 2.99992,
33 | 3.00008
34 | ],
35 | [
36 | 3.0,
37 | 2.99992,
38 | 3.00008
39 | ],
40 | [
41 | 3.0,
42 | -3.00008,
43 | 2.99992
44 | ],
45 | [
46 | -3.0,
47 | -3.00008,
48 | 2.99992
49 | ],
50 | [
51 | -3.0,
52 | 2.99992,
53 | 3.00008
54 | ],
55 | [
56 | 3.0,
57 | 2.99992,
58 | 3.00008
59 | ],
60 | [
61 | 3.0,
62 | 3.00008,
63 | -2.99992
64 | ],
65 | [
66 | 3.0,
67 | -2.99992,
68 | -3.00008
69 | ],
70 | [
71 | 3.0,
72 | -3.00008,
73 | 2.99992
74 | ],
75 | [
76 | -3.0,
77 | -3.00008,
78 | 2.99992
79 | ],
80 | [
81 | -3.0,
82 | -2.99992,
83 | -3.00008
84 | ]
85 | ],
86 | "degree": 1,
87 | "form": 0,
88 | "knots": [
89 | 0.0,
90 | 1.0,
91 | 2.0,
92 | 3.0,
93 | 4.0,
94 | 5.0,
95 | 6.0,
96 | 7.0,
97 | 8.0,
98 | 9.0,
99 | 10.0,
100 | 11.0,
101 | 12.0,
102 | 13.0,
103 | 14.0,
104 | 15.0
105 | ]
106 | }
107 | ],
108 | "node": "transform15",
109 | "up_axis": "y"
110 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/cup.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.40282,
7 | 0.10885,
8 | 0.0
9 | ],
10 | [
11 | -0.0,
12 | -0.058,
13 | -0.0
14 | ],
15 | [
16 | -0.40282,
17 | 0.10887,
18 | 0.0
19 | ],
20 | [
21 | -0.56966,
22 | 0.51169,
23 | 0.0
24 | ],
25 | [
26 | -0.40281,
27 | 0.51391,
28 | 0.0
29 | ],
30 | [
31 | 1e-05,
32 | 0.5139,
33 | 0.0
34 | ],
35 | [
36 | 0.40283,
37 | 0.51388,
38 | 0.0
39 | ],
40 | [
41 | 0.56969,
42 | 0.51166,
43 | -0.0
44 | ],
45 | [
46 | 0.40282,
47 | 0.10885,
48 | 0.0
49 | ],
50 | [
51 | -0.0,
52 | -0.058,
53 | -0.0
54 | ],
55 | [
56 | -0.40282,
57 | 0.10887,
58 | 0.0
59 | ]
60 | ],
61 | "degree": 3,
62 | "form": 0,
63 | "knots": [
64 | -2.0,
65 | -1.0,
66 | 0.0,
67 | 1.0,
68 | 2.0,
69 | 3.0,
70 | 4.0,
71 | 5.0,
72 | 6.0,
73 | 7.0,
74 | 8.0,
75 | 9.0,
76 | 10.0
77 | ]
78 | }
79 | ],
80 | "node": "transform1",
81 | "up_axis": "y"
82 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/foot.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 3.29883,
7 | 0.00404,
8 | -7.14783
9 | ],
10 | [
11 | -4.25788,
12 | 0.00384,
13 | -7.14783
14 | ],
15 | [
16 | -5.54298,
17 | 0.0038,
18 | -5.70721
19 | ],
20 | [
21 | -5.54298,
22 | 0.00383,
23 | 20.19787
24 | ],
25 | [
26 | -3.73342,
27 | 0.00388,
28 | 21.81118
29 | ],
30 | [
31 | 4.9832,
32 | 0.00411,
33 | 21.81118
34 | ],
35 | [
36 | 6.85322,
37 | 0.00416,
38 | 20.19787
39 | ],
40 | [
41 | 6.85322,
42 | 0.00414,
43 | 4.9825
44 | ],
45 | [
46 | 4.98319,
47 | 0.00409,
48 | 2.88869
49 | ],
50 | [
51 | 4.6108,
52 | 0.00407,
53 | -5.93709
54 | ],
55 | [
56 | 3.29883,
57 | 0.00404,
58 | -7.14783
59 | ]
60 | ],
61 | "degree": 1,
62 | "form": 0,
63 | "knots": [
64 | 0.0,
65 | 1.0,
66 | 2.0,
67 | 3.0,
68 | 4.0,
69 | 5.0,
70 | 6.0,
71 | 7.0,
72 | 8.0,
73 | 9.0,
74 | 10.0
75 | ]
76 | }
77 | ],
78 | "node": "transform1",
79 | "up_axis": "y"
80 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/heel.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.0,
7 | 3e-05,
8 | 0.00559
9 | ],
10 | [
11 | 2.51512,
12 | 0.0001,
13 | 0.00559
14 | ],
15 | [
16 | 3.35349,
17 | 0.00012,
18 | -0.44842
19 | ],
20 | [
21 | 3.35349,
22 | 0.00012,
23 | -1.76466
24 | ],
25 | [
26 | 4.19186,
27 | 0.00014,
28 | -2.21868
29 | ],
30 | [
31 | 4.19186,
32 | 0.00014,
33 | -13.2632
34 | ],
35 | [
36 | 3.35349,
37 | 0.00012,
38 | -13.71722
39 | ],
40 | [
41 | -3.35349,
42 | -6e-05,
43 | -13.71722
44 | ],
45 | [
46 | -4.19186,
47 | -8e-05,
48 | -13.2632
49 | ],
50 | [
51 | -4.19186,
52 | -8e-05,
53 | -2.21868
54 | ],
55 | [
56 | -3.35349,
57 | -6e-05,
58 | -1.76466
59 | ],
60 | [
61 | -3.35349,
62 | -6e-05,
63 | -0.44842
64 | ],
65 | [
66 | -2.51512,
67 | -4e-05,
68 | 0.00559
69 | ],
70 | [
71 | 0.0,
72 | 3e-05,
73 | 0.00559
74 | ]
75 | ],
76 | "degree": 1,
77 | "form": 0,
78 | "knots": [
79 | 0.0,
80 | 1.0,
81 | 2.0,
82 | 3.0,
83 | 4.0,
84 | 5.0,
85 | 6.0,
86 | 7.0,
87 | 8.0,
88 | 9.0,
89 | 10.0,
90 | 11.0,
91 | 12.0,
92 | 13.0
93 | ]
94 | }
95 | ],
96 | "node": "transform1",
97 | "up_axis": "y"
98 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/pin.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.51766,
7 | 2.47358,
8 | -0.0
9 | ],
10 | [
11 | 0.65491,
12 | 2.87017,
13 | -0.0
14 | ],
15 | [
16 | 0.60505,
17 | 3.12083,
18 | 0.0
19 | ],
20 | [
21 | 0.46306,
22 | 3.33331,
23 | -0.0
24 | ],
25 | [
26 | 0.25057,
27 | 3.47529,
28 | -0.0
29 | ],
30 | [
31 | -9e-05,
32 | 3.52514,
33 | 0.0
34 | ],
35 | [
36 | -0.25075,
37 | 3.47527,
38 | -0.0
39 | ],
40 | [
41 | -0.46324,
42 | 3.33329,
43 | 0.0
44 | ],
45 | [
46 | -0.60521,
47 | 3.12079,
48 | 0.0
49 | ],
50 | [
51 | -0.65507,
52 | 2.87013,
53 | 0.0
54 | ],
55 | [
56 | -0.5178,
57 | 2.47356,
58 | -0.0
59 | ],
60 | [
61 | -0.05128,
62 | 2.00814,
63 | -0.0
64 | ],
65 | [
66 | -0.0,
67 | 0.00915,
68 | 0.0
69 | ],
70 | [
71 | -0.0,
72 | 0.00915,
73 | 0.0
74 | ],
75 | [
76 | -0.0,
77 | 0.00915,
78 | 0.0
79 | ],
80 | [
81 | 0.05118,
82 | 2.00814,
83 | -0.0
84 | ],
85 | [
86 | 0.51766,
87 | 2.47358,
88 | -0.0
89 | ],
90 | [
91 | 0.65491,
92 | 2.87017,
93 | -0.0
94 | ],
95 | [
96 | 0.60505,
97 | 3.12083,
98 | 0.0
99 | ]
100 | ],
101 | "degree": 3,
102 | "form": 0,
103 | "knots": [
104 | -2.0,
105 | -1.0,
106 | 0.0,
107 | 1.0,
108 | 2.0,
109 | 3.0,
110 | 4.0,
111 | 5.0,
112 | 6.0,
113 | 7.0,
114 | 8.0,
115 | 9.0,
116 | 10.0,
117 | 11.0,
118 | 12.0,
119 | 13.0,
120 | 14.0,
121 | 15.0,
122 | 16.0,
123 | 17.0,
124 | 18.0
125 | ]
126 | }
127 | ],
128 | "node": "transform2",
129 | "up_axis": "y"
130 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/soft_square.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 13.18512,
7 | 0.00045,
8 | -17.07425
9 | ],
10 | [
11 | 3.90553,
12 | 0.0004,
13 | -15.21684
14 | ],
15 | [
16 | -3.90553,
17 | 0.0004,
18 | -15.21684
19 | ],
20 | [
21 | -13.18512,
22 | 0.00045,
23 | -17.07425
24 | ],
25 | [
26 | -17.07425,
27 | 0.00045,
28 | -17.07425
29 | ],
30 | [
31 | -17.07425,
32 | 0.00035,
33 | -13.27997
34 | ],
35 | [
36 | -15.4661,
37 | 0.00014,
38 | -5.15537
39 | ],
40 | [
41 | -15.4661,
42 | -0.00014,
43 | 5.15537
44 | ],
45 | [
46 | -17.07425,
47 | -0.00035,
48 | 13.27997
49 | ],
50 | [
51 | -17.07425,
52 | -0.00045,
53 | 17.07425
54 | ],
55 | [
56 | -13.27997,
57 | -0.00045,
58 | 17.07425
59 | ],
60 | [
61 | -5.15537,
62 | -0.00041,
63 | 15.4661
64 | ],
65 | [
66 | 5.15537,
67 | -0.00041,
68 | 15.4661
69 | ],
70 | [
71 | 13.27997,
72 | -0.00045,
73 | 17.07425
74 | ],
75 | [
76 | 17.07425,
77 | -0.00045,
78 | 17.07425
79 | ],
80 | [
81 | 17.07425,
82 | -0.00035,
83 | 13.27997
84 | ],
85 | [
86 | 15.4661,
87 | -0.00014,
88 | 5.15537
89 | ],
90 | [
91 | 15.4661,
92 | 0.00014,
93 | -5.15537
94 | ],
95 | [
96 | 17.07425,
97 | 0.00035,
98 | -13.27997
99 | ],
100 | [
101 | 17.07425,
102 | 0.00045,
103 | -17.07425
104 | ],
105 | [
106 | 13.18512,
107 | 0.00045,
108 | -17.07425
109 | ],
110 | [
111 | 3.90553,
112 | 0.0004,
113 | -15.21684
114 | ],
115 | [
116 | -3.90553,
117 | 0.0004,
118 | -15.21684
119 | ]
120 | ],
121 | "degree": 3,
122 | "form": 0,
123 | "knots": [
124 | -2.0,
125 | -1.0,
126 | 0.0,
127 | 1.0,
128 | 2.0,
129 | 3.0,
130 | 4.0,
131 | 5.0,
132 | 6.0,
133 | 7.0,
134 | 8.0,
135 | 9.0,
136 | 10.0,
137 | 11.0,
138 | 12.0,
139 | 13.0,
140 | 14.0,
141 | 15.0,
142 | 16.0,
143 | 17.0,
144 | 18.0,
145 | 19.0,
146 | 20.0,
147 | 21.0,
148 | 22.0
149 | ]
150 | }
151 | ],
152 | "node": "transform40",
153 | "up_axis": "y"
154 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/spiral.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.15689,
7 | 3.72271,
8 | 0.0
9 | ],
10 | [
11 | 0.56321,
12 | 3.39028,
13 | -0.0
14 | ],
15 | [
16 | 0.0461,
17 | 2.6866,
18 | 0.0
19 | ],
20 | [
21 | -0.84042,
22 | 2.9747,
23 | 0.0
24 | ],
25 | [
26 | -0.91432,
27 | 4.19919,
28 | 0.0
29 | ],
30 | [
31 | 0.32309,
32 | 4.74036,
33 | 0.0
34 | ],
35 | [
36 | 1.26502,
37 | 4.03671,
38 | -0.0
39 | ],
40 | [
41 | 0.89567,
42 | 2.52409,
43 | 0.0
44 | ],
45 | [
46 | -5e-05,
47 | 1.7576,
48 | 0.0
49 | ],
50 | [
51 | -3e-05,
52 | 1.06672,
53 | 0.0
54 | ],
55 | [
56 | -1e-05,
57 | 0.46173,
58 | 0.0
59 | ],
60 | [
61 | -0.0,
62 | -0.03694,
63 | 0.0
64 | ],
65 | [
66 | -0.0,
67 | -0.03694,
68 | 0.0
69 | ],
70 | [
71 | -0.0,
72 | -0.03694,
73 | 0.0
74 | ]
75 | ],
76 | "degree": 3,
77 | "form": 0,
78 | "knots": [
79 | 0.0,
80 | 0.0,
81 | 0.0,
82 | 1.0,
83 | 2.0,
84 | 3.0,
85 | 4.0,
86 | 5.0,
87 | 6.0,
88 | 7.0,
89 | 8.0,
90 | 9.0,
91 | 10.0,
92 | 11.0,
93 | 11.0,
94 | 11.0
95 | ]
96 | }
97 | ],
98 | "node": "transform1",
99 | "up_axis": "y"
100 | }
--------------------------------------------------------------------------------
/crab/resources/shapes/toe.json:
--------------------------------------------------------------------------------
1 | {
2 | "curves": [
3 | {
4 | "cvs": [
5 | [
6 | 0.0,
7 | 0.0,
8 | 0.00124
9 | ],
10 | [
11 | 2.3126,
12 | 6e-05,
13 | 0.00124
14 | ],
15 | [
16 | 3.08347,
17 | 8e-05,
18 | 0.58366
19 | ],
20 | [
21 | 3.08347,
22 | 8e-05,
23 | 1.47383
24 | ],
25 | [
26 | 3.85434,
27 | 0.0001,
28 | 2.05625
29 | ],
30 | [
31 | 3.85434,
32 | 0.0001,
33 | 5.95091
34 | ],
35 | [
36 | 3.08347,
37 | 8e-05,
38 | 6.53333
39 | ],
40 | [
41 | -3.08347,
42 | -8e-05,
43 | 6.53333
44 | ],
45 | [
46 | -3.85434,
47 | -0.0001,
48 | 5.95091
49 | ],
50 | [
51 | -3.85434,
52 | -0.0001,
53 | 2.05625
54 | ],
55 | [
56 | -3.08347,
57 | -8e-05,
58 | 1.47383
59 | ],
60 | [
61 | -3.08347,
62 | -8e-05,
63 | 0.58366
64 | ],
65 | [
66 | -2.3126,
67 | -6e-05,
68 | 0.00124
69 | ],
70 | [
71 | 0.0,
72 | 0.0,
73 | 0.00124
74 | ]
75 | ],
76 | "degree": 1,
77 | "form": 0,
78 | "knots": [
79 | 0.0,
80 | 1.0,
81 | 2.0,
82 | 3.0,
83 | 4.0,
84 | 5.0,
85 | 6.0,
86 | 7.0,
87 | 8.0,
88 | 9.0,
89 | 10.0,
90 | 11.0,
91 | 12.0,
92 | 13.0
93 | ]
94 | }
95 | ],
96 | "node": "transform1",
97 | "up_axis": "y"
98 | }
--------------------------------------------------------------------------------
/crab/utils/__init__.py:
--------------------------------------------------------------------------------
1 | from . import snap
2 | from . import maths
3 | from . import types
4 | from . import joints
5 | from . import shapes
6 | from . import access
7 | from . import skinning
8 | from . import organise
9 | from . import contexts
10 | from . import hierarchy
11 | from . import transform
12 | from . import resources
13 |
--------------------------------------------------------------------------------
/crab/utils/access.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pm
2 |
3 | from .. import config
4 |
5 |
6 | # --------------------------------------------------------------------------------------
7 | def get_controls(current_only=False):
8 | """
9 | Returns all the controls in a rig
10 |
11 | :param current_only: If True only the controls of the currently
12 | selected rig will be returned.
13 |
14 | :return: List(pm.nt.Transform, ..)
15 | """
16 | ns = ""
17 | recursive = True
18 |
19 | if current_only:
20 | recursive = False
21 | if pm.selected() and ":" in pm.selected()[0]:
22 | ns = ":".join(pm.selected()[0].name().split(":")[:-1]) + ":"
23 |
24 | return [
25 | ctl
26 | for ctl in pm.ls("%s%s_*" % (ns, config.CONTROL), r=recursive, type="transform")
27 | if not isinstance(ctl, pm.nt.Constraint)
28 | ]
29 |
30 |
31 | # --------------------------------------------------------------------------------------
32 | def component_nodes(node, of_type=None):
33 | root = component_root(node)
34 |
35 | all_nodes = [root]
36 |
37 | for child in root.getChildren(ad=True):
38 | if component_root(child) == root:
39 | if of_type and not child.nodeType() == of_type:
40 | continue
41 |
42 | all_nodes.append(child)
43 |
44 | return all_nodes
45 |
46 |
47 | # --------------------------------------------------------------------------------------
48 | def component_root(node):
49 | tag = "%s_" % config.RIG_COMPONENT
50 |
51 | for item in reversed(node.longName().split("|")):
52 | if item.startswith(tag) or ":" + tag in item:
53 | return pm.PyNode(item)
54 |
55 |
56 | # --------------------------------------------------------------------------------------
57 | def _filtered_children(node, match_string=None, node_type="transform"):
58 | """
59 | Private recursive function which finds all the children within the
60 | same component
61 |
62 | :param node: Node to search from
63 | :type node: pm.nt.DagNode
64 |
65 | :param match_string: Optional string to name test against which
66 | is case sensitive
67 | :type match_string: str
68 |
69 | :param node_type: Optional node type to test for
70 | :type node_type: str
71 |
72 | :return: generator(pm.nt.DagNode, ...)
73 | """
74 | for child in node.getChildren():
75 | if config.RIG_COMPONENT in child.name():
76 | continue
77 |
78 | if match_string:
79 | if match_string in child.name():
80 | yield child
81 |
82 | else:
83 | yield child
84 |
85 | for sub_child in _filtered_children(child, match_string, node_type):
86 | yield sub_child
87 |
--------------------------------------------------------------------------------
/crab/utils/contexts.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pm
2 |
3 |
4 | # --------------------------------------------------------------------------------------
5 | # noinspection PyUnresolvedReferences
6 | class UndoChunk(object):
7 |
8 | # ----------------------------------------------------------------------------------
9 | def __init__(self):
10 | self._closed = False
11 |
12 | # ----------------------------------------------------------------------------------
13 | def __enter__(self):
14 | pm.undoInfo(openChunk=True)
15 | return self
16 |
17 | # ----------------------------------------------------------------------------------
18 | def __exit__(self, *exc_info):
19 | if not self._closed:
20 | pm.undoInfo(closeChunk=True)
21 |
22 | # ----------------------------------------------------------------------------------
23 | def restore(self):
24 | pm.undoInfo(closeChunk=True)
25 | self._closed = True
26 |
27 | try:
28 | pm.undo()
29 |
30 | except StandardError:
31 | pass
32 |
33 |
34 | # --------------------------------------------------------------------------------------
35 | class RestoredTime(object):
36 |
37 | # ----------------------------------------------------------------------------------
38 | def __init__(self):
39 | # -- Cast to iterable
40 | self._time = pm.currentTime()
41 |
42 | # ----------------------------------------------------------------------------------
43 | def __enter__(self):
44 | pass
45 |
46 | # ----------------------------------------------------------------------------------
47 | def __exit__(self, *exc_info):
48 | pm.setCurrentTime(self._time)
49 |
50 |
51 | # --------------------------------------------------------------------------------------
52 | class RestoredSelection(object):
53 | """
54 | Cache selection on entering and re-select on exit
55 | """
56 |
57 | # ----------------------------------------------------------------------------------
58 | def __enter__(self):
59 | self._selection = pm.selected()
60 |
61 | # ----------------------------------------------------------------------------------
62 | def __exit__(self, *exc_info):
63 | if self._selection:
64 | pm.select(self._selection)
65 |
--------------------------------------------------------------------------------
/crab/utils/organise.py:
--------------------------------------------------------------------------------
1 | import pymel.core as pm
2 |
3 |
4 | # --------------------------------------------------------------------------------------
5 | def add_separator_attr(node):
6 | """
7 | Adds an underscored attribute as an attribute separator
8 |
9 | :param node: Node to add the separator to
10 | :tpye node: pm.nt.DependNode
11 |
12 | :return: None
13 | """
14 | character = "_"
15 | name_to_use = character * 8
16 |
17 | while node.hasAttr(name_to_use):
18 | name_to_use += character
19 |
20 | node.addAttr(
21 | name_to_use,
22 | k=True,
23 | )
24 |
25 | node.attr(name_to_use).lock()
26 |
27 |
28 | # --------------------------------------------------------------------------------------
29 | def add_to_layer(nodes, layer_name):
30 | """
31 | Adds a node to the layer with the given name. If that layer does not
32 | exist it will be created with default options.
33 |
34 | :param nodes: List of nodes (or a single node)
35 | :type nodes: pm.nt.Transform or list(pm.nt.Transform, ..)
36 |
37 | :param layer_name: Name of layer to add to
38 | :type layer_name: str
39 |
40 | :return: None
41 | """
42 |
43 | # -- If the layer does not exist, we need to create it
44 | if not pm.ls(layer_name, type="displayLayer"):
45 | pm.createDisplayLayer(
46 | name=layer_name,
47 | empty=True,
48 | )
49 |
50 | if not nodes:
51 | return
52 |
53 | # -- If the nodes is a list a single node we should convert
54 | # -- that to a list
55 | nodes = [nodes]
56 |
57 | # -- Get the layer
58 | layer = pm.PyNode(layer_name)
59 | layer.addMembers(nodes)
60 |
--------------------------------------------------------------------------------
/crab/utils/resources.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from .. import constants as c
4 |
5 |
6 | # --------------------------------------------------------------------------------------
7 | def get(resource_name):
8 | potential_file = os.path.join(
9 | os.path.dirname(os.path.dirname(__file__)),
10 | "resources",
11 | "icons",
12 | resource_name,
13 | )
14 |
15 | if os.path.exists(potential_file):
16 | return potential_file
17 |
18 | # -- Resolve possible search paths
19 | search_paths = [
20 | os.path.join(
21 | os.path.dirname(os.path.dirname(__file__)),
22 | "plugins",
23 | ),
24 | ]
25 |
26 | # -- Now register any paths defined by environment variable
27 | if c.PLUGIN_ENVIRONMENT_VARIABLE in os.environ:
28 | search_paths.extend(os.environ[c.PLUGIN_ENVIRONMENT_VARIABLE].split(";"))
29 |
30 | # -- Cycle all the search paths
31 | for root_path in search_paths:
32 | for path in resolve_sub_paths(root_path):
33 | potential_file = os.path.join(
34 | path,
35 | resource_name,
36 | )
37 |
38 | if os.path.exists(potential_file):
39 | return potential_file
40 |
41 | return ""
42 |
43 |
44 | # --------------------------------------------------------------------------------------
45 | def resolve_sub_paths(root_path):
46 | """
47 | Adds all the paths and subpaths of those given
48 |
49 | :param root_path:
50 | :return:
51 | """
52 | all_paths = [root_path]
53 |
54 | for root, _, __ in os.walk(root_path):
55 | all_paths.append(root)
56 |
57 | return list(set(all_paths))
58 |
--------------------------------------------------------------------------------
/crab/utils/skinning.py:
--------------------------------------------------------------------------------
1 | # --------------------------------------------------------------------------------------
2 | def is_skinned(joint):
3 | """
4 | Returns True if the given joint is being used for any skinning
5 | """
6 | return len(joint.outputs(type="skinCluster")) > 0
7 |
8 |
9 | # --------------------------------------------------------------------------------------
10 | def connected_skins(joint):
11 | """
12 | Returns True if the given joint is being used for any skinning
13 | """
14 | return list(set(joint.outputs(type="skinCluster")))
15 |
16 |
17 | # --------------------------------------------------------------------------------------
18 | def does_joint_have_nonzero_weights(skin, joint):
19 | """
20 | Checks if the given joint has influence values of more than zero on any vertices
21 | in the given skin
22 |
23 | :param skin: Skin Cluster to query
24 | :type skin: pm.nt.SkinCluster
25 |
26 | :param joint: The joint to test if its deforming the skin
27 | :type joint: pm.nt.Joint
28 |
29 | :return: True if any weights for the given joint are more than zero
30 | :rtype: bool
31 | """
32 | influence_objects = skin.influenceObjects()
33 |
34 | if joint not in influence_objects:
35 | return False
36 |
37 | joint_index = influence_objects.index(joint)
38 |
39 | for vertex_weights in skin.getWeights(skin.getGeometry()[0]):
40 | if vertex_weights[joint_index] > 0.0:
41 | return True
42 |
43 | return False
44 |
45 |
46 | # --------------------------------------------------------------------------------------
47 | def remove_joint_from_skin(skin, joint, force=True):
48 | """
49 | Removes the given joint from the given skin.
50 |
51 | :param skin: Skin Cluster to query
52 | :type skin: pm.nt.SkinCluster
53 |
54 | :param joint: The joint to test if its deforming the skin
55 | :type joint: pm.nt.Joint
56 |
57 | :param force: If true, even if the joint has a weighted influence it will be
58 | removed and the weights will be re-normalized
59 | :type force: bool
60 |
61 | :return: True if the joint is no longer an influence of the skin
62 | :rtype: bool
63 | """
64 |
65 | if joint not in skin.influenceObjects():
66 | return True
67 |
68 | # -- If we"re not forcing the operation, we should not proceed if there
69 | # -- are non-zero influence weights for the given joint
70 | if not force and does_joint_have_nonzero_weights(skin, joint):
71 | return False
72 |
73 | # -- This will remove the influence and re-normalize
74 | skin.removeInfluence(joint)
75 |
76 | # -- Dont assume its worked, lets check it has worked
77 | if joint in skin.influenceObjects():
78 | return False
79 |
80 | return True
81 |
--------------------------------------------------------------------------------
/crab/utils/types.py:
--------------------------------------------------------------------------------
1 | # --------------------------------------------------------------------------------------
2 | class AttributeDict(dict):
3 | """
4 | An AttributeDict is a dictionary where by its members can be accessed as
5 | properties of the class.
6 |
7 |
8 | .. code-block:: python
9 |
10 | >>> ad = AttributeDict()
11 | >>> ad["foo"] = 10
12 |
13 | >>> print(ad.foo)
14 | 10
15 |
16 | >>> ad.foo = 5
17 | >>> ad.foo
18 | 5
19 | """
20 |
21 | # --------------------------------------------------------------------------
22 | def __init__(self, *args, **kwargs):
23 | super(AttributeDict, self).__init__(*args, **kwargs)
24 |
25 | self.__dict__ = self
26 | # -- Convert all children to attribute accessible
27 | # -- dictionaries
28 | for key in self.keys():
29 | if type(self[key]) == dict:
30 | self[key] = AttributeDict(self[key])
31 |
32 | if type(self[key]) == list:
33 | for i in range(len(self[key])):
34 | if type(self[key][i]) == dict:
35 | self[key][i] = AttributeDict(self[key][i])
36 |
37 | # --------------------------------------------------------------------------
38 | def __setitem__(self, key, value):
39 | dict.__setitem__(self, key, value)
40 |
--------------------------------------------------------------------------------
/crab/vendor/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/vendor/__init__.py
--------------------------------------------------------------------------------
/crab/vendor/blackout.py:
--------------------------------------------------------------------------------
1 | """
2 | Permission is hereby granted, free of charge, to any person obtaining a copy
3 | of this software and associated documentation files (the "Software"), to deal
4 | in the Software without restriction, including without limitation the rights
5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6 | copies of the Software, and to permit persons to whom the Software is
7 | furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all
10 | copies or substantial portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18 | SOFTWARE.
19 | """
20 | """
21 | blackout is a small micro-module which makes it easier to completely
22 | forget packages. This is particularly useful when working with packages
23 | made up of multiple sub-modules.
24 |
25 | And example might be:
26 |
27 | /site-packages/foo/__init__.py
28 | /site-packages/foo/bar.py
29 |
30 | If bar.py has a function called foo() within it, and we change that
31 | function then it is not enough to reload foo, we must specifically
32 | reload foo as well as foo.bar.
33 |
34 | When working with packages with any modules this can be time consuming
35 | and problematic - particularly when developing within a host which is
36 | persistent.
37 |
38 | blackout helps, because we can unload the package in its entirety in
39 | a single line using:
40 |
41 | ```blackout.drop('foo')```
42 |
43 | This will remove any hold to foo as well as any submodules of foo. In this
44 | case we can simply call ```import foo``` again, knowing that everything
45 | within that package is being loaded fresh.
46 | """
47 |
48 | import sys
49 | import types
50 |
51 |
52 | __author__ = "Michael Malinowski"
53 | __copyright__ = "Copyright (C) 2019 Michael Malinowski"
54 | __license__ = "MIT"
55 | __version__ = "1.0.4"
56 |
57 |
58 | # ------------------------------------------------------------------------------
59 | def drop(package):
60 | """
61 | This will drop a package and all sub-packages from the sys.modules
62 | variable.
63 |
64 | This means that the package and its submodules will be reloaded whenever
65 | you next import it.
66 |
67 | Beware, this is incredibly useful for development when you're working
68 | with packages which contain sub-modules you're actively changing but this
69 | does not handle any prior references to those modules, therefore it is not
70 | code that should be utilised in release code.
71 |
72 | :param package: Name of the package to drop
73 | :type package: str
74 |
75 | :return: None
76 | """
77 |
78 | if isinstance(package, types.ModuleType):
79 | package = package.__name__
80 |
81 | for m in list(sys.modules.keys()):
82 | if m == package or m.startswith('%s.' % package):
83 | del sys.modules[m]
84 |
--------------------------------------------------------------------------------
/crab/vendor/factories/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Permission is hereby granted, free of charge, to any person obtaining a copy
3 | of this software and associated documentation files (the "Software"), to deal
4 | in the Software without restriction, including without limitation the rights
5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6 | copies of the Software, and to permit persons to whom the Software is
7 | furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all
10 | copies or substantial portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18 | SOFTWARE.
19 | """
20 | """
21 | factories is a module which exposes a take on the Factory/Plugin design
22 | pattern. The idea behind this pattern is to be able to define a structure
23 | which your functionality sits within - allowing you to call that
24 | functionality without ever really knowing what it is doing.
25 |
26 | This approach is particularly useful when building systems which are
27 | likely to expand in unknown ways over time. Example use cases might include:
28 |
29 | * Toolboxes, where each tool is represented as a plugin - and an
30 | interface which is arbitrarily populated with those tools
31 |
32 | * Node graphs, where we have no up-front knowledge of what nodes
33 | may be available to use
34 |
35 | * Data parsers which include data that changes format over time due
36 | to deprecation, meaning each data type can be represented by a
37 | plugin allowing the framework to never care about the storage
38 | details of the data
39 |
40 | The commonality between all these structures is that the core of each
41 | system needs to do something but it does not have to care about the
42 | detail of how that task is achieved. Instead the detail is held within
43 | plugins libraries which can be expanded and contracted over time.
44 |
45 | This pattern is incredibly useful but tends to come with an overhead
46 | of writing dynamic loading mechanisms and functionality to easily
47 | interact and query the plugins. The Factories module aims to diminish
48 | that overhead - allowing you to focus on your end goal and the
49 | development of plugins.
50 |
51 | This library was written based from the information here:
52 | https://sourcemaking.com/design_patterns/factory_method
53 |
54 | It is also designed based on the principals given during the
55 | GDC 2018 Talk - A Practical Approach to Developing Forward-Facing Rigs, Tools and
56 | Pipelines. Which can be explored in more detail here:
57 | https://www.gdcvault.com/play/1025427/A-Practical-Approach-to-Developing
58 | """
59 | __author__ = "Michael Malinowski"
60 | __copyright__ = "Copyright (C) 2019 Michael Malinowski"
61 | __license__ = "MIT"
62 | __version__ = "1.2.0"
63 |
64 | from .factory import (
65 | Factory,
66 | )
67 |
68 | from .constants import (
69 | log,
70 | )
71 |
--------------------------------------------------------------------------------
/crab/vendor/factories/constants.py:
--------------------------------------------------------------------------------
1 | import logging
2 |
3 | # ------------------------------------------------------------------------------
4 | logging.basicConfig(level=logging.INFO)
5 | log = logging.getLogger('factories')
6 |
--------------------------------------------------------------------------------
/crab/vendor/qute/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Permission is hereby granted, free of charge, to any person obtaining a copy
3 | of this software and associated documentation files (the "Software"), to deal
4 | in the Software without restriction, including without limitation the rights
5 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
6 | copies of the Software, and to permit persons to whom the Software is
7 | furnished to do so, subject to the following conditions:
8 |
9 | The above copyright notice and this permission notice shall be included in all
10 | copies or substantial portions of the Software.
11 |
12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
13 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
14 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
15 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
16 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
18 | SOFTWARE.
19 | """
20 | """
21 | qute is an extension to the Qt.py module written by Marcus Ottosson and
22 | can be found here:
23 | https://github.com/mottosso/Qt.py
24 |
25 | This extension aims to expose additional help utilities to Qt which are
26 | common place within coding projects.
27 | """
28 | __author__ = "Michael Malinowski"
29 | __copyright__ = "Copyright (C) 2019 Michael Malinowski"
30 | __license__ = "MIT"
31 | __version__ = "3.0.10"
32 |
33 | import imp
34 |
35 | # -- Import all our Qt variables into this namespace - which
36 | # -- makes it trivial to use later
37 | from .vendor.Qt.QtCore import *
38 | from .vendor.Qt.QtGui import *
39 | from .vendor.Qt.QtWidgets import *
40 | from .vendor.Qt import QtCompat
41 |
42 | from . import extensions
43 | from . import utilities
44 | from . import constants
45 |
46 |
47 | # ------------------------------------------------------------------------------
48 | # All imports below this line are deprecated imports which are still exposed
49 | # for backward compatibility between version 1.0.11 and 2.0.1
50 | # You should not use these imports.
51 |
52 | # -- We import QtCompat explicitly
53 |
54 | from .utilities.layouts import slimify
55 | from .utilities import qApp
56 | from .utilities.pixmaps import toGrayscale
57 | from .utilities.layouts import empty as emptyLayout
58 | from .utilities.widgets import addLabel
59 | from .utilities.widgets import getComboIndex
60 | from .utilities.widgets import setComboByText
61 |
62 | from .utilities.styling import apply as applyStyle
63 | from .utilities.styling import getCompoundedStylesheet
64 |
65 | from .utilities.menus import menuFromDictionary
66 |
67 | from .utilities.derive import deriveWidget
68 | from .utilities.derive import deriveValue
69 | from .utilities.derive import setBlindValue
70 | from .utilities.derive import connectBlind
71 |
72 | from .utilities.windows import mainWindow
73 | from .extensions.windows import MemorableWindow
74 |
75 | from .utilities.events import printEventName
76 |
77 | from .utilities.designer import load as loadUi
78 |
79 | from .utilities.launch import quick_app
80 |
81 | from .extensions.tray import TimedProcessorTray
82 | from .extensions.tray import MemorableTimedProcessorTray
83 |
84 | # -- Quick was a convenience sub-module which became a little
85 | # -- too convenient to put things. Therefore its contents is
86 | # -- now spread around. However, for the sake of backward compatability
87 | # -- we need to nest its functionality in a placeholder class
88 | from .utilities.request import confirmation as _rerouted_confirm
89 | from .utilities.request import text as _rerouted_getText
90 | from .utilities.request import filepath as _rerouted_getFilepath
91 | from .utilities.request import folderpath as _rerouted_getFolderPath
92 | from .extensions.dividers import HorizontalDivider as _rerouted_horizontalDivider
93 | from .extensions.buttons import CopyToClipboardButton as _rerouted_copyToClipBoardButton
94 |
95 | quick = imp.new_module('qute.quick')
96 |
97 | quick.confirm = _rerouted_confirm
98 | quick.getText = _rerouted_getText
99 | quick.getFilepath = _rerouted_getFilepath
100 | quick.getFolderPath = _rerouted_getFolderPath
101 | quick.horizontalDivider = _rerouted_horizontalDivider
102 | quick.copyToClipBoardButton = _rerouted_copyToClipBoardButton
103 | quick.quick_app = quick_app
104 |
--------------------------------------------------------------------------------
/crab/vendor/qute/_res/list_collapsed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/vendor/qute/_res/list_collapsed.png
--------------------------------------------------------------------------------
/crab/vendor/qute/_res/list_open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/vendor/qute/_res/list_open.png
--------------------------------------------------------------------------------
/crab/vendor/qute/_res/squares.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/vendor/qute/_res/squares.png
--------------------------------------------------------------------------------
/crab/vendor/qute/_res/squares_lite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikemalinowski/crab/8fc138b3f63a471e6cc70da4eabf84c5f0e36dc3/crab/vendor/qute/_res/squares_lite.png
--------------------------------------------------------------------------------
/crab/vendor/qute/constants.py:
--------------------------------------------------------------------------------
1 | """
2 | This holds a set of constant variables for qute
3 | """
4 | import os
5 | import logging
6 |
7 | from . import resources
8 |
9 | log = logging.getLogger('qute')
10 |
11 |
12 | # -- This is the name of the environment variable we will
13 | # -- check for
14 | QUTE_STYLE_PATH = 'QUTE_STYLE_PATH'
15 |
16 | # -- This will look at the environment variable for styles and pull
17 | # -- out a resolved list of those locations which exist
18 | QUTE_STYLE_LOCATIONS = [
19 | location.strip()
20 | for location in os.environ.get(
21 | QUTE_STYLE_PATH,
22 | ''
23 | ).split(os.path.pathsep)
24 | if os.path.isdir(location.strip())
25 | ]
26 |
27 | QUTE_STYLE_LOCATIONS.insert(
28 | 0,
29 | os.path.join(
30 | os.path.dirname(__file__),
31 | 'styles',
32 | ),
33 | )
34 |
35 | # -- This is a set of default variables used when
36 | # -- applying style sheets
37 | STYLE_DEFAULTS = {
38 | '_BACKGROUND_': '40, 40, 40',
39 | '_ALTBACKGROUND_': '70, 70, 70',
40 | '_FOREGROUND_': '66, 194, 244',
41 | '_HIGHLIGHT_': '109, 214, 255',
42 | '_TEXT_': '255, 255, 255',
43 | }
44 |
45 | # -- We expose all of our resources as special variables
46 | for resource in resources.all():
47 | key = '_%s_' % os.path.basename(resource).replace('.', '_').upper()
48 | STYLE_DEFAULTS[key] = resource
49 |
50 |
--------------------------------------------------------------------------------
/crab/vendor/qute/extensions/__init__.py:
--------------------------------------------------------------------------------
1 |
2 | from . import windows
3 | from . import dividers
4 | from . import buttons
5 | from . import tray
6 | from . import flow_layout
7 |
--------------------------------------------------------------------------------
/crab/vendor/qute/extensions/buttons.py:
--------------------------------------------------------------------------------
1 | from ..vendor import Qt
2 |
3 | from .. import utilities
4 | from .. import resources
5 |
6 |
7 | # --------------------------------------------------------------------------------------------------
8 | # noinspection PyUnresolvedReferences,PyPep8Naming
9 | class CopyToClipboardButton(Qt.QtWidgets.QPushButton):
10 |
11 | def __init__(self, value, size, tooltip='Copy to clipboard', fixed_size=True, parent=None):
12 | super(CopyToClipboardButton, self).__init__(parent=parent)
13 |
14 | # -- Store the value to copy
15 | self._value = value
16 |
17 | # -- Set an icon
18 | self.setIcon(
19 | Qt.QtGui.QIcon(
20 | resources.get('copy_to_clipboard.png'),
21 | )
22 | )
23 |
24 | # -- Fix the size of the button if requested
25 | if fixed_size:
26 | self.setFixedSize(size)
27 |
28 | # -- Assign the tooltip
29 | self.setToolTip(tooltip)
30 |
31 | # -- Hook up the callback
32 | self.clicked.connect(self._callback)
33 |
34 | # ----------------------------------------------------------------------------------------------
35 | def setCopyValue(self, value):
36 | self._value = value
37 |
38 | # ----------------------------------------------------------------------------------------------
39 | def copyValue(self):
40 | return self._value
41 |
42 | # ----------------------------------------------------------------------------------------------
43 | def _callback(self):
44 | utilities.qApp().clipboard().setText(self._value)
45 |
--------------------------------------------------------------------------------
/crab/vendor/qute/extensions/dividers.py:
--------------------------------------------------------------------------------
1 | from ..vendor import Qt
2 |
3 |
4 | # ------------------------------------------------------------------------------
5 | class HorizontalDivider(Qt.QtWidgets.QFrame):
6 |
7 | # ------------------------------------------------------------------------------
8 | def __init__(self, height=2):
9 | super(HorizontalDivider, self).__init__()
10 | self.setFrameShape(Qt.QtWidgets.QFrame.HLine)
11 | self.setFrameShadow(Qt.QtWidgets.QFrame.Sunken)
12 | self.setStyleSheet("background-color: rgb(20,20,20)")
13 | self.setFixedHeight(height)
14 |
--------------------------------------------------------------------------------
/crab/vendor/qute/extensions/flow_layout.py:
--------------------------------------------------------------------------------
1 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
2 | FlowLayout
3 | Custom layout that mimics the behaviour of a flow layout (not supported in PyQt by default)
4 | Just added comments on the offical PySide example.
5 | @date 08-2013
6 | @source http://josbalcaen.com/pyqt-flowlayout-maya-python/
7 | """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
8 | from ..vendor import Qt
9 |
10 |
11 |
12 | class FlowLayout(Qt.QtWidgets.QLayout):
13 | """Custom layout that mimics the behaviour of a flow layout"""
14 |
15 | def __init__(self, parent=None, margin=0, spacing=-1):
16 | super(FlowLayout, self).__init__(parent)
17 |
18 | self._margin = 0
19 | # Set margin and spacing
20 | if parent is not None:
21 | self.setMargin(margin)
22 |
23 | self.setSpacing(spacing)
24 |
25 | self.itemList = []
26 |
27 | def margin(self):
28 | return self._margin
29 |
30 | def setMargin(self, value):
31 | self._margin = value
32 |
33 | def __del__(self):
34 | item = self.takeAt(0)
35 | while item:
36 | item = self.takeAt(0)
37 |
38 | def addItem(self, item):
39 | self.itemList.append(item)
40 |
41 | def count(self):
42 | return len(self.itemList)
43 |
44 | def itemAt(self, index):
45 | if index >= 0 and index < len(self.itemList):
46 | return self.itemList[index]
47 | return None
48 |
49 | def takeAt(self, index):
50 | if index >= 0 and index < len(self.itemList):
51 | return self.itemList.pop(index)
52 | return None
53 |
54 | def insertWidget(self, index, widget):
55 | item = Qt.QtWidgets.QWidgetItem(widget)
56 | self.itemList.insert(index, item)
57 |
58 | def expandingDirections(self):
59 | return Qt.QtCore.Qt.Orientations(Qt.QtCore.Qt.Horizontal)
60 |
61 | def hasHeightForWidth(self):
62 | return True
63 |
64 | def heightForWidth(self, width):
65 | height = self.doLayout(Qt.QtCore.QRect(0, 0, width, 0), True)
66 | return height
67 |
68 | def setGeometry(self, rect):
69 | super(FlowLayout, self).setGeometry(rect)
70 | self.doLayout(rect, False)
71 |
72 | def sizeHint(self):
73 | return self.minimumSize()
74 |
75 | def minimumSize(self):
76 | # Calculate the size
77 | size = Qt.QtCore.QSize()
78 |
79 | for item in self.itemList:
80 | size = size.expandedTo(item.minimumSize())
81 |
82 | # Add the margins
83 | size += Qt.QtCore.QSize(2 * self.margin(), 2 * self.margin())
84 |
85 | return size
86 |
87 | def doLayout(self, rect, testOnly):
88 | x = rect.x()
89 | y = rect.y()
90 | lineHeight = 0
91 |
92 | for item in self.itemList:
93 | wid = item.widget()
94 | spaceX = self.spacing()
95 | spaceY = self.spacing()
96 | nextX = x + item.sizeHint().width() + spaceX
97 | if nextX - spaceX > rect.right() and lineHeight > 0:
98 | x = rect.x()
99 | y = y + lineHeight + spaceY
100 | nextX = x + item.sizeHint().width() + spaceX
101 | lineHeight = 0
102 |
103 | if not testOnly:
104 | item.setGeometry(Qt.QtCore.QRect(Qt.QtCore.QPoint(x, y), item.sizeHint()))
105 |
106 | x = nextX
107 | lineHeight = max(lineHeight, item.sizeHint().height())
108 |
109 | return y + lineHeight - rect.y()
110 |
--------------------------------------------------------------------------------
/crab/vendor/qute/resources.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | _RESOURCE_DIR = os.path.join(
4 | os.path.dirname(__file__),
5 | '_res',
6 | )
7 |
8 |
9 | # ------------------------------------------------------------------------------
10 | def get(name):
11 | return os.path.join(
12 | _RESOURCE_DIR,
13 | name,
14 | ).replace('\\', '/')
15 |
16 |
17 | # ------------------------------------------------------------------------------
18 | def all():
19 | files = list()
20 | for filename in os.listdir(_RESOURCE_DIR):
21 | files.append(os.path.join(_RESOURCE_DIR, filename).replace('\\', '/'))
22 | return files
23 |
24 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/__init__.py:
--------------------------------------------------------------------------------
1 | from ._core import *
2 |
3 | from . import derive
4 | from . import designer
5 | from . import events
6 | from . import layouts
7 | from . import menus
8 | from . import pixmaps
9 | from . import styling
10 | from . import widgets
11 | from . import windows
12 | from . import request
13 | from . import sizing
14 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/_core.py:
--------------------------------------------------------------------------------
1 | from ..vendor import Qt
2 |
3 |
4 | # ------------------------------------------------------------------------------
5 | def toList(given):
6 | """
7 | This will take what is given and wrap it in a list if it is not already
8 | a list, otherwise it will simply return what it has been given.
9 |
10 | :return: list()
11 | """
12 | if not isinstance(given, (tuple, list)):
13 | given = [given]
14 |
15 | return given
16 |
17 |
18 | # ------------------------------------------------------------------------------
19 | def qApp(*args, **kwargs):
20 | """
21 | This will return the QApplication instance if one is available, otherwise
22 | it will create one
23 |
24 | :return: QApplication Instance
25 | """
26 | q_app = Qt.QtWidgets.QApplication.instance()
27 |
28 | if not q_app:
29 | q_app = Qt.QtWidgets.QApplication([])
30 |
31 | return q_app
32 |
33 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/designer.py:
--------------------------------------------------------------------------------
1 | # noinspection PyPep8Naming
2 | import xml.etree.ElementTree as exml
3 |
4 | from ..vendor import Qt
5 |
6 |
7 | # ------------------------------------------------------------------------------
8 | # noinspection PyPep8Naming,PyUnresolvedReferences,PyBroadException
9 | def load(ui_file, base_instance=None):
10 | try:
11 | return Qt.QtCompat.loadUi(ui_file, base_instance)
12 |
13 | except:
14 |
15 | # -- For applications such as 3dsmax we have to compile the
16 | # -- ui differently
17 | try:
18 | import pyside2uic as pyuic
19 | from cStringIO import StringIO
20 |
21 | except:
22 | raise Exception('No implementation for loadUi found.')
23 |
24 | # -- Read out the xml file
25 | xml_data = exml.parse(ui_file)
26 |
27 | # -- Get the lcass of the widget and the form
28 | widget_class = xml_data.find('widget').get('class')
29 | form_class = xml_data.find('class').text
30 |
31 | # -- Open the ui file as text
32 | with open(ui_file, 'r') as f:
33 |
34 | # -- Create a file like object
35 | o = StringIO()
36 | frame = {}
37 |
38 | # -- Compile the ui into compiled python and execute it
39 | pyuic.compileUi(f, o, indent=0)
40 | pyc = compile(o.getvalue(), '', 'exec')
41 | eval('exec pyc in frame')
42 |
43 | # -- Get the form class
44 | form_class = frame['Ui_%s' % form_class]
45 |
46 | # -- Alter what we're evaulating based on what version
47 | # -- of qt we're running
48 | try:
49 | base_class = eval('Qt.QtGui.%s' % widget_class)
50 |
51 | except (NameError, AttributeError):
52 | base_class = eval('Qt.QtWidgets.%s' % widget_class)
53 |
54 | # -- Subclass the loaded classes to build a wrapped widget
55 | class _WrappedHelper(form_class, base_class):
56 | # noinspection PyShadowingNames
57 | def __init__(self, parent=None):
58 | super(_WrappedHelper, self).__init__(parent)
59 | self.setupUi(self)
60 |
61 | return _WrappedHelper()
62 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/launch.py:
--------------------------------------------------------------------------------
1 | from .. import utilities
2 | from .. import extensions
3 | from .. import applyStyle
4 |
5 |
6 | # ------------------------------------------------------------------------------
7 | def quick_app(window_title, style=None):
8 | """
9 | Decorator to use on a function that is expected to return a widget that
10 | will be set as the Main Widget in the window that is created.
11 |
12 | :param window_title: App and window title.
13 | :type window_title: str
14 | """
15 |
16 | def decorator(func):
17 | def wrapper(*args, **kwargs):
18 | q_app = utilities.qApp()
19 |
20 | widget = func(*args, **kwargs)
21 |
22 | window = extensions.windows.MemorableWindow(
23 | identifier=window_title,
24 | parent=utilities.windows.mainWindow(),
25 | )
26 |
27 | window.setCentralWidget(widget)
28 |
29 | # -- Set the window properties
30 | window.setWindowTitle(window_title)
31 |
32 | if style:
33 | applyStyle(
34 | style,
35 | apply_to=window
36 | )
37 |
38 | # -- Show the ui, and if we're blocking call the exec_
39 | window.show()
40 |
41 | q_app.exec_()
42 |
43 | return window
44 |
45 | return wrapper
46 |
47 | return decorator
48 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/layouts.py:
--------------------------------------------------------------------------------
1 | from ..vendor import Qt
2 |
3 |
4 | # --------------------------------------------------------------------------
5 | def empty(layout):
6 | """
7 | Clears the layout of all its children, removing them entirely.
8 |
9 | :param layout: The layout to empty.
10 | :type layout: QLayout
11 |
12 | :param recurse: If True, clear contents recursively.
13 | Default True.
14 | :type recurse: bool
15 |
16 | :return: None
17 | """
18 | for i in reversed(range(layout.count())):
19 | item = layout.takeAt(i)
20 |
21 | if isinstance(item, Qt.QtWidgets.QWidgetItem):
22 | widget = item.widget()
23 | widget.setParent(None)
24 | widget.deleteLater()
25 |
26 | elif isinstance(item, Qt.QtWidgets.QSpacerItem):
27 | pass
28 |
29 | else:
30 | empty(item.layout())
31 |
32 |
33 | # ------------------------------------------------------------------------------
34 | def slimify(layout):
35 | # -- Apply the formatting
36 | layout.setContentsMargins(
37 | *[0, 0, 0, 0]
38 | )
39 | layout.setSpacing(0)
40 |
41 | return layout
42 |
43 |
44 | # --------------------------------------------------------------------------
45 | def widgets(layout):
46 | """
47 | Returns all the child widgets (recursively) within this layout
48 |
49 | :param layout: The layout to empty.
50 | :type layout: QLayout
51 |
52 | :return: None
53 | """
54 |
55 | results = list()
56 |
57 | def _getWidgets(layout_):
58 |
59 | for idx in range(layout_.count()):
60 | item = layout_.itemAt(idx)
61 |
62 | if isinstance(item, Qt.QtWidgets.QLayout):
63 | _getWidgets(item)
64 | continue
65 |
66 | widget = item.widget()
67 |
68 | if not widget:
69 | continue
70 |
71 | results.append(widget)
72 |
73 | _getWidgets(layout)
74 |
75 | return results
76 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/menus.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from . import _core
4 | from ..vendor.Qt import QtWidgets
5 | from ..vendor.Qt import QtGui
6 |
7 |
8 | # ------------------------------------------------------------------------------
9 | def menuFromDictionary(structure, parent=None, name=None, icon_paths=None):
10 | """
11 | This will generate a menu based on a dictionary structure, whereby
12 | the key is the label and the value is a function call. You can optionally
13 | pass an icon path, and any icon found in that location with the same
14 | name as a key will be used.
15 |
16 | :param structure: Dictionary to generate the menu from. This dictionary
17 | is built of key value pairs where the key is the label and the value
18 | can be one of the following:
19 | * Function : This function will be called when the
20 | action is triggered
21 |
22 | * Dictionary : If a dictionary is found as a value then a sub
23 | menu is created. You can have any number of nested dictionaries
24 |
25 | * None : If the value is None then a seperator will be added
26 | regardless of the key.
27 | :type structure: dict
28 |
29 | :param parent: The parent of the menu.
30 | :type parent: QWidget/QMenu
31 |
32 | :param icon_paths: When adding items to the menu, icons with the same
33 | name as the keys will be looked for in any of these locations. This
34 | can either be a single string location or a list of locations.
35 | :type icon_paths: str or [str, str]
36 | :return:
37 | """
38 | if isinstance(parent, QtWidgets.QMenu):
39 | menu = parent
40 |
41 | else:
42 | menu = QtWidgets.QMenu(name or '', parent)
43 |
44 | for label, target in structure.items():
45 |
46 | # -- Deal with seperators first
47 | if not target:
48 | menu.addSeparator()
49 | continue
50 |
51 | # -- Now check if we have a sub menu
52 | if isinstance(target, dict):
53 | sub_menu = QtWidgets.QMenu(label, menu)
54 |
55 | menuFromDictionary(
56 | structure=target,
57 | parent=sub_menu,
58 | name=label,
59 | icon_paths=icon_paths,
60 | )
61 |
62 | menu.addMenu(
63 | sub_menu,
64 | )
65 |
66 | continue
67 |
68 | # -- Finally, check if the target is callable
69 | if callable(target):
70 |
71 | icon = _findIcon(label, icon_paths)
72 |
73 | if icon:
74 | # -- Create the menu action
75 | action = QtWidgets.QAction(
76 | QtGui.QIcon(icon),
77 | label,
78 | menu,
79 | )
80 |
81 | else:
82 | # -- Create the menu action
83 | action = QtWidgets.QAction(
84 | label,
85 | menu,
86 | )
87 |
88 | # -- Connect the menu action signal/slot
89 | action.triggered.connect(target)
90 |
91 | # -- Finally add the action to the menu
92 | menu.addAction(action)
93 |
94 | # -- this allows the user to pre-construct their own actions with more advanced set-ups
95 | elif isinstance(target, QtWidgets.QAction):
96 | menu.addAction(target)
97 |
98 | return menu
99 |
100 |
101 | # ------------------------------------------------------------------------------
102 | def _findIcon(label, icon_paths):
103 | """
104 | Private function for finding png icons with the label name
105 | from any of the given icon paths
106 |
107 | :param label: Name of the icon to search for
108 | :type label: str
109 |
110 | :param icon_paths: single path, or list of paths to check along
111 | :type icon_paths: str or list(str, str)
112 |
113 | :return: absolute icon path or None
114 | """
115 | # -- Ensure we're working with a list
116 | icon_paths = _core.toList(icon_paths)
117 |
118 | for icon_path in icon_paths:
119 |
120 | if not icon_path:
121 | continue
122 |
123 | for filename in os.listdir(icon_path):
124 | if filename[:-4] == label:
125 | return os.path.join(
126 | icon_path,
127 | filename,
128 | )
129 |
130 | return None
131 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/pixmaps.py:
--------------------------------------------------------------------------------
1 | from ..vendor.Qt.QtGui import QPixmap, QImage
2 | from ..vendor.Qt.QtCore import QByteArray, QDataStream, QIODevice
3 |
4 | from ..vendor import Qt
5 |
6 |
7 | # --------------------------------------------------------------------------
8 | def toGrayscale(pixmap):
9 | """
10 | Creates a new pixmap which is a grayscale version of the given
11 | pixmap
12 |
13 | :param pixmap: QPixmap
14 |
15 | :return: QPixmap
16 | """
17 | # -- Get an image object
18 | image = pixmap.toImage()
19 |
20 | # -- Cycle the pixels and convert them to grayscale
21 | for x in range(image.width()):
22 | for y in range(image.height()):
23 |
24 | # -- Grayscale the pixel
25 | gray = Qt.QtGui.qGray(
26 | image.pixel(
27 | x,
28 | y,
29 | ),
30 | )
31 |
32 | # -- Set the pixel back into the image
33 | image.setPixel(
34 | x,
35 | y,
36 | Qt.QtGui.QColor(
37 | gray,
38 | gray,
39 | gray,
40 | ).rgb()
41 | )
42 |
43 | # -- Re-apply the alpha channel
44 | image.setAlphaChannel(
45 | pixmap.toImage().alphaChannel()
46 | )
47 |
48 | # -- Return the pixmap
49 | return Qt.QtGui.QPixmap.fromImage(image)
50 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/request.py:
--------------------------------------------------------------------------------
1 | from ..vendor import Qt
2 | from . import qApp
3 |
4 |
5 | # ------------------------------------------------------------------------------
6 | def message(title='Text Request', label='', parent=None, **kwargs):
7 | """
8 | Quick and easy access for getting text input. You do not have to have a
9 | QApplication instance, as this will look for one.
10 |
11 | :return: str, or None
12 | """
13 | # -- Ensure we have a QApplication instance
14 | q_app = qApp()
15 |
16 | answer = Qt.QtWidgets.QMessageBox.information(
17 | parent,
18 | title,
19 | label,
20 | )
21 |
22 | return True
23 |
24 |
25 | # ------------------------------------------------------------------------------
26 | def confirmation(title='Text Request', label='', parent=None, **kwargs):
27 | """
28 | Quick and easy access for getting text input. You do not have to have a
29 | QApplication instance, as this will look for one.
30 |
31 | :return: str, or None
32 | """
33 | # -- Ensure we have a QApplication instance
34 | q_app = qApp()
35 |
36 | answer = Qt.QtWidgets.QMessageBox.warning(
37 | parent,
38 | title,
39 | label,
40 | Qt.QtWidgets.QMessageBox.Yes,
41 | Qt.QtWidgets.QMessageBox.No,
42 | )
43 |
44 | if answer == Qt.QtWidgets.QMessageBox.Yes:
45 | return True
46 |
47 | return False
48 |
49 |
50 | # ------------------------------------------------------------------------------
51 | def text(title='Text Request', label='', parent=None, **kwargs):
52 | """
53 | Quick and easy access for getting text input. You do not have to have a
54 | QApplication instance, as this will look for one.
55 |
56 | :return: str, or None
57 | """
58 | # -- Ensure we have a QApplication instance
59 | q_app = qApp()
60 |
61 | # -- Get the text
62 | name, ok = Qt.QtWidgets.QInputDialog.getText(
63 | parent,
64 | title,
65 | label,
66 | **kwargs
67 | )
68 |
69 | if not ok:
70 | return None
71 |
72 | return name
73 |
74 |
75 | # ------------------------------------------------------------------------------
76 | def filepath(title='Text Request',
77 | save=True,
78 | path='',
79 | filter_='* (*.*)',
80 | parent=None,
81 | **kwargs):
82 | """
83 | Quick and easy access for getting text input. You do not have to have a
84 | QApplication instance, as this will look for one.
85 |
86 | :return: str, or None
87 | """
88 |
89 | # -- Ensure we have a q application instance
90 | q_app = qApp()
91 |
92 | # -- Ask for the editor location
93 | func = Qt.QtWidgets.QFileDialog.getOpenFileName
94 |
95 | if save:
96 | func = Qt.QtWidgets.QFileDialog.getSaveFileName
97 |
98 | value, _ = func(
99 | parent,
100 | title,
101 | path,
102 | filter_,
103 | )
104 |
105 | return value
106 |
107 |
108 | # ------------------------------------------------------------------------------
109 | def folderpath(title='Folder Request', path='', parent=None):
110 | """
111 | Quick function for requesting a folder path
112 |
113 | :param title:
114 | :param path:
115 | :return:
116 | """
117 | q_app = qApp()
118 |
119 | return Qt.QtWidgets.QFileDialog.getExistingDirectory(
120 | parent,
121 | title,
122 | path,
123 | Qt.QtWidgets.QFileDialog.ShowDirsOnly,
124 | )
125 |
126 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/sizing.py:
--------------------------------------------------------------------------------
1 | from ..vendor import Qt
2 |
3 |
4 | # --------------------------------------------------------------------------------------------------
5 | def expandWidth(size_to_update, size_to_consider):
6 | return Qt.QSize(
7 | max(size_to_update.width(), size_to_consider.width()),
8 | size_to_update.height(),
9 | )
10 |
11 |
12 | # --------------------------------------------------------------------------------------------------
13 | def expandHeight(size_to_update, size_to_consider):
14 | return Qt.QSize(
15 | size_to_update.width(),
16 | max(size_to_update.height(), size_to_consider.height()),
17 | )
18 |
19 |
20 | # --------------------------------------------------------------------------------------------------
21 | def addWidth(size_to_update, size_to_consider):
22 | return Qt.QSize(
23 | size_to_update.width() + size_to_consider.width(),
24 | size_to_update.height(),
25 | )
26 |
27 |
28 | # --------------------------------------------------------------------------------------------------
29 | def addHeight(size_to_update, size_to_consider):
30 | return Qt.QSize(
31 | size_to_update.width(),
32 | size_to_update.height() + size_to_consider.height(),
33 | )
34 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/widgets.py:
--------------------------------------------------------------------------------
1 | """
2 | This holds a set of small helper functions
3 | """
4 | from ..vendor import Qt
5 | from . import layouts
6 |
7 |
8 | # ------------------------------------------------------------------------------
9 | def addLabel(widget, label, min_label_width=None):
10 | """
11 | Adds a label to the widget, returning a layout with the label
12 | on the left and the widget on the right.
13 |
14 | :param widget: Widget to be given a label
15 | :type widget: QWidget
16 |
17 | :param label: Label text to show
18 | :type label: str
19 |
20 | :return: SlimHBoxLayout
21 | """
22 | label = Qt.QtWidgets.QLabel(label)
23 | layout = layouts.slimify(Qt.QtWidgets.QHBoxLayout())
24 |
25 | # -- Apply the min label width if required
26 | if min_label_width:
27 | label.setMinimumWidth(min_label_width)
28 |
29 | layout.addWidget(label)
30 | layout.addSpacerItem(
31 | Qt.QtWidgets.QSpacerItem(
32 | 10,
33 | 0,
34 | Qt.QtWidgets.QSizePolicy.Expanding,
35 | Qt.QtWidgets.QSizePolicy.Fixed,
36 | ),
37 | )
38 | layout.addWidget(widget)
39 |
40 | layout.setStretch(
41 | 2,
42 | 1,
43 | )
44 |
45 | return layout
46 |
47 |
48 | # ------------------------------------------------------------------------------
49 | # noinspection PyPep8Naming
50 | def getComboIndex(combo_box, label, ignore_casing=False):
51 | """
52 | This will return the index of the first matching label within a combo
53 | box qwidget.
54 |
55 | If no label is found 0 is returned
56 |
57 | :param combo_box: Widget to iterate through
58 | :type combo_box: QComboBox
59 |
60 | :param label: The combo label to match against
61 | :type label: str
62 |
63 | :param ignore_casing: If true, all text matching will be done with no
64 | consideration of capitalisation. The default is False.
65 | :type ignore_casing: bool
66 |
67 | :return: int
68 | """
69 | # -- Convert the label to lower case if we're ignoring casing
70 | label = label if not ignore_casing else label.lower()
71 |
72 | # -- Cycling our combo box and test the string
73 | for i in range(combo_box.count()):
74 |
75 | # -- Get the current item text, and lower the casing if we're
76 | # -- ignoring the casing. This means we're testing both sides
77 | # -- of the argument in lower case
78 | combo_text = combo_box.itemText(i)
79 | combo_text = combo_text if not ignore_casing else combo_text.lower()
80 |
81 | if combo_text == label:
82 | return i
83 |
84 | return 0
85 |
86 |
87 | # ------------------------------------------------------------------------------
88 | def setComboByText(combo_box, label, ignore_casing=False):
89 | """
90 | This will return the index of the first matching label within a combo
91 | box qwidget.
92 |
93 | If no label is found 0 is returned
94 |
95 | :param combo_box: Widget to iterate through
96 | :type combo_box: QComboBox
97 |
98 | :param label: The combo label to match against
99 | :type label: str
100 |
101 | :param ignore_casing: If true, all text matching will be done with no
102 | consideration of capitalisation. The default is False.
103 | :type ignore_casing: bool
104 |
105 | :return: int
106 | """
107 | idx = getComboIndex(combo_box, label, ignore_casing=ignore_casing)
108 | combo_box.setCurrentIndex(idx)
109 |
110 |
--------------------------------------------------------------------------------
/crab/vendor/qute/utilities/windows.py:
--------------------------------------------------------------------------------
1 | """
2 | This module is specifically intended for use when in environments where
3 | you're actively trying to share/develop tools across multiple applications
4 | which support PyQt, PySide or PySide2.
5 |
6 | The premise is that you can request the main application window using
7 | a common function regardless of the actual application - making it trivial
8 | to implement a tool which works in multiple host applications without any
9 | bespoke code.
10 |
11 | The current list of supported applications are:
12 |
13 | * Native Python
14 | * Maya
15 | * 3dsmax
16 | * Motion Builder
17 |
18 | """
19 | import sys
20 |
21 | from ..vendor import Qt
22 |
23 |
24 | # ------------------------------------------------------------------------------
25 | def get_host():
26 |
27 | global HOST
28 |
29 | if HOST:
30 | return HOST
31 |
32 | if 'maya.exe' in sys.executable or 'mayapy.exe' in sys.executable:
33 | HOST = 'Maya'
34 | return HOST
35 |
36 | if 'motionbuilder.exe' in sys.executable:
37 | HOST = 'Mobu'
38 | return HOST
39 |
40 | if '3dsmax.exe' in sys.executable:
41 | HOST = 'Max'
42 | return HOST
43 |
44 | houdini_execs = [
45 | 'houdini.exe',
46 | 'houdinifx.exe',
47 | 'houdinicore.exe',
48 | ]
49 | if any(houdini_exec in sys.executable for houdini_exec in houdini_execs):
50 | HOST = 'Houdini'
51 | return HOST
52 |
53 | return 'Pure'
54 |
55 |
56 | # ------------------------------------------------------------------------------
57 | # noinspection PyPep8Naming
58 | def mainWindow():
59 | """
60 | Returns the main window regardless of what the host is
61 |
62 | :return:
63 | """
64 | return HOST_MAPPING[get_host()]()
65 |
66 |
67 | # ------------------------------------------------------------------------------
68 | # noinspection PyUnresolvedReferences,PyPep8Naming
69 | def returnNativeWindow():
70 | for candidate in Qt.QtWidgets.QApplication.topLevelWidgets():
71 | if isinstance(candidate, Qt.QtWidgets.QMainWindow):
72 | return candidate
73 |
74 |
75 | # ------------------------------------------------------------------------------
76 | # noinspection PyUnresolvedReferences,PyPep8Naming
77 | def _findWindowByTitle(title):
78 | # -- Find the main application window
79 | for candidate in Qt.QtWidgets.QApplication.topLevelWidgets():
80 | # noinspection PyBroadException
81 | try:
82 | if title in candidate.windowTitle():
83 | return candidate
84 | except: pass
85 |
86 |
87 | # ------------------------------------------------------------------------------
88 | # noinspection PyPep8Naming
89 | def returnModoMainWindow():
90 | pass
91 |
92 |
93 | # ------------------------------------------------------------------------------
94 | # noinspection PyPep8Naming
95 | def returnMaxMainWindow():
96 | return _findWindowByTitle('Autodesk 3ds Max')
97 |
98 |
99 | # ------------------------------------------------------------------------------
100 | # noinspection PyUnresolvedReferences,PyPep8Naming
101 | def returnMayaMainWindow():
102 |
103 | from maya import OpenMayaUI as omui
104 |
105 | func = Qt.QtCompat.wrapInstance
106 |
107 | try:
108 | return func(long(omui.MQtUtil.mainWindow()), Qt.QtWidgets.QWidget)
109 |
110 | except:
111 | pass
112 |
113 | return func(int(omui.MQtUtil.mainWindow()), Qt.QtWidgets.QWidget)
114 |
115 |
116 | # ------------------------------------------------------------------------------
117 | # noinspection PyPep8Naming
118 | def returnHoudiniMainWindow():
119 | import hou
120 | return hou.qt.mainWindow()
121 |
122 |
123 | # ------------------------------------------------------------------------------
124 | # noinspection PyPep8Naming
125 | def returnMobuMainWindow():
126 | return _findWindowByTitle('MotionBuilder 20')
127 |
128 |
129 | # ------------------------------------------------------------------------------
130 | HOST = None
131 | HOST_MAPPING = dict(
132 | Maya=returnMayaMainWindow,
133 | Max=returnMaxMainWindow,
134 | Modo=returnModoMainWindow,
135 | Mobu=returnMobuMainWindow,
136 | Pure=returnNativeWindow,
137 | Houdini=returnHoudiniMainWindow,
138 | )
139 |
--------------------------------------------------------------------------------
/crab/vendor/qute/vendor/__init__.py:
--------------------------------------------------------------------------------
1 | from . import Qt
2 |
--------------------------------------------------------------------------------
/crab/vendor/qute/vendor/scribble/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Scribble is a mechanism to store dictionary information to a persistent
3 | state. This allows the data to be easily retrieved and edited, making it
4 | a good option when trying to store settings data.
5 |
6 | Note: Data saved within the ScribbleDictionary must be json serialisable.
7 |
8 | An example of creating a persisten data block would be:
9 |
10 | ..code-block:: python
11 |
12 | import os
13 | import scribble
14 |
15 |
16 | # -- Instance a scribble dictionary. We can treat this exactly
17 | # -- as we treat a dictionary
18 | data = scribble.get('foobar')
19 |
20 | # -- Set some key/value pairs in the ScribbleDictionary
21 | data['option_a'] = True
22 | data['option_b'] = 123
23 |
24 | # -- Calling .save() will trigger the dictionary
25 | # -- to store its current state to a persistent state
26 | data.save()
27 |
28 | # -- We can see the location the data is saved to, and we can
29 | # -- see that it does indeed exist
30 | print(data.location())
31 | print(os.path.exists(data.location()))
32 |
33 |
34 | Equally, we can re-retrieve that data in a completely new instance of python
35 | using the following code:
36 |
37 | ..code-block:: python
38 |
39 | # -- Instance a scribble dictionary with the same identifier
40 | data = scribble.get('foobar')
41 |
42 | # -- Print the fact that we have retrieved the same
43 | # -- infromation
44 | print(data)
45 | # {'option_a': True, 'option_b': 123}
46 |
47 | # -- We can then further edit the data
48 | data['option_a'] = False
49 |
50 | # -- Calling .save() will trigger the dictionary
51 | # -- to store its current state to a persistent state
52 | data.save()
53 |
54 | By default scribble will store its data in the follow platform specific
55 | locations:
56 |
57 | * Windows: %APPDATA%/pyscribble
58 |
59 | * Linux: %XDG_CONFIG_HOME%/pyscribble if the environment variable exists, otherwise %HOME%/.config/pyscribble
60 |
61 | * OSX: Not yet supported
62 |
63 | However, you can override these paths by setting an environment variable
64 | PYSCRIBBLE_STORAGE_DIR, if this is set then the path defined by that variable
65 | will always be used over the default behaviour.
66 | """
67 | from .core import get
68 |
--------------------------------------------------------------------------------
/crab/vendor/scribble/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Scibble is a mechanism to store dictionary information to a persistent
3 | state. This allows the data to be easily retrieved and edited, making it
4 | a good option when trying to store settings data.
5 |
6 | Note: Data saved within the ScribbleDictionary must be json serialisable.
7 |
8 | An example of creating a persisten data block would be:
9 |
10 | ..code-block:: python
11 |
12 | import os
13 | import scribble
14 |
15 |
16 | # -- Instance a scribble dictionary. We can treat this exactly
17 | # -- as we treat a dictionary
18 | data = scribble.get('foobar')
19 |
20 | # -- Set some key/value pairs in the ScribbleDictionary
21 | data['option_a'] = True
22 | data['option_b'] = 123
23 |
24 | # -- Calling .save() will trigger the dictionary
25 | # -- to store its current state to a persistent state
26 | data.save()
27 |
28 | # -- We can see the location the data is saved to, and we can
29 | # -- see that it does indeed exist
30 | print(data.location())
31 | print(os.path.exists(data.location()))
32 |
33 |
34 | Equally, we can re-retrieve that data in a completely new instance of python
35 | using the following code:
36 |
37 | ..code-block:: python
38 |
39 | # -- Instance a scribble dictionary with the same identifier
40 | data = scribble.get('foobar')
41 |
42 | # -- Print the fact that we have retrieved the same
43 | # -- infromation
44 | print(data)
45 | # {'option_a': True, 'option_b': 123}
46 |
47 | # -- We can then further edit the data
48 | data['option_a'] = False
49 |
50 | # -- Calling .save() will trigger the dictionary
51 | # -- to store its current state to a persistent state
52 | data.save()
53 |
54 | By default scribble will store its data in the follow platform specific
55 | locations:
56 |
57 | * Windows: %APPDATA%/pyscribble
58 |
59 | * Linux: %XDG_CONFIG_HOME%/pyscribble if the environment variable exists, otherwise %HOME%/.config/pyscribble
60 |
61 | * OSX: Not yet supported
62 |
63 | However, you can override these paths by setting an environment variable
64 | PYSCRIBBLE_STORAGE_DIR, if this is set then the path defined by that variable
65 | will always be used over the default behaviour.
66 | """
67 | from .core import get
68 |
--------------------------------------------------------------------------------
/userSetup.py:
--------------------------------------------------------------------------------
1 | import maya.cmds
2 |
3 | maya.cmds.evalDeferred('import crab;crab.menu.initialize()')
4 |
5 |
--------------------------------------------------------------------------------