###############################################################################
#
# File: NimbusCloudCreator.py
#
# Copyright (c) 2006 Edmond Ho.
#
###############################################################################

import Blender
from Blender import *
from Blender.Mathutils import *

import math
from math import *

import random
from random import *

import os.path


###############################################################################
# Class       : ColorLevel
#             :
# Description : This class defines a cloud color level. Color levels are used
#             : to define the color of a cloud at different heights.
#             :
###############################################################################
class ColorLevel:
    id = 0
    r = 0.5
    g = 0.5
    b = 0.5
    a = 0.5
    height = 0

    def __init__(self, id, r = 0.5, g = 0.5, b = 0.5, a = 0.5, height = 0):
        self.id = id
        self.r = r
        self.g = g
        self.b = b
        self.a = a
        self.height = height

    def getMenuString(self):
        return "|Level " + str(self.id) +  " %x" + str(self.id)



###############################################################################
# Class       : ColorLevelSet
#             :
# Description : This class defines a cloud color level set. Color level sets
#             : define the color for an entire cloud for a point in time.
#             :
###############################################################################
class ColorLevelSet:
    id = 0
    time = 0
    lLevels = []

    def __init__(self, id, time = 0, lLevels = []):
        self.id =id 
        self.time = time
        self.lLevels = lLevels

    def getMenuString(self):
        return "|Set " + str(self.id) + " %x" + str(self.id)



###############################################################################
# Globals
###############################################################################
#
# This dictionary stores the buttons used in this script. 
#
buttons = dict()
buttons["numberParticles"] = Draw.Create(25)
buttons["numberMinSize"] = Draw.Create(0.5)
buttons["numberMaxSize"] = Draw.Create(1.0)
buttons["numberMinRotation"] = Draw.Create(-10.0)
buttons["numberMaxRotation"] = Draw.Create(10.0)
buttons["buttonCreateCloud"] = Draw.Create(0)

buttons["buttonNewColorLevel"] = Draw.Create(0)
buttons["menuColorLevels"] = Draw.Create(0)
buttons["buttonDeleteColorLevel"] = Draw.Create(0)
buttons["sliderColorLevelR"] = Draw.Create(0.5)
buttons["sliderColorLevelG"] = Draw.Create(0.5)
buttons["sliderColorLevelB"] = Draw.Create(0.5)
buttons["sliderColorLevelA"] = Draw.Create(0.5)
buttons["numberHeight"] = Draw.Create(0)
buttons["buttonClearColorLevelData"] = Draw.Create(0)
buttons["buttonSaveColorLevelData"] = Draw.Create(0)

buttons["buttonNewColorLevelSet"] = Draw.Create(0)
buttons["menuColorLevelSets"] = Draw.Create(0)
buttons["buttonDeleteColorLevelSet"] = Draw.Create(0)
buttons["buttonAddColorLevelToSet"] = Draw.Create(0)
buttons["menuColorLevelIndex"] = Draw.Create(0)
buttons["menuColorLevelName"] = Draw.Create(0)
buttons["buttonRemoveColorLevelFromSet"] = Draw.Create(0)
buttons["numberTimeIndex"] = Draw.Create(0)
buttons["buttonClearColorLevelSetData"] = Draw.Create(0)
buttons["buttonSaveColorLevelSetData"] = Draw.Create(0)

buttons["buttonExportCloud"] = Draw.Create(0)
buttons["buttonImportCloud"] = Draw.Create(0)
buttons["buttonExit"] = Draw.Create(0)


#
# This dictionary stores the event types used in this script.
#
events = dict()
events["none"] = 0
events["exit"] = 1
events["createCloud"] = 2
events["importCloud"] = 3
events["exportCloud"] = 4

events["addColorLevel"] = 5
events["deleteColorLevel"] = 6
events["saveColorLevel"] = 7
events["clearColorLevel"] = 8
events["showColorLevel"] = 9

events["addColorLevelSet"] = 10
events["deleteColorLevelSet"] = 11
events["saveColorLevelSet"] = 12
events["clearColorLevelSet"] = 13
events["showColorLevelSet"] = 14
events["addColorLevelIndex"] = 15
events["deleteColorLevelIndex"] = 16
events["showColorLevelNames"] = 17
events["showColorLevelIndices"] = 18


#
# This list stores color levels created by the user.
#
lColorLevels = []
numColorLevels = 0


#
# This list stores color level sets created by the user.
#
lColorLevelSets = []
numColorLevelSets = 0

