Interactive Slider with its own Undo Chunk?

184 views
Skip to first unread message

Isai Calderon

unread,
Oct 18, 2017, 9:46:12 PM10/18/17
to Python Programming for Autodesk Maya
I have a window with an interactive slider moving keys around (keying is unnecessary). Unfortunately the Undo queue loads up with every tiny change in value.

The idea is to move the slider many times until it's just right, sometimes even closing the UI and opening it up again, all while not loading the Undo queue.

Any ideas on how to group all the instances of the "moveJimmy" command below that the slider activates into a single Undo function?

The script is for moving keys, but the following code is a good example of what's going on. Makes sphere, makes UI with slider, slider moves sphere up and down:

import maya.cmds as mc

class Jimmy(object):
    
    def __init__(self):
        ''' Make Jimmy '''
        mc.polySphere(name='Jimmy')

    def makeJimmyUI(self,*args,**kwargs):
        ''' Make Jimmy's window '''
        
        # Remove UI if it already exists
        if mc.window('jimmyUI',query=1,exists=1):
            mc.deleteUI('jimmyUI')
        
        mc.window('jimmyUI')
        mc.columnLayout()

        # Create Jimmy's slider
        mc.floatSliderGrp('allInTheHips',
                          label='Move Jimmy',
                          dragCommand=self.moveJimmy, # Have Jimmy's slider activate moveSphere
                          sliderStep=0.1,
                          minValue=-10,
                          maxValue=10,
                          )

        # Show Jimmy's Window
        mc.showWindow('jimmyUI')
    
    def moveJimmy(self,*args, **kwargs):
        ''' Move Jimmy up and down '''
        print 'yup'
        # value = value
        mc.setAttr('Jimmy.translateY',
                   mc.floatSliderGrp('allInTheHips',
                                     query=True,
                                     value=True)
                   )


James = Jimmy()
James.makeJimmyUI() # As long as "Jimmy" exists, this UI can be reopened later


Thank you!!

All the best,

Isai

Simon Anderson

unread,
Oct 18, 2017, 10:04:50 PM10/18/17
to Python Programming for Autodesk Maya
Hi Isai,

Sounds like a tricky situation, as when would the code know the user is done "tweaking" the slider and happy with the result, as you are continuesly apply the command. if you store the original state before a tweak, you will end up with a ton of undos, which is the situation you are in now.

You could store the original position of the selected object when the ui is loaded, that way if the user performs an undo, it goes back to the start position. That is half way there, as you wil have problems if users select another object while the ui is open. So you would have to put a check, to see if you are already storing that objects position in an undo dictionary (im rambling a bit)

You could try implement a timer, that is the user doesn't slide the bar for a few seconds then it stores overwrites the undo transform that is being stored

as you are using commands, each command store an undo in Mayas stack, I would suggest you start using OM1 or OM2, preferably OM2 to perform these moves, as that way you get to control the undo stack that Maya registers.

How do you see users interacting with the UI?

Isai Calderon

unread,
Oct 19, 2017, 3:11:57 PM10/19/17
to Python Programming for Autodesk Maya
Hi Simon, thank you for responding so quickly. Funny enough, the "rambling bit" is what helped me most :) I had tried a similar solution before, but after a bit more time of thinking, I figured it out. Just for kicks though, I tried the "import time" solution. Following are my tests and new question!

Test solution #1:
I tried using Time to solve it. It works, but with one unfortunate bug/feature: If you are holding the slider and don't move it, it'll think that you released the slider and reset the undo Queue.
Here's the modified sample code:
import maya.cmds as mc
import time

class Jimmy(object):
    
    def __init__(self):
        ''' Make Jimmy '''
        mc.polySphere(name='Jimmy')
        self.timey = 0

    def makeJimmyUI(self,*args,**kwargs):
        ''' Make Jimmy's window '''
        
        # Remove UI if it already exists
        if mc.window('jimmyUI',query=1,exists=1):
            mc.deleteUI('jimmyUI')

        mc.window('jimmyUI')
        mc.columnLayout()

        # Create Jimmy's slider
        mc.floatSliderGrp('allInTheHips',
                          label='Move Jimmy',
                          dragCommand=self.moveJimmy, # Have Jimmy's slider activate moveSphere
                          sliderStep=0.01,
                          minValue=-10,
                          maxValue=10,
                          )

        # Show Jimmy's Window
        mc.showWindow('jimmyUI')
    
    def moveJimmy(self,*args, **kwargs):
        ''' Move Jimmy up and down '''
        
        thisTime = time.time() # Store the current time
        remTime = (thisTime - self.timey) # Compare it to the new time
        if remTime <.03: # If the time difference is less than 0.03 seconds,
            mc.undo() # Run the undo function
        else:
            print 'Reset Timer' # If more than 0.03 seconds pass, reset the undo queue

        mc.setAttr('Jimmy.translateY',
                   mc.floatSliderGrp('allInTheHips', query=True, value=True)
                   )

        self.timey = time.time() # Store the new time


James = Jimmy()
James.makeJimmyUI() # As long as "Jimmy" exists, this UI can be reopened later



Test solution #2 (successful with one caveat):
Solution: Store selected Item and its initial value in global variables. If it's the same item and NOT the same initial value, undo. If undo deselects the item, redo. Proceed with code.
Bug: The script outputs every single Undo that is performed. Not sure how to make it not display the Undo output.
Sample code time! (note: the code no longer creates a sphere. It instead manipulates the selected item. Did this so you can test on different items)
import maya.cmds as mc

