Skip to content
Snippets Groups Projects
Commit 8406aafb authored by Jacques Lucke's avatar Jacques Lucke
Browse files

port IvyGen addon to Blender 2.8

I moved the panel to the properties region in the 3D view for now.
This operator might also become a tool at some point.

Reviewers: brecht

Differential Revision: https://developer.blender.org/D3868
parent 26222502
No related branches found
No related tags found
No related merge requests found
...@@ -21,9 +21,9 @@ ...@@ -21,9 +21,9 @@
bl_info = { bl_info = {
"name": "IvyGen", "name": "IvyGen",
"author": "testscreenings, PKHG, TrumanBlending", "author": "testscreenings, PKHG, TrumanBlending",
"version": (0, 1, 4), "version": (0, 1, 5),
"blender": (2, 59, 0), "blender": (2, 80, 0),
"location": "View3D > Tool Shelf > Create > Ivy Generator", "location": "View3D > Properties Panel > Ivy Generator",
"description": "Adds generated ivy to a mesh object starting " "description": "Adds generated ivy to a mesh object starting "
"at the 3D cursor", "at the 3D cursor",
"warning": "", "warning": "",
...@@ -35,29 +35,30 @@ bl_info = { ...@@ -35,29 +35,30 @@ bl_info = {
import bpy import bpy
from bpy.types import ( from bpy.types import (
Operator, Operator,
Panel, Panel,
PropertyGroup, PropertyGroup,
) )
from bpy.props import ( from bpy.props import (
BoolProperty, BoolProperty,
FloatProperty, FloatProperty,
IntProperty, IntProperty,
PointerProperty, PointerProperty,
) )
from mathutils.bvhtree import BVHTree
from mathutils import ( from mathutils import (
Vector, Vector,
Matrix, Matrix,
) )
from collections import deque from collections import deque
from math import ( from math import (
pow, cos, pow, cos,
pi, atan2, pi, atan2,
) )
from random import ( from random import (
random as rand_val, random as rand_val,
seed as rand_seed, seed as rand_seed,
) )
import time import time
...@@ -193,7 +194,7 @@ def createIvyGeometry(IVY, growLeaves): ...@@ -193,7 +194,7 @@ def createIvyGeometry(IVY, growLeaves):
# Add the object and link to scene # Add the object and link to scene
newCurve = bpy.data.objects.new("IVY_Curve", curve) newCurve = bpy.data.objects.new("IVY_Curve", curve)
bpy.context.scene.objects.link(newCurve) bpy.context.collection.objects.link(newCurve)
if growLeaves: if growLeaves:
faceList = [[4 * i + l for l in range(4)] for i in faceList = [[4 * i + l for l in range(4)] for i in
...@@ -204,9 +205,9 @@ def createIvyGeometry(IVY, growLeaves): ...@@ -204,9 +205,9 @@ def createIvyGeometry(IVY, growLeaves):
me.from_pydata(vertList, [], faceList) me.from_pydata(vertList, [], faceList)
me.update(calc_edges=True) me.update(calc_edges=True)
ob = bpy.data.objects.new('IvyLeaf', me) ob = bpy.data.objects.new('IvyLeaf', me)
bpy.context.scene.objects.link(ob) bpy.context.collection.objects.link(ob)
me.uv_textures.new("Leaves") me.uv_layers.new(name="Leaves")
# Set the uv texture coords # Set the uv texture coords
# TODO, this is non-functional, default uvs are ok? # TODO, this is non-functional, default uvs are ok?
...@@ -292,7 +293,7 @@ class Ivy: ...@@ -292,7 +293,7 @@ class Ivy:
tmpRoot.ivyNodes.append(tmpIvy) tmpRoot.ivyNodes.append(tmpIvy)
self.ivyRoots.append(tmpRoot) self.ivyRoots.append(tmpRoot)
def grow(self, ob): def grow(self, ob, bvhtree):
# Determine the local sizes # Determine the local sizes
# local_ivySize = self.ivySize # * radius # local_ivySize = self.ivySize # * radius
# local_maxFloatLength = self.maxFloatLength # * radius # local_maxFloatLength = self.maxFloatLength # * radius
...@@ -319,8 +320,8 @@ class Ivy: ...@@ -319,8 +320,8 @@ class Ivy:
randomVector.normalize() randomVector.normalize()
# Calculate the adhesion vector # Calculate the adhesion vector
adhesionVector = adhesion(prevIvy.pos, ob, adhesionVector = adhesion(
self.maxAdhesionDistance) prevIvy.pos, bvhtree, self.maxAdhesionDistance)
# Calculate the growing vector # Calculate the growing vector
growVector = self.ivySize * (primaryVector * self.primaryWeight + growVector = self.ivySize * (primaryVector * self.primaryWeight +
...@@ -337,7 +338,7 @@ class Ivy: ...@@ -337,7 +338,7 @@ class Ivy:
newPos = prevIvy.pos + growVector + gravityVector newPos = prevIvy.pos + growVector + gravityVector
# Check for collisions with the object # Check for collisions with the object
climbing = collision(ob, prevIvy.pos, newPos) climbing, newPos = collision(bvhtree, prevIvy.pos, newPos)
# Update the growing vector for any collisions # Update the growing vector for any collisions
growVector = newPos - prevIvy.pos - gravityVector growVector = newPos - prevIvy.pos - gravityVector
...@@ -396,17 +397,13 @@ class Ivy: ...@@ -396,17 +397,13 @@ class Ivy:
return return
def adhesion(loc, ob, max_l): def adhesion(loc, bvhtree, max_l):
# Get transfor vector and transformed loc
tran_mat = ob.matrix_world.inverted()
tran_loc = tran_mat * loc
# Compute the adhesion vector by finding the nearest point # Compute the adhesion vector by finding the nearest point
nearest_result = ob.closest_point_on_mesh(tran_loc, max_l) nearest_location, *_ = bvhtree.find_nearest(loc, max_l)
adhesion_vector = Vector((0.0, 0.0, 0.0)) adhesion_vector = Vector((0.0, 0.0, 0.0))
if nearest_result[0]: if nearest_location is not None:
# Compute the distance to the nearest point # Compute the distance to the nearest point
adhesion_vector = ob.matrix_world * nearest_result[1] - loc adhesion_vector = nearest_location - loc
distance = adhesion_vector.length distance = adhesion_vector.length
# If it's less than the maximum allowed and not 0, continue # If it's less than the maximum allowed and not 0, continue
if distance: if distance:
...@@ -417,32 +414,38 @@ def adhesion(loc, ob, max_l): ...@@ -417,32 +414,38 @@ def adhesion(loc, ob, max_l):
return adhesion_vector return adhesion_vector
def collision(ob, pos, new_pos): def collision(bvhtree, pos, new_pos):
# Check for collision with the object # Check for collision with the object
climbing = False climbing = False
# Transform vecs corrected_new_pos = new_pos
tran_mat = ob.matrix_world.inverted() direction = new_pos - pos
tran_pos = tran_mat * pos
tran_new_pos = tran_mat * new_pos
tran_dir = tran_new_pos - tran_pos
ray_result = ob.ray_cast(tran_pos, tran_dir, tran_dir.length) hit_location, hit_normal, *_ = bvhtree.ray_cast(pos, direction, direction.length)
# If there's a collision we need to check it # If there's a collision we need to check it
if ray_result[0]: if hit_location is not None:
# Check whether the collision is going into the object # Check whether the collision is going into the object
if tran_dir.dot(ray_result[2]) < 0.0: if direction.dot(hit_normal) < 0.0:
# Find projection of the point onto the plane reflected_direction = (new_pos - hit_location).reflect(hit_normal)
p0 = tran_new_pos - (tran_new_pos -
ray_result[1]).project(ray_result[2]) corrected_new_pos = hit_location + reflected_direction
# Reflect in the plane
tran_new_pos += 2 * (p0 - tran_new_pos)
new_pos *= 0
new_pos += ob.matrix_world * tran_new_pos
climbing = True climbing = True
return climbing
return climbing, corrected_new_pos
def bvhtree_from_object(ob):
import bmesh
bm = bmesh.new()
mesh = ob.to_mesh(bpy.context.depsgraph, True)
bm.from_mesh(mesh)
bm.transform(ob.matrix_world)
bvhtree = BVHTree.FromBMesh(bm)
bpy.data.meshes.remove(mesh)
return bvhtree
def check_mesh_faces(ob): def check_mesh_faces(ob):
me = ob.data me = ob.data
if len(me.polygons) > 0: if len(me.polygons) > 0:
...@@ -457,16 +460,16 @@ class IvyGen(Operator): ...@@ -457,16 +460,16 @@ class IvyGen(Operator):
bl_description = "Generate Ivy on an Mesh Object" bl_description = "Generate Ivy on an Mesh Object"
bl_options = {'REGISTER', 'UNDO'} bl_options = {'REGISTER', 'UNDO'}
updateIvy = BoolProperty( updateIvy: BoolProperty(
name="Update Ivy", name="Update Ivy",
description="Update the Ivy location based on the cursor and Panel settings", description="Update the Ivy location based on the cursor and Panel settings",
default=False default=False
) )
defaultIvy = BoolProperty( defaultIvy: BoolProperty(
name="Default Ivy", name="Default Ivy",
options={"HIDDEN", "SKIP_SAVE"}, options={"HIDDEN", "SKIP_SAVE"},
default=False default=False
) )
@classmethod @classmethod
def poll(self, context): def poll(self, context):
...@@ -510,6 +513,7 @@ class IvyGen(Operator): ...@@ -510,6 +513,7 @@ class IvyGen(Operator):
# Get the selected object # Get the selected object
ob = context.active_object ob = context.active_object
bvhtree = bvhtree_from_object(ob)
# Check if the mesh has at least one polygon since some functions # Check if the mesh has at least one polygon since some functions
# are expecting them in the object's data (see T51753) # are expecting them in the object's data (see T51753)
...@@ -562,7 +566,7 @@ class IvyGen(Operator): ...@@ -562,7 +566,7 @@ class IvyGen(Operator):
(IVY.maxLength < maxLength) and (IVY.maxLength < maxLength) and
(not checkTime or (time.time() - t < maxTime))): (not checkTime or (time.time() - t < maxTime))):
# Grow the ivy for this iteration # Grow the ivy for this iteration
IVY.grow(ob) IVY.grow(ob, bvhtree)
# Print the proportion of ivy growth to console # Print the proportion of ivy growth to console
if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10: if (IVY.maxLength / maxLength * 100) > 10 * startPercent // 10:
...@@ -596,9 +600,8 @@ class CURVE_PT_IvyGenPanel(Panel): ...@@ -596,9 +600,8 @@ class CURVE_PT_IvyGenPanel(Panel):
bl_label = "Ivy Generator" bl_label = "Ivy Generator"
bl_idname = "CURVE_PT_IvyGenPanel" bl_idname = "CURVE_PT_IvyGenPanel"
bl_space_type = "VIEW_3D" bl_space_type = "VIEW_3D"
bl_region_type = "TOOLS" bl_region_type = "UI"
bl_category = "Create" bl_category = "View"
bl_context = 'objectmode'
bl_options = {"DEFAULT_CLOSED"} bl_options = {"DEFAULT_CLOSED"}
def draw(self, context): def draw(self, context):
...@@ -615,26 +618,26 @@ class CURVE_PT_IvyGenPanel(Panel): ...@@ -615,26 +618,26 @@ class CURVE_PT_IvyGenPanel(Panel):
prop_def.updateIvy = True prop_def.updateIvy = True
col = layout.column(align=True) col = layout.column(align=True)
col.label("Generation Settings:") col.label(text="Generation Settings:")
col.prop(wm.ivy_gen_props, "randomSeed") col.prop(wm.ivy_gen_props, "randomSeed")
col.prop(wm.ivy_gen_props, "maxTime") col.prop(wm.ivy_gen_props, "maxTime")
col = layout.column(align=True) col = layout.column(align=True)
col.label("Size Settings:") col.label(text="Size Settings:")
col.prop(wm.ivy_gen_props, "maxIvyLength") col.prop(wm.ivy_gen_props, "maxIvyLength")
col.prop(wm.ivy_gen_props, "ivySize") col.prop(wm.ivy_gen_props, "ivySize")
col.prop(wm.ivy_gen_props, "maxFloatLength") col.prop(wm.ivy_gen_props, "maxFloatLength")
col.prop(wm.ivy_gen_props, "maxAdhesionDistance") col.prop(wm.ivy_gen_props, "maxAdhesionDistance")
col = layout.column(align=True) col = layout.column(align=True)
col.label("Weight Settings:") col.label(text="Weight Settings:")
col.prop(wm.ivy_gen_props, "primaryWeight") col.prop(wm.ivy_gen_props, "primaryWeight")
col.prop(wm.ivy_gen_props, "randomWeight") col.prop(wm.ivy_gen_props, "randomWeight")
col.prop(wm.ivy_gen_props, "gravityWeight") col.prop(wm.ivy_gen_props, "gravityWeight")
col.prop(wm.ivy_gen_props, "adhesionWeight") col.prop(wm.ivy_gen_props, "adhesionWeight")
col = layout.column(align=True) col = layout.column(align=True)
col.label("Branch Settings:") col.label(text="Branch Settings:")
col.prop(wm.ivy_gen_props, "branchingProbability") col.prop(wm.ivy_gen_props, "branchingProbability")
col.prop(wm.ivy_gen_props, "ivyBranchSize") col.prop(wm.ivy_gen_props, "ivyBranchSize")
...@@ -643,124 +646,124 @@ class CURVE_PT_IvyGenPanel(Panel): ...@@ -643,124 +646,124 @@ class CURVE_PT_IvyGenPanel(Panel):
if wm.ivy_gen_props.growLeaves: if wm.ivy_gen_props.growLeaves:
col = layout.column(align=True) col = layout.column(align=True)
col.label("Leaf Settings:") col.label(text="Leaf Settings:")
col.prop(wm.ivy_gen_props, "ivyLeafSize") col.prop(wm.ivy_gen_props, "ivyLeafSize")
col.prop(wm.ivy_gen_props, "leafProbability") col.prop(wm.ivy_gen_props, "leafProbability")
class IvyGenProperties(PropertyGroup): class IvyGenProperties(PropertyGroup):
maxIvyLength = FloatProperty( maxIvyLength: FloatProperty(
name="Max Ivy Length", name="Max Ivy Length",
description="Maximum ivy length in Blender Units", description="Maximum ivy length in Blender Units",
default=1.0, default=1.0,
min=0.0, min=0.0,
soft_max=3.0, soft_max=3.0,
subtype='DISTANCE', subtype='DISTANCE',
unit='LENGTH' unit='LENGTH'
) )
primaryWeight = FloatProperty( primaryWeight: FloatProperty(
name="Primary Weight", name="Primary Weight",
description="Weighting given to the current direction", description="Weighting given to the current direction",
default=0.5, default=0.5,
min=0.0, min=0.0,
soft_max=1.0 soft_max=1.0
) )
randomWeight = FloatProperty( randomWeight: FloatProperty(
name="Random Weight", name="Random Weight",
description="Weighting given to the random direction", description="Weighting given to the random direction",
default=0.2, default=0.2,
min=0.0, min=0.0,
soft_max=1.0 soft_max=1.0
) )
gravityWeight = FloatProperty( gravityWeight: FloatProperty(
name="Gravity Weight", name="Gravity Weight",
description="Weighting given to the gravity direction", description="Weighting given to the gravity direction",
default=1.0, default=1.0,
min=0.0, min=0.0,
soft_max=1.0 soft_max=1.0
) )
adhesionWeight = FloatProperty( adhesionWeight: FloatProperty(
name="Adhesion Weight", name="Adhesion Weight",
description="Weighting given to the adhesion direction", description="Weighting given to the adhesion direction",
default=0.1, default=0.1,
min=0.0, min=0.0,
soft_max=1.0 soft_max=1.0
) )
branchingProbability = FloatProperty( branchingProbability: FloatProperty(
name="Branching Probability", name="Branching Probability",
description="Probability of a new branch forming", description="Probability of a new branch forming",
default=0.05, default=0.05,
min=0.0, min=0.0,
soft_max=1.0 soft_max=1.0
) )
leafProbability = FloatProperty( leafProbability: FloatProperty(
name="Leaf Probability", name="Leaf Probability",
description="Probability of a leaf forming", description="Probability of a leaf forming",
default=0.35, default=0.35,
min=0.0, min=0.0,
soft_max=1.0 soft_max=1.0
) )
ivySize = FloatProperty( ivySize: FloatProperty(
name="Ivy Size", name="Ivy Size",
description="The length of an ivy segment in Blender" description="The length of an ivy segment in Blender"
" Units", " Units",
default=0.02, default=0.02,
min=0.0, min=0.0,
soft_max=1.0, soft_max=1.0,
precision=3 precision=3
) )
ivyLeafSize = FloatProperty( ivyLeafSize: FloatProperty(
name="Ivy Leaf Size", name="Ivy Leaf Size",
description="The size of the ivy leaves", description="The size of the ivy leaves",
default=0.02, default=0.02,
min=0.0, min=0.0,
soft_max=0.5, soft_max=0.5,
precision=3 precision=3
) )
ivyBranchSize = FloatProperty( ivyBranchSize: FloatProperty(
name="Ivy Branch Size", name="Ivy Branch Size",
description="The size of the ivy branches", description="The size of the ivy branches",
default=0.001, default=0.001,
min=0.0, min=0.0,
soft_max=0.1, soft_max=0.1,
precision=4 precision=4
) )
maxFloatLength = FloatProperty( maxFloatLength: FloatProperty(
name="Max Float Length", name="Max Float Length",
description="The maximum distance that a branch " description="The maximum distance that a branch "
"can live while floating", "can live while floating",
default=0.5, default=0.5,
min=0.0, min=0.0,
soft_max=1.0 soft_max=1.0
) )
maxAdhesionDistance = FloatProperty( maxAdhesionDistance: FloatProperty(
name="Max Adhesion Length", name="Max Adhesion Length",
description="The maximum distance that a branch " description="The maximum distance that a branch "
"will feel the effects of adhesion", "will feel the effects of adhesion",
default=1.0, default=1.0,
min=0.0, min=0.0,
soft_max=2.0, soft_max=2.0,
precision=2 precision=2
) )
randomSeed = IntProperty( randomSeed: IntProperty(
name="Random Seed", name="Random Seed",
description="The seed governing random generation", description="The seed governing random generation",
default=0, default=0,
min=0 min=0
) )
maxTime = FloatProperty( maxTime: FloatProperty(
name="Maximum Time", name="Maximum Time",
description="The maximum time to run the generation for " description="The maximum time to run the generation for "
"in seconds generation (0.0 = Disabled)", "in seconds generation (0.0 = Disabled)",
default=0.0, default=0.0,
min=0.0, min=0.0,
soft_max=10 soft_max=10
) )
growLeaves = BoolProperty( growLeaves: BoolProperty(
name="Grow Leaves", name="Grow Leaves",
description="Grow leaves or not", description="Grow leaves or not",
default=True default=True
) )
classes = ( classes = (
...@@ -775,8 +778,8 @@ def register(): ...@@ -775,8 +778,8 @@ def register():
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
bpy.types.WindowManager.ivy_gen_props = PointerProperty( bpy.types.WindowManager.ivy_gen_props = PointerProperty(
type=IvyGenProperties type=IvyGenProperties
) )
def unregister(): def unregister():
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment