# ##### BEGIN GPL LICENSE BLOCK #####
#
#  This program is free software; you can redistribute it and/or
#  modify it under the terms of the GNU General Public License
#  as published by the Free Software Foundation; either version 2
#  of the License, or (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software Foundation,
#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####

# <pep8 compliant>

bl_info = {
    "name": "Cloud Generator",
    "author": "Nick Keeline(nrk)",
    "version": (1, 0),
    "blender": (2, 77, 0),
    "location": "Tool Shelf > Create Tab",
    "description": "Creates Volumetric Clouds",
    "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
                "Scripts/Object/Cloud_Gen",
    "tracker_url" : "https://developer.blender.org/maniphest/project/3/type/Bug/",
    "category": "Object",
}

import bpy
from bpy.props import BoolProperty, EnumProperty
from bpy.types import Operator, Panel

# For Cycles Render we create node groups or if it already exists we return it.
def CreateNodeGroup(Type):

# Look for NodeTree if it already exists return it
    
    CreateGroup = True
    for Group in bpy.data.node_groups:
        if Group.name == Type:
            CreateGroup = False
            NodeGroup = Group
            
    if CreateGroup == True:
        NodeGroup = bpy.data.node_groups.new(name=Type,type="ShaderNodeTree")
        NodeGroup.name = Type
        NodeGroup.bl_label = Type
        NodeGroup.nodes.clear()

# Create a bunch of nodes and group them based on input to the def
# Function type
        if Type == 'CloudGen_VolumeProperties':
            AddAddAndEmission = NodeGroup.nodes.new('ShaderNodeAddShader')
            AddAddAndEmission.location = [300,395]
            AddAbsorptionAndScatter = NodeGroup.nodes.new('ShaderNodeAddShader')
            AddAbsorptionAndScatter.location = [0,395]
            VolumeAbsorption = NodeGroup.nodes.new('ShaderNodeVolumeAbsorption')
            VolumeAbsorption.location = [-300,395]
            VolumeScatter = NodeGroup.nodes.new('ShaderNodeVolumeScatter')
            VolumeScatter.location = [-300,0]
            VolumeEmission = NodeGroup.nodes.new('ShaderNodeEmission')
            VolumeEmission.location = [-300,-300]
            MathAbsorptionMultiply = NodeGroup.nodes.new('ShaderNodeMath')
            MathAbsorptionMultiply.location = [-750,395]
            MathAbsorptionMultiply.operation = 'MULTIPLY'
            MathScatterMultiply = NodeGroup.nodes.new('ShaderNodeMath')
            MathScatterMultiply.location = [-750,0]
            MathScatterMultiply.operation = 'MULTIPLY'
            MathEmissionMultiply = NodeGroup.nodes.new('ShaderNodeMath')
            MathEmissionMultiply.location = [-750,-300]
            MathEmissionMultiply.operation = 'MULTIPLY'
            MathBrightnessMultiply = NodeGroup.nodes.new('ShaderNodeMath')
            MathBrightnessMultiply.location = [-1200,0]
            MathBrightnessMultiply.operation = 'MULTIPLY'
            MathGreaterThan = NodeGroup.nodes.new('ShaderNodeMath')
            MathGreaterThan.location = [-1200,600]
            MathGreaterThan.operation = 'GREATER_THAN'
            MathGreaterThan.inputs[1].default_value = 0
    
            NodeGroup.links.new(AddAddAndEmission.inputs[0],AddAbsorptionAndScatter.outputs[0])
            NodeGroup.links.new(AddAddAndEmission.inputs[1],VolumeEmission.outputs[0])
            NodeGroup.links.new(AddAbsorptionAndScatter.inputs[0],VolumeAbsorption.outputs[0])
            NodeGroup.links.new(AddAbsorptionAndScatter.inputs[1],VolumeScatter.outputs[0])
            NodeGroup.links.new(VolumeAbsorption.inputs[1],MathAbsorptionMultiply.outputs[0])
            NodeGroup.links.new(VolumeScatter.inputs[1],MathScatterMultiply.outputs[0])
            NodeGroup.links.new(VolumeEmission.inputs[1],MathEmissionMultiply.outputs[0])
            NodeGroup.links.new(MathAbsorptionMultiply.inputs[0],MathGreaterThan.outputs[0])
            NodeGroup.links.new(MathScatterMultiply.inputs[0],MathGreaterThan.outputs[0])
            NodeGroup.links.new(MathEmissionMultiply.inputs[0],MathGreaterThan.outputs[0])
            NodeGroup.links.new(VolumeAbsorption.inputs[0],MathBrightnessMultiply.outputs[0])
            
# Create and Link In/Out to Group Node
# Outputs
            group_outputs = NodeGroup.nodes.new('NodeGroupOutput')
            group_outputs.location = (600,395)
            NodeGroup.outputs.new('NodeSocketShader','shader_out')             
            NodeGroup.links.new(AddAddAndEmission.outputs[0],group_outputs.inputs['shader_out']) 
            
