
# "Scatter" Add-on
# Copyright (C) 2019 Dorian Borremans aka BD3D
#
# 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 3 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, see <http://www.gnu.org/licenses/>.
# <pep8 compliant>

bl_info = {
    "name" : "Scatter [BD3D]", 
    "author" : "BD3D",   
    "description" : "The scattering tool of 2.8",
    "blender" : (2, 80, 0),
    "location" : "Operator",
    "warning" : "",
    "category" : "Generic"
}

import bpy
import bmesh
import random
from bpy.types import Menu
import webbrowser
from bpy.types import Operator, AddonPreferences, PropertyGroup
from bpy.props import StringProperty, IntProperty, BoolProperty, FloatProperty
import rna_keymap_ui
context = bpy.context

def find_collection(context, item):
    collections = item.users_collection
    if len(collections) > 0:
        return collections[0]
    return context.scene.collection

def make_collection(collection_name, parent_collection):
    if collection_name in bpy.data.collections:
        return bpy.data.collections[collection_name]
    else:
        new_collection = bpy.data.collections.new(collection_name)
        parent_collection.children.link(new_collection)
        return new_collection

def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'): #Message function
    def draw(self, context):
        self.layout.label(text=message)
    bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)

######################################################################################
######################################################################################
# # # # # # # # # # # #         SCATTER ADDON PREF             # # # # # # # # # # # # 
######################################################################################
######################################################################################

class ScatterPref(AddonPreferences):
    bl_idname = __name__

    scatter_01_type= bpy.props.EnumProperty(
        name = 'Particle Type',
        description = 'particle rendering type',
        items = [
            ('Collection', 'Collection', '', 'COLLECTION_NEW',1),
            ('Object', 'Object', '', 'OBJECT_DATA',2),
            ],
        default = 'Object')
    scatter_01_count = IntProperty( 
            name="Emission Number",
            subtype='NONE',
            default=1000,
            min=0,
            )#percentage couvert? 
    scatter_01_countispersquare = BoolProperty( 
            name="Emission Number is per square meters (be sure to apply the scale of the terrain)",
            subtype='NONE',
            default=True,
            )
    scatter_01_countpersquare = IntProperty( 
            name="Emission Number per square meters",
            subtype='NONE',
            default=50,
            min=0,
            )
    scatter_01_seed = IntProperty( 
            name="Emission Seed Value",
            subtype='NONE',
            default=5,
            min=0,
            )
    scatter_01_seed_is_random = BoolProperty( 
            name="Emission Random Seed Value",
            subtype='NONE',
            default=True,
            )
    scatter_01_particle_size = FloatProperty(
            name="Render Scale (Default = True size) ",
            subtype='NONE',
            default=0.25,
            min=0.01,
            max=3
            )
    scatter_01_size_random = FloatProperty(
            name="Render Scale Randomness",
            subtype='NONE',
            default=0.35,
            min=0,
            max=1
            )
    scatter_01_phase_factor_random = FloatProperty(
            name="Rotation Randomize Phase",
            subtype='NONE',
            default=2,
            min=0,
            max=2
            )
    scatter_01_display_percentage = IntProperty(
            name="Viewport Display Percentage",
            subtype='NONE',
            default=100,
            min=0,
            max=100
            )
    scatter_01_noise_scale = FloatProperty(
            name="Texture Noise Size",
            subtype='NONE',
            default=1.5,
            min=0.01,
            max=100
            )
    scatter_01_noise_scaleisrandom = BoolProperty( 
            name="Texture Noise Size Random Values",
            subtype='NONE',
            default=False,
            )
    scatter_01_noise_scaleA = FloatProperty(
            name="Texture Noise Size Possibilities Range From A",
            subtype='NONE',
            default=0.75,
            min=0.01,
            max=100
            )
    scatter_01_noise_scaleB = FloatProperty(
            name="To B",
            subtype='NONE',
            default=2.3,
            min=0.01,
            max=100
            )

    scatter_01_noise_randomtext = BoolProperty( 
            name="Texture Depth/Contrast/Brightness Randomisation",
            subtype='NONE',
            default=False,
            )
    scatter_01_noise_depth = IntProperty(
            name="Texture Noise Depth",
            subtype='NONE',
            default=0,
            min=0,
            max=20
            )
    scatter_01_contrast = FloatProperty(
            name="Texture Color Contrast",
            subtype='NONE',
            default=3,
            min=0,
            max=5
            )
    scatter_01_intensity = FloatProperty(
            name="Texture Color Brightness",
            subtype='NONE',
            default=1,
            min=0,
            max=2
            )
    scatter_01_density_factor = FloatProperty(
            name="Texture Influence Density",
            subtype='NONE',
            default=1,
            min=0,
            max=1
            )
    scatter_01_length_factor = FloatProperty(
            name="Texture Influence Length",
            subtype='NONE',
            default=0.3,
            min=0,
            max=1
            )

    scatter_01_scalex = FloatProperty(
            name="Texture Mapping size X",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )
    scatter_01_scaley = FloatProperty(
            name="Texture Mapping size Y",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )
    scatter_01_scalez = FloatProperty(
            name="Texture Mapping size Z",
            subtype='NONE',
            default=1,
            min=-100,
            max=100
            )

    scatter_01_offsetx = FloatProperty(
            name="Texture Mapping offset X in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offsety = FloatProperty(
            name="Texture Mapping offset Y in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offsetz = FloatProperty(
            name="Texture Mapping offset Z in meters",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_size_is_random = BoolProperty( 
            name="Texture Mapping Random Size Value XYZ",
            subtype='NONE',
            default=False,
            )
    scatter_01_size_A = FloatProperty(
            name="Size Possibilities Range From A",
            subtype='NONE',
            default=0.7,
            min=-100,
            max=100
            )
    scatter_01_size_B = FloatProperty(
            name="To B",
            subtype='NONE',
            default=1.4,
            min=-100,
            max=100
            )
    scatter_01_offset_is_random = BoolProperty( 
            name="Texture Mapping Random Offset Value XYZ in meters",
            subtype='NONE',
            default=True,
            )
    scatter_01_offset_A = FloatProperty(
            name="Offset Possibilities Range From A",
            subtype='NONE',
            default=0,
            min=-10,
            max=10
            )
    scatter_01_offset_B = FloatProperty(
            name="To B",
            subtype='NONE',
            default=10,
            min=-10,
            max=10
            )
    scatter_01_open= bpy.props.BoolProperty(
        name="Show Debug Tools",
        description="Expand me senpai",
        default=False
    )
######################################################################################

    def draw(self, context): #DRAWING HERE - DRAWING HERE - DRAWING HERE- DRAWING HERE
        layout = self.layout
        full= "------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------"
        addon_prefs = context.preferences.addons[__name__].preferences

        box = layout.box()
        col = box.column()
        row = col.row(align=True)
        row.alignment = 'LEFT'
        row.prop(self,
                 'scatter_01_open',
                 text="Custom Scattering Slot 01",
                 emboss=False,
                 icon="PLAY" if self.scatter_01_open else "PLAY")
        if self.scatter_01_open:

            col.label(text=full)

            col.prop(self,"scatter_01_type")
            if addon_prefs.scatter_01_type == 'Object':
                col.label(text='(Will Batch-Scatter if more than one particle asset is selected)')
            else: 
                col.label(text=' ')

            col.prop(self,"scatter_01_countispersquare")
            if addon_prefs.scatter_01_countispersquare == True:
                col.prop(self,"scatter_01_countpersquare")
            else:
                col.prop(self,"scatter_01_count")
            if addon_prefs.scatter_01_seed_is_random == True:
                col.prop(self,"scatter_01_seed_is_random")
            else:
                col.prop(self,"scatter_01_seed_is_random")
                col.prop(self,"scatter_01_seed")
            #col.prop(self,"scatter_01_particle_size")
            col.prop(self,"scatter_01_size_random")
            col.prop(self,"scatter_01_phase_factor_random")
            col.prop(self,"scatter_01_display_percentage")

            col.label(text=' ')
            col.label(text='(Set Both texture influence to 0 to turn OFF the texture clustering the particles)')
            col.prop(self,"scatter_01_density_factor")
            col.prop(self,"scatter_01_length_factor")

            if addon_prefs.scatter_01_length_factor + addon_prefs.scatter_01_density_factor !=0:

                col.prop(self,"scatter_01_offset_is_random")
                if addon_prefs.scatter_01_offset_is_random == True:
                    col.prop(self,"scatter_01_offset_A")
                    col.prop(self,"scatter_01_offset_B")
                else:
                    col.prop(self,"scatter_01_offsetx")
                    col.prop(self,"scatter_01_offsety")
                    col.prop(self,"scatter_01_offsetz")
                
                col.prop(self,"scatter_01_size_is_random")
                if addon_prefs.scatter_01_size_is_random == True:
                    col.prop(self,"scatter_01_size_A")
                    col.prop(self,"scatter_01_size_B")
                else:
                    col.prop(self,"scatter_01_scalex")
                    col.prop(self,"scatter_01_scaley")
                    col.prop(self,"scatter_01_scalez")

                col.label(text=' ')
                col.prop(self,"scatter_01_noise_scaleisrandom")
                if addon_prefs.scatter_01_noise_scaleisrandom == True:
                    col.prop(self,"scatter_01_noise_scaleA")
                    col.prop(self,"scatter_01_noise_scaleB")
                else:
                    col.prop(self,"scatter_01_noise_scale")

                col.prop(self,"scatter_01_noise_randomtext")
                if addon_prefs.scatter_01_noise_randomtext == False:
                    col.prop(self,"scatter_01_noise_depth")
                    col.prop(self,"scatter_01_contrast")
                    col.prop(self,"scatter_01_intensity")
        

 


######################################################################################
######################################################################################
# # # # # # # # # # # #        SCATTER OPERATOR                # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_OT_Custom01(bpy.types.Operator):
    bl_idname = "scatter.custom01"
    bl_label = "custom scattering operator 01"
    bl_description = ""

    def execute(self, context):

        context = bpy.context
        addon_prefs = context.preferences.addons[__name__].preferences
        scene = context.scene
        A = context.object

        bm = bmesh.new()
        bm.from_mesh(A.data)
        squarearea = sum(f.calc_area() for f in bm.faces)
        bm.free()

        if len(bpy.context.selected_objects) < 2:
            ShowMessageBox("Must have at least two object selected", "Be Careful" ,"ERROR")
            return {'FINISHED'}

        ### ### Naming
        slotname = "Custom1"

        if addon_prefs.scatter_01_type == 'Collection':

            if len(bpy.context.selected_objects) > 2:
                particlename = bpy.context.selected_objects[1].name + " ..."
            else:
                particlename = bpy.context.selected_objects[1].name #BUG why the f does the name change when multiple execution ???
            pref = "SCATTER: ["+slotname+"] ["+particlename+"] v."
            i = 1
            name = "%s%d" % (pref, i)
            while A.modifiers.get(name):    
                i += 1
                name = "%s%d" % (pref, i)
            ### ### add particle system + rename and add selection in new coll
            m = A.modifiers.new(name, type='PARTICLE_SYSTEM')

            A.select_set(state=False)
            for o in bpy.context.selected_objects:
                o_collection = find_collection(bpy.context, o)
                new_collection = make_collection(name , o_collection)
                new_collection.objects.link(o)
                o_collection.objects.unlink(o)
            A.select_set(state=True)

            ### ### particle parameters
            ps = m.particle_system
            ps.name = name
            ps.settings.name = name
            ps.settings.type = 'HAIR'

            ps.settings.render_type = 'COLLECTION'
            ps.settings.instance_collection = bpy.data.collections[name]

            ps.settings.particle_size = addon_prefs.scatter_01_particle_size
            ps.settings.hair_length = 4
            ps.settings.size_random = addon_prefs.scatter_01_size_random

            if addon_prefs.scatter_01_countispersquare == True:
                ps.settings.count = addon_prefs.scatter_01_countpersquare * squarearea
            else:
                ps.settings.count = addon_prefs.scatter_01_count

            if addon_prefs.scatter_01_seed_is_random == True:
                bpy.context.object.particle_systems[name].seed = random.randint(0,10000)
            else:
                bpy.context.object.particle_systems[name].seed = addon_prefs.scatter_01_seed
            ps.settings.display_percentage = addon_prefs.scatter_01_display_percentage
            ps.settings.use_advanced_hair = True
            ps.settings.use_rotations = True
            ps.settings.rotation_factor_random = 1
            ps.settings.phase_factor = 1
            ps.settings.phase_factor_random = addon_prefs.scatter_01_phase_factor_random
            ### ### texture parameters
            bpy.ops.texture.new()
            texturename = name
            bpy.data.textures[-1].name = texturename
            bpy.data.textures[texturename].type = 'CLOUDS' #LATER SUPPORT OTHER NOISE TYPE AND OPTIONS
            if addon_prefs.scatter_01_noise_scaleisrandom == True:
                bpy.data.textures[texturename].noise_scale = round(random.uniform(addon_prefs.scatter_01_noise_scaleA,addon_prefs.scatter_01_noise_scaleB), 2)
            else:
                bpy.data.textures[texturename].noise_scale = addon_prefs.scatter_01_noise_scale

            if addon_prefs.scatter_01_noise_randomtext == True:
                bpy.data.textures[texturename].noise_depth = random.randint(0,5)
                bpy.data.textures[texturename].contrast = round(random.uniform(1.5,5),2)
                bpy.data.textures[texturename].intensity = round(random.uniform(0.3,0.9),2)
            else:
                bpy.data.textures[texturename].noise_depth = addon_prefs.scatter_01_noise_depth 
                bpy.data.textures[texturename].contrast = addon_prefs.scatter_01_contrast 
                bpy.data.textures[texturename].intensity = addon_prefs.scatter_01_intensity 

            ps.settings.texture_slots.add().texture = bpy.data.textures[texturename]
            ps.settings.texture_slots[0].blend_type = 'MULTIPLY'
            ps.settings.texture_slots[0].use_map_time = False
            ps.settings.texture_slots[0].use_map_density = True
            ps.settings.texture_slots[0].density_factor = addon_prefs.scatter_01_density_factor
            ps.settings.texture_slots[0].use_map_length = True 
            ps.settings.texture_slots[0].length_factor = addon_prefs.scatter_01_length_factor
            ps.settings.texture_slots[0].texture_coords = 'GLOBAL'
            
            if addon_prefs.scatter_01_size_is_random == False:
                ps.settings.texture_slots[0].scale[1] = addon_prefs.scatter_01_scalex
                ps.settings.texture_slots[0].scale[2] = addon_prefs.scatter_01_scaley
                ps.settings.texture_slots[0].scale[0] = addon_prefs.scatter_01_scalez
            else:
                randomn =round(random.uniform(addon_prefs.scatter_01_size_A,addon_prefs.scatter_01_size_B), 2)
                ps.settings.texture_slots[0].scale[1] = randomn
                ps.settings.texture_slots[0].scale[2] = randomn
                ps.settings.texture_slots[0].scale[0] = randomn

            if addon_prefs.scatter_01_offset_is_random == False:
                ps.settings.texture_slots[0].offset[1] = addon_prefs.scatter_01_offsetx
                ps.settings.texture_slots[0].offset[2] = addon_prefs.scatter_01_offsety
                ps.settings.texture_slots[0].offset[0] = addon_prefs.scatter_01_offsetz
            else:
                randomno =round(random.uniform(addon_prefs.scatter_01_offset_A,addon_prefs.scatter_01_offset_B), 2)
                ps.settings.texture_slots[0].offset[1] = randomno
                ps.settings.texture_slots[0].offset[2] = randomno
                ps.settings.texture_slots[0].offset[0] = randomno
            return {'FINISHED'}

        else:
            A.select_set(state=False)
            for ob in bpy.context.selected_objects:
                particlename = ob.name

                pref = "SCATTER: ["+slotname+"] ["+particlename+"] v."
                i = 1
                name = "%s%d" % (pref, i)
                while A.modifiers.get(name):    
                    i += 1
                    name = "%s%d" % (pref, i)
                ### ### add particle system + rename and add selection in new coll
                m = A.modifiers.new(name, type='PARTICLE_SYSTEM')

                ps = m.particle_system
                ps.name = name
                ps.settings.name = name
                ps.settings.type = 'HAIR'

                ps.settings.render_type = 'OBJECT'
                ps.settings.instance_object = bpy.data.objects[ob.name]
                
                ps.settings.particle_size = addon_prefs.scatter_01_particle_size
                ps.settings.hair_length = 4
                ps.settings.size_random = addon_prefs.scatter_01_size_random

                if addon_prefs.scatter_01_countispersquare == True:
                    ps.settings.count = addon_prefs.scatter_01_countpersquare * squarearea
                else:
                    ps.settings.count = addon_prefs.scatter_01_count

                if addon_prefs.scatter_01_seed_is_random == True:
                    bpy.context.object.particle_systems[name].seed = random.randint(0,10000)
                else:
                    bpy.context.object.particle_systems[name].seed = addon_prefs.scatter_01_seed
                ps.settings.display_percentage = addon_prefs.scatter_01_display_percentage
                ps.settings.use_advanced_hair = True
                ps.settings.use_rotations = True
                ps.settings.rotation_factor_random = 1
                ps.settings.phase_factor = 1
                ps.settings.phase_factor_random = addon_prefs.scatter_01_phase_factor_random
                ### ### texture parameters
                bpy.ops.texture.new()
                texturename = name
                bpy.data.textures[-1].name = texturename
                bpy.data.textures[texturename].type = 'CLOUDS' #LATER SUPPORT OTHER NOISE TYPE AND OPTIONS
                if addon_prefs.scatter_01_noise_scaleisrandom == True:
                    bpy.data.textures[texturename].noise_scale = round(random.uniform(addon_prefs.scatter_01_noise_scaleA,addon_prefs.scatter_01_noise_scaleB), 2)
                else:
                    bpy.data.textures[texturename].noise_scale = addon_prefs.scatter_01_noise_scale

                if addon_prefs.scatter_01_noise_randomtext == True:
                    bpy.data.textures[texturename].noise_depth = random.randint(0,5)
                    bpy.data.textures[texturename].contrast = round(random.uniform(1.5,5),2)
                    bpy.data.textures[texturename].intensity = round(random.uniform(0.3,0.9),2)
                else:
                    bpy.data.textures[texturename].noise_depth = addon_prefs.scatter_01_noise_depth 
                    bpy.data.textures[texturename].contrast = addon_prefs.scatter_01_contrast 
                    bpy.data.textures[texturename].intensity = addon_prefs.scatter_01_intensity 

                ps.settings.texture_slots.add().texture = bpy.data.textures[texturename]
                ps.settings.texture_slots[0].blend_type = 'MULTIPLY'
                ps.settings.texture_slots[0].use_map_time = False
                ps.settings.texture_slots[0].use_map_density = True
                ps.settings.texture_slots[0].density_factor = addon_prefs.scatter_01_density_factor
                ps.settings.texture_slots[0].use_map_length = True 
                ps.settings.texture_slots[0].length_factor = addon_prefs.scatter_01_length_factor
                ps.settings.texture_slots[0].texture_coords = 'GLOBAL'

                if addon_prefs.scatter_01_size_is_random == False:
                    ps.settings.texture_slots[0].scale[1] = addon_prefs.scatter_01_scalex
                    ps.settings.texture_slots[0].scale[2] = addon_prefs.scatter_01_scaley
                    ps.settings.texture_slots[0].scale[0] = addon_prefs.scatter_01_scalez
                else:
                    randomn =round(random.uniform(addon_prefs.scatter_01_size_A,addon_prefs.scatter_01_size_B), 2)
                    ps.settings.texture_slots[0].scale[1] = randomn
                    ps.settings.texture_slots[0].scale[2] = randomn
                    ps.settings.texture_slots[0].scale[0] = randomn

                if addon_prefs.scatter_01_offset_is_random == False:
                    ps.settings.texture_slots[0].offset[1] = addon_prefs.scatter_01_offsetx
                    ps.settings.texture_slots[0].offset[2] = addon_prefs.scatter_01_offsety
                    ps.settings.texture_slots[0].offset[0] = addon_prefs.scatter_01_offsetz
                else:
                    randomno =round(random.uniform(addon_prefs.scatter_01_offset_A,addon_prefs.scatter_01_offset_B), 2)
                    ps.settings.texture_slots[0].offset[1] = randomno
                    ps.settings.texture_slots[0].offset[2] = randomno
                    ps.settings.texture_slots[0].offset[0] = randomno

            A.select_set(state=True)
            return {'FINISHED'}

        return {'FINISHED'}

######################################################################################
######################################################################################
# # # # # # # # # # # #            N MENU                      # # # # # # # # # # # # 
######################################################################################
######################################################################################

class Scatter_PT_ScatteringPanel(bpy.types.Panel):
    bl_idname = "Scatter_PT_ScatteringPanel"
    bl_label = "Scatter"
    bl_category = "Scatter"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"

    def draw(self, context):
        layout = self.layout
        bigbutton = layout.column()
        bigbutton.scale_y=2
        bigbutton.operator('scatter.custom01', text="SCATTER",  icon='PLAY')

######################################################################################
######################################################################################
# # # # # # # # # # # #                 REG                    # # # # # # # # # # # # 
######################################################################################
######################################################################################

def register():
    bpy.utils.register_class(ScatterPref)
    bpy.utils.register_class(Scatter_OT_Custom01)
    bpy.utils.register_class(Scatter_PT_ScatteringPanel)

def unregister():
    bpy.utils.unregister_class(ScatterPref)
    bpy.utils.unregister_class(Scatter_OT_Custom01)
    bpy.utils.unregister_class(Scatter_PT_ScatteringPanel)


if __name__ == "__main__":
    register()
    #write ops to do on script exec