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
37 from .weight_tools
import *
38 from .numba_functions
import *
39 from .tissue_properties
import *
41 from pathlib
import Path
45 def allowed_objects():
46 return ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
48 def remove_temp_objects():
50 for o
in bpy
.data
.objects
:
51 if "_tissue_tmp" in o
.name
:
52 bpy
.data
.objects
.remove(o
)
56 tess_props
= ob
.tissue_tessellate
57 if tess_props
.generator
not in list(bpy
.data
.objects
):
59 elif tess_props
.component_mode
== 'OBJECT':
60 return tess_props
.component
in list(bpy
.data
.objects
)
61 elif tess_props
.component_mode
== 'COLLECTION':
62 if tess_props
.component_coll
in list(bpy
.data
.collections
):
63 for o
in list(tess_props
.component_coll
.objects
):
64 if o
.type in allowed_objects():
67 for mat
in tess_props
.generator
.material_slots
.keys():
68 if mat
in bpy
.data
.objects
.keys():
69 if bpy
.data
.objects
[mat
].type in allowed_objects():
73 def tessellate_patch(props
):
77 _ob0
= props
['generator']
78 components
= props
['component']
79 offset
= props
['offset']
80 zscale
= props
['zscale']
81 gen_modifiers
= props
['gen_modifiers']
82 com_modifiers
= props
['com_modifiers']
84 fill_mode
= props
['fill_mode']
85 scale_mode
= props
['scale_mode']
86 rotation_mode
= props
['rotation_mode']
87 rotation_shift
= props
['rotation_shift']
88 rand_seed
= props
['rand_seed']
89 rand_step
= props
['rand_step']
90 bool_vertex_group
= props
['bool_vertex_group']
91 bool_selection
= props
['bool_selection']
92 bool_shapekeys
= props
['bool_shapekeys']
93 bool_material_id
= props
['bool_material_id']
94 material_id
= props
['material_id']
95 normals_mode
= props
['normals_mode']
96 bounds_x
= props
['bounds_x']
97 bounds_y
= props
['bounds_y']
98 use_origin_offset
= props
['use_origin_offset']
99 vertex_group_thickness
= props
['vertex_group_thickness']
100 invert_vertex_group_thickness
= props
['invert_vertex_group_thickness']
101 vertex_group_thickness_factor
= props
['vertex_group_thickness_factor']
102 vertex_group_distribution
= props
['vertex_group_distribution']
103 invert_vertex_group_distribution
= props
['invert_vertex_group_distribution']
104 vertex_group_distribution_factor
= props
['vertex_group_distribution_factor']
105 vertex_group_cap_owner
= props
['vertex_group_cap_owner']
106 vertex_group_cap
= props
['vertex_group_cap']
107 invert_vertex_group_cap
= props
['invert_vertex_group_cap']
108 vertex_group_bridge_owner
= props
['vertex_group_bridge_owner']
109 vertex_group_bridge
= props
['vertex_group_bridge']
110 invert_vertex_group_bridge
= props
['invert_vertex_group_bridge']
111 vertex_group_rotation
= props
['vertex_group_rotation']
112 invert_vertex_group_rotation
= props
['invert_vertex_group_rotation']
113 rotation_direction
= props
['rotation_direction']
114 target
= props
['target']
115 even_thickness
= props
['even_thickness']
116 even_thickness_iter
= props
['even_thickness_iter']
117 smooth_normals
= props
['smooth_normals']
118 smooth_normals_iter
= props
['smooth_normals_iter']
119 smooth_normals_uv
= props
['smooth_normals_uv']
120 vertex_group_smooth_normals
= props
['vertex_group_smooth_normals']
121 invert_vertex_group_smooth_normals
= props
['invert_vertex_group_smooth_normals']
122 #bool_multi_components = props['bool_multi_components']
123 component_mode
= props
['component_mode']
124 coll_rand_seed
= props
['coll_rand_seed']
125 consistent_wedges
= props
['consistent_wedges']
126 vertex_group_scale_normals
= props
['vertex_group_scale_normals']
127 invert_vertex_group_scale_normals
= props
['invert_vertex_group_scale_normals']
128 boundary_mat_offset
= props
['boundary_mat_offset']
130 _props
= props
.copy()
133 ob
.tissue_tessellate
.warning_message_thickness
= ''
135 if normals_mode
== 'SHAPEKEYS':
136 if _ob0
.data
.shape_keys
!= None:
139 normals_mode
= 'VERTS'
140 message
= "Base mesh doesn't have Shape Keys"
141 ob
.tissue_tessellate
.warning_message_thickness
= message
142 print("Tissue: " + message
)
143 if normals_mode
== 'OBJECT' and target
== None:
144 normals_mode
= 'VERTS'
145 message
= "Please select a target object"
146 ob
.tissue_tessellate
.warning_message_thickness
= message
147 print("Tissue: " + message
)
149 random
.seed(rand_seed
)
150 if len(_ob0
.modifiers
) == 0: gen_modifiers
= False
152 # Target mesh used for normals
153 if normals_mode
in ('SHAPEKEYS', 'OBJECT'):
154 if fill_mode
== 'PATCH':
155 ob0_sk
= convert_object_to_mesh(target
, True, True)
157 use_modifiers
= gen_modifiers
158 if normals_mode
== 'SHAPEKEYS' and not gen_modifiers
:
160 for m
in target
.modifiers
:
161 m
.show_viewport
= False
163 _props
['use_modifiers'] = use_modifiers
164 if fill_mode
== 'FAN': ob0_sk
= convert_to_fan(target
, _props
, add_id_layer
=id_layer
)
165 elif fill_mode
== 'FRAME': ob0_sk
= convert_to_frame(target
, _props
)
166 elif fill_mode
== 'TRI': ob0_sk
= convert_to_triangles(target
, _props
)
167 elif fill_mode
== 'QUAD': ob0_sk
= reduce_to_quads(target
, _props
)
169 normals_target
= get_vertices_numpy(me0_sk
)
170 bpy
.data
.objects
.remove(ob0_sk
)
171 if normals_mode
== 'SHAPEKEYS':
172 key_values0
= [sk
.value
for sk
in _ob0
.data
.shape_keys
.key_blocks
]
173 for sk
in _ob0
.data
.shape_keys
.key_blocks
: sk
.value
= 0
175 if fill_mode
== 'PATCH':
176 ob0
= convert_object_to_mesh(_ob0
)
178 if boundary_mat_offset
!= 0:
180 bm
.from_mesh(ob0
.data
)
181 bm
= offset_boundary_materials(
183 boundary_mat_offset
= _props
['boundary_mat_offset'],
184 boundary_variable_offset
= _props
['boundary_variable_offset'],
185 auto_rotate_boundary
= _props
['auto_rotate_boundary'])
191 if fill_mode
== 'FAN':
192 id_layer
= component_mode
== 'COLLECTION' and consistent_wedges
193 ob0
= convert_to_fan(_ob0
, _props
, add_id_layer
=id_layer
)
194 elif fill_mode
== 'FRAME': ob0
= convert_to_frame(_ob0
, _props
)
195 elif fill_mode
== 'TRI': ob0
= convert_to_triangles(_ob0
, _props
)
196 elif fill_mode
== 'QUAD': ob0
= reduce_to_quads(_ob0
, _props
)
197 ob0
.name
= "_tissue_tmp_ob0"
199 n_verts0
= len(me0
.vertices
)
201 # read vertices coordinates
202 verts0_co
= get_vertices_numpy(me0
)
205 if normals_mode
in ('SHAPEKEYS','OBJECT'):
206 if len(normals_target
) != len(me0
.vertices
):
207 normals_mode
= 'VERTS'
208 message
= "Base mesh and Target mesh don't match"
209 ob
.tissue_tessellate
.warning_message_thickness
= message
210 print("Tissue: " + message
)
212 if normals_mode
== 'SHAPEKEYS':
213 for sk
, val
in zip(_ob0
.data
.shape_keys
.key_blocks
, key_values0
): sk
.value
= val
214 verts0_normal
= normals_target
- verts0_co
216 While in Relative thickness method the components are built
217 between the two surfaces, in Constant mode the thickness is uniform.
219 if scale_mode
== 'CONSTANT':
221 verts0_normal
/= np
.linalg
.norm(verts0_normal
, axis
=1).reshape((-1,1))
222 if not even_thickness
:
224 #original_normals = get_normals_numpy(me0)
225 #verts0_normal /= np.multiply(verts0_normal, original_normals).sum(1)[:,None]
227 # Evaluate maximum components thickness
228 first_component
= True
229 for com
in components
:
231 com
= convert_object_to_mesh(com
, com_modifiers
, False)
232 com
, com_area
= tessellate_prepare_component(com
, props
)
233 com_verts
= get_vertices_numpy(com
.data
)
234 bpy
.data
.objects
.remove(com
)
236 all_com_verts
= com_verts
237 first_component
= False
239 all_com_verts
= np
.concatenate((all_com_verts
, com_verts
), axis
=0)
240 pos_step_dist
= abs(np
.max(all_com_verts
[:,2]))
241 neg_step_dist
= abs(np
.min(all_com_verts
[:,2]))
243 # Rescale normalized vectors according to the angle with the normals
244 original_normals
= get_normals_numpy(me0
)
245 kd
= mathutils
.kdtree
.KDTree(len(verts0_co
))
246 for i
, v
in enumerate(verts0_co
):
249 step_dist
= [neg_step_dist
, pos_step_dist
]
252 for sgn
, stp
in zip(sign
, step_dist
):
254 if sgn
== 1: verts0_normal_pos
= verts0_normal
255 if sgn
== -1: verts0_normal_neg
= verts0_normal
257 for i
in range(even_thickness_iter
):
258 test_dist
= stp
* mult
259 test_pts
= verts0_co
+ verts0_normal
* test_dist
* sgn
260 # Find the closest point to the sample point
265 for find
in test_pts
:
266 co
, index
, dist
= kd
.find(find
)
267 closest_co
.append(co
) # co, index, dist
268 closest_index
.append(index
) # co, index, dist
269 closest_co
= np
.array(closest_co
)#[:,3,None]
270 closest_index
= np
.array(closest_index
)
271 closest_nor
= original_normals
[closest_index
]
272 closest_vec
= test_pts
- closest_co
273 projected_vectors
= np
.multiply(closest_vec
, closest_nor
).sum(1)[:,None]
274 closest_dist
= np
.linalg
.norm(projected_vectors
, axis
=1)[:,None]
275 mult
= mult
*0.2 + test_dist
/closest_dist
*0.8 # Reduces bouncing effect
276 if sgn
== 1: verts0_normal_pos
= verts0_normal
* mult
277 if sgn
== -1: verts0_normal_neg
= verts0_normal
* mult
279 if normals_mode
in ('VERTS','FACES'):
280 verts0_normal
= get_normals_numpy(me0
)
283 not_allowed
= ['FLUID_SIMULATION', 'ARRAY', 'BEVEL', 'BOOLEAN', 'BUILD',
284 'DECIMATE', 'EDGE_SPLIT', 'MASK', 'MIRROR', 'REMESH',
285 'SCREW', 'SOLIDIFY', 'TRIANGULATE', 'WIREFRAME', 'SKIN',
286 'EXPLODE', 'PARTICLE_INSTANCE', 'PARTICLE_SYSTEM', 'SMOKE']
287 modifiers0
= list(_ob0
.modifiers
)
288 if len(modifiers0
) == 0 or fill_mode
!= 'PATCH':
290 if fill_mode
== 'PATCH':
293 show_modifiers
= [m
.show_viewport
for m
in _ob0
.modifiers
]
294 show_modifiers
.reverse()
297 visible
= m
.show_viewport
298 if not visible
: continue
299 #m.show_viewport = False
300 if m
.type in ('SUBSURF', 'MULTIRES') and visible
:
303 elif m
.type in not_allowed
:
304 bpy
.data
.meshes
.remove(ob0
.data
)
305 #bpy.data.meshes.remove(me0)
306 return "modifiers_error"
309 before
.name
= _ob0
.name
+ "_before_subs"
310 bpy
.context
.collection
.objects
.link(before
)
311 #if ob0.type == 'MESH': before.data = me0
312 before_mod
= list(before
.modifiers
)
315 if m
.type in ('SUBSURF', 'MULTIRES') and m
.show_viewport
:
316 before
.modifiers
.remove(m
)
318 else: before
.modifiers
.remove(m
)
320 before_subsurf
= simple_to_mesh(before
)
322 if boundary_mat_offset
!= 0:
324 bm
.from_mesh(before_subsurf
)
325 bm
= offset_boundary_materials(
327 boundary_mat_offset
= _props
['boundary_mat_offset'],
328 boundary_variable_offset
= _props
['boundary_variable_offset'],
329 auto_rotate_boundary
= _props
['auto_rotate_boundary'])
330 bm
.to_mesh(before_subsurf
)
332 before_subsurf
.update()
334 bpy
.data
.objects
.remove(before
)
336 tt
= tissue_time(tt
, "Meshes preparation", levels
=2)
340 patch_faces
= 4**levels
341 sides
= int(sqrt(patch_faces
))
344 patch_faces0
= int((sides
-2)**2)
346 if fill_mode
== 'PATCH':
347 all_verts
, mask
, materials
= get_patches(before_subsurf
, me0
, 4, levels
, bool_selection
)
349 all_verts
, mask
, materials
= get_quads(me0
, bool_selection
)
350 n_patches
= len(all_verts
)
352 tt
= tissue_time(tt
, "Indexing", levels
=2)
356 # Check if possible to use Weight Rotation
357 if rotation_mode
== 'WEIGHT':
358 if not vertex_group_rotation
in ob0
.vertex_groups
.keys():
359 rotation_mode
= 'DEFAULT'
361 bool_weight_smooth_normals
= vertex_group_smooth_normals
in ob0
.vertex_groups
.keys()
362 bool_weight_thickness
= vertex_group_thickness
in ob0
.vertex_groups
.keys()
363 bool_weight_distribution
= vertex_group_distribution
in ob0
.vertex_groups
.keys()
364 bool_weight_cap
= vertex_group_cap_owner
== 'BASE' and vertex_group_cap
in ob0
.vertex_groups
.keys()
365 bool_weight_bridge
= vertex_group_bridge_owner
== 'BASE' and vertex_group_bridge
in ob0
.vertex_groups
.keys()
366 bool_weight_normals
= vertex_group_scale_normals
in ob0
.vertex_groups
.keys()
368 read_vertex_groups
= bool_vertex_group
or rotation_mode
== 'WEIGHT' or bool_weight_thickness
or bool_weight_cap
or bool_weight_bridge
or bool_weight_smooth_normals
or bool_weight_distribution
or bool_weight_normals
369 weight
= weight_thickness
= weight_rotation
= None
370 if read_vertex_groups
:
371 if bool_vertex_group
:
372 weight
= [get_weight(vg
, n_verts0
) for vg
in ob0
.vertex_groups
]
373 weight
= np
.array(weight
)
374 n_vg
= len(ob0
.vertex_groups
)
375 if rotation_mode
== 'WEIGHT':
376 vg_id
= ob0
.vertex_groups
[vertex_group_rotation
].index
377 weight_rotation
= weight
[vg_id
]
378 if bool_weight_smooth_normals
:
379 vg_id
= ob0
.vertex_groups
[bool_weight_smooth_normals
].index
380 weight_rotation
= weight
[vg_id
]
381 if bool_weight_distribution
:
382 vg_id
= ob0
.vertex_groups
[vertex_group_distribution
].index
383 weight_distribution
= weight
[vg_id
]
384 if bool_weight_normals
:
385 vg_id
= ob0
.vertex_groups
[vertex_group_scale_normals
].index
386 weight_normals
= weight
[vg_id
]
388 if rotation_mode
== 'WEIGHT':
389 vg
= ob0
.vertex_groups
[vertex_group_rotation
]
390 weight_rotation
= get_weight_numpy(vg
, n_verts0
)
391 if bool_weight_smooth_normals
:
392 vg
= ob0
.vertex_groups
[vertex_group_smooth_normals
]
393 weight_smooth_normals
= get_weight_numpy(vg
, n_verts0
)
394 if bool_weight_distribution
:
395 vg
= ob0
.vertex_groups
[vertex_group_distribution
]
396 weight_distribution
= get_weight_numpy(vg
, n_verts0
)
397 if bool_weight_normals
:
398 vg
= ob0
.vertex_groups
[vertex_group_scale_normals
]
399 weight_normals
= get_weight_numpy(vg
, n_verts0
)
401 if component_mode
== 'COLLECTION':
402 np
.random
.seed(coll_rand_seed
)
403 if fill_mode
== 'FAN' and consistent_wedges
:
406 bm0
.faces
.ensure_lookup_table()
407 lay_id
= bm0
.faces
.layers
.int["id"]
408 faces_id
= np
.array([f
[lay_id
] for f
in bm0
.faces
])
410 n_original_faces
= faces_id
[-1]+1
411 coll_materials
= np
.random
.randint(len(components
),size
=n_original_faces
)
412 coll_materials
= coll_materials
[faces_id
]
414 coll_materials
= np
.random
.randint(len(components
),size
=n_patches
)
415 gradient_distribution
= []
416 if bool_weight_distribution
:
417 if invert_vertex_group_distribution
:
418 weight_distribution
= 1-weight_distribution
419 v00
= all_verts
[:,0,0]
420 v01
= all_verts
[:,0,-1]
421 v10
= all_verts
[:,-1,0]
422 v11
= all_verts
[:,-1,-1]
423 face_weight
= (weight_distribution
[v00
] + weight_distribution
[v01
] + weight_distribution
[v10
] + weight_distribution
[v11
])/4 * len(components
)
424 if fill_mode
== 'FAN' and consistent_wedges
:
425 for i
in range(n_original_faces
):
426 face_mask
= faces_id
== i
427 face_weight
[face_mask
] = np
.average(face_weight
[face_mask
])
428 face_weight
= face_weight
.clip(max=len(components
)-1)
429 coll_materials
= coll_materials
.astype('float')
430 coll_materials
= face_weight
+ (coll_materials
- face_weight
)*vertex_group_distribution_factor
431 coll_materials
= coll_materials
.astype('int')
433 random
.seed(rand_seed
)
436 tt
= tissue_time(tt
, "Reading Vertex Groups", levels
=2)
440 weight_smooth_normals
= 0.2
441 weight_smooth_normals0
= 0.2
442 if vertex_group_smooth_normals
in ob0
.vertex_groups
.keys():
443 vg
= ob0
.vertex_groups
[vertex_group_smooth_normals
]
444 weight_smooth_normals0
= get_weight_numpy(vg
, n_verts0
)
445 if invert_vertex_group_smooth_normals
:
446 weight_smooth_normals0
= 1-weight_smooth_normals0
447 weight_smooth_normals0
*= 0.2
449 verts0_normal
= mesh_diffusion_vector(me0
, verts0_normal
, smooth_normals_iter
, weight_smooth_normals0
, smooth_normals_uv
)
451 While in Relative thickness method the components are built
452 between the two surfaces, in Constant mode the thickness is uniform.
454 if scale_mode
== 'CONSTANT':
456 verts0_normal
/= np
.linalg
.norm(verts0_normal
, axis
=1).reshape((-1,1))
457 # Compare to the original normals direction
458 original_normals
= get_normals_numpy(me0
)
459 verts0_normal
/= np
.multiply(verts0_normal
, original_normals
).sum(1)[:,None]
461 tt
= tissue_time(tt
, "Smooth Normals", levels
=2)
463 if normals_mode
in ('FACES', 'VERTS'):
464 normals_x
= props
['normals_x']
465 normals_y
= props
['normals_y']
466 normals_z
= props
['normals_z']
467 if bool_weight_normals
:
468 if invert_vertex_group_scale_normals
:
469 weight_normals
= 1-weight_normals
470 w_normals_x
= 1 - weight_normals
* (1 - normals_x
)
471 w_normals_y
= 1 - weight_normals
* (1 - normals_y
)
472 w_normals_z
= 1 - weight_normals
* (1 - normals_z
)
474 w_normals_x
= normals_x
475 w_normals_y
= normals_y
476 w_normals_z
= normals_z
477 if normals_x
< 1: verts0_normal
[:,0] *= w_normals_x
478 if normals_y
< 1: verts0_normal
[:,1] *= w_normals_y
479 if normals_z
< 1: verts0_normal
[:,2] *= w_normals_z
480 div_value
= np
.linalg
.norm(verts0_normal
, axis
=1).reshape((-1,1))
481 div_value
[div_value
== 0] = 0.00001
482 verts0_normal
/= div_value
484 ### ROTATE PATCHES ###
486 if rotation_mode
!= 'DEFAULT' or rotation_shift
!= 0:
490 if rotation_mode
== 'WEIGHT':
491 corners_id
= np
.array(((0,0,-1,-1),(0,-1,-1,0)))
492 corners
= all_verts
[:,corners_id
[0],corners_id
[1]]
493 corners_weight
= weight_rotation
[corners
]
494 if invert_vertex_group_rotation
:
495 corners_weight
= 1-corners_weight
497 if rotation_direction
== 'DIAG':
498 c0
= corners_weight
[:,ids4
]
499 c3
= corners_weight
[:,(ids4
+2)%4]
500 differential
= c3
- c0
502 c0
= corners_weight
[:,ids4
]
503 c1
= corners_weight
[:,(ids4
+1)%4]
504 c2
= corners_weight
[:,(ids4
+2)%4]
505 c3
= corners_weight
[:,(ids4
+3)%4]
506 differential
= - c0
+ c1
+ c2
- c3
507 weight_shift
= np
.argmax(differential
, axis
=1)
511 if rotation_mode
== 'RANDOM':
512 np
.random
.seed(rand_seed
)
513 random_shift
= np
.random
.randint(0,4,size
=n_patches
)*rand_step
517 if rotation_mode
== 'UV' and ob0
.type == 'MESH':
519 bm
.from_mesh(before_subsurf
)
520 uv_lay
= bm
.loops
.layers
.uv
.active
521 UV_shift
= [0]*len(mask
)
525 uv0
= ll
[0][uv_lay
].uv
526 uv1
= ll
[3][uv_lay
].uv
527 uv2
= ll
[2][uv_lay
].uv
528 uv3
= ll
[1][uv_lay
].uv
530 v01
= (uv0
+ uv1
) # not necessary to divide by 2
532 v0132
= v32
- v01
# axis vector 1
533 v0132
.normalize() # based on the rotation not on the size
536 v1203
= v03
- v12
# axis vector 2
537 v1203
.normalize() # based on the rotation not on the size
541 if(abs(dot1203
) < abs(dot0132
)): # already vertical
542 if (dot0132
> 0): shift
= 0
543 else: shift
= 2 # rotate 180°
545 if(dot1203
< 0): shift
= 3
547 #UV_shift.append(shift)
548 UV_shift
[f
.index
] = shift
550 UV_shift
= np
.array(UV_shift
)[mask
]
554 rotation_shift
= np
.zeros((n_patches
))+rotation_shift
555 rot
= weight_shift
+ random_shift
+ UV_shift
+ rotation_shift
557 flip_u
= np
.logical_or(rot
==2,rot
==3)
558 flip_v
= np
.logical_or(rot
==1,rot
==2)
559 flip_uv
= np
.logical_or(rot
==1,rot
==3)
560 all_verts
[flip_u
] = all_verts
[flip_u
,::-1,:]
561 all_verts
[flip_v
] = all_verts
[flip_v
,:,::-1]
562 all_verts
[flip_uv
] = np
.transpose(all_verts
[flip_uv
],(0,2,1))
564 tt
= tissue_time(tt
, "Rotations", levels
=2)
566 #for o in bpy.context.view_layer.objects: o.select_set(False)
572 # Store original values
573 _com_modifiers
= com_modifiers
574 _bool_shapekeys
= bool_shapekeys
576 for mat_id
, _ob1
in enumerate(components
):
577 if _ob1
== None: continue
579 # Set original values (for next components)
580 com_modifiers
= _com_modifiers
581 bool_shapekeys
= _bool_shapekeys
583 if component_mode
!= 'OBJECT':
584 if component_mode
== 'COLLECTION':
585 mat_mask
= coll_materials
== mat_id
587 mat_mask
= materials
== mat_id
589 mat_mask
= np
.logical_and(mat_mask
, materials
== material_id
)
590 masked_verts
= all_verts
[mat_mask
]
591 masked_faces
= mat_mask
592 elif bool_material_id
:
593 masked_verts
= all_verts
[materials
== material_id
]
594 masked_faces
= np
.logical_and(mask
, materials
== material_id
)
596 masked_verts
= all_verts
598 n_patches
= len(masked_verts
)
599 if n_patches
== 0: continue
601 if com_modifiers
or _ob1
.type != 'MESH': bool_shapekeys
= False
603 # set Shape Keys to zero
604 original_key_values
= None
605 if (bool_shapekeys
or not com_modifiers
) and _ob1
.type == 'MESH':
606 if _ob1
.data
.shape_keys
:
607 original_key_values
= []
608 for sk
in _ob1
.data
.shape_keys
.key_blocks
:
609 original_key_values
.append(sk
.value
)
612 bool_shapekeys
= False
613 else: bool_shapekeys
= False
615 if not com_modifiers
and not bool_shapekeys
:
617 for m
in _ob1
.modifiers
:
618 mod_visibility
.append(m
.show_viewport
)
619 m
.show_viewport
= False
621 ob1
= convert_object_to_mesh(_ob1
, com_modifiers
, False)
622 ob1
, com_area
= tessellate_prepare_component(ob1
, props
)
623 ob1
.name
= "_tissue_tmp_ob1"
625 # restore original modifiers visibility for component object
627 for m
, vis
in zip(_ob1
.modifiers
, mod_visibility
):
628 m
.show_viewport
= vis
632 verts1
= [v
.co
for v
in me1
.vertices
]
633 n_verts1
= len(verts1
)
635 bpy
.data
.objects
.remove(ob1
)
638 ### COMPONENT GRID COORDINATES ###
640 # find relative UV component's vertices
641 if fill_mode
== 'PATCH':
642 verts1_uv_quads
= [0]*n_verts1
643 verts1_uv
= [0]*n_verts1
644 for i
, vert
in enumerate(verts1
):
646 u
= int(vert
[0]//step
)
647 v
= int(vert
[1]//step
)
663 verts1_uv_quads
[i
] = (u
,v
,u1
,v1
)
665 fu
= (vert
[0]-u
*step
)/step
666 fv
= (vert
[1]-v
*step
)/step
668 # interpolate Z scaling factor
669 verts1_uv
[i
] = Vector((fu
,fv
,fw
))
676 for sk
in ob1
.data
.shape_keys
.key_blocks
[1:]:
678 _sk_uv_quads
= [0]*n_verts1
679 _sk_uv
= [0]*n_verts1
680 for i
, sk_v
in enumerate(source
):
684 u
= int(sk_vert
[0]//step
)
685 v
= int(sk_vert
[1]//step
)
701 _sk_uv_quads
[i
] = (u
,v
,u1
,v1
)
703 fu
= (sk_vert
[0]-u
*step
)/step
704 fv
= (sk_vert
[1]-v
*step
)/step
706 _sk_uv
[i
] = Vector((fu
,fv
,fw
))
707 sk_uv_quads
.append(_sk_uv_quads
)
709 store_sk_coordinates
= [[] for t
in ob1
.data
.shape_keys
.key_blocks
[1:]]
710 sk_uv_quads
= np
.array(sk_uv_quads
)
711 sk_uv
= np
.array(sk_uv
)
713 np_verts1_uv
= np
.array(verts1_uv
)
714 if fill_mode
== 'PATCH':
715 verts1_uv_quads
= np
.array(verts1_uv_quads
)
716 np_u
= verts1_uv_quads
[:,0]
717 np_v
= verts1_uv_quads
[:,1]
718 np_u1
= verts1_uv_quads
[:,2]
719 np_v1
= verts1_uv_quads
[:,3]
726 tt
= tissue_time(tt
, "Component preparation", levels
=2)
728 ### DEFORM PATCHES ###
730 verts_xyz
= verts0_co
[masked_verts
]
731 v00
= verts_xyz
[:, np_u
, np_v
].reshape((n_patches
,-1,3))
732 v10
= verts_xyz
[:, np_u1
, np_v
].reshape((n_patches
,-1,3))
733 v01
= verts_xyz
[:, np_u
, np_v1
].reshape((n_patches
,-1,3))
734 v11
= verts_xyz
[:, np_u1
, np_v1
].reshape((n_patches
,-1,3))
735 vx
= np_verts1_uv
[:,0].reshape((1,n_verts1
,1))
736 vy
= np_verts1_uv
[:,1].reshape((1,n_verts1
,1))
737 vz
= np_verts1_uv
[:,2].reshape((1,n_verts1
,1))
738 co2
= np_lerp2(v00
, v10
, v01
, v11
, vx
, vy
, 'verts')
740 ### PATCHES WEIGHT ###
741 weight_thickness
= None
742 if bool_vertex_group
:
744 patches_weight
= weight
[:, masked_verts
]
745 w00
= patches_weight
[:, :, np_u
, np_v
].reshape((n_vg
, n_patches
,-1,1))
746 w10
= patches_weight
[:, :, np_u1
, np_v
].reshape((n_vg
, n_patches
,-1,1))
747 w01
= patches_weight
[:, :, np_u
, np_v1
].reshape((n_vg
, n_patches
,-1,1))
748 w11
= patches_weight
[:, :, np_u1
, np_v1
].reshape((n_vg
, n_patches
,-1,1))
749 store_weight
= np_lerp2(w00
,w10
,w01
,w11
,vx
[None,:,:,:],vy
[None,:,:,:],'weight')
751 if vertex_group_thickness
in ob0
.vertex_groups
.keys():
752 vg_id
= ob0
.vertex_groups
[vertex_group_thickness
].index
753 weight_thickness
= store_weight
[vg_id
,:,:]
754 if vertex_group_smooth_normals
in ob0
.vertex_groups
.keys():
755 vg_id
= ob0
.vertex_groups
[vertex_group_smooth_normals
].index
756 weight_smooth_normals
= store_weight
[vg_id
,:,:]
758 # Read vertex group Thickness
759 if vertex_group_thickness
in ob0
.vertex_groups
.keys():
760 vg
= ob0
.vertex_groups
[vertex_group_thickness
]
761 weight_thickness
= get_weight_numpy(vg
, n_verts0
)
762 wt
= weight_thickness
[masked_verts
]
763 wt
= wt
[:,:,:,np
.newaxis
]
764 w00
= wt
[:, np_u
, np_v
].reshape((n_patches
, -1, 1))
765 w10
= wt
[:, np_u1
, np_v
].reshape((n_patches
, -1, 1))
766 w01
= wt
[:, np_u
, np_v1
].reshape((n_patches
, -1, 1))
767 w11
= wt
[:, np_u1
, np_v1
].reshape((n_patches
, -1, 1))
768 weight_thickness
= np_lerp2(w00
,w10
,w01
,w11
,vx
,vy
,'verts')
770 weight_thickness
.shape
771 if invert_vertex_group_thickness
:
772 weight_thickness
= 1-weight_thickness
773 fact
= vertex_group_thickness_factor
775 weight_thickness
= weight_thickness
*(1-fact
) + fact
778 # Read vertex group smooth normals
779 if vertex_group_smooth_normals
in ob0
.vertex_groups
.keys():
780 vg
= ob0
.vertex_groups
[vertex_group_smooth_normals
]
781 weight_smooth_normals
= get_weight_numpy(vg
, n_verts0
)
782 wt
= weight_smooth_normals
[masked_verts
]
784 w00
= wt
[:, np_u
, np_v
].reshape((n_patches
, -1, 1))
785 w10
= wt
[:, np_u1
, np_v
].reshape((n_patches
, -1, 1))
786 w01
= wt
[:, np_u
, np_v1
].reshape((n_patches
, -1, 1))
787 w11
= wt
[:, np_u1
, np_v1
].reshape((n_patches
, -1, 1))
788 weight_smooth_normals
= np_lerp2(w00
,w10
,w01
,w11
,vx
,vy
,'verts')
790 weight_smooth_normals
.shape
791 if invert_vertex_group_smooth_normals
:
792 weight_smooth_normals
= 1-weight_smooth_normals
793 #fact = vertex_group_thickness_factor
795 # weight_thickness = weight_thickness*(1-fact) + fact
798 if normals_mode
== 'FACES':
799 n2
= get_attribute_numpy(before_subsurf
.polygons
,'normal',3)
800 n2
= n2
[masked_faces
][:,None,:]
802 if normals_mode
== 'CUSTOM':
803 me0
.calc_normals_split()
804 normals_split
= [0]*len(me0
.loops
)*3
805 vertex_indexes
= [0]*len(me0
.loops
)
806 me0
.loops
.foreach_get('normal', normals_split
)
807 me0
.loops
.foreach_get('vertex_index', vertex_indexes
)
808 normals_split
= np
.array(normals_split
).reshape(-1,3)
809 vertex_indexes
= np
.array(vertex_indexes
)
810 verts0_normal
= np
.zeros((len(me0
.vertices
),3))
811 np
.add
.at(verts0_normal
, vertex_indexes
, normals_split
)
812 indexes
, counts
= np
.unique(vertex_indexes
,return_counts
=True)
813 verts0_normal
[indexes
] /= counts
[:,np
.newaxis
]
815 if 'Eval_Normals' in me1
.uv_layers
.keys():
818 uv_co
= np
.array(uv_from_bmesh(bm1
, 'Eval_Normals'))
819 vx_nor
= uv_co
[:,0]#.reshape((1,n_verts1,1))
820 #vy_nor = uv_co[:,1]#.reshape((1,n_verts1,1))
823 np_u
= np
.clip(vx_nor
//step
, 0, sides
).astype('int')
824 #np_v = np.maximum(vy_nor//step, 0).astype('int')
825 np_u1
= np
.clip(np_u
+1, 0, sides
).astype('int')
826 #np_v1 = np.minimum(np_v+1, sides).astype('int')
828 vx_nor
= (vx_nor
- np_u
* step
)/step
829 #vy_nor = (vy_nor - np_v * step)/step
830 vx_nor
= vx_nor
.reshape((1,n_verts1
,1))
831 #vy_nor = vy_nor.reshape((1,n_verts1,1))
838 if normals_mode
in ('SHAPEKEYS','OBJECT') and scale_mode
== 'CONSTANT' and even_thickness
:
839 verts_norm_pos
= verts0_normal_pos
[masked_verts
]
840 verts_norm_neg
= verts0_normal_neg
[masked_verts
]
841 nor_mask
= (vz
<0).reshape((-1))
842 n00
= verts_norm_pos
[:, np_u
, np_v
].reshape((n_patches
,-1,3))
843 n10
= verts_norm_pos
[:, np_u1
, np_v
].reshape((n_patches
,-1,3))
844 n01
= verts_norm_pos
[:, np_u
, np_v1
].reshape((n_patches
,-1,3))
845 n11
= verts_norm_pos
[:, np_u1
, np_v1
].reshape((n_patches
,-1,3))
846 n00_neg
= verts_norm_neg
[:, np_u
, np_v
].reshape((n_patches
,-1,3))
847 n10_neg
= verts_norm_neg
[:, np_u1
, np_v
].reshape((n_patches
,-1,3))
848 n01_neg
= verts_norm_neg
[:, np_u
, np_v1
].reshape((n_patches
,-1,3))
849 n11_neg
= verts_norm_neg
[:, np_u1
, np_v1
].reshape((n_patches
,-1,3))
850 n00
[:,nor_mask
] = n00_neg
[:,nor_mask
]
851 n10
[:,nor_mask
] = n10_neg
[:,nor_mask
]
852 n01
[:,nor_mask
] = n01_neg
[:,nor_mask
]
853 n11
[:,nor_mask
] = n11_neg
[:,nor_mask
]
855 verts_norm
= verts0_normal
[masked_verts
]
856 n00
= verts_norm
[:, np_u
, np_v
].reshape((n_patches
,-1,3))
857 n10
= verts_norm
[:, np_u1
, np_v
].reshape((n_patches
,-1,3))
858 n01
= verts_norm
[:, np_u
, np_v1
].reshape((n_patches
,-1,3))
859 n11
= verts_norm
[:, np_u1
, np_v1
].reshape((n_patches
,-1,3))
860 n2
= np_lerp2(n00
, n10
, n01
, n11
, vx_nor
, vy_nor
, 'verts')
862 # thickness variation
865 if scale_mode
== 'ADAPTIVE' and normals_mode
not in ('SHAPEKEYS','OBJECT'):
866 #com_area = bb[0]*bb[1]
867 if mode
!= 'BOUNDS' or com_area
== 0: com_area
= 1
868 if normals_mode
== 'FACES':
869 if levels
== 0 and True:
870 areas
= [0]*len(mask
)
871 before_subsurf
.polygons
.foreach_get('area',areas
)
872 areas
= np
.sqrt(np
.array(areas
)/com_area
)[masked_faces
]
873 a2
= areas
[:,None,None]
875 areas
= calc_verts_area_bmesh(me0
)
876 verts_area
= np
.sqrt(areas
*patch_faces
/com_area
)
877 verts_area
= verts_area
[masked_verts
]
878 verts_area
= verts_area
.mean(axis
=(1,2)).reshape((n_patches
,1,1))
881 areas
= calc_verts_area_bmesh(me0
)
882 verts_area
= np
.sqrt(areas
*patch_faces
/com_area
)
883 verts_area
= verts_area
[masked_verts
]
884 a00
= verts_area
[:, np_u
, np_v
].reshape((n_patches
,-1,1))
885 a10
= verts_area
[:, np_u1
, np_v
].reshape((n_patches
,-1,1))
886 a01
= verts_area
[:, np_u
, np_v1
].reshape((n_patches
,-1,1))
887 a11
= verts_area
[:, np_u1
, np_v1
].reshape((n_patches
,-1,1))
889 a2
= np_lerp2(a00
,a10
,a01
,a11
,vx
,vy
,'verts')
891 store_coordinates
= calc_thickness(co2
,n2
,vz
,a2
,weight_thickness
)
892 co2
= n2
= vz
= a2
= None
896 n_sk
= len(sk_uv_quads
)
897 # ids of face corners for each vertex (n_sk, n_verts1, 4)
898 np_u
= np
.clip(sk_uv_quads
[:,:,0], 0, sides
).astype('int')[:,None,:]
899 np_v
= np
.clip(sk_uv_quads
[:,:,1], 0, sides
).astype('int')[:,None,:]
900 np_u1
= np
.clip(sk_uv_quads
[:,:,2], 0, sides
).astype('int')[:,None,:]
901 np_v1
= np
.clip(sk_uv_quads
[:,:,3], 0, sides
).astype('int')[:,None,:]
903 # face corners for each vertex (n_patches, n_sk, n_verts1, 4)
904 v00
= verts_xyz
[:,np_u
,np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))#.swapaxes(0,1)
905 v10
= verts_xyz
[:,np_u1
,np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))#.swapaxes(0,1)
906 v01
= verts_xyz
[:,np_u
,np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))#.swapaxes(0,1)
907 v11
= verts_xyz
[:,np_u1
,np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))#.swapaxes(0,1)
908 vx
= sk_uv
[:,:,0].reshape((1,n_sk
,n_verts1
,1))
909 vy
= sk_uv
[:,:,1].reshape((1,n_sk
,n_verts1
,1))
910 vz
= sk_uv
[:,:,2].reshape((1,n_sk
,n_verts1
,1))
911 co2
= np_lerp2(v00
,v10
,v01
,v11
,vx
,vy
,mode
='shapekeys')
913 if normals_mode
== 'FACES':
917 if normals_mode
in ('SHAPEKEYS','OBJECT') and scale_mode
== 'CONSTANT' and even_thickness
:
918 verts_norm_pos
= verts0_normal_pos
[masked_verts
]
919 verts_norm_neg
= verts0_normal_neg
[masked_verts
]
920 nor_mask
= (vz
<0).reshape((-1))
921 n00
= verts_norm_pos
[:, np_u
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))
922 n10
= verts_norm_pos
[:, np_u1
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))
923 n01
= verts_norm_pos
[:, np_u
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))
924 n11
= verts_norm_pos
[:, np_u1
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))
925 n00_neg
= verts_norm_neg
[:, np_u
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))
926 n10_neg
= verts_norm_neg
[:, np_u1
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))
927 n01_neg
= verts_norm_neg
[:, np_u
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))
928 n11_neg
= verts_norm_neg
[:, np_u1
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))
929 n00
[:,:,nor_mask
] = n00_neg
[:,:,nor_mask
]
930 n10
[:,:,nor_mask
] = n10_neg
[:,:,nor_mask
]
931 n01
[:,:,nor_mask
] = n01_neg
[:,:,nor_mask
]
932 n11
[:,:,nor_mask
] = n11_neg
[:,:,nor_mask
]
934 n00
= verts_norm
[:, np_u
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))
935 n10
= verts_norm
[:, np_u1
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,3))
936 n01
= verts_norm
[:, np_u
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))
937 n11
= verts_norm
[:, np_u1
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,3))
938 n2
= np_lerp2(n00
,n10
,n01
,n11
,vx
,vy
,'shapekeys')
940 # NOTE: weight thickness is based on the base position of the
941 # vertices, not on the coordinates of the shape keys
943 if scale_mode
== 'ADAPTIVE':# and normals_mode not in ('OBJECT', 'SHAPEKEYS'): ### not sure
944 if normals_mode
== 'FACES':
947 a00
= verts_area
[:, np_u
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,1))
948 a10
= verts_area
[:, np_u1
, np_v
].reshape((n_patches
,n_sk
,n_verts1
,1))
949 a01
= verts_area
[:, np_u
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,1))
950 a11
= verts_area
[:, np_u1
, np_v1
].reshape((n_patches
,n_sk
,n_verts1
,1))
952 a2
= np_lerp2(a00
,a10
,a01
,a11
,vx
,vy
,'shapekeys')
954 store_sk_coordinates
= calc_thickness(co2
,n2
,vz
,a2
,weight_thickness
)
955 co2
= n2
= vz
= a2
= weight_thickness
= None
956 tissue_time(tt_sk
, "Compute ShapeKeys", levels
=3)
958 tt
= tissue_time(tt
, "Compute Coordinates", levels
=2)
960 new_me
= array_mesh(ob1
, len(masked_verts
))
961 tt
= tissue_time(tt
, "Repeat component", levels
=2)
963 new_patch
= bpy
.data
.objects
.new("_tissue_tmp_patch", new_me
)
964 bpy
.context
.collection
.objects
.link(new_patch
)
966 store_coordinates
= np
.concatenate(store_coordinates
, axis
=0).reshape((-1)).tolist()
967 new_me
.vertices
.foreach_set('co',store_coordinates
)
969 for area
in bpy
.context
.screen
.areas
:
970 for space
in area
.spaces
:
971 try: new_patch
.local_view_set(space
, True)
973 tt
= tissue_time(tt
, "Inject coordinates", levels
=2)
976 for vg
in ob1
.vertex_groups
:
978 if vg_name
in ob0
.vertex_groups
.keys():
979 vg_name
= '_{}_'.format(vg_name
)
980 new_patch
.vertex_groups
.new(name
=vg_name
)
981 if bool_vertex_group
:
983 for vg
in ob0
.vertex_groups
:
984 new_groups
.append(new_patch
.vertex_groups
.new(name
=vg
.name
))
985 for vg
, w
in zip(new_groups
, store_weight
):
986 set_weight_numpy(vg
, w
.reshape(-1))
987 tt
= tissue_time(tt
, "Write Vertex Groups", levels
=2)
990 for sk
, val
in zip(_ob1
.data
.shape_keys
.key_blocks
, original_key_values
):
992 new_patch
.shape_key_add(name
=sk
.name
, from_mix
=False)
993 new_patch
.data
.shape_keys
.key_blocks
[sk
.name
].value
= val
994 for i
in range(n_sk
):
995 coordinates
= np
.concatenate(store_sk_coordinates
[:,i
], axis
=0)
996 coordinates
= coordinates
.flatten().tolist()
997 new_patch
.data
.shape_keys
.key_blocks
[i
+1].data
.foreach_set('co', coordinates
)
999 # set original values and combine Shape Keys and Vertex Groups
1000 for sk
, val
in zip(_ob1
.data
.shape_keys
.key_blocks
, original_key_values
):
1002 new_patch
.data
.shape_keys
.key_blocks
[sk
.name
].value
= val
1003 if bool_vertex_group
:
1004 vg_keys
= new_patch
.vertex_groups
.keys()
1005 for sk
in new_patch
.data
.shape_keys
.key_blocks
:
1006 if sk
.name
in vg_keys
:
1007 sk
.vertex_group
= sk
.name
1008 tt
= tissue_time(tt
, "Shape Keys", levels
=2)
1009 elif original_key_values
:
1010 for sk
, val
in zip(_ob1
.data
.shape_keys
.key_blocks
, original_key_values
):
1013 new_name
= ob0
.name
+ "_" + ob1
.name
1014 new_patch
.name
= "_tissue_tmp_patch"
1015 new_patch
.data
.update() # needed for updating the normals
1016 new_objects
.append(new_patch
)
1017 bpy
.data
.objects
.remove(ob1
)
1018 bpy
.data
.objects
.remove(ob0
)
1019 tt
= tissue_time(tt
, "Closing Tessellate Iteration", levels
=2)
1022 class tissue_tessellate(Operator
):
1023 bl_idname
= "object.tissue_tessellate"
1024 bl_label
= "Tissue Tessellate"
1025 bl_description
= ("Create a copy of selected object on the active object's "
1026 "faces, adapting the shape to the different faces")
1027 bl_options
= {'REGISTER', 'UNDO'}
1030 bool_hold
: BoolProperty(
1032 description
="Wait...",
1035 object_name
: StringProperty(
1037 description
="Name of the generated object"
1039 zscale
: FloatProperty(
1044 description
="Scale factor for the component thickness"
1046 scale_mode
: EnumProperty(
1048 ('CONSTANT', "Constant", "Uniform thickness"),
1049 ('ADAPTIVE', "Relative", "Preserve component's proportions")
1052 name
="Z-Scale according to faces size"
1054 offset
: FloatProperty(
1055 name
="Surface Offset",
1060 description
="Surface offset"
1062 component_mode
: EnumProperty(
1064 ('OBJECT', "Object", "Use the same component object for all the faces"),
1065 ('COLLECTION', "Collection", "Use multiple components from Collection"),
1066 ('MATERIALS', "Materials", "Use multiple components by materials name")
1069 name
="Component Mode"
1071 mode
: EnumProperty(
1073 ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
1074 ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
1075 ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
1077 name
="Component Mode"
1079 rotation_mode
: EnumProperty(
1080 items
=(('RANDOM', "Random", "Random faces rotation"),
1081 ('UV', "Active UV", "Face rotation is based on UV coordinates"),
1082 ('WEIGHT', "Weight Gradient", "Rotate according to Vertex Group gradient"),
1083 ('DEFAULT', "Default", "Default rotation")),
1085 name
="Component Rotation"
1087 rotation_direction
: EnumProperty(
1088 items
=(('ORTHO', "Orthogonal", "Component main directions in XY"),
1089 ('DIAG', "Diagonal", "Component main direction aligned with diagonal")),
1093 rotation_shift
: IntProperty(
1098 description
="Shift components rotation"
1100 fill_mode
: EnumProperty(
1102 ('TRI', 'Tri', 'Triangulate the base mesh'),
1103 ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
1104 ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
1105 ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
1106 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
1107 'patches.\nAfter the last Subsurf (or Multires) only ' +
1108 'deformation\nmodifiers can be used'),
1109 ('FRAME', 'Frame', 'Tessellation along the edges of each face')),
1113 combine_mode
: EnumProperty(
1115 ('LAST', 'Last', 'Show only the last iteration'),
1116 ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
1117 ('ALL', 'All', 'Combine the result of all iterations')),
1119 name
="Combine Mode",
1121 gen_modifiers
: BoolProperty(
1122 name
="Generator Modifiers",
1124 description
="Apply Modifiers and Shape Keys to the base object"
1126 com_modifiers
: BoolProperty(
1127 name
="Component Modifiers",
1129 description
="Apply Modifiers and Shape Keys to the component object"
1131 merge
: BoolProperty(
1134 description
="Merge vertices in adjacent duplicates"
1136 merge_open_edges_only
: BoolProperty(
1137 name
="Open edges only",
1139 description
="Merge only open edges"
1141 merge_thres
: FloatProperty(
1146 description
="Limit below which to merge vertices"
1148 bool_random
: BoolProperty(
1151 description
="Randomize component rotation"
1153 rand_seed
: IntProperty(
1158 description
="Random seed"
1160 coll_rand_seed
: IntProperty(
1165 description
="Random seed"
1167 rand_step
: IntProperty(
1172 description
="Random step"
1174 bool_vertex_group
: BoolProperty(
1175 name
="Map Vertex Groups",
1177 description
="Transfer all Vertex Groups from Base object"
1179 bool_selection
: BoolProperty(
1180 name
="On selected Faces",
1182 description
="Create Tessellation only on selected faces"
1184 bool_shapekeys
: BoolProperty(
1185 name
="Use Shape Keys",
1187 description
="Transfer Component's Shape Keys. If the name of Vertex "
1188 "Groups and Shape Keys are the same, they will be "
1189 "automatically combined"
1191 bool_smooth
: BoolProperty(
1192 name
="Smooth Shading",
1194 description
="Output faces with smooth shading rather than flat shaded"
1196 bool_materials
: BoolProperty(
1197 name
="Transfer Materials",
1199 description
="Preserve component's materials"
1201 generator
: StringProperty(
1203 description
="Base object for the tessellation",
1206 component
: StringProperty(
1208 description
="Component object for the tessellation",
1211 component_coll
: StringProperty(
1213 description
="Components collection for the tessellation",
1216 target
: StringProperty(
1218 description
="Target object for custom direction",
1221 even_thickness
: BoolProperty(
1222 name
="Even Thickness",
1224 description
="Iterative sampling method for determine the correct length of the vectors (Experimental)"
1226 even_thickness_iter
: IntProperty(
1227 name
="Even Thickness Iterations",
1231 description
="More iterations produces more accurate results but make the tessellation slower"
1233 bool_material_id
: BoolProperty(
1234 name
="Tessellation on Material ID",
1236 description
="Apply the component only on the selected Material"
1238 bool_dissolve_seams
: BoolProperty(
1239 name
="Dissolve Seams",
1241 description
="Dissolve all seam edges"
1243 material_id
: IntProperty(
1247 description
="Material ID"
1249 iterations
: IntProperty(
1254 description
="Automatically repeat the Tessellation using the "
1255 + "generated geometry as new base object.\nUseful for "
1256 + "for branching systems. Dangerous!"
1258 bool_combine
: BoolProperty(
1259 name
="Combine unused",
1261 description
="Combine the generated geometry with unused faces"
1263 bool_advanced
: BoolProperty(
1264 name
="Advanced Settings",
1266 description
="Show more settings"
1268 normals_mode
: EnumProperty(
1270 ('VERTS', 'Normals', 'Consistent direction based on vertices normal'),
1271 ('FACES', 'Faces', 'Based on individual faces normal'),
1272 ('SHAPEKEYS', 'Keys', "According to base object's shape keys"),
1273 ('OBJECT', 'Object', "According to a target object")),
1277 bounds_x
: EnumProperty(
1279 ('EXTEND', 'Extend', 'Default X coordinates'),
1280 ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
1281 ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
1285 bounds_y
: EnumProperty(
1287 ('EXTEND', 'Extend', 'Default Y coordinates'),
1288 ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
1289 ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
1293 close_mesh
: EnumProperty(
1295 ('NONE', 'None', 'Keep the mesh open'),
1296 ('CAP', 'Cap Holes', 'Automatically cap open loops'),
1297 ('BRIDGE', 'Bridge Open Loops', 'Automatically bridge loop pairs'),
1298 ('BRIDGE_CAP', 'Custom', 'Bridge loop pairs and cap holes according to vertex groups')),
1302 cap_faces
: BoolProperty(
1305 description
="Cap open edges loops"
1307 frame_boundary
: BoolProperty(
1308 name
="Frame Boundary",
1310 description
="Support face boundaries"
1312 fill_frame
: BoolProperty(
1315 description
="Fill inner faces with Fan tessellation"
1317 boundary_mat_offset
: IntProperty(
1318 name
="Material Offset",
1320 description
="Material Offset for boundaries (with Multi Components or Material ID)"
1322 fill_frame_mat
: IntProperty(
1323 name
="Material Offset",
1325 description
="Material Offset for inner faces (with Multi Components or Material ID)"
1327 open_edges_crease
: FloatProperty(
1328 name
="Open Edges Crease",
1332 description
="Automatically set crease for open edges"
1334 bridge_edges_crease
: FloatProperty(
1335 name
="Bridge Edges Crease",
1339 description
="Automatically set crease for bridge edges"
1341 bridge_smoothness
: FloatProperty(
1346 description
="Bridge Smoothness"
1348 frame_thickness
: FloatProperty(
1349 name
="Frame Thickness",
1353 description
="Frame Thickness"
1355 frame_mode
: EnumProperty(
1357 ('CONSTANT', 'Constant', 'Even thickness'),
1358 ('RELATIVE', 'Relative', 'Frame offset depends on face areas')),
1362 bridge_cuts
: IntProperty(
1367 description
="Bridge Cuts"
1369 cap_material_offset
: IntProperty(
1370 name
="Material Offset",
1373 description
="Material index offset for the cap faces"
1375 bridge_material_offset
: IntProperty(
1376 name
="Material Offset",
1379 description
="Material index offset for the bridge faces"
1381 patch_subs
: IntProperty(
1382 name
="Patch Subdivisions",
1385 description
="Subdivisions levels for Patch tessellation after the first iteration"
1387 use_origin_offset
: BoolProperty(
1388 name
="Align to Origins",
1390 description
="Define offset according to components origin and local Z coordinate"
1393 vertex_group_thickness
: StringProperty(
1394 name
="Thickness weight", default
='',
1395 description
="Vertex Group used for thickness"
1397 invert_vertex_group_thickness
: BoolProperty(
1398 name
="Invert", default
=False,
1399 description
="Invert the vertex group influence"
1401 vertex_group_thickness_factor
: FloatProperty(
1406 description
="Thickness factor to use for zero vertex group influence"
1409 vertex_group_distribution
: StringProperty(
1410 name
="Distribution weight", default
='',
1411 description
="Vertex Group used for gradient distribution"
1413 invert_vertex_group_distribution
: BoolProperty(
1414 name
="Invert", default
=False,
1415 description
="Invert the vertex group influence"
1417 vertex_group_distribution_factor
: FloatProperty(
1422 description
="Randomness factor to use for zero vertex group influence"
1425 vertex_group_cap_owner
: EnumProperty(
1427 ('BASE', 'Base', 'Use base vertex group'),
1428 ('COMP', 'Component', 'Use component vertex group')),
1432 vertex_group_cap
: StringProperty(
1433 name
="Cap Vertex Group", default
='',
1434 description
="Vertex Group used for cap open edges"
1436 invert_vertex_group_cap
: BoolProperty(
1437 name
="Invert", default
=False,
1438 description
="Invert the vertex group influence"
1441 vertex_group_bridge_owner
: EnumProperty(
1443 ('BASE', 'Base', 'Use base vertex group'),
1444 ('COMP', 'Component', 'Use component vertex group')),
1448 vertex_group_bridge
: StringProperty(
1449 name
="Thickness weight", default
='',
1450 description
="Vertex Group used for bridge open edges"
1452 invert_vertex_group_bridge
: BoolProperty(
1453 name
="Invert", default
=False,
1454 description
="Invert the vertex group influence"
1457 vertex_group_rotation
: StringProperty(
1458 name
="Rotation weight", default
='',
1459 description
="Vertex Group used for rotation"
1461 invert_vertex_group_rotation
: BoolProperty(
1462 name
="Invert", default
=False,
1463 description
="Invert the vertex group influence"
1465 normals_x
: FloatProperty(
1466 name
="X", default
=1, min=0, max=1,
1467 description
="Scale X component of the normals"
1469 normals_y
: FloatProperty(
1470 name
="Y", default
=1, min=0, max=1,
1471 description
="Scale Y component of the normals"
1473 normals_z
: FloatProperty(
1474 name
="Z", default
=1, min=0, max=1,
1475 description
="Scale Z component of the normals"
1477 vertex_group_scale_normals
: StringProperty(
1478 name
="Scale normals weight", default
='',
1479 description
="Vertex Group used for editing the normals directions"
1481 invert_vertex_group_scale_normals
: BoolProperty(
1482 name
="Invert", default
=False,
1483 description
="Invert the vertex group influence"
1485 smooth_normals
: BoolProperty(
1486 name
="Smooth Normals", default
=False,
1487 description
="Smooth normals of the surface in order to reduce intersections"
1489 smooth_normals_iter
: IntProperty(
1493 description
="Smooth iterations"
1495 smooth_normals_uv
: FloatProperty(
1496 name
="UV Anisotropy",
1500 description
="0 means no anisotropy, -1 represent the U direction, while 1 represent the V direction"
1502 vertex_group_smooth_normals
: StringProperty(
1503 name
="Smooth Normals weight", default
='',
1504 description
="Vertex Group used for smoothing normals"
1506 invert_vertex_group_smooth_normals
: BoolProperty(
1507 name
="Invert", default
=False,
1508 description
="Invert the vertex group influence"
1510 consistent_wedges
: BoolProperty(
1511 name
="Consistent Wedges", default
=True,
1512 description
="Use same component for the wedges generated by the Fan tessellation"
1514 boundary_variable_offset
: BoolProperty(
1515 name
="Boundary Variable Offset", default
=False,
1516 description
="Additional material offset based on the number of boundary vertices"
1518 auto_rotate_boundary
: BoolProperty(
1519 name
="Automatic Rotation", default
=False,
1520 description
="Automatically rotate the boundary faces"
1525 def draw(self
, context
):
1529 bool_working = self.working_on == self.object_name and \
1530 self.working_on != ""
1532 bool_working = False
1535 bool_working
= False
1536 bool_allowed
= False
1541 sel
= context
.selected_objects
1546 if o
.type not in allowed_objects():
1547 bool_allowed
= False
1549 if self
.component_mode
== 'OBJECT':
1550 if len(sel
) != 2 and not bool_working
:
1551 layout
= self
.layout
1552 layout
.label(icon
='OBJECT_DATA', text
='Single Object Component')
1553 layout
.label(icon
='INFO', text
="Please, select two different objects. Select first the")
1554 layout
.label(text
="Component object, then select the Base object.")
1556 elif not bool_allowed
and not bool_working
:
1557 layout
= self
.layout
1558 layout
.label(icon
='OBJECT_DATA', text
='Single Object Component')
1559 layout
.label(icon
='ERROR', text
="Please, select Mesh, Curve, Surface, Meta or Text")
1561 elif self
.component_mode
== 'COLLECTION':
1562 no_components
= True
1563 for o
in bpy
.data
.collections
[self
.component_coll
].objects
:
1564 if o
.type in ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT') and o
is not ob0
:
1565 no_components
= False
1568 layout
= self
.layout
1569 layout
.label(icon
='OUTLINER_COLLECTION', text
='Components from Active Collection')
1570 layout
.label(icon
='INFO', text
="The Active Collection does not contain any Mesh,")
1571 layout
.label(text
="Curve, Surface, Meta or Text object.")
1573 elif self
.component_mode
== 'MATERIALS':
1574 no_components
= True
1575 for mat
in ob
.material_slots
.keys():
1576 if mat
in bpy
.data
.objects
.keys():
1577 if bpy
.data
.objects
[mat
].type in allowed_objects():
1578 no_components
= False
1581 layout
= self
.layout
1582 layout
.label(icon
='INFO', text
='Components from Materials')
1583 layout
.label(text
="Can't find any object according to the materials name.")
1586 if ob0
== ob1
== None:
1587 ob0
= context
.object
1588 self
.generator
= ob0
.name
1589 if self
.component_mode
== 'OBJECT':
1593 self
.component
= o
.name
1594 self
.no_component
= False
1598 if self
.object_name
== "":
1599 if self
.generator
== "":
1600 self
.object_name
= "Tessellation"
1602 #self.object_name = self.generator + "_Tessellation"
1603 self
.object_name
= "Tessellation"
1605 layout
= self
.layout
1606 # Base and Component
1607 col
= layout
.column(align
=True)
1608 #col.prop(self, "copy_settings")
1609 row
= col
.row(align
=True)
1610 row
.label(text
="Base : " + self
.generator
, icon
='OBJECT_DATA')
1611 if self
.component_mode
== 'OBJECT':
1612 row
.label(text
="Component : " + self
.component
, icon
='OBJECT_DATA')
1613 elif self
.component_mode
== 'COLLECTION':
1614 row
.label(text
="Collection : " + self
.component_coll
, icon
='OUTLINER_COLLECTION')
1615 elif self
.component_mode
== 'MATERIALS':
1616 row
.label(text
="Multiple Components", icon
='MATERIAL')
1619 row
= col
.row(align
=True)
1620 col2
= row
.column(align
=True)
1621 col2
.prop(self
, "gen_modifiers", text
="Use Modifiers", icon
='MODIFIER')
1622 base
= bpy
.data
.objects
[self
.generator
]
1624 # Component Modifiers
1626 col3
= row
.column(align
=True)
1627 col3
.prop(self
, "com_modifiers", text
="Use Modifiers", icon
='MODIFIER')
1628 if self
.component_mode
== 'OBJECT':
1629 component
= bpy
.data
.objects
[self
.component
]
1632 row
= col
.row(align
=True)
1633 row
.label(text
="Fill Mode:")
1634 row
= col
.row(align
=True)
1636 self
, "fill_mode", icon
='NONE', expand
=True,
1637 slider
=True, toggle
=False, icon_only
=False, event
=False,
1638 full_event
=False, emboss
=True, index
=-1)
1639 row
= col
.row(align
=True)
1641 row
.prop(self
, "merge")
1642 row
.prop(self
, "bool_smooth")
1645 if self
.fill_mode
== 'FRAME':
1647 col
.label(text
="Frame Settings:")
1648 row
= col
.row(align
=True)
1649 row
.prop(self
, "frame_mode", expand
=True)
1650 col
.prop(self
, "frame_thickness", text
='Thickness', icon
='NONE')
1652 row
= col
.row(align
=True)
1653 row
.prop(self
, "fill_frame", icon
='NONE')
1654 show_frame_mat
= self
.component_mode
== 'MATERIALS' or self
.bool_material_id
1655 col2
= row
.column(align
=True)
1656 col2
.prop(self
, "fill_frame_mat", icon
='NONE')
1657 col2
.enabled
= self
.fill_frame
and show_frame_mat
1658 row
= col
.row(align
=True)
1659 row
.prop(self
, "frame_boundary", text
='Boundary', icon
='NONE')
1660 col2
= row
.column(align
=True)
1661 col2
.prop(self
, "boundary_mat_offset", icon
='NONE')
1662 col2
.enabled
= self
.frame_boundary
and show_frame_mat
1664 if self
.rotation_mode
== 'UV':
1666 if ob0
.type != 'MESH':
1667 row
= col
.row(align
=True)
1669 text
="UV rotation supported only for Mesh objects",
1673 if len(ob0
.data
.uv_layers
) == 0:
1674 row
= col
.row(align
=True)
1675 check_name
= self
.generator
1676 row
.label(text
="'" + check_name
+
1677 "' doesn't have UV Maps", icon
='ERROR')
1680 row
= col
.row(align
=True)
1681 row
.label(text
="Default rotation will be used instead",
1686 col
.label(text
="Thickness:")
1687 row
= col
.row(align
=True)
1689 self
, "scale_mode", text
="Scale Mode", icon
='NONE', expand
=True,
1690 slider
=False, toggle
=False, icon_only
=False, event
=False,
1691 full_event
=False, emboss
=True, index
=-1)
1693 self
, "zscale", text
="Scale", icon
='NONE', expand
=False,
1694 slider
=True, toggle
=False, icon_only
=False, event
=False,
1695 full_event
=False, emboss
=True, index
=-1)
1696 if self
.mode
== 'BOUNDS':
1697 row
= col
.row(align
=True)
1699 self
, "offset", text
="Offset", icon
='NONE', expand
=False,
1700 slider
=True, toggle
=False, icon_only
=False, event
=False,
1701 full_event
=False, emboss
=True, index
=-1)
1702 row
.enabled
= not self
.use_origin_offset
1704 col
.label(text
="More settings in the Object Data Properties panel...", icon
='PROPERTIES')
1707 def execute(self
, context
):
1709 ob0
= bpy
.data
.objects
[self
.generator
]
1710 if self
.component_mode
== 'OBJECT':
1711 ob1
= bpy
.data
.objects
[self
.component
]
1713 return {'CANCELLED'}
1715 self
.object_name
= "Tessellation"
1716 # Check if existing object with same name
1717 names
= [o
.name
for o
in bpy
.data
.objects
]
1718 if self
.object_name
in names
:
1721 test_name
= self
.object_name
+ '.{:03d}'.format(count_name
)
1722 if not (test_name
in names
):
1723 self
.object_name
= test_name
1726 if self
.component_mode
== 'OBJECT':
1727 if ob1
.type not in allowed_objects():
1728 message
= "Component must be Mesh, Curve, Surface, Text or Meta object!"
1729 self
.report({'ERROR'}, message
)
1730 self
.component
= None
1732 if ob0
.type not in allowed_objects():
1733 message
= "Generator must be Mesh, Curve, Surface, Text or Meta object!"
1734 self
.report({'ERROR'}, message
)
1737 if bpy
.ops
.object.select_all
.poll():
1738 bpy
.ops
.object.select_all(action
='TOGGLE')
1739 bpy
.ops
.object.mode_set(mode
='OBJECT')
1742 if context
.object == ob0
:
1743 auto_layer_collection()
1744 new_ob
= convert_object_to_mesh(ob0
,False,False)
1745 new_ob
.data
.name
= self
.object_name
1746 new_ob
.name
= self
.object_name
1748 new_ob
= context
.object
1750 new_ob
= store_parameters(self
, new_ob
)
1751 new_ob
.tissue
.tissue_type
= 'TESSELLATE'
1752 try: bpy
.ops
.object.tissue_update_tessellate()
1753 except RuntimeError as e
:
1754 bpy
.data
.objects
.remove(new_ob
)
1755 remove_temp_objects()
1756 self
.report({'ERROR'}, str(e
))
1757 return {'CANCELLED'}
1759 self
.object_name
= new_ob
.name
1760 #self.working_on = self.object_name
1761 new_ob
.location
= ob0
.location
1762 new_ob
.matrix_world
= ob0
.matrix_world
1764 # Assign collection of the base object
1765 old_coll
= new_ob
.users_collection
1766 if old_coll
!= ob0
.users_collection
:
1768 c
.objects
.unlink(new_ob
)
1769 for c
in ob0
.users_collection
:
1770 c
.objects
.link(new_ob
)
1771 context
.view_layer
.objects
.active
= new_ob
1775 def invoke(self
, context
, event
):
1776 return context
.window_manager
.invoke_props_dialog(self
)
1779 class tissue_update_tessellate_deps(Operator
):
1780 bl_idname
= "object.tissue_update_tessellate_deps"
1781 bl_label
= "Tissue Refresh"
1782 bl_description
= ("Fast update the tessellated mesh according to base and "
1783 "component changes")
1784 bl_options
= {'REGISTER', 'UNDO'}
1789 def poll(cls
, context
):
1791 return context
.object.tissue
.tissue_type
!= 'NONE'
1796 #def check_gen_comp(checking):
1797 # note pass the stored name key in here to check it out
1798 # return checking in bpy.data.objects.keys()
1800 def execute(self
, context
):
1802 active_ob
= context
.object
1803 selected_objects
= context
.selected_objects
1805 ### TO-DO: sorting according to dependencies
1806 update_objects
= [o
for o
in selected_objects
if o
.tissue
.tissue_type
!= 'NONE']
1807 for ob
in selected_objects
:
1808 update_objects
= list(reversed(update_dependencies(ob
, update_objects
)))
1809 #update_objects = list(reversed(update_dependencies(ob, [ob])))
1810 for o
in update_objects
:
1813 'selected_objects' : [o
]
1815 if o
.type == 'MESH':
1817 bpy
.ops
.object.tissue_update_tessellate(override
)
1819 self
.report({'ERROR'}, "Can't Tessellate :-(")
1822 bpy
.ops
.object.tissue_convert_to_curve_update(override
)
1824 self
.report({'ERROR'}, "Can't compute Curve :-(")
1826 context
.view_layer
.objects
.active
= active_ob
1827 for o
in context
.view_layer
.objects
:
1828 o
.select_set(o
in selected_objects
)
1833 class tissue_update_tessellate(Operator
):
1834 bl_idname
= "object.tissue_update_tessellate"
1835 bl_label
= "Tissue Refresh Simple"
1836 bl_description
= ("Fast update the tessellated mesh according to base and "
1837 "component changes. Does not update dependencies")
1838 bl_options
= {'REGISTER', 'UNDO'}
1843 def poll(cls
, context
):
1846 return ob
.tissue
.tissue_type
== 'TESSELLATE'
1850 def execute(self
, context
):
1852 tissue_time(None,'Tissue: Tessellating...', levels
=0)
1853 start_time
= time
.time()
1857 tess_props
= props_to_dict(ob
)
1859 generator
= ob
.tissue_tessellate
.generator
1860 component
= ob
.tissue_tessellate
.component
1861 zscale
= ob
.tissue_tessellate
.zscale
1862 scale_mode
= ob
.tissue_tessellate
.scale_mode
1863 rotation_mode
= ob
.tissue_tessellate
.rotation_mode
1864 rotation_shift
= ob
.tissue_tessellate
.rotation_shift
1865 rotation_direction
= ob
.tissue_tessellate
.rotation_direction
1866 offset
= ob
.tissue_tessellate
.offset
1867 merge
= ob
.tissue_tessellate
.merge
1868 merge_open_edges_only
= ob
.tissue_tessellate
.merge_open_edges_only
1869 merge_thres
= ob
.tissue_tessellate
.merge_thres
1870 mode
= ob
.tissue_tessellate
.mode
1871 gen_modifiers
= ob
.tissue_tessellate
.gen_modifiers
1872 com_modifiers
= ob
.tissue_tessellate
.com_modifiers
1873 bool_random
= ob
.tissue_tessellate
.bool_random
1874 rand_seed
= ob
.tissue_tessellate
.rand_seed
1875 rand_step
= ob
.tissue_tessellate
.rand_step
1876 fill_mode
= ob
.tissue_tessellate
.fill_mode
1877 bool_vertex_group
= ob
.tissue_tessellate
.bool_vertex_group
1878 bool_selection
= ob
.tissue_tessellate
.bool_selection
1879 bool_shapekeys
= ob
.tissue_tessellate
.bool_shapekeys
1880 bool_smooth
= ob
.tissue_tessellate
.bool_smooth
1881 bool_materials
= ob
.tissue_tessellate
.bool_materials
1882 bool_dissolve_seams
= ob
.tissue_tessellate
.bool_dissolve_seams
1883 bool_material_id
= ob
.tissue_tessellate
.bool_material_id
1884 material_id
= ob
.tissue_tessellate
.material_id
1885 iterations
= ob
.tissue_tessellate
.iterations
1886 bool_combine
= ob
.tissue_tessellate
.bool_combine
1887 normals_mode
= ob
.tissue_tessellate
.normals_mode
1888 bool_advanced
= ob
.tissue_tessellate
.bool_advanced
1889 #bool_multi_components = ob.tissue_tessellate.bool_multi_components
1890 combine_mode
= ob
.tissue_tessellate
.combine_mode
1891 bounds_x
= ob
.tissue_tessellate
.bounds_x
1892 bounds_y
= ob
.tissue_tessellate
.bounds_y
1893 cap_faces
= ob
.tissue_tessellate
.cap_faces
1894 close_mesh
= ob
.tissue_tessellate
.close_mesh
1895 open_edges_crease
= ob
.tissue_tessellate
.open_edges_crease
1896 bridge_edges_crease
= ob
.tissue_tessellate
.bridge_edges_crease
1897 bridge_smoothness
= ob
.tissue_tessellate
.bridge_smoothness
1898 frame_thickness
= ob
.tissue_tessellate
.frame_thickness
1899 frame_mode
= ob
.tissue_tessellate
.frame_mode
1900 frame_boundary
= ob
.tissue_tessellate
.frame_boundary
1901 fill_frame
= ob
.tissue_tessellate
.fill_frame
1902 boundary_mat_offset
= ob
.tissue_tessellate
.boundary_mat_offset
1903 fill_frame_mat
= ob
.tissue_tessellate
.fill_frame_mat
1904 bridge_cuts
= ob
.tissue_tessellate
.bridge_cuts
1905 cap_material_offset
= ob
.tissue_tessellate
.cap_material_offset
1906 bridge_material_offset
= ob
.tissue_tessellate
.bridge_material_offset
1907 patch_subs
= ob
.tissue_tessellate
.patch_subs
1908 use_origin_offset
= ob
.tissue_tessellate
.use_origin_offset
1909 vertex_group_thickness
= ob
.tissue_tessellate
.vertex_group_thickness
1910 invert_vertex_group_thickness
= ob
.tissue_tessellate
.invert_vertex_group_thickness
1911 vertex_group_thickness_factor
= ob
.tissue_tessellate
.vertex_group_thickness_factor
1912 vertex_group_distribution
= ob
.tissue_tessellate
.vertex_group_distribution
1913 invert_vertex_group_distribution
= ob
.tissue_tessellate
.invert_vertex_group_distribution
1914 vertex_group_distribution_factor
= ob
.tissue_tessellate
.vertex_group_distribution_factor
1915 vertex_group_cap_owner
= ob
.tissue_tessellate
.vertex_group_cap_owner
1916 vertex_group_cap
= ob
.tissue_tessellate
.vertex_group_cap
1917 invert_vertex_group_cap
= ob
.tissue_tessellate
.invert_vertex_group_cap
1918 vertex_group_bridge_owner
= ob
.tissue_tessellate
.vertex_group_bridge_owner
1919 vertex_group_bridge
= ob
.tissue_tessellate
.vertex_group_bridge
1920 invert_vertex_group_bridge
= ob
.tissue_tessellate
.invert_vertex_group_bridge
1921 vertex_group_rotation
= ob
.tissue_tessellate
.vertex_group_rotation
1922 invert_vertex_group_rotation
= ob
.tissue_tessellate
.invert_vertex_group_rotation
1923 vertex_group_smooth_normals
= ob
.tissue_tessellate
.vertex_group_smooth_normals
1924 invert_vertex_group_smooth_normals
= ob
.tissue_tessellate
.invert_vertex_group_smooth_normals
1925 target
= ob
.tissue_tessellate
.target
1926 even_thickness
= ob
.tissue_tessellate
.even_thickness
1927 even_thickness_iter
= ob
.tissue_tessellate
.even_thickness_iter
1928 component_mode
= ob
.tissue_tessellate
.component_mode
1929 component_coll
= ob
.tissue_tessellate
.component_coll
1930 coll_rand_seed
= ob
.tissue_tessellate
.coll_rand_seed
1933 if component_mode
== 'OBJECT':
1936 self
.report({'ERROR'},
1937 "Active object must be Tessellated before Update")
1938 return {'CANCELLED'}
1941 ob
.tissue_tessellate
.warning_message_merge
= ''
1943 tess_props
= props_to_dict(ob
)
1945 # Solve Local View issues
1949 for area
in context
.screen
.areas
:
1950 for space
in area
.spaces
:
1952 if ob
.local_view_get(space
):
1953 local_spaces
.append(space
)
1954 local_ob0
= ob0
.local_view_get(space
)
1955 ob0
.local_view_set(space
, True)
1956 local_ob1
= ob1
.local_view_get(space
)
1957 ob1
.local_view_set(space
, True)
1961 starting_mode
= context
.object.mode
1963 #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
1964 if bpy
.ops
.object.mode_set
.poll():
1965 bpy
.ops
.object.mode_set(mode
='OBJECT')
1969 ##### auto_layer_collection()
1971 ob0_hide
= ob0
.hide_get()
1972 ob0_hidev
= ob0
.hide_viewport
1973 ob0_hider
= ob0
.hide_render
1975 ob0
.hide_viewport
= False
1976 ob0
.hide_render
= False
1977 if component_mode
== 'OBJECT':
1978 ob1_hide
= ob1
.hide_get()
1979 ob1_hidev
= ob1
.hide_viewport
1980 ob1_hider
= ob1
.hide_render
1982 ob1
.hide_viewport
= False
1983 ob1
.hide_render
= False
1986 if component_mode
== 'COLLECTION':
1987 dict_components
= {}
1989 for _ob1
in component_coll
.objects
:
1990 if _ob1
== ob
: continue
1991 if _ob1
.type in ('MESH', 'CURVE','SURFACE','FONT','META'):
1992 if _ob1
.type == 'META':
1993 if meta_object
: meta_object
= False
1995 dict_components
[_ob1
.name
] = _ob1
1996 for k
in sorted(dict_components
):
1997 components
.append(dict_components
[k
])
1998 elif component_mode
== 'OBJECT':
1999 components
.append(ob1
)
2001 if ob0
.type == 'META':
2002 base_ob
= convert_object_to_mesh(ob0
, False, True)
2004 base_ob
= ob0
.copy()
2005 base_ob
.data
= ob0
.data
2006 context
.collection
.objects
.link(base_ob
)
2007 base_ob
.name
= '_tissue_tmp_base'
2009 # In Blender 2.80 cache of copied objects is lost, must be re-baked
2010 bool_update_cloth
= False
2011 for m
in base_ob
.modifiers
:
2012 if m
.type == 'CLOTH':
2013 m
.point_cache
.frame_end
= context
.scene
.frame_current
2014 bool_update_cloth
= True
2015 if bool_update_cloth
:
2016 scene
= context
.scene
2017 for mod
in base_ob
.modifiers
:
2018 if mod
.type == 'CLOTH':
2019 override
= {'scene': scene
, 'active_object': base_ob
, 'point_cache': mod
.point_cache
}
2020 bpy
.ops
.ptcache
.bake(override
, bake
=True)
2022 base_ob
.modifiers
.update()
2024 # clear vertex groups before creating new ones
2025 if ob
not in components
: ob
.vertex_groups
.clear()
2028 faces
= base_ob
.data
.polygons
2029 selections
= [False]*len(faces
)
2030 faces
.foreach_get('select',selections
)
2031 selections
= np
.array(selections
)
2032 if not selections
.any():
2033 message
= "There are no faces selected."
2034 context
.view_layer
.objects
.active
= ob
2036 bpy
.ops
.object.mode_set(mode
=starting_mode
)
2037 remove_temp_objects()
2038 self
.report({'ERROR'}, message
)
2039 return {'CANCELLED'}
2041 iter_objects
= [base_ob
]
2042 ob_location
= ob
.location
2043 ob_matrix_world
= ob
.matrix_world
2045 #if ob not in components:
2046 ob
.data
.clear_geometry() # Faster with heavy geometries (from previous tessellations)
2048 for iter in range(iterations
):
2049 tess_props
['generator'] = base_ob
2051 if iter > 0 and len(iter_objects
) == 0: break
2052 if iter > 0 and normals_mode
in ('SHAPEKEYS','OBJECT'):
2053 tess_props
['normals_mode'] = 'VERTS'
2055 matched_materials
= []
2057 if component_mode
== 'MATERIALS':
2059 objects_keys
= bpy
.data
.objects
.keys()
2060 for mat_slot
in base_ob
.material_slots
:
2061 mat_name
= mat_slot
.material
.name
2062 if mat_name
in objects_keys
:
2063 ob1
= bpy
.data
.objects
[mat_name
]
2064 if ob1
.type in ('MESH', 'CURVE','SURFACE','FONT','META'):
2065 components
.append(bpy
.data
.objects
[mat_name
])
2066 matched_materials
.append(mat_name
)
2068 components
.append(None)
2070 components
.append(None)
2071 tess_props
['component'] = components
2072 # patch subdivisions for additional iterations
2073 if iter > 0 and fill_mode
== 'PATCH':
2074 temp_mod
= base_ob
.modifiers
.new('Tissue_Subsurf', type='SUBSURF')
2075 temp_mod
.levels
= patch_subs
2077 # patch tessellation
2078 tissue_time(None,"Tessellate iteration...",levels
=1)
2080 same_iteration
= tessellate_patch(tess_props
)
2081 tissue_time(tt
, "Tessellate iteration",levels
=1)
2085 # if empty or error, continue
2086 #if type(same_iteration) != list:#is not bpy.types.Object and :
2087 # return {'CANCELLED'}
2089 for id, new_ob
in enumerate(same_iteration
):
2090 # rename, make active and change transformations
2091 new_ob
.name
= '_tissue_tmp_{}_{}'.format(iter,id)
2092 new_ob
.select_set(True)
2093 context
.view_layer
.objects
.active
= new_ob
2094 new_ob
.location
= ob_location
2095 new_ob
.matrix_world
= ob_matrix_world
2097 base_ob
.location
= ob_location
2098 base_ob
.matrix_world
= ob_matrix_world
2099 # join together multiple components iterations
2100 if type(same_iteration
) == list:
2101 if len(same_iteration
) == 0:
2102 remove_temp_objects()
2103 tissue_time(None,"Can't Tessellate :-(",levels
=0)
2104 return {'CANCELLED'}
2105 if len(same_iteration
) > 1:
2106 #join_objects(context, same_iteration)
2107 new_ob
= join_objects(same_iteration
)
2109 if type(same_iteration
) in (int,str):
2110 new_ob
= same_iteration
2113 bpy
.data
.objects
.remove(iter_objects
[0])
2118 # Clean last iteration, needed for combine object
2119 if (bool_selection
or bool_material_id
) and combine_mode
== 'UNUSED':
2120 # remove faces from last mesh
2122 if (fill_mode
== 'PATCH' or gen_modifiers
) and iter == 0:
2123 last_mesh
= simple_to_mesh(base_ob
)#(ob0)
2125 last_mesh
= iter_objects
[-1].data
.copy()
2126 bm
.from_mesh(last_mesh
)
2127 bm
.faces
.ensure_lookup_table()
2128 if component_mode
== 'MATERIALS':
2129 remove_materials
= matched_materials
2130 elif bool_material_id
:
2131 remove_materials
= [material_id
]
2132 else: remove_materials
= []
2134 if component_mode
== 'MATERIALS' or bool_material_id
:
2135 remove_faces
= [f
for f
in bm
.faces
if f
.material_index
in remove_materials
and f
.select
]
2137 remove_faces
= [f
for f
in bm
.faces
if f
.select
]
2139 remove_faces
= [f
for f
in bm
.faces
if f
.material_index
in remove_materials
]
2140 bmesh
.ops
.delete(bm
, geom
=remove_faces
, context
='FACES')
2141 bm
.to_mesh(last_mesh
)
2144 last_mesh
.name
= '_tissue_tmp_previous_unused'
2146 # delete previous iteration if empty or update it
2147 if len(last_mesh
.vertices
) > 0:
2148 iter_objects
[-1].data
= last_mesh
.copy()
2149 iter_objects
[-1].data
.update()
2151 bpy
.data
.objects
.remove(iter_objects
[-1])
2152 iter_objects
= iter_objects
[:-1]
2153 # set new base object for next iteration
2154 base_ob
= convert_object_to_mesh(new_ob
,True,True)
2155 if iter < iterations
-1: new_ob
.data
= base_ob
.data
2156 # store new iteration and set transformations
2157 iter_objects
.append(new_ob
)
2158 base_ob
.name
= '_tissue_tmp_base'
2159 elif combine_mode
== 'ALL':
2160 base_ob
= new_ob
.copy()
2161 iter_objects
= [new_ob
] + iter_objects
2163 if base_ob
!= new_ob
:
2164 bpy
.data
.objects
.remove(base_ob
)
2166 iter_objects
= [new_ob
]
2168 if iter > 0:# and fill_mode == 'PATCH':
2169 base_ob
.modifiers
.clear()#remove(temp_mod)
2172 if combine_mode
!= 'LAST' and len(iter_objects
) > 1:
2173 if base_ob
not in iter_objects
and type(base_ob
) == bpy
.types
.Object
:
2174 bpy
.data
.objects
.remove(base_ob
)
2175 new_ob
= join_objects(iter_objects
)
2176 new_ob
.modifiers
.clear()
2177 iter_objects
= [new_ob
]
2179 tissue_time(tt
, "Combine tessellations", levels
=1)
2182 new_ob
.active_shape_key_index
= 0
2183 use_bmesh
= not (bool_shapekeys
and fill_mode
== 'PATCH' and component_mode
!= 'OBJECT')
2184 merged
= merge_components(new_ob
, ob
.tissue_tessellate
, use_bmesh
)
2185 if merged
== 'bridge_error':
2186 message
= "Can't make the bridge!"
2187 ob
.tissue_tessellate
.warning_message_merge
= message
2189 base_ob
= new_ob
#context.view_layer.objects.active
2194 #bpy.data.objects.remove(base_ob.data)
2195 try: bpy
.data
.objects
.remove(base_ob
)
2197 message
= "The generated object is an empty geometry!"
2198 context
.view_layer
.objects
.active
= ob
2200 bpy
.ops
.object.mode_set(mode
=starting_mode
)
2201 self
.report({'ERROR'}, message
)
2202 return {'CANCELLED'}
2204 errors
["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \
2205 "after the last Subsurf (or Multires) are not allowed."
2206 if new_ob
in errors
:
2207 for o
in iter_objects
:
2208 try: bpy
.data
.objects
.remove(o
)
2210 try: bpy
.data
.meshes
.remove(data1
)
2212 context
.view_layer
.objects
.active
= ob
2214 message
= errors
[new_ob
]
2215 ob
.tissue_tessellate
.error_message
= message
2216 bpy
.ops
.object.mode_set(mode
=starting_mode
)
2217 self
.report({'ERROR'}, message
)
2218 return {'CANCELLED'}
2220 # update data and preserve name
2221 if ob
.type != 'MESH':
2222 loc
, matr
= ob
.location
, ob
.matrix_world
2223 ob
= convert_object_to_mesh(ob
,False,True)
2224 ob
.location
, ob
.matrix_world
= loc
, matr
2225 data_name
= ob
.data
.name
2227 old_data
.name
= '_tissue_tmp_old_data'
2228 #ob.data = bpy.data.meshes.new_from_object(new_ob)#
2229 linked_objects
= [o
for o
in bpy
.data
.objects
if o
.data
== old_data
]
2231 for o
in linked_objects
:
2232 o
.data
= new_ob
.data
2233 if len(linked_objects
) > 1:
2234 copy_tessellate_props(ob
, o
)
2236 #ob.data = new_ob.data
2237 ob
.data
.name
= data_name
2238 bpy
.data
.meshes
.remove(old_data
)
2241 for vg
in new_ob
.vertex_groups
:
2242 if not vg
.name
in ob
.vertex_groups
.keys():
2243 ob
.vertex_groups
.new(name
=vg
.name
)
2245 selected_objects
= [o
for o
in context
.selected_objects
]
2246 for o
in selected_objects
: o
.select_set(False)
2249 context
.view_layer
.objects
.active
= ob
2251 is_multiple
= iterations
> 1 or combine_mode
!= 'LAST'# or bool_multi_components
2252 if merge
and is_multiple
:
2253 use_bmesh
= not (bool_shapekeys
and fill_mode
== 'PATCH' and component_mode
!= 'OBJECT')
2254 merge_components(new_ob
, ob
.tissue_tessellate
, use_bmesh
)
2256 if bool_smooth
: bpy
.ops
.object.shade_smooth()
2258 for mesh
in bpy
.data
.meshes
:
2259 if not mesh
.users
: bpy
.data
.meshes
.remove(mesh
)
2261 for o
in selected_objects
:
2262 try: o
.select_set(True)
2265 ob
.tissue_tessellate
.error_message
= ""
2267 # Restore Base visibility
2268 ob0
.hide_set(ob0_hide
)
2269 ob0
.hide_viewport
= ob0_hidev
2270 ob0
.hide_render
= ob0_hider
2271 # Restore Component visibility
2272 if component_mode
== 'OBJECT':
2273 ob1
.hide_set(ob1_hide
)
2274 ob1
.hide_viewport
= ob1_hidev
2275 ob1
.hide_render
= ob1_hider
2276 # Restore Local visibility
2277 for space
, local0
, local1
in zip(local_spaces
, local_ob0
, local_ob1
):
2278 ob0
.local_view_set(space
, local0
)
2279 ob1
.local_view_set(space
, local1
)
2281 bpy
.data
.objects
.remove(new_ob
)
2283 remove_temp_objects()
2285 tissue_time(tt
, "Closing tessellation", levels
=1)
2287 tissue_time(start_time
,'Tessellation of "{}"'.format(ob
.name
),levels
=0)
2290 def check(self
, context
):
2293 class TISSUE_PT_tessellate(Panel
):
2294 bl_label
= "Tissue Tools"
2295 bl_category
= "Tissue"
2296 bl_space_type
= "VIEW_3D"
2297 bl_region_type
= "UI"
2298 #bl_options = {'DEFAULT_OPEN'}
2301 def poll(cls
, context
):
2302 return context
.mode
in {'OBJECT', 'EDIT_MESH'}
2304 def draw(self
, context
):
2305 layout
= self
.layout
2307 col
= layout
.column(align
=True)
2308 col
.label(text
="Generate:")
2309 row
= col
.row(align
=True)
2310 row
.operator("object.tissue_tessellate", text
='Tessellate', icon
='OBJECT_DATA').component_mode
= 'OBJECT'
2311 tss
= row
.operator("object.tissue_tessellate", text
='', icon
='OUTLINER_COLLECTION')
2312 tss
.component_mode
= 'COLLECTION'
2313 tss
.component_coll
= context
.collection
.name
2314 row
.operator("object.tissue_tessellate", text
='', icon
='MATERIAL').component_mode
= 'MATERIALS'
2315 #col.operator("object.tissue_tessellate_multi", text='Tessellate Multi')
2316 col
.operator("object.dual_mesh_tessellated", text
='Dual Mesh', icon
='SEQ_CHROMA_SCOPE')
2319 #col.label(text="Curves:")
2320 col
.operator("object.tissue_convert_to_curve", icon
='OUTLINER_OB_CURVE', text
="Convert to Curve")
2321 #row.operator("object.tissue_convert_to_curve_update", icon='FILE_REFRESH', text='')
2324 col
.operator("object.tissue_update_tessellate_deps", icon
='FILE_REFRESH', text
='Refresh') #####
2327 col
.label(text
="Rotate Faces:")
2328 row
= col
.row(align
=True)
2329 row
.operator("mesh.tissue_rotate_face_left", text
='Left', icon
='LOOP_BACK')
2330 row
.operator("mesh.tissue_rotate_face_flip", text
='Flip', icon
='UV_SYNC_SELECT')
2331 row
.operator("mesh.tissue_rotate_face_right", text
='Right', icon
='LOOP_FORWARDS')
2334 col
.label(text
="Other:")
2335 col
.operator("object.dual_mesh", icon
='SEQ_CHROMA_SCOPE')
2336 col
.operator("object.polyhedra_wireframe", icon
='MOD_WIREFRAME', text
='Polyhedra Wireframe')
2337 col
.operator("object.lattice_along_surface", icon
="OUTLINER_OB_LATTICE")
2339 act
= context
.object
2340 if act
and act
.type == 'MESH':
2341 col
.operator("object.uv_to_mesh", icon
="UV")
2343 if act
.mode
== 'EDIT':
2345 col
.label(text
="Weight:")
2346 col
.operator("object.tissue_weight_distance", icon
="TRACKING")
2347 col
.operator("object.tissue_weight_streamlines", icon
="ANIM")
2350 col
.label(text
="Materials:")
2351 col
.operator("object.random_materials", icon
='COLOR')
2352 col
.operator("object.weight_to_materials", icon
='GROUP_VERTEX')
2355 col
.label(text
="Utils:")
2356 col
.operator("render.tissue_render_animation", icon
='RENDER_ANIMATION')
2358 class TISSUE_PT_tessellate_object(Panel
):
2359 bl_space_type
= 'PROPERTIES'
2360 bl_region_type
= 'WINDOW'
2362 bl_label
= "Tissue Tessellate"
2363 bl_options
= {'DEFAULT_CLOSED'}
2366 def poll(cls
, context
):
2368 return context
.object.type == 'MESH'
2369 except: return False
2371 def draw(self
, context
):
2373 props
= ob
.tissue_tessellate
2374 tissue_props
= ob
.tissue
2376 bool_tessellated
= tissue_props
.tissue_type
== 'TESSELLATE'
2377 layout
= self
.layout
2378 if not bool_tessellated
:
2379 layout
.label(text
="The selected object is not a Tessellated object",
2382 if props
.error_message
!= "":
2383 layout
.label(text
=props
.error_message
,
2385 col
= layout
.column(align
=True)
2386 row
= col
.row(align
=True)
2388 set_tessellate_handler(self
,context
)
2389 ###### set_animatable_fix_handler(self,context)
2390 row
.operator("object.tissue_update_tessellate_deps", icon
='FILE_REFRESH', text
='Refresh') ####
2391 lock_icon
= 'LOCKED' if tissue_props
.bool_lock
else 'UNLOCKED'
2392 #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
2393 deps_icon
= 'LINKED' if tissue_props
.bool_dependencies
else 'UNLINKED'
2394 row
.prop(tissue_props
, "bool_dependencies", text
="", icon
=deps_icon
)
2395 row
.prop(tissue_props
, "bool_lock", text
="", icon
=lock_icon
)
2396 col2
= row
.column(align
=True)
2397 col2
.prop(tissue_props
, "bool_run", text
="",icon
='TIME')
2398 col2
.enabled
= not tissue_props
.bool_lock
2399 #layout.use_property_split = True
2400 #layout.use_property_decorate = False # No animation.
2401 col
= layout
.column(align
=True)
2402 col
.label(text
='Base object:')
2403 row
= col
.row(align
=True)
2404 row
.prop_search(props
, "generator", context
.scene
, "objects")
2405 col2
= row
.column(align
=True)
2406 col2
.prop(props
, "gen_modifiers", text
='Use Modifiers',icon
='MODIFIER')
2409 if not (props.generator.modifiers or props.generator.data.shape_keys):
2410 col2.enabled = False
2412 col2.enabled = False
2416 layout
.use_property_split
= False
2418 col
= layout
.column(align
=True)
2419 col
.label(text
="Fill Mode:")
2422 row
= col
.row(align
=True)
2423 row
.prop(props
, "fill_mode", icon
='NONE', expand
=True,
2424 slider
=True, toggle
=False, icon_only
=False, event
=False,
2425 full_event
=False, emboss
=True, index
=-1)
2427 #layout.use_property_split = True
2428 col
= layout
.column(align
=True)
2429 col
.prop(props
, "bool_smooth")
2432 class TISSUE_PT_tessellate_frame(Panel
):
2433 bl_space_type
= 'PROPERTIES'
2434 bl_region_type
= 'WINDOW'
2436 bl_parent_id
= "TISSUE_PT_tessellate_object"
2437 bl_label
= "Frame Settings"
2438 #bl_options = {'DEFAULT_CLOSED'}
2441 def poll(cls
, context
):
2443 bool_frame
= context
.object.tissue_tessellate
.fill_mode
== 'FRAME'
2444 bool_tessellated
= context
.object.tissue_tessellate
.generator
!= None
2445 return context
.object.type == 'MESH' and bool_frame
and bool_tessellated
2449 def draw(self
, context
):
2451 props
= ob
.tissue_tessellate
2452 layout
= self
.layout
2453 col
= layout
.column(align
=True)
2454 row
= col
.row(align
=True)
2455 row
.prop(props
, "frame_mode", expand
=True)
2456 row
= col
.row(align
=True)
2457 row
.prop(props
, "frame_thickness", icon
='NONE', expand
=True)
2459 row
= col
.row(align
=True)
2460 row
.prop(props
, "fill_frame", icon
='NONE')
2461 show_frame_mat
= props
.component_mode
== 'MATERIALS' or props
.bool_material_id
2462 col2
= row
.column(align
=True)
2463 col2
.prop(props
, "fill_frame_mat", icon
='NONE')
2464 col2
.enabled
= props
.fill_frame
and show_frame_mat
2465 row
= col
.row(align
=True)
2466 row
.prop(props
, "frame_boundary", text
='Boundary', icon
='NONE')
2467 col2
= row
.column(align
=True)
2468 col2
.prop(props
, "boundary_mat_offset", icon
='NONE')
2469 col2
.enabled
= props
.frame_boundary
and show_frame_mat
2472 class TISSUE_PT_tessellate_component(Panel
):
2473 bl_space_type
= 'PROPERTIES'
2474 bl_region_type
= 'WINDOW'
2476 bl_parent_id
= "TISSUE_PT_tessellate_object"
2477 bl_label
= "Components"
2478 #bl_options = {'DEFAULT_CLOSED'}
2481 def poll(cls
, context
):
2483 bool_tessellated
= context
.object.tissue
.tissue_type
== 'TESSELLATE'
2484 return context
.object.type == 'MESH' and bool_tessellated
2488 def draw(self
, context
):
2490 props
= ob
.tissue_tessellate
2492 layout
= self
.layout
2493 col
= layout
.column(align
=True)
2494 col
.label(text
='Component Mode:')
2495 row
= col
.row(align
=True)
2496 row
.prop(props
, "component_mode", icon
='NONE', expand
=True,
2497 slider
=True, toggle
=False, icon_only
=False, event
=False,
2498 full_event
=False, emboss
=True, index
=-1)
2500 if props
.component_mode
== 'OBJECT':
2502 row
= col
.row(align
=True)
2503 row
.prop_search(props
, "component", context
.scene
, "objects")
2504 col2
= row
.column(align
=True)
2505 col2
.prop(props
, "com_modifiers", text
='Use Modifiers',icon
='MODIFIER')
2508 if not (props.component.modifiers or props.component.data.shape_keys):
2509 col2.enabled = False
2511 col2.enabled = False
2513 elif props
.component_mode
== 'COLLECTION':
2516 if props
.component_coll
in list(bpy
.data
.collections
):
2518 for o
in props
.component_coll
.objects
:
2519 if o
.type in allowed_objects() and o
is not ob
:
2520 components
.append(o
.name
)
2521 n_comp
= len(components
)
2523 col
.label(text
="Can't find components in the Collection.", icon
='ERROR')
2525 text
= "{} Component{}".format(n_comp
,"s" if n_comp
>1 else "")
2526 row
= col
.row(align
=True)
2527 row
.label(text
=text
, icon
='OBJECT_DATA')
2528 row
.prop(props
, "com_modifiers", text
='Use Modifiers',icon
='MODIFIER')
2530 col
.label(text
="Please, chose one Collection.", icon
='ERROR')
2533 row
= col
.row(align
=True)
2534 row
.prop_search(props
,'component_coll',bpy
.data
,'collections')
2535 col2
= row
.column(align
=True)
2536 col2
.prop(props
, "coll_rand_seed")
2537 col
= layout
.column(align
=True)
2538 row
= col
.row(align
=True)
2539 ob0
= props
.generator
2540 row
.prop_search(props
, 'vertex_group_distribution',
2541 ob0
, "vertex_groups", text
='')
2542 col2
= row
.column(align
=True)
2543 row2
= col2
.row(align
=True)
2544 row2
.prop(props
, "invert_vertex_group_distribution", text
="",
2545 toggle
=True, icon
='ARROW_LEFTRIGHT')
2546 row2
.prop(props
, "vertex_group_distribution_factor")
2547 row2
.enabled
= props
.vertex_group_distribution
in ob0
.vertex_groups
.keys()
2548 if props
.fill_mode
== 'FAN': col
.prop(props
, "consistent_wedges")
2551 for mat
in props
.generator
.material_slots
.keys():
2552 if mat
in bpy
.data
.objects
.keys():
2553 if bpy
.data
.objects
[mat
].type in allowed_objects():
2554 components
.append(mat
)
2555 n_comp
= len(components
)
2557 col
.label(text
="Can't find components from the materials.", icon
='ERROR')
2560 text
= "{} Component{}".format(n_comp
,"s" if n_comp
>1 else "")
2561 row
= col
.row(align
=True)
2562 row
.label(text
=text
, icon
='OBJECT_DATA')
2563 row
.prop(props
, "com_modifiers", text
='Use Modifiers',icon
='MODIFIER')
2565 if props
.fill_mode
!= 'FRAME':
2568 row
= col
.row(align
=True)
2569 row
.label(text
="Boundary Faces:")
2570 row
.prop(props
, "boundary_mat_offset", icon
='NONE')
2571 row
= col
.row(align
=True)
2572 row
.prop(props
, "boundary_variable_offset", text
='Variable Offset', icon
='NONE')
2573 row
.prop(props
, "auto_rotate_boundary", icon
='NONE')
2576 class TISSUE_PT_tessellate_coordinates(Panel
):
2577 bl_space_type
= 'PROPERTIES'
2578 bl_region_type
= 'WINDOW'
2580 bl_parent_id
= "TISSUE_PT_tessellate_object"
2581 bl_label
= "Components Coordinates"
2582 bl_options
= {'DEFAULT_CLOSED'}
2585 def poll(cls
, context
):
2587 bool_tessellated
= context
.object.tissue
.tissue_type
== 'TESSELLATE'
2588 return context
.object.type == 'MESH' and bool_tessellated
2592 def draw(self
, context
):
2594 props
= ob
.tissue_tessellate
2595 layout
= self
.layout
2597 col
= layout
.column(align
=True)
2599 row
= col
.row(align
=True)
2600 row
.prop(props
, "mode", expand
=True)
2602 if props
.mode
!= 'BOUNDS':
2604 row
= col
.row(align
=True)
2605 row
.label(text
="X:")
2607 props
, "bounds_x", text
="Bounds X", icon
='NONE', expand
=True,
2608 slider
=False, toggle
=False, icon_only
=False, event
=False,
2609 full_event
=False, emboss
=True, index
=-1)
2611 row
= col
.row(align
=True)
2612 row
.label(text
="Y:")
2614 props
, "bounds_y", text
="Bounds X", icon
='NONE', expand
=True,
2615 slider
=False, toggle
=False, icon_only
=False, event
=False,
2616 full_event
=False, emboss
=True, index
=-1)
2619 class TISSUE_PT_tessellate_rotation(Panel
):
2620 bl_space_type
= 'PROPERTIES'
2621 bl_region_type
= 'WINDOW'
2623 bl_parent_id
= "TISSUE_PT_tessellate_object"
2624 bl_label
= "Rotation"
2625 bl_options
= {'DEFAULT_CLOSED'}
2628 def poll(cls
, context
):
2630 bool_tessellated
= context
.object.tissue
.tissue_type
== 'TESSELLATE'
2631 return context
.object.type == 'MESH' and bool_tessellated
2635 def draw(self
, context
):
2637 props
= ob
.tissue_tessellate
2638 layout
= self
.layout
2640 layout
.use_property_split
= True
2641 layout
.use_property_decorate
= False # No animation.
2642 col
= layout
.column(align
=True)
2643 col
.prop(props
, "rotation_mode", text
='Rotation', icon
='NONE', expand
=False,
2644 slider
=True, toggle
=False, icon_only
=False, event
=False,
2645 full_event
=False, emboss
=True, index
=-1)
2646 if props
.rotation_mode
== 'WEIGHT':
2648 row
= col
.row(align
=True)
2652 row
.prop_search(props
, 'vertex_group_rotation',
2653 ob0
, "vertex_groups", text
='Vertex Group')
2654 col2
= row
.column(align
=True)
2655 col2
.prop(props
, "invert_vertex_group_rotation", text
="", toggle
=True, icon
='ARROW_LEFTRIGHT')
2656 col2
.enabled
= props
.vertex_group_rotation
in ob0
.vertex_groups
.keys()
2658 col
.prop(props
, "rotation_direction", expand
=False,
2659 slider
=True, toggle
=False, icon_only
=False, event
=False,
2660 full_event
=False, emboss
=True, index
=-1)
2661 if props
.rotation_mode
== 'RANDOM':
2662 col
.prop(props
, "rand_seed")
2663 col
.prop(props
, "rand_step")
2665 col
.prop(props
, "rotation_shift")
2667 if props
.rotation_mode
== 'UV':
2669 if props
.generator
.type != 'MESH':
2670 row
= col
.row(align
=True)
2672 text
="UV rotation supported only for Mesh objects",
2676 if len(props
.generator
.data
.uv_layers
) == 0:
2677 row
= col
.row(align
=True)
2678 row
.label(text
="'" + props
.generator
.name
+
2679 " doesn't have UV Maps", icon
='ERROR')
2682 row
= col
.row(align
=True)
2683 row
.label(text
="Default rotation will be used instead",
2686 class TISSUE_PT_tessellate_thickness(Panel
):
2687 bl_space_type
= 'PROPERTIES'
2688 bl_region_type
= 'WINDOW'
2690 bl_parent_id
= "TISSUE_PT_tessellate_object"
2691 bl_label
= "Thickness"
2692 #bl_options = {'DEFAULT_CLOSED'}
2695 def poll(cls
, context
):
2696 try: return context
.object.tissue
.tissue_type
== 'TESSELLATE'
2697 except: return False
2699 def draw(self
, context
):
2701 props
= ob
.tissue_tessellate
2703 layout
= self
.layout
2704 #layout.use_property_split = True
2705 col
= layout
.column(align
=True)
2707 row
= col
.row(align
=True)
2708 row
.prop(props
, "scale_mode", expand
=True)
2709 col
.prop(props
, "zscale", text
="Scale", icon
='NONE', expand
=False,
2710 slider
=True, toggle
=False, icon_only
=False, event
=False,
2711 full_event
=False, emboss
=True, index
=-1)
2712 if props
.mode
== 'BOUNDS':
2713 row
= col
.row(align
=True)
2714 row
.prop(props
, "offset", text
="Offset", icon
='NONE', expand
=False,
2715 slider
=True, toggle
=False, icon_only
=False, event
=False,
2716 full_event
=False, emboss
=True, index
=-1)
2717 row
.enabled
= not props
.use_origin_offset
2718 col
.prop(props
, 'use_origin_offset')
2721 row
= col
.row(align
=True)
2722 ob0
= props
.generator
2723 row
.prop_search(props
, 'vertex_group_thickness',
2724 ob0
, "vertex_groups", text
='')
2725 col2
= row
.column(align
=True)
2726 row2
= col2
.row(align
=True)
2727 row2
.prop(props
, "invert_vertex_group_thickness", text
="",
2728 toggle
=True, icon
='ARROW_LEFTRIGHT')
2729 row2
.prop(props
, "vertex_group_thickness_factor")
2730 row2
.enabled
= props
.vertex_group_thickness
in ob0
.vertex_groups
.keys()
2732 class TISSUE_PT_tessellate_direction(Panel
):
2733 bl_space_type
= 'PROPERTIES'
2734 bl_region_type
= 'WINDOW'
2736 bl_parent_id
= "TISSUE_PT_tessellate_object"
2737 bl_label
= "Thickness Direction"
2738 bl_options
= {'DEFAULT_CLOSED'}
2741 def poll(cls
, context
):
2743 return context
.object.tissue
.tissue_type
== 'TESSELLATE'
2747 def draw(self
, context
):
2749 props
= ob
.tissue_tessellate
2750 layout
= self
.layout
2751 ob0
= props
.generator
2752 #layout.use_property_split = True
2753 col
= layout
.column(align
=True)
2754 row
= col
.row(align
=True)
2756 props
, "normals_mode", text
="Direction", icon
='NONE', expand
=True,
2757 slider
=False, toggle
=False, icon_only
=False, event
=False,
2758 full_event
=False, emboss
=True, index
=-1)
2759 if props
.normals_mode
== 'OBJECT':
2761 row
= col
.row(align
=True)
2762 row
.prop_search(props
, "target", context
.scene
, "objects", text
='Target')
2763 if props
.warning_message_thickness
!= '':
2765 col
.label(text
=props
.warning_message_thickness
, icon
='ERROR')
2766 if props
.normals_mode
!= 'FACES':
2768 col
.prop(props
, "smooth_normals")
2769 if props
.smooth_normals
:
2770 row
= col
.row(align
=True)
2771 row
.prop(props
, "smooth_normals_iter")
2773 row
.prop_search(props
, 'vertex_group_smooth_normals',
2774 ob0
, "vertex_groups", text
='')
2775 col2
= row
.column(align
=True)
2776 col2
.prop(props
, "invert_vertex_group_smooth_normals", text
="", toggle
=True, icon
='ARROW_LEFTRIGHT')
2777 col2
.enabled
= props
.vertex_group_smooth_normals
in ob0
.vertex_groups
.keys()
2778 if props
.normals_mode
== 'VERTS':
2780 row
= col
.row(align
=True)
2781 row
.prop(props
, "normals_x")
2782 row
.prop(props
, "normals_y")
2783 row
.prop(props
, "normals_z")
2784 row
= col
.row(align
=True)
2785 row
.prop_search(props
, 'vertex_group_scale_normals',
2786 ob0
, "vertex_groups", text
='')
2787 col2
= row
.column(align
=True)
2788 col2
.prop(props
, "invert_vertex_group_scale_normals", text
="", toggle
=True, icon
='ARROW_LEFTRIGHT')
2789 col2
.enabled
= props
.vertex_group_scale_normals
in ob0
.vertex_groups
.keys()
2790 if props
.normals_mode
in ('OBJECT', 'SHAPEKEYS'):
2792 row
= col
.row(align
=True)
2793 row
.prop(props
, "even_thickness")
2794 if props
.even_thickness
: row
.prop(props
, "even_thickness_iter")
2796 class TISSUE_PT_tessellate_options(Panel
):
2797 bl_space_type
= 'PROPERTIES'
2798 bl_region_type
= 'WINDOW'
2800 bl_parent_id
= "TISSUE_PT_tessellate_object"
2802 bl_options
= {'DEFAULT_CLOSED'}
2805 def poll(cls
, context
):
2807 return context
.object.tissue
.tissue_type
== 'TESSELLATE'
2811 def draw_header(self
, context
):
2813 props
= ob
.tissue_tessellate
2814 self
.layout
.prop(props
, "merge")
2816 def draw(self
, context
):
2818 props
= ob
.tissue_tessellate
2819 layout
= self
.layout
2820 layout
.use_property_split
= True
2821 layout
.use_property_decorate
= False # No animation.
2822 col
= layout
.column(align
=True)
2824 col
.prop(props
, "merge_thres")
2825 col
.prop(props
, "merge_open_edges_only")
2826 col
.prop(props
, "bool_dissolve_seams")
2827 col
.prop(props
, "close_mesh")
2828 if props
.close_mesh
in ('BRIDGE', 'BRIDGE_CAP'):
2830 if props
.close_mesh
== 'BRIDGE_CAP':
2831 if props
.vertex_group_bridge_owner
== 'BASE': ob_bridge
= ob0
2832 else: ob_bridge
= ob1
2833 row
= col
.row(align
=True)
2834 row
.prop_search(props
, 'vertex_group_bridge',
2835 ob_bridge
, "vertex_groups")
2836 row
.prop(props
, "invert_vertex_group_bridge", text
="",
2837 toggle
=True, icon
='ARROW_LEFTRIGHT')
2838 row
= col
.row(align
=True)
2839 row
.prop(props
, "vertex_group_bridge_owner", expand
=True,
2840 slider
=False, toggle
=False, icon_only
=False, event
=False,
2841 full_event
=False, emboss
=True, index
=-1)
2842 col2
= row
.column(align
=True)
2843 row2
= col2
.row(align
=True)
2844 col
.prop(props
, "bridge_edges_crease", text
="Crease")
2845 col
.prop(props
, "bridge_material_offset", text
='Material Offset')
2847 if props.close_mesh == 'BRIDGE' and False:
2849 col.prop(props, "bridge_cuts")
2850 col.prop(props, "bridge_smoothness")
2852 if props
.close_mesh
in ('CAP', 'BRIDGE_CAP'):
2853 #row = col.row(align=True)
2855 if props
.close_mesh
== 'BRIDGE_CAP':
2856 if props
.vertex_group_cap_owner
== 'BASE': ob_cap
= ob0
2858 row
= col
.row(align
=True)
2859 row
.prop_search(props
, 'vertex_group_cap',
2860 ob_cap
, "vertex_groups")
2861 row
.prop(props
, "invert_vertex_group_cap", text
="",
2862 toggle
=True, icon
='ARROW_LEFTRIGHT')
2863 row
= col
.row(align
=True)
2864 row
.prop(props
, "vertex_group_cap_owner", expand
=True,
2865 slider
=False, toggle
=False, icon_only
=False, event
=False,
2866 full_event
=False, emboss
=True, index
=-1)
2867 col
.prop(props
, "open_edges_crease", text
="Crease")
2868 col
.prop(props
, "cap_material_offset", text
='Material Offset')
2869 if props
.warning_message_merge
:
2871 col
.label(text
=props
.warning_message_merge
, icon
='ERROR')
2873 class TISSUE_PT_tessellate_morphing(Panel
):
2874 bl_space_type
= 'PROPERTIES'
2875 bl_region_type
= 'WINDOW'
2877 bl_parent_id
= "TISSUE_PT_tessellate_object"
2878 bl_label
= "Weight and Morphing"
2879 bl_options
= {'DEFAULT_CLOSED'}
2882 def poll(cls
, context
):
2883 try: return context
.object.tissue
.tissue_type
== 'TESSELLATE'
2884 except: return False
2886 def draw(self
, context
):
2888 props
= ob
.tissue_tessellate
2889 layout
= self
.layout
2890 allow_shapekeys
= not props
.com_modifiers
2893 ob0
= props
.generator
2894 for m
in ob0
.data
.materials
:
2896 o
= bpy
.data
.objects
[m
.name
]
2899 if o
.data
.shape_keys
is None: continue
2900 elif len(o
.data
.shape_keys
.key_blocks
) < 2: continue
2901 else: allow_shapekeys
= not props
.com_modifiers
2904 col
= layout
.column(align
=True)
2905 #col.label(text="Morphing:")
2906 row
= col
.row(align
=True)
2907 col2
= row
.column(align
=True)
2908 col2
.prop(props
, "bool_vertex_group", icon
='GROUP_VERTEX')
2909 #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
2911 if len(props
.generator
.vertex_groups
) == 0:
2912 col2
.enabled
= False
2914 col2
.enabled
= False
2916 col2
= row
.column(align
=True)
2917 row2
= col2
.row(align
=True)
2918 row2
.prop(props
, "bool_shapekeys", text
="Use Shape Keys", icon
='SHAPEKEY_DATA')
2919 row2
.enabled
= allow_shapekeys
2920 if not allow_shapekeys
:
2921 col2
= layout
.column(align
=True)
2922 row2
= col2
.row(align
=True)
2923 row2
.label(text
="Component's Shape Keys cannot be used together with Component's Modifiers", icon
='INFO')
2926 class TISSUE_PT_tessellate_selective(Panel
):
2927 bl_space_type
= 'PROPERTIES'
2928 bl_region_type
= 'WINDOW'
2930 bl_parent_id
= "TISSUE_PT_tessellate_object"
2931 bl_label
= "Selective"
2932 bl_options
= {'DEFAULT_CLOSED'}
2935 def poll(cls
, context
):
2937 return context
.object.tissue
.tissue_type
== 'TESSELLATE'
2941 def draw(self
, context
):
2943 props
= ob
.tissue_tessellate
2945 layout
= self
.layout
2946 #layout.use_property_split = True
2947 #layout.use_property_decorate = False # No animation.
2949 allow_shapekeys
= not props
.com_modifiers
2950 ob0
= props
.generator
2951 for m
in ob0
.data
.materials
:
2953 o
= bpy
.data
.objects
[m
.name
]
2956 if o
.data
.shape_keys
is None: continue
2957 elif len(o
.data
.shape_keys
.key_blocks
) < 2: continue
2958 else: allow_shapekeys
= not props
.com_modifiers
2961 # LIMITED TESSELLATION
2962 col
= layout
.column(align
=True)
2963 #col.label(text="Limited Tessellation:")
2964 row
= col
.row(align
=True)
2965 col2
= row
.column(align
=True)
2966 col2
.prop(props
, "bool_selection", text
="On selected Faces", icon
='RESTRICT_SELECT_OFF')
2968 if props
.generator
.type != 'MESH':
2969 col2
.enabled
= False
2970 col2
= row
.column(align
=True)
2971 col2
.prop(props
, "bool_material_id", icon
='MATERIAL_DATA', text
="Material Index")
2972 #if props.bool_material_id and not props.component_mode == 'MATERIALS':
2973 #col2 = row.column(align=True)
2974 col2
.prop(props
, "material_id")
2975 #if props.component_mode == 'MATERIALS':
2976 # col2.enabled = False
2979 #row = col.row(align=True)
2980 #col2 = row.column(align=True)
2981 #col2.prop(props, "bool_multi_components", icon='MOD_TINT')
2982 #if not allow_multi:
2983 # col2.enabled = False
2986 class TISSUE_PT_tessellate_iterations(Panel
):
2987 bl_space_type
= 'PROPERTIES'
2988 bl_region_type
= 'WINDOW'
2990 bl_parent_id
= "TISSUE_PT_tessellate_object"
2991 bl_label
= "Iterations"
2992 bl_options
= {'DEFAULT_CLOSED'}
2995 def poll(cls
, context
):
2997 return context
.object.tissue
.tissue_type
== 'TESSELLATE'
3001 def draw(self
, context
):
3003 props
= ob
.tissue_tessellate
3004 layout
= self
.layout
3005 layout
.use_property_split
= True
3006 layout
.use_property_decorate
= False # No animation.
3007 col
= layout
.column(align
=True)
3008 row
= col
.row(align
=True)
3009 #row.label(text='', icon='FILE_REFRESH')
3010 col
.prop(props
, 'iterations', text
='Repeat')#, icon='FILE_REFRESH')
3011 if props
.iterations
> 1 and props
.fill_mode
== 'PATCH':
3013 #row = col.row(align=True)
3014 col
.prop(props
, 'patch_subs')
3015 layout
.use_property_split
= False
3016 col
= layout
.column(align
=True)
3017 #row = col.row(align=True)
3018 col
.label(text
='Combine Iterations:')
3019 row
= col
.row(align
=True)
3021 props
, "combine_mode", text
="Combine:",icon
='NONE', expand
=True,
3022 slider
=False, toggle
=False, icon_only
=False, event
=False,
3023 full_event
=False, emboss
=True, index
=-1)
3025 class tissue_rotate_face_right(Operator
):
3026 bl_idname
= "mesh.tissue_rotate_face_right"
3027 bl_label
= "Tissue Rotate Faces Right"
3028 bl_description
= "Rotate clockwise selected faces and update tessellated meshes"
3029 bl_options
= {'REGISTER', 'UNDO'}
3032 def poll(cls
, context
):
3034 #bool_tessellated = context.object.tissue_tessellate.generator != None
3036 return ob
.type == 'MESH' and ob
.mode
== 'EDIT'# and bool_tessellated
3040 def execute(self
, context
):
3041 ob
= context
.active_object
3044 bm
= bmesh
.from_edit_mesh(me
)
3045 mesh_select_mode
= [sm
for sm
in context
.tool_settings
.mesh_select_mode
]
3047 for face
in bm
.faces
:
3050 vs2
= vs
[-1:]+vs
[:-1]
3051 material_index
= face
.material_index
3052 bm
.faces
.remove(face
)
3053 f2
= bm
.faces
.new(vs2
)
3055 f2
.material_index
= material_index
3059 bmesh
.update_edit_mesh(me
)
3061 ob
.select_set(False)
3063 # update tessellated meshes
3064 bpy
.ops
.object.mode_set(mode
='OBJECT')
3065 for o
in [obj
for obj
in bpy
.data
.objects
if
3066 obj
.tissue_tessellate
.generator
== ob
and obj
.visible_get()]:
3067 context
.view_layer
.objects
.active
= o
3069 #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]}
3070 if not o
.tissue
.bool_lock
:
3071 bpy
.ops
.object.tissue_update_tessellate()
3074 context
.view_layer
.objects
.active
= ob
3075 bpy
.ops
.object.mode_set(mode
='EDIT')
3076 context
.tool_settings
.mesh_select_mode
= mesh_select_mode
3080 class tissue_rotate_face_flip(Operator
):
3081 bl_idname
= "mesh.tissue_rotate_face_flip"
3082 bl_label
= "Tissue Rotate Faces Flip"
3083 bl_description
= "Fully rotate selected faces and update tessellated meshes"
3084 bl_options
= {'REGISTER', 'UNDO'}
3087 def poll(cls
, context
):
3089 #bool_tessellated = context.object.tissue_tessellate.generator != None
3091 return ob
.type == 'MESH' and ob
.mode
== 'EDIT'# and bool_tessellated
3095 def execute(self
, context
):
3096 ob
= context
.active_object
3099 bm
= bmesh
.from_edit_mesh(me
)
3100 mesh_select_mode
= [sm
for sm
in context
.tool_settings
.mesh_select_mode
]
3102 for face
in bm
.faces
:
3105 nrot
= int(len(vs
)/2)
3106 vs2
= vs
[-nrot
:]+vs
[:-nrot
]
3107 material_index
= face
.material_index
3108 bm
.faces
.remove(face
)
3109 f2
= bm
.faces
.new(vs2
)
3111 f2
.material_index
= material_index
3115 bmesh
.update_edit_mesh(me
)
3117 ob
.select_set(False)
3119 # update tessellated meshes
3120 bpy
.ops
.object.mode_set(mode
='OBJECT')
3121 for o
in [obj
for obj
in bpy
.data
.objects
if
3122 obj
.tissue_tessellate
.generator
== ob
and obj
.visible_get()]:
3123 context
.view_layer
.objects
.active
= o
3125 #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]}
3126 if not o
.tissue
.bool_lock
:
3127 bpy
.ops
.object.tissue_update_tessellate()
3130 context
.view_layer
.objects
.active
= ob
3131 bpy
.ops
.object.mode_set(mode
='EDIT')
3132 context
.tool_settings
.mesh_select_mode
= mesh_select_mode
3136 class tissue_rotate_face_left(Operator
):
3137 bl_idname
= "mesh.tissue_rotate_face_left"
3138 bl_label
= "Tissue Rotate Faces Left"
3139 bl_description
= "Rotate counterclockwise selected faces and update tessellated meshes"
3140 bl_options
= {'REGISTER', 'UNDO'}
3143 def poll(cls
, context
):
3145 #bool_tessellated = context.object.tissue_tessellate.generator != None
3147 return ob
.type == 'MESH' and ob
.mode
== 'EDIT'# and bool_tessellated
3151 def execute(self
, context
):
3152 ob
= context
.active_object
3155 bm
= bmesh
.from_edit_mesh(me
)
3156 mesh_select_mode
= [sm
for sm
in context
.tool_settings
.mesh_select_mode
]
3158 for face
in bm
.faces
:
3162 material_index
= face
.material_index
3163 bm
.faces
.remove(face
)
3164 f2
= bm
.faces
.new(vs2
)
3166 f2
.material_index
= material_index
3170 bmesh
.update_edit_mesh(me
)
3172 ob
.select_set(False)
3174 # update tessellated meshes
3175 bpy
.ops
.object.mode_set(mode
='OBJECT')
3176 for o
in [obj
for obj
in bpy
.data
.objects
if
3177 obj
.tissue_tessellate
.generator
== ob
and obj
.visible_get()]:
3178 context
.view_layer
.objects
.active
= o
3179 if not o
.tissue
.bool_lock
:
3180 bpy
.ops
.object.tissue_update_tessellate()
3183 context
.view_layer
.objects
.active
= ob
3184 bpy
.ops
.object.mode_set(mode
='EDIT')
3185 context
.tool_settings
.mesh_select_mode
= mesh_select_mode
3190 def convert_to_frame(ob
, props
, use_modifiers
):
3191 new_ob
= convert_object_to_mesh(ob
, use_modifiers
, True)
3195 bm
.from_mesh(new_ob
.data
)
3196 bm
.verts
.ensure_lookup_table()
3197 bm
.edges
.ensure_lookup_table()
3198 bm
.faces
.ensure_lookup_table()
3199 if props
['bool_selection']:
3200 original_faces
= [f
for f
in bm
.faces
if f
.select
]
3202 original_faces
= list(bm
.faces
)
3207 neigh_face_center
= []
3209 # append boundary loops
3210 if props
['frame_boundary']:
3211 #selected_edges = [e for e in bm.edges if e.select]
3212 selected_edges
= [e
for e
in bm
.edges
if e
.is_boundary
]
3213 if len(selected_edges
) > 0:
3216 e0
= selected_edges
[0]
3217 face
= e0
.link_faces
[0]
3218 boundary_mat
= [face
.material_index
+ props
['boundary_mat_offset']]
3219 face_center
= [face
.calc_center_median()]
3220 loop_normals
= [face
.normal
]
3221 selected_edges
= selected_edges
[1:]
3222 if props
['bool_vertex_group']:
3223 n_verts
= len(new_ob
.data
.vertices
)
3224 base_vg
= [get_weight(vg
,n_verts
) for vg
in new_ob
.vertex_groups
]
3228 for e1
in selected_edges
:
3229 if e1
.verts
[0] in e0
.verts
: new_vert
= e1
.verts
[1]
3230 elif e1
.verts
[1] in e0
.verts
: new_vert
= e1
.verts
[0]
3231 if new_vert
!= None:
3233 loop
= [v
for v
in e1
.verts
if v
!= new_vert
]
3234 loop
.append(new_vert
)
3236 face
= e0
.link_faces
[0]
3237 boundary_mat
.append(face
.material_index
+ props
['boundary_mat_offset'])
3238 face_center
.append(face
.calc_center_median())
3239 loop_normals
.append(face
.normal
)
3240 selected_edges
.remove(e0
)
3242 if new_vert
== None:
3246 e0
= selected_edges
[0]
3247 selected_edges
= selected_edges
[1:]
3248 boundaries_mat
.append(boundary_mat
)
3249 neigh_face_center
.append(face_center
)
3250 face_normals
.append(loop_normals
)
3251 face
= e0
.link_faces
[0]
3252 boundary_mat
= [face
.material_index
+ props
['boundary_mat_offset']]
3253 face_center
= [face
.calc_center_median()]
3254 loop_normals
= [face
.normal
]
3256 boundaries_mat
.append(boundary_mat
)
3257 neigh_face_center
.append(face_center
)
3258 face_normals
.append(loop_normals
)
3259 # compute boundary frames
3263 # append regular faces
3264 for f
in original_faces
:#bm.faces:
3265 loop
= list(f
.verts
)
3267 boundaries_mat
.append([f
.material_index
for v
in loop
])
3268 face_normals
.append([f
.normal
for v
in loop
])
3270 # calc areas for relative frame mode
3271 if props
['frame_mode'] == 'RELATIVE':
3274 linked_faces
= v
.link_faces
3275 if len(linked_faces
) > 0:
3276 area
= sum([sqrt(f
.calc_area())/len(f
.verts
) for f
in v
.link_faces
])*2
3277 area
/= len(linked_faces
)
3279 verts_area
.append(area
)
3281 for loop_index
, loop
in enumerate(loops
):
3282 is_boundary
= loop_index
< len(neigh_face_center
)
3283 materials
= boundaries_mat
[loop_index
]
3285 loop_ext
= [loop
[-1]] + loop
+ [loop
[0]]
3289 for i
in range(len(loop
)):
3292 vert
= loop_ext
[i
+1]
3293 vert1
= loop_ext
[i
+2]
3295 vec0
= (vert0
.co
- vert
.co
).normalized()
3296 vec1
= (vert
.co
- vert1
.co
).normalized()
3300 ang
= (pi
- vec0
.angle(vec1
))/2
3301 normal
= face_normals
[loop_index
][i
]
3302 tan0
= normal
.cross(vec0
)
3303 tan1
= normal
.cross(vec1
)
3304 tangent
= (tan0
+ tan1
).normalized()/sin(ang
)*props
['frame_thickness']
3305 tangents
.append(tangent
)
3307 # calc correct direction for boundaries
3311 for i
in range(len(loop
)):
3312 surf_point
= neigh_face_center
[loop_index
][i
]
3313 tangent
= tangents
[i
]
3314 vert
= loop_ext
[i
+1]
3315 dir_val
+= tangent
.dot(vert
.co
- surf_point
)
3316 if dir_val
> 0: mult
= 1
3319 for i
in range(len(loop
)):
3320 vert
= loop_ext
[i
+1]
3321 if props
['frame_mode'] == 'RELATIVE': area
= verts_area
[vert
.index
]
3323 new_co
= vert
.co
+ tangents
[i
] * mult
* area
3325 new_vert
= bm
.verts
.new(new_co
)
3326 new_loop
.append(new_vert
)
3327 vert_ids
.append(vert
.index
)
3328 new_loop
.append(new_loop
[0])
3331 materials
+= [materials
[0]]
3332 for i
in range(len(loop
)):
3337 face_verts
= [v1
,v0
,v3
,v2
]
3338 if mult
== -1: face_verts
= [v0
,v1
,v2
,v3
]
3339 new_face
= bm
.faces
.new(face_verts
)
3340 new_face
.material_index
= materials
[i
+1]
3341 new_face
.select
= True
3342 new_faces
.append(new_face
)
3344 if props
['fill_frame'] and not is_boundary
:
3345 n_verts
= len(new_loop
)-1
3346 loop_center
= Vector((0,0,0))
3347 for v
in new_loop
[1:]: loop_center
+= v
.co
3348 loop_center
/= n_verts
3349 center
= bm
.verts
.new(loop_center
)
3350 for i
in range(n_verts
):
3353 face_verts
= [v1
,v0
,center
]
3354 new_face
= bm
.faces
.new(face_verts
)
3355 new_face
.material_index
= materials
[i
] + props
['fill_frame_mat']
3356 new_face
.select
= True
3357 new_faces
.append(new_face
)
3358 #bpy.ops.object.mode_set(mode='OBJECT')
3359 #for f in bm.faces: f.select_set(f not in new_faces)
3360 for f
in original_faces
: bm
.faces
.remove(f
)
3361 bm
.to_mesh(new_ob
.data
)
3362 # propagate vertex groups
3363 if props
['bool_vertex_group']:
3365 for vg
in new_ob
.vertex_groups
:
3369 vertex_group
.append(vg
.weight(v
.index
))
3371 vertex_group
.append(0)
3372 base_vg
.append(vertex_group
)
3373 new_vert_ids
= range(len(bm
.verts
)-len(vert_ids
),len(bm
.verts
))
3374 for vg_id
, vg
in enumerate(new_ob
.vertex_groups
):
3375 for ii
, jj
in zip(vert_ids
, new_vert_ids
):
3376 vg
.add([jj
], base_vg
[vg_id
][ii
], 'REPLACE')
3377 new_ob
.data
.update()
3381 def reduce_to_quads(ob
, props
):
3383 Convert an input object to a mesh with polygons that have maximum 4 vertices
3385 new_ob
= convert_object_to_mesh(ob
, props
['gen_modifiers'], True)
3388 # Check if there are polygons with more than 4 sides
3389 np_sides
= get_attribute_numpy(me
.polygons
, 'loop_total')
3391 if not np
.any(mask
):
3392 if props
['boundary_mat_offset'] != 0 or props
['boundary_variable_offset']:
3395 bm
= offset_boundary_materials(
3397 boundary_mat_offset
= props
['boundary_mat_offset'],
3398 boundary_variable_offset
= props
['boundary_variable_offset'],
3399 auto_rotate_boundary
= props
['auto_rotate_boundary'])
3408 bm
.verts
.ensure_lookup_table()
3409 bm
.edges
.ensure_lookup_table()
3410 bm
.faces
.ensure_lookup_table()
3412 np_faces
= np
.array(bm
.faces
)
3413 np_faces
= np_faces
[mask
]
3417 verts
= list(f
.verts
)
3419 n_verts
= len(verts
)
3420 if n_verts
< 3: break
3422 face_verts
= [verts
[-2], verts
.pop(-1), verts
.pop(0)]
3424 face_verts
= [verts
[-2], verts
.pop(-1), verts
.pop(0), verts
[0]]
3425 new_face
= bm
.faces
.new(face_verts
)
3426 new_face
.material_index
= f
.material_index
3427 new_face
.select
= f
.select
3428 new_faces
.append(new_face
)
3430 for f
in np_faces
: bm
.faces
.remove(f
)
3432 bm
= offset_boundary_materials(
3434 boundary_mat_offset
= props
['boundary_mat_offset'],
3435 boundary_variable_offset
= props
['boundary_variable_offset'],
3436 auto_rotate_boundary
= props
['auto_rotate_boundary'])
3443 def convert_to_fan(ob
, props
, add_id_layer
=False):
3444 new_ob
= convert_object_to_mesh(ob
, props
['gen_modifiers'], True)
3446 bm
.from_mesh(new_ob
.data
)
3448 bm
.faces
.ensure_lookup_table()
3449 lay
= bm
.faces
.layers
.int.new("id")
3450 for i
,f
in enumerate(bm
.faces
): f
[lay
] = i
3451 bmesh
.ops
.poke(bm
, faces
=bm
.faces
)#, quad_method, ngon_method)
3452 bm
= offset_boundary_materials(
3454 boundary_mat_offset
= props
['boundary_mat_offset'],
3455 boundary_variable_offset
= props
['boundary_variable_offset'],
3456 auto_rotate_boundary
= props
['auto_rotate_boundary'])
3457 bm
.to_mesh(new_ob
.data
)
3458 new_ob
.data
.update()
3462 def convert_to_triangles(ob
, props
):
3463 new_ob
= convert_object_to_mesh(ob
, props
['gen_modifiers'], True)
3465 bm
.from_mesh(new_ob
.data
)
3466 bmesh
.ops
.triangulate(bm
, faces
=bm
.faces
, quad_method
='FIXED', ngon_method
='BEAUTY')
3468 bm
= offset_boundary_materials(
3470 boundary_mat_offset
= props
['boundary_mat_offset'],
3471 boundary_variable_offset
= props
['boundary_variable_offset'],
3472 auto_rotate_boundary
= props
['auto_rotate_boundary'])
3474 bm
.to_mesh(new_ob
.data
)
3475 new_ob
.data
.update()
3479 def merge_components(ob
, props
, use_bmesh
):
3481 if not use_bmesh
and False:
3483 ob
.active_shape_key_index
= 1
3484 if ob
.data
.shape_keys
!= None:
3485 for sk
in ob
.data
.shape_keys
.key_blocks
:
3491 bpy
.ops
.object.mode_set(mode
='EDIT')
3492 bpy
.ops
.object.mode_set(mode
='OBJECT')
3493 if ob
.data
.shape_keys
!= None:
3494 for sk
in ob
.data
.shape_keys
.key_blocks
:
3498 bpy
.ops
.object.mode_set(mode
='EDIT')
3499 bpy
.ops
.mesh
.select_mode(
3500 use_extend
=False, use_expand
=False, type='VERT')
3501 bpy
.ops
.mesh
.select_non_manifold(
3502 extend
=False, use_wire
=True, use_boundary
=True,
3503 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False)
3505 bpy
.ops
.mesh
.remove_doubles(
3506 threshold
=props
.merge_thres
, use_unselected
=False)
3508 if props
.bool_dissolve_seams
:
3509 bpy
.ops
.mesh
.select_mode(type='EDGE')
3510 bpy
.ops
.mesh
.select_all(action
='DESELECT')
3511 bpy
.ops
.object.mode_set(mode
='OBJECT')
3512 for e
in new_ob
.data
.edges
:
3513 e
.select
= e
.use_seam
3514 bpy
.ops
.object.mode_set(mode
='EDIT')
3515 bpy
.ops
.mesh
.dissolve_edges()
3516 bpy
.ops
.object.mode_set(mode
='OBJECT')
3518 if props
.close_mesh
!= 'NONE':
3519 bpy
.ops
.object.mode_set(mode
='EDIT')
3520 bpy
.ops
.mesh
.select_mode(
3521 use_extend
=False, use_expand
=False, type='EDGE')
3522 bpy
.ops
.mesh
.select_non_manifold(
3523 extend
=False, use_wire
=False, use_boundary
=True,
3524 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False)
3525 if props
.close_mesh
== 'CAP':
3526 if props
.open_edges_crease
!= 0:
3527 bpy
.ops
.transform
.edge_crease(value
=props
.open_edges_crease
)
3528 bpy
.ops
.mesh
.edge_face_add()
3529 bpy
.ops
.object.mode_set(mode
='OBJECT')
3530 for f
in ob
.data
.polygons
:
3531 if f
.select
: f
.material_index
+= props
.cap_material_offset
3532 elif props
.close_mesh
== 'BRIDGE':
3534 if props
.bridge_edges_crease
!= 0:
3535 bpy
.ops
.transform
.edge_crease(value
=props
.bridge_edges_crease
)
3536 bpy
.ops
.mesh
.bridge_edge_loops(
3538 number_cuts
=props
.bridge_cuts
,
3539 interpolation
='SURFACE',
3540 smoothness
=props
.bridge_smoothness
)
3541 bpy
.ops
.object.mode_set(mode
='OBJECT')
3542 for f
in ob
.data
.polygons
:
3543 if f
.select
: f
.material_index
+= props
.bridge_material_offset
3545 elif props
.close_mesh
== 'BRIDGE_CAP':
3548 bpy
.ops
.object.mode_set(mode
='OBJECT')
3549 vg
= ob
.vertex_groups
[props
.vertex_group_bridge
]
3550 weight
= get_weight_numpy(vg
, len(ob
.data
.vertices
))
3551 for e
in ob
.data
.edges
:
3552 if weight
[e
.vertices
[0]]*weight
[e
.vertices
[1]] < 1:
3554 bpy
.ops
.object.mode_set(mode
='EDIT')
3555 if props
.bridge_edges_crease
!= 0:
3556 bpy
.ops
.transform
.edge_crease(value
=props
.bridge_edges_crease
)
3557 bpy
.ops
.mesh
.bridge_edge_loops(
3559 number_cuts
=props
.bridge_cuts
,
3560 interpolation
='SURFACE',
3561 smoothness
=props
.bridge_smoothness
)
3562 for f
in ob
.data
.polygons
:
3563 if f
.select
: f
.material_index
+= props
.bridge_material_offset
3564 bpy
.ops
.mesh
.select_all(action
='DESELECT')
3565 bpy
.ops
.mesh
.select_non_manifold(
3566 extend
=False, use_wire
=False, use_boundary
=True,
3567 use_multi_face
=False, use_non_contiguous
=False, use_verts
=False)
3568 bpy
.ops
.object.mode_set(mode
='OBJECT')
3572 bpy
.ops
.object.mode_set(mode
='OBJECT')
3573 vg
= ob
.vertex_groups
[props
.vertex_group_cap
]
3574 weight
= get_weight_numpy(vg
, len(ob
.data
.vertices
))
3575 for e
in ob
.data
.edges
:
3576 if weight
[e
.vertices
[0]]*weight
[e
.vertices
[1]] < 1:
3578 bpy
.ops
.object.mode_set(mode
='EDIT')
3579 if props
.open_edges_crease
!= 0:
3580 bpy
.ops
.transform
.edge_crease(value
=props
.open_edges_crease
)
3581 bpy
.ops
.mesh
.edge_face_add()
3582 for f
in ob
.data
.polygons
:
3583 if f
.select
: f
.material_index
+= props
.cap_material_offset
3584 bpy
.ops
.object.mode_set(mode
='OBJECT')
3588 bm
.from_mesh(ob
.data
.copy())
3589 if props
.merge_open_edges_only
:
3590 boundary_verts
= [v
for v
in bm
.verts
if v
.is_boundary
or v
.is_wire
]
3592 boundary_verts
= bm
.verts
3593 bmesh
.ops
.remove_doubles(bm
, verts
=boundary_verts
, dist
=props
.merge_thres
)
3595 if props
.bool_dissolve_seams
:
3596 seam_edges
= [e
for e
in bm
.edges
if e
.seam
]
3597 bmesh
.ops
.dissolve_edges(bm
, edges
=seam_edges
, use_verts
=True, use_face_split
=False)
3598 if props
.close_mesh
!= 'NONE':
3599 bm
.edges
.ensure_lookup_table()
3601 crease_layer
= bm
.edges
.layers
.crease
.verify()
3602 boundary_edges
= [e
for e
in bm
.edges
if e
.is_boundary
or e
.is_wire
]
3603 if props
.close_mesh
== 'BRIDGE':
3605 for e
in boundary_edges
:
3606 e
[crease_layer
] = props
.bridge_edges_crease
3607 closed
= bmesh
.ops
.bridge_loops(bm
, edges
=boundary_edges
, use_pairs
=True)
3608 for f
in closed
['faces']: f
.material_index
+= props
.bridge_material_offset
3611 return 'bridge_error'
3612 elif props
.close_mesh
== 'CAP':
3613 for e
in boundary_edges
:
3614 e
[crease_layer
] = props
.open_edges_crease
3615 closed
= bmesh
.ops
.holes_fill(bm
, edges
=boundary_edges
)
3616 for f
in closed
['faces']: f
.material_index
+= props
.cap_material_offset
3617 elif props
.close_mesh
== 'BRIDGE_CAP':
3619 dvert_lay
= bm
.verts
.layers
.deform
.active
3621 dvert_lay
= bm
.verts
.layers
.deform
.active
3622 group_index
= ob
.vertex_groups
[props
.vertex_group_bridge
].index
3623 bw
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3624 if props
.invert_vertex_group_bridge
: bw
= 1-bw
3625 bridge_edges
= [e
for e
in boundary_edges
if bw
[e
.verts
[0].index
]*bw
[e
.verts
[1].index
] >= 1]
3626 for e
in bridge_edges
:
3627 e
[crease_layer
] = props
.bridge_edges_crease
3628 closed
= bmesh
.ops
.bridge_loops(bm
, edges
=bridge_edges
, use_pairs
=True)
3629 for f
in closed
['faces']: f
.material_index
+= props
.bridge_material_offset
3630 boundary_edges
= [e
for e
in bm
.edges
if e
.is_boundary
]
3634 dvert_lay
= bm
.verts
.layers
.deform
.active
3635 group_index
= ob
.vertex_groups
[props
.vertex_group_cap
].index
3636 bw
= bmesh_get_weight_numpy(group_index
, dvert_lay
, bm
.verts
)
3637 if props
.invert_vertex_group_cap
: bw
= 1-bw
3638 cap_edges
= [e
for e
in boundary_edges
if bw
[e
.verts
[0].index
]*bw
[e
.verts
[1].index
] >= 1]
3640 e
[crease_layer
] = props
.open_edges_crease
3641 closed
= bmesh
.ops
.holes_fill(bm
, edges
=cap_edges
)
3642 for f
in closed
['faces']: f
.material_index
+= props
.cap_material_offset
3646 class tissue_render_animation(Operator
):
3647 bl_idname
= "render.tissue_render_animation"
3648 bl_label
= "Tissue Render Animation"
3649 bl_description
= "Turnaround for issues related to animatable tessellation"
3650 bl_options
= {'REGISTER', 'UNDO'}
3656 def invoke(self
, context
, event
):
3658 return context
.window_manager
.invoke_props_dialog(self
)
3660 def draw(self
, context
):
3661 layout
= self
.layout
3662 col
= layout
.column(align
=True)
3663 col
.label(text
="All frames will be rendered in the background.")
3664 col
.label(text
="Press ESC to abort.")
3666 def modal(self
, context
, event
):
3668 # check render format
3669 format = context.scene.render.image_settings.file_format
3670 if format in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'):
3671 message = "Please use an image format as render output"
3672 self.report({'ERROR'}, message)
3673 return {'CANCELLED'}
3675 remove_tessellate_handler()
3676 scene
= context
.scene
3677 if event
.type == 'ESC' or scene
.frame_current
>= scene
.frame_end
:
3678 scene
.render
.filepath
= self
.path
3679 # set again the handler
3680 blender_handlers
= bpy
.app
.handlers
.frame_change_post
3681 blender_handlers
.append(anim_tessellate
)
3682 blender_handlers
.append(reaction_diffusion_scene
)
3683 context
.window_manager
.event_timer_remove(self
.timer
)
3684 if event
.type == 'ESC':
3685 print("Tissue: Render Animation aborted.")
3686 return {'CANCELLED'}
3688 print("Tissue: Render Animation completed!")
3691 self
.execute(context
)
3692 return {'RUNNING_MODAL'}
3694 def execute(self
, context
):
3695 # check output format
3696 format
= context
.scene
.render
.image_settings
.file_format
3697 if format
in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'):
3698 message
= "Please use an image format as render output"
3699 self
.report({'ERROR'}, message
)
3700 return {'CANCELLED'}
3702 scene
= context
.scene
3704 remove_tessellate_handler()
3705 reaction_diffusion_remove_handler(self
, context
)
3706 scene
= context
.scene
3707 scene
.frame_current
= scene
.frame_start
3708 self
.path
= scene
.render
.filepath
3709 context
.window_manager
.modal_handler_add(self
)
3710 self
.timer
= context
.window_manager
.event_timer_add(0.1, window
= context
.window
)
3713 scene
.frame_current
+= scene
.frame_step
3714 anim_tessellate(scene
)
3715 reaction_diffusion_scene(scene
)
3716 scene
.render
.filepath
= "{}{:04d}".format(self
.path
,scene
.frame_current
)
3717 bpy
.ops
.render
.render(write_still
=True)
3718 return {'RUNNING_MODAL'}
3720 def offset_boundary_materials(bm
, boundary_mat_offset
=0, boundary_variable_offset
=False, auto_rotate_boundary
=False):
3721 if boundary_mat_offset
!= 0 or boundary_variable_offset
:
3722 bm
.edges
.ensure_lookup_table()
3723 bm
.faces
.ensure_lookup_table()
3725 bound_verts_value
= [0]*len(bm
.faces
)
3726 bound_edges_value
= [0]*len(bm
.faces
)
3727 shift_faces
= [0]*len(bm
.faces
)
3728 # store boundaries information
3731 for f
in v
.link_faces
:
3732 bound_faces
.append(f
)
3733 bound_verts_value
[f
.index
] += 1
3736 for f
in e
.link_faces
:
3737 bound_edges_value
[f
.index
] += 1
3738 # Set material index offset
3739 if boundary_variable_offset
:
3741 if bound_verts_value
[f
.index
] > 0:
3742 f
.material_index
+= boundary_mat_offset
3743 if bound_verts_value
[f
.index
] == bound_edges_value
[f
.index
]+1:
3744 f
.material_index
+= bound_verts_value
[f
.index
]
3747 if bound_edges_value
[f
.index
] > 0:
3748 f
.material_index
+= boundary_mat_offset
3749 if auto_rotate_boundary
:
3753 val
= bound_verts_value
[f
.index
]
3754 val2
= bound_edges_value
[f
.index
]
3755 if val
> 0 and val2
== val
-1 and val
< len(f
.verts
):
3756 pattern
= [v
.is_boundary
for v
in f
.verts
]
3757 new_verts
= [v
for v
in f
.verts
]
3760 _pattern
= pattern
[val
//2+1:] + pattern
[:val
//2+1]
3761 for p
in _pattern
[-val
:]: mult
*=p
3763 pattern
= pattern
[-1:] + pattern
[:-1]
3764 new_verts
= new_verts
[-1:] + new_verts
[:-1]
3765 new_verts_all
.append(new_verts
)
3766 rotate_faces
.append(f
)
3767 if val
== 4 and val2
== 3:
3768 pattern
= [e
.is_boundary
for e
in f
.edges
]
3769 new_verts
= [v
for v
in f
.verts
]
3772 _pattern
= pattern
[val2
//2+1:] + pattern
[:val2
//2+1]
3773 for p
in _pattern
[-val2
:]: mult
*=p
3775 pattern
= pattern
[-1:] + pattern
[:-1]
3776 new_verts
= new_verts
[-1:] + new_verts
[:-1]
3777 new_verts_all
.append(new_verts
)
3778 rotate_faces
.append(f
)
3779 for f
, new_verts
in zip(rotate_faces
, new_verts_all
):
3780 material_index
= f
.material_index
3782 f2
= bm
.faces
.new(new_verts
)
3784 f2
.material_index
= material_index