Object Oriented Programming - Class



NOTE: We will not cover all Object Oriented Programming or Class features.

So far, we have covered procedural programming where we use independent statements and functions to accomplish our scripting tasks. Procedural programming is sufficient for most of our needs. We can group a set of data and the function that act on these data together in a single script file then load that file as we need it. Also, we can copy that file and modify its content to match our new needs. This not efficient use of code and causes us to have multiple files having similar content. Yes, we could use include statement in Maxscript which is effectively import the included file content. Still we will have bloated and unmanageable code with variable conflict and scoping nightmare.

As you'll eventually realize, we need a system of defining a group of function and data under a single component. This component would contain all information and functionality for out specific type of stuff. It will allow us to create virtual instances of it and tailor each instance to match our current unique needs.

Object Oriented Programming (OOP) comes to the rescue. OOP allows us to merge our code and data into one inseparable item and hide (encapsulate) our data from the rest of the program. Our data is accessed by calling specially written functions (methods) with checks and safeguards. We can offer simple-to-use, standardized methods for performing particular operations on our data, while concealing how we do it. In turn, allowing for future data-structure and processes alterations without modifying the rest program thus increasing our code reusability.
OOP is built on the following four main concepts:

  1. Encapsulation: the inclusion of all the resources (methods and the data) needed for an object to function.
  2. Abstraction (generalization): the emphasis on what an object does rather than how it does it.
  3. Inheritance: a way to reuse code by creating a derived class that inherits attributes and behavior from existing classes (base classes).
  4. Polymorphism (many shapes): the ability to request that the same operations be performed on a wide range of different types. We can see Polymorphism in both overloading (define several methods with the same name acting on different inputs) and overriding (a subclass overriding a specific implementation of the base class method while having the same name and parameter list as the super-class's overridden method).


Don't worry about the above fancy words.
All you need to understand is the following:
We could package (encapsulate) our data and functions in a self-contained module exposing only the necessary functionality and attribute while hiding the other stuff. Our code creates a module that is general (abstract) enough to handle all similar situations with varying data. Other modules can build-on our code (inherit) and extend it by adding specialized function and data for their specific needs. The new module contains functions (polymorphism) with the same name and signature of our original functions yet they work differently to handle their own data-structure.

Class:


A well defined class have meaningful grouping of functions and data and easily support re-usability, expandability and maintainability. Robert C. Martin (http://en.wikipedia.org/wiki/Robert_Cecil_Martin) introduced the SOLID OOP design principles in early 2000. SOLID stands for the following principles:

According to some of these principles a class should have a single responsibility, its behavior can be extended without modifying the class and it can be substituted by its derived class.
Often, none programmers get confused between the term class and object. As an artist, think of a class as a clay mold while an object is the colored cast from that mold. Furthermore, we could say an artist is a class derived from the super-class human and you are an object or an instance of the artist class. So you are an artist and human. You have all the quality and functionality of human with additional qualities and functions of an artist which make you a special class of human.

Maya MEL:

MEL is not an object oriented language. It has no class syntax or implementation.

Maxscript

http://docs.autodesk.com/3DSMAX/15/ENU/MAXScript-Help/index.html?url=files/GUID-46ECBAD7-35E9-43BD-8A8C-849772924805.htm,topicNumber=d30e120110
Maxscript provide a mechanism for creating a subset of a class called struct. Maxscript struct groups properties and functions together and instantiate objects similar to class functionality in OOP. Although Maxscript struct is derived from Maxscript super-class Value, it does not support Inheritance or Polymorphism.
Inheritance is the ability of an object class to inherit the operations and properties of its parent class. In Maxscript the classOf() method is defined for class Value, and struct inherits this method. Thus you can specify a struct object as a parameter to the classOf() method.
Polymorphism is the ability of a single-named operation to work on different values of different object classes. In Maxscript, all math operators ('+', '-', '*', '/' ) and methods are shared among many types (integers, real numbers, complex, vectors, matrices, and so on) and perform type specific actions. For example the random() function takes two arguments (int, float, box corner points or quaternion) and generates an appropriate random value between them that matches the incoming arguments.

Struct Definition Syntax:

You can create your own class in Maxscript using struct keyword. Conventions dictate that the first letter in a class name must be capitalized and camel-case the rest of the name. Astruct definition actually creates a value representing its definition and stores it in a variable with the struct name. So you can store the struct definitions in other compound objects or pass them as function arguments. The struct value constructors take positional argument as well as keyword to initialize its properties. Members are public by default accessible by outside code. As of 3ds Max 2010 you can use the qualifiers public and private to control the member's visibility to users and the Debugger.

Maxscript:

struct MyPolygon(--class definition
    public – no comma after qualifiers
    xPos, yPos, zPos, pName, --notice the comma after each property and function
    fn getName = return this.pName,
    fn setPos x y z =
    (
         xPos = x
         yPos = y
         zPos = z
    ), --end setPos function "Adding comments at each closing parenthesis reduces confusion
    fn printList args = --instead of method overloading iterate through args
    (
        try --to show try except block
        (
            list = filterString args " "
            for item in list do print item
        ) – end try
        catch
        (
            for item in args do print item
        ) – end catch
    ), – end printList function
    fn get_mOrder= format "mOrder= %" this.mOrder,
    private – no comma after qualifiers
    mOrder = 0 – private property accessible only from within this struct, no comma after the last element
)--end struct


--class instantiation
newPoly = MyPolygon 10 12 8 "bestPoly"
newPoly.getName()
newPoly.setPos 15 -5 3
format "xPos= %, yPos= %, xPos= %\n" newPoly.xPos newPoly.yPos newPoly.zPos
newPoly.printList "my list is string"
newPoly.printList #("my", "list", "is", "string") – Passing an array of strings
newPoly.get_mOrder



There are several methods associated with struct values. Given a struct object variable myStruct that has a property named myStructPropOne the definition and syntax of struct built-in methods are as follow:






Struct Inherited Methods:

All structs are derived from the Value class. Therefore struct instances implement the Value calss methods except for the copy method which is implemented to return a shallow copy. Given a struct object variable myStruct instantiated from bestStruct then:


Structure Creation and Cloning Event Handlers

In 3ds Max 2010 and higher, two private event handlers (creation and cloning) can be implemented in struct. These event handlers can be used to perform additional initialization tasks.



Cloning a struct through the copy function and trying to access a private member of the copy will cause a runtime error.

Maxscript:

struct employee
(
    public
    name , age, gender, tmpSS,

    fn equals other =return classOf other == employee and name == other.name and \
        age == other.age and gender == other. gender,

    fn getSS = return this.socialSecurity as string, --accessing private member

    private
    socialSecurity,

    on create do
    (
        this.socialSecurity = tmpSS
        tmpSS = ""
    ),

    on clone do format "in clone: %\n" this
)--end struct


jim = employee "james" "29" "male" "555-55-55"


ss = jim.getSS()


print jim.tmpSS      --""


jimy = copy jim --in clone: (employee name:"james" age:"29" gender:"male" tmpSS:"")

jimy.getSS() --Runtime error: Attempting to access private member



Python:

In Python we could create classes with a minimum syntax. Python classes provide all the standard features of Object Oriented Programming. Inheritance in Python allows multiple base classes, a derived class can override any methods of its base class or classes, and a method can call the method of a base class with the same name. In Python classes are created at runtime, and can be modified further after creation.

Python Scopes and Namespaces:

Class definitions play some neat tricks with namespaces, so we need to know how scopes and namespaces work to fully understand what's going on.
Namespace is a mapping from names to objects. It creates compartments where similar function and attribute names can coexist without confusion. The important thing to know about namespaces is that there is absolutely no relation between names in different namespaces. Users of the modules must prefix it with the module name. Namespaces are created at different moments and have different lifetimes. The namespace containing the built-in names is created when the Python interpreter starts up, and is never deleted. The global namespace for a module is created when the module definition is read in. Normally, module namespaces last until the interpreter quits. The local namespace for a function is created when the function is called, and deleted when the function returns or raises an unhandled exception. If no global statement is in effect – assignments to names always go into the innermost scope. Assignments do not copy data — they just bind names to objects. In fact, all operations that introduce new names use the local scope. In particular, import statements and function definitions bind the module or function name in the local scope.

Python Class Definition Syntax:

Use the keyword class to start a class definition followed by the name of the class. Conventions dictate that the first letter in a class name must be capitalized and camel-case the rest of the name. The class name is followed by colon ":" followed by the indented class body which includes all the class attributes (properties and methods). Often, the first argument of a method is called self. This is nothing more than a convention. The name self has absolutely no special meaning to Python.

Python:

#class definition
class MyPolygon:
    """Class Definition Sample""" #class documentation string
    def _init_(self, x=0, y=0,z=0, name="mPoly"):
        self.xPos = x
        self.yPos = y
        self.zPos = z
        self.__pName = name #private attribute


    def getName(self):
        return self.name


    def setPos(self, x=0,y=0,z=0):
        self.xPos = x
        self.yPos = y
        self.zPos = z

    def printList(self,*args): #instead of method overloading iterate through args
        try: #to show try except block
            for a in args.split():
                print a
        except AttributeError:
            for a in args:
                print a

#class instantiation
newPoly = MyPolygon(10,12,8,'bestPoly')
newPoly.setPos(15,-5,3)
print newPoly.xPos, newPoly.yPos, newPoly.zPos
#default values are set
newPoly.setPos()
print newPoly.xPos, newPoly.yPos, newPoly.zPos
#different data types
newPoly.printList ("k",12)
#built-in attributes
print newPoly._doc_



It is not necessary for a function definition to be enclosed in the class definition. You could assign a function to a local variable in the class as follow:

Python:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)
class C:
    f = f1
    def g(self):
        return 'hello world'
    h = g

Now f, g and h are all attributes of class C that refer to function objects, and they are all methods of instances of C — h being exactly equivalent to g. Although this practice is ok in Python, it only serves to confuse the reader of a program.

Inheritance:

Let say we have created a class to handle a generic polygon and we need to create another class to handle a round polygon. We could copy the body of polygon class into the body of round polygon and add to it specialized function to deal with round polygon. Inheritance provides a better way for us to reuse code, build-on it and extend it by adding specialized function and data. The base class is the original class and the derived class is the new class who did the inheritance.
To inherit from another class follow the same class definition syntax except add the name of the base class in parenthesis just after derived class name and before the colon ":".

Python:

class MyColor:
    def _init_(self, r=0, g=0, b=0):
        self.red = self.getCorrectColorValue(r)
        self.green = self.getCorrectColorValue(g)
        self.blue = self.getCorrectColorValue(b)


class RoundPoly(MyPolygon, MyColor): #MyPolygon & MyColor are the base class
    def _init_(self, x=0, y=0,z=0, name="mPoly", r=0, g=0, b=0):
        MyPolygon._init_(self,x,y,z,name)
        MyColor._init_(self,r,g,b)


    def getShape:
        return "Round"


    def setPos(self, x=0,y=0,z=0): #overriding the base class setPos method
        MyPolygon(self,x+2,y-2,z*2)



mRoundPoly = RoundPoly(-3,16,8, "rPoly", 10, 40, 250)
mRoundPoly.setPos(0,0,0)
mRoundPoly.printColor()
print mRoundPoly.xPos, mRoundPoly.yPos, mRoundPoly.zPos



The base class must be defined in a scope containing the derived class definition. When the derived class object is constructed, the base class is remembered. This is used for resolving attribute references. If a requested attribute is not found in the class, the search proceeds to look in the base class. This rule is applied recursively if the base class itself is derived from some other class. Derived classes may override methods of their base classes. An overriding method in a derived class may in fact want to extend rather than simply replace the base class by calling the base class method directly like BaseClass.method (self, arguments). http://docs.python.org/tutorial/classes.html


Overloading Methods:

Python does not have method overloading. Since Python is dynamically typed language there is no need to have different functions for different types. In practice, Python programmers use the "args" and "*kwargs" (Variable-length) arguments and iterate on them within the class method (see above).
However, there is an API to allow overloading using special decoration. http://www.python.org/dev/peps/pep-3124/#overloading-inside-classes

Overloading Operators:

Suppose you've created a Vector class to represent two-dimensional vectors. What happens when you use the plus operator to add them? Most likely Python will yell at you. You could, however, define the _add_ method in your class to perform vector addition, and then the plus operator would behave as per expectation: http://www.tutorialspoint.com/python/python_classes_objects.htm

Python:

#overloading operators
class Vector:
    def _init_(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c


    def _str_(self):
        return 'Vector (%d, %d, %d)' % (self.a, self.b, self.c)


    #overloading addition
    def _add_(self,other):
        return Vector(self.a + other.a, self.b + other.b, self.c + other.c)


    #overloading subtraction
    def _sub_(self,other):
        return Vector(self.a - other.a, self.b - other.b, self.c - other.c)


    #overloading multiplication
    def _mul_(self,other):
        return Vector((self.b * other.c) - (self.c * other.b),
            (self.c * other.a) - (self.a * other.c),
            (self.a * other.b) - (self.b * other.a))


    #overloading division
    def _div_(self,other):
        if type(other) == int and other != 0:
            return Vector(self.a / other, self.b / other, self.c / other )
        else:
            print "This oprations is not defined"
            return None


v1 = Vector(2,10, 2)
v2 = Vector(5,-2, 1)
print v1 #overloading to string operator
print v1 + v2 #overloading add operator
print v1 - v2 #overloading subtract operator
print v1 * v2 #overloading add operator
print v1 / 2 #overloading scalar division operator
print v1 / v2 #overloading vector division operator



Built-In Class Attributes:
Every Python class keeps following built-in attributes and they can be accessed using dot operator like any other attribute:


One useful Python built-in function is dir(). The dir() function without arguments, return the list of names in the current local scope. The dir(objectName) function with an argument, attempt to return a list of valid attributes for that object. The default dir() mechanism behaves differently with different types of objects, as it attempts to produce the most relevant, rather than complete information. http://docs.python.org/library/functions.html#dir



Lua:


Although Lua does not have specific object oriented programming (OOP) syntax and was not built with OOP in mind, OOP in Lua is possible and can be implemented using tables and metatables. http://www.troubleshooters.com/codecorn/lua/luaoop.htm

Mimicking Class Definition Using Tables:

(http://www.lua.org/pil/16.html)
While Lua does not have the concept of class, it is not difficult to emulate classes in it. A table in Lua has a state, has an identity independent of its value and can have its own functions. These table features makes it ideal for mimicking class and object behaviors.

Lua:

MyPolygon = {} --A table that will contain the class and its functions
MyPolygon.new = function(x,y,z,name)
    local self = {} – return object
    x = x or 0 – x default value
    y = y or 0 – y default value
    z = z or 0 – z default value
    pName = name or "mPoly" – default name


    – setting functions
    self.setXPos = function (xVal) x = xVal end – assigning a function to the table field
    self.setYPos = function (yVal) y = yVal end
    self.setZPos = function (zVal) z = zVal end
    self.setName = function (nameStr) name = nameStr end


– accessor functions
    self.getXPos = function () return x end
    self.getYPos = function () return y end
    self.getZPos = function () return z end
    self.getName = function() return pName end


    self.display = function()
        print (string.format("This object\n\tname:%s\n\tx = %d\n\ty = %d\n\tz = %d"), pName, x, y, z)
        end


    return self

end
– usage
newPoly = MyPolygon.new(10,12,8,'bestPoly')
newPoly.display()


Further implementation of OOP in Lua could be found at http://www.lua.org/pil/16.1.html through http://www.lua.org/pil/16.5.html and http://www.troubleshooters.com/codecorn/lua/luaoop.htm