IVY addon small changes

I didn’t have the opportunity to try your version of the addon yet. But I appreciate the fact that I won’t have to manually delete the previous version every time I make a change in the parameters. I use the addon to make trees (other than ivys) because it’s very easy to make the shape I want. For example, the trees below were made with the 2.79 version of the addon and its precious update button.

Doing the same thing in the latest version of Blender without having to manually delete 200 iterations is a huge time saver.

1 Like

oooh nice :smiley:

I wish you give a try to the modified addon just to see if it still works like this :slight_smile:

I give-up for today… a bit bored.
Am sorry to say python is really a shitty/obfuscated/bug-generator language… :confused:
I sincerely wish devs become serious and spend the necessary time to port all the crap to some formal, typed, explicit language…
Hope is not reality :confused:

Happy blending !

EDIT: Laugh my azz off :rofl: python does not support function overloading !!!
It’s good to know, but so funny to see this pretending language saying win7 is obsolete and offers features from the 70’s XD
As if Metallica sung My Blue suede shoes :rofl:

I gave your version a try and it works just like in 2.79, with the benefit of not having to click the update button.

I don’t think the devs will change the scripting language, unless Python dies. Choosing another language would cause the modification of a large portion of Blender’s code. But imagine that you could change the scripting language, which one would you choose ?

I’m glad the addon works like in 2.79 :slight_smile:
And i’m also ( VERY ) glad when you change parameters, ivy changes without the need of clicking the update button :smiley: This was the first thing i wanted.

For python, yes i know all the blender UI and some other parts are made in python. And for this reason, a very bad choice that has been done long ago must be kept as is.
Just pray for python to die :stuck_out_tongue:
I spent long years in my life and career, learning ( and also teaching ) computer languages from Z80 or x86 asm to C# on various systems and sincerely among all those, python is the worst language i met for many reasons.

If i could choose a new language I’d choose one that is deeply typed and explicit ( therefore comprehensive and dev-friendly ) , that prevents dev from making bugs ( as far as possible ), widely used and documented with examples, performant, cross platform and object-oriented ( though this later part is not necessarily mandatory ).
I think about C# but there are probably others.
Javascript is almost the same as C# but is too allowing for non typed members and methods.

I wish i had time ( and python and blender API knowledge ) to do the port on myself. I think the fundamental porting of low level things would be a discouraging pain but imagine the ease of dev it would be for addons !
The community would not only use blender ! it would make it grow :stuck_out_tongue:

I don’t know the ‘features’ of the C-written core of blender. I don’t even know wether it is able to compile as standalone. But starting from this and grow a C# API should be fun and give amazing things…
well… dreams… :relaxed:

Thanks for you answer @Akikun :slight_smile:
I be back soon with some more features i think ( ability to create and edit multiple ivys ).

Next step, i’ll talk about leaves instancing from a collection as ivy leaves are more than shitty and rising particles from leaves do no allow texture baking ( fault of blender i guess… )

Happy blending !

I have been experimenting the addon and found that the best way to work with the leaves is to make tem so small that they almost cannot be seen, so they can be used as an anchor point for the particles “the real leaves” growing out from those points.

If the faces of the leaves are too big there is a big chance that we end with a lot of leaves floating in the air.

I know that from a certain distance it doesn’t make a big difference, but not all the renders are supposed to be made from far away.

Yep you got it right :slight_smile: this is exactly what i did and it works pretty fine :slight_smile:

also mostly because blender draws the particle mesh emitter even when disabled the “don’t show emitter” toggle…

However, this don’t solve the particle bake problem i got :confused:
I guess the real serious solution will come when i’m able to replace each leaf with a mesh from a collection…
Till this, all will be usual sh*tty blender solution :rofl:

for now i got a problem with active objects… that look bugged…
I got to find a sh*tty-hack-python-compliant to make this work :confused:

Happy blending !

I just left particles away for that purpose since geometry nodes came to the scene.

It is much better for that and I am not sure if you cannot bake using geometry nodes. I have never tried.

The fact is that the level of control you have over the instances using geometry nodes is by far superior comparing to the particles system.

1 Like

I think you are pretty right about GN :slight_smile:

I know this very few and with my sh*tty python competences i doubt i can link the ivy script with GN :confused:
So my 1st bet for the moment and for what i need will be a tryout with collections for leaves :slight_smile:

Well, if you can solve the problem os the leaves orientations, sizes and randomization there is no reason to care about the geometry nodes.

I only think that particles have particular limitations when you work with ivys applied to objects different from walls because of the orientation of the leaves. It works ok on a wall because you just need to be sure that the leaves grow in a way they don’t colide with the wall mesh.

But if you try to use the same method for a statue, as an example, then we have problems.

Other case are the leaves that grow over the top of the wall. They will follow the same orientation axis as the rest of them. From some angles it is not a problem but from others it can be.

That is why I still prefer to use the leaves as a base for the GN.

Well well…

I’m really unhappy with the code i write ! :face_with_symbols_over_mouth:
Python is a real sh*t, blender API is a messy hell and my under-competences in both are awesome, so don’t expect this code to be clear, efficient and bug-free :stuck_out_tongue:

Nevertheless… it appears it works not too bad.

So here are the main features, in front of the standard ivy addon:

  • change ivy in live by just changing parameters.
  • select ivys branches object and edit the selected ivy ( this allows to have multiple ivys in scene and still having hair on the head :rofl: )

So here’s the code that i tried to comment for comprehension purpose:

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

bl_info = {
    "name": "IvyGen",
    "author": "testscreenings, PKHG, TrumanBlending, some Blenderartists users",
    "version": (0, 1, 6),
    "blender": (2, 80, 0),
    "location": "View3D > Sidebar > Ivy Generator (Create Tab)",
    "description": "Adds generated ivy to a mesh object starting "
                   "at the 3D cursor",
    "warning": "",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/ivy_gen.html",
    "category": "Add Curve",
}


import bpy
from bpy.types import (
    Operator,
    Panel,
    PropertyGroup,
)
from bpy.props import (
    BoolProperty,
    FloatProperty,
    IntProperty,
    PointerProperty,
)
from mathutils.bvhtree import BVHTree
from mathutils import (
    Vector,
    Matrix,
)
from collections import deque
from math import (
    pow, cos,
    pi, atan2,
)
from random import (
    random as rand_val,
    seed as rand_seed,
)
import time
import mathutils
from rna_prop_ui import rna_idprop_ui_prop_get


#====================================================
#=                                                  =
#= Adds a custom property to an object              =
#=                                                  =
#= IN: - object to add the property to
#=     - property name
#=     - property value
#=                                                  
#= OUT: nothing
#=                                                  =
#====================================================
def AddIntPropertyToObject(object, propertyName:str, propertyVal:int, toolTip:str="DO NOT DELETE THIS INT") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddFloatPropertyToObject(object, propertyName:str, propertyVal:float, toolTip:str="DO NOT DELETE THIS FLOAT") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddStringPropertyToObject(object, propertyName:str, propertyVal:str, toolTip:str="DO NOT DELETE THIS STRING") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
def AddBoolPropertyToObject(object, propertyName:str, propertyVal:bool, toolTip:str="DO NOT DELETE THIS BOOL") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
    # python is such a morons shit that it don't support function overloading ! LOOOOOL !!!!!! let's get back to 70's
def AddVectorPropertyToObject(object, propertyName:str, propertyVal:Vector, toolTip:str="DO NOT DELETE THIS VECTOR") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
    
    
#========================================================================================================    



def createIvyGeometry(IVY, growLeaves:bool, growingSupportObject):
    
    
    
    """Create the curve geometry for IVY"""
    # Compute the local size and the gauss weight filter
    # local_ivyBranchSize = IVY.ivyBranchSize  # * radius * IVY.ivySize
    gaussWeight = (1.0, 2.0, 4.0, 7.0, 9.0, 10.0, 9.0, 7.0, 4.0, 2.0, 1.0)

    # Create a new curve and initialise it
    curve = bpy.data.curves.new("IVY", type='CURVE')
    curve.dimensions = '3D'
    curve.bevel_depth = 1
    curve.fill_mode = 'FULL'
    curve.resolution_u = 4

    if growLeaves:
        # Create the ivy leaves
        # Order location of the vertices
        signList = ((-1.0, +1.0),
                    (-1.0, -1.0),
                    (+1.0, -1.0),
                    (+1.0, +1.0),
                    )

        # Get the local size
        # local_ivyLeafSize = IVY.ivyLeafSize  # * radius * IVY.ivySize

        # Initialise the vertex and face lists
        vertList = deque()

        # Store the methods for faster calling
        addV = vertList.extend
        rotMat = Matrix.Rotation

    # Loop over all roots to generate its nodes
    for root in IVY.ivyRoots:
        # Only grow if more than one node
        numNodes = len(root.ivyNodes)
        if numNodes > 1:
            # Calculate the local radius
            local_ivyBranchRadius = 1.0 / (root.parents + 1) # Cyril  +1.0
            prevIvyLength = 1.0 / root.ivyNodes[-1].length
            splineVerts = [ax for n in root.ivyNodes for ax in n.pos.to_4d()]

            radiusConstant = local_ivyBranchRadius * IVY.ivyBranchSize
            splineRadii = [radiusConstant * (1.3 - n.length * prevIvyLength)
                           for n in root.ivyNodes]

            # Add the poly curve and set coords and radii
            newSpline = curve.splines.new(type='POLY')
            newSpline.points.add(len(splineVerts) // 4 - 1)
            newSpline.points.foreach_set('co', splineVerts)
            newSpline.points.foreach_set('radius', splineRadii)

            # Loop over all nodes in the root
            for i, n in enumerate(root.ivyNodes):
                for k in range(len(gaussWeight)):
                    idx = max(0, min(i + k - 5, numNodes - 1))
                    n.smoothAdhesionVector += (gaussWeight[k] *
                                               root.ivyNodes[idx].adhesionVector)
                n.smoothAdhesionVector /= 56.0
                n.adhesionLength = n.smoothAdhesionVector.length
                n.smoothAdhesionVector.normalize()

                if growLeaves and (i < numNodes - 1):
                    node = root.ivyNodes[i]
                    nodeNext = root.ivyNodes[i + 1]

                    # Find the weight and normalize the smooth adhesion vector
                    weight = pow(node.length * prevIvyLength, 0.7)

                    # Calculate the ground ivy and the new weight
                    groundIvy = max(0.0, -node.smoothAdhesionVector.z)
                    weight += groundIvy * pow(1 - node.length *
                                                              prevIvyLength, 2)

                    # Find the alignment weight
                    alignmentWeight = node.adhesionLength

                    # Calculate the needed angles
                    phi = atan2(node.smoothAdhesionVector.y,
                                node.smoothAdhesionVector.x) - pi / 2.0

                    theta = (0.5 *
                        node.smoothAdhesionVector.angle(Vector((0, 0, -1)), 0))

                    # Find the size weight
                    sizeWeight = 1.5 - (cos(2 * pi * weight) * 0.5 + 0.5)

                    # Randomise the angles
                    phi += (rand_val() - 0.5) * (1.3 - alignmentWeight)
                    theta += (rand_val() - 0.5) * (1.1 - alignmentWeight)

                    # Calculate the leaf size an append the face to the list
                    leafSize = IVY.ivyLeafSize * sizeWeight

                    for j in range(10):
                        # Generate the probability
                        probability = rand_val()

                        # If we need to grow a leaf, do so
                        # cyril if (probability * weight) > IVY.leafProbability:
                        if (probability * (1.0-IVY.leafDensityFromHeight*(1.0-weight))) > IVY.leafProbability:

                            # Generate the random vector
                            randomVector = Vector((rand_val() - 0.5,
                                                   rand_val() - 0.5,
                                                   rand_val() - 0.5,
                                                   ))

                            # Find the leaf center
                            center = (node.pos.lerp(nodeNext.pos, j / 10.0) +
                                               IVY.ivyLeafSize * randomVector)

                            # For each of the verts, rotate/scale and append
                            basisVecX = Vector((1, 0, 0))
                            basisVecY = Vector((0, 1, 0))

                            horiRot = rotMat(theta, 3, 'X')
                            vertRot = rotMat(phi, 3, 'Z')

                            basisVecX.rotate(horiRot)
                            basisVecY.rotate(horiRot)

                            basisVecX.rotate(vertRot)
                            basisVecY.rotate(vertRot)

                            basisVecX *= leafSize
                            basisVecY *= leafSize

                            addV([k1 * basisVecX + k2 * basisVecY + center for
                                                           k1, k2 in signList])

    # Add the object and link to scene
    newCurve = bpy.data.objects.new("IVY_Curve", curve)
    bpy.context.collection.objects.link(newCurve)
    
    if growLeaves:
        faceList = [[4 * i + l for l in range(4)] for i in
                    range(len(vertList) // 4)]

        # Generate the new leaf mesh and link
        me = bpy.data.meshes.new('IvyLeaf')
        me.from_pydata(vertList, [], faceList)
        me.update(calc_edges=True)
        ob = bpy.data.objects.new('IvyLeaf', me)
        bpy.context.collection.objects.link(ob)

        me.uv_layers.new(name="Leaves")

        # Set the uv texture coords
        # TODO, this is non-functional, default uvs are ok?
        '''
        for d in tex.data:
            uv1, uv2, uv3, uv4 = signList
        '''

        # parent the leaves to the branches and tag it as 'is_an_ivy'
        ob.parent = newCurve
        AddFloatPropertyToObject(ob,'IVY_IsSelf', 0.0)
        
    # And now, as a final step, lets parent the ivy to the object it was grown on.
    # this will allow to reselect this mesh for edition....
    newCurve.parent = growingSupportObject

    # return the branches 3D object
    return(newCurve)



class IvyNode:
    """ The basic class used for each point on the ivy which is grown."""
    __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength',
                 'smoothAdhesionVector', 'length', 'floatingLength', 'climb')

    def __init__(self):
        self.pos = Vector((0, 0, 0))
        self.primaryDir = Vector((0, 0, 1))
        self.adhesionVector = Vector((0, 0, 0))
        self.smoothAdhesionVector = Vector((0, 0, 0))
        self.length = 0.0001
        self.floatingLength = 0.0
        self.climb = True


class IvyRoot:
    """ The class used to hold all ivy nodes growing from this root point."""
    __slots__ = ('ivyNodes', 'alive', 'parents')

    def __init__(self):
        self.ivyNodes = deque()
        self.alive = True
        self.parents = 0


class Ivy:
    """ The class holding all parameters and ivy roots."""
    """
    __slots__ = ('ivyRoots', 'primaryWeight', 'randomWeight',
                 'gravityWeight', 'adhesionWeight', 'branchingProbability',
                 'leafProbability', 'leafDensityFromHeight', 'ivySize', 'ivyLeafSize', 'ivyBranchSize',
                 'maxFloatLength', 'maxAdhesionDistance', 'maxLength')
                 """

    def __init__(self,
                 primaryWeight=0.5,
                 randomWeight=0.2,
                 gravityWeight=1.0,
                 adhesionWeight=0.1,
                 branchingProbability=0.05,
                 leafProbability=0.35,
                 leafDensityFromHeight=0.35,
                 ivySize=0.02,
                 ivyLeafSize=0.02,
                 ivyBranchSize=0.001,
                 maxFloatLength=0.5,
                 maxAdhesionDistance=1.0):



        self.ivyRoots = deque()
        self.primaryWeight = primaryWeight
        self.randomWeight = randomWeight
        self.gravityWeight = gravityWeight
        self.adhesionWeight = adhesionWeight
        self.branchingProbability = 1 - branchingProbability
        self.leafProbability = 1 - leafProbability
        self.leafDensityFromHeight = leafDensityFromHeight
        self.ivySize = ivySize
        self.ivyLeafSize = ivyLeafSize
        self.ivyBranchSize = ivyBranchSize
        self.maxFloatLength = maxFloatLength
        self.maxAdhesionDistance = maxAdhesionDistance
        self.maxLength = 0.0

        # Normalize all the weights only on initialisation
        sums = self.primaryWeight + self.randomWeight + self.adhesionWeight
        self.primaryWeight /= sums
        self.randomWeight /= sums
        self.adhesionWeight /= sums

    def seed(self, seedPos):
        # Seed the Ivy by making a new root and first node
        tmpRoot = IvyRoot()
        tmpIvy = IvyNode()
        tmpIvy.pos = seedPos

        tmpRoot.ivyNodes.append(tmpIvy)
        self.ivyRoots.append(tmpRoot)

    def grow(self, ob, bvhtree):
        # Determine the local sizes
        # local_ivySize = self.ivySize	# * radius
        # local_maxFloatLength = self.maxFloatLength  # * radius
        # local_maxAdhesionDistance = self.maxAdhesionDistance	# * radius

        for root in self.ivyRoots:
            # Make sure the root is alive, if not, skip
            if not root.alive:
                continue

            # Get the last node in the current root
            prevIvy = root.ivyNodes[-1]

            # If the node is floating for too long, kill the root
            if prevIvy.floatingLength > self.maxFloatLength:
                root.alive = False

            # Set the primary direction from the last node
            primaryVector = prevIvy.primaryDir

            # Make the random vector and normalize
            randomVector = Vector((rand_val() - 0.5, rand_val() - 0.5,
                                   rand_val() - 0.5)) + Vector((0, 0, 0.2))
            randomVector.normalize()

            # Calculate the adhesion vector
            adhesionVector = adhesion(
                    prevIvy.pos, bvhtree, self.maxAdhesionDistance)

            # Calculate the growing vector
            growVector = self.ivySize * (primaryVector * self.primaryWeight +
                                          randomVector * self.randomWeight +
                                          adhesionVector * self.adhesionWeight)

            # Find the gravity vector
            gravityVector = (self.ivySize * self.gravityWeight *
                                                            Vector((0, 0, -1)))
            gravityVector *= pow(prevIvy.floatingLength / self.maxFloatLength,
                                 0.7)

            # Determine the new position vector
            newPos = prevIvy.pos + growVector + gravityVector

            # Check for collisions with the object
            climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)

            # Update the growing vector for any collisions
            growVector = newPos - prevIvy.pos - gravityVector
            growVector.normalize()

            # Create a new IvyNode and set its properties
            tmpNode = IvyNode()
            tmpNode.climb = climbing
            tmpNode.pos = newPos
            tmpNode.primaryDir = prevIvy.primaryDir.lerp(growVector, 0.5)
            tmpNode.primaryDir.normalize()
            tmpNode.adhesionVector = adhesionVector
            tmpNode.length = prevIvy.length + (newPos - prevIvy.pos).length

            if tmpNode.length > self.maxLength:
                self.maxLength = tmpNode.length

            # If the node isn't climbing, update it's floating length
            # Otherwise set it to 0
            if not climbing:
                tmpNode.floatingLength = prevIvy.floatingLength + (newPos -
                                                            prevIvy.pos).length
            else:
                tmpNode.floatingLength = 0.0

            root.ivyNodes.append(tmpNode)

        # Loop through all roots to check if a new root is generated
        for root in self.ivyRoots:
            # Check the root is alive and isn't at high level of recursion
            if (root.parents > 3) or (not root.alive):
                continue

            # Check to make sure there's more than 1 node
            if len(root.ivyNodes) > 1:
                # Loop through all nodes in root to check if new root is grown
                for node in root.ivyNodes:
                    # Set the last node of the root and find the weighting
                    prevIvy = root.ivyNodes[-1]
                    weight = 1.0 - (cos(2.0 * pi * node.length /
                                        prevIvy.length) * 0.5 + 0.5)

                    probability = rand_val()

                    # Check if a new root is grown and if so, set its values
                    if (probability * weight > self.branchingProbability):
                        tmpNode = IvyNode()
                        tmpNode.pos = node.pos
                        tmpNode.floatingLength = node.floatingLength

                        tmpRoot = IvyRoot()
                        tmpRoot.parents = root.parents + 1

                        tmpRoot.ivyNodes.append(tmpNode)
                        self.ivyRoots.append(tmpRoot)
                        return


def adhesion(loc, bvhtree, max_l):
    # Compute the adhesion vector by finding the nearest point
    nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
    adhesion_vector = Vector((0.0, 0.0, 0.0))
    if nearest_location is not None:
        # Compute the distance to the nearest point
        adhesion_vector = nearest_location - loc
        distance = adhesion_vector.length
        # If it's less than the maximum allowed and not 0, continue
        if distance:
            # Compute the direction vector between the closest point and loc
            adhesion_vector.normalize()
            adhesion_vector *= 1.0 - distance / max_l
            # adhesion_vector *= getFaceWeight(ob.data, nearest_result[3])
    return adhesion_vector


def collision(bvhtree, pos, new_pos):
    # Check for collision with the object
    climbing = False

    corrected_new_pos = new_pos
    direction = new_pos - pos

    hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
    # If there's a collision we need to check it
    if hit_location is not None:
        # Check whether the collision is going into the object
        if direction.dot(hit_normal) < 0.0:
            reflected_direction = (new_pos - hit_location).reflect(hit_normal)

            corrected_new_pos = hit_location + reflected_direction
            climbing = True

    return climbing, corrected_new_pos


def bvhtree_from_object(ob):
    import bmesh
    bm = bmesh.new()

    depsgraph = bpy.context.evaluated_depsgraph_get()
    ob_eval = ob.evaluated_get(depsgraph)
    mesh = ob_eval.to_mesh()
    bm.from_mesh(mesh)
    bm.transform(ob.matrix_world)

    bvhtree = BVHTree.FromBMesh(bm)
    ob_eval.to_mesh_clear()
    return bvhtree

def check_mesh_faces(ob):
    me = ob.data
    if len(me.polygons) > 0:
        return True

    return False


class IvyGen(Operator):
    
    
    bl_idname = "curve.ivy_gen"
    bl_label = "IvyGen"
    bl_description = "Generate Ivy on an Mesh Object"
    bl_options = {'REGISTER', 'UNDO'}

    resetIvy: BoolProperty(
        name="Reset values",
        description="Reset all values to default",
        default=False
    )
    

    
    '''
    # not sure but it seems this shit is useless...
    @classmethod
    def poll(self, context):
        # Check if there's an object and whether it's a mesh
        return(True)
    '''

    # called when user clicks on Add new ivy in N-Panel
    def invoke(self, context, event):
        return(self.execute(context))


    def execute(self, context):

        wm = context.window_manager.ivy_gen_props
        
        print("expected ivy len = ",wm.totalIvyLength)
        
        
        if self.resetIvy:
            wm.totalIvyLength = 1.0 # i don't know how to retrieve the default values set in properties :-/
            wm.randomSeed = 0.0
            wm.ivySize = 0.02
            wm.maxFloatLength = 0.5
            wm.maxAdhesionDistance = 1.0
            wm.primaryWeight = 0.5
            wm.randomWeight = 0.2
            wm.gravityWeight = 1.0
            wm.adhesionWeight = 0.1
            wm.branchingProbability = 0.05
            wm.leafProbability = 0.35
            wm.leafDensityFromHeight = 0.35
            wm.ivyBranchSize = 0.001
            wm.ivyLeafSize = 0.02
            
        

        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

        # Get the selected object if we gonna create an ivy.....
        if(context.window_manager.ivy_gen_props.allowIvyCreation):
            # Get the growing support object. this object must be kept selected and active
            supportObject = context.active_object
            
        else: 
            # if we want to edit an ivy instead....
            if(context.window_manager.ivy_gen_props.allowIvyEdition):
                
                # active object is the ivy branches
                if(context.active_object.parent is None):
                    print("Ivy has no growing support. It cannot be edited")
                    
                    return {'PASS_THROUGH'} 
                else:
                    selectedIvyBranches = context.active_object
                    
                    # Retrieve the seed point and bring the 3D cursor on it
                    # the seed point is the 1st curve point
                    context.scene.cursor.location = selectedIvyBranches["IVY_StartPos"]
                    
                    # And also retrieve the growing support object
                    supportObject = context.active_object.parent
                    
                    # now delete branches and leaves as they will be recreated                    
                    selectedIvyBranches.select_set(True)
                    for child in selectedIvyBranches.children:
                        child.select_set(True)
                        
                    bpy.ops.object.delete()
                                        
            else:
                # unknown state: no edition and no creation. We might have selected leaves....
                return {'PASS_THROUGH'} 
                

                        
            
        bvhtree = bvhtree_from_object(supportObject)

        # Check if the mesh has at least one polygon since some functions
        # are expecting them in the object's data (see T51753)
        check_face = check_mesh_faces(supportObject)
        if check_face is False:
            self.report({'WARNING'},
                        "Mesh Object doesn't have at least one Face. "
                        "Operation Cancelled")
            return {"CANCELLED"}

        # Fix the random seed
        rand_seed(wm.randomSeed)

        # Make the new ivy
        New_ivy = Ivy(wm.primaryWeight,
                      wm.randomWeight,
                      wm.gravityWeight,
                      wm.adhesionWeight,
                      wm.branchingProbability,
                      wm.leafProbability,
                      wm.leafDensityFromHeight,
                      wm.ivySize,
                      wm.ivyLeafSize,
                      wm.ivyBranchSize,
                      wm.maxFloatLength,
                      wm.maxAdhesionDistance)
                      
                      
        # Generate first root and node at the 3D cursor location
        New_ivy.seed(context.scene.cursor.location)

        maxLength = wm.totalIvyLength  # * radius

        # If we need to check time set the flag
        checkTime = False
        if(context.window_manager.ivy_gen_props.maxTime != 0.0):
            checkTime = True

        t = time.time()
        startPercent = 0.0
        checkAliveIter = [True, ]

        # Grow until 200 roots is reached or backup counter exceeds limit
        while (any(checkAliveIter) and (New_ivy.maxLength < maxLength) and (not checkTime or (time.time() - t < context.window_manager.ivy_gen_props.maxTime))):
            # Grow the ivy for this iteration
            New_ivy.grow(supportObject, bvhtree)

            # Print the proportion of ivy growth to console
            if (New_ivy.maxLength / maxLength * 100) > 10 * startPercent // 10:
                #print('%0.2f%% of Ivy nodes have grown' % (New_ivy.maxLength / maxLength * 100))
                startPercent += 10

            # Make an iterator to check if all are alive
            checkAliveIter = (r.alive for r in New_ivy.ivyRoots)

        # Create the curve and leaf geometry
        Ivy3DObject = createIvyGeometry(New_ivy, wm.growLeaves,supportObject)
        
        # and add Custom Properties for param memorization for future edition
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IsSelf', 0.123)
        AddVectorPropertyToObject(Ivy3DObject,'IVY_StartPos',context.scene.cursor.location)
        
        AddIntPropertyToObject(Ivy3DObject,'IVY_RandSeed',wm.randomSeed)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_TotalIvyLength',wm.totalIvyLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvySegmentSize',wm.ivySize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxFloatLength',wm.maxFloatLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxAdhesionDistance',wm.maxAdhesionDistance)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_PrimaryWeight',wm.primaryWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_RandomWeight',wm.randomWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_GravityWeight',wm.gravityWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_AdhesionWeight',wm.adhesionWeight)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_BranchingProbability',wm.branchingProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyBranchSize',wm.ivyBranchSize)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyLeafSize',wm.ivyLeafSize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafProbability',wm.leafProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafDensityFromHeight',wm.leafDensityFromHeight)
        AddBoolPropertyToObject(Ivy3DObject,'IVY_GrowLeaves',wm.growLeaves)

        
        
        print("Ivy generated in %0.2f s ( but we don't care )" % (time.time() - t))
       
                
        if(context.window_manager.ivy_gen_props.allowIvyEdition):
            # if we are in ivy edition mode then lets reselect the ivy branches
            #if(context.window_manager.ivy_gen_props.allowIvyEdition):
            for ob in bpy.context.selected_objects:
                ob.select_set(False)
                
            
            Ivy3DObject.select_set(True)
            
            bpy.context.view_layer.objects.active = Ivy3DObject
            
        # not sure this is usefull.....    
        context.window_manager.ivy_gen_props.allowIvyCreation = False
        context.window_manager.ivy_gen_props.allowIvyEdition = False
        
        self.resetIvy = False

        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        #wm = context.window_manager
        wm = context.window_manager.ivy_gen_props
        col = layout.column(align=True)

        layout.prop(self, "resetIvy", icon="BACK")
        
        col = layout.column(align=True)
        col.label(text="Generation Settings:")
        col.prop(wm, "randomSeed")

        col = layout.column(align=True)
        col.label(text="Size Settings:")
        col.prop(wm, "totalIvyLength")
        col.prop(wm, "ivySize")
        col.prop(wm, "maxFloatLength")
        col.prop(wm, "maxAdhesionDistance")

        col = layout.column(align=True)
        col.label(text="Weight Settings:")
        col.prop(wm, "primaryWeight")
        col.prop(wm, "randomWeight")
        col.prop(wm, "gravityWeight")
        col.prop(wm, "adhesionWeight")

        col = layout.column(align=True)
        col.label(text="Branch Settings:")
        col.prop(wm, "branchingProbability")
        col.prop(wm, "ivyBranchSize")

        col = layout.column(align=True)
        col.prop(wm, "growLeaves")

        if wm.growLeaves:
            col = layout.column(align=True)
            col.label(text="Leaf Settings:")
            col.prop(wm, "ivyLeafSize")
            col.prop(wm, "leafProbability")
            col.prop(wm, "leafDensityFromHeight")
        