# Inputs
            group_inputs = NodeGroup.nodes.new('NodeGroupInput')
            group_inputs.location = (-1500,-300)
            NodeGroup.inputs.new('NodeSocketFloat','Density')   
            NodeGroup.inputs.new('NodeSocketFloat','Absorption Multiply')  
            NodeGroup.inputs.new('NodeSocketColor','Absorption Color')  
            NodeGroup.inputs.new('NodeSocketFloat','Scatter Multiply')  
            NodeGroup.inputs.new('NodeSocketColor','Scatter Color')  
            NodeGroup.inputs.new('NodeSocketFloat','Emission Amount')  
            NodeGroup.inputs.new('NodeSocketFloat','Cloud Brightness')  
                 
            NodeGroup.links.new(group_inputs.outputs['Density'],MathGreaterThan.inputs[0]) 
            NodeGroup.links.new(group_inputs.outputs['Absorption Multiply'],MathAbsorptionMultiply.inputs[1]) 
            NodeGroup.links.new(group_inputs.outputs['Absorption Color'],MathBrightnessMultiply.inputs[0]) 
            NodeGroup.links.new(group_inputs.outputs['Scatter Multiply'],MathScatterMultiply.inputs[1]) 
            NodeGroup.links.new(group_inputs.outputs['Scatter Color'],VolumeScatter.inputs[0]) 
            NodeGroup.links.new(group_inputs.outputs['Emission Amount'],MathEmissionMultiply.inputs[1]) 
            NodeGroup.links.new(group_inputs.outputs['Cloud Brightness'],MathBrightnessMultiply.inputs[1]) 
                 
            
        if Type == 'CloudGen_TextureProperties':
            MathAdd = NodeGroup.nodes.new('ShaderNodeMath')
            MathAdd.location = [-200,0]
            MathAdd.operation = 'ADD'
            MathDensityMultiply = NodeGroup.nodes.new('ShaderNodeMath')
            MathDensityMultiply.location = [-390,0]
            MathDensityMultiply.operation = 'MULTIPLY'
            PointDensityRamp = NodeGroup.nodes.new('ShaderNodeValToRGB')
            PointDensityRamp.location = [-675,-250]
            PointRamp = PointDensityRamp.color_ramp
            PElements = PointRamp.elements
            PElements[0].position = 0.418
            PElements[0].color = 0, 0, 0, 1
            PElements[1].position = 0.773
            PElements[1].color = 1, 1, 1, 1
            CloudRamp = NodeGroup.nodes.new('ShaderNodeValToRGB')
            CloudRamp.location = [-675,0]
            CRamp = CloudRamp.color_ramp
            CElements = CRamp.elements
            CElements[0].position = 0.527
            CElements[0].color = 0, 0, 0, 1
            CElements[1].position = 0.759
            CElements[1].color = 1, 1, 1, 1
            NoiseTex = NodeGroup.nodes.new('ShaderNodeTexNoise')
            NoiseTex.location = [-940,0]
            NoiseTex.inputs['Detail'].default_value = 4
            TexCoord = NodeGroup.nodes.new('ShaderNodeTexCoord')
            TexCoord.location = [-1250,0]
            
            
            NodeGroup.links.new(MathAdd.inputs[0],MathDensityMultiply.outputs[0])
            NodeGroup.links.new(MathAdd.inputs[1],PointDensityRamp.outputs[0])
            NodeGroup.links.new(MathDensityMultiply.inputs[0],CloudRamp.outputs[0])
            NodeGroup.links.new(CloudRamp.inputs[0],NoiseTex.outputs[0])
            NodeGroup.links.new(NoiseTex.inputs[0],TexCoord.outputs[3])
            
# Create and Link In/Out to Group Nodes
# Outputs
            group_outputs = NodeGroup.nodes.new('NodeGroupOutput')
            group_outputs.location = (0,0)
            NodeGroup.outputs.new('NodeSocketFloat','Density W_CloudTex')             
            NodeGroup.links.new(MathAdd.outputs[0],group_outputs.inputs['Density W_CloudTex']) 
            
# Inputs
            group_inputs = NodeGroup.nodes.new('NodeGroupInput')
            group_inputs.location = (-1250,-300)
            NodeGroup.inputs.new('NodeSocketFloat','Scale')   
            NodeGroup.inputs.new('NodeSocketFloat','Point Density In')  
            NodeGroup.links.new(group_inputs.outputs['Scale'],NoiseTex.inputs['Scale']) 
            NodeGroup.links.new(group_inputs.outputs['Point Density In'],MathDensityMultiply.inputs[1]) 
            NodeGroup.links.new(group_inputs.outputs['Point Density In'],PointDensityRamp.inputs[0]) 
            
    return NodeGroup

# This routine takes an object and deletes all of the geometry in it
# and adds a bounding box to it.
# It will add or subtract the bound box size by the variable sizeDifference.

