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:
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.
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.
MEL is not an object oriented language. It has no class syntax or implementation.
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.
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.
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:
All structs are derived from the Value class. Therefore struct instances implement the Value calss methods . Given a struct object variable myStruct instantiated from bestStruct then:
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.
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.
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.
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.
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:
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.
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 ":".
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
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
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
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
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
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.
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