home processing download documents tutorial python tutorial gallery source about
 Python Tutorials (back to the list of tutorials)

Classes in Object-Oriented Programming

     Class Definition (requires iGeo version 7.3.1 or higher)

Object-oriented programming is one of paradigms of computer programming. An "object" is a structured collection of data and definition of its computational behavior. A template to define this structured collection of data and behavior is a "class". The code below is an example of code to define a class.

add_library('igeo')

class MyModule :
    def __init__(self) : 
        self.position = IVec(0,0,0)
        self.size = 1.0

This code above defines a class named "MyModule", containing several data. "class" is a keyword to define a class, followed by a name of the class. Usually name of a class starts with a capital letter.

In the following tutorial, some key topics of object-oriented programming are shown. For more comprehensive description about object-oriented programming, please see the Processing's tutorial and the Java tutorial.


     Class Instance

This code above is just defining the template and we need to write another code to create and use an object of the class.

add_library('igeo')

size(480, 360, IG.GL)

module = MyModule()

The defined class is used as a type in declaration of a variable like "MyModule module" and an instance of the class is created by "new MyModule()".

However, the code above alone doesn't work by itself. Those two codes above need to be combined. Before combining, there is one thing to prepare. Here we introduce new programming mode "continuous mode" in Processing, whereas what we were writing so far was called "basic mode". See this page at processing.org for more information about programming modes in Processing.

The code above is rewritten in "continuous mode" below.

add_library('igeo')

def setup() :
    size(480, 360, IG.GL)
    module = MyModule()

To write in "continuous mode", you need to put all the lines we were writing into "void setup(){" ... "}" except "import" statements.

Now we can combine the class definition with the main code and run the code in Processing without compile errors.

add_library('igeo')

def setup() :
    size(480, 360, IG.GL)
    module = MyModule()

class MyModule : 
    def __init__(self) :
        self.position = IVec(0,0,0)
        self.size = 1.0

When you run this code, it doesn't generate any geometry because we haven't define any behavior in the class yet.


     Instance Field

The class in the code above contains two variables in the class. Variables in a class is called fields. They are the members which the class consists of.

You can access to the fields to read a value or to assign a value to by writing "." and the name of the field after the instance. See the example below.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    
    module = MyModule()
    module.position = IVec(0,0,0)
    module.size = 20.0
    
    print("size of module is "+str(module.size))

class MyModule : 
    # instance fields
    def __init__(self) :
        self.position = IVec(0,0,0)
        self.size = 1.0


     Class Constructor

To assign values to instance fields, it is easier to define a constructor. A constructor is a method to instantiate a class. It is used when an instance of the class is created with a keyword "new" and you can put values to initialize instance fields in a constructor. The name of a constructor method is exactly same with the class name. See the example below.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    module = MyModule(IVec(0,0,0), 20)

class MyModule : 
  # instance methods
  def __init__(self, pos, sz) : 
      self.position = pos
      self.size = sz

You can also add multiple constructors with different input arguments. When you use one of constructors, you put a specific set of types of variables in the input arguments of the constructor.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    module1 = MyModule(IVec(0,0,0), 20)
    module2 = MyModule(IVec(50,0,0))

class MyModule : 
    # instance methods
    def __init__(self, pos, sz=1.0) :  # default size
        self.position = pos
        self.size = sz

     Instance Method

An instance method is a procedure containing a series of statements to define a behavior of a class. A method can take any number of input arguments and one or no return value. You can name a method with any name but usually it starts with a small letter. Inside an instance method, you have full access to instance fields. The following is an example of an instance method with no input argument and no return value.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    module1 = MyModule(IVec(0,0,0), 20)
    module1.createModule()
    
    module2 = MyModule(IVec(50,0,0))
    module2.createModule()

class MyModule : 
    # instance methods
    def __init__(self, pos, sz=10.0) :
        self.position = pos
        self.size = sz

    def createModule(self) :
        IBox(self.position, self.size)

In this example code, name of the method is "createModule", preceded by a keyword "void" which specifies that this method has no return value, and followed by "()" to specify that this method has no input argument. This method is actually executed inside "setup()" method at the line of "module1.createModule();" for the instance "module1" and "module2.createModule();" for another instance "module2".

The following code is an example of an instance method with input arguments.

add_library('igeo')

