I mentioned before that I have been slowly working on an extension to MotionBuilder’s python classes that make things easier and add more functionality. I been playing with different ways to implement these features and I think I found the best way that makes it very easy to attach without reinventing the whole pyfbsdk module. Thanks to a python guru at work that gave me the idea, I can now attach my own classes to MotionBuilder’s native ones so your code does not have to change, but can immediately begin to take advantage of the new functionality.

Below is a sample of the direction I am taking. I am still putting together all the pieces and finishing up a few other areas before I release my prototype code. I am dubbing the name PyMobu as the project name. So that will be the name until I can think of something better.

One of my bigger complaints in MotionBuilder python is the complete lack of vector and matrix mathematics that are essential in 3D programming. So my sample is to enhance the FBVector3d class to add in all useful operations with 3 dimensional vectors:

(note: I omitted some code to keep it short and not take up the whole page, so some functions are missing)

import math
import operator
from pyfbsdk import *

class PMVector3d(object):
    '''Base class for FBVector3d. Using euclid functions'''
    def __copy__(self):
        return self.__class__(self.x, self.y, self.z)

    copy = __copy__

    def __eq__(self, other):
        if isinstance(other, FBVector3d):
            return self.x == other.x and \
                   self.y == other.y and \
                   self.z == other.z
        elif hasattr(other, '__len__') and len(other) == 3:
            return self.x == other[0] and \
                   self.y == other[1] and \
                   self.z == other[2]
        else:
            raise Exception("Comparison value must have at least 3 items")

    def __neq__(self, other):
        return not self.__eq__(other)

    def __nonzero__(self):
        return self.x != 0 or self.y != 0 or self.z != 0

    def __mul__(self, other):
        if isinstance(other, FBVector3d):
            return self.__class__(self.x * other.x,
                          self.y * other.y,
                          self.z * other.z)
        elif type(other) in (int, long, float):
            return self.__class__(self.x * other,
                           self.y * other,
                           self.z * other)
        else:
            raise TypeError("Invalid type given")

    def __neg__(self):
        return self.__class__(-self.x,
                        -self.y,
                        -self.z)

    magnitude = __abs__

    def magnitude_squared(self):
        return self.x ** 2 + \
               self.y ** 2 + \
               self.z ** 2

    def normalize(self):
        d = self.magnitude()
        if d:
            self.x /= d
            self.y /= d
            self.z /= d
        return self

    def normalized(self):
        d = self.magnitude()
        if d:
            return self.__class__(self.x / d,
                           self.y / d,
                           self.z / d)
        return self.copy()

    def dot(self, other):
        if isinstance(other, FBVector3d):
            return self.x * other.x + \
               self.y * other.y + \
               self.z * other.z
        else:
            raise TypeError("Invalid type given")

    def cross(self, other):
        if isinstance(other, FBVector3d):
            return self.__class__(self.y * other.z - self.z * other.y,
                       -self.x * other.z + self.z * other.x,
                       self.x * other.y - self.y * other.x)
        else:
            raise TypeError("Invalid type given")

    def reflect(self, normal):
        # assume normal is normalized
        if isinstance(normal, FBVector3d):
            d = 2 * (self.x * normal.x + self.y * normal.y + self.z * normal.z)
            return self.__class__(self.x - d * normal.x,
                       self.y - d * normal.y,
                       self.z - d * normal.z)
        else:
            raise TypeError("Invalid type given")

    @property
    def x(self):
        return self[0]
    @ property
    def y(self):
        return self[1]
    @property
    def z(self):
        return self[2]

_baseClasses = list(FBVector3d.__bases__)
_baseClasses.insert(0, PMVector3d)
FBVector3d.__bases__ = tuple(_baseClasses)

The last 3 lines above is what actually adds my custom class as a base class to the FBVector3d class so you still just work with FBVector3d objects but now have the benefit of additional methods. The code was taken and adjusted from the python euclid module.

This is the idea around most of my code. This makes it a lot easier to work with inside MotionBuilder without actually having to create new instances of my classes.

More updates and news on the way. Feel free to contact me with questions or comments on the direction I am taking this.

Comments are closed.