├── .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 | --------------------------------------------------------------------------------