Blog of


APyTA_17 : OBJECT Part III = Static tactics

APyTA_17 : OBJECT Part III = Static tactics


Illustation of Lesson APYTA#17:

Well, this snippet is just a more elaborate version of the latest post, about creating an object-oriented skeleton:
Because my credo here is to illustrate how objects is all about meaning, giving sense (and some reality) to our lines of code, I propose here to add some arguments to give some consistence to our skeleton, a little how we create a character in role playing game^^. So, this is my prototype of my Avatar creation:

def __init__(self, _Name="DefaultName", _Age=30, _Size=1.70, _Sex=eSexe.kFemale, _NbArm=2, _NbLeg=2, _hasTail=False):

See how we are dealing with real stuff, and also giving more flexibility than previous snippet.

By the way, you have now to discover another concept, static members and methods.
I talked previously how a class is instantiated into a variable: myInstance = myClass().
The instance will benefits to all members and methods (functions) bound to the self keyword in class definition.

Well sometimes you don't want to bind members to instances. You want a variable to be linked to the scope of the class itself !
These are called static members and static methods, and examples are explained bellow.
Again, rather than cutting the long snippet, I placed my comments through the lines inside the code itself:

import math
import maya.OpenMaya
import pymel.core
import maya.cmds

maya.cmds.currentUnit(linear='m')  # Unless you're a gridsnap-lover, I prefer working with meter units that make more sense for me.

class eSexe():
    """ An enumeration of all sex status """
    (kMale, kFemale) = ("Male",  "Female")  # This snippet is useful when you want to manage an very simple-object with several enumeration (as struct in c)

class Avatar(object):  # Also when desribing your mother-of-all class, always inherit from the python class-object (you will benefits of some built-ins)
    """ This object is a rigged-skeleton representation of a character """
    NbSpine = 8  # Arbitrary, we decide that all spinal chain will have 8 joints. So it is in a static Member of Avatar class.
    HipsRatio = 0.4  # Percentage of hips height. Arbitrary also !

    def __init__(self, _Name="DefaultName", _Age=30, _Size=1.70, _Sex=eSexe.kFemale, _NbArm=2, _NbLeg=2, _hasTail=False):
        self.Age = _Age  # With age,  bones are stacked and incurved,  int ,  in years
        self.Size = _Size  # How tall it is. Float,  in meters
        self.Sex = _Sex  # Sexe influence Hips Width and Shoulder width

        self.LimbList = []   # Limb Lists
        self.ArticulationList = []   # All Joints Lists
        self.Hips = None  # Quick Access shortcut
        self.NeckBase = None  # Quick Access shortcut

        self._createTrunk()  # We can call an method into the inialisation process of our Avatar instance.

        ArmLength = self.NeckBase.getTranslation(space='world').y - self.Hips.getTranslation(space='world').y
        for i in range(_NbArm):
            angle = (float(i) / float(_NbArm)) * 2 * math.pi
            RelativePosition = maya.OpenMaya.MVector(math.cos(angle), 0, math.sin(angle))
            newArm = Arm(chr(65 + i), self.NeckBase, RelativePosition * Limb.getGenderOffset(Arm, self.Sex), ArmLength)  # Here we create a new instance of class Arm, and Arm-Object.
            newArm.createIK()  # Notice here we call the method from the newly created instance. For Leg it is done differently.

        for i in range(_NbLeg):
            angle = (float(i) / float(_NbLeg)) * 2 * math.pi
            RelativePosition = maya.OpenMaya.MVector(math.cos(angle),  0,  math.sin(angle))
            newLeg = Leg(chr(65 + i), self.Hips,  RelativePosition * Limb.getGenderOffset(Leg, self.Sex))  # Notice also the call of a static-method of class Limb.

        if _hasTail:
            self.LimbList.append(Tail(self.Hips, 10, 2))

        RootLocator =, 1, 0), center=(0, 0, 0), radius=0.5)[0]
        pymel.core.annotate(RootLocator, text=str(_Name), point=(0, 0, 0.5))
        pymel.core.parent(self.Hips, RootLocator)

    def _createTrunk(self):
        """ Will create the trunk of the body """
        HipsPos = maya.OpenMaya.MVector(0, self.Size * Avatar.HipsRatio, 0)  # Notice here the use of static member of Avatar, treat that like a constant value.
        SpineOffset = (self.Size - HipsPos.y) / float(Avatar.NbSpine)
        SenescenceOffset = float(self.Age) / 100.0  # Factor of spine curve due to age (Cheating because Height is conserved)
        SenescenceOffset *= 0.01

        for increment in range(Avatar.NbSpine):
     # We do not want Maya to create fuzzy stuff with active selection. So before each creation we clear the selection.
            currrentArticulation = pymel.core.nodetypes.Joint(name="Spine_" + str(increment + 1).zfill(2))
            currrentArticulation.setTranslation(maya.OpenMaya.MVector(0,  HipsPos.y + increment * SpineOffset, (increment ** 2) * SenescenceOffset))

            if self.ArticulationList:  # Not the first joint, we have to connect it to the previous one...
                pymel.core.connectJoint(currrentArticulation,  self.ArticulationList[-1],  parentMode=True)  # Remember,  -1 is the index of the last member of a list.
                self.Hips = currrentArticulation

            if increment == Avatar.NbSpine - 2:  # The last two bones are Neck and Head, so Arms takes their root from bellow.
                self.NeckBase = currrentArticulation


