Campbell Barton
e8da6131fd
Move copyright text to SPDX-FileCopyrightText or set to the Blender Foundation so "make check_licenses" now runs without warnings.
780 lines
26 KiB
Python
780 lines
26 KiB
Python
# SPDX-FileCopyrightText: 2011-2022 Blender Foundation
|
|
#
|
|
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
bl_info = {
|
|
"name": "IvyGen",
|
|
"author": "testscreenings, PKHG, TrumanBlending",
|
|
"version": (0, 1, 5),
|
|
"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
|
|
|
|
|
|
def createIvyGeometry(IVY, growLeaves):
|
|
"""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) + 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
|
|
if (probability * 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
|
|
'''
|
|
|
|
ob.parent = 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', '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,
|
|
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.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'}
|
|
|
|
updateIvy: BoolProperty(
|
|
name="Update Ivy",
|
|
description="Update the Ivy location based on the cursor and Panel settings",
|
|
default=False
|
|
)
|
|
defaultIvy: BoolProperty(
|
|
name="Default Ivy",
|
|
options={"HIDDEN", "SKIP_SAVE"},
|
|
default=False
|
|
)
|
|
|
|
@classmethod
|
|
def poll(self, context):
|
|
# Check if there's an object and whether it's a mesh
|
|
ob = context.active_object
|
|
return ((ob is not None) and
|
|
(ob.type == 'MESH') and
|
|
(context.mode == 'OBJECT'))
|
|
|
|
def invoke(self, context, event):
|
|
self.updateIvy = True
|
|
return self.execute(context)
|
|
|
|
def execute(self, context):
|
|
# scene = context.scene
|
|
ivyProps = context.window_manager.ivy_gen_props
|
|
|
|
if not self.updateIvy:
|
|
return {'PASS_THROUGH'}
|
|
|
|
# assign the variables, check if it is default
|
|
# Note: update the values if window_manager props defaults are changed
|
|
randomSeed = ivyProps.randomSeed if not self.defaultIvy else 0
|
|
maxTime = ivyProps.maxTime if not self.defaultIvy else 0
|
|
maxIvyLength = ivyProps.maxIvyLength if not self.defaultIvy else 1.0
|
|
ivySize = ivyProps.ivySize if not self.defaultIvy else 0.02
|
|
maxFloatLength = ivyProps.maxFloatLength if not self.defaultIvy else 0.5
|
|
maxAdhesionDistance = ivyProps.maxAdhesionDistance if not self.defaultIvy else 1.0
|
|
primaryWeight = ivyProps.primaryWeight if not self.defaultIvy else 0.5
|
|
randomWeight = ivyProps.randomWeight if not self.defaultIvy else 0.2
|
|
gravityWeight = ivyProps.gravityWeight if not self.defaultIvy else 1.0
|
|
adhesionWeight = ivyProps.adhesionWeight if not self.defaultIvy else 0.1
|
|
branchingProbability = ivyProps.branchingProbability if not self.defaultIvy else 0.05
|
|
leafProbability = ivyProps.leafProbability if not self.defaultIvy else 0.35
|
|
ivyBranchSize = ivyProps.ivyBranchSize if not self.defaultIvy else 0.001
|
|
ivyLeafSize = ivyProps.ivyLeafSize if not self.defaultIvy else 0.02
|
|
growLeaves = ivyProps.growLeaves if not self.defaultIvy else True
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
|
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
|
|
|
# Get the selected object
|
|
ob = context.active_object
|
|
bvhtree = bvhtree_from_object(ob)
|
|
|
|
# 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(ob)
|
|
if check_face is False:
|
|
self.report({'WARNING'},
|
|
"Mesh Object doesn't have at least one Face. "
|
|
"Operation Cancelled")
|
|
return {"CANCELLED"}
|
|
|
|
# Compute bounding sphere radius
|
|
# radius = computeBoundingSphere(ob) # Not needed anymore
|
|
|
|
# Get the seeding point
|
|
seedPoint = context.scene.cursor.location
|
|
|
|
# Fix the random seed
|
|
rand_seed(randomSeed)
|
|
|
|
# Make the new ivy
|
|
IVY = Ivy(
|
|
primaryWeight=primaryWeight,
|
|
randomWeight=randomWeight,
|
|
gravityWeight=gravityWeight,
|
|
adhesionWeight=adhesionWeight,
|
|
branchingProbability=branchingProbability,
|
|
leafProbability=leafProbability,
|
|
ivySize=ivySize,
|
|
ivyLeafSize=ivyLeafSize,
|
|
ivyBranchSize=ivyBranchSize,
|
|
maxFloatLength=maxFloatLength,
|
|
maxAdhesionDistance=maxAdhesionDistance
|
|
)
|
|
# Generate first root and node
|
|
IVY.seed(seedPoint)
|
|
|
|
checkTime = False
|
|
maxLength = maxIvyLength # * radius
|
|
|
|
# If we need to check time set the flag
|
|
if 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
|
|
(IVY.maxLength < maxLength) and
|
|
(not checkTime or (time.time() - t < maxTime))):
|
|
# Grow the ivy for this iteration
|
|
IVY.grow(ob, bvhtree)
|
|
|
|
# Print the proportion of ivy growth to console
|
|
if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10:
|
|
print('%0.2f%% of Ivy nodes have grown' %
|
|
(IVY.maxLength / maxLength * 100))
|
|
startPercent += 10
|
|
if IVY.maxLength / maxLength > 1:
|
|
print("Halting Growth")
|
|
|
|
# Make an iterator to check if all are alive
|
|
checkAliveIter = (r.alive for r in IVY.ivyRoots)
|
|
|
|
# Create the curve and leaf geometry
|
|
createIvyGeometry(IVY, growLeaves)
|
|
print("Geometry Generation Complete")
|
|
|
|
print("Ivy generated in %0.2f s" % (time.time() - t))
|
|
|
|
self.updateIvy = False
|
|
self.defaultIvy = False
|
|
|
|
return {'FINISHED'}
|
|
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
|
|
layout.prop(self, "updateIvy", icon="FILE_REFRESH")
|
|
|
|
|
|
class CURVE_PT_IvyGenPanel(Panel):
|
|
bl_label = "Ivy Generator"
|
|
bl_idname = "CURVE_PT_IvyGenPanel"
|
|
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
|
|
wm = context.window_manager
|
|
col = layout.column(align=True)
|
|
|
|
prop_new = col.operator("curve.ivy_gen", text="Add New Ivy", icon="OUTLINER_OB_CURVE")
|
|
prop_new.defaultIvy = False
|
|
prop_new.updateIvy = True
|
|
|
|
prop_def = col.operator("curve.ivy_gen", text="Add New Default Ivy", icon="CURVE_DATA")
|
|
prop_def.defaultIvy = True
|
|
prop_def.updateIvy = True
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Generation Settings:")
|
|
col.prop(wm.ivy_gen_props, "randomSeed")
|
|
col.prop(wm.ivy_gen_props, "maxTime")
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Size Settings:")
|
|
col.prop(wm.ivy_gen_props, "maxIvyLength")
|
|
col.prop(wm.ivy_gen_props, "ivySize")
|
|
col.prop(wm.ivy_gen_props, "maxFloatLength")
|
|
col.prop(wm.ivy_gen_props, "maxAdhesionDistance")
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Weight Settings:")
|
|
col.prop(wm.ivy_gen_props, "primaryWeight")
|
|
col.prop(wm.ivy_gen_props, "randomWeight")
|
|
col.prop(wm.ivy_gen_props, "gravityWeight")
|
|
col.prop(wm.ivy_gen_props, "adhesionWeight")
|
|
|
|
col = layout.column(align=True)
|
|
col.label(text="Branch Settings:")
|
|
col.prop(wm.ivy_gen_props, "branchingProbability")
|
|
col.prop(wm.ivy_gen_props, "ivyBranchSize")
|
|
|
|
col = layout.column(align=True)
|
|
col.prop(wm.ivy_gen_props, "growLeaves")
|
|
|
|
if wm.ivy_gen_props.growLeaves:
|
|
col = layout.column(align=True)
|
|
col.label(text="Leaf Settings:")
|
|
col.prop(wm.ivy_gen_props, "ivyLeafSize")
|
|
col.prop(wm.ivy_gen_props, "leafProbability")
|
|
|
|
|
|
class IvyGenProperties(PropertyGroup):
|
|
maxIvyLength: FloatProperty(
|
|
name="Max Ivy Length",
|
|
description="Maximum ivy length in Blender Units",
|
|
default=1.0,
|
|
min=0.0,
|
|
soft_max=3.0,
|
|
subtype='DISTANCE',
|
|
unit='LENGTH'
|
|
)
|
|
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
|
|
)
|
|
leafProbability: FloatProperty(
|
|
name="Leaf Probability",
|
|
description="Probability of a leaf forming",
|
|
default=0.35,
|
|
min=0.0,
|
|
soft_max=1.0
|
|
)
|
|
ivySize: FloatProperty(
|
|
name="Ivy Size",
|
|
description="The length of an ivy segment in Blender"
|
|
" Units",
|
|
default=0.02,
|
|
min=0.0,
|
|
soft_max=1.0,
|
|
precision=3
|
|
)
|
|
ivyLeafSize: FloatProperty(
|
|
name="Ivy Leaf Size",
|
|
description="The size of the ivy leaves",
|
|
default=0.02,
|
|
min=0.0,
|
|
soft_max=0.5,
|
|
precision=3
|
|
)
|
|
ivyBranchSize: FloatProperty(
|
|
name="Ivy Branch Size",
|
|
description="The size of the ivy branches",
|
|
default=0.001,
|
|
min=0.0,
|
|
soft_max=0.1,
|
|
precision=4
|
|
)
|
|
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
|
|
)
|
|
randomSeed: IntProperty(
|
|
name="Random Seed",
|
|
description="The seed governing random generation",
|
|
default=0,
|
|
min=0
|
|
)
|
|
maxTime: FloatProperty(
|
|
name="Maximum Time",
|
|
description="The maximum time to run the generation for "
|
|
"in seconds generation (0.0 = Disabled)",
|
|
default=0.0,
|
|
min=0.0,
|
|
soft_max=10
|
|
)
|
|
growLeaves: BoolProperty(
|
|
name="Grow Leaves",
|
|
description="Grow leaves or not",
|
|
default=True
|
|
)
|
|
|
|
|
|
classes = (
|
|
IvyGen,
|
|
IvyGenProperties,
|
|
CURVE_PT_IvyGenPanel
|
|
)
|
|
|
|
|
|
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()
|