Is this maya/python idea sensible or too unconventional?

Locking the transform attributes of a node can get tedious since there is no pattern to it. In the past I’ve tried having a function with lots of flags but that gets so verbose and gross. I realized recently I can make a fancy object to chain attributes to lock them, ex:

lock.t.r.s( obj1 )
lock.tx.r( obj2)
lock.ry.rx.s( obj3 )

I think this is nicely compact and pretty clear what is happening despite being unconventional (at least in my own python experience). But maybe I’m getting too fancy for my own good? I’m curious what other people think.

I like it, my only comment would be to make it so you can only modify attributes of a like type, i.e. translate, rotate, and scale being separate. Or, at least make it a “convention” in your real code.

Do you mind sharing the code and how you did it?

I initially thought of it as a feature that it could lock anything but you’re right, restricting it is better to prevent a typo that locks an actual attr but I’m not sure if you were thinking to restrict it to just the first transform, like translate for example. At least for me, I want to take care of all transform locking. Additionally, calling it directly locks all transforms.

class _Lock(object):
   ''' Streamlined way of locking attrs.  Ex, `lock(obj)` locks all, `lock.tx(obj)` or `lock.ty.r.s(obj)`
   '''

   __name__ = 'lock'

   def __getattr__(self, attr):
       return _locker(attr)

   def __call__(self, obj):
       self.t.r.s(obj)


class _locker(object):

   validAttrs = ['t', 'r', 's'] + [t + a for t in 'trs' for a in 'xyz']

   def __getattr__(self, attr):
       return _locker(attr, self)

   def __init__(self, attr, other=None):
       
       assert attr in self.validAttrs, 'Only transforms can be locked, not "%s"' % attr
       
       self.attr = attr
       self.other = other

   def __call__(self, obj):
       if self.attr in 'trs': # setKeyable doesn't work on compound attr
           for axis in 'xyz':
               obj.attr(self.attr + axis).lock()
               obj.attr(self.attr + axis).setKeyable(False)
               obj.attr(self.attr + axis).showInChannelBox(False)
       else:
           obj.attr(self.attr).lock()
           obj.attr(self.attr).setKeyable(False)
           obj.attr(self.attr).showInChannelBox(False)

       if self.other:
           self.other(obj)

lock = _Lock() # A wrapper function would allow a nicer doc string.
1 Like

Snazzy, but it is a bit too weird for my liking - it’s basically an inverted function call, disguised as a chain of object attributes. If someone relatively new to python were to come along and read this, they would have many questions.
As a compromise I would just have a lock() function thus:

def lock(obj, *attrs):
   for attr in attrs:
       if not obj.hasattr(attr):
           continue
       obj.getattr(attr).lock()

lock( myObj, "rs", "tx", "s" )

More typing to add the quotes, but you still start with the main verb of “lock”.
Just my opinion

I appreciate the perspective. While also unconventional, what do you think of flipping it?

lock( objA ).tx.s
lock( objB ).t.r.s
lock( objC ).t.s

I’ve done *args in the past but hate passing in all those strings. It didn’t occur to me till now that I could do it like a named tuple.

lock( objA, 'tx s' )
lock( objB, 't r s' )
lock( objC, 't s' )

Are either of those more appealing? I agree that the original would bring up a lot of questions but, and I might be wrong, I think it’s still clear as to what’s happening. But it does, at least initially, add friction by being mysterious.

I think packing the attributes at the end still looks nice (and has a much nicer implementation) but in writing this, the namedtuple inspired version seems the most reasonable of the three; the intent is clear and I don’t think it implies clever tricks like the others do.

The implementations for the curious:


class lock(object):
    ''' Streamlined way of locking attrs.  Ex, `lock(obj)` locks all, `lock(obj).tx` or `lock(obj).ty.r.s`
    '''

    validAttrs = ['t', 'r', 's'] + [t + a for t in 'trs' for a in 'xyz']

    def __getattr__(self, attr):
        assert attr in self.validAttrs, 'Only transforms can be locked, not "%s"' % attr

        if attr in 'trs': # setKeyable doesn't work on compound attr
            for axis in 'xyz':
                self.obj.attr(attr + axis).lock()
                self.obj.attr(attr + axis).setKeyable(False)
                self.obj.attr(attr + axis).showInChannelBox(False)
        else:
            self.obj.attr(attr).lock()
            self.obj.attr(attr).setKeyable(False)
            self.obj.attr(attr).showInChannelBox(False)
        
        return self

    def __init__(self, obj):
        self.obj = obj


def lock(obj, attrs='t r s'):
    ''' Streamlined way of locking attrs.  Ex, `lock(obj)` locks all, `lock(obj, 'tx')` or `lock(obj, 'ty r s')`
    '''

    validAttrs = ['t', 'r', 's'] + [t + a for t in 'trs' for a in 'xyz']
    
    for attr in attrs.split():
        assert attr in validAttrs, 'Only transforms can be locked, not "%s"' % attr
        if attr in 'trs': # setKeyable doesn't work on compound attr
            for axis in 'xyz':
                obj.attr(attr + axis).lock()
                obj.attr(attr + axis).setKeyable(False)
                obj.attr(attr + axis).showInChannelBox(False)
        else:
            obj.attr(attr).lock()
            obj.attr(attr).setKeyable(False)
            obj.attr(attr).showInChannelBox(False)

My initial reaction is that separating arguments is not usually what the dot operator is for. It’s unclear to someone who doesn’t know what you are up to. If the intent is to ever share your code with a team, this makes it suboptimal. Well written code explains itself, within reason.

If you are the only person to see it, it and it works, then there is no issue.

I can sympathise with the death by a thousand strings (war flashbacks to connectAttr) but I’d still say the object attribute form costs more time to explain in the long run than it saves. I’m assuming you’re aiming to share this with at least one other person (otherwise the considerations here are surely immaterial :slight_smile: ).
Passing in a string separated by spaces is interesting, I’d never thought of that. Within the context of maya I think it’s quite cool, since no other kind of call will ever use the same format.

Then there is also the question of locking attributes known only as variables - neither form is optimal for that, but the object attribute route is much more not optimal.

I’d say write a lock() function accepting *args, then write a decorator to split a space-separated string into its components. That way you can reuse the same string format in other attribute functions, and you always have the option of passing in separate args anyway.

I think it complicate things quite a lot. Most maya functions in query mode will return lists of attributes (listAttr for instance), so it’d be way faster and easier to just use a list of attributes rather than prepare the parameter string everytime you want to use the function.
The less you need to convert your variables, the better.

A big style question here is who the real user is. If it’s something you expect to be typed into the listener, there’s some value in minimizing punctuation. If it’s code that will be consumed by other code, maybe default args makes more sense?

 def lock(*obj, all=True, t = False, r = False, s = False, tx=False, ty=False, tz=False, rx=False, ry=False, rz=False, sx=False, sy=False, sz=False):
      ....

would allow everything from lock ("obj", all=True) to lock ("a", "b", "c", rx=True)

I’d resist the urge to make it a class, I think – my natural use case doesn’t feel like one where I’m hanging on to an object for long. This is different from a case where you’re implementing an object interface where you hang on to the objects and might want to lock and unlock them at different times; there an idiom like

  myobject.tx.lock()

or

 myobject.ry.locked = True

makes sense

(There’s an implementation of the second of those two in nodule)

1 Like