1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
4 # ------------------------------- version 0.84 ------------------------------- #
6 # Creates duplicates of selected mesh to active morphing the shape according #
9 # (c) Alessandro Zomparelli #
12 # http://www.co-de-it.com/ #
14 # ############################################################################ #
18 from bpy
.types
import (
23 from bpy
.props
import (
31 from mathutils
import Vector
, Quaternion
, Matrix
34 import random
, time
, copy
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.\
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"
61 dissolve_inners
: BoolProperty(
62 name
="Dissolve Inners", default
=False,
63 description
="Dissolve inner edges"
67 def poll(cls
, context
):
69 #bool_tessellated = context.object.tissue_tessellate.generator != None
71 return ob
.type == 'MESH' and ob
.mode
== 'OBJECT'# and bool_tessellated
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()
86 me
= simple_to_mesh(ob
)
90 bm
.verts
.ensure_lookup_table()
91 bm
.edges
.ensure_lookup_table()
92 bm
.faces
.ensure_lookup_table()
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
):
105 for e
in split_edges
[i
]:
107 bmesh
.ops
.bisect_edges(bm
, edges
=split_edges
[i
], cuts
=i
, edge_percents
=perc
)
109 ### Create double faces
111 double_layer_edge
= []
112 double_layer_piece
= []
114 verts0
= [v
.co
for v
in f
.verts
]
115 verts1
= [v
.co
for v
in f
.verts
]
117 double_faces
.append(verts0
)
118 double_faces
.append(verts1
)
120 # Create new bmesh object and data layers
123 # Create faces and assign Edge Layers
124 for verts
in double_faces
:
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
)
144 e_faces
= len(e
.link_faces
)
148 message
= "Naked edges are not allowed"
149 self
.report({'ERROR'}, message
)
152 edge_vec
= e
.verts
[1].co
- e
.verts
[0].co
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
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
]
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
182 faces2
.append(f2
.index
+1)
183 normals2
.append(f2
.normal
)
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
198 # check consistent faces
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
:
208 if id2
: done
.append(id2
)
210 # add to existing polyhedron
212 if id1
in p
or id2
in p
:
214 if id2
not in p
: p
.append(id2
)
215 if id1
not in p
: p
.append(id1
)
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
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
:
237 if id1
in p
or id2
in p
:
239 if id2
not in p
: p
.append(id2
)
240 if id1
not in p
: p
.append(id1
)
242 if add
: polyhedra
.append([id1
, id2
])
244 for i
in range(len(bm1
.faces
)):
245 for j
in (False,True):
251 if id in p
: join
+= p
254 keep
.append(list(dict.fromkeys(join
)))
257 for i
, p
in enumerate(polyhedra
):
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
))
267 not_wireframe_faces
= []
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()
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
]
285 faces
= [bm1
.faces
[f_id
] for f_id
in faces_id
]
288 if f
.index
in delete_faces
: continue
290 cen = f.calc_center_median()
292 mid = (e.verts[0].co + e.verts[1].co)/2
293 vec1 = e.verts[0].co - e.verts[1].co
295 ang = Vector.angle(vec1,vec2)
297 #length = sin(ang)*length
298 if length < self.thickness/2:
303 for i
in range(sides
):
305 v0
= f
.verts
[(i
-1)%sides].co
306 v1
= f
.verts
[(i
+1)%sides].co
309 ang
= (pi
- vec0
.angle(vec1
))/2
310 length
= min(vec0
.length
, vec1
.length
)*sin(ang
)
311 if length
< self
.thickness
/2:
316 delete_faces_poly
.append(f
.index
)
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
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
))
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)
358 neigh_face_center
= []
361 # compute boundary frames
366 # append regular faces
368 for f
in original_faces
:
371 boundaries_mat
.append([f
.material_index
for v
in loop
])
373 face_normals
.append([f
.normal
for v
in loop
])
378 for loop_index
, loop
in enumerate(loops
):
379 is_boundary
= loop_index
< len(neigh_face_center
)
380 materials
= boundaries_mat
[loop_index
]
382 loop_ext
= [loop
[-1]] + loop
+ [loop
[0]]
386 for i
in range(len(loop
)):
390 vert1
= loop_ext
[i
+2]
392 vec0
= (vert0
.co
- vert
.co
).normalized()
393 vec1
= (vert
.co
- vert1
.co
).normalized()
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
408 for i
in range(len(loop
)):
409 surf_point
= neigh_face_center
[loop_index
][i
]
410 tangent
= tangents
[i
]
412 dir_val
+= tangent
.dot(vert
.co
- surf_point
)
413 if dir_val
> 0: mult
= 1
416 for i
in range(len(loop
)):
419 new_co
= vert
.co
+ tangents
[i
] * mult
* area
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])
427 #materials += [materials[0]]
428 for i
in range(len(loop
)):
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
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
471 corners
[id].append((v1
.co
- v0
.co
).normalized())
472 normals
[id] = v0
.normal
.copy()
474 smooth_corners
[id] = False
476 for i
, vecs
in enumerate(corners
):
482 ang
+= nor
.angle(vec
)
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
]
495 f
.material_index
= self
.subdivisions
+1
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()
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)
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
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
))