def setup() : 
    size(480, 360, IG.GL)
    module1 = MyModule(IVec(0,0,0), 20)
    module1.createModule()
    
    module2 = MyModule(IVec(50,0,0))
    module2.createModuleWithArm(5, 40)

class MyModule : 
    # instance methods
    def __init__(self,pos, sz=10.0) : 
        self.position = pos;
        self.size = sz;

    def createModule(self) : 
        IBox(self.position, self.size)
    
    def createModuleWithArm(self, armSize, armLen) : 
        IBox(self.position, self.size)
        
        armPos1 = self.position.dup().add(-armLen/2,0,0)
        armPos1.add(0,0,self.size)
        IBox(armPos1, armLen, armSize, armSize)
        
        armPos2 = armPos1.dup().add(armLen/2,-armLen/2,0)
        armPos2.add(0,0,armSize)
        IBox(armPos2, armSize, armLen, armSize)

To set up input arguments, you declare a variable inside the parenthesis "()" after the method name and the declared variables can be used inside the method as input values passed from outside of the method.


     Method with A Return Value

A method can have an output value in the form of a return value. The following code is an example to define return values of methods.

add_library('igeo')

def setup() : 
    
    size(480, 360, IG.GL)
    
    module1 = MyModule(IVec(0,0,0), 20)
    box = module1.createModule()
    box.clr(1.0,0,0)
    
    module2 = MyModule(IVec(50,0,0))
    boxes = module2.createModuleWithArm(5, 40)
    for bx in boxes : 
         bx.clr(0,0,1.0)


class MyModule : 

    # instance methods
    def __init__(self, pos, sz = 10) : 
        self.position = pos
        self.size = sz
    
    def createModule(self) : 
        return IBox(self.position, self.size)
    

    def createModuleWithArm(self, armSize, armLen) : 
        boxes = []
        boxes.append(IBox(self.position, self.size))
        
        armPos1 = self.position.dup().add(-armLen/2, 0, 0)
        armPos1.add(0,0,self.size)
        boxes.append(IBox(armPos1, armLen, armSize, armSize))
        
        armPos2 = armPos1.dup().add(armLen/2, -armLen/2, 0)
        armPos2.add(0,0,armSize)
        boxes.append(IBox(armPos2, armSize, armLen, armSize))
        return boxes

To define a return value of a method, you first put the type of return value before the method name, instead of "void". Then inside the body of the method, you add a return statement with the keyword "return" and the variable containing the data you want to output at the end of the body.


     Static Field

A static field is a type of field which is not inside each class instance but outside of it and is shared by all instances of the class. A static field has a keyword "static" in the declaration of a field.

add_library('igeo')

def setup() : 
    
    size(480, 360, IG.GL)
    
    module1 = MyModule(IVec(0,0,0), 20)
    box = module1.createModule()
    box.clr(1.0,0,0)
    
    module2 = MyModule(IVec(50,0,0))
    boxes = module2.createModuleWithArm(5, 40)
    for bx in boxes : 
        bx.clr(0,0,1.0)
    
    print("default size is " + str(MyModule.defaultSize))


class MyModule : 
    # static field (not with "self.")
    defaultSize = 10.0
    
    # instance methods
    def __init__(self, pos, sz = defaultSize) : 
        self.position = pos
        self.size = sz
        print(MyModule.defaultSize) 
    
    def createModule(self) : 
        return IBox(self.position, self.size)
    
    def createModuleWithArm(self, armSize, armLen) : 
        boxes = []
        boxes.append(IBox(self.position, self.size))
        
        armPos1 = self.position.dup().add(-armLen/2, 0, 0)
        armPos1.add(0,0,self.size)
        boxes.append(IBox(armPos1, armLen, armSize, armSize))
        
        armPos2 = armPos1.dup().add(armLen/2, -armLen/2, 0)
        armPos2.add(0,0,armSize)
        boxes.append(IBox(armPos2, armSize, armLen, armSize))
        return boxes

Inside the class you can access to static fields in the same way with instance fields but when you change the value, the change is reflected on all class instances which read the static field. Outside the class you can access to static fields by putting the class name and "." before the name of the field like "MyModule.defaultSize".

One detailed technical issue which beginners can ignore is the "static" keyword at the beginning of the class definition "class MyModule{". This issue happens when you write classes in a same file in Processing. In "continuous mode" in Processing, there is one thing hidden for convenience, which is the class definition of the sketch itself. Internally the whole sketch is inside a sketch class (this shows up in "Java mode") and classes written inside the same sketch are treated as inner classes inside the sketch class. To use static fields or static methods, an inner class needs to be a "static class". That's why there's the keyword "static" at the beginning of the class definition.

