Hi @JuhaW
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
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 !