def getMeshandPutinEditMode(scene, object):

    # Go into Object Mode
    bpy.ops.object.mode_set(mode='OBJECT')

    # Deselect All
    bpy.ops.object.select_all(action='DESELECT')

    # Select the object
    object.select = True
    scene.objects.active = object

    # Go into Edit Mode
    bpy.ops.object.mode_set(mode='EDIT')

    return object.data


def maxAndMinVerts(scene, object):

    mesh = getMeshandPutinEditMode(scene, object)
    verts = mesh.vertices

    #Set the max and min verts to the first vertex on the list
    maxVert = [verts[0].co[0], verts[0].co[1], verts[0].co[2]]
    minVert = [verts[0].co[0], verts[0].co[1], verts[0].co[2]]

    #Create Max and Min Vertex array for the outer corners of the box
    for vert in verts:
        #Max vertex
        if vert.co[0] > maxVert[0]:
            maxVert[0] = vert.co[0]
        if vert.co[1] > maxVert[1]:
            maxVert[1] = vert.co[1]
        if vert.co[2] > maxVert[2]:
            maxVert[2] = vert.co[2]

        #Min Vertex
        if vert.co[0] < minVert[0]:
            minVert[0] = vert.co[0]
        if vert.co[1] < minVert[1]:
            minVert[1] = vert.co[1]
        if vert.co[2] < minVert[2]:
            minVert[2] = vert.co[2]

    return [maxVert, minVert]


def makeObjectIntoBoundBox(scene, object, sizeDifference, takeFromObject):

    # Let's find the max and min of the reference object,
    # it can be the same as the destination object
    [maxVert, minVert] = maxAndMinVerts(scene, takeFromObject)

    #get objects mesh
    mesh = getMeshandPutinEditMode(scene, object)

    #Add the size difference to the max size of the box
    maxVert[0] = maxVert[0] + sizeDifference
    maxVert[1] = maxVert[1] + sizeDifference
    maxVert[2] = maxVert[2] + sizeDifference

    #subtract the size difference to the min size of the box
    minVert[0] = minVert[0] - sizeDifference
    minVert[1] = minVert[1] - sizeDifference
    minVert[2] = minVert[2] - sizeDifference

    #Create arrays of verts and faces to be added to the mesh
    addVerts = []

    #X high loop
    addVerts.append([maxVert[0], maxVert[1], maxVert[2]])
    addVerts.append([maxVert[0], maxVert[1], minVert[2]])
    addVerts.append([maxVert[0], minVert[1], minVert[2]])
    addVerts.append([maxVert[0], minVert[1], maxVert[2]])

    #x low loop
    addVerts.append([minVert[0], maxVert[1], maxVert[2]])
    addVerts.append([minVert[0], maxVert[1], minVert[2]])
    addVerts.append([minVert[0], minVert[1], minVert[2]])
    addVerts.append([minVert[0], minVert[1], maxVert[2]])

    # Make the faces of the bounding box.
    addFaces = []

    # Draw a box on paper and number the vertices.
    # Use right hand rule to come up with number orders for faces on
    # the box (with normals pointing out).
    addFaces.append([0, 3, 2, 1])
    addFaces.append([4, 5, 6, 7])
    addFaces.append([0, 1, 5, 4])
    addFaces.append([1, 2, 6, 5])
    addFaces.append([2, 3, 7, 6])
    addFaces.append([0, 4, 7, 3])

    # Delete all geometry from the object.
    bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.delete(type='VERT')

    # Must be in object mode for from_pydata to work
    bpy.ops.object.mode_set(mode='OBJECT')

    # Add the mesh data.
    mesh.from_pydata(addVerts, [], addFaces)

    # Update the mesh
    mesh.update()


def applyScaleRotLoc(scene, obj):
    # Deselect All
    bpy.ops.object.select_all(action='DESELECT')

    # Select the object
    obj.select = True
    scene.objects.active = obj

    bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)


def totallyDeleteObject(scene, obj):
    scene.objects.unlink(obj)
    bpy.data.objects.remove(obj)


def makeParent(parentobj, childobj, scene):

    applyScaleRotLoc(scene, parentobj)
    applyScaleRotLoc(scene, childobj)
    childobj.parent = parentobj


def addNewObject(scene, name, copyobj):

    # Create new mesh
    mesh = bpy.data.meshes.new(name)

    # Create a new object.
    ob_new = bpy.data.objects.new(name, mesh)
    tempme = copyobj.data
    ob_new.data = tempme.copy()
    ob_new.scale = copyobj.scale
    ob_new.location = copyobj.location

    # Link new object to the given scene and select it.
    scene.objects.link(ob_new)
    ob_new.select = True

    return ob_new


def getpdensitytexture(object):

    for mslot in object.material_slots:
        mat = mslot.material
        for tslot in mat.texture_slots:
            if tslot != 'NoneType':
                tex = tslot.texture
                if tex.type == 'POINT_DENSITY':
                    if tex.point_density.point_source == 'PARTICLE_SYSTEM':
                        return tex


