Olarn's DevBlog
Extending generic vector type to support HLSL/GLSL like 'Swizzling' operations.
Sorry, failed to load main article image
Bantukul Olarn
    
Nov 26 2017 02:01
  

Implementing Swizzling operation to vectors.

Goals

Implement basic per-component swizzling style access and assignment to minimal vector, maya MVector and pymel's Vector (Extended MVector) type.

What's Swizzling?

Accessing/reordering vector component in arbitrary order by chaining attribute accessors.


Wikipedia

Swizzling in HLSL

Swizzling in GLSL


The following demonstration code should be self-explanatory, run it in maya to see it in action!

Demo code and working sample
# -*- coding: utf-8 -*-
from __future__ import print_function, absolute_import, division
import inspect
import abc

import pymel.core as pm
from maya.OpenMaya import MVector


class DemoVector(object):
    def __init__(self, initial_vec):
        self.x, self.y, self.z = initial_vec

    def __repr__(self):
        return "{}".format((id(self), self.x, self.y, self.z))


class SingleCharComponentSwizzlingMixin(object):
    # REGISTERED_COMPONENT_CHAR = []

    def _component_read(self, access_name):
        # return self.__dict__[access_name]
        return getattr(self, access_name)

    def _component_write(self, access_name, value):
        # self.__dict__[access_name] = value
        setattr(self, access_name, value)

    def __getattr__(self, access_name):

        if len(access_name) == 1:
            self._component_read(access_name)
        else:
            retval = tuple(
                self._component_read(char) for char in access_name
            )

            return retval

    def __setattr__(self, access_name, value):

        try:
            if len(access_name) == 1:
                self._component_write(access_name, value)
            else:
                if not len(value) == len(value):
                    raise AttributeError

                for i, char in enumerate(access_name):
                    self._component_write(char, value)
        except:
            super(SingleCharComponentSwizzlingMixin, self).__setattr__(access_name, value)


class MinimalSwizzlingVector(SingleCharComponentSwizzlingMixin, DemoVector):
    pass


class SwizzlingMVector(SingleCharComponentSwizzlingMixin, MVector):
    pass


PYMEL_VECTOR_ALLOWED_COMPONENTS = ['x', 'y', 'z']


class PMVUseDefaultOps(Exception):
    pass


class SwizzlingPmVector(pm.dt.Vector):
    def __getattribute__(self, access_name):

        try:
            # is iterable and all key allowed
            if all(len(access_name) > 1 and key in
                    PYMEL_VECTOR_ALLOWED_COMPONENTS for key in access_name
                   ):
                retval = tuple(
                    getattr(self, char) for char in access_name
                )
                return retval
            else:
                raise PMVUseDefaultOps

        except PMVUseDefaultOps, TypeError:
            return super(SwizzlingPmVector, self).__getattribute__(access_name)

    def __setattr__(self, access_name, value):

        try:
            if not hasattr(value, "__len__"):
                raise PMVUseDefaultOps
            if all(len(access_name) > 1 and value > 1 and len(access_name) == len(
                    value) and key in PYMEL_VECTOR_ALLOWED_COMPONENTS for key in access_name
                   ):
                for i, char in enumerate(access_name):
                    setattr(self, char, value[i])
            else:
                raise PMVUseDefaultOps
        except PMVUseDefaultOps, TypeError:
            super(SwizzlingPmVector, self).__setattr__(access_name, value)


class A(object):
    b = 1234

    def __getattr__(self, item):
        print("a get attr")
        modified_item = item[0]
        return getattr(type(self), modified_item)


class B(object):
    def __getattr__(self, item):
        print("b get attr")
        modified_item = item[0]
        return getattr(type(self), modified_item)


class C(B, A):
    pass


def demo_mixin():
    t = C()
    print(t.bbb)
    # 1234


def demo_minimal():
    mv_a = MinimalSwizzlingVector([1, 2, 3])
    mv_b = MinimalSwizzlingVector([0, 0, 0])

    print(mv_a)
    # (2507560141824L, 1, 2, 3)

    print(mv_b)
    # (2507560142608L, 0, 0, 0)

    # swizzling read
    print(mv_a.xxx, mv_a.yyy, mv_a.zzz, mv_a.zyx, mv_a.xyz)
    # (1, 1, 1) (2, 2, 2) (3, 3, 3) (3, 2, 1) (1, 2, 3)

    # swizzling read & assign
    mv_b.xyz = mv_a.zzz

    print(mv_b)
    # (2507560142608L, (3, 3, 3), (3, 3, 3), (3, 3, 3))


def demo_mvector():
    mayav_a = SwizzlingMVector(1, 2, 3)
    mayav_b = SwizzlingMVector(0, 0, 0)

    print(mayav_a)
    # <__main__.SwizzlingMVector; proxy of <Swig Object of type 'MVector *' at 0x00000247D63C7A80> >
    print(mayav_b)
    # <__main__.SwizzlingMVector; proxy of <Swig Object of type 'MVector *' at 0x00000247D63C79C0> >

    # swizzling reads

    print(mayav_a.zyx)
    # (3.0, 2.0, 1.0)
    print(mayav_a.yzx)
    # (2.0, 3.0, 1.0)
    print(mayav_a.xxx)
    # (1.0, 1.0, 1.0)
    print(mayav_a.xyzyyzyx)
    # (1.0, 2.0, 3.0, 2.0, 2.0, 3.0, 2.0, 1.0)

    # swizzling read & assign
    mayav_b.xyz = mayav_a.zyx

    print(mayav_b.xyz)
    # (3.0, 2.0, 1.0)


def demo_pymel_vector():
    pmv_a = SwizzlingPmVector([1, 2, 3])
    pmv_b = SwizzlingPmVector([0, 0, 0])

    print(pmv_a.x)
    # 1.0

    print(pmv_a)
    # [1.0, 2.0, 3.0]
    print(pmv_b)
    # [0.0, 0.0, 0.0]

    # swizzling read
    print(pmv_a.zyx)
    # (3.0, 2.0, 1.0)

    # swizzling read & assign
    pmv_b.xyz = pmv_a.zyx

    print(pmv_b)
    # [3.0, 2.0, 1.0]


def main():
    print("Mixin demo:")
    demo_mixin()
    print("Minimal:")
    demo_minimal()
    print("MVector:")
    demo_mvector()
    print("Pymel Vector:")
    demo_pymel_vector()

main()

Tags: