# ##### 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 #####

# Simple aggregate of particles / meshes
# Copy the selected objects on the active object
# Based on the position of the cursor and a defined volume
# Allows to control growth by using a Build modifier

bl_info = {
    "name": "Aggregate Mesh",
    "author": "liero",
    "version": (0, 0, 5),
    "blender": (2, 70, 0),
    "location": "View3D > Tool Shelf",
    "description": "Adds geometry to a mesh like in DLA aggregators",
    "category": "Object"}


import bpy
import bmesh
from random import (
        choice,
        gauss,
        seed,
        )
from mathutils import Matrix
from bpy.props import (
        BoolProperty,
        FloatProperty,
        IntProperty,
        )
from bpy.types import Operator


def use_random_seed(self):
    seed(self.rSeed)
    return


def rg(n):
    return (round(gauss(0, n), 2))


def remover(sel=False):
    bpy.ops.object.editmode_toggle()
    if sel:
        bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.remove_doubles(threshold=0.0001)
    bpy.ops.object.mode_set()


class OBJECT_OT_agregate_mesh(Operator):
    bl_idname = "object.agregate_mesh"
    bl_label = "Aggregate"
    bl_description = ("Adds geometry to a mesh like in DLA aggregators\n"
                      "Needs at least two selected Mesh objects")
    bl_options = {'REGISTER', 'UNDO', 'PRESET'}

    volX: FloatProperty(
            name="Volume X",
            min=0.1, max=25,
            default=3,
            description="The cloud around cursor"
            )
    volY: FloatProperty(
            name="Volume Y",
            min=0.1, max=25,
            default=3,
            description="The cloud around cursor"
            )
    volZ: FloatProperty(
            name="Volume Z",
            min=0.1, max=25,
            default=3,
            description="The cloud around cursor"
            )
    baseSca: FloatProperty(
            name="Scale",
            min=0.01, max=5,
            default=.25,
            description="Particle Scale"
            )
    varSca: FloatProperty(
            name="Var",
            min=0, max=1,
            default=0,
            description="Particle Scale Variation"
            )
    rotX: FloatProperty(
            name="Rot Var X",
            min=0, max=2,
            default=0,
            description="X Rotation Variation"
            )
    rotY: FloatProperty(
            name="Rot Var Y",
            min=0, max=2,
            default=0,
            description="Y Rotation Variation"
            )
    rotZ: FloatProperty(
            name="Rot Var Z",
            min=0, max=2,
            default=1,
            description="Z Rotation Variation"
            )
    rSeed: IntProperty(
            name="Random seed",
            min=0, max=999999,
            default=1,
            description="Seed to feed random values"
            )
    numP: IntProperty(
            name="Number",
            min=1,
            max=9999, soft_max=500,
            default=50,
            description="Number of particles"
            )
    nor: BoolProperty(
            name="Normal Oriented",
            default=False,
            description="Align Z axis with Faces normals"
            )
    cent: BoolProperty(
            name="Use Face Center",
            default=False,
            description="Center on Faces"
            )
    track: BoolProperty(
            name="Cursor Follows",
            default=False,
            description="Cursor moves as structure grows / more compact results"
            )
    anim: BoolProperty(
            name="Animatable",
            default=False,
            description="Sort faces so you can regrow with Build Modifier, materials are lost"
            )
    refresh: BoolProperty(
            name="Update",
            default=False
            )
    auto_refresh: BoolProperty(
            name="Auto",
            description="Auto update spline",
            default=False
            )

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

        if self.auto_refresh is False:
            self.refresh = False
        elif self.auto_refresh is True:
            self.refresh = True

        row.prop(self, "auto_refresh", toggle=True, icon="AUTO")
        row.prop(self, "refresh", toggle=True, icon="FILE_REFRESH")

        col = layout.column(align=True)
        col.separator()

        col = layout.column(align=True)
        col.prop(self, "volX", slider=True)
        col.prop(self, "volY", slider=True)
        col.prop(self, "volZ", slider=True)

        layout.label(text="Particles:")
        col = layout.column(align=True)
        col.prop(self, "baseSca", slider=True)
        col.prop(self, "varSca", slider=True)

        col = layout.column(align=True)
        col.prop(self, "rotX", slider=True)
        col.prop(self, "rotY", slider=True)
        col.prop(self, "rotZ", slider=True)

        col = layout.column(align=True)
        col.prop(self, "rSeed", slider=False)
        col.prop(self, "numP")

        row = layout.row(align=True)
        row.prop(self, "nor")
        row.prop(self, "cent")

        row = layout.row(align=True)
        row.prop(self, "track")
        row.prop(self, "anim")

    @classmethod
    def poll(cls, context):
        return (len(bpy.context.selected_objects) > 1 and
                bpy.context.object.type == 'MESH')

    def invoke(self, context, event):
        self.refresh = True
        return self.execute(context)

    def execute(self, context):
        if not self.refresh:
            return {'PASS_THROUGH'}

        scn = bpy.context.scene
        obj = bpy.context.active_object

        use_random_seed(self)

        mat = Matrix((
                (1, 0, 0, 0),
                (0, 1, 0, 0),
                (0, 0, 1, 0),
                (0, 0, 0, 1))
                )
        if obj.matrix_world != mat:
            self.report({'WARNING'},
                         "Please, Apply transformations to Active Object first")
            return{'FINISHED'}

        par = [o for o in bpy.context.selected_objects if o.type == 'MESH' and o != obj]
        if not par:
            return{'FINISHED'}

        bpy.ops.object.mode_set()
        bpy.ops.object.select_all(action='DESELECT')
        obj.select_set(True)
        msv = []

        for i in range(len(obj.modifiers)):
            msv.append(obj.modifiers[i].show_viewport)
            obj.modifiers[i].show_viewport = False

        cur = scn.cursor.location
        for i in range(self.numP):

            mes = choice(par).data
            newobj = bpy.data.objects.new('nuevo', mes)
            scn.objects.link(newobj)
            origen = (rg(self.volX) + cur[0], rg(self.volY) + cur[1], rg(self.volZ) + cur[2])

            cpom = obj.closest_point_on_mesh(origen)

            if self.cent:
                bm = bmesh.new()
                bm.from_mesh(obj.data)
                if hasattr(bm.verts, "ensure_lookup_table"):
                    bm.verts.ensure_lookup_table()
                    bm.faces.ensure_lookup_table()

                newobj.location = bm.faces[cpom[3]].calc_center_median()

                bm.free()
            else:
                newobj.location = cpom[1]

            if self.nor:
                newobj.rotation_mode = 'QUATERNION'
                newobj.rotation_quaternion = cpom[1].to_track_quat('Z', 'Y')
                newobj.rotation_mode = 'XYZ'
                newobj.rotation_euler[0] += rg(self.rotX)
                newobj.rotation_euler[1] += rg(self.rotY)
                newobj.rotation_euler[2] += rg(self.rotZ)
            else:
                newobj.rotation_euler = (rg(self.rotX), rg(self.rotY), rg(self.rotZ))

            newobj.scale = [self.baseSca + self.baseSca * rg(self.varSca)] * 3

            if self.anim:
                newobj.select_set(True)
                bpy.ops.object.make_single_user(type='SELECTED_OBJECTS', obdata=True)
                bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

                bme = bmesh.new()
                bme.from_mesh(obj.data)

                tmp = bmesh.new()
                tmp.from_mesh(newobj.data)

                for f in tmp.faces:
                    # z = len(bme.verts)
                    for v in f.verts:
                        bme.verts.new(list(v.co))
                    bme.faces.new(bme.verts[-len(f.verts):])

                bme.to_mesh(obj.data)
                remover(True)
                # Note: foo.user_clear() is deprecated use do_unlink=True instead
                bpy.data.meshes.remove(newobj.data, do_unlink=True)

            else:
                scn.objects.active = obj
                newobj.select_set(True)
                bpy.ops.object.join()

            if self.track:
                cur = scn.cursor.location = cpom[1]

        for i in range(len(msv)):
            obj.modifiers[i].show_viewport = msv[i]

        for o in par:
            o.select_set(True)

        obj.select_set(True)

        if self.auto_refresh is False:
            self.refresh = False

        return{'FINISHED'}


def register():
    bpy.utils.register_class(OBJECT_OT_agregate_mesh)


def unregister():
    bpy.utils.unregister_class(OBJECT_OT_agregate_mesh)


if __name__ == '__main__':
    register()