#
# This list stores color level indices for the currently selected color
# level set.
#
lColorLevelIndices = []



###############################################################################
# Function    : DrawGUI
#             :
# Arguments   : none
#             :
# Returns     : none
#             :
# Description : This callback method draws the the interface for this script.
#             :
###############################################################################
def DrawGUI():
    global buttons, events, lColorLevels, lColorLevelSets

    intWindowHeight = Window.GetAreaSize()[1]

    BGL.glClear(BGL.GL_COLOR_BUFFER_BIT)

    BGL.glRasterPos2d(8, intWindowHeight - 20)
    buttons["textTitle"] = Draw.Text("Nimbus Cloud Generator")
    
    #
    # Particle properties.
    #
    buttons["numberParticles"] = Draw.Number("Particles:", events["none"], 10, intWindowHeight - 50, 150, 18, buttons["numberParticles"].val, 1, 500, "Number of cloud particles")
    buttons["numberMinSize"] = Draw.Number("Min size:", events["none"], 10, intWindowHeight - 70, 150, 18, buttons["numberMinSize"].val, 0.0, 10.0, "Minimum particle size")
    buttons["numberMaxSize"] = Draw.Number("Max size:", events["none"], 10, intWindowHeight - 90, 150, 18, buttons["numberMaxSize"].val, 0.0, 10.0, "Maximum particle size")
    buttons["numberMinRotation"] = Draw.Number("Min rotation:", events["none"], 10, intWindowHeight - 110, 150, 18, buttons["numberMinRotation"].val, -180.0, 180.0, "Minimum particle rotation")
    buttons["numberMaxRotation"] = Draw.Number("Max rotation:", events["none"], 10, intWindowHeight - 130, 150, 18, buttons["numberMaxRotation"].val, -180.0, 180.0, "Maximum particle rotation")


    #
    # Color level properties.
    #
    buttons["buttonNewColorLevel"] = Draw.Button("New", events["addColorLevel"], 10, intWindowHeight - 170, 34, 18, "New color level")
    buttons["menuColorLevels"] = Draw.Menu("Color levels %t" + ColorLevelMenuString(), events["showColorLevel"], 44, intWindowHeight - 170, 98, 18, buttons["menuColorLevels"].val, "Cloud color levels")
    buttons["buttonDeleteColorLevel"] = Draw.Button("X", events["deleteColorLevel"], 142, intWindowHeight - 170, 18, 18, "Delete selected color level")
    buttons["sliderColorLevelR"] = Draw.Slider("R: ", events["none"], 10, intWindowHeight - 190, 150, 18, buttons["sliderColorLevelR"].val, 0.0, 1.0, 0, "Color level red value")
    buttons["sliderColorLevelG"] = Draw.Slider("G: ", events["none"], 10, intWindowHeight - 210, 150, 18, buttons["sliderColorLevelG"].val, 0.0, 1.0, 0, "Color level green value")
    buttons["sliderColorLevelB"] = Draw.Slider("B: ", events["none"], 10, intWindowHeight - 230, 150, 18, buttons["sliderColorLevelB"].val, 0.0, 1.0, 0, "Color level blue value")
    buttons["sliderColorLevelA"] = Draw.Slider("A: ", events["none"], 10, intWindowHeight - 250, 150, 18, buttons["sliderColorLevelA"].val, 0.0, 1.0, 0, "Color level alpha value")
    buttons["numberHeight"] = Draw.Number("Height:", events["none"], 10, intWindowHeight - 270, 150, 18, buttons["numberHeight"].val, -1000, 1000, "Color level height")
    buttons["buttonClearColorLevelData"] = Draw.Button("Clear", events["clearColorLevel"], 10, intWindowHeight - 290, 75, 18, "Clear selected color level data")
    buttons["buttonSaveColorLevelData"] = Draw.Button("Save", events["saveColorLevel"], 85, intWindowHeight - 290, 75, 18, "Save selected color level data")


    #
    # Color level properties.
    #
    buttons["buttonNewColorLevelSet"] = Draw.Button("New", events["addColorLevelSet"], 10, intWindowHeight - 330, 34, 18, "New color level set")
    buttons["menuColorLevelSets"] = Draw.Menu("Color level sets %t" + ColorLevelSetMenuString(), events["showColorLevelSet"], 44, intWindowHeight - 330, 98, 18, buttons["menuColorLevelSets"].val, "Color level sets")
    buttons["buttonDeleteColorLevelSet"] = Draw.Button("X", events["deleteColorLevelSet"], 142, intWindowHeight - 330, 18, 18, "Delete selected color level set")
    buttons["buttonAddColorLevelToSet"] = Draw.Button("+", events["addColorLevelIndex"], 10, intWindowHeight - 350, 17, 18, "Add a color level to set")
    buttons["buttonRemoveColorLevelFromSet"] = Draw.Button("-", events["deleteColorLevelIndex"], 27, intWindowHeight - 350, 17, 18, "Delete selected color level")
    buttons["menuColorLevelIndex"] = Draw.Menu("Color level index %t" + ColorLevelIndexMenuString(), events["showColorLevelIndices"], 44, intWindowHeight - 350, 36, 18, buttons["menuColorLevelIndex"].val, "Color level index")
    buttons["menuColorLevelName"] = Draw.Menu("Color levels %t" + ColorLevelMenuString(), events["showColorLevelNames"], 80, intWindowHeight - 350, 80, 18, buttons["menuColorLevelName"].val, "Color levels name")
    buttons["numberTimeIndex"] = Draw.Number("Time Index:", events["none"], 10, intWindowHeight - 370, 150, 18, buttons["numberTimeIndex"].val, 0, 59999, "Color level set time index")
    buttons["buttonClearColorLevelSetData"] = Draw.Button("Clear", events["clearColorLevelSet"], 10, intWindowHeight - 390, 75, 18, "Clear selected color level set data")
    buttons["buttonSaveColorLevelSetData"] = Draw.Button("Save", events["saveColorLevelSet"], 85, intWindowHeight - 390, 75, 18, "Save selected color level set data")

    #
    # Action buttons.
    #
    buttons["buttonCreateCloud"] = Draw.Button("Create Cloud", events["createCloud"], 10, intWindowHeight - 450, 150, 25, "Create cloud")
    buttons["buttonImportCloud"] = Draw.Button("Import", events["importCloud"], 10, intWindowHeight - 470, 75, 18, "Import cloud from file")
    buttons["buttonExportCloud"] = Draw.Button("Export", events["exportCloud"], 85, intWindowHeight - 470, 75, 18, "Export cloud to file")
    buttons["buttonExit"] = Draw.Button("Exit", events["exit"], 10, intWindowHeight - 490, 150, 18, "Exit cloud creator")



###############################################################################
# Function    : HandleInputEvents
#             :
# Arguments   : event - Input event.
#             : value - Input value.
#             :
# Returns     : none
#             :
# Description : This callback method handles events generated by keyboard and
#             : mouse events.
#             :
###############################################################################
def HandleInputEvents(event, value):
    if(event == Draw.QKEY and not value):
        Draw.Exit()



###############################################################################
# Function    : HandleButtonEvents
#             :
# Arguments   : event - Input event.
#             :
# Returns     : none
#             :
# Description : This callback method handles events generated by Blender Draw
#             : buttons.
#             :
###############################################################################
def HandleButtonEvents(event):
    global buttons, events, lColorLevels, numColorLevels, lColorLevelSets, numColorLevelSets, lColorLevelIndices

    if(event == events["exit"]):
        Draw.Exit()

    elif(event == events["createCloud"]):
        CreateCloud()

    elif(event == events["importCloud"]):
        Window.FileSelector(ImportCloud, "Import")

    elif(event == events["exportCloud"]):
        Window.FileSelector(ExportCloud, "Export", NewFilename("cloud"))

    elif(event == events["addColorLevel"]):
        lColorLevels.append(ColorLevel(numColorLevels))
        numColorLevels += 1
        Draw.Redraw(1)

    elif(event == events["deleteColorLevel"]):
        for i in range(0, len(lColorLevels)):
            if(lColorLevels[i].id == buttons["menuColorLevels"].val):
                del lColorLevels[i]
                break

        buttons["sliderColorLevelR"].val = 0.5
        buttons["sliderColorLevelG"].val = 0.5
        buttons["sliderColorLevelB"].val = 0.5
        buttons["sliderColorLevelA"].val = 0.5
        buttons["numberHeight"].val = 0
        Draw.Redraw(1)

    elif(event == events["saveColorLevel"]):
        for i in lColorLevels:
            if(i.id == buttons["menuColorLevels"].val):
                i.r = buttons["sliderColorLevelR"].val
                i.g = buttons["sliderColorLevelG"].val
                i.b = buttons["sliderColorLevelB"].val
                i.a = buttons["sliderColorLevelA"].val
                i.height = buttons["numberHeight"].val
                break

    elif(event == events["clearColorLevel"]):
        buttons["sliderColorLevelR"].val = 0.5
        buttons["sliderColorLevelG"].val = 0.5
        buttons["sliderColorLevelB"].val = 0.5
        buttons["sliderColorLevelA"].val = 0.5
        buttons["numberHeight"].val = 0
        Draw.Redraw(1)

    elif(event == events["showColorLevel"]):
        for i in lColorLevels:
            if(i.id == buttons["menuColorLevels"].val):
                buttons["sliderColorLevelR"].val = i.r
                buttons["sliderColorLevelG"].val = i.g
                buttons["sliderColorLevelB"].val = i.b
                buttons["sliderColorLevelA"].val = i.a
                buttons["numberHeight"].val = i.height
                break
        Draw.Redraw(1)

    elif(event == events["addColorLevelSet"]):
        lColorLevelSets.append(ColorLevelSet(numColorLevelSets))
        numColorLevelSets += 1
        Draw.Redraw(1)

    elif(event == events["deleteColorLevelSet"]):
        for i in range(0, len(lColorLevelSets)):
            if(lColorLevelSets[i].id == buttons["menuColorLevelSets"].val):
                del lColorLevelSets[i]
                break

        Draw.Redraw(1)

    # FIXME Color level indices not displaying correctly.
    elif(event == events["showColorLevelSet"]):
        for i in lColorLevelSets:
            if(i.id == buttons["menuColorLevelSets"].val):
                lColorLevelIndices = i.lLevels
                if(len(lColorLevelIndices) > 0):
                    buttons["menuColorLevelIndex"].val = 0
                    buttons["menuColorLevelName"].val = lColorLevelIndices[0]
                buttons["numberTimeIndex"].val = i.time
                break
        Draw.Redraw(1)

    elif(event == events["addColorLevelIndex"]):
        lColorLevelIndices.append(0)
        Draw.Redraw(1)

    elif(event == events["deleteColorLevelIndex"]):
        if(len(lColorLevelIndices) > 0):
            lColorLevelIndices.pop()
            Draw.Redraw(1)

    elif(event == events["showColorLevelIndices"]):
        if(numColorLevels > 0):
            buttons["menuColorLevelName"].val = lColorLevelIndices[buttons["menuColorLevelIndex"].val]
            Draw.Redraw(1)

    elif(event == events["showColorLevelNames"]):
        if(len(lColorLevelIndices) > 0):
            lColorLevelIndices[buttons["menuColorLevelIndex"].val] = buttons["menuColorLevelName"].val
            Draw.Redraw(1)

    elif(event == events["saveColorLevelSet"]):
        for i in lColorLevelSets:
            if(i.id == buttons["menuColorLevelSets"].val):
                i.lLevels = lColorLevelIndices
                i.time = buttons["numberTimeIndex"].val
                break

    elif(event == events["clearColorLevelSet"]):
        lColorLevelIndices = []
        buttons["numberTimeIndex"].val = 0
        Draw.Redraw(1)



