LumaPictures / pymel

Python in Maya Done Right
Other
490 stars 131 forks source link

Allow optionvars to save long types #405

Closed assumptionsoup closed 6 years ago

assumptionsoup commented 6 years ago

OptionVar lists are a little wonky right now.

>>> import pymel.core as pm
>>> pm.env.optionVars['int_test'] = [1, 2, 3]
>>> pm.env.optionVars['int_test']
(1L, 2L, 3L)

>>> pm.env.optionVars['int_test'].append(2)
(1L, 2L, 3L, 2L)

Everything is working fine so far, but...

>>> pm.env.optionVars['int_test'] = pm.env.optionVars['int_test']
Traceback (most recent call last):
  File "<sublime_code>", line 2, in <module>
  File "/usr/autodesk/maya2018/lib/python2.7/site-packages/pymel/core/language.py", line 574, in __setitem__
    raise TypeError, ('%r is unsupported; Only strings, ints, float, lists, and their subclasses are supported' % listType)
TypeError: <type 'long'> is unsupported; Only strings, ints, float, lists, and their subclasses are supported

This is obviously not ideal when you try to modify existing optionvar lists.

By the way, is there any reason why OptionVarList is a tuple (other than terrible performance when doing anything aside from a clear/append)? I just cobbled together a mutable version for myself, and I'd be happy to integrate it once I've written some tests.

pmolodo commented 6 years ago

These changes made sense, thanks for the PR. Odd that maya would turn everything into longs, though... Can they really represent arbitrary length ints? We may have to add some sort of check where, if it is a long, it does a max bounds check. No idea about the tuple thing offhand. Maybe just didn't want to have to deal with mutuality, and maintaining the live link to the optionvar? Or was afraid people would store the result in their own data structures, and not know / forget that the result was maintaining a live link to the optionVar? Will need to take a closer look.

assumptionsoup commented 6 years ago

Thanks for merging this Paul. I should have mentioned that I was using Maya 2018. I guess this is new behavior. I'm seeing a lot of queries return longs now. As for your question about arbitrary integer lengths, the python docs have this to say:

Plain integers (also just called integers) are implemented using long in C, which gives them at least 32 bits of precision (sys.maxint is always set to the maximum plain integer value for the current platform, the minimum value is -sys.maxint - 1). Long integers have unlimited precision.

This is the opposite of what I had assumed, so that was really interesting to learn.

I'm guessing mutability wasn't maintained because optionvar lists don't allow set item operations. Here was my quick'n dirty implementation. It might help explain things. Note, that it doesn't maintain the link on get - something I would probably change if this were heading pymel's way:

class MutableOptionVarList(collections.MutableSequence):
    def __init__(self, value, key):
        if value:
            self._items = list(value)
        else:
            self._items = []
        self.key = key

    def __getitem__(self, index):
        return self._items[index]

    def __setitem__(self, index, value):
        self._items[index] = value
        self._saveOptionVar()

    def __delitem__(self, index):
        self._items.pop(index)
        self._saveOptionVar()

    def __len__(self):
        return len(self._items)

    def insert(self, index, value):
        if index == len(self._items):
            self._items.append(value)
            if isinstance(value, basestring):
                return cmd.optionVar(stringValueAppend=[self.key, value])
            if isinstance(value, (int, long)):
                return cmd.optionVar(intValueAppend=[self.key, value])
            if isinstance(value, float):
                return cmd.optionVar(floatValueAppend=[self.key, value])
            else:
                raise TypeError('unsupported datatype: strings, ints, floats and their subclasses are supported')
        else:
            self._items.insert(index, value)
            self._saveOptionVar()

    def _saveOptionVar(self):
        if len(self._items) == 0:
            return cmd.optionVar(clearArray=self.key)
        listType = type(self._items[0])
        if issubclass(listType, basestring):
            flag = 'stringValue'
        elif issubclass(listType, (int, long)):
            flag = 'intValue'
        elif issubclass(listType, float):
            flag = 'floatValue'
        else:
            raise TypeError('%r is unsupported; Only strings, ints, float, lists, and their subclasses are supported' % listType)

        cmd.optionVar(**{flag: [self.key, self._items[0]]})  # force to this datatype
        flag += "Append"
        for elem in self._items[1:]:
            if not isinstance(elem, listType):
                raise TypeError('all elements in list must be of the same datatype')
            cmd.optionVar(**{flag: [self.key, elem]})

    def __repr__(self):
        return '%s(%r, %r)' % (type(self).__name__, self._items, self.key)