forked from friggog/tree-gen
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathutilities.py
More file actions
223 lines (161 loc) · 6.66 KB
/
utilities.py
File metadata and controls
223 lines (161 loc) · 6.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import bpy
import bmesh
import mathutils
import random
import sys
import threading
import time
from queue import Queue
class _LogThread(threading.Thread):
# Set to run as a daemon, so this thread kills itself at program termination
def __init__(self, queue):
threading.Thread.__init__(self)
self.queue = queue
self.daemon = True
self.running = False
def run(self):
self.running = True
while True:
msg = self.queue.get()
sys.stdout.write(str(msg))
sys.stdout.flush()
thread_queue = None
log_thread = None
def get_logger(logging):
global log_thread, thread_queue
if logging:
if log_thread is None:
thread_queue = Queue()
log_thread = _LogThread(thread_queue)
log_thread.start()
def update_log(msg):
global log_thread
if not log_thread.running:
log_thread = _LogThread(thread_queue)
log_thread.start()
thread_queue.put(msg)
return update_log
def update_log(msg):
pass
return lambda _: None
def object_deleted(o):
try:
return bpy.data.objects.get(o.name, None) is not None
except ReferenceError: # o is deleted, so accessing name raises an error
return True
def convert_to_mesh(context):
"""
Converts tree branches from curve to mesh
"""
from . import parametric
update_log = parametric.gen.update_log
try:
tree = context.object
except AttributeError:
raise Exception('Could not find tree while attempting to convert to mesh')
new_branches = []
old_branches = [child for child in tree.children if (child.name.startswith('Trunk') or child.name.startswith('Branches'))
and str(type(child.data)) == "<class 'bpy.types.Curve'>"]
if len(old_branches) == 0:
raise Exception('No branches found while converting to mesh')
for old_branch in old_branches:
old_branch_name = old_branch.name
old_mesh_name = old_branch.data.name
# Convert the branches curve to a mesh, then get an editable copy
old_branch_mesh = old_branch.to_mesh()
br_bmesh = bmesh.new()
br_bmesh.from_mesh(old_branch_mesh)
# Purge old branch data from memory
old_branch.to_mesh_clear()
bpy.data.curves.remove(old_branch.data)
if not object_deleted(old_branch):
bpy.data.objects.remove(old_branch, True)
# Create a new mesh and container object
new_branch = bpy.data.objects.new(old_branch_name, bpy.data.meshes.new(old_mesh_name))
br_bmesh.to_mesh(new_branch.data)
# Purge bmesh from memory
br_bmesh.free()
# Make the mesh active in the scene, then associate it with the tree
context.collection.objects.link(new_branch)
new_branch.matrix_world = tree.matrix_world
new_branch.parent = tree
new_branches.append(new_branch)
if context.scene.tree_gen_merge_verts_by_distance:
update_log('Merging duplicate vertices; this will take a bit for complex trees.\n')
for new_branch in new_branches:
context.view_layer.objects.active = new_branch
bpy.ops.object.mode_set(mode='EDIT', toggle=True)
bpy.ops.mesh.select_all(action='SELECT')
bpy.ops.mesh.remove_doubles(threshold=0.0001)
def generate_leaf_lods(context, level_count=3):
from . import parametric
update_log = parametric.gen.update_log
tree = context.object
original = None
for child in tree.children:
if child.name.startswith('Leaves'):
base_name = child.name
child.name = child.name + '_LOD0'
original = child
parent = child.parent
break
if original is None:
raise Exception('No leaves found while attempting to generate LODs')
leaf_count = len(original.data.polygons)
lod_reduce_amounts = [.9, .6, .4]
lod_leaf_counts = [round(leaf_count * lod_reduce_amounts[0]),
round(leaf_count * lod_reduce_amounts[1]),
round(leaf_count * lod_reduce_amounts[2])]
for level in range(level_count):
new_leaf_data = bmesh.new()
new_leaf_data.from_mesh(original.data)
new_leaf_count = lod_leaf_counts[level]
# Delete faces
if new_leaf_count > 8: # Prevent infinite loops
amount_to_delete = leaf_count - new_leaf_count
indexes_to_delete = set(random.randint(0, leaf_count - 1) for _ in range(amount_to_delete))
while len(indexes_to_delete) < amount_to_delete:
indexes_to_delete.add(random.randint(0, leaf_count - 1))
new_leaf_data.faces.ensure_lookup_table()
to_delete = [new_leaf_data.faces[i] for i in indexes_to_delete]
bmesh.ops.delete(new_leaf_data, geom=list(to_delete), context='FACES')
# Create new leaves object and copy the new leaves data into it
lod_level_name = '_LOD' + str(level + 1)
new_leaves = bpy.data.objects.new(base_name + lod_level_name, bpy.data.meshes.new('leaves' + lod_level_name))
new_leaf_data.to_mesh(new_leaves.data)
new_leaf_data.free()
# Add new leaves object to the scene
context.collection.objects.link(new_leaves)
new_leaves.matrix_world = parent.matrix_world
new_leaves.parent = parent
new_leaves.hide_set(True)
update_log('\rLeaf LOD level ' + str(level + 1) + '/' + str(level_count) + ' generated')
context.view_layer.objects.active = tree
update_log('\n')
def render_tree(output_path):
from . import parametric
update_log = parametric.gen.update_log
update_log('\nRendering Scene\n')
targets = None
for obj in bpy.context.scene.objects:
bpy.context.active_object.select_set(state=False)
targets = [obj] + [child for child in obj.children] if obj.name.startswith('Tree') else targets
if targets is None:
print('Could not find a tree to render')
return
for target in targets:
target.select_set(state=True)
bpy.ops.view3d.camera_to_view_selected()
time.sleep(.2)
try:
camera = bpy.context.scene.camera
except KeyError:
print('Could not find camera to capture with')
return
inv = camera.matrix_world.copy()
inv.invert()
vec = mathutils.Vector((0.0, 0, 1.0)) # move camera back a bit
vec_rot = vec @ inv # vec aligned to local axis
camera.location = camera.location + vec_rot
bpy.data.scenes['Scene'].render.filepath = output_path
bpy.ops.render.render(write_still=True)