###############################################################################
# Function    : ColorLevelMenuString
#             :
# Arguments   : none
#             :
# Returns     : Blender menu string.
#             :
# Description : Returns a string listing the color levels created by the user,
#             : formatted for Blender menus.
#             :
###############################################################################
def ColorLevelMenuString():
    global lColorLevels

    s = ""

    for i in lColorLevels:
        s += i.getMenuString()

    return s



###############################################################################
# Function    : ColorLevelSetMenuString
#             :
# Arguments   : none
#             :
# Returns     : Blender menu string.
#             :
# Description : Returns a string listing the color level sets created by the
#             : user, formatted for Blender menus.
#             :
###############################################################################
def ColorLevelSetMenuString():
    global lColorLevelSets

    s = ""

    for i in lColorLevelSets:
        s += i.getMenuString()

    return s



###############################################################################
# Function    : ColorLevelIndexMenuString
#             :
# Arguments   : none
#             :
# Returns     : Blender menu string.
#             :
# Description : Returns a string listing the color level indices for the
#             : current color level set, formatted for Blender menus.
#             :
###############################################################################
def ColorLevelIndexMenuString():
    global lColorLevelIndices

    s = ""

    for i in range(0, len(lColorLevelIndices)):
        s += "|" + str(i) + " %x" + str(i)

    return s



###############################################################################
# Function    : CreateCloud
#             :
# Arguments   : none
#             :
# Returns     : none
#             :
# Description : Creates a cloud mesh randomly within the user-defined constraints.
#             :
###############################################################################
def CreateCloud():

    #
    # Get a list of all the selected objects.
    #
    lSelectedObjects = Blender.Object.GetSelected()

    #
    # Create the number of particles given by the user.
    #
    for i in range(0, buttons["numberParticles"].val):
        randObject = lSelectedObjects[randint(0, len(lSelectedObjects) - 1)]
        randRotation = RotationMatrix(uniform(buttons["numberMinRotation"].val, buttons["numberMaxRotation"].val), 3, 'z')
        randRadius = uniform(buttons["numberMinSize"].val, buttons["numberMaxSize"].val)
        DrawParticle(RandomCoordinates(randObject), randRotation, randRadius)



###############################################################################
# Function    : RandomCoordinates
#             :
# Arguments   : object - Blender object. Should be a rectangular prism.
#             :
# Returns     : Coordinates list.
#             :
# Description : Returns a random coordinate that is within the given object.
#             :
###############################################################################
def RandomCoordinates(object):
    lCoords = []

    #
    # Start with the coordinates of the center of the object.
    #
    tLocation = object.getLocation()
    tSizes = object.getSize()

    #
    # Move the coordinates by a random scaling of the 
    #
    for i in range(0, 3):
        lCoords.append(tLocation[i] + (tSizes[i] * gauss(0.0, 0.5)))

    return lCoords



###############################################################################
# Function    : DrawParticle
#             :
# Arguments   : tCoords - Coordinates tuple.
#             : matRotation - Rotation matrix.
#             : radius - Radius of the particle.
#             :
# Returns     : none
#             :
# Description : Draws a rectangle using the given values, which represents a
#             : particle in a cloud particle system.
#             :
###############################################################################
def DrawParticle(tCoords, matRotation, radius):

    #
    # Create a mesh for the particle.
    #
    meshParticle = NMesh.GetRaw()


    ###########################################################################
    # Add vertices to the particle mesh.
    ###########################################################################
    #
    # Add vertex 0 to the particle mesh.
    #
    vertex0 = NMesh.Vert(-radius, -radius, 0)
    meshParticle.verts.append(vertex0)

    #
    # Add vertex 1 to the particle mesh.
    #
    vertex1 = NMesh.Vert(-radius, radius, 0)
    meshParticle.verts.append(vertex1)

    #
    # Add vertex 2 to the particle mesh.
    #
    vertex2 = NMesh.Vert(radius, radius, 0)
    meshParticle.verts.append(vertex2)

    #
    # Add vertex 3 to the particle mesh.
    #
    vertex3 = NMesh.Vert(radius, -radius, 0)
    meshParticle.verts.append(vertex3)


    ###########################################################################
    # Add faces to the particle mesh.
    ###########################################################################
    #
    # Add face 0 to the particle mesh.
    #
    face0 = NMesh.Face()
    face0.v.append(meshParticle.verts[0])
    face0.v.append(meshParticle.verts[1])
    face0.v.append(meshParticle.verts[2])
    meshParticle.faces.append(face0)
    
    #
    # Add face 1 to the particle mesh.
    #
    face1 = NMesh.Face()
    face1.v.append(meshParticle.verts[2])
    face1.v.append(meshParticle.verts[3])
    face1.v.append(meshParticle.verts[0])
    meshParticle.faces.append(face1)

    
    #
    # Create a Blender object from the particle mesh.
    #
    objParticle = NMesh.PutRaw(meshParticle)
    objParticle.setMatrix(matRotation)
    objParticle.setLocation(tCoords[0], tCoords[1], tCoords[2])



###############################################################################
# Function    : NewFilename
#             :
# Arguments   : extension - File extension string.
#             :
# Returns     : A filename string.
#             :
# Description : Returns a filename with the given file extension.
#             :
###############################################################################
def NewFilename(extension):
    return Get('filename')[: -len(Get('filename').split('.', -1)[-1])] + extension



