Update scripts to account for removal of the context override to bpy.ops
[blender-addons.git] / mesh_tissue / polyhedra.py
blobbd5fca7825d84882e3c201c4f5d3362eafaf8e20
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
4 # ------------------------------- version 0.84 ------------------------------- #
5 # #
6 # Creates duplicates of selected mesh to active morphing the shape according #
7 # to target faces. #
8 # #
9 # (c) Alessandro Zomparelli #
10 # (2017) #
11 # #
12 # http://www.co-de-it.com/ #
13 # #
14 # ############################################################################ #
17 import bpy
18 from bpy.types import (
19 Operator,
20 Panel,
21 PropertyGroup,
23 from bpy.props import (
24 BoolProperty,
25 EnumProperty,
26 FloatProperty,
27 IntProperty,
28 StringProperty,
29 PointerProperty
31 from mathutils import Vector, Quaternion, Matrix
32 import numpy as np
33 from math import *
34 import random, time, copy
35 import bmesh
36 from .utils import *
38 class polyhedra_wireframe(Operator):
39 bl_idname = "object.polyhedra_wireframe"
40 bl_label = "Tissue Polyhedra Wireframe"
41 bl_description = "Generate wireframes around the faces.\
42 \nDoesn't works with boundary edges.\
43 \n(Experimental)"
44 bl_options = {'REGISTER', 'UNDO'}
46 thickness : FloatProperty(
47 name="Thickness", default=0.1, min=0.001, soft_max=200,
48 description="Wireframe thickness"
51 subdivisions : IntProperty(
52 name="Segments", default=1, min=1, soft_max=10,
53 description="Max sumber of segments, used for the longest edge"
56 #regular_sections : BoolProperty(
57 # name="Regular Sections", default=False,
58 # description="Turn inner loops into polygons"
59 # )
61 dissolve_inners : BoolProperty(
62 name="Dissolve Inners", default=False,
63 description="Dissolve inner edges"
66 @classmethod
67 def poll(cls, context):
68 try:
69 #bool_tessellated = context.object.tissue_tessellate.generator != None
70 ob = context.object
71 return ob.type == 'MESH' and ob.mode == 'OBJECT'# and bool_tessellated
72 except:
73 return False
75 def invoke(self, context, event):
76 return context.window_manager.invoke_props_dialog(self)
78 def execute(self, context):
80 merge_dist = self.thickness*0.001
82 subs = self.subdivisions
84 start_time = time.time()
85 ob = context.object
86 me = simple_to_mesh(ob)
87 bm = bmesh.new()
88 bm.from_mesh(me)
90 bm.verts.ensure_lookup_table()
91 bm.edges.ensure_lookup_table()
92 bm.faces.ensure_lookup_table()
94 # Subdivide edges
95 proportional_subs = True
96 if subs > 1 and proportional_subs:
97 wire_length = [e.calc_length() for e in bm.edges]
98 all_edges = list(bm.edges)
99 max_segment = max(wire_length)/subs
100 split_edges = [[] for i in range(subs+1)]
101 for e, l in zip(all_edges, wire_length):
102 split_edges[int(l//max_segment)].append(e)
103 for i in range(2,subs):
104 perc = {}
105 for e in split_edges[i]:
106 perc[e]=0.1
107 bmesh.ops.bisect_edges(bm, edges=split_edges[i], cuts=i, edge_percents=perc)
109 ### Create double faces
110 double_faces = []
111 double_layer_edge = []
112 double_layer_piece = []
113 for f in bm.faces:
114 verts0 = [v.co for v in f.verts]
115 verts1 = [v.co for v in f.verts]
116 verts1.reverse()
117 double_faces.append(verts0)
118 double_faces.append(verts1)
120 # Create new bmesh object and data layers
121 bm1 = bmesh.new()
123 # Create faces and assign Edge Layers
124 for verts in double_faces:
125 new_verts = []
126 for v in verts:
127 vert = bm1.verts.new(v)
128 new_verts.append(vert)
129 bm1.faces.new(new_verts)
131 bm1.verts.ensure_lookup_table()
132 bm1.edges.ensure_lookup_table()
133 bm1.faces.ensure_lookup_table()
135 n_faces = len(bm.faces)
136 n_doubles = len(bm1.faces)
138 polyhedra = []
140 for e in bm.edges:
141 done = []
143 # ERROR: Naked edges
144 e_faces = len(e.link_faces)
145 if e_faces < 2:
146 bm.free()
147 bm1.free()
148 message = "Naked edges are not allowed"
149 self.report({'ERROR'}, message)
150 return {'CANCELLED'}
152 edge_vec = e.verts[1].co - e.verts[0].co
154 # run first face
155 for i1 in range(e_faces-1):
156 f1 = e.link_faces[i1]
157 #edge_verts1 = [v.index for v in f1.verts if v in e.verts]
158 verts1 = [v.index for v in f1.verts]
159 va1 = verts1.index(e.verts[0].index)
160 vb1 = verts1.index(e.verts[1].index)
161 # check if order of the edge matches the order of the face
162 dir1 = va1 == (vb1+1)%len(verts1)
163 edge_vec1 = edge_vec if dir1 else -edge_vec
165 # run second face
166 faces2 = []
167 normals2 = []
168 for i2 in range(i1+1,e_faces):
169 #for i2 in range(n_faces):
170 if i1 == i2: continue
171 f2 = e.link_faces[i2]
172 f2.normal_update()
173 #edge_verts2 = [v.index for v in f2.verts if v in e.verts]
174 verts2 = [v.index for v in f2.verts]
175 va2 = verts2.index(e.verts[0].index)
176 vb2 = verts2.index(e.verts[1].index)
177 # check if order of the edge matches the order of the face
178 dir2 = va2 == (vb2+1)%len(verts2)
179 # check for normal consistency
180 if dir1 != dir2:
181 # add face
182 faces2.append(f2.index+1)
183 normals2.append(f2.normal)
184 else:
185 # add flipped face
186 faces2.append(-(f2.index+1))
187 normals2.append(-f2.normal)
191 # find first polyhedra (positive)
192 plane_x = f1.normal # normal
193 plane_y = plane_x.cross(edge_vec1) # tangent face perp edge
194 id1 = (f1.index+1)
196 min_angle0 = 10000
198 # check consistent faces
199 if id1 not in done:
200 id2 = None
201 min_angle = min_angle0
202 for i2, n2 in zip(faces2,normals2):
203 v2 = flatten_vector(-n2, plane_x, plane_y)
204 angle = vector_rotation(v2)
205 if angle < min_angle:
206 id2 = i2
207 min_angle = angle
208 if id2: done.append(id2)
209 new_poly = True
210 # add to existing polyhedron
211 for p in polyhedra:
212 if id1 in p or id2 in p:
213 new_poly = False
214 if id2 not in p: p.append(id2)
215 if id1 not in p: p.append(id1)
216 break
217 # start new polyhedron
218 if new_poly: polyhedra.append([id1, id2])
220 # find second polyhedra (negative)
221 plane_x = -f1.normal # normal
222 plane_y = plane_x.cross(-edge_vec1) # tangent face perp edge
223 id1 = -(f1.index+1)
225 if id1 not in done:
226 id2 = None
227 min_angle = min_angle0
228 for i2, n2 in zip(faces2, normals2):
229 v2 = flatten_vector(n2, plane_x, plane_y)
230 angle = vector_rotation(v2)
231 if angle < min_angle:
232 id2 = -i2
233 min_angle = angle
234 done.append(id2)
235 add = True
236 for p in polyhedra:
237 if id1 in p or id2 in p:
238 add = False
239 if id2 not in p: p.append(id2)
240 if id1 not in p: p.append(id1)
241 break
242 if add: polyhedra.append([id1, id2])
244 for i in range(len(bm1.faces)):
245 for j in (False,True):
246 if j: id = i+1
247 else: id = -(i+1)
248 join = []
249 keep = []
250 for p in polyhedra:
251 if id in p: join += p
252 else: keep.append(p)
253 if len(join) > 0:
254 keep.append(list(dict.fromkeys(join)))
255 polyhedra = keep
257 for i, p in enumerate(polyhedra):
258 for j in p:
259 bm1.faces[j].material_index = i
261 end_time = time.time()
262 print('Tissue: Polyhedra wireframe, found {} polyhedra in {:.4f} sec'.format(len(polyhedra), end_time-start_time))
265 delete_faces = []
266 wireframe_faces = []
267 not_wireframe_faces = []
268 flat_faces = []
270 bm.free()
272 #bmesh.ops.bisect_edges(bm1, edges=bm1.edges, cuts=3)
274 end_time = time.time()
275 print('Tissue: Polyhedra wireframe, subdivide edges in {:.4f} sec'.format(end_time-start_time))
277 bm1.faces.index_update()
278 #merge_verts = []
279 for p in polyhedra:
280 delete_faces_poly = []
281 wireframe_faces_poly = []
282 faces_id = [(f-1)*2 if f > 0 else (-f-1)*2+1 for f in p]
283 faces_id_neg = [(-f-1)*2 if -f > 0 else (f-1)*2+1 for f in p]
284 merge_verts = []
285 faces = [bm1.faces[f_id] for f_id in faces_id]
286 for f in faces:
287 delete = False
288 if f.index in delete_faces: continue
290 cen = f.calc_center_median()
291 for e in f.edges:
292 mid = (e.verts[0].co + e.verts[1].co)/2
293 vec1 = e.verts[0].co - e.verts[1].co
294 vec2 = mid - cen
295 ang = Vector.angle(vec1,vec2)
296 length = vec2.length
297 #length = sin(ang)*length
298 if length < self.thickness/2:
299 delete = True
301 if False:
302 sides = len(f.verts)
303 for i in range(sides):
304 v = f.verts[i].co
305 v0 = f.verts[(i-1)%sides].co
306 v1 = f.verts[(i+1)%sides].co
307 vec0 = v0 - v
308 vec1 = v1 - v
309 ang = (pi - vec0.angle(vec1))/2
310 length = min(vec0.length, vec1.length)*sin(ang)
311 if length < self.thickness/2:
312 delete = True
313 break
315 if delete:
316 delete_faces_poly.append(f.index)
317 else:
318 wireframe_faces_poly.append(f.index)
319 merge_verts += [v for v in f.verts]
320 if len(wireframe_faces_poly) < 2:
321 delete_faces += faces_id
322 not_wireframe_faces += faces_id_neg
323 else:
324 wireframe_faces += wireframe_faces_poly
325 flat_faces += delete_faces_poly
327 #wireframe_faces = list(dict.fromkeys(wireframe_faces))
328 bmesh.ops.remove_doubles(bm1, verts=merge_verts, dist=merge_dist)
329 bm1.edges.ensure_lookup_table()
330 bm1.faces.ensure_lookup_table()
331 bm1.faces.index_update()
334 wireframe_faces = [i for i in wireframe_faces if i not in not_wireframe_faces]
335 wireframe_faces = list(dict.fromkeys(wireframe_faces))
337 flat_faces = list(dict.fromkeys(flat_faces))
339 end_time = time.time()
340 print('Tissue: Polyhedra wireframe, merge and delete in {:.4f} sec'.format(end_time-start_time))
342 poly_me = me.copy()
343 bm1.to_mesh(poly_me)
344 poly_me.update()
345 new_ob = bpy.data.objects.new("Polyhedra", poly_me)
346 context.collection.objects.link(new_ob)
348 ############# FRAME #############
349 bm1.faces.index_update()
350 wireframe_faces = [bm1.faces[i] for i in wireframe_faces]
351 original_faces = wireframe_faces
352 #bmesh.ops.remove_doubles(bm1, verts=merge_verts, dist=0.001)
354 # detect edge loops
356 loops = []
357 boundaries_mat = []
358 neigh_face_center = []
359 face_normals = []
361 # compute boundary frames
362 new_faces = []
363 wire_length = []
364 vert_ids = []
366 # append regular faces
368 for f in original_faces:
369 loop = list(f.verts)
370 loops.append(loop)
371 boundaries_mat.append([f.material_index for v in loop])
372 f.normal_update()
373 face_normals.append([f.normal for v in loop])
375 push_verts = []
376 inner_loops = []
378 for loop_index, loop in enumerate(loops):
379 is_boundary = loop_index < len(neigh_face_center)
380 materials = boundaries_mat[loop_index]
381 new_loop = []
382 loop_ext = [loop[-1]] + loop + [loop[0]]
384 # calc tangents
385 tangents = []
386 for i in range(len(loop)):
387 # vertices
388 vert0 = loop_ext[i]
389 vert = loop_ext[i+1]
390 vert1 = loop_ext[i+2]
391 # edge vectors
392 vec0 = (vert0.co - vert.co).normalized()
393 vec1 = (vert.co - vert1.co).normalized()
394 # tangent
395 _vec1 = -vec1
396 _vec0 = -vec0
397 ang = (pi - vec0.angle(vec1))/2
398 normal = face_normals[loop_index][i]
399 tan0 = normal.cross(vec0)
400 tan1 = normal.cross(vec1)
401 tangent = (tan0 + tan1).normalized()/sin(ang)*self.thickness/2
402 tangents.append(tangent)
404 # calc correct direction for boundaries
405 mult = -1
406 if is_boundary:
407 dir_val = 0
408 for i in range(len(loop)):
409 surf_point = neigh_face_center[loop_index][i]
410 tangent = tangents[i]
411 vert = loop_ext[i+1]
412 dir_val += tangent.dot(vert.co - surf_point)
413 if dir_val > 0: mult = 1
415 # add vertices
416 for i in range(len(loop)):
417 vert = loop_ext[i+1]
418 area = 1
419 new_co = vert.co + tangents[i] * mult * area
420 # add vertex
421 new_vert = bm1.verts.new(new_co)
422 new_loop.append(new_vert)
423 vert_ids.append(vert.index)
424 new_loop.append(new_loop[0])
426 # add faces
427 #materials += [materials[0]]
428 for i in range(len(loop)):
429 v0 = loop_ext[i+1]
430 v1 = loop_ext[i+2]
431 v2 = new_loop[i+1]
432 v3 = new_loop[i]
433 face_verts = [v1,v0,v3,v2]
434 if mult == -1: face_verts = [v0,v1,v2,v3]
435 new_face = bm1.faces.new(face_verts)
436 # Material by original edges
437 piece_id = 0
438 new_face.select = True
439 new_faces.append(new_face)
440 wire_length.append((v0.co - v1.co).length)
441 max_segment = max(wire_length)/self.subdivisions
442 #for f,l in zip(new_faces,wire_length):
443 # f.material_index = min(int(l/max_segment), self.subdivisions-1)
444 bm1.verts.ensure_lookup_table()
445 push_verts += [v.index for v in loop_ext]
447 # At this point topology han been build, but not yet thickened
449 end_time = time.time()
450 print('Tissue: Polyhedra wireframe, frames in {:.4f} sec'.format(end_time-start_time))
452 bm1.verts.ensure_lookup_table()
453 bm1.edges.ensure_lookup_table()
454 bm1.faces.ensure_lookup_table()
455 bm1.verts.index_update()
457 ### Displace vertices ###
459 circle_center = [0]*len(bm1.verts)
460 circle_normal = [0]*len(bm1.verts)
462 smooth_corners = [True] * len(bm1.verts)
463 corners = [[] for i in range(len(bm1.verts))]
464 normals = [0]*len(bm1.verts)
465 vertices = [0]*len(bm1.verts)
466 # Define vectors direction
467 for f in new_faces:
468 v0 = f.verts[0]
469 v1 = f.verts[1]
470 id = v0.index
471 corners[id].append((v1.co - v0.co).normalized())
472 normals[id] = v0.normal.copy()
473 vertices[id] = v0
474 smooth_corners[id] = False
475 # Displace vertices
476 for i, vecs in enumerate(corners):
477 if len(vecs) > 0:
478 v = vertices[i]
479 nor = normals[i]
480 ang = 0
481 for vec in vecs:
482 ang += nor.angle(vec)
483 ang /= len(vecs)
484 div = sin(ang)
485 if div == 0: div = 1
486 v.co += nor*self.thickness/2/div
488 end_time = time.time()
489 print('Tissue: Polyhedra wireframe, corners displace in {:.4f} sec'.format(end_time-start_time))
491 # Removing original flat faces
493 flat_faces = [bm1.faces[i] for i in flat_faces]
494 for f in flat_faces:
495 f.material_index = self.subdivisions+1
496 for v in f.verts:
497 if smooth_corners[v.index]:
498 v.co += v.normal*self.thickness/2
499 smooth_corners[v.index] = False
500 delete_faces = delete_faces + [f.index for f in original_faces]
501 delete_faces = list(dict.fromkeys(delete_faces))
502 delete_faces = [bm1.faces[i] for i in delete_faces]
503 bmesh.ops.delete(bm1, geom=delete_faces, context='FACES')
505 bmesh.ops.remove_doubles(bm1, verts=bm1.verts, dist=merge_dist)
506 bm1.faces.ensure_lookup_table()
507 bm1.edges.ensure_lookup_table()
508 bm1.verts.ensure_lookup_table()
510 if self.dissolve_inners:
511 bm1.edges.index_update()
512 dissolve_edges = []
513 for f in bm1.faces:
514 e = f.edges[2]
515 if e not in dissolve_edges:
516 dissolve_edges.append(e)
517 bmesh.ops.dissolve_edges(bm1, edges=dissolve_edges, use_verts=True, use_face_split=True)
519 all_lines = [[] for e in me.edges]
520 all_end_points = [[] for e in me.edges]
521 for v in bm1.verts: v.select_set(False)
522 for f in bm1.faces: f.select_set(False)
524 _me = me.copy()
525 bm1.to_mesh(me)
526 me.update()
527 new_ob = bpy.data.objects.new("Wireframe", me)
528 context.collection.objects.link(new_ob)
529 for o in context.scene.objects: o.select_set(False)
530 new_ob.select_set(True)
531 context.view_layer.objects.active = new_ob
532 me = _me
534 bm1.free()
535 bpy.data.meshes.remove(_me)
536 #new_ob.location = ob.location
537 new_ob.matrix_world = ob.matrix_world
539 end_time = time.time()
540 print('Tissue: Polyhedra wireframe in {:.4f} sec'.format(end_time-start_time))
541 return {'FINISHED'}