You can write classes outside of the sketch class if you create separate files with the file extension of ".java" in new tabs in Processing. For more information to write separate Java files in Processing, please see this page at processing.org.


     Static Method

A static method is a method which doesn't belong to each instance but is shared by all instances of the class. A static method also has a keyword "static" in the declaration of the method.

add_library('igeo')

def setup() : 
    
    size(480, 360, IG.GL)
    
    module1 = MyModule(IVec(0,0,0), 20)
    box = module1.createModule()
    box.clr(1.0,0,0)
    
    module2 = MyModule(IVec(50,0,0))
    boxes = module2.createModuleWithArm(5, 40)
    for bx in boxes : 
        bx.clr(0,0,1.0)


class MyModule : 
    # static fields
    defaultSize = 10.0
    
    # static methods
    @staticmethod
    def randomXShift(pos, min, max) : 
        shifted = pos.dup()
        shifted.add(IRand.get(min,max),0,0)
        return shifted
    
    @staticmethod
    def randomYShift(pos, min, max) : 
        shifted = pos.dup()
        shifted.add(0,IRand.get(min,max),0)
        return shifted
    
    # instance methods
    def __init__(self, pos, sz=defaultSize) : 
        self.position = pos
        self.size = sz

    def createModule(self) : 
        return IBox(self.position, self.size)
    
    def createModuleWithArm(self, armSize, armLen) : 
        boxes = []
        boxes.append(IBox(self.position, self.size))
        
        armPos1 = MyModule.randomXShift(self.position, -(armLen-self.size), 0)
        armPos1.add(0,0,self.size)
        boxes.append(IBox(armPos1, armLen, armSize, armSize))
        
        armPos2 = MyModule.randomXShift(armPos1, 0, armLen-self.size)
        armPos2 = MyModule.randomYShift(armPos2, -(armLen-self.size), 0)
        armPos2.add(0,0,armSize)
        boxes.append(IBox(armPos2, armSize, armLen, armSize))
        return boxes


     Use of Class in Other Algorithms

Here is an example to use a class in the panelization algorithms we've seen. The sample input file used in the code is this.

surface12.3dm

add_library('igeo')

def setup() : 
    
    size(480, 360, IG.GL)
    
    IG.open("surface12.3dm")
    surfaces = IG.surfaces()

    unum=40
    vnum=40
    uinc=1.0/unum
    vinc=1.0/vnum
    for surf in surfaces : 
        for i in range(unum) : 
            for j in range(vnum) : 
                pt = surf.pt(i*uinc, j*vinc)
                module = MyModule(pt, 1.5)
                
                if IRand.pct(50) : 
                    box = module.createModule()
                    box.clr(0.7,0,0)
                else : 
                    armLength = IRand.get(2,10)
                    boxes = module.createModuleWithArm(0.5,armLength)
                    for bx in boxes :
                        bx.clr(IRand.gray())
        surf.del()


class MyModule : 
    # static fields
    defaultSize = 10.0
    
    # static methods
    @staticmethod
    def randomXShift(pos, min, max) : 
        shifted = pos.dup()
        shifted.add(IRand.get(min,max),0,0)
        return shifted
    
    @staticmethod
    def randomYShift(pos, min, max) : 
        shifted = pos.dup()
        shifted.add(0,IRand.get(min,max),0)
        return shifted
    
    # instance methods
    def __init__(self, pos, sz=defaultSize) : 
        self.position = pos
        self.size = sz

    def createModule(self) : 
        return IBox(self.position, self.size)
    
    def createModuleWithArm(self, armSize, armLen) : 
        boxes = []
        boxes.append(IBox(self.position, self.size))
        
        armPos1 = MyModule.randomXShift(self.position, -(armLen-self.size), 0)
        armPos1.add(0,0,self.size)
        boxes.append(IBox(armPos1, armLen, armSize, armSize))
        
        armPos2 = MyModule.randomXShift(armPos1, 0, armLen-self.size)
        armPos2 = MyModule.randomYShift(armPos2, -(armLen-self.size), 0)
        armPos2.add(0,0,armSize)
        boxes.append(IBox(armPos2, armSize, armLen, armSize))
        return boxes


     Comparison of Code with/without Class

