Cleanup: quiet warnings with descriptions ending with a '.'
[blender-addons.git] / mesh_tissue / tessellate_numpy.py
blob504f0654455e7d20c8862ea3b799d9db80e4cd7f
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
4 # ------------------------------- version 0.84 ------------------------------- #
5 # #
6 # Creates duplicates of selected mesh to active morphing the shape according #
7 # to target faces. #
8 # #
9 # (c) Alessandro Zomparelli #
10 # (2017) #
11 # #
12 # http://www.co-de-it.com/ #
13 # #
14 # ############################################################################ #
17 import bpy
18 from bpy.types import (
19 Operator,
20 Panel,
21 PropertyGroup,
23 from bpy.props import (
24 BoolProperty,
25 EnumProperty,
26 FloatProperty,
27 IntProperty,
28 StringProperty,
29 PointerProperty
31 from mathutils import Vector, Quaternion, Matrix
32 import numpy as np
33 from math import *
34 import random, time, copy
35 import bmesh
36 from .utils import *
37 from .weight_tools import *
38 from .numba_functions import *
39 from .tissue_properties import *
40 import os, mathutils
41 from pathlib import Path
43 from . import config
45 def allowed_objects():
46 return ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
48 def remove_temp_objects():
49 # clean objects
50 for o in bpy.data.objects:
51 if "_tissue_tmp" in o.name:
52 bpy.data.objects.remove(o)
53 return
55 def tessellated(ob):
56 tess_props = ob.tissue_tessellate
57 if tess_props.generator not in list(bpy.data.objects):
58 return False
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():
65 return True
66 else:
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():
70 return True
71 return False
73 def tessellate_patch(props):
74 tt = time.time()
76 ob = props['self']
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']
83 mode = props['mode']
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()
132 # reset messages
133 ob.tissue_tessellate.warning_message_thickness = ''
135 if normals_mode == 'SHAPEKEYS':
136 if _ob0.data.shape_keys != None:
137 target = _ob0
138 else:
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)
156 else:
157 use_modifiers = gen_modifiers
158 if normals_mode == 'SHAPEKEYS' and not gen_modifiers:
159 target = _ob0
160 for m in target.modifiers:
161 m.show_viewport = False
162 use_modifiers = True
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)
168 me0_sk = ob0_sk.data
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
174 # Base mesh
175 if fill_mode == 'PATCH':
176 ob0 = convert_object_to_mesh(_ob0)
178 if boundary_mat_offset != 0:
179 bm=bmesh.new()
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'])
186 bm.to_mesh(ob0.data)
187 bm.free()
188 ob0.data.update()
190 else:
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"
198 me0 = ob0.data
199 n_verts0 = len(me0.vertices)
201 # read vertices coordinates
202 verts0_co = get_vertices_numpy(me0)
204 # base normals
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)
211 else:
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':
220 # Normalize vectors
221 verts0_normal /= np.linalg.norm(verts0_normal, axis=1).reshape((-1,1))
222 if not even_thickness:
223 pass
224 #original_normals = get_normals_numpy(me0)
225 #verts0_normal /= np.multiply(verts0_normal, original_normals).sum(1)[:,None]
226 else:
227 # Evaluate maximum components thickness
228 first_component = True
229 for com in components:
230 if com:
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)
235 if first_component:
236 all_com_verts = com_verts
237 first_component = False
238 else:
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):
247 kd.insert(v, i)
248 kd.balance()
249 step_dist = [neg_step_dist, pos_step_dist]
250 mult = 1
251 sign = [-1,1]
252 for sgn, stp in zip(sign, step_dist):
253 if stp == 0:
254 if sgn == 1: verts0_normal_pos = verts0_normal
255 if sgn == -1: verts0_normal_neg = verts0_normal
256 continue
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
261 closest_dist = []
262 closest_co = []
263 closest_nor = []
264 closest_index = []
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)
282 levels = 0
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':
289 before_subsurf = me0
290 if fill_mode == 'PATCH':
291 fill_mode = 'QUAD'
292 else:
293 show_modifiers = [m.show_viewport for m in _ob0.modifiers]
294 show_modifiers.reverse()
295 modifiers0.reverse()
296 for m in modifiers0:
297 visible = m.show_viewport
298 if not visible: continue
299 #m.show_viewport = False
300 if m.type in ('SUBSURF', 'MULTIRES') and visible:
301 levels = m.levels
302 break
303 elif m.type in not_allowed:
304 bpy.data.meshes.remove(ob0.data)
305 #bpy.data.meshes.remove(me0)
306 return "modifiers_error"
308 before = _ob0.copy()
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)
313 before_mod.reverse()
314 for m in before_mod:
315 if m.type in ('SUBSURF', 'MULTIRES') and m.show_viewport:
316 before.modifiers.remove(m)
317 break
318 else: before.modifiers.remove(m)
320 before_subsurf = simple_to_mesh(before)
322 if boundary_mat_offset != 0:
323 bm=bmesh.new()
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)
331 bm.free()
332 before_subsurf.update()
334 bpy.data.objects.remove(before)
336 tt = tissue_time(tt, "Meshes preparation", levels=2)
338 ### PATCHES ###
340 patch_faces = 4**levels
341 sides = int(sqrt(patch_faces))
342 step = 1/sides
343 sides0 = sides-2
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)
348 else:
349 all_verts, mask, materials = get_quads(me0, bool_selection)
350 n_patches = len(all_verts)
352 tt = tissue_time(tt, "Indexing", levels=2)
354 ### WEIGHT ###
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]
387 else:
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:
404 bm0 = bmesh.new()
405 bm0.from_mesh(me0)
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])
409 bm0.clear()
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]
413 else:
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)
434 bool_correct = False
436 tt = tissue_time(tt, "Reading Vertex Groups", levels=2)
438 ### SMOOTH NORMALS
439 if smooth_normals:
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':
455 # Normalize vectors
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)
473 else:
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:
488 # Weight rotation
489 weight_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
496 ids4 = np.arange(4)
497 if rotation_direction == 'DIAG':
498 c0 = corners_weight[:,ids4]
499 c3 = corners_weight[:,(ids4+2)%4]
500 differential = c3 - c0
501 else:
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)
509 # Random rotation
510 random_shift = 0
511 if rotation_mode == 'RANDOM':
512 np.random.seed(rand_seed)
513 random_shift = np.random.randint(0,4,size=n_patches)*rand_step
515 # UV rotation
516 UV_shift = 0
517 if rotation_mode == 'UV' and ob0.type == 'MESH':
518 bm = bmesh.new()
519 bm.from_mesh(before_subsurf)
520 uv_lay = bm.loops.layers.uv.active
521 UV_shift = [0]*len(mask)
522 for f in bm.faces:
523 ll = f.loops
524 if len(ll) == 4:
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
531 v32 = (uv3 + uv2)
532 v0132 = v32 - v01 # axis vector 1
533 v0132.normalize() # based on the rotation not on the size
534 v12 = (uv1 + uv2)
535 v03 = (uv0 + uv3)
536 v1203 = v03 - v12 # axis vector 2
537 v1203.normalize() # based on the rotation not on the size
539 dot1203 = v1203.x
540 dot0132 = v0132.x
541 if(abs(dot1203) < abs(dot0132)): # already vertical
542 if (dot0132 > 0): shift = 0
543 else: shift = 2 # rotate 180°
544 else: # horizontal
545 if(dot1203 < 0): shift = 3
546 else: shift = 1
547 #UV_shift.append(shift)
548 UV_shift[f.index] = shift
550 UV_shift = np.array(UV_shift)[mask]
551 bm.free()
553 # Rotate Patch
554 rotation_shift = np.zeros((n_patches))+rotation_shift
555 rot = weight_shift + random_shift + UV_shift + rotation_shift
556 rot = rot%4
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)
567 new_patch = None
569 ### COMPONENT ###
570 new_objects = []
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
586 else:
587 mat_mask = materials == mat_id
588 if bool_material_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)
595 else:
596 masked_verts = all_verts
597 masked_faces = mask
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)
610 sk.value = 0
611 else:
612 bool_shapekeys = False
613 else: bool_shapekeys = False
615 if not com_modifiers and not bool_shapekeys:
616 mod_visibility = []
617 for m in _ob1.modifiers:
618 mod_visibility.append(m.show_viewport)
619 m.show_viewport = False
620 com_modifiers = True
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
626 try:
627 for m, vis in zip(_ob1.modifiers, mod_visibility):
628 m.show_viewport = vis
629 except: pass
631 me1 = ob1.data
632 verts1 = [v.co for v in me1.vertices]
633 n_verts1 = len(verts1)
634 if n_verts1 == 0:
635 bpy.data.objects.remove(ob1)
636 continue
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):
645 # grid coordinates
646 u = int(vert[0]//step)
647 v = int(vert[1]//step)
648 u1 = min(u+1, sides)
649 v1 = min(v+1, sides)
650 if mode != 'BOUNDS':
651 if u > sides-1:
652 u = sides-1
653 u1 = sides
654 if u < 0:
655 u = 0
656 u1 = 1
657 if v > sides-1:
658 v = sides-1
659 v1 = sides
660 if v < 0:
661 v = 0
662 v1 = 1
663 verts1_uv_quads[i] = (u,v,u1,v1)
664 # factor coordinates
665 fu = (vert[0]-u*step)/step
666 fv = (vert[1]-v*step)/step
667 fw = vert.z
668 # interpolate Z scaling factor
669 verts1_uv[i] = Vector((fu,fv,fw))
670 else:
671 verts1_uv = verts1
673 if bool_shapekeys:
674 sk_uv_quads = []
675 sk_uv = []
676 for sk in ob1.data.shape_keys.key_blocks[1:]:
677 source = sk.data
678 _sk_uv_quads = [0]*n_verts1
679 _sk_uv = [0]*n_verts1
680 for i, sk_v in enumerate(source):
681 sk_vert = sk_v.co
683 # grid coordinates
684 u = int(sk_vert[0]//step)
685 v = int(sk_vert[1]//step)
686 u1 = min(u+1, sides)
687 v1 = min(v+1, sides)
688 if mode != 'BOUNDS':
689 if u > sides-1:
690 u = sides-1
691 u1 = sides
692 if u < 0:
693 u = 0
694 u1 = 1
695 if v > sides-1:
696 v = sides-1
697 v1 = sides
698 if v < 0:
699 v = 0
700 v1 = 1
701 _sk_uv_quads[i] = (u,v,u1,v1)
702 # factor coordinates
703 fu = (sk_vert[0]-u*step)/step
704 fv = (sk_vert[1]-v*step)/step
705 fw = sk_vert.z
706 _sk_uv[i] = Vector((fu,fv,fw))
707 sk_uv_quads.append(_sk_uv_quads)
708 sk_uv.append(_sk_uv)
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]
720 else:
721 np_u = 0
722 np_v = 0
723 np_u1 = 1
724 np_v1 = 1
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:
743 n_vg = len(weight)
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,:,:]
757 else:
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')
769 try:
770 weight_thickness.shape
771 if invert_vertex_group_thickness:
772 weight_thickness = 1-weight_thickness
773 fact = vertex_group_thickness_factor
774 if fact > 0:
775 weight_thickness = weight_thickness*(1-fact) + fact
776 except: pass
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]
783 wt = wt[:,:,:,None]
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')
789 try:
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
794 #if fact > 0:
795 # weight_thickness = weight_thickness*(1-fact) + fact
796 except: pass
798 if normals_mode == 'FACES':
799 n2 = get_attribute_numpy(before_subsurf.polygons,'normal',3)
800 n2 = n2[masked_faces][:,None,:]
801 else:
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():
816 bm1 = bmesh.new()
817 bm1.from_mesh(me1)
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))
822 # grid coordinates
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))
832 vy_nor = vy
833 bm1.free()
834 else:
835 vx_nor = vx
836 vy_nor = vy
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]
854 else:
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
863 mean_area = []
864 a2 = None
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]
874 else:
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))
879 a2 = verts_area
880 else:
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))
888 # remapped z scale
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
894 if bool_shapekeys:
895 tt_sk = time.time()
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,:]
902 print(np_v1)
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':
914 n2 = n2[None,:,:,:]
915 else:
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]
933 else:
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':
945 a2 = mean_area
946 else:
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))
951 # remapped z scale
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)
972 except: pass
973 tt = tissue_time(tt, "Inject coordinates", levels=2)
975 # Vertex Group
976 for vg in ob1.vertex_groups:
977 vg_name = vg.name
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:
982 new_groups = []
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)
989 if bool_shapekeys:
990 for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
991 sk.value = val
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):
1001 sk.value = val
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):
1011 sk.value = val
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)
1020 return new_objects
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(
1031 name="Hold",
1032 description="Wait...",
1033 default=False
1035 object_name : StringProperty(
1036 name="",
1037 description="Name of the generated object"
1039 zscale : FloatProperty(
1040 name="Scale",
1041 default=1,
1042 soft_min=0,
1043 soft_max=10,
1044 description="Scale factor for the component thickness"
1046 scale_mode : EnumProperty(
1047 items=(
1048 ('CONSTANT', "Constant", "Uniform thickness"),
1049 ('ADAPTIVE', "Relative", "Preserve component's proportions")
1051 default='ADAPTIVE',
1052 name="Z-Scale according to faces size"
1054 offset : FloatProperty(
1055 name="Surface Offset",
1056 default=1,
1057 min=-1, max=1,
1058 soft_min=-1,
1059 soft_max=1,
1060 description="Surface offset"
1062 component_mode : EnumProperty(
1063 items=(
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")
1068 default='OBJECT',
1069 name="Component Mode"
1071 mode : EnumProperty(
1072 items=(
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")),
1076 default='BOUNDS',
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")),
1084 default='DEFAULT',
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")),
1090 default='ORTHO',
1091 name="Direction"
1093 rotation_shift : IntProperty(
1094 name="Shift",
1095 default=0,
1096 soft_min=0,
1097 soft_max=3,
1098 description="Shift components rotation"
1100 fill_mode : EnumProperty(
1101 items=(
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')),
1110 default='QUAD',
1111 name="Fill Mode"
1113 combine_mode : EnumProperty(
1114 items=(
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')),
1118 default='LAST',
1119 name="Combine Mode",
1121 gen_modifiers : BoolProperty(
1122 name="Generator Modifiers",
1123 default=True,
1124 description="Apply Modifiers and Shape Keys to the base object"
1126 com_modifiers : BoolProperty(
1127 name="Component Modifiers",
1128 default=True,
1129 description="Apply Modifiers and Shape Keys to the component object"
1131 merge : BoolProperty(
1132 name="Merge",
1133 default=False,
1134 description="Merge vertices in adjacent duplicates"
1136 merge_open_edges_only : BoolProperty(
1137 name="Open edges only",
1138 default=True,
1139 description="Merge only open edges"
1141 merge_thres : FloatProperty(
1142 name="Distance",
1143 default=0.0001,
1144 soft_min=0,
1145 soft_max=10,
1146 description="Limit below which to merge vertices"
1148 bool_random : BoolProperty(
1149 name="Randomize",
1150 default=False,
1151 description="Randomize component rotation"
1153 rand_seed : IntProperty(
1154 name="Seed",
1155 default=0,
1156 soft_min=0,
1157 soft_max=10,
1158 description="Random seed"
1160 coll_rand_seed : IntProperty(
1161 name="Seed",
1162 default=0,
1163 soft_min=0,
1164 soft_max=10,
1165 description="Random seed"
1167 rand_step : IntProperty(
1168 name="Step",
1169 default=1,
1170 min=1,
1171 soft_max=2,
1172 description="Random step"
1174 bool_vertex_group : BoolProperty(
1175 name="Map Vertex Groups",
1176 default=False,
1177 description="Transfer all Vertex Groups from Base object"
1179 bool_selection : BoolProperty(
1180 name="On selected Faces",
1181 default=False,
1182 description="Create Tessellation only on selected faces"
1184 bool_shapekeys : BoolProperty(
1185 name="Use Shape Keys",
1186 default=False,
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",
1193 default=False,
1194 description="Output faces with smooth shading rather than flat shaded"
1196 bool_materials : BoolProperty(
1197 name="Transfer Materials",
1198 default=True,
1199 description="Preserve component's materials"
1201 generator : StringProperty(
1202 name="",
1203 description="Base object for the tessellation",
1204 default = ""
1206 component : StringProperty(
1207 name="",
1208 description="Component object for the tessellation",
1209 default = ""
1211 component_coll : StringProperty(
1212 name="",
1213 description="Components collection for the tessellation",
1214 default = ""
1216 target : StringProperty(
1217 name="",
1218 description="Target object for custom direction",
1219 default = ""
1221 even_thickness : BoolProperty(
1222 name="Even Thickness",
1223 default=False,
1224 description="Iterative sampling method for determine the correct length of the vectors (Experimental)"
1226 even_thickness_iter : IntProperty(
1227 name="Even Thickness Iterations",
1228 default=3,
1229 min = 1,
1230 soft_max = 20,
1231 description="More iterations produces more accurate results but make the tessellation slower"
1233 bool_material_id : BoolProperty(
1234 name="Tessellation on Material ID",
1235 default=False,
1236 description="Apply the component only on the selected Material"
1238 bool_dissolve_seams : BoolProperty(
1239 name="Dissolve Seams",
1240 default=False,
1241 description="Dissolve all seam edges"
1243 material_id : IntProperty(
1244 name="Material ID",
1245 default=0,
1246 min=0,
1247 description="Material ID"
1249 iterations : IntProperty(
1250 name="Iterations",
1251 default=1,
1252 min=1,
1253 soft_max=5,
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",
1260 default=False,
1261 description="Combine the generated geometry with unused faces"
1263 bool_advanced : BoolProperty(
1264 name="Advanced Settings",
1265 default=False,
1266 description="Show more settings"
1268 normals_mode : EnumProperty(
1269 items=(
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")),
1274 default='VERTS',
1275 name="Direction"
1277 bounds_x : EnumProperty(
1278 items=(
1279 ('EXTEND', 'Extend', 'Default X coordinates'),
1280 ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
1281 ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
1282 default='EXTEND',
1283 name="Bounds X",
1285 bounds_y : EnumProperty(
1286 items=(
1287 ('EXTEND', 'Extend', 'Default Y coordinates'),
1288 ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
1289 ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
1290 default='EXTEND',
1291 name="Bounds Y",
1293 close_mesh : EnumProperty(
1294 items=(
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')),
1299 default='NONE',
1300 name="Close Mesh"
1302 cap_faces : BoolProperty(
1303 name="Cap Holes",
1304 default=False,
1305 description="Cap open edges loops"
1307 frame_boundary : BoolProperty(
1308 name="Frame Boundary",
1309 default=False,
1310 description="Support face boundaries"
1312 fill_frame : BoolProperty(
1313 name="Fill Frame",
1314 default=False,
1315 description="Fill inner faces with Fan tessellation"
1317 boundary_mat_offset : IntProperty(
1318 name="Material Offset",
1319 default=0,
1320 description="Material Offset for boundaries (with Multi Components or Material ID)"
1322 fill_frame_mat : IntProperty(
1323 name="Material Offset",
1324 default=0,
1325 description="Material Offset for inner faces (with Multi Components or Material ID)"
1327 open_edges_crease : FloatProperty(
1328 name="Open Edges Crease",
1329 default=0,
1330 min=0,
1331 max=1,
1332 description="Automatically set crease for open edges"
1334 bridge_edges_crease : FloatProperty(
1335 name="Bridge Edges Crease",
1336 default=0,
1337 min=0,
1338 max=1,
1339 description="Automatically set crease for bridge edges"
1341 bridge_smoothness : FloatProperty(
1342 name="Smoothness",
1343 default=1,
1344 min=0,
1345 max=1,
1346 description="Bridge Smoothness"
1348 frame_thickness : FloatProperty(
1349 name="Frame Thickness",
1350 default=0.2,
1351 min=0,
1352 soft_max=2,
1353 description="Frame Thickness"
1355 frame_mode : EnumProperty(
1356 items=(
1357 ('CONSTANT', 'Constant', 'Even thickness'),
1358 ('RELATIVE', 'Relative', 'Frame offset depends on face areas')),
1359 default='CONSTANT',
1360 name="Offset"
1362 bridge_cuts : IntProperty(
1363 name="Cuts",
1364 default=0,
1365 min=0,
1366 max=20,
1367 description="Bridge Cuts"
1369 cap_material_offset : IntProperty(
1370 name="Material Offset",
1371 default=0,
1372 min=0,
1373 description="Material index offset for the cap faces"
1375 bridge_material_offset : IntProperty(
1376 name="Material Offset",
1377 default=0,
1378 min=0,
1379 description="Material index offset for the bridge faces"
1381 patch_subs : IntProperty(
1382 name="Patch Subdivisions",
1383 default=1,
1384 min=0,
1385 description="Subdivisions levels for Patch tessellation after the first iteration"
1387 use_origin_offset : BoolProperty(
1388 name="Align to Origins",
1389 default=False,
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(
1402 name="Factor",
1403 default=0,
1404 min=0,
1405 max=1,
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(
1418 name="Factor",
1419 default=0,
1420 min=0,
1421 max=1,
1422 description="Randomness factor to use for zero vertex group influence"
1425 vertex_group_cap_owner : EnumProperty(
1426 items=(
1427 ('BASE', 'Base', 'Use base vertex group'),
1428 ('COMP', 'Component', 'Use component vertex group')),
1429 default='COMP',
1430 name="Source"
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(
1442 items=(
1443 ('BASE', 'Base', 'Use base vertex group'),
1444 ('COMP', 'Component', 'Use component vertex group')),
1445 default='COMP',
1446 name="Source"
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(
1490 name="Iterations",
1491 default=5,
1492 min=0,
1493 description="Smooth iterations"
1495 smooth_normals_uv : FloatProperty(
1496 name="UV Anisotropy",
1497 default=0,
1498 min=-1,
1499 max=1,
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"
1523 working_on = ""
1525 def draw(self, context):
1528 try:
1529 bool_working = self.working_on == self.object_name and \
1530 self.working_on != ""
1531 except:
1532 bool_working = False
1535 bool_working = False
1536 bool_allowed = False
1537 ob0 = None
1538 ob1 = None
1540 ob = context.object
1541 sel = context.selected_objects
1543 if len(sel) == 2:
1544 bool_allowed = True
1545 for o in sel:
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.")
1555 return
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")
1560 return
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
1566 break
1567 if no_components:
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.")
1572 return
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
1579 break
1580 if no_components:
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.")
1584 return
1586 if ob0 == ob1 == None:
1587 ob0 = context.object
1588 self.generator = ob0.name
1589 if self.component_mode == 'OBJECT':
1590 for o in sel:
1591 if o != ob0:
1592 ob1 = o
1593 self.component = o.name
1594 self.no_component = False
1595 break
1597 # new object name
1598 if self.object_name == "":
1599 if self.generator == "":
1600 self.object_name = "Tessellation"
1601 else:
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')
1618 # Base Modifiers
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
1625 row.separator()
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]
1630 col.separator()
1631 # Fill and Rotation
1632 row = col.row(align=True)
1633 row.label(text="Fill Mode:")
1634 row = col.row(align=True)
1635 row.prop(
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)
1640 # merge settings
1641 row.prop(self, "merge")
1642 row.prop(self, "bool_smooth")
1644 # frame settings
1645 if self.fill_mode == 'FRAME':
1646 col.separator()
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')
1651 col.separator()
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':
1665 uv_error = False
1666 if ob0.type != 'MESH':
1667 row = col.row(align=True)
1668 row.label(
1669 text="UV rotation supported only for Mesh objects",
1670 icon='ERROR')
1671 uv_error = True
1672 else:
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')
1678 uv_error = True
1679 if uv_error:
1680 row = col.row(align=True)
1681 row.label(text="Default rotation will be used instead",
1682 icon='INFO')
1684 # Component Z
1685 col.separator()
1686 col.label(text="Thickness:")
1687 row = col.row(align=True)
1688 row.prop(
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)
1692 col.prop(
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)
1698 row.prop(
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
1703 col.separator()
1704 col.label(text="More settings in the Object Data Properties panel...", icon='PROPERTIES')
1707 def execute(self, context):
1708 try:
1709 ob0 = bpy.data.objects[self.generator]
1710 if self.component_mode == 'OBJECT':
1711 ob1 = bpy.data.objects[self.component]
1712 except:
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:
1719 count_name = 1
1720 while True:
1721 test_name = self.object_name + '.{:03d}'.format(count_name)
1722 if not (test_name in names):
1723 self.object_name = test_name
1724 break
1725 count_name += 1
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)
1735 self.generator = ""
1737 if bpy.ops.object.select_all.poll():
1738 bpy.ops.object.select_all(action='TOGGLE')
1739 bpy.ops.object.mode_set(mode='OBJECT')
1741 bool_update = False
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
1747 else:
1748 new_ob = context.object
1749 bool_update = True
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'}
1758 if not bool_update:
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:
1767 for c in old_coll:
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
1773 return {'FINISHED'}
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'}
1786 go = False
1788 @classmethod
1789 def poll(cls, context):
1790 try:
1791 return context.object.tissue.tissue_type != 'NONE'
1792 except:
1793 return False
1795 #@staticmethod
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:
1811 override = {
1812 'object': o,
1813 'selected_objects' : [o]
1815 if o.type == 'MESH':
1816 try:
1817 bpy.ops.object.tissue_update_tessellate(override)
1818 except:
1819 self.report({'ERROR'}, "Can't Tessellate :-(")
1820 else:
1821 try:
1822 bpy.ops.object.tissue_convert_to_curve_update(override)
1823 except:
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)
1830 return {'FINISHED'}
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'}
1840 go = False
1842 @classmethod
1843 def poll(cls, context):
1844 try:
1845 ob = context.object
1846 return ob.tissue.tissue_type == 'TESSELLATE'
1847 except:
1848 return False
1850 def execute(self, context):
1852 tissue_time(None,'Tissue: Tessellating...', levels=0)
1853 start_time = time.time()
1856 ob = context.object
1857 tess_props = props_to_dict(ob)
1858 if not self.go:
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
1931 try:
1932 generator.name
1933 if component_mode == 'OBJECT':
1934 component.name
1935 except:
1936 self.report({'ERROR'},
1937 "Active object must be Tessellated before Update")
1938 return {'CANCELLED'}
1940 # reset messages
1941 ob.tissue_tessellate.warning_message_merge = ''
1943 tess_props = props_to_dict(ob)
1945 # Solve Local View issues
1946 local_spaces = []
1947 local_ob0 = []
1948 local_ob1 = []
1949 for area in context.screen.areas:
1950 for space in area.spaces:
1951 try:
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)
1958 except:
1959 pass
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')
1967 ob0 = generator
1968 ob1 = component
1969 ##### auto_layer_collection()
1971 ob0_hide = ob0.hide_get()
1972 ob0_hidev = ob0.hide_viewport
1973 ob0_hider = ob0.hide_render
1974 ob0.hide_set(False)
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
1981 ob1.hide_set(False)
1982 ob1.hide_viewport = False
1983 ob1.hide_render = False
1985 components = []
1986 if component_mode == 'COLLECTION':
1987 dict_components = {}
1988 meta_object = True
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
1994 else: continue
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)
2003 else:
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)
2021 break
2022 base_ob.modifiers.update()
2024 # clear vertex groups before creating new ones
2025 if ob not in components: ob.vertex_groups.clear()
2027 if bool_selection:
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
2035 ob.select_set(True)
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'
2054 same_iteration = []
2055 matched_materials = []
2057 if component_mode == 'MATERIALS':
2058 components = []
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)
2067 else:
2068 components.append(None)
2069 else:
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)
2079 tt = time.time()
2080 same_iteration = tessellate_patch(tess_props)
2081 tissue_time(tt, "Tessellate iteration",levels=1)
2083 tt = time.time()
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
2111 if iter == 0:
2112 try:
2113 bpy.data.objects.remove(iter_objects[0])
2114 iter_objects = []
2115 except: continue
2116 continue
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
2121 bm = bmesh.new()
2122 if (fill_mode == 'PATCH' or gen_modifiers) and iter == 0:
2123 last_mesh = simple_to_mesh(base_ob)#(ob0)
2124 else:
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 = []
2133 if bool_selection:
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]
2136 else:
2137 remove_faces = [f for f in bm.faces if f.select]
2138 else:
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)
2142 bm.free()
2143 last_mesh.update()
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()
2150 else:
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
2162 else:
2163 if base_ob != new_ob:
2164 bpy.data.objects.remove(base_ob)
2165 base_ob = new_ob
2166 iter_objects = [new_ob]
2168 if iter > 0:# and fill_mode == 'PATCH':
2169 base_ob.modifiers.clear()#remove(temp_mod)
2171 # Combine
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)
2181 if merge:
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
2191 tt = time.time()
2193 if new_ob == 0:
2194 #bpy.data.objects.remove(base_ob.data)
2195 try: bpy.data.objects.remove(base_ob)
2196 except: pass
2197 message = "The generated object is an empty geometry!"
2198 context.view_layer.objects.active = ob
2199 ob.select_set(True)
2200 bpy.ops.object.mode_set(mode=starting_mode)
2201 self.report({'ERROR'}, message)
2202 return {'CANCELLED'}
2203 errors = {}
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)
2209 except: pass
2210 try: bpy.data.meshes.remove(data1)
2211 except: pass
2212 context.view_layer.objects.active = ob
2213 ob.select_set(True)
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
2226 old_data = ob.data
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)
2240 # copy vertex group
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)
2248 ob.select_set(True)
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)
2263 except: pass
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)
2288 return {'FINISHED'}
2290 def check(self, context):
2291 return True
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'}
2300 @classmethod
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')
2317 col.separator()
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='')
2323 col.separator()
2324 col.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') #####
2326 col.separator()
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')
2333 col.separator()
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':
2344 col.separator()
2345 col.label(text="Weight:")
2346 col.operator("object.tissue_weight_distance", icon="TRACKING")
2347 col.operator("object.tissue_weight_streamlines", icon="ANIM")
2349 col.separator()
2350 col.label(text="Materials:")
2351 col.operator("object.random_materials", icon='COLOR')
2352 col.operator("object.weight_to_materials", icon='GROUP_VERTEX')
2354 col.separator()
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'
2361 bl_context = "data"
2362 bl_label = "Tissue Tessellate"
2363 bl_options = {'DEFAULT_CLOSED'}
2365 @classmethod
2366 def poll(cls, context):
2367 try:
2368 return context.object.type == 'MESH'
2369 except: return False
2371 def draw(self, context):
2372 ob = context.object
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",
2380 icon='INFO')
2381 else:
2382 if props.error_message != "":
2383 layout.label(text=props.error_message,
2384 icon='ERROR')
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')
2408 try:
2409 if not (props.generator.modifiers or props.generator.data.shape_keys):
2410 col2.enabled = False
2411 except:
2412 col2.enabled = False
2414 #col.separator()
2416 layout.use_property_split = False
2417 # Fill
2418 col = layout.column(align=True)
2419 col.label(text="Fill Mode:")
2421 # fill
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'
2435 bl_context = "data"
2436 bl_parent_id = "TISSUE_PT_tessellate_object"
2437 bl_label = "Frame Settings"
2438 #bl_options = {'DEFAULT_CLOSED'}
2440 @classmethod
2441 def poll(cls, context):
2442 try:
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
2446 except:
2447 return False
2449 def draw(self, context):
2450 ob = context.object
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)
2458 col.separator()
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'
2475 bl_context = "data"
2476 bl_parent_id = "TISSUE_PT_tessellate_object"
2477 bl_label = "Components"
2478 #bl_options = {'DEFAULT_CLOSED'}
2480 @classmethod
2481 def poll(cls, context):
2482 try:
2483 bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
2484 return context.object.type == 'MESH' and bool_tessellated
2485 except:
2486 return False
2488 def draw(self, context):
2489 ob = context.object
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':
2501 col.separator()
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')
2507 try:
2508 if not (props.component.modifiers or props.component.data.shape_keys):
2509 col2.enabled = False
2510 except:
2511 col2.enabled = False
2513 elif props.component_mode == 'COLLECTION':
2514 col.separator()
2516 if props.component_coll in list(bpy.data.collections):
2517 components = []
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)
2522 if n_comp == 0:
2523 col.label(text="Can't find components in the Collection.", icon='ERROR')
2524 else:
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')
2529 else:
2530 col.label(text="Please, chose one Collection.", icon='ERROR')
2532 col.separator()
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")
2549 else:
2550 components = []
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)
2556 if n_comp == 0:
2557 col.label(text="Can't find components from the materials.", icon='ERROR')
2558 else:
2559 col.separator()
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':
2566 col.separator()
2567 col.separator()
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')
2574 col.separator()
2576 class TISSUE_PT_tessellate_coordinates(Panel):
2577 bl_space_type = 'PROPERTIES'
2578 bl_region_type = 'WINDOW'
2579 bl_context = "data"
2580 bl_parent_id = "TISSUE_PT_tessellate_object"
2581 bl_label = "Components Coordinates"
2582 bl_options = {'DEFAULT_CLOSED'}
2584 @classmethod
2585 def poll(cls, context):
2586 try:
2587 bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
2588 return context.object.type == 'MESH' and bool_tessellated
2589 except:
2590 return False
2592 def draw(self, context):
2593 ob = context.object
2594 props = ob.tissue_tessellate
2595 layout = self.layout
2597 col = layout.column(align=True)
2598 # component XY
2599 row = col.row(align=True)
2600 row.prop(props, "mode", expand=True)
2602 if props.mode != 'BOUNDS':
2603 col.separator()
2604 row = col.row(align=True)
2605 row.label(text="X:")
2606 row.prop(
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:")
2613 row.prop(
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'
2622 bl_context = "data"
2623 bl_parent_id = "TISSUE_PT_tessellate_object"
2624 bl_label = "Rotation"
2625 bl_options = {'DEFAULT_CLOSED'}
2627 @classmethod
2628 def poll(cls, context):
2629 try:
2630 bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
2631 return context.object.type == 'MESH' and bool_tessellated
2632 except:
2633 return False
2635 def draw(self, context):
2636 ob = context.object
2637 props = ob.tissue_tessellate
2638 layout = self.layout
2639 # rotation
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':
2647 col.separator()
2648 row = col.row(align=True)
2649 row.separator()
2650 row.separator()
2651 row.separator()
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()
2657 col.separator()
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")
2664 else:
2665 col.prop(props, "rotation_shift")
2667 if props.rotation_mode == 'UV':
2668 uv_error = False
2669 if props.generator.type != 'MESH':
2670 row = col.row(align=True)
2671 row.label(
2672 text="UV rotation supported only for Mesh objects",
2673 icon='ERROR')
2674 uv_error = True
2675 else:
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')
2680 uv_error = True
2681 if uv_error:
2682 row = col.row(align=True)
2683 row.label(text="Default rotation will be used instead",
2684 icon='INFO')
2686 class TISSUE_PT_tessellate_thickness(Panel):
2687 bl_space_type = 'PROPERTIES'
2688 bl_region_type = 'WINDOW'
2689 bl_context = "data"
2690 bl_parent_id = "TISSUE_PT_tessellate_object"
2691 bl_label = "Thickness"
2692 #bl_options = {'DEFAULT_CLOSED'}
2694 @classmethod
2695 def poll(cls, context):
2696 try: return context.object.tissue.tissue_type == 'TESSELLATE'
2697 except: return False
2699 def draw(self, context):
2700 ob = context.object
2701 props = ob.tissue_tessellate
2703 layout = self.layout
2704 #layout.use_property_split = True
2705 col = layout.column(align=True)
2706 # component Z
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')
2720 col.separator()
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'
2735 bl_context = "data"
2736 bl_parent_id = "TISSUE_PT_tessellate_object"
2737 bl_label = "Thickness Direction"
2738 bl_options = {'DEFAULT_CLOSED'}
2740 @classmethod
2741 def poll(cls, context):
2742 try:
2743 return context.object.tissue.tissue_type == 'TESSELLATE'
2744 except:
2745 return False
2747 def draw(self, context):
2748 ob = context.object
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)
2755 row.prop(
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':
2760 col.separator()
2761 row = col.row(align=True)
2762 row.prop_search(props, "target", context.scene, "objects", text='Target')
2763 if props.warning_message_thickness != '':
2764 col.separator()
2765 col.label(text=props.warning_message_thickness, icon='ERROR')
2766 if props.normals_mode != 'FACES':
2767 col.separator()
2768 col.prop(props, "smooth_normals")
2769 if props.smooth_normals:
2770 row = col.row(align=True)
2771 row.prop(props, "smooth_normals_iter")
2772 row.separator()
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':
2779 col.separator()
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'):
2791 col.separator()
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'
2799 bl_context = "data"
2800 bl_parent_id = "TISSUE_PT_tessellate_object"
2801 bl_label = " "
2802 bl_options = {'DEFAULT_CLOSED'}
2804 @classmethod
2805 def poll(cls, context):
2806 try:
2807 return context.object.tissue.tissue_type == 'TESSELLATE'
2808 except:
2809 return False
2811 def draw_header(self, context):
2812 ob = context.object
2813 props = ob.tissue_tessellate
2814 self.layout.prop(props, "merge")
2816 def draw(self, context):
2817 ob = context.object
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)
2823 if props.merge:
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'):
2829 col.separator()
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:
2848 col.separator()
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)
2854 col.separator()
2855 if props.close_mesh == 'BRIDGE_CAP':
2856 if props.vertex_group_cap_owner == 'BASE': ob_cap = ob0
2857 else: ob_cap = ob1
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:
2870 col.separator()
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'
2876 bl_context = "data"
2877 bl_parent_id = "TISSUE_PT_tessellate_object"
2878 bl_label = "Weight and Morphing"
2879 bl_options = {'DEFAULT_CLOSED'}
2881 @classmethod
2882 def poll(cls, context):
2883 try: return context.object.tissue.tissue_type == 'TESSELLATE'
2884 except: return False
2886 def draw(self, context):
2887 ob = context.object
2888 props = ob.tissue_tessellate
2889 layout = self.layout
2890 allow_shapekeys = not props.com_modifiers
2892 if tessellated(ob):
2893 ob0 = props.generator
2894 for m in ob0.data.materials:
2895 try:
2896 o = bpy.data.objects[m.name]
2897 allow_multi = True
2898 try:
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
2902 except: pass
2903 except: pass
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")
2910 try:
2911 if len(props.generator.vertex_groups) == 0:
2912 col2.enabled = False
2913 except:
2914 col2.enabled = False
2915 row.separator()
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'
2929 bl_context = "data"
2930 bl_parent_id = "TISSUE_PT_tessellate_object"
2931 bl_label = "Selective"
2932 bl_options = {'DEFAULT_CLOSED'}
2934 @classmethod
2935 def poll(cls, context):
2936 try:
2937 return context.object.tissue.tissue_type == 'TESSELLATE'
2938 except:
2939 return False
2941 def draw(self, context):
2942 ob = context.object
2943 props = ob.tissue_tessellate
2945 layout = self.layout
2946 #layout.use_property_split = True
2947 #layout.use_property_decorate = False # No animation.
2948 allow_multi = False
2949 allow_shapekeys = not props.com_modifiers
2950 ob0 = props.generator
2951 for m in ob0.data.materials:
2952 try:
2953 o = bpy.data.objects[m.name]
2954 allow_multi = True
2955 try:
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
2959 except: pass
2960 except: pass
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')
2967 row.separator()
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
2978 #col.separator()
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'
2989 bl_context = "data"
2990 bl_parent_id = "TISSUE_PT_tessellate_object"
2991 bl_label = "Iterations"
2992 bl_options = {'DEFAULT_CLOSED'}
2994 @classmethod
2995 def poll(cls, context):
2996 try:
2997 return context.object.tissue.tissue_type == 'TESSELLATE'
2998 except:
2999 return False
3001 def draw(self, context):
3002 ob = context.object
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':
3012 col.separator()
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)
3020 row.prop(
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'}
3031 @classmethod
3032 def poll(cls, context):
3033 try:
3034 #bool_tessellated = context.object.tissue_tessellate.generator != None
3035 ob = context.object
3036 return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
3037 except:
3038 return False
3040 def execute(self, context):
3041 ob = context.active_object
3042 me = ob.data
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:
3048 if (face.select):
3049 vs = face.verts[:]
3050 vs2 = vs[-1:]+vs[:-1]
3051 material_index = face.material_index
3052 bm.faces.remove(face)
3053 f2 = bm.faces.new(vs2)
3054 f2.select = True
3055 f2.material_index = material_index
3056 bm.normal_update()
3058 # trigger UI update
3059 bmesh.update_edit_mesh(me)
3060 bm.free()
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()
3072 o.select_set(False)
3073 ob.select_set(True)
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
3078 return {'FINISHED'}
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'}
3086 @classmethod
3087 def poll(cls, context):
3088 try:
3089 #bool_tessellated = context.object.tissue_tessellate.generator != None
3090 ob = context.object
3091 return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
3092 except:
3093 return False
3095 def execute(self, context):
3096 ob = context.active_object
3097 me = ob.data
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:
3103 if (face.select):
3104 vs = face.verts[:]
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)
3110 f2.select = True
3111 f2.material_index = material_index
3112 bm.normal_update()
3114 # trigger UI update
3115 bmesh.update_edit_mesh(me)
3116 bm.free()
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()
3128 o.select_set(False)
3129 ob.select_set(True)
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
3134 return {'FINISHED'}
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'}
3142 @classmethod
3143 def poll(cls, context):
3144 try:
3145 #bool_tessellated = context.object.tissue_tessellate.generator != None
3146 ob = context.object
3147 return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
3148 except:
3149 return False
3151 def execute(self, context):
3152 ob = context.active_object
3153 me = ob.data
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:
3159 if (face.select):
3160 vs = face.verts[:]
3161 vs2 = vs[1:]+vs[:1]
3162 material_index = face.material_index
3163 bm.faces.remove(face)
3164 f2 = bm.faces.new(vs2)
3165 f2.select = True
3166 f2.material_index = material_index
3167 bm.normal_update()
3169 # trigger UI update
3170 bmesh.update_edit_mesh(me)
3171 bm.free()
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()
3181 o.select_set(False)
3182 ob.select_set(True)
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
3187 return {'FINISHED'}
3190 def convert_to_frame(ob, props, use_modifiers):
3191 new_ob = convert_object_to_mesh(ob, use_modifiers, True)
3193 # create bmesh
3194 bm = bmesh.new()
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]
3201 else:
3202 original_faces = list(bm.faces)
3203 # detect edge loops
3205 loops = []
3206 boundaries_mat = []
3207 neigh_face_center = []
3208 face_normals = []
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:
3214 loop = []
3215 count = 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]
3225 while True:
3226 new_vert = None
3227 face = None
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:
3232 if len(loop)==0:
3233 loop = [v for v in e1.verts if v != new_vert]
3234 loop.append(new_vert)
3235 e0 = e1
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)
3241 break
3242 if new_vert == None:
3243 try:
3244 loops.append(loop)
3245 loop = []
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]
3255 except: break
3256 boundaries_mat.append(boundary_mat)
3257 neigh_face_center.append(face_center)
3258 face_normals.append(loop_normals)
3259 # compute boundary frames
3260 new_faces = []
3261 vert_ids = []
3263 # append regular faces
3264 for f in original_faces:#bm.faces:
3265 loop = list(f.verts)
3266 loops.append(loop)
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':
3272 verts_area = []
3273 for v in bm.verts:
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)
3278 else: area = 0
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]
3284 new_loop = []
3285 loop_ext = [loop[-1]] + loop + [loop[0]]
3287 # calc tangents
3288 tangents = []
3289 for i in range(len(loop)):
3290 # vertices
3291 vert0 = loop_ext[i]
3292 vert = loop_ext[i+1]
3293 vert1 = loop_ext[i+2]
3294 # edge vectors
3295 vec0 = (vert0.co - vert.co).normalized()
3296 vec1 = (vert.co - vert1.co).normalized()
3297 # tangent
3298 _vec1 = -vec1
3299 _vec0 = -vec0
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
3308 mult = -1
3309 if is_boundary:
3310 dir_val = 0
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
3318 # add vertices
3319 for i in range(len(loop)):
3320 vert = loop_ext[i+1]
3321 if props['frame_mode'] == 'RELATIVE': area = verts_area[vert.index]
3322 else: area = 1
3323 new_co = vert.co + tangents[i] * mult * area
3324 # add vertex
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])
3330 # add faces
3331 materials += [materials[0]]
3332 for i in range(len(loop)):
3333 v0 = loop_ext[i+1]
3334 v1 = loop_ext[i+2]
3335 v2 = new_loop[i+1]
3336 v3 = new_loop[i]
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)
3343 # fill frame
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):
3351 v0 = new_loop[i+1]
3352 v1 = new_loop[i]
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']:
3364 base_vg = []
3365 for vg in new_ob.vertex_groups:
3366 vertex_group = []
3367 for v in bm.verts:
3368 try:
3369 vertex_group.append(vg.weight(v.index))
3370 except:
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()
3378 bm.free()
3379 return new_ob
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)
3386 me = new_ob.data
3388 # Check if there are polygons with more than 4 sides
3389 np_sides = get_attribute_numpy(me.polygons, 'loop_total')
3390 mask = np_sides > 4
3391 if not np.any(mask):
3392 if props['boundary_mat_offset'] != 0 or props['boundary_variable_offset']:
3393 bm=bmesh.new()
3394 bm.from_mesh(me)
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'])
3400 bm.to_mesh(me)
3401 bm.free()
3402 me.update()
3403 return new_ob
3405 # create bmesh
3406 bm = bmesh.new()
3407 bm.from_mesh(me)
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]
3415 new_faces = []
3416 for f in np_faces:
3417 verts = list(f.verts)
3418 while True:
3419 n_verts = len(verts)
3420 if n_verts < 3: break
3421 elif n_verts == 3:
3422 face_verts = [verts[-2], verts.pop(-1), verts.pop(0)]
3423 else:
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'])
3438 bm.to_mesh(me)
3439 bm.free()
3440 me.update()
3441 return new_ob
3443 def convert_to_fan(ob, props, add_id_layer=False):
3444 new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True)
3445 bm = bmesh.new()
3446 bm.from_mesh(new_ob.data)
3447 if add_id_layer:
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()
3459 bm.free()
3460 return new_ob
3462 def convert_to_triangles(ob, props):
3463 new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True)
3464 bm = bmesh.new()
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()
3476 bm.free()
3477 return new_ob
3479 def merge_components(ob, props, use_bmesh):
3481 if not use_bmesh and False:
3482 skip = True
3483 ob.active_shape_key_index = 1
3484 if ob.data.shape_keys != None:
3485 for sk in ob.data.shape_keys.key_blocks:
3486 if skip:
3487 skip = False
3488 continue
3489 sk.mute = True
3490 ob.data.update()
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:
3495 sk.mute = False
3496 ob.data.update()
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':
3533 try:
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(
3537 type='PAIRS',
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
3544 except: pass
3545 elif props.close_mesh == 'BRIDGE_CAP':
3546 # BRIDGE
3547 try:
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:
3553 e.select = False
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(
3558 type='PAIRS',
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')
3569 except: pass
3570 # CAP
3571 try:
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:
3577 e.select = False
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')
3585 except: pass
3586 else:
3587 bm = bmesh.new()
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]
3591 else:
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()
3600 # set crease
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':
3604 try:
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
3609 except:
3610 bm.to_mesh(ob.data)
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':
3618 # BRIDGE
3619 dvert_lay = bm.verts.layers.deform.active
3620 try:
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]
3631 except: pass
3632 # CAP
3633 try:
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]
3639 for e in cap_edges:
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
3643 except: pass
3644 bm.to_mesh(ob.data)
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'}
3652 start = True
3653 path = ""
3654 timer = None
3656 def invoke(self, context, event):
3657 self.start = True
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'}
3687 else:
3688 print("Tissue: Render Animation completed!")
3689 return {'FINISHED'}
3690 else:
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
3703 if self.start:
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)
3711 self.start = False
3712 else:
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()
3724 bound_faces = []
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
3729 for v in bm.verts:
3730 if v.is_boundary:
3731 for f in v.link_faces:
3732 bound_faces.append(f)
3733 bound_verts_value[f.index] += 1
3734 for e in bm.edges:
3735 if e.is_boundary:
3736 for f in e.link_faces:
3737 bound_edges_value[f.index] += 1
3738 # Set material index offset
3739 if boundary_variable_offset:
3740 for f in bm.faces:
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]
3745 else:
3746 for f in bm.faces:
3747 if bound_edges_value[f.index] > 0:
3748 f.material_index += boundary_mat_offset
3749 if auto_rotate_boundary:
3750 rotate_faces = []
3751 new_verts_all = []
3752 for f in bm.faces:
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]
3758 while True:
3759 mult = 1
3760 _pattern = pattern[val//2+1:] + pattern[:val//2+1]
3761 for p in _pattern[-val:]: mult*=p
3762 if mult == 1: break
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]
3770 while True:
3771 mult = 1
3772 _pattern = pattern[val2//2+1:] + pattern[:val2//2+1]
3773 for p in _pattern[-val2:]: mult*=p
3774 if mult == 1: break
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
3781 bm.faces.remove(f)
3782 f2 = bm.faces.new(new_verts)
3783 f2.select = True
3784 f2.material_index = material_index
3785 bm.normal_update()
3786 return bm