├── Addon_add_iterative_tree.py └── README.md /Addon_add_iterative_tree.py: -------------------------------------------------------------------------------- 1 | bl_info = { 2 | "name": "Iterative tree", 3 | "author": "Herpin Maxime", 4 | "version": (1, 0), 5 | "blender": (2, 75, 0), 6 | "location": "View3D > Add > Curve > Tree", 7 | "description": "Adds a Tree", 8 | "warning": "", 9 | "wiki_url": "", 10 | "category": "Add Curve"} 11 | 12 | 13 | 14 | import bpy 15 | import mathutils 16 | from random import * 17 | from math import * 18 | from bpy.props import * 19 | 20 | 21 | 22 | #create_tree: int*Float*Float*Float*Float*Float*int*int*Float*Int*Float*Float*Bool*Float*Float*Float*Bool*Float*Vector3 -> None 23 | # create a tree as a curve 24 | 25 | def create_tree(iterations, radius ,radius_dec, trunk_radius_dec, split_proba, dist, split_angle, first_split_angle, bevel, trunk_min_length, trunk_variation, gravity_fact, preserve_trunk, dist_decr, branch_variation, split_proba_rise,is_force, force, emitter): 26 | 27 | #The first point of the tree: 28 | root = [(0,0,0,1), (0,0,1) , 1 ] 29 | #Setting up the curve 30 | cu = bpy.data.curves.new("Tree", "CURVE") 31 | ob = bpy.data.objects.new("Tree", cu) 32 | ob.location = bpy.context.scene.cursor_location 33 | scn = bpy.context.scene 34 | scn.objects.link(ob) 35 | scn.objects.active = ob 36 | cu.dimensions = "3D" 37 | cu.fill_mode = "FULL" 38 | cu.bevel_resolution = 1 39 | #overall radius of the tree 40 | cu.bevel_depth = .3 * radius if bevel else 0 41 | cu.use_uv_as_generated = True 42 | 43 | ob.select = True 44 | spline = cu.splines.new("POLY") 45 | spline.points[0].co = root[0] 46 | spline.points[0].radius = 1 47 | # fpoint : list of the splines with their last point 48 | # points : list of fpoint 49 | fpoint = [spline, root] 50 | points = [fpoint] 51 | 52 | # function apply_force: vector3*Float -> vector3 53 | # modify the coordinates to push them from or towards the emitter 54 | emitter = ob.matrix_world * mathutils.Vector(emitter) 55 | def apply_force(coord, f): 56 | return force/100 * (f-coord) 57 | 58 | # shows an empty object at the location of the emitter, for better visualization 59 | if is_force: 60 | empt = bpy.ops.object.add(type='EMPTY', location = emitter) 61 | bpy.context.active_object.name = 'forceEmpty' 62 | elif bpy.data.objects.get("forceEmpty") is not None: 63 | empt.delete() 64 | 65 | # function create_points: point -> None 66 | # given a spline and its last point, creates new points and update the list of points 67 | def create_points(point): 68 | 69 | # currently stupid, but I keep it because I may use it to add badass new things. 70 | longevity = point[1][2] 71 | 72 | # Separate the last point coordinates for better readability 73 | xyz = point[1][0] 74 | x = xyz[0] 75 | y = xyz[1] 76 | z = xyz[2] 77 | 78 | # Same here 79 | # The direction is a vector showing where the next point should be 80 | direction = point[1][1] 81 | a = direction[0] 82 | b = direction[1] 83 | c = direction[2] 84 | 85 | # The spline we are using to create new points 86 | curr_spline = point[0] 87 | curr_spline_length = len(point[0].points) 88 | 89 | 90 | # if the iteration number is lower than wanted, keep making trunk points 91 | if longevity <= trunk_min_length: 92 | splits = 1 if longevity < trunk_min_length else 2 93 | real_split_angle = split_angle*trunk_variation 94 | real_rad_dec = trunk_radius_dec 95 | 96 | else: 97 | splits = int(2 + split_proba - random()) 98 | real_split_angle = split_angle 99 | real_rad_dec = radius_dec 100 | 101 | # Random angle to randomise the split direction 102 | rand_theta = random() * 2 * pi 103 | 104 | # Will be used to know if the current spline is the trunk 105 | pind = points.index(point) 106 | 107 | # If there is no split, change the split angle to just add variation is the branch 108 | if (splits == 1) and (pind >0): 109 | real_split_angle = split_angle*branch_variation 110 | 111 | # This is overkill since there is a maximum of two splits, but in case I want to add an option with more splits... 112 | for i in range (0,splits): 113 | # if the trunkk has to be preserved, keep the branch variation as it should be 114 | if (preserve_trunk) and (pind ==0) and (longevity > trunk_min_length): 115 | if i==0: 116 | real_split_angle = split_angle*trunk_variation 117 | real_rad_dec = trunk_radius_dec*2 118 | else : 119 | real_split_angle = first_split_angle 120 | real_rad_dec = radius_dec 121 | # rotate the angle so the points will be distributed at uniform angles 122 | theta = rand_theta + i*2*pi / splits 123 | # the split angle 124 | phi = real_split_angle*pi/180 125 | # distances between the direction coordinates 126 | n_ab = 1.0 if b*c == 0 else sqrt(a*a + b*b) 127 | n_cb = 1.0 if a*b == 0 else sqrt(c*c + b*b) 128 | n_abc = 1.0 if a*b*c == 0 else sqrt(a*a + b*b + c*c) 129 | 130 | sin_the = sin(theta) 131 | cos_the = cos(theta) 132 | sin_phi = sin(phi) 133 | cos_phi = cos(phi) 134 | 135 | # convert spherical coordinates aligned with the direction vector, to global carthesian ones 136 | new_x = x + dist*cos_phi*a/n_abc + dist*sin_phi*sin_the*b/n_ab 137 | new_y = y + dist*cos_phi*b/n_abc - dist*sin_phi*sin_the*a/n_ab + dist*sin_phi*cos_the*c/n_cb 138 | new_z = z + dist*cos_phi*c/n_abc - dist*sin_phi*cos_the*b/n_cb - gravity_fact*longevity/500 139 | 140 | # apply force to new coordinates 141 | if is_force: 142 | new_x+=apply_force(new_x, emitter[0]) 143 | new_y+=apply_force(new_y, emitter[1]) 144 | new_z+=apply_force(new_z, emitter[2]) 145 | # generate a tuple with those coordinates 146 | new_xyz =(new_x , new_y , new_z) 147 | # generate a new direction vector 148 | new_d = mathutils.Vector((new_x-x , new_y-y , new_z-z)) 149 | new_d.normalize() 150 | 151 | # if the split branch is the first one, just add the new point to current spline, else create a new spline 152 | if i==0: 153 | point[0].points.add(1) 154 | point[0].points[curr_spline_length].co = (new_x, new_y, new_z,1) 155 | point[0].points[curr_spline_length].radius = ((1-real_rad_dec/5)**(longevity-trunk_min_length))*(1-trunk_radius_dec/5)**trunk_min_length 156 | point[1][0] = new_xyz 157 | point[1][1] = new_d 158 | point[1][2]+=1 159 | else: 160 | new_spline = cu.splines.new("POLY") 161 | new_spline.points[0].co = (x,y,z,1) 162 | new_spline.points[0].radius = ((1-real_rad_dec/5)**(longevity-trunk_min_length))*(1-trunk_radius_dec/5)**trunk_min_length 163 | new_spline.points.add(1) 164 | new_spline.points[1].co = (new_x,new_y,new_z,1) 165 | new_spline.points[1].radius = ((1-real_rad_dec/5)**(longevity+1-trunk_min_length))*(1-trunk_radius_dec/5)**trunk_min_length 166 | new_point = [new_xyz, new_d, longevity+1] 167 | points.append([new_spline, new_point]) 168 | # end of the function 169 | 170 | # execute the previous function 171 | for i in range (iterations + 1): 172 | dist*=(1-dist_decr/10) 173 | 174 | split_proba+= split_proba_rise/100 175 | for pt in points: 176 | create_points(pt) 177 | 178 | # not used for now, it will be usefull to keep the tree inside a other object 179 | def inside(point, obj): 180 | point = obj.matrix_world.inverted() * point 181 | cpom = obj.closest_point_on_mesh(point) 182 | vec = point - cpom[0] 183 | ang = cpom[1].angle(vec) 184 | return (ang > 1.57079633, cpom[0]) 185 | 186 | def check_inside(ob, obj): 187 | ob_m = ob.matrix_world 188 | ob_m_i = ob.matrix_world.inverted() 189 | obj_m = obj.matrix_world 190 | for spline in cu.splines: 191 | 192 | for point in spline.points: 193 | a = ob_m * point.co 194 | b = mathutils.Vector((a[0], a[1], a[2])) 195 | (test, coord) = inside(b, obj) 196 | if test: 197 | point.co = ob_m_i * obj_m * coord 198 | 199 | 200 | 201 | 202 | 203 | # The class who adds a tree 204 | class Add_iterative_Tree(bpy.types.Operator): 205 | """Create an interative tree""" 206 | bl_idname = "mesh.add_iterative_tree" 207 | bl_label = "Add iterative tree" 208 | bl_options = {'REGISTER', 'UNDO'} 209 | 210 | def draw(self, context): 211 | layout = self.layout 212 | scene = context.scene 213 | #Geometry panel......................................................... 214 | box = layout.box() 215 | box.label("basic") 216 | box.prop(self, "SeedProp") 217 | box.prop(self, "iterations") 218 | 219 | box.prop(self, "bevel") 220 | col = box.column(True) 221 | col.prop(self, "radius") 222 | col.prop(self, "rad_dec") 223 | #trunk panel............................................................ 224 | box = layout.box() 225 | box.label("trunk") 226 | split = box.split() 227 | col = split.column(True) 228 | col.prop(self,"trunk_min_length") 229 | col.prop(self, "trunk_variation") 230 | col.prop(self, "trunk_radius_dec") 231 | col.prop(self, "preserve_trunk") 232 | #branch panel................................................... 233 | box = layout.box() 234 | box.label("branches") 235 | box.prop(self, "branch_variation") 236 | col = box.column(True) 237 | col.prop(self, "split_proba") 238 | col.prop(self, "split_proba_rise") 239 | column = box.column(True) 240 | split = box.split() 241 | col = split.column(True) 242 | col.prop(self,"dist") 243 | col.prop(self,"dist_dec") 244 | col = split.column() 245 | col.prop(self,"split_angle") 246 | col.prop(self,"first_split_angle") 247 | #advanced............................................................... 248 | box = layout.box() 249 | box.label("advanced") 250 | box.prop(self, "force") 251 | box.prop(self, "emitter") 252 | box.prop(self, "force_factor") 253 | box.prop(self, "gravity_fact") 254 | # the properties 255 | first_split_angle = FloatProperty( 256 | name= "first split angle", 257 | min = 0.0, 258 | default=35, 259 | description="how wide is the angle in a split if this split comes from the trunk", 260 | ) 261 | split_proba_rise = FloatProperty( 262 | name = "Split probabibity rise amount", 263 | default = .1, 264 | ) 265 | branch_variation = FloatProperty( 266 | name = "Branches variations", 267 | default = .5, 268 | ) 269 | force = BoolProperty( 270 | name = "force", 271 | default = False 272 | ) 273 | emitter = FloatVectorProperty( 274 | name = "force emitter", 275 | default = (5.0,5.0,5.0), 276 | ) 277 | force_factor = FloatProperty( 278 | name = "force factor", 279 | default = -.1, 280 | ) 281 | trunk_radius_dec = FloatProperty( 282 | name = "trunk radius decrease", 283 | default = .3, 284 | ) 285 | preserve_trunk = BoolProperty( 286 | name = "preserve trunk", 287 | default = True, 288 | ) 289 | trunk_variation = FloatProperty( 290 | name = "trunk variation", 291 | default = .1, 292 | ) 293 | 294 | bevel = BoolProperty( 295 | name = "bevel", 296 | default = False, 297 | ) 298 | radius = FloatProperty( 299 | name = "Radius", 300 | min = 0.0, 301 | default = 1, 302 | ) 303 | rad_dec = FloatProperty( 304 | name = "radius decrease", 305 | min = 0.0, 306 | max = 1.0, 307 | default = 0.55, 308 | ) 309 | Is_curve = BoolProperty( 310 | name = "is curve", 311 | default = False, 312 | ) 313 | iterations = IntProperty( 314 | name= "branch iterations", 315 | min = 1, 316 | default=25, 317 | description="number of recursive call. WARNING : rise fast", 318 | ) 319 | trunk_min_length = IntProperty( 320 | name= "trunk min length", 321 | min = 0, 322 | default=9, 323 | description="iteration from from which first split is alload", 324 | ) 325 | Preserve_trunk = BoolProperty( 326 | name = "preserve trunk", 327 | default = False, 328 | ) 329 | split_proba = FloatProperty( 330 | name = "split probability", 331 | min = 0.0, 332 | max = 1.0, 333 | default = 0.25, 334 | description = "probability for a branch to split. WARNING : sensitive", 335 | ) 336 | dist = FloatProperty( 337 | name= "points distance", 338 | min = 0.0, 339 | default=.5, 340 | description="distance beetwen two consecutive points", 341 | ) 342 | dist_dec = FloatProperty( 343 | name = "distance decrease", 344 | default = 0.2, 345 | description = "how do the branches sizes decrease", 346 | ) 347 | split_angle = FloatProperty( 348 | name= "split angle", 349 | min = 0.0, 350 | default=35, 351 | description="how wide is the angle in a split", 352 | ) 353 | 354 | gravity_fact = FloatProperty( 355 | name = "gravity factor", 356 | default = 0.0, 357 | ) 358 | SeedProp = IntProperty( 359 | name = "Seed", 360 | default = 1, 361 | ) 362 | 363 | def execute(self, context): 364 | seed(self.SeedProp) 365 | 366 | create_tree(self.iterations, self.radius ,self.rad_dec, self.trunk_radius_dec , self.split_proba, self.dist, self.split_angle, self.first_split_angle, self.bevel, self.trunk_min_length, self.trunk_variation, self.gravity_fact, self.preserve_trunk, self.dist_dec, self.branch_variation, self.split_proba_rise, self.force, self.force_factor, self.emitter) 367 | return {'FINISHED'} 368 | 369 | 370 | 371 | 372 | 373 | 374 | # the class who creates the buttons and call other classes 375 | class TreeMaker(bpy.types.Panel): 376 | bl_space_type = "VIEW_3D" 377 | bl_region_type = "TOOLS" 378 | bl_context = "objectmode" 379 | bl_category = "Create" 380 | bl_label = "Add Tree" 381 | 382 | def draw(self, context): 383 | TheCol = self.layout.column() 384 | TheCol.operator("mesh.add_iterative_tree" , text = "Add Tree") 385 | TheCol.operator("mesh.addleaves", text="Add leaves") 386 | TheCol.operator("mesh.adddetail", text="Add detail") 387 | 388 | 389 | # the class who adds details to the created tree 390 | class AddDetail(bpy.types.Operator): 391 | bl_idname = "mesh.adddetail" 392 | bl_label = "Add detail" 393 | bl_options = {'REGISTER', 'UNDO'} 394 | 395 | def draw(self, context): 396 | layout = self.layout 397 | scene = context.scene 398 | box = layout.box() 399 | box.label("add detail") 400 | box.prop(self, "length") 401 | box.prop(self, "iteration") 402 | box.prop(self, "fractal") 403 | 404 | length = FloatProperty( 405 | name = "length", 406 | default = .4, 407 | ) 408 | iteration = IntProperty( 409 | name = "subdivisions", 410 | default = 2, 411 | description = "add detail to large faces", 412 | ) 413 | fractal = FloatProperty( 414 | name = "fractal factor", 415 | default = .4, 416 | ) 417 | def execute(self, context): 418 | ob = context.active_object 419 | if ob and ob.select and ob.type == 'CURVE': 420 | em = (ob.mode == 'EDIT') 421 | bpy.ops.object.mode_set() 422 | bpy.ops.object.convert(target='MESH', keep_original=False) 423 | if em: bpy.ops.object.mode_set(mode='EDIT', toggle=False) 424 | add_detail (ob , self.length, self.iteration, self.fractal) 425 | return{'FINISHED'} 426 | 427 | 428 | 429 | 430 | # I'm sure you can guess for this one 431 | class AddLeaves(bpy.types.Operator): 432 | bl_idname = "mesh.addleaves" 433 | bl_label = "Add leaves" 434 | bl_options = {'REGISTER', 'UNDO'} 435 | 436 | def draw(self, context): 437 | layout = self.layout 438 | scene = context.scene 439 | box = layout.box() 440 | box.label("basic") 441 | box.prop(self, "visualize") 442 | box.prop(self, "length") 443 | box.prop(self, "create_new_leaf") 444 | col = box.column(True) 445 | col.prop(self, "number") 446 | col.prop(self, "display") 447 | visualize = BoolProperty( 448 | name = "visualize", 449 | default = False, 450 | ) 451 | length = FloatProperty( 452 | name = "length", 453 | default = .02, 454 | ) 455 | create_new_leaf = BoolProperty( 456 | name = "create new leaf", 457 | default = False, 458 | ) 459 | number = IntProperty( 460 | name = "number of leaves", 461 | default = 100000, 462 | ) 463 | display = IntProperty( 464 | name = "display", 465 | default = 2000, 466 | ) 467 | 468 | def execute(self, context): 469 | ob = context.active_object 470 | if ob and ob.select and ob.type == 'CURVE': 471 | em = (ob.mode == 'EDIT') 472 | bpy.ops.object.mode_set() 473 | bpy.ops.object.convert(target='MESH', keep_original=False) 474 | if em: bpy.ops.object.mode_set(mode='EDIT', toggle=False) 475 | else: 476 | le = 'not a curve object' 477 | Create_system(ob, self.length, self.create_new_leaf, self.number, self.display) 478 | if self.visualize: 479 | bpy.ops.paint.weight_paint_toggle() 480 | 481 | 482 | return{'FINISHED'} 483 | 484 | 485 | # function create_group: object*Float -> None 486 | # creates a vertex group based on edge length 487 | def create_group (ob , length): 488 | data = ob.data 489 | bpy.ops.object.mode_set(mode='EDIT') 490 | bpy.ops.mesh.select_all(action = 'DESELECT') 491 | bpy.ops.object.mode_set(mode='OBJECT') 492 | 493 | g = ob.vertex_groups.new("leaf") 494 | 495 | #search for a edge smaller than the length 496 | for edges in data.edges: 497 | verts = [data.vertices[edges.vertices[i]] for i in range (0,len(edges.vertices))] 498 | curr_length = (verts[0].co-verts[1].co).length 499 | # if found, select similar 500 | if curr_length < length: 501 | edges.select = True 502 | if bpy.ops.object.mode_set.poll(): 503 | bpy.ops.object.mode_set(mode='EDIT') 504 | bpy.ops.mesh.select_mode(type="EDGE") 505 | bpy.ops.mesh.select_similar(type='LENGTH', compare='LESS', threshold=0.01) 506 | bpy.ops.object.mode_set(mode='OBJECT') 507 | indexes = [i.index for i in data.vertices if i.select] 508 | #add the selection to the vertex group 509 | g.add(indexes, 1.0, "ADD") 510 | return g 511 | 512 | 513 | # function Create_system: Object*Float*Bool*Int*Int 514 | # create a particle system with some presets 515 | def Create_system(ob, length, create_new_leaf, number, display): 516 | 517 | # Choose it a new leaf object has to be created 518 | if not(create_new_leaf) and bpy.data.objects.get("leaf") is None: 519 | create_new_leaf = True 520 | 521 | if create_new_leaf: 522 | leaf_object = create_leaf() 523 | else: 524 | leaf_object = bpy.data.objects['leaf'] 525 | 526 | 527 | # get the vertex group 528 | g = create_group(ob, length) 529 | # customize the particle system 530 | leaf = ob.modifiers.new("psys name", 'PARTICLE_SYSTEM') 531 | part = ob.particle_systems[0] 532 | part.vertex_group_density = g.name 533 | set = leaf.particle_system.settings 534 | set.name = "leaf" 535 | set.type = "HAIR" 536 | set.use_advanced_hair = True 537 | set.draw_percentage = 100* display/number 538 | set.count = number 539 | set.distribution = "RAND" 540 | set.normal_factor = .250 541 | set.factor_random = .7 542 | set.use_rotations = True 543 | set.phase_factor = 1 544 | set.phase_factor_random = 1 545 | set.particle_size = .015 546 | set.size_random = .25 547 | set.brownian_factor = 1 548 | set.render_type = "OBJECT" 549 | set.dupli_object = leaf_object 550 | 551 | 552 | # function create_leaf: None -> Object 553 | # create a leaf 554 | def create_leaf(): 555 | # setting up the vertices and faces 556 | verts = [((-2.8946144580841064, -0.04747571051120758, 0.06105047091841698)), ((-0.003778815269470215, -0.18329660594463348, 0.06461216509342194)), ((-2.8945164680480957, 0.04747571051120758, 0.05965699255466461)), ((-0.0037790536880493164, -0.019920431077480316, 0.06660028547048569)), ((-0.5320309400558472, -0.5793617367744446, 0.19172759354114532)), ((-0.5320843458175659, 0.45810192823410034, 0.20311059057712555)), ((-1.0539054870605469, 0.6693136096000671, 0.31496328115463257)), ((-1.0538315773010254, -0.7448607087135315, 0.3081340789794922)), ((-1.4385014772415161, 0.7803475260734558, 0.336755633354187)), ((-1.4386969804763794, -0.7956905961036682, 0.3466308116912842)), ((-1.8466333150863647, 0.7721884250640869, 0.31564652919769287)), ((-1.9127564430236816, -0.7329368591308594, 0.33204150199890137)), ((-2.356304883956909, 0.548954963684082, 0.2288721650838852)), ((-2.3572182655334473, -0.4901292622089386, 0.24997609853744507)), ((-2.8945655822753906, 0.0, 0.04582677781581879)), ((-0.0037789344787597656, -0.1016085147857666, 0.051359549164772034)), ((-0.5320576429367065, -0.06062988191843033, 0.16666361689567566)), ((-1.0538685321807861, -0.03777353838086128, 0.32630106806755066)), ((-1.4385992288589478, -0.007671522442251444, 0.38123637437820435)), ((-1.846874713897705, -0.017651351168751717, 0.3504077196121216)), ((-2.3567614555358887, 0.02941284514963627, 0.20915958285331726))] 557 | 558 | faces = [(16, 15, 3, 5), (17, 16, 5, 6), (18, 17, 6, 8), (19, 18, 8, 10), (20, 19, 10, 12), (14, 20, 12, 2), (0, 13, 20, 14), (13, 11, 19, 20), (11, 9, 18, 19), (9, 7, 17, 18), (7, 4, 16, 17), (4, 1, 15, 16)] 559 | # setting up the object 560 | mesh = bpy.data.meshes.new("leaf") 561 | mesh.from_pydata(verts,[], faces) 562 | object = bpy.data.objects.new("leaf", mesh) 563 | object.location = (0,0,0) 564 | bpy.context.scene.objects.link(object) 565 | return object 566 | 567 | # function add_detail: Object*Float*Int*Float -> None 568 | # subdivide some parts of the object, to add detail 569 | def add_detail (ob ,length, iterations, fractal): 570 | 571 | obj = bpy.context.active_object 572 | bpy.ops.object.mode_set(mode = 'EDIT') 573 | bpy.ops.mesh.select_all(action = 'DESELECT') 574 | bpy.ops.object.mode_set(mode = 'OBJECT') 575 | # select the first face, which should be the bigger one because it's on the base of the tree and wise people don't makes trees with a growing radius 576 | obj.data.polygons[0].select = True 577 | bpy.ops.object.mode_set(mode='EDIT') 578 | bpy.ops.mesh.select_mode(type="FACE") 579 | # select similar faces 580 | bpy.ops.mesh.select_similar(type='PERIMETER', compare='EQUAL', threshold=length) 581 | # subdivide these faces 582 | bpy.ops.mesh.subdivide(number_cuts=iterations, smoothness=.645, fractal=fractal) 583 | bpy.ops.object.mode_set(mode='OBJECT') 584 | return None 585 | 586 | # registration 587 | def register(): 588 | bpy.utils.register_class(Add_iterative_Tree) 589 | bpy.utils.register_class(TreeMaker) 590 | bpy.utils.register_class(AddLeaves) 591 | bpy.utils.register_class(AddDetail) 592 | 593 | 594 | 595 | def unregister(): 596 | bpy.utils.unregister_class(Add_iterative_Tree) 597 | bpy.utils.register_class(TreeMaker) 598 | bpy.utils.register_class(AddLeaves) 599 | bpy.utils.register_class(AddDetail) 600 | 601 | if __name__ == "__main__": 602 | register() 603 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blender-Tree-add-on 2 | Tree addon 3 | 4 | this is deprecated, I'm curently working on an other tree addon. Modular tree addon 5 | --------------------------------------------------------------------------------