###############################################################################
# Function    : ImportCloud
#             :
# Arguments   : filename - Import file.
#             :
# Returns     : none
#             :
# Description : Import cloud data (particle, color level, color level set
#             : definitions) from the given file, in a Nimbus readable format.
#             :
###############################################################################
def ImportCloud(filename):
    global lColorLevels, numColorLevels, lColorLevelSets, numColorLevelSets

    #
    # Initialize script data structures.
    #
    lColorLevels = []
    numColorLevels = 0

    lColorLevelSets = []
    numColorLevelSets = 0


    #
    # Open the .cloud file.
    #
    cloudFile = open(filename, "r")


    #
    # Read and process each line in the file.
    #
    for line in cloudFile:

        ll = line.split()

        if(len(ll) == 0):
            continue

        sFirst = ll[0]

        #
        # If the line is a particle definition...
        #
        if(sFirst == "p"):
            DrawParticle((float(ll[1]), float(ll[2]), float(ll[3])), RotationMatrix(float(ll[4]), 3, 'z'), float(ll[5]) / 2.0)

        #
        # Else if the line is a color level definition...
        #
        elif(sFirst == "cl"):
            lColorLevels.append(ColorLevel(numColorLevels, float(ll[1]), float(ll[2]), float(ll[3]), float(ll[4]), int(ll[5])))
            numColorLevels += 1

        #
        # Else if the line is a color level set definition...
        #
        elif(sFirst == "cls"):
            lLevels = []

            for i in range(2, len(ll)):
                lLevels.append(int(ll[i]))

            lColorLevelSets.append(ColorLevelSet(numColorLevelSets, int(ll[1]), lLevels))
            numColorLevelSets += 1



###############################################################################
# Function    : ExportCloud
#             :
# Arguments   : filename - Export filename.
#             :
# Returns     : none
#             :
# Description : Export cloud data (particle, color level, color level set
#             : definitions) to the given file, in a Nimbus readable format.
#             :
###############################################################################
def ExportCloud(filename):
    global lColorLevels, lColorLevelSets

    #
    # Create the .cloud file.
    #
    cloudFile = open(filename, "w")

    cloudFile.write("###############################################################################\n")
    cloudFile.write("#\n")
    cloudFile.write("# File: " + os.path.basename(filename) + "\n")
    cloudFile.write("#\n")
    cloudFile.write("###############################################################################\n")


    #
    # Get a list of all the selected objects.
    #
    lSelectedObjects = Blender.Object.GetSelected()

    cloudFile.write("\n#\n# Cloud particle definitions.\n#\n")

    #
    # For each selected object...
    #
    for currObject in lSelectedObjects:
        #
        # Export this object as a particle definition.
        #
        cloudFile.write("p ")

        #
        # Export the particle's position.
        #
        cloudFile.write(str(round(currObject.getLocation()[0], 4)) + " ")
        cloudFile.write(str(round(currObject.getLocation()[1], 4)) + " ")
        cloudFile.write(str(round(currObject.getLocation()[2], 4)) + " ")

        #
        # Export the particle's rotation.
        #
        currObjRotation = currObject.RotZ * 180.0 / 3.14159
        cloudFile.write(str(round(currObjRotation, 4)) + " ")

        #
        # Export the particle's size.
        #
        currObjMesh = currObject.getData()
        currObjSize = abs(currObjMesh.verts[0].co[0]) * 2.0 
        cloudFile.write(str(round(currObjSize, 4)) + " ")

        #
        # Export the particle's texture filename.
        #
        cloudFile.write("data/puff.png")
        cloudFile.write("\n")


    cloudFile.write("\n#\n# Cloud color level definitions.\n#\n")

    #
    # For each color level...
    #
    for currColorLevel in lColorLevels:
        cloudFile.write("cl ")
        cloudFile.write(str(round(currColorLevel.r, 4)) + " ")
        cloudFile.write(str(round(currColorLevel.g, 4)) + " ")
        cloudFile.write(str(round(currColorLevel.b, 4)) + " ")
        cloudFile.write(str(round(currColorLevel.a, 4)) + " ")
        cloudFile.write(str(currColorLevel.height) + "\n")


    cloudFile.write("\n#\n# Cloud color level set definitions.\n#\n")

    #
    # For each color level set...
    #
    for currColorLevelSet in lColorLevelSets:
        cloudFile.write("cls ")
        cloudFile.write(str(currColorLevelSet.time) + " ")

        #
        # For each color level in the current set...
        #
        for currColorLevelID in currColorLevelSet.lLevels:
            #
            # Map color level ID to list index.
            #
            colorLevelIndex = 0
            for i in lColorLevels:
                if(currColorLevelID == i.id):
                    break
                colorLevelIndex += 1

            cloudFile.write(str(colorLevelIndex) + " ")

        cloudFile.write("\n")


    #
    # Finished exporting, close the file.
    #
    cloudFile.close()



###############################################################################
# Entry point
###############################################################################
Draw.Register(DrawGUI, HandleInputEvents, HandleButtonEvents)

