import bpy from random import randint, random from mathutils import Vector from math import pi, sin, cos bpy.ops.object.select_all(action='SELECT') bpy.ops.object.delete(use_global=False) def new_tree(): verts = [] faces = [] face_uvs = [] cylinder_sections = 30 def make_end_ring(start, end, radius, start_instead=False): middle_vector = end - start point_one = middle_vector.cross((1, 0, 0)).normalized() * radius point_two = middle_vector.cross(point_one).normalized() * radius ring_indices = [] for s in range(cylinder_sections + 1): angle_a = s * 2 * pi / cylinder_sections start_edge_point = cos(angle_a) * point_one + sin(angle_a) * point_two + start end_edge_point = start_edge_point + middle_vector ring_indices.append(len(verts)) if start_instead: verts.append(start_edge_point) else: verts.append(end_edge_point) return ring_indices def make_section(start_ring, end_ring): for i in range(cylinder_sections): faces.append([start_ring[i], end_ring[i], end_ring[i+1], start_ring[i+1]]) face_uvs.append(((i / cylinder_sections, 0), (i/cylinder_sections, 1))) # Should this take an old ring to connect to? # The ring calculation needs to carefully start in the same rotation around the branch def draw_branch_section(start, end, radius): middle_vector = end - start point_one = middle_vector.cross((1, 0, 0)).normalized() * radius point_two = middle_vector.cross(point_one).normalized() * radius for s in range(cylinder_sections + 1): angle_a = s * 2 * pi / cylinder_sections # angle_b = (s+1) * 2 * pi / cylinder_sections p_a = cos(angle_a) * point_one + sin(angle_a) * point_two + start # p_b = cos(angle_b) * point_one + sin(angle_b) * point_two + start # p_c = p_b + middle_vector p_d = p_a + middle_vector # Now hopefully p_a, p_b, p_c, and p_d form a cylinder panel # Optimization: We can save shared vertices if s < cylinder_sections: faces.append([n + len(verts) for n in [0, 1, 3, 2]]) face_uvs.append(((s / cylinder_sections, 0), (s/cylinder_sections, 1))) verts.extend([p_a, p_d]) # run this AFTER the tree mesh is created def add_texture_coordinates(tree_mesh): uv_layer = tree_mesh.uv_layers.new(name="Bark") for mpoly, fuv in zip(tree_mesh.polygons, face_uvs): uv_layer.data[mpoly.loop_indices[0]].uv = fuv[0] uv_layer.data[mpoly.loop_indices[1]].uv = fuv[1] # For the next: 3, than 2 uv_layer.data[mpoly.loop_indices[3]].uv = (fuv[0][0] + (1/cylinder_sections), fuv[0][1]) uv_layer.data[mpoly.loop_indices[2]].uv = (fuv[1][0] + (1/cylinder_sections), fuv[1][1]) # This is where we left off in class # Important question: How we avoid re-using a little slice of our texture? # Options: Have draw_branch_section output a y offset, have draw_branch_section support curves, etc def draw_branch(start, end, radius, prev_pos, subdivisions, start_ring): # Tuesday's bug: I used end instead of prev_pos # Really, it was that simple! section_length = (start - prev_pos).length continue_vector = section_length * (start - prev_pos).normalized() middle_of_straight_branch = (start + end) / 2 end_of_continue = start + continue_vector half_continue = start + continue_vector/2 early_spot = (2 * start + middle_of_straight_branch + 2 * half_continue ) / 5 # I did a little re-weighting on middle_spot and late_spot to avoid abrupt jogs middle_spot = (2 * start + end + end_of_continue) / 4 late_spot = (middle_spot + 2 * end + middle_of_straight_branch) / 4 early_ring = make_end_ring(start, early_spot, radius * 1.2) middle_ring = make_end_ring(early_spot, middle_spot, radius * 1.1) late_ring = make_end_ring(middle_spot, late_spot, radius * 1.05) end_ring = make_end_ring(late_spot, end, radius) make_section(start_ring, early_ring); # draw_branch(start, early_spot, radius * 1.2, prev_pos, subdivisions - 1, start_ring) make_section(early_ring, middle_ring); make_section(middle_ring, late_ring) make_section(late_ring, end_ring); return end_ring # curve? # for i in range(subdivisions): # Call draw_branch_section on one subdivision # params? def make_tree(pos, calls, prev_pos, prev_ring, stop_adjustment = 1): # Will we stop? if random() > stop_adjustment /calls: # Leaves? return branches = randint(1, 3) for i in range(branches): new_endpoint = Vector((pos.x + 2 * (random() - 0.5), pos.y + 2 * (random() - 0.5), pos.z + 1 + random())) # Draw branch to new endpoint new_end_ring = draw_branch(pos, new_endpoint, .3/calls, prev_pos, 1, prev_ring) make_tree(new_endpoint, calls + 1, pos, new_end_ring, stop_adjustment=stop_adjustment) start_ring = make_end_ring(Vector((0, 0, 0)), Vector((0, 0, 2)), 0.3, start_instead=True) #draw_branch(Vector((0, 0, 0)), Vector((2, 2, 2)), 0.2, Vector((0, 0, -2)), 1, start_ring) #draw_branch(Vector((0, 0, 0)), Vector((2, -2, 2)), 0.2, Vector((0, 0, -2)), 1, start_ring) #draw_branch(Vector((0, 0, 0)), Vector((-2, 2, 2)), 0.2, Vector((0, 0, -2)), 1, start_ring) #draw_branch(Vector((0, 0, 0)), Vector((-2, -2, 2)), 0.2, Vector((0, 0, -2)), 1, start_ring) make_tree(Vector((0, 0, 0)), 1, Vector((0, 0, -2)), start_ring, 4) #start_ring = make_end_ring(Vector((4, 0, 0)), Vector((4, 0, 2)), 0.3, start_instead=True) #draw_branch(Vector((4, 0, 0)), Vector((4, 0, 2)), 0.2, Vector((4, 0, -2)), 1, start_ring) #start_ring = make_end_ring(Vector((-4, 0, 0)), Vector((-4, 0, 2)), 0.3, start_instead=True) #draw_branch(Vector((-4, 0, 0)), Vector((-4, 0, 2)), 0.2, Vector((-4, 1, -2)), 1, start_ring) tree_mesh = bpy.data.meshes.new(name="TreeMesh") tree_mesh.from_pydata(vertices=verts, edges=[], faces=faces) add_texture_coordinates(tree_mesh) tree_mesh.validate(verbose=True) # I think this prints out errors if we have any tree_object = bpy.data.objects.new(name="Tree", object_data=tree_mesh) bpy.context.collection.objects.link(tree_object) material = bpy.data.materials.new(name="TreeBark") material.use_nodes = True tree_object.data.materials.append(material) bsdf_node= material.node_tree.nodes.get("Principled BSDF") texture_node = material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = bpy.data.images.load("bark.jpg") material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) return tree_object new_tree()