class Ivy_N_Panel(Panel):
    bl_label = "Ivy Generator"
    bl_idname = "CURVE_PT_IvyNPanel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Create"
    bl_context = "objectmode"
    bl_options = {"DEFAULT_CLOSED"}

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

        wm = context.window_manager.ivy_gen_props

        ob = bpy.context.view_layer.objects.active # here active object is properly retrieved
        


        # by default, disable ivy edition and creation
        wm.allowIvyEdition = False
        wm.allowIvyCreation = False

        # if an object is selected....                    
        if(ob is not None):
            # if object is an ivy, disable ivy creation and allow edition ( only on branches selection )
            if(ob.get('IVY_IsSelf') is not None):
                #print("object is an ivy !!!!!")
                
                if(ob['IVY_IsSelf']==0.123):
                    print("Is branches and can be edited")
                    
                    # here we grab the ivy instance and its parameters
                    # and we enable edition
                    wm.allowIvyEdition = True
                    
                    selectedIvyBranches = ob
                    
                    # retrieve all custom properties from ivy branches and set up values in the window controls
                    wm.randomSeed            = selectedIvyBranches['IVY_RandSeed']
                    wm.totalIvyLength        = selectedIvyBranches['IVY_TotalIvyLength']
                    wm.ivySize               = selectedIvyBranches['IVY_IvySegmentSize']
                    wm.maxFloatLength        = selectedIvyBranches['IVY_MaxFloatLength']
                    wm.maxAdhesionDistance   = selectedIvyBranches['IVY_MaxAdhesionDistance']
                    wm.primaryWeight         = selectedIvyBranches['IVY_PrimaryWeight']
                    wm.randomWeight          = selectedIvyBranches['IVY_RandomWeight']
                    wm.gravityWeight         = selectedIvyBranches['IVY_GravityWeight']
                    wm.adhesionWeight        = selectedIvyBranches['IVY_AdhesionWeight']
                    wm.branchingProbability  = selectedIvyBranches['IVY_BranchingProbability']
                    wm.ivyBranchSize         = selectedIvyBranches['IVY_IvyBranchSize']
                    wm.ivyLeafSize           = selectedIvyBranches['IVY_IvyLeafSize']
                    wm.leafProbability       = selectedIvyBranches['IVY_LeafProbability']
                    wm.leafDensityFromHeight = selectedIvyBranches['IVY_LeafDensityFromHeight']
                    wm.growLeaves            = selectedIvyBranches['IVY_GrowLeaves']                    
                    
                else:
                    print("Is leaves and can NOT be edited. Please select branches to enable edition")
              
            else:
                #object is not an ivy: ivy can be created on it if it is an object that have a mesh
                if((ob.type == 'MESH') and (context.mode == 'OBJECT')):
                    wm.allowIvyCreation = True
            


        

        if(wm.allowIvyCreation):
            prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE")
        else:        
            if(wm.allowIvyEdition):
                prop_def = col.operator("curve.ivy_gen", text="Edit this ivy", icon="CURVE_DATA")
            
        
        
        col = layout.column(align=True)
        col.label(text="Generation Time limit:")
        col.prop(wm, "maxTime")

            