def removeParticleSystemFromObj(scene, object):

    # Deselect All
    bpy.ops.object.select_all(action='DESELECT')

    # Select the object.
    object.select = True
    scene.objects.active = object

    bpy.ops.object.particle_system_remove()

    # Deselect All
    bpy.ops.object.select_all(action='DESELECT')


def convertParticlesToMesh(scene, particlesobj, destobj, replacemesh):

    # Select the Destination object.
    destobj.select = True
    scene.objects.active = destobj

    #Go to Edit Mode
    bpy.ops.object.mode_set(mode='EDIT', toggle=False)

    #Delete everything in mesh if replace true
    if replacemesh:
        bpy.ops.mesh.select_all(action='SELECT')
        bpy.ops.mesh.delete(type='VERT')

    meshPnts = destobj.data

    listCloudParticles = particlesobj.particles

    listMeshPnts = []
    for pTicle in listCloudParticles:
        listMeshPnts.append(pTicle.location)

    # Must be in object mode for from_pydata to work.
    bpy.ops.object.mode_set(mode='OBJECT')

    # Add in the mesh data.
    meshPnts.from_pydata(listMeshPnts, [], [])

    # Update the mesh.
    meshPnts.update()


def combineObjects(scene, combined, listobjs):
    # scene is the current scene
    # combined is the object we want to combine everything into
    # listobjs is the list of objects to stick into combined

    # Deselect All
    bpy.ops.object.select_all(action='DESELECT')

    # Select the new object.
    combined.select = True
    scene.objects.active = combined

    # Add data
    if len(listobjs) > 0:
        for i in listobjs:
            # Add a modifier
            bpy.ops.object.modifier_add(type='BOOLEAN')

            union = combined.modifiers
            union[0].name = "AddEmUp"
            union[0].object = i
            union[0].operation = 'UNION'

            # Apply modifier
            bpy.ops.object.modifier_apply(apply_as='DATA', modifier=union[0].name)


# Returns the action we want to take
def getActionToDo(obj):

    if not obj or obj.type != 'MESH':
        return 'NOT_OBJ_DO_NOTHING'
    elif obj is None:
        return 'NO_SELECTION_DO_NOTHING'
    elif "CloudMember" in obj:
        if obj["CloudMember"] != None:
            if obj["CloudMember"] == "MainObj":
                return 'DEGENERATE'
            elif obj["CloudMember"] == "CreatedObj" and len(obj.particle_systems) > 0:
                return 'CLOUD_CONVERT_TO_MESH'
            else:
                return 'CLOUD_DO_NOTHING'
    elif obj.type == 'MESH':
        return 'GENERATE'
    else:
        return 'DO_NOTHING'


class VIEW3D_PT_tools_cloud(Panel):
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'TOOLS'
    bl_category = 'Create'
    bl_label = "Cloud Generator"
    bl_context = "objectmode"
    bl_options = {'DEFAULT_CLOSED'}

    def draw(self, context):
        active_obj = context.active_object
        layout = self.layout
        col = layout.column(align=True)

        WhatToDo = getActionToDo(active_obj)

        if WhatToDo == 'DEGENERATE':
            col.operator("cloud.generate_cloud", text="DeGenerate")

        elif WhatToDo == 'CLOUD_CONVERT_TO_MESH':
            col.operator("cloud.generate_cloud", text="Convert to Mesh")

        elif WhatToDo == 'NO_SELECTION_DO_NOTHING':
            col.label(text="Select one or more")
            col.label(text="objects to generate")
            col.label(text="a cloud")

        elif WhatToDo == 'CLOUD_DO_NOTHING':
            col.label(text="Must select")
            col.label(text="bound box")

        elif WhatToDo == 'GENERATE':
            col.operator("cloud.generate_cloud", text="Generate Cloud")

            col.prop(context.scene, "cloud_type")
            col.prop(context.scene, "cloudsmoothing")
        else:
            col.label(text="Select one or more")
            col.label(text="objects to generate")
            col.label(text="a cloud")


class GenerateCloud(Operator):
    """Create a Cloud,Undo Cloud, or convert to Mesh Cloud depending on selection"""
    bl_idname = "cloud.generate_cloud"
    bl_label = "Generate Cloud"
    bl_register = True
    bl_undo = True

    @classmethod
    def poll(cls, context):
        if not context.active_object:
            return False
        else:
            return (context.active_object.type == 'MESH')

    def execute(self, context):
        # Prevent unsupported Execution in Local View modes
        space_data = bpy.context.space_data

        if True in space_data.layers_local_view:
            self.report({'INFO'}, 'Global Perspective modes only unable to continue.')
            return {'FINISHED'}

        # Make variable that is the current .blend file main data blocks
        blend_data = context.blend_data

        # Make variable that is the active object selected by user
        active_object = context.active_object

        # Make variable scene that is current scene
        scene = context.scene

        # Parameters the user may want to change:
        # Number of points this number is multiplied by the volume to get
        # the number of points the scripts will put in the volume.
        
        if bpy.context.scene.render.engine == 'BLENDER_RENDER': 
            numOfPoints = 1.0
            maxNumOfPoints = 100000
            maxPointDensityRadius = 1.5
            scattering = 2.5
            pointDensityRadiusFactor = 1.0
            densityScale = 1.5
        elif bpy.context.scene.render.engine == 'CYCLES':
            numOfPoints = .80
            maxNumOfPoints = 100000
            maxPointDensityRadius = 1.0
            scattering = 2.5
            pointDensityRadiusFactor = .37
            densityScale = 1.5
            noiseScale = 1
            
            
        # What should we do?
        WhatToDo = getActionToDo(active_object)

        if WhatToDo == 'DEGENERATE':
            # Degenerate Cloud
            mainObj = active_object
            
            bpy.ops.object.hide_view_clear()
            
            cloudMembers = active_object.children

            createdObjects = []
            definitionObjects = []
            for member in cloudMembers:
                applyScaleRotLoc(scene, member)
                if member["CloudMember"] == "CreatedObj":
                    createdObjects.append(member)
                else:
                    definitionObjects.append(member)

            for defObj in definitionObjects:
                # Delete cloudmember data from objects
                if "CloudMember" in defObj:
                    del(defObj["CloudMember"])

            for createdObj in createdObjects:
                totallyDeleteObject(scene, createdObj)

            # Delete the blend_data object
            totallyDeleteObject(scene, mainObj)

            # Select all of the left over boxes so people can immediately
            # press generate again if they want.
            for eachMember in definitionObjects:
                eachMember.draw_type = 'SOLID'
                eachMember.select = True
                eachMember.hide_render = False

        elif WhatToDo == 'CLOUD_CONVERT_TO_MESH':

            cloudParticles = active_object.particle_systems.active

            bounds = active_object.parent

            ###############Create CloudPnts for putting points in#########
            # Create a new object cloudPnts
            cloudPnts = addNewObject(scene, "CloudPoints", bounds)
            cloudPnts["CloudMember"] = "CreatedObj"
            cloudPnts.draw_type = 'WIRE'
            cloudPnts.hide_render = True

            makeParent(bounds, cloudPnts, scene)

            convertParticlesToMesh(scene, cloudParticles, cloudPnts, True)

            removeParticleSystemFromObj(scene, active_object)

            pDensity = getpdensitytexture(bounds)
            pDensity.point_density.point_source = 'OBJECT'
            pDensity.point_density.object = cloudPnts

            #Let's resize the bound box to be more accurate.
            how_much_bigger = pDensity.point_density.radius
            makeObjectIntoBoundBox(scene, bounds, how_much_bigger, cloudPnts)

        else:
            # Generate Cloud

            ###############Create Combined Object bounds##################
            # Make a list of all Selected objects.
            selectedObjects = bpy.context.selected_objects
            if not selectedObjects:
                selectedObjects = [bpy.context.active_object]

            # Create a new object bounds
            bounds = addNewObject(scene,
                    "CloudBounds",
                    selectedObjects[0])

            bounds.draw_type = 'BOUNDS'
            bounds.hide_render = False

            # Just add a Definition Property designating this
            # as the blend_data object.
            bounds["CloudMember"] = "MainObj"

            # Since we used iteration 0 to copy with object we
            # delete it off the list.
            firstObject = selectedObjects[0]
            del selectedObjects[0]

            # Apply location Rotation and Scale to all objects involved.
            applyScaleRotLoc(scene, bounds)
            for each in selectedObjects:
                applyScaleRotLoc(scene, each)

            # Let's combine all of them together.
            combineObjects(scene, bounds, selectedObjects)

            # Let's add some property info to the objects.
            for selObj in selectedObjects:
                selObj["CloudMember"] = "DefinitioinObj"
                selObj.name = "DefinitioinObj"
                selObj.draw_type = 'WIRE'
                selObj.hide_render = True
                selObj.hide = True
                makeParent(bounds, selObj, scene)

            # Do the same to the 1. object since it is no longer in list.
            firstObject["CloudMember"] = "DefinitioinObj"
            firstObject.name = "DefinitioinObj"
            firstObject.draw_type = 'WIRE'
            firstObject.hide_render = True
            makeParent(bounds, firstObject, scene)

            ###############Create Cloud for putting Cloud Mesh############
            # Create a new object cloud.
            cloud = addNewObject(scene, "CloudMesh", bounds)
            cloud["CloudMember"] = "CreatedObj"
            cloud.draw_type = 'WIRE'
            cloud.hide_render = True

            makeParent(bounds, cloud, scene)

            bpy.ops.object.editmode_toggle()
            bpy.ops.mesh.select_all(action='SELECT')

            #Don't subdivide object or smooth if smoothing box not checked.
            if scene.cloudsmoothing:
                bpy.ops.mesh.subdivide(number_cuts=2, fractal=0, smoothness=1)
 #               bpy.ops.object.transform_apply(location=True)
                bpy.ops.mesh.vertices_smooth(repeat=20)
            bpy.ops.mesh.tris_convert_to_quads()
            bpy.ops.mesh.faces_shade_smooth()
            bpy.ops.object.editmode_toggle()

            ###############Create Particles in cloud obj##################

            # Set time to 0.
            scene.frame_current = 0

            # Add a new particle system.
            bpy.ops.object.particle_system_add()

            #Particle settings setting it up!
            cloudParticles = cloud.particle_systems.active
            cloudParticles.name = "CloudParticles"
            cloudParticles.settings.frame_start = 0
            cloudParticles.settings.frame_end = 0
            cloudParticles.settings.emit_from = 'VOLUME'
            cloudParticles.settings.lifetime = scene.frame_end
            cloudParticles.settings.draw_method = 'DOT'
            cloudParticles.settings.render_type = 'NONE'
            cloudParticles.settings.distribution = 'RAND'
            cloudParticles.settings.physics_type = 'NEWTON'
            cloudParticles.settings.normal_factor = 0

            #Gravity does not effect the particle system
            eWeights = cloudParticles.settings.effector_weights
            eWeights.gravity = 0

            ####################Create Volume Material####################
            # Deselect All
            bpy.ops.object.select_all(action='DESELECT')

            # Select the object.
            bounds.select = True
            scene.objects.active = bounds

            # Turn bounds object into a box.  Use itself as a reference.
            makeObjectIntoBoundBox(scene, bounds, 1.0, bounds)

            # Delete all material slots in bounds object.
            for i in range(len(bounds.material_slots)):
                bounds.active_material_index = i - 1
                bpy.ops.object.material_slot_remove()

            # Add a new material.
            cloudMaterial = blend_data.materials.new("CloudMaterial")
            bpy.ops.object.material_slot_add()
            bounds.material_slots[0].material = cloudMaterial

            # Set time
            scene.frame_current = 1
                
            #Set Up Material for Blender Internal
            if bpy.context.scene.render.engine == 'BLENDER_RENDER': 
                # Set Up the Cloud Material
                cloudMaterial.name = "CloudMaterial"
                cloudMaterial.type = 'VOLUME'
                mVolume = cloudMaterial.volume
                mVolume.scattering = scattering
                mVolume.density = 0
                mVolume.density_scale = densityScale
                mVolume.transmission_color = 3.0, 3.0, 3.0
                mVolume.step_size = 0.1
                mVolume.use_light_cache = True
                mVolume.cache_resolution = 45

                # Add a texture
                # vMaterialTextureSlots = cloudMaterial.texture_slots  # UNUSED
                cloudtex = blend_data.textures.new("CloudTex", type='CLOUDS')
                cloudtex.noise_type = 'HARD_NOISE'
                cloudtex.noise_scale = 2
                mtex = cloudMaterial.texture_slots.add()
                mtex.texture = cloudtex
                mtex.texture_coords = 'ORCO'
                mtex.use_map_color_diffuse = True

                # Set time
                scene.frame_current = 1

                # Add a Point Density texture
                pDensity = blend_data.textures.new("CloudPointDensity", 'POINT_DENSITY')

                mtex = cloudMaterial.texture_slots.add()
                mtex.texture = pDensity
                mtex.texture_coords = 'GLOBAL'
                mtex.use_map_density = True
                mtex.use_rgb_to_intensity = True
                mtex.texture_coords = 'GLOBAL'

                pDensity.point_density.vertex_cache_space = 'WORLD_SPACE'
                pDensity.point_density.use_turbulence = True
                pDensity.point_density.noise_basis = 'VORONOI_F2'
                pDensity.point_density.turbulence_depth = 3

                pDensity.use_color_ramp = True
                pRamp = pDensity.color_ramp
                #pRamp.use_interpolation = 'LINEAR'
                pRampElements = pRamp.elements
                #pRampElements[1].position = .9
                #pRampElements[1].color = 0.18, 0.18, 0.18, 0.8
                bpy.ops.texture.slot_move(type='UP')

            #Set Up Material for Cycles Engine
            elif bpy.context.scene.render.engine == 'CYCLES': 
                VolumePropertiesGroup = CreateNodeGroup('CloudGen_VolumeProperties')
                CloudTexPropertiesGroup = CreateNodeGroup('CloudGen_TextureProperties')
  
                cloudMaterial.name = "CloudMaterial"
                # Add a texture
                # vMaterialTextureSlots = cloudMaterial.texture_slots  # UNUSED
                cloudtex = blend_data.textures.new("CloudTex", type='CLOUDS')
                cloudtex.noise_type = 'HARD_NOISE'
                cloudtex.noise_scale = 2     
                
                cloudMaterial.use_nodes = True
                cloudTree = cloudMaterial.node_tree
                cloudMatNodes = cloudTree.nodes
                cloudMatNodes.clear()
                
                outputNode = cloudMatNodes.new('ShaderNodeOutputMaterial') 
                outputNode.location = (200,300)
                
                tranparentNode = cloudMatNodes.new('ShaderNodeBsdfTransparent') 
                tranparentNode.location = (0,300)  
                
                volumeGroup = cloudMatNodes.new("ShaderNodeGroup")  
                volumeGroup.node_tree = VolumePropertiesGroup
                volumeGroup.location = (0,150)
                
                cloudTexGroup = cloudMatNodes.new("ShaderNodeGroup")  
                cloudTexGroup.node_tree = CloudTexPropertiesGroup
                cloudTexGroup.location = (-200,150)
                
                PointDensityNode = cloudMatNodes.new("ShaderNodeTexPointDensity")  
                PointDensityNode.location = (-400,150)
                PointDensityNode.resolution = 100
                PointDensityNode.space = 'OBJECT'
                PointDensityNode.interpolation = 'Linear'                
