forked from patmo141/odc_public
-
Notifications
You must be signed in to change notification settings - Fork 0
/
bmesh_fns.py
427 lines (322 loc) · 12.3 KB
/
bmesh_fns.py
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
import bpy
import bmesh
from mathutils import Matrix, Vector, Color
import loops_tools
from mathutils.bvhtree import BVHTree
def remove_undercuts(context, ob, view, world = True, smooth = True, epsilon = .000001):
'''
args:
ob - mesh object
view - Mathutils Vector
return:
Bmesh with Undercuts Removed?
best to make sure normals are consistent beforehand
best for manifold meshes, however non-man works
noisy meshes can be compensated for with island threhold
'''
#careful, this can get expensive with multires
me = ob.to_mesh(context.scene, True, 'RENDER')
bme = bmesh.new()
bme.from_mesh(me)
bme.normal_update()
bme.verts.ensure_lookup_table()
bme.edges.ensure_lookup_table()
bme.faces.ensure_lookup_table()
bvh = BVHTree.FromBMesh(bme)
#keep track of the world matrix
mx = ob.matrix_world
if world:
#meaning the vector is in world coords
#we need to take it back into local
i_mx = mx.inverted()
view = i_mx.to_quaternion() * view
face_directions = [[0]] * len(bme.faces)
up_faces = set()
overhang_faces = set() #all faces pointing away from view
#precalc all the face directions and store in dict
for f in bme.faces:
direction = f.normal.dot(view)
if direction <= -epsilon:
overhang_faces.add(f)
else:
up_faces.add(f)
face_directions[f.index] = direction
print('there are %i up_faces' % len(up_faces))
print('there are %i down_faces' % len(overhang_faces))
#for f in bme.faces:
# if f in overhangs:
# f.select_set(True)
# else:
# f.select_set(False)
overhang_islands = [] #islands bigger than a certain threshold (by surface area?
upfacing_islands = []
def face_neighbors_up(bmface):
neighbors = []
for ed in bmface.edges:
neighbors += [f for f in ed.link_faces if f != bmface and f in up_faces]
return neighbors
#remove smal islands from up_faces and add to overhangs
max_iters = len(up_faces)
iters_0 = 0
islands_removed = 0
up_faces_copy = up_faces.copy()
while len(up_faces_copy) and iters_0 < max_iters:
iters_0 += 1
max_iters_1 = len(up_faces)
seed = up_faces_copy.pop()
new_faces = set(face_neighbors_up(seed))
up_faces_copy -= new_faces
island = set([seed])
island |= new_faces
iters_1 = 0
while iters_1 < max_iters_1 and new_faces:
iters_1 += 1
new_candidates = set()
for f in new_faces:
new_candidates.update(face_neighbors_up(f))
new_faces = new_candidates - island
if new_faces:
island |= new_faces
up_faces_copy -= new_faces
if len(island) < 75: #small patch surrounded by overhang, add to overhang area
islands_removed += 1
overhang_faces |= island
else:
upfacing_islands += [island]
print('%i upfacing islands removed' % islands_removed)
print('there are now %i down faces' % len(overhang_faces))
def face_neighbors_down(bmface):
neighbors = []
for ed in bmface.edges:
neighbors += [f for f in ed.link_faces if f != bmface and f in overhang_faces]
return neighbors
overhang_faces_copy = overhang_faces.copy()
while len(overhang_faces_copy):
seed = overhang_faces_copy.pop()
new_faces = set(face_neighbors_down(seed))
island = set([seed])
island |= new_faces
overhang_faces_copy -= new_faces
iters = 0
while iters < 100000 and new_faces:
iters += 1
new_candidates = set()
for f in new_faces:
new_candidates.update(face_neighbors_down(f))
new_faces = new_candidates - island
if new_faces:
island |= new_faces
overhang_faces_copy -= new_faces
if len(island) > 75: #TODO, calc overhang factor. Surface area dotted with direction
overhang_islands += [island]
for f in bme.faces:
f.select_set(False)
for ed in bme.edges:
ed.select_set(False)
for v in bme.verts:
v.select_set(False)
island_loops = []
island_verts = []
del_faces = set()
for isl in overhang_islands:
loop_eds = []
loop_verts = []
del_faces |= isl
for f in isl:
for ed in f.edges:
if len(ed.link_faces) == 1:
loop_eds += [ed]
loop_verts += [ed.verts[0], ed.verts[1]]
elif (ed.link_faces[0] in isl) and (ed.link_faces[1] not in isl):
loop_eds += [ed]
loop_verts += [ed.verts[0], ed.verts[1]]
elif (ed.link_faces[1] in isl) and (ed.link_faces[0] not in isl):
loop_eds += [ed]
loop_verts += [ed.verts[0], ed.verts[1]]
#f.select_set(True)
island_verts += [list(set(loop_verts))]
island_loops += [loop_eds]
bme.faces.ensure_lookup_table()
bme.edges.ensure_lookup_table()
loop_edges = []
for ed_loop in island_loops:
loop_edges += ed_loop
for ed in ed_loop:
ed.select_set(True)
loops_tools.relax_loops_util(bme, loop_edges, 5)
for ed in bme.edges:
ed.select_set(False)
exclude_vs = set()
for vs in island_verts:
exclude_vs.update(vs)
smooth_verts = []
for v in exclude_vs:
smooth_verts += [ed.other_vert(v) for ed in v.link_edges if ed.other_vert(v) not in exclude_vs]
ret = bmesh.ops.extrude_edge_only(bme, edges = loop_edges)
new_fs = [ele for ele in ret['geom'] if isinstance(ele, bmesh.types.BMFace)]
new_vs = [ele for ele in ret['geom'] if isinstance(ele, bmesh.types.BMVert)]
#TODO, ray cast down to base plane?
for v in new_vs:
v.co -= 10*view
for f in new_fs:
f.select_set(True)
bmesh.ops.delete(bme, geom = list(del_faces), context = 3)
del_verts = []
for v in bme.verts:
if all([f in del_faces for f in v.link_faces]):
del_verts += [v]
bmesh.ops.delete(bme, geom = del_verts, context = 1)
del_edges = []
for ed in bme.edges:
if len(ed.link_faces) == 0:
del_edges += [ed]
print('deleting %i edges' % len(del_edges))
bmesh.ops.delete(bme, geom = del_edges, context = 4)
bmesh.ops.recalc_face_normals(bme, faces = new_fs)
bme.normal_update()
new_me = bpy.data.meshes.new(ob.name + '_blockout')
obj = bpy.data.objects.new(new_me.name, new_me)
context.scene.objects.link(obj)
obj.select = True
context.scene.objects.active = obj
bme.to_mesh(obj.data)
# Get material
mat = bpy.data.materials.get("Model Material")
if mat is None:
# create material
print('creating model material')
mat = bpy.data.materials.new(name="Model Material")
#mat.diffuse_color = Color((0.8, .8, .8))
# Assign it to object
obj.data.materials.append(mat)
print('Model material added')
mat2 = bpy.data.materials.get("Undercut Material")
if mat2 is None:
# create material
mat2 = bpy.data.materials.new(name="Undercut Material")
mat2.diffuse_color = Color((0.8, .2, .2))
obj.data.materials.append(mat2)
mat_ind = obj.data.materials.find("Undercut Material")
print('Undercut material is %i' % mat_ind)
for f in new_faces:
obj.data.polygons[f.idnex].material_index = mat_ind
if world:
obj.matrix_world = mx
bme.free()
del bvh
return
def join_bmesh_map(source, target, src_trg_map = None, src_mx = None, trg_mx = None):
'''
'''
L = len(target.verts)
if not src_trg_map:
src_trg_map = {-1:-1}
l = len(src_trg_map)
print('There are %i items in the vert map' % len(src_trg_map))
if not src_mx:
src_mx = Matrix.Identity(4)
if not trg_mx:
trg_mx = Matrix.Identity(4)
i_trg_mx = Matrix.Identity(4)
else:
i_trg_mx = trg_mx.inverted()
old_bmverts = [v for v in target.verts] #this will store them in order
new_bmverts = [] #these will be created in order
source.verts.ensure_lookup_table()
for v in source.verts:
if v.index not in src_trg_map:
new_ind = len(target.verts)
new_bv = target.verts.new(i_trg_mx * src_mx * v.co)
new_bmverts.append(new_bv) #gross...append
src_trg_map[v.index] = new_ind
else:
print('vert alread in the map %i' % v.index)
lverts = old_bmverts + new_bmverts
target.verts.index_update()
target.verts.ensure_lookup_table()
new_bmfaces = []
for f in source.faces:
v_inds = []
for v in f.verts:
new_ind = src_trg_map[v.index]
v_inds.append(new_ind)
if any([i > len(lverts)-1 for i in v_inds]):
print('impending index error')
print(len(lverts))
print(v_inds)
if target.faces.get(tuple(lverts[i] for i in v_inds)):
print(v_inds)
continue
new_bmfaces += [target.faces.new(tuple(lverts[i] for i in v_inds))]
target.faces.ensure_lookup_table()
target.verts.ensure_lookup_table()
new_L = len(target.verts)
if src_trg_map:
if new_L != L + len(source.verts) -l:
print('seems some verts were left in that should not have been')
def join_bmesh(source, target, src_mx = None, trg_mx = None):
src_trg_map = dict()
L = len(target.verts)
if not src_mx:
src_mx = Matrix.Identity(4)
if not trg_mx:
trg_mx = Matrix.Identity(4)
i_trg_mx = Matrix.Identity(4)
else:
i_trg_mx = trg_mx.inverted()
new_bmverts = []
source.verts.ensure_lookup_table()
for v in source.verts:
if v.index not in src_trg_map:
new_ind = len(target.verts)
new_bv = target.verts.new(i_trg_mx * src_mx * v.co)
new_bmverts.append(new_bv)
src_trg_map[v.index] = new_ind
target.verts.index_update()
target.verts.ensure_lookup_table()
new_bmfaces = []
for f in source.faces:
v_inds = []
for v in f.verts:
new_ind = src_trg_map[v.index]
v_inds.append(new_ind)
new_bmfaces += [target.faces.new(tuple(target.verts[i] for i in v_inds))]
target.faces.ensure_lookup_table()
target.verts.ensure_lookup_table()
target.verts.index_update()
target.verts.index_update()
target.verts.ensure_lookup_table()
target.faces.ensure_lookup_table()
new_L = len(target.verts)
if new_L != L + len(source.verts):
print('seems some verts were left out')
def join_objects(obs, name = ''):
'''
uses BMesh to join objects. Advantage is that it is context
agnostic, so no editmoe or bpy.ops has to be used.
Args:
obs - list of Blender objects
Returns:
new object with name specified. Otherwise '_joined' will
be added to the name of the first object in the list
'''
target_bme = bmesh.new()
target_bme.verts.ensure_lookup_table()
target_bme.faces.ensure_lookup_table()
trg_mx = obs[0].matrix_world
if name == '':
name = obs[0].name + '_joined'
for ob in obs:
src_mx = ob.matrix_world
if ob.data.is_editmode:
src_bme = bmesh.from_editmesh(ob.data)
else:
src_bme = bmesh.new()
src_bme.from_mesh(ob.data)
join_bmesh(src_bme, target_bme, src_mx, trg_mx)
src_bme.free()
new_me = bpy.data.meshes.new(name)
new_ob = bpy.data.objects.new(name, new_me)
target_bme.to_mesh(new_me)
target_bme.free()
return new_ob