Export_3ds: Added distance cue chunk export
[blender-addons.git] / mesh_tissue / tessellate_numpy.py
blob2bfa9dda5d54a78af47b4ace60d08b0af4f2fe78
1 # SPDX-FileCopyrightText: 2017 Alessandro Zomparelli
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ---------------------------- ADAPTIVE DUPLIFACES --------------------------- #
6 # ------------------------------- version 0.84 ------------------------------- #
7 # #
8 # Creates duplicates of selected mesh to active morphing the shape according #
9 # to target faces. #
10 # #
11 # (c) Alessandro Zomparelli #
12 # (2017) #
13 # #
14 # http://www.co-de-it.com/ #
15 # #
16 # ############################################################################ #
19 import bpy
20 from bpy.types import (
21 Operator,
22 Panel,
23 PropertyGroup,
25 from bpy.props import (
26 BoolProperty,
27 EnumProperty,
28 FloatProperty,
29 IntProperty,
30 StringProperty,
31 PointerProperty
33 from mathutils import Vector, Quaternion, Matrix
34 import numpy as np
35 from math import *
36 import random, time, copy
37 import bmesh
38 from .utils import *
39 from .weight_tools import *
40 from .numba_functions import *
41 from .tissue_properties import *
42 import os, mathutils
43 from pathlib import Path
45 from . import config
47 def allowed_objects():
48 return ('MESH', 'CURVE', 'SURFACE', 'FONT', 'META')
50 def tessellated(ob):
51 props = ob.tissue_tessellate
52 if props.generator not in list(bpy.data.objects):
53 return False
54 elif props.component_mode == 'OBJECT':
55 return props.component in list(bpy.data.objects)
56 elif props.component_mode == 'COLLECTION':
57 if props.component_coll in list(bpy.data.collections):
58 for o in list(props.component_coll.objects):
59 if o.type in allowed_objects():
60 return True
61 else:
62 for mat in props.generator.material_slots.keys():
63 if mat in bpy.data.objects.keys():
64 if bpy.data.objects[mat].type in allowed_objects():
65 return True
66 return False
68 def tessellate_patch(props):
69 tt = time.time()
71 ob = props['self']
72 _ob0 = props['generator']
73 components = props['component']
74 offset = props['offset']
75 zscale = props['zscale']
76 gen_modifiers = props['gen_modifiers']
77 com_modifiers = props['com_modifiers']
78 mode = props['mode']
79 fill_mode = props['fill_mode']
80 scale_mode = props['scale_mode']
81 rotation_mode = props['rotation_mode']
82 rotation_shift = props['rotation_shift']
83 rand_seed = props['rand_seed']
84 rand_step = props['rand_step']
85 bool_vertex_group = props['bool_vertex_group']
86 bool_selection = props['bool_selection']
87 bool_shapekeys = props['bool_shapekeys']
88 bool_material_id = props['bool_material_id']
89 material_id = props['material_id']
90 normals_mode = props['normals_mode']
91 bounds_x = props['bounds_x']
92 bounds_y = props['bounds_y']
93 use_origin_offset = props['use_origin_offset']
94 vertex_group_thickness = props['vertex_group_thickness']
95 invert_vertex_group_thickness = props['invert_vertex_group_thickness']
96 vertex_group_thickness_factor = props['vertex_group_thickness_factor']
97 vertex_group_frame_thickness = props['vertex_group_frame_thickness']
98 invert_vertex_group_frame_thickness = props['invert_vertex_group_frame_thickness']
99 vertex_group_frame_thickness_factor = props['vertex_group_frame_thickness_factor']
100 face_weight_frame = props['face_weight_frame']
101 vertex_group_distribution = props['vertex_group_distribution']
102 invert_vertex_group_distribution = props['invert_vertex_group_distribution']
103 vertex_group_distribution_factor = props['vertex_group_distribution_factor']
104 vertex_group_cap_owner = props['vertex_group_cap_owner']
105 vertex_group_cap = props['vertex_group_cap']
106 invert_vertex_group_cap = props['invert_vertex_group_cap']
107 vertex_group_bridge_owner = props['vertex_group_bridge_owner']
108 vertex_group_bridge = props['vertex_group_bridge']
109 invert_vertex_group_bridge = props['invert_vertex_group_bridge']
110 vertex_group_rotation = props['vertex_group_rotation']
111 invert_vertex_group_rotation = props['invert_vertex_group_rotation']
112 rotation_direction = props['rotation_direction']
113 target = props['target']
114 even_thickness = props['even_thickness']
115 even_thickness_iter = props['even_thickness_iter']
116 smooth_normals = props['smooth_normals']
117 smooth_normals_iter = props['smooth_normals_iter']
118 smooth_normals_uv = props['smooth_normals_uv']
119 vertex_group_smooth_normals = props['vertex_group_smooth_normals']
120 invert_vertex_group_smooth_normals = props['invert_vertex_group_smooth_normals']
121 #bool_multi_components = props['bool_multi_components']
122 component_mode = props['component_mode']
123 coll_rand_seed = props['coll_rand_seed']
124 consistent_wedges = props['consistent_wedges']
125 vertex_group_scale_normals = props['vertex_group_scale_normals']
126 invert_vertex_group_scale_normals = props['invert_vertex_group_scale_normals']
127 boundary_mat_offset = props['boundary_mat_offset']
128 preserve_quads = props['preserve_quads']
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, rotation_mode!='UV')
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, True, True, rotation_mode!='UV')
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, 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 if rotation_mode!='UV':
321 before_subsurf = simple_to_mesh_mirror(before)
322 else:
323 before_subsurf = simple_to_mesh(before)
325 if boundary_mat_offset != 0:
326 bm=bmesh.new()
327 bm.from_mesh(before_subsurf)
328 bm = offset_boundary_materials(
330 boundary_mat_offset = _props['boundary_mat_offset'],
331 boundary_variable_offset = _props['boundary_variable_offset'],
332 auto_rotate_boundary = _props['auto_rotate_boundary'])
333 bm.to_mesh(before_subsurf)
334 bm.free()
335 before_subsurf.update()
337 bpy.data.objects.remove(before)
339 tt = tissue_time(tt, "Meshes preparation", levels=2)
341 ### PATCHES ###
343 patch_faces = 4**levels
344 sides = int(sqrt(patch_faces))
345 step = 1/sides
346 sides0 = sides-2
347 patch_faces0 = int((sides-2)**2)
349 if fill_mode == 'PATCH':
350 all_verts, mask, materials = get_patches(before_subsurf, me0, 4, levels, bool_selection)
351 else:
352 all_verts, mask, materials = get_quads(me0, bool_selection)
353 n_patches = len(all_verts)
355 tt = tissue_time(tt, "Indexing", levels=2)
357 ### WEIGHT ###
359 # Check if possible to use Weight Rotation
360 if rotation_mode == 'WEIGHT':
361 if not vertex_group_rotation in ob0.vertex_groups.keys():
362 rotation_mode = 'DEFAULT'
364 bool_vertex_group = bool_vertex_group and len(ob0.vertex_groups.keys()) > 0
365 bool_weight_smooth_normals = vertex_group_smooth_normals in ob0.vertex_groups.keys()
366 bool_weight_thickness = vertex_group_thickness in ob0.vertex_groups.keys()
367 bool_weight_distribution = vertex_group_distribution in ob0.vertex_groups.keys()
368 bool_weight_cap = vertex_group_cap_owner == 'BASE' and vertex_group_cap in ob0.vertex_groups.keys()
369 bool_weight_bridge = vertex_group_bridge_owner == 'BASE' and vertex_group_bridge in ob0.vertex_groups.keys()
370 bool_weight_normals = vertex_group_scale_normals in ob0.vertex_groups.keys()
372 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
373 weight = weight_thickness = weight_rotation = None
374 if read_vertex_groups:
375 if bool_vertex_group:
376 weight = [get_weight(vg, n_verts0) for vg in ob0.vertex_groups]
377 weight = np.array(weight)
378 n_vg = len(ob0.vertex_groups)
379 if rotation_mode == 'WEIGHT':
380 vg_id = ob0.vertex_groups[vertex_group_rotation].index
381 weight_rotation = weight[vg_id]
382 if bool_weight_smooth_normals:
383 vg_id = ob0.vertex_groups[bool_weight_smooth_normals].index
384 weight_rotation = weight[vg_id]
385 if bool_weight_distribution:
386 vg_id = ob0.vertex_groups[vertex_group_distribution].index
387 weight_distribution = weight[vg_id]
388 if bool_weight_normals:
389 vg_id = ob0.vertex_groups[vertex_group_scale_normals].index
390 weight_normals = weight[vg_id]
391 else:
392 if rotation_mode == 'WEIGHT':
393 vg = ob0.vertex_groups[vertex_group_rotation]
394 weight_rotation = get_weight_numpy(vg, n_verts0)
395 if bool_weight_smooth_normals:
396 vg = ob0.vertex_groups[vertex_group_smooth_normals]
397 weight_smooth_normals = get_weight_numpy(vg, n_verts0)
398 if bool_weight_distribution:
399 vg = ob0.vertex_groups[vertex_group_distribution]
400 weight_distribution = get_weight_numpy(vg, n_verts0)
401 if bool_weight_normals:
402 vg = ob0.vertex_groups[vertex_group_scale_normals]
403 weight_normals = get_weight_numpy(vg, n_verts0)
405 if component_mode == 'COLLECTION':
406 np.random.seed(coll_rand_seed)
407 if fill_mode == 'FAN' and consistent_wedges:
408 bm0 = bmesh.new()
409 bm0.from_mesh(me0)
410 bm0.faces.ensure_lookup_table()
411 lay_id = bm0.faces.layers.int["id"]
412 faces_id = np.array([f[lay_id] for f in bm0.faces])
413 bm0.clear()
414 n_original_faces = faces_id[-1]+1
415 coll_materials = np.random.randint(len(components),size=n_original_faces)
416 coll_materials = coll_materials[faces_id]
417 else:
418 coll_materials = np.random.randint(len(components),size=n_patches)
419 gradient_distribution = []
420 if bool_weight_distribution:
421 if invert_vertex_group_distribution:
422 weight_distribution = 1-weight_distribution
423 v00 = all_verts[:,0,0]
424 v01 = all_verts[:,0,-1]
425 v10 = all_verts[:,-1,0]
426 v11 = all_verts[:,-1,-1]
427 # Average method
428 face_weight = np.average(weight_distribution[all_verts.reshape((all_verts.shape[0], -1))], axis=1) * len(components)
429 # Corners Method
430 #face_weight = (weight_distribution[v00] + weight_distribution[v01] + weight_distribution[v10] + weight_distribution[v11])/4 * len(components)
431 if fill_mode == 'FAN' and consistent_wedges:
432 for i in range(n_original_faces):
433 face_mask = faces_id == i
434 face_weight[face_mask] = np.average(face_weight[face_mask])
435 face_weight = face_weight.clip(max=len(components)-1)
436 coll_materials = coll_materials.astype('float')
437 coll_materials = face_weight + (coll_materials - face_weight)*vertex_group_distribution_factor
438 coll_materials = coll_materials.astype('int')
440 random.seed(rand_seed)
441 bool_correct = False
443 tt = tissue_time(tt, "Reading Vertex Groups", levels=2)
445 ### SMOOTH NORMALS
446 if smooth_normals:
447 weight_smooth_normals = 0.2
448 weight_smooth_normals0 = 0.2
449 if vertex_group_smooth_normals in ob0.vertex_groups.keys():
450 vg = ob0.vertex_groups[vertex_group_smooth_normals]
451 weight_smooth_normals0 = get_weight_numpy(vg, n_verts0)
452 if invert_vertex_group_smooth_normals:
453 weight_smooth_normals0 = 1-weight_smooth_normals0
454 weight_smooth_normals0 *= 0.2
456 verts0_normal = mesh_diffusion_vector(me0, verts0_normal, smooth_normals_iter, weight_smooth_normals0, smooth_normals_uv)
458 While in Relative thickness method the components are built
459 between the two surfaces, in Constant mode the thickness is uniform.
461 if scale_mode == 'CONSTANT':
462 # Normalize vectors
463 verts0_normal /= np.linalg.norm(verts0_normal, axis=1).reshape((-1,1))
464 # Compare to the original normals direction
465 original_normals = get_normals_numpy(me0)
466 verts0_normal /= np.multiply(verts0_normal, original_normals).sum(1)[:,None]
468 tt = tissue_time(tt, "Smooth Normals", levels=2)
470 if normals_mode in ('FACES', 'VERTS'):
471 normals_x = props['normals_x']
472 normals_y = props['normals_y']
473 normals_z = props['normals_z']
474 if bool_weight_normals:
475 if invert_vertex_group_scale_normals:
476 weight_normals = 1-weight_normals
477 w_normals_x = 1 - weight_normals * (1 - normals_x)
478 w_normals_y = 1 - weight_normals * (1 - normals_y)
479 w_normals_z = 1 - weight_normals * (1 - normals_z)
480 else:
481 w_normals_x = normals_x
482 w_normals_y = normals_y
483 w_normals_z = normals_z
484 if normals_x < 1: verts0_normal[:,0] *= w_normals_x
485 if normals_y < 1: verts0_normal[:,1] *= w_normals_y
486 if normals_z < 1: verts0_normal[:,2] *= w_normals_z
487 div_value = np.linalg.norm(verts0_normal, axis=1).reshape((-1,1))
488 div_value[div_value == 0] = 0.00001
489 verts0_normal /= div_value
491 ### ROTATE PATCHES ###
493 if rotation_mode != 'DEFAULT' or rotation_shift != 0:
495 # Weight rotation
496 weight_shift = 0
497 if rotation_mode == 'WEIGHT':
498 corners_id = np.array(((0,0,-1,-1),(0,-1,-1,0)))
499 corners = all_verts[:,corners_id[0],corners_id[1]]
500 corners_weight = weight_rotation[corners]
501 if invert_vertex_group_rotation:
502 corners_weight = 1-corners_weight
503 ids4 = np.arange(4)
504 if rotation_direction == 'DIAG':
505 c0 = corners_weight[:,ids4]
506 c3 = corners_weight[:,(ids4+2)%4]
507 differential = c3 - c0
508 else:
509 c0 = corners_weight[:,ids4]
510 c1 = corners_weight[:,(ids4+1)%4]
511 c2 = corners_weight[:,(ids4+2)%4]
512 c3 = corners_weight[:,(ids4+3)%4]
513 differential = - c0 + c1 + c2 - c3
514 weight_shift = np.argmax(differential, axis=1)
516 # Random rotation
517 random_shift = 0
518 if rotation_mode == 'RANDOM':
519 np.random.seed(rand_seed)
520 random_shift = np.random.randint(0,4,size=n_patches)*rand_step
522 # UV rotation
523 UV_shift = 0
524 if rotation_mode == 'UV' and ob0.type == 'MESH':
525 bm = bmesh.new()
526 bm.from_mesh(before_subsurf)
527 uv_lay = bm.loops.layers.uv.active
528 UV_shift = [0]*len(mask)
529 for f in bm.faces:
530 ll = f.loops
531 if len(ll) == 4:
532 uv0 = ll[0][uv_lay].uv
533 uv1 = ll[3][uv_lay].uv
534 uv2 = ll[2][uv_lay].uv
535 uv3 = ll[1][uv_lay].uv
537 v01 = (uv0 + uv1) # not necessary to divide by 2
538 v32 = (uv3 + uv2)
539 v0132 = v32 - v01 # axis vector 1
540 v0132.normalize() # based on the rotation not on the size
541 v12 = (uv1 + uv2)
542 v03 = (uv0 + uv3)
543 v1203 = v03 - v12 # axis vector 2
544 v1203.normalize() # based on the rotation not on the size
546 dot1203 = v1203.x
547 dot0132 = v0132.x
548 if(abs(dot1203) < abs(dot0132)): # already vertical
549 if (dot0132 > 0): shift = 0
550 else: shift = 2 # rotate 180°
551 else: # horizontal
552 if(dot1203 < 0): shift = 3
553 else: shift = 1
554 #UV_shift.append(shift)
555 UV_shift[f.index] = shift
557 UV_shift = np.array(UV_shift)[mask]
558 bm.free()
560 # Rotate Patch
561 rotation_shift = np.zeros((n_patches))+rotation_shift
562 rot = weight_shift + random_shift + UV_shift + rotation_shift
563 rot = rot%4
564 flip_u = np.logical_or(rot==2,rot==3)
565 flip_v = np.logical_or(rot==1,rot==2)
566 flip_uv = np.logical_or(rot==1,rot==3)
567 all_verts[flip_u] = all_verts[flip_u,::-1,:]
568 all_verts[flip_v] = all_verts[flip_v,:,::-1]
569 all_verts[flip_uv] = np.transpose(all_verts[flip_uv],(0,2,1))
571 tt = tissue_time(tt, "Rotations", levels=2)
573 #for o in bpy.context.view_layer.objects: o.select_set(False)
574 new_patch = None
576 ### COMPONENT ###
577 new_objects = []
579 # Store original values
580 _com_modifiers = com_modifiers
581 _bool_shapekeys = bool_shapekeys
583 for mat_id, _ob1 in enumerate(components):
584 if _ob1 == None: continue
586 # Set original values (for next commponents)
587 com_modifiers = _com_modifiers
588 bool_shapekeys = _bool_shapekeys
590 if component_mode != 'OBJECT':
591 if component_mode == 'COLLECTION':
592 mat_mask = coll_materials == mat_id
593 else:
594 mat_mask = materials == mat_id
595 if bool_material_id:
596 mat_mask = np.logical_and(mat_mask, materials == material_id)
597 masked_verts = all_verts[mat_mask]
598 masked_faces = mat_mask
599 elif bool_material_id:
600 masked_verts = all_verts[materials == material_id]
601 masked_faces = np.logical_and(mask, materials == material_id)
602 else:
603 masked_verts = all_verts
604 masked_faces = mask
605 n_patches = len(masked_verts)
606 if n_patches == 0: continue
608 if com_modifiers or _ob1.type != 'MESH': bool_shapekeys = False
610 # set Shape Keys to zero
611 original_key_values = None
612 if (bool_shapekeys or not com_modifiers) and _ob1.type == 'MESH':
613 if _ob1.data.shape_keys:
614 original_key_values = []
615 for sk in _ob1.data.shape_keys.key_blocks:
616 original_key_values.append(sk.value)
617 sk.value = 0
618 else:
619 bool_shapekeys = False
620 else: bool_shapekeys = False
622 if not com_modifiers and not bool_shapekeys:
623 mod_visibility = []
624 for m in _ob1.modifiers:
625 mod_visibility.append(m.show_viewport)
626 m.show_viewport = False
627 com_modifiers = True
628 ob1 = convert_object_to_mesh(_ob1, com_modifiers, False, False)
629 ob1, com_area = tessellate_prepare_component(ob1, props)
630 ob1.name = "_tissue_tmp_ob1"
632 # restore original modifiers visibility for component object
633 try:
634 for m, vis in zip(_ob1.modifiers, mod_visibility):
635 m.show_viewport = vis
636 except: pass
638 me1 = ob1.data
639 verts1 = [v.co for v in me1.vertices]
640 n_verts1 = len(verts1)
641 if n_verts1 == 0:
642 bpy.data.objects.remove(ob1)
643 continue
645 ### COMPONENT GRID COORDINATES ###
647 # find relative UV component's vertices
648 if fill_mode == 'PATCH':
649 verts1_uv_quads = [0]*n_verts1
650 verts1_uv = [0]*n_verts1
651 for i, vert in enumerate(verts1):
652 # grid coordinates
653 u = int(vert[0]//step)
654 v = int(vert[1]//step)
655 u1 = min(u+1, sides)
656 v1 = min(v+1, sides)
657 if mode != 'BOUNDS':
658 if u > sides-1:
659 u = sides-1
660 u1 = sides
661 if u < 0:
662 u = 0
663 u1 = 1
664 if v > sides-1:
665 v = sides-1
666 v1 = sides
667 if v < 0:
668 v = 0
669 v1 = 1
670 verts1_uv_quads[i] = (u,v,u1,v1)
671 # factor coordinates
672 fu = (vert[0]-u*step)/step
673 fv = (vert[1]-v*step)/step
674 fw = vert.z
675 # interpolate Z scaling factor
676 verts1_uv[i] = Vector((fu,fv,fw))
677 else:
678 verts1_uv = verts1
680 if bool_shapekeys:
681 sk_uv_quads = []
682 sk_uv = []
683 for sk in ob1.data.shape_keys.key_blocks[1:]:
684 source = sk.data
685 _sk_uv_quads = [0]*n_verts1
686 _sk_uv = [0]*n_verts1
687 for i, sk_v in enumerate(source):
688 sk_vert = sk_v.co
690 # grid coordinates
691 u = int(sk_vert[0]//step)
692 v = int(sk_vert[1]//step)
693 u1 = min(u+1, sides)
694 v1 = min(v+1, sides)
695 if mode != 'BOUNDS':
696 if u > sides-1:
697 u = sides-1
698 u1 = sides
699 if u < 0:
700 u = 0
701 u1 = 1
702 if v > sides-1:
703 v = sides-1
704 v1 = sides
705 if v < 0:
706 v = 0
707 v1 = 1
708 _sk_uv_quads[i] = (u,v,u1,v1)
709 # factor coordinates
710 fu = (sk_vert[0]-u*step)/step
711 fv = (sk_vert[1]-v*step)/step
712 fw = sk_vert.z
713 _sk_uv[i] = Vector((fu,fv,fw))
714 sk_uv_quads.append(_sk_uv_quads)
715 sk_uv.append(_sk_uv)
716 store_sk_coordinates = [[] for t in ob1.data.shape_keys.key_blocks[1:]]
717 sk_uv_quads = np.array(sk_uv_quads)
718 sk_uv = np.array(sk_uv)
720 np_verts1_uv = np.array(verts1_uv)
721 if fill_mode == 'PATCH':
722 verts1_uv_quads = np.array(verts1_uv_quads)
723 np_u = verts1_uv_quads[:,0]
724 np_v = verts1_uv_quads[:,1]
725 np_u1 = verts1_uv_quads[:,2]
726 np_v1 = verts1_uv_quads[:,3]
727 else:
728 np_u = 0
729 np_v = 0
730 np_u1 = 1
731 np_v1 = 1
733 tt = tissue_time(tt, "Component preparation", levels=2)
735 ### DEFORM PATCHES ###
737 verts_xyz = verts0_co[masked_verts]
738 v00 = verts_xyz[:, np_u, np_v].reshape((n_patches,-1,3))
739 v10 = verts_xyz[:, np_u1, np_v].reshape((n_patches,-1,3))
740 v01 = verts_xyz[:, np_u, np_v1].reshape((n_patches,-1,3))
741 v11 = verts_xyz[:, np_u1, np_v1].reshape((n_patches,-1,3))
742 vx = np_verts1_uv[:,0].reshape((1,n_verts1,1))
743 vy = np_verts1_uv[:,1].reshape((1,n_verts1,1))
744 vz = np_verts1_uv[:,2].reshape((1,n_verts1,1))
745 co2 = np_lerp2(v00, v10, v01, v11, vx, vy, 'verts')
747 ### PATCHES WEIGHT ###
748 weight_thickness = None
749 if bool_vertex_group:
750 n_vg = len(weight)
751 patches_weight = weight[:, masked_verts]
752 w00 = patches_weight[:, :, np_u, np_v].reshape((n_vg, n_patches,-1,1))
753 w10 = patches_weight[:, :, np_u1, np_v].reshape((n_vg, n_patches,-1,1))
754 w01 = patches_weight[:, :, np_u, np_v1].reshape((n_vg, n_patches,-1,1))
755 w11 = patches_weight[:, :, np_u1, np_v1].reshape((n_vg, n_patches,-1,1))
756 store_weight = np_lerp2(w00,w10,w01,w11,vx[None,:,:,:],vy[None,:,:,:],'weight')
758 if vertex_group_thickness in ob0.vertex_groups.keys():
759 vg_id = ob0.vertex_groups[vertex_group_thickness].index
760 weight_thickness = store_weight[vg_id,:,:]
761 if invert_vertex_group_thickness:
762 weight_thickness = 1-weight_thickness
763 fact = vertex_group_thickness_factor
764 if fact > 0:
765 weight_thickness = weight_thickness*(1-fact) + fact
766 if vertex_group_smooth_normals in ob0.vertex_groups.keys():
767 vg_id = ob0.vertex_groups[vertex_group_smooth_normals].index
768 weight_smooth_normals = store_weight[vg_id,:,:]
769 else:
770 # Read vertex group Thickness
771 if vertex_group_thickness in ob0.vertex_groups.keys():
772 vg = ob0.vertex_groups[vertex_group_thickness]
773 weight_thickness = get_weight_numpy(vg, n_verts0)
774 wt = weight_thickness[masked_verts]
775 wt = wt[:,:,:,np.newaxis]
776 w00 = wt[:, np_u, np_v].reshape((n_patches, -1, 1))
777 w10 = wt[:, np_u1, np_v].reshape((n_patches, -1, 1))
778 w01 = wt[:, np_u, np_v1].reshape((n_patches, -1, 1))
779 w11 = wt[:, np_u1, np_v1].reshape((n_patches, -1, 1))
780 weight_thickness = np_lerp2(w00,w10,w01,w11,vx,vy,'verts')
781 try:
782 weight_thickness.shape
783 if invert_vertex_group_thickness:
784 weight_thickness = 1-weight_thickness
785 fact = vertex_group_thickness_factor
786 if fact > 0:
787 weight_thickness = weight_thickness*(1-fact) + fact
788 except: pass
790 # Read vertex group smooth normals
791 if vertex_group_smooth_normals in ob0.vertex_groups.keys():
792 vg = ob0.vertex_groups[vertex_group_smooth_normals]
793 weight_smooth_normals = get_weight_numpy(vg, n_verts0)
794 wt = weight_smooth_normals[masked_verts]
795 wt = wt[:,:,:,None]
796 w00 = wt[:, np_u, np_v].reshape((n_patches, -1, 1))
797 w10 = wt[:, np_u1, np_v].reshape((n_patches, -1, 1))
798 w01 = wt[:, np_u, np_v1].reshape((n_patches, -1, 1))
799 w11 = wt[:, np_u1, np_v1].reshape((n_patches, -1, 1))
800 weight_smooth_normals = np_lerp2(w00,w10,w01,w11,vx,vy,'verts')
801 try:
802 weight_smooth_normals.shape
803 if invert_vertex_group_smooth_normals:
804 weight_smooth_normals = 1-weight_smooth_normals
805 #fact = vertex_group_thickness_factor
806 #if fact > 0:
807 # weight_thickness = weight_thickness*(1-fact) + fact
808 except: pass
810 if normals_mode == 'FACES':
811 n2 = get_attribute_numpy(before_subsurf.polygons,'normal',3)
812 n2 = n2[masked_faces][:,None,:]
813 else:
814 if normals_mode == 'CUSTOM':
815 me0.calc_normals_split()
816 normals_split = [0]*len(me0.loops)*3
817 vertex_indexes = [0]*len(me0.loops)
818 me0.loops.foreach_get('normal', normals_split)
819 me0.loops.foreach_get('vertex_index', vertex_indexes)
820 normals_split = np.array(normals_split).reshape(-1,3)
821 vertex_indexes = np.array(vertex_indexes)
822 verts0_normal = np.zeros((len(me0.vertices),3))
823 np.add.at(verts0_normal, vertex_indexes, normals_split)
824 indexes, counts = np.unique(vertex_indexes,return_counts=True)
825 verts0_normal[indexes] /= counts[:,np.newaxis]
827 if 'Eval_Normals' in me1.uv_layers.keys():
828 bm1 = bmesh.new()
829 bm1.from_mesh(me1)
830 uv_co = np.array(uv_from_bmesh(bm1, 'Eval_Normals'))
831 vx_nor = uv_co[:,0]#.reshape((1,n_verts1,1))
832 #vy_nor = uv_co[:,1]#.reshape((1,n_verts1,1))
834 # grid coordinates
835 np_u = np.clip(vx_nor//step, 0, sides).astype('int')
836 #np_v = np.maximum(vy_nor//step, 0).astype('int')
837 np_u1 = np.clip(np_u+1, 0, sides).astype('int')
838 #np_v1 = np.minimum(np_v+1, sides).astype('int')
840 vx_nor = (vx_nor - np_u * step)/step
841 #vy_nor = (vy_nor - np_v * step)/step
842 vx_nor = vx_nor.reshape((1,n_verts1,1))
843 #vy_nor = vy_nor.reshape((1,n_verts1,1))
844 vy_nor = vy
845 bm1.free()
846 else:
847 vx_nor = vx
848 vy_nor = vy
850 if normals_mode in ('SHAPEKEYS','OBJECT') and scale_mode == 'CONSTANT' and even_thickness:
851 verts_norm_pos = verts0_normal_pos[masked_verts]
852 verts_norm_neg = verts0_normal_neg[masked_verts]
853 nor_mask = (vz<0).reshape((-1))
854 n00 = verts_norm_pos[:, np_u, np_v].reshape((n_patches,-1,3))
855 n10 = verts_norm_pos[:, np_u1, np_v].reshape((n_patches,-1,3))
856 n01 = verts_norm_pos[:, np_u, np_v1].reshape((n_patches,-1,3))
857 n11 = verts_norm_pos[:, np_u1, np_v1].reshape((n_patches,-1,3))
858 n00_neg = verts_norm_neg[:, np_u, np_v].reshape((n_patches,-1,3))
859 n10_neg = verts_norm_neg[:, np_u1, np_v].reshape((n_patches,-1,3))
860 n01_neg = verts_norm_neg[:, np_u, np_v1].reshape((n_patches,-1,3))
861 n11_neg = verts_norm_neg[:, np_u1, np_v1].reshape((n_patches,-1,3))
862 n00[:,nor_mask] = n00_neg[:,nor_mask]
863 n10[:,nor_mask] = n10_neg[:,nor_mask]
864 n01[:,nor_mask] = n01_neg[:,nor_mask]
865 n11[:,nor_mask] = n11_neg[:,nor_mask]
866 else:
867 verts_norm = verts0_normal[masked_verts]
868 n00 = verts_norm[:, np_u, np_v].reshape((n_patches,-1,3))
869 n10 = verts_norm[:, np_u1, np_v].reshape((n_patches,-1,3))
870 n01 = verts_norm[:, np_u, np_v1].reshape((n_patches,-1,3))
871 n11 = verts_norm[:, np_u1, np_v1].reshape((n_patches,-1,3))
872 n2 = np_lerp2(n00, n10, n01, n11, vx_nor, vy_nor, 'verts')
874 # thickness variation
875 mean_area = []
876 a2 = None
877 if scale_mode == 'ADAPTIVE':# and normals_mode not in ('SHAPEKEYS','OBJECT'):
878 #com_area = bb[0]*bb[1]
879 if mode != 'BOUNDS' or com_area == 0: com_area = 1
880 if normals_mode == 'FACES':
881 if levels == 0 and True:
882 areas = [0]*len(mask)
883 before_subsurf.polygons.foreach_get('area',areas)
884 areas = np.sqrt(np.array(areas)/com_area)[masked_faces]
885 a2 = areas[:,None,None]
886 else:
887 areas = calc_verts_area_bmesh(me0)
888 verts_area = np.sqrt(areas*patch_faces/com_area)
889 verts_area = verts_area[masked_verts]
890 verts_area = verts_area.mean(axis=(1,2)).reshape((n_patches,1,1))
891 a2 = verts_area
892 if normals_mode in ('SHAPEKEYS','OBJECT'):
893 verts_area = np.ones(n_verts0)
894 verts_area = verts_area[masked_verts]
895 else:
896 areas = calc_verts_area_bmesh(me0)
897 verts_area = np.sqrt(areas*patch_faces/com_area)
898 verts_area = verts_area[masked_verts]
899 a00 = verts_area[:, np_u, np_v].reshape((n_patches,-1,1))
900 a10 = verts_area[:, np_u1, np_v].reshape((n_patches,-1,1))
901 a01 = verts_area[:, np_u, np_v1].reshape((n_patches,-1,1))
902 a11 = verts_area[:, np_u1, np_v1].reshape((n_patches,-1,1))
903 # remapped z scale
904 a2 = np_lerp2(a00,a10,a01,a11,vx,vy,'verts')
906 store_coordinates = calc_thickness(co2,n2,vz,a2,weight_thickness)
907 co2 = n2 = vz = a2 = None
909 if bool_shapekeys:
910 tt_sk = time.time()
911 n_sk = len(sk_uv_quads)
912 # ids of face corners for each vertex (n_sk, n_verts1, 4)
913 np_u = np.clip(sk_uv_quads[:,:,0], 0, sides).astype('int')[:,None,:]
914 np_v = np.clip(sk_uv_quads[:,:,1], 0, sides).astype('int')[:,None,:]
915 np_u1 = np.clip(sk_uv_quads[:,:,2], 0, sides).astype('int')[:,None,:]
916 np_v1 = np.clip(sk_uv_quads[:,:,3], 0, sides).astype('int')[:,None,:]
917 # face corners for each vertex (n_patches, n_sk, n_verts1, 4)
918 v00 = verts_xyz[:,np_u,np_v].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
919 v10 = verts_xyz[:,np_u1,np_v].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
920 v01 = verts_xyz[:,np_u,np_v1].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
921 v11 = verts_xyz[:,np_u1,np_v1].reshape((n_patches,n_sk,n_verts1,3))#.swapaxes(0,1)
922 vx = sk_uv[:,:,0].reshape((1,n_sk,n_verts1,1))
923 vy = sk_uv[:,:,1].reshape((1,n_sk,n_verts1,1))
924 vz = sk_uv[:,:,2].reshape((1,n_sk,n_verts1,1))
925 co2 = np_lerp2(v00,v10,v01,v11,vx,vy,mode='shapekeys')
927 if normals_mode == 'FACES':
928 n2 = n2[None,:,:,:]
929 else:
931 if normals_mode in ('SHAPEKEYS','OBJECT') and scale_mode == 'CONSTANT' and even_thickness:
932 verts_norm_pos = verts0_normal_pos[masked_verts]
933 verts_norm_neg = verts0_normal_neg[masked_verts]
934 nor_mask = (vz<0).reshape((-1))
935 n00 = verts_norm_pos[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3))
936 n10 = verts_norm_pos[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3))
937 n01 = verts_norm_pos[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3))
938 n11 = verts_norm_pos[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3))
939 n00_neg = verts_norm_neg[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3))
940 n10_neg = verts_norm_neg[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3))
941 n01_neg = verts_norm_neg[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3))
942 n11_neg = verts_norm_neg[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3))
943 n00[:,:,nor_mask] = n00_neg[:,:,nor_mask]
944 n10[:,:,nor_mask] = n10_neg[:,:,nor_mask]
945 n01[:,:,nor_mask] = n01_neg[:,:,nor_mask]
946 n11[:,:,nor_mask] = n11_neg[:,:,nor_mask]
947 else:
948 n00 = verts_norm[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,3))
949 n10 = verts_norm[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,3))
950 n01 = verts_norm[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,3))
951 n11 = verts_norm[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,3))
952 n2 = np_lerp2(n00,n10,n01,n11,vx,vy,'shapekeys')
954 # NOTE: weight thickness is based on the base position of the
955 # vertices, not on the coordinates of the shape keys
957 if scale_mode == 'ADAPTIVE':# and normals_mode not in ('OBJECT', 'SHAPEKEYS'): ### not sure
958 if normals_mode == 'FACES':
959 a2 = mean_area
960 else:
961 a00 = verts_area[:, np_u, np_v].reshape((n_patches,n_sk,n_verts1,1))
962 a10 = verts_area[:, np_u1, np_v].reshape((n_patches,n_sk,n_verts1,1))
963 a01 = verts_area[:, np_u, np_v1].reshape((n_patches,n_sk,n_verts1,1))
964 a11 = verts_area[:, np_u1, np_v1].reshape((n_patches,n_sk,n_verts1,1))
965 # remapped z scale
966 a2 = np_lerp2(a00,a10,a01,a11,vx,vy,'shapekeys')
968 store_sk_coordinates = calc_thickness(co2,n2,vz,a2,weight_thickness)
969 co2 = n2 = vz = a2 = weight_thickness = None
970 tissue_time(tt_sk, "Compute ShapeKeys", levels=3)
972 tt = tissue_time(tt, "Compute Coordinates", levels=2)
974 new_me = array_mesh(ob1, len(masked_verts))
975 tt = tissue_time(tt, "Repeat component", levels=2)
977 new_patch = bpy.data.objects.new("_tissue_tmp_patch", new_me)
978 bpy.context.collection.objects.link(new_patch)
980 store_coordinates = np.concatenate(store_coordinates, axis=0).reshape((-1)).tolist()
981 new_me.vertices.foreach_set('co',store_coordinates)
983 for area in bpy.context.screen.areas:
984 for space in area.spaces:
985 try: new_patch.local_view_set(space, True)
986 except: pass
987 tt = tissue_time(tt, "Inject coordinates", levels=2)
990 # Vertex Group
991 for vg in ob1.vertex_groups:
992 vg_name = vg.name
993 if vg_name in ob0.vertex_groups.keys():
994 if bool_vertex_group:
995 vg_name = '{} (Component)'.format(vg_name)
996 else:
997 vg_name = vg_name
998 #new_patch.vertex_groups.new(name=vg_name)
999 new_patch.vertex_groups[vg.name].name = vg_name
1001 if bool_vertex_group:
1002 new_groups = []
1003 for vg in ob0.vertex_groups:
1004 new_groups.append(new_patch.vertex_groups.new(name=vg.name))
1005 for vg, w in zip(new_groups, store_weight):
1006 set_weight_numpy(vg, w.reshape(-1))
1007 tt = tissue_time(tt, "Write Vertex Groups", levels=2)
1009 if bool_shapekeys:
1010 for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
1011 sk.value = val
1012 new_patch.shape_key_add(name=sk.name, from_mix=False)
1013 new_patch.data.shape_keys.key_blocks[sk.name].value = val
1014 for i in range(n_sk):
1015 coordinates = np.concatenate(store_sk_coordinates[:,i], axis=0)
1016 coordinates = coordinates.flatten().tolist()
1017 new_patch.data.shape_keys.key_blocks[i+1].data.foreach_set('co', coordinates)
1019 # set original values and combine Shape Keys and Vertex Groups
1020 for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
1021 sk.value = val
1022 new_patch.data.shape_keys.key_blocks[sk.name].value = val
1023 if bool_vertex_group:
1024 vg_keys = new_patch.vertex_groups.keys()
1025 for sk in new_patch.data.shape_keys.key_blocks:
1026 if sk.name in vg_keys:
1027 sk.vertex_group = sk.name
1028 tt = tissue_time(tt, "Shape Keys", levels=2)
1029 elif original_key_values:
1030 for sk, val in zip(_ob1.data.shape_keys.key_blocks, original_key_values):
1031 sk.value = val
1033 new_name = ob0.name + "_" + ob1.name
1034 new_patch.name = "_tissue_tmp_patch"
1035 new_patch.data.update() # needed for updating the normals
1036 new_objects.append(new_patch)
1037 bpy.data.objects.remove(ob1)
1038 bpy.data.objects.remove(ob0)
1039 tt = tissue_time(tt, "Closing Tessellate Iteration", levels=2)
1040 return new_objects
1042 class tissue_tessellate(Operator):
1043 bl_idname = "object.tissue_tessellate"
1044 bl_label = "Tissue Tessellate"
1045 bl_description = ("Create a copy of selected object on the active object's "
1046 "faces, adapting the shape to the different faces")
1047 bl_options = {'REGISTER', 'UNDO'}
1050 bool_hold : BoolProperty(
1051 name="Hold",
1052 description="Wait...",
1053 default=False
1055 object_name : StringProperty(
1056 name="",
1057 description="Name of the generated object"
1059 zscale : FloatProperty(
1060 name="Scale",
1061 default=1,
1062 soft_min=0,
1063 soft_max=10,
1064 description="Scale factor for the component thickness"
1066 scale_mode : EnumProperty(
1067 items=(
1068 ('CONSTANT', "Constant", "Uniform thickness"),
1069 ('ADAPTIVE', "Relative", "Preserve component's proportions")
1071 default='ADAPTIVE',
1072 name="Z-Scale according to faces size"
1074 offset : FloatProperty(
1075 name="Surface Offset",
1076 default=1,
1077 min=-1, max=1,
1078 soft_min=-1,
1079 soft_max=1,
1080 description="Surface offset"
1082 component_mode : EnumProperty(
1083 items=(
1084 ('OBJECT', "Object", "Use the same component object for all the faces"),
1085 ('COLLECTION', "Collection", "Use multiple components from Collection"),
1086 ('MATERIALS', "Materials", "Use multiple components by materials name")
1088 default='OBJECT',
1089 name="Component Mode"
1091 mode : EnumProperty(
1092 items=(
1093 ('BOUNDS', "Bounds", "The component fits automatically the size of the target face"),
1094 ('LOCAL', "Local", "Based on Local coordinates, from 0 to 1"),
1095 ('GLOBAL', 'Global', "Based on Global coordinates, from 0 to 1")),
1096 default='BOUNDS',
1097 name="Component Mode"
1099 rotation_mode : EnumProperty(
1100 items=(('RANDOM', "Random", "Random faces rotation"),
1101 ('UV', "Active UV", "Face rotation is based on UV coordinates"),
1102 ('WEIGHT', "Weight Gradient", "Rotate according to Vertex Group gradient"),
1103 ('DEFAULT', "Default", "Default rotation")),
1104 default='DEFAULT',
1105 name="Component Rotation"
1107 rotation_direction : EnumProperty(
1108 items=(('ORTHO', "Orthogonal", "Component main directions in XY"),
1109 ('DIAG', "Diagonal", "Component main direction aligned with diagonal")),
1110 default='ORTHO',
1111 name="Direction"
1113 rotation_shift : IntProperty(
1114 name="Shift",
1115 default=0,
1116 soft_min=0,
1117 soft_max=3,
1118 description="Shift components rotation"
1120 fill_mode : EnumProperty(
1121 items=(
1122 ('TRI', 'Tri', 'Triangulate the base mesh'),
1123 ('QUAD', 'Quad', 'Regular quad tessellation. Uses only 3 or 4 vertices'),
1124 ('FAN', 'Fan', 'Radial tessellation for polygonal faces'),
1125 ('PATCH', 'Patch', 'Curved tessellation according to the last ' +
1126 'Subsurf\n(or Multires) modifiers. Works only with 4 sides ' +
1127 'patches.\nAfter the last Subsurf (or Multires) only ' +
1128 'deformation\nmodifiers can be used'),
1129 ('FRAME', 'Frame', 'Tessellation along the edges of each face')),
1130 default='QUAD',
1131 name="Fill Mode"
1133 combine_mode : EnumProperty(
1134 items=(
1135 ('LAST', 'Last', 'Show only the last iteration'),
1136 ('UNUSED', 'Unused', 'Combine each iteration with the unused faces of the previous iteration. Used for branching systems'),
1137 ('ALL', 'All', 'Combine the result of all iterations')),
1138 default='LAST',
1139 name="Combine Mode",
1141 gen_modifiers : BoolProperty(
1142 name="Generator Modifiers",
1143 default=True,
1144 description="Apply Modifiers and Shape Keys to the base object"
1146 com_modifiers : BoolProperty(
1147 name="Component Modifiers",
1148 default=True,
1149 description="Apply Modifiers and Shape Keys to the component object"
1151 merge : BoolProperty(
1152 name="Merge",
1153 default=False,
1154 description="Merge vertices in adjacent duplicates"
1156 merge_open_edges_only : BoolProperty(
1157 name="Open edges only",
1158 default=True,
1159 description="Merge only open edges"
1161 merge_thres : FloatProperty(
1162 name="Distance",
1163 default=0.0001,
1164 soft_min=0,
1165 soft_max=10,
1166 description="Limit below which to merge vertices"
1168 bool_random : BoolProperty(
1169 name="Randomize",
1170 default=False,
1171 description="Randomize component rotation"
1173 rand_seed : IntProperty(
1174 name="Seed",
1175 default=0,
1176 soft_min=0,
1177 soft_max=10,
1178 description="Random seed"
1180 coll_rand_seed : IntProperty(
1181 name="Seed",
1182 default=0,
1183 soft_min=0,
1184 soft_max=10,
1185 description="Random seed"
1187 rand_step : IntProperty(
1188 name="Step",
1189 default=1,
1190 min=1,
1191 soft_max=2,
1192 description="Random step"
1194 bool_vertex_group : BoolProperty(
1195 name="Map Vertex Groups",
1196 default=False,
1197 description="Transfer all Vertex Groups from Base object"
1199 bool_selection : BoolProperty(
1200 name="On selected Faces",
1201 default=False,
1202 description="Create Tessellation only on selected faces"
1204 bool_shapekeys : BoolProperty(
1205 name="Use Shape Keys",
1206 default=False,
1207 description="Transfer Component's Shape Keys. If the name of Vertex "
1208 "Groups and Shape Keys are the same, they will be "
1209 "automatically combined"
1211 bool_smooth : BoolProperty(
1212 name="Smooth Shading",
1213 default=False,
1214 description="Output faces with smooth shading rather than flat shaded"
1216 bool_materials : BoolProperty(
1217 name="Transfer Materials",
1218 default=True,
1219 description="Preserve component's materials"
1221 generator : StringProperty(
1222 name="",
1223 description="Base object for the tessellation",
1224 default = ""
1226 component : StringProperty(
1227 name="",
1228 description="Component object for the tessellation",
1229 default = ""
1231 component_coll : StringProperty(
1232 name="",
1233 description="Components collection for the tessellation",
1234 default = ""
1236 target : StringProperty(
1237 name="",
1238 description="Target object for custom direction",
1239 default = ""
1241 even_thickness : BoolProperty(
1242 name="Even Thickness",
1243 default=False,
1244 description="Iterative sampling method for determine the correct length of the vectors (Experimental)"
1246 even_thickness_iter : IntProperty(
1247 name="Even Thickness Iterations",
1248 default=3,
1249 min = 1,
1250 soft_max = 20,
1251 description="More iterations produces more accurate results but make the tessellation slower"
1253 bool_material_id : BoolProperty(
1254 name="Tessellation on Material ID",
1255 default=False,
1256 description="Apply the component only on the selected Material"
1258 bool_dissolve_seams : BoolProperty(
1259 name="Dissolve Seams",
1260 default=False,
1261 description="Dissolve all seam edges"
1263 material_id : IntProperty(
1264 name="Material ID",
1265 default=0,
1266 min=0,
1267 description="Material ID"
1269 iterations : IntProperty(
1270 name="Iterations",
1271 default=1,
1272 min=1,
1273 soft_max=5,
1274 description="Automatically repeat the Tessellation using the "
1275 + "generated geometry as new base object.\nUsefull for "
1276 + "for branching systems. Dangerous!"
1278 bool_combine : BoolProperty(
1279 name="Combine unused",
1280 default=False,
1281 description="Combine the generated geometry with unused faces"
1283 bool_advanced : BoolProperty(
1284 name="Advanced Settings",
1285 default=False,
1286 description="Show more settings"
1288 normals_mode : EnumProperty(
1289 items=(
1290 ('VERTS', 'Normals', 'Consistent direction based on vertices normal'),
1291 ('FACES', 'Faces', 'Based on individual faces normal'),
1292 ('SHAPEKEYS', 'Keys', "According to base object's shape keys"),
1293 ('OBJECT', 'Object', "According to a target object")),
1294 default='VERTS',
1295 name="Direction"
1297 bounds_x : EnumProperty(
1298 items=(
1299 ('EXTEND', 'Extend', 'Default X coordinates'),
1300 ('CLIP', 'Clip', 'Trim out of bounds in X direction'),
1301 ('CYCLIC', 'Cyclic', 'Cyclic components in X direction')),
1302 default='EXTEND',
1303 name="Bounds X",
1305 bounds_y : EnumProperty(
1306 items=(
1307 ('EXTEND', 'Extend', 'Default Y coordinates'),
1308 ('CLIP', 'Clip', 'Trim out of bounds in Y direction'),
1309 ('CYCLIC', 'Cyclic', 'Cyclic components in Y direction')),
1310 default='EXTEND',
1311 name="Bounds Y",
1313 close_mesh : EnumProperty(
1314 items=(
1315 ('NONE', 'None', 'Keep the mesh open'),
1316 ('CAP', 'Cap Holes', 'Automatically cap open loops'),
1317 ('BRIDGE', 'Bridge Open Loops', 'Automatically bridge loop pairs'),
1318 ('BRIDGE_CAP', 'Custom', 'Bridge loop pairs and cap holes according to vertex groups')),
1319 default='NONE',
1320 name="Close Mesh"
1322 cap_faces : BoolProperty(
1323 name="Cap Holes",
1324 default=False,
1325 description="Cap open edges loops"
1327 frame_boundary : BoolProperty(
1328 name="Frame Boundary",
1329 default=False,
1330 description="Support face boundaries"
1332 fill_frame : BoolProperty(
1333 name="Fill Frame",
1334 default=False,
1335 description="Fill inner faces with Fan tessellation"
1337 boundary_mat_offset : IntProperty(
1338 name="Material Offset",
1339 default=0,
1340 description="Material Offset for boundaries (with Multi Components or Material ID)"
1342 fill_frame_mat : IntProperty(
1343 name="Material Offset",
1344 default=0,
1345 description="Material Offset for inner faces (with Multi Components or Material ID)"
1347 open_edges_crease : FloatProperty(
1348 name="Open Edges Crease",
1349 default=0,
1350 min=0,
1351 max=1,
1352 description="Automatically set crease for open edges"
1354 bridge_edges_crease : FloatProperty(
1355 name="Bridge Edges Crease",
1356 default=0,
1357 min=0,
1358 max=1,
1359 description="Automatically set crease for bridge edges"
1361 bridge_smoothness : FloatProperty(
1362 name="Smoothness",
1363 default=1,
1364 min=0,
1365 max=1,
1366 description="Bridge Smoothness"
1368 frame_thickness : FloatProperty(
1369 name="Frame Thickness",
1370 default=0.2,
1371 min=0,
1372 soft_max=1,
1373 description="Frame Thickness"
1375 frame_boundary_thickness : FloatProperty(
1376 name="Frame Boundary Thickness",
1377 default=0,
1378 min=0,
1379 soft_max=1,
1380 description="Frame Boundary Thickness (if zero, it uses the Frame Thickness instead)"
1382 frame_mode : EnumProperty(
1383 items=(
1384 ('CONSTANT', 'Constant', 'Even thickness'),
1385 ('RELATIVE', 'Relative', 'Frame offset depends on face areas'),
1386 ('CENTER', 'Center', 'Toward the center of the face (uses Incenter for Triangles)')),
1387 default='CONSTANT',
1388 name="Offset"
1390 bridge_cuts : IntProperty(
1391 name="Cuts",
1392 default=0,
1393 min=0,
1394 max=20,
1395 description="Bridge Cuts"
1397 cap_material_offset : IntProperty(
1398 name="Material Offset",
1399 default=0,
1400 min=0,
1401 description="Material index offset for the cap faces"
1403 bridge_material_offset : IntProperty(
1404 name="Material Offset",
1405 default=0,
1406 min=0,
1407 description="Material index offset for the bridge faces"
1409 patch_subs : IntProperty(
1410 name="Patch Subdivisions",
1411 default=1,
1412 min=0,
1413 description="Subdivisions levels for Patch tessellation after the first iteration"
1415 use_origin_offset : BoolProperty(
1416 name="Align to Origins",
1417 default=True,
1418 description="Define offset according to components origin and local Z coordinate"
1421 vertex_group_thickness : StringProperty(
1422 name="Thickness weight", default='',
1423 description="Vertex Group used for thickness"
1425 invert_vertex_group_thickness : BoolProperty(
1426 name="Invert", default=False,
1427 description="Invert the vertex group influence"
1429 vertex_group_thickness_factor : FloatProperty(
1430 name="Factor",
1431 default=0,
1432 min=0,
1433 max=1,
1434 description="Thickness factor to use for zero vertex group influence"
1437 vertex_group_frame_thickness : StringProperty(
1438 name="Frame thickness weight", default='',
1439 description="Vertex Group used for frame thickness"
1441 invert_vertex_group_frame_thickness : BoolProperty(
1442 name="Invert", default=False,
1443 description="Invert the vertex group influence"
1445 vertex_group_frame_thickness_factor : FloatProperty(
1446 name="Factor",
1447 default=0,
1448 min=0,
1449 max=1,
1450 description="Thickness factor to use for zero vertex group influence"
1452 face_weight_frame : BoolProperty(
1453 name="Face Weight",
1454 default=True,
1455 description="Uniform weight for individual faces"
1458 vertex_group_distribution : StringProperty(
1459 name="Distribution weight", default='',
1460 description="Vertex Group used for gradient distribution"
1462 invert_vertex_group_distribution : BoolProperty(
1463 name="Invert", default=False,
1464 description="Invert the vertex group influence"
1466 vertex_group_distribution_factor : FloatProperty(
1467 name="Factor",
1468 default=0,
1469 min=0,
1470 max=1,
1471 description="Randomness factor to use for zero vertex group influence"
1474 vertex_group_cap_owner : EnumProperty(
1475 items=(
1476 ('BASE', 'Base', 'Use base vertex group'),
1477 ('COMP', 'Component', 'Use component vertex group')),
1478 default='COMP',
1479 name="Source"
1481 vertex_group_cap : StringProperty(
1482 name="Cap Vertex Group", default='',
1483 description="Vertex Group used for cap open edges"
1485 invert_vertex_group_cap : BoolProperty(
1486 name="Invert", default=False,
1487 description="Invert the vertex group influence"
1490 vertex_group_bridge_owner : EnumProperty(
1491 items=(
1492 ('BASE', 'Base', 'Use base vertex group'),
1493 ('COMP', 'Component', 'Use component vertex group')),
1494 default='COMP',
1495 name="Source"
1497 vertex_group_bridge : StringProperty(
1498 name="Thickness weight", default='',
1499 description="Vertex Group used for bridge open edges"
1501 invert_vertex_group_bridge : BoolProperty(
1502 name="Invert", default=False,
1503 description="Invert the vertex group influence"
1506 vertex_group_rotation : StringProperty(
1507 name="Rotation weight", default='',
1508 description="Vertex Group used for rotation"
1510 invert_vertex_group_rotation : BoolProperty(
1511 name="Invert", default=False,
1512 description="Invert the vertex group influence"
1514 normals_x : FloatProperty(
1515 name="X", default=1, min=0, max=1,
1516 description="Scale X component of the normals"
1518 normals_y : FloatProperty(
1519 name="Y", default=1, min=0, max=1,
1520 description="Scale Y component of the normals"
1522 normals_z : FloatProperty(
1523 name="Z", default=1, min=0, max=1,
1524 description="Scale Z component of the normals"
1526 vertex_group_scale_normals : StringProperty(
1527 name="Scale normals weight", default='',
1528 description="Vertex Group used for editing the normals directions"
1530 invert_vertex_group_scale_normals : BoolProperty(
1531 name="Invert", default=False,
1532 description="Invert the vertex group influence"
1534 smooth_normals : BoolProperty(
1535 name="Smooth Normals", default=False,
1536 description="Smooth normals of the surface in order to reduce intersections"
1538 smooth_normals_iter : IntProperty(
1539 name="Iterations",
1540 default=5,
1541 min=0,
1542 description="Smooth iterations"
1544 smooth_normals_uv : FloatProperty(
1545 name="UV Anisotropy",
1546 default=0,
1547 min=-1,
1548 max=1,
1549 description="0 means no anisotropy, -1 represent the U direction, while 1 represent the V direction"
1551 vertex_group_smooth_normals : StringProperty(
1552 name="Smooth Normals weight", default='',
1553 description="Vertex Group used for smoothing normals"
1555 invert_vertex_group_smooth_normals : BoolProperty(
1556 name="Invert", default=False,
1557 description="Invert the vertex group influence"
1559 consistent_wedges : BoolProperty(
1560 name="Consistent Wedges", default=True,
1561 description="Use same component for the wedges generated by the Fan tessellation"
1563 boundary_variable_offset : BoolProperty(
1564 name="Boundary Variable Offset", default=False,
1565 description="Additional material offset based on the number of boundary vertices"
1567 auto_rotate_boundary : BoolProperty(
1568 name="Automatic Rotation", default=False,
1569 description="Automatically rotate the boundary faces"
1571 preserve_quads : BoolProperty(
1572 name="Preserve Quads",
1573 default=False,
1574 description="Quad faces are tessellated using QUAD mode"
1577 working_on = ""
1579 def draw(self, context):
1582 try:
1583 bool_working = self.working_on == self.object_name and \
1584 self.working_on != ""
1585 except:
1586 bool_working = False
1589 bool_working = False
1590 bool_allowed = False
1591 ob0 = None
1592 ob1 = None
1594 ob = context.object
1595 sel = context.selected_objects
1597 if len(sel) == 2:
1598 bool_allowed = True
1599 for o in sel:
1600 if o.type not in allowed_objects():
1601 bool_allowed = False
1603 if self.component_mode == 'OBJECT':
1604 if len(sel) != 2 and not bool_working:
1605 layout = self.layout
1606 layout.label(icon='OBJECT_DATA', text='Single Object Component')
1607 layout.label(icon='INFO', text="Please, select two different objects. Select first the")
1608 layout.label(text="Component object, then select the Base object.")
1609 return
1610 elif not bool_allowed and not bool_working:
1611 layout = self.layout
1612 layout.label(icon='OBJECT_DATA', text='Single Object Component')
1613 layout.label(icon='ERROR', text="Please, select Mesh, Curve, Surface, Meta or Text")
1614 return
1615 elif self.component_mode == 'COLLECTION':
1616 no_components = True
1617 for o in bpy.data.collections[self.component_coll].objects:
1618 if o.type in ('MESH', 'CURVE', 'META', 'SURFACE', 'FONT') and o is not ob0:
1619 no_components = False
1620 break
1621 if no_components:
1622 layout = self.layout
1623 layout.label(icon='OUTLINER_COLLECTION', text='Components from Active Collection')
1624 layout.label(icon='INFO', text="The Active Collection does not containt any Mesh,")
1625 layout.label(text="Curve, Surface, Meta or Text object.")
1626 return
1627 elif self.component_mode == 'MATERIALS':
1628 no_components = True
1629 for mat in ob.material_slots.keys():
1630 if mat in bpy.data.objects.keys():
1631 if bpy.data.objects[mat].type in allowed_objects():
1632 no_components = False
1633 break
1634 if no_components:
1635 layout = self.layout
1636 layout.label(icon='INFO', text='Components from Materials')
1637 layout.label(text="Can't find any object according to the materials name.")
1638 return
1640 if ob0 == ob1 == None:
1641 ob0 = context.object
1642 self.generator = ob0.name
1643 if self.component_mode == 'OBJECT':
1644 for o in sel:
1645 if o != ob0:
1646 ob1 = o
1647 self.component = o.name
1648 self.no_component = False
1649 break
1651 # new object name
1652 if self.object_name == "":
1653 if self.generator == "":
1654 self.object_name = "Tessellation"
1655 else:
1656 #self.object_name = self.generator + "_Tessellation"
1657 self.object_name = "Tessellation"
1659 layout = self.layout
1660 # Base and Component
1661 col = layout.column(align=True)
1662 #col.prop(self, "copy_settings")
1663 row = col.row(align=True)
1664 row.label(text="Base : " + self.generator, icon='OBJECT_DATA')
1665 if self.component_mode == 'OBJECT':
1666 row.label(text="Component : " + self.component, icon='OBJECT_DATA')
1667 elif self.component_mode == 'COLLECTION':
1668 row.label(text="Collection : " + self.component_coll, icon='OUTLINER_COLLECTION')
1669 elif self.component_mode == 'MATERIALS':
1670 row.label(text="Multiple Components", icon='MATERIAL')
1672 # Base Modifiers
1673 row = col.row(align=True)
1674 col2 = row.column(align=True)
1675 col2.prop(self, "gen_modifiers", text="Use Modifiers", icon='MODIFIER')
1676 base = bpy.data.objects[self.generator]
1678 # Component Modifiers
1679 row.separator()
1680 col3 = row.column(align=True)
1681 col3.prop(self, "com_modifiers", text="Use Modifiers", icon='MODIFIER')
1682 if self.component_mode == 'OBJECT':
1683 component = bpy.data.objects[self.component]
1684 col.separator()
1685 # Fill and Rotation
1686 row = col.row(align=True)
1687 row.label(text="Fill Mode:")
1688 row = col.row(align=True)
1689 row.prop(
1690 self, "fill_mode", icon='NONE', expand=True,
1691 slider=True, toggle=False, icon_only=False, event=False,
1692 full_event=False, emboss=True, index=-1)
1693 row = col.row(align=True)
1694 # merge settings
1695 row.prop(self, "merge")
1696 row.prop(self, "bool_smooth")
1698 # frame settings
1699 if self.fill_mode == 'FRAME':
1700 col.separator()
1701 col.label(text="Frame Settings:")
1702 col.prop(self, "preserve_quads", expand=True)
1703 col.separator()
1704 row = col.row(align=True)
1705 row.prop(self, "frame_mode", expand=True)
1706 col.prop(self, "frame_thickness", text='Thickness', icon='NONE')
1707 # Vertex Group Frame Thickness
1708 row = col.row(align=True)
1709 row.prop_search(self, 'vertex_group_frame_thickness',
1710 ob0, "vertex_groups", text='')
1711 col2 = row.column(align=True)
1712 row2 = col2.row(align=True)
1713 row2.prop(self, "invert_vertex_group_frame_thickness", text="",
1714 toggle=True, icon='ARROW_LEFTRIGHT')
1715 row2.prop(self, "vertex_group_frame_thickness_factor")
1716 row2.enabled = self.vertex_group_frame_thickness in ob0.vertex_groups.keys()
1717 col.separator()
1718 row = col.row(align=True)
1719 row.prop(self, "fill_frame", icon='NONE')
1720 show_frame_mat = self.component_mode == 'MATERIALS' or self.bool_material_id
1721 col2 = row.column(align=True)
1722 col2.prop(self, "fill_frame_mat", icon='NONE')
1723 col2.enabled = self.fill_frame and show_frame_mat
1724 row = col.row(align=True)
1725 row.prop(self, "frame_boundary", text='Boundary', icon='NONE')
1726 col2 = row.column(align=True)
1727 col2.prop(self, "boundary_mat_offset", icon='NONE')
1728 col2.enabled = self.frame_boundary and show_frame_mat
1729 if self.frame_boundary:
1730 col.separator()
1731 row = col.row(align=True)
1732 col.prop(self, "frame_boundary_thickness", icon='NONE')
1734 if self.rotation_mode == 'UV':
1735 uv_error = False
1736 if ob0.type != 'MESH':
1737 row = col.row(align=True)
1738 row.label(
1739 text="UV rotation supported only for Mesh objects",
1740 icon='ERROR')
1741 uv_error = True
1742 else:
1743 if len(ob0.data.uv_layers) == 0:
1744 row = col.row(align=True)
1745 check_name = self.generator
1746 row.label(text="'" + check_name +
1747 "' doesn't have UV Maps", icon='ERROR')
1748 uv_error = True
1749 if uv_error:
1750 row = col.row(align=True)
1751 row.label(text="Default rotation will be used instead",
1752 icon='INFO')
1754 # Component Z
1755 col.separator()
1756 col.label(text="Thickness:")
1757 row = col.row(align=True)
1758 row.prop(
1759 self, "scale_mode", text="Scale Mode", icon='NONE', expand=True,
1760 slider=False, toggle=False, icon_only=False, event=False,
1761 full_event=False, emboss=True, index=-1)
1762 col.prop(
1763 self, "zscale", text="Scale", icon='NONE', expand=False,
1764 slider=True, toggle=False, icon_only=False, event=False,
1765 full_event=False, emboss=True, index=-1)
1766 if self.mode == 'BOUNDS':
1767 row = col.row(align=True)
1768 row.prop(
1769 self, "offset", text="Offset", icon='NONE', expand=False,
1770 slider=True, toggle=False, icon_only=False, event=False,
1771 full_event=False, emboss=True, index=-1)
1772 row.enabled = not self.use_origin_offset
1773 col.separator()
1774 col.label(text="More settings in the Object Data Properties panel...", icon='PROPERTIES')
1777 def execute(self, context):
1778 try:
1779 ob0 = bpy.data.objects[self.generator]
1780 if self.component_mode == 'OBJECT':
1781 ob1 = bpy.data.objects[self.component]
1782 except:
1783 return {'CANCELLED'}
1785 self.object_name = "Tessellation"
1786 # Check if existing object with same name
1787 names = [o.name for o in bpy.data.objects]
1788 if self.object_name in names:
1789 count_name = 1
1790 while True:
1791 test_name = self.object_name + '.{:03d}'.format(count_name)
1792 if not (test_name in names):
1793 self.object_name = test_name
1794 break
1795 count_name += 1
1796 if self.component_mode == 'OBJECT':
1797 if ob1.type not in allowed_objects():
1798 message = "Component must be Mesh, Curve, Surface, Text or Meta object!"
1799 self.report({'ERROR'}, message)
1800 self.component = None
1802 if ob0.type not in allowed_objects():
1803 message = "Generator must be Mesh, Curve, Surface, Text or Meta object!"
1804 self.report({'ERROR'}, message)
1805 self.generator = ""
1807 if bpy.ops.object.select_all.poll():
1808 bpy.ops.object.select_all(action='TOGGLE')
1809 bpy.ops.object.mode_set(mode='OBJECT')
1811 bool_update = False
1812 if context.object == ob0:
1813 auto_layer_collection()
1814 new_ob = convert_object_to_mesh(ob0, False, False, self.rotation_mode!='UV') #///
1815 new_ob.data.name = self.object_name
1816 new_ob.name = self.object_name
1817 else:
1818 new_ob = context.object
1819 bool_update = True
1820 new_ob = store_parameters(self, new_ob)
1821 new_ob.tissue.tissue_type = 'TESSELLATE'
1822 try: bpy.ops.object.tissue_update_tessellate()
1823 except RuntimeError as e:
1824 bpy.data.objects.remove(new_ob)
1825 remove_temp_objects()
1826 self.report({'ERROR'}, str(e))
1827 return {'CANCELLED'}
1828 if not bool_update:
1829 self.object_name = new_ob.name
1830 #self.working_on = self.object_name
1831 new_ob.location = ob0.location
1832 new_ob.matrix_world = ob0.matrix_world
1834 # Assign collection of the base object
1835 old_coll = new_ob.users_collection
1836 if old_coll != ob0.users_collection:
1837 for c in old_coll:
1838 c.objects.unlink(new_ob)
1839 for c in ob0.users_collection:
1840 c.objects.link(new_ob)
1841 context.view_layer.objects.active = new_ob
1843 return {'FINISHED'}
1845 def invoke(self, context, event):
1846 return context.window_manager.invoke_props_dialog(self)
1849 class tissue_update_tessellate_deps(Operator):
1850 bl_idname = "object.tissue_update_tessellate_deps"
1851 bl_label = "Tissue Refresh"
1852 bl_description = ("Fast update the tessellated mesh according to base and "
1853 "component changes.")
1854 bl_options = {'REGISTER', 'UNDO'}
1856 go = False
1858 @classmethod
1859 def poll(cls, context):
1860 try:
1861 return context.object.tissue.tissue_type != 'NONE'
1862 except:
1863 return False
1865 #@staticmethod
1866 #def check_gen_comp(checking):
1867 # note pass the stored name key in here to check it out
1868 # return checking in bpy.data.objects.keys()
1870 def execute(self, context):
1872 active_ob = context.object
1873 selected_objects = context.selected_objects
1875 ### TO-DO: sorting according to dependencies
1876 update_objects = [o for o in selected_objects if o.tissue.tissue_type != 'NONE']
1877 for ob in selected_objects:
1878 update_objects = list(reversed(update_dependencies(ob, update_objects)))
1879 #update_objects = list(reversed(update_dependencies(ob, [ob])))
1880 for o in update_objects:
1881 override = {'object': o, 'selected_objects': [o]}
1882 with context.temp_override(**override):
1883 if o.type == 'MESH':
1884 if o.tissue.tissue_type == 'TESSELLATE':
1885 try:
1886 bpy.ops.object.tissue_update_tessellate()
1887 except:
1888 self.report({'ERROR'}, "Can't Tessellate :-(")
1889 if o.tissue.tissue_type == 'POLYHEDRA':
1890 try:
1891 bpy.ops.object.tissue_update_polyhedra()
1892 except:
1893 self.report({'ERROR'}, "Can't compute Polyhedra :-(")
1894 else:
1895 if o.tissue.tissue_type == 'TO_CURVE':
1896 try:
1897 bpy.ops.object.tissue_update_convert_to_curve()
1898 except:
1899 self.report({'ERROR'}, "Can't compute Curve :-(")
1900 if o.tissue.tissue_type == 'CONTOUR_CURVES':
1901 try:
1902 bpy.ops.object.tissue_update_contour_curves()
1903 except:
1904 self.report({'ERROR'}, "Can't compute Contour Curves :-(")
1906 context.view_layer.objects.active = active_ob
1907 for o in context.view_layer.objects:
1908 o.select_set(o in selected_objects)
1910 return {'FINISHED'}
1913 class tissue_update_tessellate(Operator):
1914 bl_idname = "object.tissue_update_tessellate"
1915 bl_label = "Tissue Refresh Simple"
1916 bl_description = ("Fast update the tessellated mesh according to base and "
1917 "component changes. Does not update dependencies")
1918 bl_options = {'REGISTER', 'UNDO'}
1920 go = False
1922 @classmethod
1923 def poll(cls, context):
1924 try:
1925 ob = context.object
1926 return ob.tissue.tissue_type == 'TESSELLATE'
1927 except:
1928 return False
1930 def execute(self, context):
1931 ob = context.object
1932 tissue_time(None,'Tissue: Tessellate of "{}"...'.format(ob.name), levels=0)
1933 start_time = time.time()
1935 props = props_to_dict(ob)
1936 if not self.go:
1937 generator = ob.tissue_tessellate.generator
1938 component = ob.tissue_tessellate.component
1939 zscale = ob.tissue_tessellate.zscale
1940 scale_mode = ob.tissue_tessellate.scale_mode
1941 rotation_mode = ob.tissue_tessellate.rotation_mode
1942 rotation_shift = ob.tissue_tessellate.rotation_shift
1943 rotation_direction = ob.tissue_tessellate.rotation_direction
1944 offset = ob.tissue_tessellate.offset
1945 merge = ob.tissue_tessellate.merge
1946 merge_open_edges_only = ob.tissue_tessellate.merge_open_edges_only
1947 merge_thres = ob.tissue_tessellate.merge_thres
1948 mode = ob.tissue_tessellate.mode
1949 gen_modifiers = ob.tissue_tessellate.gen_modifiers
1950 com_modifiers = ob.tissue_tessellate.com_modifiers
1951 bool_random = ob.tissue_tessellate.bool_random
1952 rand_seed = ob.tissue_tessellate.rand_seed
1953 rand_step = ob.tissue_tessellate.rand_step
1954 fill_mode = ob.tissue_tessellate.fill_mode
1955 bool_vertex_group = ob.tissue_tessellate.bool_vertex_group
1956 bool_selection = ob.tissue_tessellate.bool_selection
1957 bool_shapekeys = ob.tissue_tessellate.bool_shapekeys
1958 bool_smooth = ob.tissue_tessellate.bool_smooth
1959 bool_materials = ob.tissue_tessellate.bool_materials
1960 bool_dissolve_seams = ob.tissue_tessellate.bool_dissolve_seams
1961 bool_material_id = ob.tissue_tessellate.bool_material_id
1962 material_id = ob.tissue_tessellate.material_id
1963 iterations = ob.tissue_tessellate.iterations
1964 bool_combine = ob.tissue_tessellate.bool_combine
1965 normals_mode = ob.tissue_tessellate.normals_mode
1966 bool_advanced = ob.tissue_tessellate.bool_advanced
1967 #bool_multi_components = ob.tissue_tessellate.bool_multi_components
1968 combine_mode = ob.tissue_tessellate.combine_mode
1969 bounds_x = ob.tissue_tessellate.bounds_x
1970 bounds_y = ob.tissue_tessellate.bounds_y
1971 cap_faces = ob.tissue_tessellate.cap_faces
1972 close_mesh = ob.tissue_tessellate.close_mesh
1973 open_edges_crease = ob.tissue_tessellate.open_edges_crease
1974 bridge_edges_crease = ob.tissue_tessellate.bridge_edges_crease
1975 bridge_smoothness = ob.tissue_tessellate.bridge_smoothness
1976 frame_thickness = ob.tissue_tessellate.frame_thickness
1977 frame_boundary_thickness = ob.tissue_tessellate.frame_boundary_thickness
1978 frame_mode = ob.tissue_tessellate.frame_mode
1979 frame_boundary = ob.tissue_tessellate.frame_boundary
1980 fill_frame = ob.tissue_tessellate.fill_frame
1981 boundary_mat_offset = ob.tissue_tessellate.boundary_mat_offset
1982 fill_frame_mat = ob.tissue_tessellate.fill_frame_mat
1983 bridge_cuts = ob.tissue_tessellate.bridge_cuts
1984 cap_material_offset = ob.tissue_tessellate.cap_material_offset
1985 bridge_material_offset = ob.tissue_tessellate.bridge_material_offset
1986 patch_subs = ob.tissue_tessellate.patch_subs
1987 use_origin_offset = ob.tissue_tessellate.use_origin_offset
1988 vertex_group_thickness = ob.tissue_tessellate.vertex_group_thickness
1989 invert_vertex_group_thickness = ob.tissue_tessellate.invert_vertex_group_thickness
1990 vertex_group_thickness_factor = ob.tissue_tessellate.vertex_group_thickness_factor
1991 vertex_group_frame_thickness = ob.tissue_tessellate.vertex_group_frame_thickness
1992 invert_vertex_group_frame_thickness = ob.tissue_tessellate.invert_vertex_group_frame_thickness
1993 vertex_group_frame_thickness_factor = ob.tissue_tessellate.vertex_group_frame_thickness_factor
1994 face_weight_frame = ob.tissue_tessellate.face_weight_frame
1995 vertex_group_distribution = ob.tissue_tessellate.vertex_group_distribution
1996 invert_vertex_group_distribution = ob.tissue_tessellate.invert_vertex_group_distribution
1997 vertex_group_distribution_factor = ob.tissue_tessellate.vertex_group_distribution_factor
1998 vertex_group_cap_owner = ob.tissue_tessellate.vertex_group_cap_owner
1999 vertex_group_cap = ob.tissue_tessellate.vertex_group_cap
2000 invert_vertex_group_cap = ob.tissue_tessellate.invert_vertex_group_cap
2001 vertex_group_bridge_owner = ob.tissue_tessellate.vertex_group_bridge_owner
2002 vertex_group_bridge = ob.tissue_tessellate.vertex_group_bridge
2003 invert_vertex_group_bridge = ob.tissue_tessellate.invert_vertex_group_bridge
2004 vertex_group_rotation = ob.tissue_tessellate.vertex_group_rotation
2005 invert_vertex_group_rotation = ob.tissue_tessellate.invert_vertex_group_rotation
2006 vertex_group_smooth_normals = ob.tissue_tessellate.vertex_group_smooth_normals
2007 invert_vertex_group_smooth_normals = ob.tissue_tessellate.invert_vertex_group_smooth_normals
2008 target = ob.tissue_tessellate.target
2009 even_thickness = ob.tissue_tessellate.even_thickness
2010 even_thickness_iter = ob.tissue_tessellate.even_thickness_iter
2011 component_mode = ob.tissue_tessellate.component_mode
2012 component_coll = ob.tissue_tessellate.component_coll
2013 coll_rand_seed = ob.tissue_tessellate.coll_rand_seed
2014 try:
2015 generator.name
2016 if component_mode == 'OBJECT':
2017 component.name
2018 except:
2019 self.report({'ERROR'},
2020 "Active object must be Tessellated before Update")
2021 return {'CANCELLED'}
2023 # reset messages
2024 ob.tissue_tessellate.warning_message_merge = ''
2026 props = props_to_dict(ob)
2028 # Solve Local View issues
2029 local_spaces = []
2030 local_ob0 = []
2031 local_ob1 = []
2032 for area in context.screen.areas:
2033 for space in area.spaces:
2034 try:
2035 if ob.local_view_get(space):
2036 local_spaces.append(space)
2037 local_ob0 = ob0.local_view_get(space)
2038 ob0.local_view_set(space, True)
2039 local_ob1 = ob1.local_view_get(space)
2040 ob1.local_view_set(space, True)
2041 except:
2042 pass
2044 starting_mode = context.object.mode
2046 #if starting_mode == 'PAINT_WEIGHT': starting_mode = 'WEIGHT_PAINT'
2047 if bpy.ops.object.mode_set.poll():
2048 bpy.ops.object.mode_set(mode='OBJECT')
2050 ob0 = generator
2051 ob1 = component
2052 ##### auto_layer_collection()
2054 ob0_hide = ob0.hide_get()
2055 ob0_hidev = ob0.hide_viewport
2056 ob0_hider = ob0.hide_render
2057 ob0.hide_set(False)
2058 ob0.hide_viewport = False
2059 ob0.hide_render = False
2060 if component_mode == 'OBJECT':
2061 ob1_hide = ob1.hide_get()
2062 ob1_hidev = ob1.hide_viewport
2063 ob1_hider = ob1.hide_render
2064 ob1.hide_set(False)
2065 ob1.hide_viewport = False
2066 ob1.hide_render = False
2068 components = []
2069 if component_mode == 'COLLECTION':
2070 dict_components = {}
2071 meta_object = True
2072 for _ob1 in component_coll.objects:
2073 if _ob1 == ob: continue
2074 if _ob1.type in ('MESH', 'CURVE','SURFACE','FONT','META'):
2075 if _ob1.type == 'META':
2076 if meta_object: meta_object = False
2077 else: continue
2078 dict_components[_ob1.name] = _ob1
2079 for k in sorted(dict_components):
2080 components.append(dict_components[k])
2081 elif component_mode == 'OBJECT':
2082 components.append(ob1)
2084 if ob0.type == 'META':
2085 base_ob = convert_object_to_mesh(ob0, False, True, props['rotation_mode']!='UV')
2086 else:
2087 base_ob = ob0.copy()
2088 base_ob.data = ob0.data
2089 context.collection.objects.link(base_ob)
2090 base_ob.name = '_tissue_tmp_base'
2092 # In Blender 2.80 cache of copied objects is lost, must be re-baked
2093 bool_update_cloth = False
2094 for m in base_ob.modifiers:
2095 if m.type == 'CLOTH':
2096 m.point_cache.frame_end = context.scene.frame_current
2097 bool_update_cloth = True
2098 if bool_update_cloth:
2099 scene = context.scene
2100 for mod in base_ob.modifiers:
2101 if mod.type == 'CLOTH':
2102 override = {'scene': scene, 'active_object': base_ob, 'point_cache': mod.point_cache}
2103 with context.temp_override(**override):
2104 bpy.ops.ptcache.bake(bake=True)
2105 break
2106 base_ob.modifiers.update()
2108 # clear vertex groups before creating new ones
2109 if ob not in components: ob.vertex_groups.clear()
2111 if bool_selection:
2112 faces = base_ob.data.polygons
2113 selections = [False]*len(faces)
2114 faces.foreach_get('select',selections)
2115 selections = np.array(selections)
2116 if not selections.any():
2117 message = "There are no faces selected."
2118 context.view_layer.objects.active = ob
2119 ob.select_set(True)
2120 bpy.ops.object.mode_set(mode=starting_mode)
2121 remove_temp_objects()
2122 self.report({'ERROR'}, message)
2123 return {'CANCELLED'}
2125 iter_objects = [base_ob]
2126 ob_location = ob.location
2127 ob_matrix_world = ob.matrix_world
2129 #if ob not in components:
2130 ob.data.clear_geometry() # Faster with heavy geometries (from previous tessellations)
2132 for iter in range(iterations):
2133 props['generator'] = base_ob
2135 if iter > 0 and len(iter_objects) == 0: break
2136 if iter > 0 and normals_mode in ('SHAPEKEYS','OBJECT'):
2137 props['normals_mode'] = 'VERTS'
2138 same_iteration = []
2139 matched_materials = []
2141 if component_mode == 'MATERIALS':
2142 components = []
2143 objects_keys = bpy.data.objects.keys()
2144 for mat_slot in base_ob.material_slots:
2145 mat_name = mat_slot.material.name
2146 if mat_name in objects_keys:
2147 ob1 = bpy.data.objects[mat_name]
2148 if ob1.type in ('MESH', 'CURVE','SURFACE','FONT','META'):
2149 components.append(bpy.data.objects[mat_name])
2150 matched_materials.append(mat_name)
2151 else:
2152 components.append(None)
2153 else:
2154 components.append(None)
2155 props['component'] = components
2156 # patch subdivisions for additional iterations
2157 if iter > 0 and fill_mode == 'PATCH':
2158 temp_mod = base_ob.modifiers.new('Tissue_Subsurf', type='SUBSURF')
2159 temp_mod.levels = patch_subs
2161 # patch tessellation
2162 tissue_time(None,"Tessellate iteration...",levels=1)
2163 tt = time.time()
2164 same_iteration = tessellate_patch(props)
2165 tissue_time(tt, "Tessellate iteration",levels=1)
2166 tt = time.time()
2168 # if empty or error, continue
2169 #if type(same_iteration) != list:#is not bpy.types.Object and :
2170 # return {'CANCELLED'}
2172 for id, new_ob in enumerate(same_iteration):
2173 # rename, make active and change transformations
2174 new_ob.name = '_tissue_tmp_{}_{}'.format(iter,id)
2175 new_ob.select_set(True)
2176 context.view_layer.objects.active = new_ob
2177 new_ob.location = ob_location
2178 new_ob.matrix_world = ob_matrix_world
2180 base_ob.location = ob_location
2181 base_ob.matrix_world = ob_matrix_world
2182 # join together multiple components iterations
2183 if type(same_iteration) == list:
2184 if len(same_iteration) == 0:
2185 remove_temp_objects()
2186 tissue_time(None,"Can't Tessellate :-(",levels=0)
2187 return {'CANCELLED'}
2188 if len(same_iteration) > 1:
2189 #join_objects(context, same_iteration)
2190 new_ob = join_objects(same_iteration)
2192 if type(same_iteration) in (int,str):
2193 new_ob = same_iteration
2194 if iter == 0:
2195 try:
2196 bpy.data.objects.remove(iter_objects[0])
2197 iter_objects = []
2198 except: continue
2199 continue
2201 # Clean last iteration, needed for combine object
2202 if (bool_selection or bool_material_id) and combine_mode == 'UNUSED':
2203 # remove faces from last mesh
2204 bm = bmesh.new()
2205 if (fill_mode == 'PATCH' or gen_modifiers) and iter == 0:
2207 if props['rotation_mode']!='UV':
2208 last_mesh = simple_to_mesh_mirror(base_ob)#(ob0)
2209 else:
2210 last_mesh = simple_to_mesh(base_ob)#(ob0)
2211 else:
2212 last_mesh = iter_objects[-1].data.copy()
2213 bm.from_mesh(last_mesh)
2214 bm.faces.ensure_lookup_table()
2215 if component_mode == 'MATERIALS':
2216 remove_materials = matched_materials
2217 elif bool_material_id:
2218 remove_materials = [material_id]
2219 else: remove_materials = []
2220 if bool_selection:
2221 if component_mode == 'MATERIALS' or bool_material_id:
2222 remove_faces = [f for f in bm.faces if f.material_index in remove_materials and f.select]
2223 else:
2224 remove_faces = [f for f in bm.faces if f.select]
2225 else:
2226 remove_faces = [f for f in bm.faces if f.material_index in remove_materials]
2227 bmesh.ops.delete(bm, geom=remove_faces, context='FACES')
2228 bm.to_mesh(last_mesh)
2229 bm.free()
2230 last_mesh.update()
2231 last_mesh.name = '_tissue_tmp_previous_unused'
2233 # delete previous iteration if empty or update it
2234 if len(last_mesh.vertices) > 0:
2235 iter_objects[-1].data = last_mesh.copy()
2236 iter_objects[-1].data.update()
2237 else:
2238 bpy.data.objects.remove(iter_objects[-1])
2239 iter_objects = iter_objects[:-1]
2240 # set new base object for next iteration
2241 base_ob = convert_object_to_mesh(new_ob,True,True, props['rotation_mode']!='UV')
2242 if iter < iterations-1: new_ob.data = base_ob.data
2243 # store new iteration and set transformations
2244 iter_objects.append(new_ob)
2245 base_ob.name = '_tissue_tmp_base'
2246 elif combine_mode == 'ALL':
2247 base_ob = new_ob.copy()
2248 iter_objects = [new_ob] + iter_objects
2249 else:
2250 if base_ob != new_ob:
2251 bpy.data.objects.remove(base_ob)
2252 base_ob = new_ob
2253 iter_objects = [new_ob]
2255 if iter > 0:# and fill_mode == 'PATCH':
2256 base_ob.modifiers.clear()#remove(temp_mod)
2258 # Combine
2259 if combine_mode != 'LAST' and len(iter_objects) > 1:
2260 if base_ob not in iter_objects and type(base_ob) == bpy.types.Object:
2261 bpy.data.objects.remove(base_ob)
2262 new_ob = join_objects(iter_objects)
2263 new_ob.modifiers.clear()
2264 iter_objects = [new_ob]
2266 tissue_time(tt, "Combine tessellations", levels=1)
2268 if merge:
2269 new_ob.active_shape_key_index = 0
2270 use_bmesh = not (bool_shapekeys and fill_mode == 'PATCH' and component_mode != 'OBJECT')
2271 merged = merge_components(new_ob, ob.tissue_tessellate, use_bmesh)
2272 if merged == 'bridge_error':
2273 message = "Can't make the bridge!"
2274 ob.tissue_tessellate.warning_message_merge = message
2276 base_ob = new_ob #context.view_layer.objects.active
2278 tt = time.time()
2280 if new_ob == 0:
2281 #bpy.data.objects.remove(base_ob.data)
2282 try: bpy.data.objects.remove(base_ob)
2283 except: pass
2284 message = "The generated object is an empty geometry!"
2285 context.view_layer.objects.active = ob
2286 ob.select_set(True)
2287 bpy.ops.object.mode_set(mode=starting_mode)
2288 self.report({'ERROR'}, message)
2289 return {'CANCELLED'}
2290 errors = {}
2291 errors["modifiers_error"] = "Modifiers that change the topology of the mesh \n" \
2292 "after the last Subsurf (or Multires) are not allowed."
2293 if new_ob in errors:
2294 for o in iter_objects:
2295 try: bpy.data.objects.remove(o)
2296 except: pass
2297 #try: bpy.data.meshes.remove(data1)
2298 #except: pass
2299 context.view_layer.objects.active = ob
2300 ob.select_set(True)
2301 message = errors[new_ob]
2302 ob.tissue_tessellate.error_message = message
2303 bpy.ops.object.mode_set(mode=starting_mode)
2304 self.report({'ERROR'}, message)
2305 return {'CANCELLED'}
2307 # update data and preserve name
2308 if ob.type != 'MESH':
2309 loc, matr = ob.location, ob.matrix_world
2310 ob = convert_object_to_mesh(ob,False,True,props['rotation_mode']!='UV')
2311 ob.location, ob.matrix_world = loc, matr
2312 data_name = ob.data.name
2313 old_data = ob.data
2314 old_data.name = '_tissue_tmp_old_data'
2315 #ob.data = bpy.data.meshes.new_from_object(new_ob)#
2316 linked_objects = [o for o in bpy.data.objects if o.data == old_data]
2318 for o in linked_objects:
2319 o.data = new_ob.data
2320 if len(linked_objects) > 1:
2321 copy_tessellate_props(ob, o)
2323 #ob.data = new_ob.data
2324 ob.data.name = data_name
2325 bpy.data.meshes.remove(old_data)
2328 # copy vertex group
2329 for vg in new_ob.vertex_groups:
2330 if not vg.name in ob.vertex_groups.keys():
2331 ob.vertex_groups.new(name=vg.name)
2334 selected_objects = [o for o in context.selected_objects]
2335 for o in selected_objects: o.select_set(False)
2337 ob.select_set(True)
2338 context.view_layer.objects.active = ob
2340 is_multiple = iterations > 1 or combine_mode != 'LAST'# or bool_multi_components
2341 if merge and is_multiple:
2342 use_bmesh = not (bool_shapekeys and fill_mode == 'PATCH' and component_mode != 'OBJECT')
2343 merge_components(new_ob, ob.tissue_tessellate, use_bmesh)
2345 if bool_smooth: bpy.ops.object.shade_smooth()
2347 for mesh in bpy.data.meshes:
2348 if not mesh.users: bpy.data.meshes.remove(mesh)
2350 for o in selected_objects:
2351 try: o.select_set(True)
2352 except: pass
2354 ob.tissue_tessellate.error_message = ""
2356 # Restore Base visibility
2357 ob0.hide_set(ob0_hide)
2358 ob0.hide_viewport = ob0_hidev
2359 ob0.hide_render = ob0_hider
2360 # Restore Component visibility
2361 if component_mode == 'OBJECT':
2362 ob1.hide_set(ob1_hide)
2363 ob1.hide_viewport = ob1_hidev
2364 ob1.hide_render = ob1_hider
2365 # Restore Local visibility
2366 for space, local0, local1 in zip(local_spaces, local_ob0, local_ob1):
2367 ob0.local_view_set(space, local0)
2368 ob1.local_view_set(space, local1)
2370 bpy.data.objects.remove(new_ob)
2372 remove_temp_objects()
2374 tissue_time(tt, "Closing tessellation", levels=1)
2376 tissue_time(start_time,'Tessellate',levels=0)
2377 return {'FINISHED'}
2379 def check(self, context):
2380 return True
2382 class TISSUE_PT_tessellate(Panel):
2383 bl_label = "Tissue Tools"
2384 bl_category = "Tissue"
2385 bl_space_type = "VIEW_3D"
2386 bl_region_type = "UI"
2387 #bl_options = {'DEFAULT_OPEN'}
2389 @classmethod
2390 def poll(cls, context):
2391 return context.mode in {'OBJECT', 'EDIT_MESH'}
2393 def draw(self, context):
2394 layout = self.layout
2396 col = layout.column(align=True)
2397 col.label(text="Generate:")
2398 row = col.row(align=True)
2399 row.operator("object.tissue_tessellate", text='Tessellate', icon='OBJECT_DATA').component_mode = 'OBJECT'
2400 tss = row.operator("object.tissue_tessellate", text='', icon='OUTLINER_COLLECTION')
2401 tss.component_mode = 'COLLECTION'
2402 tss.component_coll = context.collection.name
2403 row.operator("object.tissue_tessellate", text='', icon='MATERIAL').component_mode = 'MATERIALS'
2404 #col.operator("object.tissue_tessellate_multi", text='Tessellate Multi')
2405 col.operator("object.dual_mesh_tessellated", text='Dual Mesh', icon='SEQ_CHROMA_SCOPE')
2406 col.separator()
2408 op = col.operator("object.polyhedral_wireframe", icon='MESH_CUBE', text='Polyhedral Decomposition')
2409 op.mode = 'POLYHEDRA'
2410 op = col.operator("object.polyhedral_wireframe", icon='MOD_WIREFRAME', text='Polyhedral Wireframe')
2411 op.mode = 'WIREFRAME'
2412 col.separator()
2414 #col.label(text="Curves:")
2415 col.operator("object.tissue_convert_to_curve", icon='OUTLINER_OB_CURVE', text="Convert to Curve")
2416 col.operator("object.tissue_weight_contour_curves_pattern", icon='FORCE_TURBULENCE', text="Contour Curves")
2418 #row.operator("object.tissue_update_convert_to_curve", icon='FILE_REFRESH', text='')
2420 col.separator()
2421 col.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') #####
2423 col.separator()
2424 col.label(text="Rotate Faces:")
2425 row = col.row(align=True)
2426 row.operator("mesh.tissue_rotate_face_left", text='Left', icon='LOOP_BACK')
2427 row.operator("mesh.tissue_rotate_face_flip", text='Flip', icon='UV_SYNC_SELECT')
2428 row.operator("mesh.tissue_rotate_face_right", text='Right', icon='LOOP_FORWARDS')
2430 col.separator()
2431 col.label(text="Other:")
2432 col.operator("object.dual_mesh", icon='SEQ_CHROMA_SCOPE')
2433 col.operator("object.lattice_along_surface", icon="OUTLINER_OB_LATTICE")
2435 act = context.object
2436 if act and act.type == 'MESH':
2437 col.operator("object.uv_to_mesh", icon="UV")
2439 if act.mode == 'EDIT':
2440 col.separator()
2441 col.label(text="Weight:")
2442 col.operator("object.tissue_weight_distance", icon="TRACKING")
2443 col.operator("object.tissue_weight_streamlines", icon="ANIM")
2445 col.separator()
2446 col.label(text="Materials:")
2447 col.operator("object.random_materials", icon='COLOR')
2448 col.operator("object.weight_to_materials", icon='GROUP_VERTEX')
2450 col.separator()
2451 col.label(text="Utils:")
2452 col.operator("render.tissue_render_animation", icon='RENDER_ANIMATION')
2454 class TISSUE_PT_tessellate_object(Panel):
2455 bl_space_type = 'PROPERTIES'
2456 bl_region_type = 'WINDOW'
2457 bl_context = "data"
2458 bl_label = "Tissue Tessellate"
2459 bl_options = {'DEFAULT_CLOSED'}
2461 @classmethod
2462 def poll(cls, context):
2463 try:
2464 return context.object.type == 'MESH'
2465 except: return False
2467 def draw(self, context):
2468 ob = context.object
2469 props = ob.tissue_tessellate
2470 tissue_props = ob.tissue
2472 bool_tessellated = tissue_props.tissue_type == 'TESSELLATE'
2473 layout = self.layout
2474 if not bool_tessellated:
2475 layout.label(text="The selected object is not a Tessellated object",
2476 icon='INFO')
2477 else:
2478 if props.error_message != "":
2479 layout.label(text=props.error_message,
2480 icon='ERROR')
2481 col = layout.column(align=True)
2482 row = col.row(align=True)
2484 set_tissue_handler(self,context)
2485 ###### set_animatable_fix_handler(self,context)
2486 row.operator("object.tissue_update_tessellate_deps", icon='FILE_REFRESH', text='Refresh') ####
2487 lock_icon = 'LOCKED' if tissue_props.bool_lock else 'UNLOCKED'
2488 #lock_icon = 'PINNED' if props.bool_lock else 'UNPINNED'
2489 deps_icon = 'LINKED' if tissue_props.bool_dependencies else 'UNLINKED'
2490 row.prop(tissue_props, "bool_dependencies", text="", icon=deps_icon)
2491 row.prop(tissue_props, "bool_lock", text="", icon=lock_icon)
2492 col2 = row.column(align=True)
2493 col2.prop(tissue_props, "bool_run", text="", icon='TIME')
2494 col2.enabled = not tissue_props.bool_lock
2495 col2 = row.column(align=True)
2496 col2.operator("mesh.tissue_remove", text="", icon='X')
2497 #layout.use_property_split = True
2498 #layout.use_property_decorate = False # No animation.
2499 col = layout.column(align=True)
2500 col.label(text='Base object:')
2501 row = col.row(align=True)
2502 row.prop_search(props, "generator", context.scene, "objects")
2503 col2 = row.column(align=True)
2504 col2.prop(props, "gen_modifiers", text='Use Modifiers',icon='MODIFIER')
2506 layout.use_property_split = False
2507 # Fill
2508 col = layout.column(align=True)
2509 col.label(text="Fill Mode:")
2511 # fill
2512 row = col.row(align=True)
2513 row.prop(props, "fill_mode", icon='NONE', expand=True,
2514 slider=True, toggle=False, icon_only=False, event=False,
2515 full_event=False, emboss=True, index=-1)
2517 #layout.use_property_split = True
2518 col = layout.column(align=True)
2519 col.prop(props, "bool_smooth")
2522 class TISSUE_PT_tessellate_frame(Panel):
2523 bl_space_type = 'PROPERTIES'
2524 bl_region_type = 'WINDOW'
2525 bl_context = "data"
2526 bl_parent_id = "TISSUE_PT_tessellate_object"
2527 bl_label = "Frame Settings"
2528 #bl_options = {'DEFAULT_CLOSED'}
2530 @classmethod
2531 def poll(cls, context):
2532 try:
2533 bool_frame = context.object.tissue_tessellate.fill_mode == 'FRAME'
2534 bool_tessellated = context.object.tissue_tessellate.generator != None
2535 return context.object.type == 'MESH' and bool_frame and bool_tessellated and context.object.tissue.tissue_type == 'TESSELLATE'
2536 except:
2537 return False
2539 def draw(self, context):
2540 ob = context.object
2541 props = ob.tissue_tessellate
2542 layout = self.layout
2543 col = layout.column(align=True)
2544 col.prop(props, "preserve_quads")
2545 col.separator()
2546 row = col.row(align=True)
2547 row.prop(props, "frame_mode", expand=True)
2548 row = col.row(align=True)
2549 row.prop(props, "frame_thickness", icon='NONE', expand=True)
2551 # Vertex Group Frame Thickness
2552 row = col.row(align=True)
2553 ob0 = props.generator
2554 row.prop_search(props, 'vertex_group_frame_thickness',
2555 ob0, "vertex_groups", text='')
2556 col2 = row.column(align=True)
2557 row2 = col2.row(align=True)
2558 row2.prop(props, "invert_vertex_group_frame_thickness", text="",
2559 toggle=True, icon='ARROW_LEFTRIGHT')
2560 row2.prop(props, "vertex_group_frame_thickness_factor")
2561 row2.enabled = props.vertex_group_frame_thickness in ob0.vertex_groups.keys()
2562 row = col.row(align=True)
2563 row.prop(props, "face_weight_frame")
2564 row.enabled = props.vertex_group_frame_thickness in ob0.vertex_groups.keys()
2566 col.separator()
2567 row = col.row(align=True)
2568 row.prop(props, "fill_frame", icon='NONE')
2569 show_frame_mat = props.component_mode == 'MATERIALS' or props.bool_material_id
2570 col2 = row.column(align=True)
2571 col2.prop(props, "fill_frame_mat", icon='NONE')
2572 col2.enabled = props.fill_frame and show_frame_mat
2573 row = col.row(align=True)
2574 row.prop(props, "frame_boundary", text='Boundary', icon='NONE')
2575 col2 = row.column(align=True)
2576 col2.prop(props, "boundary_mat_offset", icon='NONE')
2577 col2.enabled = props.frame_boundary and show_frame_mat
2578 if props.frame_boundary:
2579 col.separator()
2580 row = col.row(align=True)
2581 col.prop(props, "frame_boundary_thickness", icon='NONE')
2584 class TISSUE_PT_tessellate_component(Panel):
2585 bl_space_type = 'PROPERTIES'
2586 bl_region_type = 'WINDOW'
2587 bl_context = "data"
2588 bl_parent_id = "TISSUE_PT_tessellate_object"
2589 bl_label = "Components"
2590 #bl_options = {'DEFAULT_CLOSED'}
2592 @classmethod
2593 def poll(cls, context):
2594 try:
2595 bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
2596 return context.object.type == 'MESH' and bool_tessellated
2597 except:
2598 return False
2600 def draw(self, context):
2601 ob = context.object
2602 props = ob.tissue_tessellate
2604 layout = self.layout
2605 col = layout.column(align=True)
2606 col.label(text='Component Mode:')
2607 row = col.row(align=True)
2608 row.prop(props, "component_mode", icon='NONE', expand=True,
2609 slider=True, toggle=False, icon_only=False, event=False,
2610 full_event=False, emboss=True, index=-1)
2612 if props.component_mode == 'OBJECT':
2613 col.separator()
2614 row = col.row(align=True)
2615 row.prop_search(props, "component", context.scene, "objects")
2616 col2 = row.column(align=True)
2617 col2.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER')
2618 elif props.component_mode == 'COLLECTION':
2619 col.separator()
2621 if props.component_coll in list(bpy.data.collections):
2622 components = []
2623 for o in props.component_coll.objects:
2624 if o.type in allowed_objects() and o is not ob:
2625 components.append(o.name)
2626 n_comp = len(components)
2627 if n_comp == 0:
2628 col.label(text="Can't find components in the Collection.", icon='ERROR')
2629 else:
2630 text = "{} Component{}".format(n_comp,"s" if n_comp>1 else "")
2631 row = col.row(align=True)
2632 row.label(text=text, icon='OBJECT_DATA')
2633 row.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER')
2634 else:
2635 col.label(text="Please, chose one Collection.", icon='ERROR')
2637 col.separator()
2638 row = col.row(align=True)
2639 row.prop_search(props,'component_coll',bpy.data,'collections')
2640 col2 = row.column(align=True)
2641 col2.prop(props, "coll_rand_seed")
2642 col = layout.column(align=True)
2643 row = col.row(align=True)
2644 ob0 = props.generator
2645 row.prop_search(props, 'vertex_group_distribution',
2646 ob0, "vertex_groups", text='')
2647 col2 = row.column(align=True)
2648 row2 = col2.row(align=True)
2649 row2.prop(props, "invert_vertex_group_distribution", text="",
2650 toggle=True, icon='ARROW_LEFTRIGHT')
2651 row2.prop(props, "vertex_group_distribution_factor")
2652 row2.enabled = props.vertex_group_distribution in ob0.vertex_groups.keys()
2653 if props.fill_mode == 'FAN': col.prop(props, "consistent_wedges")
2654 else:
2655 components = []
2656 for mat in props.generator.material_slots.keys():
2657 if mat in bpy.data.objects.keys():
2658 if bpy.data.objects[mat].type in allowed_objects():
2659 components.append(mat)
2660 n_comp = len(components)
2661 if n_comp == 0:
2662 col.label(text="Can't find components from the materials.", icon='ERROR')
2663 else:
2664 col.separator()
2665 text = "{} Component{}".format(n_comp,"s" if n_comp>1 else "")
2666 row = col.row(align=True)
2667 row.label(text=text, icon='OBJECT_DATA')
2668 row.prop(props, "com_modifiers", text='Use Modifiers',icon='MODIFIER')
2670 if props.fill_mode != 'FRAME':
2671 col.separator()
2672 col.separator()
2673 row = col.row(align=True)
2674 row.label(text="Boundary Faces:")
2675 row.prop(props, "boundary_mat_offset", icon='NONE')
2676 row = col.row(align=True)
2677 row.prop(props, "boundary_variable_offset", text='Variable Offset', icon='NONE')
2678 row.prop(props, "auto_rotate_boundary", icon='NONE')
2679 col.separator()
2681 class TISSUE_PT_tessellate_coordinates(Panel):
2682 bl_space_type = 'PROPERTIES'
2683 bl_region_type = 'WINDOW'
2684 bl_context = "data"
2685 bl_parent_id = "TISSUE_PT_tessellate_object"
2686 bl_label = "Components Coordinates"
2687 bl_options = {'DEFAULT_CLOSED'}
2689 @classmethod
2690 def poll(cls, context):
2691 try:
2692 bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
2693 return context.object.type == 'MESH' and bool_tessellated
2694 except:
2695 return False
2697 def draw(self, context):
2698 ob = context.object
2699 props = ob.tissue_tessellate
2700 layout = self.layout
2702 col = layout.column(align=True)
2703 # component XY
2704 row = col.row(align=True)
2705 row.prop(props, "mode", expand=True)
2707 if props.mode != 'BOUNDS':
2708 col.separator()
2709 row = col.row(align=True)
2710 row.label(text="X:")
2711 row.prop(
2712 props, "bounds_x", text="Bounds X", icon='NONE', expand=True,
2713 slider=False, toggle=False, icon_only=False, event=False,
2714 full_event=False, emboss=True, index=-1)
2716 row = col.row(align=True)
2717 row.label(text="Y:")
2718 row.prop(
2719 props, "bounds_y", text="Bounds X", icon='NONE', expand=True,
2720 slider=False, toggle=False, icon_only=False, event=False,
2721 full_event=False, emboss=True, index=-1)
2724 class TISSUE_PT_tessellate_rotation(Panel):
2725 bl_space_type = 'PROPERTIES'
2726 bl_region_type = 'WINDOW'
2727 bl_context = "data"
2728 bl_parent_id = "TISSUE_PT_tessellate_object"
2729 bl_label = "Rotation"
2730 bl_options = {'DEFAULT_CLOSED'}
2732 @classmethod
2733 def poll(cls, context):
2734 try:
2735 bool_tessellated = context.object.tissue.tissue_type == 'TESSELLATE'
2736 return context.object.type == 'MESH' and bool_tessellated
2737 except:
2738 return False
2740 def draw(self, context):
2741 ob = context.object
2742 props = ob.tissue_tessellate
2743 layout = self.layout
2744 # rotation
2745 layout.use_property_split = True
2746 layout.use_property_decorate = False # No animation.
2747 col = layout.column(align=True)
2748 col.prop(props, "rotation_mode", text='Rotation', icon='NONE', expand=False,
2749 slider=True, toggle=False, icon_only=False, event=False,
2750 full_event=False, emboss=True, index=-1)
2751 if props.rotation_mode == 'WEIGHT':
2752 col.separator()
2753 row = col.row(align=True)
2754 row.separator()
2755 row.separator()
2756 row.separator()
2757 ob0 = props['generator']
2758 row.prop_search(props, 'vertex_group_rotation',
2759 ob0, "vertex_groups", text='Vertex Group')
2760 col2 = row.column(align=True)
2761 col2.prop(props, "invert_vertex_group_rotation", text="", toggle=True, icon='ARROW_LEFTRIGHT')
2762 col2.enabled = props.vertex_group_rotation in ob0.vertex_groups.keys()
2763 col.separator()
2764 col.prop(props, "rotation_direction", expand=False,
2765 slider=True, toggle=False, icon_only=False, event=False,
2766 full_event=False, emboss=True, index=-1)
2767 if props.rotation_mode == 'RANDOM':
2768 col.prop(props, "rand_seed")
2769 col.prop(props, "rand_step")
2770 else:
2771 col.prop(props, "rotation_shift")
2773 if props.rotation_mode == 'UV':
2774 uv_error = False
2775 if props.generator.type != 'MESH':
2776 row = col.row(align=True)
2777 row.label(
2778 text="UV rotation supported only for Mesh objects",
2779 icon='ERROR')
2780 uv_error = True
2781 else:
2782 if len(props.generator.data.uv_layers) == 0:
2783 row = col.row(align=True)
2784 row.label(text="'" + props.generator.name +
2785 " doesn't have UV Maps", icon='ERROR')
2786 uv_error = True
2787 if uv_error:
2788 row = col.row(align=True)
2789 row.label(text="Default rotation will be used instead",
2790 icon='INFO')
2792 class TISSUE_PT_tessellate_thickness(Panel):
2793 bl_space_type = 'PROPERTIES'
2794 bl_region_type = 'WINDOW'
2795 bl_context = "data"
2796 bl_parent_id = "TISSUE_PT_tessellate_object"
2797 bl_label = "Thickness"
2798 #bl_options = {'DEFAULT_CLOSED'}
2800 @classmethod
2801 def poll(cls, context):
2802 try: return context.object.tissue.tissue_type == 'TESSELLATE'
2803 except: return False
2805 def draw(self, context):
2806 ob = context.object
2807 props = ob.tissue_tessellate
2809 layout = self.layout
2810 #layout.use_property_split = True
2811 col = layout.column(align=True)
2812 # component Z
2813 row = col.row(align=True)
2814 row.prop(props, "scale_mode", expand=True)
2815 col.prop(props, "zscale", text="Scale", icon='NONE', expand=False,
2816 slider=True, toggle=False, icon_only=False, event=False,
2817 full_event=False, emboss=True, index=-1)
2818 if props.mode == 'BOUNDS':
2819 row = col.row(align=True)
2820 row.prop(props, "offset", text="Offset", icon='NONE', expand=False,
2821 slider=True, toggle=False, icon_only=False, event=False,
2822 full_event=False, emboss=True, index=-1)
2823 row.enabled = not props.use_origin_offset
2824 col.prop(props, 'use_origin_offset')
2826 col.separator()
2827 row = col.row(align=True)
2828 ob0 = props.generator
2829 row.prop_search(props, 'vertex_group_thickness',
2830 ob0, "vertex_groups", text='')
2831 col2 = row.column(align=True)
2832 row2 = col2.row(align=True)
2833 row2.prop(props, "invert_vertex_group_thickness", text="",
2834 toggle=True, icon='ARROW_LEFTRIGHT')
2835 row2.prop(props, "vertex_group_thickness_factor")
2836 row2.enabled = props.vertex_group_thickness in ob0.vertex_groups.keys()
2838 class TISSUE_PT_tessellate_direction(Panel):
2839 bl_space_type = 'PROPERTIES'
2840 bl_region_type = 'WINDOW'
2841 bl_context = "data"
2842 bl_parent_id = "TISSUE_PT_tessellate_object"
2843 bl_label = "Thickness Direction"
2844 bl_options = {'DEFAULT_CLOSED'}
2846 @classmethod
2847 def poll(cls, context):
2848 try:
2849 return context.object.tissue.tissue_type == 'TESSELLATE'
2850 except:
2851 return False
2853 def draw(self, context):
2854 ob = context.object
2855 props = ob.tissue_tessellate
2856 layout = self.layout
2857 ob0 = props.generator
2858 #layout.use_property_split = True
2859 col = layout.column(align=True)
2860 row = col.row(align=True)
2861 row.prop(
2862 props, "normals_mode", text="Direction", icon='NONE', expand=True,
2863 slider=False, toggle=False, icon_only=False, event=False,
2864 full_event=False, emboss=True, index=-1)
2865 if props.normals_mode == 'OBJECT':
2866 col.separator()
2867 row = col.row(align=True)
2868 row.prop_search(props, "target", context.scene, "objects", text='Target')
2869 if props.warning_message_thickness != '':
2870 col.separator()
2871 col.label(text=props.warning_message_thickness, icon='ERROR')
2872 if props.normals_mode != 'FACES':
2873 col.separator()
2874 col.prop(props, "smooth_normals")
2875 if props.smooth_normals:
2876 row = col.row(align=True)
2877 row.prop(props, "smooth_normals_iter")
2878 row.separator()
2879 row.prop_search(props, 'vertex_group_smooth_normals',
2880 ob0, "vertex_groups", text='')
2881 col2 = row.column(align=True)
2882 col2.prop(props, "invert_vertex_group_smooth_normals", text="", toggle=True, icon='ARROW_LEFTRIGHT')
2883 col2.enabled = props.vertex_group_smooth_normals in ob0.vertex_groups.keys()
2884 if props.normals_mode == 'VERTS':
2885 col.separator()
2886 row = col.row(align=True)
2887 row.prop(props, "normals_x")
2888 row.prop(props, "normals_y")
2889 row.prop(props, "normals_z")
2890 row = col.row(align=True)
2891 row.prop_search(props, 'vertex_group_scale_normals',
2892 ob0, "vertex_groups", text='')
2893 col2 = row.column(align=True)
2894 col2.prop(props, "invert_vertex_group_scale_normals", text="", toggle=True, icon='ARROW_LEFTRIGHT')
2895 col2.enabled = props.vertex_group_scale_normals in ob0.vertex_groups.keys()
2896 if props.normals_mode in ('OBJECT', 'SHAPEKEYS'):
2897 col.separator()
2898 row = col.row(align=True)
2899 row.prop(props, "even_thickness")
2900 if props.even_thickness: row.prop(props, "even_thickness_iter")
2902 class TISSUE_PT_tessellate_options(Panel):
2903 bl_space_type = 'PROPERTIES'
2904 bl_region_type = 'WINDOW'
2905 bl_context = "data"
2906 bl_parent_id = "TISSUE_PT_tessellate_object"
2907 bl_label = " "
2908 bl_options = {'DEFAULT_CLOSED'}
2910 @classmethod
2911 def poll(cls, context):
2912 try:
2913 return context.object.tissue.tissue_type == 'TESSELLATE'
2914 except:
2915 return False
2917 def draw_header(self, context):
2918 ob = context.object
2919 props = ob.tissue_tessellate
2920 self.layout.prop(props, "merge")
2922 def draw(self, context):
2923 ob = context.object
2924 props = ob.tissue_tessellate
2925 ob0 = props.generator
2926 ob1 = props.component
2927 layout = self.layout
2928 layout.use_property_split = True
2929 layout.use_property_decorate = False # No animation.
2930 col = layout.column(align=True)
2931 if props.merge:
2932 col.prop(props, "merge_thres")
2933 col.prop(props, "merge_open_edges_only")
2934 col.prop(props, "bool_dissolve_seams")
2935 col.prop(props, "close_mesh")
2936 if props.close_mesh in ('BRIDGE', 'BRIDGE_CAP'):
2937 col.separator()
2938 if props.close_mesh == 'BRIDGE_CAP':
2939 if props.vertex_group_bridge_owner == 'BASE': ob_bridge = ob0
2940 else: ob_bridge = ob1
2941 row = col.row(align=True)
2942 row.prop_search(props, 'vertex_group_bridge',
2943 ob_bridge, "vertex_groups")
2944 row.prop(props, "invert_vertex_group_bridge", text="",
2945 toggle=True, icon='ARROW_LEFTRIGHT')
2946 row = col.row(align=True)
2947 row.prop(props, "vertex_group_bridge_owner", expand=True,
2948 slider=False, toggle=False, icon_only=False, event=False,
2949 full_event=False, emboss=True, index=-1)
2950 col2 = row.column(align=True)
2951 row2 = col2.row(align=True)
2952 col.prop(props, "bridge_edges_crease", text="Crease")
2953 col.prop(props, "bridge_material_offset", text='Material Offset')
2955 if props.close_mesh == 'BRIDGE' and False:
2956 col.separator()
2957 col.prop(props, "bridge_cuts")
2958 col.prop(props, "bridge_smoothness")
2960 if props.close_mesh in ('CAP', 'BRIDGE_CAP'):
2961 #row = col.row(align=True)
2962 col.separator()
2963 if props.close_mesh == 'BRIDGE_CAP':
2964 if props.vertex_group_cap_owner == 'BASE': ob_cap = ob0
2965 else: ob_cap = ob1
2966 row = col.row(align=True)
2967 row.prop_search(props, 'vertex_group_cap',
2968 ob_cap, "vertex_groups")
2969 row.prop(props, "invert_vertex_group_cap", text="",
2970 toggle=True, icon='ARROW_LEFTRIGHT')
2971 row = col.row(align=True)
2972 row.prop(props, "vertex_group_cap_owner", expand=True,
2973 slider=False, toggle=False, icon_only=False, event=False,
2974 full_event=False, emboss=True, index=-1)
2975 col.prop(props, "open_edges_crease", text="Crease")
2976 col.prop(props, "cap_material_offset", text='Material Offset')
2977 if props.warning_message_merge:
2978 col.separator()
2979 col.label(text=props.warning_message_merge, icon='ERROR')
2981 class TISSUE_PT_tessellate_morphing(Panel):
2982 bl_space_type = 'PROPERTIES'
2983 bl_region_type = 'WINDOW'
2984 bl_context = "data"
2985 bl_parent_id = "TISSUE_PT_tessellate_object"
2986 bl_label = "Weight and Morphing"
2987 bl_options = {'DEFAULT_CLOSED'}
2989 @classmethod
2990 def poll(cls, context):
2991 try: return context.object.tissue.tissue_type == 'TESSELLATE'
2992 except: return False
2994 def draw(self, context):
2995 ob = context.object
2996 props = ob.tissue_tessellate
2997 layout = self.layout
2998 allow_shapekeys = not props.com_modifiers
3000 if tessellated(ob):
3001 ob0 = props.generator
3002 for m in ob0.data.materials:
3003 try:
3004 o = bpy.data.objects[m.name]
3005 allow_multi = True
3006 try:
3007 if o.data.shape_keys is None: continue
3008 elif len(o.data.shape_keys.key_blocks) < 2: continue
3009 else: allow_shapekeys = not props.com_modifiers
3010 except: pass
3011 except: pass
3012 col = layout.column(align=True)
3013 #col.label(text="Morphing:")
3014 row = col.row(align=True)
3015 col2 = row.column(align=True)
3016 col2.prop(props, "bool_vertex_group", icon='GROUP_VERTEX')
3017 #col2.prop_search(props, "vertex_group", props.generator, "vertex_groups")
3018 try:
3019 if len(props.generator.vertex_groups) == 0:
3020 col2.enabled = False
3021 except:
3022 col2.enabled = False
3023 row.separator()
3024 col2 = row.column(align=True)
3025 row2 = col2.row(align=True)
3026 row2.prop(props, "bool_shapekeys", text="Use Shape Keys", icon='SHAPEKEY_DATA')
3027 row2.enabled = allow_shapekeys
3028 if not allow_shapekeys:
3029 col2 = layout.column(align=True)
3030 row2 = col2.row(align=True)
3031 row2.label(text="Component's Shape Keys cannot be used together with Component's Modifiers", icon='INFO')
3034 class TISSUE_PT_tessellate_selective(Panel):
3035 bl_space_type = 'PROPERTIES'
3036 bl_region_type = 'WINDOW'
3037 bl_context = "data"
3038 bl_parent_id = "TISSUE_PT_tessellate_object"
3039 bl_label = "Selective"
3040 bl_options = {'DEFAULT_CLOSED'}
3042 @classmethod
3043 def poll(cls, context):
3044 try:
3045 return context.object.tissue.tissue_type == 'TESSELLATE'
3046 except:
3047 return False
3049 def draw(self, context):
3050 ob = context.object
3051 props = ob.tissue_tessellate
3053 layout = self.layout
3054 #layout.use_property_split = True
3055 #layout.use_property_decorate = False # No animation.
3056 allow_multi = False
3057 allow_shapekeys = not props.com_modifiers
3058 ob0 = props.generator
3059 for m in ob0.data.materials:
3060 try:
3061 o = bpy.data.objects[m.name]
3062 allow_multi = True
3063 try:
3064 if o.data.shape_keys is None: continue
3065 elif len(o.data.shape_keys.key_blocks) < 2: continue
3066 else: allow_shapekeys = not props.com_modifiers
3067 except: pass
3068 except: pass
3069 # LIMITED TESSELLATION
3070 col = layout.column(align=True)
3071 #col.label(text="Limited Tessellation:")
3072 row = col.row(align=True)
3073 col2 = row.column(align=True)
3074 col2.prop(props, "bool_selection", text="On selected Faces", icon='RESTRICT_SELECT_OFF')
3075 row.separator()
3076 if props.generator.type != 'MESH':
3077 col2.enabled = False
3078 col2 = row.column(align=True)
3079 col2.prop(props, "bool_material_id", icon='MATERIAL_DATA', text="Material Index")
3080 #if props.bool_material_id and not props.component_mode == 'MATERIALS':
3081 #col2 = row.column(align=True)
3082 col2.prop(props, "material_id")
3083 #if props.component_mode == 'MATERIALS':
3084 # col2.enabled = False
3086 #col.separator()
3087 #row = col.row(align=True)
3088 #col2 = row.column(align=True)
3089 #col2.prop(props, "bool_multi_components", icon='MOD_TINT')
3090 #if not allow_multi:
3091 # col2.enabled = False
3094 class TISSUE_PT_tessellate_iterations(Panel):
3095 bl_space_type = 'PROPERTIES'
3096 bl_region_type = 'WINDOW'
3097 bl_context = "data"
3098 bl_parent_id = "TISSUE_PT_tessellate_object"
3099 bl_label = "Iterations"
3100 bl_options = {'DEFAULT_CLOSED'}
3102 @classmethod
3103 def poll(cls, context):
3104 try:
3105 return context.object.tissue.tissue_type == 'TESSELLATE'
3106 except:
3107 return False
3109 def draw(self, context):
3110 ob = context.object
3111 props = ob.tissue_tessellate
3112 layout = self.layout
3113 layout.use_property_split = True
3114 layout.use_property_decorate = False # No animation.
3115 col = layout.column(align=True)
3116 row = col.row(align=True)
3117 #row.label(text='', icon='FILE_REFRESH')
3118 col.prop(props, 'iterations', text='Repeat')#, icon='FILE_REFRESH')
3119 if props.iterations > 1 and props.fill_mode == 'PATCH':
3120 col.separator()
3121 #row = col.row(align=True)
3122 col.prop(props, 'patch_subs')
3123 layout.use_property_split = False
3124 col = layout.column(align=True)
3125 #row = col.row(align=True)
3126 col.label(text='Combine Iterations:')
3127 row = col.row(align=True)
3128 row.prop(
3129 props, "combine_mode", text="Combine:",icon='NONE', expand=True,
3130 slider=False, toggle=False, icon_only=False, event=False,
3131 full_event=False, emboss=True, index=-1)
3133 class tissue_remove(Operator):
3134 bl_idname = "mesh.tissue_remove"
3135 bl_label = "Tissue Remove"
3136 bl_description = "Remove Tissue properties"
3137 bl_options = {'REGISTER', 'UNDO'}
3139 def invoke(self, context, event):
3140 return context.window_manager.invoke_props_dialog(self)
3142 def draw(self, context):
3143 ob = context.object
3144 layout = self.layout
3145 col = layout.column(align=True)
3146 col.label(text='This is a destructive operation! Are you sure?', icon='ERROR')
3148 def execute(self, context):
3149 ob = context.active_object
3150 ob.tissue.tissue_type = 'NONE'
3151 return {'FINISHED'}
3153 class tissue_rotate_face_right(Operator):
3154 bl_idname = "mesh.tissue_rotate_face_right"
3155 bl_label = "Tissue Rotate Faces Right"
3156 bl_description = "Rotate clockwise selected faces and update tessellated meshes"
3157 bl_options = {'REGISTER', 'UNDO'}
3159 @classmethod
3160 def poll(cls, context):
3161 try:
3162 #bool_tessellated = context.object.tissue_tessellate.generator != None
3163 ob = context.object
3164 return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
3165 except:
3166 return False
3168 def execute(self, context):
3169 ob = context.active_object
3170 me = ob.data
3172 bm = bmesh.from_edit_mesh(me)
3173 mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
3175 for face in bm.faces:
3176 if (face.select):
3177 vs = face.verts[:]
3178 vs2 = vs[-1:]+vs[:-1]
3179 material_index = face.material_index
3180 bm.faces.remove(face)
3181 f2 = bm.faces.new(vs2)
3182 f2.select = True
3183 f2.material_index = material_index
3184 bm.normal_update()
3186 # trigger UI update
3187 bmesh.update_edit_mesh(me)
3188 bm.free()
3189 ob.select_set(False)
3191 # update tessellated meshes
3192 bpy.ops.object.mode_set(mode='OBJECT')
3193 for o in [obj for obj in bpy.data.objects if
3194 obj.tissue_tessellate.generator == ob and obj.visible_get()]:
3195 context.view_layer.objects.active = o
3197 #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]}
3198 if not o.tissue.bool_lock:
3199 bpy.ops.object.tissue_update_tessellate()
3200 o.select_set(False)
3201 ob.select_set(True)
3202 context.view_layer.objects.active = ob
3203 bpy.ops.object.mode_set(mode='EDIT')
3204 context.tool_settings.mesh_select_mode = mesh_select_mode
3206 return {'FINISHED'}
3208 class tissue_rotate_face_flip(Operator):
3209 bl_idname = "mesh.tissue_rotate_face_flip"
3210 bl_label = "Tissue Rotate Faces Flip"
3211 bl_description = "Fully rotate selected faces and update tessellated meshes"
3212 bl_options = {'REGISTER', 'UNDO'}
3214 @classmethod
3215 def poll(cls, context):
3216 try:
3217 #bool_tessellated = context.object.tissue_tessellate.generator != None
3218 ob = context.object
3219 return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
3220 except:
3221 return False
3223 def execute(self, context):
3224 ob = context.active_object
3225 me = ob.data
3227 bm = bmesh.from_edit_mesh(me)
3228 mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
3230 for face in bm.faces:
3231 if (face.select):
3232 vs = face.verts[:]
3233 nrot = int(len(vs)/2)
3234 vs2 = vs[-nrot:]+vs[:-nrot]
3235 material_index = face.material_index
3236 bm.faces.remove(face)
3237 f2 = bm.faces.new(vs2)
3238 f2.select = True
3239 f2.material_index = material_index
3240 bm.normal_update()
3242 # trigger UI update
3243 bmesh.update_edit_mesh(me)
3244 bm.free()
3245 ob.select_set(False)
3247 # update tessellated meshes
3248 bpy.ops.object.mode_set(mode='OBJECT')
3249 for o in [obj for obj in bpy.data.objects if
3250 obj.tissue_tessellate.generator == ob and obj.visible_get()]:
3251 context.view_layer.objects.active = o
3253 #override = {'object': o, 'mode': 'OBJECT', 'selected_objects': [o]}
3254 if not o.tissue.bool_lock:
3255 bpy.ops.object.tissue_update_tessellate()
3256 o.select_set(False)
3257 ob.select_set(True)
3258 context.view_layer.objects.active = ob
3259 bpy.ops.object.mode_set(mode='EDIT')
3260 context.tool_settings.mesh_select_mode = mesh_select_mode
3262 return {'FINISHED'}
3264 class tissue_rotate_face_left(Operator):
3265 bl_idname = "mesh.tissue_rotate_face_left"
3266 bl_label = "Tissue Rotate Faces Left"
3267 bl_description = "Rotate counterclockwise selected faces and update tessellated meshes"
3268 bl_options = {'REGISTER', 'UNDO'}
3270 @classmethod
3271 def poll(cls, context):
3272 try:
3273 #bool_tessellated = context.object.tissue_tessellate.generator != None
3274 ob = context.object
3275 return ob.type == 'MESH' and ob.mode == 'EDIT'# and bool_tessellated
3276 except:
3277 return False
3279 def execute(self, context):
3280 ob = context.active_object
3281 me = ob.data
3283 bm = bmesh.from_edit_mesh(me)
3284 mesh_select_mode = [sm for sm in context.tool_settings.mesh_select_mode]
3286 for face in bm.faces:
3287 if (face.select):
3288 vs = face.verts[:]
3289 vs2 = vs[1:]+vs[:1]
3290 material_index = face.material_index
3291 bm.faces.remove(face)
3292 f2 = bm.faces.new(vs2)
3293 f2.select = True
3294 f2.material_index = material_index
3295 bm.normal_update()
3297 # trigger UI update
3298 bmesh.update_edit_mesh(me)
3299 bm.free()
3300 ob.select_set(False)
3302 # update tessellated meshes
3303 bpy.ops.object.mode_set(mode='OBJECT')
3304 for o in [obj for obj in bpy.data.objects if
3305 obj.tissue_tessellate.generator == ob and obj.visible_get()]:
3306 context.view_layer.objects.active = o
3307 if not o.tissue.bool_lock:
3308 bpy.ops.object.tissue_update_tessellate()
3309 o.select_set(False)
3310 ob.select_set(True)
3311 context.view_layer.objects.active = ob
3312 bpy.ops.object.mode_set(mode='EDIT')
3313 context.tool_settings.mesh_select_mode = mesh_select_mode
3315 return {'FINISHED'}
3317 def convert_to_frame(ob, props, use_modifiers=True):
3318 new_ob = convert_object_to_mesh(ob, use_modifiers, True,props['rotation_mode']!='UV')
3320 # create bmesh
3321 bm = bmesh.new()
3322 bm.from_mesh(new_ob.data)
3323 bm.verts.ensure_lookup_table()
3324 bm.edges.ensure_lookup_table()
3325 bm.faces.ensure_lookup_table()
3326 if props['bool_selection']:
3327 original_faces = [f for f in bm.faces if f.select]
3328 elif props['preserve_quads']:
3329 original_faces = [f for f in bm.faces if len(f.verts)!=4]
3330 else:
3331 original_faces = list(bm.faces)
3333 # detect edge loops
3335 loops = []
3336 boundaries_mat = []
3337 neigh_face_center = []
3338 face_normals = []
3340 # append boundary loops
3341 if props['frame_boundary']:
3342 #selected_edges = [e for e in bm.edges if e.select]
3343 selected_edges = [e for e in bm.edges if e.is_boundary]
3344 if len(selected_edges) > 0:
3345 loop = []
3346 count = 0
3347 e0 = selected_edges[0]
3348 face = e0.link_faces[0]
3349 boundary_mat = [face.material_index + props['boundary_mat_offset']]
3350 face_center = [face.calc_center_median()]
3351 loop_normals = [face.normal]
3352 selected_edges = selected_edges[1:]
3353 if props['bool_vertex_group'] or True:
3354 n_verts = len(new_ob.data.vertices)
3355 base_vg = [get_weight(vg,n_verts) for vg in new_ob.vertex_groups]
3356 while True:
3357 new_vert = None
3358 face = None
3359 for e1 in selected_edges:
3360 if e1.verts[0] in e0.verts: new_vert = e1.verts[1]
3361 elif e1.verts[1] in e0.verts: new_vert = e1.verts[0]
3362 if new_vert != None:
3363 if len(loop)==0:
3364 loop = [v for v in e1.verts if v != new_vert]
3365 loop.append(new_vert)
3366 e0 = e1
3367 face = e0.link_faces[0]
3368 boundary_mat.append(face.material_index + props['boundary_mat_offset'])
3369 face_center.append(face.calc_center_median())
3370 loop_normals.append(face.normal)
3371 selected_edges.remove(e0)
3372 break
3373 if new_vert == None:
3374 try:
3375 loops.append(loop)
3376 loop = []
3377 e0 = selected_edges[0]
3378 selected_edges = selected_edges[1:]
3379 boundaries_mat.append(boundary_mat)
3380 neigh_face_center.append(face_center)
3381 face_normals.append(loop_normals)
3382 face = e0.link_faces[0]
3383 boundary_mat = [face.material_index + props['boundary_mat_offset']]
3384 face_center = [face.calc_center_median()]
3385 loop_normals = [face.normal]
3386 except: break
3387 boundaries_mat.append(boundary_mat)
3388 neigh_face_center.append(face_center)
3389 face_normals.append(loop_normals)
3390 # compute boundary frames
3391 new_faces = []
3392 vert_ids = []
3394 # append regular faces
3395 for f in original_faces:
3396 loop = list(f.verts)
3397 loops.append(loop)
3398 boundaries_mat.append([f.material_index for v in loop])
3399 face_normals.append([f.normal for v in loop])
3401 # calc areas for relative frame mode
3402 if props['frame_mode'] == 'RELATIVE':
3403 verts_area = []
3404 for v in bm.verts:
3405 linked_faces = v.link_faces
3406 if len(linked_faces) > 0:
3407 area = sum([sqrt(f.calc_area())/len(f.verts) for f in v.link_faces])*2
3408 area /= len(linked_faces)
3409 else: area = 0
3410 verts_area.append(area)
3412 bool_weight_thick = props['vertex_group_frame_thickness'] in new_ob.vertex_groups.keys()
3413 if bool_weight_thick:
3414 vg = new_ob.vertex_groups[props['vertex_group_frame_thickness']]
3415 weight_frame = get_weight_numpy(vg, len(bm.verts))
3416 if props['invert_vertex_group_frame_thickness']:
3417 weight_frame = 1-weight_frame
3418 fact = props['vertex_group_frame_thickness_factor']
3419 if fact > 0:
3420 weight_frame = weight_frame*(1-fact) + fact
3421 else:
3422 weight_frame = np.ones((len(bm.verts)))
3424 centers_neigh = []
3425 centers_id = []
3426 verts_count = len(bm.verts)-1
3427 for loop_index, loop in enumerate(loops):
3428 is_boundary = loop_index < len(neigh_face_center)
3429 materials = boundaries_mat[loop_index]
3430 new_loop = []
3431 loop_ext = [loop[-1]] + loop + [loop[0]]
3433 # calc tangents
3434 tangents = []
3435 for i in range(len(loop)):
3436 # vertices
3437 vert0 = loop_ext[i]
3438 vert = loop_ext[i+1]
3439 vert1 = loop_ext[i+2]
3440 # edge vectors
3441 vec0 = (vert0.co - vert.co).normalized()
3442 vec1 = (vert.co - vert1.co).normalized()
3443 # tangent
3444 _vec1 = -vec1
3445 _vec0 = -vec0
3446 ang = (pi - vec0.angle(vec1))/2
3447 normal = face_normals[loop_index][i]
3448 tan0 = normal.cross(vec0)
3449 tan1 = normal.cross(vec1)
3450 if is_boundary and props['frame_boundary_thickness'] != 0:
3451 thickness = props['frame_boundary_thickness']
3452 else:
3453 thickness = props['frame_thickness']
3454 tangent = (tan0 + tan1).normalized()/sin(ang)*thickness
3455 tangents.append(tangent)
3457 # calc correct direction for boundaries
3458 mult = -1
3459 if is_boundary:
3460 dir_val = 0
3461 for i in range(len(loop)):
3462 surf_point = neigh_face_center[loop_index][i]
3463 tangent = tangents[i]
3464 vert = loop_ext[i+1]
3465 dir_val += tangent.dot(vert.co - surf_point)
3466 if dir_val > 0: mult = 1
3468 if props['frame_mode'] == 'CENTER':
3469 # uses incenter for triangular loops and average point for generic polygons
3470 polygon_loop = list(dict.fromkeys(loop_ext))
3471 if len(polygon_loop) == 3:
3472 loop_center = incenter([v.co for v in polygon_loop])
3473 else:
3474 loop_center = Vector((0,0,0))
3475 for v in polygon_loop:
3476 loop_center += v.co
3477 loop_center /= len(polygon_loop)
3479 # add vertices
3480 central_vertex = None
3481 skip_vertex = False
3482 for i in range(len(loop)):
3483 vert = loop_ext[i+1]
3484 if props['frame_mode'] == 'RELATIVE': area = verts_area[vert.index]
3485 else: area = 1
3486 if props['face_weight_frame']:
3487 weight_factor = [weight_frame[v.index] for v in loop_ext]
3488 weight_factor = sum(weight_factor)/len(weight_factor)
3489 else:
3490 weight_factor = weight_frame[vert.index]
3491 if props['frame_mode'] == 'CENTER':
3492 if is_boundary:
3493 new_co = vert.co + tangents[i] * mult * weight_factor
3494 else:
3495 factor = weight_factor*props['frame_thickness']
3496 if factor == 1 and props['frame_thickness']:
3497 skip_vertex = True
3498 else:
3499 new_co = vert.co + (loop_center-vert.co)*factor
3500 else:
3501 new_co = vert.co + tangents[i] * mult * area * weight_factor
3502 # add vertex
3503 if skip_vertex:
3504 # prevents dublicates in the center of the loop
3505 if central_vertex:
3506 new_vert = central_vertex
3507 else:
3508 central_vertex = bm.verts.new(loop_center)
3509 new_vert = central_vertex
3510 vert_ids.append(vert.index)
3511 skip_vertex = False
3512 else:
3513 new_vert = bm.verts.new(new_co)
3514 vert_ids.append(vert.index)
3515 new_loop.append(new_vert)
3516 new_loop.append(new_loop[0])
3518 # add faces
3519 materials += [materials[0]]
3520 for i in range(len(loop)):
3521 v0 = loop_ext[i+1]
3522 v1 = loop_ext[i+2]
3523 v2 = new_loop[i+1]
3524 v3 = new_loop[i]
3525 face_verts = [v1,v0,v3,v2]
3526 if mult == -1: face_verts = [v0,v1,v2,v3]
3527 face_verts = list(dict.fromkeys(face_verts))
3528 new_face = bm.faces.new(face_verts)
3529 new_face.material_index = materials[i+1]
3530 new_face.select = True
3531 new_faces.append(new_face)
3532 # fill frame
3533 if props['fill_frame'] and not is_boundary:
3534 center_neigh = []
3535 n_verts = len(new_loop)-1
3536 loop_center = Vector((0,0,0))
3537 for v in new_loop[1:]:
3538 loop_center += v.co
3539 verts_count += 1
3540 center_neigh.append(verts_count)
3541 centers_neigh.append(center_neigh)
3542 loop_center /= n_verts
3543 center = bm.verts.new(loop_center)
3544 verts_count += 1
3545 vert_ids.append(center.index)
3546 centers_id.append(verts_count)
3547 for i in range(n_verts):
3548 v0 = new_loop[i+1]
3549 v1 = new_loop[i]
3550 face_verts = [v1,v0,center]
3551 face_verts = list(dict.fromkeys(face_verts))
3552 if len(face_verts) < 3: continue
3553 new_face = bm.faces.new(face_verts)
3554 new_face.material_index = materials[i] + props['fill_frame_mat']
3555 new_face.select = True
3556 new_faces.append(new_face)
3557 for f in original_faces: bm.faces.remove(f)
3558 bm.to_mesh(new_ob.data)
3559 # propagate vertex groups
3560 if props['bool_vertex_group'] or bool_weight_thick:
3561 base_vg = []
3562 for vg in new_ob.vertex_groups:
3563 vertex_group = []
3564 for v in bm.verts:
3565 try:
3566 vertex_group.append(vg.weight(v.index))
3567 except:
3568 vertex_group.append(0)
3569 base_vg.append(vertex_group)
3570 new_vert_ids = range(len(bm.verts)-len(vert_ids),len(bm.verts))
3571 for vg_id, vg in enumerate(new_ob.vertex_groups):
3572 for ii, jj in zip(vert_ids, new_vert_ids):
3573 vg.add([jj], base_vg[vg_id][ii], 'REPLACE')
3574 # set weight for the central points
3575 if props['fill_frame']:
3576 for cn, ii in zip(centers_neigh, centers_id):
3577 cw = [vg.weight(cni) for cni in cn]
3578 cw = sum(cw)/len(cw)
3579 vg.add([ii], cw, 'REPLACE')
3581 new_ob.data.update()
3582 bm.free()
3583 return new_ob
3585 def reduce_to_quads(ob, props):
3587 Convert an input object to a mesh with polygons that have maximum 4 vertices
3589 new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True, props['rotation_mode']!='UV')
3590 me = new_ob.data
3592 # Check if there are polygons with more than 4 sides
3593 np_sides = get_attribute_numpy(me.polygons, 'loop_total')
3594 mask = np_sides > 4
3595 if not np.any(mask):
3596 if props['boundary_mat_offset'] != 0 or props['boundary_variable_offset']:
3597 bm=bmesh.new()
3598 bm.from_mesh(me)
3599 bm = offset_boundary_materials(
3601 boundary_mat_offset = props['boundary_mat_offset'],
3602 boundary_variable_offset = props['boundary_variable_offset'],
3603 auto_rotate_boundary = props['auto_rotate_boundary'])
3604 bm.to_mesh(me)
3605 bm.free()
3606 me.update()
3607 return new_ob
3609 # create bmesh
3610 bm = bmesh.new()
3611 bm.from_mesh(me)
3612 bm.verts.ensure_lookup_table()
3613 bm.edges.ensure_lookup_table()
3614 bm.faces.ensure_lookup_table()
3616 np_faces = np.array(bm.faces)
3617 np_faces = np_faces[mask]
3619 new_faces = []
3620 for f in np_faces:
3621 verts = list(f.verts)
3622 while True:
3623 n_verts = len(verts)
3624 if n_verts < 3: break
3625 elif n_verts == 3:
3626 face_verts = [verts[-2], verts.pop(-1), verts.pop(0)]
3627 else:
3628 face_verts = [verts[-2], verts.pop(-1), verts.pop(0), verts[0]]
3629 new_face = bm.faces.new(face_verts)
3630 new_face.material_index = f.material_index
3631 new_face.select = f.select
3632 new_faces.append(new_face)
3634 for f in np_faces: bm.faces.remove(f)
3636 bm = offset_boundary_materials(
3638 boundary_mat_offset = props['boundary_mat_offset'],
3639 boundary_variable_offset = props['boundary_variable_offset'],
3640 auto_rotate_boundary = props['auto_rotate_boundary'])
3642 bm.to_mesh(me)
3643 bm.free()
3644 me.update()
3645 return new_ob
3647 def convert_to_fan(ob, props, add_id_layer=False):
3648 new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True, props['rotation_mode']!='UV')
3649 bm = bmesh.new()
3650 bm.from_mesh(new_ob.data)
3651 if add_id_layer:
3652 bm.faces.ensure_lookup_table()
3653 lay = bm.faces.layers.int.new("id")
3654 for i,f in enumerate(bm.faces): f[lay] = i
3655 bmesh.ops.poke(bm, faces=bm.faces)#, quad_method, ngon_method)
3656 bm = offset_boundary_materials(
3658 boundary_mat_offset = props['boundary_mat_offset'],
3659 boundary_variable_offset = props['boundary_variable_offset'],
3660 auto_rotate_boundary = props['auto_rotate_boundary'])
3661 bm.to_mesh(new_ob.data)
3662 new_ob.data.update()
3663 bm.free()
3664 return new_ob
3666 def convert_to_triangles(ob, props):
3667 new_ob = convert_object_to_mesh(ob, props['gen_modifiers'], True, props['rotation_mode']!='UV')
3668 bm = bmesh.new()
3669 bm.from_mesh(new_ob.data)
3670 bmesh.ops.triangulate(bm, faces=bm.faces, quad_method='FIXED', ngon_method='BEAUTY')
3672 bm = offset_boundary_materials(
3674 boundary_mat_offset = props['boundary_mat_offset'],
3675 boundary_variable_offset = props['boundary_variable_offset'],
3676 auto_rotate_boundary = props['auto_rotate_boundary'])
3678 bm.to_mesh(new_ob.data)
3679 new_ob.data.update()
3680 bm.free()
3681 return new_ob
3683 def merge_components(ob, props, use_bmesh):
3685 if not use_bmesh and False:
3686 skip = True
3687 ob.active_shape_key_index = 1
3688 if ob.data.shape_keys != None:
3689 for sk in ob.data.shape_keys.key_blocks:
3690 if skip:
3691 skip = False
3692 continue
3693 sk.mute = True
3694 ob.data.update()
3695 bpy.ops.object.mode_set(mode='EDIT')
3696 bpy.ops.object.mode_set(mode='OBJECT')
3697 if ob.data.shape_keys != None:
3698 for sk in ob.data.shape_keys.key_blocks:
3699 sk.mute = False
3700 ob.data.update()
3702 bpy.ops.object.mode_set(mode='EDIT')
3703 bpy.ops.mesh.select_mode(
3704 use_extend=False, use_expand=False, type='VERT')
3705 bpy.ops.mesh.select_non_manifold(
3706 extend=False, use_wire=True, use_boundary=True,
3707 use_multi_face=False, use_non_contiguous=False, use_verts=False)
3709 bpy.ops.mesh.remove_doubles(
3710 threshold=props.merge_thres, use_unselected=False)
3712 if props.bool_dissolve_seams:
3713 bpy.ops.mesh.select_mode(type='EDGE')
3714 bpy.ops.mesh.select_all(action='DESELECT')
3715 bpy.ops.object.mode_set(mode='OBJECT')
3716 for e in new_ob.data.edges:
3717 e.select = e.use_seam
3718 bpy.ops.object.mode_set(mode='EDIT')
3719 bpy.ops.mesh.dissolve_edges()
3720 bpy.ops.object.mode_set(mode='OBJECT')
3722 if props.close_mesh != 'NONE':
3723 bpy.ops.object.mode_set(mode='EDIT')
3724 bpy.ops.mesh.select_mode(
3725 use_extend=False, use_expand=False, type='EDGE')
3726 bpy.ops.mesh.select_non_manifold(
3727 extend=False, use_wire=False, use_boundary=True,
3728 use_multi_face=False, use_non_contiguous=False, use_verts=False)
3729 if props.close_mesh == 'CAP':
3730 if props.open_edges_crease != 0:
3731 bpy.ops.transform.edge_crease(value=props.open_edges_crease)
3732 bpy.ops.mesh.edge_face_add()
3733 bpy.ops.object.mode_set(mode='OBJECT')
3734 for f in ob.data.polygons:
3735 if f.select: f.material_index += props.cap_material_offset
3736 elif props.close_mesh == 'BRIDGE':
3737 try:
3738 if props.bridge_edges_crease != 0:
3739 bpy.ops.transform.edge_crease(value=props.bridge_edges_crease)
3740 bpy.ops.mesh.bridge_edge_loops(
3741 type='PAIRS',
3742 number_cuts=props.bridge_cuts,
3743 interpolation='SURFACE',
3744 smoothness=props.bridge_smoothness)
3745 bpy.ops.object.mode_set(mode='OBJECT')
3746 for f in ob.data.polygons:
3747 if f.select: f.material_index += props.bridge_material_offset
3748 except: pass
3749 elif props.close_mesh == 'BRIDGE_CAP':
3750 # BRIDGE
3751 try:
3752 bpy.ops.object.mode_set(mode='OBJECT')
3753 vg = ob.vertex_groups[props.vertex_group_bridge]
3754 weight = get_weight_numpy(vg, len(ob.data.vertices))
3755 for e in ob.data.edges:
3756 if weight[e.vertices[0]]*weight[e.vertices[1]] < 1:
3757 e.select = False
3758 bpy.ops.object.mode_set(mode='EDIT')
3759 if props.bridge_edges_crease != 0:
3760 bpy.ops.transform.edge_crease(value=props.bridge_edges_crease)
3761 bpy.ops.mesh.bridge_edge_loops(
3762 type='PAIRS',
3763 number_cuts=props.bridge_cuts,
3764 interpolation='SURFACE',
3765 smoothness=props.bridge_smoothness)
3766 for f in ob.data.polygons:
3767 if f.select: f.material_index += props.bridge_material_offset
3768 bpy.ops.mesh.select_all(action='DESELECT')
3769 bpy.ops.mesh.select_non_manifold(
3770 extend=False, use_wire=False, use_boundary=True,
3771 use_multi_face=False, use_non_contiguous=False, use_verts=False)
3772 bpy.ops.object.mode_set(mode='OBJECT')
3773 except: pass
3774 # CAP
3775 try:
3776 bpy.ops.object.mode_set(mode='OBJECT')
3777 vg = ob.vertex_groups[props.vertex_group_cap]
3778 weight = get_weight_numpy(vg, len(ob.data.vertices))
3779 for e in ob.data.edges:
3780 if weight[e.vertices[0]]*weight[e.vertices[1]] < 1:
3781 e.select = False
3782 bpy.ops.object.mode_set(mode='EDIT')
3783 if props.open_edges_crease != 0:
3784 bpy.ops.transform.edge_crease(value=props.open_edges_crease)
3785 bpy.ops.mesh.edge_face_add()
3786 for f in ob.data.polygons:
3787 if f.select: f.material_index += props.cap_material_offset
3788 bpy.ops.object.mode_set(mode='OBJECT')
3789 except: pass
3790 else:
3791 if(props.bridge_edges_crease>0 or props.open_edges_crease>0):
3792 ob.data.edge_creases_ensure()
3793 bm = bmesh.new()
3794 bm.from_mesh(ob.data.copy())
3795 if props.merge_open_edges_only:
3796 boundary_verts = [v for v in bm.verts if v.is_boundary or v.is_wire]
3797 else:
3798 boundary_verts = bm.verts
3799 bmesh.ops.remove_doubles(bm, verts=boundary_verts, dist=props.merge_thres)
3801 if props.bool_dissolve_seams:
3802 seam_edges = [e for e in bm.edges if e.seam]
3803 bmesh.ops.dissolve_edges(bm, edges=seam_edges, use_verts=True, use_face_split=False)
3804 if props.close_mesh != 'NONE':
3805 bm.edges.ensure_lookup_table()
3806 # set crease
3807 crease_layer = bm.edges.layers.float.new('crease_edge')
3808 boundary_edges = [e for e in bm.edges if e.is_boundary or e.is_wire]
3809 n_materials = len(ob.material_slots)-1
3810 if props.close_mesh == 'BRIDGE':
3811 try:
3812 for e in boundary_edges:
3813 e[crease_layer] = props.bridge_edges_crease
3814 closed = bmesh.ops.bridge_loops(bm, edges=boundary_edges, use_pairs=True)
3815 if n_materials >= 0:
3816 for f in closed['faces']:
3817 f.material_index = min(f.material_index + props.bridge_material_offset, n_materials)
3818 except:
3819 bm.to_mesh(ob.data)
3820 return 'bridge_error'
3821 elif props.close_mesh == 'CAP':
3822 for e in boundary_edges:
3823 e[crease_layer] = props.open_edges_crease
3824 closed = bmesh.ops.holes_fill(bm, edges=boundary_edges)
3825 if n_materials >= 0:
3826 for f in closed['faces']:
3827 f.material_index = min(f.material_index + props.cap_material_offset, n_materials)
3828 elif props.close_mesh == 'BRIDGE_CAP':
3829 # BRIDGE
3830 dvert_lay = bm.verts.layers.deform.active
3831 try:
3832 dvert_lay = bm.verts.layers.deform.active
3833 group_index = ob.vertex_groups[props.vertex_group_bridge].index
3834 bw = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
3835 if props.invert_vertex_group_bridge: bw = 1-bw
3836 bridge_edges = [e for e in boundary_edges if bw[e.verts[0].index]*bw[e.verts[1].index] >= 1]
3837 for e in bridge_edges:
3838 e[crease_layer] = props.bridge_edges_crease
3839 closed = bmesh.ops.bridge_loops(bm, edges=bridge_edges, use_pairs=True)
3840 if n_materials >= 0:
3841 for f in closed['faces']:
3842 f.material_index = min(f.material_index + props.bridge_material_offset, n_materials)
3843 boundary_edges = [e for e in bm.edges if e.is_boundary]
3844 except: pass
3845 # CAP
3846 try:
3847 dvert_lay = bm.verts.layers.deform.active
3848 group_index = ob.vertex_groups[props.vertex_group_cap].index
3849 bw = bmesh_get_weight_numpy(group_index, dvert_lay, bm.verts)
3850 if props.invert_vertex_group_cap: bw = 1-bw
3851 cap_edges = [e for e in boundary_edges if bw[e.verts[0].index]*bw[e.verts[1].index] >= 1]
3852 for e in cap_edges:
3853 e[crease_layer] = props.open_edges_crease
3854 closed = bmesh.ops.holes_fill(bm, edges=cap_edges)
3855 if n_materials >= 0:
3856 for f in closed['faces']:
3857 f.material_index = min(f.material_index + props.bridge_material_offset, n_materials)
3858 except: pass
3859 bm.to_mesh(ob.data)
3861 class tissue_render_animation(Operator):
3862 bl_idname = "render.tissue_render_animation"
3863 bl_label = "Tissue Render Animation"
3864 bl_description = "Turnaround for issues related to animatable tessellation"
3865 bl_options = {'REGISTER', 'UNDO'}
3867 start = True
3868 path = ""
3869 timer = None
3871 def invoke(self, context, event):
3872 self.start = True
3873 return context.window_manager.invoke_props_dialog(self)
3875 def draw(self, context):
3876 layout = self.layout
3877 col = layout.column(align=True)
3878 col.label(text="All frames will be rendered in the background.")
3879 col.label(text="Press ESC to abort.")
3881 def modal(self, context, event):
3883 # check render format
3884 format = context.scene.render.image_settings.file_format
3885 if format in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'):
3886 message = "Please use an image format as render output"
3887 self.report({'ERROR'}, message)
3888 return {'CANCELLED'}
3890 remove_tissue_handler()
3891 scene = context.scene
3892 if event.type == 'ESC' or scene.frame_current >= scene.frame_end:
3893 scene.render.filepath = self.path
3894 # set again the handler
3895 blender_handlers = bpy.app.handlers.frame_change_post
3896 blender_handlers.append(anim_tissue)
3897 blender_handlers.append(reaction_diffusion_scene)
3898 context.window_manager.event_timer_remove(self.timer)
3899 if event.type == 'ESC':
3900 print("Tissue: Render Animation aborted.")
3901 return {'CANCELLED'}
3902 else:
3903 print("Tissue: Render Animation completed!")
3904 return {'FINISHED'}
3905 else:
3906 self.execute(context)
3907 return {'RUNNING_MODAL'}
3909 def execute(self, context):
3910 # check output format
3911 format = context.scene.render.image_settings.file_format
3912 if format in ('FFMPEG', 'AVI_RAW', 'AVI_JPEG'):
3913 message = "Please use an image format as render output"
3914 self.report({'ERROR'}, message)
3915 return {'CANCELLED'}
3917 scene = context.scene
3918 if self.start:
3919 remove_tissue_handler()
3920 reaction_diffusion_remove_handler(self, context)
3921 scene = context.scene
3922 scene.frame_current = scene.frame_start
3923 self.path = scene.render.filepath
3924 context.window_manager.modal_handler_add(self)
3925 self.timer = context.window_manager.event_timer_add(0.1, window = context.window)
3926 self.start = False
3927 else:
3928 scene.frame_current += scene.frame_step
3929 anim_tissue(scene)
3930 reaction_diffusion_scene(scene)
3931 scene.render.filepath = "{}{:04d}".format(self.path,scene.frame_current)
3932 bpy.ops.render.render(write_still=True)
3933 return {'RUNNING_MODAL'}
3935 def offset_boundary_materials(bm, boundary_mat_offset=0, boundary_variable_offset=False, auto_rotate_boundary=False):
3936 if boundary_mat_offset != 0 or boundary_variable_offset:
3937 bm.edges.ensure_lookup_table()
3938 bm.faces.ensure_lookup_table()
3939 bound_faces = []
3940 bound_verts_value = [0]*len(bm.faces)
3941 bound_edges_value = [0]*len(bm.faces)
3942 shift_faces = [0]*len(bm.faces)
3943 # store boundaries informations
3944 for v in bm.verts:
3945 if v.is_boundary:
3946 for f in v.link_faces:
3947 bound_faces.append(f)
3948 bound_verts_value[f.index] += 1
3949 for e in bm.edges:
3950 if e.is_boundary:
3951 for f in e.link_faces:
3952 bound_edges_value[f.index] += 1
3953 # Set material index offset
3954 if boundary_variable_offset:
3955 for f in bm.faces:
3956 if bound_verts_value[f.index] > 0:
3957 f.material_index += boundary_mat_offset
3958 if bound_verts_value[f.index] == bound_edges_value[f.index]+1:
3959 f.material_index += bound_verts_value[f.index]
3960 else:
3961 for f in bm.faces:
3962 if bound_edges_value[f.index] > 0:
3963 f.material_index += boundary_mat_offset
3964 if auto_rotate_boundary:
3965 rotate_faces = []
3966 new_verts_all = []
3967 for f in bm.faces:
3968 val = bound_verts_value[f.index]
3969 val2 = bound_edges_value[f.index]
3970 if val > 0 and val2 == val-1 and val < len(f.verts):
3971 pattern = [v.is_boundary for v in f.verts]
3972 new_verts = [v for v in f.verts]
3973 while True:
3974 mult = 1
3975 _pattern = pattern[val//2+1:] + pattern[:val//2+1]
3976 for p in _pattern[-val:]: mult*=p
3977 if mult == 1: break
3978 pattern = pattern[-1:] + pattern[:-1]
3979 new_verts = new_verts[-1:] + new_verts[:-1]
3980 new_verts_all.append(new_verts)
3981 rotate_faces.append(f)
3982 if val == 4 and val2 == 3:
3983 pattern = [e.is_boundary for e in f.edges]
3984 new_verts = [v for v in f.verts]
3985 while True:
3986 mult = 1
3987 _pattern = pattern[val2//2+1:] + pattern[:val2//2+1]
3988 for p in _pattern[-val2:]: mult*=p
3989 if mult == 1: break
3990 pattern = pattern[-1:] + pattern[:-1]
3991 new_verts = new_verts[-1:] + new_verts[:-1]
3992 new_verts_all.append(new_verts)
3993 rotate_faces.append(f)
3994 for f, new_verts in zip(rotate_faces, new_verts_all):
3995 material_index = f.material_index
3996 bm.faces.remove(f)
3997 f2 = bm.faces.new(new_verts)
3998 f2.select = True
3999 f2.material_index = material_index
4000 bm.normal_update()
4001 return bm