1 # SPDX-FileCopyrightText: 2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
6 # ------------------------------- version 0.84 ------------------------------- #
8 # Creates duplicates of selected mesh to active morphing the shape according #
11 # (c) Alessandro Zomparelli #
14 # http://www.co-de-it.com/ #
16 # ############################################################################ #
20 from bpy
.types
import (
25 from bpy
.props
import (
33 from mathutils
import Vector
, Quaternion
, Matrix
36 import random
, time
, copy
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.\
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"
63 dissolve_inners
: BoolProperty(
64 name
="Dissolve Inners", default
=False,
65 description
="Dissolve inner edges"
69 def poll(cls
, context
):
71 #bool_tessellated = context.object.tissue_tessellate.generator != None
73 return ob
.type == 'MESH' and ob
.mode
== 'OBJECT'# and bool_tessellated
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()
88 me
= simple_to_mesh(ob
)
92 bm
.verts
.ensure_lookup_table()
93 bm
.edges
.ensure_lookup_table()
94 bm
.faces
.ensure_lookup_table()
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
):
107 for e
in split_edges
[i
]:
109 bmesh
.ops
.bisect_edges(bm
, edges
=split_edges
[i
], cuts
=i
, edge_percents
=perc
)
111 ### Create double faces
113 double_layer_edge
= []
114 double_layer_piece
= []
116 verts0
= [v
.co
for v
in f
.verts
]
117 verts1
= [v
.co
for v
in f
.verts
]
119 double_faces
.append(verts0
)
120 double_faces
.append(verts1
)
122 # Create new bmesh object and data layers
125 # Create faces and assign Edge Layers
126 for verts
in double_faces
:
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
)
146 e_faces
= len(e
.link_faces
)
150 message
= "Naked edges are not allowed"
151 self
.report({'ERROR'}, message
)
154 edge_vec
= e
.verts
[1].co
- e
.verts
[0].co
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
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
]
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
184 faces2
.append(f2
.index
+1)
185 normals2
.append(f2
.normal
)
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
200 # check consistent faces
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
:
210 if id2
: done
.append(id2
)
212 # add to existing polyhedron
214 if id1
in p
or id2
in p
:
216 if id2
not in p
: p
.append(id2
)
217 if id1
not in p
: p
.append(id1
)
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
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
:
239 if id1
in p
or id2
in p
:
241 if id2
not in p
: p
.append(id2
)
242 if id1
not in p
: p
.append(id1
)
244 if add
: polyhedra
.append([id1
, id2
])
246 for i
in range(len(bm1
.faces
)):
247 for j
in (False,True):
253 if id in p
: join
+= p
256 keep
.append(list(dict.fromkeys(join
)))
259 for i
, p
in enumerate(polyhedra
):
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
))
269 not_wireframe_faces
= []
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()
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
]
287 faces
= [bm1
.faces
[f_id
] for f_id
in faces_id
]
290 if f
.index
in delete_faces
: continue
292 cen = f.calc_center_median()
294 mid = (e.verts[0].co + e.verts[1].co)/2
295 vec1 = e.verts[0].co - e.verts[1].co
297 ang = Vector.angle(vec1,vec2)
299 #length = sin(ang)*length
300 if length < self.thickness/2:
305 for i
in range(sides
):
307 v0
= f
.verts
[(i
-1)%sides].co
308 v1
= f
.verts
[(i
+1)%sides].co
311 ang
= (pi
- vec0
.angle(vec1
))/2
312 length
= min(vec0
.length
, vec1
.length
)*sin(ang
)
313 if length
< self
.thickness
/2:
318 delete_faces_poly
.append(f
.index
)
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
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
))
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)
360 neigh_face_center
= []
363 # compute boundary frames
368 # append regular faces
370 for f
in original_faces
:
373 boundaries_mat
.append([f
.material_index
for v
in loop
])
375 face_normals
.append([f
.normal
for v
in loop
])
380 for loop_index
, loop
in enumerate(loops
):
381 is_boundary
= loop_index
< len(neigh_face_center
)
382 materials
= boundaries_mat
[loop_index
]
384 loop_ext
= [loop
[-1]] + loop
+ [loop
[0]]
388 for i
in range(len(loop
)):
392 vert1
= loop_ext
[i
+2]
394 vec0
= (vert0
.co
- vert
.co
).normalized()
395 vec1
= (vert
.co
- vert1
.co
).normalized()
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
410 for i
in range(len(loop
)):
411 surf_point
= neigh_face_center
[loop_index
][i
]
412 tangent
= tangents
[i
]
414 dir_val
+= tangent
.dot(vert
.co
- surf_point
)
415 if dir_val
> 0: mult
= 1
418 for i
in range(len(loop
)):
421 new_co
= vert
.co
+ tangents
[i
] * mult
* area
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])
429 #materials += [materials[0]]
430 for i
in range(len(loop
)):
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
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
473 corners
[id].append((v1
.co
- v0
.co
).normalized())
474 normals
[id] = v0
.normal
.copy()
476 smooth_corners
[id] = False
478 for i
, vecs
in enumerate(corners
):
484 ang
+= nor
.angle(vec
)
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
]
497 f
.material_index
= self
.subdivisions
+1
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()
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)
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
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
))