diff --git a/GETOOLS_SOURCE/_prototypes/CurveSpaceSwitching.py b/GETOOLS_SOURCE/_prototypes/CurveSpaceSwitching.py new file mode 100644 index 0000000..181ed30 --- /dev/null +++ b/GETOOLS_SOURCE/_prototypes/CurveSpaceSwitching.py @@ -0,0 +1,35 @@ +# GETOOLS is under the terms of the MIT License +# Copyright (c) 2018-2024 Eugene Gataulin (GenEugene). All Rights Reserved. + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +# Author: Eugene Gataulin tek942@gmail.com https://www.linkedin.com/in/geneugene https://discord.gg/heMxJhTqCz +# Source code: https://github.com/GenEugene/GETools or https://app.gumroad.com/geneugene + +import maya.cmds as cmds + +# from ..utils import Selector +# from ..values import Enums + + +def SetupSpaceDeformation(curves=None, *args): # TODO + if curves is None: + cmds.warning("No curves provided") + return None + + diff --git a/GETOOLS_SOURCE/modules/Tools.py b/GETOOLS_SOURCE/modules/Tools.py index fdc754d..0918570 100644 --- a/GETOOLS_SOURCE/modules/Tools.py +++ b/GETOOLS_SOURCE/modules/Tools.py @@ -240,6 +240,12 @@ def UILayoutLocators(self, layoutMain): cmds.button(label = "Translate + Rotate", command = partial(self.LocatorsBakeAim, False), backgroundColor = Colors.orange10, annotation = ToolsAnnotations.locatorAimSpaceBakeAll) cmds.button(label = "Only Rotate", command = partial(self.LocatorsBakeAim, True), backgroundColor = Colors.orange10, annotation = ToolsAnnotations.locatorAimSpaceBakeRotate) # cmds.setParent("..") + + ### CURVE SPACE SWITCHING + layoutCurveSpace = cmds.frameLayout(parent = layoutColumn, label = "Curve Space Switching", labelIndent = 72, collapsable = False, backgroundColor = Settings.frames2Color, marginWidth = 0, marginHeight = 0) + cmds.button(label = "Create Motion Path Locator", command = Locators.CreateWithMotionPath, backgroundColor = Colors.green10) + + def UILayoutBaking(self, layoutMain): cmds.frameLayout(parent = layoutMain, label = Settings.frames2Prefix + "BAKING", collapsable = True, backgroundColor = Settings.frames2Color, highlightColor = Colors.green100, marginWidth = 0, marginHeight = 0, borderVisible = True) layoutColumn = cmds.columnLayout(adjustableColumn = True, rowSpacing = Settings.columnLayoutRowSpacing) diff --git a/GETOOLS_SOURCE/utils/Baker.py b/GETOOLS_SOURCE/utils/Baker.py index 81de4ea..895ebec 100644 --- a/GETOOLS_SOURCE/utils/Baker.py +++ b/GETOOLS_SOURCE/utils/Baker.py @@ -32,8 +32,8 @@ def BakeSelected(classic=True, preserveOutsideKeys=True, sampleBy=1.0, selectedRange=False, channelBox=False, attributes=None, euler=False): # Check selected objects - selectedList = Selector.MultipleObjects(1) - if (selectedList == None): + selectedList = Selector.MultipleObjects(minimalCount = 1) + if selectedList is None: return # Calculate time range if range highlighted @@ -53,7 +53,7 @@ def BakeSelected(classic=True, preserveOutsideKeys=True, sampleBy=1.0, selectedR if (channelBox): bakeRegular = selectedAttributes == None if (bakeRegular): - if (attributes == None): + if attributes is None: cmds.bakeResults(time = (timeRange[0], timeRange[1]), preserveOutsideKeys = preserveOutsideKeys, simulation = True, minimizeRotation = True, sampleBy = sampleBy) else: cmds.bakeResults(time = (timeRange[0], timeRange[1]), preserveOutsideKeys = preserveOutsideKeys, simulation = True, minimizeRotation = True, sampleBy = sampleBy, attribute = attributes) @@ -76,8 +76,8 @@ def BakeSelected(classic=True, preserveOutsideKeys=True, sampleBy=1.0, selectedR def BakeSelectedByLastObject(pairOnly=False, sampleBy=1.0, selectedRange=False, channelBox=False, attributes=None, euler=False): # Check selected objects - selectedList = Selector.MultipleObjects(2) - if (selectedList == None): + selectedList = Selector.MultipleObjects(minimalCount = 2) + if selectedList is None: return # Cut list by last 2 items @@ -100,8 +100,8 @@ def BakeSelectedByLastObject(pairOnly=False, sampleBy=1.0, selectedRange=False, def BakeSelectedByWorld(sampleBy=1.0, selectedRange=False, channelBox=False, attributes=None, euler=False): # Check selected objects - selectedList = Selector.MultipleObjects(1) - if (selectedList == None): + selectedList = Selector.MultipleObjects(minimalCount = 1) + if selectedList is None: return world = cmds.group(world = True, empty = True) diff --git a/GETOOLS_SOURCE/utils/Constraints.py b/GETOOLS_SOURCE/utils/Constraints.py index b667262..ecd62f9 100644 --- a/GETOOLS_SOURCE/utils/Constraints.py +++ b/GETOOLS_SOURCE/utils/Constraints.py @@ -51,25 +51,25 @@ def ConstrainSecondToFirstObject(objectParent, objectChild, maintainOffset=True, ### Check attributes with fixed axis labels def CheckAttributes(attributeName, attributesFiltered): axisLabels = ["x", "y", "z"] - # Check which attributes are missing + ### Check which attributes are missing check = [attr in attributesFiltered for attr in attributeName] - # If all attributes are present, return "none" + ### If all attributes are present, return "none" if all(check): return "none" - # Return the axis labels for missing attributes + ### Return the axis labels for missing attributes return [axisLabels[i] for i, valid in enumerate(check) if not valid] ### General function to process attributes def ProcessAttributes(objectChild, attributeName): - # Construct attributes with object name + ### Construct attributes with object name attributes = ["{0}.{1}".format(objectChild, attr) for attr in attributeName] attributesFiltered = Attributes.FilterAttributesAnimatable(attributes=attributes, skipConstrainedKeys = False) - # If no attributes are left after filtering, return "none" + ### If no attributes are left after filtering, return "none" if not attributesFiltered: return ("x", "y", "z") - # Remove object name from attributes + ### Remove object name from attributes attributesFiltered = [attr.replace(objectChild + ".", "") for attr in attributesFiltered] - # Check attributes and return the axes to skip + ### Check attributes and return the axes to skip return CheckAttributes(attributeName, attributesFiltered) ### Use generalized logic for all attributes @@ -107,14 +107,14 @@ def ProcessAttributes(objectChild, attributeName): ConstrainAim(objectParent, objectChild, maintainOffset, weight) # TODO add customization logic def ConstrainAim(objectParent, objectChild, maintainOffset=True, weight=1, aimVector=(0, 0, 1), upVector=(0, 1, 0), worldUpVector=(0, 1, 0), worldUpObject=None): # TODO complete aim logic - # "scene" "object" "objectrotation" "vector" "none" + ### "scene" "object" "objectrotation" "vector" "none" if worldUpObject is None: cmds.aimConstraint(objectParent, objectChild, maintainOffset = maintainOffset, weight = weight, skip = "none", aimVector = aimVector, upVector = upVector, worldUpType = "vector", worldUpVector = worldUpVector) else: cmds.aimConstraint(objectParent, objectChild, maintainOffset = maintainOffset, weight = weight, skip = "none", aimVector = aimVector, upVector = upVector, worldUpType = "objectrotation", worldUpVector = worldUpVector, worldUpObject = worldUpObject) def DeleteConstraints(selected): - # First pass + ### First pass connections = Selector.GetConnectionsOfType(selected, type = Enums.Types.constraint, source = True, destination = False) for item in connections: if item is None: @@ -126,7 +126,7 @@ def DeleteConstraints(selected): if constraint in connection: cmds.delete(connection) - # Second pass with checking child objects (if constraint exists but not connected) + ### Second pass with checking child objects (if constraint exists but not connected) children = Selector.GetChildrenOfType(selected, type = Enums.Types.constraint) for i in range(len(selected)): if children[i] is not None: diff --git a/GETOOLS_SOURCE/utils/Curves.py b/GETOOLS_SOURCE/utils/Curves.py index b1d0756..98f8592 100644 --- a/GETOOLS_SOURCE/utils/Curves.py +++ b/GETOOLS_SOURCE/utils/Curves.py @@ -27,34 +27,31 @@ from ..values import Enums -_curveName = "newCurve" -_curveDegree = 1 - - def CreateCurveFromSelectedObjects(*args): + degree = 1 # TODO add options to UI + # Check selected objects - selectedList = Selector.MultipleObjects(2) - if (selectedList == None): + selected = Selector.MultipleObjects(minimalCount = 2) + if (selected == None): return None positions = [] - for item in selectedList: + for item in selected: position = cmds.xform(item, query = True, translation = True, worldSpace = True) positions.append(position) - curve = cmds.curve(name = _curveName, degree = _curveDegree, point = positions) + curve = cmds.curve(name = "newCurve", degree = degree, point = positions) return curve -def CreateCurveFromTrajectory(*args): # TODO rework +def CreateCurveFromTrajectory(name="curve", *args): # TODO rework ### Variables step = 1 - degree = 3 + degree = 1 ### Names mtName = "newMotionTrail" mtFinalName = mtName + Enums.MotionTrail.handle - curveName = "testCurve" ### Get time start/end @@ -65,8 +62,8 @@ def CreateCurveFromTrajectory(*args): # TODO rework cmds.snapshot(name = mtName, motionTrail = 1, increment = step, startTime = start, endTime = end) ### Get points from motion trail - cmds.select(mtFinalName, replace = 1) - selected = cmds.ls(selection = 1, dagObjects = 1, exactType = Enums.MotionTrail.snapshotShape) + cmds.select(mtFinalName, replace = True) + selected = cmds.ls(selection = True, dagObjects = True, exactType = Enums.MotionTrail.snapshotShape) pts = cmds.getAttr(selected[0] + "." + Enums.MotionTrail.pts) size = len(pts) for i in range(size): @@ -74,12 +71,11 @@ def CreateCurveFromTrajectory(*args): # TODO rework #print "{0}: {1}".format(i, pts[i]) ### Create curve - newCurve = cmds.curve(name = curveName, degree = degree, point = pts) + newCurve = cmds.curve(name = name, degree = degree, point = pts) ### End cmds.delete(mtFinalName) - cmds.select(clear = 1) + cmds.select(clear = True) return newCurve - diff --git a/GETOOLS_SOURCE/utils/Locators.py b/GETOOLS_SOURCE/utils/Locators.py index 6b52e41..2816cdc 100644 --- a/GETOOLS_SOURCE/utils/Locators.py +++ b/GETOOLS_SOURCE/utils/Locators.py @@ -26,6 +26,7 @@ from ..utils import Animation from ..utils import Baker from ..utils import Constraints +from ..utils import Curves from ..utils import Parent from ..utils import Selector from ..utils import Text @@ -38,7 +39,7 @@ _minSelectedCount = 1 -# SIZE +### SIZE def GetSize(locator): shape = cmds.listRelatives(locator, shapes = True, type = Enums.Types.locator)[0] if (shape != None): @@ -86,7 +87,7 @@ def SelectedLocatorsSizeSet(value, *args): if (shape != None): SetSize(item, value, value, value) -# CREATE +### CREATE def Create(name=_nameBase, scale=_scale, hideParent=False, subLocator=False): locatorCurrent = cmds.spaceLocator(name = Text.SetUniqueFromText(name))[0] SetSize(locatorCurrent, scale, scale, scale) @@ -106,7 +107,7 @@ def Create(name=_nameBase, scale=_scale, hideParent=False, subLocator=False): else: return locatorCurrent def CreateOnSelected(name=_nameBase, scale=_scale, minSelectedCount=_minSelectedCount, hideParent=False, subLocator=False, constraint=False, bake=False, parentToLastSelected=False, constrainReverse=False, constrainTranslate=True, constrainRotate=True, euler=False): - # Check selected objects + ### Check selected objects selectedList = Selector.MultipleObjects(minSelectedCount) if (selectedList == None): return None @@ -114,7 +115,7 @@ def CreateOnSelected(name=_nameBase, scale=_scale, minSelectedCount=_minSelected locatorsList = [] sublocatorsList = [] - # Create locators on selected + ### Create locators on selected for item in selectedList: nameCurrent = Text.GetShortName(item, removeSpaces = True) + "_" + name created = Create(name = nameCurrent, scale = scale, hideParent = hideParent, subLocator = subLocator) @@ -125,12 +126,12 @@ def CreateOnSelected(name=_nameBase, scale=_scale, minSelectedCount=_minSelected locatorsList.append(created) cmds.matchTransform(locatorsList[-1], item, position = True, rotation = True, scale = True) - # Constrain locators to selected objects + ### Constrain locators to selected objects if (constraint): for i in range(len(selectedList)): Constraints.ConstrainSecondToFirstObject(selectedList[i], locatorsList[i], maintainOffset = False) - # Parent locators to last or to last sublocator + ### Parent locators to last or to last sublocator if (bake): if (parentToLastSelected): if subLocator: @@ -140,13 +141,13 @@ def CreateOnSelected(name=_nameBase, scale=_scale, minSelectedCount=_minSelected else: Parent.ListToLastObjects(locatorsList) - # Bake locators and delete constraints + ### Bake locators and delete constraints cmds.select(locatorsList) Baker.BakeSelected(euler = euler) Animation.DeleteStaticCurves() Constraints.DeleteConstraints(locatorsList) - # Reverse constrain original objects to new locators + ### Reverse constrain original objects to new locators if constrainReverse: for i in range(len(selectedList)): if subLocator: @@ -155,7 +156,7 @@ def CreateOnSelected(name=_nameBase, scale=_scale, minSelectedCount=_minSelected firstObject = locatorsList[i] Constraints.ConstrainSecondToFirstObject(firstObject, selectedList[i], maintainOffset = False, parent = constrainTranslate and constrainRotate, point = constrainTranslate, orient = constrainRotate) - # Select objects and return + ### Select objects and return if subLocator: cmds.select(sublocatorsList) return selectedList, locatorsList, sublocatorsList @@ -163,12 +164,12 @@ def CreateOnSelected(name=_nameBase, scale=_scale, minSelectedCount=_minSelected cmds.select(locatorsList) return selectedList, locatorsList def CreateAndBakeAsChildrenFromLastSelected(scale=_scale, minSelectedCount=2, hideParent=False, subLocator=False, constraintReverse=False, skipLastReverse=True, euler=False): - # Check selected objects + ### Check selected objects objects = CreateOnSelected(scale = scale, minSelectedCount = minSelectedCount, hideParent = hideParent, subLocator = subLocator, constraint = True, bake = True, parentToLastSelected = True, euler = euler) if (objects == None): return None - # Constrain objects to locators + ### Constrain objects to locators if (constraintReverse): for i in range(len(objects[0])): if (skipLastReverse and i == len(objects[0]) - 1): @@ -178,19 +179,19 @@ def CreateAndBakeAsChildrenFromLastSelected(scale=_scale, minSelectedCount=2, hi else: Constraints.ConstrainSecondToFirstObject(objects[1][i], objects[0][i], maintainOffset = False) - # Select objects and return + ### Select objects and return if subLocator: cmds.select(objects[2][-1]) else: cmds.select(objects[1][-1]) return objects def CreateOnSelectedAim(name=_nameAim, scale=_scale, minSelectedCount=_minSelectedCount, hideParent=False, subLocator=False, rotateOnly=False, vectorAim=(1,0,0), distance=100, reverse=True, euler=False): - # Check selected objects + ### Check selected objects objects = CreateOnSelected(name = name, scale = scale, minSelectedCount = minSelectedCount, hideParent = hideParent, subLocator = subLocator, euler = euler) if (objects == None): return None - # Create aim locators + ### Create aim locators groupsList = [] locatorsOffsetsList = [] locatorsTargetsList = [] @@ -231,7 +232,7 @@ def CreateOnSelectedAim(name=_nameAim, scale=_scale, minSelectedCount=_minSelect Constraints.ConstrainListToLastElement(selected = (locTarget, objects[0][i])) Constraints.ConstrainListToLastElement(selected = (locUp, objects[0][i])) - # Bake animation from original objects + ### Bake animation from original objects cmds.select(objects[1] + locatorsTargetsList, replace = True) cmds.select(objects[1] + locatorsUpList, add = True) Baker.BakeSelected(euler = euler) @@ -240,11 +241,11 @@ def CreateOnSelectedAim(name=_nameAim, scale=_scale, minSelectedCount=_minSelect Constraints.DeleteConstraints(locatorsUpList) Animation.DeleteStaticCurves() - # Create aim constraint + ### Create aim constraint for i in range(len(objects[0])): cmds.aimConstraint(locatorsTargetsList[i], locatorsOffsetsList[i], maintainOffset = True, weight = 1, aimVector = vectorAim, worldUpType = "object", worldUpObject = locatorsUpList[i]) - # Reverse constrain # TODO move constraint to temp group + ### Reverse constrain # TODO move constraint to temp group if (reverse): for i in range(len(objects[0])): parentObject = None @@ -253,17 +254,86 @@ def CreateOnSelectedAim(name=_nameAim, scale=_scale, minSelectedCount=_minSelect else: parentObject = locatorsOffsetsList[i] - # Constraints + ### Constraints if (rotateOnly): Constraints.ConstrainSecondToFirstObject(objects[0][i], objects[1][i], maintainOffset = False, parent = False, point = True, orient = False) Constraints.ConstrainSecondToFirstObject(parentObject, objects[0][i], maintainOffset = False, parent = False, point = False, orient = True) else: Constraints.ConstrainSecondToFirstObject(parentObject, objects[0][i], maintainOffset = False, parent = False, point = True, orient = True) - # Select objects and return + ### Select objects and return cmds.select(locatorsTargetsList) # if subLocator: # return objects[0], aimGroup, objects[1], locatorsOffsetsList, locatorsTargetsList, objects[2] # else: # return objects[0], aimGroup, objects[1], locatorsOffsetsList, locatorsTargetsList +def CreateWithMotionPath(*args): # TODO + ### Check selected objects + selected = Selector.MultipleObjects(minimalCount = 1) + if (selected == None): + return None + cmds.select(clear = True) + + ### Create main group as a container for all new objects + mainGroup = cmds.group(name = Text.SetUniqueFromText("mpGroup"), empty = True) + cmds.select(clear = True) + + + ### TODO Create loop logic for each selected object + firstObject = selected[0] + + + ### Create curve and select + cmds.select(firstObject, replace = True) + curve = Curves.CreateCurveFromTrajectory(name = Text.SetUniqueFromText("mpCurve")) + cmds.parent(curve, mainGroup) + + ### Create closest point node with locators + cmds.select(curve, replace = True) + cmds.ClosestPointOn() + closestPointLocatorPos = cmds.ls(selection = True)[0] + closestPointNode = cmds.listConnections(closestPointLocatorPos, source = True, destination = False)[0] + closestPointLocatorIn = cmds.listConnections(closestPointNode, source = True, destination = False, type = "transform")[0] + cmds.setAttr(closestPointLocatorPos + "." + Enums.Attributes.visibility, 0) + cmds.setAttr(closestPointLocatorIn + "." + Enums.Attributes.visibility, 0) + cmds.select(clear = True) + + ### Constrain locators to source object + Constraints.ConstrainSecondToFirstObject(firstObject, closestPointLocatorIn, maintainOffset = False, parent = False, point = True, orient = False) + + ### Create locator outer + locatorOuter = Create(name = Text.SetUniqueFromText("mpLocatorOuter"), scale = 1) + cmds.parent(locatorOuter, mainGroup) + cmds.select(clear = True) + + ### Create locator inner + # locatorInner = Create(name = Text.SetUniqueFromText("mpLocatorInner"), scale = 1) + # cmds.parent(locatorInner, locatorOuter) + # cmds.select(clear = True) + + ### Create motion path constraint with outer locator + timeMin = cmds.playbackOptions(query = True, min = True) + timeMax = cmds.playbackOptions(query = True, max = True) + motionPath = cmds.pathAnimation(locatorOuter, curve = curve, startTimeU = timeMin, endTimeU = timeMax, follow = False) + + ### Constrain inner locator to outer locator + # Constraints.ConstrainSecondToFirstObject(firstObject, locatorInner, maintainOffset = False, parent = False, point = False, orient = True) + + ### Connect closest point parameter to U parameter in motion path + cmds.connectAttr(closestPointNode + ".parameter", motionPath + ".uValue", force = True) + + ### Bake motion path uValue + cmds.select(motionPath, replace = True) + # cmds.select(locatorInner, add = True) + cmds.bakeResults(time = (timeMin, timeMax), preserveOutsideKeys = True, simulation = True, minimizeRotation = True, sampleBy = 1, disableImplicitControl = True, attribute = ("uValue",) + Enums.Attributes.rotateShort) + cmds.select(clear = True) + + ### Delete temp locators + cmds.delete(closestPointNode) + cmds.delete(closestPointLocatorPos) + cmds.delete(closestPointLocatorIn) + + ### Constrain original object to locator + Constraints.ConstrainSecondToFirstObject(locatorOuter, firstObject, maintainOffset = True, parent = False, point = True, orient = False) + diff --git a/todo.txt b/todo.txt index 09957a2..5fecd31 100644 --- a/todo.txt +++ b/todo.txt @@ -22,3 +22,21 @@ GETools TODO List [CENTER OF MASS] - improve projection approach + +[CURVE SPACE DEFORMATION] +### Prepare Curves +1. Select main rootmotion control +2. Select IKs like feet and hands +3. Convert movement to curves +4. Rebind controls to curves with Motion trails and sync timings + +### Setup Deformation Space Relationship +1. Get curves list +2. Get latest curve from list as a deformation curve +3. Create a "Sweep Mesh" on deformation curve with "Line" profile, interpolation mode "Start To End" and steps +4. Connect other curves to "Sweep Mesh" with "Wrap" deformer using "Volume" falloff mode + +### Organize objects in one group per space deformation +- Parent each top level object to the group +- Use unique naming with naming converter +