1 # SPDX-FileCopyrightText: 2017 Alessandro Zomparelli
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 def anim_polyhedra_active(self
, context
):
42 props
= ob
.tissue_polyhedra
43 if ob
.tissue
.tissue_type
=='POLYHEDRA' and not ob
.tissue
.bool_lock
:
45 bpy
.ops
.object.tissue_update_polyhedra()
47 class tissue_polyhedra_prop(PropertyGroup
):
48 object : PointerProperty(
49 type=bpy
.types
.Object
,
51 description
="Source object",
52 update
= anim_polyhedra_active
57 ('POLYHEDRA', "Polyhedra", "Polyhedral Complex Decomposition, the result are disconnected polyhedra geometries"),
58 ('WIREFRAME', "Wireframe", "Polyhedral Wireframe through edges tickening")
61 name
="Polyhedra Mode",
62 update
= anim_polyhedra_active
65 bool_modifiers
: BoolProperty(
69 update
= anim_polyhedra_active
72 dissolve
: EnumProperty(
74 ('NONE', "None", "Keeps original topology"),
75 ('INNER', "Inner", "Dissolve inner loops"),
76 ('OUTER', "Outer", "Dissolve outer loops")
80 update
= anim_polyhedra_active
83 thickness
: FloatProperty(
84 name
="Thickness", default
=1, soft_min
=0, soft_max
=10,
85 description
="Thickness along the edges",
86 update
= anim_polyhedra_active
89 crease
: FloatProperty(
90 name
="Crease", default
=0, min=0, max=1,
91 description
="Crease Inner Loops",
92 update
= anim_polyhedra_active
95 segments
: IntProperty(
100 description
="Segments for every edge",
101 update
= anim_polyhedra_active
104 proportional_segments
: BoolProperty(
105 name
="Proportional Segments", default
=True,
106 description
="The number of segments is proportional to the length of the edges",
107 update
= anim_polyhedra_active
110 selective_wireframe
: EnumProperty(
113 ('NONE', "None", "Apply wireframe to every cell"),
114 ('THICKNESS', "Thickness", "Wireframe only on bigger cells compared to the thickness"),
115 ('AREA', "Area", "Wireframe based on cells dimensions"),
116 ('WEIGHT', "Weight", "Wireframe based on vertex groups")
119 update
= anim_polyhedra_active
122 thickness_threshold_correction
: FloatProperty(
123 name
="Correction", default
=1, min=0, soft_max
=2,
124 description
="Adjust threshold based on thickness",
125 update
= anim_polyhedra_active
128 area_threshold
: FloatProperty(
129 name
="Threshold", default
=0, min=0, soft_max
=10,
130 description
="Use only faces with an area greater than the threshold",
131 update
= anim_polyhedra_active
134 thicken_all
: BoolProperty(
136 description
="Thicken original faces as well",
138 update
= anim_polyhedra_active
141 vertex_group_thickness
: StringProperty(
142 name
="Thickness weight", default
='',
143 description
="Vertex Group used for thickness",
144 update
= anim_polyhedra_active
146 invert_vertex_group_thickness
: BoolProperty(
147 name
="Invert", default
=False,
148 description
="Invert the vertex group influence",
149 update
= anim_polyhedra_active
151 vertex_group_thickness_factor
: FloatProperty(
156 description
="Thickness factor to use for zero vertex group influence",
157 update
= anim_polyhedra_active
160 vertex_group_selective
: StringProperty(
161 name
="Thickness weight", default
='',
162 description
="Vertex Group used for selective wireframe",
163 update
= anim_polyhedra_active
165 invert_vertex_group_selective
: BoolProperty(
166 name
="Invert", default
=False,
167 description
="Invert the vertex group influence",
168 update
= anim_polyhedra_active
170 vertex_group_selective_threshold
: FloatProperty(
175 description
="Selective wireframe threshold",
176 update
= anim_polyhedra_active
178 bool_smooth
: BoolProperty(
179 name
="Smooth Shading",
181 description
="Output faces with smooth shading rather than flat shaded",
182 update
= anim_polyhedra_active
185 error_message
: StringProperty(
186 name
="Error Message",
190 class polyhedral_wireframe(Operator
):
191 bl_idname
= "object.polyhedral_wireframe"
192 bl_label
= "Tissue Polyhedral Wireframe"
193 bl_description
= "Generate wireframes around the faces.\
194 \nDoesn't works with boundary edges.\
196 bl_options
= {'REGISTER', 'UNDO'}
198 thickness
: FloatProperty(
199 name
="Thickness", default
=0.1, min=0.001, soft_max
=200,
200 description
="Wireframe thickness"
203 crease
: FloatProperty(
204 name
="Crease", default
=0, min=0, max=1,
205 description
="Crease Inner Loops"
208 segments
: IntProperty(
209 name
="Segments", default
=1, min=1, soft_max
=10,
210 description
="Segments for every edge"
213 proportional_segments
: BoolProperty(
214 name
="Proportional Segments", default
=True,
215 description
="The number of segments is proportional to the length of the edges"
220 ('POLYHEDRA', "Polyhedra", "Polyhedral Complex Decomposition, the result are disconnected polyhedra geometries"),
221 ('WIREFRAME', "Wireframe", "Polyhedral Wireframe through edges tickening")
224 name
="Polyhedra Mode"
227 dissolve
: EnumProperty(
229 ('NONE', "None", "Keeps original topology"),
230 ('INNER', "Inner", "Dissolve inner loops"),
231 ('OUTER', "Outer", "Dissolve outer loops")
237 selective_wireframe
: EnumProperty(
239 ('NONE', "None", "Apply wireframe to every cell"),
240 ('THICKNESS', "Thickness", "Wireframe only on bigger cells compared to the thickness"),
241 ('AREA', "Area", "Wireframe based on cells dimensions"),
242 ('WEIGHT', "Weight", "Wireframe based on vertex groups")
248 thickness_threshold_correction
: FloatProperty(
249 name
="Correction", default
=1, min=0, soft_max
=2,
250 description
="Adjust threshold based on thickness"
253 area_threshold
: FloatProperty(
254 name
="Threshold", default
=0, min=0, soft_max
=10,
255 description
="Use only faces with an area greater than the threshold"
258 thicken_all
: BoolProperty(
260 description
="Thicken original faces as well",
264 vertex_group_thickness
: StringProperty(
265 name
="Thickness weight", default
='',
266 description
="Vertex Group used for thickness"
269 invert_vertex_group_thickness
: BoolProperty(
270 name
="Invert", default
=False,
271 description
="Invert the vertex group influence"
274 vertex_group_thickness_factor
: FloatProperty(
279 description
="Thickness factor to use for zero vertex group influence"
282 vertex_group_selective
: StringProperty(
283 name
="Thickness weight", default
='',
284 description
="Vertex Group used for thickness"
287 invert_vertex_group_selective
: BoolProperty(
288 name
="Invert", default
=False,
289 description
="Invert the vertex group influence"
292 vertex_group_selective_threshold
: FloatProperty(
297 description
="Selective wireframe threshold"
300 bool_smooth
: BoolProperty(
301 name
="Smooth Shading",
303 description
="Output faces with smooth shading rather than flat shaded"
306 bool_hold
: BoolProperty(
308 description
="Wait...",
312 def draw(self
, context
):
315 col
= layout
.column(align
=True)
316 self
.bool_hold
= True
317 if self
.mode
== 'WIREFRAME':
319 col
.prop(self
, "thickness")
321 col
.prop(self
, "segments")
324 def invoke(self
, context
, event
):
325 return context
.window_manager
.invoke_props_dialog(self
)
327 def execute(self
, context
):
330 self
.object_name
= "Polyhedral Wireframe"
331 # Check if existing object with same name
332 names
= [o
.name
for o
in bpy
.data
.objects
]
333 if self
.object_name
in names
:
336 test_name
= self
.object_name
+ '.{:03d}'.format(count_name
)
337 if not (test_name
in names
):
338 self
.object_name
= test_name
342 if ob0
.type not in ('MESH'):
343 message
= "Source object must be a Mesh!"
344 self
.report({'ERROR'}, message
)
346 if bpy
.ops
.object.select_all
.poll():
347 bpy
.ops
.object.select_all(action
='TOGGLE')
348 bpy
.ops
.object.mode_set(mode
='OBJECT')
351 auto_layer_collection()
352 new_ob
= convert_object_to_mesh(ob0
,False,False)
353 new_ob
.data
.name
= self
.object_name
354 new_ob
.name
= self
.object_name
357 props
= new_ob
.tissue_polyhedra
358 lock_status
= new_ob
.tissue
.bool_lock
359 new_ob
.tissue
.bool_lock
= True
360 props
.mode
= self
.mode
361 props
.thickness
= self
.thickness
362 props
.segments
= self
.segments
363 props
.dissolve
= self
.dissolve
364 props
.proportional_segments
= self
.proportional_segments
365 props
.crease
= self
.crease
368 new_ob
.tissue
.tissue_type
= 'POLYHEDRA'
369 try: bpy
.ops
.object.tissue_update_polyhedra()
370 except RuntimeError as e
:
371 bpy
.data
.objects
.remove(new_ob
)
372 remove_temp_objects()
373 self
.report({'ERROR'}, str(e
))
376 self
.object_name
= new_ob
.name
377 new_ob
.location
= ob0
.location
378 new_ob
.matrix_world
= ob0
.matrix_world
380 # Assign collection of the base object
381 old_coll
= new_ob
.users_collection
382 if old_coll
!= ob0
.users_collection
:
384 c
.objects
.unlink(new_ob
)
385 for c
in ob0
.users_collection
:
386 c
.objects
.link(new_ob
)
387 context
.view_layer
.objects
.active
= new_ob
390 new_ob
.tissue
.bool_lock
= lock_status
394 class tissue_update_polyhedra(Operator
):
395 bl_idname
= "object.tissue_update_polyhedra"
396 bl_label
= "Tissue Update Polyhedral Wireframe"
397 bl_description
= "Update a previously generated polyhedral object"
398 bl_options
= {'REGISTER', 'UNDO'}
400 def execute(self
, context
):
402 tissue_time(None,'Tissue: Polyhedral Wireframe of "{}"...'.format(ob
.name
), levels
=0)
403 start_time
= time
.time()
404 begin_time
= time
.time()
405 props
= ob
.tissue_polyhedra
406 thickness
= props
.thickness
408 merge_dist
= thickness
*0.0001
410 subs
= props
.segments
411 if props
.mode
== 'POLYHEDRA': subs
= 1
416 if props
.bool_modifiers
:
417 me
= simple_to_mesh(ob0
)
425 polyhedral_subdivide_edges(bm
, subs
, props
.proportional_segments
)
426 tissue_time(start_time
,'Subdivide edges',levels
=1)
427 start_time
= time
.time()
429 thickness
= np
.ones(len(bm
.verts
))*props
.thickness
430 if(props
.vertex_group_thickness
in ob
.vertex_groups
.keys()):
431 dvert_lay
= bm
.verts
.layers
.deform
.active
432 group_index_thickness
= ob
.vertex_groups
[props
.vertex_group_thickness
].index
433 thickness_weight
= bmesh_get_weight_numpy(group_index_thickness
, dvert_lay
, bm
.verts
)
434 if 'invert_vertex_group_thickness' in props
.keys():
435 if props
['invert_vertex_group_thickness']:
436 thickness_weight
= 1-thickness_weight
438 if 'vertex_group_thickness_factor' in props
.keys():
439 fact
= props
['vertex_group_thickness_factor']
441 thickness_weight
= thickness_weight
*(1-fact
) + fact
442 thickness
*= thickness_weight
443 thickness_dict
= dict(zip([tuple(v
.co
) for v
in bm
.verts
],thickness
))
445 bm1
= get_double_faces_bmesh(bm
)
446 polyhedra
= get_decomposed_polyhedra(bm
)
447 if(type(polyhedra
) is str):
450 self
.report({'ERROR'}, polyhedra
)
453 selective_dict
= None
455 if props
.selective_wireframe
== 'THICKNESS':
458 area_threshold
= (thickness
*props
.thickness_threshold_correction
)**2
459 elif props
.selective_wireframe
== 'AREA':
461 area_threshold
= props
.area_threshold
462 elif props
.selective_wireframe
== 'WEIGHT':
464 if(props
.vertex_group_selective
in ob
.vertex_groups
.keys()):
465 dvert_lay
= bm
.verts
.layers
.deform
.active
466 group_index_selective
= ob
.vertex_groups
[props
.vertex_group_selective
].index
467 thresh
= props
.vertex_group_selective_threshold
468 selective_weight
= bmesh_get_weight_numpy(group_index_selective
, dvert_lay
, bm
.verts
)
469 selective_weight
= selective_weight
>= thresh
471 if 'invert_vertex_group_selective' in props
.keys():
472 if props
['invert_vertex_group_selective']:
475 selective_weight
= selective_weight
<= thresh
477 selective_weight
= selective_weight
>= thresh
478 selective_dict
= dict(zip([tuple(v
.co
) for v
in bm
.verts
],selective_weight
))
486 end_time
= time
.time()
487 tissue_time(start_time
,'Found {} polyhedra'.format(len(polyhedra
)),levels
=1)
488 start_time
= time
.time()
490 bm1
.faces
.ensure_lookup_table()
491 bm1
.faces
.index_update()
493 #unique_verts_dict = dict(zip([tuple(v.co) for v in bm1.verts],bm1.verts))
494 bm1
, all_faces_dict
, polyhedra_faces_id
, polyhedra_faces_id_neg
= combine_polyhedra_faces(bm1
, polyhedra
)
496 if props
.mode
== 'POLYHEDRA':
502 mesh_name
= old_me
.name
503 bpy
.data
.meshes
.remove(old_me
)
504 bpy
.data
.meshes
.remove(me
)
505 ob
.data
.name
= mesh_name
506 end_time
= time
.time()
507 print('Tissue: Polyhedral wireframe in {:.4f} sec'.format(end_time
-start_time
))
510 delete_faces
= set({})
512 not_wireframe_faces
= []
515 outer_faces
= get_outer_faces(bm1
)
516 for faces_id
in polyhedra_faces_id
:
517 delete_faces_poly
= []
518 wireframe_faces_poly
= []
520 if id in delete_faces
: continue
525 f
= all_faces_dict
[id]
528 if selective_dict
[tuple(v
.co
)]:
532 cen
= f
.calc_center_median()
536 mid
= (v0
.co
+ v1
.co
)/2
539 ang
= Vector
.angle(vec1
,vec2
)
541 length
= sin(ang
)*length
542 thick0
= thickness_dict
[tuple(v0
.co
)]
543 thick1
= thickness_dict
[tuple(v1
.co
)]
544 thick
= (thick0
+ thick1
)/4
545 if length
< thick
*props
.thickness_threshold_correction
:
549 delete
= f
.calc_area() < area_threshold
551 if props
.thicken_all
:
552 delete_faces_poly
.append(id)
554 wireframe_faces_poly
.append(id)
555 if len(wireframe_faces_poly
) <= 2:
556 delete_faces
.update(set([id for id in faces_id
]))
557 not_wireframe_faces
+= [polyhedra_faces_id_neg
[id] for id in faces_id
]
559 wireframe_faces
+= wireframe_faces_poly
560 #flat_faces += delete_faces_poly
561 wireframe_faces_id
= [i
for i
in wireframe_faces
if i
not in not_wireframe_faces
]
562 wireframe_faces
= [all_faces_dict
[i
] for i
in wireframe_faces_id
]
563 #flat_faces = [all_faces_dict[i] for i in flat_faces]
564 delete_faces
= [all_faces_dict
[i
] for i
in delete_faces
if all_faces_dict
[i
] not in outer_faces
]
566 tissue_time(start_time
,'Merge and delete',levels
=1)
567 start_time
= time
.time()
569 ############# FRAME #############
570 new_faces
, outer_wireframe_faces
= create_frame_faces(
574 polyhedra_faces_id_neg
,
578 faces_to_delete
= wireframe_faces
+delete_faces
579 outer_wireframe_faces
+= [f
for f
in outer_faces
if not f
in faces_to_delete
]
580 bmesh
.ops
.delete(bm1
, geom
=faces_to_delete
, context
='FACES')
582 bm1
.verts
.ensure_lookup_table()
583 bm1
.edges
.ensure_lookup_table()
584 bm1
.faces
.ensure_lookup_table()
585 bm1
.verts
.index_update()
587 wireframe_indexes
= [f
.index
for f
in new_faces
]
588 outer_indexes
= [f
.index
for f
in outer_wireframe_faces
]
589 edges_to_crease
= [f
.edges
[2].index
for f
in new_faces
]
590 layer_is_wireframe
= bm1
.faces
.layers
.int.new('tissue_is_wireframe')
591 for id in wireframe_indexes
:
592 bm1
.faces
[id][layer_is_wireframe
] = 1
593 layer_is_outer
= bm1
.faces
.layers
.int.new('tissue_is_outer')
594 for id in outer_indexes
:
595 bm1
.faces
[id][layer_is_outer
] = 1
596 if props
.crease
> 0 and props
.dissolve
!= 'INNER':
597 crease_layer
= bm1
.edges
.layers
.float.new('crease_edge')
598 bm1
.edges
.index_update()
600 for edge_index
in edges_to_crease
:
601 bm1
.edges
[edge_index
][crease_layer
] = props
.crease
603 tissue_time(start_time
,'Generate frames',levels
=1)
604 start_time
= time
.time()
606 ### Displace vertices ###
607 corners
= [[] for i
in range(len(bm1
.verts
))]
608 normals
= [0]*len(bm1
.verts
)
609 vertices
= [0]*len(bm1
.verts
)
610 # Define vectors direction
615 corners
[id].append((v1
.co
- v0
.co
).normalized())
617 normals
[id] = v0
.normal
.copy()
620 for i
, vecs
in enumerate(corners
):
626 if nor
== Vector((0,0,0)): continue
627 ang
+= nor
.angle(vec
)
631 v
.co
+= nor
*thickness_dict
[tuple(v
.co
)]/div
633 tissue_time(start_time
,'Corners displace',levels
=1)
634 start_time
= time
.time()
636 if props
.dissolve
!= 'NONE':
637 if props
.dissolve
== 'INNER': dissolve_id
= 2
638 if props
.dissolve
== 'OUTER': dissolve_id
= 0
639 bm1
.edges
.index_update()
642 e
= f
.edges
[dissolve_id
]
643 if e
not in dissolve_edges
:
644 dissolve_edges
.append(e
)
645 bmesh
.ops
.dissolve_edges(bm1
, edges
=dissolve_edges
, use_verts
=True, use_face_split
=False)
647 for v
in bm1
.verts
: v
.select_set(False)
648 for f
in bm1
.faces
: f
.select_set(False)
650 dissolve_verts
= [v
for v
in bm1
.verts
if len(v
.link_edges
) < 3]
651 bmesh
.ops
.dissolve_verts(bm1
, verts
=dissolve_verts
, use_face_split
=False, use_boundary_tear
=False)
655 if props
.bool_smooth
: me
.shade_smooth()
659 mesh_name
= old_me
.name
660 bpy
.data
.meshes
.remove(old_me
)
661 ob
.data
.name
= mesh_name
664 bpy
.ops
.object.mode_set(mode
='EDIT')
665 bpy
.ops
.mesh
.select_all(action
='SELECT')
667 bpy
.ops
.object.mode_set(mode
='OBJECT')
669 tissue_time(start_time
,'Clean mesh',levels
=1)
670 start_time
= time
.time()
672 tissue_time(begin_time
,'Polyhedral Wireframe',levels
=0)
675 def pre_processing(bm
):
676 delete
= [e
for e
in bm
.edges
if len(e
.link_faces
) < 2]
677 while len(delete
) > 0:
678 bmesh
.ops
.delete(bm
, geom
=delete
, context
='EDGES')
679 bm
.faces
.ensure_lookup_table()
680 bm
.edges
.ensure_lookup_table()
681 bm
.verts
.ensure_lookup_table()
682 delete
= [e
for e
in bm
.edges
if len(e
.link_faces
) < 2]
685 def get_outer_faces(bm
):
687 bmesh
.ops
.recalc_face_normals(bm_copy
, faces
=bm_copy
.faces
)
689 for f1
, f2
in zip(bm
.faces
, bm_copy
.faces
):
691 if f1
.normal
== f2
.normal
:
695 def create_frame_faces(
699 polyhedra_faces_id_neg
,
704 for f
in wireframe_faces
:
706 all_loops
= [[loop
for loop
in f
.loops
] for f
in wireframe_faces
]
707 is_outer
= [f
in outer_faces
for f
in wireframe_faces
]
708 outer_wireframe_faces
= []
709 frames_verts_dict
= {}
710 for loops_index
, loops
in enumerate(all_loops
):
712 frame_id
= wireframe_faces_id
[loops_index
]
713 single_face_id
= min(frame_id
,polyhedra_faces_id_neg
[frame_id
])
715 loops_keys
= [tuple(loop
.vert
.co
) + tuple((single_face_id
,)) for loop
in loops
]
716 if loops_keys
[0] in frames_verts_dict
:
717 verts_inner
= [frames_verts_dict
[key
] for key
in loops_keys
]
720 nor
= wireframe_faces
[loops_index
].normal
722 tan
= loop
.calc_tangent() #nor.cross(loop.calc_tangent().cross(nor)).normalized()
723 thickness
= thickness_dict
[tuple(loop
.vert
.co
)]
724 tangents
.append(tan
/sin(loop
.calc_angle()/2)*thickness
)
725 for i
in range(n_loop
):
727 new_co
= loop
.vert
.co
+ tangents
[i
]
728 new_vert
= bm
.verts
.new(new_co
)
729 frames_verts_dict
[loops_keys
[i
]] = new_vert
730 verts_inner
.append(new_vert
)
733 verts_inner
+= [verts_inner
[0]]
734 for i
in range(n_loop
):
737 v2
= verts_inner
[i
+1]
739 face_verts
= [v0
,v1
,v2
,v3
]
740 new_face
= bm
.faces
.new(face_verts
)
741 new_face
.select
= True
742 new_faces
.append(new_face
)
743 if is_outer
[loops_index
]:
744 outer_wireframe_faces
.append(new_face
)
745 new_face
.normal_update()
746 return new_faces
, outer_wireframe_faces
748 def polyhedral_subdivide_edges(bm
, subs
, proportional_segments
):
750 if proportional_segments
:
751 wire_length
= [e
.calc_length() for e
in bm
.edges
]
752 all_edges
= list(bm
.edges
)
753 max_segment
= max(wire_length
)/subs
+0.00001 # prevent out_of_bounds
754 split_edges
= [[] for i
in range(subs
)]
755 for e
, l
in zip(all_edges
, wire_length
):
756 split_edges
[int(l
//max_segment
)].append(e
)
757 for i
in range(1,subs
):
758 bmesh
.ops
.bisect_edges(bm
, edges
=split_edges
[i
], cuts
=i
)
760 bmesh
.ops
.bisect_edges(bm
, edges
=bm
.edges
, cuts
=subs
-1)
762 def get_double_faces_bmesh(bm
):
765 verts0
= [v
.co
for v
in f
.verts
]
766 verts1
= verts0
.copy()
768 double_faces
.append(verts0
)
769 double_faces
.append(verts1
)
771 for verts_co
in double_faces
:
772 bm1
.faces
.new([bm1
.verts
.new(v
) for v
in verts_co
])
773 bm1
.verts
.ensure_lookup_table()
774 bm1
.edges
.ensure_lookup_table()
775 bm1
.faces
.ensure_lookup_table()
778 def get_decomposed_polyhedra(bm
):
779 polyhedra_from_facekey
= {}
785 link_faces
= e
.link_faces
786 n_radial_faces
= len(link_faces
)
787 if n_radial_faces
< 2:
788 return "Naked edges are not allowed"
791 edge_vec
= vert1
.co
- vert0
.co
793 for id1
in range(n_radial_faces
-1):
795 facekey1
= f1
.index
+1
796 verts1
= [v
.index
for v
in f1
.verts
]
797 v0_index
= verts1
.index(vert0
.index
)
798 v1_index
= verts1
.index(vert1
.index
)
800 ref_loop_dir
= v0_index
== (v1_index
+1)%len(verts1
)
801 edge_vec1
= edge_vec
if ref_loop_dir
else -edge_vec
802 tan1
= f1
.normal
.cross(edge_vec1
)
804 # faces to compare with
805 faceskeys2
, normals2
= get_second_faces(
813 tangents2
= [nor
.cross(-edge_vec1
) for nor
in normals2
]
816 facekey2_pos
= get_closest_face(
823 polyhedra_from_facekey
, count
, to_merge
= store_neighbor_faces(
826 polyhedra_from_facekey
,
831 facekey2_neg
= get_closest_face(
838 polyhedra_from_facekey
, count
, to_merge
= store_neighbor_faces(
841 polyhedra_from_facekey
,
846 polyhedra
= [ [] for i
in range(count
)]
847 unique_index
= get_unique_polyhedra_index(count
, to_merge
)
848 for key
, val
in polyhedra_from_facekey
.items():
849 polyhedra
[unique_index
[val
]].append(key
)
850 polyhedra
= list(set(tuple(i
) for i
in polyhedra
if i
))
851 polyhedra
= remove_double_faces_from_polyhedra(polyhedra
)
854 def remove_double_faces_from_polyhedra(polyhedra
):
856 for polyhedron
in polyhedra
:
857 new_polyhedron
= [key
for key
in polyhedron
if not -key
in polyhedron
]
858 new_polyhedra
.append(new_polyhedron
)
861 def get_unique_polyhedra_index(count
, to_merge
):
862 out
= list(range(count
))
866 for pair
in to_merge
:
867 if out
[pair
[1]] != out
[pair
[0]]:
868 out
[pair
[0]] = out
[pair
[1]] = min(out
[pair
[0]], out
[pair
[1]])
872 def get_closest_face(faces
, tangents
, ref_vector
, axis
, is_positive
):
875 for fk
, tangent
in zip(faces
, tangents
):
876 rot_axis
= -axis
if is_positive
else axis
877 angle
= round_angle_with_axis(ref_vector
, tangent
, rot_axis
)
878 if angle
< min_angle
:
881 return facekey
if is_positive
else -facekey
883 def get_second_faces(face_list
, edge_v0
, edge_v1
, reference_loop_dir
, self
):
884 nFaces
= len(face_list
)-1
885 facekeys
= [None]*nFaces
886 normals
= [None]*nFaces
888 for face
in face_list
:
889 if(face
== self
): continue
890 verts
= [v
.index
for v
in face
.verts
]
891 v0_index
= verts
.index(edge_v0
)
892 v1_index
= verts
.index(edge_v1
)
893 loop_dir
= v0_index
== (v1_index
+1)%len(verts
)
894 if reference_loop_dir
!= loop_dir
:
895 facekeys
[count
] = face
.index
+1
896 normals
[count
] = face
.normal
898 facekeys
[count
] = -(face
.index
+1)
899 normals
[count
] = -face
.normal
901 return facekeys
, normals
903 def store_neighbor_faces(
910 poly1
= polyhedra
.get(key1
)
911 poly2
= polyhedra
.get(key2
)
914 to_merge
.append((poly1
, poly2
))
916 polyhedra
[key2
] = poly1
918 polyhedra
[key1
] = poly2
920 polyhedra
[key1
] = polyhedra
[key2
] = polyhedra_count
922 return polyhedra
, polyhedra_count
, to_merge
924 def add_polyhedron(bm
,source_faces
):
925 faces_verts_key
= [[tuple(v
.co
) for v
in f
.verts
] for f
in source_faces
]
926 polyhedron_verts_key
= [key
for face_key
in faces_verts_key
for key
in face_key
]
927 polyhedron_verts
= [bm
.verts
.new(co
) for co
in polyhedron_verts_key
]
928 polyhedron_verts_dict
= dict(zip(polyhedron_verts_key
, polyhedron_verts
))
929 new_faces
= [None]*len(faces_verts_key
)
931 for verts_keys
in faces_verts_key
:
932 new_faces
[count
] = bm
.faces
.new([polyhedron_verts_dict
.get(key
) for key
in verts_keys
])
935 bm
.faces
.ensure_lookup_table()
936 bm
.faces
.index_update()
939 def combine_polyhedra_faces(bm
,polyhedra
):
941 polyhedra_faces_id
= [None]*len(polyhedra
)
943 #polyhedra_faces_pos = {}
944 polyhedra_faces_id_neg
= {}
945 vertices_key
= [tuple(v
.co
) for v
in bm
.verts
]
948 faces_id
= [(f
-1)*2 if f
> 0 else (-f
-1)*2+1 for f
in p
]
949 faces_id_neg
= [(-f
-1)*2 if f
< 0 else (f
-1)*2+1 for f
in p
]
950 new_faces
= add_polyhedron(new_bm
,[bm
.faces
[f_id
] for f_id
in faces_id
])
952 for i
in range(len(new_faces
)):
955 id_neg
= faces_id_neg
[i
]
956 polyhedra_faces_id_neg
[id] = id_neg
957 all_faces_dict
[id] = face
958 polyhedra_faces_id
[count
] = faces_id
960 return new_bm
, all_faces_dict
, polyhedra_faces_id
, polyhedra_faces_id_neg
962 class TISSUE_PT_polyhedra_object(Panel
):
963 bl_space_type
= 'PROPERTIES'
964 bl_region_type
= 'WINDOW'
966 bl_label
= "Tissue Polyhedra"
967 bl_options
= {'DEFAULT_CLOSED'}
970 def poll(cls
, context
):
973 return ob
.type == 'MESH' and ob
.tissue
.tissue_type
== 'POLYHEDRA'
976 def draw(self
, context
):
978 props
= ob
.tissue_polyhedra
979 tissue_props
= ob
.tissue
981 bool_polyhedra
= tissue_props
.tissue_type
== 'POLYHEDRA'
983 if not bool_polyhedra
:
984 layout
.label(text
="The selected object is not a Polyhedral object",
987 if props
.error_message
!= "":
988 layout
.label(text
=props
.error_message
,
990 col
= layout
.column(align
=True)
991 row
= col
.row(align
=True)
993 #set_tessellate_handler(self,context)
994 row
.operator("object.tissue_update_tessellate_deps", icon
='FILE_REFRESH', text
='Refresh') ####
995 lock_icon
= 'LOCKED' if tissue_props
.bool_lock
else 'UNLOCKED'
996 #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
997 deps_icon
= 'LINKED' if tissue_props
.bool_dependencies
else 'UNLINKED'
998 row
.prop(tissue_props
, "bool_dependencies", text
="", icon
=deps_icon
)
999 row
.prop(tissue_props
, "bool_lock", text
="", icon
=lock_icon
)
1000 col2
= row
.column(align
=True)
1001 col2
.prop(tissue_props
, "bool_run", text
="",icon
='TIME')
1002 col2
.enabled
= not tissue_props
.bool_lock
1003 col2
= row
.column(align
=True)
1004 col2
.operator("mesh.tissue_remove", text
="", icon
='X')
1005 #layout.use_property_split = True
1006 #layout.use_property_decorate = False # No animation.
1007 col
= layout
.column(align
=True)
1008 col
.label(text
='Polyhedral Mode:')
1009 col
.prop(props
, 'mode', text
='')
1011 col
.label(text
='Source object:')
1012 row
= col
.row(align
=True)
1013 row
.prop_search(props
, "object", context
.scene
, "objects", text
='')
1014 col2
= row
.column(align
=True)
1015 col2
.prop(props
, "bool_modifiers", text
='Use Modifiers',icon
='MODIFIER')
1016 if props
.mode
== 'WIREFRAME':
1018 col
.prop(props
, 'thickness')
1019 row
= col
.row(align
=True)
1021 row
.prop_search(props
, 'vertex_group_thickness',
1022 ob0
, "vertex_groups", text
='')
1023 col2
= row
.column(align
=True)
1024 row2
= col2
.row(align
=True)
1025 row2
.prop(props
, "invert_vertex_group_thickness", text
="",
1026 toggle
=True, icon
='ARROW_LEFTRIGHT')
1027 row2
.prop(props
, "vertex_group_thickness_factor")
1028 row2
.enabled
= props
.vertex_group_thickness
in ob0
.vertex_groups
.keys()
1029 col
.prop(props
, 'bool_smooth')
1031 col
.label(text
='Selective Wireframe:')
1032 col
.prop(props
, 'selective_wireframe', text
='Mode')
1034 if props
.selective_wireframe
== 'THICKNESS':
1035 col
.prop(props
, 'thickness_threshold_correction')
1036 elif props
.selective_wireframe
== 'AREA':
1037 col
.prop(props
, 'area_threshold')
1038 elif props
.selective_wireframe
== 'WEIGHT':
1039 row
= col
.row(align
=True)
1040 row
.prop_search(props
, 'vertex_group_selective',
1041 ob0
, "vertex_groups", text
='')
1042 col2
= row
.column(align
=True)
1043 row2
= col2
.row(align
=True)
1044 row2
.prop(props
, "invert_vertex_group_selective", text
="",
1045 toggle
=True, icon
='ARROW_LEFTRIGHT')
1046 row2
.prop(props
, "vertex_group_selective_threshold")
1047 row2
.enabled
= props
.vertex_group_selective
in ob0
.vertex_groups
.keys()
1048 #if props.selective_wireframe != 'NONE':
1049 # col.prop(props, 'thicken_all')
1051 col
.label(text
='Subdivide edges:')
1053 row
.prop(props
, 'segments')
1054 row
.prop(props
, 'proportional_segments', text
='Proportional')
1056 col
.label(text
='Loops:')
1057 col
.prop(props
, 'dissolve')
1059 col
.prop(props
, 'crease')