How to create new objects?

Extending DOSSS is rather easy. To create a new object, create a new object class derived from DOSSSObject and save it in a file with the exactly same name as the class object, i.e., MyFancyObject goes to MyFancyObject.py. In order for DOSSS to find your new object, place the file in the objects folder. The new object will then be automatically loaded and added to the library.

For your object to actually do something, you have to overwrite the following four interface functions:

  • GetDisplayPoints():

    Returns a list of points for drawing the object into the ClientDC.

  • Intersection():

    Returns some information about the intersection with a light ray: intersection point, distance of travel, emerging rays.

  • ShowPropertyDialog():

    Show the property dialog box for the object.

  • GetLight():

    Returns a list of light rays if the object is a light source.

In addition, you have to provide a unique name / identifier for the object in the name class attribute, which will then be used in the objects menu.

Example

In the following I will give a short example of how to extend DOSSS by creating a new object. The code is taken from DOSSS_GlassSlab and represents a rectangular piece of glass.

First, derive a new class from DOSSSObject:

# for the dialog box
import wx

# for the object base class
from core.opsim_objectbase import *

# when creating a light source, also import the following
from core.opsim_lightray import *

class DOSSS_GlassSlab(DOSSSObject):

    # make sure that all parameters have default values - this is important for the automatic import
    def __init__(self, xpos = 0, ypos = 0, width = 100, height = 20, ior = 1.5):

        # call the parent constructor
        DOSSSObject.__init__(self, xpos, ypos)

        # assign the object's parameters
        self.width = width
        self.height = height
        self.refractiveIndex = ior

        # give it a unique identifier
        self.name = "Glass Slab"

Now, you have to overwrite the interface functions. The first one is responsible for drawing the object to the screen. The object will be drawn as a poly line in the order the points are added to the list. The last point is automatically connected with the first one:

def GetDisplayPoints(self):
    p = []

    # the points' coordinates have to be given in the objects coordinate system
    p.append([-self.width / 2, -self.height / 2])
    p.append([+self.width / 2, -self.height / 2])
    p.append([+self.width / 2, +self.height / 2])
    p.append([-self.width / 2, +self.height / 2])

    return p

Next, overwrite the interface function for calculating the intersection with a light ray. This function is the actual work horse and does all the work. Make sure your calculations are correct, as this function completely determines the optical properties:

def Intersection(self, line):
    # first, transform the light ray into the object's coordinate system
    l = self.ProjectIntoObjectCosy(line)

    # second, test for intersection:
    # this is done by checking every segment of the object for intersection
    # if a point is found, it is stored together with the distance to the rays origin
    # and a list of emerging rays (transmitted or reflected)
    # at the end, the point with the shortest distance along the light ray is picked and returned

    # create a set of vectors corresponding to the four corners of the glass slab
    tl = DOSSSVector(-self.width/2, -self.height/2)
    tr = DOSSSVector(+self.width/2, -self.height/2)
    br = DOSSSVector(+self.width/2, +self.height/2)
    bl = DOSSSVector(-self.width/2, +self.height/2)

    # prepare return values
    p0 = None
    d0 = d = -1
    er = []

    # now test for intersection using the built in geometric functions..

    # get a line corresponding to the first (upper) side of the slab
    lb = getLineThroughPoints(tl, tr)

    # calculate the intersection using the bounded_intersection() method,
    # as the intersection should lie between the two corner points
    p = lb.bounded_intersection(l, tl, tr)

    # if there is an intersection, calculate the distance of this point along the light ray
    if(p != None):
        d = (l.a - p).length()
        # if it is smaller than the distance to any previously found intersection, keep this point
        if(d > 1e-7 and (d0 == -1 or d < d0)):
            # distance to intersection
            d0 = d

            # intersection point
            p0 = p

            # emitted rays as list
            # Snell() expects the surface normal to point outwards, i.e. from glass to vacuum
            er = [DOSSSLine(p0, Snell(l.u, DOSSSVector(0, -1), self.refractiveIndex))]

    # repeat for the other sides...
    # ...
    # ...

    # finally, the intersection and the emitted rays have to be transformed back into the laboratory frame
    if(p0 != None):
        # note the 1 in the projection function; this tells DOSSS to do the reverse transformation
        p0 = self.ProjectIntoObjectCosy(p0, 1)

        # do the same for all emerging beams
        for i in range(len(er)):
            er[i] = self.ProjectIntoObjectCosy(er[i], 1)

    # return values are: [intersection point, distance of travel, [emerging rays]]
    return [p0, d0, er]

And the last function to overwrite is the property dialog. This dialog is used to control the appearance and optical properties of the object:

def ShowPropertyDialog(self):

    # first, create the options list
    options = []

    # there are general options regarding the position
    options.append(["Position X", self.position[0]])
    options.append(["Position Y", self.position[1]])
    options.append(["Rotate", self.alpha])
    options.append(["FlipH", self.flip_h])
    options.append(["FlipV", self.flip_v])

    # and object specific properties, like width, height and refractive index
    options.append(["Width", self.width, 1])
    options.append(["Height", self.height, 1])
    options.append(["Refractive Index", self.refractiveIndex, 1])

    # second, open the standard dialog with the options list as parameter
    dlg = DOSSS_PropertyDialog(options)

    if (dlg.ShowModal() == wx.ID_OK):

        # third, read out the new values and store
        options = dlg.getOptions()

        self.position[0] = options[0]
        self.position[1] = options[1]
        self.alpha = options[2]
        self.flip_h = options[3]
        self.flip_v = options[4]
        self.width = options[5]
        self.height = options[6]
        self.refractiveIndex = options[7]

    # finally, destroy the dialog object
    dlg.Destroy()

If you are about to create a new light source, you also have to overwrite the following function (code example taken from DOSSS_ParallelLight):

# this function applies only for light sources
# returns a list of DOSSS_LightRay - objects
# for an object to be a light source, the 'lightsource' flag has to be set
def GetLight(self):
    rays = []

    if self.noRays > 1:
        a0 = -self.width / 2
        da = self.width / (float(self.noRays) - 1.0)
        print a0, da
    else:
        a0 = 0
        da = 0

    for i in range(int(self.noRays)):
        av = DOSSSVector(0, a0)
        uv = DOSSSVector(1, 0)
        a0 = -self.width / 2 + (i + 1) * da
        # project vectors in LabSpace
        uv = self.project(uv, 1, 1)
        av = self.project(av, 0, 1)

        # create a list of DOSSS_LightRays
        rays.append(DOSSS_LightRay(av.x(), av.y(), uv.x(), uv.y()))

    return rays