class IvyGenProperties(PropertyGroup):

    
    # values that are used as flags for control, and that are not editable
    allowIvyEdition: BoolProperty(
        name="allowIvyEdition",
        description="allow selected ivy edition",
        default=False
    )

    allowIvyCreation: BoolProperty(
        name="allowIvyCreation",
        description="allow ivy creation on selected object",
        default=False
    )
    # ===========================================================================
            
    # value set in Left N panel
    maxTime: FloatProperty(
        name="Maximum Time",
        description="The maximum time to run the generation for "
                    "in seconds generation (0.0 = Disabled)",
        default=20.0,
        min=0.0,
        soft_max=10
    )
    # ===========================================================================

                    
                    
    # values set in parameters bottom-left panel
    randomSeed: IntProperty(
        name="Random Seed",
        description="The seed governing random generation",
        default=0,
            min=0
        )
    # --------------------------------------------------------
    totalIvyLength: FloatProperty(
        name="Total Ivy Length",
        description="Maximum ivy length in Blender Units",
        default=1.0,
        min=0.0,
        soft_max=3.0,
        subtype='DISTANCE',
        unit='LENGTH',
        precision=3,
        step=10.0
        )
    # --------------------------------------------------------
    ivySize: FloatProperty(
        name="Branch segment Length",
        description="The length of an ivy segment"
                    " Units",
        default=0.02,
        min=0.005,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    maxFloatLength: FloatProperty(
        name="Max Float Length",
        description="The maximum distance that a branch "
                    "can live while floating",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    maxAdhesionDistance: FloatProperty(
        name="Max Adhesion Length",
        description="The maximum distance that a branch "
                    "will feel the effects of adhesion",
        default=1.0,
        min=0.0,
        soft_max=2.0,
        precision=2
    )
    # --------------------------------------------------------
    primaryWeight: FloatProperty(
        name="Primary Weight",
        description="Weighting given to the current direction",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    randomWeight: FloatProperty(
        name="Random Weight",
        description="Weighting given to the random direction",
        default=0.2,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    gravityWeight: FloatProperty(
        name="Gravity Weight",
        description="Weighting given to the gravity direction",
        default=1.0,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    adhesionWeight: FloatProperty(
        name="Adhesion Weight",
        description="Weighting given to the adhesion direction",
        default=0.1,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    branchingProbability: FloatProperty(
        name="Branching Probability",
        description="Probability of a new branch forming",
        default=0.05,
        min=0.0,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    ivyBranchSize: FloatProperty(
        name="Branch start diameter",
        description="The size of the ivy branches",
        default=0.001,
        min=0.0,
        soft_max=0.1,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    ivyLeafSize: FloatProperty(
        name="Leaf Size",
        description="The size of the ivy leaves",
        default=0.02,
        min=0.0,
        soft_max=0.5,
        precision=3
    )
    # --------------------------------------------------------
    leafProbability: FloatProperty(
        name="Leaf Probability",
        description="Probability of a leaf forming",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    leafDensityFromHeight: FloatProperty(
        name="leaf density/height fact.",
        description="defines wether leaf density is controlled by branches height or not.",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    growLeaves: BoolProperty(
        name="Grow Leaves",
        description="Grow leaves or not",
        default=True
    )
    # --------------------------------------------------------


    


classes = (
    IvyGen,
    IvyGenProperties,
    Ivy_N_Panel
)



def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.WindowManager.ivy_gen_props = PointerProperty(type=IvyGenProperties)


def unregister():
    del bpy.types.WindowManager.ivy_gen_props

    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()



To those of you most mighty ( and crazy :smiley: ) who dare to try it out, please return here with remarks and malfunctions… i’ll try my best for solving them :slight_smile:

Hope you like it !

Happy blending !

Nice. Works Blender 3.0 Alpha.
You can also use object properties as dictionary.

o = bpy.context.object
o["ivy"] = {}
o["ivy"]["seed"] = 2
o["ivy"]["length"] = 0.5

Propably you dont need any functions to set object properties, it detects type and change Vector to 3 floats of list, and Boolean to Int (0/1)

owww i’m glad to hear this works on 3.0 alpha :slight_smile:

A dictionnary ???
lol :rofl: i think i’m too dumb noob in python for catching this but…
you can ‘nest’ or ‘iterate’ custom properties ??? :face_with_raised_eyebrow:

i have to grab you lil piece of code and try this out !
This could prevent from messing the custom properties tab for what i want to do !

I’ll be back soon with some updates…
I managed ( don’t ask me how :stuck_out_tongue: ) to use a collection of meshes in place of the leaf ugly square.
I guess this will bring @Calandro attention who will not forget to ask me his expectations, and it’s good :smiley:

Thanks a lot and happy blending !

Finally here’s the thing that can handle leaves coming from a collection :slight_smile:

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

bl_info = {
    "name": "IvyGen",
    "author": "testscreenings, PKHG, TrumanBlending, some Blenderartists users",
    "version": (0, 1, 6),
    "blender": (2, 80, 0),
    "location": "View3D > Sidebar > Ivy Generator (Create Tab)",
    "description": "Adds generated ivy to a mesh object starting "
                   "at the 3D cursor",
    "warning": "",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/ivy_gen.html",
    "category": "Add Curve",
}


import bpy
from bpy.types import (
    Operator,
    Panel,
    PropertyGroup,
)
from bpy.props import (
    BoolProperty,
    FloatProperty,
    IntProperty,
    PointerProperty,
    CollectionProperty,
)
from mathutils.bvhtree import BVHTree
from mathutils import (
    Euler,
    Vector,
    Matrix,
)
from collections import deque
from math import (
    pow, cos,
    pi, atan2,
    radians,
)
from random import (
    random as rand_val,
    seed as rand_seed,
)
import time
import mathutils
import random
from rna_prop_ui import rna_idprop_ui_prop_get


#====================================================
#=                                                  =
#= Adds a custom property to an object              =
#=                                                  =
#= IN: - object to add the property to
#=     - property name
#=     - property value
#=                                                  
#= OUT: nothing
#=                                                  =
#====================================================
def AddIntPropertyToObject(object, propertyName:str, propertyVal:int, toolTip:str="DO NOT DELETE THIS INT") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddFloatPropertyToObject(object, propertyName:str, propertyVal:float, toolTip:str="DO NOT DELETE THIS FLOAT") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddStringPropertyToObject(object, propertyName:str, propertyVal:str, toolTip:str="DO NOT DELETE THIS STRING") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
def AddBoolPropertyToObject(object, propertyName:str, propertyVal:bool, toolTip:str="DO NOT DELETE THIS BOOL") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
    # python is such a morons shit that it don't support function overloading ! LOOOOOL !!!!!! let's get back to 70's
def AddVectorPropertyToObject(object, propertyName:str, propertyVal:Vector, toolTip:str="DO NOT DELETE THIS VECTOR") -> None:
    object[propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
    
    
#========================================================================================================    



def createIvyGeometry(IVY, growLeaves:bool, collection4Leaves, growingSupportObject):
    
    
    
    """Create the curve geometry for IVY"""
    # Compute the local size and the gauss weight filter
    # local_ivyBranchSize = IVY.ivyBranchSize  # * radius * IVY.ivySize
    gaussWeight = (1.0, 2.0, 4.0, 7.0, 9.0, 10.0, 9.0, 7.0, 4.0, 2.0, 1.0)

    # Create a new curve and initialise it
    curve = bpy.data.curves.new("IVY", type='CURVE')
    curve.dimensions = '3D'
    curve.bevel_depth = 1
    curve.fill_mode = 'FULL'
    curve.resolution_u = 4
    
    prevSelectedObject = bpy.context.selected_objects
    #print("sel=",prevSelectedObject)
    prevActiveObject = bpy.context.active_object
    #    print("act=",prevActiveObject)
    
    
    # deal with the collections to avoid mess.....
    # 1st get the growingSupportObject collection:
    targetCollection = growingSupportObject.users_collection[0]

        
    # check wether we should use a collection for leaves or not ?
    collection4LeavesSize = 0
    useCollectionForLeaves:bool = False
    if(collection4Leaves is not None):
        useCollectionForLeaves = True
        collection4LeavesSize = len(collection4Leaves.objects) - 1
    
    # process is quite slow and deserves the work of python experts....
    # so limit the amount of leaves at dev time
    max_leaves=300000
    

    if(growLeaves):
        
        # create a leaves mesh and object
        leavesMesh = bpy.data.meshes.new('Msh_IvyLeaf')
        leavesObject = bpy.data.objects.new('IvyLeaf',leavesMesh)
        targetCollection.objects.link(leavesObject)

        
        rotMat = Matrix.Rotation
        
        if(useCollectionForLeaves==False):
            # Create the ivy leaves
            # Order location of the vertices
            signList = ((-1.0, +1.0),
                        (-1.0, -1.0),
                        (+1.0, -1.0),
                        (+1.0, +1.0),)

            # Initialise the vertex and face lists
            LeavesVertList = deque()

            # Store the methods for faster calling
            addV = LeavesVertList.extend
        else:
            # we got to deselect all and make the leaves object active as we'll have to join objects
            for ooo in prevSelectedObject:
                ooo.select_set(False)
            if(prevActiveObject is not None):
                prevActiveObject.select_set(False)
            
            # select and activate the leaves object as we will join all instanced leaves to it...
            leavesObject.select_set(True)
            bpy.context.view_layer.objects.active = leavesObject
            
                    

    # Loop over all roots to generate its nodes
    for root in IVY.ivyRoots:
        # Only grow if more than one node
        numNodes = len(root.ivyNodes)
        if numNodes > 1:
            # Calculate the local radius
            local_ivyBranchRadius = 1.0 / (root.parents + 1) # Cyril  +1.0
            prevIvyLength = 1.0 / root.ivyNodes[-1].length
            splineVerts = [ax for n in root.ivyNodes for ax in n.pos.to_4d()]

            radiusConstant = local_ivyBranchRadius * IVY.ivyBranchSize
            splineRadii = [radiusConstant * (1.3 - n.length * prevIvyLength)
                           for n in root.ivyNodes]

            # Add the poly curve and set coords and radii
            newSpline = curve.splines.new(type='POLY')
            newSpline.points.add(len(splineVerts) // 4 - 1)
            newSpline.points.foreach_set('co', splineVerts)
            newSpline.points.foreach_set('radius', splineRadii)

            # Loop over all nodes in the root
            for i, n in enumerate(root.ivyNodes):
                for k in range(len(gaussWeight)):
                    idx = max(0, min(i + k - 5, numNodes - 1))
                    n.smoothAdhesionVector += (gaussWeight[k] *
                                               root.ivyNodes[idx].adhesionVector)
                n.smoothAdhesionVector /= 56.0
                n.adhesionLength = n.smoothAdhesionVector.length
                n.smoothAdhesionVector.normalize()

                if growLeaves and (i < numNodes - 1):
                    node = root.ivyNodes[i]
                    nodeNext = root.ivyNodes[i + 1]

                    # Find the weight and normalize the smooth adhesion vector
                    weight = pow(node.length * prevIvyLength, 0.7)

                    # Calculate the ground ivy and the new weight
                    groundIvy = max(0.0, -node.smoothAdhesionVector.z)
                    weight += groundIvy * pow(1 - node.length *
                                                              prevIvyLength, 2)

                    # Find the alignment weight
                    alignmentWeight = node.adhesionLength

                    # Calculate the needed angles
                    phi = atan2(node.smoothAdhesionVector.y,
                                node.smoothAdhesionVector.x) - pi / 2.0

                    theta = (0.5 *
                        node.smoothAdhesionVector.angle(Vector((0, 0, -1)), 0))

                    # Find the size weight
                    sizeWeight = 1.5 - (cos(2 * pi * weight) * 0.5 + 0.5)

                    # Randomise the angles
                    phi += (rand_val() - 0.5) * (1.3 - alignmentWeight)
                    theta += (rand_val() - 0.5) * (1.1 - alignmentWeight)

                    # Calculate the leaf size an append the face to the list
                    leafSize = IVY.ivyLeafSize * sizeWeight

                    for j in range(10):
                        # Generate the probability
                        probability = rand_val()

                        # If we need to grow a leaf, do so
                        # cyril if (probability * weight) > IVY.leafProbability:
                        if (probability * (1.0-IVY.leafDensityFromHeight*(1.0-weight))) > IVY.leafProbability:

                            # Generate the random vector
                            #randomVector = Vector((rand_val()-0.5,rand_val()-0.5,rand_val()-0.5,))
                            # no randomness for leaves origin !!!!
                            randomVector = Vector((0,0,0))

                            # Find the leaf center
                            center = (node.pos.lerp(nodeNext.pos, j / 10.0) +
                                               IVY.ivyLeafSize * randomVector)

                            # For each of the verts, rotate/scale and append
                            basisVecX = Vector((1, 0, 0))
                            basisVecY = Vector((0, 1, 0))

                            horiRot = rotMat(theta, 3, 'X')
                            vertRot = rotMat(phi, 3, 'Z')

                            basisVecX.rotate(horiRot)
                            basisVecY.rotate(horiRot)

                            basisVecX.rotate(vertRot)
                            basisVecY.rotate(vertRot)

                            basisVecX *= leafSize
                            basisVecY *= leafSize


                            randomValue = random.randint(0,1000)

                            
                            if(useCollectionForLeaves==False):
                                addV([k1 * basisVecX + k2 * basisVecY + center for
                                                           k1, k2 in signList])
                            else:
                                if(max_leaves >0):
                                    max_leaves=max_leaves-1
                                    
                                    # randomly choose a leaf in collection and copy it
                                    leafToClone = collection4Leaves.objects[randomValue % collection4LeavesSize]
                                    newLeaf = leafToClone.copy()
                                    newLeaf.data = leafToClone.data.copy()
                                    
                                    #print("cloned : ",newLeaf)
                                    #print("active = ",bpy.context.active_object) #bpy.context.selected_objects)
                                    
                                    # scale the leaf
                                    newLeaf.scale *= leafSize*10.0 # why the hell is it divided by 1K ??????
                                    # move the leaf to right place
                                    newLeaf.location = center
                                    
                                    # leaf rotation on X and Z global axis
                                    newLeaf.rotation_euler = Euler((theta,0,phi), 'XYZ')
                                    
                                    # and join it with the leavesObject
                                    targetCollection.objects.link(newLeaf)
                                    newLeaf.select_set(True)
                                                                

    # here leaves a created if needed and all selected. Join all in one mesh
    if(useCollectionForLeaves==True):
        bpy.ops.object.join()

                                                                    

    # Add the object and link to scene in the growingSupportObject collection
    newCurve = bpy.data.objects.new("IVY_Curve", curve)
    
    # add branches to the proper collection....
    targetCollection.objects.link(newCurve)
    
    if growLeaves:
        if(useCollectionForLeaves==False):

            faceList = [[4 * i + l for l in range(4)] for i in range(len(LeavesVertList) // 4)]

            # Generate the new leaf mesh and link
            leavesMesh.from_pydata(LeavesVertList, [], faceList)
            leavesMesh.update(calc_edges=True)
            
            leavesObject.data = leavesMesh


            # add leaves to the proper collection.....
            #targetCollection.objects.link(leavesObject)

            # Create and set the uv texture coords
            leavesMesh.uv_layers.new(name="UV_diffuse")

            # TODO, this is non-functional, default uvs are ok?
            '''
            for d in tex.data:
                uv1, uv2, uv3, uv4 = signList
            '''
        else:
            #removeMeshFromMemory (leavesMesh)
            print("use of collection here....")

        # parent the leaves to the branches and tag it as 'is_an_ivy'
        leavesObject.parent = newCurve
        AddFloatPropertyToObject(leavesObject,'IVY_IsSelf', 0.0)

        # unselect the leaves object 
        leavesObject.select_set(False)

        
    # And now, as a final step, lets parent the ivy to the object it was grown on.
    # this will allow to reselect this mesh for edition....
    newCurve.parent = growingSupportObject


    # before quit, we reselect and activate what was at entry
    for ooo in prevSelectedObject:
        ooo.select_set(True)
    if(prevActiveObject is not None):
        prevActiveObject.select_set(True)
    
    bpy.context.view_layer.objects.active =prevActiveObject


    
    # return the branches 3D object
    return(newCurve)



class IvyNode:
    """ The basic class used for each point on the ivy which is grown."""
    __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength',
                 'smoothAdhesionVector', 'length', 'floatingLength', 'climb')

    def __init__(self):
        self.pos = Vector((0, 0, 0))
        self.primaryDir = Vector((0, 0, 1))
        self.adhesionVector = Vector((0, 0, 0))
        self.smoothAdhesionVector = Vector((0, 0, 0))
        self.length = 0.0001
        self.floatingLength = 0.0
        self.climb = True


class IvyRoot:
    """ The class used to hold all ivy nodes growing from this root point."""
    __slots__ = ('ivyNodes', 'alive', 'parents')

    def __init__(self):
        self.ivyNodes = deque()
        self.alive = True
        self.parents = 0


class Ivy:
    """ The class holding all parameters and ivy roots."""
    """
    __slots__ = ('ivyRoots', 'primaryWeight', 'randomWeight',
                 'gravityWeight', 'adhesionWeight', 'branchingProbability',
                 'leafProbability', 'leafDensityFromHeight', 'ivySize', 'ivyLeafSize', 'ivyBranchSize',
                 'maxFloatLength', 'maxAdhesionDistance', 'maxLength')
                 """

    def __init__(self,
                 primaryWeight=0.5,
                 randomWeight=0.2,
                 gravityWeight=1.0,
                 adhesionWeight=0.1,
                 branchingProbability=0.05,
                 leafProbability=0.35,
                 leafDensityFromHeight=0.35,
                 ivySize=0.02,
                 ivyLeafSize=0.02,
                 ivyBranchSize=0.001,
                 maxFloatLength=0.5,
                 maxAdhesionDistance=1.0):



        self.ivyRoots = deque()
        self.primaryWeight = primaryWeight
        self.randomWeight = randomWeight
        self.gravityWeight = gravityWeight
        self.adhesionWeight = adhesionWeight
        self.branchingProbability = 1 - branchingProbability
        self.leafProbability = 1 - leafProbability
        self.leafDensityFromHeight = leafDensityFromHeight
        self.ivySize = ivySize
        self.ivyLeafSize = ivyLeafSize
        self.ivyBranchSize = ivyBranchSize
        self.maxFloatLength = maxFloatLength
        self.maxAdhesionDistance = maxAdhesionDistance
        self.maxLength = 0.0

        # Normalize all the weights only on initialisation
        sums = self.primaryWeight + self.randomWeight + self.adhesionWeight
        self.primaryWeight /= sums
        self.randomWeight /= sums
        self.adhesionWeight /= sums

    def seed(self, seedPos):
        # Seed the Ivy by making a new root and first node
        tmpRoot = IvyRoot()
        tmpIvy = IvyNode()
        tmpIvy.pos = seedPos

        tmpRoot.ivyNodes.append(tmpIvy)
        self.ivyRoots.append(tmpRoot)

    def grow(self, ob, bvhtree):
        # Determine the local sizes
        # local_ivySize = self.ivySize	# * radius
        # local_maxFloatLength = self.maxFloatLength  # * radius
        # local_maxAdhesionDistance = self.maxAdhesionDistance	# * radius

        for root in self.ivyRoots:
            # Make sure the root is alive, if not, skip
            if not root.alive:
                continue

            # Get the last node in the current root
            prevIvy = root.ivyNodes[-1]

            # If the node is floating for too long, kill the root
            if prevIvy.floatingLength > self.maxFloatLength:
                root.alive = False

            # Set the primary direction from the last node
            primaryVector = prevIvy.primaryDir

            # Make the random vector and normalize
            randomVector = Vector((rand_val() - 0.5, rand_val() - 0.5,
                                   rand_val() - 0.5)) + Vector((0, 0, 0.2))
            randomVector.normalize()

            # Calculate the adhesion vector
            adhesionVector = adhesion(
                    prevIvy.pos, bvhtree, self.maxAdhesionDistance)

            # Calculate the growing vector
            growVector = self.ivySize * (primaryVector * self.primaryWeight +
                                          randomVector * self.randomWeight +
                                          adhesionVector * self.adhesionWeight)

            # Find the gravity vector
            gravityVector = (self.ivySize * self.gravityWeight *
                                                            Vector((0, 0, -1)))
            gravityVector *= pow(prevIvy.floatingLength / self.maxFloatLength,
                                 0.7)

            # Determine the new position vector
            newPos = prevIvy.pos + growVector + gravityVector

            # Check for collisions with the object
            climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)

            # Update the growing vector for any collisions
            growVector = newPos - prevIvy.pos - gravityVector
            growVector.normalize()

            # Create a new IvyNode and set its properties
            tmpNode = IvyNode()
            tmpNode.climb = climbing
            tmpNode.pos = newPos
            tmpNode.primaryDir = prevIvy.primaryDir.lerp(growVector, 0.5)
            tmpNode.primaryDir.normalize()
            tmpNode.adhesionVector = adhesionVector
            tmpNode.length = prevIvy.length + (newPos - prevIvy.pos).length

            if tmpNode.length > self.maxLength:
                self.maxLength = tmpNode.length

            # If the node isn't climbing, update it's floating length
            # Otherwise set it to 0
            if not climbing:
                tmpNode.floatingLength = prevIvy.floatingLength + (newPos -
                                                            prevIvy.pos).length
            else:
                tmpNode.floatingLength = 0.0

            root.ivyNodes.append(tmpNode)

        # Loop through all roots to check if a new root is generated
        for root in self.ivyRoots:
            # Check the root is alive and isn't at high level of recursion
            if (root.parents > 3) or (not root.alive):
                continue

            # Check to make sure there's more than 1 node
            if len(root.ivyNodes) > 1:
                # Loop through all nodes in root to check if new root is grown
                for node in root.ivyNodes:
                    # Set the last node of the root and find the weighting
                    prevIvy = root.ivyNodes[-1]
                    weight = 1.0 - (cos(2.0 * pi * node.length /
                                        prevIvy.length) * 0.5 + 0.5)

                    probability = rand_val()

                    # Check if a new root is grown and if so, set its values
                    if (probability * weight > self.branchingProbability):
                        tmpNode = IvyNode()
                        tmpNode.pos = node.pos
                        tmpNode.floatingLength = node.floatingLength

                        tmpRoot = IvyRoot()
                        tmpRoot.parents = root.parents + 1

                        tmpRoot.ivyNodes.append(tmpNode)
                        self.ivyRoots.append(tmpRoot)
                        return


def adhesion(loc, bvhtree, max_l):
    # Compute the adhesion vector by finding the nearest point
    nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
    adhesion_vector = Vector((0.0, 0.0, 0.0))
    if nearest_location is not None:
        # Compute the distance to the nearest point
        adhesion_vector = nearest_location - loc
        distance = adhesion_vector.length
        # If it's less than the maximum allowed and not 0, continue
        if distance:
            # Compute the direction vector between the closest point and loc
            adhesion_vector.normalize()
            adhesion_vector *= 1.0 - distance / max_l
            # adhesion_vector *= getFaceWeight(ob.data, nearest_result[3])
    return adhesion_vector


def collision(bvhtree, pos, new_pos):
    # Check for collision with the object
    climbing = False

    corrected_new_pos = new_pos
    direction = new_pos - pos

    hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
    # If there's a collision we need to check it
    if hit_location is not None:
        # Check whether the collision is going into the object
        if direction.dot(hit_normal) < 0.0:
            reflected_direction = (new_pos - hit_location).reflect(hit_normal)

            corrected_new_pos = hit_location + reflected_direction
            climbing = True

    return climbing, corrected_new_pos


def bvhtree_from_object(ob):
    import bmesh
    bm = bmesh.new()

    depsgraph = bpy.context.evaluated_depsgraph_get()
    ob_eval = ob.evaluated_get(depsgraph)
    mesh = ob_eval.to_mesh()
    bm.from_mesh(mesh)
    bm.transform(ob.matrix_world)

    bvhtree = BVHTree.FromBMesh(bm)
    ob_eval.to_mesh_clear()
    return bvhtree

def check_mesh_faces(ob):
    me = ob.data
    if len(me.polygons) > 0:
        return True

    return False


class IvyGen(Operator):
    
    
    bl_idname = "curve.ivy_gen"
    bl_label = "IvyGen"
    bl_description = "Generate Ivy on an Mesh Object"
    bl_options = {'REGISTER', 'UNDO'}

    resetIvy: BoolProperty(
        name="Reset values",
        description="Reset all values to default",
        default=False
    )
    

    
    '''
    # not sure but it seems this is useless...
    @classmethod
    def poll(self, context):
        # Check if there's an object and whether it's a mesh
        return(True)
    '''

    # called when user clicks on Add new ivy in N-Panel
    def invoke(self, context, event):
        return(self.execute(context))


    def execute(self, context):

        wm = context.window_manager.ivy_gen_props
        
        print("expected ivy len = ",wm.totalIvyLength)
        
        
        if self.resetIvy:
            wm.totalIvyLength = 1.0 # i don't know how to retrieve the default values set in properties :-/
            wm.randomSeed = 0.0
            wm.ivySize = 0.02
            wm.maxFloatLength = 0.5
            wm.maxAdhesionDistance = 1.0
            wm.primaryWeight = 0.5
            wm.randomWeight = 0.2
            wm.gravityWeight = 1.0
            wm.adhesionWeight = 0.1
            wm.branchingProbability = 0.05
            wm.leafProbability = 0.35
            wm.leafDensityFromHeight = 0.35
            wm.ivyBranchSize = 0.001
            wm.ivyLeafSize = 0.02
            
        

        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

        # Get the active object if we gonna create an ivy.....
        if(context.window_manager.ivy_gen_props.allowIvyCreation):
            # Get the growing support object. this object must be kept selected and active
            supportObject = context.active_object
            
        else: 
            # if we want to edit an ivy instead....
            if(context.window_manager.ivy_gen_props.allowIvyEdition):
                
                # active object is the ivy branches
                if(context.active_object.parent is None):
                    print("Ivy has no growing support. It cannot be edited")
                    
                    return {'PASS_THROUGH'} 
                else:
                    selectedIvyBranches = context.active_object
                    
                    # Retrieve the seed point and bring the 3D cursor on it
                    # the seed point is the 1st curve point
                    context.scene.cursor.location = selectedIvyBranches["IVY_StartPos"]
                    
                    # And also retrieve the growing support object
                    supportObject = context.active_object.parent
                    
                    # now delete branches and leaves as they will be recreated                    
                    selectedIvyBranches.select_set(True)
                    for child in selectedIvyBranches.children:
                        child.select_set(True)
                        
                    bpy.ops.object.delete()
                                        
            else:
                # unknown state: no edition and no creation. We might have selected leaves....
                return {'PASS_THROUGH'} 
                

                        
            
        bvhtree = bvhtree_from_object(supportObject)

        # Check if the mesh has at least one polygon since some functions
        # are expecting them in the object's data (see T51753)
        check_face = check_mesh_faces(supportObject)
        if check_face is False:
            self.report({'WARNING'},
                        "Mesh Object doesn't have at least one Face. "
                        "Operation Cancelled")
            return {"CANCELLED"}

        # Fix the random seed
        rand_seed(wm.randomSeed)

        # Make the new ivy
        New_ivy = Ivy(wm.primaryWeight,
                      wm.randomWeight,
                      wm.gravityWeight,
                      wm.adhesionWeight,
                      wm.branchingProbability,
                      wm.leafProbability,
                      wm.leafDensityFromHeight,
                      wm.ivySize,
                      wm.ivyLeafSize,
                      wm.ivyBranchSize,
                      wm.maxFloatLength,
                      wm.maxAdhesionDistance)
                      
                      
        # Generate first root and node at the 3D cursor location
        New_ivy.seed(context.scene.cursor.location)

        maxLength = wm.totalIvyLength  # * radius

        # If we need to check time set the flag
        checkTime = False
        if(context.window_manager.ivy_gen_props.maxTime != 0.0):
            checkTime = True

        t = time.time()
        startPercent = 0.0
        checkAliveIter = [True, ]

        # Grow until 200 roots is reached or backup counter exceeds limit
        while (any(checkAliveIter) and (New_ivy.maxLength < maxLength) and (not checkTime or (time.time() - t < context.window_manager.ivy_gen_props.maxTime))):
            # Grow the ivy for this iteration
            New_ivy.grow(supportObject, bvhtree)

            # Print the proportion of ivy growth to console
            if (New_ivy.maxLength / maxLength * 100) > 10 * startPercent // 10:
                #print('%0.2f%% of Ivy nodes have grown' % (New_ivy.maxLength / maxLength * 100))
                startPercent += 10

            # Make an iterator to check if all are alive
            checkAliveIter = (r.alive for r in New_ivy.ivyRoots)

        # Create the curve and leaf geometry
        Ivy3DObject = createIvyGeometry(New_ivy, wm.growLeaves, wm.leavesCollection, supportObject)
            
        
        # and add Custom Properties for param memorization for future edition
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IsSelf', 0.123)
        AddVectorPropertyToObject(Ivy3DObject,'IVY_StartPos',context.scene.cursor.location)
        
        AddIntPropertyToObject(Ivy3DObject,'IVY_RandSeed',wm.randomSeed)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_TotalIvyLength',wm.totalIvyLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvySegmentSize',wm.ivySize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxFloatLength',wm.maxFloatLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxAdhesionDistance',wm.maxAdhesionDistance)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_PrimaryWeight',wm.primaryWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_RandomWeight',wm.randomWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_GravityWeight',wm.gravityWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_AdhesionWeight',wm.adhesionWeight)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_BranchingProbability',wm.branchingProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyBranchSize',wm.ivyBranchSize)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyLeafSize',wm.ivyLeafSize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafProbability',wm.leafProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafDensityFromHeight',wm.leafDensityFromHeight)
        AddBoolPropertyToObject(Ivy3DObject,'IVY_GrowLeaves',wm.growLeaves)

        
        
        print("Ivy generated in %0.2f s ( but we don't care )" % (time.time() - t))
       
                
        if(context.window_manager.ivy_gen_props.allowIvyEdition):
            # if we are in ivy edition mode then lets reselect the ivy branches
            #if(context.window_manager.ivy_gen_props.allowIvyEdition):
            for ob in bpy.context.selected_objects:
                ob.select_set(False)
                
            
            Ivy3DObject.select_set(True)
            
            bpy.context.view_layer.objects.active = Ivy3DObject
            
        # not sure this is usefull.....    
        context.window_manager.ivy_gen_props.allowIvyCreation = False
        context.window_manager.ivy_gen_props.allowIvyEdition = False
        
        self.resetIvy = False

        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        #wm = context.window_manager
        wm = context.window_manager.ivy_gen_props
        col = layout.column(align=True)

        layout.prop(self, "resetIvy", icon="BACK")
        
        col = layout.column(align=True)
        col.label(text="Generation Settings:")
        col.prop(wm, "randomSeed")

        col = layout.column(align=True)
        col.label(text="Size Settings:")
        col.prop(wm, "totalIvyLength")
        col.prop(wm, "ivySize")
        col.prop(wm, "maxFloatLength")
        col.prop(wm, "maxAdhesionDistance")

        col = layout.column(align=True)
        col.label(text="Weight Settings:")
        col.prop(wm, "primaryWeight")
        col.prop(wm, "randomWeight")
        col.prop(wm, "gravityWeight")
        col.prop(wm, "adhesionWeight")

        col = layout.column(align=True)
        col.label(text="Branch Settings:")
        col.prop(wm, "branchingProbability")
        col.prop(wm, "ivyBranchSize")

        col = layout.column(align=True)
        ro = layout.row(align=True)
        ro.prop(wm, "growLeaves")

        if wm.growLeaves:
            ro.prop(wm, "leavesCollection")
            #if(wm.leavesCollection is not None):
                #for ooo in wm.leavesCollection.all_objects:
                    #print("obj: ", ooo.name)
            col = layout.column(align=True)
            col.label(text="Leaf Settings:")
            col.prop(wm, "ivyLeafSize")
            col.prop(wm, "leafProbability")
            col.prop(wm, "leafDensityFromHeight")
        


class Ivy_N_Panel(Panel):
    bl_label = "Ivy Generator"
    bl_idname = "CURVE_PT_IvyNPanel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Create"
    bl_context = "objectmode"
    bl_options = {"DEFAULT_CLOSED"}

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

        wm = context.window_manager.ivy_gen_props

        ob = bpy.context.view_layer.objects.active # here active object is properly retrieved
        


        # by default, disable ivy edition and creation
        wm.allowIvyEdition = False
        wm.allowIvyCreation = False

        # if an object is selected....                    
        if(ob is not None):
            # if object is an ivy, disable ivy creation and allow edition ( only on branches selection )
            if(ob.get('IVY_IsSelf') is not None):
                #print("object is an ivy !!!!!")
                
                if(ob['IVY_IsSelf']==0.123):
                    print("Is branches and can be edited")
                    
                    # here we grab the ivy instance and its parameters
                    # and we enable edition
                    wm.allowIvyEdition = True
                    
                    selectedIvyBranches = ob
                    
                    # retrieve all custom properties from ivy branches and set up values in the window controls
                    wm.randomSeed            = selectedIvyBranches['IVY_RandSeed']
                    wm.totalIvyLength        = selectedIvyBranches['IVY_TotalIvyLength']
                    wm.ivySize               = selectedIvyBranches['IVY_IvySegmentSize']
                    wm.maxFloatLength        = selectedIvyBranches['IVY_MaxFloatLength']
                    wm.maxAdhesionDistance   = selectedIvyBranches['IVY_MaxAdhesionDistance']
                    wm.primaryWeight         = selectedIvyBranches['IVY_PrimaryWeight']
                    wm.randomWeight          = selectedIvyBranches['IVY_RandomWeight']
                    wm.gravityWeight         = selectedIvyBranches['IVY_GravityWeight']
                    wm.adhesionWeight        = selectedIvyBranches['IVY_AdhesionWeight']
                    wm.branchingProbability  = selectedIvyBranches['IVY_BranchingProbability']
                    wm.ivyBranchSize         = selectedIvyBranches['IVY_IvyBranchSize']
                    wm.ivyLeafSize           = selectedIvyBranches['IVY_IvyLeafSize']
                    wm.leafProbability       = selectedIvyBranches['IVY_LeafProbability']
                    wm.leafDensityFromHeight = selectedIvyBranches['IVY_LeafDensityFromHeight']
                    wm.growLeaves            = selectedIvyBranches['IVY_GrowLeaves']                    
                    
                else:
                    print("Is leaves and can NOT be edited. Please select branches to enable edition")
              
            else:
                #object is not an ivy: ivy can be created on it if it is an object that have a mesh
                if((ob.type == 'MESH') and (context.mode == 'OBJECT')):
                    wm.allowIvyCreation = True
            


        

        if(wm.allowIvyCreation):
            prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE")
        else:        
            if(wm.allowIvyEdition):
                prop_def = col.operator("curve.ivy_gen", text="Edit this ivy", icon="CURVE_DATA")
            
        
        
        col = layout.column(align=True)
        col.label(text="Generation Time limit:")
        col.prop(wm, "maxTime")

            


class IvyGenProperties(PropertyGroup):
#bpy.types.Object.my_custom_property = bpy.props.BoolProperty(name="Test Property", description="This should show up as API Defined")
#bpy.context.active_object.my_custom_property = True
    
    # values that are used as flags for control, and that are not editable
    allowIvyEdition: BoolProperty(
        name="allowIvyEdition",
        description="allow selected ivy edition",
        default=False
    )

    allowIvyCreation: BoolProperty(
        name="allowIvyCreation",
        description="allow ivy creation on selected object",
        default=False
    )
    # ===========================================================================
            
    # value set in Left N panel
    maxTime: FloatProperty(
        name="Maximum Time",
        description="The maximum time to run the generation for "
                    "in seconds generation (0.0 = Disabled)",
        default=20.0,
        min=0.0,
        soft_max=10
    )
    # ===========================================================================

                    
                    
    # values set in parameters bottom-left panel
    randomSeed: IntProperty(
        name="Random Seed",
        description="The seed governing random generation",
        default=0,
            min=0
        )
    # --------------------------------------------------------
    totalIvyLength: FloatProperty(
        name="Total Ivy Length",
        description="Maximum ivy length in Blender Units",
        default=1.0,
        min=0.0,
        soft_max=3.0,
        subtype='DISTANCE',
        unit='LENGTH',
        precision=3,
        step=10.0
        )
    # --------------------------------------------------------
    ivySize: FloatProperty(
        name="Branch segment Length",
        description="The length of an ivy segment"
                    " Units",
        default=0.02,
        min=0.005,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    maxFloatLength: FloatProperty(
        name="Max Float Length",
        description="The maximum distance that a branch "
                    "can live while floating",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    maxAdhesionDistance: FloatProperty(
        name="Max Adhesion Length",
        description="The maximum distance that a branch "
                    "will feel the effects of adhesion",
        default=1.0,
        min=0.0,
        soft_max=2.0,
        precision=2
    )
    # --------------------------------------------------------
    primaryWeight: FloatProperty(
        name="Primary Weight",
        description="Weighting given to the current direction",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    randomWeight: FloatProperty(
        name="Random Weight",
        description="Weighting given to the random direction",
        default=0.2,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    gravityWeight: FloatProperty(
        name="Gravity Weight",
        description="Weighting given to the gravity direction",
        default=1.0,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    adhesionWeight: FloatProperty(
        name="Adhesion Weight",
        description="Weighting given to the adhesion direction",
        default=0.1,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    branchingProbability: FloatProperty(
        name="Branching Probability",
        description="Probability of a new branch forming",
        default=0.05,
        min=0.0,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    ivyBranchSize: FloatProperty(
        name="Branch start diameter",
        description="The size of the ivy branches",
        default=0.001,
        min=0.0,
        soft_max=0.1,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    ivyLeafSize: FloatProperty(
        name="Leaf Size",
        description="The size of the ivy leaves",
        default=0.02,
        min=0.0,
        soft_max=0.5,
        precision=3
    )
    # --------------------------------------------------------
    leafProbability: FloatProperty(
        name="Leaf Probability",
        description="Probability of a leaf forming",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    leafDensityFromHeight: FloatProperty(
        name="leaf density/height fact.",
        description="defines wether leaf density is controlled by branches height or not.",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    growLeaves: BoolProperty(
        name="Grow Leaves",
        description="Grow leaves or not",
        default=True
    )
    # --------------------------------------------------------
    leavesCollection: PointerProperty(
        type=bpy.types.Collection,
        name="--->",
        description="Collection of leaves. If empty, squares will be used"
    )
    # --------------------------------------------------------

    


classes = (
    IvyGen,
    IvyGenProperties,
    Ivy_N_Panel
)



def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.WindowManager.ivy_gen_props = PointerProperty(type=IvyGenProperties)


def unregister():
    del bpy.types.WindowManager.ivy_gen_props

    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()



be warned that random leaves selection in a collection and their instantiation/positionning/scaling/rotating is an heavy process ( much more than handling basic leaves squares ) and therefore is slow :confused:

I guess this process need the intervention of python experts who know better than i do, blender and python internals…

Happy blending !

Hi @JuhaW :slight_smile:

This dictionnary works like a charm !!!
It’s less convenient for dev and debugging purpose but is much more convenient for use purpose by not flooding the custom properties.
I wish there were in python defines like in C or C++ allowing for removing the [“IVY”] from code and revealing all the props.
Of course this can be done in active code but will stuff code with useless things.

I adopt your dictionnary feature :smiley:

So here’s the final feature:
Ivy creation with square leaves and the same one with complex mesh leaves


here’s the code to set up like explained a bit higher:

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

bl_info = {
    "name": "IvyGen",
    "author": "testscreenings, PKHG, TrumanBlending, some Blenderartists users",
    "version": (0, 1, 6),
    "blender": (2, 80, 0),
    "location": "View3D > Sidebar > Ivy Generator (Create Tab)",
    "description": "Adds generated ivy to a mesh object starting "
                   "at the 3D cursor",
    "warning": "",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/ivy_gen.html",
    "category": "Add Curve",
}


import bpy
from bpy.types import (
    Operator,
    Panel,
    PropertyGroup,
)
from bpy.props import (
    BoolProperty,
    FloatProperty,
    IntProperty,
    PointerProperty,
    CollectionProperty,
)
from mathutils.bvhtree import BVHTree
from mathutils import (
    Euler,
    Vector,
    Matrix,
)
from collections import deque
from math import (
    pow, cos,
    pi, atan2,
    radians,
)
from random import (
    random as rand_val,
    seed as rand_seed,
)
import time
import mathutils
import random
from rna_prop_ui import rna_idprop_ui_prop_get


#====================================================
#=                                                  =
#= Adds a custom property to an object              =
#=                                                  =
#= IN: - object to add the property to
#=     - property name
#=     - property value
#=                                                  
#= OUT: nothing
#=                                                  =
#====================================================
def AddIntPropertyToObject(object, propertyName:str, propertyVal:int, toolTip:str="DO NOT DELETE THIS INT") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddFloatPropertyToObject(object, propertyName:str, propertyVal:float, toolTip:str="DO NOT DELETE THIS FLOAT") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddStringPropertyToObject(object, propertyName:str, propertyVal:str, toolTip:str="DO NOT DELETE THIS STRING") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
def AddBoolPropertyToObject(object, propertyName:str, propertyVal:bool, toolTip:str="DO NOT DELETE THIS BOOL") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
    # python is such a morons shit that it don't support function overloading ! LOOOOOL !!!!!! let's get back to 70's
def AddVectorPropertyToObject(object, propertyName:str, propertyVal:Vector, toolTip:str="DO NOT DELETE THIS VECTOR") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
    
def AddCollectionPropertyToObject(object, propertyName:str, propertyVal, toolTip:str="DO NOT DELETE THIS COLLECTION") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
    
    
#========================================================================================================    



def createIvyGeometry(IVY, growLeaves:bool, collection4Leaves, growingSupportObject):
    
    
    
    """Create the curve geometry for IVY"""
    # Compute the local size and the gauss weight filter
    # local_ivyBranchSize = IVY.ivyBranchSize  # * radius * IVY.ivySize
    gaussWeight = (1.0, 2.0, 4.0, 7.0, 9.0, 10.0, 9.0, 7.0, 4.0, 2.0, 1.0)

    # Create a new curve and initialise it
    curve = bpy.data.curves.new("IVY", type='CURVE')
    curve.dimensions = '3D'
    curve.bevel_depth = 1
    curve.fill_mode = 'FULL'
    curve.resolution_u = 4
    
    prevSelectedObject = bpy.context.selected_objects
    #print("sel=",prevSelectedObject)
    prevActiveObject = bpy.context.active_object
    #    print("act=",prevActiveObject)
    
    
    # deal with the collections to avoid mess.....
    # 1st get the growingSupportObject collection:
    targetCollection = growingSupportObject.users_collection[0]

        
    # check wether we should use a collection for leaves or not ?
    collection4LeavesSize = 0
    useCollectionForLeaves:bool = False
    if(collection4Leaves is not None):
        useCollectionForLeaves = True
        collection4LeavesSize = len(collection4Leaves.objects) - 1
    
    # process is quite slow and deserves the work of python experts....
    # so limit the amount of leaves at dev time
    max_leaves=300000
    

    if(growLeaves):
        
        # create a leaves mesh and object
        leavesMesh = bpy.data.meshes.new('Msh_IvyLeaf')
        leavesObject = bpy.data.objects.new('IvyLeaf',leavesMesh)
        targetCollection.objects.link(leavesObject)

        
        rotMat = Matrix.Rotation
        
        if(useCollectionForLeaves==False):
            # Create the ivy leaves
            # Order location of the vertices
            signList = ((-1.0, +1.0),
                        (-1.0, -1.0),
                        (+1.0, -1.0),
                        (+1.0, +1.0),)

            # Initialise the vertex and face lists
            LeavesVertList = deque()

            # Store the methods for faster calling
            addV = LeavesVertList.extend
        else:
            # we got to deselect all and make the leaves object active as we'll have to join objects
            for ooo in prevSelectedObject:
                ooo.select_set(False)
            if(prevActiveObject is not None):
                prevActiveObject.select_set(False)
            
            # select and activate the leaves object as we will join all instanced leaves to it...
            leavesObject.select_set(True)
            bpy.context.view_layer.objects.active = leavesObject
            
                    

    # Loop over all roots to generate its nodes
    for root in IVY.ivyRoots:
        # Only grow if more than one node
        numNodes = len(root.ivyNodes)
        if numNodes > 1:
            # Calculate the local radius
            local_ivyBranchRadius = 1.0 / (root.parents + 1) # Cyril  +1.0
            prevIvyLength = 1.0 / root.ivyNodes[-1].length
            splineVerts = [ax for n in root.ivyNodes for ax in n.pos.to_4d()]

            radiusConstant = local_ivyBranchRadius * IVY.ivyBranchSize
            splineRadii = [radiusConstant * (1.3 - n.length * prevIvyLength)
                           for n in root.ivyNodes]

            # Add the poly curve and set coords and radii
            newSpline = curve.splines.new(type='POLY')
            newSpline.points.add(len(splineVerts) // 4 - 1)
            newSpline.points.foreach_set('co', splineVerts)
            newSpline.points.foreach_set('radius', splineRadii)

            # Loop over all nodes in the root
            for i, n in enumerate(root.ivyNodes):
                for k in range(len(gaussWeight)):
                    idx = max(0, min(i + k - 5, numNodes - 1))
                    n.smoothAdhesionVector += (gaussWeight[k] *
                                               root.ivyNodes[idx].adhesionVector)
                n.smoothAdhesionVector /= 56.0
                n.adhesionLength = n.smoothAdhesionVector.length
                n.smoothAdhesionVector.normalize()

                if growLeaves and (i < numNodes - 1):
                    node = root.ivyNodes[i]
                    nodeNext = root.ivyNodes[i + 1]

                    # Find the weight and normalize the smooth adhesion vector
                    weight = pow(node.length * prevIvyLength, 0.7)

                    # Calculate the ground ivy and the new weight
                    groundIvy = max(0.0, -node.smoothAdhesionVector.z)
                    weight += groundIvy * pow(1 - node.length *
                                                              prevIvyLength, 2)

                    # Find the alignment weight
                    alignmentWeight = node.adhesionLength

                    # Calculate the needed angles
                    phi = atan2(node.smoothAdhesionVector.y,
                                node.smoothAdhesionVector.x) - pi / 2.0

                    theta = (0.5 *
                        node.smoothAdhesionVector.angle(Vector((0, 0, -1)), 0))

                    # Find the size weight
                    sizeWeight = 1.5 - (cos(2 * pi * weight) * 0.5 + 0.5)

                    # Randomise the angles
                    phi += (rand_val() - 0.5) * (1.3 - alignmentWeight)
                    theta += (rand_val() - 0.5) * (1.1 - alignmentWeight)

                    # Calculate the leaf size an append the face to the list
                    leafSize = IVY.ivyLeafSize * sizeWeight

                    for j in range(10):
                        # Generate the probability
                        probability = rand_val()

                        # If we need to grow a leaf, do so
                        # cyril if (probability * weight) > IVY.leafProbability:
                        if (probability * (1.0-IVY.leafDensityFromHeight*(1.0-weight))) > IVY.leafProbability:

                            # Generate the random vector
                            #randomVector = Vector((rand_val()-0.5,rand_val()-0.5,rand_val()-0.5,))
                            # no randomness for leaves origin !!!!
                            randomVector = Vector((0,0,0))

                            # Find the leaf center
                            center = (node.pos.lerp(nodeNext.pos, j / 10.0) +
                                               IVY.ivyLeafSize * randomVector)

                            # For each of the verts, rotate/scale and append
                            basisVecX = Vector((1, 0, 0))
                            basisVecY = Vector((0, 1, 0))

                            horiRot = rotMat(theta, 3, 'X')
                            vertRot = rotMat(phi, 3, 'Z')

                            basisVecX.rotate(horiRot)
                            basisVecY.rotate(horiRot)

                            basisVecX.rotate(vertRot)
                            basisVecY.rotate(vertRot)

                            basisVecX *= leafSize / 5.0 # /5.0 for coherent leaf size....
                            basisVecY *= leafSize / 5.0


                            randomValue = random.randint(0,1000)

                            
                            if(useCollectionForLeaves==False):
                                addV([k1 * basisVecX + k2 * basisVecY + center for
                                                           k1, k2 in signList])
                            else:
                                if(max_leaves >0):
                                    max_leaves=max_leaves-1
                                    
                                    # randomly choose a leaf in collection and copy it
                                    leafToClone = collection4Leaves.objects[randomValue % collection4LeavesSize]
                                    newLeaf = leafToClone.copy()
                                    newLeaf.data = leafToClone.data.copy()
                                    
                                    #print("cloned : ",newLeaf)
                                    #print("active = ",bpy.context.active_object) #bpy.context.selected_objects)
                                    
                                    # scale the leaf
                                    newLeaf.scale *= leafSize*10.0 # scale with 10 for coherent size...
                                    # move the leaf to right place
                                    newLeaf.location = center
                                    
                                    # leaf rotation on X and Z global axis
                                    newLeaf.rotation_euler = Euler((theta,0,phi), 'XYZ')
                                    
                                    # I guess setting a 4x4 matrix handling scale pos and rot would be faster^^
                                    
                                    # and join it with the leavesObject
                                    targetCollection.objects.link(newLeaf)
                                    newLeaf.select_set(True)
                                                                

    # here leaves a created if needed and all selected. Join all in one mesh
    if(useCollectionForLeaves==True):
        bpy.ops.object.join()

                                                                    

    # Add the object and link to scene in the growingSupportObject collection
    newCurve = bpy.data.objects.new("IVY_Curve", curve)
    
    # add branches to the proper collection....
    targetCollection.objects.link(newCurve)
    
    if growLeaves:
        if(useCollectionForLeaves==False):

            faceList = [[4 * i + l for l in range(4)] for i in range(len(LeavesVertList) // 4)]

            # Generate the new leaf mesh and link
            leavesMesh.from_pydata(LeavesVertList, [], faceList)
            leavesMesh.update(calc_edges=True)
            
            leavesObject.data = leavesMesh


            # add leaves to the proper collection.....
            #targetCollection.objects.link(leavesObject)

            # Create and set the uv texture coords
            leavesMesh.uv_layers.new(name="UV_diffuse")

            # TODO, this is non-functional, default uvs are ok?
            '''
            for d in tex.data:
                uv1, uv2, uv3, uv4 = signList
            '''
        else:
            #removeMeshFromMemory (leavesMesh)
            print("use of collection here....")

        # parent the leaves to the branches and tag it as 'is_an_ivy'
        leavesObject.parent = newCurve
        leavesObject["IVY"] = {} # create "IVY" dictionnary
        AddFloatPropertyToObject(leavesObject,'IVY_IsSelf', 0.0)

        # unselect the leaves object 
        leavesObject.select_set(False)

        
    # And now, as a final step, lets parent the ivy to the object it was grown on.
    # this will allow to reselect this mesh for edition....
    newCurve.parent = growingSupportObject


    # before quit, we reselect and activate what was at entry
    for ooo in prevSelectedObject:
        ooo.select_set(True)
    if(prevActiveObject is not None):
        prevActiveObject.select_set(True)
    
    bpy.context.view_layer.objects.active =prevActiveObject


    
    # return the branches 3D object
    return(newCurve)



class IvyNode:
    """ The basic class used for each point on the ivy which is grown."""
    __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength',
                 'smoothAdhesionVector', 'length', 'floatingLength', 'climb')

    def __init__(self):
        self.pos = Vector((0, 0, 0))
        self.primaryDir = Vector((0, 0, 1))
        self.adhesionVector = Vector((0, 0, 0))
        self.smoothAdhesionVector = Vector((0, 0, 0))
        self.length = 0.0001
        self.floatingLength = 0.0
        self.climb = True


class IvyRoot:
    """ The class used to hold all ivy nodes growing from this root point."""
    __slots__ = ('ivyNodes', 'alive', 'parents')

    def __init__(self):
        self.ivyNodes = deque()
        self.alive = True
        self.parents = 0


class Ivy:
    """ The class holding all parameters and ivy roots."""
    """
    __slots__ = ('ivyRoots', 'primaryWeight', 'randomWeight',
                 'gravityWeight', 'adhesionWeight', 'branchingProbability',
                 'leafProbability', 'leafDensityFromHeight', 'ivySize', 'ivyLeafSize', 'ivyBranchSize',
                 'maxFloatLength', 'maxAdhesionDistance', 'maxLength')
                 """

    def __init__(self,
                 primaryWeight=0.5,
                 randomWeight=0.2,
                 gravityWeight=1.0,
                 adhesionWeight=0.1,
                 branchingProbability=0.05,
                 leafProbability=0.35,
                 leafDensityFromHeight=0.35,
                 ivySize=0.02,
                 ivyLeafSize=0.02,
                 ivyBranchSize=0.001,
                 maxFloatLength=0.5,
                 maxAdhesionDistance=1.0):



        self.ivyRoots = deque()
        self.primaryWeight = primaryWeight
        self.randomWeight = randomWeight
        self.gravityWeight = gravityWeight
        self.adhesionWeight = adhesionWeight
        self.branchingProbability = 1 - branchingProbability
        self.leafProbability = 1 - leafProbability
        self.leafDensityFromHeight = leafDensityFromHeight
        self.ivySize = ivySize
        self.ivyLeafSize = ivyLeafSize
        self.ivyBranchSize = ivyBranchSize
        self.maxFloatLength = maxFloatLength
        self.maxAdhesionDistance = maxAdhesionDistance
        self.maxLength = 0.0

        # Normalize all the weights only on initialisation
        sums = self.primaryWeight + self.randomWeight + self.adhesionWeight
        self.primaryWeight /= sums
        self.randomWeight /= sums
        self.adhesionWeight /= sums

    def seed(self, seedPos):
        # Seed the Ivy by making a new root and first node
        tmpRoot = IvyRoot()
        tmpIvy = IvyNode()
        tmpIvy.pos = seedPos

        tmpRoot.ivyNodes.append(tmpIvy)
        self.ivyRoots.append(tmpRoot)

    def grow(self, ob, bvhtree):
        # Determine the local sizes
        # local_ivySize = self.ivySize	# * radius
        # local_maxFloatLength = self.maxFloatLength  # * radius
        # local_maxAdhesionDistance = self.maxAdhesionDistance	# * radius

        for root in self.ivyRoots:
            # Make sure the root is alive, if not, skip
            if not root.alive:
                continue

            # Get the last node in the current root
            prevIvy = root.ivyNodes[-1]

            # If the node is floating for too long, kill the root
            if prevIvy.floatingLength > self.maxFloatLength:
                root.alive = False

            # Set the primary direction from the last node
            primaryVector = prevIvy.primaryDir

            # Make the random vector and normalize
            randomVector = Vector((rand_val() - 0.5, rand_val() - 0.5,
                                   rand_val() - 0.5)) + Vector((0, 0, 0.2))
            randomVector.normalize()

            # Calculate the adhesion vector
            adhesionVector = adhesion(
                    prevIvy.pos, bvhtree, self.maxAdhesionDistance)

            # Calculate the growing vector
            growVector = self.ivySize * (primaryVector * self.primaryWeight +
                                          randomVector * self.randomWeight +
                                          adhesionVector * self.adhesionWeight)

            # Find the gravity vector
            gravityVector = (self.ivySize * self.gravityWeight *
                                                            Vector((0, 0, -1)))
            gravityVector *= pow(prevIvy.floatingLength / self.maxFloatLength,
                                 0.7)

            # Determine the new position vector
            newPos = prevIvy.pos + growVector + gravityVector

            # Check for collisions with the object
            climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)

            # Update the growing vector for any collisions
            growVector = newPos - prevIvy.pos - gravityVector
            growVector.normalize()

            # Create a new IvyNode and set its properties
            tmpNode = IvyNode()
            tmpNode.climb = climbing
            tmpNode.pos = newPos
            tmpNode.primaryDir = prevIvy.primaryDir.lerp(growVector, 0.5)
            tmpNode.primaryDir.normalize()
            tmpNode.adhesionVector = adhesionVector
            tmpNode.length = prevIvy.length + (newPos - prevIvy.pos).length

            if tmpNode.length > self.maxLength:
                self.maxLength = tmpNode.length

            # If the node isn't climbing, update it's floating length
            # Otherwise set it to 0
            if not climbing:
                tmpNode.floatingLength = prevIvy.floatingLength + (newPos -
                                                            prevIvy.pos).length
            else:
                tmpNode.floatingLength = 0.0

            root.ivyNodes.append(tmpNode)

        # Loop through all roots to check if a new root is generated
        for root in self.ivyRoots:
            # Check the root is alive and isn't at high level of recursion
            if (root.parents > 3) or (not root.alive):
                continue

            # Check to make sure there's more than 1 node
            if len(root.ivyNodes) > 1:
                # Loop through all nodes in root to check if new root is grown
                for node in root.ivyNodes:
                    # Set the last node of the root and find the weighting
                    prevIvy = root.ivyNodes[-1]
                    weight = 1.0 - (cos(2.0 * pi * node.length /
                                        prevIvy.length) * 0.5 + 0.5)

                    probability = rand_val()

                    # Check if a new root is grown and if so, set its values
                    if (probability * weight > self.branchingProbability):
                        tmpNode = IvyNode()
                        tmpNode.pos = node.pos
                        tmpNode.floatingLength = node.floatingLength

                        tmpRoot = IvyRoot()
                        tmpRoot.parents = root.parents + 1

                        tmpRoot.ivyNodes.append(tmpNode)
                        self.ivyRoots.append(tmpRoot)
                        return


def adhesion(loc, bvhtree, max_l):
    # Compute the adhesion vector by finding the nearest point
    nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
    adhesion_vector = Vector((0.0, 0.0, 0.0))
    if nearest_location is not None:
        # Compute the distance to the nearest point
        adhesion_vector = nearest_location - loc
        distance = adhesion_vector.length
        # If it's less than the maximum allowed and not 0, continue
        if distance:
            # Compute the direction vector between the closest point and loc
            adhesion_vector.normalize()
            adhesion_vector *= 1.0 - distance / max_l
            # adhesion_vector *= getFaceWeight(ob.data, nearest_result[3])
    return adhesion_vector


def collision(bvhtree, pos, new_pos):
    # Check for collision with the object
    climbing = False

    corrected_new_pos = new_pos
    direction = new_pos - pos

    hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
    # If there's a collision we need to check it
    if hit_location is not None:
        # Check whether the collision is going into the object
        if direction.dot(hit_normal) < 0.0:
            reflected_direction = (new_pos - hit_location).reflect(hit_normal)

            corrected_new_pos = hit_location + reflected_direction
            climbing = True

    return climbing, corrected_new_pos


def bvhtree_from_object(ob):
    import bmesh
    bm = bmesh.new()

    depsgraph = bpy.context.evaluated_depsgraph_get()
    ob_eval = ob.evaluated_get(depsgraph)
    mesh = ob_eval.to_mesh()
    bm.from_mesh(mesh)
    bm.transform(ob.matrix_world)

    bvhtree = BVHTree.FromBMesh(bm)
    ob_eval.to_mesh_clear()
    return bvhtree

def check_mesh_faces(ob):
    me = ob.data
    if len(me.polygons) > 0:
        return True

    return False


class IvyGen(Operator):
    
    
    bl_idname = "curve.ivy_gen"
    bl_label = "IvyGen"
    bl_description = "Generate Ivy on an Mesh Object"
    bl_options = {'REGISTER', 'UNDO'}

    resetIvy: BoolProperty(
        name="Reset values",
        description="Reset all values to default",
        default=False
    )
    

    
    '''
    # not sure but it seems this is useless...
    @classmethod
    def poll(self, context):
        # Check if there's an object and whether it's a mesh
        return(True)
    '''

    # called when user clicks on Add new ivy in N-Panel
    def invoke(self, context, event):
        return(self.execute(context))


    def execute(self, context):

        wm = context.window_manager.ivy_gen_props
        
        print("expected ivy len = ",wm.totalIvyLength)
        
        
        if self.resetIvy:
            wm.totalIvyLength = 1.0 # i don't know how to retrieve the default values set in properties :-/
            wm.randomSeed = 0.0
            wm.ivySize = 0.02
            wm.maxFloatLength = 0.5
            wm.maxAdhesionDistance = 1.0
            wm.primaryWeight = 0.5
            wm.randomWeight = 0.2
            wm.gravityWeight = 1.0
            wm.adhesionWeight = 0.1
            wm.branchingProbability = 0.05
            wm.leafProbability = 0.35
            wm.leafDensityFromHeight = 0.35
            wm.ivyBranchSize = 0.001
            wm.ivyLeafSize = 0.02
            
        

        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

        # Get the active object if we gonna create an ivy.....
        if(context.window_manager.ivy_gen_props.allowIvyCreation):
            # Get the growing support object. this object must be kept selected and active
            supportObject = context.active_object
            
        else: 
            # if we want to edit an ivy instead....
            if(context.window_manager.ivy_gen_props.allowIvyEdition):
                
                # active object is the ivy branches
                if(context.active_object.parent is None):
                    print("Ivy has no growing support. It cannot be edited")
                    
                    return {'PASS_THROUGH'} 
                else:
                    selectedIvyBranches = context.active_object
                    
                    # Retrieve the seed point and bring the 3D cursor on it
                    # the seed point is the 1st curve point
                    context.scene.cursor.location = selectedIvyBranches["IVY"]["IVY_StartPos"]
                    
                    # And also retrieve the growing support object
                    supportObject = context.active_object.parent
                    
                    # now delete branches and leaves as they will be recreated                    
                    selectedIvyBranches.select_set(True)
                    for child in selectedIvyBranches.children:
                        child.select_set(True)
                        
                    bpy.ops.object.delete()
                                        
            else:
                # unknown state: no edition and no creation. We might have selected leaves....
                return {'PASS_THROUGH'} 
                

                        
            
        bvhtree = bvhtree_from_object(supportObject)

        # Check if the mesh has at least one polygon since some functions
        # are expecting them in the object's data (see T51753)
        check_face = check_mesh_faces(supportObject)
        if check_face is False:
            self.report({'WARNING'},
                        "Mesh Object doesn't have at least one Face. "
                        "Operation Cancelled")
            return {"CANCELLED"}

        # Fix the random seed
        rand_seed(wm.randomSeed)

        # Make the new ivy
        New_ivy = Ivy(wm.primaryWeight,
                      wm.randomWeight,
                      wm.gravityWeight,
                      wm.adhesionWeight,
                      wm.branchingProbability,
                      wm.leafProbability,
                      wm.leafDensityFromHeight,
                      wm.ivySize,
                      wm.ivyLeafSize,
                      wm.ivyBranchSize,
                      wm.maxFloatLength,
                      wm.maxAdhesionDistance)
                      
                      
        # Generate first root and node at the 3D cursor location
        New_ivy.seed(context.scene.cursor.location)

        maxLength = wm.totalIvyLength  # * radius

        # If we need to check time set the flag
        checkTime = False
        if(context.window_manager.ivy_gen_props.maxTime != 0.0):
            checkTime = True

        t = time.time()
        startPercent = 0.0
        checkAliveIter = [True, ]

        # Grow until 200 roots is reached or backup counter exceeds limit
        while (any(checkAliveIter) and (New_ivy.maxLength < maxLength) and (not checkTime or (time.time() - t < context.window_manager.ivy_gen_props.maxTime))):
            # Grow the ivy for this iteration
            New_ivy.grow(supportObject, bvhtree)

            # Print the proportion of ivy growth to console
            if (New_ivy.maxLength / maxLength * 100) > 10 * startPercent // 10:
                #print('%0.2f%% of Ivy nodes have grown' % (New_ivy.maxLength / maxLength * 100))
                startPercent += 10

            # Make an iterator to check if all are alive
            checkAliveIter = (r.alive for r in New_ivy.ivyRoots)

        # Create the curve and leaf geometry
        Ivy3DObject = createIvyGeometry(New_ivy, wm.growLeaves, wm.leavesCollection, supportObject)
            
        
        # and add Custom Properties for param memorization for future edition
        Ivy3DObject["IVY"] = {}
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IsSelf', 0.123)
        AddVectorPropertyToObject(Ivy3DObject,'IVY_StartPos',context.scene.cursor.location)
        
        AddIntPropertyToObject(Ivy3DObject,'IVY_RandSeed',wm.randomSeed)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_TotalIvyLength',wm.totalIvyLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvySegmentSize',wm.ivySize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxFloatLength',wm.maxFloatLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxAdhesionDistance',wm.maxAdhesionDistance)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_PrimaryWeight',wm.primaryWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_RandomWeight',wm.randomWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_GravityWeight',wm.gravityWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_AdhesionWeight',wm.adhesionWeight)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_BranchingProbability',wm.branchingProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyBranchSize',wm.ivyBranchSize)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyLeafSize',wm.ivyLeafSize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafProbability',wm.leafProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafDensityFromHeight',wm.leafDensityFromHeight)
        AddBoolPropertyToObject( Ivy3DObject,'IVY_GrowLeaves',wm.growLeaves)
        
        # I won't memorize leaves collection as creation/edition with thousands of complex
        # meshe might be heavy and freeze blender for too long, making ivy edition/creation boring and useless....
        # then i for the use of square leaves and when the user is happy with the preview, he chooses the
        # collection to take the leaves from.
        #Ivy3DObject["IVY"]["IVY_LeavesCollection"] = wm.leavesCollection
        #wm.leavesCollection = Ivy3DObject["IVY"]["IVY_LeavesCollection"] # test: is cool mem okay ?


        
        
        print("Ivy generated in %0.2f s ( but we don't care )" % (time.time() - t))
       
                
        if(context.window_manager.ivy_gen_props.allowIvyEdition):
            # if we are in ivy edition mode then lets reselect the ivy branches
            #if(context.window_manager.ivy_gen_props.allowIvyEdition):
            for ob in bpy.context.selected_objects:
                ob.select_set(False)
                
            
            Ivy3DObject.select_set(True)
            
            bpy.context.view_layer.objects.active = Ivy3DObject
            
        # not sure this is usefull.....    
        context.window_manager.ivy_gen_props.allowIvyCreation = False
        context.window_manager.ivy_gen_props.allowIvyEdition = False
        
        self.resetIvy = False

        return {'FINISHED'}

    def draw(self, context):
        layout = self.layout
        #wm = context.window_manager
        wm = context.window_manager.ivy_gen_props
        col = layout.column(align=True,)

        layout.prop(self, "resetIvy", icon="COLOR_RED")
        
        col = layout.column(align=True)
        col.label(text="Generation Settings:")
        col.prop(wm, "randomSeed")

        col = layout.column(align=True)
        col.label(text="Size Settings:")
        col.prop(wm, "totalIvyLength")
        col.prop(wm, "ivySize")
        col.prop(wm, "maxFloatLength")
        col.prop(wm, "maxAdhesionDistance")

        col = layout.column(align=True)
        col.label(text="Weight Settings:")
        col.prop(wm, "primaryWeight")
        col.prop(wm, "randomWeight")
        col.prop(wm, "gravityWeight")
        col.prop(wm, "adhesionWeight")

        col = layout.column(align=True)
        col.label(text="Branch Settings:")
        col.prop(wm, "branchingProbability")
        col.prop(wm, "ivyBranchSize")

        col = layout.column(align=True)
        ro = layout.row(align=True)
        ro.prop(wm, "growLeaves")

        if wm.growLeaves:
            ro.prop(wm, "leavesCollection")
            #if(wm.leavesCollection is not None):
                #for ooo in wm.leavesCollection.all_objects:
                    #print("obj: ", ooo.name)
            col = layout.column(align=True)
            col.label(text="Leaf Settings:")
            col.prop(wm, "ivyLeafSize")
            col.prop(wm, "leafProbability")
            col.prop(wm, "leafDensityFromHeight")
        


class Ivy_N_Panel(Panel):
    bl_label = "Ivy Generator"
    bl_idname = "CURVE_PT_IvyNPanel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Create"
    bl_context = "objectmode"
    bl_options = {"DEFAULT_CLOSED"}

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

        wm = context.window_manager.ivy_gen_props

        ob = bpy.context.view_layer.objects.active # here active object is properly retrieved
        


        # by default, disable ivy edition and creation
        wm.allowIvyEdition = False
        wm.allowIvyCreation = False

        # if an object is selected....                    
        if(ob is not None):
            # if object is an ivy, disable ivy creation and allow edition ( only on branches selection )
            if(ob.get("IVY") is not None):
                #print("object is an ivy !!!!!")
                
                if(ob["IVY"]["IVY_IsSelf"]==0.123):
                    print("Is branches and can be edited")
                    
                    # here we grab the ivy instance and its parameters
                    # and we enable edition
                    wm.allowIvyEdition = True
                    
                    selectedIvyBranches = ob
                    
                    # retrieve all custom properties from ivy branches and set up values in the window controls
                    wm.randomSeed            = selectedIvyBranches["IVY"]['IVY_RandSeed']
                    wm.totalIvyLength        = selectedIvyBranches["IVY"]['IVY_TotalIvyLength']
                    wm.ivySize               = selectedIvyBranches["IVY"]['IVY_IvySegmentSize']
                    wm.maxFloatLength        = selectedIvyBranches["IVY"]['IVY_MaxFloatLength']
                    wm.maxAdhesionDistance   = selectedIvyBranches["IVY"]['IVY_MaxAdhesionDistance']
                    wm.primaryWeight         = selectedIvyBranches["IVY"]['IVY_PrimaryWeight']
                    wm.randomWeight          = selectedIvyBranches["IVY"]['IVY_RandomWeight']
                    wm.gravityWeight         = selectedIvyBranches["IVY"]['IVY_GravityWeight']
                    wm.adhesionWeight        = selectedIvyBranches["IVY"]['IVY_AdhesionWeight']
                    wm.branchingProbability  = selectedIvyBranches["IVY"]['IVY_BranchingProbability']
                    wm.ivyBranchSize         = selectedIvyBranches["IVY"]['IVY_IvyBranchSize']
                    wm.ivyLeafSize           = selectedIvyBranches["IVY"]['IVY_IvyLeafSize']
                    wm.leafProbability       = selectedIvyBranches["IVY"]['IVY_LeafProbability']
                    wm.leafDensityFromHeight = selectedIvyBranches["IVY"]['IVY_LeafDensityFromHeight']
                    wm.growLeaves            = selectedIvyBranches["IVY"]['IVY_GrowLeaves']                    
                    
                else:
                    print("Is leaves and can NOT be edited. Please select branches to enable edition")
              
            else:
                #object is not an ivy: ivy can be created on it if it is an object that have a mesh
                if((ob.type == 'MESH') and (context.mode == 'OBJECT')):
                    wm.allowIvyCreation = True
            


        

        if(wm.allowIvyCreation):
            prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE")
            wm.leavesCollection = None # no leaves for ivy creation to avoid blender freeze on big ivis
        else:        
            if(wm.allowIvyEdition):
                prop_def = col.operator("curve.ivy_gen", text="Edit this ivy", icon="CURVE_DATA")
                wm.leavesCollection = None # no leaves for ivy creation to avoid blender freeze on big ivis
            
        
        
        col = layout.column(align=True)
        col.label(text="Generation Time limit:")
        col.prop(wm, "maxTime")

            


class IvyGenProperties(PropertyGroup):
#bpy.types.Object.my_custom_property = bpy.props.BoolProperty(name="Test Property", description="This should show up as API Defined")
#bpy.context.active_object.my_custom_property = True
    
    # values that are used as flags for control, and that are not editable
    allowIvyEdition: BoolProperty(
        name="allowIvyEdition",
        description="allow selected ivy edition",
        default=False
    )

    allowIvyCreation: BoolProperty(
        name="allowIvyCreation",
        description="allow ivy creation on selected object",
        default=False
    )
    # ===========================================================================
            
    # value set in Left N panel
    maxTime: FloatProperty(
        name="Maximum Time",
        description="The maximum time to run the generation for "
                    "in seconds generation (0.0 = Disabled)",
        default=20.0,
        min=0.0,
        soft_max=10
    )
    # ===========================================================================

                    
                    
    # values set in parameters bottom-left panel
    randomSeed: IntProperty(
        name="Random Seed",
        description="The seed governing random generation",
        default=0,
            min=0
        )
    # --------------------------------------------------------
    totalIvyLength: FloatProperty(
        name="Total Ivy Length",
        description="Maximum ivy length in Blender Units",
        default=1.0,
        min=0.0,
        soft_max=3.0,
        subtype='DISTANCE',
        unit='LENGTH',
        precision=3,
        step=10.0
        )
    # --------------------------------------------------------
    ivySize: FloatProperty(
        name="Branch segment Length",
        description="The length of an ivy segment"
                    " Units",
        default=0.02,
        min=0.005,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    maxFloatLength: FloatProperty(
        name="Max Float Length",
        description="The maximum distance that a branch "
                    "can live while floating",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    maxAdhesionDistance: FloatProperty(
        name="Max Adhesion Length",
        description="The maximum distance that a branch "
                    "will feel the effects of adhesion",
        default=1.0,
        min=0.0,
        soft_max=2.0,
        precision=2
    )
    # --------------------------------------------------------
    primaryWeight: FloatProperty(
        name="Primary Weight",
        description="Weighting given to the current direction",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    randomWeight: FloatProperty(
        name="Random Weight",
        description="Weighting given to the random direction",
        default=0.2,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    gravityWeight: FloatProperty(
        name="Gravity Weight",
        description="Weighting given to the gravity direction",
        default=1.0,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    adhesionWeight: FloatProperty(
        name="Adhesion Weight",
        description="Weighting given to the adhesion direction",
        default=0.1,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    branchingProbability: FloatProperty(
        name="Branching Probability",
        description="Probability of a new branch forming",
        default=0.05,
        min=0.0,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    ivyBranchSize: FloatProperty(
        name="Branch start diameter",
        description="The size of the ivy branches",
        default=0.001,
        min=0.0,
        soft_max=0.1,
        precision=4,
        step=0.1
    )
    # --------------------------------------------------------
    ivyLeafSize: FloatProperty(
        name="Leaf Size",
        description="The size of the ivy leaves",
        default=0.02,
        min=0.0,
        soft_max=0.5,
        precision=3
    )
    # --------------------------------------------------------
    leafProbability: FloatProperty(
        name="Leaf Probability",
        description="Probability of a leaf forming",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    leafDensityFromHeight: FloatProperty(
        name="leaf density VS height factor",
        description="defines wether leaf density is controlled by branches height or not.",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    growLeaves: BoolProperty(
        name="Grow Leaves",
        description="Grow leaves or not",
        default=True
    )
    # --------------------------------------------------------
    leavesCollection: PointerProperty(
        type=bpy.types.Collection,
        name="--->",
        description="Collection of leaves. If empty, squares will be used"
    )
    # --------------------------------------------------------

    


classes = (
    IvyGen,
    IvyGenProperties,
    Ivy_N_Panel
)



def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.WindowManager.ivy_gen_props = PointerProperty(type=IvyGenProperties)


def unregister():
    del bpy.types.WindowManager.ivy_gen_props

    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()


 

Problems created with this version:

  • use of complex mesh leaves is much heavier than simple square ones. This is the reason why i don’t keep leaves collection between edition and always get back to square leaves.
  • when deleting the collection from the collection property, the parameters window closes. It shouldn’t and i don’t even understand why it does ( shitty python dev i’m )

Thanks all and happy blending ! :smiley:

1 Like

Hi all :slight_smile:

Small cosmetic addings:

  • display of the leaves number ( giving an idea of the mesh complexity )
  • keeping of the previously assigned materials to branches and leaves
  • rotated the basic leaves so that they’re properly oriented
  • lowered leaf size increment to 0.01

Here’s the code:

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

bl_info = {
    "name": "IvyGen",
    "author": "testscreenings, PKHG, TrumanBlending, some Blenderartists users",
    "version": (0, 1, 6),
    "blender": (2, 80, 0),
    "location": "View3D > Sidebar > Ivy Generator (Create Tab)",
    "description": "Adds generated ivy to a mesh object starting "
                   "at the 3D cursor",
    "warning": "",
    "doc_url": "{BLENDER_MANUAL_URL}/addons/add_curve/ivy_gen.html",
    "category": "Add Curve",
}


import bpy
from bpy.types import (
    Operator,
    Panel,
    PropertyGroup,
)
from bpy.props import (
    BoolProperty,
    FloatProperty,
    IntProperty,
    PointerProperty,
    CollectionProperty,
)
from mathutils.bvhtree import BVHTree
from mathutils import (
    Euler,
    Vector,
    Matrix,
)
from collections import deque
from math import (
    pow, cos,
    pi, atan2,
    radians,
)
from random import (
    random as rand_val,
    seed as rand_seed,
)
import time
import mathutils
import random
from rna_prop_ui import rna_idprop_ui_prop_get


#====================================================
#=                                                  =
#= Adds a custom property to an object              =
#=                                                  =
#= IN: - object to add the property to
#=     - property name
#=     - property value
#=                                                  
#= OUT: nothing
#=                                                  =
#====================================================
def AddIntPropertyToObject(object, propertyName:str, propertyVal:int, toolTip:str="DO NOT DELETE THIS INT") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddFloatPropertyToObject(object, propertyName:str, propertyVal:float, toolTip:str="DO NOT DELETE THIS FLOAT") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
def AddStringPropertyToObject(object, propertyName:str, propertyVal:str, toolTip:str="DO NOT DELETE THIS STRING") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
def AddBoolPropertyToObject(object, propertyName:str, propertyVal:bool, toolTip:str="DO NOT DELETE THIS BOOL") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
    # python is such a morons shit that it don't support function overloading ! LOOOOOL !!!!!! let's get back to 70's
def AddVectorPropertyToObject(object, propertyName:str, propertyVal:Vector, toolTip:str="DO NOT DELETE THIS VECTOR") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip
    
def AddCollectionPropertyToObject(object, propertyName:str, propertyVal, toolTip:str="DO NOT DELETE THIS COLLECTION") -> None:
    object["IVY"][propertyName] = propertyVal
    ui = rna_idprop_ui_prop_get(object,propertyName, create=True)
    ui['description'] = toolTip      
    
    
#========================================================================================================    



def createIvyGeometry(IVY, growLeaves:bool, collection4Leaves, growingSupportObject):
    
    
    
    """Create the curve geometry for IVY"""
    # Compute the local size and the gauss weight filter
    # local_ivyBranchSize = IVY.ivyBranchSize  # * radius * IVY.ivySize
    gaussWeight = (1.0, 2.0, 4.0, 7.0, 9.0, 10.0, 9.0, 7.0, 4.0, 2.0, 1.0)

    # Create a new curve and initialise it
    curve = bpy.data.curves.new("IVY", type='CURVE')
    curve.dimensions = '3D'
    curve.bevel_depth = 1
    curve.fill_mode = 'FULL'
    curve.resolution_u = 4
    
    prevSelectedObject = bpy.context.selected_objects
    #print("sel=",prevSelectedObject)
    prevActiveObject = bpy.context.active_object
    #    print("act=",prevActiveObject)
    
    
    # deal with the collections to avoid mess.....
    # 1st get the growingSupportObject collection:
    targetCollection = growingSupportObject.users_collection[0]

        
    # check wether we should use a collection for leaves or not ?
    collection4LeavesSize = 0
    useCollectionForLeaves:bool = False
    if(collection4Leaves is not None):
        useCollectionForLeaves = True
        collection4LeavesSize = len(collection4Leaves.objects) - 1
    
    # process is quite slow and deserves the work of python experts....
    # so limit the amount of leaves at dev time
    max_leaves=300000
    
    # counting leaves...
    TotalLeavesCount = 0
    

    if(growLeaves):
        
        # create a leaves mesh and object
        leavesMesh = bpy.data.meshes.new('Msh_IvyLeaf')
        leavesObject = bpy.data.objects.new('IvyLeaf',leavesMesh)
        targetCollection.objects.link(leavesObject)

        
        rotMat = Matrix.Rotation
        
        if(useCollectionForLeaves==False):
            # Create the ivy leaves
            # Order location of the vertices

            signList = ((+1.0, +0.0),
                        (-1.0, +0.0),
                        (-1.0, -2.0),
                        (+1.0, -2.0),)
            '''
            signList = ((-1.0, +1.0),
                        (-1.0, -1.0),
                        (+1.0, -1.0),
                        (+1.0, +1.0),)
            '''

            # Initialise the vertex and face lists
            LeavesVertList = deque()

            # Store the methods for faster calling
            addV = LeavesVertList.extend
        else:
            # we got to deselect all and make the leaves object active as we'll have to join objects
            for ooo in prevSelectedObject:
                ooo.select_set(False)
            if(prevActiveObject is not None):
                prevActiveObject.select_set(False)
            
            # select and activate the leaves object as we will join all instanced leaves to it...
            leavesObject.select_set(True)
            bpy.context.view_layer.objects.active = leavesObject
            
                    

    # Loop over all roots to generate its nodes
    for root in IVY.ivyRoots:
        # Only grow if more than one node
        numNodes = len(root.ivyNodes)
        if numNodes > 1:
            # Calculate the local radius
            local_ivyBranchRadius = 1.0 / (root.parents + 1) # Cyril  +1.0
            prevIvyLength = 1.0 / root.ivyNodes[-1].length
            splineVerts = [ax for n in root.ivyNodes for ax in n.pos.to_4d()]

            radiusConstant = local_ivyBranchRadius * IVY.ivyBranchSize
            splineRadii = [radiusConstant * (1.3 - n.length * prevIvyLength)
                           for n in root.ivyNodes]

            # Add the poly curve and set coords and radii
            newSpline = curve.splines.new(type='POLY')
            newSpline.points.add(len(splineVerts) // 4 - 1)
            newSpline.points.foreach_set('co', splineVerts)
            newSpline.points.foreach_set('radius', splineRadii)

            # Loop over all nodes in the root
            for i, n in enumerate(root.ivyNodes):
                for k in range(len(gaussWeight)):
                    idx = max(0, min(i + k - 5, numNodes - 1))
                    n.smoothAdhesionVector += (gaussWeight[k] *
                                               root.ivyNodes[idx].adhesionVector)
                n.smoothAdhesionVector /= 56.0
                n.adhesionLength = n.smoothAdhesionVector.length
                n.smoothAdhesionVector.normalize()

                if growLeaves and (i < numNodes - 1):
                    node = root.ivyNodes[i]
                    nodeNext = root.ivyNodes[i + 1]

                    # Find the weight and normalize the smooth adhesion vector
                    weight = pow(node.length * prevIvyLength, 0.7)

                    # Calculate the ground ivy and the new weight
                    groundIvy = max(0.0, -node.smoothAdhesionVector.z)
                    weight += groundIvy * pow(1 - node.length *
                                                              prevIvyLength, 2)

                    # Find the alignment weight
                    alignmentWeight = node.adhesionLength

                    # Calculate the needed angles
                    phi = atan2(node.smoothAdhesionVector.y,
                                node.smoothAdhesionVector.x) - pi / 2.0

                    theta = (0.5 *
                        node.smoothAdhesionVector.angle(Vector((0, 0, -1)), 0))

                    # Find the size weight
                    sizeWeight = 1.5 - (cos(2 * pi * weight) * 0.5 + 0.5)

                    # Randomise the angles
                    phi += (rand_val() - 0.5) * (1.3 - alignmentWeight)
                    theta += (rand_val() - 0.5) * (1.1 - alignmentWeight)

                    # Calculate the leaf size an append the face to the list
                    leafSize = IVY.ivyLeafSize * sizeWeight

                    for j in range(10):
                        # Generate the probability
                        probability = rand_val()

                        # If we need to grow a leaf, do so
                        # cyril if (probability * weight) > IVY.leafProbability:
                        if (probability * (1.0-IVY.leafDensityFromHeight*(1.0-weight))) > IVY.leafProbability:

                            
                            # Generate the random vector
                            #randomVector = Vector((rand_val()-0.5,rand_val()-0.5,rand_val()-0.5,))
                            # no randomness for leaves origin !!!!
                            randomVector = Vector((0,0,0))

                            # Find the leaf center
                            center = (node.pos.lerp(nodeNext.pos, j / 10.0) +
                                               IVY.ivyLeafSize * randomVector)

                            # For each of the verts, rotate/scale and append
                            basisVecX = Vector((1, 0, 0))
                            basisVecY = Vector((0, 1, 0))

                            horiRot = rotMat(theta, 3, 'X')
                            vertRot = rotMat(phi, 3, 'Z')

                            basisVecX.rotate(horiRot)
                            basisVecY.rotate(horiRot)

                            basisVecX.rotate(vertRot)
                            basisVecY.rotate(vertRot)

                            basisVecX *= leafSize / 5.0 # /5.0 for coherent leaf size....
                            basisVecY *= leafSize / 5.0

                            # generates random here to ensure same values between square leaves and instances leaves.
                            randomValue = random.randint(0,1000)

                            # add 1 leaf to count
                            TotalLeavesCount +=1
                            
                            if(useCollectionForLeaves==False):
                                addV([k1 * basisVecX + k2 * basisVecY + center for
                                                           k1, k2 in signList])
                            else:
                                if(max_leaves >0):
                                    max_leaves=max_leaves-1
                                    
                                    # randomly choose a leaf in collection and copy it
                                    leafToClone = collection4Leaves.objects[randomValue % collection4LeavesSize]
                                    newLeaf = leafToClone.copy()
                                    newLeaf.data = leafToClone.data.copy()
                                    
                                    #print("cloned : ",newLeaf)
                                    #print("active = ",bpy.context.active_object) #bpy.context.selected_objects)
                                    
                                    # scale the leaf
                                    newLeaf.scale *= leafSize*10.0 # scale with 10 for coherent size...
                                    # move the leaf to right place
                                    newLeaf.location = center
                                    
                                    # leaf rotation on X and Z global axis
                                    newLeaf.rotation_euler = Euler((theta,0,phi), 'XYZ')
                                    
                                    # I guess setting a 4x4 matrix handling scale pos and rot would be faster^^
                                    
                                    # and join it with the leavesObject
                                    targetCollection.objects.link(newLeaf)
                                    newLeaf.select_set(True)
                                                                

    # here leaves a created if needed and all selected. Join all in one mesh
    if(useCollectionForLeaves==True):
        bpy.ops.object.join()

                                                                    

    # Add the object and link to scene in the growingSupportObject collection
    newCurve = bpy.data.objects.new("IVY_Curve", curve)
    
    # add branches to the proper collection....
    targetCollection.objects.link(newCurve)
    
    if growLeaves:
        if(useCollectionForLeaves==False):

            faceList = [[4 * i + l for l in range(4)] for i in range(len(LeavesVertList) // 4)]

            # Generate the new leaves mesh and link
            leavesMesh.from_pydata(LeavesVertList, [], faceList)
            leavesMesh.update(calc_edges=True)
            
            leavesObject.data = leavesMesh


            # Create and set the uv texture coords
            leavesMesh.uv_layers.new(name="UV_diffuse")

            # TODO, this is non-functional, default uvs are ok?
            '''
            for d in tex.data:
                uv1, uv2, uv3, uv4 = signList
            '''
        else:
            print("use of collection here....")

        # parent the leaves to the branches and tag it as 'is_an_ivy'
        leavesObject.parent = newCurve
        leavesObject["IVY"] = {} # create "IVY" dictionnary
        AddFloatPropertyToObject(leavesObject,'IVY_IsSelf', 0.0)

        newCurve["IVY"] = {}
        newCurve["IVY"]["IVY_TotalLeavesCount"] = TotalLeavesCount    

        # unselect the leaves object 
        leavesObject.select_set(False)

        
    # And now, as a final step, lets parent the ivy to the object it was grown on.
    # this will allow to reselect this mesh for edition....
    newCurve.parent = growingSupportObject


    # before quit, we reselect and activate what was at entry
    for ooo in prevSelectedObject:
        ooo.select_set(True)
    if(prevActiveObject is not None):
        prevActiveObject.select_set(True)
    
    bpy.context.view_layer.objects.active =prevActiveObject


    
    # return the branches 3D object
    return(newCurve)



class IvyNode:
    """ The basic class used for each point on the ivy which is grown."""
    __slots__ = ('pos', 'primaryDir', 'adhesionVector', 'adhesionLength',
                 'smoothAdhesionVector', 'length', 'floatingLength', 'climb')

    def __init__(self):
        self.pos = Vector((0, 0, 0))
        self.primaryDir = Vector((0, 0, 1))
        self.adhesionVector = Vector((0, 0, 0))
        self.smoothAdhesionVector = Vector((0, 0, 0))
        self.length = 0.0001
        self.floatingLength = 0.0
        self.climb = True


class IvyRoot:
    """ The class used to hold all ivy nodes growing from this root point."""
    __slots__ = ('ivyNodes', 'alive', 'parents')

    def __init__(self):
        self.ivyNodes = deque()
        self.alive = True
        self.parents = 0


class Ivy:
    """ The class holding all parameters and ivy roots."""
    """
    __slots__ = ('ivyRoots', 'primaryWeight', 'randomWeight',
                 'gravityWeight', 'adhesionWeight', 'branchingProbability',
                 'leafProbability', 'leafDensityFromHeight', 'ivySize', 'ivyLeafSize', 'ivyBranchSize',
                 'maxFloatLength', 'maxAdhesionDistance', 'maxLength')
                 """

    def __init__(self,
                 primaryWeight=0.5,
                 randomWeight=0.2,
                 gravityWeight=1.0,
                 adhesionWeight=0.1,
                 branchingProbability=0.05,
                 leafProbability=0.35,
                 leafDensityFromHeight=0.35,
                 ivySize=0.02,
                 ivyLeafSize=0.02,
                 ivyBranchSize=0.001,
                 maxFloatLength=0.5,
                 maxAdhesionDistance=1.0):



        self.ivyRoots = deque()
        self.primaryWeight = primaryWeight
        self.randomWeight = randomWeight
        self.gravityWeight = gravityWeight
        self.adhesionWeight = adhesionWeight
        self.branchingProbability = 1 - branchingProbability
        self.leafProbability = 1 - leafProbability
        self.leafDensityFromHeight = leafDensityFromHeight
        self.ivySize = ivySize
        self.ivyLeafSize = ivyLeafSize
        self.ivyBranchSize = ivyBranchSize
        self.maxFloatLength = maxFloatLength
        self.maxAdhesionDistance = maxAdhesionDistance
        self.maxLength = 0.0

        # Normalize all the weights only on initialisation
        sums = self.primaryWeight + self.randomWeight + self.adhesionWeight
        self.primaryWeight /= sums
        self.randomWeight /= sums
        self.adhesionWeight /= sums

    def seed(self, seedPos):
        # Seed the Ivy by making a new root and first node
        tmpRoot = IvyRoot()
        tmpIvy = IvyNode()
        tmpIvy.pos = seedPos

        tmpRoot.ivyNodes.append(tmpIvy)
        self.ivyRoots.append(tmpRoot)

    def grow(self, ob, bvhtree):
        # Determine the local sizes
        # local_ivySize = self.ivySize	# * radius
        # local_maxFloatLength = self.maxFloatLength  # * radius
        # local_maxAdhesionDistance = self.maxAdhesionDistance	# * radius

        for root in self.ivyRoots:
            # Make sure the root is alive, if not, skip
            if not root.alive:
                continue

            # Get the last node in the current root
            prevIvy = root.ivyNodes[-1]

            # If the node is floating for too long, kill the root
            if prevIvy.floatingLength > self.maxFloatLength:
                root.alive = False

            # Set the primary direction from the last node
            primaryVector = prevIvy.primaryDir

            # Make the random vector and normalize
            randomVector = Vector((rand_val() - 0.5, rand_val() - 0.5,
                                   rand_val() - 0.5)) + Vector((0, 0, 0.2))
            randomVector.normalize()

            # Calculate the adhesion vector
            adhesionVector = adhesion(
                    prevIvy.pos, bvhtree, self.maxAdhesionDistance)

            # Calculate the growing vector
            growVector = self.ivySize * (primaryVector * self.primaryWeight +
                                          randomVector * self.randomWeight +
                                          adhesionVector * self.adhesionWeight)

            # Find the gravity vector
            gravityVector = (self.ivySize * self.gravityWeight *
                                                            Vector((0, 0, -1)))
            gravityVector *= pow(prevIvy.floatingLength / self.maxFloatLength,
                                 0.7)

            # Determine the new position vector
            newPos = prevIvy.pos + growVector + gravityVector

            # Check for collisions with the object
            climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)

            # Update the growing vector for any collisions
            growVector = newPos - prevIvy.pos - gravityVector
            growVector.normalize()

            # Create a new IvyNode and set its properties
            tmpNode = IvyNode()
            tmpNode.climb = climbing
            tmpNode.pos = newPos
            tmpNode.primaryDir = prevIvy.primaryDir.lerp(growVector, 0.5)
            tmpNode.primaryDir.normalize()
            tmpNode.adhesionVector = adhesionVector
            tmpNode.length = prevIvy.length + (newPos - prevIvy.pos).length

            if tmpNode.length > self.maxLength:
                self.maxLength = tmpNode.length

            # If the node isn't climbing, update it's floating length
            # Otherwise set it to 0
            if not climbing:
                tmpNode.floatingLength = prevIvy.floatingLength + (newPos -
                                                            prevIvy.pos).length
            else:
                tmpNode.floatingLength = 0.0

            root.ivyNodes.append(tmpNode)

        # Loop through all roots to check if a new root is generated
        for root in self.ivyRoots:
            # Check the root is alive and isn't at high level of recursion
            if (root.parents > 3) or (not root.alive):
                continue

            # Check to make sure there's more than 1 node
            if len(root.ivyNodes) > 1:
                # Loop through all nodes in root to check if new root is grown
                for node in root.ivyNodes:
                    # Set the last node of the root and find the weighting
                    prevIvy = root.ivyNodes[-1]
                    weight = 1.0 - (cos(2.0 * pi * node.length /
                                        prevIvy.length) * 0.5 + 0.5)

                    probability = rand_val()

                    # Check if a new root is grown and if so, set its values
                    if (probability * weight > self.branchingProbability):
                        tmpNode = IvyNode()
                        tmpNode.pos = node.pos
                        tmpNode.floatingLength = node.floatingLength

                        tmpRoot = IvyRoot()
                        tmpRoot.parents = root.parents + 1

                        tmpRoot.ivyNodes.append(tmpNode)
                        self.ivyRoots.append(tmpRoot)
                        return


def adhesion(loc, bvhtree, max_l):
    # Compute the adhesion vector by finding the nearest point
    nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
    adhesion_vector = Vector((0.0, 0.0, 0.0))
    if nearest_location is not None:
        # Compute the distance to the nearest point
        adhesion_vector = nearest_location - loc
        distance = adhesion_vector.length
        # If it's less than the maximum allowed and not 0, continue
        if distance:
            # Compute the direction vector between the closest point and loc
            adhesion_vector.normalize()
            adhesion_vector *= 1.0 - distance / max_l
            # adhesion_vector *= getFaceWeight(ob.data, nearest_result[3])
    return adhesion_vector


def collision(bvhtree, pos, new_pos):
    # Check for collision with the object
    climbing = False

    corrected_new_pos = new_pos
    direction = new_pos - pos

    hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
    # If there's a collision we need to check it
    if hit_location is not None:
        # Check whether the collision is going into the object
        if direction.dot(hit_normal) < 0.0:
            reflected_direction = (new_pos - hit_location).reflect(hit_normal)

            corrected_new_pos = hit_location + reflected_direction
            climbing = True

    return climbing, corrected_new_pos


def bvhtree_from_object(ob):
    import bmesh
    bm = bmesh.new()

    depsgraph = bpy.context.evaluated_depsgraph_get()
    ob_eval = ob.evaluated_get(depsgraph)
    mesh = ob_eval.to_mesh()
    bm.from_mesh(mesh)
    bm.transform(ob.matrix_world)

    bvhtree = BVHTree.FromBMesh(bm)
    ob_eval.to_mesh_clear()
    return bvhtree

def check_mesh_faces(ob):
    me = ob.data
    if len(me.polygons) > 0:
        return True

    return False


class IvyGen(Operator):
    
    
    bl_idname = "curve.ivy_gen"
    bl_label = "IvyGen"
    bl_description = "Generate Ivy on an Mesh Object"
    bl_options = {'REGISTER', 'UNDO'}

    resetIvy: BoolProperty(
        name="Reset values",
        description="Reset all values to default",
        default=False
    )
    

    
    '''
    # not sure but it seems this is useless...
    @classmethod
    def poll(self, context):
        # Check if there's an object and whether it's a mesh
        return(True)
    '''

    # called when user clicks on Add new ivy in N-Panel
    def invoke(self, context, event):
        return(self.execute(context))


    def execute(self, context):

        wm = context.window_manager.ivy_gen_props
        
        print("expected ivy len = ",wm.totalIvyLength)
        
        #create leaves and truk materials with any mat....
        previousLeavesMat=bpy.context.object.active_material
        previousTrunkMat=bpy.context.object.active_material
        
        if self.resetIvy:
            wm.totalIvyLength = 1.0 # i don't know how to retrieve the default values set in properties :-/
            wm.randomSeed = 0.0
            wm.ivySize = 0.02
            wm.maxFloatLength = 0.5
            wm.maxAdhesionDistance = 1.0
            wm.primaryWeight = 0.5
            wm.randomWeight = 0.2
            wm.gravityWeight = 1.0
            wm.adhesionWeight = 0.1
            wm.branchingProbability = 0.05
            wm.leafProbability = 0.35
            wm.leafDensityFromHeight = 0.35
            wm.ivyBranchSize = 0.001
            wm.ivyLeafSize = 0.02
            
        

        bpy.ops.object.mode_set(mode='EDIT', toggle=False)
        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

        # Get the active object if we gonna create an ivy.....
        if(context.window_manager.ivy_gen_props.allowIvyCreation):
            # Get the growing support object. this object must be kept selected and active
            supportObject = context.active_object
            
        else: 
            # if we want to edit an ivy instead....
            if(context.window_manager.ivy_gen_props.allowIvyEdition):
                
                # active object is the ivy branches
                if(context.active_object.parent is None):
                    print("Ivy has no growing support. It cannot be edited")
                    
                    return {'PASS_THROUGH'} 
                else:
                    selectedIvyBranches = context.active_object
                    
                    # Retrieve the seed point and bring the 3D cursor on it
                    # the seed point is the 1st curve point
                    context.scene.cursor.location = selectedIvyBranches["IVY"]["IVY_StartPos"]
                    
                    # And also retrieve the growing support object
                    supportObject = context.active_object.parent
                    
                    # and the leaves and truk materials
                    previousLeavesMat=selectedIvyBranches.children[0].active_material
                    previousTrunkMat=selectedIvyBranches.active_material                 
                    
                    # now delete branches and leaves as they will be recreated                    
                    selectedIvyBranches.select_set(True)
                    for child in selectedIvyBranches.children:
                        child.select_set(True)
                        
                    bpy.ops.object.delete()
                                        
            else:
                # unknown state: no edition and no creation. We might have selected leaves....
                return {'PASS_THROUGH'} 
                

                        
            
        bvhtree = bvhtree_from_object(supportObject)

        # Check if the mesh has at least one polygon since some functions
        # are expecting them in the object's data (see T51753)
        check_face = check_mesh_faces(supportObject)
        if check_face is False:
            self.report({'WARNING'},
                        "Mesh Object doesn't have at least one Face. "
                        "Operation Cancelled")
            return {"CANCELLED"}

        # Fix the random seed
        rand_seed(wm.randomSeed)

        # Make the new ivy
        New_ivy = Ivy(wm.primaryWeight,
                      wm.randomWeight,
                      wm.gravityWeight,
                      wm.adhesionWeight,
                      wm.branchingProbability,
                      wm.leafProbability,
                      wm.leafDensityFromHeight,
                      wm.ivySize,
                      wm.ivyLeafSize,
                      wm.ivyBranchSize,
                      wm.maxFloatLength,
                      wm.maxAdhesionDistance)
                      
                      
        # Generate first root and node at the 3D cursor location
        New_ivy.seed(context.scene.cursor.location)

        maxLength = wm.totalIvyLength  # * radius

        # If we need to check time set the flag
        checkTime = False
        if(context.window_manager.ivy_gen_props.maxTime != 0.0):
            checkTime = True

        t = time.time()
        startPercent = 0.0
        checkAliveIter = [True, ]

        # Grow until 200 roots is reached or backup counter exceeds limit
        while (any(checkAliveIter) and (New_ivy.maxLength < maxLength) and (not checkTime or (time.time() - t < context.window_manager.ivy_gen_props.maxTime))):
            # Grow the ivy for this iteration
            New_ivy.grow(supportObject, bvhtree)

            # Print the proportion of ivy growth to console
            if (New_ivy.maxLength / maxLength * 100) > 10 * startPercent // 10:
                #print('%0.2f%% of Ivy nodes have grown' % (New_ivy.maxLength / maxLength * 100))
                startPercent += 10

            # Make an iterator to check if all are alive
            checkAliveIter = (r.alive for r in New_ivy.ivyRoots)

        # Create the curve and leaf geometry
        Ivy3DObject = createIvyGeometry(New_ivy, wm.growLeaves, wm.leavesCollection, supportObject)
        
        # and assign materials of previous ivy...
        if(previousTrunkMat is not None):
            Ivy3DObject.active_material = previousTrunkMat
        if(previousLeavesMat is not None):
            Ivy3DObject.children[0].active_material = previousLeavesMat
        
        
        # and add Custom Properties for param memorization for future edition
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IsSelf', 0.123)
        AddVectorPropertyToObject(Ivy3DObject,'IVY_StartPos',context.scene.cursor.location)
        
        AddIntPropertyToObject(Ivy3DObject,'IVY_RandSeed',wm.randomSeed)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_TotalIvyLength',wm.totalIvyLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvySegmentSize',wm.ivySize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxFloatLength',wm.maxFloatLength)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_MaxAdhesionDistance',wm.maxAdhesionDistance)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_PrimaryWeight',wm.primaryWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_RandomWeight',wm.randomWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_GravityWeight',wm.gravityWeight)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_AdhesionWeight',wm.adhesionWeight)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_BranchingProbability',wm.branchingProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyBranchSize',wm.ivyBranchSize)

        AddFloatPropertyToObject(Ivy3DObject,'IVY_IvyLeafSize',wm.ivyLeafSize)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafProbability',wm.leafProbability)
        AddFloatPropertyToObject(Ivy3DObject,'IVY_LeafDensityFromHeight',wm.leafDensityFromHeight)
        AddBoolPropertyToObject( Ivy3DObject,'IVY_GrowLeaves',wm.growLeaves)
        wm.TotalLeavesCount = Ivy3DObject["IVY"]["IVY_TotalLeavesCount"]

        
        # I won't memorize leaves collection as creation/edition with thousands of complex
        # meshe might be heavy and freeze blender for too long, making ivy edition/creation boring and useless....
        # then i for the use of square leaves and when the user is happy with the preview, he chooses the
        # collection to take the leaves from.
        #Ivy3DObject["IVY"]["IVY_LeavesCollection"] = wm.leavesCollection
        #wm.leavesCollection = Ivy3DObject["IVY"]["IVY_LeavesCollection"] # test: is cool mem okay ?


        
        
        print("Ivy generated in %0.2f s ( but we don't care )" % (time.time() - t))
       
                
        if(context.window_manager.ivy_gen_props.allowIvyEdition):
            # if we are in ivy edition mode then lets reselect the ivy branches
            #if(context.window_manager.ivy_gen_props.allowIvyEdition):
            for ob in bpy.context.selected_objects:
                ob.select_set(False)
                
            
            Ivy3DObject.select_set(True)
            
            bpy.context.view_layer.objects.active = Ivy3DObject
            
        # not sure this is usefull.....    
        context.window_manager.ivy_gen_props.allowIvyCreation = False
        context.window_manager.ivy_gen_props.allowIvyEdition = False
        
        self.resetIvy = False

        return {'FINISHED'}






    def draw(self, context):
        
        layout = self.layout
        wm = context.window_manager.ivy_gen_props
        col = layout.column(align=True,)

        layout.prop(self, "resetIvy", icon="BACK")
        
        col = layout.column(align=True)
        col.label(text="Generation Settings:")
        col.prop(wm, "randomSeed")

        col = layout.column(align=True)
        col.label(text="Size Settings:")
        col.prop(wm, "totalIvyLength")
        col.prop(wm, "ivySize")
        col.prop(wm, "maxFloatLength")
        col.prop(wm, "maxAdhesionDistance")

        col = layout.column(align=True)
        col.label(text="Weight Settings:")
        col.prop(wm, "primaryWeight")
        col.prop(wm, "randomWeight")
        col.prop(wm, "gravityWeight")
        col.prop(wm, "adhesionWeight")

        col = layout.column(align=True)
        col.label(text="Branch Settings:")
        col.prop(wm, "branchingProbability")
        col.prop(wm, "ivyBranchSize")

        col = layout.column(align=True)
        ro = layout.row(align=True)
        ro.prop(wm, "growLeaves")

        if wm.growLeaves:
            ro.prop(wm, "leavesCollection")
            #if(wm.leavesCollection is not None):
                #for ooo in wm.leavesCollection.all_objects:
                    #print("obj: ", ooo.name)
            col = layout.column(align=True)
            col.label(text=("Leaf Settings: "+str(wm.TotalLeavesCount)+" leaves"))
            
            col.prop(wm, "ivyLeafSize")
            col.prop(wm, "leafProbability")
            col.prop(wm, "leafDensityFromHeight")
        


class Ivy_N_Panel(Panel):
    bl_label = "Ivy Generator"
    bl_idname = "CURVE_PT_IvyNPanel"
    bl_space_type = "VIEW_3D"
    bl_region_type = "UI"
    bl_category = "Create"
    bl_context = "objectmode"
    bl_options = {"DEFAULT_CLOSED"}

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

        wm = context.window_manager.ivy_gen_props

        ob = bpy.context.view_layer.objects.active # here active object is properly retrieved
        


        # by default, disable ivy edition and creation
        wm.allowIvyEdition = False
        wm.allowIvyCreation = False

        # if an object is selected....                    
        if(ob is not None):
            # if object is an ivy, disable ivy creation and allow edition ( only on branches selection )
            if(ob.get("IVY") is not None):
                #print("object is an ivy !!!!!")
                
                if(ob["IVY"]["IVY_IsSelf"]==0.123):
                    print("Is branches and can be edited")
                    
                    # here we grab the ivy instance and its parameters
                    # and we enable edition
                    wm.allowIvyEdition = True
                    
                    selectedIvyBranches = ob
                    
                    # retrieve all custom properties from ivy branches and set up values in the window controls
                    wm.randomSeed            = selectedIvyBranches["IVY"]['IVY_RandSeed']
                    wm.totalIvyLength        = selectedIvyBranches["IVY"]['IVY_TotalIvyLength']
                    wm.ivySize               = selectedIvyBranches["IVY"]['IVY_IvySegmentSize']
                    wm.maxFloatLength        = selectedIvyBranches["IVY"]['IVY_MaxFloatLength']
                    wm.maxAdhesionDistance   = selectedIvyBranches["IVY"]['IVY_MaxAdhesionDistance']
                    wm.primaryWeight         = selectedIvyBranches["IVY"]['IVY_PrimaryWeight']
                    wm.randomWeight          = selectedIvyBranches["IVY"]['IVY_RandomWeight']
                    wm.gravityWeight         = selectedIvyBranches["IVY"]['IVY_GravityWeight']
                    wm.adhesionWeight        = selectedIvyBranches["IVY"]['IVY_AdhesionWeight']
                    wm.branchingProbability  = selectedIvyBranches["IVY"]['IVY_BranchingProbability']
                    wm.ivyBranchSize         = selectedIvyBranches["IVY"]['IVY_IvyBranchSize']
                    wm.ivyLeafSize           = selectedIvyBranches["IVY"]['IVY_IvyLeafSize']
                    wm.leafProbability       = selectedIvyBranches["IVY"]['IVY_LeafProbability']
                    wm.leafDensityFromHeight = selectedIvyBranches["IVY"]['IVY_LeafDensityFromHeight']
                    wm.growLeaves            = selectedIvyBranches["IVY"]['IVY_GrowLeaves']                    
                    
                else:
                    print("Is leaves and can NOT be edited. Please select branches to enable edition")
              
            else:
                #object is not an ivy: ivy can be created on it if it is an object that have a mesh
                if((ob.type == 'MESH') and (context.mode == 'OBJECT')):
                    wm.allowIvyCreation = True
            


        

        if(wm.allowIvyCreation):
            prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE")
            wm.leavesCollection = None # no leaves for ivy creation to avoid blender freeze on big ivis
        else:        
            if(wm.allowIvyEdition):
                prop_def = col.operator("curve.ivy_gen", text="Edit this ivy", icon="CURVE_DATA")
                wm.leavesCollection = None # no leaves for ivy creation to avoid blender freeze on big ivis
            
        
        
        col = layout.column(align=True)
        col.label(text="Generation Time limit:")
        col.prop(wm, "maxTime")

            


class IvyGenProperties(PropertyGroup):
#bpy.types.Object.my_custom_property = bpy.props.BoolProperty(name="Test Property", description="This should show up as API Defined")
#bpy.context.active_object.my_custom_property = True


    # values that are used as flags for control or info, and that are not vewable/editable
    TotalLeavesCount: IntProperty(
        name="total leaves count",
        description="The number of leaves",
        default=0,
            min=0
        )
    
    allowIvyEdition: BoolProperty(
        name="allowIvyEdition",
        description="allow selected ivy edition",
        default=False
    )

    allowIvyCreation: BoolProperty(
        name="allowIvyCreation",
        description="allow ivy creation on selected object",
        default=False
    )
    # ===========================================================================
            
    # value set in Left N panel
    maxTime: FloatProperty(
        name="Maximum Time",
        description="The maximum time to run the generation for "
                    "in seconds generation (0.0 = Disabled)",
        default=20.0,
        min=0.0,
        soft_max=10
    )
    # ===========================================================================

                    
                    
    # values set in parameters bottom-left panel
    randomSeed: IntProperty(
        name="Random Seed",
        description="The seed governing random generation",
        default=0,
            min=0
        )
    # --------------------------------------------------------
    totalIvyLength: FloatProperty(
        name="Total Ivy Length",
        description="Maximum ivy length in Blender Units",
        default=1.0,
        min=0.0,
        soft_max=3.0,
        subtype='DISTANCE',
        unit='LENGTH',
        precision=3,
        step=10.0
        )
    # --------------------------------------------------------
    ivySize: FloatProperty(
        name="Branch segment Length",
        description="The length of an ivy segment"
                    " Units",
        default=0.02,
        min=0.005,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    maxFloatLength: FloatProperty(
        name="Max Float Length",
        description="The maximum distance that a branch "
                    "can live while floating",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    maxAdhesionDistance: FloatProperty(
        name="Max Adhesion Length",
        description="The maximum distance that a branch "
                    "will feel the effects of adhesion",
        default=1.0,
        min=0.0,
        soft_max=2.0,
        precision=2
    )
    # --------------------------------------------------------
    primaryWeight: FloatProperty(
        name="Primary Weight",
        description="Weighting given to the current direction",
        default=0.5,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    randomWeight: FloatProperty(
        name="Random Weight",
        description="Weighting given to the random direction",
        default=0.2,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    gravityWeight: FloatProperty(
        name="Gravity Weight",
        description="Weighting given to the gravity direction",
        default=1.0,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    adhesionWeight: FloatProperty(
        name="Adhesion Weight",
        description="Weighting given to the adhesion direction",
        default=0.1,
        min=0.0,
        soft_max=1.0
    )
    # --------------------------------------------------------
    branchingProbability: FloatProperty(
        name="Branching Probability",
        description="Probability of a new branch forming",
        default=0.05,
        min=0.0,
        soft_max=1.0,
        precision=4,
        step=1.0
    )
    # --------------------------------------------------------
    ivyBranchSize: FloatProperty(
        name="Branch start diameter",
        description="The size of the ivy branches",
        default=0.001,
        min=0.0,
        soft_max=0.1,
        precision=4,
        step=0.1
    )
    # --------------------------------------------------------
    ivyLeafSize: FloatProperty(
        name="Leaf Size",
        description="The size of the ivy leaves",
        default=0.02,
        min=0.0,
        soft_max=0.5,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    leafProbability: FloatProperty(
        name="Leaf Probability",
        description="Probability of a leaf forming",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    leafDensityFromHeight: FloatProperty(
        name="leaf density VS height factor",
        description="defines wether leaf density is controlled by branches height or not.",
        default=0.35,
        min=0.0,
        soft_max=1.0,
        precision=3,
        step=1.0
    )
    # --------------------------------------------------------
    growLeaves: BoolProperty(
        name="Grow Leaves",
        description="Grow leaves or not",
        default=True
    )
    # --------------------------------------------------------
    leavesCollection: PointerProperty(
        type=bpy.types.Collection,
        name="--->",
        description="Collection of leaves. If empty, squares will be used"
    )
    # --------------------------------------------------------

    


classes = (
    IvyGen,
    IvyGenProperties,
    Ivy_N_Panel
)



def register():
    for cls in classes:
        bpy.utils.register_class(cls)

    bpy.types.WindowManager.ivy_gen_props = PointerProperty(type=IvyGenProperties)


def unregister():
    del bpy.types.WindowManager.ivy_gen_props

    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)


if __name__ == "__main__":
    register()


 

Hope you like it and happy blending !

2 Likes

For someone who hates python, not only you are doing a pretty good job but also digging a lot into the language.

Maybe one day you may even start to like it a bit.

Good job.

1 Like

Thanks a lot @Calandro :slight_smile:

I don’t really hate python. It’s a language like many others with its qualities and lacks. And though i got hard time with the indentation concept and the unexplicit vars typing, it does the job. And to be honest i’m surprised of the perfs for an interpreted language.
I fell in some traps novices fall in i guess and i think it hurt my 40 years of programming experience and my ego :rofl: and the lack of time to loose i had really bothered me a lot. But language learning is just a matter of time…

What i’m really uncomfortable with is the blender API. I can say i hate it and feel it’s almost as useless as the binary dump of blender.exe :rofl:

At last but not least i’m often angry because of limits due to old choices that are kept today in blender just because it would be too much to restart all.
When i see what is possible to do in U3D with a solid doc and some few lines of codes, compared to 10 times simpler things in blender, this makes me scream XD

2.79 to 2.8 was a big jump ! why keeping all defects and lack ? why not redo all things based on experience ?
It’s often faster and better to redo all in a better way as experience is a powerfull friend.
My anger is nothing but a deception from what things could be: better if not perfect :stuck_out_tongue:
But i know i often dream too far :wink:

For the leaves orientation you mentioned in a previous post, i took a look and some leaves are differently oriented compared to the ‘climbing’ ones…

I guess this could be changed according to the branch grow vector and the gravity…
This should be investigated but i did not change the generation methods and i guess thy should be rewritten.
Unfortunately i lack of time for this now…
Maybe one day :wink:

Or maybe someone who got time and might for diving in my code changes ? :joy:

If you try this new ivy, do not hesitate to give your feedback here :wink:

Thanks and happy blending !

1 Like

Sure, I will!

I am really sorry I cannot help with anything on programming. It’s an alien language to me.

1 Like

Did you see this addon? Can you imagine if they could somehow work together? I don’t know, just thinking.