class Limb(object):
    def __init__(self, _Name):
        self.ArticulationList = []  # Same name as in Avatar member, but 'self.' indicates the ownership of this class instance. We're deadling with two very different variables so.
        self.Name = _Name
        self.Effector = None  # Every Limb MUST have an effector. But right now it is purely virtual. The member will be set when Ik will be constructed.

    def getGenderOffset(_Limb, _Sex):  # Notive there is no 'self' first argument.
        """ Static Method that return offset of Base position for given Limb """
        if _Limb is Arm:  # Notice also the first expected argument here is the class itself, not one of their instances: That's where is operator fit better than ==
            if _Sex == eSexe.kFemale:
                return 0.25
            elif _Sex == eSexe.kMale:  # Male have larger shoulder...
                return 0.3
        elif _Limb is Leg:
            if _Sex == eSexe.kFemale:  # Male have larger hips...
                return 0.3
            elif _Sex == eSexe.kMale:
                return 0.25
            raise  # Unknown Limb ! (Not the purpose here but why not raise a custom Exception... as a new class of course)

class Arm(Limb):
    NbArticulation = 3  # We iterate through a max joint define in the class member, not the instance member. It is call a static member. Useful for storing scoped-constants and convert factor.

    def __init__(self, _Name, _NeckBase,  _Offset, _Length):
        super(Arm, self).__init__("Arm_" + _Name)  # Inheritance in python is not automatic. Super (Mother) class of Arm is Limb, and its methods are callable form Arm instance, but it needs the keywords super to do that.
        LastPosition = _NeckBase.getTranslation(space='world')

        for i in range(Arm.NbArticulation):
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))
            NewPosition = LastPosition + _Offset * (_Length / float(Arm.NbArticulation))
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation,  _NeckBase,  parentMode=True)
                pymel.core.connectJoint(currentArticulation,  self.ArticulationList[-1],  parentMode=True)
            LastPosition = NewPosition

    def createIK(self):
        result = pymel.core.ikHandle(name=self.Name + "_IKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSCsolver)
        self.Effector = result[0]  # Filling Limb inherited member with the newly created effector.

class Leg(Limb):
    NbArticulation = 3

    def __init__(self, _Name, _HipsBase,  _Offset):
        super(Leg, self).__init__("Leg_" + _Name)
        BasePosition = _HipsBase.getTranslation(space='world')
        previouslyCreated = _HipsBase
        for i in range(0, Leg.NbArticulation):
            currentArticulation = pymel.core.nodetypes.Joint(name=self.Name + "_" + str(i + 1).zfill(2))

            if(previouslyCreated == _HipsBase):  # First joint
                currentArticulation.setTranslation(BasePosition + _Offset, space='world')
                InverseLerp = (1 - (i / float(Arm.NbArticulation - 1)))   # Lerp is the transpose of a range FROM [min = >max] TO [0 = >1]   # Arm.NbArticulation-1 because we do not care about the first Articulation
                currentArticulation.setTranslation(maya.OpenMaya.MVector(previouslyCreated.getTranslation(space='world').x, previouslyCreated.getTranslation(space='world').y * InverseLerp, previouslyCreated.getTranslation(space='world').z), space='world')

            pymel.core.connectJoint(currentArticulation,  previouslyCreated,  parentMode=True)
            previouslyCreated = currentArticulation
        self.createIK()  # Instead of Arm, here we decided that it is into the __init__ process to create IK, so we call the methods from here.

    def createIK(self):
        result = pymel.core.ikHandle(name=self.Name + "_IKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSCsolver)  # IkRPsolver TOASK
        self.Effector = result[0]
        return result[0]

class Tail(Limb):
    def __init__(self,  _HipsBase,  _NbSubdiv, _Size=1):
        super(Tail, self).__init__("Tail")
        BasePosition = _HipsBase.getTranslation(space='world')
        for i in range(1, _NbSubdiv):
            currentArticulation = pymel.core.nodetypes.Joint(name="Tail_" + str(i + 1).zfill(2))
            offSet = i / float(_NbSubdiv)
            currentArticulation.setTranslation(maya.OpenMaya.MVector(BasePosition.x, BasePosition.y, BasePosition.z - (offSet * _Size)),  space='world')
            if not self.ArticulationList:  # The first arm joint must be connected to the Base.
                pymel.core.connectJoint(currentArticulation, _HipsBase,  parentMode=True)
                pymel.core.connectJoint(currentArticulation, self.ArticulationList[-1],  parentMode=True)

    def createSplineIK(self):
        pymel.core.ikHandle(name="TailIKEffector", startJoint=self.ArticulationList[0], endEffector=self.ArticulationList[-1],  solver=pymel.core.nodetypes.IkSplineSolver)

JohnDoe = Avatar("John Doe", _Sex=eSexe.kMale)
TentacleMonster = Avatar("Weirdo", _Size=2.5, _NbArm=5, _NbLeg=3)
GrandMaMouse = Avatar("Grand'Ma Mouse", _Size=1.2, _Age=86, _hasTail=True)

Comments (0) Trackbacks (0)

No comments yet.

Leave a comment

No trackbacks yet.