As an example to use a class, the codes below show comparison of a panelization algorithm written without and with a class. The panelization algorithm is based on the triangular panelization on this tutorial.

surface1.3dm

add_library('igeo')

size( 480, 360, IG.GL )

IG.open("surface1.3dm")  #input geometry from 3dm file

surfs = IG.surfaces()

for surf in surfs : 
    unum = 8
    vnum = 8
    uinc = 1.0/unum
    vinc = 1.0/vnum
    for i in range(unum) : 
        for j in range(vnum) : 
            pt11 = surf.pt( i*uinc, j*vinc )
            pt21 = surf.pt( (i+1)*uinc, j*vinc )
            pt12 = surf.pt( i*uinc, (j+1)*vinc )
            pt22 = surf.pt( (i+1)*uinc, (j+1)*vinc )
            ISurface(pt11,pt21,pt22).clr(i*uinc,0,j*vinc)
            ISurface(pt22,pt12,pt11).clr(1-i*uinc,1-j*vinc,1)
            
            depth = -2
            spt11 = surf.pt(i*uinc, j*vinc, depth)
            spt21 = surf.pt((i+1)*uinc, j*vinc, depth)
            spt12 = surf.pt(i*uinc, (j+1)*vinc, depth)
            spt22 = surf.pt((i+1)*uinc, (j+1)*vinc, depth)
            
            frameSize = 0.2
            IG.squarePipe([spt11, spt21, spt22, spt12], 1, True, frameSize)
            
            radius = 0.1
            ICylinder(pt11,spt11,radius)
            ICylinder(pt21,spt21,radius)
            ICylinder(pt12,spt12,radius)
            ICylinder(pt22,spt22,radius)
    surf.del()

The code above is re-written with a class "MyPanel" below.
Note that the code below creates frames overlapping with adjacent frames due to the simplicity of the tutorial code.

add_library('igeo')

def setup() : 
    
    size( 480, 360, IG.GL )
    IG.open("surface1.3dm")  #input geometry from 3dm file
    surfs = IG.surfaces()
    
    for surf in surfs : 
        unum = 8
        vnum = 8
        uinc = 1.0/unum
        vinc = 1.0/vnum
        for i in range(unum) : 
            for j in range(vnum) : 
                depth = -2
                
                panel = \
                MyPanel(surf.pt(i*uinc, j*vinc), \
                        surf.pt((i+1)*uinc, j*vinc), \
                        surf.pt(i*uinc, (j+1)*vinc), \
                        surf.pt((i+1)*uinc, (j+1)*vinc), \
                        surf.pt(i*uinc, j*vinc, depth), \
                        surf.pt((i+1)*uinc, j*vinc, depth), \
                        surf.pt(i*uinc, (j+1)*vinc, depth), \
                        surf.pt((i+1)*uinc,(j+1)*vinc,depth))
                
                panel.createPanel(i*uinc, 0, j*vinc, \
                                  1-i*uinc, 1-j*vinc, 1)
        surf.del()


class MyPanel : 
    # static field
    frameSize = 0.2
    connectionRadius = 0.1
    
    def __init__(self,p11,p21,p12,p22,s11,s21,s12,s22) : 
        self.pt11 = p11
        self.pt21 = p21
        self.pt12 = p12
        self.pt22 = p22
        self.spt11 = s11
        self.spt21 = s21
        self.spt12 = s12
        self.spt22 = s22

    def createPanel(self,red1,green1,blue1,red2,green2,blue2) : 

        ISurface(self.pt11,self.pt21,self.pt22).clr(red1,green1,blue1)
        ISurface(self.pt22,self.pt12,self.pt11).clr(red2,green2,blue2)
        
        IG.squarePipe([ self.spt11,self.spt21,self.spt22,self.spt12 ], 1, True, MyPanel.frameSize)
        
        ICylinder(self.pt11,self.spt11,MyPanel.connectionRadius)
        ICylinder(self.pt21,self.spt21,MyPanel.connectionRadius)
        ICylinder(self.pt12,self.spt12,MyPanel.connectionRadius)
        ICylinder(self.pt22,self.spt22,MyPanel.connectionRadius)

(back to the list of tutorials)

HOME
FOR PROCESSING
DOWNLOAD
DOCUMENTS
TUTORIALS (Java / Python)
GALLERY
SOURCE CODE(GitHub)
ABOUT