class Jimmy(object):
    ''' The code no longer creates an item. Instead, create multiple objects, select one, then another,
    then re-select the previous one. The code works!! IT IS ALIIIIIIIIVE!!!!!
    '''
    def __init__(self):
        ''' Get Jimmy's attributes '''
        self.oldItem = None
        self.oldItemTranslate = None

    def makeJimmyUI(self,*args,**kwargs):
        ''' Make Jimmy's window '''
        
        # Remove UI if it already exists
        if mc.window('jimmyUI',query=1,exists=1):
            mc.deleteUI('jimmyUI')
        
        mc.window('jimmyUI')
        mc.columnLayout()

        # Create Jimmy's slider
        mc.floatSliderGrp('allInTheHips',
                          label='Move Jimmy',
                          dragCommand=self.moveJimmy, # Have Jimmy's slider activate moveSphere
                          sliderStep=0.01,
                          minValue=-10,
                          maxValue=10,
                          )

        # Show Jimmy's Window
        mc.showWindow('jimmyUI')
    
    def moveJimmy(self,*args, **kwargs):
        ''' Move selected item up and down '''
        
        # Move the first item in the selection only
        newItem = mc.ls(sl=1)[0]
        newItemTranslate = mc.getAttr(newItem+'.translateY')
        
        # If this item was just moved by the slider and if item's value is the same as its original value
        if self.oldItem == newItem and self.oldItemTranslate != newItemTranslate:
            
            # Undo the move!
            mc.undo()
            # But if undoing deselects the manipulated item, Redo :)
            if mc.ls(sl=1) == []:
                mc.redo()
            
        else:
            # Store the new item with the old Item
            self.oldItem = newItem
            self.oldItemTranslate = newItemTranslate
        
        # Perform the calculation!
        newItemTranslate = mc.floatSliderGrp('allInTheHips',
                                             query=True,
                                             value=True) + self.oldItemTranslate
        mc.setAttr(newItem + '.translateY', newItemTranslate)


James = Jimmy()
James.makeJimmyUI() # As long as "Jimmy" exists, this UI can be reopened later


As I mentioned, it works!! Go ahead and Undo to your heart's content :)

Only problem is that the script editor spits out code every time the Undo function is run. So my new question is:

Is there a way to turn off the script editor echo for a single line of code!? I want the animator to be able to see output code as if it was native.

Also, if anyone can come up with a better solution to this overall puzzle, I'd love feedback!

Thank you again, Simon!!

Isai

Simon Anderson

unread,
Oct 19, 2017, 5:22:18 PM10/19/17
to Python Programming for Autodesk Maya
Ahoy Isai,

Nice implementations.

For Solution 1, you could add two events one for the "click" mousePressEvent and another for the "release" mouseReleaseEvent. That way when the user clicks on the slider it stores the initial position if timer is 0, and then when the user releases the mouse it starts the timer.

I would suggest looking into using the OM python models and creating a command, that way you can make it feel more native as it will be native :)

also if you write your own command you can write your own undo, which will save you a huge amount of head ache, as instead of trying to bash something to fake the native feel, you will have full control. Doing this will also help in your solution 2, as it is a bit dirty to use a redo as a selection. I would suggest using the selection comand if you are sticking with maya cmds, or if you try om2 then use om2.MGlobal.setActiveSelectionList()

Hope it all helps,

Cheers

Isai Calderon

unread,
Oct 30, 2017, 1:03:19 PM10/30/17
to Python Programming for Autodesk Maya
After finding a few scripts that have a magical way to do things faster (magical because I don't understand it yet), I'm going to dissect their methods. Fortunately the tools are available for free :)

The one that works great is aTools by Alan Camilo. His tool moves keyframes in the graph editor using an interactive slider, and it works beautifully with a lot of data.

I really like the idea of controlling my own Undo's, especially if it'll give me more control. That's where I don't know how to proceed, so it sounds like tutorial season is on.

I will be looking into OM through these tutorials:

Simon, do you know where else I should look to get into OM?

Thank you again!

Isai

Simon Anderson

unread,
Oct 31, 2017, 6:12:03 AM10/31/17
to Python Programming for Autodesk Maya
Hi Isai,

I would suggest looking at Om2

I find the best way is set up some small projects that test specific areas and then try and implement them.

Hope that helps

Isai Calderon

unread,
Nov 2, 2017, 5:46:05 PM11/2/17
to Python Programming for Autodesk Maya
Short-term Update:

OM1 and OM2 are dang tough cookies :) Gonna spend quite a bit of time doing this before I can use it actively at work.

I did discover the connectControl feature:
https://help.autodesk.com/cloudhelp/2016/ENU/Maya-Tech-Docs/CommandsPython/connectControl.html

It ALMOST does what I need. It drives any attribute(s) you connect to it, and it stays live until you disconnect it, all at full speed without infinitely calling functions. The only problem is that I cannot figure out how to connect it to a series of keyframes in the graph editor, driving each one individually at a specified multiplier.

One thought I had was to connect it to each attribute I want to change, and cycle the control through every keyframe. I think I could figure that out, but it might run a tad slower since it has to go through every selected key for every selected attribute for every selected object.

I can see the command above being incredibly helpful to connect pose libraries to UIs, so this trek has indeed helped me to discover more and more.

Thanks!

Isai
Reply all
Reply to author
Forward
0 new messages