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