|
| 1 | +# GETOOLS is under the terms of the MIT License |
| 2 | +# Copyright (c) 2018-2024 Eugene Gataulin (GenEugene). All Rights Reserved. |
| 3 | + |
| 4 | +# Permission is hereby granted, free of charge, to any person obtaining a copy |
| 5 | +# of this software and associated documentation files (the "Software"), to deal |
| 6 | +# in the Software without restriction, including without limitation the rights |
| 7 | +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| 8 | +# copies of the Software, and to permit persons to whom the Software is |
| 9 | +# furnished to do so, subject to the following conditions: |
| 10 | + |
| 11 | +# The above copyright notice and this permission notice shall be included in all |
| 12 | +# copies or substantial portions of the Software. |
| 13 | + |
| 14 | +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| 15 | +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| 16 | +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| 17 | +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| 18 | +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| 19 | +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 20 | + |
| 21 | +# Author: Eugene Gataulin tek942@gmail.com https://www.linkedin.com/in/geneugene https://discord.gg/heMxJhTqCz |
| 22 | +# Source code: https://github.com/GenEugene/GETools or https://app.gumroad.com/geneugene |
| 23 | + |
| 24 | +import maya.cmds as cmds |
| 25 | +from functools import partial |
| 26 | + |
| 27 | +from .. import Settings |
| 28 | +from ..utils import Colors |
| 29 | +# from ..utils import Selector |
| 30 | +# from ..values import Enums |
| 31 | + |
| 32 | + |
| 33 | +class TransformationsAnnotations: |
| 34 | + space = "WORLD SPACE uses global axes orientations.\nLOCAL SPACE uses parent space, useful when selected object is a child of other object.\nOBJECT SPACE uses selected objects' axes and move accordingly arrows." |
| 35 | + relative = "Current position used as a start point resulting move addition each step.\nDeactivate to use absolute mode to set specific coordinates." |
| 36 | + preserveChildPosition = "Child object will stay on the same position while selected object will be moved.\nVery useful when you need to tweak object's position in the middle of hierarchy." |
| 37 | + pivot = "Move pivot of selected objects instead of moving objects transform nodes" |
| 38 | + multiplier = "Extra multiplier for DIRECTION and DISTANCE values." |
| 39 | + |
| 40 | + editPivot = "Toggle pivot editing for selected objects.\nThe same action as 'D' hotkey on keyboard." |
| 41 | + pivotOn = "Show pivot attributes in channel box" |
| 42 | + pivotOff = "Hide pivot attributes in channel box" |
| 43 | + |
| 44 | + _negativePositive = "-+ symbols mean negative and positive directions" |
| 45 | + direction = "XYZ values for directional movement" |
| 46 | + moveDirection = "Apply XYZ movement with direction values.\n{0}.".format(_negativePositive) |
| 47 | + distance = "Distance value for separate axes movement" |
| 48 | + moveDistance = "Apply axis movement using distance.\n{0}.".format(_negativePositive) |
| 49 | + |
| 50 | +class Transformations: |
| 51 | + _version = "v1.0" |
| 52 | + _name = "TRANSFORMATIONS" |
| 53 | + _title = _name + " " + _version |
| 54 | + |
| 55 | + def __init__(self, options): |
| 56 | + self.optionsPlugin = options |
| 57 | + ### Check Maya version to avoid cycle import, Maya 2020 and older can't use cycle import |
| 58 | + if cmds.about(version = True) in ["2022", "2023", "2024", "2025"]: |
| 59 | + from . import Options |
| 60 | + if isinstance(options, Options.PluginVariables): |
| 61 | + self.optionsPlugin = options |
| 62 | + |
| 63 | + ### UI elements with values |
| 64 | + self.radioButtonGrpSpace = None |
| 65 | + self.checkBoxRelative = None |
| 66 | + self.checkBoxPreserveChildPosition = None |
| 67 | + self.checkBoxPivot = None |
| 68 | + self.floatFieldMultiplier = None |
| 69 | + self.floatFieldGrpDirection = None |
| 70 | + self.floatFieldDistance = None |
| 71 | + def UICreate(self, layoutMain): |
| 72 | + self.UILayoutLocators(layoutMain) |
| 73 | + cmds.separator(parent = layoutMain, height = Settings.separatorHeight, style = "none") |
| 74 | + def UILayoutLocators(self, layoutMain): |
| 75 | + cmds.frameLayout(parent = layoutMain, label = Settings.frames2Prefix + "MOVE", collapsable = True, backgroundColor = Settings.frames2Color, highlightColor = Colors.green100, marginWidth = 0, marginHeight = 0, borderVisible = True) |
| 76 | + layoutColumn = cmds.columnLayout(adjustableColumn = True) |
| 77 | + |
| 78 | + self.radioButtonGrpSpace = cmds.radioButtonGrp(parent = layoutColumn, label = "Space: ", labelArray3 = ["World", "Local", "Object"], select = 1, numberOfRadioButtons = 3, columnWidth4 = (60, 50, 50, 50), columnAlign = [(1, "right"), (2, "center"), (3, "center"), (4, "center")], height = Settings.lineHeight, annotation = TransformationsAnnotations.space) |
| 79 | + self.radioButtonGrpSpace = self.radioButtonGrpSpace.replace(Settings.windowName + "|", "") # HACK fix for docked window only. Don't know how to avoid issue |
| 80 | + |
| 81 | + cmds.rowLayout(parent = layoutColumn, numberOfColumns = 3, columnWidth3 = (70, 150, 50), columnAlign = [(1, "center"), (2, "center"), (3, "center")], columnAttach = [(1, "both", 0), (2, "both", 0), (3, "both", 0)], height = Settings.lineHeight) |
| 82 | + self.checkBoxRelative = cmds.checkBox(label = "Relative", value = True, annotation = TransformationsAnnotations.relative) |
| 83 | + self.checkBoxPreserveChildPosition = cmds.checkBox(label = "Preserve Child Position", value = False, annotation = TransformationsAnnotations.preserveChildPosition) |
| 84 | + self.checkBoxPivot = cmds.checkBox(label = "Pivot", value = False, annotation = TransformationsAnnotations.pivot) |
| 85 | + |
| 86 | + cmds.rowLayout(parent = layoutColumn, adjustableColumn = 2, numberOfColumns = 5, columnWidth5 = (60, 50, 60, 50, 50), columnAlign = [(1, "right"), (2, "center"), (3, "center"), (4, "center"), (5, "center")], columnAttach = [(1, "both", 0), (2, "both", 0), (3, "both", 0), (4, "both", 0), (5, "both", 0)], height = Settings.lineHeight) |
| 87 | + cmds.text(label = "Multiplier: ", annotation = TransformationsAnnotations.multiplier) |
| 88 | + self.floatFieldMultiplier = cmds.floatField(value = 1, precision = 3, annotation = TransformationsAnnotations.multiplier) |
| 89 | + cmds.popupMenu() |
| 90 | + cmds.menuItem(label = "0.001", command = partial(self.SetMultiplier, 0.001)) |
| 91 | + cmds.menuItem(label = "0.01", command = partial(self.SetMultiplier, 0.01)) |
| 92 | + cmds.menuItem(label = "0.1", command = partial(self.SetMultiplier, 0.1)) |
| 93 | + cmds.menuItem(label = "0.2", command = partial(self.SetMultiplier, 0.1)) |
| 94 | + cmds.menuItem(label = "0.5", command = partial(self.SetMultiplier, 0.5)) |
| 95 | + cmds.menuItem(divider = True) |
| 96 | + cmds.menuItem(label = "1", command = partial(self.SetMultiplier, 1)) |
| 97 | + cmds.menuItem(divider = True) |
| 98 | + cmds.menuItem(label = "1.05", command = partial(self.SetMultiplier, 1.05)) |
| 99 | + cmds.menuItem(label = "1.1", command = partial(self.SetMultiplier, 1.1)) |
| 100 | + cmds.menuItem(label = "1.5", command = partial(self.SetMultiplier, 1.5)) |
| 101 | + cmds.menuItem(divider = True) |
| 102 | + cmds.menuItem(label = "2", command = partial(self.SetMultiplier, 2)) |
| 103 | + cmds.menuItem(label = "3", command = partial(self.SetMultiplier, 3)) |
| 104 | + cmds.menuItem(label = "4", command = partial(self.SetMultiplier, 4)) |
| 105 | + cmds.menuItem(label = "5", command = partial(self.SetMultiplier, 5)) |
| 106 | + cmds.menuItem(divider = True) |
| 107 | + cmds.menuItem(label = "10", command = partial(self.SetMultiplier, 10)) |
| 108 | + cmds.menuItem(label = "100", command = partial(self.SetMultiplier, 100)) |
| 109 | + cmds.menuItem(label = "1000", command = partial(self.SetMultiplier, 1000)) |
| 110 | + cmds.button(label = "Edit Pivot", command = cmds.EnterEditModePress, backgroundColor = Colors.yellow10, annotation = TransformationsAnnotations.editPivot) |
| 111 | + cmds.button(label = "Pivot On", command = partial(self.SetPivotAttributes, True), backgroundColor = Colors.blackWhite80, annotation = TransformationsAnnotations.pivotOn) |
| 112 | + cmds.button(label = "Pivot Off", command = partial(self.SetPivotAttributes, False), backgroundColor = Colors.blackWhite80, annotation = TransformationsAnnotations.pivotOff) |
| 113 | + |
| 114 | + rowLayoutDirection = cmds.rowLayout(parent = layoutColumn, adjustableColumn = 2, numberOfColumns = 2, columnWidth2 = (188, 60), columnAlign = [(1, "right"), (2, "center")], columnAttach = [(1, "both", 0), (2, "both", 0)], height = Settings.lineHeight) |
| 115 | + self.floatFieldGrpDirection = cmds.floatFieldGrp(parent = rowLayoutDirection, label = "Direction: ", numberOfFields = 3, columnWidth4 = (60, 40, 40, 40), value = [0, 0, 0, 0], columnAlign = [(1, "right"), (2, "center"), (3, "center"), (4, "center")], annotation = TransformationsAnnotations.direction) |
| 116 | + self.floatFieldGrpDirection = self.floatFieldGrpDirection.replace(Settings.windowName + "|", "") # HACK fix for docked window only. Don't know how to avoid issue |
| 117 | + cmds.popupMenu() |
| 118 | + cmds.menuItem(label = "0, 0, 0", command = partial(self.SetDirection, [0, 0, 0])) |
| 119 | + cmds.menuItem(divider = True) |
| 120 | + cmds.menuItem(label = "1, 0, 0", command = partial(self.SetDirection, [1, 0, 0])) |
| 121 | + cmds.menuItem(label = "0, 1, 0", command = partial(self.SetDirection, [0, 1, 0])) |
| 122 | + cmds.menuItem(label = "0, 0, 1", command = partial(self.SetDirection, [0, 0, 1])) |
| 123 | + cmds.gridLayout(parent = rowLayoutDirection, numberOfColumns = 2, cellWidth = 44, cellHeight = Settings.lineHeight) |
| 124 | + cmds.button(label = "-XYZ", command = partial(self.MoveSelected, 0, True), backgroundColor = Colors.orange10, annotation = TransformationsAnnotations.moveDirection) |
| 125 | + cmds.button(label = "+XYZ", command = partial(self.MoveSelected, 0, False), backgroundColor = Colors.orange50, annotation = TransformationsAnnotations.moveDirection) |
| 126 | + |
| 127 | + cmds.rowLayout(parent = layoutColumn, adjustableColumn = 2, numberOfColumns = 3, columnWidth3 = (60, 50, 156), columnAlign = [(1, "right"), (2, "center"), (3, "center")], columnAttach = [(1, "both", 0), (2, "both", 0), (3, "both", 0)], height = Settings.lineHeight) |
| 128 | + cmds.text(label = "Distance: ", annotation = TransformationsAnnotations.distance) |
| 129 | + self.floatFieldDistance = cmds.floatField(value = 1, precision = 3, annotation = TransformationsAnnotations.distance) |
| 130 | + cmds.popupMenu() |
| 131 | + cmds.menuItem(label = "0", command = partial(self.SetDistance, 0)) |
| 132 | + cmds.menuItem(divider = True) |
| 133 | + cmds.menuItem(label = "0.01", command = partial(self.SetDistance, 0.01)) |
| 134 | + cmds.menuItem(label = "0.1", command = partial(self.SetDistance, 0.1)) |
| 135 | + cmds.menuItem(label = "0.5", command = partial(self.SetDistance, 0.5)) |
| 136 | + cmds.menuItem(divider = True) |
| 137 | + cmds.menuItem(label = "1", command = partial(self.SetDistance, 1)) |
| 138 | + cmds.menuItem(label = "2", command = partial(self.SetDistance, 2)) |
| 139 | + cmds.menuItem(label = "3", command = partial(self.SetDistance, 3)) |
| 140 | + cmds.menuItem(label = "5", command = partial(self.SetDistance, 5)) |
| 141 | + cmds.menuItem(divider = True) |
| 142 | + cmds.menuItem(label = "10", command = partial(self.SetDistance, 10)) |
| 143 | + cmds.menuItem(label = "100", command = partial(self.SetDistance, 100)) |
| 144 | + cmds.menuItem(label = "1000", command = partial(self.SetDistance, 1000)) |
| 145 | + cmds.gridLayout(numberOfColumns = 6, cellWidth = 26, cellHeight = Settings.lineHeight) |
| 146 | + cmds.button(label = "-X", command = partial(self.MoveSelected, 1, True), backgroundColor = Colors.red10, annotation = TransformationsAnnotations.moveDistance) |
| 147 | + cmds.button(label = "+X", command = partial(self.MoveSelected, 1, False), backgroundColor = Colors.red50, annotation = TransformationsAnnotations.moveDistance) |
| 148 | + cmds.button(label = "-Y", command = partial(self.MoveSelected, 2, True), backgroundColor = Colors.green10, annotation = TransformationsAnnotations.moveDistance) |
| 149 | + cmds.button(label = "+Y", command = partial(self.MoveSelected, 2, False), backgroundColor = Colors.green50, annotation = TransformationsAnnotations.moveDistance) |
| 150 | + cmds.button(label = "-Z", command = partial(self.MoveSelected, 3, True), backgroundColor = Colors.blue10, annotation = TransformationsAnnotations.moveDistance) |
| 151 | + cmds.button(label = "+Z", command = partial(self.MoveSelected, 3, False), backgroundColor = Colors.blue50, annotation = TransformationsAnnotations.moveDistance) |
| 152 | + |
| 153 | + def SetMultiplier(self, value, *args): |
| 154 | + cmds.floatField(self.floatFieldMultiplier, edit = True, value = value) |
| 155 | + def SetDirection(self, value, *args): |
| 156 | + cmds.floatFieldGrp(self.floatFieldGrpDirection, edit = True, value = [value[0], value[1], value[2], 0]) |
| 157 | + def SetDistance(self, value, *args): |
| 158 | + cmds.floatField(self.floatFieldDistance, edit = True, value = value) |
| 159 | + def SetPivotAttributes(self, value, *args): |
| 160 | + selected = cmds.ls(selection = True) |
| 161 | + for i in range(len(selected)): |
| 162 | + cmds.setAttr(selected[i] + ".rotatePivotX", channelBox = value) |
| 163 | + cmds.setAttr(selected[i] + ".rotatePivotY", channelBox = value) |
| 164 | + cmds.setAttr(selected[i] + ".rotatePivotZ", channelBox = value) |
| 165 | + cmds.setAttr(selected[i] + ".scalePivotX", channelBox = value) |
| 166 | + cmds.setAttr(selected[i] + ".scalePivotY", channelBox = value) |
| 167 | + cmds.setAttr(selected[i] + ".scalePivotZ", channelBox = value) |
| 168 | + |
| 169 | + def MoveSelected(self, axis, reverse=False, *args): |
| 170 | + space = cmds.radioButtonGrp(self.radioButtonGrpSpace, query = True, select = True) |
| 171 | + isWorldSpace = space == 1 |
| 172 | + isLocalSpace = space == 2 |
| 173 | + isObjectSpace = space == 3 |
| 174 | + |
| 175 | + relative = cmds.checkBox(self.checkBoxRelative, query = True, value = True) |
| 176 | + preserveChildPosition = cmds.checkBox(self.checkBoxPreserveChildPosition, query = True, value = True) |
| 177 | + pivot = cmds.checkBox(self.checkBoxPivot, query = True, value = True) |
| 178 | + |
| 179 | + direction = [0, 0, 0] |
| 180 | + if axis == 0: |
| 181 | + direction = cmds.floatFieldGrp(self.floatFieldGrpDirection, query = True, value = True) |
| 182 | + else: |
| 183 | + distance = cmds.floatField(self.floatFieldDistance, query = True, value = True) |
| 184 | + if axis == 1: |
| 185 | + direction = [distance, 0, 0] |
| 186 | + if axis == 2: |
| 187 | + direction = [0, distance, 0] |
| 188 | + if axis == 3: |
| 189 | + direction = [0, 0, distance] |
| 190 | + |
| 191 | + multiplier = cmds.floatField(self.floatFieldMultiplier, query = True, value = True) |
| 192 | + multiplier = multiplier * (-1 if reverse else 1) |
| 193 | + direction[0] = direction[0] * multiplier |
| 194 | + direction[1] = direction[1] * multiplier |
| 195 | + direction[2] = direction[2] * multiplier |
| 196 | + |
| 197 | + selected = cmds.ls(selection = True) |
| 198 | + selectedFinal = [] |
| 199 | + if (pivot): |
| 200 | + for i in range (len(selected)): |
| 201 | + selectedFinal.append(selected[i] + ".scalePivot") |
| 202 | + selectedFinal.append(selected[i] + ".rotatePivot") |
| 203 | + else: |
| 204 | + selectedFinal = selected; |
| 205 | + |
| 206 | + cmds.move(direction[0], direction[1], direction[2], selectedFinal, worldSpace = isWorldSpace, objectSpace = isObjectSpace, localSpace = isLocalSpace, relative = relative, preserveChildPosition = preserveChildPosition) |
| 207 | + |
0 commit comments