#                PointDensityNode.color_source = 'CONSTANT'
                
                cloudTree.links.new(outputNode.inputs[0],tranparentNode.outputs[0])
                cloudTree.links.new(outputNode.inputs[1],volumeGroup.outputs[0])
                cloudTree.links.new(volumeGroup.inputs[0],cloudTexGroup.outputs[0])
                cloudTree.links.new(cloudTexGroup.inputs[1],PointDensityNode.outputs[1])
                
                #PointDensityNode.point_source = 'PARTICLE_SYSTEM'
                #VolumePropsNode = cloudMatNodes.new(VolumePropertiesGroup)
                #VolumePropsNode.location = (-200,0)
                
                
                #tree = bpy.data.materials['CloudMaterial'].node_tree
                #group = bpy.data.groups.data.node_groups['CloudGen_VolumeProperties']
                #newgroup = tree.nodes.new("ShaderNodeGroup")
                #newgroup.node_tree = bpy.data.node_groups['CloudGen_VolumeProperties']
                #ramp = tree.nodes.new('ShaderNodeValToRGB') 
                #cramp = ramp.color_ramp
                
                #mport bpy
                #obj = bpy.data.objects['CloudBounds']
                #(obj.dimensions[0] * obj.dimensions[1] * obj.dimensions[2])
                
            # Estimate the number of particles for the size of bounds.
            volumeBoundBox = (bounds.dimensions[0] * bounds.dimensions[1] * bounds.dimensions[2])
            numParticles = int((2.4462 * volumeBoundBox + 430.4) * numOfPoints)
            if numParticles > maxNumOfPoints:
                numParticles = maxNumOfPoints
            if numParticles < 10000:
                numParticles = int(numParticles + 15 * volumeBoundBox)
            print(numParticles)

            # Set the number of particles according to the volume
            # of bounds.
            cloudParticles.settings.count = numParticles

            PDensityRadius = (.00013764 * volumeBoundBox + .3989) * pointDensityRadiusFactor
            
            if bpy.context.scene.render.engine == 'BLENDER_RENDER': 
                pDensity.point_density.radius = PDensityRadius

                if pDensity.point_density.radius > maxPointDensityRadius:
                    pDensity.point_density.radius = maxPointDensityRadius
                    
            elif bpy.context.scene.render.engine == 'CYCLES': 
                PointDensityNode.radius = PDensityRadius

                if PDensityRadius > maxPointDensityRadius:
                    PointDensityNode.radius = maxPointDensityRadius

            # Set time to 1.
            scene.frame_current = 1

            if not scene.cloudparticles:
                ###############Create CloudPnts for putting points in#########
                # Create a new object cloudPnts
                cloudPnts = addNewObject(scene, "CloudPoints", bounds)
                cloudPnts["CloudMember"] = "CreatedObj"
                cloudPnts.draw_type = 'WIRE'
                cloudPnts.hide_render = True

                makeParent(bounds, cloudPnts, scene)

                convertParticlesToMesh(scene, cloudParticles, cloudPnts, True)

                # Add a modifier.
                bpy.ops.object.modifier_add(type='DISPLACE')

                cldPntsModifiers = cloudPnts.modifiers
                cldPntsModifiers[0].name = "CloudPnts"
                cldPntsModifiers[0].texture = cloudtex
                cldPntsModifiers[0].texture_coords = 'OBJECT'
                cldPntsModifiers[0].texture_coords_object = cloud
                cldPntsModifiers[0].strength = -1.4

                # Apply modifier
                bpy.ops.object.modifier_apply(apply_as='DATA', modifier=cldPntsModifiers[0].name)

                if bpy.context.scene.render.engine == 'BLENDER_RENDER': 
                    pDensity.point_density.point_source = 'OBJECT'
                    pDensity.point_density.object = cloudPnts

                elif bpy.context.scene.render.engine == 'CYCLES': 
                    PointDensityNode.point_source = 'OBJECT'
                    PointDensityNode.object = cloudPnts
                    
                removeParticleSystemFromObj(scene, cloud)

            else:

                if bpy.context.scene.render.engine == 'BLENDER_RENDER': 
                    pDensity.point_density.point_source = 'PARTICLE_SYSTEM'
                    pDensity.point_density.object = cloud
                    pDensity.point_density.particle_system = cloudParticles

                elif bpy.context.scene.render.engine == 'CYCLES': 
                    PointDensityNode.point_source = 'PARTICLE_SYSTEM'
                    PointDensityNode.particle_system = cloudPnts
                    
            if bpy.context.scene.render.engine == 'BLENDER_RENDER': 
                if scene.cloud_type == '1':  # Cumulous
                    print("Cumulous")
                    mVolume.density_scale = 2.22
                    pDensity.point_density.turbulence_depth = 10
                    pDensity.point_density.turbulence_strength = 6.3
                    pDensity.point_density.turbulence_scale = 2.9
                    pRampElements[1].position = .606
                    pDensity.point_density.radius = pDensity.point_density.radius + 0.1

                elif scene.cloud_type == '2':  # Cirrus
                    print("Cirrus")
                    pDensity.point_density.turbulence_strength = 22
                    mVolume.transmission_color = 3.5, 3.5, 3.5
                    mVolume.scattering = 0.13

                elif scene.cloud_type == '3':  # Explosion
                    print("Explosion")
                    mVolume.emission = 1.42
                    mtex.use_rgb_to_intensity = False
                    pRampElements[0].position = 0.825
                    pRampElements[0].color = 0.119, 0.119, 0.119, 1
                    pRampElements[1].position = .049
                    pRampElements[1].color = 1.0, 1.0, 1.0, 0
                    pDensity.point_density.turbulence_strength = 1.5
                    pRampElement1 = pRampElements.new(.452)
                    pRampElement1.color = 0.814, 0.112, 0, 1
                    pRampElement2 = pRampElements.new(.234)
                    pRampElement2.color = 0.814, 0.310, 0.002, 1
                    pRampElement3 = pRampElements.new(0.669)
                    pRampElement3.color = 0.0, 0.0, 0.040, 1

                    
            elif bpy.context.scene.render.engine == 'CYCLES':
                
                volumeGroup.inputs['Absorption Multiply'].default_value = 50       
                volumeGroup.inputs['Absorption Color'].default_value = (1.0, 1.0, 1.0, 1.0)      
                volumeGroup.inputs['Scatter Multiply'].default_value  = 30       
                volumeGroup.inputs['Scatter Color'].default_value = (.58, .58, .58, 1.0)            
                volumeGroup.inputs['Emission Amount'].default_value = .1   
                volumeGroup.inputs['Cloud Brightness'].default_value  = 1.3
                noiseCloudScale = volumeBoundBox*(-.001973)+5.1216
                if noiseCloudScale < .05:
                    noiseCloudScale = .05    
                cloudTexGroup.inputs['Scale'].default_value  = noiseCloudScale
                   
                if scene.cloud_type == '1':  # Cumulous
                    print("Cumulous")
                    
                elif scene.cloud_type == '2':  # Cirrus
                    print("Cirrus")
                    
                elif scene.cloud_type == '3':  # Explosion
                    print("Explosion")
                
                #to cloud to view in cycles in render mode we need to hide geometry meshes...
                firstObject.hide = True   
                cloud.hide = True 
                    
            # Select the object.
            bounds.select = True
            scene.objects.active = bounds

            #Let's resize the bound box to be more accurate.
            how_much_bigger = PDensityRadius + 0.1

            #If it's a particle cloud use cloud mesh if otherwise use point mesh
            if not scene.cloudparticles:
                makeObjectIntoBoundBox(scene, bounds, how_much_bigger, cloudPnts)
            else:
                makeObjectIntoBoundBox(scene, bounds, how_much_bigger, cloud)

        return {'FINISHED'}


def register():
    bpy.utils.register_module(__name__)

    bpy.types.Scene.cloudparticles = BoolProperty(
        name="Particles",
        description="Generate Cloud as Particle System",
        default=False)

    bpy.types.Scene.cloudsmoothing = BoolProperty(
        name="Smoothing",
        description="Smooth Resultant Geometry From Gen Cloud Operation",
        default=True)

    bpy.types.Scene.cloud_type = EnumProperty(
        name="Type",
        description="Select the type of cloud to create with material settings",
        items=[("0", "Stratus", "Generate Stratus_foggy Cloud"),
               ("1", "Cumulous", "Generate Cumulous_puffy Cloud"),
               ("2", "Cirrus", "Generate Cirrus_wispy Cloud"),
               ("3", "Explosion", "Generate Explosion"),
              ],
        default='0')


def unregister():
    bpy.utils.unregister_module(__name__)

    del bpy.types.Scene.cloudparticles
    del bpy.types.Scene.cloud_type


if __name__ == "__main__":
    register()
